@ohhwells/bridge 0.1.155 → 0.1.157-next.420

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -64,6 +64,7 @@ __export(index_exports, {
64
64
  OhwLoaderSurface: () => OhwLoaderSurface,
65
65
  SITE_OG_IMAGE_KEY: () => SITE_OG_IMAGE_KEY,
66
66
  SchedulingWidget: () => SchedulingWidget,
67
+ TEMPLATE_LOGO_ATTR: () => TEMPLATE_LOGO_ATTR,
67
68
  Toggle: () => Toggle,
68
69
  ToggleGroup: () => ToggleGroup,
69
70
  ToggleGroupItem: () => ToggleGroupItem,
@@ -83,6 +84,7 @@ __export(index_exports, {
83
84
  foldAlignIntoTrees: () => foldAlignIntoTrees,
84
85
  getEditModeInitialState: () => getEditModeInitialState,
85
86
  hasMountedContentNodes: () => hasMountedContentNodes,
87
+ indexCollectionItems: () => indexCollectionItems,
86
88
  insertFooterHours: () => insertFooterHours,
87
89
  isEditSessionActive: () => isEditSessionActive,
88
90
  isPlainLeftClick: () => isPlainLeftClick,
@@ -90,11 +92,13 @@ __export(index_exports, {
90
92
  isRenderableTree: () => isRenderableTree,
91
93
  isStickySearchParam: () => isStickySearchParam,
92
94
  isValidUrl: () => isValidUrl,
95
+ matchCollectionItemClick: () => matchCollectionItemClick,
93
96
  nextViableSlide: () => nextViableSlide,
94
97
  parseAiFooterState: () => parseAiFooterState,
95
98
  parseAiSectionsState: () => parseAiSectionsState,
96
99
  parseTarget: () => parseTarget,
97
100
  recordOverriddenPaths: () => recordOverriddenPaths,
101
+ releaseTemplateLogoIfReplaced: () => releaseTemplateLogoIfReplaced,
98
102
  removeFooterBlock: () => removeFooterBlock,
99
103
  resetOverriddenPathsCache: () => resetOverriddenPathsCache,
100
104
  resolveFooterCopyright: () => resolveFooterCopyright,
@@ -516,11 +520,37 @@ function logoTextElement(root) {
516
520
  );
517
521
  return hasOwnText ? root : null;
518
522
  }
523
+ var TEMPLATE_LOGO_ATTR = "data-ohw-template-logo";
524
+ function releaseTemplateLogoIfReplaced(img, url) {
525
+ if (!img.hasAttribute(TEMPLATE_LOGO_ATTR)) return;
526
+ let incoming = url;
527
+ try {
528
+ incoming = new URL(url, document.baseURI).href;
529
+ } catch {
530
+ }
531
+ if (incoming !== img.src) img.removeAttribute(TEMPLATE_LOGO_ATTR);
532
+ }
533
+ function logoImage(root, selector) {
534
+ if (root instanceof HTMLImageElement && root.matches(selector)) return root;
535
+ return root.querySelector(selector);
536
+ }
519
537
  function logoRootImage(root) {
520
- const img = root.querySelector("img");
538
+ const img = logoImage(root, `img:not([${TEMPLATE_LOGO_ATTR}])`);
521
539
  const src = img?.getAttribute("src")?.trim() ?? "";
522
540
  return img && src && img.style.display !== "none" ? img : null;
523
541
  }
542
+ function templateLogoImage(root) {
543
+ const img = logoImage(root, `img[${TEMPLATE_LOGO_ATTR}]`);
544
+ return img && img.style.display !== "none" ? img : null;
545
+ }
546
+ function createWordmark(root) {
547
+ const el = document.createElement("span");
548
+ el.setAttribute("data-ohw-editable", "plain");
549
+ el.setAttribute("data-ohw-key", textKeyForRoot(root));
550
+ if (root instanceof HTMLImageElement) root.parentNode?.insertBefore(el, root.nextSibling);
551
+ else root.appendChild(el);
552
+ return el;
553
+ }
524
554
  function applyLogoIdentity(text, isPlaceholder) {
525
555
  const display = resolveLogoDisplayText(text);
526
556
  const placeholder = isPlaceholder || !text.trim() || display === PLACEHOLDER_BUSINESS_NAME;
@@ -539,7 +569,9 @@ function applyLogoIdentity(text, isPlaceholder) {
539
569
  if (placeholder) el.setAttribute("data-ohw-placeholder", "");
540
570
  else el.removeAttribute("data-ohw-placeholder");
541
571
  if (logoRootImage(el)) return;
542
- const textEl = logoTextElement(el);
572
+ const templateMark = placeholder ? null : templateLogoImage(el);
573
+ if (templateMark) templateMark.style.display = "none";
574
+ const textEl = logoTextElement(el) ?? (templateMark ? createWordmark(el) : null);
543
575
  if (textEl && textEl.textContent !== display) textEl.textContent = display;
544
576
  });
545
577
  return display;
@@ -571,6 +603,7 @@ function applyLogoImage(url, alt, placement) {
571
603
  }
572
604
  img.removeAttribute("srcset");
573
605
  img.removeAttribute("sizes");
606
+ img.removeAttribute(TEMPLATE_LOGO_ATTR);
574
607
  img.src = url;
575
608
  img.alt = displayAlt;
576
609
  img.style.display = "block";
@@ -590,10 +623,7 @@ function applyLogoImage(url, alt, placement) {
590
623
  img.style.display = "none";
591
624
  }
592
625
  if (!textEl) {
593
- textEl = document.createElement("span");
594
- textEl.setAttribute("data-ohw-editable", "plain");
595
- textEl.setAttribute("data-ohw-key", textKey);
596
- root.appendChild(textEl);
626
+ textEl = createWordmark(root);
597
627
  }
598
628
  textEl.style.display = "";
599
629
  if (textEl.textContent !== displayAlt) textEl.textContent = displayAlt;
@@ -6652,6 +6682,21 @@ function pricingCardFeatureCount(tree, index) {
6652
6682
  const target = findPricingCollection(tree)?.children?.[index];
6653
6683
  return target ? pricingFeatureList(target).length : 0;
6654
6684
  }
6685
+ function prevailingFeatureIcon(features) {
6686
+ const counts = /* @__PURE__ */ new Map();
6687
+ for (const row of features) {
6688
+ const key = `${row.icon ?? ""}|${row.iconColor ?? ""}`;
6689
+ const entry = counts.get(key) ?? { count: 0, ...row.icon ? { icon: row.icon } : {}, ...row.iconColor ? { iconColor: row.iconColor } : {} };
6690
+ entry.count += 1;
6691
+ counts.set(key, entry);
6692
+ }
6693
+ let best = null;
6694
+ for (const entry of counts.values()) {
6695
+ if (!best || entry.count > best.count) best = entry;
6696
+ }
6697
+ if (!best) return {};
6698
+ return { ...best.icon ? { icon: best.icon } : {}, ...best.iconColor ? { iconColor: best.iconColor } : {} };
6699
+ }
6655
6700
  function addPricingCardFeature(tree, index, text) {
6656
6701
  return withPricingCollection(tree, (collection) => {
6657
6702
  const children = collection.children;
@@ -6660,7 +6705,7 @@ function addPricingCardFeature(tree, index, text) {
6660
6705
  const features = pricingFeatureList(target);
6661
6706
  if (features.length >= PRICING_FEATURES_MAX) return false;
6662
6707
  target.slots = target.slots ?? {};
6663
- target.slots.features = [...features, { text }];
6708
+ target.slots.features = [...features, { text, ...prevailingFeatureIcon(features) }];
6664
6709
  return true;
6665
6710
  });
6666
6711
  }
@@ -6847,6 +6892,7 @@ function applyBrandChrome(content) {
6847
6892
  if (el.textContent !== name) el.textContent = name;
6848
6893
  if (el.getAttribute("title") !== name) el.setAttribute("title", name);
6849
6894
  });
6895
+ applyLogoIdentity(name, false);
6850
6896
  }
6851
6897
  const title = content[BRAND_TITLE_KEY];
6852
6898
  if (typeof title === "string" && title.length > 0) {
@@ -7859,7 +7905,7 @@ var AI_MOBILE_CSS = [
7859
7905
  // page's first section. Top only (bottom needs no nav clearance); ~96px approximates the
7860
7906
  // template nav's own height (measured ~94px) — there's no shared `--ohw-header-h` var to read
7861
7907
  // an exact figure from (no live measurement exists), so this is a safe approximation, not a
7862
- // pixel-exact fit. Horizontal spacing already comes from `data-ai-container`.
7908
+ // pixel-exact fit. Horizontal spacing already comes from `data-ai-section-inner`.
7863
7909
  '[data-ai-overlay-clearance] [data-ai-overlap-layer="overlay"]{padding-top:96px}',
7864
7910
  // A full-bleed carousel's height is pure arithmetic: it spans the whole window, so 16/9 turns
7865
7911
  // window width straight into height. Measured at 16/9 it came out ~90% of the viewport at EVERY
@@ -7875,8 +7921,10 @@ var AI_MOBILE_CSS = [
7875
7921
  "[data-ai-section]{overflow-x:hidden}",
7876
7922
  // `:not([data-ai-full-bleed])`: this `!important` gutter beat the inline `padding: 0` that a
7877
7923
  // full-bleed section's container sets, so a carousel/media hero that is edge-to-edge on desktop
7878
- // still got a 20px band of section background down both sides on a phone.
7879
- "[data-ai-container]:not([data-ai-full-bleed]){padding:0 20px !important}",
7924
+ // still got a 20px band of section background down both sides on a phone. The footer's own
7925
+ // container keeps the unguarded rule below — `data-ai-full-bleed` can never appear on it.
7926
+ "[data-ai-section-inner]:not([data-ai-full-bleed]){padding:0 20px !important}",
7927
+ "[data-ai-container]{padding:0 20px !important}",
7880
7928
  "[data-ai-row]{display:flex !important;flex-direction:column !important;align-items:stretch !important}",
7881
7929
  "[data-ai-cell]{width:100%;min-width:0}",
7882
7930
  // The rule above turns every row into a COLUMN flex container on phones, which silently breaks
@@ -8081,6 +8129,31 @@ function pricingCardEmphasis(featured, ctx, hasBg) {
8081
8129
  function textAttrs(ctx, path) {
8082
8130
  return ctx.keyFor ? { "data-ohw-key": ctx.keyFor(path), "data-ohw-editable": "text" } : {};
8083
8131
  }
8132
+ var AI_RESPONSIVE_CSS = [
8133
+ "@media (max-width: 960px) {",
8134
+ " [data-ai-responsive] [data-ai-grid] { grid-template-columns: repeat(2, 1fr) !important; }",
8135
+ ' [data-ai-responsive] [data-ai-grid="1"] { grid-template-columns: 1fr !important; }',
8136
+ "}",
8137
+ "@media (max-width: 640px) {",
8138
+ " [data-ai-responsive] [data-ai-grid] { grid-template-columns: 1fr !important; }",
8139
+ " [data-ai-responsive] [data-ai-columns] > * { grid-column: 1 / -1 !important; }",
8140
+ // Group containers flatten to a column on phones; span placements come along for free.
8141
+ // `:not([data-ai-group="overlap"])` for the same reason as the twin pair in AI_MOBILE_CSS — see
8142
+ // the comment there. This sheet's copy is the narrower selector, so without the exemption it
8143
+ // flattens an `overlap` hero below 640px even though the other rule stands down: measured a 390px
8144
+ // viewport with the heading/CTA in flow *under* the image, 219px of section background where the
8145
+ // layered text used to be.
8146
+ ' [data-ai-responsive] [data-ai-group]:not([data-ai-group="overlap"]) { display: flex !important; flex-direction: column !important; }',
8147
+ ' [data-ai-responsive] [data-ai-group]:not([data-ai-group="overlap"]) > * { grid-column: auto !important; }',
8148
+ // `:not([data-ai-full-bleed])` for the same reason as the twin rule in AI_MOBILE_CSS: this one
8149
+ // is narrower (0,2,0 vs 0,1,0) and also `!important`, so without the guard it wins outright and
8150
+ // a full-bleed hero gets its 20px side band back below 640px only — the one width where the
8151
+ // other rule had already been taught to stand down.
8152
+ " [data-ai-responsive] [data-ai-section-inner]:not([data-ai-full-bleed]) { padding-left: 20px !important; padding-right: 20px !important; }",
8153
+ " [data-ai-responsive] { overflow-x: hidden; }",
8154
+ " [data-ai-responsive] img { max-width: 100%; }",
8155
+ "}"
8156
+ ].join("\n");
8084
8157
  var TRANSPARENT_PX = "data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7";
8085
8158
  function MediaBox({
8086
8159
  refValue,
@@ -8135,7 +8208,7 @@ function MediaBox({
8135
8208
  position: fill ? "absolute" : void 0,
8136
8209
  inset: fill ? 0 : void 0,
8137
8210
  width: fill ? "100%" : fixedSize ? "auto" : "100%",
8138
- height: fill ? "100%" : void 0,
8211
+ height: fill ? "100%" : fixedSize ? height : void 0,
8139
8212
  aspectRatio: fill ? void 0 : aspectRatio,
8140
8213
  minHeight: fill || fixedSize ? void 0 : height ?? (aspectRatio ? void 0 : 180),
8141
8214
  borderRadius: radius,
@@ -9631,7 +9704,7 @@ function CollectionBlock({ node, ctx, path }) {
9631
9704
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
9632
9705
  "div",
9633
9706
  {
9634
- "data-ai-grid": "",
9707
+ "data-ai-grid": String(itemsPerRow),
9635
9708
  "data-ohw-pricing-collection": isPricing ? "" : void 0,
9636
9709
  style: {
9637
9710
  display: "grid",
@@ -9933,7 +10006,7 @@ function renderNode(node, ctx, path, ambientAlign) {
9933
10006
  },
9934
10007
  children: [
9935
10008
  i > 0 && ctx.heroOverlapFullBleed ? (
9936
- // The section's `data-ai-container` gave up its max-width/gutter so the base
10009
+ // The section's `data-ai-section-inner` gave up its max-width/gutter so the base
9937
10010
  // layer (this group's i===0 sibling) can span edge-to-edge — reintroduced here,
9938
10011
  // scoped to just the overlay text, so a heading/copy/CTA stack doesn't stretch
9939
10012
  // the full section width instead of reading at the usual content width.
@@ -10352,6 +10425,7 @@ function AiTreeRenderer({
10352
10425
  ...fillHeight ? { "data-ai-fill-height": "" } : {},
10353
10426
  ...needsOverlayClearance ? { "data-ai-overlay-clearance": "" } : {},
10354
10427
  ...bgAttrs,
10428
+ "data-ai-responsive": "",
10355
10429
  style: {
10356
10430
  position: "relative",
10357
10431
  padding: `${padCss} 0`,
@@ -10365,12 +10439,13 @@ function AiTreeRenderer({
10365
10439
  ...fillHeight ? { display: "grid" } : {}
10366
10440
  },
10367
10441
  children: [
10368
- isOverlay && backgroundUrl && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { position: "absolute", inset: 0, background: `rgba(255,255,255,${overlayAlpha})` } }),
10442
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("style", { children: AI_RESPONSIVE_CSS }),
10369
10443
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("style", { children: AI_MOBILE_CSS }),
10444
+ isOverlay && backgroundUrl && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { position: "absolute", inset: 0, background: `rgba(255,255,255,${overlayAlpha})` } }),
10370
10445
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
10371
10446
  "div",
10372
10447
  {
10373
- "data-ai-container": "",
10448
+ "data-ai-section-inner": "",
10374
10449
  ...fullBleedMedia ? { "data-ai-full-bleed": "" } : {},
10375
10450
  style: {
10376
10451
  position: "relative",
@@ -10387,7 +10462,7 @@ function AiTreeRenderer({
10387
10462
  children: tree.rows.map((row, r2) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
10388
10463
  "div",
10389
10464
  {
10390
- "data-ai-row": "",
10465
+ "data-ai-columns": "",
10391
10466
  style: {
10392
10467
  display: "grid",
10393
10468
  gridTemplateColumns: "repeat(12, 1fr)",
@@ -11049,9 +11124,10 @@ function applyAiSectionsToDom(state, options) {
11049
11124
  if (existing && existing.serialized === serialized && existing.container.isConnected) {
11050
11125
  continue;
11051
11126
  }
11052
- const isNewMount = !existing || !existing.container.isConnected;
11053
11127
  let container = existing?.container;
11128
+ let isNewMount = false;
11054
11129
  if (!container || !container.isConnected) {
11130
+ isNewMount = true;
11055
11131
  existing?.root.unmount();
11056
11132
  container?.remove();
11057
11133
  container = document.createElement("div");
@@ -11695,6 +11771,172 @@ function useLinkHrefGuardian(...deps) {
11695
11771
  }, deps);
11696
11772
  }
11697
11773
 
11774
+ // src/ui/link-modal/linkModal.utils.ts
11775
+ function parseTarget(target) {
11776
+ if (!target) return { pageRoute: "", sectionId: null };
11777
+ const hashIndex = target.indexOf("#");
11778
+ if (hashIndex === -1) return { pageRoute: target, sectionId: null };
11779
+ return {
11780
+ pageRoute: target.slice(0, hashIndex),
11781
+ sectionId: target.slice(hashIndex + 1) || null
11782
+ };
11783
+ }
11784
+ function normalizePath(path) {
11785
+ const trimmed = path.trim();
11786
+ if (!trimmed) return "/";
11787
+ try {
11788
+ if (/^https?:\/\//i.test(trimmed)) {
11789
+ return new URL(trimmed).pathname || "/";
11790
+ }
11791
+ } catch {
11792
+ }
11793
+ const withSlash = trimmed.startsWith("/") ? trimmed : `/${trimmed}`;
11794
+ if (withSlash.length > 1 && withSlash.endsWith("/")) return withSlash.slice(0, -1);
11795
+ return withSlash;
11796
+ }
11797
+ function findPageByPath(pages, path) {
11798
+ const normalized = normalizePath(path);
11799
+ return pages.find((p) => normalizePath(p.path) === normalized);
11800
+ }
11801
+ function inferPageTitleFromPath(path) {
11802
+ const normalized = normalizePath(path);
11803
+ if (normalized === "/") return "Home";
11804
+ const segment = normalized.split("/").filter(Boolean).pop() ?? "";
11805
+ if (!segment) return "Home";
11806
+ return segment.split("-").filter(Boolean).map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join(" ");
11807
+ }
11808
+ function isInternalPath(route) {
11809
+ const trimmed = route.trim();
11810
+ if (!trimmed) return false;
11811
+ if (/^https?:\/\//i.test(trimmed)) return false;
11812
+ if (trimmed.startsWith("/")) return true;
11813
+ if (trimmed.includes(" ") || trimmed.includes(".")) return false;
11814
+ return true;
11815
+ }
11816
+ function resolvePage(route, pages) {
11817
+ const found = findPageByPath(pages, route);
11818
+ if (found) return { path: found.path, title: found.title };
11819
+ return { path: normalizePath(route), title: inferPageTitleFromPath(route) };
11820
+ }
11821
+ function getSectionsForPath(sectionsByPath, pageRoute) {
11822
+ if (!sectionsByPath) return [];
11823
+ const normalized = normalizePath(pageRoute);
11824
+ return sectionsByPath[normalized] ?? sectionsByPath[pageRoute] ?? [];
11825
+ }
11826
+ function buildTarget(page, section) {
11827
+ if (section) return `${page.path}#${section.id}`;
11828
+ return page.path;
11829
+ }
11830
+ function isValidUrl(urlString) {
11831
+ try {
11832
+ const url = new URL(urlString);
11833
+ if (url.protocol !== "http:" && url.protocol !== "https:") return false;
11834
+ const hostname = url.hostname;
11835
+ if (!hostname || hostname.length === 0) return false;
11836
+ if (hostname.includes(" ")) return false;
11837
+ if (hostname === "localhost") return true;
11838
+ if (!hostname.includes(".")) return false;
11839
+ if (!/[a-z]/i.test(hostname)) return false;
11840
+ return true;
11841
+ } catch {
11842
+ return false;
11843
+ }
11844
+ }
11845
+ function normalizeUrl(value) {
11846
+ const trimmed = value.trim();
11847
+ if (trimmed.startsWith("http://") || trimmed.startsWith("https://")) return trimmed;
11848
+ return `https://${trimmed}`;
11849
+ }
11850
+ function validateUrlInput(value, pages, allPages) {
11851
+ const trimmed = value.trim();
11852
+ if (!trimmed) return "";
11853
+ const exactPage = allPages.find((p) => p.title.toLowerCase() === trimmed.toLowerCase());
11854
+ if (exactPage) return "";
11855
+ const hasPrefixMatch = pages.some((p) => p.title.toLowerCase().startsWith(trimmed.toLowerCase()));
11856
+ if (hasPrefixMatch) {
11857
+ return "Select a page from the list or enter a valid URL";
11858
+ }
11859
+ const hasScheme = /^https?:\/\//i.test(trimmed);
11860
+ if (!hasScheme && !trimmed.includes(".")) {
11861
+ return "Please enter a valid URL (e.g., https://example.com)";
11862
+ }
11863
+ if (!isValidUrl(normalizeUrl(trimmed))) {
11864
+ return "Please enter a valid URL (e.g., https://example.com)";
11865
+ }
11866
+ return "";
11867
+ }
11868
+ function getEditModeInitialState(target, pages, sections) {
11869
+ const empty = {
11870
+ searchValue: "",
11871
+ selectedPage: null,
11872
+ selectedSection: null,
11873
+ step: "input"
11874
+ };
11875
+ if (!target) return empty;
11876
+ if (isValidUrl(normalizeUrl(target))) {
11877
+ return { searchValue: target, selectedPage: null, selectedSection: null, step: "input" };
11878
+ }
11879
+ const { pageRoute, sectionId } = parseTarget(target);
11880
+ if (sectionId) {
11881
+ if (isInternalPath(pageRoute)) {
11882
+ const page = resolvePage(pageRoute, pages);
11883
+ const sec = sections.find((s) => s.id === sectionId);
11884
+ return {
11885
+ searchValue: page.title,
11886
+ selectedPage: page,
11887
+ selectedSection: sec ? { id: sec.id, label: sec.label } : null,
11888
+ step: "input"
11889
+ };
11890
+ }
11891
+ return { searchValue: pageRoute || target, selectedPage: null, selectedSection: null, step: "input" };
11892
+ }
11893
+ if (isInternalPath(pageRoute)) {
11894
+ const page = resolvePage(pageRoute, pages);
11895
+ return {
11896
+ searchValue: page.title,
11897
+ selectedPage: page,
11898
+ selectedSection: null,
11899
+ step: "input"
11900
+ };
11901
+ }
11902
+ return { searchValue: target, selectedPage: null, selectedSection: null, step: "input" };
11903
+ }
11904
+ function filterAvailablePages(pages, existingTargets) {
11905
+ const taken = new Set(existingTargets);
11906
+ return pages.filter((p) => !taken.has(p.path));
11907
+ }
11908
+
11909
+ // src/lib/collection-item-click.ts
11910
+ function indexCollectionItems(pages) {
11911
+ const index = /* @__PURE__ */ new Map();
11912
+ for (const page of pages) {
11913
+ if (page.kind !== "COLLECTION_ITEM" || !page.collection || !page.path) continue;
11914
+ index.set(normalizePath(page.path), { path: page.path, collection: page.collection });
11915
+ }
11916
+ return index;
11917
+ }
11918
+ function pathOfHref(href) {
11919
+ const trimmed = href.trim();
11920
+ if (!trimmed || trimmed.startsWith("#")) return null;
11921
+ if (/^[a-z][a-z0-9+.-]*:/i.test(trimmed) && !/^https?:/i.test(trimmed)) return null;
11922
+ const withoutHash = trimmed.split("#")[0];
11923
+ const withoutQuery = withoutHash.split("?")[0];
11924
+ if (!withoutQuery) return null;
11925
+ return normalizePath(withoutQuery);
11926
+ }
11927
+ function matchCollectionItemClick(target, index) {
11928
+ if (!target || index.size === 0) return null;
11929
+ const text = target.closest('[data-ohw-editable="text"], [data-ohw-editable="plain"]');
11930
+ if (!text) return null;
11931
+ const anchor = text.closest("a");
11932
+ if (!anchor || anchor.closest(CTA_BUTTON_SELECTOR)) return null;
11933
+ const href = anchor.getAttribute("href");
11934
+ if (!href) return null;
11935
+ const path = pathOfHref(href);
11936
+ if (!path) return null;
11937
+ return index.get(path) ?? null;
11938
+ }
11939
+
11698
11940
  // src/lib/sticky-search-params.ts
11699
11941
  var STICKY_EXACT = /* @__PURE__ */ new Set([
11700
11942
  // Site identity. Required in local dev and in the canvas editor's iframe, where there is no
@@ -17044,7 +17286,7 @@ function apiBase() {
17044
17286
  function bridgeVersion() {
17045
17287
  try {
17046
17288
  if (true) {
17047
- return "0.1.155";
17289
+ return "0.1.157-next.420";
17048
17290
  }
17049
17291
  } catch {
17050
17292
  }
@@ -17058,7 +17300,7 @@ function stampBridgeVersionOnDocument() {
17058
17300
  return;
17059
17301
  }
17060
17302
  try {
17061
- const v = true ? "0.1.155" : "";
17303
+ const v = true ? "0.1.157-next.420" : "";
17062
17304
  if (v) {
17063
17305
  document.documentElement.setAttribute("data-ohw-bridge-version", v);
17064
17306
  window.__OHW_BRIDGE_VERSION__ = v;
@@ -17066,6 +17308,7 @@ function stampBridgeVersionOnDocument() {
17066
17308
  } catch {
17067
17309
  }
17068
17310
  }
17311
+ var TRANSIENT_CONTENT_FETCH = /^(Failed to fetch|Load failed|NetworkError when attempting to fetch resource\.?|fetch failed|The user aborted a request\.?|The operation was aborted\.?)$/i;
17069
17312
  var RECOVERABLE_PAGE_ERROR = /Minified React error #(?:418|419|421|422|423|425)\b|Hydration failed because the initial UI does not match|Text content does not match server-rendered HTML|There was an error while hydrating|did not match\. Server:/i;
17070
17313
  function isVercelPreviewHost() {
17071
17314
  if (typeof location === "undefined") {
@@ -17083,6 +17326,9 @@ function reportOutageBeacon(args) {
17083
17326
  if (args.reason === "page_error" && args.detail && RECOVERABLE_PAGE_ERROR.test(args.detail)) {
17084
17327
  return;
17085
17328
  }
17329
+ if (args.reason === "content_fetch_failed" && args.detail && TRANSIENT_CONTENT_FETCH.test(args.detail.trim())) {
17330
+ return;
17331
+ }
17086
17332
  const key = `${args.subdomain}|${args.reason}`;
17087
17333
  const now = Date.now();
17088
17334
  const prev = lastSent.get(key) || 0;
@@ -17732,6 +17978,7 @@ function MediaOverlay({
17732
17978
  (prev) => prev && prev.fullWidth === width && prev.height === height ? prev : { fullWidth: width, height }
17733
17979
  );
17734
17980
  }, [isVideo]);
17981
+ const replaceLabel = isVideo ? "Replace video" : hover.elementType === "bg-image" ? "Replace background" : "Replace image";
17735
17982
  const replaceMode = buttonSize ? replaceButtonMode({ width: rect.width, height: rect.height }, buttonSize) : "none";
17736
17983
  const box = {
17737
17984
  position: "fixed",
@@ -17830,11 +18077,10 @@ function MediaOverlay({
17830
18077
  className: "flex items-center justify-center cursor-pointer",
17831
18078
  style: {
17832
18079
  ...box,
17833
- // When text overlays this media, let clicks fall THROUGH to the text underneath. In the
17834
- // parent this needed a whole `ow:click-at` round-trip to re-hit-test inside the iframe;
17835
- // in-document, pointer-events does it natively. The button below opts back in, so
17836
- // Replace still works.
17837
- pointerEvents: hover.hasTextOverlap || cueOnly ? "none" : "auto",
18080
+ // Visual only. `pointer-events: auto` on this full-bleed rect covered the navbar
18081
+ // (hero bg includes the absolute TopNav) and swallowed CTA press-drag. Text overlay
18082
+ // and cueOnly also need clicks to fall through — Replace / video controls opt back in.
18083
+ pointerEvents: "none",
17838
18084
  // Selected: a firm component ring with no wash, so the image reads as chosen rather
17839
18085
  // than hovered. Hover keeps the existing tinted preview.
17840
18086
  boxShadow: selected ? "inset 0 0 0 2px var(--color-bridge-primary)" : "inset 0 0 0 1.5px var(--color-bridge-primary)",
@@ -17861,17 +18107,17 @@ function MediaOverlay({
17861
18107
  },
17862
18108
  children: [
17863
18109
  isVideo ? /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_lucide_react5.Film, { size: 14 }) : /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_lucide_react5.ImageIcon, { size: 14 }),
17864
- isVideo ? "Replace video" : "Replace image"
18110
+ replaceLabel
17865
18111
  ]
17866
18112
  }
17867
18113
  ),
17868
- replaceMode === "none" || cueOnly ? null : /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(
18114
+ showChrome && replaceMode !== "none" && /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(
17869
18115
  Button,
17870
18116
  {
17871
18117
  "data-ohw-media-overlay": "",
17872
18118
  variant: "outline",
17873
18119
  size: "sm",
17874
- "aria-label": isVideo ? "Replace video" : "Replace image",
18120
+ "aria-label": replaceLabel,
17875
18121
  className: "gap-1.5 cursor-pointer hover:bg-background",
17876
18122
  style: {
17877
18123
  ...OVERLAY_BUTTON_STYLE,
@@ -17894,7 +18140,7 @@ function MediaOverlay({
17894
18140
  },
17895
18141
  children: [
17896
18142
  isVideo ? /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_lucide_react5.Film, { size: 14 }) : /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_lucide_react5.ImageIcon, { size: 14 }),
17897
- replaceMode === "full" ? isVideo ? "Replace video" : hover.elementType === "bg-image" ? "Replace background" : "Replace image" : null
18143
+ replaceMode === "full" ? replaceLabel : null
17898
18144
  ]
17899
18145
  }
17900
18146
  )
@@ -18404,7 +18650,7 @@ function AiSectionOverlay({
18404
18650
 
18405
18651
  // src/ui/ai-section/AiMediaStripOverlay.tsx
18406
18652
  var import_react10 = require("react");
18407
- var import_lucide_react8 = require("lucide-react");
18653
+ var import_lucide_react9 = require("lucide-react");
18408
18654
 
18409
18655
  // src/lib/ai-media-strip-tree.ts
18410
18656
  var MEDIA_STRIP_ITEM_MIN = 1;
@@ -18518,14 +18764,6 @@ function setMediaStripItemAsset(tree, index, assetId) {
18518
18764
  return true;
18519
18765
  });
18520
18766
  }
18521
- function setMediaStripItemAlt(tree, index, alt) {
18522
- return withMediaStrip(tree, (block) => {
18523
- const items = block.items;
18524
- if (index < 0 || index >= items.length) return false;
18525
- items[index] = { ...items[index], alt };
18526
- return true;
18527
- });
18528
- }
18529
18767
  function setMediaStripItemLink(tree, index, link) {
18530
18768
  return withMediaStrip(tree, (block) => {
18531
18769
  const items = block.items;
@@ -18543,11 +18781,117 @@ function setMediaStripItemLink(tree, index, link) {
18543
18781
  });
18544
18782
  }
18545
18783
 
18546
- // src/ui/ai-section/AiMediaStripOverlay.tsx
18784
+ // src/ui/dialog.tsx
18785
+ var React9 = __toESM(require("react"), 1);
18786
+ var import_radix_ui6 = require("radix-ui");
18787
+ var import_lucide_react8 = require("lucide-react");
18547
18788
  var import_jsx_runtime20 = require("react/jsx-runtime");
18548
- var PRIMARY3 = "#0885FE";
18549
- var OHW_BLUE = `var(--ohw-primary, ${PRIMARY3})`;
18550
- var OHW_BLUE_DARK = `color-mix(in srgb, var(--ohw-primary, ${PRIMARY3}) 60%, #000000)`;
18789
+ function Dialog2({
18790
+ ...props
18791
+ }) {
18792
+ return /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(import_radix_ui6.Dialog.Root, { "data-slot": "dialog", ...props });
18793
+ }
18794
+ function DialogPortal({
18795
+ ...props
18796
+ }) {
18797
+ return /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(import_radix_ui6.Dialog.Portal, { ...props });
18798
+ }
18799
+ function DialogOverlay({
18800
+ className,
18801
+ ...props
18802
+ }) {
18803
+ return /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
18804
+ import_radix_ui6.Dialog.Overlay,
18805
+ {
18806
+ "data-slot": "dialog-overlay",
18807
+ "data-ohw-link-modal-root": "",
18808
+ className: cn("fixed inset-0 z-[2147483646] bg-black/50", className),
18809
+ ...props
18810
+ }
18811
+ );
18812
+ }
18813
+ var DialogContent = React9.forwardRef(
18814
+ ({ className, children, showCloseButton = true, container, ...props }, ref) => {
18815
+ const positionMode = container ? "absolute" : "fixed";
18816
+ return /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)(DialogPortal, { container: container ?? void 0, children: [
18817
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(DialogOverlay, { className: cn(positionMode, "inset-0") }),
18818
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)(
18819
+ import_radix_ui6.Dialog.Content,
18820
+ {
18821
+ ref,
18822
+ "data-slot": "dialog-content",
18823
+ className: cn(
18824
+ positionMode,
18825
+ "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",
18826
+ "rounded-xl border border-border bg-background font-sans shadow-lg outline-none",
18827
+ container ? "max-h-[calc(100%-32px)]" : "max-h-[calc(100vh-32px)]",
18828
+ className
18829
+ ),
18830
+ onMouseDown: (e) => e.stopPropagation(),
18831
+ ...props,
18832
+ children: [
18833
+ children,
18834
+ showCloseButton ? /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
18835
+ import_radix_ui6.Dialog.Close,
18836
+ {
18837
+ type: "button",
18838
+ className: "absolute right-[9px] top-[9px] rounded-sm p-1.5 text-foreground hover:bg-muted/50",
18839
+ "aria-label": "Close",
18840
+ children: /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(import_lucide_react8.X, { size: 16, "aria-hidden": true })
18841
+ }
18842
+ ) : null
18843
+ ]
18844
+ }
18845
+ )
18846
+ ] });
18847
+ }
18848
+ );
18849
+ DialogContent.displayName = import_radix_ui6.Dialog.Content.displayName;
18850
+ function DialogHeader({
18851
+ className,
18852
+ ...props
18853
+ }) {
18854
+ return /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("div", { className: cn("flex flex-col gap-1.5", className), ...props });
18855
+ }
18856
+ function DialogFooter({
18857
+ className,
18858
+ ...props
18859
+ }) {
18860
+ return /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
18861
+ "div",
18862
+ {
18863
+ className: cn("flex items-center justify-end gap-2", className),
18864
+ ...props
18865
+ }
18866
+ );
18867
+ }
18868
+ var DialogTitle = React9.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
18869
+ import_radix_ui6.Dialog.Title,
18870
+ {
18871
+ ref,
18872
+ className: cn(
18873
+ "text-lg font-semibold leading-none tracking-tight text-card-foreground",
18874
+ className
18875
+ ),
18876
+ ...props
18877
+ }
18878
+ ));
18879
+ DialogTitle.displayName = import_radix_ui6.Dialog.Title.displayName;
18880
+ var DialogDescription = React9.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
18881
+ import_radix_ui6.Dialog.Description,
18882
+ {
18883
+ ref,
18884
+ className: cn("text-sm text-muted-foreground", className),
18885
+ ...props
18886
+ }
18887
+ ));
18888
+ DialogDescription.displayName = import_radix_ui6.Dialog.Description.displayName;
18889
+ var DialogClose = import_radix_ui6.Dialog.Close;
18890
+
18891
+ // src/ui/ai-section/AiMediaStripOverlay.tsx
18892
+ var import_jsx_runtime21 = require("react/jsx-runtime");
18893
+ var PRIMARY3 = "var(--ohw-primary, #0885FE)";
18894
+ 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]";
18551
18895
  function findMediaStripItemElement(sectionId, index) {
18552
18896
  const sectionEl = findByInstanceId(sectionId);
18553
18897
  if (!sectionEl) return null;
@@ -18557,14 +18901,35 @@ function findMediaStripItemElement(sectionId, index) {
18557
18901
  }
18558
18902
  return null;
18559
18903
  }
18560
- function readRect2(ref) {
18561
- const el = ref && findMediaStripItemElement(ref.sectionId, ref.index);
18904
+ function itemRefFromTarget(target) {
18905
+ if (!(target instanceof Element)) return null;
18906
+ if (target.closest(CHROME_SELECTOR)) return null;
18907
+ const itemEl = target.closest("[data-ohw-media-strip-item]");
18908
+ if (!itemEl) return null;
18909
+ const sectionEl = itemEl.closest("[data-ohw-section]");
18910
+ const unit = sectionEl ? movableUnit(sectionEl) : null;
18911
+ const sectionId = unit ? instanceIdOf(unit) || null : null;
18912
+ const index = Number(itemEl.dataset.ohwMediaStripIndex);
18913
+ if (!sectionId || Number.isNaN(index)) return null;
18914
+ return { sectionId, index };
18915
+ }
18916
+ function sameRef(a, b) {
18917
+ return a?.sectionId === b?.sectionId && a?.index === b?.index;
18918
+ }
18919
+ function rectOf(el) {
18562
18920
  if (!el) return null;
18563
18921
  const r2 = el.getBoundingClientRect();
18564
18922
  if (r2.width <= 0 || r2.height <= 0) return null;
18565
18923
  return { top: r2.top, left: r2.left, width: r2.width, height: r2.height };
18566
18924
  }
18567
- function useLiveItemRect(ref) {
18925
+ function readRect2(ref) {
18926
+ return rectOf(ref && findMediaStripItemElement(ref.sectionId, ref.index));
18927
+ }
18928
+ function readStripRect(ref) {
18929
+ const item = ref && findMediaStripItemElement(ref.sectionId, ref.index);
18930
+ return rectOf(item?.closest("[data-ohw-media-strip]") ?? item);
18931
+ }
18932
+ function useLiveItemRect(ref, strip = false) {
18568
18933
  const [rect, setRect] = (0, import_react10.useState)(null);
18569
18934
  (0, import_react10.useEffect)(() => {
18570
18935
  if (!ref) {
@@ -18572,7 +18937,7 @@ function useLiveItemRect(ref) {
18572
18937
  return;
18573
18938
  }
18574
18939
  const update = () => {
18575
- const next = readRect2(ref);
18940
+ const next = strip ? readStripRect(ref) : readRect2(ref);
18576
18941
  setRect(
18577
18942
  (prev) => prev && next && prev.top === next.top && prev.left === next.left && prev.width === next.width && prev.height === next.height ? prev : next
18578
18943
  );
@@ -18587,9 +18952,49 @@ function useLiveItemRect(ref) {
18587
18952
  window.removeEventListener("resize", update);
18588
18953
  clearInterval(interval);
18589
18954
  };
18590
- }, [ref?.sectionId, ref?.index]);
18955
+ }, [ref?.sectionId, ref?.index, strip]);
18591
18956
  return rect;
18592
18957
  }
18958
+ var TOOLBAR_STRIP_GAP = 52;
18959
+ var TOOLBAR_STRIP_GAP_MOBILE = 42;
18960
+ var TOOLBAR_EDGE_MARGIN2 = 4;
18961
+ function StripToolbarSlot({ stripRect, children }) {
18962
+ const slotRef = (0, import_react10.useRef)(null);
18963
+ const [shift, setShift] = (0, import_react10.useState)(0);
18964
+ const centerX = stripRect.left + stripRect.width / 2;
18965
+ (0, import_react10.useLayoutEffect)(() => {
18966
+ const slot = slotRef.current;
18967
+ if (!slot) return;
18968
+ const clamp = () => {
18969
+ const w = slot.offsetWidth;
18970
+ if (w <= 0) return;
18971
+ const half = w / 2;
18972
+ const clamped = Math.max(TOOLBAR_EDGE_MARGIN2 + half, Math.min(centerX, window.innerWidth - TOOLBAR_EDGE_MARGIN2 - half));
18973
+ setShift((prev) => Math.abs(prev - (clamped - centerX)) < 0.5 ? prev : clamped - centerX);
18974
+ };
18975
+ clamp();
18976
+ const ro = new ResizeObserver(clamp);
18977
+ ro.observe(slot);
18978
+ return () => ro.disconnect();
18979
+ }, [centerX, children]);
18980
+ const mobile = window.innerWidth <= 480;
18981
+ return /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
18982
+ "div",
18983
+ {
18984
+ ref: slotRef,
18985
+ "data-ohw-bridge": "",
18986
+ "data-ohw-item-interaction": "",
18987
+ className: "pointer-events-auto fixed",
18988
+ style: {
18989
+ top: stripRect.top - (mobile ? TOOLBAR_STRIP_GAP_MOBILE : TOOLBAR_STRIP_GAP),
18990
+ left: centerX + shift,
18991
+ transform: "translate(-50%, -100%)",
18992
+ zIndex: 2147483645
18993
+ },
18994
+ children
18995
+ }
18996
+ );
18997
+ }
18593
18998
  function requestValidateTree(postToParent2, tree) {
18594
18999
  return new Promise((resolve) => {
18595
19000
  const id = `ai-validate-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
@@ -18626,116 +19031,72 @@ function requestAssetPick(postToParent2) {
18626
19031
  postToParent2({ type: "ow:asset-pick", id });
18627
19032
  });
18628
19033
  }
18629
- function Pill({
19034
+ function ToolbarAction({
18630
19035
  label,
18631
19036
  onClick,
18632
- disabled,
18633
- active
19037
+ disabled = false,
19038
+ active = false,
19039
+ children
18634
19040
  }) {
18635
- return /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
18636
- "button",
18637
- {
18638
- type: "button",
18639
- onClick,
18640
- disabled,
18641
- style: {
18642
- height: 28,
18643
- padding: "0 10px",
18644
- display: "inline-flex",
18645
- alignItems: "center",
18646
- gap: 4,
18647
- borderRadius: 999,
18648
- border: "none",
18649
- cursor: disabled ? "default" : "pointer",
18650
- background: active ? OHW_BLUE_DARK : "#ffffff",
18651
- color: active ? "#ffffff" : OHW_BLUE_DARK,
18652
- opacity: disabled ? 0.4 : 1,
18653
- fontSize: 12,
18654
- lineHeight: "16px",
18655
- fontFamily: "Figtree, ui-sans-serif, system-ui, sans-serif",
18656
- whiteSpace: "nowrap",
18657
- flex: "0 0 auto"
18658
- },
18659
- children: label
18660
- }
18661
- );
19041
+ const button = /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(CustomToolbarButton, { type: "button", "aria-label": label, disabled, active, onClick, className: "size-8", children });
19042
+ return /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(Tooltip, { children: [
19043
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(TooltipTrigger, { asChild: true, children: disabled ? /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("span", { className: "inline-flex", children: button }) : button }),
19044
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(TooltipContent, { side: "bottom", sideOffset: 8, children: label })
19045
+ ] });
18662
19046
  }
18663
- function AltInput({ value, onCommit }) {
18664
- const [draft, setDraft] = (0, import_react10.useState)(value);
18665
- (0, import_react10.useEffect)(() => setDraft(value), [value]);
18666
- return /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)(
18667
- "div",
18668
- {
18669
- style: {
18670
- display: "flex",
18671
- alignItems: "center",
18672
- gap: 6,
18673
- height: 28,
18674
- padding: "0 12px",
18675
- borderRadius: 999,
18676
- background: "#ffffff",
18677
- width: "100%",
18678
- maxWidth: 260,
18679
- boxSizing: "border-box"
18680
- },
18681
- children: [
18682
- /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
18683
- "span",
18684
- {
18685
- style: {
18686
- flex: "0 0 auto",
18687
- color: OHW_BLUE_DARK,
18688
- fontSize: 10,
18689
- fontWeight: 700,
18690
- textTransform: "uppercase",
18691
- letterSpacing: 0.4
18692
- },
18693
- children: "Alt"
18694
- }
18695
- ),
18696
- /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
18697
- "input",
19047
+ function ToolbarSeparator() {
19048
+ return /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("span", { "aria-hidden": true, className: "mx-0.5 block h-6 w-px shrink-0 bg-border" });
19049
+ }
19050
+ function OptionMenu({
19051
+ label,
19052
+ value,
19053
+ options,
19054
+ onChange
19055
+ }) {
19056
+ const current = options.find((o) => o.value === value) ?? options[0];
19057
+ return /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("div", { className: "flex items-center gap-1.5 pl-1.5", children: [
19058
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("span", { className: "text-xs text-muted-foreground", children: label }),
19059
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(DropdownMenu, { modal: false, children: [
19060
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(DropdownMenuTrigger, { asChild: true, children: /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(
19061
+ "button",
19062
+ {
19063
+ type: "button",
19064
+ "aria-label": label,
19065
+ 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",
19066
+ children: [
19067
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("span", { className: "flex-1 text-left", children: current.label }),
19068
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(import_lucide_react9.ChevronDown, { size: 16, style: { pointerEvents: "none" } })
19069
+ ]
19070
+ }
19071
+ ) }),
19072
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(DropdownMenuContent, { align: "end", sideOffset: 8, className: "w-[132px] min-w-[132px] p-1", children: options.map((option) => {
19073
+ const selected = option.value === value;
19074
+ return /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(
19075
+ DropdownMenuItem,
18698
19076
  {
18699
- type: "text",
18700
- value: draft,
18701
- onChange: (e) => setDraft(e.target.value),
18702
- onBlur: () => {
18703
- if (draft !== value) onCommit(draft);
18704
- },
18705
- onKeyDown: (e) => {
18706
- if (e.key === "Enter") e.currentTarget.blur();
18707
- if (e.key === "Escape") {
18708
- setDraft(value);
18709
- e.currentTarget.blur();
18710
- }
18711
- },
18712
- placeholder: "Describe this image",
18713
- "aria-label": "Image alt text",
18714
- style: {
18715
- flex: 1,
18716
- minWidth: 0,
18717
- border: "none",
18718
- outline: "none",
18719
- background: "transparent",
18720
- fontSize: 12,
18721
- lineHeight: "16px",
18722
- fontFamily: "Figtree, ui-sans-serif, system-ui, sans-serif",
18723
- color: "#1f2937"
18724
- }
18725
- }
18726
- )
18727
- ]
18728
- }
18729
- );
19077
+ role: "menuitemradio",
19078
+ "aria-checked": selected,
19079
+ onSelect: () => onChange(option.value),
19080
+ className: cn("grid grid-cols-[1fr_16px] gap-2 py-2 pl-2 pr-2 text-[13px]", selected && "bg-muted"),
19081
+ children: [
19082
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("span", { children: option.label }),
19083
+ selected && /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(import_lucide_react9.Check, { size: 16, className: "text-bridge-primary" })
19084
+ ]
19085
+ },
19086
+ option.value
19087
+ );
19088
+ }) })
19089
+ ] })
19090
+ ] });
18730
19091
  }
18731
19092
  var VARIANTS = [
18732
19093
  { value: "logos", label: "Logos" },
18733
19094
  { value: "reel", label: "Reel" }
18734
19095
  ];
18735
19096
  var ALIGNS = [
18736
- { value: "left", label: "Left" },
18737
- { value: "center", label: "Center" },
18738
- { value: "right", label: "Right" }
19097
+ { value: "left", label: "Align left", Icon: import_lucide_react9.AlignLeft },
19098
+ { value: "center", label: "Align center", Icon: import_lucide_react9.AlignCenter },
19099
+ { value: "right", label: "Align right", Icon: import_lucide_react9.AlignRight }
18739
19100
  ];
18740
19101
  var HOVERS = [
18741
19102
  { value: "none", label: "None" },
@@ -18743,104 +19104,104 @@ var HOVERS = [
18743
19104
  { value: "zoom", label: "Zoom" },
18744
19105
  { value: "fade", label: "Fade" }
18745
19106
  ];
18746
- var TREATMENTS = [
18747
- { value: "color", label: "Color" },
18748
- { value: "grayscale", label: "Grayscale" }
18749
- ];
18750
19107
  var HTTP_URL_RE = /^https?:\/\/\S+$/i;
18751
- function LinkInput({ value, onCommit }) {
18752
- const [draft, setDraft] = (0, import_react10.useState)(value);
19108
+ function LinkModal({
19109
+ href,
19110
+ linkTarget,
19111
+ onClose,
19112
+ onSave
19113
+ }) {
19114
+ const [draft, setDraft] = (0, import_react10.useState)(href);
19115
+ const [newTab, setNewTab] = (0, import_react10.useState)(linkTarget !== "same-tab");
18753
19116
  const [invalid, setInvalid] = (0, import_react10.useState)(false);
18754
- (0, import_react10.useEffect)(() => {
18755
- setDraft(value);
18756
- setInvalid(false);
18757
- }, [value]);
18758
- const commit = () => {
19117
+ const save = () => {
18759
19118
  const next = draft.trim();
18760
- if (next === value) return setInvalid(false);
18761
- if (next && !HTTP_URL_RE.test(next)) return setInvalid(true);
18762
- setInvalid(false);
18763
- onCommit(next);
18764
- };
18765
- return /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)(
18766
- "div",
18767
- {
18768
- style: {
18769
- display: "flex",
18770
- alignItems: "center",
18771
- gap: 6,
18772
- height: 28,
18773
- padding: "0 12px",
18774
- borderRadius: 999,
18775
- background: "#ffffff",
18776
- width: "100%",
18777
- maxWidth: 260,
18778
- boxSizing: "border-box",
18779
- boxShadow: invalid ? "inset 0 0 0 1.5px #EF4444" : "none"
18780
- },
18781
- children: [
18782
- /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
18783
- "span",
18784
- {
18785
- style: {
18786
- flex: "0 0 auto",
18787
- color: OHW_BLUE_DARK,
18788
- fontSize: 10,
18789
- fontWeight: 700,
18790
- textTransform: "uppercase",
18791
- letterSpacing: 0.4
18792
- },
18793
- children: "Link"
18794
- }
18795
- ),
18796
- /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
18797
- "input",
18798
- {
18799
- type: "text",
18800
- inputMode: "url",
18801
- value: draft,
18802
- onChange: (e) => setDraft(e.target.value),
18803
- onBlur: commit,
18804
- onKeyDown: (e) => {
18805
- if (e.key === "Enter") e.currentTarget.blur();
18806
- if (e.key === "Escape") {
18807
- setDraft(value);
18808
- setInvalid(false);
18809
- e.currentTarget.blur();
18810
- }
18811
- },
18812
- placeholder: "https://example.com",
18813
- "aria-label": "Image link URL",
18814
- "aria-invalid": invalid,
18815
- style: {
18816
- flex: 1,
18817
- minWidth: 0,
18818
- border: "none",
18819
- outline: "none",
18820
- background: "transparent",
18821
- fontSize: 12,
18822
- lineHeight: "16px",
18823
- fontFamily: "Figtree, ui-sans-serif, system-ui, sans-serif",
18824
- color: "#1f2937"
18825
- }
18826
- }
18827
- )
18828
- ]
19119
+ if (next && !HTTP_URL_RE.test(next)) {
19120
+ setInvalid(true);
19121
+ return;
18829
19122
  }
18830
- );
19123
+ onSave(next, newTab ? "new-tab" : "same-tab");
19124
+ onClose();
19125
+ };
19126
+ return /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(Dialog2, { open: true, onOpenChange: (open) => !open && onClose(), children: /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(DialogContent, { "data-ohw-link-modal-root": "", className: "max-w-[400px] gap-4 p-5", children: [
19127
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(DialogHeader, { children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(DialogTitle, { className: "text-base", children: "Image link" }) }),
19128
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("div", { className: "flex flex-col gap-3", children: [
19129
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
19130
+ "input",
19131
+ {
19132
+ autoFocus: true,
19133
+ type: "text",
19134
+ inputMode: "url",
19135
+ value: draft,
19136
+ onChange: (e) => {
19137
+ setDraft(e.target.value);
19138
+ setInvalid(false);
19139
+ },
19140
+ onKeyDown: (e) => {
19141
+ if (e.key === "Enter") save();
19142
+ },
19143
+ placeholder: "https://example.com",
19144
+ "aria-label": "Image link URL",
19145
+ "aria-invalid": invalid,
19146
+ className: cn(
19147
+ "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",
19148
+ invalid && "border-destructive focus:border-destructive"
19149
+ )
19150
+ }
19151
+ ),
19152
+ invalid && /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("p", { className: "text-xs text-destructive", children: "Enter a full URL starting with http:// or https://" }),
19153
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("label", { className: "flex items-center gap-2 text-[13px] text-foreground", children: [
19154
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("input", { type: "checkbox", checked: newTab, onChange: (e) => setNewTab(e.target.checked) }),
19155
+ "Open in new tab"
19156
+ ] })
19157
+ ] }),
19158
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(DialogFooter, { children: [
19159
+ href && /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
19160
+ "button",
19161
+ {
19162
+ type: "button",
19163
+ onClick: () => {
19164
+ onSave("", "new-tab");
19165
+ onClose();
19166
+ },
19167
+ className: "mr-auto rounded-[6px] px-3 py-2 text-[13px] font-medium text-destructive transition-colors hover:bg-muted",
19168
+ children: "Remove link"
19169
+ }
19170
+ ),
19171
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
19172
+ "button",
19173
+ {
19174
+ type: "button",
19175
+ onClick: onClose,
19176
+ className: "rounded-[6px] px-3 py-2 text-[13px] font-medium text-foreground transition-colors hover:bg-muted",
19177
+ children: "Cancel"
19178
+ }
19179
+ ),
19180
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
19181
+ "button",
19182
+ {
19183
+ type: "button",
19184
+ onClick: save,
19185
+ className: "rounded-[6px] bg-bridge-primary px-3 py-2 text-[13px] font-medium text-primary-foreground transition-opacity hover:opacity-90",
19186
+ children: "Save"
19187
+ }
19188
+ )
19189
+ ] })
19190
+ ] }) });
18831
19191
  }
18832
19192
  function MediaStripToolbar({
18833
19193
  entry,
18834
19194
  index,
18835
19195
  pending,
18836
19196
  error,
19197
+ itemEditOn,
19198
+ onToggleItemEdit,
18837
19199
  onSetVariant,
18838
19200
  onSetAlign,
18839
19201
  onSetHover,
18840
19202
  onSetTreatment,
18841
19203
  onResize,
18842
- onSetAlt,
18843
- onSetLink,
19204
+ onOpenLink,
18844
19205
  onAddItem,
18845
19206
  onRemoveItem,
18846
19207
  onChangeImage,
@@ -18849,81 +19210,102 @@ function MediaStripToolbar({
18849
19210
  }) {
18850
19211
  const items = mediaStripItems(entry.tree);
18851
19212
  const itemCount = items.length;
18852
- const currentAlt = items[index]?.alt ?? "";
18853
- const currentHref = items[index]?.href ?? "";
18854
- const currentTarget = items[index]?.linkTarget === "same-tab" ? "same-tab" : "new-tab";
18855
19213
  const slots = findMediaStripSlots(entry.tree);
18856
19214
  const variant = slots?.variant === "reel" ? "reel" : "logos";
18857
19215
  const align = slots?.align === "left" || slots?.align === "right" ? slots.align : "center";
18858
19216
  const hover = slots?.hover === "none" || slots?.hover === "lift" || slots?.hover === "zoom" || slots?.hover === "fade" ? slots.hover : "lift";
18859
19217
  const treatment = slots?.treatment === "color" || slots?.treatment === "grayscale" ? slots.treatment : variant === "logos" ? "grayscale" : "color";
18860
19218
  const sizeValue = variant === "logos" ? typeof slots?.logoMaxHeight === "number" ? slots.logoMaxHeight : 48 : typeof slots?.cardWidth === "number" ? slots.cardWidth : 240;
18861
- const isMobile = window.innerWidth <= 480;
18862
- const rowStyle = isMobile ? {
18863
- display: "flex",
18864
- gap: 6,
18865
- flexWrap: "nowrap",
18866
- justifyContent: "center",
18867
- overflowX: "auto",
18868
- WebkitOverflowScrolling: "touch",
18869
- width: "100%",
18870
- scrollbarWidth: "none"
18871
- } : { display: "flex", gap: 6, flexWrap: "nowrap", justifyContent: "center" };
18872
- return /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)(
19219
+ const mobile = window.innerWidth <= 480;
19220
+ const gatedLabel = (label) => itemEditOn ? label : "Turn on the star to edit this image";
19221
+ return /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(TooltipProvider, { delayDuration: 0, children: /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(
18873
19222
  "div",
18874
19223
  {
18875
19224
  "data-ohw-bridge": "",
18876
19225
  "data-ohw-toolbar": "",
18877
19226
  onMouseDown: (e) => e.stopPropagation(),
18878
- style: {
18879
- display: "flex",
18880
- flexDirection: "column",
18881
- gap: 6,
18882
- alignItems: "center",
18883
- background: OHW_BLUE,
18884
- padding: 8,
18885
- borderRadius: 12,
18886
- boxShadow: "0 8px 24px rgba(0,0,0,0.25)",
18887
- pointerEvents: pending ? "none" : "auto",
18888
- opacity: pending ? 0.6 : 1,
18889
- maxWidth: isMobile ? "calc(100vw - 24px)" : "none"
18890
- },
19227
+ className: cn("flex flex-col items-center gap-1.5 font-sans", pending && "pointer-events-none opacity-60"),
19228
+ style: { maxWidth: mobile ? "calc(100vw - 24px)" : void 0 },
18891
19229
  children: [
18892
- /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("div", { style: rowStyle, children: [
18893
- VARIANTS.map((v) => /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(Pill, { label: v.label, active: variant === v.value, onClick: () => onSetVariant(v.value) }, v.value)),
18894
- ALIGNS.map((a) => /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(Pill, { label: a.label, active: align === a.value, onClick: () => onSetAlign(a.value) }, a.value))
18895
- ] }),
18896
- /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("div", { style: rowStyle, children: [
18897
- HOVERS.map((h) => /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(Pill, { label: h.label, active: hover === h.value, onClick: () => onSetHover(h.value) }, h.value)),
18898
- TREATMENTS.map((t) => /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(Pill, { label: t.label, active: treatment === t.value, onClick: () => onSetTreatment(t.value) }, t.value))
18899
- ] }),
18900
- /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("div", { style: { display: "flex", gap: 6, alignItems: "center" }, children: [
18901
- /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(Pill, { label: "\u2212", onClick: () => onResize(-8) }),
18902
- /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("span", { style: { color: "#ffffff", fontSize: 12, minWidth: 34, textAlign: "center" }, children: [
18903
- sizeValue,
18904
- "px"
18905
- ] }),
18906
- /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(Pill, { label: "+", onClick: () => onResize(8) })
18907
- ] }),
18908
- /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("div", { style: { display: "flex", justifyContent: "center", width: "100%" }, children: /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(AltInput, { value: currentAlt, onCommit: onSetAlt }) }),
18909
- /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("div", { style: { display: "flex", justifyContent: "center", width: "100%" }, children: /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(LinkInput, { value: currentHref, onCommit: (href) => onSetLink(href, currentTarget) }) }),
18910
- currentHref && /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("div", { style: rowStyle, children: [
18911
- /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(Pill, { label: "New tab", active: currentTarget === "new-tab", onClick: () => onSetLink(currentHref, "new-tab") }),
18912
- /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(Pill, { label: "Same tab", active: currentTarget === "same-tab", onClick: () => onSetLink(currentHref, "same-tab") })
18913
- ] }),
18914
- /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("div", { style: rowStyle, children: [
18915
- /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(Pill, { label: "Change Image", onClick: onChangeImage }),
18916
- /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(Pill, { label: "+ Item", onClick: onAddItem }),
18917
- /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(Pill, { label: "\u2212 Item", onClick: onRemoveItem, disabled: itemCount <= 1 })
18918
- ] }),
18919
- /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("div", { style: rowStyle, children: [
18920
- /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(Pill, { label: "\u2190 Move", onClick: onMoveLeft, disabled: index <= 0 }),
18921
- /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(Pill, { label: "\u2192 Move", onClick: onMoveRight, disabled: index >= itemCount - 1 })
18922
- ] }),
18923
- error && /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("div", { style: { color: "#FCA5A5", fontSize: 11, maxWidth: 220, textAlign: "center" }, children: error })
19230
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(
19231
+ "div",
19232
+ {
19233
+ className: cn(
19234
+ "flex items-center gap-1 rounded-[10px] border border-border bg-background p-1 whitespace-nowrap",
19235
+ "shadow-[0px_10px_15px_-3px_rgba(0,0,0,0.1),0px_4px_6px_-4px_rgba(0,0,0,0.1)]",
19236
+ mobile ? "flex-wrap justify-center" : "flex-nowrap"
19237
+ ),
19238
+ children: [
19239
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(Tooltip, { children: [
19240
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
19241
+ CustomToolbarButton,
19242
+ {
19243
+ type: "button",
19244
+ "aria-label": itemEditOn ? "Done editing this image" : "Edit this image",
19245
+ "aria-pressed": itemEditOn,
19246
+ onClick: onToggleItemEdit,
19247
+ className: "group size-8",
19248
+ children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
19249
+ import_lucide_react9.Star,
19250
+ {
19251
+ size: 16,
19252
+ fill: "currentColor",
19253
+ strokeWidth: 0,
19254
+ className: cn(
19255
+ "transition-colors",
19256
+ itemEditOn ? "text-[#f59e0b] group-hover:text-[#d97706]" : "text-[#d6d3d1] group-hover:text-[#fbbf24]"
19257
+ ),
19258
+ style: { pointerEvents: "none" }
19259
+ }
19260
+ )
19261
+ }
19262
+ ) }),
19263
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(TooltipContent, { side: "bottom", sideOffset: 8, children: itemEditOn ? "Done editing this image" : "Edit this image" })
19264
+ ] }),
19265
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(ToolbarSeparator, {}),
19266
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(ToolbarAction, { label: gatedLabel("Change image"), onClick: onChangeImage, disabled: !itemEditOn, children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(import_lucide_react9.Image, { size: 16, style: { pointerEvents: "none" } }) }),
19267
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(ToolbarAction, { label: gatedLabel("Link"), onClick: onOpenLink, disabled: !itemEditOn, children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(import_lucide_react9.Link, { size: 16, style: { pointerEvents: "none" } }) }),
19268
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(ToolbarAction, { label: gatedLabel("Move left"), onClick: onMoveLeft, disabled: !itemEditOn || index <= 0, children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(import_lucide_react9.ArrowLeft, { size: 16, style: { pointerEvents: "none" } }) }),
19269
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
19270
+ ToolbarAction,
19271
+ {
19272
+ label: gatedLabel("Move right"),
19273
+ onClick: onMoveRight,
19274
+ disabled: !itemEditOn || index >= itemCount - 1,
19275
+ children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(import_lucide_react9.ArrowRight, { size: 16, style: { pointerEvents: "none" } })
19276
+ }
19277
+ ),
19278
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(ToolbarAction, { label: gatedLabel("Remove image"), onClick: onRemoveItem, disabled: !itemEditOn || itemCount <= 1, children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(import_lucide_react9.ImageMinus, { size: 16, style: { pointerEvents: "none" } }) }),
19279
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(ToolbarSeparator, {}),
19280
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(OptionMenu, { label: "Layout", value: variant, options: VARIANTS, onChange: onSetVariant }),
19281
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(OptionMenu, { label: "Hover", value: hover, options: HOVERS, onChange: onSetHover }),
19282
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
19283
+ ToolbarAction,
19284
+ {
19285
+ label: treatment === "grayscale" ? "Show in color" : "Show grayscale",
19286
+ active: treatment === "grayscale",
19287
+ onClick: () => onSetTreatment(treatment === "grayscale" ? "color" : "grayscale"),
19288
+ children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(import_lucide_react9.Palette, { size: 16, style: { pointerEvents: "none" } })
19289
+ }
19290
+ ),
19291
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(ToolbarSeparator, {}),
19292
+ ALIGNS.map(({ value, label, Icon }) => /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(ToolbarAction, { label, active: align === value, onClick: () => onSetAlign(value), children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(Icon, { size: 16, style: { pointerEvents: "none" } }) }, value)),
19293
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(ToolbarSeparator, {}),
19294
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(ToolbarAction, { label: "Smaller", onClick: () => onResize(-8), children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(import_lucide_react9.Minus, { size: 16, style: { pointerEvents: "none" } }) }),
19295
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("span", { className: "min-w-[40px] text-center text-xs tabular-nums text-muted-foreground", children: [
19296
+ sizeValue,
19297
+ "px"
19298
+ ] }),
19299
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(ToolbarAction, { label: "Larger", onClick: () => onResize(8), children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(import_lucide_react9.Plus, { size: 16, style: { pointerEvents: "none" } }) }),
19300
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(ToolbarSeparator, {}),
19301
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(ToolbarAction, { label: "Add image", onClick: onAddItem, children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(import_lucide_react9.ImagePlus, { size: 16, style: { pointerEvents: "none" } }) })
19302
+ ]
19303
+ }
19304
+ ),
19305
+ error && /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("div", { className: "max-w-[260px] rounded-md bg-background px-2 py-1 text-center text-[11px] text-destructive shadow-sm", children: error })
18924
19306
  ]
18925
19307
  }
18926
- );
19308
+ ) });
18927
19309
  }
18928
19310
  function findMediaStripSlots(tree) {
18929
19311
  const stack = tree.rows.flatMap((row) => row.blocks);
@@ -18942,31 +19324,23 @@ function AiMediaStripOverlay({
18942
19324
  }) {
18943
19325
  const [hover, setHover] = (0, import_react10.useState)(null);
18944
19326
  const [active, setActive] = (0, import_react10.useState)(null);
19327
+ const [itemEditOn, setItemEditOn] = (0, import_react10.useState)(false);
19328
+ const [linkOpen, setLinkOpen] = (0, import_react10.useState)(false);
18945
19329
  const [pending, setPending] = (0, import_react10.useState)(false);
18946
19330
  const [error, setError] = (0, import_react10.useState)(null);
18947
19331
  const activeRef = (0, import_react10.useRef)(null);
18948
19332
  activeRef.current = active;
19333
+ const linkOpenRef = (0, import_react10.useRef)(false);
19334
+ linkOpenRef.current = linkOpen;
18949
19335
  (0, import_react10.useEffect)(() => {
18950
19336
  const onMove = (e) => {
19337
+ const ref = itemRefFromTarget(e.target);
18951
19338
  const t = e.target;
18952
- if (!(t instanceof HTMLElement)) return;
18953
- 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]")) {
18954
- return;
18955
- }
18956
- const itemEl = t.closest("[data-ohw-media-strip-item]");
18957
- if (!itemEl) {
18958
- setHover(null);
18959
- return;
18960
- }
18961
- const sectionEl = itemEl.closest("[data-ohw-section]");
18962
- const unit = sectionEl ? movableUnit(sectionEl) : null;
18963
- const sectionId = unit ? instanceIdOf(unit) || null : null;
18964
- const index = Number(itemEl.dataset.ohwMediaStripIndex);
18965
- if (!sectionId || Number.isNaN(index)) {
18966
- setHover(null);
18967
- return;
18968
- }
18969
- setHover({ sectionId, index });
19339
+ const overEditable = Boolean(t?.closest?.("[data-ohw-editable]"));
19340
+ setHover((prev) => {
19341
+ const next = ref && !overEditable ? ref : null;
19342
+ return sameRef(prev, next) ? prev : next;
19343
+ });
18970
19344
  };
18971
19345
  const onLeave = () => setHover(null);
18972
19346
  document.addEventListener("mousemove", onMove, { passive: true });
@@ -18978,23 +19352,25 @@ function AiMediaStripOverlay({
18978
19352
  }, []);
18979
19353
  (0, import_react10.useEffect)(() => {
18980
19354
  const onClick = (e) => {
18981
- const current = activeRef.current;
18982
- if (!current) return;
18983
19355
  const t = e.target;
18984
- if (!(t instanceof HTMLElement)) return;
18985
- if (t.closest("[data-ohw-item-interaction]")) return;
18986
- const itemEl = findMediaStripItemElement(current.sectionId, current.index);
18987
- if (itemEl && itemEl.contains(t)) return;
18988
- setActive(null);
19356
+ if (!(t instanceof Element)) return;
19357
+ if (t.closest(CHROME_SELECTOR)) return;
19358
+ const ref = itemRefFromTarget(t);
19359
+ if (sameRef(activeRef.current, ref)) return;
19360
+ if (ref) clearInteractionChromeRef.current();
19361
+ setItemEditOn(false);
18989
19362
  setError(null);
19363
+ setActive(ref);
18990
19364
  };
18991
19365
  document.addEventListener("click", onClick, true);
18992
19366
  return () => document.removeEventListener("click", onClick, true);
18993
- }, []);
19367
+ }, [clearInteractionChromeRef]);
18994
19368
  (0, import_react10.useEffect)(() => {
18995
19369
  const onBlur = () => {
18996
19370
  if (!activeRef.current) return;
18997
19371
  setActive(null);
19372
+ setItemEditOn(false);
19373
+ setLinkOpen(false);
18998
19374
  setError(null);
18999
19375
  };
19000
19376
  window.addEventListener("blur", onBlur);
@@ -19004,7 +19380,10 @@ function AiMediaStripOverlay({
19004
19380
  const onKeyDown = (e) => {
19005
19381
  if (e.key !== "Escape") return;
19006
19382
  if (!activeRef.current) return;
19383
+ if (linkOpenRef.current) return;
19384
+ if (document.querySelector('[data-ohw-more-menu][data-state="open"]')) return;
19007
19385
  setActive(null);
19386
+ setItemEditOn(false);
19008
19387
  setError(null);
19009
19388
  };
19010
19389
  document.addEventListener("keydown", onKeyDown);
@@ -19038,137 +19417,133 @@ function AiMediaStripOverlay({
19038
19417
  tree: nextTree,
19039
19418
  label: entry.label,
19040
19419
  media: extraMedia ? { ...entry.media, ...extraMedia } : entry.media,
19041
- ...entry.brand ? { brand: entry.brand } : {}
19420
+ ...entry.brand ? { brand: entry.brand } : {},
19421
+ // The user is already looking at the strip they just edited — recentering the viewport
19422
+ // on every edit reads as the section jumping around.
19423
+ skipScrollIntoView: true
19042
19424
  });
19043
19425
  return true;
19044
19426
  },
19045
19427
  [findEntry, postToParent2, applyTreeLocalRef]
19046
19428
  );
19047
19429
  const activeEntry = active ? findEntry(active.sectionId) : null;
19048
- const hoverHandleRect = useLiveItemRect(!active && hover ? hover : null);
19049
19430
  const activeItemRect = useLiveItemRect(active);
19050
- const TOOLBAR_APPROX_H = 170;
19051
- const toolbarState = activeItemRect && activeItemRect.top < TOOLBAR_APPROX_H ? "active-bottom" : "active-top";
19052
- return /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)(import_jsx_runtime20.Fragment, { children: [
19053
- hoverHandleRect && hover && /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
19054
- "button",
19055
- {
19056
- type: "button",
19057
- "data-ohw-toolbar": "",
19058
- onClick: () => {
19059
- clearInteractionChromeRef.current();
19060
- setActive(hover);
19061
- },
19062
- "aria-label": "Configure media item",
19063
- style: {
19064
- position: "fixed",
19065
- top: hoverHandleRect.top + 8,
19066
- left: hoverHandleRect.left + hoverHandleRect.width - 36,
19067
- width: 28,
19068
- height: 28,
19069
- borderRadius: 8,
19070
- border: `1.5px dashed ${PRIMARY3}`,
19071
- background: "#ffffff",
19072
- color: PRIMARY3,
19073
- display: "flex",
19074
- alignItems: "center",
19075
- justifyContent: "center",
19076
- cursor: "pointer",
19077
- zIndex: 2147483643,
19078
- fontSize: 14,
19079
- fontWeight: 700
19080
- },
19081
- children: /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(import_lucide_react8.Star, { size: 14, style: { pointerEvents: "none" } })
19082
- }
19083
- ),
19084
- activeItemRect && active && activeEntry && /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
19085
- ItemInteractionLayer,
19086
- {
19087
- rect: activeItemRect,
19088
- state: toolbarState,
19089
- toolbar: /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
19090
- MediaStripToolbar,
19091
- {
19092
- entry: activeEntry,
19093
- index: active.index,
19094
- pending,
19095
- error,
19096
- onSetVariant: (variant) => runMutation(active.sectionId, (tree) => setMediaStripVariant(tree, variant)),
19097
- onSetAlign: (align) => runMutation(active.sectionId, (tree) => setMediaStripAlign(tree, align)),
19098
- onSetHover: (hoverPreset) => runMutation(active.sectionId, (tree) => setMediaStripHover(tree, hoverPreset)),
19099
- onSetTreatment: (treatment) => runMutation(active.sectionId, (tree) => setMediaStripTreatment(tree, treatment)),
19100
- onResize: (delta) => {
19101
- const slots = findMediaStripSlots(activeEntry.tree);
19102
- const variant = slots?.variant === "reel" ? "reel" : "logos";
19103
- const current = variant === "logos" ? typeof slots?.logoMaxHeight === "number" ? slots.logoMaxHeight : 48 : typeof slots?.cardWidth === "number" ? slots.cardWidth : 240;
19104
- void runMutation(active.sectionId, (tree) => setMediaStripSizing(tree, current + delta));
19105
- },
19106
- onSetAlt: (alt) => {
19107
- void runMutation(active.sectionId, (tree) => setMediaStripItemAlt(tree, active.index, alt));
19108
- },
19109
- onSetLink: (href, linkTarget) => {
19431
+ const activeStripRect = useLiveItemRect(active, true);
19432
+ const hoverRect = useLiveItemRect(hover && !sameRef(hover, active) ? hover : null);
19433
+ const activeItems = activeEntry ? mediaStripItems(activeEntry.tree) : [];
19434
+ const activeItem = active ? activeItems[active.index] : void 0;
19435
+ const activeHref = activeItem?.href ?? "";
19436
+ const activeTarget = activeItem?.linkTarget === "same-tab" ? "same-tab" : "new-tab";
19437
+ return /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(import_jsx_runtime21.Fragment, { children: [
19438
+ hoverRect && /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(ItemInteractionLayer, { rect: hoverRect, state: "hover" }),
19439
+ activeItemRect && activeStripRect && active && activeEntry && /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(import_jsx_runtime21.Fragment, { children: [
19440
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(ItemInteractionLayer, { rect: activeItemRect, state: "active-top" }),
19441
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(StripToolbarSlot, { stripRect: activeStripRect, children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
19442
+ MediaStripToolbar,
19443
+ {
19444
+ entry: activeEntry,
19445
+ index: active.index,
19446
+ pending,
19447
+ error,
19448
+ itemEditOn,
19449
+ onToggleItemEdit: () => setItemEditOn((on) => !on),
19450
+ onSetVariant: (variant) => runMutation(active.sectionId, (tree) => setMediaStripVariant(tree, variant)),
19451
+ onSetAlign: (align) => runMutation(active.sectionId, (tree) => setMediaStripAlign(tree, align)),
19452
+ onSetHover: (hoverPreset) => runMutation(active.sectionId, (tree) => setMediaStripHover(tree, hoverPreset)),
19453
+ onSetTreatment: (treatment) => runMutation(active.sectionId, (tree) => setMediaStripTreatment(tree, treatment)),
19454
+ onResize: (delta) => {
19455
+ const slots = findMediaStripSlots(activeEntry.tree);
19456
+ const variant = slots?.variant === "reel" ? "reel" : "logos";
19457
+ const current = variant === "logos" ? typeof slots?.logoMaxHeight === "number" ? slots.logoMaxHeight : 48 : typeof slots?.cardWidth === "number" ? slots.cardWidth : 240;
19458
+ void runMutation(active.sectionId, (tree) => setMediaStripSizing(tree, current + delta));
19459
+ },
19460
+ onOpenLink: () => setLinkOpen(true),
19461
+ onAddItem: () => {
19462
+ void requestAssetPick(postToParent2).then((picked) => {
19463
+ if (!picked) return;
19464
+ const item = { assetId: picked.assetId, alt: "Logo" };
19110
19465
  void runMutation(
19111
19466
  active.sectionId,
19112
- (tree) => setMediaStripItemLink(tree, active.index, href ? { href, linkTarget } : {})
19113
- );
19114
- },
19115
- onAddItem: () => {
19116
- void requestAssetPick(postToParent2).then((picked) => {
19117
- if (!picked) return;
19118
- const item = { assetId: picked.assetId, alt: "Logo" };
19119
- void runMutation(
19120
- active.sectionId,
19121
- (tree) => addMediaStripItem(tree, item, active.index),
19122
- { [picked.assetId]: picked.url }
19123
- ).then((applied) => {
19124
- if (applied) setActive({ sectionId: active.sectionId, index: active.index + 1 });
19125
- });
19467
+ (tree) => addMediaStripItem(tree, item, active.index),
19468
+ { [picked.assetId]: picked.url }
19469
+ ).then((applied) => {
19470
+ if (applied) setActive({ sectionId: active.sectionId, index: active.index + 1 });
19126
19471
  });
19127
- },
19128
- onChangeImage: () => {
19129
- void requestAssetPick(postToParent2).then((picked) => {
19130
- if (!picked) return;
19131
- void runMutation(
19132
- active.sectionId,
19133
- (tree) => setMediaStripItemAsset(tree, active.index, picked.assetId),
19134
- { [picked.assetId]: picked.url }
19135
- );
19136
- });
19137
- },
19138
- onRemoveItem: () => {
19139
- void runMutation(active.sectionId, (tree) => removeMediaStripItem(tree, active.index)).then(
19140
- (applied) => {
19141
- if (applied) setActive(null);
19142
- }
19143
- );
19144
- },
19145
- onMoveLeft: () => {
19146
- void runMutation(active.sectionId, (tree) => reorderMediaStripItem(tree, active.index, -1)).then(
19147
- (applied) => {
19148
- if (applied) setActive({ sectionId: active.sectionId, index: active.index - 1 });
19149
- }
19150
- );
19151
- },
19152
- onMoveRight: () => {
19153
- void runMutation(active.sectionId, (tree) => reorderMediaStripItem(tree, active.index, 1)).then(
19154
- (applied) => {
19155
- if (applied) setActive({ sectionId: active.sectionId, index: active.index + 1 });
19156
- }
19472
+ });
19473
+ },
19474
+ onChangeImage: () => {
19475
+ void requestAssetPick(postToParent2).then((picked) => {
19476
+ if (!picked) return;
19477
+ void runMutation(
19478
+ active.sectionId,
19479
+ (tree) => setMediaStripItemAsset(tree, active.index, picked.assetId),
19480
+ { [picked.assetId]: picked.url }
19157
19481
  );
19158
- }
19482
+ });
19483
+ },
19484
+ onRemoveItem: () => {
19485
+ void runMutation(active.sectionId, (tree) => removeMediaStripItem(tree, active.index)).then(
19486
+ (applied) => {
19487
+ if (applied) setActive(null);
19488
+ }
19489
+ );
19490
+ },
19491
+ onMoveLeft: () => {
19492
+ void runMutation(active.sectionId, (tree) => reorderMediaStripItem(tree, active.index, -1)).then(
19493
+ (applied) => {
19494
+ if (applied) setActive({ sectionId: active.sectionId, index: active.index - 1 });
19495
+ }
19496
+ );
19497
+ },
19498
+ onMoveRight: () => {
19499
+ void runMutation(active.sectionId, (tree) => reorderMediaStripItem(tree, active.index, 1)).then(
19500
+ (applied) => {
19501
+ if (applied) setActive({ sectionId: active.sectionId, index: active.index + 1 });
19502
+ }
19503
+ );
19159
19504
  }
19160
- )
19161
- }
19162
- )
19505
+ }
19506
+ ) }),
19507
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
19508
+ "div",
19509
+ {
19510
+ "data-ohw-bridge": "",
19511
+ "data-ohw-item-interaction": "",
19512
+ "aria-hidden": true,
19513
+ className: "pointer-events-none fixed rounded-[4px] px-[7px] py-[3px] font-sans text-[11px] font-medium leading-none text-primary-foreground whitespace-nowrap",
19514
+ style: {
19515
+ top: activeItemRect.top + activeItemRect.height + 10,
19516
+ left: activeItemRect.left - 5,
19517
+ background: PRIMARY3,
19518
+ zIndex: 2147483644
19519
+ },
19520
+ children: "Media item"
19521
+ }
19522
+ ),
19523
+ linkOpen && /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
19524
+ LinkModal,
19525
+ {
19526
+ href: activeHref,
19527
+ linkTarget: activeTarget,
19528
+ onClose: () => setLinkOpen(false),
19529
+ onSave: (href, linkTarget) => {
19530
+ void runMutation(
19531
+ active.sectionId,
19532
+ (tree) => setMediaStripItemLink(tree, active.index, href ? { href, linkTarget } : {})
19533
+ );
19534
+ }
19535
+ }
19536
+ )
19537
+ ] })
19163
19538
  ] });
19164
19539
  }
19165
19540
 
19166
19541
  // src/ui/ai-section/AiPricingCardOverlay.tsx
19167
19542
  var import_react11 = require("react");
19168
- var import_lucide_react9 = require("lucide-react");
19169
- var import_jsx_runtime21 = require("react/jsx-runtime");
19543
+ var import_lucide_react10 = require("lucide-react");
19544
+ var import_jsx_runtime22 = require("react/jsx-runtime");
19170
19545
  var PRIMARY4 = "var(--ohw-primary, #0885FE)";
19171
- 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]";
19546
+ 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]";
19172
19547
  function findPricingCardElement(sectionId, index) {
19173
19548
  const sectionEl = findByInstanceId(sectionId);
19174
19549
  if (!sectionEl) return null;
@@ -19180,7 +19555,7 @@ function findPricingCardElement(sectionId, index) {
19180
19555
  }
19181
19556
  function cardRefFromTarget(target) {
19182
19557
  if (!(target instanceof Element)) return null;
19183
- if (target.closest(CHROME_SELECTOR)) return null;
19558
+ if (target.closest(CHROME_SELECTOR2)) return null;
19184
19559
  const cardEl = target.closest("[data-ohw-pricing-card]");
19185
19560
  if (!cardEl) return null;
19186
19561
  const sectionEl = cardEl.closest("[data-ohw-section]");
@@ -19190,21 +19565,21 @@ function cardRefFromTarget(target) {
19190
19565
  if (!sectionId || Number.isNaN(index)) return null;
19191
19566
  return { sectionId, index };
19192
19567
  }
19193
- function sameRef(a, b) {
19568
+ function sameRef2(a, b) {
19194
19569
  return a?.sectionId === b?.sectionId && a?.index === b?.index;
19195
19570
  }
19196
- function rectOf(el) {
19571
+ function rectOf2(el) {
19197
19572
  if (!el) return null;
19198
19573
  const r2 = el.getBoundingClientRect();
19199
19574
  if (r2.width <= 0 || r2.height <= 0) return null;
19200
19575
  return { top: r2.top, left: r2.left, width: r2.width, height: r2.height };
19201
19576
  }
19202
19577
  function readRect3(ref) {
19203
- return rectOf(ref && findPricingCardElement(ref.sectionId, ref.index));
19578
+ return rectOf2(ref && findPricingCardElement(ref.sectionId, ref.index));
19204
19579
  }
19205
19580
  function readGridRect(ref) {
19206
19581
  const card = ref && findPricingCardElement(ref.sectionId, ref.index);
19207
- return rectOf(card?.closest("[data-ohw-pricing-collection]") ?? card);
19582
+ return rectOf2(card?.closest("[data-ohw-pricing-collection]") ?? card);
19208
19583
  }
19209
19584
  function useLiveCardRect(ref, grid = false) {
19210
19585
  const [rect, setRect] = (0, import_react11.useState)(null);
@@ -19234,7 +19609,7 @@ function useLiveCardRect(ref, grid = false) {
19234
19609
  }
19235
19610
  var TOOLBAR_GRID_GAP = 52;
19236
19611
  var TOOLBAR_GRID_GAP_MOBILE = 42;
19237
- var TOOLBAR_EDGE_MARGIN2 = 4;
19612
+ var TOOLBAR_EDGE_MARGIN3 = 4;
19238
19613
  function GridToolbarSlot({ gridRect, children }) {
19239
19614
  const slotRef = (0, import_react11.useRef)(null);
19240
19615
  const [shift, setShift] = (0, import_react11.useState)(0);
@@ -19246,7 +19621,7 @@ function GridToolbarSlot({ gridRect, children }) {
19246
19621
  const w = slot.offsetWidth;
19247
19622
  if (w <= 0) return;
19248
19623
  const half = w / 2;
19249
- const clamped = Math.max(TOOLBAR_EDGE_MARGIN2 + half, Math.min(centerX, window.innerWidth - TOOLBAR_EDGE_MARGIN2 - half));
19624
+ const clamped = Math.max(TOOLBAR_EDGE_MARGIN3 + half, Math.min(centerX, window.innerWidth - TOOLBAR_EDGE_MARGIN3 - half));
19250
19625
  setShift((prev) => Math.abs(prev - (clamped - centerX)) < 0.5 ? prev : clamped - centerX);
19251
19626
  };
19252
19627
  clamp();
@@ -19255,7 +19630,7 @@ function GridToolbarSlot({ gridRect, children }) {
19255
19630
  return () => ro.disconnect();
19256
19631
  }, [centerX, children]);
19257
19632
  const mobile = window.innerWidth <= 480;
19258
- return /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
19633
+ return /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
19259
19634
  "div",
19260
19635
  {
19261
19636
  ref: slotRef,
@@ -19290,7 +19665,7 @@ function requestValidateTree2(postToParent2, tree) {
19290
19665
  });
19291
19666
  }
19292
19667
  function StyleSwatch({ style }) {
19293
- return /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
19668
+ return /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
19294
19669
  "span",
19295
19670
  {
19296
19671
  "aria-hidden": true,
@@ -19304,35 +19679,35 @@ function StyleSwatch({ style }) {
19304
19679
  );
19305
19680
  }
19306
19681
  function ButtonGlyph() {
19307
- return /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("svg", { width: "18", height: "12", viewBox: "0 0 18 12", fill: "none", "aria-hidden": true, style: { pointerEvents: "none" }, children: [
19308
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("rect", { x: "0.75", y: "0.75", width: "16.5", height: "10.5", rx: "3", stroke: "currentColor", strokeWidth: "1.5" }),
19309
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("path", { d: "M5.5 6H12.5", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round" })
19682
+ return /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("svg", { width: "18", height: "12", viewBox: "0 0 18 12", fill: "none", "aria-hidden": true, style: { pointerEvents: "none" }, children: [
19683
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("rect", { x: "0.75", y: "0.75", width: "16.5", height: "10.5", rx: "3", stroke: "currentColor", strokeWidth: "1.5" }),
19684
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("path", { d: "M5.5 6H12.5", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round" })
19310
19685
  ] });
19311
19686
  }
19312
19687
  function FeatureGlyph({ mode }) {
19313
- return /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("svg", { width: "18", height: "18", viewBox: "0 0 18 18", fill: "none", "aria-hidden": true, style: { pointerEvents: "none" }, children: [
19314
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("circle", { cx: "8", cy: "8", r: "7.25", stroke: "currentColor", strokeWidth: "1.5" }),
19315
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("path", { d: "M5 8.2L7.2 10.3L11 6", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round", strokeLinejoin: "round" }),
19316
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("circle", { cx: "13.5", cy: "13.5", r: "4.25", fill: "var(--ohw-background, #ffffff)", stroke: "var(--ohw-background, #ffffff)", strokeWidth: "1" }),
19317
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("circle", { cx: "13.5", cy: "13.5", r: "3.75", fill: "var(--ohw-background, #ffffff)", stroke: PRIMARY4, strokeWidth: "1" }),
19318
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("path", { d: "M11.5 13.5H15.5", stroke: PRIMARY4, strokeWidth: "1.5", strokeLinecap: "round" }),
19319
- mode === "add" && /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("path", { d: "M13.5 11.5V15.5", stroke: PRIMARY4, strokeWidth: "1.5", strokeLinecap: "round" })
19688
+ return /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("svg", { width: "18", height: "18", viewBox: "0 0 18 18", fill: "none", "aria-hidden": true, style: { pointerEvents: "none" }, children: [
19689
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("circle", { cx: "8", cy: "8", r: "7.25", stroke: "currentColor", strokeWidth: "1.5" }),
19690
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("path", { d: "M5 8.2L7.2 10.3L11 6", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round", strokeLinejoin: "round" }),
19691
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("circle", { cx: "13.5", cy: "13.5", r: "4.25", fill: "var(--ohw-background, #ffffff)", stroke: "var(--ohw-background, #ffffff)", strokeWidth: "1" }),
19692
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("circle", { cx: "13.5", cy: "13.5", r: "3.75", fill: "var(--ohw-background, #ffffff)", stroke: PRIMARY4, strokeWidth: "1" }),
19693
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("path", { d: "M11.5 13.5H15.5", stroke: PRIMARY4, strokeWidth: "1.5", strokeLinecap: "round" }),
19694
+ mode === "add" && /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("path", { d: "M13.5 11.5V15.5", stroke: PRIMARY4, strokeWidth: "1.5", strokeLinecap: "round" })
19320
19695
  ] });
19321
19696
  }
19322
- function ToolbarAction({
19697
+ function ToolbarAction2({
19323
19698
  label,
19324
19699
  onClick,
19325
19700
  disabled = false,
19326
19701
  children
19327
19702
  }) {
19328
- const button = /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(CustomToolbarButton, { type: "button", "aria-label": label, disabled, onClick, className: "size-8", children });
19329
- return /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(Tooltip, { children: [
19330
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(TooltipTrigger, { asChild: true, children: disabled ? /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("span", { className: "inline-flex", children: button }) : button }),
19331
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(TooltipContent, { side: "bottom", sideOffset: 8, children: label })
19703
+ const button = /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(CustomToolbarButton, { type: "button", "aria-label": label, disabled, onClick, className: "size-8", children });
19704
+ return /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(Tooltip, { children: [
19705
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(TooltipTrigger, { asChild: true, children: disabled ? /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("span", { className: "inline-flex", children: button }) : button }),
19706
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(TooltipContent, { side: "bottom", sideOffset: 8, children: label })
19332
19707
  ] });
19333
19708
  }
19334
- function ToolbarSeparator() {
19335
- return /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("span", { "aria-hidden": true, className: "mx-0.5 block h-6 w-px shrink-0 bg-border" });
19709
+ function ToolbarSeparator2() {
19710
+ return /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("span", { "aria-hidden": true, className: "mx-0.5 block h-6 w-px shrink-0 bg-border" });
19336
19711
  }
19337
19712
  function CardStyleMenu({
19338
19713
  value,
@@ -19340,7 +19715,7 @@ function CardStyleMenu({
19340
19715
  disabled = false
19341
19716
  }) {
19342
19717
  const current = PRICING_CARD_STYLES.find((s) => s.value === value) ?? PRICING_CARD_STYLES[0];
19343
- const trigger = /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(
19718
+ const trigger = /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(
19344
19719
  "button",
19345
19720
  {
19346
19721
  type: "button",
@@ -19351,28 +19726,28 @@ function CardStyleMenu({
19351
19726
  disabled && "cursor-not-allowed opacity-40 hover:bg-transparent"
19352
19727
  ),
19353
19728
  children: [
19354
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(StyleSwatch, { style: current.value }),
19355
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("span", { className: "flex-1 text-left", children: current.label }),
19356
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(import_lucide_react9.ChevronDown, { size: 16, style: { pointerEvents: "none" } })
19729
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(StyleSwatch, { style: current.value }),
19730
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("span", { className: "flex-1 text-left", children: current.label }),
19731
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(import_lucide_react10.ChevronDown, { size: 16, style: { pointerEvents: "none" } })
19357
19732
  ]
19358
19733
  }
19359
19734
  );
19360
19735
  if (disabled) {
19361
- return /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("div", { className: "flex items-center gap-1.5 pl-1.5", children: [
19362
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("span", { className: "text-xs text-muted-foreground opacity-50", children: "Card style" }),
19363
- /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(Tooltip, { children: [
19364
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("span", { className: "inline-flex", children: trigger }) }),
19365
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(TooltipContent, { side: "bottom", sideOffset: 8, children: "Mark as featured to edit card style" })
19736
+ return /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("div", { className: "flex items-center gap-1.5 pl-1.5", children: [
19737
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("span", { className: "text-xs text-muted-foreground opacity-50", children: "Card style" }),
19738
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(Tooltip, { children: [
19739
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("span", { className: "inline-flex", children: trigger }) }),
19740
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(TooltipContent, { side: "bottom", sideOffset: 8, children: "Mark as featured to edit card style" })
19366
19741
  ] })
19367
19742
  ] });
19368
19743
  }
19369
- return /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("div", { className: "flex items-center gap-1.5 pl-1.5", children: [
19370
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("span", { className: "text-xs text-muted-foreground", children: "Card style" }),
19371
- /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(DropdownMenu, { modal: false, children: [
19372
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(DropdownMenuTrigger, { asChild: true, children: trigger }),
19373
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(DropdownMenuContent, { align: "end", sideOffset: 8, className: "w-[152px] min-w-[152px] p-1", children: PRICING_CARD_STYLES.map((style) => {
19744
+ return /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("div", { className: "flex items-center gap-1.5 pl-1.5", children: [
19745
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("span", { className: "text-xs text-muted-foreground", children: "Card style" }),
19746
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(DropdownMenu, { modal: false, children: [
19747
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(DropdownMenuTrigger, { asChild: true, children: trigger }),
19748
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(DropdownMenuContent, { align: "end", sideOffset: 8, className: "w-[152px] min-w-[152px] p-1", children: PRICING_CARD_STYLES.map((style) => {
19374
19749
  const selected = style.value === value;
19375
- return /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(
19750
+ return /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(
19376
19751
  DropdownMenuItem,
19377
19752
  {
19378
19753
  role: "menuitemradio",
@@ -19380,9 +19755,9 @@ function CardStyleMenu({
19380
19755
  onSelect: () => onChange(style.value),
19381
19756
  className: cn("grid grid-cols-[18px_1fr_16px] gap-2 py-2 pl-2 pr-2 text-[13px]", selected && "bg-muted"),
19382
19757
  children: [
19383
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(StyleSwatch, { style: style.value }),
19384
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("span", { children: style.label }),
19385
- selected && /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(import_lucide_react9.Check, { size: 16, className: "text-bridge-primary" })
19758
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(StyleSwatch, { style: style.value }),
19759
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("span", { children: style.label }),
19760
+ selected && /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(import_lucide_react10.Check, { size: 16, className: "text-bridge-primary" })
19386
19761
  ]
19387
19762
  },
19388
19763
  style.value
@@ -19412,7 +19787,7 @@ function PricingCardToolbar({
19412
19787
  const style = pricingCardStyle(entry.tree, index);
19413
19788
  const featuredTag = pricingCardFeaturedTag(entry.tree, index);
19414
19789
  const mobile = window.innerWidth <= 480;
19415
- return /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(TooltipProvider, { delayDuration: 0, children: /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(
19790
+ return /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(TooltipProvider, { delayDuration: 0, children: /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(
19416
19791
  "div",
19417
19792
  {
19418
19793
  "data-ohw-bridge": "",
@@ -19424,7 +19799,7 @@ function PricingCardToolbar({
19424
19799
  ),
19425
19800
  style: { maxWidth: mobile ? "calc(100vw - 24px)" : void 0 },
19426
19801
  children: [
19427
- /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(
19802
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(
19428
19803
  "div",
19429
19804
  {
19430
19805
  className: cn(
@@ -19433,8 +19808,8 @@ function PricingCardToolbar({
19433
19808
  mobile ? "flex-wrap justify-center" : "flex-nowrap"
19434
19809
  ),
19435
19810
  children: [
19436
- /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(Tooltip, { children: [
19437
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
19811
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(Tooltip, { children: [
19812
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
19438
19813
  CustomToolbarButton,
19439
19814
  {
19440
19815
  type: "button",
@@ -19442,8 +19817,8 @@ function PricingCardToolbar({
19442
19817
  "aria-pressed": featuredTag,
19443
19818
  onClick: () => onSetFeaturedTag(!featuredTag),
19444
19819
  className: "group size-8",
19445
- children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
19446
- import_lucide_react9.Star,
19820
+ children: /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
19821
+ import_lucide_react10.Star,
19447
19822
  {
19448
19823
  size: 16,
19449
19824
  fill: "currentColor",
@@ -19457,25 +19832,25 @@ function PricingCardToolbar({
19457
19832
  )
19458
19833
  }
19459
19834
  ) }),
19460
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(TooltipContent, { side: "bottom", sideOffset: 8, children: featuredTag ? "Unmark featured" : "Mark as featured" })
19835
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(TooltipContent, { side: "bottom", sideOffset: 8, children: featuredTag ? "Unmark featured" : "Mark as featured" })
19461
19836
  ] }),
19462
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(ToolbarSeparator, {}),
19463
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(CardStyleMenu, { value: style, onChange: onSetStyle, disabled: !featuredTag }),
19464
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(ToolbarSeparator, {}),
19465
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(ToolbarAction, { label: "Add button", onClick: onAddButton, disabled: buttonCount >= 2, children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(ButtonGlyph, {}) }),
19466
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(ToolbarAction, { label: "Remove button", onClick: onRemoveButton, disabled: buttonCount === 0, children: /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("span", { className: "relative inline-flex", children: [
19467
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(ButtonGlyph, {}),
19468
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(import_lucide_react9.Minus, { size: 10, strokeWidth: 2.5, className: "absolute -right-1.5 -top-1.5 rounded-full bg-background text-bridge-primary" })
19837
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(ToolbarSeparator2, {}),
19838
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(CardStyleMenu, { value: style, onChange: onSetStyle, disabled: !featuredTag }),
19839
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(ToolbarSeparator2, {}),
19840
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(ToolbarAction2, { label: "Add button", onClick: onAddButton, disabled: buttonCount >= 2, children: /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(ButtonGlyph, {}) }),
19841
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(ToolbarAction2, { label: "Remove button", onClick: onRemoveButton, disabled: buttonCount === 0, children: /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("span", { className: "relative inline-flex", children: [
19842
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(ButtonGlyph, {}),
19843
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(import_lucide_react10.Minus, { size: 10, strokeWidth: 2.5, className: "absolute -right-1.5 -top-1.5 rounded-full bg-background text-bridge-primary" })
19469
19844
  ] }) }),
19470
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(ToolbarAction, { label: "Add feature", onClick: onAddFeature, disabled: featureCount >= 10, children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(FeatureGlyph, { mode: "add" }) }),
19471
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(ToolbarAction, { label: "Remove feature", onClick: onRemoveFeature, disabled: featureCount <= 1, children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(FeatureGlyph, { mode: "remove" }) }),
19472
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(ToolbarSeparator, {}),
19473
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(ToolbarAction, { label: "Add tier", onClick: onAddTier, disabled: collectionCount >= 4, children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(import_lucide_react9.Plus, { size: 16, style: { pointerEvents: "none" } }) }),
19474
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(ToolbarAction, { label: "Remove tier", onClick: onRemoveTier, disabled: collectionCount <= 1, children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(import_lucide_react9.Minus, { size: 16, style: { pointerEvents: "none" } }) })
19845
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(ToolbarAction2, { label: "Add feature", onClick: onAddFeature, disabled: featureCount >= 10, children: /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(FeatureGlyph, { mode: "add" }) }),
19846
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(ToolbarAction2, { label: "Remove feature", onClick: onRemoveFeature, disabled: featureCount <= 1, children: /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(FeatureGlyph, { mode: "remove" }) }),
19847
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(ToolbarSeparator2, {}),
19848
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(ToolbarAction2, { label: "Add tier", onClick: onAddTier, disabled: collectionCount >= 4, children: /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(import_lucide_react10.Plus, { size: 16, style: { pointerEvents: "none" } }) }),
19849
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(ToolbarAction2, { label: "Remove tier", onClick: onRemoveTier, disabled: collectionCount <= 1, children: /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(import_lucide_react10.Minus, { size: 16, style: { pointerEvents: "none" } }) })
19475
19850
  ]
19476
19851
  }
19477
19852
  ),
19478
- error && /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("div", { className: "max-w-[260px] rounded-md bg-background px-2 py-1 text-center text-[11px] text-destructive shadow-sm", children: error })
19853
+ error && /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("div", { className: "max-w-[260px] rounded-md bg-background px-2 py-1 text-center text-[11px] text-destructive shadow-sm", children: error })
19479
19854
  ]
19480
19855
  }
19481
19856
  ) });
@@ -19509,7 +19884,7 @@ function AiPricingCardOverlay({
19509
19884
  const overEditable = Boolean(t?.closest?.("[data-ohw-editable], [data-ohw-role], a, button"));
19510
19885
  setHover((prev) => {
19511
19886
  const next = ref && !overEditable ? ref : null;
19512
- return sameRef(prev, next) ? prev : next;
19887
+ return sameRef2(prev, next) ? prev : next;
19513
19888
  });
19514
19889
  };
19515
19890
  const onLeave = () => setHover(null);
@@ -19524,10 +19899,10 @@ function AiPricingCardOverlay({
19524
19899
  const onClick = (e) => {
19525
19900
  const t = e.target;
19526
19901
  if (!(t instanceof Element)) return;
19527
- if (t.closest(CHROME_SELECTOR)) return;
19902
+ if (t.closest(CHROME_SELECTOR2)) return;
19528
19903
  const ref = cardRefFromTarget(t);
19529
- setActive((prev) => sameRef(prev, ref) ? prev : ref);
19530
- if (!ref || !sameRef(activeRef.current, ref)) setError(null);
19904
+ setActive((prev) => sameRef2(prev, ref) ? prev : ref);
19905
+ if (!ref || !sameRef2(activeRef.current, ref)) setError(null);
19531
19906
  };
19532
19907
  document.addEventListener("click", onClick, true);
19533
19908
  return () => document.removeEventListener("click", onClick, true);
@@ -19592,12 +19967,12 @@ function AiPricingCardOverlay({
19592
19967
  const activeEntry = active ? findEntry(active.sectionId) : null;
19593
19968
  const activeCardRect = useLiveCardRect(active);
19594
19969
  const activeGridRect = useLiveCardRect(active, true);
19595
- const hoverRect = useLiveCardRect(hover && !sameRef(hover, active) ? hover : null);
19596
- return /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(import_jsx_runtime21.Fragment, { children: [
19597
- hoverRect && /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(ItemInteractionLayer, { rect: hoverRect, state: "hover" }),
19598
- activeCardRect && activeGridRect && active && activeEntry && /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(import_jsx_runtime21.Fragment, { children: [
19599
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(ItemInteractionLayer, { rect: activeCardRect, state: "active-top" }),
19600
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(GridToolbarSlot, { gridRect: activeGridRect, children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
19970
+ const hoverRect = useLiveCardRect(hover && !sameRef2(hover, active) ? hover : null);
19971
+ return /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(import_jsx_runtime22.Fragment, { children: [
19972
+ hoverRect && /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(ItemInteractionLayer, { rect: hoverRect, state: "hover" }),
19973
+ activeCardRect && activeGridRect && active && activeEntry && /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(import_jsx_runtime22.Fragment, { children: [
19974
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(ItemInteractionLayer, { rect: activeCardRect, state: "active-top" }),
19975
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(GridToolbarSlot, { gridRect: activeGridRect, children: /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
19601
19976
  PricingCardToolbar,
19602
19977
  {
19603
19978
  entry: activeEntry,
@@ -19625,7 +20000,7 @@ function AiPricingCardOverlay({
19625
20000
  onRemoveFeature: () => runMutation(active.sectionId, (tree) => removePricingCardFeature(tree, active.index))
19626
20001
  }
19627
20002
  ) }),
19628
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
20003
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
19629
20004
  "div",
19630
20005
  {
19631
20006
  "data-ohw-bridge": "",
@@ -19647,7 +20022,7 @@ function AiPricingCardOverlay({
19647
20022
 
19648
20023
  // src/ui/ai-section/AiVideoOverlay.tsx
19649
20024
  var import_react12 = require("react");
19650
- var import_lucide_react10 = require("lucide-react");
20025
+ var import_lucide_react11 = require("lucide-react");
19651
20026
 
19652
20027
  // src/lib/ai-video-tree.ts
19653
20028
  function resolveVideoBlock(tree, path) {
@@ -19718,10 +20093,10 @@ function extractYouTubeId(input) {
19718
20093
  }
19719
20094
 
19720
20095
  // src/ui/ai-section/AiVideoOverlay.tsx
19721
- var import_jsx_runtime22 = require("react/jsx-runtime");
20096
+ var import_jsx_runtime23 = require("react/jsx-runtime");
19722
20097
  var PRIMARY5 = "#0885FE";
19723
- var OHW_BLUE2 = `var(--ohw-primary, ${PRIMARY5})`;
19724
- var OHW_BLUE_DARK2 = `color-mix(in srgb, var(--ohw-primary, ${PRIMARY5}) 60%, #000000)`;
20098
+ var OHW_BLUE = `var(--ohw-primary, ${PRIMARY5})`;
20099
+ var OHW_BLUE_DARK = `color-mix(in srgb, var(--ohw-primary, ${PRIMARY5}) 60%, #000000)`;
19725
20100
  function findVideoBlockElement(sectionId, path) {
19726
20101
  const sectionEl = findByInstanceId(sectionId);
19727
20102
  if (!sectionEl) return null;
@@ -19781,12 +20156,12 @@ function requestValidateTree3(postToParent2, tree) {
19781
20156
  postToParent2({ type: "ow:ai-validate-tree-request", id, tree });
19782
20157
  });
19783
20158
  }
19784
- function Pill2({
20159
+ function Pill({
19785
20160
  label,
19786
20161
  onClick,
19787
20162
  disabled
19788
20163
  }) {
19789
- return /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
20164
+ return /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
19790
20165
  "button",
19791
20166
  {
19792
20167
  type: "button",
@@ -19802,7 +20177,7 @@ function Pill2({
19802
20177
  border: "none",
19803
20178
  cursor: disabled ? "default" : "pointer",
19804
20179
  background: "#ffffff",
19805
- color: OHW_BLUE_DARK2,
20180
+ color: OHW_BLUE_DARK,
19806
20181
  opacity: disabled ? 0.4 : 1,
19807
20182
  fontSize: 12,
19808
20183
  lineHeight: "16px",
@@ -19845,8 +20220,8 @@ function VideoUrlInput({ value, onCommit }) {
19845
20220
  onCommit(id);
19846
20221
  }
19847
20222
  };
19848
- return /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("div", { style: { display: "flex", flexDirection: "column", gap: 4, width: "100%", maxWidth: 280 }, children: [
19849
- /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(
20223
+ return /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)("div", { style: { display: "flex", flexDirection: "column", gap: 4, width: "100%", maxWidth: 280 }, children: [
20224
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)(
19850
20225
  "div",
19851
20226
  {
19852
20227
  style: {
@@ -19862,12 +20237,12 @@ function VideoUrlInput({ value, onCommit }) {
19862
20237
  boxShadow: invalid ? "inset 0 0 0 1.5px #EF4444" : "none"
19863
20238
  },
19864
20239
  children: [
19865
- /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
20240
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
19866
20241
  "span",
19867
20242
  {
19868
20243
  style: {
19869
20244
  flex: "0 0 auto",
19870
- color: OHW_BLUE_DARK2,
20245
+ color: OHW_BLUE_DARK,
19871
20246
  fontSize: 10,
19872
20247
  lineHeight: "16px",
19873
20248
  fontWeight: 700,
@@ -19877,7 +20252,7 @@ function VideoUrlInput({ value, onCommit }) {
19877
20252
  children: "Video"
19878
20253
  }
19879
20254
  ),
19880
- /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
20255
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
19881
20256
  "input",
19882
20257
  {
19883
20258
  type: "text",
@@ -19914,7 +20289,7 @@ function VideoUrlInput({ value, onCommit }) {
19914
20289
  ]
19915
20290
  }
19916
20291
  ),
19917
- invalid && /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("div", { style: { color: "#FCA5A5", fontSize: 11, textAlign: "center" }, children: "That doesn't look like a YouTube link \u2014 try pasting the video URL again." })
20292
+ invalid && /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("div", { style: { color: "#FCA5A5", fontSize: 11, textAlign: "center" }, children: "That doesn't look like a YouTube link \u2014 try pasting the video URL again." })
19918
20293
  ] });
19919
20294
  }
19920
20295
  function VideoToolbar({
@@ -19924,7 +20299,7 @@ function VideoToolbar({
19924
20299
  onSetVideoId,
19925
20300
  onRemove
19926
20301
  }) {
19927
- return /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(
20302
+ return /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)(
19928
20303
  "div",
19929
20304
  {
19930
20305
  "data-ohw-bridge": "",
@@ -19936,7 +20311,7 @@ function VideoToolbar({
19936
20311
  gap: 6,
19937
20312
  alignItems: "center",
19938
20313
  width: 280,
19939
- background: OHW_BLUE2,
20314
+ background: OHW_BLUE,
19940
20315
  padding: 8,
19941
20316
  borderRadius: 12,
19942
20317
  boxShadow: "0 8px 24px rgba(0,0,0,0.25)",
@@ -19944,9 +20319,9 @@ function VideoToolbar({
19944
20319
  opacity: pending ? 0.6 : 1
19945
20320
  },
19946
20321
  children: [
19947
- /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(VideoUrlInput, { value: videoId, onCommit: onSetVideoId }),
19948
- /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(Pill2, { label: "Remove video", onClick: onRemove, disabled: !videoId }),
19949
- error && /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("div", { style: { color: "#FCA5A5", fontSize: 11, maxWidth: 220, textAlign: "center" }, children: error })
20322
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(VideoUrlInput, { value: videoId, onCommit: onSetVideoId }),
20323
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(Pill, { label: "Remove video", onClick: onRemove, disabled: !videoId }),
20324
+ error && /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("div", { style: { color: "#FCA5A5", fontSize: 11, maxWidth: 220, textAlign: "center" }, children: error })
19950
20325
  ]
19951
20326
  }
19952
20327
  );
@@ -20066,8 +20441,8 @@ function AiVideoOverlay({
20066
20441
  const activeBlockRect = useLiveBlockRect(active);
20067
20442
  const TOOLBAR_APPROX_H = 90;
20068
20443
  const toolbarState = activeBlockRect && activeBlockRect.top < TOOLBAR_APPROX_H ? "active-bottom" : "active-top";
20069
- return /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(import_jsx_runtime22.Fragment, { children: [
20070
- hoverHandleRect && hover && /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
20444
+ return /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)(import_jsx_runtime23.Fragment, { children: [
20445
+ hoverHandleRect && hover && /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
20071
20446
  "button",
20072
20447
  {
20073
20448
  type: "button",
@@ -20095,15 +20470,15 @@ function AiVideoOverlay({
20095
20470
  fontSize: 14,
20096
20471
  fontWeight: 700
20097
20472
  },
20098
- children: /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(import_lucide_react10.Star, { size: 14, style: { pointerEvents: "none" } })
20473
+ children: /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(import_lucide_react11.Star, { size: 14, style: { pointerEvents: "none" } })
20099
20474
  }
20100
20475
  ),
20101
- activeBlockRect && active && activeEntry && /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
20476
+ activeBlockRect && active && activeEntry && /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
20102
20477
  ItemInteractionLayer,
20103
20478
  {
20104
20479
  rect: activeBlockRect,
20105
20480
  state: toolbarState,
20106
- toolbar: /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
20481
+ toolbar: /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
20107
20482
  VideoToolbar,
20108
20483
  {
20109
20484
  videoId: videoIdAt(activeEntry.tree, active.path),
@@ -20127,7 +20502,7 @@ function AiVideoOverlay({
20127
20502
 
20128
20503
  // src/ui/ai-section/AiFadeCarouselOverlay.tsx
20129
20504
  var import_react13 = require("react");
20130
- var import_lucide_react11 = require("lucide-react");
20505
+ var import_lucide_react12 = require("lucide-react");
20131
20506
 
20132
20507
  // src/lib/ai-carousel-tree.ts
20133
20508
  function isCarouselBlock(node) {
@@ -20178,10 +20553,10 @@ function setCarouselAutoplay(tree, on) {
20178
20553
  }
20179
20554
 
20180
20555
  // src/ui/ai-section/AiFadeCarouselOverlay.tsx
20181
- var import_jsx_runtime23 = require("react/jsx-runtime");
20556
+ var import_jsx_runtime24 = require("react/jsx-runtime");
20182
20557
  var PRIMARY6 = "#0885FE";
20183
- var OHW_BLUE3 = `var(--ohw-primary, ${PRIMARY6})`;
20184
- var OHW_BLUE_DARK3 = `color-mix(in srgb, var(--ohw-primary, ${PRIMARY6}) 60%, #000000)`;
20558
+ var OHW_BLUE2 = `var(--ohw-primary, ${PRIMARY6})`;
20559
+ var OHW_BLUE_DARK2 = `color-mix(in srgb, var(--ohw-primary, ${PRIMARY6}) 60%, #000000)`;
20185
20560
  function findCarouselItemElement(sectionId, index) {
20186
20561
  const sectionEl = findByInstanceId(sectionId);
20187
20562
  if (!sectionEl) return null;
@@ -20260,8 +20635,8 @@ function requestAssetPick2(postToParent2) {
20260
20635
  postToParent2({ type: "ow:asset-pick", id });
20261
20636
  });
20262
20637
  }
20263
- function Pill3({ label, onClick, disabled }) {
20264
- return /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
20638
+ function Pill2({ label, onClick, disabled }) {
20639
+ return /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
20265
20640
  "button",
20266
20641
  {
20267
20642
  type: "button",
@@ -20277,7 +20652,7 @@ function Pill3({ label, onClick, disabled }) {
20277
20652
  border: "none",
20278
20653
  cursor: disabled ? "default" : "pointer",
20279
20654
  background: "#ffffff",
20280
- color: OHW_BLUE_DARK3,
20655
+ color: OHW_BLUE_DARK2,
20281
20656
  opacity: disabled ? 0.4 : 1,
20282
20657
  fontSize: 12,
20283
20658
  lineHeight: "16px",
@@ -20295,7 +20670,7 @@ function TogglePill({
20295
20670
  onToggle,
20296
20671
  disabled
20297
20672
  }) {
20298
- return /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)(
20673
+ return /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)(
20299
20674
  "button",
20300
20675
  {
20301
20676
  type: "button",
@@ -20314,7 +20689,7 @@ function TogglePill({
20314
20689
  border: "none",
20315
20690
  cursor: disabled ? "default" : "pointer",
20316
20691
  background: "#ffffff",
20317
- color: OHW_BLUE_DARK3,
20692
+ color: OHW_BLUE_DARK2,
20318
20693
  opacity: disabled ? 0.4 : 1,
20319
20694
  fontSize: 12,
20320
20695
  lineHeight: "16px",
@@ -20324,7 +20699,7 @@ function TogglePill({
20324
20699
  },
20325
20700
  children: [
20326
20701
  label,
20327
- /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
20702
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
20328
20703
  "span",
20329
20704
  {
20330
20705
  "aria-hidden": true,
@@ -20337,10 +20712,10 @@ function TogglePill({
20337
20712
  // Off is a grey track, not a washed-out blue: the pill's own white background already
20338
20713
  // carries the brand colour in the text, and a pale blue track next to it read as
20339
20714
  // "disabled control" rather than "switch that is off".
20340
- background: on ? OHW_BLUE_DARK3 : "#CBD5E1",
20715
+ background: on ? OHW_BLUE_DARK2 : "#CBD5E1",
20341
20716
  transition: "background 120ms ease"
20342
20717
  },
20343
- children: /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
20718
+ children: /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
20344
20719
  "span",
20345
20720
  {
20346
20721
  style: {
@@ -20369,7 +20744,7 @@ function CarouselSlideToolbar({
20369
20744
  onChangeImage,
20370
20745
  onToggleAutoplay
20371
20746
  }) {
20372
- return /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)(
20747
+ return /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)(
20373
20748
  "div",
20374
20749
  {
20375
20750
  "data-ohw-bridge": "",
@@ -20380,7 +20755,7 @@ function CarouselSlideToolbar({
20380
20755
  flexDirection: "column",
20381
20756
  gap: 6,
20382
20757
  alignItems: "center",
20383
- background: OHW_BLUE3,
20758
+ background: OHW_BLUE2,
20384
20759
  padding: 8,
20385
20760
  borderRadius: 12,
20386
20761
  boxShadow: "0 8px 24px rgba(0,0,0,0.25)",
@@ -20392,11 +20767,11 @@ function CarouselSlideToolbar({
20392
20767
  pointerEvents: pending ? "none" : "auto"
20393
20768
  },
20394
20769
  children: [
20395
- /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)("div", { style: { display: "flex", gap: 6, alignItems: "center" }, children: [
20396
- /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(Pill3, { label: "Change Image", onClick: onChangeImage }),
20397
- /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(TogglePill, { label: "Autoplay", on: autoplay, onToggle: onToggleAutoplay })
20770
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)("div", { style: { display: "flex", gap: 6, alignItems: "center" }, children: [
20771
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(Pill2, { label: "Change Image", onClick: onChangeImage }),
20772
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(TogglePill, { label: "Autoplay", on: autoplay, onToggle: onToggleAutoplay })
20398
20773
  ] }),
20399
- error && /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("div", { style: { color: "#FCA5A5", fontSize: 11, maxWidth: 220, textAlign: "center" }, children: error })
20774
+ error && /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("div", { style: { color: "#FCA5A5", fontSize: 11, maxWidth: 220, textAlign: "center" }, children: error })
20400
20775
  ]
20401
20776
  }
20402
20777
  );
@@ -20536,8 +20911,8 @@ function AiFadeCarouselOverlay({
20536
20911
  const activeItemRect = useLiveItemRect2(active);
20537
20912
  const TOOLBAR_APPROX_H = 90;
20538
20913
  const toolbarState = activeItemRect && activeItemRect.top < TOOLBAR_APPROX_H ? "active-bottom" : "active-top";
20539
- return /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)(import_jsx_runtime23.Fragment, { children: [
20540
- hoverHandleRect && hover && /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
20914
+ return /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)(import_jsx_runtime24.Fragment, { children: [
20915
+ hoverHandleRect && hover && /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
20541
20916
  "button",
20542
20917
  {
20543
20918
  type: "button",
@@ -20566,10 +20941,10 @@ function AiFadeCarouselOverlay({
20566
20941
  fontSize: 14,
20567
20942
  fontWeight: 700
20568
20943
  },
20569
- children: /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(import_lucide_react11.Star, { size: 14, style: { pointerEvents: "none" } })
20944
+ children: /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(import_lucide_react12.Star, { size: 14, style: { pointerEvents: "none" } })
20570
20945
  }
20571
20946
  ),
20572
- swapping && /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
20947
+ swapping && /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
20573
20948
  "div",
20574
20949
  {
20575
20950
  ref: skeletonRef,
@@ -20587,15 +20962,15 @@ function AiFadeCarouselOverlay({
20587
20962
  zIndex: 2147483646,
20588
20963
  pointerEvents: "none"
20589
20964
  },
20590
- children: /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("style", { children: MEDIA_SKELETON_CSS })
20965
+ children: /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("style", { children: MEDIA_SKELETON_CSS })
20591
20966
  }
20592
20967
  ),
20593
- activeItemRect && active && activeEntry && /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
20968
+ activeItemRect && active && activeEntry && /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
20594
20969
  ItemInteractionLayer,
20595
20970
  {
20596
20971
  rect: activeItemRect,
20597
20972
  state: toolbarState,
20598
- toolbar: /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
20973
+ toolbar: /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
20599
20974
  CarouselSlideToolbar,
20600
20975
  {
20601
20976
  pending,
@@ -20714,141 +21089,6 @@ function isEditSessionActive() {
20714
21089
  return new URLSearchParams(q).get("mode") === "edit";
20715
21090
  }
20716
21091
 
20717
- // src/ui/link-modal/linkModal.utils.ts
20718
- function parseTarget(target) {
20719
- if (!target) return { pageRoute: "", sectionId: null };
20720
- const hashIndex = target.indexOf("#");
20721
- if (hashIndex === -1) return { pageRoute: target, sectionId: null };
20722
- return {
20723
- pageRoute: target.slice(0, hashIndex),
20724
- sectionId: target.slice(hashIndex + 1) || null
20725
- };
20726
- }
20727
- function normalizePath(path) {
20728
- const trimmed = path.trim();
20729
- if (!trimmed) return "/";
20730
- try {
20731
- if (/^https?:\/\//i.test(trimmed)) {
20732
- return new URL(trimmed).pathname || "/";
20733
- }
20734
- } catch {
20735
- }
20736
- const withSlash = trimmed.startsWith("/") ? trimmed : `/${trimmed}`;
20737
- if (withSlash.length > 1 && withSlash.endsWith("/")) return withSlash.slice(0, -1);
20738
- return withSlash;
20739
- }
20740
- function findPageByPath(pages, path) {
20741
- const normalized = normalizePath(path);
20742
- return pages.find((p) => normalizePath(p.path) === normalized);
20743
- }
20744
- function inferPageTitleFromPath(path) {
20745
- const normalized = normalizePath(path);
20746
- if (normalized === "/") return "Home";
20747
- const segment = normalized.split("/").filter(Boolean).pop() ?? "";
20748
- if (!segment) return "Home";
20749
- return segment.split("-").filter(Boolean).map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join(" ");
20750
- }
20751
- function isInternalPath(route) {
20752
- const trimmed = route.trim();
20753
- if (!trimmed) return false;
20754
- if (/^https?:\/\//i.test(trimmed)) return false;
20755
- if (trimmed.startsWith("/")) return true;
20756
- if (trimmed.includes(" ") || trimmed.includes(".")) return false;
20757
- return true;
20758
- }
20759
- function resolvePage(route, pages) {
20760
- const found = findPageByPath(pages, route);
20761
- if (found) return { path: found.path, title: found.title };
20762
- return { path: normalizePath(route), title: inferPageTitleFromPath(route) };
20763
- }
20764
- function getSectionsForPath(sectionsByPath, pageRoute) {
20765
- if (!sectionsByPath) return [];
20766
- const normalized = normalizePath(pageRoute);
20767
- return sectionsByPath[normalized] ?? sectionsByPath[pageRoute] ?? [];
20768
- }
20769
- function buildTarget(page, section) {
20770
- if (section) return `${page.path}#${section.id}`;
20771
- return page.path;
20772
- }
20773
- function isValidUrl(urlString) {
20774
- try {
20775
- const url = new URL(urlString);
20776
- if (url.protocol !== "http:" && url.protocol !== "https:") return false;
20777
- const hostname = url.hostname;
20778
- if (!hostname || hostname.length === 0) return false;
20779
- if (hostname.includes(" ")) return false;
20780
- if (hostname === "localhost") return true;
20781
- if (!hostname.includes(".")) return false;
20782
- if (!/[a-z]/i.test(hostname)) return false;
20783
- return true;
20784
- } catch {
20785
- return false;
20786
- }
20787
- }
20788
- function normalizeUrl(value) {
20789
- const trimmed = value.trim();
20790
- if (trimmed.startsWith("http://") || trimmed.startsWith("https://")) return trimmed;
20791
- return `https://${trimmed}`;
20792
- }
20793
- function validateUrlInput(value, pages, allPages) {
20794
- const trimmed = value.trim();
20795
- if (!trimmed) return "";
20796
- const exactPage = allPages.find((p) => p.title.toLowerCase() === trimmed.toLowerCase());
20797
- if (exactPage) return "";
20798
- const hasPrefixMatch = pages.some((p) => p.title.toLowerCase().startsWith(trimmed.toLowerCase()));
20799
- if (hasPrefixMatch) {
20800
- return "Select a page from the list or enter a valid URL";
20801
- }
20802
- const hasScheme = /^https?:\/\//i.test(trimmed);
20803
- if (!hasScheme && !trimmed.includes(".")) {
20804
- return "Please enter a valid URL (e.g., https://example.com)";
20805
- }
20806
- if (!isValidUrl(normalizeUrl(trimmed))) {
20807
- return "Please enter a valid URL (e.g., https://example.com)";
20808
- }
20809
- return "";
20810
- }
20811
- function getEditModeInitialState(target, pages, sections) {
20812
- const empty = {
20813
- searchValue: "",
20814
- selectedPage: null,
20815
- selectedSection: null,
20816
- step: "input"
20817
- };
20818
- if (!target) return empty;
20819
- if (isValidUrl(normalizeUrl(target))) {
20820
- return { searchValue: target, selectedPage: null, selectedSection: null, step: "input" };
20821
- }
20822
- const { pageRoute, sectionId } = parseTarget(target);
20823
- if (sectionId) {
20824
- if (isInternalPath(pageRoute)) {
20825
- const page = resolvePage(pageRoute, pages);
20826
- const sec = sections.find((s) => s.id === sectionId);
20827
- return {
20828
- searchValue: page.title,
20829
- selectedPage: page,
20830
- selectedSection: sec ? { id: sec.id, label: sec.label } : null,
20831
- step: "input"
20832
- };
20833
- }
20834
- return { searchValue: pageRoute || target, selectedPage: null, selectedSection: null, step: "input" };
20835
- }
20836
- if (isInternalPath(pageRoute)) {
20837
- const page = resolvePage(pageRoute, pages);
20838
- return {
20839
- searchValue: page.title,
20840
- selectedPage: page,
20841
- selectedSection: null,
20842
- step: "input"
20843
- };
20844
- }
20845
- return { searchValue: target, selectedPage: null, selectedSection: null, step: "input" };
20846
- }
20847
- function filterAvailablePages(pages, existingTargets) {
20848
- const taken = new Set(existingTargets);
20849
- return pages.filter((p) => !taken.has(p.path));
20850
- }
20851
-
20852
21092
  // src/lib/prefetch-sections.ts
20853
21093
  function buildPageFetchUrl(path) {
20854
21094
  const normalized = normalizePath(path);
@@ -20948,113 +21188,6 @@ function scrollToHashSectionWhenReady(behavior = "smooth") {
20948
21188
  // src/ui/link-modal/LinkPopover.tsx
20949
21189
  var import_react17 = require("react");
20950
21190
 
20951
- // src/ui/dialog.tsx
20952
- var React9 = __toESM(require("react"), 1);
20953
- var import_radix_ui6 = require("radix-ui");
20954
- var import_lucide_react12 = require("lucide-react");
20955
- var import_jsx_runtime24 = require("react/jsx-runtime");
20956
- function Dialog2({
20957
- ...props
20958
- }) {
20959
- return /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(import_radix_ui6.Dialog.Root, { "data-slot": "dialog", ...props });
20960
- }
20961
- function DialogPortal({
20962
- ...props
20963
- }) {
20964
- return /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(import_radix_ui6.Dialog.Portal, { ...props });
20965
- }
20966
- function DialogOverlay({
20967
- className,
20968
- ...props
20969
- }) {
20970
- return /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
20971
- import_radix_ui6.Dialog.Overlay,
20972
- {
20973
- "data-slot": "dialog-overlay",
20974
- "data-ohw-link-modal-root": "",
20975
- className: cn("fixed inset-0 z-[2147483646] bg-black/50", className),
20976
- ...props
20977
- }
20978
- );
20979
- }
20980
- var DialogContent = React9.forwardRef(
20981
- ({ className, children, showCloseButton = true, container, ...props }, ref) => {
20982
- const positionMode = container ? "absolute" : "fixed";
20983
- return /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)(DialogPortal, { container: container ?? void 0, children: [
20984
- /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(DialogOverlay, { className: cn(positionMode, "inset-0") }),
20985
- /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)(
20986
- import_radix_ui6.Dialog.Content,
20987
- {
20988
- ref,
20989
- "data-slot": "dialog-content",
20990
- className: cn(
20991
- positionMode,
20992
- "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",
20993
- "rounded-xl border border-border bg-background font-sans shadow-lg outline-none",
20994
- container ? "max-h-[calc(100%-32px)]" : "max-h-[calc(100vh-32px)]",
20995
- className
20996
- ),
20997
- onMouseDown: (e) => e.stopPropagation(),
20998
- ...props,
20999
- children: [
21000
- children,
21001
- showCloseButton ? /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
21002
- import_radix_ui6.Dialog.Close,
21003
- {
21004
- type: "button",
21005
- className: "absolute right-[9px] top-[9px] rounded-sm p-1.5 text-foreground hover:bg-muted/50",
21006
- "aria-label": "Close",
21007
- children: /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(import_lucide_react12.X, { size: 16, "aria-hidden": true })
21008
- }
21009
- ) : null
21010
- ]
21011
- }
21012
- )
21013
- ] });
21014
- }
21015
- );
21016
- DialogContent.displayName = import_radix_ui6.Dialog.Content.displayName;
21017
- function DialogHeader({
21018
- className,
21019
- ...props
21020
- }) {
21021
- return /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("div", { className: cn("flex flex-col gap-1.5", className), ...props });
21022
- }
21023
- function DialogFooter({
21024
- className,
21025
- ...props
21026
- }) {
21027
- return /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
21028
- "div",
21029
- {
21030
- className: cn("flex items-center justify-end gap-2", className),
21031
- ...props
21032
- }
21033
- );
21034
- }
21035
- var DialogTitle = React9.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
21036
- import_radix_ui6.Dialog.Title,
21037
- {
21038
- ref,
21039
- className: cn(
21040
- "text-lg font-semibold leading-none tracking-tight text-card-foreground",
21041
- className
21042
- ),
21043
- ...props
21044
- }
21045
- ));
21046
- DialogTitle.displayName = import_radix_ui6.Dialog.Title.displayName;
21047
- var DialogDescription = React9.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
21048
- import_radix_ui6.Dialog.Description,
21049
- {
21050
- ref,
21051
- className: cn("text-sm text-muted-foreground", className),
21052
- ...props
21053
- }
21054
- ));
21055
- DialogDescription.displayName = import_radix_ui6.Dialog.Description.displayName;
21056
- var DialogClose = import_radix_ui6.Dialog.Close;
21057
-
21058
21191
  // src/ui/link-modal/LinkEditorPanel.tsx
21059
21192
  var import_lucide_react16 = require("lucide-react");
21060
21193
 
@@ -22158,6 +22291,7 @@ function shouldUseDevFixtures() {
22158
22291
  function getLogoElement(el) {
22159
22292
  const marked = el.closest('[data-ohw-role="logo"], [data-ohw-logo]');
22160
22293
  if (marked) return marked;
22294
+ if (el.closest('[data-ohw-editable="icon"]')) return null;
22161
22295
  const root = el.closest("nav, [data-ohw-nav-root], footer");
22162
22296
  if (!root) return null;
22163
22297
  const anchor = el.closest("a");
@@ -23994,6 +24128,7 @@ function applyEditableImageSrc(img, url) {
23994
24128
  img.removeAttribute("srcset");
23995
24129
  img.removeAttribute("sizes");
23996
24130
  if (img.loading === "lazy") img.loading = "eager";
24131
+ releaseTemplateLogoIfReplaced(img, url);
23997
24132
  img.src = url;
23998
24133
  }
23999
24134
  function fadeInBgImage(el, url, onReady) {
@@ -24738,7 +24873,7 @@ function getNavigationSelectionParent(el) {
24738
24873
  if (!isFooterLinksContainer(el) && (el.hasAttribute("data-ohw-footer-col") || el.hasAttribute("data-ohw-footer-column") || isInferredFooterGroup2(el))) {
24739
24874
  return resolveFooterLinksContainerFrom(el);
24740
24875
  }
24741
- if (el.hasAttribute("data-ohw-nav-container") || el.hasAttribute("data-ohw-nav-drawer") || isFooterLinksContainer(el)) {
24876
+ 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)) {
24742
24877
  return getNavigationRoot(el);
24743
24878
  }
24744
24879
  return null;
@@ -24960,7 +25095,6 @@ var ICONS = {
24960
25095
  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"/>',
24961
25096
  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"/>'
24962
25097
  };
24963
- var HEIGHT_SETTLE_DELAYS = [150, 400, 800];
24964
25098
  var SELECTION_CHROME_GAP2 = 4;
24965
25099
  var TOOLBAR_STROKE_GAP2 = 4;
24966
25100
  var TOOLBAR_OFFSET_FROM_ELEMENT = SELECTION_CHROME_GAP2 + TOOLBAR_STROKE_GAP2;
@@ -25058,7 +25192,7 @@ function applyVisibleViewport(el, parentScroll) {
25058
25192
  el.style.top = `${top}px`;
25059
25193
  el.style.height = `${height}px`;
25060
25194
  }
25061
- var TOOLBAR_EDGE_MARGIN3 = 4;
25195
+ var TOOLBAR_EDGE_MARGIN4 = 4;
25062
25196
  function clampToolbarToClip(top, transform, rect, clip, approxH, gap) {
25063
25197
  const isAbove = transform.includes("translateY(-100%)");
25064
25198
  let visualTop = isAbove ? top - approxH : top;
@@ -25085,8 +25219,8 @@ function clampToolbarToClip(top, transform, rect, clip, approxH, gap) {
25085
25219
  function clampToolbarLeft(centerX, toolbarWidth) {
25086
25220
  const half = toolbarWidth / 2;
25087
25221
  return Math.max(
25088
- TOOLBAR_EDGE_MARGIN3 + half,
25089
- Math.min(centerX, window.innerWidth - TOOLBAR_EDGE_MARGIN3 - half)
25222
+ TOOLBAR_EDGE_MARGIN4 + half,
25223
+ Math.min(centerX, window.innerWidth - TOOLBAR_EDGE_MARGIN4 - half)
25090
25224
  );
25091
25225
  }
25092
25226
  function calcToolbarPos(rect, parentScroll, toolbarW = 306) {
@@ -25966,21 +26100,6 @@ function OhhwellsBridge() {
25966
26100
  const [isItemDragging, setIsItemDragging] = (0, import_react21.useState)(false);
25967
26101
  const [isFooterFrameSelection, setIsFooterFrameSelection] = (0, import_react21.useState)(false);
25968
26102
  isFooterFrameSelectionRef.current = isFooterFrameSelection;
25969
- const [floatingPanel, setFloatingPanel] = (0, import_react21.useState)(null);
25970
- const floatingPanelOpenRef = (0, import_react21.useRef)(false);
25971
- floatingPanelOpenRef.current = floatingPanel !== null;
25972
- const floatingPanelRef = (0, import_react21.useRef)(null);
25973
- floatingPanelRef.current = floatingPanel;
25974
- const [floatingPanelPos, setFloatingPanelPos] = (0, import_react21.useState)(null);
25975
- const buttonCollectionDragRef = (0, import_react21.useRef)(null);
25976
- const buttonDropInsertIndexRef = (0, import_react21.useRef)(null);
25977
- const [buttonDropSlots, setButtonDropSlots] = (0, import_react21.useState)([]);
25978
- const [activeButtonDropIndex, setActiveButtonDropIndex] = (0, import_react21.useState)(null);
25979
- const buttonPointerDragRef = (0, import_react21.useRef)(null);
25980
- const [buttonStyleTick, setButtonStyleTick] = (0, import_react21.useState)(0);
25981
- const [logoSizeDraft, setLogoSizeDraft] = (0, import_react21.useState)(null);
25982
- const [editorViewport, setEditorViewport] = (0, import_react21.useState)("desktop");
25983
- const [parentScrollSnap, setParentScrollSnap] = (0, import_react21.useState)(null);
25984
26103
  const [navDropdownPreviewOpen, setNavDropdownPreviewOpen] = (0, import_react21.useState)(null);
25985
26104
  const [footerHeadingVisible, setFooterHeadingVisible] = (0, import_react21.useState)(null);
25986
26105
  const footerDragRef = (0, import_react21.useRef)(null);
@@ -26006,7 +26125,23 @@ function OhhwellsBridge() {
26006
26125
  const stylesRef = (0, import_react21.useRef)("");
26007
26126
  const appliedDrawerRef = (0, import_react21.useRef)(null);
26008
26127
  const pendingDeleteUndoRef = (0, import_react21.useRef)(null);
26128
+ const [floatingPanel, setFloatingPanel] = (0, import_react21.useState)(null);
26129
+ const floatingPanelOpenRef = (0, import_react21.useRef)(false);
26130
+ const floatingPanelRef = (0, import_react21.useRef)(null);
26131
+ floatingPanelRef.current = floatingPanel;
26132
+ const setFloatingPanelRef = (0, import_react21.useRef)(setFloatingPanel);
26133
+ const [floatingPanelPos, setFloatingPanelPos] = (0, import_react21.useState)(null);
26134
+ const buttonCollectionDragRef = (0, import_react21.useRef)(null);
26135
+ const buttonDropInsertIndexRef = (0, import_react21.useRef)(null);
26136
+ const [buttonDropSlots, setButtonDropSlots] = (0, import_react21.useState)([]);
26137
+ const [activeButtonDropIndex, setActiveButtonDropIndex] = (0, import_react21.useState)(null);
26138
+ const buttonPointerDragRef = (0, import_react21.useRef)(null);
26139
+ const [buttonStyleTick, setButtonStyleTick] = (0, import_react21.useState)(0);
26140
+ const [logoSizeDraft, setLogoSizeDraft] = (0, import_react21.useState)(null);
26141
+ const [editorViewport, setEditorViewport] = (0, import_react21.useState)("desktop");
26142
+ const [parentScrollSnap, setParentScrollSnap] = (0, import_react21.useState)(null);
26009
26143
  const [sitePages, setSitePages] = (0, import_react21.useState)([]);
26144
+ const collectionItemsRef = (0, import_react21.useRef)(/* @__PURE__ */ new Map());
26010
26145
  const [sectionsByPath, setSectionsByPath] = (0, import_react21.useState)({});
26011
26146
  const sectionsPrefetchGenRef = (0, import_react21.useRef)(0);
26012
26147
  const setLinkPopoverRef = (0, import_react21.useRef)(setLinkPopover);
@@ -26014,7 +26149,18 @@ function OhhwellsBridge() {
26014
26149
  const linkPopoverOpenRef = (0, import_react21.useRef)(false);
26015
26150
  const linkPopoverGraceUntilRef = (0, import_react21.useRef)(0);
26016
26151
  setLinkPopoverRef.current = setLinkPopover;
26152
+ setFloatingPanelRef.current = setFloatingPanel;
26017
26153
  linkPopoverSessionRef.current = linkPopover;
26154
+ floatingPanelOpenRef.current = Boolean(floatingPanel);
26155
+ (0, import_react21.useEffect)(() => {
26156
+ const syncViewport = () => {
26157
+ const next = window.innerWidth <= 480 ? "mobile" : "desktop";
26158
+ setEditorViewport((prev) => prev === next ? prev : next);
26159
+ };
26160
+ syncViewport();
26161
+ window.addEventListener("resize", syncViewport);
26162
+ return () => window.removeEventListener("resize", syncViewport);
26163
+ }, []);
26018
26164
  (0, import_react21.useEffect)(() => {
26019
26165
  setSectionOrderPersistHandler((entries) => {
26020
26166
  const currentPath = window.location.pathname;
@@ -26150,10 +26296,12 @@ function OhhwellsBridge() {
26150
26296
  const onSitePages = (e) => {
26151
26297
  if (e.data?.type !== "ow:site-pages" || !Array.isArray(e.data.pages)) return;
26152
26298
  if (useFixtures) return;
26153
- const mapped = e.data.pages.map((p) => ({
26299
+ const incoming = e.data.pages;
26300
+ const mapped = incoming.map((p) => ({
26154
26301
  path: p.path,
26155
26302
  title: p.title
26156
26303
  }));
26304
+ collectionItemsRef.current = indexCollectionItems(incoming);
26157
26305
  setSitePages(mapped);
26158
26306
  runSectionsPrefetchRef.current(mapped);
26159
26307
  };
@@ -27709,6 +27857,7 @@ function OhhwellsBridge() {
27709
27857
  applyAiFooterFromContent(content);
27710
27858
  if (typeof content[STYLE_STORE_KEY] === "string") {
27711
27859
  stylesRef.current = content[STYLE_STORE_KEY];
27860
+ document.querySelectorAll('[data-ohw-editable="form"]').forEach((f) => markFormFields(f));
27712
27861
  applyStylesToDom(parseStyleStore(content[STYLE_STORE_KEY]));
27713
27862
  }
27714
27863
  applyBrandChrome(content);
@@ -27719,12 +27868,12 @@ function OhhwellsBridge() {
27719
27868
  if (key === "__ohw_sections") continue;
27720
27869
  if (key === AI_SECTIONS_KEY) continue;
27721
27870
  if (key === AI_FOOTER_KEY) continue;
27871
+ if (key === LOGO_PLACEHOLDER_KEY) continue;
27872
+ if (LOGO_IMAGE_KEYS.includes(key)) continue;
27722
27873
  if (key === BRAND_KIT_KEY) continue;
27723
27874
  if (key === STYLE_STORE_KEY) continue;
27724
27875
  if (BRAND_CHROME_KEYS.has(key)) continue;
27725
- if (key === LOGO_PLACEHOLDER_KEY) continue;
27726
27876
  if (key === CUSTOM_CODE_AFTER_HEAD_KEY || key === CUSTOM_CODE_BEFORE_HEAD_KEY) continue;
27727
- if (LOGO_IMAGE_KEYS.includes(key)) continue;
27728
27877
  if (typeof content[AI_FOOTER_KEY] === "string" && content[AI_FOOTER_KEY] && isAiFooterOwnedContentKey(key)) {
27729
27878
  continue;
27730
27879
  }
@@ -27955,17 +28104,18 @@ function OhhwellsBridge() {
27955
28104
  }
27956
28105
  applyAiFooterFromContent(content);
27957
28106
  if (typeof content[STYLE_STORE_KEY] === "string") {
28107
+ document.querySelectorAll('[data-ohw-editable="form"]').forEach((f) => markFormFields(f));
27958
28108
  applyStylesToDom(parseStyleStore(content[STYLE_STORE_KEY]));
27959
28109
  }
27960
28110
  for (const [key, val] of Object.entries(content)) {
27961
28111
  if (key === "__ohw_sections") continue;
27962
28112
  if (key === AI_SECTIONS_KEY) continue;
27963
28113
  if (key === AI_FOOTER_KEY) continue;
28114
+ if (key === LOGO_PLACEHOLDER_KEY) continue;
28115
+ if (LOGO_IMAGE_KEYS.includes(key)) continue;
27964
28116
  if (key === BRAND_KIT_KEY) continue;
27965
28117
  if (key === STYLE_STORE_KEY) continue;
27966
28118
  if (BRAND_CHROME_KEYS.has(key)) continue;
27967
- if (key === LOGO_PLACEHOLDER_KEY) continue;
27968
- if (LOGO_IMAGE_KEYS.includes(key)) continue;
27969
28119
  if (typeof content[AI_FOOTER_KEY] === "string" && content[AI_FOOTER_KEY] && isAiFooterOwnedContentKey(key)) {
27970
28120
  continue;
27971
28121
  }
@@ -28232,26 +28382,11 @@ function OhhwellsBridge() {
28232
28382
  const t2 = setTimeout(measure, 500);
28233
28383
  const ro = new ResizeObserver(schedule);
28234
28384
  ro.observe(document.body);
28235
- let lastWidth = window.innerWidth;
28236
- let resizeTimers = [];
28237
- const clearResizeTimers = () => {
28238
- resizeTimers.forEach(clearTimeout);
28239
- resizeTimers = [];
28240
- };
28241
- const handleResize = () => {
28242
- if (window.innerWidth === lastWidth) return;
28243
- lastWidth = window.innerWidth;
28244
- clearResizeTimers();
28245
- resizeTimers = HEIGHT_SETTLE_DELAYS.map((delay) => setTimeout(measure, delay));
28246
- };
28247
- window.addEventListener("resize", handleResize);
28248
28385
  return () => {
28249
28386
  clearTimeout(t1);
28250
28387
  clearTimeout(t2);
28251
28388
  if (raf != null) cancelAnimationFrame(raf);
28252
28389
  ro.disconnect();
28253
- clearResizeTimers();
28254
- window.removeEventListener("resize", handleResize);
28255
28390
  };
28256
28391
  }, [pathname, isEditMode, postToParent2]);
28257
28392
  (0, import_react21.useEffect)(() => {
@@ -28548,6 +28683,13 @@ function OhhwellsBridge() {
28548
28683
  if (target.closest("[data-ohw-state-toggle]")) return;
28549
28684
  if (target.closest("[data-ohw-max-badge]")) return;
28550
28685
  if (isInsideLinkEditor(target)) return;
28686
+ if (isInsideFloatingPanel(target)) return;
28687
+ if (target.closest("[data-ohw-form-toolbar]")) return;
28688
+ if (target.closest(
28689
+ '[data-ohw-field-toolbar], [data-ohw-field-type-picker], [data-radix-popper-content-wrapper], [role="menu"], [data-slot="dropdown-menu-content"]'
28690
+ )) {
28691
+ return;
28692
+ }
28551
28693
  if (selectedMediaElRef.current && !selectedMediaElRef.current.contains(target) && !target.closest("[data-ohw-media-overlay], [data-ohw-edit-chrome], [data-ohw-item-interaction]")) {
28552
28694
  clearMediaSelectionRef.current();
28553
28695
  }
@@ -28571,11 +28713,15 @@ function OhhwellsBridge() {
28571
28713
  return;
28572
28714
  }
28573
28715
  }
28574
- if (isInsideFloatingPanel(target)) return;
28575
- if (target.closest("[data-ohw-form-toolbar]")) return;
28576
- if (target.closest(
28577
- '[data-ohw-field-toolbar], [data-ohw-field-type-picker], [data-radix-popper-content-wrapper], [role="menu"], [data-slot="dropdown-menu-content"]'
28578
- )) {
28716
+ const collectionItem = matchCollectionItemClick(target, collectionItemsRef.current);
28717
+ if (collectionItem) {
28718
+ e.preventDefault();
28719
+ e.stopPropagation();
28720
+ postToParentRef.current({
28721
+ type: "ow:collection-item-selected",
28722
+ path: collectionItem.path,
28723
+ collection: collectionItem.collection
28724
+ });
28579
28725
  return;
28580
28726
  }
28581
28727
  {
@@ -28731,9 +28877,13 @@ function OhhwellsBridge() {
28731
28877
  if (isIconEditable(editable) && !getSocialItem(editable)) {
28732
28878
  e.preventDefault();
28733
28879
  e.stopPropagation();
28734
- selectIconElementRef.current(editable);
28735
28880
  const aiIcon = editable.hasAttribute("data-ai-icon");
28736
28881
  const featureIcon = aiIcon && /\.features\.\d+\.icon$/.test(editable.dataset.ohwKey ?? "");
28882
+ if (aiIcon) {
28883
+ aiSectionApiRef.current?.selectFromElement(editable);
28884
+ } else {
28885
+ selectIconElementRef.current(editable);
28886
+ }
28737
28887
  postToParentRef.current({
28738
28888
  type: "ow:icon-pick",
28739
28889
  key: editable.dataset.ohwKey ?? "",
@@ -28961,6 +29111,9 @@ function OhhwellsBridge() {
28961
29111
  setHoveredItemRect(null);
28962
29112
  hoveredNavContainerRef.current = null;
28963
29113
  setHoveredNavContainerRect(null);
29114
+ siblingHintElRef.current = null;
29115
+ setSiblingHintRect(null);
29116
+ setSiblingHintRects([]);
28964
29117
  return;
28965
29118
  }
28966
29119
  {
@@ -29081,7 +29234,6 @@ function OhhwellsBridge() {
29081
29234
  hoveredNavContainerRef.current = null;
29082
29235
  setHoveredNavContainerRect(null);
29083
29236
  hoveredItemElRef.current = editable;
29084
- setHoveredItemRect(isSelectedForHoverRef.current(editable) ? null : editable.getBoundingClientRect());
29085
29237
  }
29086
29238
  }
29087
29239
  }
@@ -29791,7 +29943,11 @@ function OhhwellsBridge() {
29791
29943
  };
29792
29944
  const handleMouseMove = (e) => {
29793
29945
  const { clientX, clientY } = e;
29794
- if (pointOwnedByFloatingPanel(clientX, clientY)) return;
29946
+ if (pointOwnedByFloatingPanel(clientX, clientY)) {
29947
+ dismissImageHover();
29948
+ clearImageHover();
29949
+ return;
29950
+ }
29795
29951
  if (isNavDrawerOpen() && isPointOutsideOpenDrawer(clientX, clientY)) {
29796
29952
  document.querySelectorAll("[data-ohw-hovered]").forEach((el) => el.removeAttribute("data-ohw-hovered"));
29797
29953
  formHoverElRef.current = null;
@@ -29820,7 +29976,12 @@ function OhhwellsBridge() {
29820
29976
  setHoveredItemRect(null);
29821
29977
  hoveredNavContainerRef.current = null;
29822
29978
  setHoveredNavContainerRect(null);
29979
+ siblingHintElRef.current = null;
29980
+ setSiblingHintRect(null);
29981
+ setSiblingHintRects([]);
29823
29982
  dismissImageHover();
29983
+ clearImageHover();
29984
+ setSectionGap(null);
29824
29985
  return;
29825
29986
  }
29826
29987
  if (probeSocialsRowAt(clientX, clientY)) return;
@@ -29832,7 +29993,11 @@ function OhhwellsBridge() {
29832
29993
  if (e.data?.type !== "ow:pointer-sync") return;
29833
29994
  const { clientX, clientY } = e.data;
29834
29995
  if (typeof clientX !== "number" || typeof clientY !== "number") return;
29835
- if (pointOwnedByFloatingPanel(clientX, clientY)) return;
29996
+ if (pointOwnedByFloatingPanel(clientX, clientY)) {
29997
+ dismissImageHover();
29998
+ clearImageHover();
29999
+ return;
30000
+ }
29836
30001
  if (isNavDrawerOpen() && isPointOutsideOpenDrawer(clientX, clientY)) return;
29837
30002
  if (probeSocialsRowAt(clientX, clientY)) return;
29838
30003
  probeSectionGapAt(clientX, clientY);
@@ -30212,6 +30377,7 @@ function OhhwellsBridge() {
30212
30377
  applyAiFooterFromContent(content);
30213
30378
  if (typeof content[STYLE_STORE_KEY] === "string") {
30214
30379
  stylesRef.current = content[STYLE_STORE_KEY];
30380
+ document.querySelectorAll('[data-ohw-editable="form"]').forEach((f) => markFormFields(f));
30215
30381
  applyStylesToDom(parseStyleStore(content[STYLE_STORE_KEY]));
30216
30382
  }
30217
30383
  applyBrandChrome(content);
@@ -30226,12 +30392,12 @@ function OhhwellsBridge() {
30226
30392
  }
30227
30393
  if (key === AI_SECTIONS_KEY) continue;
30228
30394
  if (key === AI_FOOTER_KEY) continue;
30395
+ if (key === LOGO_PLACEHOLDER_KEY) continue;
30396
+ if (LOGO_IMAGE_KEYS.includes(key) && !String(val ?? "").trim()) continue;
30229
30397
  if (key === BRAND_KIT_KEY) continue;
30230
30398
  if (key === STYLE_STORE_KEY) continue;
30231
30399
  if (BRAND_CHROME_KEYS.has(key)) continue;
30232
- if (key === LOGO_PLACEHOLDER_KEY) continue;
30233
30400
  if (key === CUSTOM_CODE_AFTER_HEAD_KEY || key === CUSTOM_CODE_BEFORE_HEAD_KEY) continue;
30234
- if (LOGO_IMAGE_KEYS.includes(key) && !String(val ?? "").trim()) continue;
30235
30401
  if (typeof content[AI_FOOTER_KEY] === "string" && content[AI_FOOTER_KEY] && isAiFooterOwnedContentKey(key)) {
30236
30402
  continue;
30237
30403
  }
@@ -30867,6 +31033,12 @@ function OhhwellsBridge() {
30867
31033
  closeLinkPopoverRef.current();
30868
31034
  return;
30869
31035
  }
31036
+ if (floatingPanelOpenRef.current) {
31037
+ setFloatingPanelRef.current(null);
31038
+ deselectRef.current();
31039
+ deactivateRef.current();
31040
+ return;
31041
+ }
30870
31042
  deselectRef.current();
30871
31043
  deactivateRef.current();
30872
31044
  clearMediaSelectionRef.current();
@@ -31696,10 +31868,10 @@ function OhhwellsBridge() {
31696
31868
  window.removeEventListener("message", handleDuplicateSection);
31697
31869
  window.removeEventListener("message", handleDeactivate);
31698
31870
  window.removeEventListener("message", handleSelectNav);
31699
- document.documentElement.removeAttribute("data-ohw-panel-dragging");
31700
31871
  window.removeEventListener("message", handleToastAction);
31701
31872
  window.removeEventListener("message", handleFormCount);
31702
31873
  window.removeEventListener("message", handleUiEscape);
31874
+ document.documentElement.removeAttribute("data-ohw-panel-dragging");
31703
31875
  autoSaveTimers.current.forEach(clearTimeout);
31704
31876
  autoSaveTimers.current.clear();
31705
31877
  if (imageUnhoverTimerRef.current) clearTimeout(imageUnhoverTimerRef.current);
@@ -31993,7 +32165,7 @@ function OhhwellsBridge() {
31993
32165
  postToParent2({
31994
32166
  type: "ow:ready",
31995
32167
  version: "1",
31996
- bridgeVersion: "0.1.155",
32168
+ bridgeVersion: "0.1.157-next.420",
31997
32169
  path: pathname,
31998
32170
  nodes: collectEditableNodes(editContentRef.current),
31999
32171
  sections
@@ -33200,6 +33372,7 @@ function EmptySection({ title, homeHref = "/", eyebrowKey, titleKey, subtitleKey
33200
33372
  OhwLoaderSurface,
33201
33373
  SITE_OG_IMAGE_KEY,
33202
33374
  SchedulingWidget,
33375
+ TEMPLATE_LOGO_ATTR,
33203
33376
  Toggle,
33204
33377
  ToggleGroup,
33205
33378
  ToggleGroupItem,
@@ -33219,6 +33392,7 @@ function EmptySection({ title, homeHref = "/", eyebrowKey, titleKey, subtitleKey
33219
33392
  foldAlignIntoTrees,
33220
33393
  getEditModeInitialState,
33221
33394
  hasMountedContentNodes,
33395
+ indexCollectionItems,
33222
33396
  insertFooterHours,
33223
33397
  isEditSessionActive,
33224
33398
  isPlainLeftClick,
@@ -33226,11 +33400,13 @@ function EmptySection({ title, homeHref = "/", eyebrowKey, titleKey, subtitleKey
33226
33400
  isRenderableTree,
33227
33401
  isStickySearchParam,
33228
33402
  isValidUrl,
33403
+ matchCollectionItemClick,
33229
33404
  nextViableSlide,
33230
33405
  parseAiFooterState,
33231
33406
  parseAiSectionsState,
33232
33407
  parseTarget,
33233
33408
  recordOverriddenPaths,
33409
+ releaseTemplateLogoIfReplaced,
33234
33410
  removeFooterBlock,
33235
33411
  resetOverriddenPathsCache,
33236
33412
  resolveFooterCopyright,