@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.js CHANGED
@@ -1,7 +1,7 @@
1
1
  "use client";
2
2
 
3
3
  // src/OhhwellsBridge.tsx
4
- import React12, { useCallback as useCallback12, useEffect as useEffect17, useLayoutEffect as useLayoutEffect6, useRef as useRef14, useState as useState18 } from "react";
4
+ import React12, { useCallback as useCallback12, useEffect as useEffect17, useLayoutEffect as useLayoutEffect7, useRef as useRef14, useState as useState18 } from "react";
5
5
  import { createRoot as createRoot3 } from "react-dom/client";
6
6
  import { flushSync as flushSync3 } from "react-dom";
7
7
 
@@ -1094,21 +1094,21 @@ function chromeFrom(sample) {
1094
1094
  };
1095
1095
  }
1096
1096
  function filledPrimary(seed) {
1097
- const paint = parseColor(paintColor(seed)) ?? { r: 15, g: 15, b: 15, a: 1 };
1098
- const luminance = relativeLuminance(paint);
1097
+ const paint2 = parseColor(paintColor(seed)) ?? { r: 15, g: 15, b: 15, a: 1 };
1098
+ const luminance = relativeLuminance(paint2);
1099
1099
  return {
1100
- background: toCss({ ...paint, a: 1 }),
1100
+ background: toCss({ ...paint2, a: 1 }),
1101
1101
  color: luminance > 0.55 ? "rgb(15, 15, 15)" : "rgb(255, 255, 255)",
1102
1102
  borderColor: TRANSPARENT,
1103
1103
  borderWidth: "0px"
1104
1104
  };
1105
1105
  }
1106
1106
  function generatedSecondary(primary) {
1107
- const paint = accentColor(primary);
1107
+ const paint2 = accentColor(primary);
1108
1108
  return {
1109
1109
  background: TRANSPARENT,
1110
- color: paint,
1111
- borderColor: paint,
1110
+ color: paint2,
1111
+ borderColor: paint2,
1112
1112
  borderWidth: "1px"
1113
1113
  };
1114
1114
  }
@@ -1121,9 +1121,9 @@ function generatedGhost(primary) {
1121
1121
  };
1122
1122
  }
1123
1123
  function normalizeOutlineChrome(outline, primary) {
1124
- const paint = accentColor(primary);
1124
+ const paint2 = accentColor(primary);
1125
1125
  const text = parseColor(outline.color);
1126
- const paintRgb = parseColor(paint);
1126
+ const paintRgb = parseColor(paint2);
1127
1127
  const primaryLabel = parseColor(primary.color);
1128
1128
  const matchesOnPrimary = Boolean(text && primaryLabel) && colorKey(outline.color) === colorKey(primary.color);
1129
1129
  const textTooLight = text ? relativeLuminance(text) > 0.7 : false;
@@ -1132,8 +1132,8 @@ function normalizeOutlineChrome(outline, primary) {
1132
1132
  const hasBorder = parseFloat(outline.borderWidth) > 0;
1133
1133
  return {
1134
1134
  ...outline,
1135
- color: paint,
1136
- borderColor: hasBorder ? paint : outline.borderColor
1135
+ color: paint2,
1136
+ borderColor: hasBorder ? paint2 : outline.borderColor
1137
1137
  };
1138
1138
  }
1139
1139
  function generateButtonVariants(samples) {
@@ -6649,6 +6649,21 @@ function pricingCardFeatureCount(tree, index) {
6649
6649
  const target = findPricingCollection(tree)?.children?.[index];
6650
6650
  return target ? pricingFeatureList(target).length : 0;
6651
6651
  }
6652
+ function prevailingFeatureIcon(features) {
6653
+ const counts = /* @__PURE__ */ new Map();
6654
+ for (const row of features) {
6655
+ const key = `${row.icon ?? ""}|${row.iconColor ?? ""}`;
6656
+ const entry = counts.get(key) ?? { count: 0, ...row.icon ? { icon: row.icon } : {}, ...row.iconColor ? { iconColor: row.iconColor } : {} };
6657
+ entry.count += 1;
6658
+ counts.set(key, entry);
6659
+ }
6660
+ let best = null;
6661
+ for (const entry of counts.values()) {
6662
+ if (!best || entry.count > best.count) best = entry;
6663
+ }
6664
+ if (!best) return {};
6665
+ return { ...best.icon ? { icon: best.icon } : {}, ...best.iconColor ? { iconColor: best.iconColor } : {} };
6666
+ }
6652
6667
  function addPricingCardFeature(tree, index, text) {
6653
6668
  return withPricingCollection(tree, (collection) => {
6654
6669
  const children = collection.children;
@@ -6657,7 +6672,7 @@ function addPricingCardFeature(tree, index, text) {
6657
6672
  const features = pricingFeatureList(target);
6658
6673
  if (features.length >= PRICING_FEATURES_MAX) return false;
6659
6674
  target.slots = target.slots ?? {};
6660
- target.slots.features = [...features, { text }];
6675
+ target.slots.features = [...features, { text, ...prevailingFeatureIcon(features) }];
6661
6676
  return true;
6662
6677
  });
6663
6678
  }
@@ -7269,6 +7284,23 @@ function applyCustomCodeToHead(content) {
7269
7284
  if (beforeNodes.length > 0) document.head.append(...beforeNodes);
7270
7285
  }
7271
7286
 
7287
+ // src/lib/node-font-size.ts
7288
+ var TEXT_NODE_TYPES = /* @__PURE__ */ new Set(["text", "text/rich", "plain", "link"]);
7289
+ function escapeKey(key) {
7290
+ return typeof CSS !== "undefined" && typeof CSS.escape === "function" ? CSS.escape(key) : key.replace(/["\\]/g, "\\$&");
7291
+ }
7292
+ function measureNodeFontSize(key, type, root) {
7293
+ if (!TEXT_NODE_TYPES.has(type)) return void 0;
7294
+ const el = root.querySelector(`[data-ohw-editable][data-ohw-key="${escapeKey(key)}"]`);
7295
+ if (!el) return void 0;
7296
+ const size = Number.parseFloat(window.getComputedStyle(el).fontSize);
7297
+ return Number.isFinite(size) && size > 0 ? Math.round(size) : void 0;
7298
+ }
7299
+ function withNodeFontSize(node, root) {
7300
+ const fontSize = measureNodeFontSize(node.key, node.type, root);
7301
+ return fontSize === void 0 ? node : { ...node, fontSize };
7302
+ }
7303
+
7272
7304
  // src/lib/selectNavContainer.ts
7273
7305
  function isOnScreen(el) {
7274
7306
  const rect = el.getBoundingClientRect();
@@ -7284,6 +7316,120 @@ function findSelectableNavContainer(doc = document) {
7284
7316
  );
7285
7317
  }
7286
7318
 
7319
+ // src/lib/youtube.ts
7320
+ var YOUTUBE_ID_PATTERN = /^[A-Za-z0-9_-]{11}$/;
7321
+ function extractYouTubeId(input) {
7322
+ const trimmed = input.trim();
7323
+ if (!trimmed) return null;
7324
+ if (YOUTUBE_ID_PATTERN.test(trimmed)) return trimmed;
7325
+ let url;
7326
+ try {
7327
+ url = new URL(trimmed);
7328
+ } catch {
7329
+ return null;
7330
+ }
7331
+ const host = url.hostname.replace(/^www\./, "");
7332
+ if (host !== "youtube.com" && host !== "m.youtube.com" && host !== "youtu.be" && host !== "youtube-nocookie.com") {
7333
+ return null;
7334
+ }
7335
+ let candidate = null;
7336
+ if (host === "youtu.be") {
7337
+ candidate = url.pathname.slice(1).split("/")[0] ?? null;
7338
+ } else if (url.pathname === "/watch") {
7339
+ candidate = url.searchParams.get("v");
7340
+ } else {
7341
+ const match = url.pathname.match(/^\/(?:embed|shorts)\/([^/]+)/);
7342
+ candidate = match ? match[1] : null;
7343
+ }
7344
+ return candidate && YOUTUBE_ID_PATTERN.test(candidate) ? candidate : null;
7345
+ }
7346
+
7347
+ // src/lib/section-embed.ts
7348
+ var SECTION_EMBED_SUFFIX = "__ohw_embed";
7349
+ function readStoredEmbed(value) {
7350
+ if (value.startsWith("start\n")) {
7351
+ return { raw: value.slice("start\n".length), at: "start" };
7352
+ }
7353
+ return { raw: value, at: "end" };
7354
+ }
7355
+ function embedFrameSrc(value) {
7356
+ const videoId = extractYouTubeId(value);
7357
+ if (videoId) {
7358
+ return { src: `https://www.youtube-nocookie.com/embed/${videoId}?rel=0`, title: "YouTube video" };
7359
+ }
7360
+ let url;
7361
+ try {
7362
+ url = new URL(value);
7363
+ } catch {
7364
+ return null;
7365
+ }
7366
+ const host = url.hostname.replace(/^www\./, "").toLowerCase();
7367
+ if (host === "google.com" && url.pathname.startsWith("/maps") || host === "maps.google.com") {
7368
+ const query = url.searchParams.get("q") || url.searchParams.get("query") || "";
7369
+ const src = query ? `https://maps.google.com/maps?q=${encodeURIComponent(query)}&output=embed` : `https://maps.google.com/maps?output=embed`;
7370
+ return { src, title: "Map" };
7371
+ }
7372
+ if (host === "maps.app.goo.gl") {
7373
+ return { src: value, title: "Map" };
7374
+ }
7375
+ if (host === "calendly.com" || host.endsWith(".calendly.com") || host === "calendar.google.com") {
7376
+ return { src: value, title: "Calendar" };
7377
+ }
7378
+ if (host === "docs.google.com" && url.pathname.includes("/forms/")) {
7379
+ return { src: value.replace(/\/edit(?:\?.*)?$/i, "/viewform"), title: "Form" };
7380
+ }
7381
+ if (host === "forms.gle") {
7382
+ return { src: value, title: "Form" };
7383
+ }
7384
+ return null;
7385
+ }
7386
+ function place(section, box, at) {
7387
+ if (at === "start") {
7388
+ section.insertBefore(box, section.firstChild);
7389
+ return;
7390
+ }
7391
+ section.appendChild(box);
7392
+ }
7393
+ function paint(section, key, src, title, at) {
7394
+ const existing = section.querySelector(`[data-ohw-section-embed="${CSS.escape(key)}"]`);
7395
+ const frame = existing?.querySelector("iframe");
7396
+ if (existing && frame) {
7397
+ if (frame.getAttribute("src") !== src) frame.setAttribute("src", src);
7398
+ frame.title = title;
7399
+ place(section, existing, at);
7400
+ return;
7401
+ }
7402
+ existing?.remove();
7403
+ const box = document.createElement("div");
7404
+ box.setAttribute("data-ohw-section-embed", key);
7405
+ 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;";
7406
+ const frameWrap = document.createElement("div");
7407
+ frameWrap.style.cssText = "position:relative;width:100%;aspect-ratio:16/9;min-height:250px;border-radius:12px;overflow:hidden;background:#0f172a;";
7408
+ const iframe = document.createElement("iframe");
7409
+ iframe.src = src;
7410
+ iframe.title = title;
7411
+ iframe.style.cssText = "position:absolute;inset:0;width:100%;height:100%;border:0;";
7412
+ iframe.allow = "accelerometer; autoplay; clipboard-write; encrypted-media; fullscreen; gyroscope; picture-in-picture";
7413
+ iframe.setAttribute("allowfullscreen", "");
7414
+ frameWrap.appendChild(iframe);
7415
+ box.appendChild(frameWrap);
7416
+ place(section, box, at);
7417
+ }
7418
+ function applySectionEmbeds(content) {
7419
+ for (const [key, val] of Object.entries(content)) {
7420
+ if (!key.endsWith(SECTION_EMBED_SUFFIX)) continue;
7421
+ const sectionId = key.slice(0, -SECTION_EMBED_SUFFIX.length);
7422
+ const section = document.querySelector(`[data-ohw-section="${CSS.escape(sectionId)}"]`);
7423
+ const stored = typeof val === "string" ? readStoredEmbed(val) : null;
7424
+ const frame = stored ? embedFrameSrc(stored.raw) : null;
7425
+ if (!section || !stored || !frame) {
7426
+ section?.querySelector(`[data-ohw-section-embed="${CSS.escape(key)}"]`)?.remove();
7427
+ continue;
7428
+ }
7429
+ paint(section, key, frame.src, frame.title, stored.at);
7430
+ }
7431
+ }
7432
+
7287
7433
  // src/ui/ai-tree/aiSectionsManager.tsx
7288
7434
  import { flushSync } from "react-dom";
7289
7435
  import { createRoot } from "react-dom/client";
@@ -7857,7 +8003,7 @@ var AI_MOBILE_CSS = [
7857
8003
  // page's first section. Top only (bottom needs no nav clearance); ~96px approximates the
7858
8004
  // template nav's own height (measured ~94px) — there's no shared `--ohw-header-h` var to read
7859
8005
  // an exact figure from (no live measurement exists), so this is a safe approximation, not a
7860
- // pixel-exact fit. Horizontal spacing already comes from `data-ai-container`.
8006
+ // pixel-exact fit. Horizontal spacing already comes from `data-ai-section-inner`.
7861
8007
  '[data-ai-overlay-clearance] [data-ai-overlap-layer="overlay"]{padding-top:96px}',
7862
8008
  // A full-bleed carousel's height is pure arithmetic: it spans the whole window, so 16/9 turns
7863
8009
  // window width straight into height. Measured at 16/9 it came out ~90% of the viewport at EVERY
@@ -7873,8 +8019,10 @@ var AI_MOBILE_CSS = [
7873
8019
  "[data-ai-section]{overflow-x:hidden}",
7874
8020
  // `:not([data-ai-full-bleed])`: this `!important` gutter beat the inline `padding: 0` that a
7875
8021
  // full-bleed section's container sets, so a carousel/media hero that is edge-to-edge on desktop
7876
- // still got a 20px band of section background down both sides on a phone.
7877
- "[data-ai-container]:not([data-ai-full-bleed]){padding:0 20px !important}",
8022
+ // still got a 20px band of section background down both sides on a phone. The footer's own
8023
+ // container keeps the unguarded rule below — `data-ai-full-bleed` can never appear on it.
8024
+ "[data-ai-section-inner]:not([data-ai-full-bleed]){padding:0 20px !important}",
8025
+ "[data-ai-container]{padding:0 20px !important}",
7878
8026
  "[data-ai-row]{display:flex !important;flex-direction:column !important;align-items:stretch !important}",
7879
8027
  "[data-ai-cell]{width:100%;min-width:0}",
7880
8028
  // The rule above turns every row into a COLUMN flex container on phones, which silently breaks
@@ -8079,6 +8227,31 @@ function pricingCardEmphasis(featured, ctx, hasBg) {
8079
8227
  function textAttrs(ctx, path) {
8080
8228
  return ctx.keyFor ? { "data-ohw-key": ctx.keyFor(path), "data-ohw-editable": "text" } : {};
8081
8229
  }
8230
+ var AI_RESPONSIVE_CSS = [
8231
+ "@media (max-width: 960px) {",
8232
+ " [data-ai-responsive] [data-ai-grid] { grid-template-columns: repeat(2, 1fr) !important; }",
8233
+ ' [data-ai-responsive] [data-ai-grid="1"] { grid-template-columns: 1fr !important; }',
8234
+ "}",
8235
+ "@media (max-width: 640px) {",
8236
+ " [data-ai-responsive] [data-ai-grid] { grid-template-columns: 1fr !important; }",
8237
+ " [data-ai-responsive] [data-ai-columns] > * { grid-column: 1 / -1 !important; }",
8238
+ // Group containers flatten to a column on phones; span placements come along for free.
8239
+ // `:not([data-ai-group="overlap"])` for the same reason as the twin pair in AI_MOBILE_CSS — see
8240
+ // the comment there. This sheet's copy is the narrower selector, so without the exemption it
8241
+ // flattens an `overlap` hero below 640px even though the other rule stands down: measured a 390px
8242
+ // viewport with the heading/CTA in flow *under* the image, 219px of section background where the
8243
+ // layered text used to be.
8244
+ ' [data-ai-responsive] [data-ai-group]:not([data-ai-group="overlap"]) { display: flex !important; flex-direction: column !important; }',
8245
+ ' [data-ai-responsive] [data-ai-group]:not([data-ai-group="overlap"]) > * { grid-column: auto !important; }',
8246
+ // `:not([data-ai-full-bleed])` for the same reason as the twin rule in AI_MOBILE_CSS: this one
8247
+ // is narrower (0,2,0 vs 0,1,0) and also `!important`, so without the guard it wins outright and
8248
+ // a full-bleed hero gets its 20px side band back below 640px only — the one width where the
8249
+ // other rule had already been taught to stand down.
8250
+ " [data-ai-responsive] [data-ai-section-inner]:not([data-ai-full-bleed]) { padding-left: 20px !important; padding-right: 20px !important; }",
8251
+ " [data-ai-responsive] { overflow-x: hidden; }",
8252
+ " [data-ai-responsive] img { max-width: 100%; }",
8253
+ "}"
8254
+ ].join("\n");
8082
8255
  var TRANSPARENT_PX = "data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7";
8083
8256
  function MediaBox({
8084
8257
  refValue,
@@ -8133,7 +8306,7 @@ function MediaBox({
8133
8306
  position: fill ? "absolute" : void 0,
8134
8307
  inset: fill ? 0 : void 0,
8135
8308
  width: fill ? "100%" : fixedSize ? "auto" : "100%",
8136
- height: fill ? "100%" : void 0,
8309
+ height: fill ? "100%" : fixedSize ? height : void 0,
8137
8310
  aspectRatio: fill ? void 0 : aspectRatio,
8138
8311
  minHeight: fill || fixedSize ? void 0 : height ?? (aspectRatio ? void 0 : 180),
8139
8312
  borderRadius: radius,
@@ -9629,7 +9802,7 @@ function CollectionBlock({ node, ctx, path }) {
9629
9802
  return /* @__PURE__ */ jsx(
9630
9803
  "div",
9631
9804
  {
9632
- "data-ai-grid": "",
9805
+ "data-ai-grid": String(itemsPerRow),
9633
9806
  "data-ohw-pricing-collection": isPricing ? "" : void 0,
9634
9807
  style: {
9635
9808
  display: "grid",
@@ -9931,7 +10104,7 @@ function renderNode(node, ctx, path, ambientAlign) {
9931
10104
  },
9932
10105
  children: [
9933
10106
  i > 0 && ctx.heroOverlapFullBleed ? (
9934
- // The section's `data-ai-container` gave up its max-width/gutter so the base
10107
+ // The section's `data-ai-section-inner` gave up its max-width/gutter so the base
9935
10108
  // layer (this group's i===0 sibling) can span edge-to-edge — reintroduced here,
9936
10109
  // scoped to just the overlay text, so a heading/copy/CTA stack doesn't stretch
9937
10110
  // the full section width instead of reading at the usual content width.
@@ -10350,6 +10523,7 @@ function AiTreeRenderer({
10350
10523
  ...fillHeight ? { "data-ai-fill-height": "" } : {},
10351
10524
  ...needsOverlayClearance ? { "data-ai-overlay-clearance": "" } : {},
10352
10525
  ...bgAttrs,
10526
+ "data-ai-responsive": "",
10353
10527
  style: {
10354
10528
  position: "relative",
10355
10529
  padding: `${padCss} 0`,
@@ -10363,12 +10537,13 @@ function AiTreeRenderer({
10363
10537
  ...fillHeight ? { display: "grid" } : {}
10364
10538
  },
10365
10539
  children: [
10366
- isOverlay && backgroundUrl && /* @__PURE__ */ jsx("div", { style: { position: "absolute", inset: 0, background: `rgba(255,255,255,${overlayAlpha})` } }),
10540
+ /* @__PURE__ */ jsx("style", { children: AI_RESPONSIVE_CSS }),
10367
10541
  /* @__PURE__ */ jsx("style", { children: AI_MOBILE_CSS }),
10542
+ isOverlay && backgroundUrl && /* @__PURE__ */ jsx("div", { style: { position: "absolute", inset: 0, background: `rgba(255,255,255,${overlayAlpha})` } }),
10368
10543
  /* @__PURE__ */ jsx(
10369
10544
  "div",
10370
10545
  {
10371
- "data-ai-container": "",
10546
+ "data-ai-section-inner": "",
10372
10547
  ...fullBleedMedia ? { "data-ai-full-bleed": "" } : {},
10373
10548
  style: {
10374
10549
  position: "relative",
@@ -10385,7 +10560,7 @@ function AiTreeRenderer({
10385
10560
  children: tree.rows.map((row, r2) => /* @__PURE__ */ jsx(
10386
10561
  "div",
10387
10562
  {
10388
- "data-ai-row": "",
10563
+ "data-ai-columns": "",
10389
10564
  style: {
10390
10565
  display: "grid",
10391
10566
  gridTemplateColumns: "repeat(12, 1fr)",
@@ -11047,9 +11222,10 @@ function applyAiSectionsToDom(state, options) {
11047
11222
  if (existing && existing.serialized === serialized && existing.container.isConnected) {
11048
11223
  continue;
11049
11224
  }
11050
- const isNewMount = !existing || !existing.container.isConnected;
11051
11225
  let container = existing?.container;
11226
+ let isNewMount = false;
11052
11227
  if (!container || !container.isConnected) {
11228
+ isNewMount = true;
11053
11229
  existing?.root.unmount();
11054
11230
  container?.remove();
11055
11231
  container = document.createElement("div");
@@ -11387,6 +11563,34 @@ function minSpanForFooterBlock(type) {
11387
11563
  return 3;
11388
11564
  }
11389
11565
  }
11566
+ function readLiveSocialItems() {
11567
+ const root = getFooterRoot();
11568
+ const row = root?.querySelector("[data-ohw-socials-row]");
11569
+ if (!row) return [];
11570
+ const items = [];
11571
+ for (const anchor of Array.from(row.querySelectorAll("a[href]"))) {
11572
+ const href = anchor.getAttribute("href")?.trim() ?? "";
11573
+ const aria = anchor.getAttribute("aria-label")?.trim().toLowerCase() ?? "";
11574
+ const platform = FOOTER_SOCIAL_PLATFORMS.find((name) => aria === name) ?? FOOTER_SOCIAL_PLATFORMS.find((name) => href.toLowerCase().includes(name));
11575
+ if (!platform || !/^https?:\/\//i.test(href)) continue;
11576
+ items.push({ platform, href });
11577
+ }
11578
+ return items;
11579
+ }
11580
+ function syncSocialRowsFromDom(tree) {
11581
+ const root = getFooterRoot();
11582
+ if (!root) return;
11583
+ const row = root.querySelector("[data-ohw-socials-row]");
11584
+ const live = readLiveSocialItems();
11585
+ if (row && live.length === 0) return;
11586
+ for (const col of tree.columns) {
11587
+ col.blocks = col.blocks.flatMap((block) => {
11588
+ if (block.type !== "social-row") return [block];
11589
+ if (!row) return [];
11590
+ return [{ ...block, items: live.map((item) => ({ platform: item.platform, href: item.href })) }];
11591
+ });
11592
+ }
11593
+ }
11390
11594
  function collectBrandBlocks(tree) {
11391
11595
  const brand = [];
11392
11596
  for (const col of tree.columns) {
@@ -11632,6 +11836,7 @@ function textOf2(el) {
11632
11836
  }
11633
11837
  function syncFooterTreeLinksFromDom(tree) {
11634
11838
  const next = cloneTree(tree);
11839
+ syncSocialRowsFromDom(next);
11635
11840
  const domCols = listFooterColumns().map((col) => {
11636
11841
  const heading = getFooterColumnHeadingEl(col);
11637
11842
  const title = heading ? textOf2(heading) : void 0;
@@ -17208,7 +17413,7 @@ function apiBase() {
17208
17413
  function bridgeVersion() {
17209
17414
  try {
17210
17415
  if (true) {
17211
- return "0.1.159";
17416
+ return "0.1.160-next.428";
17212
17417
  }
17213
17418
  } catch {
17214
17419
  }
@@ -17222,7 +17427,7 @@ function stampBridgeVersionOnDocument() {
17222
17427
  return;
17223
17428
  }
17224
17429
  try {
17225
- const v = true ? "0.1.159" : "";
17430
+ const v = true ? "0.1.160-next.428" : "";
17226
17431
  if (v) {
17227
17432
  document.documentElement.setAttribute("data-ohw-bridge-version", v);
17228
17433
  window.__OHW_BRIDGE_VERSION__ = v;
@@ -17900,6 +18105,7 @@ function MediaOverlay({
17900
18105
  (prev) => prev && prev.fullWidth === width && prev.height === height ? prev : { fullWidth: width, height }
17901
18106
  );
17902
18107
  }, [isVideo]);
18108
+ const replaceLabel = isVideo ? "Replace video" : hover.elementType === "bg-image" ? "Replace background" : "Replace image";
17903
18109
  const replaceMode = buttonSize ? replaceButtonMode({ width: rect.width, height: rect.height }, buttonSize) : "none";
17904
18110
  const box = {
17905
18111
  position: "fixed",
@@ -17998,11 +18204,10 @@ function MediaOverlay({
17998
18204
  className: "flex items-center justify-center cursor-pointer",
17999
18205
  style: {
18000
18206
  ...box,
18001
- // When text overlays this media, let clicks fall THROUGH to the text underneath. In the
18002
- // parent this needed a whole `ow:click-at` round-trip to re-hit-test inside the iframe;
18003
- // in-document, pointer-events does it natively. The button below opts back in, so
18004
- // Replace still works.
18005
- pointerEvents: hover.hasTextOverlap || cueOnly ? "none" : "auto",
18207
+ // Visual only. `pointer-events: auto` on this full-bleed rect covered the navbar
18208
+ // (hero bg includes the absolute TopNav) and swallowed CTA press-drag. Text overlay
18209
+ // and cueOnly also need clicks to fall through — Replace / video controls opt back in.
18210
+ pointerEvents: "none",
18006
18211
  // Selected: a firm component ring with no wash, so the image reads as chosen rather
18007
18212
  // than hovered. Hover keeps the existing tinted preview.
18008
18213
  boxShadow: selected ? "inset 0 0 0 2px var(--color-bridge-primary)" : "inset 0 0 0 1.5px var(--color-bridge-primary)",
@@ -18029,17 +18234,17 @@ function MediaOverlay({
18029
18234
  },
18030
18235
  children: [
18031
18236
  isVideo ? /* @__PURE__ */ jsx17(Film, { size: 14 }) : /* @__PURE__ */ jsx17(ImageIcon, { size: 14 }),
18032
- isVideo ? "Replace video" : "Replace image"
18237
+ replaceLabel
18033
18238
  ]
18034
18239
  }
18035
18240
  ),
18036
- replaceMode === "none" || cueOnly ? null : /* @__PURE__ */ jsxs8(
18241
+ showChrome && replaceMode !== "none" && /* @__PURE__ */ jsxs8(
18037
18242
  Button,
18038
18243
  {
18039
18244
  "data-ohw-media-overlay": "",
18040
18245
  variant: "outline",
18041
18246
  size: "sm",
18042
- "aria-label": isVideo ? "Replace video" : "Replace image",
18247
+ "aria-label": replaceLabel,
18043
18248
  className: "gap-1.5 cursor-pointer hover:bg-background",
18044
18249
  style: {
18045
18250
  ...OVERLAY_BUTTON_STYLE,
@@ -18062,7 +18267,7 @@ function MediaOverlay({
18062
18267
  },
18063
18268
  children: [
18064
18269
  isVideo ? /* @__PURE__ */ jsx17(Film, { size: 14 }) : /* @__PURE__ */ jsx17(ImageIcon, { size: 14 }),
18065
- replaceMode === "full" ? isVideo ? "Replace video" : hover.elementType === "bg-image" ? "Replace background" : "Replace image" : null
18270
+ replaceMode === "full" ? replaceLabel : null
18066
18271
  ]
18067
18272
  }
18068
18273
  )
@@ -18655,8 +18860,24 @@ function AiSectionOverlay({
18655
18860
  }
18656
18861
 
18657
18862
  // src/ui/ai-section/AiMediaStripOverlay.tsx
18658
- import { useCallback as useCallback3, useEffect as useEffect6, useRef as useRef5, useState as useState8 } from "react";
18659
- import { Star } from "lucide-react";
18863
+ import { useCallback as useCallback3, useEffect as useEffect6, useLayoutEffect as useLayoutEffect4, useRef as useRef5, useState as useState8 } from "react";
18864
+ import {
18865
+ AlignCenter,
18866
+ AlignLeft,
18867
+ AlignRight,
18868
+ ArrowLeft as ArrowLeft2,
18869
+ ArrowRight as ArrowRight2,
18870
+ Check as Check2,
18871
+ ChevronDown as ChevronDown3,
18872
+ Image as ImageIcon2,
18873
+ ImageMinus,
18874
+ ImagePlus,
18875
+ Link as LinkIcon,
18876
+ Minus,
18877
+ Palette,
18878
+ Plus as Plus2,
18879
+ Star
18880
+ } from "lucide-react";
18660
18881
 
18661
18882
  // src/lib/ai-media-strip-tree.ts
18662
18883
  var MEDIA_STRIP_ITEM_MIN = 1;
@@ -18770,14 +18991,6 @@ function setMediaStripItemAsset(tree, index, assetId) {
18770
18991
  return true;
18771
18992
  });
18772
18993
  }
18773
- function setMediaStripItemAlt(tree, index, alt) {
18774
- return withMediaStrip(tree, (block) => {
18775
- const items = block.items;
18776
- if (index < 0 || index >= items.length) return false;
18777
- items[index] = { ...items[index], alt };
18778
- return true;
18779
- });
18780
- }
18781
18994
  function setMediaStripItemLink(tree, index, link) {
18782
18995
  return withMediaStrip(tree, (block) => {
18783
18996
  const items = block.items;
@@ -18795,11 +19008,117 @@ function setMediaStripItemLink(tree, index, link) {
18795
19008
  });
18796
19009
  }
18797
19010
 
19011
+ // src/ui/dialog.tsx
19012
+ import * as React9 from "react";
19013
+ import { Dialog as DialogPrimitive } from "radix-ui";
19014
+ import { X as X2 } from "lucide-react";
19015
+ import { jsx as jsx20, jsxs as jsxs11 } from "react/jsx-runtime";
19016
+ function Dialog2({
19017
+ ...props
19018
+ }) {
19019
+ return /* @__PURE__ */ jsx20(DialogPrimitive.Root, { "data-slot": "dialog", ...props });
19020
+ }
19021
+ function DialogPortal({
19022
+ ...props
19023
+ }) {
19024
+ return /* @__PURE__ */ jsx20(DialogPrimitive.Portal, { ...props });
19025
+ }
19026
+ function DialogOverlay({
19027
+ className,
19028
+ ...props
19029
+ }) {
19030
+ return /* @__PURE__ */ jsx20(
19031
+ DialogPrimitive.Overlay,
19032
+ {
19033
+ "data-slot": "dialog-overlay",
19034
+ "data-ohw-link-modal-root": "",
19035
+ className: cn("fixed inset-0 z-[2147483646] bg-black/50", className),
19036
+ ...props
19037
+ }
19038
+ );
19039
+ }
19040
+ var DialogContent = React9.forwardRef(
19041
+ ({ className, children, showCloseButton = true, container, ...props }, ref) => {
19042
+ const positionMode = container ? "absolute" : "fixed";
19043
+ return /* @__PURE__ */ jsxs11(DialogPortal, { container: container ?? void 0, children: [
19044
+ /* @__PURE__ */ jsx20(DialogOverlay, { className: cn(positionMode, "inset-0") }),
19045
+ /* @__PURE__ */ jsxs11(
19046
+ DialogPrimitive.Content,
19047
+ {
19048
+ ref,
19049
+ "data-slot": "dialog-content",
19050
+ className: cn(
19051
+ positionMode,
19052
+ "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",
19053
+ "rounded-xl border border-border bg-background font-sans shadow-lg outline-none",
19054
+ container ? "max-h-[calc(100%-32px)]" : "max-h-[calc(100vh-32px)]",
19055
+ className
19056
+ ),
19057
+ onMouseDown: (e) => e.stopPropagation(),
19058
+ ...props,
19059
+ children: [
19060
+ children,
19061
+ showCloseButton ? /* @__PURE__ */ jsx20(
19062
+ DialogPrimitive.Close,
19063
+ {
19064
+ type: "button",
19065
+ className: "absolute right-[9px] top-[9px] rounded-sm p-1.5 text-foreground hover:bg-muted/50",
19066
+ "aria-label": "Close",
19067
+ children: /* @__PURE__ */ jsx20(X2, { size: 16, "aria-hidden": true })
19068
+ }
19069
+ ) : null
19070
+ ]
19071
+ }
19072
+ )
19073
+ ] });
19074
+ }
19075
+ );
19076
+ DialogContent.displayName = DialogPrimitive.Content.displayName;
19077
+ function DialogHeader({
19078
+ className,
19079
+ ...props
19080
+ }) {
19081
+ return /* @__PURE__ */ jsx20("div", { className: cn("flex flex-col gap-1.5", className), ...props });
19082
+ }
19083
+ function DialogFooter({
19084
+ className,
19085
+ ...props
19086
+ }) {
19087
+ return /* @__PURE__ */ jsx20(
19088
+ "div",
19089
+ {
19090
+ className: cn("flex items-center justify-end gap-2", className),
19091
+ ...props
19092
+ }
19093
+ );
19094
+ }
19095
+ var DialogTitle = React9.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx20(
19096
+ DialogPrimitive.Title,
19097
+ {
19098
+ ref,
19099
+ className: cn(
19100
+ "text-lg font-semibold leading-none tracking-tight text-card-foreground",
19101
+ className
19102
+ ),
19103
+ ...props
19104
+ }
19105
+ ));
19106
+ DialogTitle.displayName = DialogPrimitive.Title.displayName;
19107
+ var DialogDescription = React9.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx20(
19108
+ DialogPrimitive.Description,
19109
+ {
19110
+ ref,
19111
+ className: cn("text-sm text-muted-foreground", className),
19112
+ ...props
19113
+ }
19114
+ ));
19115
+ DialogDescription.displayName = DialogPrimitive.Description.displayName;
19116
+ var DialogClose = DialogPrimitive.Close;
19117
+
18798
19118
  // src/ui/ai-section/AiMediaStripOverlay.tsx
18799
- import { Fragment as Fragment7, jsx as jsx20, jsxs as jsxs11 } from "react/jsx-runtime";
18800
- var PRIMARY3 = "#0885FE";
18801
- var OHW_BLUE = `var(--ohw-primary, ${PRIMARY3})`;
18802
- var OHW_BLUE_DARK = `color-mix(in srgb, var(--ohw-primary, ${PRIMARY3}) 60%, #000000)`;
19119
+ import { Fragment as Fragment7, jsx as jsx21, jsxs as jsxs12 } from "react/jsx-runtime";
19120
+ var PRIMARY3 = "var(--ohw-primary, #0885FE)";
19121
+ 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]";
18803
19122
  function findMediaStripItemElement(sectionId, index) {
18804
19123
  const sectionEl = findByInstanceId(sectionId);
18805
19124
  if (!sectionEl) return null;
@@ -18809,14 +19128,35 @@ function findMediaStripItemElement(sectionId, index) {
18809
19128
  }
18810
19129
  return null;
18811
19130
  }
18812
- function readRect2(ref) {
18813
- const el = ref && findMediaStripItemElement(ref.sectionId, ref.index);
19131
+ function itemRefFromTarget(target) {
19132
+ if (!(target instanceof Element)) return null;
19133
+ if (target.closest(CHROME_SELECTOR)) return null;
19134
+ const itemEl = target.closest("[data-ohw-media-strip-item]");
19135
+ if (!itemEl) return null;
19136
+ const sectionEl = itemEl.closest("[data-ohw-section]");
19137
+ const unit = sectionEl ? movableUnit(sectionEl) : null;
19138
+ const sectionId = unit ? instanceIdOf(unit) || null : null;
19139
+ const index = Number(itemEl.dataset.ohwMediaStripIndex);
19140
+ if (!sectionId || Number.isNaN(index)) return null;
19141
+ return { sectionId, index };
19142
+ }
19143
+ function sameRef(a, b) {
19144
+ return a?.sectionId === b?.sectionId && a?.index === b?.index;
19145
+ }
19146
+ function rectOf(el) {
18814
19147
  if (!el) return null;
18815
19148
  const r2 = el.getBoundingClientRect();
18816
19149
  if (r2.width <= 0 || r2.height <= 0) return null;
18817
19150
  return { top: r2.top, left: r2.left, width: r2.width, height: r2.height };
18818
19151
  }
18819
- function useLiveItemRect(ref) {
19152
+ function readRect2(ref) {
19153
+ return rectOf(ref && findMediaStripItemElement(ref.sectionId, ref.index));
19154
+ }
19155
+ function readStripRect(ref) {
19156
+ const item = ref && findMediaStripItemElement(ref.sectionId, ref.index);
19157
+ return rectOf(item?.closest("[data-ohw-media-strip]") ?? item);
19158
+ }
19159
+ function useLiveItemRect(ref, strip = false) {
18820
19160
  const [rect, setRect] = useState8(null);
18821
19161
  useEffect6(() => {
18822
19162
  if (!ref) {
@@ -18824,7 +19164,7 @@ function useLiveItemRect(ref) {
18824
19164
  return;
18825
19165
  }
18826
19166
  const update = () => {
18827
- const next = readRect2(ref);
19167
+ const next = strip ? readStripRect(ref) : readRect2(ref);
18828
19168
  setRect(
18829
19169
  (prev) => prev && next && prev.top === next.top && prev.left === next.left && prev.width === next.width && prev.height === next.height ? prev : next
18830
19170
  );
@@ -18839,9 +19179,49 @@ function useLiveItemRect(ref) {
18839
19179
  window.removeEventListener("resize", update);
18840
19180
  clearInterval(interval);
18841
19181
  };
18842
- }, [ref?.sectionId, ref?.index]);
19182
+ }, [ref?.sectionId, ref?.index, strip]);
18843
19183
  return rect;
18844
19184
  }
19185
+ var TOOLBAR_STRIP_GAP = 52;
19186
+ var TOOLBAR_STRIP_GAP_MOBILE = 42;
19187
+ var TOOLBAR_EDGE_MARGIN2 = 4;
19188
+ function StripToolbarSlot({ stripRect, children }) {
19189
+ const slotRef = useRef5(null);
19190
+ const [shift, setShift] = useState8(0);
19191
+ const centerX = stripRect.left + stripRect.width / 2;
19192
+ useLayoutEffect4(() => {
19193
+ const slot = slotRef.current;
19194
+ if (!slot) return;
19195
+ const clamp = () => {
19196
+ const w = slot.offsetWidth;
19197
+ if (w <= 0) return;
19198
+ const half = w / 2;
19199
+ const clamped = Math.max(TOOLBAR_EDGE_MARGIN2 + half, Math.min(centerX, window.innerWidth - TOOLBAR_EDGE_MARGIN2 - half));
19200
+ setShift((prev) => Math.abs(prev - (clamped - centerX)) < 0.5 ? prev : clamped - centerX);
19201
+ };
19202
+ clamp();
19203
+ const ro = new ResizeObserver(clamp);
19204
+ ro.observe(slot);
19205
+ return () => ro.disconnect();
19206
+ }, [centerX, children]);
19207
+ const mobile = window.innerWidth <= 480;
19208
+ return /* @__PURE__ */ jsx21(
19209
+ "div",
19210
+ {
19211
+ ref: slotRef,
19212
+ "data-ohw-bridge": "",
19213
+ "data-ohw-item-interaction": "",
19214
+ className: "pointer-events-auto fixed",
19215
+ style: {
19216
+ top: stripRect.top - (mobile ? TOOLBAR_STRIP_GAP_MOBILE : TOOLBAR_STRIP_GAP),
19217
+ left: centerX + shift,
19218
+ transform: "translate(-50%, -100%)",
19219
+ zIndex: 2147483645
19220
+ },
19221
+ children
19222
+ }
19223
+ );
19224
+ }
18845
19225
  function requestValidateTree(postToParent2, tree) {
18846
19226
  return new Promise((resolve) => {
18847
19227
  const id = `ai-validate-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
@@ -18878,116 +19258,72 @@ function requestAssetPick(postToParent2) {
18878
19258
  postToParent2({ type: "ow:asset-pick", id });
18879
19259
  });
18880
19260
  }
18881
- function Pill({
19261
+ function ToolbarAction({
18882
19262
  label,
18883
19263
  onClick,
18884
- disabled,
18885
- active
19264
+ disabled = false,
19265
+ active = false,
19266
+ children
18886
19267
  }) {
18887
- return /* @__PURE__ */ jsx20(
18888
- "button",
18889
- {
18890
- type: "button",
18891
- onClick,
18892
- disabled,
18893
- style: {
18894
- height: 28,
18895
- padding: "0 10px",
18896
- display: "inline-flex",
18897
- alignItems: "center",
18898
- gap: 4,
18899
- borderRadius: 999,
18900
- border: "none",
18901
- cursor: disabled ? "default" : "pointer",
18902
- background: active ? OHW_BLUE_DARK : "#ffffff",
18903
- color: active ? "#ffffff" : OHW_BLUE_DARK,
18904
- opacity: disabled ? 0.4 : 1,
18905
- fontSize: 12,
18906
- lineHeight: "16px",
18907
- fontFamily: "Figtree, ui-sans-serif, system-ui, sans-serif",
18908
- whiteSpace: "nowrap",
18909
- flex: "0 0 auto"
18910
- },
18911
- children: label
18912
- }
18913
- );
19268
+ const button = /* @__PURE__ */ jsx21(CustomToolbarButton, { type: "button", "aria-label": label, disabled, active, onClick, className: "size-8", children });
19269
+ return /* @__PURE__ */ jsxs12(Tooltip, { children: [
19270
+ /* @__PURE__ */ jsx21(TooltipTrigger, { asChild: true, children: disabled ? /* @__PURE__ */ jsx21("span", { className: "inline-flex", children: button }) : button }),
19271
+ /* @__PURE__ */ jsx21(TooltipContent, { side: "bottom", sideOffset: 8, children: label })
19272
+ ] });
18914
19273
  }
18915
- function AltInput({ value, onCommit }) {
18916
- const [draft, setDraft] = useState8(value);
18917
- useEffect6(() => setDraft(value), [value]);
18918
- return /* @__PURE__ */ jsxs11(
18919
- "div",
18920
- {
18921
- style: {
18922
- display: "flex",
18923
- alignItems: "center",
18924
- gap: 6,
18925
- height: 28,
18926
- padding: "0 12px",
18927
- borderRadius: 999,
18928
- background: "#ffffff",
18929
- width: "100%",
18930
- maxWidth: 260,
18931
- boxSizing: "border-box"
18932
- },
18933
- children: [
18934
- /* @__PURE__ */ jsx20(
18935
- "span",
18936
- {
18937
- style: {
18938
- flex: "0 0 auto",
18939
- color: OHW_BLUE_DARK,
18940
- fontSize: 10,
18941
- fontWeight: 700,
18942
- textTransform: "uppercase",
18943
- letterSpacing: 0.4
18944
- },
18945
- children: "Alt"
18946
- }
18947
- ),
18948
- /* @__PURE__ */ jsx20(
18949
- "input",
19274
+ function ToolbarSeparator() {
19275
+ return /* @__PURE__ */ jsx21("span", { "aria-hidden": true, className: "mx-0.5 block h-6 w-px shrink-0 bg-border" });
19276
+ }
19277
+ function OptionMenu({
19278
+ label,
19279
+ value,
19280
+ options,
19281
+ onChange
19282
+ }) {
19283
+ const current = options.find((o) => o.value === value) ?? options[0];
19284
+ return /* @__PURE__ */ jsxs12("div", { className: "flex items-center gap-1.5 pl-1.5", children: [
19285
+ /* @__PURE__ */ jsx21("span", { className: "text-xs text-muted-foreground", children: label }),
19286
+ /* @__PURE__ */ jsxs12(DropdownMenu, { modal: false, children: [
19287
+ /* @__PURE__ */ jsx21(DropdownMenuTrigger, { asChild: true, children: /* @__PURE__ */ jsxs12(
19288
+ "button",
19289
+ {
19290
+ type: "button",
19291
+ "aria-label": label,
19292
+ 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",
19293
+ children: [
19294
+ /* @__PURE__ */ jsx21("span", { className: "flex-1 text-left", children: current.label }),
19295
+ /* @__PURE__ */ jsx21(ChevronDown3, { size: 16, style: { pointerEvents: "none" } })
19296
+ ]
19297
+ }
19298
+ ) }),
19299
+ /* @__PURE__ */ jsx21(DropdownMenuContent, { align: "end", sideOffset: 8, className: "w-[132px] min-w-[132px] p-1", children: options.map((option) => {
19300
+ const selected = option.value === value;
19301
+ return /* @__PURE__ */ jsxs12(
19302
+ DropdownMenuItem,
18950
19303
  {
18951
- type: "text",
18952
- value: draft,
18953
- onChange: (e) => setDraft(e.target.value),
18954
- onBlur: () => {
18955
- if (draft !== value) onCommit(draft);
18956
- },
18957
- onKeyDown: (e) => {
18958
- if (e.key === "Enter") e.currentTarget.blur();
18959
- if (e.key === "Escape") {
18960
- setDraft(value);
18961
- e.currentTarget.blur();
18962
- }
18963
- },
18964
- placeholder: "Describe this image",
18965
- "aria-label": "Image alt text",
18966
- style: {
18967
- flex: 1,
18968
- minWidth: 0,
18969
- border: "none",
18970
- outline: "none",
18971
- background: "transparent",
18972
- fontSize: 12,
18973
- lineHeight: "16px",
18974
- fontFamily: "Figtree, ui-sans-serif, system-ui, sans-serif",
18975
- color: "#1f2937"
18976
- }
18977
- }
18978
- )
18979
- ]
18980
- }
18981
- );
19304
+ role: "menuitemradio",
19305
+ "aria-checked": selected,
19306
+ onSelect: () => onChange(option.value),
19307
+ className: cn("grid grid-cols-[1fr_16px] gap-2 py-2 pl-2 pr-2 text-[13px]", selected && "bg-muted"),
19308
+ children: [
19309
+ /* @__PURE__ */ jsx21("span", { children: option.label }),
19310
+ selected && /* @__PURE__ */ jsx21(Check2, { size: 16, className: "text-bridge-primary" })
19311
+ ]
19312
+ },
19313
+ option.value
19314
+ );
19315
+ }) })
19316
+ ] })
19317
+ ] });
18982
19318
  }
18983
19319
  var VARIANTS = [
18984
19320
  { value: "logos", label: "Logos" },
18985
19321
  { value: "reel", label: "Reel" }
18986
19322
  ];
18987
19323
  var ALIGNS = [
18988
- { value: "left", label: "Left" },
18989
- { value: "center", label: "Center" },
18990
- { value: "right", label: "Right" }
19324
+ { value: "left", label: "Align left", Icon: AlignLeft },
19325
+ { value: "center", label: "Align center", Icon: AlignCenter },
19326
+ { value: "right", label: "Align right", Icon: AlignRight }
18991
19327
  ];
18992
19328
  var HOVERS = [
18993
19329
  { value: "none", label: "None" },
@@ -18995,104 +19331,104 @@ var HOVERS = [
18995
19331
  { value: "zoom", label: "Zoom" },
18996
19332
  { value: "fade", label: "Fade" }
18997
19333
  ];
18998
- var TREATMENTS = [
18999
- { value: "color", label: "Color" },
19000
- { value: "grayscale", label: "Grayscale" }
19001
- ];
19002
19334
  var HTTP_URL_RE = /^https?:\/\/\S+$/i;
19003
- function LinkInput({ value, onCommit }) {
19004
- const [draft, setDraft] = useState8(value);
19335
+ function LinkModal({
19336
+ href,
19337
+ linkTarget,
19338
+ onClose,
19339
+ onSave
19340
+ }) {
19341
+ const [draft, setDraft] = useState8(href);
19342
+ const [newTab, setNewTab] = useState8(linkTarget !== "same-tab");
19005
19343
  const [invalid, setInvalid] = useState8(false);
19006
- useEffect6(() => {
19007
- setDraft(value);
19008
- setInvalid(false);
19009
- }, [value]);
19010
- const commit = () => {
19344
+ const save = () => {
19011
19345
  const next = draft.trim();
19012
- if (next === value) return setInvalid(false);
19013
- if (next && !HTTP_URL_RE.test(next)) return setInvalid(true);
19014
- setInvalid(false);
19015
- onCommit(next);
19016
- };
19017
- return /* @__PURE__ */ jsxs11(
19018
- "div",
19019
- {
19020
- style: {
19021
- display: "flex",
19022
- alignItems: "center",
19023
- gap: 6,
19024
- height: 28,
19025
- padding: "0 12px",
19026
- borderRadius: 999,
19027
- background: "#ffffff",
19028
- width: "100%",
19029
- maxWidth: 260,
19030
- boxSizing: "border-box",
19031
- boxShadow: invalid ? "inset 0 0 0 1.5px #EF4444" : "none"
19032
- },
19033
- children: [
19034
- /* @__PURE__ */ jsx20(
19035
- "span",
19036
- {
19037
- style: {
19038
- flex: "0 0 auto",
19039
- color: OHW_BLUE_DARK,
19040
- fontSize: 10,
19041
- fontWeight: 700,
19042
- textTransform: "uppercase",
19043
- letterSpacing: 0.4
19044
- },
19045
- children: "Link"
19046
- }
19047
- ),
19048
- /* @__PURE__ */ jsx20(
19049
- "input",
19050
- {
19051
- type: "text",
19052
- inputMode: "url",
19053
- value: draft,
19054
- onChange: (e) => setDraft(e.target.value),
19055
- onBlur: commit,
19056
- onKeyDown: (e) => {
19057
- if (e.key === "Enter") e.currentTarget.blur();
19058
- if (e.key === "Escape") {
19059
- setDraft(value);
19060
- setInvalid(false);
19061
- e.currentTarget.blur();
19062
- }
19063
- },
19064
- placeholder: "https://example.com",
19065
- "aria-label": "Image link URL",
19066
- "aria-invalid": invalid,
19067
- style: {
19068
- flex: 1,
19069
- minWidth: 0,
19070
- border: "none",
19071
- outline: "none",
19072
- background: "transparent",
19073
- fontSize: 12,
19074
- lineHeight: "16px",
19075
- fontFamily: "Figtree, ui-sans-serif, system-ui, sans-serif",
19076
- color: "#1f2937"
19077
- }
19078
- }
19079
- )
19080
- ]
19346
+ if (next && !HTTP_URL_RE.test(next)) {
19347
+ setInvalid(true);
19348
+ return;
19081
19349
  }
19082
- );
19350
+ onSave(next, newTab ? "new-tab" : "same-tab");
19351
+ onClose();
19352
+ };
19353
+ return /* @__PURE__ */ jsx21(Dialog2, { open: true, onOpenChange: (open) => !open && onClose(), children: /* @__PURE__ */ jsxs12(DialogContent, { "data-ohw-link-modal-root": "", className: "max-w-[400px] gap-4 p-5", children: [
19354
+ /* @__PURE__ */ jsx21(DialogHeader, { children: /* @__PURE__ */ jsx21(DialogTitle, { className: "text-base", children: "Image link" }) }),
19355
+ /* @__PURE__ */ jsxs12("div", { className: "flex flex-col gap-3", children: [
19356
+ /* @__PURE__ */ jsx21(
19357
+ "input",
19358
+ {
19359
+ autoFocus: true,
19360
+ type: "text",
19361
+ inputMode: "url",
19362
+ value: draft,
19363
+ onChange: (e) => {
19364
+ setDraft(e.target.value);
19365
+ setInvalid(false);
19366
+ },
19367
+ onKeyDown: (e) => {
19368
+ if (e.key === "Enter") save();
19369
+ },
19370
+ placeholder: "https://example.com",
19371
+ "aria-label": "Image link URL",
19372
+ "aria-invalid": invalid,
19373
+ className: cn(
19374
+ "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",
19375
+ invalid && "border-destructive focus:border-destructive"
19376
+ )
19377
+ }
19378
+ ),
19379
+ invalid && /* @__PURE__ */ jsx21("p", { className: "text-xs text-destructive", children: "Enter a full URL starting with http:// or https://" }),
19380
+ /* @__PURE__ */ jsxs12("label", { className: "flex items-center gap-2 text-[13px] text-foreground", children: [
19381
+ /* @__PURE__ */ jsx21("input", { type: "checkbox", checked: newTab, onChange: (e) => setNewTab(e.target.checked) }),
19382
+ "Open in new tab"
19383
+ ] })
19384
+ ] }),
19385
+ /* @__PURE__ */ jsxs12(DialogFooter, { children: [
19386
+ href && /* @__PURE__ */ jsx21(
19387
+ "button",
19388
+ {
19389
+ type: "button",
19390
+ onClick: () => {
19391
+ onSave("", "new-tab");
19392
+ onClose();
19393
+ },
19394
+ className: "mr-auto rounded-[6px] px-3 py-2 text-[13px] font-medium text-destructive transition-colors hover:bg-muted",
19395
+ children: "Remove link"
19396
+ }
19397
+ ),
19398
+ /* @__PURE__ */ jsx21(
19399
+ "button",
19400
+ {
19401
+ type: "button",
19402
+ onClick: onClose,
19403
+ className: "rounded-[6px] px-3 py-2 text-[13px] font-medium text-foreground transition-colors hover:bg-muted",
19404
+ children: "Cancel"
19405
+ }
19406
+ ),
19407
+ /* @__PURE__ */ jsx21(
19408
+ "button",
19409
+ {
19410
+ type: "button",
19411
+ onClick: save,
19412
+ className: "rounded-[6px] bg-bridge-primary px-3 py-2 text-[13px] font-medium text-primary-foreground transition-opacity hover:opacity-90",
19413
+ children: "Save"
19414
+ }
19415
+ )
19416
+ ] })
19417
+ ] }) });
19083
19418
  }
19084
19419
  function MediaStripToolbar({
19085
19420
  entry,
19086
19421
  index,
19087
19422
  pending,
19088
19423
  error,
19424
+ itemEditOn,
19425
+ onToggleItemEdit,
19089
19426
  onSetVariant,
19090
19427
  onSetAlign,
19091
19428
  onSetHover,
19092
19429
  onSetTreatment,
19093
19430
  onResize,
19094
- onSetAlt,
19095
- onSetLink,
19431
+ onOpenLink,
19096
19432
  onAddItem,
19097
19433
  onRemoveItem,
19098
19434
  onChangeImage,
@@ -19101,81 +19437,102 @@ function MediaStripToolbar({
19101
19437
  }) {
19102
19438
  const items = mediaStripItems(entry.tree);
19103
19439
  const itemCount = items.length;
19104
- const currentAlt = items[index]?.alt ?? "";
19105
- const currentHref = items[index]?.href ?? "";
19106
- const currentTarget = items[index]?.linkTarget === "same-tab" ? "same-tab" : "new-tab";
19107
19440
  const slots = findMediaStripSlots(entry.tree);
19108
19441
  const variant = slots?.variant === "reel" ? "reel" : "logos";
19109
19442
  const align = slots?.align === "left" || slots?.align === "right" ? slots.align : "center";
19110
19443
  const hover = slots?.hover === "none" || slots?.hover === "lift" || slots?.hover === "zoom" || slots?.hover === "fade" ? slots.hover : "lift";
19111
19444
  const treatment = slots?.treatment === "color" || slots?.treatment === "grayscale" ? slots.treatment : variant === "logos" ? "grayscale" : "color";
19112
19445
  const sizeValue = variant === "logos" ? typeof slots?.logoMaxHeight === "number" ? slots.logoMaxHeight : 48 : typeof slots?.cardWidth === "number" ? slots.cardWidth : 240;
19113
- const isMobile = window.innerWidth <= 480;
19114
- const rowStyle = isMobile ? {
19115
- display: "flex",
19116
- gap: 6,
19117
- flexWrap: "nowrap",
19118
- justifyContent: "center",
19119
- overflowX: "auto",
19120
- WebkitOverflowScrolling: "touch",
19121
- width: "100%",
19122
- scrollbarWidth: "none"
19123
- } : { display: "flex", gap: 6, flexWrap: "nowrap", justifyContent: "center" };
19124
- return /* @__PURE__ */ jsxs11(
19446
+ const mobile = window.innerWidth <= 480;
19447
+ const gatedLabel = (label) => itemEditOn ? label : "Turn on the star to edit this image";
19448
+ return /* @__PURE__ */ jsx21(TooltipProvider, { delayDuration: 0, children: /* @__PURE__ */ jsxs12(
19125
19449
  "div",
19126
19450
  {
19127
19451
  "data-ohw-bridge": "",
19128
19452
  "data-ohw-toolbar": "",
19129
19453
  onMouseDown: (e) => e.stopPropagation(),
19130
- style: {
19131
- display: "flex",
19132
- flexDirection: "column",
19133
- gap: 6,
19134
- alignItems: "center",
19135
- background: OHW_BLUE,
19136
- padding: 8,
19137
- borderRadius: 12,
19138
- boxShadow: "0 8px 24px rgba(0,0,0,0.25)",
19139
- pointerEvents: pending ? "none" : "auto",
19140
- opacity: pending ? 0.6 : 1,
19141
- maxWidth: isMobile ? "calc(100vw - 24px)" : "none"
19142
- },
19454
+ className: cn("flex flex-col items-center gap-1.5 font-sans", pending && "pointer-events-none opacity-60"),
19455
+ style: { maxWidth: mobile ? "calc(100vw - 24px)" : void 0 },
19143
19456
  children: [
19144
- /* @__PURE__ */ jsxs11("div", { style: rowStyle, children: [
19145
- VARIANTS.map((v) => /* @__PURE__ */ jsx20(Pill, { label: v.label, active: variant === v.value, onClick: () => onSetVariant(v.value) }, v.value)),
19146
- ALIGNS.map((a) => /* @__PURE__ */ jsx20(Pill, { label: a.label, active: align === a.value, onClick: () => onSetAlign(a.value) }, a.value))
19147
- ] }),
19148
- /* @__PURE__ */ jsxs11("div", { style: rowStyle, children: [
19149
- HOVERS.map((h) => /* @__PURE__ */ jsx20(Pill, { label: h.label, active: hover === h.value, onClick: () => onSetHover(h.value) }, h.value)),
19150
- TREATMENTS.map((t) => /* @__PURE__ */ jsx20(Pill, { label: t.label, active: treatment === t.value, onClick: () => onSetTreatment(t.value) }, t.value))
19151
- ] }),
19152
- /* @__PURE__ */ jsxs11("div", { style: { display: "flex", gap: 6, alignItems: "center" }, children: [
19153
- /* @__PURE__ */ jsx20(Pill, { label: "\u2212", onClick: () => onResize(-8) }),
19154
- /* @__PURE__ */ jsxs11("span", { style: { color: "#ffffff", fontSize: 12, minWidth: 34, textAlign: "center" }, children: [
19155
- sizeValue,
19156
- "px"
19157
- ] }),
19158
- /* @__PURE__ */ jsx20(Pill, { label: "+", onClick: () => onResize(8) })
19159
- ] }),
19160
- /* @__PURE__ */ jsx20("div", { style: { display: "flex", justifyContent: "center", width: "100%" }, children: /* @__PURE__ */ jsx20(AltInput, { value: currentAlt, onCommit: onSetAlt }) }),
19161
- /* @__PURE__ */ jsx20("div", { style: { display: "flex", justifyContent: "center", width: "100%" }, children: /* @__PURE__ */ jsx20(LinkInput, { value: currentHref, onCommit: (href) => onSetLink(href, currentTarget) }) }),
19162
- currentHref && /* @__PURE__ */ jsxs11("div", { style: rowStyle, children: [
19163
- /* @__PURE__ */ jsx20(Pill, { label: "New tab", active: currentTarget === "new-tab", onClick: () => onSetLink(currentHref, "new-tab") }),
19164
- /* @__PURE__ */ jsx20(Pill, { label: "Same tab", active: currentTarget === "same-tab", onClick: () => onSetLink(currentHref, "same-tab") })
19165
- ] }),
19166
- /* @__PURE__ */ jsxs11("div", { style: rowStyle, children: [
19167
- /* @__PURE__ */ jsx20(Pill, { label: "Change Image", onClick: onChangeImage }),
19168
- /* @__PURE__ */ jsx20(Pill, { label: "+ Item", onClick: onAddItem }),
19169
- /* @__PURE__ */ jsx20(Pill, { label: "\u2212 Item", onClick: onRemoveItem, disabled: itemCount <= 1 })
19170
- ] }),
19171
- /* @__PURE__ */ jsxs11("div", { style: rowStyle, children: [
19172
- /* @__PURE__ */ jsx20(Pill, { label: "\u2190 Move", onClick: onMoveLeft, disabled: index <= 0 }),
19173
- /* @__PURE__ */ jsx20(Pill, { label: "\u2192 Move", onClick: onMoveRight, disabled: index >= itemCount - 1 })
19174
- ] }),
19175
- error && /* @__PURE__ */ jsx20("div", { style: { color: "#FCA5A5", fontSize: 11, maxWidth: 220, textAlign: "center" }, children: error })
19457
+ /* @__PURE__ */ jsxs12(
19458
+ "div",
19459
+ {
19460
+ className: cn(
19461
+ "flex items-center gap-1 rounded-[10px] border border-border bg-background p-1 whitespace-nowrap",
19462
+ "shadow-[0px_10px_15px_-3px_rgba(0,0,0,0.1),0px_4px_6px_-4px_rgba(0,0,0,0.1)]",
19463
+ mobile ? "flex-wrap justify-center" : "flex-nowrap"
19464
+ ),
19465
+ children: [
19466
+ /* @__PURE__ */ jsxs12(Tooltip, { children: [
19467
+ /* @__PURE__ */ jsx21(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsx21(
19468
+ CustomToolbarButton,
19469
+ {
19470
+ type: "button",
19471
+ "aria-label": itemEditOn ? "Done editing this image" : "Edit this image",
19472
+ "aria-pressed": itemEditOn,
19473
+ onClick: onToggleItemEdit,
19474
+ className: "group size-8",
19475
+ children: /* @__PURE__ */ jsx21(
19476
+ Star,
19477
+ {
19478
+ size: 16,
19479
+ fill: "currentColor",
19480
+ strokeWidth: 0,
19481
+ className: cn(
19482
+ "transition-colors",
19483
+ itemEditOn ? "text-[#f59e0b] group-hover:text-[#d97706]" : "text-[#d6d3d1] group-hover:text-[#fbbf24]"
19484
+ ),
19485
+ style: { pointerEvents: "none" }
19486
+ }
19487
+ )
19488
+ }
19489
+ ) }),
19490
+ /* @__PURE__ */ jsx21(TooltipContent, { side: "bottom", sideOffset: 8, children: itemEditOn ? "Done editing this image" : "Edit this image" })
19491
+ ] }),
19492
+ /* @__PURE__ */ jsx21(ToolbarSeparator, {}),
19493
+ /* @__PURE__ */ jsx21(ToolbarAction, { label: gatedLabel("Change image"), onClick: onChangeImage, disabled: !itemEditOn, children: /* @__PURE__ */ jsx21(ImageIcon2, { size: 16, style: { pointerEvents: "none" } }) }),
19494
+ /* @__PURE__ */ jsx21(ToolbarAction, { label: gatedLabel("Link"), onClick: onOpenLink, disabled: !itemEditOn, children: /* @__PURE__ */ jsx21(LinkIcon, { size: 16, style: { pointerEvents: "none" } }) }),
19495
+ /* @__PURE__ */ jsx21(ToolbarAction, { label: gatedLabel("Move left"), onClick: onMoveLeft, disabled: !itemEditOn || index <= 0, children: /* @__PURE__ */ jsx21(ArrowLeft2, { size: 16, style: { pointerEvents: "none" } }) }),
19496
+ /* @__PURE__ */ jsx21(
19497
+ ToolbarAction,
19498
+ {
19499
+ label: gatedLabel("Move right"),
19500
+ onClick: onMoveRight,
19501
+ disabled: !itemEditOn || index >= itemCount - 1,
19502
+ children: /* @__PURE__ */ jsx21(ArrowRight2, { size: 16, style: { pointerEvents: "none" } })
19503
+ }
19504
+ ),
19505
+ /* @__PURE__ */ jsx21(ToolbarAction, { label: gatedLabel("Remove image"), onClick: onRemoveItem, disabled: !itemEditOn || itemCount <= 1, children: /* @__PURE__ */ jsx21(ImageMinus, { size: 16, style: { pointerEvents: "none" } }) }),
19506
+ /* @__PURE__ */ jsx21(ToolbarSeparator, {}),
19507
+ /* @__PURE__ */ jsx21(OptionMenu, { label: "Layout", value: variant, options: VARIANTS, onChange: onSetVariant }),
19508
+ /* @__PURE__ */ jsx21(OptionMenu, { label: "Hover", value: hover, options: HOVERS, onChange: onSetHover }),
19509
+ /* @__PURE__ */ jsx21(
19510
+ ToolbarAction,
19511
+ {
19512
+ label: treatment === "grayscale" ? "Show in color" : "Show grayscale",
19513
+ active: treatment === "grayscale",
19514
+ onClick: () => onSetTreatment(treatment === "grayscale" ? "color" : "grayscale"),
19515
+ children: /* @__PURE__ */ jsx21(Palette, { size: 16, style: { pointerEvents: "none" } })
19516
+ }
19517
+ ),
19518
+ /* @__PURE__ */ jsx21(ToolbarSeparator, {}),
19519
+ ALIGNS.map(({ value, label, Icon }) => /* @__PURE__ */ jsx21(ToolbarAction, { label, active: align === value, onClick: () => onSetAlign(value), children: /* @__PURE__ */ jsx21(Icon, { size: 16, style: { pointerEvents: "none" } }) }, value)),
19520
+ /* @__PURE__ */ jsx21(ToolbarSeparator, {}),
19521
+ /* @__PURE__ */ jsx21(ToolbarAction, { label: "Smaller", onClick: () => onResize(-8), children: /* @__PURE__ */ jsx21(Minus, { size: 16, style: { pointerEvents: "none" } }) }),
19522
+ /* @__PURE__ */ jsxs12("span", { className: "min-w-[40px] text-center text-xs tabular-nums text-muted-foreground", children: [
19523
+ sizeValue,
19524
+ "px"
19525
+ ] }),
19526
+ /* @__PURE__ */ jsx21(ToolbarAction, { label: "Larger", onClick: () => onResize(8), children: /* @__PURE__ */ jsx21(Plus2, { size: 16, style: { pointerEvents: "none" } }) }),
19527
+ /* @__PURE__ */ jsx21(ToolbarSeparator, {}),
19528
+ /* @__PURE__ */ jsx21(ToolbarAction, { label: "Add image", onClick: onAddItem, children: /* @__PURE__ */ jsx21(ImagePlus, { size: 16, style: { pointerEvents: "none" } }) })
19529
+ ]
19530
+ }
19531
+ ),
19532
+ error && /* @__PURE__ */ jsx21("div", { className: "max-w-[260px] rounded-md bg-background px-2 py-1 text-center text-[11px] text-destructive shadow-sm", children: error })
19176
19533
  ]
19177
19534
  }
19178
- );
19535
+ ) });
19179
19536
  }
19180
19537
  function findMediaStripSlots(tree) {
19181
19538
  const stack = tree.rows.flatMap((row) => row.blocks);
@@ -19194,31 +19551,23 @@ function AiMediaStripOverlay({
19194
19551
  }) {
19195
19552
  const [hover, setHover] = useState8(null);
19196
19553
  const [active, setActive] = useState8(null);
19554
+ const [itemEditOn, setItemEditOn] = useState8(false);
19555
+ const [linkOpen, setLinkOpen] = useState8(false);
19197
19556
  const [pending, setPending] = useState8(false);
19198
19557
  const [error, setError] = useState8(null);
19199
19558
  const activeRef = useRef5(null);
19200
19559
  activeRef.current = active;
19560
+ const linkOpenRef = useRef5(false);
19561
+ linkOpenRef.current = linkOpen;
19201
19562
  useEffect6(() => {
19202
19563
  const onMove = (e) => {
19564
+ const ref = itemRefFromTarget(e.target);
19203
19565
  const t = e.target;
19204
- if (!(t instanceof HTMLElement)) return;
19205
- 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]")) {
19206
- return;
19207
- }
19208
- const itemEl = t.closest("[data-ohw-media-strip-item]");
19209
- if (!itemEl) {
19210
- setHover(null);
19211
- return;
19212
- }
19213
- const sectionEl = itemEl.closest("[data-ohw-section]");
19214
- const unit = sectionEl ? movableUnit(sectionEl) : null;
19215
- const sectionId = unit ? instanceIdOf(unit) || null : null;
19216
- const index = Number(itemEl.dataset.ohwMediaStripIndex);
19217
- if (!sectionId || Number.isNaN(index)) {
19218
- setHover(null);
19219
- return;
19220
- }
19221
- setHover({ sectionId, index });
19566
+ const overEditable = Boolean(t?.closest?.("[data-ohw-editable]"));
19567
+ setHover((prev) => {
19568
+ const next = ref && !overEditable ? ref : null;
19569
+ return sameRef(prev, next) ? prev : next;
19570
+ });
19222
19571
  };
19223
19572
  const onLeave = () => setHover(null);
19224
19573
  document.addEventListener("mousemove", onMove, { passive: true });
@@ -19230,23 +19579,25 @@ function AiMediaStripOverlay({
19230
19579
  }, []);
19231
19580
  useEffect6(() => {
19232
19581
  const onClick = (e) => {
19233
- const current = activeRef.current;
19234
- if (!current) return;
19235
19582
  const t = e.target;
19236
- if (!(t instanceof HTMLElement)) return;
19237
- if (t.closest("[data-ohw-item-interaction]")) return;
19238
- const itemEl = findMediaStripItemElement(current.sectionId, current.index);
19239
- if (itemEl && itemEl.contains(t)) return;
19240
- setActive(null);
19583
+ if (!(t instanceof Element)) return;
19584
+ if (t.closest(CHROME_SELECTOR)) return;
19585
+ const ref = itemRefFromTarget(t);
19586
+ if (sameRef(activeRef.current, ref)) return;
19587
+ if (ref) clearInteractionChromeRef.current();
19588
+ setItemEditOn(false);
19241
19589
  setError(null);
19590
+ setActive(ref);
19242
19591
  };
19243
19592
  document.addEventListener("click", onClick, true);
19244
19593
  return () => document.removeEventListener("click", onClick, true);
19245
- }, []);
19594
+ }, [clearInteractionChromeRef]);
19246
19595
  useEffect6(() => {
19247
19596
  const onBlur = () => {
19248
19597
  if (!activeRef.current) return;
19249
19598
  setActive(null);
19599
+ setItemEditOn(false);
19600
+ setLinkOpen(false);
19250
19601
  setError(null);
19251
19602
  };
19252
19603
  window.addEventListener("blur", onBlur);
@@ -19256,7 +19607,10 @@ function AiMediaStripOverlay({
19256
19607
  const onKeyDown = (e) => {
19257
19608
  if (e.key !== "Escape") return;
19258
19609
  if (!activeRef.current) return;
19610
+ if (linkOpenRef.current) return;
19611
+ if (document.querySelector('[data-ohw-more-menu][data-state="open"]')) return;
19259
19612
  setActive(null);
19613
+ setItemEditOn(false);
19260
19614
  setError(null);
19261
19615
  };
19262
19616
  document.addEventListener("keydown", onKeyDown);
@@ -19290,137 +19644,133 @@ function AiMediaStripOverlay({
19290
19644
  tree: nextTree,
19291
19645
  label: entry.label,
19292
19646
  media: extraMedia ? { ...entry.media, ...extraMedia } : entry.media,
19293
- ...entry.brand ? { brand: entry.brand } : {}
19647
+ ...entry.brand ? { brand: entry.brand } : {},
19648
+ // The user is already looking at the strip they just edited — recentering the viewport
19649
+ // on every edit reads as the section jumping around.
19650
+ skipScrollIntoView: true
19294
19651
  });
19295
19652
  return true;
19296
19653
  },
19297
19654
  [findEntry, postToParent2, applyTreeLocalRef]
19298
19655
  );
19299
19656
  const activeEntry = active ? findEntry(active.sectionId) : null;
19300
- const hoverHandleRect = useLiveItemRect(!active && hover ? hover : null);
19301
19657
  const activeItemRect = useLiveItemRect(active);
19302
- const TOOLBAR_APPROX_H = 170;
19303
- const toolbarState = activeItemRect && activeItemRect.top < TOOLBAR_APPROX_H ? "active-bottom" : "active-top";
19304
- return /* @__PURE__ */ jsxs11(Fragment7, { children: [
19305
- hoverHandleRect && hover && /* @__PURE__ */ jsx20(
19306
- "button",
19307
- {
19308
- type: "button",
19309
- "data-ohw-toolbar": "",
19310
- onClick: () => {
19311
- clearInteractionChromeRef.current();
19312
- setActive(hover);
19313
- },
19314
- "aria-label": "Configure media item",
19315
- style: {
19316
- position: "fixed",
19317
- top: hoverHandleRect.top + 8,
19318
- left: hoverHandleRect.left + hoverHandleRect.width - 36,
19319
- width: 28,
19320
- height: 28,
19321
- borderRadius: 8,
19322
- border: `1.5px dashed ${PRIMARY3}`,
19323
- background: "#ffffff",
19324
- color: PRIMARY3,
19325
- display: "flex",
19326
- alignItems: "center",
19327
- justifyContent: "center",
19328
- cursor: "pointer",
19329
- zIndex: 2147483643,
19330
- fontSize: 14,
19331
- fontWeight: 700
19332
- },
19333
- children: /* @__PURE__ */ jsx20(Star, { size: 14, style: { pointerEvents: "none" } })
19334
- }
19335
- ),
19336
- activeItemRect && active && activeEntry && /* @__PURE__ */ jsx20(
19337
- ItemInteractionLayer,
19338
- {
19339
- rect: activeItemRect,
19340
- state: toolbarState,
19341
- toolbar: /* @__PURE__ */ jsx20(
19342
- MediaStripToolbar,
19343
- {
19344
- entry: activeEntry,
19345
- index: active.index,
19346
- pending,
19347
- error,
19348
- onSetVariant: (variant) => runMutation(active.sectionId, (tree) => setMediaStripVariant(tree, variant)),
19349
- onSetAlign: (align) => runMutation(active.sectionId, (tree) => setMediaStripAlign(tree, align)),
19350
- onSetHover: (hoverPreset) => runMutation(active.sectionId, (tree) => setMediaStripHover(tree, hoverPreset)),
19351
- onSetTreatment: (treatment) => runMutation(active.sectionId, (tree) => setMediaStripTreatment(tree, treatment)),
19352
- onResize: (delta) => {
19353
- const slots = findMediaStripSlots(activeEntry.tree);
19354
- const variant = slots?.variant === "reel" ? "reel" : "logos";
19355
- const current = variant === "logos" ? typeof slots?.logoMaxHeight === "number" ? slots.logoMaxHeight : 48 : typeof slots?.cardWidth === "number" ? slots.cardWidth : 240;
19356
- void runMutation(active.sectionId, (tree) => setMediaStripSizing(tree, current + delta));
19357
- },
19358
- onSetAlt: (alt) => {
19359
- void runMutation(active.sectionId, (tree) => setMediaStripItemAlt(tree, active.index, alt));
19360
- },
19361
- onSetLink: (href, linkTarget) => {
19658
+ const activeStripRect = useLiveItemRect(active, true);
19659
+ const hoverRect = useLiveItemRect(hover && !sameRef(hover, active) ? hover : null);
19660
+ const activeItems = activeEntry ? mediaStripItems(activeEntry.tree) : [];
19661
+ const activeItem = active ? activeItems[active.index] : void 0;
19662
+ const activeHref = activeItem?.href ?? "";
19663
+ const activeTarget = activeItem?.linkTarget === "same-tab" ? "same-tab" : "new-tab";
19664
+ return /* @__PURE__ */ jsxs12(Fragment7, { children: [
19665
+ hoverRect && /* @__PURE__ */ jsx21(ItemInteractionLayer, { rect: hoverRect, state: "hover" }),
19666
+ activeItemRect && activeStripRect && active && activeEntry && /* @__PURE__ */ jsxs12(Fragment7, { children: [
19667
+ /* @__PURE__ */ jsx21(ItemInteractionLayer, { rect: activeItemRect, state: "active-top" }),
19668
+ /* @__PURE__ */ jsx21(StripToolbarSlot, { stripRect: activeStripRect, children: /* @__PURE__ */ jsx21(
19669
+ MediaStripToolbar,
19670
+ {
19671
+ entry: activeEntry,
19672
+ index: active.index,
19673
+ pending,
19674
+ error,
19675
+ itemEditOn,
19676
+ onToggleItemEdit: () => setItemEditOn((on) => !on),
19677
+ onSetVariant: (variant) => runMutation(active.sectionId, (tree) => setMediaStripVariant(tree, variant)),
19678
+ onSetAlign: (align) => runMutation(active.sectionId, (tree) => setMediaStripAlign(tree, align)),
19679
+ onSetHover: (hoverPreset) => runMutation(active.sectionId, (tree) => setMediaStripHover(tree, hoverPreset)),
19680
+ onSetTreatment: (treatment) => runMutation(active.sectionId, (tree) => setMediaStripTreatment(tree, treatment)),
19681
+ onResize: (delta) => {
19682
+ const slots = findMediaStripSlots(activeEntry.tree);
19683
+ const variant = slots?.variant === "reel" ? "reel" : "logos";
19684
+ const current = variant === "logos" ? typeof slots?.logoMaxHeight === "number" ? slots.logoMaxHeight : 48 : typeof slots?.cardWidth === "number" ? slots.cardWidth : 240;
19685
+ void runMutation(active.sectionId, (tree) => setMediaStripSizing(tree, current + delta));
19686
+ },
19687
+ onOpenLink: () => setLinkOpen(true),
19688
+ onAddItem: () => {
19689
+ void requestAssetPick(postToParent2).then((picked) => {
19690
+ if (!picked) return;
19691
+ const item = { assetId: picked.assetId, alt: "Logo" };
19362
19692
  void runMutation(
19363
19693
  active.sectionId,
19364
- (tree) => setMediaStripItemLink(tree, active.index, href ? { href, linkTarget } : {})
19365
- );
19366
- },
19367
- onAddItem: () => {
19368
- void requestAssetPick(postToParent2).then((picked) => {
19369
- if (!picked) return;
19370
- const item = { assetId: picked.assetId, alt: "Logo" };
19371
- void runMutation(
19372
- active.sectionId,
19373
- (tree) => addMediaStripItem(tree, item, active.index),
19374
- { [picked.assetId]: picked.url }
19375
- ).then((applied) => {
19376
- if (applied) setActive({ sectionId: active.sectionId, index: active.index + 1 });
19377
- });
19378
- });
19379
- },
19380
- onChangeImage: () => {
19381
- void requestAssetPick(postToParent2).then((picked) => {
19382
- if (!picked) return;
19383
- void runMutation(
19384
- active.sectionId,
19385
- (tree) => setMediaStripItemAsset(tree, active.index, picked.assetId),
19386
- { [picked.assetId]: picked.url }
19387
- );
19694
+ (tree) => addMediaStripItem(tree, item, active.index),
19695
+ { [picked.assetId]: picked.url }
19696
+ ).then((applied) => {
19697
+ if (applied) setActive({ sectionId: active.sectionId, index: active.index + 1 });
19388
19698
  });
19389
- },
19390
- onRemoveItem: () => {
19391
- void runMutation(active.sectionId, (tree) => removeMediaStripItem(tree, active.index)).then(
19392
- (applied) => {
19393
- if (applied) setActive(null);
19394
- }
19395
- );
19396
- },
19397
- onMoveLeft: () => {
19398
- void runMutation(active.sectionId, (tree) => reorderMediaStripItem(tree, active.index, -1)).then(
19399
- (applied) => {
19400
- if (applied) setActive({ sectionId: active.sectionId, index: active.index - 1 });
19401
- }
19402
- );
19403
- },
19404
- onMoveRight: () => {
19405
- void runMutation(active.sectionId, (tree) => reorderMediaStripItem(tree, active.index, 1)).then(
19406
- (applied) => {
19407
- if (applied) setActive({ sectionId: active.sectionId, index: active.index + 1 });
19408
- }
19699
+ });
19700
+ },
19701
+ onChangeImage: () => {
19702
+ void requestAssetPick(postToParent2).then((picked) => {
19703
+ if (!picked) return;
19704
+ void runMutation(
19705
+ active.sectionId,
19706
+ (tree) => setMediaStripItemAsset(tree, active.index, picked.assetId),
19707
+ { [picked.assetId]: picked.url }
19409
19708
  );
19410
- }
19709
+ });
19710
+ },
19711
+ onRemoveItem: () => {
19712
+ void runMutation(active.sectionId, (tree) => removeMediaStripItem(tree, active.index)).then(
19713
+ (applied) => {
19714
+ if (applied) setActive(null);
19715
+ }
19716
+ );
19717
+ },
19718
+ onMoveLeft: () => {
19719
+ void runMutation(active.sectionId, (tree) => reorderMediaStripItem(tree, active.index, -1)).then(
19720
+ (applied) => {
19721
+ if (applied) setActive({ sectionId: active.sectionId, index: active.index - 1 });
19722
+ }
19723
+ );
19724
+ },
19725
+ onMoveRight: () => {
19726
+ void runMutation(active.sectionId, (tree) => reorderMediaStripItem(tree, active.index, 1)).then(
19727
+ (applied) => {
19728
+ if (applied) setActive({ sectionId: active.sectionId, index: active.index + 1 });
19729
+ }
19730
+ );
19411
19731
  }
19412
- )
19413
- }
19414
- )
19732
+ }
19733
+ ) }),
19734
+ /* @__PURE__ */ jsx21(
19735
+ "div",
19736
+ {
19737
+ "data-ohw-bridge": "",
19738
+ "data-ohw-item-interaction": "",
19739
+ "aria-hidden": true,
19740
+ className: "pointer-events-none fixed rounded-[4px] px-[7px] py-[3px] font-sans text-[11px] font-medium leading-none text-primary-foreground whitespace-nowrap",
19741
+ style: {
19742
+ top: activeItemRect.top + activeItemRect.height + 10,
19743
+ left: activeItemRect.left - 5,
19744
+ background: PRIMARY3,
19745
+ zIndex: 2147483644
19746
+ },
19747
+ children: "Media item"
19748
+ }
19749
+ ),
19750
+ linkOpen && /* @__PURE__ */ jsx21(
19751
+ LinkModal,
19752
+ {
19753
+ href: activeHref,
19754
+ linkTarget: activeTarget,
19755
+ onClose: () => setLinkOpen(false),
19756
+ onSave: (href, linkTarget) => {
19757
+ void runMutation(
19758
+ active.sectionId,
19759
+ (tree) => setMediaStripItemLink(tree, active.index, href ? { href, linkTarget } : {})
19760
+ );
19761
+ }
19762
+ }
19763
+ )
19764
+ ] })
19415
19765
  ] });
19416
19766
  }
19417
19767
 
19418
19768
  // src/ui/ai-section/AiPricingCardOverlay.tsx
19419
- import { useCallback as useCallback4, useEffect as useEffect7, useLayoutEffect as useLayoutEffect4, useRef as useRef6, useState as useState9 } from "react";
19420
- import { Check as Check2, ChevronDown as ChevronDown3, Minus, Plus as Plus2, Star as Star2 } from "lucide-react";
19421
- import { Fragment as Fragment8, jsx as jsx21, jsxs as jsxs12 } from "react/jsx-runtime";
19769
+ import { useCallback as useCallback4, useEffect as useEffect7, useLayoutEffect as useLayoutEffect5, useRef as useRef6, useState as useState9 } from "react";
19770
+ import { Check as Check3, ChevronDown as ChevronDown4, Minus as Minus2, Plus as Plus3, Star as Star2 } from "lucide-react";
19771
+ import { Fragment as Fragment8, jsx as jsx22, jsxs as jsxs13 } from "react/jsx-runtime";
19422
19772
  var PRIMARY4 = "var(--ohw-primary, #0885FE)";
19423
- 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]";
19773
+ 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]";
19424
19774
  function findPricingCardElement(sectionId, index) {
19425
19775
  const sectionEl = findByInstanceId(sectionId);
19426
19776
  if (!sectionEl) return null;
@@ -19432,7 +19782,7 @@ function findPricingCardElement(sectionId, index) {
19432
19782
  }
19433
19783
  function cardRefFromTarget(target) {
19434
19784
  if (!(target instanceof Element)) return null;
19435
- if (target.closest(CHROME_SELECTOR)) return null;
19785
+ if (target.closest(CHROME_SELECTOR2)) return null;
19436
19786
  const cardEl = target.closest("[data-ohw-pricing-card]");
19437
19787
  if (!cardEl) return null;
19438
19788
  const sectionEl = cardEl.closest("[data-ohw-section]");
@@ -19442,21 +19792,21 @@ function cardRefFromTarget(target) {
19442
19792
  if (!sectionId || Number.isNaN(index)) return null;
19443
19793
  return { sectionId, index };
19444
19794
  }
19445
- function sameRef(a, b) {
19795
+ function sameRef2(a, b) {
19446
19796
  return a?.sectionId === b?.sectionId && a?.index === b?.index;
19447
19797
  }
19448
- function rectOf(el) {
19798
+ function rectOf2(el) {
19449
19799
  if (!el) return null;
19450
19800
  const r2 = el.getBoundingClientRect();
19451
19801
  if (r2.width <= 0 || r2.height <= 0) return null;
19452
19802
  return { top: r2.top, left: r2.left, width: r2.width, height: r2.height };
19453
19803
  }
19454
19804
  function readRect3(ref) {
19455
- return rectOf(ref && findPricingCardElement(ref.sectionId, ref.index));
19805
+ return rectOf2(ref && findPricingCardElement(ref.sectionId, ref.index));
19456
19806
  }
19457
19807
  function readGridRect(ref) {
19458
19808
  const card = ref && findPricingCardElement(ref.sectionId, ref.index);
19459
- return rectOf(card?.closest("[data-ohw-pricing-collection]") ?? card);
19809
+ return rectOf2(card?.closest("[data-ohw-pricing-collection]") ?? card);
19460
19810
  }
19461
19811
  function useLiveCardRect(ref, grid = false) {
19462
19812
  const [rect, setRect] = useState9(null);
@@ -19486,19 +19836,19 @@ function useLiveCardRect(ref, grid = false) {
19486
19836
  }
19487
19837
  var TOOLBAR_GRID_GAP = 52;
19488
19838
  var TOOLBAR_GRID_GAP_MOBILE = 42;
19489
- var TOOLBAR_EDGE_MARGIN2 = 4;
19839
+ var TOOLBAR_EDGE_MARGIN3 = 4;
19490
19840
  function GridToolbarSlot({ gridRect, children }) {
19491
19841
  const slotRef = useRef6(null);
19492
19842
  const [shift, setShift] = useState9(0);
19493
19843
  const centerX = gridRect.left + gridRect.width / 2;
19494
- useLayoutEffect4(() => {
19844
+ useLayoutEffect5(() => {
19495
19845
  const slot = slotRef.current;
19496
19846
  if (!slot) return;
19497
19847
  const clamp = () => {
19498
19848
  const w = slot.offsetWidth;
19499
19849
  if (w <= 0) return;
19500
19850
  const half = w / 2;
19501
- const clamped = Math.max(TOOLBAR_EDGE_MARGIN2 + half, Math.min(centerX, window.innerWidth - TOOLBAR_EDGE_MARGIN2 - half));
19851
+ const clamped = Math.max(TOOLBAR_EDGE_MARGIN3 + half, Math.min(centerX, window.innerWidth - TOOLBAR_EDGE_MARGIN3 - half));
19502
19852
  setShift((prev) => Math.abs(prev - (clamped - centerX)) < 0.5 ? prev : clamped - centerX);
19503
19853
  };
19504
19854
  clamp();
@@ -19507,7 +19857,7 @@ function GridToolbarSlot({ gridRect, children }) {
19507
19857
  return () => ro.disconnect();
19508
19858
  }, [centerX, children]);
19509
19859
  const mobile = window.innerWidth <= 480;
19510
- return /* @__PURE__ */ jsx21(
19860
+ return /* @__PURE__ */ jsx22(
19511
19861
  "div",
19512
19862
  {
19513
19863
  ref: slotRef,
@@ -19542,7 +19892,7 @@ function requestValidateTree2(postToParent2, tree) {
19542
19892
  });
19543
19893
  }
19544
19894
  function StyleSwatch({ style }) {
19545
- return /* @__PURE__ */ jsx21(
19895
+ return /* @__PURE__ */ jsx22(
19546
19896
  "span",
19547
19897
  {
19548
19898
  "aria-hidden": true,
@@ -19556,35 +19906,35 @@ function StyleSwatch({ style }) {
19556
19906
  );
19557
19907
  }
19558
19908
  function ButtonGlyph() {
19559
- return /* @__PURE__ */ jsxs12("svg", { width: "18", height: "12", viewBox: "0 0 18 12", fill: "none", "aria-hidden": true, style: { pointerEvents: "none" }, children: [
19560
- /* @__PURE__ */ jsx21("rect", { x: "0.75", y: "0.75", width: "16.5", height: "10.5", rx: "3", stroke: "currentColor", strokeWidth: "1.5" }),
19561
- /* @__PURE__ */ jsx21("path", { d: "M5.5 6H12.5", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round" })
19909
+ return /* @__PURE__ */ jsxs13("svg", { width: "18", height: "12", viewBox: "0 0 18 12", fill: "none", "aria-hidden": true, style: { pointerEvents: "none" }, children: [
19910
+ /* @__PURE__ */ jsx22("rect", { x: "0.75", y: "0.75", width: "16.5", height: "10.5", rx: "3", stroke: "currentColor", strokeWidth: "1.5" }),
19911
+ /* @__PURE__ */ jsx22("path", { d: "M5.5 6H12.5", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round" })
19562
19912
  ] });
19563
19913
  }
19564
19914
  function FeatureGlyph({ mode }) {
19565
- return /* @__PURE__ */ jsxs12("svg", { width: "18", height: "18", viewBox: "0 0 18 18", fill: "none", "aria-hidden": true, style: { pointerEvents: "none" }, children: [
19566
- /* @__PURE__ */ jsx21("circle", { cx: "8", cy: "8", r: "7.25", stroke: "currentColor", strokeWidth: "1.5" }),
19567
- /* @__PURE__ */ jsx21("path", { d: "M5 8.2L7.2 10.3L11 6", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round", strokeLinejoin: "round" }),
19568
- /* @__PURE__ */ jsx21("circle", { cx: "13.5", cy: "13.5", r: "4.25", fill: "var(--ohw-background, #ffffff)", stroke: "var(--ohw-background, #ffffff)", strokeWidth: "1" }),
19569
- /* @__PURE__ */ jsx21("circle", { cx: "13.5", cy: "13.5", r: "3.75", fill: "var(--ohw-background, #ffffff)", stroke: PRIMARY4, strokeWidth: "1" }),
19570
- /* @__PURE__ */ jsx21("path", { d: "M11.5 13.5H15.5", stroke: PRIMARY4, strokeWidth: "1.5", strokeLinecap: "round" }),
19571
- mode === "add" && /* @__PURE__ */ jsx21("path", { d: "M13.5 11.5V15.5", stroke: PRIMARY4, strokeWidth: "1.5", strokeLinecap: "round" })
19915
+ return /* @__PURE__ */ jsxs13("svg", { width: "18", height: "18", viewBox: "0 0 18 18", fill: "none", "aria-hidden": true, style: { pointerEvents: "none" }, children: [
19916
+ /* @__PURE__ */ jsx22("circle", { cx: "8", cy: "8", r: "7.25", stroke: "currentColor", strokeWidth: "1.5" }),
19917
+ /* @__PURE__ */ jsx22("path", { d: "M5 8.2L7.2 10.3L11 6", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round", strokeLinejoin: "round" }),
19918
+ /* @__PURE__ */ jsx22("circle", { cx: "13.5", cy: "13.5", r: "4.25", fill: "var(--ohw-background, #ffffff)", stroke: "var(--ohw-background, #ffffff)", strokeWidth: "1" }),
19919
+ /* @__PURE__ */ jsx22("circle", { cx: "13.5", cy: "13.5", r: "3.75", fill: "var(--ohw-background, #ffffff)", stroke: PRIMARY4, strokeWidth: "1" }),
19920
+ /* @__PURE__ */ jsx22("path", { d: "M11.5 13.5H15.5", stroke: PRIMARY4, strokeWidth: "1.5", strokeLinecap: "round" }),
19921
+ mode === "add" && /* @__PURE__ */ jsx22("path", { d: "M13.5 11.5V15.5", stroke: PRIMARY4, strokeWidth: "1.5", strokeLinecap: "round" })
19572
19922
  ] });
19573
19923
  }
19574
- function ToolbarAction({
19924
+ function ToolbarAction2({
19575
19925
  label,
19576
19926
  onClick,
19577
19927
  disabled = false,
19578
19928
  children
19579
19929
  }) {
19580
- const button = /* @__PURE__ */ jsx21(CustomToolbarButton, { type: "button", "aria-label": label, disabled, onClick, className: "size-8", children });
19581
- return /* @__PURE__ */ jsxs12(Tooltip, { children: [
19582
- /* @__PURE__ */ jsx21(TooltipTrigger, { asChild: true, children: disabled ? /* @__PURE__ */ jsx21("span", { className: "inline-flex", children: button }) : button }),
19583
- /* @__PURE__ */ jsx21(TooltipContent, { side: "bottom", sideOffset: 8, children: label })
19930
+ const button = /* @__PURE__ */ jsx22(CustomToolbarButton, { type: "button", "aria-label": label, disabled, onClick, className: "size-8", children });
19931
+ return /* @__PURE__ */ jsxs13(Tooltip, { children: [
19932
+ /* @__PURE__ */ jsx22(TooltipTrigger, { asChild: true, children: disabled ? /* @__PURE__ */ jsx22("span", { className: "inline-flex", children: button }) : button }),
19933
+ /* @__PURE__ */ jsx22(TooltipContent, { side: "bottom", sideOffset: 8, children: label })
19584
19934
  ] });
19585
19935
  }
19586
- function ToolbarSeparator() {
19587
- return /* @__PURE__ */ jsx21("span", { "aria-hidden": true, className: "mx-0.5 block h-6 w-px shrink-0 bg-border" });
19936
+ function ToolbarSeparator2() {
19937
+ return /* @__PURE__ */ jsx22("span", { "aria-hidden": true, className: "mx-0.5 block h-6 w-px shrink-0 bg-border" });
19588
19938
  }
19589
19939
  function CardStyleMenu({
19590
19940
  value,
@@ -19592,7 +19942,7 @@ function CardStyleMenu({
19592
19942
  disabled = false
19593
19943
  }) {
19594
19944
  const current = PRICING_CARD_STYLES.find((s) => s.value === value) ?? PRICING_CARD_STYLES[0];
19595
- const trigger = /* @__PURE__ */ jsxs12(
19945
+ const trigger = /* @__PURE__ */ jsxs13(
19596
19946
  "button",
19597
19947
  {
19598
19948
  type: "button",
@@ -19603,28 +19953,28 @@ function CardStyleMenu({
19603
19953
  disabled && "cursor-not-allowed opacity-40 hover:bg-transparent"
19604
19954
  ),
19605
19955
  children: [
19606
- /* @__PURE__ */ jsx21(StyleSwatch, { style: current.value }),
19607
- /* @__PURE__ */ jsx21("span", { className: "flex-1 text-left", children: current.label }),
19608
- /* @__PURE__ */ jsx21(ChevronDown3, { size: 16, style: { pointerEvents: "none" } })
19956
+ /* @__PURE__ */ jsx22(StyleSwatch, { style: current.value }),
19957
+ /* @__PURE__ */ jsx22("span", { className: "flex-1 text-left", children: current.label }),
19958
+ /* @__PURE__ */ jsx22(ChevronDown4, { size: 16, style: { pointerEvents: "none" } })
19609
19959
  ]
19610
19960
  }
19611
19961
  );
19612
19962
  if (disabled) {
19613
- return /* @__PURE__ */ jsxs12("div", { className: "flex items-center gap-1.5 pl-1.5", children: [
19614
- /* @__PURE__ */ jsx21("span", { className: "text-xs text-muted-foreground opacity-50", children: "Card style" }),
19615
- /* @__PURE__ */ jsxs12(Tooltip, { children: [
19616
- /* @__PURE__ */ jsx21(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsx21("span", { className: "inline-flex", children: trigger }) }),
19617
- /* @__PURE__ */ jsx21(TooltipContent, { side: "bottom", sideOffset: 8, children: "Mark as featured to edit card style" })
19963
+ return /* @__PURE__ */ jsxs13("div", { className: "flex items-center gap-1.5 pl-1.5", children: [
19964
+ /* @__PURE__ */ jsx22("span", { className: "text-xs text-muted-foreground opacity-50", children: "Card style" }),
19965
+ /* @__PURE__ */ jsxs13(Tooltip, { children: [
19966
+ /* @__PURE__ */ jsx22(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsx22("span", { className: "inline-flex", children: trigger }) }),
19967
+ /* @__PURE__ */ jsx22(TooltipContent, { side: "bottom", sideOffset: 8, children: "Mark as featured to edit card style" })
19618
19968
  ] })
19619
19969
  ] });
19620
19970
  }
19621
- return /* @__PURE__ */ jsxs12("div", { className: "flex items-center gap-1.5 pl-1.5", children: [
19622
- /* @__PURE__ */ jsx21("span", { className: "text-xs text-muted-foreground", children: "Card style" }),
19623
- /* @__PURE__ */ jsxs12(DropdownMenu, { modal: false, children: [
19624
- /* @__PURE__ */ jsx21(DropdownMenuTrigger, { asChild: true, children: trigger }),
19625
- /* @__PURE__ */ jsx21(DropdownMenuContent, { align: "end", sideOffset: 8, className: "w-[152px] min-w-[152px] p-1", children: PRICING_CARD_STYLES.map((style) => {
19971
+ return /* @__PURE__ */ jsxs13("div", { className: "flex items-center gap-1.5 pl-1.5", children: [
19972
+ /* @__PURE__ */ jsx22("span", { className: "text-xs text-muted-foreground", children: "Card style" }),
19973
+ /* @__PURE__ */ jsxs13(DropdownMenu, { modal: false, children: [
19974
+ /* @__PURE__ */ jsx22(DropdownMenuTrigger, { asChild: true, children: trigger }),
19975
+ /* @__PURE__ */ jsx22(DropdownMenuContent, { align: "end", sideOffset: 8, className: "w-[152px] min-w-[152px] p-1", children: PRICING_CARD_STYLES.map((style) => {
19626
19976
  const selected = style.value === value;
19627
- return /* @__PURE__ */ jsxs12(
19977
+ return /* @__PURE__ */ jsxs13(
19628
19978
  DropdownMenuItem,
19629
19979
  {
19630
19980
  role: "menuitemradio",
@@ -19632,9 +19982,9 @@ function CardStyleMenu({
19632
19982
  onSelect: () => onChange(style.value),
19633
19983
  className: cn("grid grid-cols-[18px_1fr_16px] gap-2 py-2 pl-2 pr-2 text-[13px]", selected && "bg-muted"),
19634
19984
  children: [
19635
- /* @__PURE__ */ jsx21(StyleSwatch, { style: style.value }),
19636
- /* @__PURE__ */ jsx21("span", { children: style.label }),
19637
- selected && /* @__PURE__ */ jsx21(Check2, { size: 16, className: "text-bridge-primary" })
19985
+ /* @__PURE__ */ jsx22(StyleSwatch, { style: style.value }),
19986
+ /* @__PURE__ */ jsx22("span", { children: style.label }),
19987
+ selected && /* @__PURE__ */ jsx22(Check3, { size: 16, className: "text-bridge-primary" })
19638
19988
  ]
19639
19989
  },
19640
19990
  style.value
@@ -19664,7 +20014,7 @@ function PricingCardToolbar({
19664
20014
  const style = pricingCardStyle(entry.tree, index);
19665
20015
  const featuredTag = pricingCardFeaturedTag(entry.tree, index);
19666
20016
  const mobile = window.innerWidth <= 480;
19667
- return /* @__PURE__ */ jsx21(TooltipProvider, { delayDuration: 0, children: /* @__PURE__ */ jsxs12(
20017
+ return /* @__PURE__ */ jsx22(TooltipProvider, { delayDuration: 0, children: /* @__PURE__ */ jsxs13(
19668
20018
  "div",
19669
20019
  {
19670
20020
  "data-ohw-bridge": "",
@@ -19676,7 +20026,7 @@ function PricingCardToolbar({
19676
20026
  ),
19677
20027
  style: { maxWidth: mobile ? "calc(100vw - 24px)" : void 0 },
19678
20028
  children: [
19679
- /* @__PURE__ */ jsxs12(
20029
+ /* @__PURE__ */ jsxs13(
19680
20030
  "div",
19681
20031
  {
19682
20032
  className: cn(
@@ -19685,8 +20035,8 @@ function PricingCardToolbar({
19685
20035
  mobile ? "flex-wrap justify-center" : "flex-nowrap"
19686
20036
  ),
19687
20037
  children: [
19688
- /* @__PURE__ */ jsxs12(Tooltip, { children: [
19689
- /* @__PURE__ */ jsx21(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsx21(
20038
+ /* @__PURE__ */ jsxs13(Tooltip, { children: [
20039
+ /* @__PURE__ */ jsx22(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsx22(
19690
20040
  CustomToolbarButton,
19691
20041
  {
19692
20042
  type: "button",
@@ -19694,7 +20044,7 @@ function PricingCardToolbar({
19694
20044
  "aria-pressed": featuredTag,
19695
20045
  onClick: () => onSetFeaturedTag(!featuredTag),
19696
20046
  className: "group size-8",
19697
- children: /* @__PURE__ */ jsx21(
20047
+ children: /* @__PURE__ */ jsx22(
19698
20048
  Star2,
19699
20049
  {
19700
20050
  size: 16,
@@ -19709,25 +20059,25 @@ function PricingCardToolbar({
19709
20059
  )
19710
20060
  }
19711
20061
  ) }),
19712
- /* @__PURE__ */ jsx21(TooltipContent, { side: "bottom", sideOffset: 8, children: featuredTag ? "Unmark featured" : "Mark as featured" })
20062
+ /* @__PURE__ */ jsx22(TooltipContent, { side: "bottom", sideOffset: 8, children: featuredTag ? "Unmark featured" : "Mark as featured" })
19713
20063
  ] }),
19714
- /* @__PURE__ */ jsx21(ToolbarSeparator, {}),
19715
- /* @__PURE__ */ jsx21(CardStyleMenu, { value: style, onChange: onSetStyle, disabled: !featuredTag }),
19716
- /* @__PURE__ */ jsx21(ToolbarSeparator, {}),
19717
- /* @__PURE__ */ jsx21(ToolbarAction, { label: "Add button", onClick: onAddButton, disabled: buttonCount >= 2, children: /* @__PURE__ */ jsx21(ButtonGlyph, {}) }),
19718
- /* @__PURE__ */ jsx21(ToolbarAction, { label: "Remove button", onClick: onRemoveButton, disabled: buttonCount === 0, children: /* @__PURE__ */ jsxs12("span", { className: "relative inline-flex", children: [
19719
- /* @__PURE__ */ jsx21(ButtonGlyph, {}),
19720
- /* @__PURE__ */ jsx21(Minus, { size: 10, strokeWidth: 2.5, className: "absolute -right-1.5 -top-1.5 rounded-full bg-background text-bridge-primary" })
20064
+ /* @__PURE__ */ jsx22(ToolbarSeparator2, {}),
20065
+ /* @__PURE__ */ jsx22(CardStyleMenu, { value: style, onChange: onSetStyle, disabled: !featuredTag }),
20066
+ /* @__PURE__ */ jsx22(ToolbarSeparator2, {}),
20067
+ /* @__PURE__ */ jsx22(ToolbarAction2, { label: "Add button", onClick: onAddButton, disabled: buttonCount >= 2, children: /* @__PURE__ */ jsx22(ButtonGlyph, {}) }),
20068
+ /* @__PURE__ */ jsx22(ToolbarAction2, { label: "Remove button", onClick: onRemoveButton, disabled: buttonCount === 0, children: /* @__PURE__ */ jsxs13("span", { className: "relative inline-flex", children: [
20069
+ /* @__PURE__ */ jsx22(ButtonGlyph, {}),
20070
+ /* @__PURE__ */ jsx22(Minus2, { size: 10, strokeWidth: 2.5, className: "absolute -right-1.5 -top-1.5 rounded-full bg-background text-bridge-primary" })
19721
20071
  ] }) }),
19722
- /* @__PURE__ */ jsx21(ToolbarAction, { label: "Add feature", onClick: onAddFeature, disabled: featureCount >= 10, children: /* @__PURE__ */ jsx21(FeatureGlyph, { mode: "add" }) }),
19723
- /* @__PURE__ */ jsx21(ToolbarAction, { label: "Remove feature", onClick: onRemoveFeature, disabled: featureCount <= 1, children: /* @__PURE__ */ jsx21(FeatureGlyph, { mode: "remove" }) }),
19724
- /* @__PURE__ */ jsx21(ToolbarSeparator, {}),
19725
- /* @__PURE__ */ jsx21(ToolbarAction, { label: "Add tier", onClick: onAddTier, disabled: collectionCount >= 4, children: /* @__PURE__ */ jsx21(Plus2, { size: 16, style: { pointerEvents: "none" } }) }),
19726
- /* @__PURE__ */ jsx21(ToolbarAction, { label: "Remove tier", onClick: onRemoveTier, disabled: collectionCount <= 1, children: /* @__PURE__ */ jsx21(Minus, { size: 16, style: { pointerEvents: "none" } }) })
20072
+ /* @__PURE__ */ jsx22(ToolbarAction2, { label: "Add feature", onClick: onAddFeature, disabled: featureCount >= 10, children: /* @__PURE__ */ jsx22(FeatureGlyph, { mode: "add" }) }),
20073
+ /* @__PURE__ */ jsx22(ToolbarAction2, { label: "Remove feature", onClick: onRemoveFeature, disabled: featureCount <= 1, children: /* @__PURE__ */ jsx22(FeatureGlyph, { mode: "remove" }) }),
20074
+ /* @__PURE__ */ jsx22(ToolbarSeparator2, {}),
20075
+ /* @__PURE__ */ jsx22(ToolbarAction2, { label: "Add tier", onClick: onAddTier, disabled: collectionCount >= 4, children: /* @__PURE__ */ jsx22(Plus3, { size: 16, style: { pointerEvents: "none" } }) }),
20076
+ /* @__PURE__ */ jsx22(ToolbarAction2, { label: "Remove tier", onClick: onRemoveTier, disabled: collectionCount <= 1, children: /* @__PURE__ */ jsx22(Minus2, { size: 16, style: { pointerEvents: "none" } }) })
19727
20077
  ]
19728
20078
  }
19729
20079
  ),
19730
- error && /* @__PURE__ */ jsx21("div", { className: "max-w-[260px] rounded-md bg-background px-2 py-1 text-center text-[11px] text-destructive shadow-sm", children: error })
20080
+ error && /* @__PURE__ */ jsx22("div", { className: "max-w-[260px] rounded-md bg-background px-2 py-1 text-center text-[11px] text-destructive shadow-sm", children: error })
19731
20081
  ]
19732
20082
  }
19733
20083
  ) });
@@ -19761,7 +20111,7 @@ function AiPricingCardOverlay({
19761
20111
  const overEditable = Boolean(t?.closest?.("[data-ohw-editable], [data-ohw-role], a, button"));
19762
20112
  setHover((prev) => {
19763
20113
  const next = ref && !overEditable ? ref : null;
19764
- return sameRef(prev, next) ? prev : next;
20114
+ return sameRef2(prev, next) ? prev : next;
19765
20115
  });
19766
20116
  };
19767
20117
  const onLeave = () => setHover(null);
@@ -19776,10 +20126,10 @@ function AiPricingCardOverlay({
19776
20126
  const onClick = (e) => {
19777
20127
  const t = e.target;
19778
20128
  if (!(t instanceof Element)) return;
19779
- if (t.closest(CHROME_SELECTOR)) return;
20129
+ if (t.closest(CHROME_SELECTOR2)) return;
19780
20130
  const ref = cardRefFromTarget(t);
19781
- setActive((prev) => sameRef(prev, ref) ? prev : ref);
19782
- if (!ref || !sameRef(activeRef.current, ref)) setError(null);
20131
+ setActive((prev) => sameRef2(prev, ref) ? prev : ref);
20132
+ if (!ref || !sameRef2(activeRef.current, ref)) setError(null);
19783
20133
  };
19784
20134
  document.addEventListener("click", onClick, true);
19785
20135
  return () => document.removeEventListener("click", onClick, true);
@@ -19844,12 +20194,12 @@ function AiPricingCardOverlay({
19844
20194
  const activeEntry = active ? findEntry(active.sectionId) : null;
19845
20195
  const activeCardRect = useLiveCardRect(active);
19846
20196
  const activeGridRect = useLiveCardRect(active, true);
19847
- const hoverRect = useLiveCardRect(hover && !sameRef(hover, active) ? hover : null);
19848
- return /* @__PURE__ */ jsxs12(Fragment8, { children: [
19849
- hoverRect && /* @__PURE__ */ jsx21(ItemInteractionLayer, { rect: hoverRect, state: "hover" }),
19850
- activeCardRect && activeGridRect && active && activeEntry && /* @__PURE__ */ jsxs12(Fragment8, { children: [
19851
- /* @__PURE__ */ jsx21(ItemInteractionLayer, { rect: activeCardRect, state: "active-top" }),
19852
- /* @__PURE__ */ jsx21(GridToolbarSlot, { gridRect: activeGridRect, children: /* @__PURE__ */ jsx21(
20197
+ const hoverRect = useLiveCardRect(hover && !sameRef2(hover, active) ? hover : null);
20198
+ return /* @__PURE__ */ jsxs13(Fragment8, { children: [
20199
+ hoverRect && /* @__PURE__ */ jsx22(ItemInteractionLayer, { rect: hoverRect, state: "hover" }),
20200
+ activeCardRect && activeGridRect && active && activeEntry && /* @__PURE__ */ jsxs13(Fragment8, { children: [
20201
+ /* @__PURE__ */ jsx22(ItemInteractionLayer, { rect: activeCardRect, state: "active-top" }),
20202
+ /* @__PURE__ */ jsx22(GridToolbarSlot, { gridRect: activeGridRect, children: /* @__PURE__ */ jsx22(
19853
20203
  PricingCardToolbar,
19854
20204
  {
19855
20205
  entry: activeEntry,
@@ -19877,7 +20227,7 @@ function AiPricingCardOverlay({
19877
20227
  onRemoveFeature: () => runMutation(active.sectionId, (tree) => removePricingCardFeature(tree, active.index))
19878
20228
  }
19879
20229
  ) }),
19880
- /* @__PURE__ */ jsx21(
20230
+ /* @__PURE__ */ jsx22(
19881
20231
  "div",
19882
20232
  {
19883
20233
  "data-ohw-bridge": "",
@@ -19929,51 +20279,23 @@ function videoIdAt(tree, path) {
19929
20279
  function setVideoId(tree, path, videoId) {
19930
20280
  return withVideo(tree, path, (block) => {
19931
20281
  block.slots = { ...block.slots, videoId };
19932
- return true;
19933
- });
19934
- }
19935
- function clearVideo(tree, path) {
19936
- return withVideo(tree, path, (block) => {
19937
- const slots = { ...block.slots };
19938
- delete slots.videoId;
19939
- block.slots = slots;
19940
- return true;
19941
- });
19942
- }
19943
-
19944
- // src/lib/youtube.ts
19945
- var YOUTUBE_ID_PATTERN = /^[A-Za-z0-9_-]{11}$/;
19946
- function extractYouTubeId(input) {
19947
- const trimmed = input.trim();
19948
- if (!trimmed) return null;
19949
- if (YOUTUBE_ID_PATTERN.test(trimmed)) return trimmed;
19950
- let url;
19951
- try {
19952
- url = new URL(trimmed);
19953
- } catch {
19954
- return null;
19955
- }
19956
- const host = url.hostname.replace(/^www\./, "");
19957
- if (host !== "youtube.com" && host !== "m.youtube.com" && host !== "youtu.be" && host !== "youtube-nocookie.com") {
19958
- return null;
19959
- }
19960
- let candidate = null;
19961
- if (host === "youtu.be") {
19962
- candidate = url.pathname.slice(1).split("/")[0] ?? null;
19963
- } else if (url.pathname === "/watch") {
19964
- candidate = url.searchParams.get("v");
19965
- } else {
19966
- const match = url.pathname.match(/^\/(?:embed|shorts)\/([^/]+)/);
19967
- candidate = match ? match[1] : null;
19968
- }
19969
- return candidate && YOUTUBE_ID_PATTERN.test(candidate) ? candidate : null;
20282
+ return true;
20283
+ });
20284
+ }
20285
+ function clearVideo(tree, path) {
20286
+ return withVideo(tree, path, (block) => {
20287
+ const slots = { ...block.slots };
20288
+ delete slots.videoId;
20289
+ block.slots = slots;
20290
+ return true;
20291
+ });
19970
20292
  }
19971
20293
 
19972
20294
  // src/ui/ai-section/AiVideoOverlay.tsx
19973
- import { Fragment as Fragment9, jsx as jsx22, jsxs as jsxs13 } from "react/jsx-runtime";
20295
+ import { Fragment as Fragment9, jsx as jsx23, jsxs as jsxs14 } from "react/jsx-runtime";
19974
20296
  var PRIMARY5 = "#0885FE";
19975
- var OHW_BLUE2 = `var(--ohw-primary, ${PRIMARY5})`;
19976
- var OHW_BLUE_DARK2 = `color-mix(in srgb, var(--ohw-primary, ${PRIMARY5}) 60%, #000000)`;
20297
+ var OHW_BLUE = `var(--ohw-primary, ${PRIMARY5})`;
20298
+ var OHW_BLUE_DARK = `color-mix(in srgb, var(--ohw-primary, ${PRIMARY5}) 60%, #000000)`;
19977
20299
  function findVideoBlockElement(sectionId, path) {
19978
20300
  const sectionEl = findByInstanceId(sectionId);
19979
20301
  if (!sectionEl) return null;
@@ -20033,12 +20355,12 @@ function requestValidateTree3(postToParent2, tree) {
20033
20355
  postToParent2({ type: "ow:ai-validate-tree-request", id, tree });
20034
20356
  });
20035
20357
  }
20036
- function Pill2({
20358
+ function Pill({
20037
20359
  label,
20038
20360
  onClick,
20039
20361
  disabled
20040
20362
  }) {
20041
- return /* @__PURE__ */ jsx22(
20363
+ return /* @__PURE__ */ jsx23(
20042
20364
  "button",
20043
20365
  {
20044
20366
  type: "button",
@@ -20054,7 +20376,7 @@ function Pill2({
20054
20376
  border: "none",
20055
20377
  cursor: disabled ? "default" : "pointer",
20056
20378
  background: "#ffffff",
20057
- color: OHW_BLUE_DARK2,
20379
+ color: OHW_BLUE_DARK,
20058
20380
  opacity: disabled ? 0.4 : 1,
20059
20381
  fontSize: 12,
20060
20382
  lineHeight: "16px",
@@ -20076,7 +20398,7 @@ function VideoUrlInput({ value, onCommit }) {
20076
20398
  setDraft(value ? watchUrl(value) : "");
20077
20399
  setInvalid(false);
20078
20400
  }, [value]);
20079
- const commit = () => {
20401
+ const commit2 = () => {
20080
20402
  const next = draft.trim();
20081
20403
  if (!next) {
20082
20404
  setInvalid(false);
@@ -20097,8 +20419,8 @@ function VideoUrlInput({ value, onCommit }) {
20097
20419
  onCommit(id);
20098
20420
  }
20099
20421
  };
20100
- return /* @__PURE__ */ jsxs13("div", { style: { display: "flex", flexDirection: "column", gap: 4, width: "100%", maxWidth: 280 }, children: [
20101
- /* @__PURE__ */ jsxs13(
20422
+ return /* @__PURE__ */ jsxs14("div", { style: { display: "flex", flexDirection: "column", gap: 4, width: "100%", maxWidth: 280 }, children: [
20423
+ /* @__PURE__ */ jsxs14(
20102
20424
  "div",
20103
20425
  {
20104
20426
  style: {
@@ -20114,12 +20436,12 @@ function VideoUrlInput({ value, onCommit }) {
20114
20436
  boxShadow: invalid ? "inset 0 0 0 1.5px #EF4444" : "none"
20115
20437
  },
20116
20438
  children: [
20117
- /* @__PURE__ */ jsx22(
20439
+ /* @__PURE__ */ jsx23(
20118
20440
  "span",
20119
20441
  {
20120
20442
  style: {
20121
20443
  flex: "0 0 auto",
20122
- color: OHW_BLUE_DARK2,
20444
+ color: OHW_BLUE_DARK,
20123
20445
  fontSize: 10,
20124
20446
  lineHeight: "16px",
20125
20447
  fontWeight: 700,
@@ -20129,14 +20451,14 @@ function VideoUrlInput({ value, onCommit }) {
20129
20451
  children: "Video"
20130
20452
  }
20131
20453
  ),
20132
- /* @__PURE__ */ jsx22(
20454
+ /* @__PURE__ */ jsx23(
20133
20455
  "input",
20134
20456
  {
20135
20457
  type: "text",
20136
20458
  inputMode: "url",
20137
20459
  value: draft,
20138
20460
  onChange: (e) => handleChange(e.target.value),
20139
- onBlur: commit,
20461
+ onBlur: commit2,
20140
20462
  onKeyDown: (e) => {
20141
20463
  if (e.key === "Enter") e.currentTarget.blur();
20142
20464
  if (e.key === "Escape") {
@@ -20166,7 +20488,7 @@ function VideoUrlInput({ value, onCommit }) {
20166
20488
  ]
20167
20489
  }
20168
20490
  ),
20169
- invalid && /* @__PURE__ */ jsx22("div", { style: { color: "#FCA5A5", fontSize: 11, textAlign: "center" }, children: "That doesn't look like a YouTube link \u2014 try pasting the video URL again." })
20491
+ invalid && /* @__PURE__ */ jsx23("div", { style: { color: "#FCA5A5", fontSize: 11, textAlign: "center" }, children: "That doesn't look like a YouTube link \u2014 try pasting the video URL again." })
20170
20492
  ] });
20171
20493
  }
20172
20494
  function VideoToolbar({
@@ -20176,7 +20498,7 @@ function VideoToolbar({
20176
20498
  onSetVideoId,
20177
20499
  onRemove
20178
20500
  }) {
20179
- return /* @__PURE__ */ jsxs13(
20501
+ return /* @__PURE__ */ jsxs14(
20180
20502
  "div",
20181
20503
  {
20182
20504
  "data-ohw-bridge": "",
@@ -20188,7 +20510,7 @@ function VideoToolbar({
20188
20510
  gap: 6,
20189
20511
  alignItems: "center",
20190
20512
  width: 280,
20191
- background: OHW_BLUE2,
20513
+ background: OHW_BLUE,
20192
20514
  padding: 8,
20193
20515
  borderRadius: 12,
20194
20516
  boxShadow: "0 8px 24px rgba(0,0,0,0.25)",
@@ -20196,9 +20518,9 @@ function VideoToolbar({
20196
20518
  opacity: pending ? 0.6 : 1
20197
20519
  },
20198
20520
  children: [
20199
- /* @__PURE__ */ jsx22(VideoUrlInput, { value: videoId, onCommit: onSetVideoId }),
20200
- /* @__PURE__ */ jsx22(Pill2, { label: "Remove video", onClick: onRemove, disabled: !videoId }),
20201
- error && /* @__PURE__ */ jsx22("div", { style: { color: "#FCA5A5", fontSize: 11, maxWidth: 220, textAlign: "center" }, children: error })
20521
+ /* @__PURE__ */ jsx23(VideoUrlInput, { value: videoId, onCommit: onSetVideoId }),
20522
+ /* @__PURE__ */ jsx23(Pill, { label: "Remove video", onClick: onRemove, disabled: !videoId }),
20523
+ error && /* @__PURE__ */ jsx23("div", { style: { color: "#FCA5A5", fontSize: 11, maxWidth: 220, textAlign: "center" }, children: error })
20202
20524
  ]
20203
20525
  }
20204
20526
  );
@@ -20318,8 +20640,8 @@ function AiVideoOverlay({
20318
20640
  const activeBlockRect = useLiveBlockRect(active);
20319
20641
  const TOOLBAR_APPROX_H = 90;
20320
20642
  const toolbarState = activeBlockRect && activeBlockRect.top < TOOLBAR_APPROX_H ? "active-bottom" : "active-top";
20321
- return /* @__PURE__ */ jsxs13(Fragment9, { children: [
20322
- hoverHandleRect && hover && /* @__PURE__ */ jsx22(
20643
+ return /* @__PURE__ */ jsxs14(Fragment9, { children: [
20644
+ hoverHandleRect && hover && /* @__PURE__ */ jsx23(
20323
20645
  "button",
20324
20646
  {
20325
20647
  type: "button",
@@ -20347,15 +20669,15 @@ function AiVideoOverlay({
20347
20669
  fontSize: 14,
20348
20670
  fontWeight: 700
20349
20671
  },
20350
- children: /* @__PURE__ */ jsx22(Star3, { size: 14, style: { pointerEvents: "none" } })
20672
+ children: /* @__PURE__ */ jsx23(Star3, { size: 14, style: { pointerEvents: "none" } })
20351
20673
  }
20352
20674
  ),
20353
- activeBlockRect && active && activeEntry && /* @__PURE__ */ jsx22(
20675
+ activeBlockRect && active && activeEntry && /* @__PURE__ */ jsx23(
20354
20676
  ItemInteractionLayer,
20355
20677
  {
20356
20678
  rect: activeBlockRect,
20357
20679
  state: toolbarState,
20358
- toolbar: /* @__PURE__ */ jsx22(
20680
+ toolbar: /* @__PURE__ */ jsx23(
20359
20681
  VideoToolbar,
20360
20682
  {
20361
20683
  videoId: videoIdAt(activeEntry.tree, active.path),
@@ -20430,10 +20752,10 @@ function setCarouselAutoplay(tree, on) {
20430
20752
  }
20431
20753
 
20432
20754
  // src/ui/ai-section/AiFadeCarouselOverlay.tsx
20433
- import { Fragment as Fragment10, jsx as jsx23, jsxs as jsxs14 } from "react/jsx-runtime";
20755
+ import { Fragment as Fragment10, jsx as jsx24, jsxs as jsxs15 } from "react/jsx-runtime";
20434
20756
  var PRIMARY6 = "#0885FE";
20435
- var OHW_BLUE3 = `var(--ohw-primary, ${PRIMARY6})`;
20436
- var OHW_BLUE_DARK3 = `color-mix(in srgb, var(--ohw-primary, ${PRIMARY6}) 60%, #000000)`;
20757
+ var OHW_BLUE2 = `var(--ohw-primary, ${PRIMARY6})`;
20758
+ var OHW_BLUE_DARK2 = `color-mix(in srgb, var(--ohw-primary, ${PRIMARY6}) 60%, #000000)`;
20437
20759
  function findCarouselItemElement(sectionId, index) {
20438
20760
  const sectionEl = findByInstanceId(sectionId);
20439
20761
  if (!sectionEl) return null;
@@ -20512,8 +20834,8 @@ function requestAssetPick2(postToParent2) {
20512
20834
  postToParent2({ type: "ow:asset-pick", id });
20513
20835
  });
20514
20836
  }
20515
- function Pill3({ label, onClick, disabled }) {
20516
- return /* @__PURE__ */ jsx23(
20837
+ function Pill2({ label, onClick, disabled }) {
20838
+ return /* @__PURE__ */ jsx24(
20517
20839
  "button",
20518
20840
  {
20519
20841
  type: "button",
@@ -20529,7 +20851,7 @@ function Pill3({ label, onClick, disabled }) {
20529
20851
  border: "none",
20530
20852
  cursor: disabled ? "default" : "pointer",
20531
20853
  background: "#ffffff",
20532
- color: OHW_BLUE_DARK3,
20854
+ color: OHW_BLUE_DARK2,
20533
20855
  opacity: disabled ? 0.4 : 1,
20534
20856
  fontSize: 12,
20535
20857
  lineHeight: "16px",
@@ -20547,7 +20869,7 @@ function TogglePill({
20547
20869
  onToggle,
20548
20870
  disabled
20549
20871
  }) {
20550
- return /* @__PURE__ */ jsxs14(
20872
+ return /* @__PURE__ */ jsxs15(
20551
20873
  "button",
20552
20874
  {
20553
20875
  type: "button",
@@ -20566,7 +20888,7 @@ function TogglePill({
20566
20888
  border: "none",
20567
20889
  cursor: disabled ? "default" : "pointer",
20568
20890
  background: "#ffffff",
20569
- color: OHW_BLUE_DARK3,
20891
+ color: OHW_BLUE_DARK2,
20570
20892
  opacity: disabled ? 0.4 : 1,
20571
20893
  fontSize: 12,
20572
20894
  lineHeight: "16px",
@@ -20576,7 +20898,7 @@ function TogglePill({
20576
20898
  },
20577
20899
  children: [
20578
20900
  label,
20579
- /* @__PURE__ */ jsx23(
20901
+ /* @__PURE__ */ jsx24(
20580
20902
  "span",
20581
20903
  {
20582
20904
  "aria-hidden": true,
@@ -20589,10 +20911,10 @@ function TogglePill({
20589
20911
  // Off is a grey track, not a washed-out blue: the pill's own white background already
20590
20912
  // carries the brand colour in the text, and a pale blue track next to it read as
20591
20913
  // "disabled control" rather than "switch that is off".
20592
- background: on ? OHW_BLUE_DARK3 : "#CBD5E1",
20914
+ background: on ? OHW_BLUE_DARK2 : "#CBD5E1",
20593
20915
  transition: "background 120ms ease"
20594
20916
  },
20595
- children: /* @__PURE__ */ jsx23(
20917
+ children: /* @__PURE__ */ jsx24(
20596
20918
  "span",
20597
20919
  {
20598
20920
  style: {
@@ -20621,7 +20943,7 @@ function CarouselSlideToolbar({
20621
20943
  onChangeImage,
20622
20944
  onToggleAutoplay
20623
20945
  }) {
20624
- return /* @__PURE__ */ jsxs14(
20946
+ return /* @__PURE__ */ jsxs15(
20625
20947
  "div",
20626
20948
  {
20627
20949
  "data-ohw-bridge": "",
@@ -20632,7 +20954,7 @@ function CarouselSlideToolbar({
20632
20954
  flexDirection: "column",
20633
20955
  gap: 6,
20634
20956
  alignItems: "center",
20635
- background: OHW_BLUE3,
20957
+ background: OHW_BLUE2,
20636
20958
  padding: 8,
20637
20959
  borderRadius: 12,
20638
20960
  boxShadow: "0 8px 24px rgba(0,0,0,0.25)",
@@ -20644,11 +20966,11 @@ function CarouselSlideToolbar({
20644
20966
  pointerEvents: pending ? "none" : "auto"
20645
20967
  },
20646
20968
  children: [
20647
- /* @__PURE__ */ jsxs14("div", { style: { display: "flex", gap: 6, alignItems: "center" }, children: [
20648
- /* @__PURE__ */ jsx23(Pill3, { label: "Change Image", onClick: onChangeImage }),
20649
- /* @__PURE__ */ jsx23(TogglePill, { label: "Autoplay", on: autoplay, onToggle: onToggleAutoplay })
20969
+ /* @__PURE__ */ jsxs15("div", { style: { display: "flex", gap: 6, alignItems: "center" }, children: [
20970
+ /* @__PURE__ */ jsx24(Pill2, { label: "Change Image", onClick: onChangeImage }),
20971
+ /* @__PURE__ */ jsx24(TogglePill, { label: "Autoplay", on: autoplay, onToggle: onToggleAutoplay })
20650
20972
  ] }),
20651
- error && /* @__PURE__ */ jsx23("div", { style: { color: "#FCA5A5", fontSize: 11, maxWidth: 220, textAlign: "center" }, children: error })
20973
+ error && /* @__PURE__ */ jsx24("div", { style: { color: "#FCA5A5", fontSize: 11, maxWidth: 220, textAlign: "center" }, children: error })
20652
20974
  ]
20653
20975
  }
20654
20976
  );
@@ -20788,8 +21110,8 @@ function AiFadeCarouselOverlay({
20788
21110
  const activeItemRect = useLiveItemRect2(active);
20789
21111
  const TOOLBAR_APPROX_H = 90;
20790
21112
  const toolbarState = activeItemRect && activeItemRect.top < TOOLBAR_APPROX_H ? "active-bottom" : "active-top";
20791
- return /* @__PURE__ */ jsxs14(Fragment10, { children: [
20792
- hoverHandleRect && hover && /* @__PURE__ */ jsx23(
21113
+ return /* @__PURE__ */ jsxs15(Fragment10, { children: [
21114
+ hoverHandleRect && hover && /* @__PURE__ */ jsx24(
20793
21115
  "button",
20794
21116
  {
20795
21117
  type: "button",
@@ -20818,10 +21140,10 @@ function AiFadeCarouselOverlay({
20818
21140
  fontSize: 14,
20819
21141
  fontWeight: 700
20820
21142
  },
20821
- children: /* @__PURE__ */ jsx23(Star4, { size: 14, style: { pointerEvents: "none" } })
21143
+ children: /* @__PURE__ */ jsx24(Star4, { size: 14, style: { pointerEvents: "none" } })
20822
21144
  }
20823
21145
  ),
20824
- swapping && /* @__PURE__ */ jsx23(
21146
+ swapping && /* @__PURE__ */ jsx24(
20825
21147
  "div",
20826
21148
  {
20827
21149
  ref: skeletonRef,
@@ -20839,15 +21161,15 @@ function AiFadeCarouselOverlay({
20839
21161
  zIndex: 2147483646,
20840
21162
  pointerEvents: "none"
20841
21163
  },
20842
- children: /* @__PURE__ */ jsx23("style", { children: MEDIA_SKELETON_CSS })
21164
+ children: /* @__PURE__ */ jsx24("style", { children: MEDIA_SKELETON_CSS })
20843
21165
  }
20844
21166
  ),
20845
- activeItemRect && active && activeEntry && /* @__PURE__ */ jsx23(
21167
+ activeItemRect && active && activeEntry && /* @__PURE__ */ jsx24(
20846
21168
  ItemInteractionLayer,
20847
21169
  {
20848
21170
  rect: activeItemRect,
20849
21171
  state: toolbarState,
20850
- toolbar: /* @__PURE__ */ jsx23(
21172
+ toolbar: /* @__PURE__ */ jsx24(
20851
21173
  CarouselSlideToolbar,
20852
21174
  {
20853
21175
  pending,
@@ -21065,118 +21387,11 @@ function scrollToHashSectionWhenReady(behavior = "smooth") {
21065
21387
  // src/ui/link-modal/LinkPopover.tsx
21066
21388
  import { useEffect as useEffect13 } from "react";
21067
21389
 
21068
- // src/ui/dialog.tsx
21069
- import * as React9 from "react";
21070
- import { Dialog as DialogPrimitive } from "radix-ui";
21071
- import { X as X2 } from "lucide-react";
21072
- import { jsx as jsx24, jsxs as jsxs15 } from "react/jsx-runtime";
21073
- function Dialog2({
21074
- ...props
21075
- }) {
21076
- return /* @__PURE__ */ jsx24(DialogPrimitive.Root, { "data-slot": "dialog", ...props });
21077
- }
21078
- function DialogPortal({
21079
- ...props
21080
- }) {
21081
- return /* @__PURE__ */ jsx24(DialogPrimitive.Portal, { ...props });
21082
- }
21083
- function DialogOverlay({
21084
- className,
21085
- ...props
21086
- }) {
21087
- return /* @__PURE__ */ jsx24(
21088
- DialogPrimitive.Overlay,
21089
- {
21090
- "data-slot": "dialog-overlay",
21091
- "data-ohw-link-modal-root": "",
21092
- className: cn("fixed inset-0 z-[2147483646] bg-black/50", className),
21093
- ...props
21094
- }
21095
- );
21096
- }
21097
- var DialogContent = React9.forwardRef(
21098
- ({ className, children, showCloseButton = true, container, ...props }, ref) => {
21099
- const positionMode = container ? "absolute" : "fixed";
21100
- return /* @__PURE__ */ jsxs15(DialogPortal, { container: container ?? void 0, children: [
21101
- /* @__PURE__ */ jsx24(DialogOverlay, { className: cn(positionMode, "inset-0") }),
21102
- /* @__PURE__ */ jsxs15(
21103
- DialogPrimitive.Content,
21104
- {
21105
- ref,
21106
- "data-slot": "dialog-content",
21107
- className: cn(
21108
- positionMode,
21109
- "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",
21110
- "rounded-xl border border-border bg-background font-sans shadow-lg outline-none",
21111
- container ? "max-h-[calc(100%-32px)]" : "max-h-[calc(100vh-32px)]",
21112
- className
21113
- ),
21114
- onMouseDown: (e) => e.stopPropagation(),
21115
- ...props,
21116
- children: [
21117
- children,
21118
- showCloseButton ? /* @__PURE__ */ jsx24(
21119
- DialogPrimitive.Close,
21120
- {
21121
- type: "button",
21122
- className: "absolute right-[9px] top-[9px] rounded-sm p-1.5 text-foreground hover:bg-muted/50",
21123
- "aria-label": "Close",
21124
- children: /* @__PURE__ */ jsx24(X2, { size: 16, "aria-hidden": true })
21125
- }
21126
- ) : null
21127
- ]
21128
- }
21129
- )
21130
- ] });
21131
- }
21132
- );
21133
- DialogContent.displayName = DialogPrimitive.Content.displayName;
21134
- function DialogHeader({
21135
- className,
21136
- ...props
21137
- }) {
21138
- return /* @__PURE__ */ jsx24("div", { className: cn("flex flex-col gap-1.5", className), ...props });
21139
- }
21140
- function DialogFooter({
21141
- className,
21142
- ...props
21143
- }) {
21144
- return /* @__PURE__ */ jsx24(
21145
- "div",
21146
- {
21147
- className: cn("flex items-center justify-end gap-2", className),
21148
- ...props
21149
- }
21150
- );
21151
- }
21152
- var DialogTitle = React9.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx24(
21153
- DialogPrimitive.Title,
21154
- {
21155
- ref,
21156
- className: cn(
21157
- "text-lg font-semibold leading-none tracking-tight text-card-foreground",
21158
- className
21159
- ),
21160
- ...props
21161
- }
21162
- ));
21163
- DialogTitle.displayName = DialogPrimitive.Title.displayName;
21164
- var DialogDescription = React9.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx24(
21165
- DialogPrimitive.Description,
21166
- {
21167
- ref,
21168
- className: cn("text-sm text-muted-foreground", className),
21169
- ...props
21170
- }
21171
- ));
21172
- DialogDescription.displayName = DialogPrimitive.Description.displayName;
21173
- var DialogClose = DialogPrimitive.Close;
21174
-
21175
21390
  // src/ui/link-modal/LinkEditorPanel.tsx
21176
21391
  import { Info, X as X4 } from "lucide-react";
21177
21392
 
21178
21393
  // src/ui/link-modal/DestinationBreadcrumb.tsx
21179
- import { ArrowRight as ArrowRight2, File, GalleryVertical } from "lucide-react";
21394
+ import { ArrowRight as ArrowRight3, File, GalleryVertical } from "lucide-react";
21180
21395
  import { jsx as jsx25, jsxs as jsxs16 } from "react/jsx-runtime";
21181
21396
  function DestinationBreadcrumb({
21182
21397
  pageTitle,
@@ -21190,7 +21405,7 @@ function DestinationBreadcrumb({
21190
21405
  /* @__PURE__ */ jsx25("span", { className: "text-sm font-medium leading-none text-foreground!", children: pageTitle })
21191
21406
  ] }),
21192
21407
  /* @__PURE__ */ jsx25(
21193
- ArrowRight2,
21408
+ ArrowRight3,
21194
21409
  {
21195
21410
  size: 16,
21196
21411
  className: "shrink-0 text-muted-foreground",
@@ -21302,7 +21517,7 @@ function Label({ className, ...props }) {
21302
21517
  }
21303
21518
 
21304
21519
  // src/ui/link-modal/UrlOrPageInput.tsx
21305
- import { ChevronDown as ChevronDown4, File as File2, X as X3 } from "lucide-react";
21520
+ import { ChevronDown as ChevronDown5, File as File2, X as X3 } from "lucide-react";
21306
21521
  import { jsx as jsx29, jsxs as jsxs18 } from "react/jsx-runtime";
21307
21522
  function FieldChevron({
21308
21523
  onClick
@@ -21315,7 +21530,7 @@ function FieldChevron({
21315
21530
  onClick,
21316
21531
  "aria-label": "Open page list",
21317
21532
  tabIndex: -1,
21318
- children: /* @__PURE__ */ jsx29(ChevronDown4, { size: 16 })
21533
+ children: /* @__PURE__ */ jsx29(ChevronDown5, { size: 16 })
21319
21534
  }
21320
21535
  );
21321
21536
  }
@@ -21545,7 +21760,7 @@ function LinkEditorPanel({ state, onClose }) {
21545
21760
  // src/ui/link-modal/SectionPickerOverlay.tsx
21546
21761
  import { useCallback as useCallback7, useEffect as useEffect11, useMemo as useMemo2, useRef as useRef10, useState as useState13 } from "react";
21547
21762
  import { createPortal as createPortal2 } from "react-dom";
21548
- import { ArrowLeft as ArrowLeft2, Check as Check3 } from "lucide-react";
21763
+ import { ArrowLeft as ArrowLeft3, Check as Check4 } from "lucide-react";
21549
21764
  import { usePathname, useRouter as useRouter2 } from "next/navigation";
21550
21765
 
21551
21766
  // src/lib/parent-scroll.ts
@@ -21822,7 +22037,7 @@ function SectionPickerOverlay({
21822
22037
  className: "h-8 min-w-0 gap-1 border-border bg-background px-2 py-1.5 shadow-sm hover:bg-muted cursor-pointer",
21823
22038
  onClick: onBack,
21824
22039
  children: [
21825
- /* @__PURE__ */ jsx31(ArrowLeft2, { className: "size-4 shrink-0", "aria-hidden": true }),
22040
+ /* @__PURE__ */ jsx31(ArrowLeft3, { className: "size-4 shrink-0", "aria-hidden": true }),
21826
22041
  "Back"
21827
22042
  ]
21828
22043
  }
@@ -21891,7 +22106,7 @@ function SectionPickerOverlay({
21891
22106
  className: "absolute right-3 top-3 flex size-8 items-center justify-center rounded-full text-white",
21892
22107
  style: { backgroundColor: "var(--ohw-primary, #0885fe)" },
21893
22108
  "aria-hidden": true,
21894
- children: /* @__PURE__ */ jsx31(Check3, { className: "size-5" })
22109
+ children: /* @__PURE__ */ jsx31(Check4, { className: "size-5" })
21895
22110
  }
21896
22111
  ) : null
21897
22112
  ]
@@ -22275,6 +22490,7 @@ function shouldUseDevFixtures() {
22275
22490
  function getLogoElement(el) {
22276
22491
  const marked = el.closest('[data-ohw-role="logo"], [data-ohw-logo]');
22277
22492
  if (marked) return marked;
22493
+ if (el.closest('[data-ohw-editable="icon"]')) return null;
22278
22494
  const root = el.closest("nav, [data-ohw-nav-root], footer");
22279
22495
  if (!root) return null;
22280
22496
  const anchor = el.closest("a");
@@ -22344,7 +22560,7 @@ function addFooterColumnWithPersist({
22344
22560
  import {
22345
22561
  useCallback as useCallback9,
22346
22562
  useEffect as useEffect14,
22347
- useLayoutEffect as useLayoutEffect5,
22563
+ useLayoutEffect as useLayoutEffect6,
22348
22564
  useRef as useRef11,
22349
22565
  useState as useState15
22350
22566
  } from "react";
@@ -22441,7 +22657,7 @@ function FloatingPanel({
22441
22657
  return () => window.removeEventListener("message", onParentScroll);
22442
22658
  }, [open]);
22443
22659
  const positionRef = useRef11(position);
22444
- useLayoutEffect5(() => {
22660
+ useLayoutEffect6(() => {
22445
22661
  if (!open) {
22446
22662
  positionRef.current = position;
22447
22663
  return;
@@ -22451,7 +22667,7 @@ function FloatingPanel({
22451
22667
  }
22452
22668
  positionRef.current = position;
22453
22669
  }, [open, position, liveScroll, parentScroll]);
22454
- useLayoutEffect5(() => {
22670
+ useLayoutEffect6(() => {
22455
22671
  if (!open || !panelRef.current) return;
22456
22672
  const el = panelRef.current;
22457
22673
  const header = el.querySelector("[data-ohw-floating-panel-header]");
@@ -22660,7 +22876,7 @@ function SocialsDisplayPanel({ display, onChange, className }) {
22660
22876
  }
22661
22877
 
22662
22878
  // src/ui/button-style-panel.tsx
22663
- import { Check as Check4 } from "lucide-react";
22879
+ import { Check as Check5 } from "lucide-react";
22664
22880
  import { jsx as jsx35, jsxs as jsxs24 } from "react/jsx-runtime";
22665
22881
  var SIZE_LABEL = { s: "S", m: "M", l: "L" };
22666
22882
  function ButtonStylePanel({
@@ -22731,7 +22947,7 @@ function ButtonStylePanel({
22731
22947
  {
22732
22948
  className: "absolute right-0.5 top-0.5 flex size-5 items-center justify-center rounded-full bg-primary text-primary-foreground",
22733
22949
  "aria-hidden": true,
22734
- children: /* @__PURE__ */ jsx35(Check4, { className: "size-3", strokeWidth: 3 })
22950
+ children: /* @__PURE__ */ jsx35(Check5, { className: "size-3", strokeWidth: 3 })
22735
22951
  }
22736
22952
  ) : null
22737
22953
  ]
@@ -23756,7 +23972,7 @@ function useSectionDrag({
23756
23972
  }
23757
23973
 
23758
23974
  // src/ui/footer-container-chrome.tsx
23759
- import { Plus as Plus3 } from "lucide-react";
23975
+ import { Plus as Plus4 } from "lucide-react";
23760
23976
  import { jsx as jsx36, jsxs as jsxs25 } from "react/jsx-runtime";
23761
23977
  function FooterContainerChrome({
23762
23978
  rect,
@@ -23797,7 +24013,7 @@ function FooterContainerChrome({
23797
24013
  if (addDisabled) return;
23798
24014
  onAdd();
23799
24015
  },
23800
- children: /* @__PURE__ */ jsx36(Plus3, { className: "size-4 shrink-0 text-foreground", "aria-hidden": true })
24016
+ children: /* @__PURE__ */ jsx36(Plus4, { className: "size-4 shrink-0 text-foreground", "aria-hidden": true })
23801
24017
  }
23802
24018
  ) }),
23803
24019
  /* @__PURE__ */ jsx36(TooltipContent, { side: "bottom", sideOffset: 9, children: addDisabled ? "Maximum columns reached" : "Add item" })
@@ -23975,8 +24191,104 @@ function deleteSelectedNavFooterItem(deps) {
23975
24191
  return false;
23976
24192
  }
23977
24193
 
24194
+ // src/lib/nav-page-sync.ts
24195
+ function labelKeyFor(hrefKey) {
24196
+ return hrefKey.replace(/-href$/, "-label");
24197
+ }
24198
+ function isPageLinkKey(key) {
24199
+ return isNavbarHrefKey(key) || isFooterHrefKey(key);
24200
+ }
24201
+ function currentHref(key, anchor, content) {
24202
+ const stored = content[key];
24203
+ if (typeof stored === "string" && stored.length > 0) return stored;
24204
+ return anchor.getAttribute("href") ?? "";
24205
+ }
24206
+ function normalizePath2(value) {
24207
+ if (value.length > 1 && value.endsWith("/")) return value.slice(0, -1);
24208
+ return value;
24209
+ }
24210
+ function pointsAt(href, path) {
24211
+ const normalized = normalizePath2(href);
24212
+ const target = normalizePath2(path);
24213
+ if (normalized === target) return true;
24214
+ return (normalized.startsWith(`${target}#`) || normalized.startsWith(`${target}?`)) && target !== "/";
24215
+ }
24216
+ function retargetHref(href, oldPath, newPath) {
24217
+ const normalized = normalizePath2(href);
24218
+ const target = normalizePath2(oldPath);
24219
+ if (normalized === target) return newPath;
24220
+ return `${newPath}${normalized.slice(target.length)}`;
24221
+ }
24222
+ function setLabelText(key, text) {
24223
+ document.querySelectorAll(`[data-ohw-key="${CSS.escape(key)}"]`).forEach((el) => {
24224
+ el.textContent = text;
24225
+ });
24226
+ }
24227
+ function commit(delta, deps) {
24228
+ if (Object.keys(delta).length === 0) return;
24229
+ deps.setEditContent({ ...deps.getEditContent(), ...delta });
24230
+ deps.postToParent({
24231
+ type: "ow:change",
24232
+ nodes: Object.entries(delta).map(([key, text]) => ({ key, text }))
24233
+ });
24234
+ }
24235
+ function navLinksTo(path, content) {
24236
+ return Array.from(document.querySelectorAll("[data-ohw-href-key]")).filter((anchor) => {
24237
+ const key = anchor.getAttribute("data-ohw-href-key");
24238
+ return isNavbarHrefKey(key) && pointsAt(currentHref(key, anchor, content), path);
24239
+ }).map((anchor) => anchor.getAttribute("data-ohw-href-key"));
24240
+ }
24241
+ function addNavLinkForPage(page, deps) {
24242
+ const content = deps.getEditContent();
24243
+ if (navLinksTo(page.path, content).length > 0) return false;
24244
+ const roots = listNavbarRootItems();
24245
+ const afterAnchor = roots[roots.length - 1] ?? null;
24246
+ let inserted;
24247
+ try {
24248
+ inserted = insertNavbarItem(page.path, page.title, afterAnchor);
24249
+ } catch {
24250
+ return false;
24251
+ }
24252
+ const { hrefKey, labelKey, index } = inserted;
24253
+ deps.applyLinkByKey(hrefKey, page.path);
24254
+ setLabelText(labelKey, page.title);
24255
+ commit(
24256
+ {
24257
+ [hrefKey]: page.path,
24258
+ [labelKey]: page.title,
24259
+ [NAV_COUNT_KEY]: String(index + 1),
24260
+ [NAV_ORDER_KEY]: getNavbarOrderJson()
24261
+ },
24262
+ deps
24263
+ );
24264
+ return true;
24265
+ }
24266
+ function retargetPageLinks(change, deps) {
24267
+ const { oldPath, newPath, oldTitle, newTitle } = change;
24268
+ if (!oldPath || !newPath || normalizePath2(oldPath) === normalizePath2(newPath)) return 0;
24269
+ const content = deps.getEditContent();
24270
+ const delta = {};
24271
+ for (const anchor of Array.from(document.querySelectorAll("[data-ohw-href-key]"))) {
24272
+ const hrefKey = anchor.getAttribute("data-ohw-href-key");
24273
+ if (!isPageLinkKey(hrefKey)) continue;
24274
+ const href = currentHref(hrefKey, anchor, content);
24275
+ if (!pointsAt(href, oldPath)) continue;
24276
+ const nextHref = retargetHref(href, oldPath, newPath);
24277
+ deps.applyLinkByKey(hrefKey, nextHref);
24278
+ delta[hrefKey] = nextHref;
24279
+ if (!oldTitle || !newTitle) continue;
24280
+ const labelKey = labelKeyFor(hrefKey);
24281
+ const label = content[labelKey] ?? anchor.querySelector('[data-ohw-editable="text"]')?.textContent ?? "";
24282
+ if (label.trim() !== oldTitle) continue;
24283
+ setLabelText(labelKey, newTitle);
24284
+ delta[labelKey] = newTitle;
24285
+ }
24286
+ commit(delta, deps);
24287
+ return Object.keys(delta).filter((key) => key.endsWith("-href")).length;
24288
+ }
24289
+
23978
24290
  // src/ui/navbar-container-chrome.tsx
23979
- import { Plus as Plus4 } from "lucide-react";
24291
+ import { Plus as Plus5 } from "lucide-react";
23980
24292
  import { jsx as jsx37 } from "react/jsx-runtime";
23981
24293
  function NavbarContainerChrome({
23982
24294
  rect,
@@ -24012,7 +24324,7 @@ function NavbarContainerChrome({
24012
24324
  e.stopPropagation();
24013
24325
  onAdd();
24014
24326
  },
24015
- children: /* @__PURE__ */ jsx37(Plus4, { className: "size-4 shrink-0 text-foreground", "aria-hidden": true })
24327
+ children: /* @__PURE__ */ jsx37(Plus5, { className: "size-4 shrink-0 text-foreground", "aria-hidden": true })
24016
24328
  }
24017
24329
  )
24018
24330
  }
@@ -24084,7 +24396,7 @@ function Badge({ className, variant, ...props }) {
24084
24396
  }
24085
24397
 
24086
24398
  // src/OhhwellsBridge.tsx
24087
- import { Brush as Brush2, Link as Link2, Plus as Plus5, Settings } from "lucide-react";
24399
+ import { Brush as Brush2, Link as Link2, Plus as Plus6, Settings } from "lucide-react";
24088
24400
  import { Fragment as Fragment13, jsx as jsx40, jsxs as jsxs26 } from "react/jsx-runtime";
24089
24401
  var PRIMARY7 = "#0885FE";
24090
24402
  var IMAGE_FADE_MS = 300;
@@ -24862,7 +25174,7 @@ function getNavigationSelectionParent(el) {
24862
25174
  if (!isFooterLinksContainer(el) && (el.hasAttribute("data-ohw-footer-col") || el.hasAttribute("data-ohw-footer-column") || isInferredFooterGroup2(el))) {
24863
25175
  return resolveFooterLinksContainerFrom(el);
24864
25176
  }
24865
- if (el.hasAttribute("data-ohw-nav-container") || el.hasAttribute("data-ohw-nav-drawer") || isFooterLinksContainer(el)) {
25177
+ 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)) {
24866
25178
  return getNavigationRoot(el);
24867
25179
  }
24868
25180
  return null;
@@ -25084,7 +25396,6 @@ var ICONS = {
25084
25396
  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"/>',
25085
25397
  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"/>'
25086
25398
  };
25087
- var HEIGHT_SETTLE_DELAYS = [150, 400, 800];
25088
25399
  var SELECTION_CHROME_GAP2 = 4;
25089
25400
  var TOOLBAR_STROKE_GAP2 = 4;
25090
25401
  var TOOLBAR_OFFSET_FROM_ELEMENT = SELECTION_CHROME_GAP2 + TOOLBAR_STROKE_GAP2;
@@ -25182,7 +25493,7 @@ function applyVisibleViewport(el, parentScroll) {
25182
25493
  el.style.top = `${top}px`;
25183
25494
  el.style.height = `${height}px`;
25184
25495
  }
25185
- var TOOLBAR_EDGE_MARGIN3 = 4;
25496
+ var TOOLBAR_EDGE_MARGIN4 = 4;
25186
25497
  function clampToolbarToClip(top, transform, rect, clip, approxH, gap) {
25187
25498
  const isAbove = transform.includes("translateY(-100%)");
25188
25499
  let visualTop = isAbove ? top - approxH : top;
@@ -25209,8 +25520,8 @@ function clampToolbarToClip(top, transform, rect, clip, approxH, gap) {
25209
25520
  function clampToolbarLeft(centerX, toolbarWidth) {
25210
25521
  const half = toolbarWidth / 2;
25211
25522
  return Math.max(
25212
- TOOLBAR_EDGE_MARGIN3 + half,
25213
- Math.min(centerX, window.innerWidth - TOOLBAR_EDGE_MARGIN3 - half)
25523
+ TOOLBAR_EDGE_MARGIN4 + half,
25524
+ Math.min(centerX, window.innerWidth - TOOLBAR_EDGE_MARGIN4 - half)
25214
25525
  );
25215
25526
  }
25216
25527
  function calcToolbarPos(rect, parentScroll, toolbarW = 306) {
@@ -25611,7 +25922,7 @@ function OhhwellsBridge() {
25611
25922
  }, []);
25612
25923
  const subdomainFromQuery = searchParams.get("subdomain");
25613
25924
  const [subdomain, setSubdomain] = useState18(() => resolveSubdomain(subdomainFromQuery));
25614
- useLayoutEffect6(() => {
25925
+ useLayoutEffect7(() => {
25615
25926
  const next = resolveSubdomain(subdomainFromQuery);
25616
25927
  if (next !== subdomain) setSubdomain(next);
25617
25928
  if (next) return;
@@ -26090,21 +26401,6 @@ function OhhwellsBridge() {
26090
26401
  const [isItemDragging, setIsItemDragging] = useState18(false);
26091
26402
  const [isFooterFrameSelection, setIsFooterFrameSelection] = useState18(false);
26092
26403
  isFooterFrameSelectionRef.current = isFooterFrameSelection;
26093
- const [floatingPanel, setFloatingPanel] = useState18(null);
26094
- const floatingPanelOpenRef = useRef14(false);
26095
- floatingPanelOpenRef.current = floatingPanel !== null;
26096
- const floatingPanelRef = useRef14(null);
26097
- floatingPanelRef.current = floatingPanel;
26098
- const [floatingPanelPos, setFloatingPanelPos] = useState18(null);
26099
- const buttonCollectionDragRef = useRef14(null);
26100
- const buttonDropInsertIndexRef = useRef14(null);
26101
- const [buttonDropSlots, setButtonDropSlots] = useState18([]);
26102
- const [activeButtonDropIndex, setActiveButtonDropIndex] = useState18(null);
26103
- const buttonPointerDragRef = useRef14(null);
26104
- const [buttonStyleTick, setButtonStyleTick] = useState18(0);
26105
- const [logoSizeDraft, setLogoSizeDraft] = useState18(null);
26106
- const [editorViewport, setEditorViewport] = useState18("desktop");
26107
- const [parentScrollSnap, setParentScrollSnap] = useState18(null);
26108
26404
  const [navDropdownPreviewOpen, setNavDropdownPreviewOpen] = useState18(null);
26109
26405
  const [footerHeadingVisible, setFooterHeadingVisible] = useState18(null);
26110
26406
  const footerDragRef = useRef14(null);
@@ -26130,6 +26426,21 @@ function OhhwellsBridge() {
26130
26426
  const stylesRef = useRef14("");
26131
26427
  const appliedDrawerRef = useRef14(null);
26132
26428
  const pendingDeleteUndoRef = useRef14(null);
26429
+ const [floatingPanel, setFloatingPanel] = useState18(null);
26430
+ const floatingPanelOpenRef = useRef14(false);
26431
+ const floatingPanelRef = useRef14(null);
26432
+ floatingPanelRef.current = floatingPanel;
26433
+ const setFloatingPanelRef = useRef14(setFloatingPanel);
26434
+ const [floatingPanelPos, setFloatingPanelPos] = useState18(null);
26435
+ const buttonCollectionDragRef = useRef14(null);
26436
+ const buttonDropInsertIndexRef = useRef14(null);
26437
+ const [buttonDropSlots, setButtonDropSlots] = useState18([]);
26438
+ const [activeButtonDropIndex, setActiveButtonDropIndex] = useState18(null);
26439
+ const buttonPointerDragRef = useRef14(null);
26440
+ const [buttonStyleTick, setButtonStyleTick] = useState18(0);
26441
+ const [logoSizeDraft, setLogoSizeDraft] = useState18(null);
26442
+ const [editorViewport, setEditorViewport] = useState18("desktop");
26443
+ const [parentScrollSnap, setParentScrollSnap] = useState18(null);
26133
26444
  const [sitePages, setSitePages] = useState18([]);
26134
26445
  const collectionItemsRef = useRef14(/* @__PURE__ */ new Map());
26135
26446
  const [sectionsByPath, setSectionsByPath] = useState18({});
@@ -26139,7 +26450,18 @@ function OhhwellsBridge() {
26139
26450
  const linkPopoverOpenRef = useRef14(false);
26140
26451
  const linkPopoverGraceUntilRef = useRef14(0);
26141
26452
  setLinkPopoverRef.current = setLinkPopover;
26453
+ setFloatingPanelRef.current = setFloatingPanel;
26142
26454
  linkPopoverSessionRef.current = linkPopover;
26455
+ floatingPanelOpenRef.current = Boolean(floatingPanel);
26456
+ useEffect17(() => {
26457
+ const syncViewport = () => {
26458
+ const next = window.innerWidth <= 480 ? "mobile" : "desktop";
26459
+ setEditorViewport((prev) => prev === next ? prev : next);
26460
+ };
26461
+ syncViewport();
26462
+ window.addEventListener("resize", syncViewport);
26463
+ return () => window.removeEventListener("resize", syncViewport);
26464
+ }, []);
26143
26465
  useEffect17(() => {
26144
26466
  setSectionOrderPersistHandler((entries) => {
26145
26467
  const currentPath = window.location.pathname;
@@ -26288,6 +26610,43 @@ function OhhwellsBridge() {
26288
26610
  if (!useFixtures) postToParent2({ type: "ow:request-site-pages" });
26289
26611
  return () => window.removeEventListener("message", onSitePages);
26290
26612
  }, [isEditMode, postToParent2]);
26613
+ useEffect17(() => {
26614
+ if (!isEditMode) return;
26615
+ const deps = {
26616
+ getEditContent: () => editContentRef.current,
26617
+ setEditContent: (next) => {
26618
+ editContentRef.current = next;
26619
+ },
26620
+ applyLinkByKey,
26621
+ postToParent: postToParent2
26622
+ };
26623
+ const onPageMessage = (e) => {
26624
+ if (e.data?.type === "ow:page-created") {
26625
+ const { path, title } = e.data;
26626
+ if (typeof path !== "string" || !path) return;
26627
+ if (addNavLinkForPage({ path, title: typeof title === "string" && title ? title : path }, deps)) {
26628
+ enforceLinkHrefs();
26629
+ }
26630
+ return;
26631
+ }
26632
+ if (e.data?.type === "ow:page-renamed") {
26633
+ const { oldPath, newPath, oldTitle, newTitle } = e.data;
26634
+ if (typeof oldPath !== "string" || typeof newPath !== "string") return;
26635
+ const moved = retargetPageLinks(
26636
+ {
26637
+ oldPath,
26638
+ newPath,
26639
+ oldTitle: typeof oldTitle === "string" ? oldTitle : void 0,
26640
+ newTitle: typeof newTitle === "string" ? newTitle : void 0
26641
+ },
26642
+ deps
26643
+ );
26644
+ if (moved > 0) enforceLinkHrefs();
26645
+ }
26646
+ };
26647
+ window.addEventListener("message", onPageMessage);
26648
+ return () => window.removeEventListener("message", onPageMessage);
26649
+ }, [isEditMode, postToParent2]);
26291
26650
  useEffect17(() => {
26292
26651
  if (!isEditMode || shouldUseDevFixtures()) return;
26293
26652
  void loadAllSectionsManifest().then((manifest) => {
@@ -27809,7 +28168,7 @@ function OhhwellsBridge() {
27809
28168
  isFooterFrameSelection,
27810
28169
  postToParent2
27811
28170
  ]);
27812
- useLayoutEffect6(() => {
28171
+ useLayoutEffect7(() => {
27813
28172
  if (!subdomain || isEditMode) {
27814
28173
  setFetchState("done");
27815
28174
  return;
@@ -27836,6 +28195,7 @@ function OhhwellsBridge() {
27836
28195
  applyAiFooterFromContent(content);
27837
28196
  if (typeof content[STYLE_STORE_KEY] === "string") {
27838
28197
  stylesRef.current = content[STYLE_STORE_KEY];
28198
+ document.querySelectorAll('[data-ohw-editable="form"]').forEach((f) => markFormFields(f));
27839
28199
  applyStylesToDom(parseStyleStore(content[STYLE_STORE_KEY]));
27840
28200
  }
27841
28201
  applyBrandChrome(content);
@@ -27846,15 +28206,16 @@ function OhhwellsBridge() {
27846
28206
  if (key === "__ohw_sections") continue;
27847
28207
  if (key === AI_SECTIONS_KEY) continue;
27848
28208
  if (key === AI_FOOTER_KEY) continue;
28209
+ if (key === LOGO_PLACEHOLDER_KEY) continue;
28210
+ if (LOGO_IMAGE_KEYS.includes(key)) continue;
27849
28211
  if (key === BRAND_KIT_KEY) continue;
27850
28212
  if (key === STYLE_STORE_KEY) continue;
27851
28213
  if (BRAND_CHROME_KEYS.has(key)) continue;
27852
- if (key === LOGO_PLACEHOLDER_KEY) continue;
27853
28214
  if (key === CUSTOM_CODE_AFTER_HEAD_KEY || key === CUSTOM_CODE_BEFORE_HEAD_KEY) continue;
27854
- if (LOGO_IMAGE_KEYS.includes(key)) continue;
27855
28215
  if (typeof content[AI_FOOTER_KEY] === "string" && content[AI_FOOTER_KEY] && isAiFooterOwnedContentKey(key)) {
27856
28216
  continue;
27857
28217
  }
28218
+ if (key.endsWith(SECTION_EMBED_SUFFIX)) continue;
27858
28219
  if (applyVideoSettingNode(key, val)) continue;
27859
28220
  if (applyCarouselNode(key, val)) continue;
27860
28221
  document.querySelectorAll(`[data-ohw-key="${key}"]`).forEach((el) => {
@@ -27894,6 +28255,7 @@ function OhhwellsBridge() {
27894
28255
  });
27895
28256
  applyLinkByKey(key, val);
27896
28257
  }
28258
+ applySectionEmbeds(content);
27897
28259
  applyLogoFromContent(content);
27898
28260
  applyLogoSizes(content);
27899
28261
  reconcileNavbarItemsFromContent(content);
@@ -28082,20 +28444,22 @@ function OhhwellsBridge() {
28082
28444
  }
28083
28445
  applyAiFooterFromContent(content);
28084
28446
  if (typeof content[STYLE_STORE_KEY] === "string") {
28447
+ document.querySelectorAll('[data-ohw-editable="form"]').forEach((f) => markFormFields(f));
28085
28448
  applyStylesToDom(parseStyleStore(content[STYLE_STORE_KEY]));
28086
28449
  }
28087
28450
  for (const [key, val] of Object.entries(content)) {
28088
28451
  if (key === "__ohw_sections") continue;
28089
28452
  if (key === AI_SECTIONS_KEY) continue;
28090
28453
  if (key === AI_FOOTER_KEY) continue;
28454
+ if (key === LOGO_PLACEHOLDER_KEY) continue;
28455
+ if (LOGO_IMAGE_KEYS.includes(key)) continue;
28091
28456
  if (key === BRAND_KIT_KEY) continue;
28092
28457
  if (key === STYLE_STORE_KEY) continue;
28093
28458
  if (BRAND_CHROME_KEYS.has(key)) continue;
28094
- if (key === LOGO_PLACEHOLDER_KEY) continue;
28095
- if (LOGO_IMAGE_KEYS.includes(key)) continue;
28096
28459
  if (typeof content[AI_FOOTER_KEY] === "string" && content[AI_FOOTER_KEY] && isAiFooterOwnedContentKey(key)) {
28097
28460
  continue;
28098
28461
  }
28462
+ if (key.endsWith(SECTION_EMBED_SUFFIX)) continue;
28099
28463
  if (applyVideoSettingNode(key, val)) continue;
28100
28464
  if (applyCarouselNode(key, val)) continue;
28101
28465
  document.querySelectorAll(`[data-ohw-key="${key}"]`).forEach((el) => {
@@ -28120,6 +28484,7 @@ function OhhwellsBridge() {
28120
28484
  });
28121
28485
  applyLinkByKey(key, val);
28122
28486
  }
28487
+ applySectionEmbeds(content);
28123
28488
  applyLogoFromContent(content);
28124
28489
  reconcileNavbarItemsFromContent(content);
28125
28490
  reconcileFooterOrderFromContent(content);
@@ -28175,7 +28540,7 @@ function OhhwellsBridge() {
28175
28540
  if (debounceTimer) clearTimeout(debounceTimer);
28176
28541
  };
28177
28542
  }, [subdomain, isEditMode, pathname]);
28178
- useLayoutEffect6(() => {
28543
+ useLayoutEffect7(() => {
28179
28544
  const el = document.getElementById("ohw-loader");
28180
28545
  if (!el) return;
28181
28546
  const visible = Boolean(subdomain) && fetchState !== "done";
@@ -28359,26 +28724,11 @@ function OhhwellsBridge() {
28359
28724
  const t2 = setTimeout(measure, 500);
28360
28725
  const ro = new ResizeObserver(schedule);
28361
28726
  ro.observe(document.body);
28362
- let lastWidth = window.innerWidth;
28363
- let resizeTimers = [];
28364
- const clearResizeTimers = () => {
28365
- resizeTimers.forEach(clearTimeout);
28366
- resizeTimers = [];
28367
- };
28368
- const handleResize = () => {
28369
- if (window.innerWidth === lastWidth) return;
28370
- lastWidth = window.innerWidth;
28371
- clearResizeTimers();
28372
- resizeTimers = HEIGHT_SETTLE_DELAYS.map((delay) => setTimeout(measure, delay));
28373
- };
28374
- window.addEventListener("resize", handleResize);
28375
28727
  return () => {
28376
28728
  clearTimeout(t1);
28377
28729
  clearTimeout(t2);
28378
28730
  if (raf != null) cancelAnimationFrame(raf);
28379
28731
  ro.disconnect();
28380
- clearResizeTimers();
28381
- window.removeEventListener("resize", handleResize);
28382
28732
  };
28383
28733
  }, [pathname, isEditMode, postToParent2]);
28384
28734
  useEffect17(() => {
@@ -28675,6 +29025,13 @@ function OhhwellsBridge() {
28675
29025
  if (target.closest("[data-ohw-state-toggle]")) return;
28676
29026
  if (target.closest("[data-ohw-max-badge]")) return;
28677
29027
  if (isInsideLinkEditor(target)) return;
29028
+ if (isInsideFloatingPanel(target)) return;
29029
+ if (target.closest("[data-ohw-form-toolbar]")) return;
29030
+ if (target.closest(
29031
+ '[data-ohw-field-toolbar], [data-ohw-field-type-picker], [data-radix-popper-content-wrapper], [role="menu"], [data-slot="dropdown-menu-content"]'
29032
+ )) {
29033
+ return;
29034
+ }
28678
29035
  if (selectedMediaElRef.current && !selectedMediaElRef.current.contains(target) && !target.closest("[data-ohw-media-overlay], [data-ohw-edit-chrome], [data-ohw-item-interaction]")) {
28679
29036
  clearMediaSelectionRef.current();
28680
29037
  }
@@ -28698,8 +29055,6 @@ function OhhwellsBridge() {
28698
29055
  return;
28699
29056
  }
28700
29057
  }
28701
- if (isInsideFloatingPanel(target)) return;
28702
- if (target.closest("[data-ohw-form-toolbar]")) return;
28703
29058
  const collectionItem = matchCollectionItemClick(target, collectionItemsRef.current);
28704
29059
  if (collectionItem) {
28705
29060
  e.preventDefault();
@@ -28711,11 +29066,6 @@ function OhhwellsBridge() {
28711
29066
  });
28712
29067
  return;
28713
29068
  }
28714
- if (target.closest(
28715
- '[data-ohw-field-toolbar], [data-ohw-field-type-picker], [data-radix-popper-content-wrapper], [role="menu"], [data-slot="dropdown-menu-content"]'
28716
- )) {
28717
- return;
28718
- }
28719
29069
  {
28720
29070
  const formEl = getFormElement(target);
28721
29071
  const onSuccessText = target.closest(`[${SUCCESS_TEXT_ATTR}]`);
@@ -28869,9 +29219,13 @@ function OhhwellsBridge() {
28869
29219
  if (isIconEditable(editable) && !getSocialItem(editable)) {
28870
29220
  e.preventDefault();
28871
29221
  e.stopPropagation();
28872
- selectIconElementRef.current(editable);
28873
29222
  const aiIcon = editable.hasAttribute("data-ai-icon");
28874
29223
  const featureIcon = aiIcon && /\.features\.\d+\.icon$/.test(editable.dataset.ohwKey ?? "");
29224
+ if (aiIcon) {
29225
+ aiSectionApiRef.current?.selectFromElement(editable);
29226
+ } else {
29227
+ selectIconElementRef.current(editable);
29228
+ }
28875
29229
  postToParentRef.current({
28876
29230
  type: "ow:icon-pick",
28877
29231
  key: editable.dataset.ohwKey ?? "",
@@ -29099,6 +29453,9 @@ function OhhwellsBridge() {
29099
29453
  setHoveredItemRect(null);
29100
29454
  hoveredNavContainerRef.current = null;
29101
29455
  setHoveredNavContainerRect(null);
29456
+ siblingHintElRef.current = null;
29457
+ setSiblingHintRect(null);
29458
+ setSiblingHintRects([]);
29102
29459
  return;
29103
29460
  }
29104
29461
  {
@@ -29219,7 +29576,6 @@ function OhhwellsBridge() {
29219
29576
  hoveredNavContainerRef.current = null;
29220
29577
  setHoveredNavContainerRect(null);
29221
29578
  hoveredItemElRef.current = editable;
29222
- setHoveredItemRect(isSelectedForHoverRef.current(editable) ? null : editable.getBoundingClientRect());
29223
29579
  }
29224
29580
  }
29225
29581
  }
@@ -29929,7 +30285,11 @@ function OhhwellsBridge() {
29929
30285
  };
29930
30286
  const handleMouseMove = (e) => {
29931
30287
  const { clientX, clientY } = e;
29932
- if (pointOwnedByFloatingPanel(clientX, clientY)) return;
30288
+ if (pointOwnedByFloatingPanel(clientX, clientY)) {
30289
+ dismissImageHover();
30290
+ clearImageHover();
30291
+ return;
30292
+ }
29933
30293
  if (isNavDrawerOpen() && isPointOutsideOpenDrawer(clientX, clientY)) {
29934
30294
  document.querySelectorAll("[data-ohw-hovered]").forEach((el) => el.removeAttribute("data-ohw-hovered"));
29935
30295
  formHoverElRef.current = null;
@@ -29958,7 +30318,12 @@ function OhhwellsBridge() {
29958
30318
  setHoveredItemRect(null);
29959
30319
  hoveredNavContainerRef.current = null;
29960
30320
  setHoveredNavContainerRect(null);
30321
+ siblingHintElRef.current = null;
30322
+ setSiblingHintRect(null);
30323
+ setSiblingHintRects([]);
29961
30324
  dismissImageHover();
30325
+ clearImageHover();
30326
+ setSectionGap(null);
29962
30327
  return;
29963
30328
  }
29964
30329
  if (probeSocialsRowAt(clientX, clientY)) return;
@@ -29970,7 +30335,11 @@ function OhhwellsBridge() {
29970
30335
  if (e.data?.type !== "ow:pointer-sync") return;
29971
30336
  const { clientX, clientY } = e.data;
29972
30337
  if (typeof clientX !== "number" || typeof clientY !== "number") return;
29973
- if (pointOwnedByFloatingPanel(clientX, clientY)) return;
30338
+ if (pointOwnedByFloatingPanel(clientX, clientY)) {
30339
+ dismissImageHover();
30340
+ clearImageHover();
30341
+ return;
30342
+ }
29974
30343
  if (isNavDrawerOpen() && isPointOutsideOpenDrawer(clientX, clientY)) return;
29975
30344
  if (probeSocialsRowAt(clientX, clientY)) return;
29976
30345
  probeSectionGapAt(clientX, clientY);
@@ -30350,6 +30719,7 @@ function OhhwellsBridge() {
30350
30719
  applyAiFooterFromContent(content);
30351
30720
  if (typeof content[STYLE_STORE_KEY] === "string") {
30352
30721
  stylesRef.current = content[STYLE_STORE_KEY];
30722
+ document.querySelectorAll('[data-ohw-editable="form"]').forEach((f) => markFormFields(f));
30353
30723
  applyStylesToDom(parseStyleStore(content[STYLE_STORE_KEY]));
30354
30724
  }
30355
30725
  applyBrandChrome(content);
@@ -30364,15 +30734,16 @@ function OhhwellsBridge() {
30364
30734
  }
30365
30735
  if (key === AI_SECTIONS_KEY) continue;
30366
30736
  if (key === AI_FOOTER_KEY) continue;
30737
+ if (key === LOGO_PLACEHOLDER_KEY) continue;
30738
+ if (LOGO_IMAGE_KEYS.includes(key) && !String(val ?? "").trim()) continue;
30367
30739
  if (key === BRAND_KIT_KEY) continue;
30368
30740
  if (key === STYLE_STORE_KEY) continue;
30369
30741
  if (BRAND_CHROME_KEYS.has(key)) continue;
30370
- if (key === LOGO_PLACEHOLDER_KEY) continue;
30371
30742
  if (key === CUSTOM_CODE_AFTER_HEAD_KEY || key === CUSTOM_CODE_BEFORE_HEAD_KEY) continue;
30372
- if (LOGO_IMAGE_KEYS.includes(key) && !String(val ?? "").trim()) continue;
30373
30743
  if (typeof content[AI_FOOTER_KEY] === "string" && content[AI_FOOTER_KEY] && isAiFooterOwnedContentKey(key)) {
30374
30744
  continue;
30375
30745
  }
30746
+ if (key.endsWith(SECTION_EMBED_SUFFIX)) continue;
30376
30747
  if (applyVideoSettingNode(key, val)) continue;
30377
30748
  if (applyCarouselNode(key, val)) continue;
30378
30749
  document.querySelectorAll(`[data-ohw-key="${key}"]`).forEach((el) => {
@@ -30398,6 +30769,7 @@ function OhhwellsBridge() {
30398
30769
  });
30399
30770
  applyLinkByKey(key, val);
30400
30771
  }
30772
+ applySectionEmbeds(content);
30401
30773
  applyLogoFromContent(content);
30402
30774
  applyLogoSizes(content);
30403
30775
  document.querySelectorAll('[data-ohw-editable="form"]').forEach((form) => {
@@ -31005,6 +31377,12 @@ function OhhwellsBridge() {
31005
31377
  closeLinkPopoverRef.current();
31006
31378
  return;
31007
31379
  }
31380
+ if (floatingPanelOpenRef.current) {
31381
+ setFloatingPanelRef.current(null);
31382
+ deselectRef.current();
31383
+ deactivateRef.current();
31384
+ return;
31385
+ }
31008
31386
  deselectRef.current();
31009
31387
  deactivateRef.current();
31010
31388
  clearMediaSelectionRef.current();
@@ -31393,7 +31771,7 @@ function OhhwellsBridge() {
31393
31771
  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;
31394
31772
  const nodes = sectionEl ? collectEditableNodes(void 0, sectionEl).filter(
31395
31773
  (n) => n.key && !n.key.startsWith("__ohw_") && !["meta", "sections", "video-setting"].includes(n.type)
31396
- ).map((n) => ({ ...n, text: n.text ?? "", type: normalizeAiNodeType(n.type) })) : [];
31774
+ ).map((n) => ({ ...n, text: n.text ?? "", type: normalizeAiNodeType(n.type) })).map((n) => withNodeFontSize(n, sectionEl)) : [];
31397
31775
  const items = sectionEl ? collectItemGroups(sectionEl) : [];
31398
31776
  const box = sectionEl?.getBoundingClientRect();
31399
31777
  const rect = box ? {
@@ -31840,10 +32218,10 @@ function OhhwellsBridge() {
31840
32218
  window.removeEventListener("message", handleDuplicateSection);
31841
32219
  window.removeEventListener("message", handleDeactivate);
31842
32220
  window.removeEventListener("message", handleSelectNav);
31843
- document.documentElement.removeAttribute("data-ohw-panel-dragging");
31844
32221
  window.removeEventListener("message", handleToastAction);
31845
32222
  window.removeEventListener("message", handleFormCount);
31846
32223
  window.removeEventListener("message", handleUiEscape);
32224
+ document.documentElement.removeAttribute("data-ohw-panel-dragging");
31847
32225
  autoSaveTimers.current.forEach(clearTimeout);
31848
32226
  autoSaveTimers.current.clear();
31849
32227
  if (imageUnhoverTimerRef.current) clearTimeout(imageUnhoverTimerRef.current);
@@ -32137,7 +32515,7 @@ function OhhwellsBridge() {
32137
32515
  postToParent2({
32138
32516
  type: "ow:ready",
32139
32517
  version: "1",
32140
- bridgeVersion: "0.1.159",
32518
+ bridgeVersion: "0.1.160-next.428",
32141
32519
  path: pathname,
32142
32520
  nodes: collectEditableNodes(editContentRef.current),
32143
32521
  sections
@@ -32872,7 +33250,7 @@ function OhhwellsBridge() {
32872
33250
  className: "flex h-7 w-7 items-center justify-center rounded-md text-foreground transition-colors hover:bg-muted/80",
32873
33251
  onClick: () => setFieldTypePickerOpen((open) => !open),
32874
33252
  "data-ohw-add-field": "",
32875
- children: /* @__PURE__ */ jsx40(Plus5, { size: 15, "aria-hidden": true })
33253
+ children: /* @__PURE__ */ jsx40(Plus6, { size: 15, "aria-hidden": true })
32876
33254
  }
32877
33255
  ),
32878
33256
  /* @__PURE__ */ jsx40("div", { className: "mx-0.5 h-5 w-px bg-border" }),