@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.cjs CHANGED
@@ -83,6 +83,7 @@ __export(index_exports, {
83
83
  foldAlignIntoTrees: () => foldAlignIntoTrees,
84
84
  getEditModeInitialState: () => getEditModeInitialState,
85
85
  hasMountedContentNodes: () => hasMountedContentNodes,
86
+ indexCollectionItems: () => indexCollectionItems,
86
87
  insertFooterHours: () => insertFooterHours,
87
88
  isEditSessionActive: () => isEditSessionActive,
88
89
  isPlainLeftClick: () => isPlainLeftClick,
@@ -90,6 +91,7 @@ __export(index_exports, {
90
91
  isRenderableTree: () => isRenderableTree,
91
92
  isStickySearchParam: () => isStickySearchParam,
92
93
  isValidUrl: () => isValidUrl,
94
+ matchCollectionItemClick: () => matchCollectionItemClick,
93
95
  nextViableSlide: () => nextViableSlide,
94
96
  parseAiFooterState: () => parseAiFooterState,
95
97
  parseAiSectionsState: () => parseAiSectionsState,
@@ -6652,6 +6654,21 @@ function pricingCardFeatureCount(tree, index) {
6652
6654
  const target = findPricingCollection(tree)?.children?.[index];
6653
6655
  return target ? pricingFeatureList(target).length : 0;
6654
6656
  }
6657
+ function prevailingFeatureIcon(features) {
6658
+ const counts = /* @__PURE__ */ new Map();
6659
+ for (const row of features) {
6660
+ const key = `${row.icon ?? ""}|${row.iconColor ?? ""}`;
6661
+ const entry = counts.get(key) ?? { count: 0, ...row.icon ? { icon: row.icon } : {}, ...row.iconColor ? { iconColor: row.iconColor } : {} };
6662
+ entry.count += 1;
6663
+ counts.set(key, entry);
6664
+ }
6665
+ let best = null;
6666
+ for (const entry of counts.values()) {
6667
+ if (!best || entry.count > best.count) best = entry;
6668
+ }
6669
+ if (!best) return {};
6670
+ return { ...best.icon ? { icon: best.icon } : {}, ...best.iconColor ? { iconColor: best.iconColor } : {} };
6671
+ }
6655
6672
  function addPricingCardFeature(tree, index, text) {
6656
6673
  return withPricingCollection(tree, (collection) => {
6657
6674
  const children = collection.children;
@@ -6660,7 +6677,7 @@ function addPricingCardFeature(tree, index, text) {
6660
6677
  const features = pricingFeatureList(target);
6661
6678
  if (features.length >= PRICING_FEATURES_MAX) return false;
6662
6679
  target.slots = target.slots ?? {};
6663
- target.slots.features = [...features, { text }];
6680
+ target.slots.features = [...features, { text, ...prevailingFeatureIcon(features) }];
6664
6681
  return true;
6665
6682
  });
6666
6683
  }
@@ -7271,6 +7288,21 @@ function applyCustomCodeToHead(content) {
7271
7288
  if (beforeNodes.length > 0) document.head.append(...beforeNodes);
7272
7289
  }
7273
7290
 
7291
+ // src/lib/selectNavContainer.ts
7292
+ function isOnScreen(el) {
7293
+ const rect = el.getBoundingClientRect();
7294
+ if (rect.width <= 0 || rect.height <= 0) return false;
7295
+ if (el.offsetParent !== null) return true;
7296
+ return getComputedStyle(el).position === "fixed";
7297
+ }
7298
+ function findSelectableNavContainer(doc = document) {
7299
+ const visible = Array.from(doc.querySelectorAll("[data-ohw-nav-container]")).filter(isOnScreen);
7300
+ if (visible.length === 0) return null;
7301
+ return visible.reduce(
7302
+ (best, el) => el.getBoundingClientRect().top < best.getBoundingClientRect().top ? el : best
7303
+ );
7304
+ }
7305
+
7274
7306
  // src/ui/ai-tree/aiSectionsManager.tsx
7275
7307
  var import_react_dom = require("react-dom");
7276
7308
  var import_client = require("react-dom/client");
@@ -7844,7 +7876,7 @@ var AI_MOBILE_CSS = [
7844
7876
  // page's first section. Top only (bottom needs no nav clearance); ~96px approximates the
7845
7877
  // template nav's own height (measured ~94px) — there's no shared `--ohw-header-h` var to read
7846
7878
  // an exact figure from (no live measurement exists), so this is a safe approximation, not a
7847
- // pixel-exact fit. Horizontal spacing already comes from `data-ai-container`.
7879
+ // pixel-exact fit. Horizontal spacing already comes from `data-ai-section-inner`.
7848
7880
  '[data-ai-overlay-clearance] [data-ai-overlap-layer="overlay"]{padding-top:96px}',
7849
7881
  // A full-bleed carousel's height is pure arithmetic: it spans the whole window, so 16/9 turns
7850
7882
  // window width straight into height. Measured at 16/9 it came out ~90% of the viewport at EVERY
@@ -7860,8 +7892,10 @@ var AI_MOBILE_CSS = [
7860
7892
  "[data-ai-section]{overflow-x:hidden}",
7861
7893
  // `:not([data-ai-full-bleed])`: this `!important` gutter beat the inline `padding: 0` that a
7862
7894
  // full-bleed section's container sets, so a carousel/media hero that is edge-to-edge on desktop
7863
- // still got a 20px band of section background down both sides on a phone.
7864
- "[data-ai-container]:not([data-ai-full-bleed]){padding:0 20px !important}",
7895
+ // still got a 20px band of section background down both sides on a phone. The footer's own
7896
+ // container keeps the unguarded rule below — `data-ai-full-bleed` can never appear on it.
7897
+ "[data-ai-section-inner]:not([data-ai-full-bleed]){padding:0 20px !important}",
7898
+ "[data-ai-container]{padding:0 20px !important}",
7865
7899
  "[data-ai-row]{display:flex !important;flex-direction:column !important;align-items:stretch !important}",
7866
7900
  "[data-ai-cell]{width:100%;min-width:0}",
7867
7901
  // The rule above turns every row into a COLUMN flex container on phones, which silently breaks
@@ -8066,6 +8100,31 @@ function pricingCardEmphasis(featured, ctx, hasBg) {
8066
8100
  function textAttrs(ctx, path) {
8067
8101
  return ctx.keyFor ? { "data-ohw-key": ctx.keyFor(path), "data-ohw-editable": "text" } : {};
8068
8102
  }
8103
+ var AI_RESPONSIVE_CSS = [
8104
+ "@media (max-width: 960px) {",
8105
+ " [data-ai-responsive] [data-ai-grid] { grid-template-columns: repeat(2, 1fr) !important; }",
8106
+ ' [data-ai-responsive] [data-ai-grid="1"] { grid-template-columns: 1fr !important; }',
8107
+ "}",
8108
+ "@media (max-width: 640px) {",
8109
+ " [data-ai-responsive] [data-ai-grid] { grid-template-columns: 1fr !important; }",
8110
+ " [data-ai-responsive] [data-ai-columns] > * { grid-column: 1 / -1 !important; }",
8111
+ // Group containers flatten to a column on phones; span placements come along for free.
8112
+ // `:not([data-ai-group="overlap"])` for the same reason as the twin pair in AI_MOBILE_CSS — see
8113
+ // the comment there. This sheet's copy is the narrower selector, so without the exemption it
8114
+ // flattens an `overlap` hero below 640px even though the other rule stands down: measured a 390px
8115
+ // viewport with the heading/CTA in flow *under* the image, 219px of section background where the
8116
+ // layered text used to be.
8117
+ ' [data-ai-responsive] [data-ai-group]:not([data-ai-group="overlap"]) { display: flex !important; flex-direction: column !important; }',
8118
+ ' [data-ai-responsive] [data-ai-group]:not([data-ai-group="overlap"]) > * { grid-column: auto !important; }',
8119
+ // `:not([data-ai-full-bleed])` for the same reason as the twin rule in AI_MOBILE_CSS: this one
8120
+ // is narrower (0,2,0 vs 0,1,0) and also `!important`, so without the guard it wins outright and
8121
+ // a full-bleed hero gets its 20px side band back below 640px only — the one width where the
8122
+ // other rule had already been taught to stand down.
8123
+ " [data-ai-responsive] [data-ai-section-inner]:not([data-ai-full-bleed]) { padding-left: 20px !important; padding-right: 20px !important; }",
8124
+ " [data-ai-responsive] { overflow-x: hidden; }",
8125
+ " [data-ai-responsive] img { max-width: 100%; }",
8126
+ "}"
8127
+ ].join("\n");
8069
8128
  var TRANSPARENT_PX = "data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7";
8070
8129
  function MediaBox({
8071
8130
  refValue,
@@ -8120,7 +8179,7 @@ function MediaBox({
8120
8179
  position: fill ? "absolute" : void 0,
8121
8180
  inset: fill ? 0 : void 0,
8122
8181
  width: fill ? "100%" : fixedSize ? "auto" : "100%",
8123
- height: fill ? "100%" : void 0,
8182
+ height: fill ? "100%" : fixedSize ? height : void 0,
8124
8183
  aspectRatio: fill ? void 0 : aspectRatio,
8125
8184
  minHeight: fill || fixedSize ? void 0 : height ?? (aspectRatio ? void 0 : 180),
8126
8185
  borderRadius: radius,
@@ -9616,7 +9675,7 @@ function CollectionBlock({ node, ctx, path }) {
9616
9675
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
9617
9676
  "div",
9618
9677
  {
9619
- "data-ai-grid": "",
9678
+ "data-ai-grid": String(itemsPerRow),
9620
9679
  "data-ohw-pricing-collection": isPricing ? "" : void 0,
9621
9680
  style: {
9622
9681
  display: "grid",
@@ -9918,7 +9977,7 @@ function renderNode(node, ctx, path, ambientAlign) {
9918
9977
  },
9919
9978
  children: [
9920
9979
  i > 0 && ctx.heroOverlapFullBleed ? (
9921
- // The section's `data-ai-container` gave up its max-width/gutter so the base
9980
+ // The section's `data-ai-section-inner` gave up its max-width/gutter so the base
9922
9981
  // layer (this group's i===0 sibling) can span edge-to-edge — reintroduced here,
9923
9982
  // scoped to just the overlay text, so a heading/copy/CTA stack doesn't stretch
9924
9983
  // the full section width instead of reading at the usual content width.
@@ -10337,6 +10396,7 @@ function AiTreeRenderer({
10337
10396
  ...fillHeight ? { "data-ai-fill-height": "" } : {},
10338
10397
  ...needsOverlayClearance ? { "data-ai-overlay-clearance": "" } : {},
10339
10398
  ...bgAttrs,
10399
+ "data-ai-responsive": "",
10340
10400
  style: {
10341
10401
  position: "relative",
10342
10402
  padding: `${padCss} 0`,
@@ -10350,12 +10410,13 @@ function AiTreeRenderer({
10350
10410
  ...fillHeight ? { display: "grid" } : {}
10351
10411
  },
10352
10412
  children: [
10353
- isOverlay && backgroundUrl && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { position: "absolute", inset: 0, background: `rgba(255,255,255,${overlayAlpha})` } }),
10413
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("style", { children: AI_RESPONSIVE_CSS }),
10354
10414
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("style", { children: AI_MOBILE_CSS }),
10415
+ isOverlay && backgroundUrl && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { position: "absolute", inset: 0, background: `rgba(255,255,255,${overlayAlpha})` } }),
10355
10416
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
10356
10417
  "div",
10357
10418
  {
10358
- "data-ai-container": "",
10419
+ "data-ai-section-inner": "",
10359
10420
  ...fullBleedMedia ? { "data-ai-full-bleed": "" } : {},
10360
10421
  style: {
10361
10422
  position: "relative",
@@ -10372,7 +10433,7 @@ function AiTreeRenderer({
10372
10433
  children: tree.rows.map((row, r2) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
10373
10434
  "div",
10374
10435
  {
10375
- "data-ai-row": "",
10436
+ "data-ai-columns": "",
10376
10437
  style: {
10377
10438
  display: "grid",
10378
10439
  gridTemplateColumns: "repeat(12, 1fr)",
@@ -11034,9 +11095,10 @@ function applyAiSectionsToDom(state, options) {
11034
11095
  if (existing && existing.serialized === serialized && existing.container.isConnected) {
11035
11096
  continue;
11036
11097
  }
11037
- const isNewMount = !existing || !existing.container.isConnected;
11038
11098
  let container = existing?.container;
11099
+ let isNewMount = false;
11039
11100
  if (!container || !container.isConnected) {
11101
+ isNewMount = true;
11040
11102
  existing?.root.unmount();
11041
11103
  container?.remove();
11042
11104
  container = document.createElement("div");
@@ -11680,6 +11742,172 @@ function useLinkHrefGuardian(...deps) {
11680
11742
  }, deps);
11681
11743
  }
11682
11744
 
11745
+ // src/ui/link-modal/linkModal.utils.ts
11746
+ function parseTarget(target) {
11747
+ if (!target) return { pageRoute: "", sectionId: null };
11748
+ const hashIndex = target.indexOf("#");
11749
+ if (hashIndex === -1) return { pageRoute: target, sectionId: null };
11750
+ return {
11751
+ pageRoute: target.slice(0, hashIndex),
11752
+ sectionId: target.slice(hashIndex + 1) || null
11753
+ };
11754
+ }
11755
+ function normalizePath(path) {
11756
+ const trimmed = path.trim();
11757
+ if (!trimmed) return "/";
11758
+ try {
11759
+ if (/^https?:\/\//i.test(trimmed)) {
11760
+ return new URL(trimmed).pathname || "/";
11761
+ }
11762
+ } catch {
11763
+ }
11764
+ const withSlash = trimmed.startsWith("/") ? trimmed : `/${trimmed}`;
11765
+ if (withSlash.length > 1 && withSlash.endsWith("/")) return withSlash.slice(0, -1);
11766
+ return withSlash;
11767
+ }
11768
+ function findPageByPath(pages, path) {
11769
+ const normalized = normalizePath(path);
11770
+ return pages.find((p) => normalizePath(p.path) === normalized);
11771
+ }
11772
+ function inferPageTitleFromPath(path) {
11773
+ const normalized = normalizePath(path);
11774
+ if (normalized === "/") return "Home";
11775
+ const segment = normalized.split("/").filter(Boolean).pop() ?? "";
11776
+ if (!segment) return "Home";
11777
+ return segment.split("-").filter(Boolean).map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join(" ");
11778
+ }
11779
+ function isInternalPath(route) {
11780
+ const trimmed = route.trim();
11781
+ if (!trimmed) return false;
11782
+ if (/^https?:\/\//i.test(trimmed)) return false;
11783
+ if (trimmed.startsWith("/")) return true;
11784
+ if (trimmed.includes(" ") || trimmed.includes(".")) return false;
11785
+ return true;
11786
+ }
11787
+ function resolvePage(route, pages) {
11788
+ const found = findPageByPath(pages, route);
11789
+ if (found) return { path: found.path, title: found.title };
11790
+ return { path: normalizePath(route), title: inferPageTitleFromPath(route) };
11791
+ }
11792
+ function getSectionsForPath(sectionsByPath, pageRoute) {
11793
+ if (!sectionsByPath) return [];
11794
+ const normalized = normalizePath(pageRoute);
11795
+ return sectionsByPath[normalized] ?? sectionsByPath[pageRoute] ?? [];
11796
+ }
11797
+ function buildTarget(page, section) {
11798
+ if (section) return `${page.path}#${section.id}`;
11799
+ return page.path;
11800
+ }
11801
+ function isValidUrl(urlString) {
11802
+ try {
11803
+ const url = new URL(urlString);
11804
+ if (url.protocol !== "http:" && url.protocol !== "https:") return false;
11805
+ const hostname = url.hostname;
11806
+ if (!hostname || hostname.length === 0) return false;
11807
+ if (hostname.includes(" ")) return false;
11808
+ if (hostname === "localhost") return true;
11809
+ if (!hostname.includes(".")) return false;
11810
+ if (!/[a-z]/i.test(hostname)) return false;
11811
+ return true;
11812
+ } catch {
11813
+ return false;
11814
+ }
11815
+ }
11816
+ function normalizeUrl(value) {
11817
+ const trimmed = value.trim();
11818
+ if (trimmed.startsWith("http://") || trimmed.startsWith("https://")) return trimmed;
11819
+ return `https://${trimmed}`;
11820
+ }
11821
+ function validateUrlInput(value, pages, allPages) {
11822
+ const trimmed = value.trim();
11823
+ if (!trimmed) return "";
11824
+ const exactPage = allPages.find((p) => p.title.toLowerCase() === trimmed.toLowerCase());
11825
+ if (exactPage) return "";
11826
+ const hasPrefixMatch = pages.some((p) => p.title.toLowerCase().startsWith(trimmed.toLowerCase()));
11827
+ if (hasPrefixMatch) {
11828
+ return "Select a page from the list or enter a valid URL";
11829
+ }
11830
+ const hasScheme = /^https?:\/\//i.test(trimmed);
11831
+ if (!hasScheme && !trimmed.includes(".")) {
11832
+ return "Please enter a valid URL (e.g., https://example.com)";
11833
+ }
11834
+ if (!isValidUrl(normalizeUrl(trimmed))) {
11835
+ return "Please enter a valid URL (e.g., https://example.com)";
11836
+ }
11837
+ return "";
11838
+ }
11839
+ function getEditModeInitialState(target, pages, sections) {
11840
+ const empty = {
11841
+ searchValue: "",
11842
+ selectedPage: null,
11843
+ selectedSection: null,
11844
+ step: "input"
11845
+ };
11846
+ if (!target) return empty;
11847
+ if (isValidUrl(normalizeUrl(target))) {
11848
+ return { searchValue: target, selectedPage: null, selectedSection: null, step: "input" };
11849
+ }
11850
+ const { pageRoute, sectionId } = parseTarget(target);
11851
+ if (sectionId) {
11852
+ if (isInternalPath(pageRoute)) {
11853
+ const page = resolvePage(pageRoute, pages);
11854
+ const sec = sections.find((s) => s.id === sectionId);
11855
+ return {
11856
+ searchValue: page.title,
11857
+ selectedPage: page,
11858
+ selectedSection: sec ? { id: sec.id, label: sec.label } : null,
11859
+ step: "input"
11860
+ };
11861
+ }
11862
+ return { searchValue: pageRoute || target, selectedPage: null, selectedSection: null, step: "input" };
11863
+ }
11864
+ if (isInternalPath(pageRoute)) {
11865
+ const page = resolvePage(pageRoute, pages);
11866
+ return {
11867
+ searchValue: page.title,
11868
+ selectedPage: page,
11869
+ selectedSection: null,
11870
+ step: "input"
11871
+ };
11872
+ }
11873
+ return { searchValue: target, selectedPage: null, selectedSection: null, step: "input" };
11874
+ }
11875
+ function filterAvailablePages(pages, existingTargets) {
11876
+ const taken = new Set(existingTargets);
11877
+ return pages.filter((p) => !taken.has(p.path));
11878
+ }
11879
+
11880
+ // src/lib/collection-item-click.ts
11881
+ function indexCollectionItems(pages) {
11882
+ const index = /* @__PURE__ */ new Map();
11883
+ for (const page of pages) {
11884
+ if (page.kind !== "COLLECTION_ITEM" || !page.collection || !page.path) continue;
11885
+ index.set(normalizePath(page.path), { path: page.path, collection: page.collection });
11886
+ }
11887
+ return index;
11888
+ }
11889
+ function pathOfHref(href) {
11890
+ const trimmed = href.trim();
11891
+ if (!trimmed || trimmed.startsWith("#")) return null;
11892
+ if (/^[a-z][a-z0-9+.-]*:/i.test(trimmed) && !/^https?:/i.test(trimmed)) return null;
11893
+ const withoutHash = trimmed.split("#")[0];
11894
+ const withoutQuery = withoutHash.split("?")[0];
11895
+ if (!withoutQuery) return null;
11896
+ return normalizePath(withoutQuery);
11897
+ }
11898
+ function matchCollectionItemClick(target, index) {
11899
+ if (!target || index.size === 0) return null;
11900
+ const text = target.closest('[data-ohw-editable="text"], [data-ohw-editable="plain"]');
11901
+ if (!text) return null;
11902
+ const anchor = text.closest("a");
11903
+ if (!anchor || anchor.closest(CTA_BUTTON_SELECTOR)) return null;
11904
+ const href = anchor.getAttribute("href");
11905
+ if (!href) return null;
11906
+ const path = pathOfHref(href);
11907
+ if (!path) return null;
11908
+ return index.get(path) ?? null;
11909
+ }
11910
+
11683
11911
  // src/lib/sticky-search-params.ts
11684
11912
  var STICKY_EXACT = /* @__PURE__ */ new Set([
11685
11913
  // Site identity. Required in local dev and in the canvas editor's iframe, where there is no
@@ -17020,6 +17248,107 @@ function bindPublishedForms(apiUrl, subdomain, content = {}) {
17020
17248
  );
17021
17249
  }
17022
17250
 
17251
+ // src/lib/outage-beacon.ts
17252
+ var DEDUP_MS = 5 * 60 * 1e3;
17253
+ var lastSent = /* @__PURE__ */ new Map();
17254
+ function apiBase() {
17255
+ return process.env.NEXT_PUBLIC_FLOWOPS_API_URL ?? "https://flowops-backend-staging-2yfdh7pwpq-as.a.run.app";
17256
+ }
17257
+ function bridgeVersion() {
17258
+ try {
17259
+ if (true) {
17260
+ return "0.1.153-next.417";
17261
+ }
17262
+ } catch {
17263
+ }
17264
+ if (typeof document !== "undefined") {
17265
+ return document.documentElement.getAttribute("data-ohw-bridge-version") || void 0;
17266
+ }
17267
+ return void 0;
17268
+ }
17269
+ function stampBridgeVersionOnDocument() {
17270
+ if (typeof document === "undefined") {
17271
+ return;
17272
+ }
17273
+ try {
17274
+ const v = true ? "0.1.153-next.417" : "";
17275
+ if (v) {
17276
+ document.documentElement.setAttribute("data-ohw-bridge-version", v);
17277
+ window.__OHW_BRIDGE_VERSION__ = v;
17278
+ }
17279
+ } catch {
17280
+ }
17281
+ }
17282
+ 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;
17283
+ 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;
17284
+ function isVercelPreviewHost() {
17285
+ if (typeof location === "undefined") {
17286
+ return false;
17287
+ }
17288
+ return location.hostname.toLowerCase().endsWith(".vercel.ohhwells.site");
17289
+ }
17290
+ function reportOutageBeacon(args) {
17291
+ if (typeof window === "undefined" || !args.subdomain) {
17292
+ return;
17293
+ }
17294
+ if (isVercelPreviewHost()) {
17295
+ return;
17296
+ }
17297
+ if (args.reason === "page_error" && args.detail && RECOVERABLE_PAGE_ERROR.test(args.detail)) {
17298
+ return;
17299
+ }
17300
+ if (args.reason === "content_fetch_failed" && args.detail && TRANSIENT_CONTENT_FETCH.test(args.detail.trim())) {
17301
+ return;
17302
+ }
17303
+ const key = `${args.subdomain}|${args.reason}`;
17304
+ const now = Date.now();
17305
+ const prev = lastSent.get(key) || 0;
17306
+ if (now - prev < DEDUP_MS) {
17307
+ return;
17308
+ }
17309
+ lastSent.set(key, now);
17310
+ const body = JSON.stringify({
17311
+ subdomain: args.subdomain,
17312
+ reason: args.reason,
17313
+ detail: args.detail?.slice(0, 300),
17314
+ templateId: args.templateId,
17315
+ bridgeVersion: bridgeVersion(),
17316
+ href: typeof location !== "undefined" ? location.href : void 0
17317
+ });
17318
+ const url = `${apiBase()}/api/public/sites/outage-beacon`;
17319
+ try {
17320
+ if (typeof navigator !== "undefined" && typeof navigator.sendBeacon === "function") {
17321
+ const blob = new Blob([body], { type: "application/json" });
17322
+ if (navigator.sendBeacon(url, blob)) {
17323
+ return;
17324
+ }
17325
+ }
17326
+ void fetch(url, {
17327
+ method: "POST",
17328
+ headers: { "Content-Type": "application/json" },
17329
+ body,
17330
+ keepalive: true,
17331
+ cache: "no-store"
17332
+ }).catch(() => {
17333
+ });
17334
+ } catch {
17335
+ }
17336
+ }
17337
+ function armLoaderHangBeacon(subdomain, fetchState, hangMs = 15e3) {
17338
+ if (!subdomain || fetchState !== "loading") {
17339
+ return () => {
17340
+ };
17341
+ }
17342
+ const timer = window.setTimeout(() => {
17343
+ reportOutageBeacon({
17344
+ subdomain,
17345
+ reason: "loader_stuck",
17346
+ detail: `fetchState still loading after ${hangMs}ms`
17347
+ });
17348
+ }, hangMs);
17349
+ return () => window.clearTimeout(timer);
17350
+ }
17351
+
17023
17352
  // src/lib/form-fields.ts
17024
17353
  var FIELD_DEFAULTS = {
17025
17354
  "short-text": { label: "Short text", placeholder: "Type placeholder text..." },
@@ -17620,6 +17949,7 @@ function MediaOverlay({
17620
17949
  (prev) => prev && prev.fullWidth === width && prev.height === height ? prev : { fullWidth: width, height }
17621
17950
  );
17622
17951
  }, [isVideo]);
17952
+ const replaceLabel = isVideo ? "Replace video" : hover.elementType === "bg-image" ? "Replace background" : "Replace image";
17623
17953
  const replaceMode = buttonSize ? replaceButtonMode({ width: rect.width, height: rect.height }, buttonSize) : "none";
17624
17954
  const box = {
17625
17955
  position: "fixed",
@@ -17718,11 +18048,10 @@ function MediaOverlay({
17718
18048
  className: "flex items-center justify-center cursor-pointer",
17719
18049
  style: {
17720
18050
  ...box,
17721
- // When text overlays this media, let clicks fall THROUGH to the text underneath. In the
17722
- // parent this needed a whole `ow:click-at` round-trip to re-hit-test inside the iframe;
17723
- // in-document, pointer-events does it natively. The button below opts back in, so
17724
- // Replace still works.
17725
- pointerEvents: hover.hasTextOverlap || cueOnly ? "none" : "auto",
18051
+ // Visual only. `pointer-events: auto` on this full-bleed rect covered the navbar
18052
+ // (hero bg includes the absolute TopNav) and swallowed CTA press-drag. Text overlay
18053
+ // and cueOnly also need clicks to fall through — Replace / video controls opt back in.
18054
+ pointerEvents: "none",
17726
18055
  // Selected: a firm component ring with no wash, so the image reads as chosen rather
17727
18056
  // than hovered. Hover keeps the existing tinted preview.
17728
18057
  boxShadow: selected ? "inset 0 0 0 2px var(--color-bridge-primary)" : "inset 0 0 0 1.5px var(--color-bridge-primary)",
@@ -17749,17 +18078,17 @@ function MediaOverlay({
17749
18078
  },
17750
18079
  children: [
17751
18080
  isVideo ? /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_lucide_react5.Film, { size: 14 }) : /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_lucide_react5.ImageIcon, { size: 14 }),
17752
- isVideo ? "Replace video" : "Replace image"
18081
+ replaceLabel
17753
18082
  ]
17754
18083
  }
17755
18084
  ),
17756
- replaceMode === "none" || cueOnly ? null : /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(
18085
+ showChrome && replaceMode !== "none" && /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(
17757
18086
  Button,
17758
18087
  {
17759
18088
  "data-ohw-media-overlay": "",
17760
18089
  variant: "outline",
17761
18090
  size: "sm",
17762
- "aria-label": isVideo ? "Replace video" : "Replace image",
18091
+ "aria-label": replaceLabel,
17763
18092
  className: "gap-1.5 cursor-pointer hover:bg-background",
17764
18093
  style: {
17765
18094
  ...OVERLAY_BUTTON_STYLE,
@@ -17782,7 +18111,7 @@ function MediaOverlay({
17782
18111
  },
17783
18112
  children: [
17784
18113
  isVideo ? /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_lucide_react5.Film, { size: 14 }) : /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_lucide_react5.ImageIcon, { size: 14 }),
17785
- replaceMode === "full" ? isVideo ? "Replace video" : hover.elementType === "bg-image" ? "Replace background" : "Replace image" : null
18114
+ replaceMode === "full" ? replaceLabel : null
17786
18115
  ]
17787
18116
  }
17788
18117
  )
@@ -18292,7 +18621,7 @@ function AiSectionOverlay({
18292
18621
 
18293
18622
  // src/ui/ai-section/AiMediaStripOverlay.tsx
18294
18623
  var import_react10 = require("react");
18295
- var import_lucide_react8 = require("lucide-react");
18624
+ var import_lucide_react9 = require("lucide-react");
18296
18625
 
18297
18626
  // src/lib/ai-media-strip-tree.ts
18298
18627
  var MEDIA_STRIP_ITEM_MIN = 1;
@@ -18406,14 +18735,6 @@ function setMediaStripItemAsset(tree, index, assetId) {
18406
18735
  return true;
18407
18736
  });
18408
18737
  }
18409
- function setMediaStripItemAlt(tree, index, alt) {
18410
- return withMediaStrip(tree, (block) => {
18411
- const items = block.items;
18412
- if (index < 0 || index >= items.length) return false;
18413
- items[index] = { ...items[index], alt };
18414
- return true;
18415
- });
18416
- }
18417
18738
  function setMediaStripItemLink(tree, index, link) {
18418
18739
  return withMediaStrip(tree, (block) => {
18419
18740
  const items = block.items;
@@ -18431,38 +18752,165 @@ function setMediaStripItemLink(tree, index, link) {
18431
18752
  });
18432
18753
  }
18433
18754
 
18434
- // src/ui/ai-section/AiMediaStripOverlay.tsx
18755
+ // src/ui/dialog.tsx
18756
+ var React9 = __toESM(require("react"), 1);
18757
+ var import_radix_ui6 = require("radix-ui");
18758
+ var import_lucide_react8 = require("lucide-react");
18435
18759
  var import_jsx_runtime20 = require("react/jsx-runtime");
18436
- var PRIMARY3 = "#0885FE";
18437
- var OHW_BLUE = `var(--ohw-primary, ${PRIMARY3})`;
18438
- var OHW_BLUE_DARK = `color-mix(in srgb, var(--ohw-primary, ${PRIMARY3}) 60%, #000000)`;
18439
- function findMediaStripItemElement(sectionId, index) {
18440
- const sectionEl = findByInstanceId(sectionId);
18441
- if (!sectionEl) return null;
18442
- const items = sectionEl.querySelectorAll("[data-ohw-media-strip-item]");
18443
- for (const el of Array.from(items)) {
18444
- if (Number(el.dataset.ohwMediaStripIndex) === index) return el;
18445
- }
18446
- return null;
18760
+ function Dialog2({
18761
+ ...props
18762
+ }) {
18763
+ return /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(import_radix_ui6.Dialog.Root, { "data-slot": "dialog", ...props });
18447
18764
  }
18448
- function readRect2(ref) {
18449
- const el = ref && findMediaStripItemElement(ref.sectionId, ref.index);
18450
- if (!el) return null;
18451
- const r2 = el.getBoundingClientRect();
18452
- if (r2.width <= 0 || r2.height <= 0) return null;
18453
- return { top: r2.top, left: r2.left, width: r2.width, height: r2.height };
18765
+ function DialogPortal({
18766
+ ...props
18767
+ }) {
18768
+ return /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(import_radix_ui6.Dialog.Portal, { ...props });
18454
18769
  }
18455
- function useLiveItemRect(ref) {
18456
- const [rect, setRect] = (0, import_react10.useState)(null);
18457
- (0, import_react10.useEffect)(() => {
18458
- if (!ref) {
18459
- setRect(null);
18460
- return;
18770
+ function DialogOverlay({
18771
+ className,
18772
+ ...props
18773
+ }) {
18774
+ return /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
18775
+ import_radix_ui6.Dialog.Overlay,
18776
+ {
18777
+ "data-slot": "dialog-overlay",
18778
+ "data-ohw-link-modal-root": "",
18779
+ className: cn("fixed inset-0 z-[2147483646] bg-black/50", className),
18780
+ ...props
18461
18781
  }
18462
- const update = () => {
18463
- const next = readRect2(ref);
18464
- setRect(
18465
- (prev) => prev && next && prev.top === next.top && prev.left === next.left && prev.width === next.width && prev.height === next.height ? prev : next
18782
+ );
18783
+ }
18784
+ var DialogContent = React9.forwardRef(
18785
+ ({ className, children, showCloseButton = true, container, ...props }, ref) => {
18786
+ const positionMode = container ? "absolute" : "fixed";
18787
+ return /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)(DialogPortal, { container: container ?? void 0, children: [
18788
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(DialogOverlay, { className: cn(positionMode, "inset-0") }),
18789
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)(
18790
+ import_radix_ui6.Dialog.Content,
18791
+ {
18792
+ ref,
18793
+ "data-slot": "dialog-content",
18794
+ className: cn(
18795
+ positionMode,
18796
+ "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",
18797
+ "rounded-xl border border-border bg-background font-sans shadow-lg outline-none",
18798
+ container ? "max-h-[calc(100%-32px)]" : "max-h-[calc(100vh-32px)]",
18799
+ className
18800
+ ),
18801
+ onMouseDown: (e) => e.stopPropagation(),
18802
+ ...props,
18803
+ children: [
18804
+ children,
18805
+ showCloseButton ? /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
18806
+ import_radix_ui6.Dialog.Close,
18807
+ {
18808
+ type: "button",
18809
+ className: "absolute right-[9px] top-[9px] rounded-sm p-1.5 text-foreground hover:bg-muted/50",
18810
+ "aria-label": "Close",
18811
+ children: /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(import_lucide_react8.X, { size: 16, "aria-hidden": true })
18812
+ }
18813
+ ) : null
18814
+ ]
18815
+ }
18816
+ )
18817
+ ] });
18818
+ }
18819
+ );
18820
+ DialogContent.displayName = import_radix_ui6.Dialog.Content.displayName;
18821
+ function DialogHeader({
18822
+ className,
18823
+ ...props
18824
+ }) {
18825
+ return /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("div", { className: cn("flex flex-col gap-1.5", className), ...props });
18826
+ }
18827
+ function DialogFooter({
18828
+ className,
18829
+ ...props
18830
+ }) {
18831
+ return /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
18832
+ "div",
18833
+ {
18834
+ className: cn("flex items-center justify-end gap-2", className),
18835
+ ...props
18836
+ }
18837
+ );
18838
+ }
18839
+ var DialogTitle = React9.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
18840
+ import_radix_ui6.Dialog.Title,
18841
+ {
18842
+ ref,
18843
+ className: cn(
18844
+ "text-lg font-semibold leading-none tracking-tight text-card-foreground",
18845
+ className
18846
+ ),
18847
+ ...props
18848
+ }
18849
+ ));
18850
+ DialogTitle.displayName = import_radix_ui6.Dialog.Title.displayName;
18851
+ var DialogDescription = React9.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
18852
+ import_radix_ui6.Dialog.Description,
18853
+ {
18854
+ ref,
18855
+ className: cn("text-sm text-muted-foreground", className),
18856
+ ...props
18857
+ }
18858
+ ));
18859
+ DialogDescription.displayName = import_radix_ui6.Dialog.Description.displayName;
18860
+ var DialogClose = import_radix_ui6.Dialog.Close;
18861
+
18862
+ // src/ui/ai-section/AiMediaStripOverlay.tsx
18863
+ var import_jsx_runtime21 = require("react/jsx-runtime");
18864
+ var PRIMARY3 = "var(--ohw-primary, #0885FE)";
18865
+ 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]";
18866
+ function findMediaStripItemElement(sectionId, index) {
18867
+ const sectionEl = findByInstanceId(sectionId);
18868
+ if (!sectionEl) return null;
18869
+ const items = sectionEl.querySelectorAll("[data-ohw-media-strip-item]");
18870
+ for (const el of Array.from(items)) {
18871
+ if (Number(el.dataset.ohwMediaStripIndex) === index) return el;
18872
+ }
18873
+ return null;
18874
+ }
18875
+ function itemRefFromTarget(target) {
18876
+ if (!(target instanceof Element)) return null;
18877
+ if (target.closest(CHROME_SELECTOR)) return null;
18878
+ const itemEl = target.closest("[data-ohw-media-strip-item]");
18879
+ if (!itemEl) return null;
18880
+ const sectionEl = itemEl.closest("[data-ohw-section]");
18881
+ const unit = sectionEl ? movableUnit(sectionEl) : null;
18882
+ const sectionId = unit ? instanceIdOf(unit) || null : null;
18883
+ const index = Number(itemEl.dataset.ohwMediaStripIndex);
18884
+ if (!sectionId || Number.isNaN(index)) return null;
18885
+ return { sectionId, index };
18886
+ }
18887
+ function sameRef(a, b) {
18888
+ return a?.sectionId === b?.sectionId && a?.index === b?.index;
18889
+ }
18890
+ function rectOf(el) {
18891
+ if (!el) return null;
18892
+ const r2 = el.getBoundingClientRect();
18893
+ if (r2.width <= 0 || r2.height <= 0) return null;
18894
+ return { top: r2.top, left: r2.left, width: r2.width, height: r2.height };
18895
+ }
18896
+ function readRect2(ref) {
18897
+ return rectOf(ref && findMediaStripItemElement(ref.sectionId, ref.index));
18898
+ }
18899
+ function readStripRect(ref) {
18900
+ const item = ref && findMediaStripItemElement(ref.sectionId, ref.index);
18901
+ return rectOf(item?.closest("[data-ohw-media-strip]") ?? item);
18902
+ }
18903
+ function useLiveItemRect(ref, strip = false) {
18904
+ const [rect, setRect] = (0, import_react10.useState)(null);
18905
+ (0, import_react10.useEffect)(() => {
18906
+ if (!ref) {
18907
+ setRect(null);
18908
+ return;
18909
+ }
18910
+ const update = () => {
18911
+ const next = strip ? readStripRect(ref) : readRect2(ref);
18912
+ setRect(
18913
+ (prev) => prev && next && prev.top === next.top && prev.left === next.left && prev.width === next.width && prev.height === next.height ? prev : next
18466
18914
  );
18467
18915
  };
18468
18916
  update();
@@ -18475,9 +18923,49 @@ function useLiveItemRect(ref) {
18475
18923
  window.removeEventListener("resize", update);
18476
18924
  clearInterval(interval);
18477
18925
  };
18478
- }, [ref?.sectionId, ref?.index]);
18926
+ }, [ref?.sectionId, ref?.index, strip]);
18479
18927
  return rect;
18480
18928
  }
18929
+ var TOOLBAR_STRIP_GAP = 52;
18930
+ var TOOLBAR_STRIP_GAP_MOBILE = 42;
18931
+ var TOOLBAR_EDGE_MARGIN2 = 4;
18932
+ function StripToolbarSlot({ stripRect, children }) {
18933
+ const slotRef = (0, import_react10.useRef)(null);
18934
+ const [shift, setShift] = (0, import_react10.useState)(0);
18935
+ const centerX = stripRect.left + stripRect.width / 2;
18936
+ (0, import_react10.useLayoutEffect)(() => {
18937
+ const slot = slotRef.current;
18938
+ if (!slot) return;
18939
+ const clamp = () => {
18940
+ const w = slot.offsetWidth;
18941
+ if (w <= 0) return;
18942
+ const half = w / 2;
18943
+ const clamped = Math.max(TOOLBAR_EDGE_MARGIN2 + half, Math.min(centerX, window.innerWidth - TOOLBAR_EDGE_MARGIN2 - half));
18944
+ setShift((prev) => Math.abs(prev - (clamped - centerX)) < 0.5 ? prev : clamped - centerX);
18945
+ };
18946
+ clamp();
18947
+ const ro = new ResizeObserver(clamp);
18948
+ ro.observe(slot);
18949
+ return () => ro.disconnect();
18950
+ }, [centerX, children]);
18951
+ const mobile = window.innerWidth <= 480;
18952
+ return /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
18953
+ "div",
18954
+ {
18955
+ ref: slotRef,
18956
+ "data-ohw-bridge": "",
18957
+ "data-ohw-item-interaction": "",
18958
+ className: "pointer-events-auto fixed",
18959
+ style: {
18960
+ top: stripRect.top - (mobile ? TOOLBAR_STRIP_GAP_MOBILE : TOOLBAR_STRIP_GAP),
18961
+ left: centerX + shift,
18962
+ transform: "translate(-50%, -100%)",
18963
+ zIndex: 2147483645
18964
+ },
18965
+ children
18966
+ }
18967
+ );
18968
+ }
18481
18969
  function requestValidateTree(postToParent2, tree) {
18482
18970
  return new Promise((resolve) => {
18483
18971
  const id = `ai-validate-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
@@ -18514,116 +19002,72 @@ function requestAssetPick(postToParent2) {
18514
19002
  postToParent2({ type: "ow:asset-pick", id });
18515
19003
  });
18516
19004
  }
18517
- function Pill({
19005
+ function ToolbarAction({
18518
19006
  label,
18519
19007
  onClick,
18520
- disabled,
18521
- active
19008
+ disabled = false,
19009
+ active = false,
19010
+ children
18522
19011
  }) {
18523
- return /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
18524
- "button",
18525
- {
18526
- type: "button",
18527
- onClick,
18528
- disabled,
18529
- style: {
18530
- height: 28,
18531
- padding: "0 10px",
18532
- display: "inline-flex",
18533
- alignItems: "center",
18534
- gap: 4,
18535
- borderRadius: 999,
18536
- border: "none",
18537
- cursor: disabled ? "default" : "pointer",
18538
- background: active ? OHW_BLUE_DARK : "#ffffff",
18539
- color: active ? "#ffffff" : OHW_BLUE_DARK,
18540
- opacity: disabled ? 0.4 : 1,
18541
- fontSize: 12,
18542
- lineHeight: "16px",
18543
- fontFamily: "Figtree, ui-sans-serif, system-ui, sans-serif",
18544
- whiteSpace: "nowrap",
18545
- flex: "0 0 auto"
18546
- },
18547
- children: label
18548
- }
18549
- );
19012
+ const button = /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(CustomToolbarButton, { type: "button", "aria-label": label, disabled, active, onClick, className: "size-8", children });
19013
+ return /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(Tooltip, { children: [
19014
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(TooltipTrigger, { asChild: true, children: disabled ? /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("span", { className: "inline-flex", children: button }) : button }),
19015
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(TooltipContent, { side: "bottom", sideOffset: 8, children: label })
19016
+ ] });
18550
19017
  }
18551
- function AltInput({ value, onCommit }) {
18552
- const [draft, setDraft] = (0, import_react10.useState)(value);
18553
- (0, import_react10.useEffect)(() => setDraft(value), [value]);
18554
- return /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)(
18555
- "div",
18556
- {
18557
- style: {
18558
- display: "flex",
18559
- alignItems: "center",
18560
- gap: 6,
18561
- height: 28,
18562
- padding: "0 12px",
18563
- borderRadius: 999,
18564
- background: "#ffffff",
18565
- width: "100%",
18566
- maxWidth: 260,
18567
- boxSizing: "border-box"
18568
- },
18569
- children: [
18570
- /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
18571
- "span",
18572
- {
18573
- style: {
18574
- flex: "0 0 auto",
18575
- color: OHW_BLUE_DARK,
18576
- fontSize: 10,
18577
- fontWeight: 700,
18578
- textTransform: "uppercase",
18579
- letterSpacing: 0.4
18580
- },
18581
- children: "Alt"
18582
- }
18583
- ),
18584
- /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
18585
- "input",
19018
+ function ToolbarSeparator() {
19019
+ return /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("span", { "aria-hidden": true, className: "mx-0.5 block h-6 w-px shrink-0 bg-border" });
19020
+ }
19021
+ function OptionMenu({
19022
+ label,
19023
+ value,
19024
+ options,
19025
+ onChange
19026
+ }) {
19027
+ const current = options.find((o) => o.value === value) ?? options[0];
19028
+ return /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("div", { className: "flex items-center gap-1.5 pl-1.5", children: [
19029
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("span", { className: "text-xs text-muted-foreground", children: label }),
19030
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(DropdownMenu, { modal: false, children: [
19031
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(DropdownMenuTrigger, { asChild: true, children: /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(
19032
+ "button",
19033
+ {
19034
+ type: "button",
19035
+ "aria-label": label,
19036
+ 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",
19037
+ children: [
19038
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("span", { className: "flex-1 text-left", children: current.label }),
19039
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(import_lucide_react9.ChevronDown, { size: 16, style: { pointerEvents: "none" } })
19040
+ ]
19041
+ }
19042
+ ) }),
19043
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(DropdownMenuContent, { align: "end", sideOffset: 8, className: "w-[132px] min-w-[132px] p-1", children: options.map((option) => {
19044
+ const selected = option.value === value;
19045
+ return /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(
19046
+ DropdownMenuItem,
18586
19047
  {
18587
- type: "text",
18588
- value: draft,
18589
- onChange: (e) => setDraft(e.target.value),
18590
- onBlur: () => {
18591
- if (draft !== value) onCommit(draft);
18592
- },
18593
- onKeyDown: (e) => {
18594
- if (e.key === "Enter") e.currentTarget.blur();
18595
- if (e.key === "Escape") {
18596
- setDraft(value);
18597
- e.currentTarget.blur();
18598
- }
18599
- },
18600
- placeholder: "Describe this image",
18601
- "aria-label": "Image alt text",
18602
- style: {
18603
- flex: 1,
18604
- minWidth: 0,
18605
- border: "none",
18606
- outline: "none",
18607
- background: "transparent",
18608
- fontSize: 12,
18609
- lineHeight: "16px",
18610
- fontFamily: "Figtree, ui-sans-serif, system-ui, sans-serif",
18611
- color: "#1f2937"
18612
- }
18613
- }
18614
- )
18615
- ]
18616
- }
18617
- );
19048
+ role: "menuitemradio",
19049
+ "aria-checked": selected,
19050
+ onSelect: () => onChange(option.value),
19051
+ className: cn("grid grid-cols-[1fr_16px] gap-2 py-2 pl-2 pr-2 text-[13px]", selected && "bg-muted"),
19052
+ children: [
19053
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("span", { children: option.label }),
19054
+ selected && /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(import_lucide_react9.Check, { size: 16, className: "text-bridge-primary" })
19055
+ ]
19056
+ },
19057
+ option.value
19058
+ );
19059
+ }) })
19060
+ ] })
19061
+ ] });
18618
19062
  }
18619
19063
  var VARIANTS = [
18620
19064
  { value: "logos", label: "Logos" },
18621
19065
  { value: "reel", label: "Reel" }
18622
19066
  ];
18623
19067
  var ALIGNS = [
18624
- { value: "left", label: "Left" },
18625
- { value: "center", label: "Center" },
18626
- { value: "right", label: "Right" }
19068
+ { value: "left", label: "Align left", Icon: import_lucide_react9.AlignLeft },
19069
+ { value: "center", label: "Align center", Icon: import_lucide_react9.AlignCenter },
19070
+ { value: "right", label: "Align right", Icon: import_lucide_react9.AlignRight }
18627
19071
  ];
18628
19072
  var HOVERS = [
18629
19073
  { value: "none", label: "None" },
@@ -18631,104 +19075,104 @@ var HOVERS = [
18631
19075
  { value: "zoom", label: "Zoom" },
18632
19076
  { value: "fade", label: "Fade" }
18633
19077
  ];
18634
- var TREATMENTS = [
18635
- { value: "color", label: "Color" },
18636
- { value: "grayscale", label: "Grayscale" }
18637
- ];
18638
19078
  var HTTP_URL_RE = /^https?:\/\/\S+$/i;
18639
- function LinkInput({ value, onCommit }) {
18640
- const [draft, setDraft] = (0, import_react10.useState)(value);
19079
+ function LinkModal({
19080
+ href,
19081
+ linkTarget,
19082
+ onClose,
19083
+ onSave
19084
+ }) {
19085
+ const [draft, setDraft] = (0, import_react10.useState)(href);
19086
+ const [newTab, setNewTab] = (0, import_react10.useState)(linkTarget !== "same-tab");
18641
19087
  const [invalid, setInvalid] = (0, import_react10.useState)(false);
18642
- (0, import_react10.useEffect)(() => {
18643
- setDraft(value);
18644
- setInvalid(false);
18645
- }, [value]);
18646
- const commit = () => {
19088
+ const save = () => {
18647
19089
  const next = draft.trim();
18648
- if (next === value) return setInvalid(false);
18649
- if (next && !HTTP_URL_RE.test(next)) return setInvalid(true);
18650
- setInvalid(false);
18651
- onCommit(next);
18652
- };
18653
- return /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)(
18654
- "div",
18655
- {
18656
- style: {
18657
- display: "flex",
18658
- alignItems: "center",
18659
- gap: 6,
18660
- height: 28,
18661
- padding: "0 12px",
18662
- borderRadius: 999,
18663
- background: "#ffffff",
18664
- width: "100%",
18665
- maxWidth: 260,
18666
- boxSizing: "border-box",
18667
- boxShadow: invalid ? "inset 0 0 0 1.5px #EF4444" : "none"
18668
- },
18669
- children: [
18670
- /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
18671
- "span",
18672
- {
18673
- style: {
18674
- flex: "0 0 auto",
18675
- color: OHW_BLUE_DARK,
18676
- fontSize: 10,
18677
- fontWeight: 700,
18678
- textTransform: "uppercase",
18679
- letterSpacing: 0.4
18680
- },
18681
- children: "Link"
18682
- }
18683
- ),
18684
- /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
18685
- "input",
18686
- {
18687
- type: "text",
18688
- inputMode: "url",
18689
- value: draft,
18690
- onChange: (e) => setDraft(e.target.value),
18691
- onBlur: commit,
18692
- onKeyDown: (e) => {
18693
- if (e.key === "Enter") e.currentTarget.blur();
18694
- if (e.key === "Escape") {
18695
- setDraft(value);
18696
- setInvalid(false);
18697
- e.currentTarget.blur();
18698
- }
18699
- },
18700
- placeholder: "https://example.com",
18701
- "aria-label": "Image link URL",
18702
- "aria-invalid": invalid,
18703
- style: {
18704
- flex: 1,
18705
- minWidth: 0,
18706
- border: "none",
18707
- outline: "none",
18708
- background: "transparent",
18709
- fontSize: 12,
18710
- lineHeight: "16px",
18711
- fontFamily: "Figtree, ui-sans-serif, system-ui, sans-serif",
18712
- color: "#1f2937"
18713
- }
18714
- }
18715
- )
18716
- ]
19090
+ if (next && !HTTP_URL_RE.test(next)) {
19091
+ setInvalid(true);
19092
+ return;
18717
19093
  }
18718
- );
19094
+ onSave(next, newTab ? "new-tab" : "same-tab");
19095
+ onClose();
19096
+ };
19097
+ return /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(Dialog2, { open: true, onOpenChange: (open) => !open && onClose(), children: /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(DialogContent, { "data-ohw-link-modal-root": "", className: "max-w-[400px] gap-4 p-5", children: [
19098
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(DialogHeader, { children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(DialogTitle, { className: "text-base", children: "Image link" }) }),
19099
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("div", { className: "flex flex-col gap-3", children: [
19100
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
19101
+ "input",
19102
+ {
19103
+ autoFocus: true,
19104
+ type: "text",
19105
+ inputMode: "url",
19106
+ value: draft,
19107
+ onChange: (e) => {
19108
+ setDraft(e.target.value);
19109
+ setInvalid(false);
19110
+ },
19111
+ onKeyDown: (e) => {
19112
+ if (e.key === "Enter") save();
19113
+ },
19114
+ placeholder: "https://example.com",
19115
+ "aria-label": "Image link URL",
19116
+ "aria-invalid": invalid,
19117
+ className: cn(
19118
+ "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",
19119
+ invalid && "border-destructive focus:border-destructive"
19120
+ )
19121
+ }
19122
+ ),
19123
+ invalid && /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("p", { className: "text-xs text-destructive", children: "Enter a full URL starting with http:// or https://" }),
19124
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("label", { className: "flex items-center gap-2 text-[13px] text-foreground", children: [
19125
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("input", { type: "checkbox", checked: newTab, onChange: (e) => setNewTab(e.target.checked) }),
19126
+ "Open in new tab"
19127
+ ] })
19128
+ ] }),
19129
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(DialogFooter, { children: [
19130
+ href && /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
19131
+ "button",
19132
+ {
19133
+ type: "button",
19134
+ onClick: () => {
19135
+ onSave("", "new-tab");
19136
+ onClose();
19137
+ },
19138
+ className: "mr-auto rounded-[6px] px-3 py-2 text-[13px] font-medium text-destructive transition-colors hover:bg-muted",
19139
+ children: "Remove link"
19140
+ }
19141
+ ),
19142
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
19143
+ "button",
19144
+ {
19145
+ type: "button",
19146
+ onClick: onClose,
19147
+ className: "rounded-[6px] px-3 py-2 text-[13px] font-medium text-foreground transition-colors hover:bg-muted",
19148
+ children: "Cancel"
19149
+ }
19150
+ ),
19151
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
19152
+ "button",
19153
+ {
19154
+ type: "button",
19155
+ onClick: save,
19156
+ className: "rounded-[6px] bg-bridge-primary px-3 py-2 text-[13px] font-medium text-primary-foreground transition-opacity hover:opacity-90",
19157
+ children: "Save"
19158
+ }
19159
+ )
19160
+ ] })
19161
+ ] }) });
18719
19162
  }
18720
19163
  function MediaStripToolbar({
18721
19164
  entry,
18722
19165
  index,
18723
19166
  pending,
18724
19167
  error,
19168
+ itemEditOn,
19169
+ onToggleItemEdit,
18725
19170
  onSetVariant,
18726
19171
  onSetAlign,
18727
19172
  onSetHover,
18728
19173
  onSetTreatment,
18729
19174
  onResize,
18730
- onSetAlt,
18731
- onSetLink,
19175
+ onOpenLink,
18732
19176
  onAddItem,
18733
19177
  onRemoveItem,
18734
19178
  onChangeImage,
@@ -18737,81 +19181,102 @@ function MediaStripToolbar({
18737
19181
  }) {
18738
19182
  const items = mediaStripItems(entry.tree);
18739
19183
  const itemCount = items.length;
18740
- const currentAlt = items[index]?.alt ?? "";
18741
- const currentHref = items[index]?.href ?? "";
18742
- const currentTarget = items[index]?.linkTarget === "same-tab" ? "same-tab" : "new-tab";
18743
19184
  const slots = findMediaStripSlots(entry.tree);
18744
19185
  const variant = slots?.variant === "reel" ? "reel" : "logos";
18745
19186
  const align = slots?.align === "left" || slots?.align === "right" ? slots.align : "center";
18746
19187
  const hover = slots?.hover === "none" || slots?.hover === "lift" || slots?.hover === "zoom" || slots?.hover === "fade" ? slots.hover : "lift";
18747
19188
  const treatment = slots?.treatment === "color" || slots?.treatment === "grayscale" ? slots.treatment : variant === "logos" ? "grayscale" : "color";
18748
19189
  const sizeValue = variant === "logos" ? typeof slots?.logoMaxHeight === "number" ? slots.logoMaxHeight : 48 : typeof slots?.cardWidth === "number" ? slots.cardWidth : 240;
18749
- const isMobile = window.innerWidth <= 480;
18750
- const rowStyle = isMobile ? {
18751
- display: "flex",
18752
- gap: 6,
18753
- flexWrap: "nowrap",
18754
- justifyContent: "center",
18755
- overflowX: "auto",
18756
- WebkitOverflowScrolling: "touch",
18757
- width: "100%",
18758
- scrollbarWidth: "none"
18759
- } : { display: "flex", gap: 6, flexWrap: "nowrap", justifyContent: "center" };
18760
- return /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)(
19190
+ const mobile = window.innerWidth <= 480;
19191
+ const gatedLabel = (label) => itemEditOn ? label : "Turn on the star to edit this image";
19192
+ return /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(TooltipProvider, { delayDuration: 0, children: /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(
18761
19193
  "div",
18762
19194
  {
18763
19195
  "data-ohw-bridge": "",
18764
19196
  "data-ohw-toolbar": "",
18765
19197
  onMouseDown: (e) => e.stopPropagation(),
18766
- style: {
18767
- display: "flex",
18768
- flexDirection: "column",
18769
- gap: 6,
18770
- alignItems: "center",
18771
- background: OHW_BLUE,
18772
- padding: 8,
18773
- borderRadius: 12,
18774
- boxShadow: "0 8px 24px rgba(0,0,0,0.25)",
18775
- pointerEvents: pending ? "none" : "auto",
18776
- opacity: pending ? 0.6 : 1,
18777
- maxWidth: isMobile ? "calc(100vw - 24px)" : "none"
18778
- },
19198
+ className: cn("flex flex-col items-center gap-1.5 font-sans", pending && "pointer-events-none opacity-60"),
19199
+ style: { maxWidth: mobile ? "calc(100vw - 24px)" : void 0 },
18779
19200
  children: [
18780
- /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("div", { style: rowStyle, children: [
18781
- VARIANTS.map((v) => /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(Pill, { label: v.label, active: variant === v.value, onClick: () => onSetVariant(v.value) }, v.value)),
18782
- ALIGNS.map((a) => /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(Pill, { label: a.label, active: align === a.value, onClick: () => onSetAlign(a.value) }, a.value))
18783
- ] }),
18784
- /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("div", { style: rowStyle, children: [
18785
- HOVERS.map((h) => /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(Pill, { label: h.label, active: hover === h.value, onClick: () => onSetHover(h.value) }, h.value)),
18786
- TREATMENTS.map((t) => /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(Pill, { label: t.label, active: treatment === t.value, onClick: () => onSetTreatment(t.value) }, t.value))
18787
- ] }),
18788
- /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("div", { style: { display: "flex", gap: 6, alignItems: "center" }, children: [
18789
- /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(Pill, { label: "\u2212", onClick: () => onResize(-8) }),
18790
- /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("span", { style: { color: "#ffffff", fontSize: 12, minWidth: 34, textAlign: "center" }, children: [
18791
- sizeValue,
18792
- "px"
18793
- ] }),
18794
- /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(Pill, { label: "+", onClick: () => onResize(8) })
18795
- ] }),
18796
- /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("div", { style: { display: "flex", justifyContent: "center", width: "100%" }, children: /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(AltInput, { value: currentAlt, onCommit: onSetAlt }) }),
18797
- /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("div", { style: { display: "flex", justifyContent: "center", width: "100%" }, children: /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(LinkInput, { value: currentHref, onCommit: (href) => onSetLink(href, currentTarget) }) }),
18798
- currentHref && /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("div", { style: rowStyle, children: [
18799
- /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(Pill, { label: "New tab", active: currentTarget === "new-tab", onClick: () => onSetLink(currentHref, "new-tab") }),
18800
- /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(Pill, { label: "Same tab", active: currentTarget === "same-tab", onClick: () => onSetLink(currentHref, "same-tab") })
18801
- ] }),
18802
- /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("div", { style: rowStyle, children: [
18803
- /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(Pill, { label: "Change Image", onClick: onChangeImage }),
18804
- /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(Pill, { label: "+ Item", onClick: onAddItem }),
18805
- /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(Pill, { label: "\u2212 Item", onClick: onRemoveItem, disabled: itemCount <= 1 })
18806
- ] }),
18807
- /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("div", { style: rowStyle, children: [
18808
- /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(Pill, { label: "\u2190 Move", onClick: onMoveLeft, disabled: index <= 0 }),
18809
- /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(Pill, { label: "\u2192 Move", onClick: onMoveRight, disabled: index >= itemCount - 1 })
18810
- ] }),
18811
- error && /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("div", { style: { color: "#FCA5A5", fontSize: 11, maxWidth: 220, textAlign: "center" }, children: error })
19201
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(
19202
+ "div",
19203
+ {
19204
+ className: cn(
19205
+ "flex items-center gap-1 rounded-[10px] border border-border bg-background p-1 whitespace-nowrap",
19206
+ "shadow-[0px_10px_15px_-3px_rgba(0,0,0,0.1),0px_4px_6px_-4px_rgba(0,0,0,0.1)]",
19207
+ mobile ? "flex-wrap justify-center" : "flex-nowrap"
19208
+ ),
19209
+ children: [
19210
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(Tooltip, { children: [
19211
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
19212
+ CustomToolbarButton,
19213
+ {
19214
+ type: "button",
19215
+ "aria-label": itemEditOn ? "Done editing this image" : "Edit this image",
19216
+ "aria-pressed": itemEditOn,
19217
+ onClick: onToggleItemEdit,
19218
+ className: "group size-8",
19219
+ children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
19220
+ import_lucide_react9.Star,
19221
+ {
19222
+ size: 16,
19223
+ fill: "currentColor",
19224
+ strokeWidth: 0,
19225
+ className: cn(
19226
+ "transition-colors",
19227
+ itemEditOn ? "text-[#f59e0b] group-hover:text-[#d97706]" : "text-[#d6d3d1] group-hover:text-[#fbbf24]"
19228
+ ),
19229
+ style: { pointerEvents: "none" }
19230
+ }
19231
+ )
19232
+ }
19233
+ ) }),
19234
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(TooltipContent, { side: "bottom", sideOffset: 8, children: itemEditOn ? "Done editing this image" : "Edit this image" })
19235
+ ] }),
19236
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(ToolbarSeparator, {}),
19237
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(ToolbarAction, { label: gatedLabel("Change image"), onClick: onChangeImage, disabled: !itemEditOn, children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(import_lucide_react9.Image, { size: 16, style: { pointerEvents: "none" } }) }),
19238
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(ToolbarAction, { label: gatedLabel("Link"), onClick: onOpenLink, disabled: !itemEditOn, children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(import_lucide_react9.Link, { size: 16, style: { pointerEvents: "none" } }) }),
19239
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(ToolbarAction, { label: gatedLabel("Move left"), onClick: onMoveLeft, disabled: !itemEditOn || index <= 0, children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(import_lucide_react9.ArrowLeft, { size: 16, style: { pointerEvents: "none" } }) }),
19240
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
19241
+ ToolbarAction,
19242
+ {
19243
+ label: gatedLabel("Move right"),
19244
+ onClick: onMoveRight,
19245
+ disabled: !itemEditOn || index >= itemCount - 1,
19246
+ children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(import_lucide_react9.ArrowRight, { size: 16, style: { pointerEvents: "none" } })
19247
+ }
19248
+ ),
19249
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(ToolbarAction, { label: gatedLabel("Remove image"), onClick: onRemoveItem, disabled: !itemEditOn || itemCount <= 1, children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(import_lucide_react9.ImageMinus, { size: 16, style: { pointerEvents: "none" } }) }),
19250
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(ToolbarSeparator, {}),
19251
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(OptionMenu, { label: "Layout", value: variant, options: VARIANTS, onChange: onSetVariant }),
19252
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(OptionMenu, { label: "Hover", value: hover, options: HOVERS, onChange: onSetHover }),
19253
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
19254
+ ToolbarAction,
19255
+ {
19256
+ label: treatment === "grayscale" ? "Show in color" : "Show grayscale",
19257
+ active: treatment === "grayscale",
19258
+ onClick: () => onSetTreatment(treatment === "grayscale" ? "color" : "grayscale"),
19259
+ children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(import_lucide_react9.Palette, { size: 16, style: { pointerEvents: "none" } })
19260
+ }
19261
+ ),
19262
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(ToolbarSeparator, {}),
19263
+ ALIGNS.map(({ value, label, Icon }) => /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(ToolbarAction, { label, active: align === value, onClick: () => onSetAlign(value), children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(Icon, { size: 16, style: { pointerEvents: "none" } }) }, value)),
19264
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(ToolbarSeparator, {}),
19265
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(ToolbarAction, { label: "Smaller", onClick: () => onResize(-8), children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(import_lucide_react9.Minus, { size: 16, style: { pointerEvents: "none" } }) }),
19266
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("span", { className: "min-w-[40px] text-center text-xs tabular-nums text-muted-foreground", children: [
19267
+ sizeValue,
19268
+ "px"
19269
+ ] }),
19270
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(ToolbarAction, { label: "Larger", onClick: () => onResize(8), children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(import_lucide_react9.Plus, { size: 16, style: { pointerEvents: "none" } }) }),
19271
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(ToolbarSeparator, {}),
19272
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(ToolbarAction, { label: "Add image", onClick: onAddItem, children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(import_lucide_react9.ImagePlus, { size: 16, style: { pointerEvents: "none" } }) })
19273
+ ]
19274
+ }
19275
+ ),
19276
+ error && /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("div", { className: "max-w-[260px] rounded-md bg-background px-2 py-1 text-center text-[11px] text-destructive shadow-sm", children: error })
18812
19277
  ]
18813
19278
  }
18814
- );
19279
+ ) });
18815
19280
  }
18816
19281
  function findMediaStripSlots(tree) {
18817
19282
  const stack = tree.rows.flatMap((row) => row.blocks);
@@ -18830,31 +19295,23 @@ function AiMediaStripOverlay({
18830
19295
  }) {
18831
19296
  const [hover, setHover] = (0, import_react10.useState)(null);
18832
19297
  const [active, setActive] = (0, import_react10.useState)(null);
19298
+ const [itemEditOn, setItemEditOn] = (0, import_react10.useState)(false);
19299
+ const [linkOpen, setLinkOpen] = (0, import_react10.useState)(false);
18833
19300
  const [pending, setPending] = (0, import_react10.useState)(false);
18834
19301
  const [error, setError] = (0, import_react10.useState)(null);
18835
19302
  const activeRef = (0, import_react10.useRef)(null);
18836
19303
  activeRef.current = active;
19304
+ const linkOpenRef = (0, import_react10.useRef)(false);
19305
+ linkOpenRef.current = linkOpen;
18837
19306
  (0, import_react10.useEffect)(() => {
18838
19307
  const onMove = (e) => {
19308
+ const ref = itemRefFromTarget(e.target);
18839
19309
  const t = e.target;
18840
- if (!(t instanceof HTMLElement)) return;
18841
- 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]")) {
18842
- return;
18843
- }
18844
- const itemEl = t.closest("[data-ohw-media-strip-item]");
18845
- if (!itemEl) {
18846
- setHover(null);
18847
- return;
18848
- }
18849
- const sectionEl = itemEl.closest("[data-ohw-section]");
18850
- const unit = sectionEl ? movableUnit(sectionEl) : null;
18851
- const sectionId = unit ? instanceIdOf(unit) || null : null;
18852
- const index = Number(itemEl.dataset.ohwMediaStripIndex);
18853
- if (!sectionId || Number.isNaN(index)) {
18854
- setHover(null);
18855
- return;
18856
- }
18857
- setHover({ sectionId, index });
19310
+ const overEditable = Boolean(t?.closest?.("[data-ohw-editable]"));
19311
+ setHover((prev) => {
19312
+ const next = ref && !overEditable ? ref : null;
19313
+ return sameRef(prev, next) ? prev : next;
19314
+ });
18858
19315
  };
18859
19316
  const onLeave = () => setHover(null);
18860
19317
  document.addEventListener("mousemove", onMove, { passive: true });
@@ -18866,23 +19323,25 @@ function AiMediaStripOverlay({
18866
19323
  }, []);
18867
19324
  (0, import_react10.useEffect)(() => {
18868
19325
  const onClick = (e) => {
18869
- const current = activeRef.current;
18870
- if (!current) return;
18871
19326
  const t = e.target;
18872
- if (!(t instanceof HTMLElement)) return;
18873
- if (t.closest("[data-ohw-item-interaction]")) return;
18874
- const itemEl = findMediaStripItemElement(current.sectionId, current.index);
18875
- if (itemEl && itemEl.contains(t)) return;
18876
- setActive(null);
19327
+ if (!(t instanceof Element)) return;
19328
+ if (t.closest(CHROME_SELECTOR)) return;
19329
+ const ref = itemRefFromTarget(t);
19330
+ if (sameRef(activeRef.current, ref)) return;
19331
+ if (ref) clearInteractionChromeRef.current();
19332
+ setItemEditOn(false);
18877
19333
  setError(null);
19334
+ setActive(ref);
18878
19335
  };
18879
19336
  document.addEventListener("click", onClick, true);
18880
19337
  return () => document.removeEventListener("click", onClick, true);
18881
- }, []);
19338
+ }, [clearInteractionChromeRef]);
18882
19339
  (0, import_react10.useEffect)(() => {
18883
19340
  const onBlur = () => {
18884
19341
  if (!activeRef.current) return;
18885
19342
  setActive(null);
19343
+ setItemEditOn(false);
19344
+ setLinkOpen(false);
18886
19345
  setError(null);
18887
19346
  };
18888
19347
  window.addEventListener("blur", onBlur);
@@ -18892,7 +19351,10 @@ function AiMediaStripOverlay({
18892
19351
  const onKeyDown = (e) => {
18893
19352
  if (e.key !== "Escape") return;
18894
19353
  if (!activeRef.current) return;
19354
+ if (linkOpenRef.current) return;
19355
+ if (document.querySelector('[data-ohw-more-menu][data-state="open"]')) return;
18895
19356
  setActive(null);
19357
+ setItemEditOn(false);
18896
19358
  setError(null);
18897
19359
  };
18898
19360
  document.addEventListener("keydown", onKeyDown);
@@ -18926,137 +19388,133 @@ function AiMediaStripOverlay({
18926
19388
  tree: nextTree,
18927
19389
  label: entry.label,
18928
19390
  media: extraMedia ? { ...entry.media, ...extraMedia } : entry.media,
18929
- ...entry.brand ? { brand: entry.brand } : {}
19391
+ ...entry.brand ? { brand: entry.brand } : {},
19392
+ // The user is already looking at the strip they just edited — recentering the viewport
19393
+ // on every edit reads as the section jumping around.
19394
+ skipScrollIntoView: true
18930
19395
  });
18931
19396
  return true;
18932
19397
  },
18933
19398
  [findEntry, postToParent2, applyTreeLocalRef]
18934
19399
  );
18935
19400
  const activeEntry = active ? findEntry(active.sectionId) : null;
18936
- const hoverHandleRect = useLiveItemRect(!active && hover ? hover : null);
18937
19401
  const activeItemRect = useLiveItemRect(active);
18938
- const TOOLBAR_APPROX_H = 170;
18939
- const toolbarState = activeItemRect && activeItemRect.top < TOOLBAR_APPROX_H ? "active-bottom" : "active-top";
18940
- return /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)(import_jsx_runtime20.Fragment, { children: [
18941
- hoverHandleRect && hover && /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
18942
- "button",
18943
- {
18944
- type: "button",
18945
- "data-ohw-toolbar": "",
18946
- onClick: () => {
18947
- clearInteractionChromeRef.current();
18948
- setActive(hover);
18949
- },
18950
- "aria-label": "Configure media item",
18951
- style: {
18952
- position: "fixed",
18953
- top: hoverHandleRect.top + 8,
18954
- left: hoverHandleRect.left + hoverHandleRect.width - 36,
18955
- width: 28,
18956
- height: 28,
18957
- borderRadius: 8,
18958
- border: `1.5px dashed ${PRIMARY3}`,
18959
- background: "#ffffff",
18960
- color: PRIMARY3,
18961
- display: "flex",
18962
- alignItems: "center",
18963
- justifyContent: "center",
18964
- cursor: "pointer",
18965
- zIndex: 2147483643,
18966
- fontSize: 14,
18967
- fontWeight: 700
18968
- },
18969
- children: /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(import_lucide_react8.Star, { size: 14, style: { pointerEvents: "none" } })
18970
- }
18971
- ),
18972
- activeItemRect && active && activeEntry && /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
18973
- ItemInteractionLayer,
18974
- {
18975
- rect: activeItemRect,
18976
- state: toolbarState,
18977
- toolbar: /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
18978
- MediaStripToolbar,
18979
- {
18980
- entry: activeEntry,
18981
- index: active.index,
18982
- pending,
18983
- error,
18984
- onSetVariant: (variant) => runMutation(active.sectionId, (tree) => setMediaStripVariant(tree, variant)),
18985
- onSetAlign: (align) => runMutation(active.sectionId, (tree) => setMediaStripAlign(tree, align)),
18986
- onSetHover: (hoverPreset) => runMutation(active.sectionId, (tree) => setMediaStripHover(tree, hoverPreset)),
18987
- onSetTreatment: (treatment) => runMutation(active.sectionId, (tree) => setMediaStripTreatment(tree, treatment)),
18988
- onResize: (delta) => {
18989
- const slots = findMediaStripSlots(activeEntry.tree);
18990
- const variant = slots?.variant === "reel" ? "reel" : "logos";
18991
- const current = variant === "logos" ? typeof slots?.logoMaxHeight === "number" ? slots.logoMaxHeight : 48 : typeof slots?.cardWidth === "number" ? slots.cardWidth : 240;
18992
- void runMutation(active.sectionId, (tree) => setMediaStripSizing(tree, current + delta));
18993
- },
18994
- onSetAlt: (alt) => {
18995
- void runMutation(active.sectionId, (tree) => setMediaStripItemAlt(tree, active.index, alt));
18996
- },
18997
- onSetLink: (href, linkTarget) => {
19402
+ const activeStripRect = useLiveItemRect(active, true);
19403
+ const hoverRect = useLiveItemRect(hover && !sameRef(hover, active) ? hover : null);
19404
+ const activeItems = activeEntry ? mediaStripItems(activeEntry.tree) : [];
19405
+ const activeItem = active ? activeItems[active.index] : void 0;
19406
+ const activeHref = activeItem?.href ?? "";
19407
+ const activeTarget = activeItem?.linkTarget === "same-tab" ? "same-tab" : "new-tab";
19408
+ return /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(import_jsx_runtime21.Fragment, { children: [
19409
+ hoverRect && /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(ItemInteractionLayer, { rect: hoverRect, state: "hover" }),
19410
+ activeItemRect && activeStripRect && active && activeEntry && /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(import_jsx_runtime21.Fragment, { children: [
19411
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(ItemInteractionLayer, { rect: activeItemRect, state: "active-top" }),
19412
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(StripToolbarSlot, { stripRect: activeStripRect, children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
19413
+ MediaStripToolbar,
19414
+ {
19415
+ entry: activeEntry,
19416
+ index: active.index,
19417
+ pending,
19418
+ error,
19419
+ itemEditOn,
19420
+ onToggleItemEdit: () => setItemEditOn((on) => !on),
19421
+ onSetVariant: (variant) => runMutation(active.sectionId, (tree) => setMediaStripVariant(tree, variant)),
19422
+ onSetAlign: (align) => runMutation(active.sectionId, (tree) => setMediaStripAlign(tree, align)),
19423
+ onSetHover: (hoverPreset) => runMutation(active.sectionId, (tree) => setMediaStripHover(tree, hoverPreset)),
19424
+ onSetTreatment: (treatment) => runMutation(active.sectionId, (tree) => setMediaStripTreatment(tree, treatment)),
19425
+ onResize: (delta) => {
19426
+ const slots = findMediaStripSlots(activeEntry.tree);
19427
+ const variant = slots?.variant === "reel" ? "reel" : "logos";
19428
+ const current = variant === "logos" ? typeof slots?.logoMaxHeight === "number" ? slots.logoMaxHeight : 48 : typeof slots?.cardWidth === "number" ? slots.cardWidth : 240;
19429
+ void runMutation(active.sectionId, (tree) => setMediaStripSizing(tree, current + delta));
19430
+ },
19431
+ onOpenLink: () => setLinkOpen(true),
19432
+ onAddItem: () => {
19433
+ void requestAssetPick(postToParent2).then((picked) => {
19434
+ if (!picked) return;
19435
+ const item = { assetId: picked.assetId, alt: "Logo" };
18998
19436
  void runMutation(
18999
19437
  active.sectionId,
19000
- (tree) => setMediaStripItemLink(tree, active.index, href ? { href, linkTarget } : {})
19001
- );
19002
- },
19003
- onAddItem: () => {
19004
- void requestAssetPick(postToParent2).then((picked) => {
19005
- if (!picked) return;
19006
- const item = { assetId: picked.assetId, alt: "Logo" };
19007
- void runMutation(
19008
- active.sectionId,
19009
- (tree) => addMediaStripItem(tree, item, active.index),
19010
- { [picked.assetId]: picked.url }
19011
- ).then((applied) => {
19012
- if (applied) setActive({ sectionId: active.sectionId, index: active.index + 1 });
19013
- });
19014
- });
19015
- },
19016
- onChangeImage: () => {
19017
- void requestAssetPick(postToParent2).then((picked) => {
19018
- if (!picked) return;
19019
- void runMutation(
19020
- active.sectionId,
19021
- (tree) => setMediaStripItemAsset(tree, active.index, picked.assetId),
19022
- { [picked.assetId]: picked.url }
19023
- );
19438
+ (tree) => addMediaStripItem(tree, item, active.index),
19439
+ { [picked.assetId]: picked.url }
19440
+ ).then((applied) => {
19441
+ if (applied) setActive({ sectionId: active.sectionId, index: active.index + 1 });
19024
19442
  });
19025
- },
19026
- onRemoveItem: () => {
19027
- void runMutation(active.sectionId, (tree) => removeMediaStripItem(tree, active.index)).then(
19028
- (applied) => {
19029
- if (applied) setActive(null);
19030
- }
19031
- );
19032
- },
19033
- onMoveLeft: () => {
19034
- void runMutation(active.sectionId, (tree) => reorderMediaStripItem(tree, active.index, -1)).then(
19035
- (applied) => {
19036
- if (applied) setActive({ sectionId: active.sectionId, index: active.index - 1 });
19037
- }
19038
- );
19039
- },
19040
- onMoveRight: () => {
19041
- void runMutation(active.sectionId, (tree) => reorderMediaStripItem(tree, active.index, 1)).then(
19042
- (applied) => {
19043
- if (applied) setActive({ sectionId: active.sectionId, index: active.index + 1 });
19044
- }
19443
+ });
19444
+ },
19445
+ onChangeImage: () => {
19446
+ void requestAssetPick(postToParent2).then((picked) => {
19447
+ if (!picked) return;
19448
+ void runMutation(
19449
+ active.sectionId,
19450
+ (tree) => setMediaStripItemAsset(tree, active.index, picked.assetId),
19451
+ { [picked.assetId]: picked.url }
19045
19452
  );
19046
- }
19453
+ });
19454
+ },
19455
+ onRemoveItem: () => {
19456
+ void runMutation(active.sectionId, (tree) => removeMediaStripItem(tree, active.index)).then(
19457
+ (applied) => {
19458
+ if (applied) setActive(null);
19459
+ }
19460
+ );
19461
+ },
19462
+ onMoveLeft: () => {
19463
+ void runMutation(active.sectionId, (tree) => reorderMediaStripItem(tree, active.index, -1)).then(
19464
+ (applied) => {
19465
+ if (applied) setActive({ sectionId: active.sectionId, index: active.index - 1 });
19466
+ }
19467
+ );
19468
+ },
19469
+ onMoveRight: () => {
19470
+ void runMutation(active.sectionId, (tree) => reorderMediaStripItem(tree, active.index, 1)).then(
19471
+ (applied) => {
19472
+ if (applied) setActive({ sectionId: active.sectionId, index: active.index + 1 });
19473
+ }
19474
+ );
19047
19475
  }
19048
- )
19049
- }
19050
- )
19476
+ }
19477
+ ) }),
19478
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
19479
+ "div",
19480
+ {
19481
+ "data-ohw-bridge": "",
19482
+ "data-ohw-item-interaction": "",
19483
+ "aria-hidden": true,
19484
+ className: "pointer-events-none fixed rounded-[4px] px-[7px] py-[3px] font-sans text-[11px] font-medium leading-none text-primary-foreground whitespace-nowrap",
19485
+ style: {
19486
+ top: activeItemRect.top + activeItemRect.height + 10,
19487
+ left: activeItemRect.left - 5,
19488
+ background: PRIMARY3,
19489
+ zIndex: 2147483644
19490
+ },
19491
+ children: "Media item"
19492
+ }
19493
+ ),
19494
+ linkOpen && /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
19495
+ LinkModal,
19496
+ {
19497
+ href: activeHref,
19498
+ linkTarget: activeTarget,
19499
+ onClose: () => setLinkOpen(false),
19500
+ onSave: (href, linkTarget) => {
19501
+ void runMutation(
19502
+ active.sectionId,
19503
+ (tree) => setMediaStripItemLink(tree, active.index, href ? { href, linkTarget } : {})
19504
+ );
19505
+ }
19506
+ }
19507
+ )
19508
+ ] })
19051
19509
  ] });
19052
19510
  }
19053
19511
 
19054
19512
  // src/ui/ai-section/AiPricingCardOverlay.tsx
19055
19513
  var import_react11 = require("react");
19056
- var import_lucide_react9 = require("lucide-react");
19057
- var import_jsx_runtime21 = require("react/jsx-runtime");
19514
+ var import_lucide_react10 = require("lucide-react");
19515
+ var import_jsx_runtime22 = require("react/jsx-runtime");
19058
19516
  var PRIMARY4 = "var(--ohw-primary, #0885FE)";
19059
- 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]";
19517
+ 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]";
19060
19518
  function findPricingCardElement(sectionId, index) {
19061
19519
  const sectionEl = findByInstanceId(sectionId);
19062
19520
  if (!sectionEl) return null;
@@ -19068,7 +19526,7 @@ function findPricingCardElement(sectionId, index) {
19068
19526
  }
19069
19527
  function cardRefFromTarget(target) {
19070
19528
  if (!(target instanceof Element)) return null;
19071
- if (target.closest(CHROME_SELECTOR)) return null;
19529
+ if (target.closest(CHROME_SELECTOR2)) return null;
19072
19530
  const cardEl = target.closest("[data-ohw-pricing-card]");
19073
19531
  if (!cardEl) return null;
19074
19532
  const sectionEl = cardEl.closest("[data-ohw-section]");
@@ -19078,21 +19536,21 @@ function cardRefFromTarget(target) {
19078
19536
  if (!sectionId || Number.isNaN(index)) return null;
19079
19537
  return { sectionId, index };
19080
19538
  }
19081
- function sameRef(a, b) {
19539
+ function sameRef2(a, b) {
19082
19540
  return a?.sectionId === b?.sectionId && a?.index === b?.index;
19083
19541
  }
19084
- function rectOf(el) {
19542
+ function rectOf2(el) {
19085
19543
  if (!el) return null;
19086
19544
  const r2 = el.getBoundingClientRect();
19087
19545
  if (r2.width <= 0 || r2.height <= 0) return null;
19088
19546
  return { top: r2.top, left: r2.left, width: r2.width, height: r2.height };
19089
19547
  }
19090
19548
  function readRect3(ref) {
19091
- return rectOf(ref && findPricingCardElement(ref.sectionId, ref.index));
19549
+ return rectOf2(ref && findPricingCardElement(ref.sectionId, ref.index));
19092
19550
  }
19093
19551
  function readGridRect(ref) {
19094
19552
  const card = ref && findPricingCardElement(ref.sectionId, ref.index);
19095
- return rectOf(card?.closest("[data-ohw-pricing-collection]") ?? card);
19553
+ return rectOf2(card?.closest("[data-ohw-pricing-collection]") ?? card);
19096
19554
  }
19097
19555
  function useLiveCardRect(ref, grid = false) {
19098
19556
  const [rect, setRect] = (0, import_react11.useState)(null);
@@ -19122,7 +19580,7 @@ function useLiveCardRect(ref, grid = false) {
19122
19580
  }
19123
19581
  var TOOLBAR_GRID_GAP = 52;
19124
19582
  var TOOLBAR_GRID_GAP_MOBILE = 42;
19125
- var TOOLBAR_EDGE_MARGIN2 = 4;
19583
+ var TOOLBAR_EDGE_MARGIN3 = 4;
19126
19584
  function GridToolbarSlot({ gridRect, children }) {
19127
19585
  const slotRef = (0, import_react11.useRef)(null);
19128
19586
  const [shift, setShift] = (0, import_react11.useState)(0);
@@ -19134,7 +19592,7 @@ function GridToolbarSlot({ gridRect, children }) {
19134
19592
  const w = slot.offsetWidth;
19135
19593
  if (w <= 0) return;
19136
19594
  const half = w / 2;
19137
- const clamped = Math.max(TOOLBAR_EDGE_MARGIN2 + half, Math.min(centerX, window.innerWidth - TOOLBAR_EDGE_MARGIN2 - half));
19595
+ const clamped = Math.max(TOOLBAR_EDGE_MARGIN3 + half, Math.min(centerX, window.innerWidth - TOOLBAR_EDGE_MARGIN3 - half));
19138
19596
  setShift((prev) => Math.abs(prev - (clamped - centerX)) < 0.5 ? prev : clamped - centerX);
19139
19597
  };
19140
19598
  clamp();
@@ -19143,7 +19601,7 @@ function GridToolbarSlot({ gridRect, children }) {
19143
19601
  return () => ro.disconnect();
19144
19602
  }, [centerX, children]);
19145
19603
  const mobile = window.innerWidth <= 480;
19146
- return /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
19604
+ return /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
19147
19605
  "div",
19148
19606
  {
19149
19607
  ref: slotRef,
@@ -19178,7 +19636,7 @@ function requestValidateTree2(postToParent2, tree) {
19178
19636
  });
19179
19637
  }
19180
19638
  function StyleSwatch({ style }) {
19181
- return /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
19639
+ return /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
19182
19640
  "span",
19183
19641
  {
19184
19642
  "aria-hidden": true,
@@ -19192,35 +19650,35 @@ function StyleSwatch({ style }) {
19192
19650
  );
19193
19651
  }
19194
19652
  function ButtonGlyph() {
19195
- return /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("svg", { width: "18", height: "12", viewBox: "0 0 18 12", fill: "none", "aria-hidden": true, style: { pointerEvents: "none" }, children: [
19196
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("rect", { x: "0.75", y: "0.75", width: "16.5", height: "10.5", rx: "3", stroke: "currentColor", strokeWidth: "1.5" }),
19197
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("path", { d: "M5.5 6H12.5", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round" })
19653
+ return /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("svg", { width: "18", height: "12", viewBox: "0 0 18 12", fill: "none", "aria-hidden": true, style: { pointerEvents: "none" }, children: [
19654
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("rect", { x: "0.75", y: "0.75", width: "16.5", height: "10.5", rx: "3", stroke: "currentColor", strokeWidth: "1.5" }),
19655
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("path", { d: "M5.5 6H12.5", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round" })
19198
19656
  ] });
19199
19657
  }
19200
19658
  function FeatureGlyph({ mode }) {
19201
- return /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("svg", { width: "18", height: "18", viewBox: "0 0 18 18", fill: "none", "aria-hidden": true, style: { pointerEvents: "none" }, children: [
19202
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("circle", { cx: "8", cy: "8", r: "7.25", stroke: "currentColor", strokeWidth: "1.5" }),
19203
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("path", { d: "M5 8.2L7.2 10.3L11 6", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round", strokeLinejoin: "round" }),
19204
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("circle", { cx: "13.5", cy: "13.5", r: "4.25", fill: "var(--ohw-background, #ffffff)", stroke: "var(--ohw-background, #ffffff)", strokeWidth: "1" }),
19205
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("circle", { cx: "13.5", cy: "13.5", r: "3.75", fill: "var(--ohw-background, #ffffff)", stroke: PRIMARY4, strokeWidth: "1" }),
19206
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("path", { d: "M11.5 13.5H15.5", stroke: PRIMARY4, strokeWidth: "1.5", strokeLinecap: "round" }),
19207
- mode === "add" && /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("path", { d: "M13.5 11.5V15.5", stroke: PRIMARY4, strokeWidth: "1.5", strokeLinecap: "round" })
19659
+ return /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("svg", { width: "18", height: "18", viewBox: "0 0 18 18", fill: "none", "aria-hidden": true, style: { pointerEvents: "none" }, children: [
19660
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("circle", { cx: "8", cy: "8", r: "7.25", stroke: "currentColor", strokeWidth: "1.5" }),
19661
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("path", { d: "M5 8.2L7.2 10.3L11 6", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round", strokeLinejoin: "round" }),
19662
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("circle", { cx: "13.5", cy: "13.5", r: "4.25", fill: "var(--ohw-background, #ffffff)", stroke: "var(--ohw-background, #ffffff)", strokeWidth: "1" }),
19663
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("circle", { cx: "13.5", cy: "13.5", r: "3.75", fill: "var(--ohw-background, #ffffff)", stroke: PRIMARY4, strokeWidth: "1" }),
19664
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("path", { d: "M11.5 13.5H15.5", stroke: PRIMARY4, strokeWidth: "1.5", strokeLinecap: "round" }),
19665
+ mode === "add" && /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("path", { d: "M13.5 11.5V15.5", stroke: PRIMARY4, strokeWidth: "1.5", strokeLinecap: "round" })
19208
19666
  ] });
19209
19667
  }
19210
- function ToolbarAction({
19668
+ function ToolbarAction2({
19211
19669
  label,
19212
19670
  onClick,
19213
19671
  disabled = false,
19214
19672
  children
19215
19673
  }) {
19216
- const button = /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(CustomToolbarButton, { type: "button", "aria-label": label, disabled, onClick, className: "size-8", children });
19217
- return /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(Tooltip, { children: [
19218
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(TooltipTrigger, { asChild: true, children: disabled ? /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("span", { className: "inline-flex", children: button }) : button }),
19219
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(TooltipContent, { side: "bottom", sideOffset: 8, children: label })
19674
+ const button = /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(CustomToolbarButton, { type: "button", "aria-label": label, disabled, onClick, className: "size-8", children });
19675
+ return /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(Tooltip, { children: [
19676
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(TooltipTrigger, { asChild: true, children: disabled ? /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("span", { className: "inline-flex", children: button }) : button }),
19677
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(TooltipContent, { side: "bottom", sideOffset: 8, children: label })
19220
19678
  ] });
19221
19679
  }
19222
- function ToolbarSeparator() {
19223
- return /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("span", { "aria-hidden": true, className: "mx-0.5 block h-6 w-px shrink-0 bg-border" });
19680
+ function ToolbarSeparator2() {
19681
+ return /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("span", { "aria-hidden": true, className: "mx-0.5 block h-6 w-px shrink-0 bg-border" });
19224
19682
  }
19225
19683
  function CardStyleMenu({
19226
19684
  value,
@@ -19228,7 +19686,7 @@ function CardStyleMenu({
19228
19686
  disabled = false
19229
19687
  }) {
19230
19688
  const current = PRICING_CARD_STYLES.find((s) => s.value === value) ?? PRICING_CARD_STYLES[0];
19231
- const trigger = /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(
19689
+ const trigger = /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(
19232
19690
  "button",
19233
19691
  {
19234
19692
  type: "button",
@@ -19239,28 +19697,28 @@ function CardStyleMenu({
19239
19697
  disabled && "cursor-not-allowed opacity-40 hover:bg-transparent"
19240
19698
  ),
19241
19699
  children: [
19242
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(StyleSwatch, { style: current.value }),
19243
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("span", { className: "flex-1 text-left", children: current.label }),
19244
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(import_lucide_react9.ChevronDown, { size: 16, style: { pointerEvents: "none" } })
19700
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(StyleSwatch, { style: current.value }),
19701
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("span", { className: "flex-1 text-left", children: current.label }),
19702
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(import_lucide_react10.ChevronDown, { size: 16, style: { pointerEvents: "none" } })
19245
19703
  ]
19246
19704
  }
19247
19705
  );
19248
19706
  if (disabled) {
19249
- return /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("div", { className: "flex items-center gap-1.5 pl-1.5", children: [
19250
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("span", { className: "text-xs text-muted-foreground opacity-50", children: "Card style" }),
19251
- /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(Tooltip, { children: [
19252
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("span", { className: "inline-flex", children: trigger }) }),
19253
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(TooltipContent, { side: "bottom", sideOffset: 8, children: "Mark as featured to edit card style" })
19707
+ return /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("div", { className: "flex items-center gap-1.5 pl-1.5", children: [
19708
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("span", { className: "text-xs text-muted-foreground opacity-50", children: "Card style" }),
19709
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(Tooltip, { children: [
19710
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("span", { className: "inline-flex", children: trigger }) }),
19711
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(TooltipContent, { side: "bottom", sideOffset: 8, children: "Mark as featured to edit card style" })
19254
19712
  ] })
19255
19713
  ] });
19256
19714
  }
19257
- return /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("div", { className: "flex items-center gap-1.5 pl-1.5", children: [
19258
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("span", { className: "text-xs text-muted-foreground", children: "Card style" }),
19259
- /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(DropdownMenu, { modal: false, children: [
19260
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(DropdownMenuTrigger, { asChild: true, children: trigger }),
19261
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(DropdownMenuContent, { align: "end", sideOffset: 8, className: "w-[152px] min-w-[152px] p-1", children: PRICING_CARD_STYLES.map((style) => {
19715
+ return /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("div", { className: "flex items-center gap-1.5 pl-1.5", children: [
19716
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("span", { className: "text-xs text-muted-foreground", children: "Card style" }),
19717
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(DropdownMenu, { modal: false, children: [
19718
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(DropdownMenuTrigger, { asChild: true, children: trigger }),
19719
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(DropdownMenuContent, { align: "end", sideOffset: 8, className: "w-[152px] min-w-[152px] p-1", children: PRICING_CARD_STYLES.map((style) => {
19262
19720
  const selected = style.value === value;
19263
- return /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(
19721
+ return /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(
19264
19722
  DropdownMenuItem,
19265
19723
  {
19266
19724
  role: "menuitemradio",
@@ -19268,9 +19726,9 @@ function CardStyleMenu({
19268
19726
  onSelect: () => onChange(style.value),
19269
19727
  className: cn("grid grid-cols-[18px_1fr_16px] gap-2 py-2 pl-2 pr-2 text-[13px]", selected && "bg-muted"),
19270
19728
  children: [
19271
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(StyleSwatch, { style: style.value }),
19272
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("span", { children: style.label }),
19273
- selected && /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(import_lucide_react9.Check, { size: 16, className: "text-bridge-primary" })
19729
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(StyleSwatch, { style: style.value }),
19730
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("span", { children: style.label }),
19731
+ selected && /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(import_lucide_react10.Check, { size: 16, className: "text-bridge-primary" })
19274
19732
  ]
19275
19733
  },
19276
19734
  style.value
@@ -19300,7 +19758,7 @@ function PricingCardToolbar({
19300
19758
  const style = pricingCardStyle(entry.tree, index);
19301
19759
  const featuredTag = pricingCardFeaturedTag(entry.tree, index);
19302
19760
  const mobile = window.innerWidth <= 480;
19303
- return /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(TooltipProvider, { delayDuration: 0, children: /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(
19761
+ return /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(TooltipProvider, { delayDuration: 0, children: /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(
19304
19762
  "div",
19305
19763
  {
19306
19764
  "data-ohw-bridge": "",
@@ -19312,7 +19770,7 @@ function PricingCardToolbar({
19312
19770
  ),
19313
19771
  style: { maxWidth: mobile ? "calc(100vw - 24px)" : void 0 },
19314
19772
  children: [
19315
- /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(
19773
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(
19316
19774
  "div",
19317
19775
  {
19318
19776
  className: cn(
@@ -19321,8 +19779,8 @@ function PricingCardToolbar({
19321
19779
  mobile ? "flex-wrap justify-center" : "flex-nowrap"
19322
19780
  ),
19323
19781
  children: [
19324
- /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(Tooltip, { children: [
19325
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
19782
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(Tooltip, { children: [
19783
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
19326
19784
  CustomToolbarButton,
19327
19785
  {
19328
19786
  type: "button",
@@ -19330,8 +19788,8 @@ function PricingCardToolbar({
19330
19788
  "aria-pressed": featuredTag,
19331
19789
  onClick: () => onSetFeaturedTag(!featuredTag),
19332
19790
  className: "group size-8",
19333
- children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
19334
- import_lucide_react9.Star,
19791
+ children: /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
19792
+ import_lucide_react10.Star,
19335
19793
  {
19336
19794
  size: 16,
19337
19795
  fill: "currentColor",
@@ -19345,25 +19803,25 @@ function PricingCardToolbar({
19345
19803
  )
19346
19804
  }
19347
19805
  ) }),
19348
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(TooltipContent, { side: "bottom", sideOffset: 8, children: featuredTag ? "Unmark featured" : "Mark as featured" })
19806
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(TooltipContent, { side: "bottom", sideOffset: 8, children: featuredTag ? "Unmark featured" : "Mark as featured" })
19349
19807
  ] }),
19350
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(ToolbarSeparator, {}),
19351
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(CardStyleMenu, { value: style, onChange: onSetStyle, disabled: !featuredTag }),
19352
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(ToolbarSeparator, {}),
19353
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(ToolbarAction, { label: "Add button", onClick: onAddButton, disabled: buttonCount >= 2, children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(ButtonGlyph, {}) }),
19354
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(ToolbarAction, { label: "Remove button", onClick: onRemoveButton, disabled: buttonCount === 0, children: /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("span", { className: "relative inline-flex", children: [
19355
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(ButtonGlyph, {}),
19356
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(import_lucide_react9.Minus, { size: 10, strokeWidth: 2.5, className: "absolute -right-1.5 -top-1.5 rounded-full bg-background text-bridge-primary" })
19808
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(ToolbarSeparator2, {}),
19809
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(CardStyleMenu, { value: style, onChange: onSetStyle, disabled: !featuredTag }),
19810
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(ToolbarSeparator2, {}),
19811
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(ToolbarAction2, { label: "Add button", onClick: onAddButton, disabled: buttonCount >= 2, children: /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(ButtonGlyph, {}) }),
19812
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(ToolbarAction2, { label: "Remove button", onClick: onRemoveButton, disabled: buttonCount === 0, children: /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("span", { className: "relative inline-flex", children: [
19813
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(ButtonGlyph, {}),
19814
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(import_lucide_react10.Minus, { size: 10, strokeWidth: 2.5, className: "absolute -right-1.5 -top-1.5 rounded-full bg-background text-bridge-primary" })
19357
19815
  ] }) }),
19358
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(ToolbarAction, { label: "Add feature", onClick: onAddFeature, disabled: featureCount >= 10, children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(FeatureGlyph, { mode: "add" }) }),
19359
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(ToolbarAction, { label: "Remove feature", onClick: onRemoveFeature, disabled: featureCount <= 1, children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(FeatureGlyph, { mode: "remove" }) }),
19360
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(ToolbarSeparator, {}),
19361
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(ToolbarAction, { label: "Add tier", onClick: onAddTier, disabled: collectionCount >= 4, children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(import_lucide_react9.Plus, { size: 16, style: { pointerEvents: "none" } }) }),
19362
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(ToolbarAction, { label: "Remove tier", onClick: onRemoveTier, disabled: collectionCount <= 1, children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(import_lucide_react9.Minus, { size: 16, style: { pointerEvents: "none" } }) })
19816
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(ToolbarAction2, { label: "Add feature", onClick: onAddFeature, disabled: featureCount >= 10, children: /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(FeatureGlyph, { mode: "add" }) }),
19817
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(ToolbarAction2, { label: "Remove feature", onClick: onRemoveFeature, disabled: featureCount <= 1, children: /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(FeatureGlyph, { mode: "remove" }) }),
19818
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(ToolbarSeparator2, {}),
19819
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(ToolbarAction2, { label: "Add tier", onClick: onAddTier, disabled: collectionCount >= 4, children: /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(import_lucide_react10.Plus, { size: 16, style: { pointerEvents: "none" } }) }),
19820
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(ToolbarAction2, { label: "Remove tier", onClick: onRemoveTier, disabled: collectionCount <= 1, children: /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(import_lucide_react10.Minus, { size: 16, style: { pointerEvents: "none" } }) })
19363
19821
  ]
19364
19822
  }
19365
19823
  ),
19366
- error && /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("div", { className: "max-w-[260px] rounded-md bg-background px-2 py-1 text-center text-[11px] text-destructive shadow-sm", children: error })
19824
+ error && /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("div", { className: "max-w-[260px] rounded-md bg-background px-2 py-1 text-center text-[11px] text-destructive shadow-sm", children: error })
19367
19825
  ]
19368
19826
  }
19369
19827
  ) });
@@ -19397,7 +19855,7 @@ function AiPricingCardOverlay({
19397
19855
  const overEditable = Boolean(t?.closest?.("[data-ohw-editable], [data-ohw-role], a, button"));
19398
19856
  setHover((prev) => {
19399
19857
  const next = ref && !overEditable ? ref : null;
19400
- return sameRef(prev, next) ? prev : next;
19858
+ return sameRef2(prev, next) ? prev : next;
19401
19859
  });
19402
19860
  };
19403
19861
  const onLeave = () => setHover(null);
@@ -19412,10 +19870,10 @@ function AiPricingCardOverlay({
19412
19870
  const onClick = (e) => {
19413
19871
  const t = e.target;
19414
19872
  if (!(t instanceof Element)) return;
19415
- if (t.closest(CHROME_SELECTOR)) return;
19873
+ if (t.closest(CHROME_SELECTOR2)) return;
19416
19874
  const ref = cardRefFromTarget(t);
19417
- setActive((prev) => sameRef(prev, ref) ? prev : ref);
19418
- if (!ref || !sameRef(activeRef.current, ref)) setError(null);
19875
+ setActive((prev) => sameRef2(prev, ref) ? prev : ref);
19876
+ if (!ref || !sameRef2(activeRef.current, ref)) setError(null);
19419
19877
  };
19420
19878
  document.addEventListener("click", onClick, true);
19421
19879
  return () => document.removeEventListener("click", onClick, true);
@@ -19480,12 +19938,12 @@ function AiPricingCardOverlay({
19480
19938
  const activeEntry = active ? findEntry(active.sectionId) : null;
19481
19939
  const activeCardRect = useLiveCardRect(active);
19482
19940
  const activeGridRect = useLiveCardRect(active, true);
19483
- const hoverRect = useLiveCardRect(hover && !sameRef(hover, active) ? hover : null);
19484
- return /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(import_jsx_runtime21.Fragment, { children: [
19485
- hoverRect && /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(ItemInteractionLayer, { rect: hoverRect, state: "hover" }),
19486
- activeCardRect && activeGridRect && active && activeEntry && /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(import_jsx_runtime21.Fragment, { children: [
19487
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(ItemInteractionLayer, { rect: activeCardRect, state: "active-top" }),
19488
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(GridToolbarSlot, { gridRect: activeGridRect, children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
19941
+ const hoverRect = useLiveCardRect(hover && !sameRef2(hover, active) ? hover : null);
19942
+ return /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(import_jsx_runtime22.Fragment, { children: [
19943
+ hoverRect && /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(ItemInteractionLayer, { rect: hoverRect, state: "hover" }),
19944
+ activeCardRect && activeGridRect && active && activeEntry && /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(import_jsx_runtime22.Fragment, { children: [
19945
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(ItemInteractionLayer, { rect: activeCardRect, state: "active-top" }),
19946
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(GridToolbarSlot, { gridRect: activeGridRect, children: /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
19489
19947
  PricingCardToolbar,
19490
19948
  {
19491
19949
  entry: activeEntry,
@@ -19513,7 +19971,7 @@ function AiPricingCardOverlay({
19513
19971
  onRemoveFeature: () => runMutation(active.sectionId, (tree) => removePricingCardFeature(tree, active.index))
19514
19972
  }
19515
19973
  ) }),
19516
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
19974
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
19517
19975
  "div",
19518
19976
  {
19519
19977
  "data-ohw-bridge": "",
@@ -19535,7 +19993,7 @@ function AiPricingCardOverlay({
19535
19993
 
19536
19994
  // src/ui/ai-section/AiVideoOverlay.tsx
19537
19995
  var import_react12 = require("react");
19538
- var import_lucide_react10 = require("lucide-react");
19996
+ var import_lucide_react11 = require("lucide-react");
19539
19997
 
19540
19998
  // src/lib/ai-video-tree.ts
19541
19999
  function resolveVideoBlock(tree, path) {
@@ -19606,10 +20064,10 @@ function extractYouTubeId(input) {
19606
20064
  }
19607
20065
 
19608
20066
  // src/ui/ai-section/AiVideoOverlay.tsx
19609
- var import_jsx_runtime22 = require("react/jsx-runtime");
20067
+ var import_jsx_runtime23 = require("react/jsx-runtime");
19610
20068
  var PRIMARY5 = "#0885FE";
19611
- var OHW_BLUE2 = `var(--ohw-primary, ${PRIMARY5})`;
19612
- var OHW_BLUE_DARK2 = `color-mix(in srgb, var(--ohw-primary, ${PRIMARY5}) 60%, #000000)`;
20069
+ var OHW_BLUE = `var(--ohw-primary, ${PRIMARY5})`;
20070
+ var OHW_BLUE_DARK = `color-mix(in srgb, var(--ohw-primary, ${PRIMARY5}) 60%, #000000)`;
19613
20071
  function findVideoBlockElement(sectionId, path) {
19614
20072
  const sectionEl = findByInstanceId(sectionId);
19615
20073
  if (!sectionEl) return null;
@@ -19669,12 +20127,12 @@ function requestValidateTree3(postToParent2, tree) {
19669
20127
  postToParent2({ type: "ow:ai-validate-tree-request", id, tree });
19670
20128
  });
19671
20129
  }
19672
- function Pill2({
20130
+ function Pill({
19673
20131
  label,
19674
20132
  onClick,
19675
20133
  disabled
19676
20134
  }) {
19677
- return /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
20135
+ return /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
19678
20136
  "button",
19679
20137
  {
19680
20138
  type: "button",
@@ -19690,7 +20148,7 @@ function Pill2({
19690
20148
  border: "none",
19691
20149
  cursor: disabled ? "default" : "pointer",
19692
20150
  background: "#ffffff",
19693
- color: OHW_BLUE_DARK2,
20151
+ color: OHW_BLUE_DARK,
19694
20152
  opacity: disabled ? 0.4 : 1,
19695
20153
  fontSize: 12,
19696
20154
  lineHeight: "16px",
@@ -19733,8 +20191,8 @@ function VideoUrlInput({ value, onCommit }) {
19733
20191
  onCommit(id);
19734
20192
  }
19735
20193
  };
19736
- return /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("div", { style: { display: "flex", flexDirection: "column", gap: 4, width: "100%", maxWidth: 280 }, children: [
19737
- /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(
20194
+ return /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)("div", { style: { display: "flex", flexDirection: "column", gap: 4, width: "100%", maxWidth: 280 }, children: [
20195
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)(
19738
20196
  "div",
19739
20197
  {
19740
20198
  style: {
@@ -19750,12 +20208,12 @@ function VideoUrlInput({ value, onCommit }) {
19750
20208
  boxShadow: invalid ? "inset 0 0 0 1.5px #EF4444" : "none"
19751
20209
  },
19752
20210
  children: [
19753
- /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
20211
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
19754
20212
  "span",
19755
20213
  {
19756
20214
  style: {
19757
20215
  flex: "0 0 auto",
19758
- color: OHW_BLUE_DARK2,
20216
+ color: OHW_BLUE_DARK,
19759
20217
  fontSize: 10,
19760
20218
  lineHeight: "16px",
19761
20219
  fontWeight: 700,
@@ -19765,7 +20223,7 @@ function VideoUrlInput({ value, onCommit }) {
19765
20223
  children: "Video"
19766
20224
  }
19767
20225
  ),
19768
- /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
20226
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
19769
20227
  "input",
19770
20228
  {
19771
20229
  type: "text",
@@ -19802,7 +20260,7 @@ function VideoUrlInput({ value, onCommit }) {
19802
20260
  ]
19803
20261
  }
19804
20262
  ),
19805
- invalid && /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("div", { style: { color: "#FCA5A5", fontSize: 11, textAlign: "center" }, children: "That doesn't look like a YouTube link \u2014 try pasting the video URL again." })
20263
+ invalid && /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("div", { style: { color: "#FCA5A5", fontSize: 11, textAlign: "center" }, children: "That doesn't look like a YouTube link \u2014 try pasting the video URL again." })
19806
20264
  ] });
19807
20265
  }
19808
20266
  function VideoToolbar({
@@ -19812,7 +20270,7 @@ function VideoToolbar({
19812
20270
  onSetVideoId,
19813
20271
  onRemove
19814
20272
  }) {
19815
- return /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(
20273
+ return /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)(
19816
20274
  "div",
19817
20275
  {
19818
20276
  "data-ohw-bridge": "",
@@ -19824,7 +20282,7 @@ function VideoToolbar({
19824
20282
  gap: 6,
19825
20283
  alignItems: "center",
19826
20284
  width: 280,
19827
- background: OHW_BLUE2,
20285
+ background: OHW_BLUE,
19828
20286
  padding: 8,
19829
20287
  borderRadius: 12,
19830
20288
  boxShadow: "0 8px 24px rgba(0,0,0,0.25)",
@@ -19832,9 +20290,9 @@ function VideoToolbar({
19832
20290
  opacity: pending ? 0.6 : 1
19833
20291
  },
19834
20292
  children: [
19835
- /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(VideoUrlInput, { value: videoId, onCommit: onSetVideoId }),
19836
- /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(Pill2, { label: "Remove video", onClick: onRemove, disabled: !videoId }),
19837
- error && /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("div", { style: { color: "#FCA5A5", fontSize: 11, maxWidth: 220, textAlign: "center" }, children: error })
20293
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(VideoUrlInput, { value: videoId, onCommit: onSetVideoId }),
20294
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(Pill, { label: "Remove video", onClick: onRemove, disabled: !videoId }),
20295
+ error && /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("div", { style: { color: "#FCA5A5", fontSize: 11, maxWidth: 220, textAlign: "center" }, children: error })
19838
20296
  ]
19839
20297
  }
19840
20298
  );
@@ -19954,8 +20412,8 @@ function AiVideoOverlay({
19954
20412
  const activeBlockRect = useLiveBlockRect(active);
19955
20413
  const TOOLBAR_APPROX_H = 90;
19956
20414
  const toolbarState = activeBlockRect && activeBlockRect.top < TOOLBAR_APPROX_H ? "active-bottom" : "active-top";
19957
- return /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(import_jsx_runtime22.Fragment, { children: [
19958
- hoverHandleRect && hover && /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
20415
+ return /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)(import_jsx_runtime23.Fragment, { children: [
20416
+ hoverHandleRect && hover && /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
19959
20417
  "button",
19960
20418
  {
19961
20419
  type: "button",
@@ -19983,15 +20441,15 @@ function AiVideoOverlay({
19983
20441
  fontSize: 14,
19984
20442
  fontWeight: 700
19985
20443
  },
19986
- children: /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(import_lucide_react10.Star, { size: 14, style: { pointerEvents: "none" } })
20444
+ children: /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(import_lucide_react11.Star, { size: 14, style: { pointerEvents: "none" } })
19987
20445
  }
19988
20446
  ),
19989
- activeBlockRect && active && activeEntry && /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
20447
+ activeBlockRect && active && activeEntry && /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
19990
20448
  ItemInteractionLayer,
19991
20449
  {
19992
20450
  rect: activeBlockRect,
19993
20451
  state: toolbarState,
19994
- toolbar: /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
20452
+ toolbar: /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
19995
20453
  VideoToolbar,
19996
20454
  {
19997
20455
  videoId: videoIdAt(activeEntry.tree, active.path),
@@ -20015,7 +20473,7 @@ function AiVideoOverlay({
20015
20473
 
20016
20474
  // src/ui/ai-section/AiFadeCarouselOverlay.tsx
20017
20475
  var import_react13 = require("react");
20018
- var import_lucide_react11 = require("lucide-react");
20476
+ var import_lucide_react12 = require("lucide-react");
20019
20477
 
20020
20478
  // src/lib/ai-carousel-tree.ts
20021
20479
  function isCarouselBlock(node) {
@@ -20066,10 +20524,10 @@ function setCarouselAutoplay(tree, on) {
20066
20524
  }
20067
20525
 
20068
20526
  // src/ui/ai-section/AiFadeCarouselOverlay.tsx
20069
- var import_jsx_runtime23 = require("react/jsx-runtime");
20527
+ var import_jsx_runtime24 = require("react/jsx-runtime");
20070
20528
  var PRIMARY6 = "#0885FE";
20071
- var OHW_BLUE3 = `var(--ohw-primary, ${PRIMARY6})`;
20072
- var OHW_BLUE_DARK3 = `color-mix(in srgb, var(--ohw-primary, ${PRIMARY6}) 60%, #000000)`;
20529
+ var OHW_BLUE2 = `var(--ohw-primary, ${PRIMARY6})`;
20530
+ var OHW_BLUE_DARK2 = `color-mix(in srgb, var(--ohw-primary, ${PRIMARY6}) 60%, #000000)`;
20073
20531
  function findCarouselItemElement(sectionId, index) {
20074
20532
  const sectionEl = findByInstanceId(sectionId);
20075
20533
  if (!sectionEl) return null;
@@ -20148,8 +20606,8 @@ function requestAssetPick2(postToParent2) {
20148
20606
  postToParent2({ type: "ow:asset-pick", id });
20149
20607
  });
20150
20608
  }
20151
- function Pill3({ label, onClick, disabled }) {
20152
- return /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
20609
+ function Pill2({ label, onClick, disabled }) {
20610
+ return /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
20153
20611
  "button",
20154
20612
  {
20155
20613
  type: "button",
@@ -20165,7 +20623,7 @@ function Pill3({ label, onClick, disabled }) {
20165
20623
  border: "none",
20166
20624
  cursor: disabled ? "default" : "pointer",
20167
20625
  background: "#ffffff",
20168
- color: OHW_BLUE_DARK3,
20626
+ color: OHW_BLUE_DARK2,
20169
20627
  opacity: disabled ? 0.4 : 1,
20170
20628
  fontSize: 12,
20171
20629
  lineHeight: "16px",
@@ -20183,7 +20641,7 @@ function TogglePill({
20183
20641
  onToggle,
20184
20642
  disabled
20185
20643
  }) {
20186
- return /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)(
20644
+ return /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)(
20187
20645
  "button",
20188
20646
  {
20189
20647
  type: "button",
@@ -20202,7 +20660,7 @@ function TogglePill({
20202
20660
  border: "none",
20203
20661
  cursor: disabled ? "default" : "pointer",
20204
20662
  background: "#ffffff",
20205
- color: OHW_BLUE_DARK3,
20663
+ color: OHW_BLUE_DARK2,
20206
20664
  opacity: disabled ? 0.4 : 1,
20207
20665
  fontSize: 12,
20208
20666
  lineHeight: "16px",
@@ -20212,7 +20670,7 @@ function TogglePill({
20212
20670
  },
20213
20671
  children: [
20214
20672
  label,
20215
- /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
20673
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
20216
20674
  "span",
20217
20675
  {
20218
20676
  "aria-hidden": true,
@@ -20225,10 +20683,10 @@ function TogglePill({
20225
20683
  // Off is a grey track, not a washed-out blue: the pill's own white background already
20226
20684
  // carries the brand colour in the text, and a pale blue track next to it read as
20227
20685
  // "disabled control" rather than "switch that is off".
20228
- background: on ? OHW_BLUE_DARK3 : "#CBD5E1",
20686
+ background: on ? OHW_BLUE_DARK2 : "#CBD5E1",
20229
20687
  transition: "background 120ms ease"
20230
20688
  },
20231
- children: /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
20689
+ children: /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
20232
20690
  "span",
20233
20691
  {
20234
20692
  style: {
@@ -20257,7 +20715,7 @@ function CarouselSlideToolbar({
20257
20715
  onChangeImage,
20258
20716
  onToggleAutoplay
20259
20717
  }) {
20260
- return /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)(
20718
+ return /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)(
20261
20719
  "div",
20262
20720
  {
20263
20721
  "data-ohw-bridge": "",
@@ -20268,7 +20726,7 @@ function CarouselSlideToolbar({
20268
20726
  flexDirection: "column",
20269
20727
  gap: 6,
20270
20728
  alignItems: "center",
20271
- background: OHW_BLUE3,
20729
+ background: OHW_BLUE2,
20272
20730
  padding: 8,
20273
20731
  borderRadius: 12,
20274
20732
  boxShadow: "0 8px 24px rgba(0,0,0,0.25)",
@@ -20280,11 +20738,11 @@ function CarouselSlideToolbar({
20280
20738
  pointerEvents: pending ? "none" : "auto"
20281
20739
  },
20282
20740
  children: [
20283
- /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)("div", { style: { display: "flex", gap: 6, alignItems: "center" }, children: [
20284
- /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(Pill3, { label: "Change Image", onClick: onChangeImage }),
20285
- /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(TogglePill, { label: "Autoplay", on: autoplay, onToggle: onToggleAutoplay })
20741
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)("div", { style: { display: "flex", gap: 6, alignItems: "center" }, children: [
20742
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(Pill2, { label: "Change Image", onClick: onChangeImage }),
20743
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(TogglePill, { label: "Autoplay", on: autoplay, onToggle: onToggleAutoplay })
20286
20744
  ] }),
20287
- error && /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("div", { style: { color: "#FCA5A5", fontSize: 11, maxWidth: 220, textAlign: "center" }, children: error })
20745
+ error && /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("div", { style: { color: "#FCA5A5", fontSize: 11, maxWidth: 220, textAlign: "center" }, children: error })
20288
20746
  ]
20289
20747
  }
20290
20748
  );
@@ -20424,8 +20882,8 @@ function AiFadeCarouselOverlay({
20424
20882
  const activeItemRect = useLiveItemRect2(active);
20425
20883
  const TOOLBAR_APPROX_H = 90;
20426
20884
  const toolbarState = activeItemRect && activeItemRect.top < TOOLBAR_APPROX_H ? "active-bottom" : "active-top";
20427
- return /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)(import_jsx_runtime23.Fragment, { children: [
20428
- hoverHandleRect && hover && /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
20885
+ return /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)(import_jsx_runtime24.Fragment, { children: [
20886
+ hoverHandleRect && hover && /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
20429
20887
  "button",
20430
20888
  {
20431
20889
  type: "button",
@@ -20454,10 +20912,10 @@ function AiFadeCarouselOverlay({
20454
20912
  fontSize: 14,
20455
20913
  fontWeight: 700
20456
20914
  },
20457
- children: /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(import_lucide_react11.Star, { size: 14, style: { pointerEvents: "none" } })
20915
+ children: /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(import_lucide_react12.Star, { size: 14, style: { pointerEvents: "none" } })
20458
20916
  }
20459
20917
  ),
20460
- swapping && /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
20918
+ swapping && /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
20461
20919
  "div",
20462
20920
  {
20463
20921
  ref: skeletonRef,
@@ -20475,15 +20933,15 @@ function AiFadeCarouselOverlay({
20475
20933
  zIndex: 2147483646,
20476
20934
  pointerEvents: "none"
20477
20935
  },
20478
- children: /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("style", { children: MEDIA_SKELETON_CSS })
20936
+ children: /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("style", { children: MEDIA_SKELETON_CSS })
20479
20937
  }
20480
20938
  ),
20481
- activeItemRect && active && activeEntry && /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
20939
+ activeItemRect && active && activeEntry && /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
20482
20940
  ItemInteractionLayer,
20483
20941
  {
20484
20942
  rect: activeItemRect,
20485
20943
  state: toolbarState,
20486
- toolbar: /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
20944
+ toolbar: /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
20487
20945
  CarouselSlideToolbar,
20488
20946
  {
20489
20947
  pending,
@@ -20576,165 +21034,30 @@ function findButtonAtPoint(x, y, media) {
20576
21034
  if (hits.length === 0) return null;
20577
21035
  return hits.reduce((best, el) => best.contains(el) ? el : best);
20578
21036
  }
20579
- function findNavbarCtaAtPoint(x, y) {
20580
- const hits = Array.from(
20581
- document.querySelectorAll('[data-ohw-role="navbar-button"]')
20582
- ).filter((el) => containsPoint(el, x, y) && !el.closest("[data-ohw-bridge]"));
20583
- if (hits.length === 0) return null;
20584
- return hits.reduce((best, el) => best.contains(el) ? el : best);
20585
- }
20586
-
20587
- // src/lib/session-search.ts
20588
- var STORAGE_KEY = "ohw-preserved-search";
20589
- function readPreservedSearch() {
20590
- if (typeof window === "undefined") return "";
20591
- const fromUrl = window.location.search;
20592
- if (fromUrl && fromUrl !== "?") {
20593
- sessionStorage.setItem(STORAGE_KEY, fromUrl);
20594
- return fromUrl;
20595
- }
20596
- return sessionStorage.getItem(STORAGE_KEY) ?? "";
20597
- }
20598
- function isEditSessionActive() {
20599
- const search = readPreservedSearch();
20600
- if (!search || search === "?") return false;
20601
- const q = search.startsWith("?") ? search.slice(1) : search;
20602
- return new URLSearchParams(q).get("mode") === "edit";
20603
- }
20604
-
20605
- // src/ui/link-modal/linkModal.utils.ts
20606
- function parseTarget(target) {
20607
- if (!target) return { pageRoute: "", sectionId: null };
20608
- const hashIndex = target.indexOf("#");
20609
- if (hashIndex === -1) return { pageRoute: target, sectionId: null };
20610
- return {
20611
- pageRoute: target.slice(0, hashIndex),
20612
- sectionId: target.slice(hashIndex + 1) || null
20613
- };
20614
- }
20615
- function normalizePath(path) {
20616
- const trimmed = path.trim();
20617
- if (!trimmed) return "/";
20618
- try {
20619
- if (/^https?:\/\//i.test(trimmed)) {
20620
- return new URL(trimmed).pathname || "/";
20621
- }
20622
- } catch {
20623
- }
20624
- const withSlash = trimmed.startsWith("/") ? trimmed : `/${trimmed}`;
20625
- if (withSlash.length > 1 && withSlash.endsWith("/")) return withSlash.slice(0, -1);
20626
- return withSlash;
20627
- }
20628
- function findPageByPath(pages, path) {
20629
- const normalized = normalizePath(path);
20630
- return pages.find((p) => normalizePath(p.path) === normalized);
20631
- }
20632
- function inferPageTitleFromPath(path) {
20633
- const normalized = normalizePath(path);
20634
- if (normalized === "/") return "Home";
20635
- const segment = normalized.split("/").filter(Boolean).pop() ?? "";
20636
- if (!segment) return "Home";
20637
- return segment.split("-").filter(Boolean).map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join(" ");
20638
- }
20639
- function isInternalPath(route) {
20640
- const trimmed = route.trim();
20641
- if (!trimmed) return false;
20642
- if (/^https?:\/\//i.test(trimmed)) return false;
20643
- if (trimmed.startsWith("/")) return true;
20644
- if (trimmed.includes(" ") || trimmed.includes(".")) return false;
20645
- return true;
20646
- }
20647
- function resolvePage(route, pages) {
20648
- const found = findPageByPath(pages, route);
20649
- if (found) return { path: found.path, title: found.title };
20650
- return { path: normalizePath(route), title: inferPageTitleFromPath(route) };
20651
- }
20652
- function getSectionsForPath(sectionsByPath, pageRoute) {
20653
- if (!sectionsByPath) return [];
20654
- const normalized = normalizePath(pageRoute);
20655
- return sectionsByPath[normalized] ?? sectionsByPath[pageRoute] ?? [];
20656
- }
20657
- function buildTarget(page, section) {
20658
- if (section) return `${page.path}#${section.id}`;
20659
- return page.path;
20660
- }
20661
- function isValidUrl(urlString) {
20662
- try {
20663
- const url = new URL(urlString);
20664
- if (url.protocol !== "http:" && url.protocol !== "https:") return false;
20665
- const hostname = url.hostname;
20666
- if (!hostname || hostname.length === 0) return false;
20667
- if (hostname.includes(" ")) return false;
20668
- if (hostname === "localhost") return true;
20669
- if (!hostname.includes(".")) return false;
20670
- if (!/[a-z]/i.test(hostname)) return false;
20671
- return true;
20672
- } catch {
20673
- return false;
20674
- }
20675
- }
20676
- function normalizeUrl(value) {
20677
- const trimmed = value.trim();
20678
- if (trimmed.startsWith("http://") || trimmed.startsWith("https://")) return trimmed;
20679
- return `https://${trimmed}`;
20680
- }
20681
- function validateUrlInput(value, pages, allPages) {
20682
- const trimmed = value.trim();
20683
- if (!trimmed) return "";
20684
- const exactPage = allPages.find((p) => p.title.toLowerCase() === trimmed.toLowerCase());
20685
- if (exactPage) return "";
20686
- const hasPrefixMatch = pages.some((p) => p.title.toLowerCase().startsWith(trimmed.toLowerCase()));
20687
- if (hasPrefixMatch) {
20688
- return "Select a page from the list or enter a valid URL";
20689
- }
20690
- const hasScheme = /^https?:\/\//i.test(trimmed);
20691
- if (!hasScheme && !trimmed.includes(".")) {
20692
- return "Please enter a valid URL (e.g., https://example.com)";
20693
- }
20694
- if (!isValidUrl(normalizeUrl(trimmed))) {
20695
- return "Please enter a valid URL (e.g., https://example.com)";
20696
- }
20697
- return "";
20698
- }
20699
- function getEditModeInitialState(target, pages, sections) {
20700
- const empty = {
20701
- searchValue: "",
20702
- selectedPage: null,
20703
- selectedSection: null,
20704
- step: "input"
20705
- };
20706
- if (!target) return empty;
20707
- if (isValidUrl(normalizeUrl(target))) {
20708
- return { searchValue: target, selectedPage: null, selectedSection: null, step: "input" };
20709
- }
20710
- const { pageRoute, sectionId } = parseTarget(target);
20711
- if (sectionId) {
20712
- if (isInternalPath(pageRoute)) {
20713
- const page = resolvePage(pageRoute, pages);
20714
- const sec = sections.find((s) => s.id === sectionId);
20715
- return {
20716
- searchValue: page.title,
20717
- selectedPage: page,
20718
- selectedSection: sec ? { id: sec.id, label: sec.label } : null,
20719
- step: "input"
20720
- };
20721
- }
20722
- return { searchValue: pageRoute || target, selectedPage: null, selectedSection: null, step: "input" };
20723
- }
20724
- if (isInternalPath(pageRoute)) {
20725
- const page = resolvePage(pageRoute, pages);
20726
- return {
20727
- searchValue: page.title,
20728
- selectedPage: page,
20729
- selectedSection: null,
20730
- step: "input"
20731
- };
21037
+ function findNavbarCtaAtPoint(x, y) {
21038
+ const hits = Array.from(
21039
+ document.querySelectorAll('[data-ohw-role="navbar-button"]')
21040
+ ).filter((el) => containsPoint(el, x, y) && !el.closest("[data-ohw-bridge]"));
21041
+ if (hits.length === 0) return null;
21042
+ return hits.reduce((best, el) => best.contains(el) ? el : best);
21043
+ }
21044
+
21045
+ // src/lib/session-search.ts
21046
+ var STORAGE_KEY = "ohw-preserved-search";
21047
+ function readPreservedSearch() {
21048
+ if (typeof window === "undefined") return "";
21049
+ const fromUrl = window.location.search;
21050
+ if (fromUrl && fromUrl !== "?") {
21051
+ sessionStorage.setItem(STORAGE_KEY, fromUrl);
21052
+ return fromUrl;
20732
21053
  }
20733
- return { searchValue: target, selectedPage: null, selectedSection: null, step: "input" };
21054
+ return sessionStorage.getItem(STORAGE_KEY) ?? "";
20734
21055
  }
20735
- function filterAvailablePages(pages, existingTargets) {
20736
- const taken = new Set(existingTargets);
20737
- return pages.filter((p) => !taken.has(p.path));
21056
+ function isEditSessionActive() {
21057
+ const search = readPreservedSearch();
21058
+ if (!search || search === "?") return false;
21059
+ const q = search.startsWith("?") ? search.slice(1) : search;
21060
+ return new URLSearchParams(q).get("mode") === "edit";
20738
21061
  }
20739
21062
 
20740
21063
  // src/lib/prefetch-sections.ts
@@ -20836,113 +21159,6 @@ function scrollToHashSectionWhenReady(behavior = "smooth") {
20836
21159
  // src/ui/link-modal/LinkPopover.tsx
20837
21160
  var import_react17 = require("react");
20838
21161
 
20839
- // src/ui/dialog.tsx
20840
- var React9 = __toESM(require("react"), 1);
20841
- var import_radix_ui6 = require("radix-ui");
20842
- var import_lucide_react12 = require("lucide-react");
20843
- var import_jsx_runtime24 = require("react/jsx-runtime");
20844
- function Dialog2({
20845
- ...props
20846
- }) {
20847
- return /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(import_radix_ui6.Dialog.Root, { "data-slot": "dialog", ...props });
20848
- }
20849
- function DialogPortal({
20850
- ...props
20851
- }) {
20852
- return /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(import_radix_ui6.Dialog.Portal, { ...props });
20853
- }
20854
- function DialogOverlay({
20855
- className,
20856
- ...props
20857
- }) {
20858
- return /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
20859
- import_radix_ui6.Dialog.Overlay,
20860
- {
20861
- "data-slot": "dialog-overlay",
20862
- "data-ohw-link-modal-root": "",
20863
- className: cn("fixed inset-0 z-[2147483646] bg-black/50", className),
20864
- ...props
20865
- }
20866
- );
20867
- }
20868
- var DialogContent = React9.forwardRef(
20869
- ({ className, children, showCloseButton = true, container, ...props }, ref) => {
20870
- const positionMode = container ? "absolute" : "fixed";
20871
- return /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)(DialogPortal, { container: container ?? void 0, children: [
20872
- /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(DialogOverlay, { className: cn(positionMode, "inset-0") }),
20873
- /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)(
20874
- import_radix_ui6.Dialog.Content,
20875
- {
20876
- ref,
20877
- "data-slot": "dialog-content",
20878
- className: cn(
20879
- positionMode,
20880
- "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",
20881
- "rounded-xl border border-border bg-background font-sans shadow-lg outline-none",
20882
- container ? "max-h-[calc(100%-32px)]" : "max-h-[calc(100vh-32px)]",
20883
- className
20884
- ),
20885
- onMouseDown: (e) => e.stopPropagation(),
20886
- ...props,
20887
- children: [
20888
- children,
20889
- showCloseButton ? /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
20890
- import_radix_ui6.Dialog.Close,
20891
- {
20892
- type: "button",
20893
- className: "absolute right-[9px] top-[9px] rounded-sm p-1.5 text-foreground hover:bg-muted/50",
20894
- "aria-label": "Close",
20895
- children: /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(import_lucide_react12.X, { size: 16, "aria-hidden": true })
20896
- }
20897
- ) : null
20898
- ]
20899
- }
20900
- )
20901
- ] });
20902
- }
20903
- );
20904
- DialogContent.displayName = import_radix_ui6.Dialog.Content.displayName;
20905
- function DialogHeader({
20906
- className,
20907
- ...props
20908
- }) {
20909
- return /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("div", { className: cn("flex flex-col gap-1.5", className), ...props });
20910
- }
20911
- function DialogFooter({
20912
- className,
20913
- ...props
20914
- }) {
20915
- return /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
20916
- "div",
20917
- {
20918
- className: cn("flex items-center justify-end gap-2", className),
20919
- ...props
20920
- }
20921
- );
20922
- }
20923
- var DialogTitle = React9.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
20924
- import_radix_ui6.Dialog.Title,
20925
- {
20926
- ref,
20927
- className: cn(
20928
- "text-lg font-semibold leading-none tracking-tight text-card-foreground",
20929
- className
20930
- ),
20931
- ...props
20932
- }
20933
- ));
20934
- DialogTitle.displayName = import_radix_ui6.Dialog.Title.displayName;
20935
- var DialogDescription = React9.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
20936
- import_radix_ui6.Dialog.Description,
20937
- {
20938
- ref,
20939
- className: cn("text-sm text-muted-foreground", className),
20940
- ...props
20941
- }
20942
- ));
20943
- DialogDescription.displayName = import_radix_ui6.Dialog.Description.displayName;
20944
- var DialogClose = import_radix_ui6.Dialog.Close;
20945
-
20946
21162
  // src/ui/link-modal/LinkEditorPanel.tsx
20947
21163
  var import_lucide_react16 = require("lucide-react");
20948
21164
 
@@ -22046,6 +22262,7 @@ function shouldUseDevFixtures() {
22046
22262
  function getLogoElement(el) {
22047
22263
  const marked = el.closest('[data-ohw-role="logo"], [data-ohw-logo]');
22048
22264
  if (marked) return marked;
22265
+ if (el.closest('[data-ohw-editable="icon"]')) return null;
22049
22266
  const root = el.closest("nav, [data-ohw-nav-root], footer");
22050
22267
  if (!root) return null;
22051
22268
  const anchor = el.closest("a");
@@ -24626,7 +24843,7 @@ function getNavigationSelectionParent(el) {
24626
24843
  if (!isFooterLinksContainer(el) && (el.hasAttribute("data-ohw-footer-col") || el.hasAttribute("data-ohw-footer-column") || isInferredFooterGroup2(el))) {
24627
24844
  return resolveFooterLinksContainerFrom(el);
24628
24845
  }
24629
- if (el.hasAttribute("data-ohw-nav-container") || el.hasAttribute("data-ohw-nav-drawer") || isFooterLinksContainer(el)) {
24846
+ 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)) {
24630
24847
  return getNavigationRoot(el);
24631
24848
  }
24632
24849
  return null;
@@ -24848,7 +25065,6 @@ var ICONS = {
24848
25065
  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"/>',
24849
25066
  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"/>'
24850
25067
  };
24851
- var HEIGHT_SETTLE_DELAYS = [150, 400, 800];
24852
25068
  var SELECTION_CHROME_GAP2 = 4;
24853
25069
  var TOOLBAR_STROKE_GAP2 = 4;
24854
25070
  var TOOLBAR_OFFSET_FROM_ELEMENT = SELECTION_CHROME_GAP2 + TOOLBAR_STROKE_GAP2;
@@ -24946,7 +25162,7 @@ function applyVisibleViewport(el, parentScroll) {
24946
25162
  el.style.top = `${top}px`;
24947
25163
  el.style.height = `${height}px`;
24948
25164
  }
24949
- var TOOLBAR_EDGE_MARGIN3 = 4;
25165
+ var TOOLBAR_EDGE_MARGIN4 = 4;
24950
25166
  function clampToolbarToClip(top, transform, rect, clip, approxH, gap) {
24951
25167
  const isAbove = transform.includes("translateY(-100%)");
24952
25168
  let visualTop = isAbove ? top - approxH : top;
@@ -24973,8 +25189,8 @@ function clampToolbarToClip(top, transform, rect, clip, approxH, gap) {
24973
25189
  function clampToolbarLeft(centerX, toolbarWidth) {
24974
25190
  const half = toolbarWidth / 2;
24975
25191
  return Math.max(
24976
- TOOLBAR_EDGE_MARGIN3 + half,
24977
- Math.min(centerX, window.innerWidth - TOOLBAR_EDGE_MARGIN3 - half)
25192
+ TOOLBAR_EDGE_MARGIN4 + half,
25193
+ Math.min(centerX, window.innerWidth - TOOLBAR_EDGE_MARGIN4 - half)
24978
25194
  );
24979
25195
  }
24980
25196
  function calcToolbarPos(rect, parentScroll, toolbarW = 306) {
@@ -25405,6 +25621,34 @@ function OhhwellsBridge() {
25405
25621
  const [fetchState, setFetchState] = (0, import_react21.useState)("idle");
25406
25622
  const [showBranding, setShowBranding] = (0, import_react21.useState)(false);
25407
25623
  const autoSaveTimers = (0, import_react21.useRef)(/* @__PURE__ */ new Map());
25624
+ (0, import_react21.useEffect)(() => {
25625
+ stampBridgeVersionOnDocument();
25626
+ }, []);
25627
+ (0, import_react21.useEffect)(() => {
25628
+ if (isEditMode) return;
25629
+ return armLoaderHangBeacon(subdomain, fetchState);
25630
+ }, [isEditMode, subdomain, fetchState]);
25631
+ (0, import_react21.useEffect)(() => {
25632
+ if (isEditMode || !subdomain) return;
25633
+ const onError = (event) => {
25634
+ reportOutageBeacon({
25635
+ subdomain,
25636
+ reason: "page_error",
25637
+ detail: event.message || "window error"
25638
+ });
25639
+ };
25640
+ const onRejection = (event) => {
25641
+ const reason = event.reason;
25642
+ const detail = reason instanceof Error ? reason.message : typeof reason === "string" ? reason : "unhandledrejection";
25643
+ reportOutageBeacon({ subdomain, reason: "page_error", detail });
25644
+ };
25645
+ window.addEventListener("error", onError);
25646
+ window.addEventListener("unhandledrejection", onRejection);
25647
+ return () => {
25648
+ window.removeEventListener("error", onError);
25649
+ window.removeEventListener("unhandledrejection", onRejection);
25650
+ };
25651
+ }, [isEditMode, subdomain]);
25408
25652
  const activeElRef = (0, import_react21.useRef)(null);
25409
25653
  const pointerHeldRef = (0, import_react21.useRef)(false);
25410
25654
  const selectedElRef = (0, import_react21.useRef)(null);
@@ -25826,21 +26070,6 @@ function OhhwellsBridge() {
25826
26070
  const [isItemDragging, setIsItemDragging] = (0, import_react21.useState)(false);
25827
26071
  const [isFooterFrameSelection, setIsFooterFrameSelection] = (0, import_react21.useState)(false);
25828
26072
  isFooterFrameSelectionRef.current = isFooterFrameSelection;
25829
- const [floatingPanel, setFloatingPanel] = (0, import_react21.useState)(null);
25830
- const floatingPanelOpenRef = (0, import_react21.useRef)(false);
25831
- floatingPanelOpenRef.current = floatingPanel !== null;
25832
- const floatingPanelRef = (0, import_react21.useRef)(null);
25833
- floatingPanelRef.current = floatingPanel;
25834
- const [floatingPanelPos, setFloatingPanelPos] = (0, import_react21.useState)(null);
25835
- const buttonCollectionDragRef = (0, import_react21.useRef)(null);
25836
- const buttonDropInsertIndexRef = (0, import_react21.useRef)(null);
25837
- const [buttonDropSlots, setButtonDropSlots] = (0, import_react21.useState)([]);
25838
- const [activeButtonDropIndex, setActiveButtonDropIndex] = (0, import_react21.useState)(null);
25839
- const buttonPointerDragRef = (0, import_react21.useRef)(null);
25840
- const [buttonStyleTick, setButtonStyleTick] = (0, import_react21.useState)(0);
25841
- const [logoSizeDraft, setLogoSizeDraft] = (0, import_react21.useState)(null);
25842
- const [editorViewport, setEditorViewport] = (0, import_react21.useState)("desktop");
25843
- const [parentScrollSnap, setParentScrollSnap] = (0, import_react21.useState)(null);
25844
26073
  const [navDropdownPreviewOpen, setNavDropdownPreviewOpen] = (0, import_react21.useState)(null);
25845
26074
  const [footerHeadingVisible, setFooterHeadingVisible] = (0, import_react21.useState)(null);
25846
26075
  const footerDragRef = (0, import_react21.useRef)(null);
@@ -25866,7 +26095,23 @@ function OhhwellsBridge() {
25866
26095
  const stylesRef = (0, import_react21.useRef)("");
25867
26096
  const appliedDrawerRef = (0, import_react21.useRef)(null);
25868
26097
  const pendingDeleteUndoRef = (0, import_react21.useRef)(null);
26098
+ const [floatingPanel, setFloatingPanel] = (0, import_react21.useState)(null);
26099
+ const floatingPanelOpenRef = (0, import_react21.useRef)(false);
26100
+ const floatingPanelRef = (0, import_react21.useRef)(null);
26101
+ floatingPanelRef.current = floatingPanel;
26102
+ const setFloatingPanelRef = (0, import_react21.useRef)(setFloatingPanel);
26103
+ const [floatingPanelPos, setFloatingPanelPos] = (0, import_react21.useState)(null);
26104
+ const buttonCollectionDragRef = (0, import_react21.useRef)(null);
26105
+ const buttonDropInsertIndexRef = (0, import_react21.useRef)(null);
26106
+ const [buttonDropSlots, setButtonDropSlots] = (0, import_react21.useState)([]);
26107
+ const [activeButtonDropIndex, setActiveButtonDropIndex] = (0, import_react21.useState)(null);
26108
+ const buttonPointerDragRef = (0, import_react21.useRef)(null);
26109
+ const [buttonStyleTick, setButtonStyleTick] = (0, import_react21.useState)(0);
26110
+ const [logoSizeDraft, setLogoSizeDraft] = (0, import_react21.useState)(null);
26111
+ const [editorViewport, setEditorViewport] = (0, import_react21.useState)("desktop");
26112
+ const [parentScrollSnap, setParentScrollSnap] = (0, import_react21.useState)(null);
25869
26113
  const [sitePages, setSitePages] = (0, import_react21.useState)([]);
26114
+ const collectionItemsRef = (0, import_react21.useRef)(/* @__PURE__ */ new Map());
25870
26115
  const [sectionsByPath, setSectionsByPath] = (0, import_react21.useState)({});
25871
26116
  const sectionsPrefetchGenRef = (0, import_react21.useRef)(0);
25872
26117
  const setLinkPopoverRef = (0, import_react21.useRef)(setLinkPopover);
@@ -25874,7 +26119,18 @@ function OhhwellsBridge() {
25874
26119
  const linkPopoverOpenRef = (0, import_react21.useRef)(false);
25875
26120
  const linkPopoverGraceUntilRef = (0, import_react21.useRef)(0);
25876
26121
  setLinkPopoverRef.current = setLinkPopover;
26122
+ setFloatingPanelRef.current = setFloatingPanel;
25877
26123
  linkPopoverSessionRef.current = linkPopover;
26124
+ floatingPanelOpenRef.current = Boolean(floatingPanel);
26125
+ (0, import_react21.useEffect)(() => {
26126
+ const syncViewport = () => {
26127
+ const next = window.innerWidth <= 480 ? "mobile" : "desktop";
26128
+ setEditorViewport((prev) => prev === next ? prev : next);
26129
+ };
26130
+ syncViewport();
26131
+ window.addEventListener("resize", syncViewport);
26132
+ return () => window.removeEventListener("resize", syncViewport);
26133
+ }, []);
25878
26134
  (0, import_react21.useEffect)(() => {
25879
26135
  setSectionOrderPersistHandler((entries) => {
25880
26136
  const currentPath = window.location.pathname;
@@ -26010,10 +26266,12 @@ function OhhwellsBridge() {
26010
26266
  const onSitePages = (e) => {
26011
26267
  if (e.data?.type !== "ow:site-pages" || !Array.isArray(e.data.pages)) return;
26012
26268
  if (useFixtures) return;
26013
- const mapped = e.data.pages.map((p) => ({
26269
+ const incoming = e.data.pages;
26270
+ const mapped = incoming.map((p) => ({
26014
26271
  path: p.path,
26015
26272
  title: p.title
26016
26273
  }));
26274
+ collectionItemsRef.current = indexCollectionItems(incoming);
26017
26275
  setSitePages(mapped);
26018
26276
  runSectionsPrefetchRef.current(mapped);
26019
26277
  };
@@ -27569,6 +27827,7 @@ function OhhwellsBridge() {
27569
27827
  applyAiFooterFromContent(content);
27570
27828
  if (typeof content[STYLE_STORE_KEY] === "string") {
27571
27829
  stylesRef.current = content[STYLE_STORE_KEY];
27830
+ document.querySelectorAll('[data-ohw-editable="form"]').forEach((f) => markFormFields(f));
27572
27831
  applyStylesToDom(parseStyleStore(content[STYLE_STORE_KEY]));
27573
27832
  }
27574
27833
  applyBrandChrome(content);
@@ -27579,12 +27838,12 @@ function OhhwellsBridge() {
27579
27838
  if (key === "__ohw_sections") continue;
27580
27839
  if (key === AI_SECTIONS_KEY) continue;
27581
27840
  if (key === AI_FOOTER_KEY) continue;
27841
+ if (key === LOGO_PLACEHOLDER_KEY) continue;
27842
+ if (LOGO_IMAGE_KEYS.includes(key)) continue;
27582
27843
  if (key === BRAND_KIT_KEY) continue;
27583
27844
  if (key === STYLE_STORE_KEY) continue;
27584
27845
  if (BRAND_CHROME_KEYS.has(key)) continue;
27585
- if (key === LOGO_PLACEHOLDER_KEY) continue;
27586
27846
  if (key === CUSTOM_CODE_AFTER_HEAD_KEY || key === CUSTOM_CODE_BEFORE_HEAD_KEY) continue;
27587
- if (LOGO_IMAGE_KEYS.includes(key)) continue;
27588
27847
  if (typeof content[AI_FOOTER_KEY] === "string" && content[AI_FOOTER_KEY] && isAiFooterOwnedContentKey(key)) {
27589
27848
  continue;
27590
27849
  }
@@ -27661,7 +27920,17 @@ function OhhwellsBridge() {
27661
27920
  const apiUrl = process.env.NEXT_PUBLIC_FLOWOPS_API_URL ?? "https://flowops-backend-staging-2yfdh7pwpq-as.a.run.app";
27662
27921
  const initialPath = pathname;
27663
27922
  fetchedContentPaths.add(`${subdomain}::${initialPath}`);
27664
- fetch(`${apiUrl}/api/public/sites/${subdomain}/content?path=${encodeURIComponent(initialPath)}`, { cache: "no-store" }).then((r2) => r2.ok ? r2.json() : null).then((data) => {
27923
+ fetch(`${apiUrl}/api/public/sites/${subdomain}/content?path=${encodeURIComponent(initialPath)}`, { cache: "no-store" }).then((r2) => {
27924
+ if (!r2.ok) {
27925
+ reportOutageBeacon({
27926
+ subdomain,
27927
+ reason: "content_fetch_failed",
27928
+ detail: `HTTP ${r2.status}`
27929
+ });
27930
+ return null;
27931
+ }
27932
+ return r2.json();
27933
+ }).then((data) => {
27665
27934
  if (cancelled) return;
27666
27935
  const content = data?.content ?? {};
27667
27936
  const branding = Boolean(data?.showBranding);
@@ -27670,7 +27939,12 @@ function OhhwellsBridge() {
27670
27939
  recordOverriddenPaths(subdomain, data?.overriddenPaths);
27671
27940
  setShowBranding(branding);
27672
27941
  return applyContent(content);
27673
- }).catch(() => {
27942
+ }).catch((err) => {
27943
+ reportOutageBeacon({
27944
+ subdomain,
27945
+ reason: "content_fetch_failed",
27946
+ detail: err instanceof Error ? err.message : "fetch failed"
27947
+ });
27674
27948
  }).finally(() => {
27675
27949
  if (!cancelled) setFetchState("done");
27676
27950
  });
@@ -27800,17 +28074,18 @@ function OhhwellsBridge() {
27800
28074
  }
27801
28075
  applyAiFooterFromContent(content);
27802
28076
  if (typeof content[STYLE_STORE_KEY] === "string") {
28077
+ document.querySelectorAll('[data-ohw-editable="form"]').forEach((f) => markFormFields(f));
27803
28078
  applyStylesToDom(parseStyleStore(content[STYLE_STORE_KEY]));
27804
28079
  }
27805
28080
  for (const [key, val] of Object.entries(content)) {
27806
28081
  if (key === "__ohw_sections") continue;
27807
28082
  if (key === AI_SECTIONS_KEY) continue;
27808
28083
  if (key === AI_FOOTER_KEY) continue;
28084
+ if (key === LOGO_PLACEHOLDER_KEY) continue;
28085
+ if (LOGO_IMAGE_KEYS.includes(key)) continue;
27809
28086
  if (key === BRAND_KIT_KEY) continue;
27810
28087
  if (key === STYLE_STORE_KEY) continue;
27811
28088
  if (BRAND_CHROME_KEYS.has(key)) continue;
27812
- if (key === LOGO_PLACEHOLDER_KEY) continue;
27813
- if (LOGO_IMAGE_KEYS.includes(key)) continue;
27814
28089
  if (typeof content[AI_FOOTER_KEY] === "string" && content[AI_FOOTER_KEY] && isAiFooterOwnedContentKey(key)) {
27815
28090
  continue;
27816
28091
  }
@@ -28077,26 +28352,11 @@ function OhhwellsBridge() {
28077
28352
  const t2 = setTimeout(measure, 500);
28078
28353
  const ro = new ResizeObserver(schedule);
28079
28354
  ro.observe(document.body);
28080
- let lastWidth = window.innerWidth;
28081
- let resizeTimers = [];
28082
- const clearResizeTimers = () => {
28083
- resizeTimers.forEach(clearTimeout);
28084
- resizeTimers = [];
28085
- };
28086
- const handleResize = () => {
28087
- if (window.innerWidth === lastWidth) return;
28088
- lastWidth = window.innerWidth;
28089
- clearResizeTimers();
28090
- resizeTimers = HEIGHT_SETTLE_DELAYS.map((delay) => setTimeout(measure, delay));
28091
- };
28092
- window.addEventListener("resize", handleResize);
28093
28355
  return () => {
28094
28356
  clearTimeout(t1);
28095
28357
  clearTimeout(t2);
28096
28358
  if (raf != null) cancelAnimationFrame(raf);
28097
28359
  ro.disconnect();
28098
- clearResizeTimers();
28099
- window.removeEventListener("resize", handleResize);
28100
28360
  };
28101
28361
  }, [pathname, isEditMode, postToParent2]);
28102
28362
  (0, import_react21.useEffect)(() => {
@@ -28393,6 +28653,13 @@ function OhhwellsBridge() {
28393
28653
  if (target.closest("[data-ohw-state-toggle]")) return;
28394
28654
  if (target.closest("[data-ohw-max-badge]")) return;
28395
28655
  if (isInsideLinkEditor(target)) return;
28656
+ if (isInsideFloatingPanel(target)) return;
28657
+ if (target.closest("[data-ohw-form-toolbar]")) return;
28658
+ if (target.closest(
28659
+ '[data-ohw-field-toolbar], [data-ohw-field-type-picker], [data-radix-popper-content-wrapper], [role="menu"], [data-slot="dropdown-menu-content"]'
28660
+ )) {
28661
+ return;
28662
+ }
28396
28663
  if (selectedMediaElRef.current && !selectedMediaElRef.current.contains(target) && !target.closest("[data-ohw-media-overlay], [data-ohw-edit-chrome], [data-ohw-item-interaction]")) {
28397
28664
  clearMediaSelectionRef.current();
28398
28665
  }
@@ -28416,11 +28683,15 @@ function OhhwellsBridge() {
28416
28683
  return;
28417
28684
  }
28418
28685
  }
28419
- if (isInsideFloatingPanel(target)) return;
28420
- if (target.closest("[data-ohw-form-toolbar]")) return;
28421
- if (target.closest(
28422
- '[data-ohw-field-toolbar], [data-ohw-field-type-picker], [data-radix-popper-content-wrapper], [role="menu"], [data-slot="dropdown-menu-content"]'
28423
- )) {
28686
+ const collectionItem = matchCollectionItemClick(target, collectionItemsRef.current);
28687
+ if (collectionItem) {
28688
+ e.preventDefault();
28689
+ e.stopPropagation();
28690
+ postToParentRef.current({
28691
+ type: "ow:collection-item-selected",
28692
+ path: collectionItem.path,
28693
+ collection: collectionItem.collection
28694
+ });
28424
28695
  return;
28425
28696
  }
28426
28697
  {
@@ -28576,9 +28847,13 @@ function OhhwellsBridge() {
28576
28847
  if (isIconEditable(editable) && !getSocialItem(editable)) {
28577
28848
  e.preventDefault();
28578
28849
  e.stopPropagation();
28579
- selectIconElementRef.current(editable);
28580
28850
  const aiIcon = editable.hasAttribute("data-ai-icon");
28581
28851
  const featureIcon = aiIcon && /\.features\.\d+\.icon$/.test(editable.dataset.ohwKey ?? "");
28852
+ if (aiIcon) {
28853
+ aiSectionApiRef.current?.selectFromElement(editable);
28854
+ } else {
28855
+ selectIconElementRef.current(editable);
28856
+ }
28582
28857
  postToParentRef.current({
28583
28858
  type: "ow:icon-pick",
28584
28859
  key: editable.dataset.ohwKey ?? "",
@@ -28806,6 +29081,9 @@ function OhhwellsBridge() {
28806
29081
  setHoveredItemRect(null);
28807
29082
  hoveredNavContainerRef.current = null;
28808
29083
  setHoveredNavContainerRect(null);
29084
+ siblingHintElRef.current = null;
29085
+ setSiblingHintRect(null);
29086
+ setSiblingHintRects([]);
28809
29087
  return;
28810
29088
  }
28811
29089
  {
@@ -28926,7 +29204,6 @@ function OhhwellsBridge() {
28926
29204
  hoveredNavContainerRef.current = null;
28927
29205
  setHoveredNavContainerRect(null);
28928
29206
  hoveredItemElRef.current = editable;
28929
- setHoveredItemRect(isSelectedForHoverRef.current(editable) ? null : editable.getBoundingClientRect());
28930
29207
  }
28931
29208
  }
28932
29209
  }
@@ -29636,7 +29913,11 @@ function OhhwellsBridge() {
29636
29913
  };
29637
29914
  const handleMouseMove = (e) => {
29638
29915
  const { clientX, clientY } = e;
29639
- if (pointOwnedByFloatingPanel(clientX, clientY)) return;
29916
+ if (pointOwnedByFloatingPanel(clientX, clientY)) {
29917
+ dismissImageHover();
29918
+ clearImageHover();
29919
+ return;
29920
+ }
29640
29921
  if (isNavDrawerOpen() && isPointOutsideOpenDrawer(clientX, clientY)) {
29641
29922
  document.querySelectorAll("[data-ohw-hovered]").forEach((el) => el.removeAttribute("data-ohw-hovered"));
29642
29923
  formHoverElRef.current = null;
@@ -29665,7 +29946,12 @@ function OhhwellsBridge() {
29665
29946
  setHoveredItemRect(null);
29666
29947
  hoveredNavContainerRef.current = null;
29667
29948
  setHoveredNavContainerRect(null);
29949
+ siblingHintElRef.current = null;
29950
+ setSiblingHintRect(null);
29951
+ setSiblingHintRects([]);
29668
29952
  dismissImageHover();
29953
+ clearImageHover();
29954
+ setSectionGap(null);
29669
29955
  return;
29670
29956
  }
29671
29957
  if (probeSocialsRowAt(clientX, clientY)) return;
@@ -29677,7 +29963,11 @@ function OhhwellsBridge() {
29677
29963
  if (e.data?.type !== "ow:pointer-sync") return;
29678
29964
  const { clientX, clientY } = e.data;
29679
29965
  if (typeof clientX !== "number" || typeof clientY !== "number") return;
29680
- if (pointOwnedByFloatingPanel(clientX, clientY)) return;
29966
+ if (pointOwnedByFloatingPanel(clientX, clientY)) {
29967
+ dismissImageHover();
29968
+ clearImageHover();
29969
+ return;
29970
+ }
29681
29971
  if (isNavDrawerOpen() && isPointOutsideOpenDrawer(clientX, clientY)) return;
29682
29972
  if (probeSocialsRowAt(clientX, clientY)) return;
29683
29973
  probeSectionGapAt(clientX, clientY);
@@ -30057,6 +30347,7 @@ function OhhwellsBridge() {
30057
30347
  applyAiFooterFromContent(content);
30058
30348
  if (typeof content[STYLE_STORE_KEY] === "string") {
30059
30349
  stylesRef.current = content[STYLE_STORE_KEY];
30350
+ document.querySelectorAll('[data-ohw-editable="form"]').forEach((f) => markFormFields(f));
30060
30351
  applyStylesToDom(parseStyleStore(content[STYLE_STORE_KEY]));
30061
30352
  }
30062
30353
  applyBrandChrome(content);
@@ -30071,12 +30362,12 @@ function OhhwellsBridge() {
30071
30362
  }
30072
30363
  if (key === AI_SECTIONS_KEY) continue;
30073
30364
  if (key === AI_FOOTER_KEY) continue;
30365
+ if (key === LOGO_PLACEHOLDER_KEY) continue;
30366
+ if (LOGO_IMAGE_KEYS.includes(key) && !String(val ?? "").trim()) continue;
30074
30367
  if (key === BRAND_KIT_KEY) continue;
30075
30368
  if (key === STYLE_STORE_KEY) continue;
30076
30369
  if (BRAND_CHROME_KEYS.has(key)) continue;
30077
- if (key === LOGO_PLACEHOLDER_KEY) continue;
30078
30370
  if (key === CUSTOM_CODE_AFTER_HEAD_KEY || key === CUSTOM_CODE_BEFORE_HEAD_KEY) continue;
30079
- if (LOGO_IMAGE_KEYS.includes(key) && !String(val ?? "").trim()) continue;
30080
30371
  if (typeof content[AI_FOOTER_KEY] === "string" && content[AI_FOOTER_KEY] && isAiFooterOwnedContentKey(key)) {
30081
30372
  continue;
30082
30373
  }
@@ -30712,12 +31003,24 @@ function OhhwellsBridge() {
30712
31003
  closeLinkPopoverRef.current();
30713
31004
  return;
30714
31005
  }
31006
+ if (floatingPanelOpenRef.current) {
31007
+ setFloatingPanelRef.current(null);
31008
+ deselectRef.current();
31009
+ deactivateRef.current();
31010
+ return;
31011
+ }
30715
31012
  deselectRef.current();
30716
31013
  deactivateRef.current();
30717
31014
  clearMediaSelectionRef.current();
30718
31015
  clearIconSelectionRef.current();
30719
31016
  };
30720
31017
  window.addEventListener("message", handleDeactivate);
31018
+ const handleSelectNav = (e) => {
31019
+ if (e.data?.type !== "ow:select-nav") return;
31020
+ const navContainer = findSelectableNavContainer();
31021
+ if (navContainer) selectFrameRef.current(navContainer);
31022
+ };
31023
+ window.addEventListener("message", handleSelectNav);
30721
31024
  const handleToastAction = (e) => {
30722
31025
  if (e.data?.type !== "ow:toast-action") return;
30723
31026
  const actionId = typeof e.data.actionId === "string" ? e.data.actionId : null;
@@ -31534,10 +31837,11 @@ function OhhwellsBridge() {
31534
31837
  window.removeEventListener("message", handleDeleteSection);
31535
31838
  window.removeEventListener("message", handleDuplicateSection);
31536
31839
  window.removeEventListener("message", handleDeactivate);
31537
- document.documentElement.removeAttribute("data-ohw-panel-dragging");
31840
+ window.removeEventListener("message", handleSelectNav);
31538
31841
  window.removeEventListener("message", handleToastAction);
31539
31842
  window.removeEventListener("message", handleFormCount);
31540
31843
  window.removeEventListener("message", handleUiEscape);
31844
+ document.documentElement.removeAttribute("data-ohw-panel-dragging");
31541
31845
  autoSaveTimers.current.forEach(clearTimeout);
31542
31846
  autoSaveTimers.current.clear();
31543
31847
  if (imageUnhoverTimerRef.current) clearTimeout(imageUnhoverTimerRef.current);
@@ -31831,7 +32135,7 @@ function OhhwellsBridge() {
31831
32135
  postToParent2({
31832
32136
  type: "ow:ready",
31833
32137
  version: "1",
31834
- bridgeVersion: "0.1.152",
32138
+ bridgeVersion: "0.1.153-next.417",
31835
32139
  path: pathname,
31836
32140
  nodes: collectEditableNodes(editContentRef.current),
31837
32141
  sections
@@ -33057,6 +33361,7 @@ function EmptySection({ title, homeHref = "/", eyebrowKey, titleKey, subtitleKey
33057
33361
  foldAlignIntoTrees,
33058
33362
  getEditModeInitialState,
33059
33363
  hasMountedContentNodes,
33364
+ indexCollectionItems,
33060
33365
  insertFooterHours,
33061
33366
  isEditSessionActive,
33062
33367
  isPlainLeftClick,
@@ -33064,6 +33369,7 @@ function EmptySection({ title, homeHref = "/", eyebrowKey, titleKey, subtitleKey
33064
33369
  isRenderableTree,
33065
33370
  isStickySearchParam,
33066
33371
  isValidUrl,
33372
+ matchCollectionItemClick,
33067
33373
  nextViableSlide,
33068
33374
  parseAiFooterState,
33069
33375
  parseAiSectionsState,