@ohhwells/bridge 0.1.152 → 0.1.153-next.417

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  "use client";
2
2
 
3
3
  // src/OhhwellsBridge.tsx
4
- import React12, { useCallback as useCallback12, useEffect as useEffect17, useLayoutEffect as useLayoutEffect6, useRef as useRef14, useState as useState18 } from "react";
4
+ import React12, { useCallback as useCallback12, useEffect as useEffect17, useLayoutEffect as useLayoutEffect7, useRef as useRef14, useState as useState18 } from "react";
5
5
  import { createRoot as createRoot3 } from "react-dom/client";
6
6
  import { flushSync as flushSync3 } from "react-dom";
7
7
 
@@ -6542,6 +6542,21 @@ function pricingCardFeatureCount(tree, index) {
6542
6542
  const target = findPricingCollection(tree)?.children?.[index];
6543
6543
  return target ? pricingFeatureList(target).length : 0;
6544
6544
  }
6545
+ function prevailingFeatureIcon(features) {
6546
+ const counts = /* @__PURE__ */ new Map();
6547
+ for (const row of features) {
6548
+ const key = `${row.icon ?? ""}|${row.iconColor ?? ""}`;
6549
+ const entry = counts.get(key) ?? { count: 0, ...row.icon ? { icon: row.icon } : {}, ...row.iconColor ? { iconColor: row.iconColor } : {} };
6550
+ entry.count += 1;
6551
+ counts.set(key, entry);
6552
+ }
6553
+ let best = null;
6554
+ for (const entry of counts.values()) {
6555
+ if (!best || entry.count > best.count) best = entry;
6556
+ }
6557
+ if (!best) return {};
6558
+ return { ...best.icon ? { icon: best.icon } : {}, ...best.iconColor ? { iconColor: best.iconColor } : {} };
6559
+ }
6545
6560
  function addPricingCardFeature(tree, index, text) {
6546
6561
  return withPricingCollection(tree, (collection) => {
6547
6562
  const children = collection.children;
@@ -6550,7 +6565,7 @@ function addPricingCardFeature(tree, index, text) {
6550
6565
  const features = pricingFeatureList(target);
6551
6566
  if (features.length >= PRICING_FEATURES_MAX) return false;
6552
6567
  target.slots = target.slots ?? {};
6553
- target.slots.features = [...features, { text }];
6568
+ target.slots.features = [...features, { text, ...prevailingFeatureIcon(features) }];
6554
6569
  return true;
6555
6570
  });
6556
6571
  }
@@ -7161,6 +7176,21 @@ function applyCustomCodeToHead(content) {
7161
7176
  if (beforeNodes.length > 0) document.head.append(...beforeNodes);
7162
7177
  }
7163
7178
 
7179
+ // src/lib/selectNavContainer.ts
7180
+ function isOnScreen(el) {
7181
+ const rect = el.getBoundingClientRect();
7182
+ if (rect.width <= 0 || rect.height <= 0) return false;
7183
+ if (el.offsetParent !== null) return true;
7184
+ return getComputedStyle(el).position === "fixed";
7185
+ }
7186
+ function findSelectableNavContainer(doc = document) {
7187
+ const visible = Array.from(doc.querySelectorAll("[data-ohw-nav-container]")).filter(isOnScreen);
7188
+ if (visible.length === 0) return null;
7189
+ return visible.reduce(
7190
+ (best, el) => el.getBoundingClientRect().top < best.getBoundingClientRect().top ? el : best
7191
+ );
7192
+ }
7193
+
7164
7194
  // src/ui/ai-tree/aiSectionsManager.tsx
7165
7195
  import { flushSync } from "react-dom";
7166
7196
  import { createRoot } from "react-dom/client";
@@ -7734,7 +7764,7 @@ var AI_MOBILE_CSS = [
7734
7764
  // page's first section. Top only (bottom needs no nav clearance); ~96px approximates the
7735
7765
  // template nav's own height (measured ~94px) — there's no shared `--ohw-header-h` var to read
7736
7766
  // an exact figure from (no live measurement exists), so this is a safe approximation, not a
7737
- // pixel-exact fit. Horizontal spacing already comes from `data-ai-container`.
7767
+ // pixel-exact fit. Horizontal spacing already comes from `data-ai-section-inner`.
7738
7768
  '[data-ai-overlay-clearance] [data-ai-overlap-layer="overlay"]{padding-top:96px}',
7739
7769
  // A full-bleed carousel's height is pure arithmetic: it spans the whole window, so 16/9 turns
7740
7770
  // window width straight into height. Measured at 16/9 it came out ~90% of the viewport at EVERY
@@ -7750,8 +7780,10 @@ var AI_MOBILE_CSS = [
7750
7780
  "[data-ai-section]{overflow-x:hidden}",
7751
7781
  // `:not([data-ai-full-bleed])`: this `!important` gutter beat the inline `padding: 0` that a
7752
7782
  // full-bleed section's container sets, so a carousel/media hero that is edge-to-edge on desktop
7753
- // still got a 20px band of section background down both sides on a phone.
7754
- "[data-ai-container]:not([data-ai-full-bleed]){padding:0 20px !important}",
7783
+ // still got a 20px band of section background down both sides on a phone. The footer's own
7784
+ // container keeps the unguarded rule below — `data-ai-full-bleed` can never appear on it.
7785
+ "[data-ai-section-inner]:not([data-ai-full-bleed]){padding:0 20px !important}",
7786
+ "[data-ai-container]{padding:0 20px !important}",
7755
7787
  "[data-ai-row]{display:flex !important;flex-direction:column !important;align-items:stretch !important}",
7756
7788
  "[data-ai-cell]{width:100%;min-width:0}",
7757
7789
  // The rule above turns every row into a COLUMN flex container on phones, which silently breaks
@@ -7956,6 +7988,31 @@ function pricingCardEmphasis(featured, ctx, hasBg) {
7956
7988
  function textAttrs(ctx, path) {
7957
7989
  return ctx.keyFor ? { "data-ohw-key": ctx.keyFor(path), "data-ohw-editable": "text" } : {};
7958
7990
  }
7991
+ var AI_RESPONSIVE_CSS = [
7992
+ "@media (max-width: 960px) {",
7993
+ " [data-ai-responsive] [data-ai-grid] { grid-template-columns: repeat(2, 1fr) !important; }",
7994
+ ' [data-ai-responsive] [data-ai-grid="1"] { grid-template-columns: 1fr !important; }',
7995
+ "}",
7996
+ "@media (max-width: 640px) {",
7997
+ " [data-ai-responsive] [data-ai-grid] { grid-template-columns: 1fr !important; }",
7998
+ " [data-ai-responsive] [data-ai-columns] > * { grid-column: 1 / -1 !important; }",
7999
+ // Group containers flatten to a column on phones; span placements come along for free.
8000
+ // `:not([data-ai-group="overlap"])` for the same reason as the twin pair in AI_MOBILE_CSS — see
8001
+ // the comment there. This sheet's copy is the narrower selector, so without the exemption it
8002
+ // flattens an `overlap` hero below 640px even though the other rule stands down: measured a 390px
8003
+ // viewport with the heading/CTA in flow *under* the image, 219px of section background where the
8004
+ // layered text used to be.
8005
+ ' [data-ai-responsive] [data-ai-group]:not([data-ai-group="overlap"]) { display: flex !important; flex-direction: column !important; }',
8006
+ ' [data-ai-responsive] [data-ai-group]:not([data-ai-group="overlap"]) > * { grid-column: auto !important; }',
8007
+ // `:not([data-ai-full-bleed])` for the same reason as the twin rule in AI_MOBILE_CSS: this one
8008
+ // is narrower (0,2,0 vs 0,1,0) and also `!important`, so without the guard it wins outright and
8009
+ // a full-bleed hero gets its 20px side band back below 640px only — the one width where the
8010
+ // other rule had already been taught to stand down.
8011
+ " [data-ai-responsive] [data-ai-section-inner]:not([data-ai-full-bleed]) { padding-left: 20px !important; padding-right: 20px !important; }",
8012
+ " [data-ai-responsive] { overflow-x: hidden; }",
8013
+ " [data-ai-responsive] img { max-width: 100%; }",
8014
+ "}"
8015
+ ].join("\n");
7959
8016
  var TRANSPARENT_PX = "data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7";
7960
8017
  function MediaBox({
7961
8018
  refValue,
@@ -8010,7 +8067,7 @@ function MediaBox({
8010
8067
  position: fill ? "absolute" : void 0,
8011
8068
  inset: fill ? 0 : void 0,
8012
8069
  width: fill ? "100%" : fixedSize ? "auto" : "100%",
8013
- height: fill ? "100%" : void 0,
8070
+ height: fill ? "100%" : fixedSize ? height : void 0,
8014
8071
  aspectRatio: fill ? void 0 : aspectRatio,
8015
8072
  minHeight: fill || fixedSize ? void 0 : height ?? (aspectRatio ? void 0 : 180),
8016
8073
  borderRadius: radius,
@@ -9506,7 +9563,7 @@ function CollectionBlock({ node, ctx, path }) {
9506
9563
  return /* @__PURE__ */ jsx(
9507
9564
  "div",
9508
9565
  {
9509
- "data-ai-grid": "",
9566
+ "data-ai-grid": String(itemsPerRow),
9510
9567
  "data-ohw-pricing-collection": isPricing ? "" : void 0,
9511
9568
  style: {
9512
9569
  display: "grid",
@@ -9808,7 +9865,7 @@ function renderNode(node, ctx, path, ambientAlign) {
9808
9865
  },
9809
9866
  children: [
9810
9867
  i > 0 && ctx.heroOverlapFullBleed ? (
9811
- // The section's `data-ai-container` gave up its max-width/gutter so the base
9868
+ // The section's `data-ai-section-inner` gave up its max-width/gutter so the base
9812
9869
  // layer (this group's i===0 sibling) can span edge-to-edge — reintroduced here,
9813
9870
  // scoped to just the overlay text, so a heading/copy/CTA stack doesn't stretch
9814
9871
  // the full section width instead of reading at the usual content width.
@@ -10227,6 +10284,7 @@ function AiTreeRenderer({
10227
10284
  ...fillHeight ? { "data-ai-fill-height": "" } : {},
10228
10285
  ...needsOverlayClearance ? { "data-ai-overlay-clearance": "" } : {},
10229
10286
  ...bgAttrs,
10287
+ "data-ai-responsive": "",
10230
10288
  style: {
10231
10289
  position: "relative",
10232
10290
  padding: `${padCss} 0`,
@@ -10240,12 +10298,13 @@ function AiTreeRenderer({
10240
10298
  ...fillHeight ? { display: "grid" } : {}
10241
10299
  },
10242
10300
  children: [
10243
- isOverlay && backgroundUrl && /* @__PURE__ */ jsx("div", { style: { position: "absolute", inset: 0, background: `rgba(255,255,255,${overlayAlpha})` } }),
10301
+ /* @__PURE__ */ jsx("style", { children: AI_RESPONSIVE_CSS }),
10244
10302
  /* @__PURE__ */ jsx("style", { children: AI_MOBILE_CSS }),
10303
+ isOverlay && backgroundUrl && /* @__PURE__ */ jsx("div", { style: { position: "absolute", inset: 0, background: `rgba(255,255,255,${overlayAlpha})` } }),
10245
10304
  /* @__PURE__ */ jsx(
10246
10305
  "div",
10247
10306
  {
10248
- "data-ai-container": "",
10307
+ "data-ai-section-inner": "",
10249
10308
  ...fullBleedMedia ? { "data-ai-full-bleed": "" } : {},
10250
10309
  style: {
10251
10310
  position: "relative",
@@ -10262,7 +10321,7 @@ function AiTreeRenderer({
10262
10321
  children: tree.rows.map((row, r2) => /* @__PURE__ */ jsx(
10263
10322
  "div",
10264
10323
  {
10265
- "data-ai-row": "",
10324
+ "data-ai-columns": "",
10266
10325
  style: {
10267
10326
  display: "grid",
10268
10327
  gridTemplateColumns: "repeat(12, 1fr)",
@@ -10924,9 +10983,10 @@ function applyAiSectionsToDom(state, options) {
10924
10983
  if (existing && existing.serialized === serialized && existing.container.isConnected) {
10925
10984
  continue;
10926
10985
  }
10927
- const isNewMount = !existing || !existing.container.isConnected;
10928
10986
  let container = existing?.container;
10987
+ let isNewMount = false;
10929
10988
  if (!container || !container.isConnected) {
10989
+ isNewMount = true;
10930
10990
  existing?.root.unmount();
10931
10991
  container?.remove();
10932
10992
  container = document.createElement("div");
@@ -11570,6 +11630,172 @@ function useLinkHrefGuardian(...deps) {
11570
11630
  }, deps);
11571
11631
  }
11572
11632
 
11633
+ // src/ui/link-modal/linkModal.utils.ts
11634
+ function parseTarget(target) {
11635
+ if (!target) return { pageRoute: "", sectionId: null };
11636
+ const hashIndex = target.indexOf("#");
11637
+ if (hashIndex === -1) return { pageRoute: target, sectionId: null };
11638
+ return {
11639
+ pageRoute: target.slice(0, hashIndex),
11640
+ sectionId: target.slice(hashIndex + 1) || null
11641
+ };
11642
+ }
11643
+ function normalizePath(path) {
11644
+ const trimmed = path.trim();
11645
+ if (!trimmed) return "/";
11646
+ try {
11647
+ if (/^https?:\/\//i.test(trimmed)) {
11648
+ return new URL(trimmed).pathname || "/";
11649
+ }
11650
+ } catch {
11651
+ }
11652
+ const withSlash = trimmed.startsWith("/") ? trimmed : `/${trimmed}`;
11653
+ if (withSlash.length > 1 && withSlash.endsWith("/")) return withSlash.slice(0, -1);
11654
+ return withSlash;
11655
+ }
11656
+ function findPageByPath(pages, path) {
11657
+ const normalized = normalizePath(path);
11658
+ return pages.find((p) => normalizePath(p.path) === normalized);
11659
+ }
11660
+ function inferPageTitleFromPath(path) {
11661
+ const normalized = normalizePath(path);
11662
+ if (normalized === "/") return "Home";
11663
+ const segment = normalized.split("/").filter(Boolean).pop() ?? "";
11664
+ if (!segment) return "Home";
11665
+ return segment.split("-").filter(Boolean).map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join(" ");
11666
+ }
11667
+ function isInternalPath(route) {
11668
+ const trimmed = route.trim();
11669
+ if (!trimmed) return false;
11670
+ if (/^https?:\/\//i.test(trimmed)) return false;
11671
+ if (trimmed.startsWith("/")) return true;
11672
+ if (trimmed.includes(" ") || trimmed.includes(".")) return false;
11673
+ return true;
11674
+ }
11675
+ function resolvePage(route, pages) {
11676
+ const found = findPageByPath(pages, route);
11677
+ if (found) return { path: found.path, title: found.title };
11678
+ return { path: normalizePath(route), title: inferPageTitleFromPath(route) };
11679
+ }
11680
+ function getSectionsForPath(sectionsByPath, pageRoute) {
11681
+ if (!sectionsByPath) return [];
11682
+ const normalized = normalizePath(pageRoute);
11683
+ return sectionsByPath[normalized] ?? sectionsByPath[pageRoute] ?? [];
11684
+ }
11685
+ function buildTarget(page, section) {
11686
+ if (section) return `${page.path}#${section.id}`;
11687
+ return page.path;
11688
+ }
11689
+ function isValidUrl(urlString) {
11690
+ try {
11691
+ const url = new URL(urlString);
11692
+ if (url.protocol !== "http:" && url.protocol !== "https:") return false;
11693
+ const hostname = url.hostname;
11694
+ if (!hostname || hostname.length === 0) return false;
11695
+ if (hostname.includes(" ")) return false;
11696
+ if (hostname === "localhost") return true;
11697
+ if (!hostname.includes(".")) return false;
11698
+ if (!/[a-z]/i.test(hostname)) return false;
11699
+ return true;
11700
+ } catch {
11701
+ return false;
11702
+ }
11703
+ }
11704
+ function normalizeUrl(value) {
11705
+ const trimmed = value.trim();
11706
+ if (trimmed.startsWith("http://") || trimmed.startsWith("https://")) return trimmed;
11707
+ return `https://${trimmed}`;
11708
+ }
11709
+ function validateUrlInput(value, pages, allPages) {
11710
+ const trimmed = value.trim();
11711
+ if (!trimmed) return "";
11712
+ const exactPage = allPages.find((p) => p.title.toLowerCase() === trimmed.toLowerCase());
11713
+ if (exactPage) return "";
11714
+ const hasPrefixMatch = pages.some((p) => p.title.toLowerCase().startsWith(trimmed.toLowerCase()));
11715
+ if (hasPrefixMatch) {
11716
+ return "Select a page from the list or enter a valid URL";
11717
+ }
11718
+ const hasScheme = /^https?:\/\//i.test(trimmed);
11719
+ if (!hasScheme && !trimmed.includes(".")) {
11720
+ return "Please enter a valid URL (e.g., https://example.com)";
11721
+ }
11722
+ if (!isValidUrl(normalizeUrl(trimmed))) {
11723
+ return "Please enter a valid URL (e.g., https://example.com)";
11724
+ }
11725
+ return "";
11726
+ }
11727
+ function getEditModeInitialState(target, pages, sections) {
11728
+ const empty = {
11729
+ searchValue: "",
11730
+ selectedPage: null,
11731
+ selectedSection: null,
11732
+ step: "input"
11733
+ };
11734
+ if (!target) return empty;
11735
+ if (isValidUrl(normalizeUrl(target))) {
11736
+ return { searchValue: target, selectedPage: null, selectedSection: null, step: "input" };
11737
+ }
11738
+ const { pageRoute, sectionId } = parseTarget(target);
11739
+ if (sectionId) {
11740
+ if (isInternalPath(pageRoute)) {
11741
+ const page = resolvePage(pageRoute, pages);
11742
+ const sec = sections.find((s) => s.id === sectionId);
11743
+ return {
11744
+ searchValue: page.title,
11745
+ selectedPage: page,
11746
+ selectedSection: sec ? { id: sec.id, label: sec.label } : null,
11747
+ step: "input"
11748
+ };
11749
+ }
11750
+ return { searchValue: pageRoute || target, selectedPage: null, selectedSection: null, step: "input" };
11751
+ }
11752
+ if (isInternalPath(pageRoute)) {
11753
+ const page = resolvePage(pageRoute, pages);
11754
+ return {
11755
+ searchValue: page.title,
11756
+ selectedPage: page,
11757
+ selectedSection: null,
11758
+ step: "input"
11759
+ };
11760
+ }
11761
+ return { searchValue: target, selectedPage: null, selectedSection: null, step: "input" };
11762
+ }
11763
+ function filterAvailablePages(pages, existingTargets) {
11764
+ const taken = new Set(existingTargets);
11765
+ return pages.filter((p) => !taken.has(p.path));
11766
+ }
11767
+
11768
+ // src/lib/collection-item-click.ts
11769
+ function indexCollectionItems(pages) {
11770
+ const index = /* @__PURE__ */ new Map();
11771
+ for (const page of pages) {
11772
+ if (page.kind !== "COLLECTION_ITEM" || !page.collection || !page.path) continue;
11773
+ index.set(normalizePath(page.path), { path: page.path, collection: page.collection });
11774
+ }
11775
+ return index;
11776
+ }
11777
+ function pathOfHref(href) {
11778
+ const trimmed = href.trim();
11779
+ if (!trimmed || trimmed.startsWith("#")) return null;
11780
+ if (/^[a-z][a-z0-9+.-]*:/i.test(trimmed) && !/^https?:/i.test(trimmed)) return null;
11781
+ const withoutHash = trimmed.split("#")[0];
11782
+ const withoutQuery = withoutHash.split("?")[0];
11783
+ if (!withoutQuery) return null;
11784
+ return normalizePath(withoutQuery);
11785
+ }
11786
+ function matchCollectionItemClick(target, index) {
11787
+ if (!target || index.size === 0) return null;
11788
+ const text = target.closest('[data-ohw-editable="text"], [data-ohw-editable="plain"]');
11789
+ if (!text) return null;
11790
+ const anchor = text.closest("a");
11791
+ if (!anchor || anchor.closest(CTA_BUTTON_SELECTOR)) return null;
11792
+ const href = anchor.getAttribute("href");
11793
+ if (!href) return null;
11794
+ const path = pathOfHref(href);
11795
+ if (!path) return null;
11796
+ return index.get(path) ?? null;
11797
+ }
11798
+
11573
11799
  // src/lib/sticky-search-params.ts
11574
11800
  var STICKY_EXACT = /* @__PURE__ */ new Set([
11575
11801
  // Site identity. Required in local dev and in the canvas editor's iframe, where there is no
@@ -16910,6 +17136,107 @@ function bindPublishedForms(apiUrl, subdomain, content = {}) {
16910
17136
  );
16911
17137
  }
16912
17138
 
17139
+ // src/lib/outage-beacon.ts
17140
+ var DEDUP_MS = 5 * 60 * 1e3;
17141
+ var lastSent = /* @__PURE__ */ new Map();
17142
+ function apiBase() {
17143
+ return process.env.NEXT_PUBLIC_FLOWOPS_API_URL ?? "https://flowops-backend-staging-2yfdh7pwpq-as.a.run.app";
17144
+ }
17145
+ function bridgeVersion() {
17146
+ try {
17147
+ if (true) {
17148
+ return "0.1.153-next.417";
17149
+ }
17150
+ } catch {
17151
+ }
17152
+ if (typeof document !== "undefined") {
17153
+ return document.documentElement.getAttribute("data-ohw-bridge-version") || void 0;
17154
+ }
17155
+ return void 0;
17156
+ }
17157
+ function stampBridgeVersionOnDocument() {
17158
+ if (typeof document === "undefined") {
17159
+ return;
17160
+ }
17161
+ try {
17162
+ const v = true ? "0.1.153-next.417" : "";
17163
+ if (v) {
17164
+ document.documentElement.setAttribute("data-ohw-bridge-version", v);
17165
+ window.__OHW_BRIDGE_VERSION__ = v;
17166
+ }
17167
+ } catch {
17168
+ }
17169
+ }
17170
+ 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;
17171
+ 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;
17172
+ function isVercelPreviewHost() {
17173
+ if (typeof location === "undefined") {
17174
+ return false;
17175
+ }
17176
+ return location.hostname.toLowerCase().endsWith(".vercel.ohhwells.site");
17177
+ }
17178
+ function reportOutageBeacon(args) {
17179
+ if (typeof window === "undefined" || !args.subdomain) {
17180
+ return;
17181
+ }
17182
+ if (isVercelPreviewHost()) {
17183
+ return;
17184
+ }
17185
+ if (args.reason === "page_error" && args.detail && RECOVERABLE_PAGE_ERROR.test(args.detail)) {
17186
+ return;
17187
+ }
17188
+ if (args.reason === "content_fetch_failed" && args.detail && TRANSIENT_CONTENT_FETCH.test(args.detail.trim())) {
17189
+ return;
17190
+ }
17191
+ const key = `${args.subdomain}|${args.reason}`;
17192
+ const now = Date.now();
17193
+ const prev = lastSent.get(key) || 0;
17194
+ if (now - prev < DEDUP_MS) {
17195
+ return;
17196
+ }
17197
+ lastSent.set(key, now);
17198
+ const body = JSON.stringify({
17199
+ subdomain: args.subdomain,
17200
+ reason: args.reason,
17201
+ detail: args.detail?.slice(0, 300),
17202
+ templateId: args.templateId,
17203
+ bridgeVersion: bridgeVersion(),
17204
+ href: typeof location !== "undefined" ? location.href : void 0
17205
+ });
17206
+ const url = `${apiBase()}/api/public/sites/outage-beacon`;
17207
+ try {
17208
+ if (typeof navigator !== "undefined" && typeof navigator.sendBeacon === "function") {
17209
+ const blob = new Blob([body], { type: "application/json" });
17210
+ if (navigator.sendBeacon(url, blob)) {
17211
+ return;
17212
+ }
17213
+ }
17214
+ void fetch(url, {
17215
+ method: "POST",
17216
+ headers: { "Content-Type": "application/json" },
17217
+ body,
17218
+ keepalive: true,
17219
+ cache: "no-store"
17220
+ }).catch(() => {
17221
+ });
17222
+ } catch {
17223
+ }
17224
+ }
17225
+ function armLoaderHangBeacon(subdomain, fetchState, hangMs = 15e3) {
17226
+ if (!subdomain || fetchState !== "loading") {
17227
+ return () => {
17228
+ };
17229
+ }
17230
+ const timer = window.setTimeout(() => {
17231
+ reportOutageBeacon({
17232
+ subdomain,
17233
+ reason: "loader_stuck",
17234
+ detail: `fetchState still loading after ${hangMs}ms`
17235
+ });
17236
+ }, hangMs);
17237
+ return () => window.clearTimeout(timer);
17238
+ }
17239
+
16913
17240
  // src/lib/form-fields.ts
16914
17241
  var FIELD_DEFAULTS = {
16915
17242
  "short-text": { label: "Short text", placeholder: "Type placeholder text..." },
@@ -17510,6 +17837,7 @@ function MediaOverlay({
17510
17837
  (prev) => prev && prev.fullWidth === width && prev.height === height ? prev : { fullWidth: width, height }
17511
17838
  );
17512
17839
  }, [isVideo]);
17840
+ const replaceLabel = isVideo ? "Replace video" : hover.elementType === "bg-image" ? "Replace background" : "Replace image";
17513
17841
  const replaceMode = buttonSize ? replaceButtonMode({ width: rect.width, height: rect.height }, buttonSize) : "none";
17514
17842
  const box = {
17515
17843
  position: "fixed",
@@ -17608,11 +17936,10 @@ function MediaOverlay({
17608
17936
  className: "flex items-center justify-center cursor-pointer",
17609
17937
  style: {
17610
17938
  ...box,
17611
- // When text overlays this media, let clicks fall THROUGH to the text underneath. In the
17612
- // parent this needed a whole `ow:click-at` round-trip to re-hit-test inside the iframe;
17613
- // in-document, pointer-events does it natively. The button below opts back in, so
17614
- // Replace still works.
17615
- pointerEvents: hover.hasTextOverlap || cueOnly ? "none" : "auto",
17939
+ // Visual only. `pointer-events: auto` on this full-bleed rect covered the navbar
17940
+ // (hero bg includes the absolute TopNav) and swallowed CTA press-drag. Text overlay
17941
+ // and cueOnly also need clicks to fall through — Replace / video controls opt back in.
17942
+ pointerEvents: "none",
17616
17943
  // Selected: a firm component ring with no wash, so the image reads as chosen rather
17617
17944
  // than hovered. Hover keeps the existing tinted preview.
17618
17945
  boxShadow: selected ? "inset 0 0 0 2px var(--color-bridge-primary)" : "inset 0 0 0 1.5px var(--color-bridge-primary)",
@@ -17639,17 +17966,17 @@ function MediaOverlay({
17639
17966
  },
17640
17967
  children: [
17641
17968
  isVideo ? /* @__PURE__ */ jsx17(Film, { size: 14 }) : /* @__PURE__ */ jsx17(ImageIcon, { size: 14 }),
17642
- isVideo ? "Replace video" : "Replace image"
17969
+ replaceLabel
17643
17970
  ]
17644
17971
  }
17645
17972
  ),
17646
- replaceMode === "none" || cueOnly ? null : /* @__PURE__ */ jsxs8(
17973
+ showChrome && replaceMode !== "none" && /* @__PURE__ */ jsxs8(
17647
17974
  Button,
17648
17975
  {
17649
17976
  "data-ohw-media-overlay": "",
17650
17977
  variant: "outline",
17651
17978
  size: "sm",
17652
- "aria-label": isVideo ? "Replace video" : "Replace image",
17979
+ "aria-label": replaceLabel,
17653
17980
  className: "gap-1.5 cursor-pointer hover:bg-background",
17654
17981
  style: {
17655
17982
  ...OVERLAY_BUTTON_STYLE,
@@ -17672,7 +17999,7 @@ function MediaOverlay({
17672
17999
  },
17673
18000
  children: [
17674
18001
  isVideo ? /* @__PURE__ */ jsx17(Film, { size: 14 }) : /* @__PURE__ */ jsx17(ImageIcon, { size: 14 }),
17675
- replaceMode === "full" ? isVideo ? "Replace video" : hover.elementType === "bg-image" ? "Replace background" : "Replace image" : null
18002
+ replaceMode === "full" ? replaceLabel : null
17676
18003
  ]
17677
18004
  }
17678
18005
  )
@@ -18181,8 +18508,24 @@ function AiSectionOverlay({
18181
18508
  }
18182
18509
 
18183
18510
  // src/ui/ai-section/AiMediaStripOverlay.tsx
18184
- import { useCallback as useCallback3, useEffect as useEffect6, useRef as useRef5, useState as useState8 } from "react";
18185
- import { Star } from "lucide-react";
18511
+ import { useCallback as useCallback3, useEffect as useEffect6, useLayoutEffect as useLayoutEffect4, useRef as useRef5, useState as useState8 } from "react";
18512
+ import {
18513
+ AlignCenter,
18514
+ AlignLeft,
18515
+ AlignRight,
18516
+ ArrowLeft as ArrowLeft2,
18517
+ ArrowRight as ArrowRight2,
18518
+ Check as Check2,
18519
+ ChevronDown as ChevronDown3,
18520
+ Image as ImageIcon2,
18521
+ ImageMinus,
18522
+ ImagePlus,
18523
+ Link as LinkIcon,
18524
+ Minus,
18525
+ Palette,
18526
+ Plus as Plus2,
18527
+ Star
18528
+ } from "lucide-react";
18186
18529
 
18187
18530
  // src/lib/ai-media-strip-tree.ts
18188
18531
  var MEDIA_STRIP_ITEM_MIN = 1;
@@ -18296,14 +18639,6 @@ function setMediaStripItemAsset(tree, index, assetId) {
18296
18639
  return true;
18297
18640
  });
18298
18641
  }
18299
- function setMediaStripItemAlt(tree, index, alt) {
18300
- return withMediaStrip(tree, (block) => {
18301
- const items = block.items;
18302
- if (index < 0 || index >= items.length) return false;
18303
- items[index] = { ...items[index], alt };
18304
- return true;
18305
- });
18306
- }
18307
18642
  function setMediaStripItemLink(tree, index, link) {
18308
18643
  return withMediaStrip(tree, (block) => {
18309
18644
  const items = block.items;
@@ -18321,39 +18656,166 @@ function setMediaStripItemLink(tree, index, link) {
18321
18656
  });
18322
18657
  }
18323
18658
 
18324
- // src/ui/ai-section/AiMediaStripOverlay.tsx
18325
- import { Fragment as Fragment7, jsx as jsx20, jsxs as jsxs11 } from "react/jsx-runtime";
18326
- var PRIMARY3 = "#0885FE";
18327
- var OHW_BLUE = `var(--ohw-primary, ${PRIMARY3})`;
18328
- var OHW_BLUE_DARK = `color-mix(in srgb, var(--ohw-primary, ${PRIMARY3}) 60%, #000000)`;
18329
- function findMediaStripItemElement(sectionId, index) {
18330
- const sectionEl = findByInstanceId(sectionId);
18331
- if (!sectionEl) return null;
18332
- const items = sectionEl.querySelectorAll("[data-ohw-media-strip-item]");
18333
- for (const el of Array.from(items)) {
18334
- if (Number(el.dataset.ohwMediaStripIndex) === index) return el;
18335
- }
18336
- return null;
18659
+ // src/ui/dialog.tsx
18660
+ import * as React9 from "react";
18661
+ import { Dialog as DialogPrimitive } from "radix-ui";
18662
+ import { X as X2 } from "lucide-react";
18663
+ import { jsx as jsx20, jsxs as jsxs11 } from "react/jsx-runtime";
18664
+ function Dialog2({
18665
+ ...props
18666
+ }) {
18667
+ return /* @__PURE__ */ jsx20(DialogPrimitive.Root, { "data-slot": "dialog", ...props });
18337
18668
  }
18338
- function readRect2(ref) {
18339
- const el = ref && findMediaStripItemElement(ref.sectionId, ref.index);
18340
- if (!el) return null;
18341
- const r2 = el.getBoundingClientRect();
18342
- if (r2.width <= 0 || r2.height <= 0) return null;
18343
- return { top: r2.top, left: r2.left, width: r2.width, height: r2.height };
18669
+ function DialogPortal({
18670
+ ...props
18671
+ }) {
18672
+ return /* @__PURE__ */ jsx20(DialogPrimitive.Portal, { ...props });
18344
18673
  }
18345
- function useLiveItemRect(ref) {
18346
- const [rect, setRect] = useState8(null);
18347
- useEffect6(() => {
18348
- if (!ref) {
18349
- setRect(null);
18350
- return;
18674
+ function DialogOverlay({
18675
+ className,
18676
+ ...props
18677
+ }) {
18678
+ return /* @__PURE__ */ jsx20(
18679
+ DialogPrimitive.Overlay,
18680
+ {
18681
+ "data-slot": "dialog-overlay",
18682
+ "data-ohw-link-modal-root": "",
18683
+ className: cn("fixed inset-0 z-[2147483646] bg-black/50", className),
18684
+ ...props
18351
18685
  }
18352
- const update = () => {
18353
- const next = readRect2(ref);
18354
- setRect(
18355
- (prev) => prev && next && prev.top === next.top && prev.left === next.left && prev.width === next.width && prev.height === next.height ? prev : next
18356
- );
18686
+ );
18687
+ }
18688
+ var DialogContent = React9.forwardRef(
18689
+ ({ className, children, showCloseButton = true, container, ...props }, ref) => {
18690
+ const positionMode = container ? "absolute" : "fixed";
18691
+ return /* @__PURE__ */ jsxs11(DialogPortal, { container: container ?? void 0, children: [
18692
+ /* @__PURE__ */ jsx20(DialogOverlay, { className: cn(positionMode, "inset-0") }),
18693
+ /* @__PURE__ */ jsxs11(
18694
+ DialogPrimitive.Content,
18695
+ {
18696
+ ref,
18697
+ "data-slot": "dialog-content",
18698
+ className: cn(
18699
+ positionMode,
18700
+ "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",
18701
+ "rounded-xl border border-border bg-background font-sans shadow-lg outline-none",
18702
+ container ? "max-h-[calc(100%-32px)]" : "max-h-[calc(100vh-32px)]",
18703
+ className
18704
+ ),
18705
+ onMouseDown: (e) => e.stopPropagation(),
18706
+ ...props,
18707
+ children: [
18708
+ children,
18709
+ showCloseButton ? /* @__PURE__ */ jsx20(
18710
+ DialogPrimitive.Close,
18711
+ {
18712
+ type: "button",
18713
+ className: "absolute right-[9px] top-[9px] rounded-sm p-1.5 text-foreground hover:bg-muted/50",
18714
+ "aria-label": "Close",
18715
+ children: /* @__PURE__ */ jsx20(X2, { size: 16, "aria-hidden": true })
18716
+ }
18717
+ ) : null
18718
+ ]
18719
+ }
18720
+ )
18721
+ ] });
18722
+ }
18723
+ );
18724
+ DialogContent.displayName = DialogPrimitive.Content.displayName;
18725
+ function DialogHeader({
18726
+ className,
18727
+ ...props
18728
+ }) {
18729
+ return /* @__PURE__ */ jsx20("div", { className: cn("flex flex-col gap-1.5", className), ...props });
18730
+ }
18731
+ function DialogFooter({
18732
+ className,
18733
+ ...props
18734
+ }) {
18735
+ return /* @__PURE__ */ jsx20(
18736
+ "div",
18737
+ {
18738
+ className: cn("flex items-center justify-end gap-2", className),
18739
+ ...props
18740
+ }
18741
+ );
18742
+ }
18743
+ var DialogTitle = React9.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx20(
18744
+ DialogPrimitive.Title,
18745
+ {
18746
+ ref,
18747
+ className: cn(
18748
+ "text-lg font-semibold leading-none tracking-tight text-card-foreground",
18749
+ className
18750
+ ),
18751
+ ...props
18752
+ }
18753
+ ));
18754
+ DialogTitle.displayName = DialogPrimitive.Title.displayName;
18755
+ var DialogDescription = React9.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx20(
18756
+ DialogPrimitive.Description,
18757
+ {
18758
+ ref,
18759
+ className: cn("text-sm text-muted-foreground", className),
18760
+ ...props
18761
+ }
18762
+ ));
18763
+ DialogDescription.displayName = DialogPrimitive.Description.displayName;
18764
+ var DialogClose = DialogPrimitive.Close;
18765
+
18766
+ // src/ui/ai-section/AiMediaStripOverlay.tsx
18767
+ import { Fragment as Fragment7, jsx as jsx21, jsxs as jsxs12 } from "react/jsx-runtime";
18768
+ var PRIMARY3 = "var(--ohw-primary, #0885FE)";
18769
+ 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]";
18770
+ function findMediaStripItemElement(sectionId, index) {
18771
+ const sectionEl = findByInstanceId(sectionId);
18772
+ if (!sectionEl) return null;
18773
+ const items = sectionEl.querySelectorAll("[data-ohw-media-strip-item]");
18774
+ for (const el of Array.from(items)) {
18775
+ if (Number(el.dataset.ohwMediaStripIndex) === index) return el;
18776
+ }
18777
+ return null;
18778
+ }
18779
+ function itemRefFromTarget(target) {
18780
+ if (!(target instanceof Element)) return null;
18781
+ if (target.closest(CHROME_SELECTOR)) return null;
18782
+ const itemEl = target.closest("[data-ohw-media-strip-item]");
18783
+ if (!itemEl) return null;
18784
+ const sectionEl = itemEl.closest("[data-ohw-section]");
18785
+ const unit = sectionEl ? movableUnit(sectionEl) : null;
18786
+ const sectionId = unit ? instanceIdOf(unit) || null : null;
18787
+ const index = Number(itemEl.dataset.ohwMediaStripIndex);
18788
+ if (!sectionId || Number.isNaN(index)) return null;
18789
+ return { sectionId, index };
18790
+ }
18791
+ function sameRef(a, b) {
18792
+ return a?.sectionId === b?.sectionId && a?.index === b?.index;
18793
+ }
18794
+ function rectOf(el) {
18795
+ if (!el) return null;
18796
+ const r2 = el.getBoundingClientRect();
18797
+ if (r2.width <= 0 || r2.height <= 0) return null;
18798
+ return { top: r2.top, left: r2.left, width: r2.width, height: r2.height };
18799
+ }
18800
+ function readRect2(ref) {
18801
+ return rectOf(ref && findMediaStripItemElement(ref.sectionId, ref.index));
18802
+ }
18803
+ function readStripRect(ref) {
18804
+ const item = ref && findMediaStripItemElement(ref.sectionId, ref.index);
18805
+ return rectOf(item?.closest("[data-ohw-media-strip]") ?? item);
18806
+ }
18807
+ function useLiveItemRect(ref, strip = false) {
18808
+ const [rect, setRect] = useState8(null);
18809
+ useEffect6(() => {
18810
+ if (!ref) {
18811
+ setRect(null);
18812
+ return;
18813
+ }
18814
+ const update = () => {
18815
+ const next = strip ? readStripRect(ref) : readRect2(ref);
18816
+ setRect(
18817
+ (prev) => prev && next && prev.top === next.top && prev.left === next.left && prev.width === next.width && prev.height === next.height ? prev : next
18818
+ );
18357
18819
  };
18358
18820
  update();
18359
18821
  const opts = { capture: true, passive: true };
@@ -18365,9 +18827,49 @@ function useLiveItemRect(ref) {
18365
18827
  window.removeEventListener("resize", update);
18366
18828
  clearInterval(interval);
18367
18829
  };
18368
- }, [ref?.sectionId, ref?.index]);
18830
+ }, [ref?.sectionId, ref?.index, strip]);
18369
18831
  return rect;
18370
18832
  }
18833
+ var TOOLBAR_STRIP_GAP = 52;
18834
+ var TOOLBAR_STRIP_GAP_MOBILE = 42;
18835
+ var TOOLBAR_EDGE_MARGIN2 = 4;
18836
+ function StripToolbarSlot({ stripRect, children }) {
18837
+ const slotRef = useRef5(null);
18838
+ const [shift, setShift] = useState8(0);
18839
+ const centerX = stripRect.left + stripRect.width / 2;
18840
+ useLayoutEffect4(() => {
18841
+ const slot = slotRef.current;
18842
+ if (!slot) return;
18843
+ const clamp = () => {
18844
+ const w = slot.offsetWidth;
18845
+ if (w <= 0) return;
18846
+ const half = w / 2;
18847
+ const clamped = Math.max(TOOLBAR_EDGE_MARGIN2 + half, Math.min(centerX, window.innerWidth - TOOLBAR_EDGE_MARGIN2 - half));
18848
+ setShift((prev) => Math.abs(prev - (clamped - centerX)) < 0.5 ? prev : clamped - centerX);
18849
+ };
18850
+ clamp();
18851
+ const ro = new ResizeObserver(clamp);
18852
+ ro.observe(slot);
18853
+ return () => ro.disconnect();
18854
+ }, [centerX, children]);
18855
+ const mobile = window.innerWidth <= 480;
18856
+ return /* @__PURE__ */ jsx21(
18857
+ "div",
18858
+ {
18859
+ ref: slotRef,
18860
+ "data-ohw-bridge": "",
18861
+ "data-ohw-item-interaction": "",
18862
+ className: "pointer-events-auto fixed",
18863
+ style: {
18864
+ top: stripRect.top - (mobile ? TOOLBAR_STRIP_GAP_MOBILE : TOOLBAR_STRIP_GAP),
18865
+ left: centerX + shift,
18866
+ transform: "translate(-50%, -100%)",
18867
+ zIndex: 2147483645
18868
+ },
18869
+ children
18870
+ }
18871
+ );
18872
+ }
18371
18873
  function requestValidateTree(postToParent2, tree) {
18372
18874
  return new Promise((resolve) => {
18373
18875
  const id = `ai-validate-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
@@ -18404,116 +18906,72 @@ function requestAssetPick(postToParent2) {
18404
18906
  postToParent2({ type: "ow:asset-pick", id });
18405
18907
  });
18406
18908
  }
18407
- function Pill({
18909
+ function ToolbarAction({
18408
18910
  label,
18409
18911
  onClick,
18410
- disabled,
18411
- active
18912
+ disabled = false,
18913
+ active = false,
18914
+ children
18412
18915
  }) {
18413
- return /* @__PURE__ */ jsx20(
18414
- "button",
18415
- {
18416
- type: "button",
18417
- onClick,
18418
- disabled,
18419
- style: {
18420
- height: 28,
18421
- padding: "0 10px",
18422
- display: "inline-flex",
18423
- alignItems: "center",
18424
- gap: 4,
18425
- borderRadius: 999,
18426
- border: "none",
18427
- cursor: disabled ? "default" : "pointer",
18428
- background: active ? OHW_BLUE_DARK : "#ffffff",
18429
- color: active ? "#ffffff" : OHW_BLUE_DARK,
18430
- opacity: disabled ? 0.4 : 1,
18431
- fontSize: 12,
18432
- lineHeight: "16px",
18433
- fontFamily: "Figtree, ui-sans-serif, system-ui, sans-serif",
18434
- whiteSpace: "nowrap",
18435
- flex: "0 0 auto"
18436
- },
18437
- children: label
18438
- }
18439
- );
18916
+ const button = /* @__PURE__ */ jsx21(CustomToolbarButton, { type: "button", "aria-label": label, disabled, active, onClick, className: "size-8", children });
18917
+ return /* @__PURE__ */ jsxs12(Tooltip, { children: [
18918
+ /* @__PURE__ */ jsx21(TooltipTrigger, { asChild: true, children: disabled ? /* @__PURE__ */ jsx21("span", { className: "inline-flex", children: button }) : button }),
18919
+ /* @__PURE__ */ jsx21(TooltipContent, { side: "bottom", sideOffset: 8, children: label })
18920
+ ] });
18440
18921
  }
18441
- function AltInput({ value, onCommit }) {
18442
- const [draft, setDraft] = useState8(value);
18443
- useEffect6(() => setDraft(value), [value]);
18444
- return /* @__PURE__ */ jsxs11(
18445
- "div",
18446
- {
18447
- style: {
18448
- display: "flex",
18449
- alignItems: "center",
18450
- gap: 6,
18451
- height: 28,
18452
- padding: "0 12px",
18453
- borderRadius: 999,
18454
- background: "#ffffff",
18455
- width: "100%",
18456
- maxWidth: 260,
18457
- boxSizing: "border-box"
18458
- },
18459
- children: [
18460
- /* @__PURE__ */ jsx20(
18461
- "span",
18462
- {
18463
- style: {
18464
- flex: "0 0 auto",
18465
- color: OHW_BLUE_DARK,
18466
- fontSize: 10,
18467
- fontWeight: 700,
18468
- textTransform: "uppercase",
18469
- letterSpacing: 0.4
18470
- },
18471
- children: "Alt"
18472
- }
18473
- ),
18474
- /* @__PURE__ */ jsx20(
18475
- "input",
18922
+ function ToolbarSeparator() {
18923
+ return /* @__PURE__ */ jsx21("span", { "aria-hidden": true, className: "mx-0.5 block h-6 w-px shrink-0 bg-border" });
18924
+ }
18925
+ function OptionMenu({
18926
+ label,
18927
+ value,
18928
+ options,
18929
+ onChange
18930
+ }) {
18931
+ const current = options.find((o) => o.value === value) ?? options[0];
18932
+ return /* @__PURE__ */ jsxs12("div", { className: "flex items-center gap-1.5 pl-1.5", children: [
18933
+ /* @__PURE__ */ jsx21("span", { className: "text-xs text-muted-foreground", children: label }),
18934
+ /* @__PURE__ */ jsxs12(DropdownMenu, { modal: false, children: [
18935
+ /* @__PURE__ */ jsx21(DropdownMenuTrigger, { asChild: true, children: /* @__PURE__ */ jsxs12(
18936
+ "button",
18937
+ {
18938
+ type: "button",
18939
+ "aria-label": label,
18940
+ 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",
18941
+ children: [
18942
+ /* @__PURE__ */ jsx21("span", { className: "flex-1 text-left", children: current.label }),
18943
+ /* @__PURE__ */ jsx21(ChevronDown3, { size: 16, style: { pointerEvents: "none" } })
18944
+ ]
18945
+ }
18946
+ ) }),
18947
+ /* @__PURE__ */ jsx21(DropdownMenuContent, { align: "end", sideOffset: 8, className: "w-[132px] min-w-[132px] p-1", children: options.map((option) => {
18948
+ const selected = option.value === value;
18949
+ return /* @__PURE__ */ jsxs12(
18950
+ DropdownMenuItem,
18476
18951
  {
18477
- type: "text",
18478
- value: draft,
18479
- onChange: (e) => setDraft(e.target.value),
18480
- onBlur: () => {
18481
- if (draft !== value) onCommit(draft);
18482
- },
18483
- onKeyDown: (e) => {
18484
- if (e.key === "Enter") e.currentTarget.blur();
18485
- if (e.key === "Escape") {
18486
- setDraft(value);
18487
- e.currentTarget.blur();
18488
- }
18489
- },
18490
- placeholder: "Describe this image",
18491
- "aria-label": "Image alt text",
18492
- style: {
18493
- flex: 1,
18494
- minWidth: 0,
18495
- border: "none",
18496
- outline: "none",
18497
- background: "transparent",
18498
- fontSize: 12,
18499
- lineHeight: "16px",
18500
- fontFamily: "Figtree, ui-sans-serif, system-ui, sans-serif",
18501
- color: "#1f2937"
18502
- }
18503
- }
18504
- )
18505
- ]
18506
- }
18507
- );
18952
+ role: "menuitemradio",
18953
+ "aria-checked": selected,
18954
+ onSelect: () => onChange(option.value),
18955
+ className: cn("grid grid-cols-[1fr_16px] gap-2 py-2 pl-2 pr-2 text-[13px]", selected && "bg-muted"),
18956
+ children: [
18957
+ /* @__PURE__ */ jsx21("span", { children: option.label }),
18958
+ selected && /* @__PURE__ */ jsx21(Check2, { size: 16, className: "text-bridge-primary" })
18959
+ ]
18960
+ },
18961
+ option.value
18962
+ );
18963
+ }) })
18964
+ ] })
18965
+ ] });
18508
18966
  }
18509
18967
  var VARIANTS = [
18510
18968
  { value: "logos", label: "Logos" },
18511
18969
  { value: "reel", label: "Reel" }
18512
18970
  ];
18513
18971
  var ALIGNS = [
18514
- { value: "left", label: "Left" },
18515
- { value: "center", label: "Center" },
18516
- { value: "right", label: "Right" }
18972
+ { value: "left", label: "Align left", Icon: AlignLeft },
18973
+ { value: "center", label: "Align center", Icon: AlignCenter },
18974
+ { value: "right", label: "Align right", Icon: AlignRight }
18517
18975
  ];
18518
18976
  var HOVERS = [
18519
18977
  { value: "none", label: "None" },
@@ -18521,104 +18979,104 @@ var HOVERS = [
18521
18979
  { value: "zoom", label: "Zoom" },
18522
18980
  { value: "fade", label: "Fade" }
18523
18981
  ];
18524
- var TREATMENTS = [
18525
- { value: "color", label: "Color" },
18526
- { value: "grayscale", label: "Grayscale" }
18527
- ];
18528
18982
  var HTTP_URL_RE = /^https?:\/\/\S+$/i;
18529
- function LinkInput({ value, onCommit }) {
18530
- const [draft, setDraft] = useState8(value);
18983
+ function LinkModal({
18984
+ href,
18985
+ linkTarget,
18986
+ onClose,
18987
+ onSave
18988
+ }) {
18989
+ const [draft, setDraft] = useState8(href);
18990
+ const [newTab, setNewTab] = useState8(linkTarget !== "same-tab");
18531
18991
  const [invalid, setInvalid] = useState8(false);
18532
- useEffect6(() => {
18533
- setDraft(value);
18534
- setInvalid(false);
18535
- }, [value]);
18536
- const commit = () => {
18992
+ const save = () => {
18537
18993
  const next = draft.trim();
18538
- if (next === value) return setInvalid(false);
18539
- if (next && !HTTP_URL_RE.test(next)) return setInvalid(true);
18540
- setInvalid(false);
18541
- onCommit(next);
18542
- };
18543
- return /* @__PURE__ */ jsxs11(
18544
- "div",
18545
- {
18546
- style: {
18547
- display: "flex",
18548
- alignItems: "center",
18549
- gap: 6,
18550
- height: 28,
18551
- padding: "0 12px",
18552
- borderRadius: 999,
18553
- background: "#ffffff",
18554
- width: "100%",
18555
- maxWidth: 260,
18556
- boxSizing: "border-box",
18557
- boxShadow: invalid ? "inset 0 0 0 1.5px #EF4444" : "none"
18558
- },
18559
- children: [
18560
- /* @__PURE__ */ jsx20(
18561
- "span",
18562
- {
18563
- style: {
18564
- flex: "0 0 auto",
18565
- color: OHW_BLUE_DARK,
18566
- fontSize: 10,
18567
- fontWeight: 700,
18568
- textTransform: "uppercase",
18569
- letterSpacing: 0.4
18570
- },
18571
- children: "Link"
18572
- }
18573
- ),
18574
- /* @__PURE__ */ jsx20(
18575
- "input",
18576
- {
18577
- type: "text",
18578
- inputMode: "url",
18579
- value: draft,
18580
- onChange: (e) => setDraft(e.target.value),
18581
- onBlur: commit,
18582
- onKeyDown: (e) => {
18583
- if (e.key === "Enter") e.currentTarget.blur();
18584
- if (e.key === "Escape") {
18585
- setDraft(value);
18586
- setInvalid(false);
18587
- e.currentTarget.blur();
18588
- }
18589
- },
18590
- placeholder: "https://example.com",
18591
- "aria-label": "Image link URL",
18592
- "aria-invalid": invalid,
18593
- style: {
18594
- flex: 1,
18595
- minWidth: 0,
18596
- border: "none",
18597
- outline: "none",
18598
- background: "transparent",
18599
- fontSize: 12,
18600
- lineHeight: "16px",
18601
- fontFamily: "Figtree, ui-sans-serif, system-ui, sans-serif",
18602
- color: "#1f2937"
18603
- }
18604
- }
18605
- )
18606
- ]
18994
+ if (next && !HTTP_URL_RE.test(next)) {
18995
+ setInvalid(true);
18996
+ return;
18607
18997
  }
18608
- );
18998
+ onSave(next, newTab ? "new-tab" : "same-tab");
18999
+ onClose();
19000
+ };
19001
+ return /* @__PURE__ */ jsx21(Dialog2, { open: true, onOpenChange: (open) => !open && onClose(), children: /* @__PURE__ */ jsxs12(DialogContent, { "data-ohw-link-modal-root": "", className: "max-w-[400px] gap-4 p-5", children: [
19002
+ /* @__PURE__ */ jsx21(DialogHeader, { children: /* @__PURE__ */ jsx21(DialogTitle, { className: "text-base", children: "Image link" }) }),
19003
+ /* @__PURE__ */ jsxs12("div", { className: "flex flex-col gap-3", children: [
19004
+ /* @__PURE__ */ jsx21(
19005
+ "input",
19006
+ {
19007
+ autoFocus: true,
19008
+ type: "text",
19009
+ inputMode: "url",
19010
+ value: draft,
19011
+ onChange: (e) => {
19012
+ setDraft(e.target.value);
19013
+ setInvalid(false);
19014
+ },
19015
+ onKeyDown: (e) => {
19016
+ if (e.key === "Enter") save();
19017
+ },
19018
+ placeholder: "https://example.com",
19019
+ "aria-label": "Image link URL",
19020
+ "aria-invalid": invalid,
19021
+ className: cn(
19022
+ "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",
19023
+ invalid && "border-destructive focus:border-destructive"
19024
+ )
19025
+ }
19026
+ ),
19027
+ invalid && /* @__PURE__ */ jsx21("p", { className: "text-xs text-destructive", children: "Enter a full URL starting with http:// or https://" }),
19028
+ /* @__PURE__ */ jsxs12("label", { className: "flex items-center gap-2 text-[13px] text-foreground", children: [
19029
+ /* @__PURE__ */ jsx21("input", { type: "checkbox", checked: newTab, onChange: (e) => setNewTab(e.target.checked) }),
19030
+ "Open in new tab"
19031
+ ] })
19032
+ ] }),
19033
+ /* @__PURE__ */ jsxs12(DialogFooter, { children: [
19034
+ href && /* @__PURE__ */ jsx21(
19035
+ "button",
19036
+ {
19037
+ type: "button",
19038
+ onClick: () => {
19039
+ onSave("", "new-tab");
19040
+ onClose();
19041
+ },
19042
+ className: "mr-auto rounded-[6px] px-3 py-2 text-[13px] font-medium text-destructive transition-colors hover:bg-muted",
19043
+ children: "Remove link"
19044
+ }
19045
+ ),
19046
+ /* @__PURE__ */ jsx21(
19047
+ "button",
19048
+ {
19049
+ type: "button",
19050
+ onClick: onClose,
19051
+ className: "rounded-[6px] px-3 py-2 text-[13px] font-medium text-foreground transition-colors hover:bg-muted",
19052
+ children: "Cancel"
19053
+ }
19054
+ ),
19055
+ /* @__PURE__ */ jsx21(
19056
+ "button",
19057
+ {
19058
+ type: "button",
19059
+ onClick: save,
19060
+ className: "rounded-[6px] bg-bridge-primary px-3 py-2 text-[13px] font-medium text-primary-foreground transition-opacity hover:opacity-90",
19061
+ children: "Save"
19062
+ }
19063
+ )
19064
+ ] })
19065
+ ] }) });
18609
19066
  }
18610
19067
  function MediaStripToolbar({
18611
19068
  entry,
18612
19069
  index,
18613
19070
  pending,
18614
19071
  error,
19072
+ itemEditOn,
19073
+ onToggleItemEdit,
18615
19074
  onSetVariant,
18616
19075
  onSetAlign,
18617
19076
  onSetHover,
18618
19077
  onSetTreatment,
18619
19078
  onResize,
18620
- onSetAlt,
18621
- onSetLink,
19079
+ onOpenLink,
18622
19080
  onAddItem,
18623
19081
  onRemoveItem,
18624
19082
  onChangeImage,
@@ -18627,81 +19085,102 @@ function MediaStripToolbar({
18627
19085
  }) {
18628
19086
  const items = mediaStripItems(entry.tree);
18629
19087
  const itemCount = items.length;
18630
- const currentAlt = items[index]?.alt ?? "";
18631
- const currentHref = items[index]?.href ?? "";
18632
- const currentTarget = items[index]?.linkTarget === "same-tab" ? "same-tab" : "new-tab";
18633
19088
  const slots = findMediaStripSlots(entry.tree);
18634
19089
  const variant = slots?.variant === "reel" ? "reel" : "logos";
18635
19090
  const align = slots?.align === "left" || slots?.align === "right" ? slots.align : "center";
18636
19091
  const hover = slots?.hover === "none" || slots?.hover === "lift" || slots?.hover === "zoom" || slots?.hover === "fade" ? slots.hover : "lift";
18637
19092
  const treatment = slots?.treatment === "color" || slots?.treatment === "grayscale" ? slots.treatment : variant === "logos" ? "grayscale" : "color";
18638
19093
  const sizeValue = variant === "logos" ? typeof slots?.logoMaxHeight === "number" ? slots.logoMaxHeight : 48 : typeof slots?.cardWidth === "number" ? slots.cardWidth : 240;
18639
- const isMobile = window.innerWidth <= 480;
18640
- const rowStyle = isMobile ? {
18641
- display: "flex",
18642
- gap: 6,
18643
- flexWrap: "nowrap",
18644
- justifyContent: "center",
18645
- overflowX: "auto",
18646
- WebkitOverflowScrolling: "touch",
18647
- width: "100%",
18648
- scrollbarWidth: "none"
18649
- } : { display: "flex", gap: 6, flexWrap: "nowrap", justifyContent: "center" };
18650
- return /* @__PURE__ */ jsxs11(
19094
+ const mobile = window.innerWidth <= 480;
19095
+ const gatedLabel = (label) => itemEditOn ? label : "Turn on the star to edit this image";
19096
+ return /* @__PURE__ */ jsx21(TooltipProvider, { delayDuration: 0, children: /* @__PURE__ */ jsxs12(
18651
19097
  "div",
18652
19098
  {
18653
19099
  "data-ohw-bridge": "",
18654
19100
  "data-ohw-toolbar": "",
18655
19101
  onMouseDown: (e) => e.stopPropagation(),
18656
- style: {
18657
- display: "flex",
18658
- flexDirection: "column",
18659
- gap: 6,
18660
- alignItems: "center",
18661
- background: OHW_BLUE,
18662
- padding: 8,
18663
- borderRadius: 12,
18664
- boxShadow: "0 8px 24px rgba(0,0,0,0.25)",
18665
- pointerEvents: pending ? "none" : "auto",
18666
- opacity: pending ? 0.6 : 1,
18667
- maxWidth: isMobile ? "calc(100vw - 24px)" : "none"
18668
- },
19102
+ className: cn("flex flex-col items-center gap-1.5 font-sans", pending && "pointer-events-none opacity-60"),
19103
+ style: { maxWidth: mobile ? "calc(100vw - 24px)" : void 0 },
18669
19104
  children: [
18670
- /* @__PURE__ */ jsxs11("div", { style: rowStyle, children: [
18671
- VARIANTS.map((v) => /* @__PURE__ */ jsx20(Pill, { label: v.label, active: variant === v.value, onClick: () => onSetVariant(v.value) }, v.value)),
18672
- ALIGNS.map((a) => /* @__PURE__ */ jsx20(Pill, { label: a.label, active: align === a.value, onClick: () => onSetAlign(a.value) }, a.value))
18673
- ] }),
18674
- /* @__PURE__ */ jsxs11("div", { style: rowStyle, children: [
18675
- HOVERS.map((h) => /* @__PURE__ */ jsx20(Pill, { label: h.label, active: hover === h.value, onClick: () => onSetHover(h.value) }, h.value)),
18676
- TREATMENTS.map((t) => /* @__PURE__ */ jsx20(Pill, { label: t.label, active: treatment === t.value, onClick: () => onSetTreatment(t.value) }, t.value))
18677
- ] }),
18678
- /* @__PURE__ */ jsxs11("div", { style: { display: "flex", gap: 6, alignItems: "center" }, children: [
18679
- /* @__PURE__ */ jsx20(Pill, { label: "\u2212", onClick: () => onResize(-8) }),
18680
- /* @__PURE__ */ jsxs11("span", { style: { color: "#ffffff", fontSize: 12, minWidth: 34, textAlign: "center" }, children: [
18681
- sizeValue,
18682
- "px"
18683
- ] }),
18684
- /* @__PURE__ */ jsx20(Pill, { label: "+", onClick: () => onResize(8) })
18685
- ] }),
18686
- /* @__PURE__ */ jsx20("div", { style: { display: "flex", justifyContent: "center", width: "100%" }, children: /* @__PURE__ */ jsx20(AltInput, { value: currentAlt, onCommit: onSetAlt }) }),
18687
- /* @__PURE__ */ jsx20("div", { style: { display: "flex", justifyContent: "center", width: "100%" }, children: /* @__PURE__ */ jsx20(LinkInput, { value: currentHref, onCommit: (href) => onSetLink(href, currentTarget) }) }),
18688
- currentHref && /* @__PURE__ */ jsxs11("div", { style: rowStyle, children: [
18689
- /* @__PURE__ */ jsx20(Pill, { label: "New tab", active: currentTarget === "new-tab", onClick: () => onSetLink(currentHref, "new-tab") }),
18690
- /* @__PURE__ */ jsx20(Pill, { label: "Same tab", active: currentTarget === "same-tab", onClick: () => onSetLink(currentHref, "same-tab") })
18691
- ] }),
18692
- /* @__PURE__ */ jsxs11("div", { style: rowStyle, children: [
18693
- /* @__PURE__ */ jsx20(Pill, { label: "Change Image", onClick: onChangeImage }),
18694
- /* @__PURE__ */ jsx20(Pill, { label: "+ Item", onClick: onAddItem }),
18695
- /* @__PURE__ */ jsx20(Pill, { label: "\u2212 Item", onClick: onRemoveItem, disabled: itemCount <= 1 })
18696
- ] }),
18697
- /* @__PURE__ */ jsxs11("div", { style: rowStyle, children: [
18698
- /* @__PURE__ */ jsx20(Pill, { label: "\u2190 Move", onClick: onMoveLeft, disabled: index <= 0 }),
18699
- /* @__PURE__ */ jsx20(Pill, { label: "\u2192 Move", onClick: onMoveRight, disabled: index >= itemCount - 1 })
18700
- ] }),
18701
- error && /* @__PURE__ */ jsx20("div", { style: { color: "#FCA5A5", fontSize: 11, maxWidth: 220, textAlign: "center" }, children: error })
19105
+ /* @__PURE__ */ jsxs12(
19106
+ "div",
19107
+ {
19108
+ className: cn(
19109
+ "flex items-center gap-1 rounded-[10px] border border-border bg-background p-1 whitespace-nowrap",
19110
+ "shadow-[0px_10px_15px_-3px_rgba(0,0,0,0.1),0px_4px_6px_-4px_rgba(0,0,0,0.1)]",
19111
+ mobile ? "flex-wrap justify-center" : "flex-nowrap"
19112
+ ),
19113
+ children: [
19114
+ /* @__PURE__ */ jsxs12(Tooltip, { children: [
19115
+ /* @__PURE__ */ jsx21(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsx21(
19116
+ CustomToolbarButton,
19117
+ {
19118
+ type: "button",
19119
+ "aria-label": itemEditOn ? "Done editing this image" : "Edit this image",
19120
+ "aria-pressed": itemEditOn,
19121
+ onClick: onToggleItemEdit,
19122
+ className: "group size-8",
19123
+ children: /* @__PURE__ */ jsx21(
19124
+ Star,
19125
+ {
19126
+ size: 16,
19127
+ fill: "currentColor",
19128
+ strokeWidth: 0,
19129
+ className: cn(
19130
+ "transition-colors",
19131
+ itemEditOn ? "text-[#f59e0b] group-hover:text-[#d97706]" : "text-[#d6d3d1] group-hover:text-[#fbbf24]"
19132
+ ),
19133
+ style: { pointerEvents: "none" }
19134
+ }
19135
+ )
19136
+ }
19137
+ ) }),
19138
+ /* @__PURE__ */ jsx21(TooltipContent, { side: "bottom", sideOffset: 8, children: itemEditOn ? "Done editing this image" : "Edit this image" })
19139
+ ] }),
19140
+ /* @__PURE__ */ jsx21(ToolbarSeparator, {}),
19141
+ /* @__PURE__ */ jsx21(ToolbarAction, { label: gatedLabel("Change image"), onClick: onChangeImage, disabled: !itemEditOn, children: /* @__PURE__ */ jsx21(ImageIcon2, { size: 16, style: { pointerEvents: "none" } }) }),
19142
+ /* @__PURE__ */ jsx21(ToolbarAction, { label: gatedLabel("Link"), onClick: onOpenLink, disabled: !itemEditOn, children: /* @__PURE__ */ jsx21(LinkIcon, { size: 16, style: { pointerEvents: "none" } }) }),
19143
+ /* @__PURE__ */ jsx21(ToolbarAction, { label: gatedLabel("Move left"), onClick: onMoveLeft, disabled: !itemEditOn || index <= 0, children: /* @__PURE__ */ jsx21(ArrowLeft2, { size: 16, style: { pointerEvents: "none" } }) }),
19144
+ /* @__PURE__ */ jsx21(
19145
+ ToolbarAction,
19146
+ {
19147
+ label: gatedLabel("Move right"),
19148
+ onClick: onMoveRight,
19149
+ disabled: !itemEditOn || index >= itemCount - 1,
19150
+ children: /* @__PURE__ */ jsx21(ArrowRight2, { size: 16, style: { pointerEvents: "none" } })
19151
+ }
19152
+ ),
19153
+ /* @__PURE__ */ jsx21(ToolbarAction, { label: gatedLabel("Remove image"), onClick: onRemoveItem, disabled: !itemEditOn || itemCount <= 1, children: /* @__PURE__ */ jsx21(ImageMinus, { size: 16, style: { pointerEvents: "none" } }) }),
19154
+ /* @__PURE__ */ jsx21(ToolbarSeparator, {}),
19155
+ /* @__PURE__ */ jsx21(OptionMenu, { label: "Layout", value: variant, options: VARIANTS, onChange: onSetVariant }),
19156
+ /* @__PURE__ */ jsx21(OptionMenu, { label: "Hover", value: hover, options: HOVERS, onChange: onSetHover }),
19157
+ /* @__PURE__ */ jsx21(
19158
+ ToolbarAction,
19159
+ {
19160
+ label: treatment === "grayscale" ? "Show in color" : "Show grayscale",
19161
+ active: treatment === "grayscale",
19162
+ onClick: () => onSetTreatment(treatment === "grayscale" ? "color" : "grayscale"),
19163
+ children: /* @__PURE__ */ jsx21(Palette, { size: 16, style: { pointerEvents: "none" } })
19164
+ }
19165
+ ),
19166
+ /* @__PURE__ */ jsx21(ToolbarSeparator, {}),
19167
+ ALIGNS.map(({ value, label, Icon }) => /* @__PURE__ */ jsx21(ToolbarAction, { label, active: align === value, onClick: () => onSetAlign(value), children: /* @__PURE__ */ jsx21(Icon, { size: 16, style: { pointerEvents: "none" } }) }, value)),
19168
+ /* @__PURE__ */ jsx21(ToolbarSeparator, {}),
19169
+ /* @__PURE__ */ jsx21(ToolbarAction, { label: "Smaller", onClick: () => onResize(-8), children: /* @__PURE__ */ jsx21(Minus, { size: 16, style: { pointerEvents: "none" } }) }),
19170
+ /* @__PURE__ */ jsxs12("span", { className: "min-w-[40px] text-center text-xs tabular-nums text-muted-foreground", children: [
19171
+ sizeValue,
19172
+ "px"
19173
+ ] }),
19174
+ /* @__PURE__ */ jsx21(ToolbarAction, { label: "Larger", onClick: () => onResize(8), children: /* @__PURE__ */ jsx21(Plus2, { size: 16, style: { pointerEvents: "none" } }) }),
19175
+ /* @__PURE__ */ jsx21(ToolbarSeparator, {}),
19176
+ /* @__PURE__ */ jsx21(ToolbarAction, { label: "Add image", onClick: onAddItem, children: /* @__PURE__ */ jsx21(ImagePlus, { size: 16, style: { pointerEvents: "none" } }) })
19177
+ ]
19178
+ }
19179
+ ),
19180
+ error && /* @__PURE__ */ jsx21("div", { className: "max-w-[260px] rounded-md bg-background px-2 py-1 text-center text-[11px] text-destructive shadow-sm", children: error })
18702
19181
  ]
18703
19182
  }
18704
- );
19183
+ ) });
18705
19184
  }
18706
19185
  function findMediaStripSlots(tree) {
18707
19186
  const stack = tree.rows.flatMap((row) => row.blocks);
@@ -18720,31 +19199,23 @@ function AiMediaStripOverlay({
18720
19199
  }) {
18721
19200
  const [hover, setHover] = useState8(null);
18722
19201
  const [active, setActive] = useState8(null);
19202
+ const [itemEditOn, setItemEditOn] = useState8(false);
19203
+ const [linkOpen, setLinkOpen] = useState8(false);
18723
19204
  const [pending, setPending] = useState8(false);
18724
19205
  const [error, setError] = useState8(null);
18725
19206
  const activeRef = useRef5(null);
18726
19207
  activeRef.current = active;
19208
+ const linkOpenRef = useRef5(false);
19209
+ linkOpenRef.current = linkOpen;
18727
19210
  useEffect6(() => {
18728
19211
  const onMove = (e) => {
19212
+ const ref = itemRefFromTarget(e.target);
18729
19213
  const t = e.target;
18730
- if (!(t instanceof HTMLElement)) return;
18731
- 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]")) {
18732
- return;
18733
- }
18734
- const itemEl = t.closest("[data-ohw-media-strip-item]");
18735
- if (!itemEl) {
18736
- setHover(null);
18737
- return;
18738
- }
18739
- const sectionEl = itemEl.closest("[data-ohw-section]");
18740
- const unit = sectionEl ? movableUnit(sectionEl) : null;
18741
- const sectionId = unit ? instanceIdOf(unit) || null : null;
18742
- const index = Number(itemEl.dataset.ohwMediaStripIndex);
18743
- if (!sectionId || Number.isNaN(index)) {
18744
- setHover(null);
18745
- return;
18746
- }
18747
- setHover({ sectionId, index });
19214
+ const overEditable = Boolean(t?.closest?.("[data-ohw-editable]"));
19215
+ setHover((prev) => {
19216
+ const next = ref && !overEditable ? ref : null;
19217
+ return sameRef(prev, next) ? prev : next;
19218
+ });
18748
19219
  };
18749
19220
  const onLeave = () => setHover(null);
18750
19221
  document.addEventListener("mousemove", onMove, { passive: true });
@@ -18756,23 +19227,25 @@ function AiMediaStripOverlay({
18756
19227
  }, []);
18757
19228
  useEffect6(() => {
18758
19229
  const onClick = (e) => {
18759
- const current = activeRef.current;
18760
- if (!current) return;
18761
19230
  const t = e.target;
18762
- if (!(t instanceof HTMLElement)) return;
18763
- if (t.closest("[data-ohw-item-interaction]")) return;
18764
- const itemEl = findMediaStripItemElement(current.sectionId, current.index);
18765
- if (itemEl && itemEl.contains(t)) return;
18766
- setActive(null);
19231
+ if (!(t instanceof Element)) return;
19232
+ if (t.closest(CHROME_SELECTOR)) return;
19233
+ const ref = itemRefFromTarget(t);
19234
+ if (sameRef(activeRef.current, ref)) return;
19235
+ if (ref) clearInteractionChromeRef.current();
19236
+ setItemEditOn(false);
18767
19237
  setError(null);
19238
+ setActive(ref);
18768
19239
  };
18769
19240
  document.addEventListener("click", onClick, true);
18770
19241
  return () => document.removeEventListener("click", onClick, true);
18771
- }, []);
19242
+ }, [clearInteractionChromeRef]);
18772
19243
  useEffect6(() => {
18773
19244
  const onBlur = () => {
18774
19245
  if (!activeRef.current) return;
18775
19246
  setActive(null);
19247
+ setItemEditOn(false);
19248
+ setLinkOpen(false);
18776
19249
  setError(null);
18777
19250
  };
18778
19251
  window.addEventListener("blur", onBlur);
@@ -18782,7 +19255,10 @@ function AiMediaStripOverlay({
18782
19255
  const onKeyDown = (e) => {
18783
19256
  if (e.key !== "Escape") return;
18784
19257
  if (!activeRef.current) return;
19258
+ if (linkOpenRef.current) return;
19259
+ if (document.querySelector('[data-ohw-more-menu][data-state="open"]')) return;
18785
19260
  setActive(null);
19261
+ setItemEditOn(false);
18786
19262
  setError(null);
18787
19263
  };
18788
19264
  document.addEventListener("keydown", onKeyDown);
@@ -18816,137 +19292,133 @@ function AiMediaStripOverlay({
18816
19292
  tree: nextTree,
18817
19293
  label: entry.label,
18818
19294
  media: extraMedia ? { ...entry.media, ...extraMedia } : entry.media,
18819
- ...entry.brand ? { brand: entry.brand } : {}
19295
+ ...entry.brand ? { brand: entry.brand } : {},
19296
+ // The user is already looking at the strip they just edited — recentering the viewport
19297
+ // on every edit reads as the section jumping around.
19298
+ skipScrollIntoView: true
18820
19299
  });
18821
19300
  return true;
18822
19301
  },
18823
19302
  [findEntry, postToParent2, applyTreeLocalRef]
18824
19303
  );
18825
19304
  const activeEntry = active ? findEntry(active.sectionId) : null;
18826
- const hoverHandleRect = useLiveItemRect(!active && hover ? hover : null);
18827
19305
  const activeItemRect = useLiveItemRect(active);
18828
- const TOOLBAR_APPROX_H = 170;
18829
- const toolbarState = activeItemRect && activeItemRect.top < TOOLBAR_APPROX_H ? "active-bottom" : "active-top";
18830
- return /* @__PURE__ */ jsxs11(Fragment7, { children: [
18831
- hoverHandleRect && hover && /* @__PURE__ */ jsx20(
18832
- "button",
18833
- {
18834
- type: "button",
18835
- "data-ohw-toolbar": "",
18836
- onClick: () => {
18837
- clearInteractionChromeRef.current();
18838
- setActive(hover);
18839
- },
18840
- "aria-label": "Configure media item",
18841
- style: {
18842
- position: "fixed",
18843
- top: hoverHandleRect.top + 8,
18844
- left: hoverHandleRect.left + hoverHandleRect.width - 36,
18845
- width: 28,
18846
- height: 28,
18847
- borderRadius: 8,
18848
- border: `1.5px dashed ${PRIMARY3}`,
18849
- background: "#ffffff",
18850
- color: PRIMARY3,
18851
- display: "flex",
18852
- alignItems: "center",
18853
- justifyContent: "center",
18854
- cursor: "pointer",
18855
- zIndex: 2147483643,
18856
- fontSize: 14,
18857
- fontWeight: 700
18858
- },
18859
- children: /* @__PURE__ */ jsx20(Star, { size: 14, style: { pointerEvents: "none" } })
18860
- }
18861
- ),
18862
- activeItemRect && active && activeEntry && /* @__PURE__ */ jsx20(
18863
- ItemInteractionLayer,
18864
- {
18865
- rect: activeItemRect,
18866
- state: toolbarState,
18867
- toolbar: /* @__PURE__ */ jsx20(
18868
- MediaStripToolbar,
18869
- {
18870
- entry: activeEntry,
18871
- index: active.index,
18872
- pending,
18873
- error,
18874
- onSetVariant: (variant) => runMutation(active.sectionId, (tree) => setMediaStripVariant(tree, variant)),
18875
- onSetAlign: (align) => runMutation(active.sectionId, (tree) => setMediaStripAlign(tree, align)),
18876
- onSetHover: (hoverPreset) => runMutation(active.sectionId, (tree) => setMediaStripHover(tree, hoverPreset)),
18877
- onSetTreatment: (treatment) => runMutation(active.sectionId, (tree) => setMediaStripTreatment(tree, treatment)),
18878
- onResize: (delta) => {
18879
- const slots = findMediaStripSlots(activeEntry.tree);
18880
- const variant = slots?.variant === "reel" ? "reel" : "logos";
18881
- const current = variant === "logos" ? typeof slots?.logoMaxHeight === "number" ? slots.logoMaxHeight : 48 : typeof slots?.cardWidth === "number" ? slots.cardWidth : 240;
18882
- void runMutation(active.sectionId, (tree) => setMediaStripSizing(tree, current + delta));
18883
- },
18884
- onSetAlt: (alt) => {
18885
- void runMutation(active.sectionId, (tree) => setMediaStripItemAlt(tree, active.index, alt));
18886
- },
18887
- onSetLink: (href, linkTarget) => {
19306
+ const activeStripRect = useLiveItemRect(active, true);
19307
+ const hoverRect = useLiveItemRect(hover && !sameRef(hover, active) ? hover : null);
19308
+ const activeItems = activeEntry ? mediaStripItems(activeEntry.tree) : [];
19309
+ const activeItem = active ? activeItems[active.index] : void 0;
19310
+ const activeHref = activeItem?.href ?? "";
19311
+ const activeTarget = activeItem?.linkTarget === "same-tab" ? "same-tab" : "new-tab";
19312
+ return /* @__PURE__ */ jsxs12(Fragment7, { children: [
19313
+ hoverRect && /* @__PURE__ */ jsx21(ItemInteractionLayer, { rect: hoverRect, state: "hover" }),
19314
+ activeItemRect && activeStripRect && active && activeEntry && /* @__PURE__ */ jsxs12(Fragment7, { children: [
19315
+ /* @__PURE__ */ jsx21(ItemInteractionLayer, { rect: activeItemRect, state: "active-top" }),
19316
+ /* @__PURE__ */ jsx21(StripToolbarSlot, { stripRect: activeStripRect, children: /* @__PURE__ */ jsx21(
19317
+ MediaStripToolbar,
19318
+ {
19319
+ entry: activeEntry,
19320
+ index: active.index,
19321
+ pending,
19322
+ error,
19323
+ itemEditOn,
19324
+ onToggleItemEdit: () => setItemEditOn((on) => !on),
19325
+ onSetVariant: (variant) => runMutation(active.sectionId, (tree) => setMediaStripVariant(tree, variant)),
19326
+ onSetAlign: (align) => runMutation(active.sectionId, (tree) => setMediaStripAlign(tree, align)),
19327
+ onSetHover: (hoverPreset) => runMutation(active.sectionId, (tree) => setMediaStripHover(tree, hoverPreset)),
19328
+ onSetTreatment: (treatment) => runMutation(active.sectionId, (tree) => setMediaStripTreatment(tree, treatment)),
19329
+ onResize: (delta) => {
19330
+ const slots = findMediaStripSlots(activeEntry.tree);
19331
+ const variant = slots?.variant === "reel" ? "reel" : "logos";
19332
+ const current = variant === "logos" ? typeof slots?.logoMaxHeight === "number" ? slots.logoMaxHeight : 48 : typeof slots?.cardWidth === "number" ? slots.cardWidth : 240;
19333
+ void runMutation(active.sectionId, (tree) => setMediaStripSizing(tree, current + delta));
19334
+ },
19335
+ onOpenLink: () => setLinkOpen(true),
19336
+ onAddItem: () => {
19337
+ void requestAssetPick(postToParent2).then((picked) => {
19338
+ if (!picked) return;
19339
+ const item = { assetId: picked.assetId, alt: "Logo" };
18888
19340
  void runMutation(
18889
19341
  active.sectionId,
18890
- (tree) => setMediaStripItemLink(tree, active.index, href ? { href, linkTarget } : {})
18891
- );
18892
- },
18893
- onAddItem: () => {
18894
- void requestAssetPick(postToParent2).then((picked) => {
18895
- if (!picked) return;
18896
- const item = { assetId: picked.assetId, alt: "Logo" };
18897
- void runMutation(
18898
- active.sectionId,
18899
- (tree) => addMediaStripItem(tree, item, active.index),
18900
- { [picked.assetId]: picked.url }
18901
- ).then((applied) => {
18902
- if (applied) setActive({ sectionId: active.sectionId, index: active.index + 1 });
18903
- });
19342
+ (tree) => addMediaStripItem(tree, item, active.index),
19343
+ { [picked.assetId]: picked.url }
19344
+ ).then((applied) => {
19345
+ if (applied) setActive({ sectionId: active.sectionId, index: active.index + 1 });
18904
19346
  });
18905
- },
18906
- onChangeImage: () => {
18907
- void requestAssetPick(postToParent2).then((picked) => {
18908
- if (!picked) return;
18909
- void runMutation(
18910
- active.sectionId,
18911
- (tree) => setMediaStripItemAsset(tree, active.index, picked.assetId),
18912
- { [picked.assetId]: picked.url }
18913
- );
18914
- });
18915
- },
18916
- onRemoveItem: () => {
18917
- void runMutation(active.sectionId, (tree) => removeMediaStripItem(tree, active.index)).then(
18918
- (applied) => {
18919
- if (applied) setActive(null);
18920
- }
18921
- );
18922
- },
18923
- onMoveLeft: () => {
18924
- void runMutation(active.sectionId, (tree) => reorderMediaStripItem(tree, active.index, -1)).then(
18925
- (applied) => {
18926
- if (applied) setActive({ sectionId: active.sectionId, index: active.index - 1 });
18927
- }
18928
- );
18929
- },
18930
- onMoveRight: () => {
18931
- void runMutation(active.sectionId, (tree) => reorderMediaStripItem(tree, active.index, 1)).then(
18932
- (applied) => {
18933
- if (applied) setActive({ sectionId: active.sectionId, index: active.index + 1 });
18934
- }
19347
+ });
19348
+ },
19349
+ onChangeImage: () => {
19350
+ void requestAssetPick(postToParent2).then((picked) => {
19351
+ if (!picked) return;
19352
+ void runMutation(
19353
+ active.sectionId,
19354
+ (tree) => setMediaStripItemAsset(tree, active.index, picked.assetId),
19355
+ { [picked.assetId]: picked.url }
18935
19356
  );
18936
- }
19357
+ });
19358
+ },
19359
+ onRemoveItem: () => {
19360
+ void runMutation(active.sectionId, (tree) => removeMediaStripItem(tree, active.index)).then(
19361
+ (applied) => {
19362
+ if (applied) setActive(null);
19363
+ }
19364
+ );
19365
+ },
19366
+ onMoveLeft: () => {
19367
+ void runMutation(active.sectionId, (tree) => reorderMediaStripItem(tree, active.index, -1)).then(
19368
+ (applied) => {
19369
+ if (applied) setActive({ sectionId: active.sectionId, index: active.index - 1 });
19370
+ }
19371
+ );
19372
+ },
19373
+ onMoveRight: () => {
19374
+ void runMutation(active.sectionId, (tree) => reorderMediaStripItem(tree, active.index, 1)).then(
19375
+ (applied) => {
19376
+ if (applied) setActive({ sectionId: active.sectionId, index: active.index + 1 });
19377
+ }
19378
+ );
19379
+ }
19380
+ }
19381
+ ) }),
19382
+ /* @__PURE__ */ jsx21(
19383
+ "div",
19384
+ {
19385
+ "data-ohw-bridge": "",
19386
+ "data-ohw-item-interaction": "",
19387
+ "aria-hidden": true,
19388
+ className: "pointer-events-none fixed rounded-[4px] px-[7px] py-[3px] font-sans text-[11px] font-medium leading-none text-primary-foreground whitespace-nowrap",
19389
+ style: {
19390
+ top: activeItemRect.top + activeItemRect.height + 10,
19391
+ left: activeItemRect.left - 5,
19392
+ background: PRIMARY3,
19393
+ zIndex: 2147483644
19394
+ },
19395
+ children: "Media item"
19396
+ }
19397
+ ),
19398
+ linkOpen && /* @__PURE__ */ jsx21(
19399
+ LinkModal,
19400
+ {
19401
+ href: activeHref,
19402
+ linkTarget: activeTarget,
19403
+ onClose: () => setLinkOpen(false),
19404
+ onSave: (href, linkTarget) => {
19405
+ void runMutation(
19406
+ active.sectionId,
19407
+ (tree) => setMediaStripItemLink(tree, active.index, href ? { href, linkTarget } : {})
19408
+ );
18937
19409
  }
18938
- )
18939
- }
18940
- )
19410
+ }
19411
+ )
19412
+ ] })
18941
19413
  ] });
18942
19414
  }
18943
19415
 
18944
19416
  // src/ui/ai-section/AiPricingCardOverlay.tsx
18945
- import { useCallback as useCallback4, useEffect as useEffect7, useLayoutEffect as useLayoutEffect4, useRef as useRef6, useState as useState9 } from "react";
18946
- import { Check as Check2, ChevronDown as ChevronDown3, Minus, Plus as Plus2, Star as Star2 } from "lucide-react";
18947
- import { Fragment as Fragment8, jsx as jsx21, jsxs as jsxs12 } from "react/jsx-runtime";
19417
+ import { useCallback as useCallback4, useEffect as useEffect7, useLayoutEffect as useLayoutEffect5, useRef as useRef6, useState as useState9 } from "react";
19418
+ import { Check as Check3, ChevronDown as ChevronDown4, Minus as Minus2, Plus as Plus3, Star as Star2 } from "lucide-react";
19419
+ import { Fragment as Fragment8, jsx as jsx22, jsxs as jsxs13 } from "react/jsx-runtime";
18948
19420
  var PRIMARY4 = "var(--ohw-primary, #0885FE)";
18949
- 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]";
19421
+ 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]";
18950
19422
  function findPricingCardElement(sectionId, index) {
18951
19423
  const sectionEl = findByInstanceId(sectionId);
18952
19424
  if (!sectionEl) return null;
@@ -18958,7 +19430,7 @@ function findPricingCardElement(sectionId, index) {
18958
19430
  }
18959
19431
  function cardRefFromTarget(target) {
18960
19432
  if (!(target instanceof Element)) return null;
18961
- if (target.closest(CHROME_SELECTOR)) return null;
19433
+ if (target.closest(CHROME_SELECTOR2)) return null;
18962
19434
  const cardEl = target.closest("[data-ohw-pricing-card]");
18963
19435
  if (!cardEl) return null;
18964
19436
  const sectionEl = cardEl.closest("[data-ohw-section]");
@@ -18968,21 +19440,21 @@ function cardRefFromTarget(target) {
18968
19440
  if (!sectionId || Number.isNaN(index)) return null;
18969
19441
  return { sectionId, index };
18970
19442
  }
18971
- function sameRef(a, b) {
19443
+ function sameRef2(a, b) {
18972
19444
  return a?.sectionId === b?.sectionId && a?.index === b?.index;
18973
19445
  }
18974
- function rectOf(el) {
19446
+ function rectOf2(el) {
18975
19447
  if (!el) return null;
18976
19448
  const r2 = el.getBoundingClientRect();
18977
19449
  if (r2.width <= 0 || r2.height <= 0) return null;
18978
19450
  return { top: r2.top, left: r2.left, width: r2.width, height: r2.height };
18979
19451
  }
18980
19452
  function readRect3(ref) {
18981
- return rectOf(ref && findPricingCardElement(ref.sectionId, ref.index));
19453
+ return rectOf2(ref && findPricingCardElement(ref.sectionId, ref.index));
18982
19454
  }
18983
19455
  function readGridRect(ref) {
18984
19456
  const card = ref && findPricingCardElement(ref.sectionId, ref.index);
18985
- return rectOf(card?.closest("[data-ohw-pricing-collection]") ?? card);
19457
+ return rectOf2(card?.closest("[data-ohw-pricing-collection]") ?? card);
18986
19458
  }
18987
19459
  function useLiveCardRect(ref, grid = false) {
18988
19460
  const [rect, setRect] = useState9(null);
@@ -19012,19 +19484,19 @@ function useLiveCardRect(ref, grid = false) {
19012
19484
  }
19013
19485
  var TOOLBAR_GRID_GAP = 52;
19014
19486
  var TOOLBAR_GRID_GAP_MOBILE = 42;
19015
- var TOOLBAR_EDGE_MARGIN2 = 4;
19487
+ var TOOLBAR_EDGE_MARGIN3 = 4;
19016
19488
  function GridToolbarSlot({ gridRect, children }) {
19017
19489
  const slotRef = useRef6(null);
19018
19490
  const [shift, setShift] = useState9(0);
19019
19491
  const centerX = gridRect.left + gridRect.width / 2;
19020
- useLayoutEffect4(() => {
19492
+ useLayoutEffect5(() => {
19021
19493
  const slot = slotRef.current;
19022
19494
  if (!slot) return;
19023
19495
  const clamp = () => {
19024
19496
  const w = slot.offsetWidth;
19025
19497
  if (w <= 0) return;
19026
19498
  const half = w / 2;
19027
- const clamped = Math.max(TOOLBAR_EDGE_MARGIN2 + half, Math.min(centerX, window.innerWidth - TOOLBAR_EDGE_MARGIN2 - half));
19499
+ const clamped = Math.max(TOOLBAR_EDGE_MARGIN3 + half, Math.min(centerX, window.innerWidth - TOOLBAR_EDGE_MARGIN3 - half));
19028
19500
  setShift((prev) => Math.abs(prev - (clamped - centerX)) < 0.5 ? prev : clamped - centerX);
19029
19501
  };
19030
19502
  clamp();
@@ -19033,7 +19505,7 @@ function GridToolbarSlot({ gridRect, children }) {
19033
19505
  return () => ro.disconnect();
19034
19506
  }, [centerX, children]);
19035
19507
  const mobile = window.innerWidth <= 480;
19036
- return /* @__PURE__ */ jsx21(
19508
+ return /* @__PURE__ */ jsx22(
19037
19509
  "div",
19038
19510
  {
19039
19511
  ref: slotRef,
@@ -19068,7 +19540,7 @@ function requestValidateTree2(postToParent2, tree) {
19068
19540
  });
19069
19541
  }
19070
19542
  function StyleSwatch({ style }) {
19071
- return /* @__PURE__ */ jsx21(
19543
+ return /* @__PURE__ */ jsx22(
19072
19544
  "span",
19073
19545
  {
19074
19546
  "aria-hidden": true,
@@ -19082,35 +19554,35 @@ function StyleSwatch({ style }) {
19082
19554
  );
19083
19555
  }
19084
19556
  function ButtonGlyph() {
19085
- return /* @__PURE__ */ jsxs12("svg", { width: "18", height: "12", viewBox: "0 0 18 12", fill: "none", "aria-hidden": true, style: { pointerEvents: "none" }, children: [
19086
- /* @__PURE__ */ jsx21("rect", { x: "0.75", y: "0.75", width: "16.5", height: "10.5", rx: "3", stroke: "currentColor", strokeWidth: "1.5" }),
19087
- /* @__PURE__ */ jsx21("path", { d: "M5.5 6H12.5", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round" })
19557
+ return /* @__PURE__ */ jsxs13("svg", { width: "18", height: "12", viewBox: "0 0 18 12", fill: "none", "aria-hidden": true, style: { pointerEvents: "none" }, children: [
19558
+ /* @__PURE__ */ jsx22("rect", { x: "0.75", y: "0.75", width: "16.5", height: "10.5", rx: "3", stroke: "currentColor", strokeWidth: "1.5" }),
19559
+ /* @__PURE__ */ jsx22("path", { d: "M5.5 6H12.5", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round" })
19088
19560
  ] });
19089
19561
  }
19090
19562
  function FeatureGlyph({ mode }) {
19091
- return /* @__PURE__ */ jsxs12("svg", { width: "18", height: "18", viewBox: "0 0 18 18", fill: "none", "aria-hidden": true, style: { pointerEvents: "none" }, children: [
19092
- /* @__PURE__ */ jsx21("circle", { cx: "8", cy: "8", r: "7.25", stroke: "currentColor", strokeWidth: "1.5" }),
19093
- /* @__PURE__ */ jsx21("path", { d: "M5 8.2L7.2 10.3L11 6", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round", strokeLinejoin: "round" }),
19094
- /* @__PURE__ */ jsx21("circle", { cx: "13.5", cy: "13.5", r: "4.25", fill: "var(--ohw-background, #ffffff)", stroke: "var(--ohw-background, #ffffff)", strokeWidth: "1" }),
19095
- /* @__PURE__ */ jsx21("circle", { cx: "13.5", cy: "13.5", r: "3.75", fill: "var(--ohw-background, #ffffff)", stroke: PRIMARY4, strokeWidth: "1" }),
19096
- /* @__PURE__ */ jsx21("path", { d: "M11.5 13.5H15.5", stroke: PRIMARY4, strokeWidth: "1.5", strokeLinecap: "round" }),
19097
- mode === "add" && /* @__PURE__ */ jsx21("path", { d: "M13.5 11.5V15.5", stroke: PRIMARY4, strokeWidth: "1.5", strokeLinecap: "round" })
19563
+ return /* @__PURE__ */ jsxs13("svg", { width: "18", height: "18", viewBox: "0 0 18 18", fill: "none", "aria-hidden": true, style: { pointerEvents: "none" }, children: [
19564
+ /* @__PURE__ */ jsx22("circle", { cx: "8", cy: "8", r: "7.25", stroke: "currentColor", strokeWidth: "1.5" }),
19565
+ /* @__PURE__ */ jsx22("path", { d: "M5 8.2L7.2 10.3L11 6", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round", strokeLinejoin: "round" }),
19566
+ /* @__PURE__ */ jsx22("circle", { cx: "13.5", cy: "13.5", r: "4.25", fill: "var(--ohw-background, #ffffff)", stroke: "var(--ohw-background, #ffffff)", strokeWidth: "1" }),
19567
+ /* @__PURE__ */ jsx22("circle", { cx: "13.5", cy: "13.5", r: "3.75", fill: "var(--ohw-background, #ffffff)", stroke: PRIMARY4, strokeWidth: "1" }),
19568
+ /* @__PURE__ */ jsx22("path", { d: "M11.5 13.5H15.5", stroke: PRIMARY4, strokeWidth: "1.5", strokeLinecap: "round" }),
19569
+ mode === "add" && /* @__PURE__ */ jsx22("path", { d: "M13.5 11.5V15.5", stroke: PRIMARY4, strokeWidth: "1.5", strokeLinecap: "round" })
19098
19570
  ] });
19099
19571
  }
19100
- function ToolbarAction({
19572
+ function ToolbarAction2({
19101
19573
  label,
19102
19574
  onClick,
19103
19575
  disabled = false,
19104
19576
  children
19105
19577
  }) {
19106
- const button = /* @__PURE__ */ jsx21(CustomToolbarButton, { type: "button", "aria-label": label, disabled, onClick, className: "size-8", children });
19107
- return /* @__PURE__ */ jsxs12(Tooltip, { children: [
19108
- /* @__PURE__ */ jsx21(TooltipTrigger, { asChild: true, children: disabled ? /* @__PURE__ */ jsx21("span", { className: "inline-flex", children: button }) : button }),
19109
- /* @__PURE__ */ jsx21(TooltipContent, { side: "bottom", sideOffset: 8, children: label })
19578
+ const button = /* @__PURE__ */ jsx22(CustomToolbarButton, { type: "button", "aria-label": label, disabled, onClick, className: "size-8", children });
19579
+ return /* @__PURE__ */ jsxs13(Tooltip, { children: [
19580
+ /* @__PURE__ */ jsx22(TooltipTrigger, { asChild: true, children: disabled ? /* @__PURE__ */ jsx22("span", { className: "inline-flex", children: button }) : button }),
19581
+ /* @__PURE__ */ jsx22(TooltipContent, { side: "bottom", sideOffset: 8, children: label })
19110
19582
  ] });
19111
19583
  }
19112
- function ToolbarSeparator() {
19113
- return /* @__PURE__ */ jsx21("span", { "aria-hidden": true, className: "mx-0.5 block h-6 w-px shrink-0 bg-border" });
19584
+ function ToolbarSeparator2() {
19585
+ return /* @__PURE__ */ jsx22("span", { "aria-hidden": true, className: "mx-0.5 block h-6 w-px shrink-0 bg-border" });
19114
19586
  }
19115
19587
  function CardStyleMenu({
19116
19588
  value,
@@ -19118,7 +19590,7 @@ function CardStyleMenu({
19118
19590
  disabled = false
19119
19591
  }) {
19120
19592
  const current = PRICING_CARD_STYLES.find((s) => s.value === value) ?? PRICING_CARD_STYLES[0];
19121
- const trigger = /* @__PURE__ */ jsxs12(
19593
+ const trigger = /* @__PURE__ */ jsxs13(
19122
19594
  "button",
19123
19595
  {
19124
19596
  type: "button",
@@ -19129,28 +19601,28 @@ function CardStyleMenu({
19129
19601
  disabled && "cursor-not-allowed opacity-40 hover:bg-transparent"
19130
19602
  ),
19131
19603
  children: [
19132
- /* @__PURE__ */ jsx21(StyleSwatch, { style: current.value }),
19133
- /* @__PURE__ */ jsx21("span", { className: "flex-1 text-left", children: current.label }),
19134
- /* @__PURE__ */ jsx21(ChevronDown3, { size: 16, style: { pointerEvents: "none" } })
19604
+ /* @__PURE__ */ jsx22(StyleSwatch, { style: current.value }),
19605
+ /* @__PURE__ */ jsx22("span", { className: "flex-1 text-left", children: current.label }),
19606
+ /* @__PURE__ */ jsx22(ChevronDown4, { size: 16, style: { pointerEvents: "none" } })
19135
19607
  ]
19136
19608
  }
19137
19609
  );
19138
19610
  if (disabled) {
19139
- return /* @__PURE__ */ jsxs12("div", { className: "flex items-center gap-1.5 pl-1.5", children: [
19140
- /* @__PURE__ */ jsx21("span", { className: "text-xs text-muted-foreground opacity-50", children: "Card style" }),
19141
- /* @__PURE__ */ jsxs12(Tooltip, { children: [
19142
- /* @__PURE__ */ jsx21(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsx21("span", { className: "inline-flex", children: trigger }) }),
19143
- /* @__PURE__ */ jsx21(TooltipContent, { side: "bottom", sideOffset: 8, children: "Mark as featured to edit card style" })
19611
+ return /* @__PURE__ */ jsxs13("div", { className: "flex items-center gap-1.5 pl-1.5", children: [
19612
+ /* @__PURE__ */ jsx22("span", { className: "text-xs text-muted-foreground opacity-50", children: "Card style" }),
19613
+ /* @__PURE__ */ jsxs13(Tooltip, { children: [
19614
+ /* @__PURE__ */ jsx22(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsx22("span", { className: "inline-flex", children: trigger }) }),
19615
+ /* @__PURE__ */ jsx22(TooltipContent, { side: "bottom", sideOffset: 8, children: "Mark as featured to edit card style" })
19144
19616
  ] })
19145
19617
  ] });
19146
19618
  }
19147
- return /* @__PURE__ */ jsxs12("div", { className: "flex items-center gap-1.5 pl-1.5", children: [
19148
- /* @__PURE__ */ jsx21("span", { className: "text-xs text-muted-foreground", children: "Card style" }),
19149
- /* @__PURE__ */ jsxs12(DropdownMenu, { modal: false, children: [
19150
- /* @__PURE__ */ jsx21(DropdownMenuTrigger, { asChild: true, children: trigger }),
19151
- /* @__PURE__ */ jsx21(DropdownMenuContent, { align: "end", sideOffset: 8, className: "w-[152px] min-w-[152px] p-1", children: PRICING_CARD_STYLES.map((style) => {
19619
+ return /* @__PURE__ */ jsxs13("div", { className: "flex items-center gap-1.5 pl-1.5", children: [
19620
+ /* @__PURE__ */ jsx22("span", { className: "text-xs text-muted-foreground", children: "Card style" }),
19621
+ /* @__PURE__ */ jsxs13(DropdownMenu, { modal: false, children: [
19622
+ /* @__PURE__ */ jsx22(DropdownMenuTrigger, { asChild: true, children: trigger }),
19623
+ /* @__PURE__ */ jsx22(DropdownMenuContent, { align: "end", sideOffset: 8, className: "w-[152px] min-w-[152px] p-1", children: PRICING_CARD_STYLES.map((style) => {
19152
19624
  const selected = style.value === value;
19153
- return /* @__PURE__ */ jsxs12(
19625
+ return /* @__PURE__ */ jsxs13(
19154
19626
  DropdownMenuItem,
19155
19627
  {
19156
19628
  role: "menuitemradio",
@@ -19158,9 +19630,9 @@ function CardStyleMenu({
19158
19630
  onSelect: () => onChange(style.value),
19159
19631
  className: cn("grid grid-cols-[18px_1fr_16px] gap-2 py-2 pl-2 pr-2 text-[13px]", selected && "bg-muted"),
19160
19632
  children: [
19161
- /* @__PURE__ */ jsx21(StyleSwatch, { style: style.value }),
19162
- /* @__PURE__ */ jsx21("span", { children: style.label }),
19163
- selected && /* @__PURE__ */ jsx21(Check2, { size: 16, className: "text-bridge-primary" })
19633
+ /* @__PURE__ */ jsx22(StyleSwatch, { style: style.value }),
19634
+ /* @__PURE__ */ jsx22("span", { children: style.label }),
19635
+ selected && /* @__PURE__ */ jsx22(Check3, { size: 16, className: "text-bridge-primary" })
19164
19636
  ]
19165
19637
  },
19166
19638
  style.value
@@ -19190,7 +19662,7 @@ function PricingCardToolbar({
19190
19662
  const style = pricingCardStyle(entry.tree, index);
19191
19663
  const featuredTag = pricingCardFeaturedTag(entry.tree, index);
19192
19664
  const mobile = window.innerWidth <= 480;
19193
- return /* @__PURE__ */ jsx21(TooltipProvider, { delayDuration: 0, children: /* @__PURE__ */ jsxs12(
19665
+ return /* @__PURE__ */ jsx22(TooltipProvider, { delayDuration: 0, children: /* @__PURE__ */ jsxs13(
19194
19666
  "div",
19195
19667
  {
19196
19668
  "data-ohw-bridge": "",
@@ -19202,7 +19674,7 @@ function PricingCardToolbar({
19202
19674
  ),
19203
19675
  style: { maxWidth: mobile ? "calc(100vw - 24px)" : void 0 },
19204
19676
  children: [
19205
- /* @__PURE__ */ jsxs12(
19677
+ /* @__PURE__ */ jsxs13(
19206
19678
  "div",
19207
19679
  {
19208
19680
  className: cn(
@@ -19211,8 +19683,8 @@ function PricingCardToolbar({
19211
19683
  mobile ? "flex-wrap justify-center" : "flex-nowrap"
19212
19684
  ),
19213
19685
  children: [
19214
- /* @__PURE__ */ jsxs12(Tooltip, { children: [
19215
- /* @__PURE__ */ jsx21(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsx21(
19686
+ /* @__PURE__ */ jsxs13(Tooltip, { children: [
19687
+ /* @__PURE__ */ jsx22(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsx22(
19216
19688
  CustomToolbarButton,
19217
19689
  {
19218
19690
  type: "button",
@@ -19220,7 +19692,7 @@ function PricingCardToolbar({
19220
19692
  "aria-pressed": featuredTag,
19221
19693
  onClick: () => onSetFeaturedTag(!featuredTag),
19222
19694
  className: "group size-8",
19223
- children: /* @__PURE__ */ jsx21(
19695
+ children: /* @__PURE__ */ jsx22(
19224
19696
  Star2,
19225
19697
  {
19226
19698
  size: 16,
@@ -19235,25 +19707,25 @@ function PricingCardToolbar({
19235
19707
  )
19236
19708
  }
19237
19709
  ) }),
19238
- /* @__PURE__ */ jsx21(TooltipContent, { side: "bottom", sideOffset: 8, children: featuredTag ? "Unmark featured" : "Mark as featured" })
19710
+ /* @__PURE__ */ jsx22(TooltipContent, { side: "bottom", sideOffset: 8, children: featuredTag ? "Unmark featured" : "Mark as featured" })
19239
19711
  ] }),
19240
- /* @__PURE__ */ jsx21(ToolbarSeparator, {}),
19241
- /* @__PURE__ */ jsx21(CardStyleMenu, { value: style, onChange: onSetStyle, disabled: !featuredTag }),
19242
- /* @__PURE__ */ jsx21(ToolbarSeparator, {}),
19243
- /* @__PURE__ */ jsx21(ToolbarAction, { label: "Add button", onClick: onAddButton, disabled: buttonCount >= 2, children: /* @__PURE__ */ jsx21(ButtonGlyph, {}) }),
19244
- /* @__PURE__ */ jsx21(ToolbarAction, { label: "Remove button", onClick: onRemoveButton, disabled: buttonCount === 0, children: /* @__PURE__ */ jsxs12("span", { className: "relative inline-flex", children: [
19245
- /* @__PURE__ */ jsx21(ButtonGlyph, {}),
19246
- /* @__PURE__ */ jsx21(Minus, { size: 10, strokeWidth: 2.5, className: "absolute -right-1.5 -top-1.5 rounded-full bg-background text-bridge-primary" })
19712
+ /* @__PURE__ */ jsx22(ToolbarSeparator2, {}),
19713
+ /* @__PURE__ */ jsx22(CardStyleMenu, { value: style, onChange: onSetStyle, disabled: !featuredTag }),
19714
+ /* @__PURE__ */ jsx22(ToolbarSeparator2, {}),
19715
+ /* @__PURE__ */ jsx22(ToolbarAction2, { label: "Add button", onClick: onAddButton, disabled: buttonCount >= 2, children: /* @__PURE__ */ jsx22(ButtonGlyph, {}) }),
19716
+ /* @__PURE__ */ jsx22(ToolbarAction2, { label: "Remove button", onClick: onRemoveButton, disabled: buttonCount === 0, children: /* @__PURE__ */ jsxs13("span", { className: "relative inline-flex", children: [
19717
+ /* @__PURE__ */ jsx22(ButtonGlyph, {}),
19718
+ /* @__PURE__ */ jsx22(Minus2, { size: 10, strokeWidth: 2.5, className: "absolute -right-1.5 -top-1.5 rounded-full bg-background text-bridge-primary" })
19247
19719
  ] }) }),
19248
- /* @__PURE__ */ jsx21(ToolbarAction, { label: "Add feature", onClick: onAddFeature, disabled: featureCount >= 10, children: /* @__PURE__ */ jsx21(FeatureGlyph, { mode: "add" }) }),
19249
- /* @__PURE__ */ jsx21(ToolbarAction, { label: "Remove feature", onClick: onRemoveFeature, disabled: featureCount <= 1, children: /* @__PURE__ */ jsx21(FeatureGlyph, { mode: "remove" }) }),
19250
- /* @__PURE__ */ jsx21(ToolbarSeparator, {}),
19251
- /* @__PURE__ */ jsx21(ToolbarAction, { label: "Add tier", onClick: onAddTier, disabled: collectionCount >= 4, children: /* @__PURE__ */ jsx21(Plus2, { size: 16, style: { pointerEvents: "none" } }) }),
19252
- /* @__PURE__ */ jsx21(ToolbarAction, { label: "Remove tier", onClick: onRemoveTier, disabled: collectionCount <= 1, children: /* @__PURE__ */ jsx21(Minus, { size: 16, style: { pointerEvents: "none" } }) })
19720
+ /* @__PURE__ */ jsx22(ToolbarAction2, { label: "Add feature", onClick: onAddFeature, disabled: featureCount >= 10, children: /* @__PURE__ */ jsx22(FeatureGlyph, { mode: "add" }) }),
19721
+ /* @__PURE__ */ jsx22(ToolbarAction2, { label: "Remove feature", onClick: onRemoveFeature, disabled: featureCount <= 1, children: /* @__PURE__ */ jsx22(FeatureGlyph, { mode: "remove" }) }),
19722
+ /* @__PURE__ */ jsx22(ToolbarSeparator2, {}),
19723
+ /* @__PURE__ */ jsx22(ToolbarAction2, { label: "Add tier", onClick: onAddTier, disabled: collectionCount >= 4, children: /* @__PURE__ */ jsx22(Plus3, { size: 16, style: { pointerEvents: "none" } }) }),
19724
+ /* @__PURE__ */ jsx22(ToolbarAction2, { label: "Remove tier", onClick: onRemoveTier, disabled: collectionCount <= 1, children: /* @__PURE__ */ jsx22(Minus2, { size: 16, style: { pointerEvents: "none" } }) })
19253
19725
  ]
19254
19726
  }
19255
19727
  ),
19256
- error && /* @__PURE__ */ jsx21("div", { className: "max-w-[260px] rounded-md bg-background px-2 py-1 text-center text-[11px] text-destructive shadow-sm", children: error })
19728
+ error && /* @__PURE__ */ jsx22("div", { className: "max-w-[260px] rounded-md bg-background px-2 py-1 text-center text-[11px] text-destructive shadow-sm", children: error })
19257
19729
  ]
19258
19730
  }
19259
19731
  ) });
@@ -19287,7 +19759,7 @@ function AiPricingCardOverlay({
19287
19759
  const overEditable = Boolean(t?.closest?.("[data-ohw-editable], [data-ohw-role], a, button"));
19288
19760
  setHover((prev) => {
19289
19761
  const next = ref && !overEditable ? ref : null;
19290
- return sameRef(prev, next) ? prev : next;
19762
+ return sameRef2(prev, next) ? prev : next;
19291
19763
  });
19292
19764
  };
19293
19765
  const onLeave = () => setHover(null);
@@ -19302,10 +19774,10 @@ function AiPricingCardOverlay({
19302
19774
  const onClick = (e) => {
19303
19775
  const t = e.target;
19304
19776
  if (!(t instanceof Element)) return;
19305
- if (t.closest(CHROME_SELECTOR)) return;
19777
+ if (t.closest(CHROME_SELECTOR2)) return;
19306
19778
  const ref = cardRefFromTarget(t);
19307
- setActive((prev) => sameRef(prev, ref) ? prev : ref);
19308
- if (!ref || !sameRef(activeRef.current, ref)) setError(null);
19779
+ setActive((prev) => sameRef2(prev, ref) ? prev : ref);
19780
+ if (!ref || !sameRef2(activeRef.current, ref)) setError(null);
19309
19781
  };
19310
19782
  document.addEventListener("click", onClick, true);
19311
19783
  return () => document.removeEventListener("click", onClick, true);
@@ -19370,12 +19842,12 @@ function AiPricingCardOverlay({
19370
19842
  const activeEntry = active ? findEntry(active.sectionId) : null;
19371
19843
  const activeCardRect = useLiveCardRect(active);
19372
19844
  const activeGridRect = useLiveCardRect(active, true);
19373
- const hoverRect = useLiveCardRect(hover && !sameRef(hover, active) ? hover : null);
19374
- return /* @__PURE__ */ jsxs12(Fragment8, { children: [
19375
- hoverRect && /* @__PURE__ */ jsx21(ItemInteractionLayer, { rect: hoverRect, state: "hover" }),
19376
- activeCardRect && activeGridRect && active && activeEntry && /* @__PURE__ */ jsxs12(Fragment8, { children: [
19377
- /* @__PURE__ */ jsx21(ItemInteractionLayer, { rect: activeCardRect, state: "active-top" }),
19378
- /* @__PURE__ */ jsx21(GridToolbarSlot, { gridRect: activeGridRect, children: /* @__PURE__ */ jsx21(
19845
+ const hoverRect = useLiveCardRect(hover && !sameRef2(hover, active) ? hover : null);
19846
+ return /* @__PURE__ */ jsxs13(Fragment8, { children: [
19847
+ hoverRect && /* @__PURE__ */ jsx22(ItemInteractionLayer, { rect: hoverRect, state: "hover" }),
19848
+ activeCardRect && activeGridRect && active && activeEntry && /* @__PURE__ */ jsxs13(Fragment8, { children: [
19849
+ /* @__PURE__ */ jsx22(ItemInteractionLayer, { rect: activeCardRect, state: "active-top" }),
19850
+ /* @__PURE__ */ jsx22(GridToolbarSlot, { gridRect: activeGridRect, children: /* @__PURE__ */ jsx22(
19379
19851
  PricingCardToolbar,
19380
19852
  {
19381
19853
  entry: activeEntry,
@@ -19403,7 +19875,7 @@ function AiPricingCardOverlay({
19403
19875
  onRemoveFeature: () => runMutation(active.sectionId, (tree) => removePricingCardFeature(tree, active.index))
19404
19876
  }
19405
19877
  ) }),
19406
- /* @__PURE__ */ jsx21(
19878
+ /* @__PURE__ */ jsx22(
19407
19879
  "div",
19408
19880
  {
19409
19881
  "data-ohw-bridge": "",
@@ -19496,10 +19968,10 @@ function extractYouTubeId(input) {
19496
19968
  }
19497
19969
 
19498
19970
  // src/ui/ai-section/AiVideoOverlay.tsx
19499
- import { Fragment as Fragment9, jsx as jsx22, jsxs as jsxs13 } from "react/jsx-runtime";
19971
+ import { Fragment as Fragment9, jsx as jsx23, jsxs as jsxs14 } from "react/jsx-runtime";
19500
19972
  var PRIMARY5 = "#0885FE";
19501
- var OHW_BLUE2 = `var(--ohw-primary, ${PRIMARY5})`;
19502
- var OHW_BLUE_DARK2 = `color-mix(in srgb, var(--ohw-primary, ${PRIMARY5}) 60%, #000000)`;
19973
+ var OHW_BLUE = `var(--ohw-primary, ${PRIMARY5})`;
19974
+ var OHW_BLUE_DARK = `color-mix(in srgb, var(--ohw-primary, ${PRIMARY5}) 60%, #000000)`;
19503
19975
  function findVideoBlockElement(sectionId, path) {
19504
19976
  const sectionEl = findByInstanceId(sectionId);
19505
19977
  if (!sectionEl) return null;
@@ -19559,12 +20031,12 @@ function requestValidateTree3(postToParent2, tree) {
19559
20031
  postToParent2({ type: "ow:ai-validate-tree-request", id, tree });
19560
20032
  });
19561
20033
  }
19562
- function Pill2({
20034
+ function Pill({
19563
20035
  label,
19564
20036
  onClick,
19565
20037
  disabled
19566
20038
  }) {
19567
- return /* @__PURE__ */ jsx22(
20039
+ return /* @__PURE__ */ jsx23(
19568
20040
  "button",
19569
20041
  {
19570
20042
  type: "button",
@@ -19580,7 +20052,7 @@ function Pill2({
19580
20052
  border: "none",
19581
20053
  cursor: disabled ? "default" : "pointer",
19582
20054
  background: "#ffffff",
19583
- color: OHW_BLUE_DARK2,
20055
+ color: OHW_BLUE_DARK,
19584
20056
  opacity: disabled ? 0.4 : 1,
19585
20057
  fontSize: 12,
19586
20058
  lineHeight: "16px",
@@ -19623,8 +20095,8 @@ function VideoUrlInput({ value, onCommit }) {
19623
20095
  onCommit(id);
19624
20096
  }
19625
20097
  };
19626
- return /* @__PURE__ */ jsxs13("div", { style: { display: "flex", flexDirection: "column", gap: 4, width: "100%", maxWidth: 280 }, children: [
19627
- /* @__PURE__ */ jsxs13(
20098
+ return /* @__PURE__ */ jsxs14("div", { style: { display: "flex", flexDirection: "column", gap: 4, width: "100%", maxWidth: 280 }, children: [
20099
+ /* @__PURE__ */ jsxs14(
19628
20100
  "div",
19629
20101
  {
19630
20102
  style: {
@@ -19640,12 +20112,12 @@ function VideoUrlInput({ value, onCommit }) {
19640
20112
  boxShadow: invalid ? "inset 0 0 0 1.5px #EF4444" : "none"
19641
20113
  },
19642
20114
  children: [
19643
- /* @__PURE__ */ jsx22(
20115
+ /* @__PURE__ */ jsx23(
19644
20116
  "span",
19645
20117
  {
19646
20118
  style: {
19647
20119
  flex: "0 0 auto",
19648
- color: OHW_BLUE_DARK2,
20120
+ color: OHW_BLUE_DARK,
19649
20121
  fontSize: 10,
19650
20122
  lineHeight: "16px",
19651
20123
  fontWeight: 700,
@@ -19655,7 +20127,7 @@ function VideoUrlInput({ value, onCommit }) {
19655
20127
  children: "Video"
19656
20128
  }
19657
20129
  ),
19658
- /* @__PURE__ */ jsx22(
20130
+ /* @__PURE__ */ jsx23(
19659
20131
  "input",
19660
20132
  {
19661
20133
  type: "text",
@@ -19692,7 +20164,7 @@ function VideoUrlInput({ value, onCommit }) {
19692
20164
  ]
19693
20165
  }
19694
20166
  ),
19695
- invalid && /* @__PURE__ */ jsx22("div", { style: { color: "#FCA5A5", fontSize: 11, textAlign: "center" }, children: "That doesn't look like a YouTube link \u2014 try pasting the video URL again." })
20167
+ invalid && /* @__PURE__ */ jsx23("div", { style: { color: "#FCA5A5", fontSize: 11, textAlign: "center" }, children: "That doesn't look like a YouTube link \u2014 try pasting the video URL again." })
19696
20168
  ] });
19697
20169
  }
19698
20170
  function VideoToolbar({
@@ -19702,7 +20174,7 @@ function VideoToolbar({
19702
20174
  onSetVideoId,
19703
20175
  onRemove
19704
20176
  }) {
19705
- return /* @__PURE__ */ jsxs13(
20177
+ return /* @__PURE__ */ jsxs14(
19706
20178
  "div",
19707
20179
  {
19708
20180
  "data-ohw-bridge": "",
@@ -19714,7 +20186,7 @@ function VideoToolbar({
19714
20186
  gap: 6,
19715
20187
  alignItems: "center",
19716
20188
  width: 280,
19717
- background: OHW_BLUE2,
20189
+ background: OHW_BLUE,
19718
20190
  padding: 8,
19719
20191
  borderRadius: 12,
19720
20192
  boxShadow: "0 8px 24px rgba(0,0,0,0.25)",
@@ -19722,9 +20194,9 @@ function VideoToolbar({
19722
20194
  opacity: pending ? 0.6 : 1
19723
20195
  },
19724
20196
  children: [
19725
- /* @__PURE__ */ jsx22(VideoUrlInput, { value: videoId, onCommit: onSetVideoId }),
19726
- /* @__PURE__ */ jsx22(Pill2, { label: "Remove video", onClick: onRemove, disabled: !videoId }),
19727
- error && /* @__PURE__ */ jsx22("div", { style: { color: "#FCA5A5", fontSize: 11, maxWidth: 220, textAlign: "center" }, children: error })
20197
+ /* @__PURE__ */ jsx23(VideoUrlInput, { value: videoId, onCommit: onSetVideoId }),
20198
+ /* @__PURE__ */ jsx23(Pill, { label: "Remove video", onClick: onRemove, disabled: !videoId }),
20199
+ error && /* @__PURE__ */ jsx23("div", { style: { color: "#FCA5A5", fontSize: 11, maxWidth: 220, textAlign: "center" }, children: error })
19728
20200
  ]
19729
20201
  }
19730
20202
  );
@@ -19844,8 +20316,8 @@ function AiVideoOverlay({
19844
20316
  const activeBlockRect = useLiveBlockRect(active);
19845
20317
  const TOOLBAR_APPROX_H = 90;
19846
20318
  const toolbarState = activeBlockRect && activeBlockRect.top < TOOLBAR_APPROX_H ? "active-bottom" : "active-top";
19847
- return /* @__PURE__ */ jsxs13(Fragment9, { children: [
19848
- hoverHandleRect && hover && /* @__PURE__ */ jsx22(
20319
+ return /* @__PURE__ */ jsxs14(Fragment9, { children: [
20320
+ hoverHandleRect && hover && /* @__PURE__ */ jsx23(
19849
20321
  "button",
19850
20322
  {
19851
20323
  type: "button",
@@ -19873,15 +20345,15 @@ function AiVideoOverlay({
19873
20345
  fontSize: 14,
19874
20346
  fontWeight: 700
19875
20347
  },
19876
- children: /* @__PURE__ */ jsx22(Star3, { size: 14, style: { pointerEvents: "none" } })
20348
+ children: /* @__PURE__ */ jsx23(Star3, { size: 14, style: { pointerEvents: "none" } })
19877
20349
  }
19878
20350
  ),
19879
- activeBlockRect && active && activeEntry && /* @__PURE__ */ jsx22(
20351
+ activeBlockRect && active && activeEntry && /* @__PURE__ */ jsx23(
19880
20352
  ItemInteractionLayer,
19881
20353
  {
19882
20354
  rect: activeBlockRect,
19883
20355
  state: toolbarState,
19884
- toolbar: /* @__PURE__ */ jsx22(
20356
+ toolbar: /* @__PURE__ */ jsx23(
19885
20357
  VideoToolbar,
19886
20358
  {
19887
20359
  videoId: videoIdAt(activeEntry.tree, active.path),
@@ -19956,10 +20428,10 @@ function setCarouselAutoplay(tree, on) {
19956
20428
  }
19957
20429
 
19958
20430
  // src/ui/ai-section/AiFadeCarouselOverlay.tsx
19959
- import { Fragment as Fragment10, jsx as jsx23, jsxs as jsxs14 } from "react/jsx-runtime";
20431
+ import { Fragment as Fragment10, jsx as jsx24, jsxs as jsxs15 } from "react/jsx-runtime";
19960
20432
  var PRIMARY6 = "#0885FE";
19961
- var OHW_BLUE3 = `var(--ohw-primary, ${PRIMARY6})`;
19962
- var OHW_BLUE_DARK3 = `color-mix(in srgb, var(--ohw-primary, ${PRIMARY6}) 60%, #000000)`;
20433
+ var OHW_BLUE2 = `var(--ohw-primary, ${PRIMARY6})`;
20434
+ var OHW_BLUE_DARK2 = `color-mix(in srgb, var(--ohw-primary, ${PRIMARY6}) 60%, #000000)`;
19963
20435
  function findCarouselItemElement(sectionId, index) {
19964
20436
  const sectionEl = findByInstanceId(sectionId);
19965
20437
  if (!sectionEl) return null;
@@ -20038,8 +20510,8 @@ function requestAssetPick2(postToParent2) {
20038
20510
  postToParent2({ type: "ow:asset-pick", id });
20039
20511
  });
20040
20512
  }
20041
- function Pill3({ label, onClick, disabled }) {
20042
- return /* @__PURE__ */ jsx23(
20513
+ function Pill2({ label, onClick, disabled }) {
20514
+ return /* @__PURE__ */ jsx24(
20043
20515
  "button",
20044
20516
  {
20045
20517
  type: "button",
@@ -20055,7 +20527,7 @@ function Pill3({ label, onClick, disabled }) {
20055
20527
  border: "none",
20056
20528
  cursor: disabled ? "default" : "pointer",
20057
20529
  background: "#ffffff",
20058
- color: OHW_BLUE_DARK3,
20530
+ color: OHW_BLUE_DARK2,
20059
20531
  opacity: disabled ? 0.4 : 1,
20060
20532
  fontSize: 12,
20061
20533
  lineHeight: "16px",
@@ -20073,7 +20545,7 @@ function TogglePill({
20073
20545
  onToggle,
20074
20546
  disabled
20075
20547
  }) {
20076
- return /* @__PURE__ */ jsxs14(
20548
+ return /* @__PURE__ */ jsxs15(
20077
20549
  "button",
20078
20550
  {
20079
20551
  type: "button",
@@ -20092,7 +20564,7 @@ function TogglePill({
20092
20564
  border: "none",
20093
20565
  cursor: disabled ? "default" : "pointer",
20094
20566
  background: "#ffffff",
20095
- color: OHW_BLUE_DARK3,
20567
+ color: OHW_BLUE_DARK2,
20096
20568
  opacity: disabled ? 0.4 : 1,
20097
20569
  fontSize: 12,
20098
20570
  lineHeight: "16px",
@@ -20102,7 +20574,7 @@ function TogglePill({
20102
20574
  },
20103
20575
  children: [
20104
20576
  label,
20105
- /* @__PURE__ */ jsx23(
20577
+ /* @__PURE__ */ jsx24(
20106
20578
  "span",
20107
20579
  {
20108
20580
  "aria-hidden": true,
@@ -20115,10 +20587,10 @@ function TogglePill({
20115
20587
  // Off is a grey track, not a washed-out blue: the pill's own white background already
20116
20588
  // carries the brand colour in the text, and a pale blue track next to it read as
20117
20589
  // "disabled control" rather than "switch that is off".
20118
- background: on ? OHW_BLUE_DARK3 : "#CBD5E1",
20590
+ background: on ? OHW_BLUE_DARK2 : "#CBD5E1",
20119
20591
  transition: "background 120ms ease"
20120
20592
  },
20121
- children: /* @__PURE__ */ jsx23(
20593
+ children: /* @__PURE__ */ jsx24(
20122
20594
  "span",
20123
20595
  {
20124
20596
  style: {
@@ -20147,7 +20619,7 @@ function CarouselSlideToolbar({
20147
20619
  onChangeImage,
20148
20620
  onToggleAutoplay
20149
20621
  }) {
20150
- return /* @__PURE__ */ jsxs14(
20622
+ return /* @__PURE__ */ jsxs15(
20151
20623
  "div",
20152
20624
  {
20153
20625
  "data-ohw-bridge": "",
@@ -20158,7 +20630,7 @@ function CarouselSlideToolbar({
20158
20630
  flexDirection: "column",
20159
20631
  gap: 6,
20160
20632
  alignItems: "center",
20161
- background: OHW_BLUE3,
20633
+ background: OHW_BLUE2,
20162
20634
  padding: 8,
20163
20635
  borderRadius: 12,
20164
20636
  boxShadow: "0 8px 24px rgba(0,0,0,0.25)",
@@ -20170,11 +20642,11 @@ function CarouselSlideToolbar({
20170
20642
  pointerEvents: pending ? "none" : "auto"
20171
20643
  },
20172
20644
  children: [
20173
- /* @__PURE__ */ jsxs14("div", { style: { display: "flex", gap: 6, alignItems: "center" }, children: [
20174
- /* @__PURE__ */ jsx23(Pill3, { label: "Change Image", onClick: onChangeImage }),
20175
- /* @__PURE__ */ jsx23(TogglePill, { label: "Autoplay", on: autoplay, onToggle: onToggleAutoplay })
20645
+ /* @__PURE__ */ jsxs15("div", { style: { display: "flex", gap: 6, alignItems: "center" }, children: [
20646
+ /* @__PURE__ */ jsx24(Pill2, { label: "Change Image", onClick: onChangeImage }),
20647
+ /* @__PURE__ */ jsx24(TogglePill, { label: "Autoplay", on: autoplay, onToggle: onToggleAutoplay })
20176
20648
  ] }),
20177
- error && /* @__PURE__ */ jsx23("div", { style: { color: "#FCA5A5", fontSize: 11, maxWidth: 220, textAlign: "center" }, children: error })
20649
+ error && /* @__PURE__ */ jsx24("div", { style: { color: "#FCA5A5", fontSize: 11, maxWidth: 220, textAlign: "center" }, children: error })
20178
20650
  ]
20179
20651
  }
20180
20652
  );
@@ -20314,8 +20786,8 @@ function AiFadeCarouselOverlay({
20314
20786
  const activeItemRect = useLiveItemRect2(active);
20315
20787
  const TOOLBAR_APPROX_H = 90;
20316
20788
  const toolbarState = activeItemRect && activeItemRect.top < TOOLBAR_APPROX_H ? "active-bottom" : "active-top";
20317
- return /* @__PURE__ */ jsxs14(Fragment10, { children: [
20318
- hoverHandleRect && hover && /* @__PURE__ */ jsx23(
20789
+ return /* @__PURE__ */ jsxs15(Fragment10, { children: [
20790
+ hoverHandleRect && hover && /* @__PURE__ */ jsx24(
20319
20791
  "button",
20320
20792
  {
20321
20793
  type: "button",
@@ -20344,10 +20816,10 @@ function AiFadeCarouselOverlay({
20344
20816
  fontSize: 14,
20345
20817
  fontWeight: 700
20346
20818
  },
20347
- children: /* @__PURE__ */ jsx23(Star4, { size: 14, style: { pointerEvents: "none" } })
20819
+ children: /* @__PURE__ */ jsx24(Star4, { size: 14, style: { pointerEvents: "none" } })
20348
20820
  }
20349
20821
  ),
20350
- swapping && /* @__PURE__ */ jsx23(
20822
+ swapping && /* @__PURE__ */ jsx24(
20351
20823
  "div",
20352
20824
  {
20353
20825
  ref: skeletonRef,
@@ -20365,15 +20837,15 @@ function AiFadeCarouselOverlay({
20365
20837
  zIndex: 2147483646,
20366
20838
  pointerEvents: "none"
20367
20839
  },
20368
- children: /* @__PURE__ */ jsx23("style", { children: MEDIA_SKELETON_CSS })
20840
+ children: /* @__PURE__ */ jsx24("style", { children: MEDIA_SKELETON_CSS })
20369
20841
  }
20370
20842
  ),
20371
- activeItemRect && active && activeEntry && /* @__PURE__ */ jsx23(
20843
+ activeItemRect && active && activeEntry && /* @__PURE__ */ jsx24(
20372
20844
  ItemInteractionLayer,
20373
20845
  {
20374
20846
  rect: activeItemRect,
20375
20847
  state: toolbarState,
20376
- toolbar: /* @__PURE__ */ jsx23(
20848
+ toolbar: /* @__PURE__ */ jsx24(
20377
20849
  CarouselSlideToolbar,
20378
20850
  {
20379
20851
  pending,
@@ -20441,190 +20913,55 @@ function isPointOverBridgeChrome(x, y) {
20441
20913
  }
20442
20914
  return false;
20443
20915
  }
20444
- var BUTTON_LIKE_SELECTOR = `button, [role="button"], ${CTA_BUTTON_SELECTOR}`;
20445
- function isButtonLikeElement(el) {
20446
- return el.matches(BUTTON_LIKE_SELECTOR);
20447
- }
20448
- function findClosestButtonLike(target) {
20449
- return target.closest(BUTTON_LIKE_SELECTOR);
20450
- }
20451
- function findButtonAtPoint(x, y, media) {
20452
- const hits = Array.from(
20453
- document.querySelectorAll(BUTTON_LIKE_SELECTOR)
20454
- ).filter(
20455
- (el) => containsPoint(el, x, y) && !(media && el.contains(media)) && // Bridge-injected chrome (e.g. MediaOverlay's own "Replace image" button) is never a
20456
- // site button. Without this, once the media overlay renders its button lands right
20457
- // under the cursor, gets detected as "a button on the media", and dismisses the very
20458
- // overlay it belongs to — which removes the button, so the next hit-test finds nothing
20459
- // and the overlay reappears. Flip-flops every tick: the "Replace image" button flashes.
20460
- !el.closest("[data-ohw-bridge]") && // A carousel's own prev/next/dot controls (OHH-796) are `<button>`s in the template's
20461
- // markup, so the bbox sweep that makes real CTAs win over a full-bleed hero image
20462
- // (OHH-798) claimed them too and drew the editable-item hover frame around a control
20463
- // that isn't content. They move the slideshow; there is nothing there to edit.
20464
- !el.closest("[data-ohw-carousel-control]")
20465
- );
20466
- if (hits.length === 0) return null;
20467
- return hits.reduce((best, el) => best.contains(el) ? el : best);
20468
- }
20469
- function findNavbarCtaAtPoint(x, y) {
20470
- const hits = Array.from(
20471
- document.querySelectorAll('[data-ohw-role="navbar-button"]')
20472
- ).filter((el) => containsPoint(el, x, y) && !el.closest("[data-ohw-bridge]"));
20473
- if (hits.length === 0) return null;
20474
- return hits.reduce((best, el) => best.contains(el) ? el : best);
20475
- }
20476
-
20477
- // src/lib/session-search.ts
20478
- var STORAGE_KEY = "ohw-preserved-search";
20479
- function readPreservedSearch() {
20480
- if (typeof window === "undefined") return "";
20481
- const fromUrl = window.location.search;
20482
- if (fromUrl && fromUrl !== "?") {
20483
- sessionStorage.setItem(STORAGE_KEY, fromUrl);
20484
- return fromUrl;
20485
- }
20486
- return sessionStorage.getItem(STORAGE_KEY) ?? "";
20487
- }
20488
- function isEditSessionActive() {
20489
- const search = readPreservedSearch();
20490
- if (!search || search === "?") return false;
20491
- const q = search.startsWith("?") ? search.slice(1) : search;
20492
- return new URLSearchParams(q).get("mode") === "edit";
20493
- }
20494
-
20495
- // src/ui/link-modal/linkModal.utils.ts
20496
- function parseTarget(target) {
20497
- if (!target) return { pageRoute: "", sectionId: null };
20498
- const hashIndex = target.indexOf("#");
20499
- if (hashIndex === -1) return { pageRoute: target, sectionId: null };
20500
- return {
20501
- pageRoute: target.slice(0, hashIndex),
20502
- sectionId: target.slice(hashIndex + 1) || null
20503
- };
20504
- }
20505
- function normalizePath(path) {
20506
- const trimmed = path.trim();
20507
- if (!trimmed) return "/";
20508
- try {
20509
- if (/^https?:\/\//i.test(trimmed)) {
20510
- return new URL(trimmed).pathname || "/";
20511
- }
20512
- } catch {
20513
- }
20514
- const withSlash = trimmed.startsWith("/") ? trimmed : `/${trimmed}`;
20515
- if (withSlash.length > 1 && withSlash.endsWith("/")) return withSlash.slice(0, -1);
20516
- return withSlash;
20517
- }
20518
- function findPageByPath(pages, path) {
20519
- const normalized = normalizePath(path);
20520
- return pages.find((p) => normalizePath(p.path) === normalized);
20521
- }
20522
- function inferPageTitleFromPath(path) {
20523
- const normalized = normalizePath(path);
20524
- if (normalized === "/") return "Home";
20525
- const segment = normalized.split("/").filter(Boolean).pop() ?? "";
20526
- if (!segment) return "Home";
20527
- return segment.split("-").filter(Boolean).map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join(" ");
20528
- }
20529
- function isInternalPath(route) {
20530
- const trimmed = route.trim();
20531
- if (!trimmed) return false;
20532
- if (/^https?:\/\//i.test(trimmed)) return false;
20533
- if (trimmed.startsWith("/")) return true;
20534
- if (trimmed.includes(" ") || trimmed.includes(".")) return false;
20535
- return true;
20536
- }
20537
- function resolvePage(route, pages) {
20538
- const found = findPageByPath(pages, route);
20539
- if (found) return { path: found.path, title: found.title };
20540
- return { path: normalizePath(route), title: inferPageTitleFromPath(route) };
20541
- }
20542
- function getSectionsForPath(sectionsByPath, pageRoute) {
20543
- if (!sectionsByPath) return [];
20544
- const normalized = normalizePath(pageRoute);
20545
- return sectionsByPath[normalized] ?? sectionsByPath[pageRoute] ?? [];
20546
- }
20547
- function buildTarget(page, section) {
20548
- if (section) return `${page.path}#${section.id}`;
20549
- return page.path;
20550
- }
20551
- function isValidUrl(urlString) {
20552
- try {
20553
- const url = new URL(urlString);
20554
- if (url.protocol !== "http:" && url.protocol !== "https:") return false;
20555
- const hostname = url.hostname;
20556
- if (!hostname || hostname.length === 0) return false;
20557
- if (hostname.includes(" ")) return false;
20558
- if (hostname === "localhost") return true;
20559
- if (!hostname.includes(".")) return false;
20560
- if (!/[a-z]/i.test(hostname)) return false;
20561
- return true;
20562
- } catch {
20563
- return false;
20564
- }
20565
- }
20566
- function normalizeUrl(value) {
20567
- const trimmed = value.trim();
20568
- if (trimmed.startsWith("http://") || trimmed.startsWith("https://")) return trimmed;
20569
- return `https://${trimmed}`;
20570
- }
20571
- function validateUrlInput(value, pages, allPages) {
20572
- const trimmed = value.trim();
20573
- if (!trimmed) return "";
20574
- const exactPage = allPages.find((p) => p.title.toLowerCase() === trimmed.toLowerCase());
20575
- if (exactPage) return "";
20576
- const hasPrefixMatch = pages.some((p) => p.title.toLowerCase().startsWith(trimmed.toLowerCase()));
20577
- if (hasPrefixMatch) {
20578
- return "Select a page from the list or enter a valid URL";
20579
- }
20580
- const hasScheme = /^https?:\/\//i.test(trimmed);
20581
- if (!hasScheme && !trimmed.includes(".")) {
20582
- return "Please enter a valid URL (e.g., https://example.com)";
20583
- }
20584
- if (!isValidUrl(normalizeUrl(trimmed))) {
20585
- return "Please enter a valid URL (e.g., https://example.com)";
20586
- }
20587
- return "";
20588
- }
20589
- function getEditModeInitialState(target, pages, sections) {
20590
- const empty = {
20591
- searchValue: "",
20592
- selectedPage: null,
20593
- selectedSection: null,
20594
- step: "input"
20595
- };
20596
- if (!target) return empty;
20597
- if (isValidUrl(normalizeUrl(target))) {
20598
- return { searchValue: target, selectedPage: null, selectedSection: null, step: "input" };
20599
- }
20600
- const { pageRoute, sectionId } = parseTarget(target);
20601
- if (sectionId) {
20602
- if (isInternalPath(pageRoute)) {
20603
- const page = resolvePage(pageRoute, pages);
20604
- const sec = sections.find((s) => s.id === sectionId);
20605
- return {
20606
- searchValue: page.title,
20607
- selectedPage: page,
20608
- selectedSection: sec ? { id: sec.id, label: sec.label } : null,
20609
- step: "input"
20610
- };
20611
- }
20612
- return { searchValue: pageRoute || target, selectedPage: null, selectedSection: null, step: "input" };
20613
- }
20614
- if (isInternalPath(pageRoute)) {
20615
- const page = resolvePage(pageRoute, pages);
20616
- return {
20617
- searchValue: page.title,
20618
- selectedPage: page,
20619
- selectedSection: null,
20620
- step: "input"
20621
- };
20916
+ var BUTTON_LIKE_SELECTOR = `button, [role="button"], ${CTA_BUTTON_SELECTOR}`;
20917
+ function isButtonLikeElement(el) {
20918
+ return el.matches(BUTTON_LIKE_SELECTOR);
20919
+ }
20920
+ function findClosestButtonLike(target) {
20921
+ return target.closest(BUTTON_LIKE_SELECTOR);
20922
+ }
20923
+ function findButtonAtPoint(x, y, media) {
20924
+ const hits = Array.from(
20925
+ document.querySelectorAll(BUTTON_LIKE_SELECTOR)
20926
+ ).filter(
20927
+ (el) => containsPoint(el, x, y) && !(media && el.contains(media)) && // Bridge-injected chrome (e.g. MediaOverlay's own "Replace image" button) is never a
20928
+ // site button. Without this, once the media overlay renders its button lands right
20929
+ // under the cursor, gets detected as "a button on the media", and dismisses the very
20930
+ // overlay it belongs to — which removes the button, so the next hit-test finds nothing
20931
+ // and the overlay reappears. Flip-flops every tick: the "Replace image" button flashes.
20932
+ !el.closest("[data-ohw-bridge]") && // A carousel's own prev/next/dot controls (OHH-796) are `<button>`s in the template's
20933
+ // markup, so the bbox sweep that makes real CTAs win over a full-bleed hero image
20934
+ // (OHH-798) claimed them too and drew the editable-item hover frame around a control
20935
+ // that isn't content. They move the slideshow; there is nothing there to edit.
20936
+ !el.closest("[data-ohw-carousel-control]")
20937
+ );
20938
+ if (hits.length === 0) return null;
20939
+ return hits.reduce((best, el) => best.contains(el) ? el : best);
20940
+ }
20941
+ function findNavbarCtaAtPoint(x, y) {
20942
+ const hits = Array.from(
20943
+ document.querySelectorAll('[data-ohw-role="navbar-button"]')
20944
+ ).filter((el) => containsPoint(el, x, y) && !el.closest("[data-ohw-bridge]"));
20945
+ if (hits.length === 0) return null;
20946
+ return hits.reduce((best, el) => best.contains(el) ? el : best);
20947
+ }
20948
+
20949
+ // src/lib/session-search.ts
20950
+ var STORAGE_KEY = "ohw-preserved-search";
20951
+ function readPreservedSearch() {
20952
+ if (typeof window === "undefined") return "";
20953
+ const fromUrl = window.location.search;
20954
+ if (fromUrl && fromUrl !== "?") {
20955
+ sessionStorage.setItem(STORAGE_KEY, fromUrl);
20956
+ return fromUrl;
20622
20957
  }
20623
- return { searchValue: target, selectedPage: null, selectedSection: null, step: "input" };
20958
+ return sessionStorage.getItem(STORAGE_KEY) ?? "";
20624
20959
  }
20625
- function filterAvailablePages(pages, existingTargets) {
20626
- const taken = new Set(existingTargets);
20627
- return pages.filter((p) => !taken.has(p.path));
20960
+ function isEditSessionActive() {
20961
+ const search = readPreservedSearch();
20962
+ if (!search || search === "?") return false;
20963
+ const q = search.startsWith("?") ? search.slice(1) : search;
20964
+ return new URLSearchParams(q).get("mode") === "edit";
20628
20965
  }
20629
20966
 
20630
20967
  // src/lib/prefetch-sections.ts
@@ -20726,118 +21063,11 @@ function scrollToHashSectionWhenReady(behavior = "smooth") {
20726
21063
  // src/ui/link-modal/LinkPopover.tsx
20727
21064
  import { useEffect as useEffect13 } from "react";
20728
21065
 
20729
- // src/ui/dialog.tsx
20730
- import * as React9 from "react";
20731
- import { Dialog as DialogPrimitive } from "radix-ui";
20732
- import { X as X2 } from "lucide-react";
20733
- import { jsx as jsx24, jsxs as jsxs15 } from "react/jsx-runtime";
20734
- function Dialog2({
20735
- ...props
20736
- }) {
20737
- return /* @__PURE__ */ jsx24(DialogPrimitive.Root, { "data-slot": "dialog", ...props });
20738
- }
20739
- function DialogPortal({
20740
- ...props
20741
- }) {
20742
- return /* @__PURE__ */ jsx24(DialogPrimitive.Portal, { ...props });
20743
- }
20744
- function DialogOverlay({
20745
- className,
20746
- ...props
20747
- }) {
20748
- return /* @__PURE__ */ jsx24(
20749
- DialogPrimitive.Overlay,
20750
- {
20751
- "data-slot": "dialog-overlay",
20752
- "data-ohw-link-modal-root": "",
20753
- className: cn("fixed inset-0 z-[2147483646] bg-black/50", className),
20754
- ...props
20755
- }
20756
- );
20757
- }
20758
- var DialogContent = React9.forwardRef(
20759
- ({ className, children, showCloseButton = true, container, ...props }, ref) => {
20760
- const positionMode = container ? "absolute" : "fixed";
20761
- return /* @__PURE__ */ jsxs15(DialogPortal, { container: container ?? void 0, children: [
20762
- /* @__PURE__ */ jsx24(DialogOverlay, { className: cn(positionMode, "inset-0") }),
20763
- /* @__PURE__ */ jsxs15(
20764
- DialogPrimitive.Content,
20765
- {
20766
- ref,
20767
- "data-slot": "dialog-content",
20768
- className: cn(
20769
- positionMode,
20770
- "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",
20771
- "rounded-xl border border-border bg-background font-sans shadow-lg outline-none",
20772
- container ? "max-h-[calc(100%-32px)]" : "max-h-[calc(100vh-32px)]",
20773
- className
20774
- ),
20775
- onMouseDown: (e) => e.stopPropagation(),
20776
- ...props,
20777
- children: [
20778
- children,
20779
- showCloseButton ? /* @__PURE__ */ jsx24(
20780
- DialogPrimitive.Close,
20781
- {
20782
- type: "button",
20783
- className: "absolute right-[9px] top-[9px] rounded-sm p-1.5 text-foreground hover:bg-muted/50",
20784
- "aria-label": "Close",
20785
- children: /* @__PURE__ */ jsx24(X2, { size: 16, "aria-hidden": true })
20786
- }
20787
- ) : null
20788
- ]
20789
- }
20790
- )
20791
- ] });
20792
- }
20793
- );
20794
- DialogContent.displayName = DialogPrimitive.Content.displayName;
20795
- function DialogHeader({
20796
- className,
20797
- ...props
20798
- }) {
20799
- return /* @__PURE__ */ jsx24("div", { className: cn("flex flex-col gap-1.5", className), ...props });
20800
- }
20801
- function DialogFooter({
20802
- className,
20803
- ...props
20804
- }) {
20805
- return /* @__PURE__ */ jsx24(
20806
- "div",
20807
- {
20808
- className: cn("flex items-center justify-end gap-2", className),
20809
- ...props
20810
- }
20811
- );
20812
- }
20813
- var DialogTitle = React9.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx24(
20814
- DialogPrimitive.Title,
20815
- {
20816
- ref,
20817
- className: cn(
20818
- "text-lg font-semibold leading-none tracking-tight text-card-foreground",
20819
- className
20820
- ),
20821
- ...props
20822
- }
20823
- ));
20824
- DialogTitle.displayName = DialogPrimitive.Title.displayName;
20825
- var DialogDescription = React9.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx24(
20826
- DialogPrimitive.Description,
20827
- {
20828
- ref,
20829
- className: cn("text-sm text-muted-foreground", className),
20830
- ...props
20831
- }
20832
- ));
20833
- DialogDescription.displayName = DialogPrimitive.Description.displayName;
20834
- var DialogClose = DialogPrimitive.Close;
20835
-
20836
21066
  // src/ui/link-modal/LinkEditorPanel.tsx
20837
21067
  import { Info, X as X4 } from "lucide-react";
20838
21068
 
20839
21069
  // src/ui/link-modal/DestinationBreadcrumb.tsx
20840
- import { ArrowRight as ArrowRight2, File, GalleryVertical } from "lucide-react";
21070
+ import { ArrowRight as ArrowRight3, File, GalleryVertical } from "lucide-react";
20841
21071
  import { jsx as jsx25, jsxs as jsxs16 } from "react/jsx-runtime";
20842
21072
  function DestinationBreadcrumb({
20843
21073
  pageTitle,
@@ -20851,7 +21081,7 @@ function DestinationBreadcrumb({
20851
21081
  /* @__PURE__ */ jsx25("span", { className: "text-sm font-medium leading-none text-foreground!", children: pageTitle })
20852
21082
  ] }),
20853
21083
  /* @__PURE__ */ jsx25(
20854
- ArrowRight2,
21084
+ ArrowRight3,
20855
21085
  {
20856
21086
  size: 16,
20857
21087
  className: "shrink-0 text-muted-foreground",
@@ -20963,7 +21193,7 @@ function Label({ className, ...props }) {
20963
21193
  }
20964
21194
 
20965
21195
  // src/ui/link-modal/UrlOrPageInput.tsx
20966
- import { ChevronDown as ChevronDown4, File as File2, X as X3 } from "lucide-react";
21196
+ import { ChevronDown as ChevronDown5, File as File2, X as X3 } from "lucide-react";
20967
21197
  import { jsx as jsx29, jsxs as jsxs18 } from "react/jsx-runtime";
20968
21198
  function FieldChevron({
20969
21199
  onClick
@@ -20976,7 +21206,7 @@ function FieldChevron({
20976
21206
  onClick,
20977
21207
  "aria-label": "Open page list",
20978
21208
  tabIndex: -1,
20979
- children: /* @__PURE__ */ jsx29(ChevronDown4, { size: 16 })
21209
+ children: /* @__PURE__ */ jsx29(ChevronDown5, { size: 16 })
20980
21210
  }
20981
21211
  );
20982
21212
  }
@@ -21206,7 +21436,7 @@ function LinkEditorPanel({ state, onClose }) {
21206
21436
  // src/ui/link-modal/SectionPickerOverlay.tsx
21207
21437
  import { useCallback as useCallback7, useEffect as useEffect11, useMemo as useMemo2, useRef as useRef10, useState as useState13 } from "react";
21208
21438
  import { createPortal as createPortal2 } from "react-dom";
21209
- import { ArrowLeft as ArrowLeft2, Check as Check3 } from "lucide-react";
21439
+ import { ArrowLeft as ArrowLeft3, Check as Check4 } from "lucide-react";
21210
21440
  import { usePathname, useRouter as useRouter2 } from "next/navigation";
21211
21441
 
21212
21442
  // src/lib/parent-scroll.ts
@@ -21483,7 +21713,7 @@ function SectionPickerOverlay({
21483
21713
  className: "h-8 min-w-0 gap-1 border-border bg-background px-2 py-1.5 shadow-sm hover:bg-muted cursor-pointer",
21484
21714
  onClick: onBack,
21485
21715
  children: [
21486
- /* @__PURE__ */ jsx31(ArrowLeft2, { className: "size-4 shrink-0", "aria-hidden": true }),
21716
+ /* @__PURE__ */ jsx31(ArrowLeft3, { className: "size-4 shrink-0", "aria-hidden": true }),
21487
21717
  "Back"
21488
21718
  ]
21489
21719
  }
@@ -21552,7 +21782,7 @@ function SectionPickerOverlay({
21552
21782
  className: "absolute right-3 top-3 flex size-8 items-center justify-center rounded-full text-white",
21553
21783
  style: { backgroundColor: "var(--ohw-primary, #0885fe)" },
21554
21784
  "aria-hidden": true,
21555
- children: /* @__PURE__ */ jsx31(Check3, { className: "size-5" })
21785
+ children: /* @__PURE__ */ jsx31(Check4, { className: "size-5" })
21556
21786
  }
21557
21787
  ) : null
21558
21788
  ]
@@ -21936,6 +22166,7 @@ function shouldUseDevFixtures() {
21936
22166
  function getLogoElement(el) {
21937
22167
  const marked = el.closest('[data-ohw-role="logo"], [data-ohw-logo]');
21938
22168
  if (marked) return marked;
22169
+ if (el.closest('[data-ohw-editable="icon"]')) return null;
21939
22170
  const root = el.closest("nav, [data-ohw-nav-root], footer");
21940
22171
  if (!root) return null;
21941
22172
  const anchor = el.closest("a");
@@ -22005,7 +22236,7 @@ function addFooterColumnWithPersist({
22005
22236
  import {
22006
22237
  useCallback as useCallback9,
22007
22238
  useEffect as useEffect14,
22008
- useLayoutEffect as useLayoutEffect5,
22239
+ useLayoutEffect as useLayoutEffect6,
22009
22240
  useRef as useRef11,
22010
22241
  useState as useState15
22011
22242
  } from "react";
@@ -22102,7 +22333,7 @@ function FloatingPanel({
22102
22333
  return () => window.removeEventListener("message", onParentScroll);
22103
22334
  }, [open]);
22104
22335
  const positionRef = useRef11(position);
22105
- useLayoutEffect5(() => {
22336
+ useLayoutEffect6(() => {
22106
22337
  if (!open) {
22107
22338
  positionRef.current = position;
22108
22339
  return;
@@ -22112,7 +22343,7 @@ function FloatingPanel({
22112
22343
  }
22113
22344
  positionRef.current = position;
22114
22345
  }, [open, position, liveScroll, parentScroll]);
22115
- useLayoutEffect5(() => {
22346
+ useLayoutEffect6(() => {
22116
22347
  if (!open || !panelRef.current) return;
22117
22348
  const el = panelRef.current;
22118
22349
  const header = el.querySelector("[data-ohw-floating-panel-header]");
@@ -22321,7 +22552,7 @@ function SocialsDisplayPanel({ display, onChange, className }) {
22321
22552
  }
22322
22553
 
22323
22554
  // src/ui/button-style-panel.tsx
22324
- import { Check as Check4 } from "lucide-react";
22555
+ import { Check as Check5 } from "lucide-react";
22325
22556
  import { jsx as jsx35, jsxs as jsxs24 } from "react/jsx-runtime";
22326
22557
  var SIZE_LABEL = { s: "S", m: "M", l: "L" };
22327
22558
  function ButtonStylePanel({
@@ -22392,7 +22623,7 @@ function ButtonStylePanel({
22392
22623
  {
22393
22624
  className: "absolute right-0.5 top-0.5 flex size-5 items-center justify-center rounded-full bg-primary text-primary-foreground",
22394
22625
  "aria-hidden": true,
22395
- children: /* @__PURE__ */ jsx35(Check4, { className: "size-3", strokeWidth: 3 })
22626
+ children: /* @__PURE__ */ jsx35(Check5, { className: "size-3", strokeWidth: 3 })
22396
22627
  }
22397
22628
  ) : null
22398
22629
  ]
@@ -23417,7 +23648,7 @@ function useSectionDrag({
23417
23648
  }
23418
23649
 
23419
23650
  // src/ui/footer-container-chrome.tsx
23420
- import { Plus as Plus3 } from "lucide-react";
23651
+ import { Plus as Plus4 } from "lucide-react";
23421
23652
  import { jsx as jsx36, jsxs as jsxs25 } from "react/jsx-runtime";
23422
23653
  function FooterContainerChrome({
23423
23654
  rect,
@@ -23458,7 +23689,7 @@ function FooterContainerChrome({
23458
23689
  if (addDisabled) return;
23459
23690
  onAdd();
23460
23691
  },
23461
- children: /* @__PURE__ */ jsx36(Plus3, { className: "size-4 shrink-0 text-foreground", "aria-hidden": true })
23692
+ children: /* @__PURE__ */ jsx36(Plus4, { className: "size-4 shrink-0 text-foreground", "aria-hidden": true })
23462
23693
  }
23463
23694
  ) }),
23464
23695
  /* @__PURE__ */ jsx36(TooltipContent, { side: "bottom", sideOffset: 9, children: addDisabled ? "Maximum columns reached" : "Add item" })
@@ -23637,7 +23868,7 @@ function deleteSelectedNavFooterItem(deps) {
23637
23868
  }
23638
23869
 
23639
23870
  // src/ui/navbar-container-chrome.tsx
23640
- import { Plus as Plus4 } from "lucide-react";
23871
+ import { Plus as Plus5 } from "lucide-react";
23641
23872
  import { jsx as jsx37 } from "react/jsx-runtime";
23642
23873
  function NavbarContainerChrome({
23643
23874
  rect,
@@ -23673,7 +23904,7 @@ function NavbarContainerChrome({
23673
23904
  e.stopPropagation();
23674
23905
  onAdd();
23675
23906
  },
23676
- children: /* @__PURE__ */ jsx37(Plus4, { className: "size-4 shrink-0 text-foreground", "aria-hidden": true })
23907
+ children: /* @__PURE__ */ jsx37(Plus5, { className: "size-4 shrink-0 text-foreground", "aria-hidden": true })
23677
23908
  }
23678
23909
  )
23679
23910
  }
@@ -23745,7 +23976,7 @@ function Badge({ className, variant, ...props }) {
23745
23976
  }
23746
23977
 
23747
23978
  // src/OhhwellsBridge.tsx
23748
- import { Brush as Brush2, Link as Link2, Plus as Plus5, Settings } from "lucide-react";
23979
+ import { Brush as Brush2, Link as Link2, Plus as Plus6, Settings } from "lucide-react";
23749
23980
  import { Fragment as Fragment13, jsx as jsx40, jsxs as jsxs26 } from "react/jsx-runtime";
23750
23981
  var PRIMARY7 = "#0885FE";
23751
23982
  var IMAGE_FADE_MS = 300;
@@ -24522,7 +24753,7 @@ function getNavigationSelectionParent(el) {
24522
24753
  if (!isFooterLinksContainer(el) && (el.hasAttribute("data-ohw-footer-col") || el.hasAttribute("data-ohw-footer-column") || isInferredFooterGroup2(el))) {
24523
24754
  return resolveFooterLinksContainerFrom(el);
24524
24755
  }
24525
- if (el.hasAttribute("data-ohw-nav-container") || el.hasAttribute("data-ohw-nav-drawer") || isFooterLinksContainer(el)) {
24756
+ 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)) {
24526
24757
  return getNavigationRoot(el);
24527
24758
  }
24528
24759
  return null;
@@ -24744,7 +24975,6 @@ var ICONS = {
24744
24975
  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"/>',
24745
24976
  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"/>'
24746
24977
  };
24747
- var HEIGHT_SETTLE_DELAYS = [150, 400, 800];
24748
24978
  var SELECTION_CHROME_GAP2 = 4;
24749
24979
  var TOOLBAR_STROKE_GAP2 = 4;
24750
24980
  var TOOLBAR_OFFSET_FROM_ELEMENT = SELECTION_CHROME_GAP2 + TOOLBAR_STROKE_GAP2;
@@ -24842,7 +25072,7 @@ function applyVisibleViewport(el, parentScroll) {
24842
25072
  el.style.top = `${top}px`;
24843
25073
  el.style.height = `${height}px`;
24844
25074
  }
24845
- var TOOLBAR_EDGE_MARGIN3 = 4;
25075
+ var TOOLBAR_EDGE_MARGIN4 = 4;
24846
25076
  function clampToolbarToClip(top, transform, rect, clip, approxH, gap) {
24847
25077
  const isAbove = transform.includes("translateY(-100%)");
24848
25078
  let visualTop = isAbove ? top - approxH : top;
@@ -24869,8 +25099,8 @@ function clampToolbarToClip(top, transform, rect, clip, approxH, gap) {
24869
25099
  function clampToolbarLeft(centerX, toolbarWidth) {
24870
25100
  const half = toolbarWidth / 2;
24871
25101
  return Math.max(
24872
- TOOLBAR_EDGE_MARGIN3 + half,
24873
- Math.min(centerX, window.innerWidth - TOOLBAR_EDGE_MARGIN3 - half)
25102
+ TOOLBAR_EDGE_MARGIN4 + half,
25103
+ Math.min(centerX, window.innerWidth - TOOLBAR_EDGE_MARGIN4 - half)
24874
25104
  );
24875
25105
  }
24876
25106
  function calcToolbarPos(rect, parentScroll, toolbarW = 306) {
@@ -25271,7 +25501,7 @@ function OhhwellsBridge() {
25271
25501
  }, []);
25272
25502
  const subdomainFromQuery = searchParams.get("subdomain");
25273
25503
  const [subdomain, setSubdomain] = useState18(() => resolveSubdomain(subdomainFromQuery));
25274
- useLayoutEffect6(() => {
25504
+ useLayoutEffect7(() => {
25275
25505
  const next = resolveSubdomain(subdomainFromQuery);
25276
25506
  if (next !== subdomain) setSubdomain(next);
25277
25507
  if (next) return;
@@ -25301,6 +25531,34 @@ function OhhwellsBridge() {
25301
25531
  const [fetchState, setFetchState] = useState18("idle");
25302
25532
  const [showBranding, setShowBranding] = useState18(false);
25303
25533
  const autoSaveTimers = useRef14(/* @__PURE__ */ new Map());
25534
+ useEffect17(() => {
25535
+ stampBridgeVersionOnDocument();
25536
+ }, []);
25537
+ useEffect17(() => {
25538
+ if (isEditMode) return;
25539
+ return armLoaderHangBeacon(subdomain, fetchState);
25540
+ }, [isEditMode, subdomain, fetchState]);
25541
+ useEffect17(() => {
25542
+ if (isEditMode || !subdomain) return;
25543
+ const onError = (event) => {
25544
+ reportOutageBeacon({
25545
+ subdomain,
25546
+ reason: "page_error",
25547
+ detail: event.message || "window error"
25548
+ });
25549
+ };
25550
+ const onRejection = (event) => {
25551
+ const reason = event.reason;
25552
+ const detail = reason instanceof Error ? reason.message : typeof reason === "string" ? reason : "unhandledrejection";
25553
+ reportOutageBeacon({ subdomain, reason: "page_error", detail });
25554
+ };
25555
+ window.addEventListener("error", onError);
25556
+ window.addEventListener("unhandledrejection", onRejection);
25557
+ return () => {
25558
+ window.removeEventListener("error", onError);
25559
+ window.removeEventListener("unhandledrejection", onRejection);
25560
+ };
25561
+ }, [isEditMode, subdomain]);
25304
25562
  const activeElRef = useRef14(null);
25305
25563
  const pointerHeldRef = useRef14(false);
25306
25564
  const selectedElRef = useRef14(null);
@@ -25722,21 +25980,6 @@ function OhhwellsBridge() {
25722
25980
  const [isItemDragging, setIsItemDragging] = useState18(false);
25723
25981
  const [isFooterFrameSelection, setIsFooterFrameSelection] = useState18(false);
25724
25982
  isFooterFrameSelectionRef.current = isFooterFrameSelection;
25725
- const [floatingPanel, setFloatingPanel] = useState18(null);
25726
- const floatingPanelOpenRef = useRef14(false);
25727
- floatingPanelOpenRef.current = floatingPanel !== null;
25728
- const floatingPanelRef = useRef14(null);
25729
- floatingPanelRef.current = floatingPanel;
25730
- const [floatingPanelPos, setFloatingPanelPos] = useState18(null);
25731
- const buttonCollectionDragRef = useRef14(null);
25732
- const buttonDropInsertIndexRef = useRef14(null);
25733
- const [buttonDropSlots, setButtonDropSlots] = useState18([]);
25734
- const [activeButtonDropIndex, setActiveButtonDropIndex] = useState18(null);
25735
- const buttonPointerDragRef = useRef14(null);
25736
- const [buttonStyleTick, setButtonStyleTick] = useState18(0);
25737
- const [logoSizeDraft, setLogoSizeDraft] = useState18(null);
25738
- const [editorViewport, setEditorViewport] = useState18("desktop");
25739
- const [parentScrollSnap, setParentScrollSnap] = useState18(null);
25740
25983
  const [navDropdownPreviewOpen, setNavDropdownPreviewOpen] = useState18(null);
25741
25984
  const [footerHeadingVisible, setFooterHeadingVisible] = useState18(null);
25742
25985
  const footerDragRef = useRef14(null);
@@ -25762,7 +26005,23 @@ function OhhwellsBridge() {
25762
26005
  const stylesRef = useRef14("");
25763
26006
  const appliedDrawerRef = useRef14(null);
25764
26007
  const pendingDeleteUndoRef = useRef14(null);
26008
+ const [floatingPanel, setFloatingPanel] = useState18(null);
26009
+ const floatingPanelOpenRef = useRef14(false);
26010
+ const floatingPanelRef = useRef14(null);
26011
+ floatingPanelRef.current = floatingPanel;
26012
+ const setFloatingPanelRef = useRef14(setFloatingPanel);
26013
+ const [floatingPanelPos, setFloatingPanelPos] = useState18(null);
26014
+ const buttonCollectionDragRef = useRef14(null);
26015
+ const buttonDropInsertIndexRef = useRef14(null);
26016
+ const [buttonDropSlots, setButtonDropSlots] = useState18([]);
26017
+ const [activeButtonDropIndex, setActiveButtonDropIndex] = useState18(null);
26018
+ const buttonPointerDragRef = useRef14(null);
26019
+ const [buttonStyleTick, setButtonStyleTick] = useState18(0);
26020
+ const [logoSizeDraft, setLogoSizeDraft] = useState18(null);
26021
+ const [editorViewport, setEditorViewport] = useState18("desktop");
26022
+ const [parentScrollSnap, setParentScrollSnap] = useState18(null);
25765
26023
  const [sitePages, setSitePages] = useState18([]);
26024
+ const collectionItemsRef = useRef14(/* @__PURE__ */ new Map());
25766
26025
  const [sectionsByPath, setSectionsByPath] = useState18({});
25767
26026
  const sectionsPrefetchGenRef = useRef14(0);
25768
26027
  const setLinkPopoverRef = useRef14(setLinkPopover);
@@ -25770,7 +26029,18 @@ function OhhwellsBridge() {
25770
26029
  const linkPopoverOpenRef = useRef14(false);
25771
26030
  const linkPopoverGraceUntilRef = useRef14(0);
25772
26031
  setLinkPopoverRef.current = setLinkPopover;
26032
+ setFloatingPanelRef.current = setFloatingPanel;
25773
26033
  linkPopoverSessionRef.current = linkPopover;
26034
+ floatingPanelOpenRef.current = Boolean(floatingPanel);
26035
+ useEffect17(() => {
26036
+ const syncViewport = () => {
26037
+ const next = window.innerWidth <= 480 ? "mobile" : "desktop";
26038
+ setEditorViewport((prev) => prev === next ? prev : next);
26039
+ };
26040
+ syncViewport();
26041
+ window.addEventListener("resize", syncViewport);
26042
+ return () => window.removeEventListener("resize", syncViewport);
26043
+ }, []);
25774
26044
  useEffect17(() => {
25775
26045
  setSectionOrderPersistHandler((entries) => {
25776
26046
  const currentPath = window.location.pathname;
@@ -25906,10 +26176,12 @@ function OhhwellsBridge() {
25906
26176
  const onSitePages = (e) => {
25907
26177
  if (e.data?.type !== "ow:site-pages" || !Array.isArray(e.data.pages)) return;
25908
26178
  if (useFixtures) return;
25909
- const mapped = e.data.pages.map((p) => ({
26179
+ const incoming = e.data.pages;
26180
+ const mapped = incoming.map((p) => ({
25910
26181
  path: p.path,
25911
26182
  title: p.title
25912
26183
  }));
26184
+ collectionItemsRef.current = indexCollectionItems(incoming);
25913
26185
  setSitePages(mapped);
25914
26186
  runSectionsPrefetchRef.current(mapped);
25915
26187
  };
@@ -27438,7 +27710,7 @@ function OhhwellsBridge() {
27438
27710
  isFooterFrameSelection,
27439
27711
  postToParent2
27440
27712
  ]);
27441
- useLayoutEffect6(() => {
27713
+ useLayoutEffect7(() => {
27442
27714
  if (!subdomain || isEditMode) {
27443
27715
  setFetchState("done");
27444
27716
  return;
@@ -27465,6 +27737,7 @@ function OhhwellsBridge() {
27465
27737
  applyAiFooterFromContent(content);
27466
27738
  if (typeof content[STYLE_STORE_KEY] === "string") {
27467
27739
  stylesRef.current = content[STYLE_STORE_KEY];
27740
+ document.querySelectorAll('[data-ohw-editable="form"]').forEach((f) => markFormFields(f));
27468
27741
  applyStylesToDom(parseStyleStore(content[STYLE_STORE_KEY]));
27469
27742
  }
27470
27743
  applyBrandChrome(content);
@@ -27475,12 +27748,12 @@ function OhhwellsBridge() {
27475
27748
  if (key === "__ohw_sections") continue;
27476
27749
  if (key === AI_SECTIONS_KEY) continue;
27477
27750
  if (key === AI_FOOTER_KEY) continue;
27751
+ if (key === LOGO_PLACEHOLDER_KEY) continue;
27752
+ if (LOGO_IMAGE_KEYS.includes(key)) continue;
27478
27753
  if (key === BRAND_KIT_KEY) continue;
27479
27754
  if (key === STYLE_STORE_KEY) continue;
27480
27755
  if (BRAND_CHROME_KEYS.has(key)) continue;
27481
- if (key === LOGO_PLACEHOLDER_KEY) continue;
27482
27756
  if (key === CUSTOM_CODE_AFTER_HEAD_KEY || key === CUSTOM_CODE_BEFORE_HEAD_KEY) continue;
27483
- if (LOGO_IMAGE_KEYS.includes(key)) continue;
27484
27757
  if (typeof content[AI_FOOTER_KEY] === "string" && content[AI_FOOTER_KEY] && isAiFooterOwnedContentKey(key)) {
27485
27758
  continue;
27486
27759
  }
@@ -27557,7 +27830,17 @@ function OhhwellsBridge() {
27557
27830
  const apiUrl = process.env.NEXT_PUBLIC_FLOWOPS_API_URL ?? "https://flowops-backend-staging-2yfdh7pwpq-as.a.run.app";
27558
27831
  const initialPath = pathname;
27559
27832
  fetchedContentPaths.add(`${subdomain}::${initialPath}`);
27560
- fetch(`${apiUrl}/api/public/sites/${subdomain}/content?path=${encodeURIComponent(initialPath)}`, { cache: "no-store" }).then((r2) => r2.ok ? r2.json() : null).then((data) => {
27833
+ fetch(`${apiUrl}/api/public/sites/${subdomain}/content?path=${encodeURIComponent(initialPath)}`, { cache: "no-store" }).then((r2) => {
27834
+ if (!r2.ok) {
27835
+ reportOutageBeacon({
27836
+ subdomain,
27837
+ reason: "content_fetch_failed",
27838
+ detail: `HTTP ${r2.status}`
27839
+ });
27840
+ return null;
27841
+ }
27842
+ return r2.json();
27843
+ }).then((data) => {
27561
27844
  if (cancelled) return;
27562
27845
  const content = data?.content ?? {};
27563
27846
  const branding = Boolean(data?.showBranding);
@@ -27566,7 +27849,12 @@ function OhhwellsBridge() {
27566
27849
  recordOverriddenPaths(subdomain, data?.overriddenPaths);
27567
27850
  setShowBranding(branding);
27568
27851
  return applyContent(content);
27569
- }).catch(() => {
27852
+ }).catch((err) => {
27853
+ reportOutageBeacon({
27854
+ subdomain,
27855
+ reason: "content_fetch_failed",
27856
+ detail: err instanceof Error ? err.message : "fetch failed"
27857
+ });
27570
27858
  }).finally(() => {
27571
27859
  if (!cancelled) setFetchState("done");
27572
27860
  });
@@ -27696,17 +27984,18 @@ function OhhwellsBridge() {
27696
27984
  }
27697
27985
  applyAiFooterFromContent(content);
27698
27986
  if (typeof content[STYLE_STORE_KEY] === "string") {
27987
+ document.querySelectorAll('[data-ohw-editable="form"]').forEach((f) => markFormFields(f));
27699
27988
  applyStylesToDom(parseStyleStore(content[STYLE_STORE_KEY]));
27700
27989
  }
27701
27990
  for (const [key, val] of Object.entries(content)) {
27702
27991
  if (key === "__ohw_sections") continue;
27703
27992
  if (key === AI_SECTIONS_KEY) continue;
27704
27993
  if (key === AI_FOOTER_KEY) continue;
27994
+ if (key === LOGO_PLACEHOLDER_KEY) continue;
27995
+ if (LOGO_IMAGE_KEYS.includes(key)) continue;
27705
27996
  if (key === BRAND_KIT_KEY) continue;
27706
27997
  if (key === STYLE_STORE_KEY) continue;
27707
27998
  if (BRAND_CHROME_KEYS.has(key)) continue;
27708
- if (key === LOGO_PLACEHOLDER_KEY) continue;
27709
- if (LOGO_IMAGE_KEYS.includes(key)) continue;
27710
27999
  if (typeof content[AI_FOOTER_KEY] === "string" && content[AI_FOOTER_KEY] && isAiFooterOwnedContentKey(key)) {
27711
28000
  continue;
27712
28001
  }
@@ -27789,7 +28078,7 @@ function OhhwellsBridge() {
27789
28078
  if (debounceTimer) clearTimeout(debounceTimer);
27790
28079
  };
27791
28080
  }, [subdomain, isEditMode, pathname]);
27792
- useLayoutEffect6(() => {
28081
+ useLayoutEffect7(() => {
27793
28082
  const el = document.getElementById("ohw-loader");
27794
28083
  if (!el) return;
27795
28084
  const visible = Boolean(subdomain) && fetchState !== "done";
@@ -27973,26 +28262,11 @@ function OhhwellsBridge() {
27973
28262
  const t2 = setTimeout(measure, 500);
27974
28263
  const ro = new ResizeObserver(schedule);
27975
28264
  ro.observe(document.body);
27976
- let lastWidth = window.innerWidth;
27977
- let resizeTimers = [];
27978
- const clearResizeTimers = () => {
27979
- resizeTimers.forEach(clearTimeout);
27980
- resizeTimers = [];
27981
- };
27982
- const handleResize = () => {
27983
- if (window.innerWidth === lastWidth) return;
27984
- lastWidth = window.innerWidth;
27985
- clearResizeTimers();
27986
- resizeTimers = HEIGHT_SETTLE_DELAYS.map((delay) => setTimeout(measure, delay));
27987
- };
27988
- window.addEventListener("resize", handleResize);
27989
28265
  return () => {
27990
28266
  clearTimeout(t1);
27991
28267
  clearTimeout(t2);
27992
28268
  if (raf != null) cancelAnimationFrame(raf);
27993
28269
  ro.disconnect();
27994
- clearResizeTimers();
27995
- window.removeEventListener("resize", handleResize);
27996
28270
  };
27997
28271
  }, [pathname, isEditMode, postToParent2]);
27998
28272
  useEffect17(() => {
@@ -28289,6 +28563,13 @@ function OhhwellsBridge() {
28289
28563
  if (target.closest("[data-ohw-state-toggle]")) return;
28290
28564
  if (target.closest("[data-ohw-max-badge]")) return;
28291
28565
  if (isInsideLinkEditor(target)) return;
28566
+ if (isInsideFloatingPanel(target)) return;
28567
+ if (target.closest("[data-ohw-form-toolbar]")) return;
28568
+ if (target.closest(
28569
+ '[data-ohw-field-toolbar], [data-ohw-field-type-picker], [data-radix-popper-content-wrapper], [role="menu"], [data-slot="dropdown-menu-content"]'
28570
+ )) {
28571
+ return;
28572
+ }
28292
28573
  if (selectedMediaElRef.current && !selectedMediaElRef.current.contains(target) && !target.closest("[data-ohw-media-overlay], [data-ohw-edit-chrome], [data-ohw-item-interaction]")) {
28293
28574
  clearMediaSelectionRef.current();
28294
28575
  }
@@ -28312,11 +28593,15 @@ function OhhwellsBridge() {
28312
28593
  return;
28313
28594
  }
28314
28595
  }
28315
- if (isInsideFloatingPanel(target)) return;
28316
- if (target.closest("[data-ohw-form-toolbar]")) return;
28317
- if (target.closest(
28318
- '[data-ohw-field-toolbar], [data-ohw-field-type-picker], [data-radix-popper-content-wrapper], [role="menu"], [data-slot="dropdown-menu-content"]'
28319
- )) {
28596
+ const collectionItem = matchCollectionItemClick(target, collectionItemsRef.current);
28597
+ if (collectionItem) {
28598
+ e.preventDefault();
28599
+ e.stopPropagation();
28600
+ postToParentRef.current({
28601
+ type: "ow:collection-item-selected",
28602
+ path: collectionItem.path,
28603
+ collection: collectionItem.collection
28604
+ });
28320
28605
  return;
28321
28606
  }
28322
28607
  {
@@ -28472,9 +28757,13 @@ function OhhwellsBridge() {
28472
28757
  if (isIconEditable(editable) && !getSocialItem(editable)) {
28473
28758
  e.preventDefault();
28474
28759
  e.stopPropagation();
28475
- selectIconElementRef.current(editable);
28476
28760
  const aiIcon = editable.hasAttribute("data-ai-icon");
28477
28761
  const featureIcon = aiIcon && /\.features\.\d+\.icon$/.test(editable.dataset.ohwKey ?? "");
28762
+ if (aiIcon) {
28763
+ aiSectionApiRef.current?.selectFromElement(editable);
28764
+ } else {
28765
+ selectIconElementRef.current(editable);
28766
+ }
28478
28767
  postToParentRef.current({
28479
28768
  type: "ow:icon-pick",
28480
28769
  key: editable.dataset.ohwKey ?? "",
@@ -28702,6 +28991,9 @@ function OhhwellsBridge() {
28702
28991
  setHoveredItemRect(null);
28703
28992
  hoveredNavContainerRef.current = null;
28704
28993
  setHoveredNavContainerRect(null);
28994
+ siblingHintElRef.current = null;
28995
+ setSiblingHintRect(null);
28996
+ setSiblingHintRects([]);
28705
28997
  return;
28706
28998
  }
28707
28999
  {
@@ -28822,7 +29114,6 @@ function OhhwellsBridge() {
28822
29114
  hoveredNavContainerRef.current = null;
28823
29115
  setHoveredNavContainerRect(null);
28824
29116
  hoveredItemElRef.current = editable;
28825
- setHoveredItemRect(isSelectedForHoverRef.current(editable) ? null : editable.getBoundingClientRect());
28826
29117
  }
28827
29118
  }
28828
29119
  }
@@ -29532,7 +29823,11 @@ function OhhwellsBridge() {
29532
29823
  };
29533
29824
  const handleMouseMove = (e) => {
29534
29825
  const { clientX, clientY } = e;
29535
- if (pointOwnedByFloatingPanel(clientX, clientY)) return;
29826
+ if (pointOwnedByFloatingPanel(clientX, clientY)) {
29827
+ dismissImageHover();
29828
+ clearImageHover();
29829
+ return;
29830
+ }
29536
29831
  if (isNavDrawerOpen() && isPointOutsideOpenDrawer(clientX, clientY)) {
29537
29832
  document.querySelectorAll("[data-ohw-hovered]").forEach((el) => el.removeAttribute("data-ohw-hovered"));
29538
29833
  formHoverElRef.current = null;
@@ -29561,7 +29856,12 @@ function OhhwellsBridge() {
29561
29856
  setHoveredItemRect(null);
29562
29857
  hoveredNavContainerRef.current = null;
29563
29858
  setHoveredNavContainerRect(null);
29859
+ siblingHintElRef.current = null;
29860
+ setSiblingHintRect(null);
29861
+ setSiblingHintRects([]);
29564
29862
  dismissImageHover();
29863
+ clearImageHover();
29864
+ setSectionGap(null);
29565
29865
  return;
29566
29866
  }
29567
29867
  if (probeSocialsRowAt(clientX, clientY)) return;
@@ -29573,7 +29873,11 @@ function OhhwellsBridge() {
29573
29873
  if (e.data?.type !== "ow:pointer-sync") return;
29574
29874
  const { clientX, clientY } = e.data;
29575
29875
  if (typeof clientX !== "number" || typeof clientY !== "number") return;
29576
- if (pointOwnedByFloatingPanel(clientX, clientY)) return;
29876
+ if (pointOwnedByFloatingPanel(clientX, clientY)) {
29877
+ dismissImageHover();
29878
+ clearImageHover();
29879
+ return;
29880
+ }
29577
29881
  if (isNavDrawerOpen() && isPointOutsideOpenDrawer(clientX, clientY)) return;
29578
29882
  if (probeSocialsRowAt(clientX, clientY)) return;
29579
29883
  probeSectionGapAt(clientX, clientY);
@@ -29953,6 +30257,7 @@ function OhhwellsBridge() {
29953
30257
  applyAiFooterFromContent(content);
29954
30258
  if (typeof content[STYLE_STORE_KEY] === "string") {
29955
30259
  stylesRef.current = content[STYLE_STORE_KEY];
30260
+ document.querySelectorAll('[data-ohw-editable="form"]').forEach((f) => markFormFields(f));
29956
30261
  applyStylesToDom(parseStyleStore(content[STYLE_STORE_KEY]));
29957
30262
  }
29958
30263
  applyBrandChrome(content);
@@ -29967,12 +30272,12 @@ function OhhwellsBridge() {
29967
30272
  }
29968
30273
  if (key === AI_SECTIONS_KEY) continue;
29969
30274
  if (key === AI_FOOTER_KEY) continue;
30275
+ if (key === LOGO_PLACEHOLDER_KEY) continue;
30276
+ if (LOGO_IMAGE_KEYS.includes(key) && !String(val ?? "").trim()) continue;
29970
30277
  if (key === BRAND_KIT_KEY) continue;
29971
30278
  if (key === STYLE_STORE_KEY) continue;
29972
30279
  if (BRAND_CHROME_KEYS.has(key)) continue;
29973
- if (key === LOGO_PLACEHOLDER_KEY) continue;
29974
30280
  if (key === CUSTOM_CODE_AFTER_HEAD_KEY || key === CUSTOM_CODE_BEFORE_HEAD_KEY) continue;
29975
- if (LOGO_IMAGE_KEYS.includes(key) && !String(val ?? "").trim()) continue;
29976
30281
  if (typeof content[AI_FOOTER_KEY] === "string" && content[AI_FOOTER_KEY] && isAiFooterOwnedContentKey(key)) {
29977
30282
  continue;
29978
30283
  }
@@ -30608,12 +30913,24 @@ function OhhwellsBridge() {
30608
30913
  closeLinkPopoverRef.current();
30609
30914
  return;
30610
30915
  }
30916
+ if (floatingPanelOpenRef.current) {
30917
+ setFloatingPanelRef.current(null);
30918
+ deselectRef.current();
30919
+ deactivateRef.current();
30920
+ return;
30921
+ }
30611
30922
  deselectRef.current();
30612
30923
  deactivateRef.current();
30613
30924
  clearMediaSelectionRef.current();
30614
30925
  clearIconSelectionRef.current();
30615
30926
  };
30616
30927
  window.addEventListener("message", handleDeactivate);
30928
+ const handleSelectNav = (e) => {
30929
+ if (e.data?.type !== "ow:select-nav") return;
30930
+ const navContainer = findSelectableNavContainer();
30931
+ if (navContainer) selectFrameRef.current(navContainer);
30932
+ };
30933
+ window.addEventListener("message", handleSelectNav);
30617
30934
  const handleToastAction = (e) => {
30618
30935
  if (e.data?.type !== "ow:toast-action") return;
30619
30936
  const actionId = typeof e.data.actionId === "string" ? e.data.actionId : null;
@@ -31430,10 +31747,11 @@ function OhhwellsBridge() {
31430
31747
  window.removeEventListener("message", handleDeleteSection);
31431
31748
  window.removeEventListener("message", handleDuplicateSection);
31432
31749
  window.removeEventListener("message", handleDeactivate);
31433
- document.documentElement.removeAttribute("data-ohw-panel-dragging");
31750
+ window.removeEventListener("message", handleSelectNav);
31434
31751
  window.removeEventListener("message", handleToastAction);
31435
31752
  window.removeEventListener("message", handleFormCount);
31436
31753
  window.removeEventListener("message", handleUiEscape);
31754
+ document.documentElement.removeAttribute("data-ohw-panel-dragging");
31437
31755
  autoSaveTimers.current.forEach(clearTimeout);
31438
31756
  autoSaveTimers.current.clear();
31439
31757
  if (imageUnhoverTimerRef.current) clearTimeout(imageUnhoverTimerRef.current);
@@ -31727,7 +32045,7 @@ function OhhwellsBridge() {
31727
32045
  postToParent2({
31728
32046
  type: "ow:ready",
31729
32047
  version: "1",
31730
- bridgeVersion: "0.1.152",
32048
+ bridgeVersion: "0.1.153-next.417",
31731
32049
  path: pathname,
31732
32050
  nodes: collectEditableNodes(editContentRef.current),
31733
32051
  sections
@@ -32462,7 +32780,7 @@ function OhhwellsBridge() {
32462
32780
  className: "flex h-7 w-7 items-center justify-center rounded-md text-foreground transition-colors hover:bg-muted/80",
32463
32781
  onClick: () => setFieldTypePickerOpen((open) => !open),
32464
32782
  "data-ohw-add-field": "",
32465
- children: /* @__PURE__ */ jsx40(Plus5, { size: 15, "aria-hidden": true })
32783
+ children: /* @__PURE__ */ jsx40(Plus6, { size: 15, "aria-hidden": true })
32466
32784
  }
32467
32785
  ),
32468
32786
  /* @__PURE__ */ jsx40("div", { className: "mx-0.5 h-5 w-px bg-border" }),
@@ -32952,6 +33270,7 @@ export {
32952
33270
  foldAlignIntoTrees,
32953
33271
  getEditModeInitialState,
32954
33272
  hasMountedContentNodes,
33273
+ indexCollectionItems,
32955
33274
  insertFooterHours,
32956
33275
  isEditSessionActive,
32957
33276
  isPlainLeftClick,
@@ -32959,6 +33278,7 @@ export {
32959
33278
  isRenderableTree,
32960
33279
  isStickySearchParam,
32961
33280
  isValidUrl,
33281
+ matchCollectionItemClick,
32962
33282
  nextViableSlide,
32963
33283
  parseAiFooterState,
32964
33284
  parseAiSectionsState,