@ohhwells/bridge 0.1.152 → 0.1.153-next.415

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