@ohhwells/bridge 0.1.107 → 0.1.108-next.286

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
@@ -279,16 +279,17 @@ var LEGACY_BRAND_VAR_NAMES = [
279
279
  "text-muted",
280
280
  "surface",
281
281
  "border",
282
- "on-primary",
283
- "navbar-background"
282
+ "on-primary"
284
283
  ].map((role) => `--brand-${role}`);
285
284
  var FONT_VARS = {
286
285
  heading: ["--font-heading", "--font-display", "--brand-font-heading"],
287
286
  body: ["--font-body", "--brand-font-body"]
288
287
  };
289
288
  var BRAND_FONT_LINK_ID = "ohw-brand-fonts";
290
- function brandColorVars(kit) {
291
- const { dark, primary, accent, light } = kit.palette;
289
+ var CUSTOM_FONT_STYLE_ID = "ohw-brand-custom-fonts";
290
+ var CUSTOM_FONT_FORMATS = /* @__PURE__ */ new Set(["woff2", "woff", "truetype", "opentype"]);
291
+ function brandColorVars(palette) {
292
+ const { dark, primary, accent, light } = palette;
292
293
  const mix = (a, aPct, b) => `color-mix(in srgb, ${a} ${aPct}%, ${b})`;
293
294
  const surface = mix(light, 95, dark);
294
295
  const border = mix(light, 85, dark);
@@ -311,22 +312,42 @@ function brandColorVars(kit) {
311
312
  "--brand-border": border,
312
313
  // Buttons/bands painted in the primary colour assume it's dark/saturated enough to need
313
314
  // light text on top — the same assumption LOGO_IMAGE's light-on-dark navbar mark makes.
314
- "--brand-on-primary": light,
315
- "--brand-navbar-background": dark
315
+ "--brand-on-primary": light
316
316
  };
317
317
  }
318
+ function parseCustomFontWeight(raw) {
319
+ if (typeof raw !== "object" || raw === null) return null;
320
+ const w = raw;
321
+ if (typeof w.weight !== "number" || !Number.isFinite(w.weight)) return null;
322
+ if (typeof w.url !== "string" || !w.url) return null;
323
+ if (typeof w.format !== "string" || !CUSTOM_FONT_FORMATS.has(w.format)) return null;
324
+ const weightEnd = typeof w.weightEnd === "number" && Number.isFinite(w.weightEnd) && w.weightEnd > w.weight ? w.weightEnd : void 0;
325
+ return { weight: w.weight, ...weightEnd !== void 0 ? { weightEnd } : {}, url: w.url, format: w.format };
326
+ }
327
+ function parseCustomFont(raw) {
328
+ if (typeof raw !== "object" || raw === null) return null;
329
+ const f = raw;
330
+ if (typeof f.family !== "string" || !f.family) return null;
331
+ if (typeof f.label !== "string" || !f.label) return null;
332
+ if (!Array.isArray(f.weights)) return null;
333
+ const weights = f.weights.map(parseCustomFontWeight).filter((w) => w !== null);
334
+ if (weights.length === 0) return null;
335
+ return { family: f.family, label: f.label, weights };
336
+ }
318
337
  function parseBrandKit(raw) {
319
338
  if (!raw) return null;
320
339
  try {
321
340
  const parsed = JSON.parse(raw);
322
341
  const p = parsed?.palette;
323
342
  const f = parsed?.fonts;
324
- if (!p || !f || typeof p.dark !== "string" || typeof p.primary !== "string" || typeof p.accent !== "string" || typeof p.light !== "string" || typeof f.heading !== "string" || typeof f.body !== "string") {
325
- return null;
326
- }
343
+ const palette = p && typeof p.dark === "string" && typeof p.primary === "string" && typeof p.accent === "string" && typeof p.light === "string" ? { dark: p.dark, primary: p.primary, accent: p.accent, light: p.light } : void 0;
344
+ const fonts = f && typeof f.heading === "string" && typeof f.body === "string" ? { heading: f.heading, body: f.body } : void 0;
345
+ const customFonts = Array.isArray(parsed?.customFonts) ? parsed.customFonts.map(parseCustomFont).filter((c) => c !== null) : void 0;
346
+ if (!palette && !fonts && !customFonts) return null;
327
347
  return {
328
- palette: { dark: p.dark, primary: p.primary, accent: p.accent, light: p.light },
329
- fonts: { heading: f.heading, body: f.body }
348
+ ...palette ? { palette } : {},
349
+ ...fonts ? { fonts } : {},
350
+ ...customFonts ? { customFonts } : {}
330
351
  };
331
352
  } catch {
332
353
  return null;
@@ -336,11 +357,16 @@ function familyOf(stack) {
336
357
  const first = stack.split(",")[0]?.trim() ?? "";
337
358
  return first.replace(/^['"]|['"]$/g, "");
338
359
  }
360
+ function quoteFamily(family) {
361
+ return `'${family.replace(/\\/g, "\\\\").replace(/'/g, "\\'")}'`;
362
+ }
363
+ var WEIGHT_SCALE = [100, 200, 300, 400, 500, 600, 700, 800, 900];
339
364
  function loadBrandFonts(families) {
340
365
  const unique = [...new Set(families.map((f) => f.trim()).filter(Boolean))];
341
366
  if (unique.length === 0) return;
342
- const spec = unique.map((f) => `${f.replace(/ /g, "+")}:400,500,600,700`).join("|");
343
- const href = `https://fonts.googleapis.com/css?family=${spec}&display=swap`;
367
+ const weights = WEIGHT_SCALE.join(";");
368
+ const spec = unique.map((f) => `family=${f.replace(/ /g, "+")}:wght@${weights}`).join("&");
369
+ const href = `https://fonts.googleapis.com/css2?${spec}&display=swap`;
344
370
  let link = document.getElementById(BRAND_FONT_LINK_ID);
345
371
  if (!link) {
346
372
  link = document.createElement("link");
@@ -350,18 +376,54 @@ function loadBrandFonts(families) {
350
376
  }
351
377
  if (link.href !== href) link.href = href;
352
378
  }
379
+ function escapeCssString(value) {
380
+ return value.replace(/\\/g, "\\\\").replace(/'/g, "\\'");
381
+ }
382
+ function loadCustomFonts(fonts) {
383
+ const list = fonts ?? [];
384
+ let style = document.getElementById(CUSTOM_FONT_STYLE_ID);
385
+ if (list.length === 0) {
386
+ style?.remove();
387
+ return;
388
+ }
389
+ if (!style) {
390
+ style = document.createElement("style");
391
+ style.id = CUSTOM_FONT_STYLE_ID;
392
+ document.head.appendChild(style);
393
+ }
394
+ style.textContent = list.flatMap(
395
+ (font) => font.weights.map((w) => {
396
+ const weightDescriptor = w.weightEnd ? `${w.weight} ${w.weightEnd}` : `${w.weight}`;
397
+ return `@font-face { font-family: '${escapeCssString(font.family)}'; src: url('${escapeCssString(w.url)}') format('${w.format}'); font-weight: ${weightDescriptor}; font-display: swap; }`;
398
+ })
399
+ ).join("\n");
400
+ }
353
401
  function applyBrandToDom(kit) {
354
402
  const root = document.documentElement;
355
403
  if (!kit) {
356
404
  for (const name of [...BRAND_VAR_NAMES, ...LEGACY_BRAND_VAR_NAMES]) root.style.removeProperty(name);
357
405
  for (const name of [...FONT_VARS.heading, ...FONT_VARS.body]) root.style.removeProperty(name);
358
406
  document.getElementById(BRAND_FONT_LINK_ID)?.remove();
407
+ document.getElementById(CUSTOM_FONT_STYLE_ID)?.remove();
359
408
  return;
360
409
  }
361
- for (const [name, value] of Object.entries(brandColorVars(kit))) root.style.setProperty(name, value);
362
- for (const name of FONT_VARS.heading) root.style.setProperty(name, kit.fonts.heading);
363
- for (const name of FONT_VARS.body) root.style.setProperty(name, kit.fonts.body);
364
- loadBrandFonts([familyOf(kit.fonts.heading), familyOf(kit.fonts.body)]);
410
+ if (kit.palette) {
411
+ for (const [name, value] of Object.entries(brandColorVars(kit.palette))) root.style.setProperty(name, value);
412
+ }
413
+ if (kit.customFonts) {
414
+ loadCustomFonts(kit.customFonts);
415
+ }
416
+ if (kit.fonts) {
417
+ const heading = quoteFamily(kit.fonts.heading);
418
+ const body = quoteFamily(kit.fonts.body);
419
+ for (const name of FONT_VARS.heading) root.style.setProperty(name, heading);
420
+ for (const name of FONT_VARS.body) root.style.setProperty(name, body);
421
+ const customFamilies = new Set((kit.customFonts ?? []).map((f) => f.family));
422
+ const googleFamilies = [familyOf(kit.fonts.heading), familyOf(kit.fonts.body)].filter(
423
+ (f) => !customFamilies.has(f)
424
+ );
425
+ loadBrandFonts(googleFamilies);
426
+ }
365
427
  }
366
428
 
367
429
  // src/lib/section-styles.ts
@@ -420,7 +482,9 @@ function styleSheetCss() {
420
482
  }
421
483
  rules.push(
422
484
  `[data-ohw-style-corners="sharp"] :is(.card, [data-ohw-card], img, picture, figure, [data-ohw-editable="bg-image"]) { border-radius: 0 !important; }`,
423
- `[data-ohw-style-corners="sharp"] :has(> img) { border-radius: 0 !important; }`
485
+ `[data-ohw-style-corners="sharp"] :has(> img) { border-radius: 0 !important; }`,
486
+ `[data-ohw-style-corners="rounded"] :is(.card, [data-ohw-card], img, picture, figure, [data-ohw-editable="bg-image"]) { border-radius: max(0.75rem, var(--radius, 0px)) !important; }`,
487
+ `[data-ohw-style-corners="rounded"] :has(> img) { border-radius: max(0.75rem, var(--radius, 0px)) !important; }`
424
488
  );
425
489
  for (const align of ["left", "center", "right"]) {
426
490
  rules.push(`[data-ohw-style-align="${align}"] { text-align: ${align} !important; }`);
@@ -1280,6 +1344,22 @@ function accentBandContext(brand) {
1280
1344
  function textAttrs(ctx, path) {
1281
1345
  return ctx.keyFor ? { "data-ohw-key": ctx.keyFor(path), "data-ohw-editable": "text" } : {};
1282
1346
  }
1347
+ var AI_RESPONSIVE_CSS = [
1348
+ "@media (max-width: 960px) {",
1349
+ " [data-ai-responsive] [data-ai-grid] { grid-template-columns: repeat(2, 1fr) !important; }",
1350
+ ' [data-ai-responsive] [data-ai-grid="1"] { grid-template-columns: 1fr !important; }',
1351
+ "}",
1352
+ "@media (max-width: 640px) {",
1353
+ " [data-ai-responsive] [data-ai-grid] { grid-template-columns: 1fr !important; }",
1354
+ " [data-ai-responsive] [data-ai-columns] > * { grid-column: 1 / -1 !important; }",
1355
+ // Group containers flatten to a column on phones; span placements come along for free.
1356
+ " [data-ai-responsive] [data-ai-group] { display: flex !important; flex-direction: column !important; }",
1357
+ " [data-ai-responsive] [data-ai-group] > * { grid-column: auto !important; }",
1358
+ " [data-ai-responsive] [data-ai-section-inner] { padding-left: 20px !important; padding-right: 20px !important; }",
1359
+ " [data-ai-responsive] { overflow-x: hidden; }",
1360
+ " [data-ai-responsive] img { max-width: 100%; }",
1361
+ "}"
1362
+ ].join("\n");
1283
1363
  var TRANSPARENT_PX = "data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7";
1284
1364
  function MediaBox({
1285
1365
  refValue,
@@ -1292,13 +1372,17 @@ function MediaBox({
1292
1372
  const url = refValue ? ctx.resolveMedia(refValue) : null;
1293
1373
  const isIcon = /^(lucide|simple):/.test(refValue);
1294
1374
  const aspectRatio = aspect && /^\d+:\d+$/.test(aspect) ? aspect.replace(":", " / ") : void 0;
1295
- const editAttrs = ctx.keyFor && editPath && !isIcon ? { "data-ohw-key": ctx.keyFor(editPath), "data-ohw-editable": "image" } : {};
1375
+ const editAttrs = ctx.keyFor && editPath ? {
1376
+ "data-ohw-key": ctx.keyFor(editPath),
1377
+ "data-ohw-editable": isIcon ? "icon" : "image"
1378
+ } : {};
1296
1379
  if (isIcon) {
1297
1380
  const Icon = refValue.startsWith("lucide:") ? lucideByName(refValue.slice(7)) : null;
1298
1381
  return /* @__PURE__ */ jsx(
1299
1382
  "span",
1300
1383
  {
1301
1384
  "data-ai-icon": refValue,
1385
+ ...editAttrs,
1302
1386
  style: {
1303
1387
  display: "inline-flex",
1304
1388
  width: 48,
@@ -2191,7 +2275,7 @@ function CollectionBlock({ node, ctx, path }) {
2191
2275
  return /* @__PURE__ */ jsx(
2192
2276
  "div",
2193
2277
  {
2194
- "data-ai-grid": "",
2278
+ "data-ai-grid": String(itemsPerRow),
2195
2279
  style: {
2196
2280
  display: "grid",
2197
2281
  gridTemplateColumns: `repeat(${itemsPerRow}, 1fr)`,
@@ -2544,6 +2628,7 @@ function AiTreeRenderer({
2544
2628
  {
2545
2629
  "data-ai-section": tree.tag ?? "",
2546
2630
  ...bgAttrs,
2631
+ "data-ai-responsive": "",
2547
2632
  style: {
2548
2633
  position: "relative",
2549
2634
  padding: `${pad}px 0`,
@@ -2554,12 +2639,13 @@ function AiTreeRenderer({
2554
2639
  color: settings.sectionBackground === "accent" ? blockBrand.palette.dark : void 0
2555
2640
  },
2556
2641
  children: [
2557
- isOverlay && backgroundUrl && /* @__PURE__ */ jsx("div", { style: { position: "absolute", inset: 0, background: `rgba(255,255,255,${overlayAlpha})` } }),
2642
+ /* @__PURE__ */ jsx("style", { children: AI_RESPONSIVE_CSS }),
2558
2643
  /* @__PURE__ */ jsx("style", { children: AI_MOBILE_CSS }),
2644
+ isOverlay && backgroundUrl && /* @__PURE__ */ jsx("div", { style: { position: "absolute", inset: 0, background: `rgba(255,255,255,${overlayAlpha})` } }),
2559
2645
  /* @__PURE__ */ jsx(
2560
2646
  "div",
2561
2647
  {
2562
- "data-ai-container": "",
2648
+ "data-ai-section-inner": "",
2563
2649
  style: {
2564
2650
  position: "relative",
2565
2651
  maxWidth: AI_TREE_TOKENS.sectionMaxWidth,
@@ -2570,7 +2656,7 @@ function AiTreeRenderer({
2570
2656
  children: tree.rows.map((row, r2) => /* @__PURE__ */ jsx(
2571
2657
  "div",
2572
2658
  {
2573
- "data-ai-row": "",
2659
+ "data-ai-columns": "",
2574
2660
  style: {
2575
2661
  display: "grid",
2576
2662
  gridTemplateColumns: "repeat(12, 1fr)",
@@ -2620,14 +2706,52 @@ function readRootVar(name) {
2620
2706
  if (typeof document === "undefined") return "";
2621
2707
  return getComputedStyle(document.documentElement).getPropertyValue(name).trim();
2622
2708
  }
2709
+ function normalizeColorToHex(value) {
2710
+ if (!value || typeof document === "undefined") return value;
2711
+ const canvas = document.createElement("canvas");
2712
+ canvas.width = 1;
2713
+ canvas.height = 1;
2714
+ const ctx = canvas.getContext("2d");
2715
+ if (!ctx) return value;
2716
+ ctx.fillStyle = value;
2717
+ ctx.fillRect(0, 0, 1, 1);
2718
+ const [r2, g, b] = ctx.getImageData(0, 0, 1, 1).data;
2719
+ const toHex = (n) => n.toString(16).padStart(2, "0");
2720
+ return `#${toHex(r2)}${toHex(g)}${toHex(b)}`;
2721
+ }
2722
+ var GENERIC_FONT_KEYWORDS = /* @__PURE__ */ new Set([
2723
+ "serif",
2724
+ "sans-serif",
2725
+ "monospace",
2726
+ "cursive",
2727
+ "fantasy",
2728
+ "system-ui",
2729
+ "ui-serif",
2730
+ "ui-sans-serif",
2731
+ "ui-monospace",
2732
+ "ui-rounded",
2733
+ "math",
2734
+ "emoji",
2735
+ "fangsong"
2736
+ ]);
2737
+ function primaryFontFamily(stack) {
2738
+ const first = stack.split(",").map((part) => part.trim().replace(/^["']|["']$/g, "")).find((part) => part && !GENERIC_FONT_KEYWORDS.has(part.toLowerCase()));
2739
+ return first ?? "";
2740
+ }
2741
+ function humanizeFontFamily(name) {
2742
+ return name.replace(/^__/, "").replace(/_[0-9a-f]{6}$/i, "").replace(/_/g, " ").replace(/([a-z0-9])([A-Z])/g, "$1 $2").trim().replace(/\s+/g, " ").replace(/(^|\s)([a-z])/g, (_, sep, c) => sep + c.toUpperCase());
2743
+ }
2744
+ function readFontVar(name) {
2745
+ return humanizeFontFamily(primaryFontFamily(readRootVar(name)));
2746
+ }
2623
2747
  function deriveBrandOverride() {
2624
2748
  const dark = readRootVar("--ohw-brand-dark");
2625
2749
  const primary = readRootVar("--ohw-brand-primary");
2626
2750
  const light = readRootVar("--ohw-brand-light");
2627
2751
  if (!dark || !primary || !light) return null;
2628
2752
  const accent = readRootVar("--ohw-brand-accent");
2629
- const heading = readRootVar("--font-heading") || readRootVar("--font-display");
2630
- const body = readRootVar("--font-body");
2753
+ const heading = readFontVar("--font-heading") || readFontVar("--font-display");
2754
+ const body = readFontVar("--font-body");
2631
2755
  return {
2632
2756
  palette: { dark, primary, accent: accent || dark, light },
2633
2757
  fonts: {
@@ -2636,27 +2760,65 @@ function deriveBrandOverride() {
2636
2760
  }
2637
2761
  };
2638
2762
  }
2639
- function deriveTemplateBrand() {
2763
+ function deriveTemplateBrandLive() {
2640
2764
  const primary = readRootVar("--brand-primary") || readRootVar("--color-primary");
2641
2765
  const dark = readRootVar("--brand-text") || readRootVar("--color-dark");
2642
2766
  const light = readRootVar("--brand-background") || readRootVar("--color-light");
2643
2767
  if (!dark || !primary || !light) return null;
2644
2768
  const accent = readRootVar("--brand-accent") || readRootVar("--color-accent");
2645
- const heading = readRootVar("--brand-font-heading") || readRootVar("--font-heading") || readRootVar("--font-display");
2646
- const body = readRootVar("--brand-font-body") || readRootVar("--font-body");
2769
+ const heading = readFontVar("--brand-font-heading") || readFontVar("--font-heading") || readFontVar("--font-display");
2770
+ const body = readFontVar("--brand-font-body") || readFontVar("--font-body");
2647
2771
  return {
2648
- palette: { dark, primary, accent: accent || dark, light },
2772
+ palette: {
2773
+ dark: normalizeColorToHex(dark),
2774
+ primary: normalizeColorToHex(primary),
2775
+ accent: normalizeColorToHex(accent || dark),
2776
+ light: normalizeColorToHex(light)
2777
+ },
2649
2778
  fonts: {
2650
2779
  heading: heading || AI_DEFAULT_BRAND.fonts.heading,
2651
2780
  body: body || AI_DEFAULT_BRAND.fonts.body
2652
2781
  }
2653
2782
  };
2654
2783
  }
2784
+ function deriveTemplateFontsLive() {
2785
+ const heading = readFontVar("--brand-font-heading") || readFontVar("--font-heading") || readFontVar("--font-display");
2786
+ const body = readFontVar("--brand-font-body") || readFontVar("--font-body");
2787
+ if (!heading && !body) return null;
2788
+ return {
2789
+ heading: heading || AI_DEFAULT_BRAND.fonts.heading,
2790
+ body: body || AI_DEFAULT_BRAND.fonts.body
2791
+ };
2792
+ }
2793
+ var OVERRIDE_VAR_NAMES = [...BRAND_VAR_NAMES, ...LEGACY_BRAND_VAR_NAMES, ...FONT_VARS.heading, ...FONT_VARS.body];
2794
+ function withOverrideStripped(read) {
2795
+ const root = document.documentElement;
2796
+ const restore = OVERRIDE_VAR_NAMES.map((name) => [name, root.style.getPropertyValue(name)]);
2797
+ for (const name of OVERRIDE_VAR_NAMES) root.style.removeProperty(name);
2798
+ try {
2799
+ return read();
2800
+ } finally {
2801
+ for (const [name, value] of restore) if (value) root.style.setProperty(name, value);
2802
+ }
2803
+ }
2804
+ var TEMPLATE_BRAND_SNAPSHOT = typeof document === "undefined" ? null : withOverrideStripped(deriveTemplateBrandLive);
2805
+ var TEMPLATE_FONTS_SNAPSHOT = typeof document === "undefined" ? null : withOverrideStripped(deriveTemplateFontsLive);
2806
+ function deriveTemplateBrand() {
2807
+ return TEMPLATE_BRAND_SNAPSHOT;
2808
+ }
2809
+ function deriveTemplateFonts() {
2810
+ return TEMPLATE_FONTS_SNAPSHOT;
2811
+ }
2655
2812
  function deriveTemplateButtonStyle() {
2656
2813
  if (typeof document === "undefined") return null;
2657
2814
  const candidates = Array.from(
2658
2815
  document.querySelectorAll('[data-ohw-role="button"]')
2659
- ).filter((el) => !el.closest(`[${CONTAINER_ATTR}]`));
2816
+ ).filter(
2817
+ (el) => !el.closest(`[${CONTAINER_ATTR}]`) && // Whole-card links legitimately carry the button role (serene's clickable treatment
2818
+ // cards) but must never donate the "button look" — a real button holds at most its
2819
+ // one editable label, a card holds a headline, copy and prices.
2820
+ el.querySelectorAll("[data-ohw-editable]").length <= 1
2821
+ );
2660
2822
  const isFilled = (el) => {
2661
2823
  const bg = getComputedStyle(el).backgroundColor;
2662
2824
  if (!bg || bg === "transparent") return false;
@@ -7144,12 +7306,12 @@ var cva = (base, config) => (props) => {
7144
7306
  import { Toggle as TogglePrimitive } from "radix-ui";
7145
7307
  import { jsx as jsx5 } from "react/jsx-runtime";
7146
7308
  var toggleVariants = cva(
7147
- "inline-flex items-center justify-center gap-2 rounded-md text-sm font-medium whitespace-nowrap transition-[color,box-shadow] outline-none hover:bg-muted hover:text-muted-foreground focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-destructive/20 data-[state=on]:bg-accent data-[state=on]:text-accent-foreground [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
7309
+ "inline-flex items-center justify-center gap-2 rounded-md text-sm font-medium whitespace-nowrap transition-[color,box-shadow] outline-none hover:bg-muted hover:text-muted-foreground focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-destructive/20 data-[state=on]:bg-bridge-accent data-[state=on]:text-accent-foreground [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
7148
7310
  {
7149
7311
  variants: {
7150
7312
  variant: {
7151
7313
  default: "bg-transparent border-0",
7152
- outline: "border border-input bg-transparent shadow-xs hover:bg-accent hover:text-accent-foreground"
7314
+ outline: "border border-input bg-transparent shadow-xs hover:bg-bridge-accent hover:text-accent-foreground"
7153
7315
  },
7154
7316
  size: {
7155
7317
  default: "px-2 py-1",
@@ -7242,10 +7404,10 @@ var DragHandle = React4.forwardRef(
7242
7404
  type,
7243
7405
  "data-slot": "drag-handle",
7244
7406
  className: cn(
7245
- "inline-flex h-7 w-4 shrink-0 items-center justify-center rounded-md transition-all duration-200 max-h-[30px]",
7407
+ "inline-flex h-7 w-4 shrink-0 items-center justify-center rounded-md transition-all duration-200 max-h-7.5",
7246
7408
  "bg-white border border-transparent text-stone-500 shadow-md cursor-grab",
7247
7409
  "enabled:hover:border enabled:hover:border-stone-200 enabled:hover:text-stone-950",
7248
- "enabled:active:border enabled:active:border-primary enabled:active:bg-primary-50 enabled:active:text-stone-950 enabled:active:shadow enabled:active:cursor-grabbing",
7410
+ "enabled:active:border enabled:active:border-bridge-primary enabled:active:bg-primary-50 enabled:active:text-stone-950 enabled:active:shadow enabled:active:cursor-grabbing",
7249
7411
  "disabled:cursor-not-allowed disabled:opacity-40 disabled:text-stone-950 disabled:pointer-events-none",
7250
7412
  className
7251
7413
  ),
@@ -7267,7 +7429,7 @@ var CustomToolbar = React5.forwardRef(({ className, onMouseDown, ...props }, ref
7267
7429
  "data-ohw-toolbar": "",
7268
7430
  className: cn(
7269
7431
  // Figma: bg background, radius 8, gap-1, p-0.5, shadow-md — no border
7270
- "inline-flex h-8 items-center gap-1 rounded-[var(--radius,0.5rem)] bg-background p-0.5 font-sans whitespace-nowrap shadow-md",
7432
+ "inline-flex h-8 items-center gap-1 rounded-(--radius,0.5rem) bg-background p-0.5 font-sans whitespace-nowrap shadow-md",
7271
7433
  className
7272
7434
  ),
7273
7435
  onMouseDown: (e) => {
@@ -7296,7 +7458,7 @@ var CustomToolbarButton = React5.forwardRef(
7296
7458
  type,
7297
7459
  className: cn(
7298
7460
  "inline-flex size-7 shrink-0 items-center justify-center rounded-[calc(var(--radius,0.5rem)-2px)] text-foreground transition-colors",
7299
- active ? "bg-primary text-primary-foreground" : "bg-transparent hover:bg-muted disabled:cursor-not-allowed disabled:text-muted-foreground disabled:opacity-60",
7461
+ active ? "bg-bridge-primary text-primary-foreground" : "bg-transparent hover:bg-muted disabled:cursor-not-allowed disabled:text-muted-foreground disabled:opacity-60",
7300
7462
  className
7301
7463
  ),
7302
7464
  ...props
@@ -8498,13 +8660,13 @@ function FormFieldToolbar({
8498
8660
  ]
8499
8661
  }
8500
8662
  ) }),
8501
- /* @__PURE__ */ jsx13(DropdownMenuContent, { align: "start", sideOffset: 8, className: "min-w-[190px] p-1", children: FIELD_TYPES.map((entry) => {
8663
+ /* @__PURE__ */ jsx13(DropdownMenuContent, { align: "start", sideOffset: 8, className: "min-w-47.5 p-1", children: FIELD_TYPES.map((entry) => {
8502
8664
  const Icon = TYPE_ICONS[entry.type];
8503
8665
  return /* @__PURE__ */ jsxs6(
8504
8666
  DropdownMenuItem,
8505
8667
  {
8506
8668
  onSelect: () => onTypeChange(entry.type),
8507
- className: "rounded-md py-2 text-[13px] " + (entry.type === type ? "bg-primary/10" : ""),
8669
+ className: "rounded-md py-2 text-[13px] " + (entry.type === type ? "bg-bridge-primary/10" : ""),
8508
8670
  children: [
8509
8671
  /* @__PURE__ */ jsx13(Icon, { size: 14, strokeWidth: 1.75, className: "shrink-0", "aria-hidden": true }),
8510
8672
  entry.label
@@ -8521,7 +8683,7 @@ function FormFieldToolbar({
8521
8683
  type: "button",
8522
8684
  "aria-pressed": required,
8523
8685
  onClick: onRequiredToggle,
8524
- className: "flex h-7 items-center gap-1.5 whitespace-nowrap rounded-md px-2 text-[13px] font-medium transition-colors " + (required ? "bg-primary/10 text-primary" : "text-foreground hover:bg-muted/70"),
8686
+ className: "flex h-7 items-center gap-1.5 whitespace-nowrap rounded-md px-2 text-[13px] font-medium transition-colors " + (required ? "bg-bridge-primary/10 text-bridge-primary" : "text-foreground hover:bg-muted/70"),
8525
8687
  "data-ohw-field-required-toggle": "",
8526
8688
  children: [
8527
8689
  /* @__PURE__ */ jsx13(Asterisk, { size: 14, strokeWidth: 2, "aria-hidden": true }),
@@ -8541,7 +8703,7 @@ function FormFieldToolbar({
8541
8703
  children: /* @__PURE__ */ jsx13(MoreHorizontal2, { size: 15, "aria-hidden": true })
8542
8704
  }
8543
8705
  ) }),
8544
- /* @__PURE__ */ jsxs6(DropdownMenuContent, { align: "start", sideOffset: 8, className: "min-w-[170px] p-1", children: [
8706
+ /* @__PURE__ */ jsxs6(DropdownMenuContent, { align: "start", sideOffset: 8, className: "min-w-42.5 p-1", children: [
8545
8707
  /* @__PURE__ */ jsxs6(DropdownMenuItem, { onSelect: onDuplicate, className: "rounded-md py-2 text-[13px]", children: [
8546
8708
  /* @__PURE__ */ jsx13(Copy2, { size: 14, strokeWidth: 1.75, className: "shrink-0", "aria-hidden": true }),
8547
8709
  "Duplicate"
@@ -8569,7 +8731,7 @@ function FieldTypePicker({ onPick }) {
8569
8731
  {
8570
8732
  type: "button",
8571
8733
  onClick: () => onPick(entry.type),
8572
- className: "flex h-[76px] flex-col items-center justify-center gap-2 rounded-xl border border-border text-[15px] font-medium text-foreground transition-colors hover:border-primary hover:bg-primary/5",
8734
+ className: "flex h-[76px] flex-col items-center justify-center gap-2 rounded-xl border border-border text-[15px] font-medium text-foreground transition-colors hover:border-bridge-primary hover:bg-bridge-primary/5",
8573
8735
  children: [
8574
8736
  /* @__PURE__ */ jsx13(Icon, { size: 26, strokeWidth: 1.5, "aria-hidden": true }),
8575
8737
  entry.label
@@ -8595,7 +8757,7 @@ var buttonVariants = cva(
8595
8757
  {
8596
8758
  variants: {
8597
8759
  variant: {
8598
- default: "bg-primary text-primary-foreground hover:opacity-90",
8760
+ default: "bg-bridge-primary text-primary-foreground hover:opacity-90",
8599
8761
  outline: "border border-border bg-background text-foreground shadow-sm hover:bg-muted/80",
8600
8762
  ghost: "min-w-0 px-3 py-2 text-foreground hover:bg-muted/50"
8601
8763
  },
@@ -8690,6 +8852,7 @@ function MediaOverlay({
8690
8852
  (prev) => prev && prev.fullWidth === width && prev.height === height ? prev : { fullWidth: width, height }
8691
8853
  );
8692
8854
  }, [isVideo]);
8855
+ const replaceLabel = isVideo ? "Replace video" : hover.elementType === "bg-image" ? "Replace background" : "Replace image";
8693
8856
  const replaceMode = buttonSize ? replaceButtonMode({ width: rect.width, height: rect.height }, buttonSize) : "none";
8694
8857
  const box = {
8695
8858
  position: "fixed",
@@ -8795,8 +8958,8 @@ function MediaOverlay({
8795
8958
  pointerEvents: hover.hasTextOverlap ? "none" : "auto",
8796
8959
  // Selected: a firm component ring with no wash, so the image reads as chosen rather
8797
8960
  // than hovered. Hover keeps the existing tinted preview.
8798
- boxShadow: selected ? "inset 0 0 0 2px var(--color-primary)" : "inset 0 0 0 1.5px var(--color-primary)",
8799
- background: selected ? "transparent" : "color-mix(in srgb, var(--color-primary) 20%, transparent)"
8961
+ boxShadow: selected ? "inset 0 0 0 2px var(--color-bridge-primary)" : "inset 0 0 0 1.5px var(--color-bridge-primary)",
8962
+ background: selected ? "transparent" : "color-mix(in srgb, var(--color-bridge-primary) 20%, transparent)"
8800
8963
  },
8801
8964
  onClick: () => onSelect && !selected ? onSelect(hover.key) : onReplace(hover.key),
8802
8965
  children: [
@@ -8819,17 +8982,17 @@ function MediaOverlay({
8819
8982
  },
8820
8983
  children: [
8821
8984
  isVideo ? /* @__PURE__ */ jsx15(Film, { size: 14 }) : /* @__PURE__ */ jsx15(ImageIcon, { size: 14 }),
8822
- isVideo ? "Replace video" : "Replace image"
8985
+ replaceLabel
8823
8986
  ]
8824
8987
  }
8825
8988
  ),
8826
- replaceMode === "none" ? null : /* @__PURE__ */ jsxs7(
8989
+ showChrome && replaceMode !== "none" && /* @__PURE__ */ jsxs7(
8827
8990
  Button,
8828
8991
  {
8829
8992
  "data-ohw-media-overlay": "",
8830
8993
  variant: "outline",
8831
8994
  size: "sm",
8832
- "aria-label": isVideo ? "Replace video" : "Replace image",
8995
+ "aria-label": replaceLabel,
8833
8996
  className: "gap-1.5 cursor-pointer hover:bg-background",
8834
8997
  style: {
8835
8998
  ...OVERLAY_BUTTON_STYLE,
@@ -8852,7 +9015,7 @@ function MediaOverlay({
8852
9015
  },
8853
9016
  children: [
8854
9017
  isVideo ? /* @__PURE__ */ jsx15(Film, { size: 14 }) : /* @__PURE__ */ jsx15(ImageIcon, { size: 14 }),
8855
- replaceMode === "full" ? isVideo ? "Replace video" : hover.elementType === "bg-image" ? "Replace background" : "Replace image" : null
9018
+ replaceMode === "full" ? replaceLabel : null
8856
9019
  ]
8857
9020
  }
8858
9021
  )
@@ -8890,8 +9053,8 @@ function CarouselOverlay({
8890
9053
  height: rect.height,
8891
9054
  zIndex: 2147483646,
8892
9055
  pointerEvents: "auto",
8893
- boxShadow: "inset 0 0 0 1.5px var(--color-primary)",
8894
- background: "color-mix(in srgb, var(--color-primary) 20%, transparent)"
9056
+ boxShadow: "inset 0 0 0 1.5px var(--color-bridge-primary)",
9057
+ background: "color-mix(in srgb, var(--color-bridge-primary) 20%, transparent)"
8895
9058
  },
8896
9059
  onClick: () => onEdit(hover.key),
8897
9060
  children: /* @__PURE__ */ jsxs8(
@@ -9729,7 +9892,7 @@ function SectionTreeItem({
9729
9892
  /* @__PURE__ */ jsx20(
9730
9893
  "div",
9731
9894
  {
9732
- className: "mr-[-1px] h-9 w-2 shrink-0 rounded-bl-sm border-b border-l border-border mb-4",
9895
+ className: "-mr-px h-9 w-2 shrink-0 rounded-bl-sm border-b border-l border-border mb-4",
9733
9896
  "aria-hidden": true
9734
9897
  }
9735
9898
  ),
@@ -9748,7 +9911,7 @@ function SectionTreeItem({
9748
9911
  className: cn(
9749
9912
  "flex h-9 min-w-0 flex-1 items-center gap-2 rounded-md border border-border bg-background p-3",
9750
9913
  interactive && "cursor-pointer hover:bg-muted/30",
9751
- interactive && selected && "border-primary"
9914
+ interactive && selected && "border-bridge-primary"
9752
9915
  ),
9753
9916
  children: [
9754
9917
  /* @__PURE__ */ jsx20(
@@ -9878,7 +10041,7 @@ function UrlOrPageInput({
9878
10041
  };
9879
10042
  const fieldClassName = cn(
9880
10043
  "data-ohw-link-field flex h-[36px] w-full items-center overflow-hidden rounded-md border bg-background pl-3 pr-3 py-2 outline-none transition-[border-color,box-shadow]",
9881
- urlError ? "border-destructive shadow-[0_0_0_1px_var(--ohw-destructive)]" : isFocused ? "border-primary shadow-[0_0_0_1px_var(--ohw-primary)]" : "border-input"
10044
+ urlError ? "border-destructive shadow-[0_0_0_1px_var(--ohw-destructive)]" : isFocused ? "border-bridge-primary shadow-[0_0_0_1px_var(--ohw-primary)]" : "border-input"
9882
10045
  );
9883
10046
  return /* @__PURE__ */ jsxs13("div", { className: "flex w-full flex-col gap-2 p-0", children: [
9884
10047
  /* @__PURE__ */ jsx23(Label, { htmlFor: inputId, className: cn(urlError && "text-destructive"), children: "Destination" }),
@@ -13727,6 +13890,7 @@ function readLogoSizeState(content, placement) {
13727
13890
  function getLogoElement(el) {
13728
13891
  const marked = el.closest('[data-ohw-role="logo"], [data-ohw-logo]');
13729
13892
  if (marked) return marked;
13893
+ if (el.closest('[data-ohw-editable="icon"]')) return null;
13730
13894
  const root = el.closest("nav, [data-ohw-nav-root], footer");
13731
13895
  if (!root) return null;
13732
13896
  const anchor = el.closest("a");
@@ -14020,7 +14184,7 @@ function DisplaySwitch({
14020
14184
  onClick: () => onChange(!checked),
14021
14185
  className: cn(
14022
14186
  "relative h-5 w-9 shrink-0 rounded-full transition-colors",
14023
- checked ? "bg-primary" : "bg-primary-50",
14187
+ checked ? "bg-bridge-primary" : "bg-primary-50",
14024
14188
  disabled ? "cursor-default opacity-50" : "cursor-pointer"
14025
14189
  ),
14026
14190
  children: /* @__PURE__ */ jsx28(
@@ -14028,7 +14192,7 @@ function DisplaySwitch({
14028
14192
  {
14029
14193
  className: cn(
14030
14194
  "absolute top-0.5 h-4 w-4 rounded-full bg-white transition-all",
14031
- checked ? "left-[1.125rem]" : "left-0.5"
14195
+ checked ? "left-4.5" : "left-0.5"
14032
14196
  )
14033
14197
  }
14034
14198
  )
@@ -15207,7 +15371,9 @@ function collectEditableNodes(extraContent, root = document) {
15207
15371
  return {
15208
15372
  key: el.dataset.ohwKey ?? "",
15209
15373
  type: el.dataset.ohwEditable ?? "text",
15210
- text: el.dataset.ohwEditable === "plain" ? el.innerText ?? "" : el.innerHTML
15374
+ // innerText does not exist on SVG elements (a circular-badge <textPath> is a legitimate
15375
+ // plain editable) — textContent is the value there.
15376
+ text: el.dataset.ohwEditable === "plain" ? el.innerText ?? el.textContent ?? "" : el.innerHTML
15211
15377
  };
15212
15378
  });
15213
15379
  const hrefEls = Array.from(root.querySelectorAll("[data-ohw-href-key]"));
@@ -15644,7 +15810,7 @@ var badgeVariants = cva(
15644
15810
  {
15645
15811
  variants: {
15646
15812
  variant: {
15647
- default: "border-transparent bg-primary text-primary-foreground",
15813
+ default: "border-transparent bg-bridge-primary text-primary-foreground",
15648
15814
  secondary: "border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80",
15649
15815
  destructive: "border-transparent bg-destructive text-destructive-foreground hover:bg-destructive/80",
15650
15816
  outline: "text-foreground"
@@ -15826,21 +15992,10 @@ function parseSchedulingInsertAfter(insertAfter) {
15826
15992
  insertBefore: insertAfter.slice(idx + INSERT_BEFORE_MARKER.length)
15827
15993
  };
15828
15994
  }
15829
- function resolveSchedulingInsert(insertAfter, explicitInsertBefore) {
15830
- const parsed = parseSchedulingInsertAfter(insertAfter);
15831
- const insertBefore = explicitInsertBefore ?? parsed.insertBefore;
15832
- const effectiveInsertAfter = insertBefore && parsed.insertBefore === null ? `${insertAfter}${INSERT_BEFORE_MARKER}${insertBefore}` : insertAfter;
15833
- return { effectiveInsertAfter, insertBefore };
15834
- }
15835
- function getSchedulingMountPoint(insertAfter) {
15836
- const { anchor } = parseSchedulingInsertAfter(insertAfter);
15837
- let anchorEl = document.querySelector(`[data-ohw-section="${anchor}"]`);
15838
- if (!anchorEl && anchor === "scheduling") {
15839
- const widgets = Array.from(document.querySelectorAll('[data-ohw-section^="scheduling-"]'));
15840
- anchorEl = widgets.at(-1) ?? null;
15841
- }
15842
- if (!anchorEl) return null;
15843
- return anchorEl.closest('[data-ohw-section-container="scheduling"]') ?? anchorEl;
15995
+ function resolveEntryAnchor(entry) {
15996
+ if (entry.anchorId !== void 0) return { anchorId: entry.anchorId, beforeId: entry.beforeId ?? null };
15997
+ const parsed = parseSchedulingInsertAfter(entry.insertAfter);
15998
+ return { anchorId: parsed.anchor, beforeId: parsed.insertBefore };
15844
15999
  }
15845
16000
  function schedulingMountDepth(insertAfter) {
15846
16001
  if (!insertAfter.includes(INSERT_BEFORE_MARKER)) return 0;
@@ -15857,8 +16012,7 @@ function getPageSchedulingEntries(raw) {
15857
16012
  }
15858
16013
  }
15859
16014
  function isSchedulingWidgetMissing(entry) {
15860
- const { effectiveInsertAfter } = resolveSchedulingInsert(entry.insertAfter);
15861
- return !document.querySelector(`[data-ohw-section="${schedulingSectionId(effectiveInsertAfter)}"]`);
16015
+ return !document.querySelector(`[data-ohw-section="${schedulingSectionId(entry.insertAfter)}"]`);
15862
16016
  }
15863
16017
  function hasMissingSchedulingWidgets(entries) {
15864
16018
  return entries.some(isSchedulingWidgetMissing);
@@ -15896,17 +16050,18 @@ function initSectionsFromContent(content, removeExisting = false, currentPath =
15896
16050
  } catch {
15897
16051
  }
15898
16052
  }
15899
- function mountSchedulingWidget(insertAfter, notifyOnConnect = false, scheduleId, explicitInsertBefore) {
15900
- const { effectiveInsertAfter, insertBefore } = resolveSchedulingInsert(insertAfter, explicitInsertBefore);
15901
- const sectionId = schedulingSectionId(effectiveInsertAfter);
16053
+ function mountSchedulingWidget(anchorId, notifyOnConnect = false, scheduleId, beforeId, existingWidgetId) {
16054
+ const widgetId = existingWidgetId ?? (beforeId ? `${anchorId}${INSERT_BEFORE_MARKER}${beforeId}` : anchorId);
16055
+ const sectionId = schedulingSectionId(widgetId);
15902
16056
  if (document.querySelector(`[data-ohw-section="${sectionId}"]`)) return false;
15903
- const mountPoint = getSchedulingMountPoint(effectiveInsertAfter);
15904
- if (!mountPoint) return false;
16057
+ const anchorEl = document.querySelector(`[data-ohw-section="${anchorId}"]`);
16058
+ if (!anchorEl) return false;
16059
+ const mountPoint = anchorEl.closest('[data-ohw-section-container="scheduling"]') ?? anchorEl;
15905
16060
  const container = document.createElement("div");
15906
16061
  container.dataset.ohwSectionContainer = "scheduling";
15907
16062
  container.dataset.ohwInstance = sectionId;
15908
- if (insertBefore) {
15909
- const beforeAnchor = document.querySelector(`[data-ohw-section="${insertBefore}"]`);
16063
+ if (beforeId) {
16064
+ const beforeAnchor = document.querySelector(`[data-ohw-section="${beforeId}"]`);
15910
16065
  const beforePoint = beforeAnchor?.closest('[data-ohw-section-container="scheduling"]') ?? beforeAnchor;
15911
16066
  if (!beforePoint) return false;
15912
16067
  beforePoint.insertAdjacentElement("beforebegin", container);
@@ -15917,20 +16072,26 @@ function mountSchedulingWidget(insertAfter, notifyOnConnect = false, scheduleId,
15917
16072
  }
15918
16073
  tail.insertAdjacentElement("afterend", container);
15919
16074
  }
15920
- const root = createRoot2(container);
15921
- schedulingRoots.set(container, root);
15922
- flushSync2(() => {
15923
- root.render(
15924
- /* @__PURE__ */ jsx33(
15925
- SchedulingWidget,
15926
- {
15927
- notifyOnConnect,
15928
- initialScheduleId: scheduleId,
15929
- insertAfter: effectiveInsertAfter
15930
- }
15931
- )
15932
- );
15933
- });
16075
+ try {
16076
+ const root = createRoot2(container);
16077
+ schedulingRoots.set(container, root);
16078
+ flushSync2(() => {
16079
+ root.render(
16080
+ /* @__PURE__ */ jsx33(
16081
+ SchedulingWidget,
16082
+ {
16083
+ notifyOnConnect,
16084
+ initialScheduleId: scheduleId,
16085
+ insertAfter: widgetId
16086
+ }
16087
+ )
16088
+ );
16089
+ });
16090
+ } catch (err) {
16091
+ console.error("[ow:scheduling] render threw", err);
16092
+ container.remove();
16093
+ return false;
16094
+ }
15934
16095
  const tracker = getSectionsTracker();
15935
16096
  let sections = [];
15936
16097
  try {
@@ -15938,10 +16099,12 @@ function mountSchedulingWidget(insertAfter, notifyOnConnect = false, scheduleId,
15938
16099
  } catch {
15939
16100
  }
15940
16101
  const inEditor = typeof window !== "undefined" && window.self !== window.top;
15941
- if (inEditor && !sections.find((s) => s.type === "scheduling" && s.insertAfter === effectiveInsertAfter)) {
16102
+ if (inEditor && !sections.find((s) => s.type === "scheduling" && s.insertAfter === widgetId)) {
15942
16103
  sections.push({
15943
16104
  type: "scheduling",
15944
- insertAfter: effectiveInsertAfter,
16105
+ insertAfter: widgetId,
16106
+ anchorId,
16107
+ beforeId: beforeId ?? null,
15945
16108
  pagePath: window.location.pathname,
15946
16109
  ...scheduleId ? { scheduleId } : {}
15947
16110
  });
@@ -15955,7 +16118,8 @@ function mountSchedulingEntries(entries, notifyOnConnect = false) {
15955
16118
  for (let i = pending.length - 1; i >= 0; i--) {
15956
16119
  const entry = pending[i];
15957
16120
  const shouldNotify = typeof notifyOnConnect === "function" ? notifyOnConnect(entry) : notifyOnConnect;
15958
- if (mountSchedulingWidget(entry.insertAfter, shouldNotify, entry.scheduleId ?? null)) {
16121
+ const { anchorId, beforeId } = resolveEntryAnchor(entry);
16122
+ if (mountSchedulingWidget(anchorId, shouldNotify, entry.scheduleId ?? null, beforeId, entry.insertAfter)) {
15959
16123
  pending.splice(i, 1);
15960
16124
  }
15961
16125
  }
@@ -16119,6 +16283,11 @@ function applyLinkByKey(key, val) {
16119
16283
  hrefAnchors.forEach((el) => applyLinkHref(el, val));
16120
16284
  }
16121
16285
  }
16286
+ function isInsideLinkEditor(target) {
16287
+ return Boolean(
16288
+ target.closest("[data-ohw-link-popover-root]") || target.closest("[data-ohw-link-modal-root]") || target.closest("[data-ohw-link-page-dropdown]") || target.closest("[data-ohw-section-picker]") || target.closest("[data-ohw-navbar-container-chrome]") || target.closest("[data-ohw-navbar-add-button]") || target.closest("[data-ohw-footer-container-chrome]") || target.closest("[data-ohw-footer-add-button]") || target.closest("[data-ohw-more-menu]") || target.closest('[data-slot="dropdown-menu-content"]') || target.closest('[data-slot="popover-content"]') || target.closest('[data-slot="dialog-content"]') || target.closest('[data-slot="dialog-overlay"]')
16289
+ );
16290
+ }
16122
16291
  function isInsideFloatingPanel(target) {
16123
16292
  return Boolean(target.closest("[data-ohw-floating-panel]"));
16124
16293
  }
@@ -16126,11 +16295,6 @@ function isPointOverFloatingPanel(clientX, clientY) {
16126
16295
  const el = document.elementFromPoint(clientX, clientY);
16127
16296
  return Boolean(el instanceof Element && el.closest("[data-ohw-floating-panel]"));
16128
16297
  }
16129
- function isInsideLinkEditor(target) {
16130
- return Boolean(
16131
- target.closest("[data-ohw-link-popover-root]") || target.closest("[data-ohw-link-modal-root]") || target.closest("[data-ohw-link-page-dropdown]") || target.closest("[data-ohw-section-picker]") || target.closest("[data-ohw-navbar-container-chrome]") || target.closest("[data-ohw-navbar-add-button]") || target.closest("[data-ohw-footer-container-chrome]") || target.closest("[data-ohw-footer-add-button]") || target.closest("[data-ohw-more-menu]") || target.closest('[data-slot="dropdown-menu-content"]') || target.closest('[data-slot="popover-content"]') || target.closest('[data-slot="dialog-content"]') || target.closest('[data-slot="dialog-overlay"]')
16132
- );
16133
- }
16134
16298
  function getHrefKeyFromElement(el) {
16135
16299
  if (!el) return null;
16136
16300
  const anchor = el.closest("[data-ohw-href-key]");
@@ -16395,7 +16559,7 @@ function getNavigationSelectionParent(el) {
16395
16559
  if (!isFooterLinksContainer(el) && (el.hasAttribute("data-ohw-footer-col") || el.hasAttribute("data-ohw-footer-column") || isInferredFooterGroup2(el))) {
16396
16560
  return getFooterLinksContainer();
16397
16561
  }
16398
- if (el.hasAttribute("data-ohw-nav-container") || el.hasAttribute("data-ohw-nav-drawer") || isFooterLinksContainer(el)) {
16562
+ 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)) {
16399
16563
  return getNavigationRoot(el);
16400
16564
  }
16401
16565
  return null;
@@ -16611,7 +16775,6 @@ var ICONS = {
16611
16775
  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"/>',
16612
16776
  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"/>'
16613
16777
  };
16614
- var HEIGHT_SETTLE_DELAYS = [150, 400, 800];
16615
16778
  var SELECTION_CHROME_GAP2 = 4;
16616
16779
  var TOOLBAR_STROKE_GAP2 = 4;
16617
16780
  var TOOLBAR_OFFSET_FROM_ELEMENT = SELECTION_CHROME_GAP2 + TOOLBAR_STROKE_GAP2;
@@ -16708,6 +16871,10 @@ function getIframeVisibleClip(parentScroll) {
16708
16871
  );
16709
16872
  return { top: clipTop, bottom: Math.max(clipTop, clipBottom) };
16710
16873
  }
16874
+ function isRectOutsideClip(rect, clip) {
16875
+ if (!clip) return false;
16876
+ return rect.bottom < clip.top || rect.top > clip.bottom;
16877
+ }
16711
16878
  function applyVisibleViewport(el, parentScroll) {
16712
16879
  const clip = getIframeVisibleClip(parentScroll);
16713
16880
  const top = clip?.top ?? 0;
@@ -16991,6 +17158,7 @@ function StateToggle({
16991
17158
  );
16992
17159
  }
16993
17160
  var contentCache = /* @__PURE__ */ new Map();
17161
+ var fetchedContentPaths = /* @__PURE__ */ new Set();
16994
17162
  var brandingCache = /* @__PURE__ */ new Map();
16995
17163
  var OHW_LOADER_STYLE = {
16996
17164
  position: "fixed",
@@ -17539,13 +17707,6 @@ function OhhwellsBridge() {
17539
17707
  const [isItemDragging, setIsItemDragging] = useState13(false);
17540
17708
  const [isFooterFrameSelection, setIsFooterFrameSelection] = useState13(false);
17541
17709
  isFooterFrameSelectionRef.current = isFooterFrameSelection;
17542
- const [floatingPanel, setFloatingPanel] = useState13(null);
17543
- const floatingPanelOpenRef = useRef10(false);
17544
- floatingPanelOpenRef.current = floatingPanel !== null;
17545
- const [floatingPanelPos, setFloatingPanelPos] = useState13(null);
17546
- const [logoSizeDraft, setLogoSizeDraft] = useState13(null);
17547
- const [editorViewport, setEditorViewport] = useState13("desktop");
17548
- const [parentScrollSnap, setParentScrollSnap] = useState13(null);
17549
17710
  const [navDropdownPreviewOpen, setNavDropdownPreviewOpen] = useState13(null);
17550
17711
  const [footerHeadingVisible, setFooterHeadingVisible] = useState13(null);
17551
17712
  const footerDragRef = useRef10(null);
@@ -17563,6 +17724,13 @@ function OhhwellsBridge() {
17563
17724
  const brandKitRef = useRef10("");
17564
17725
  const stylesRef = useRef10("");
17565
17726
  const pendingDeleteUndoRef = useRef10(null);
17727
+ const [floatingPanel, setFloatingPanel] = useState13(null);
17728
+ const floatingPanelOpenRef = useRef10(false);
17729
+ const setFloatingPanelRef = useRef10(setFloatingPanel);
17730
+ const [floatingPanelPos, setFloatingPanelPos] = useState13(null);
17731
+ const [logoSizeDraft, setLogoSizeDraft] = useState13(null);
17732
+ const [editorViewport, setEditorViewport] = useState13("desktop");
17733
+ const [parentScrollSnap, setParentScrollSnap] = useState13(null);
17566
17734
  const [sitePages, setSitePages] = useState13([]);
17567
17735
  const [sectionsByPath, setSectionsByPath] = useState13({});
17568
17736
  const sectionsPrefetchGenRef = useRef10(0);
@@ -17571,7 +17739,18 @@ function OhhwellsBridge() {
17571
17739
  const linkPopoverOpenRef = useRef10(false);
17572
17740
  const linkPopoverGraceUntilRef = useRef10(0);
17573
17741
  setLinkPopoverRef.current = setLinkPopover;
17742
+ setFloatingPanelRef.current = setFloatingPanel;
17574
17743
  linkPopoverSessionRef.current = linkPopover;
17744
+ floatingPanelOpenRef.current = Boolean(floatingPanel);
17745
+ useEffect13(() => {
17746
+ const syncViewport = () => {
17747
+ const next = window.innerWidth <= 480 ? "mobile" : "desktop";
17748
+ setEditorViewport((prev) => prev === next ? prev : next);
17749
+ };
17750
+ syncViewport();
17751
+ window.addEventListener("resize", syncViewport);
17752
+ return () => window.removeEventListener("resize", syncViewport);
17753
+ }, []);
17575
17754
  const {
17576
17755
  navDragRef,
17577
17756
  navPointerDragRef,
@@ -18912,6 +19091,7 @@ function OhhwellsBridge() {
18912
19091
  }
18913
19092
  if (typeof content[STYLE_STORE_KEY] === "string") {
18914
19093
  stylesRef.current = content[STYLE_STORE_KEY];
19094
+ document.querySelectorAll('[data-ohw-editable="form"]').forEach((f) => markFormFields(f));
18915
19095
  applyStylesToDom(parseStyleStore(content[STYLE_STORE_KEY]));
18916
19096
  }
18917
19097
  applyBrandChrome(content);
@@ -18919,11 +19099,11 @@ function OhhwellsBridge() {
18919
19099
  for (const [key, val] of Object.entries(content)) {
18920
19100
  if (key === "__ohw_sections") continue;
18921
19101
  if (key === AI_SECTIONS_KEY) continue;
19102
+ if (key === LOGO_PLACEHOLDER_KEY) continue;
19103
+ if (LOGO_IMAGE_KEYS.includes(key)) continue;
18922
19104
  if (key === BRAND_KIT_KEY) continue;
18923
19105
  if (key === STYLE_STORE_KEY) continue;
18924
19106
  if (BRAND_CHROME_KEYS.has(key)) continue;
18925
- if (key === LOGO_PLACEHOLDER_KEY) continue;
18926
- if (LOGO_IMAGE_KEYS.includes(key)) continue;
18927
19107
  if (applyVideoSettingNode(key, val)) continue;
18928
19108
  if (applyCarouselNode(key, val)) continue;
18929
19109
  document.querySelectorAll(`[data-ohw-key="${key}"]`).forEach((el) => {
@@ -18994,7 +19174,9 @@ function OhhwellsBridge() {
18994
19174
  let cancelled = false;
18995
19175
  setFetchState("loading");
18996
19176
  const apiUrl = process.env.NEXT_PUBLIC_FLOWOPS_API_URL ?? "https://flowops-backend-staging-2yfdh7pwpq-as.a.run.app";
18997
- fetch(`${apiUrl}/api/public/sites/${subdomain}/content`, { cache: "no-store" }).then((r2) => r2.ok ? r2.json() : null).then((data) => {
19177
+ const initialPath = pathname;
19178
+ fetchedContentPaths.add(`${subdomain}::${initialPath}`);
19179
+ fetch(`${apiUrl}/api/public/sites/${subdomain}/content?path=${encodeURIComponent(initialPath)}`, { cache: "no-store" }).then((r2) => r2.ok ? r2.json() : null).then((data) => {
18998
19180
  if (cancelled) return;
18999
19181
  const content = data?.content ?? {};
19000
19182
  const branding = Boolean(data?.showBranding);
@@ -19103,7 +19285,9 @@ function OhhwellsBridge() {
19103
19285
  }, [isEditMode]);
19104
19286
  useEffect13(() => {
19105
19287
  if (isEditMode || fetchState !== "done") return;
19288
+ console.log("env", process.env.NEXT_PUBLIC_FLOWOPS_API_URL);
19106
19289
  const apiUrl = process.env.NEXT_PUBLIC_FLOWOPS_API_URL ?? "https://flowops-backend-staging-2yfdh7pwpq-as.a.run.app";
19290
+ console.log({ apiUrl, subdomain });
19107
19291
  bindPublishedForms(apiUrl, subdomain, contentCache.get(subdomain) ?? {});
19108
19292
  }, [isEditMode, fetchState, subdomain]);
19109
19293
  useEffect13(() => {
@@ -19128,16 +19312,17 @@ function OhhwellsBridge() {
19128
19312
  applyAiSectionsToDom(parseAiSectionsState(content[AI_SECTIONS_KEY]));
19129
19313
  }
19130
19314
  if (typeof content[STYLE_STORE_KEY] === "string") {
19315
+ document.querySelectorAll('[data-ohw-editable="form"]').forEach((f) => markFormFields(f));
19131
19316
  applyStylesToDom(parseStyleStore(content[STYLE_STORE_KEY]));
19132
19317
  }
19133
19318
  for (const [key, val] of Object.entries(content)) {
19134
19319
  if (key === "__ohw_sections") continue;
19135
19320
  if (key === AI_SECTIONS_KEY) continue;
19321
+ if (key === LOGO_PLACEHOLDER_KEY) continue;
19322
+ if (LOGO_IMAGE_KEYS.includes(key)) continue;
19136
19323
  if (key === BRAND_KIT_KEY) continue;
19137
19324
  if (key === STYLE_STORE_KEY) continue;
19138
19325
  if (BRAND_CHROME_KEYS.has(key)) continue;
19139
- if (key === LOGO_PLACEHOLDER_KEY) continue;
19140
- if (LOGO_IMAGE_KEYS.includes(key)) continue;
19141
19326
  if (applyVideoSettingNode(key, val)) continue;
19142
19327
  if (applyCarouselNode(key, val)) continue;
19143
19328
  document.querySelectorAll(`[data-ohw-key="${key}"]`).forEach((el) => {
@@ -19188,6 +19373,17 @@ function OhhwellsBridge() {
19188
19373
  debounceTimer = setTimeout(applyFromCache, 150);
19189
19374
  };
19190
19375
  applyFromCache();
19376
+ const pathCacheKey = `${subdomain}::${pathname}`;
19377
+ if (!fetchedContentPaths.has(pathCacheKey)) {
19378
+ fetchedContentPaths.add(pathCacheKey);
19379
+ const apiUrl = process.env.NEXT_PUBLIC_FLOWOPS_API_URL ?? "https://flowops-backend-staging-2yfdh7pwpq-as.a.run.app";
19380
+ fetch(`${apiUrl}/api/public/sites/${subdomain}/content?path=${encodeURIComponent(pathname)}`, { cache: "no-store" }).then((r2) => r2.ok ? r2.json() : null).then((data) => {
19381
+ if (!data?.content) return;
19382
+ contentCache.set(subdomain, data.content);
19383
+ applyFromCache();
19384
+ }).catch(() => {
19385
+ });
19386
+ }
19191
19387
  observer = new MutationObserver(scheduleApply);
19192
19388
  observer.observe(document.body, { childList: true, subtree: true });
19193
19389
  return () => {
@@ -19323,26 +19519,11 @@ function OhhwellsBridge() {
19323
19519
  const t2 = setTimeout(measure, 500);
19324
19520
  const ro = new ResizeObserver(schedule);
19325
19521
  ro.observe(document.body);
19326
- let lastWidth = window.innerWidth;
19327
- let resizeTimers = [];
19328
- const clearResizeTimers = () => {
19329
- resizeTimers.forEach(clearTimeout);
19330
- resizeTimers = [];
19331
- };
19332
- const handleResize = () => {
19333
- if (window.innerWidth === lastWidth) return;
19334
- lastWidth = window.innerWidth;
19335
- clearResizeTimers();
19336
- resizeTimers = HEIGHT_SETTLE_DELAYS.map((delay) => setTimeout(measure, delay));
19337
- };
19338
- window.addEventListener("resize", handleResize);
19339
19522
  return () => {
19340
19523
  clearTimeout(t1);
19341
19524
  clearTimeout(t2);
19342
19525
  if (raf != null) cancelAnimationFrame(raf);
19343
19526
  ro.disconnect();
19344
- clearResizeTimers();
19345
- window.removeEventListener("resize", handleResize);
19346
19527
  };
19347
19528
  }, [pathname, isEditMode, postToParent2]);
19348
19529
  useEffect13(() => {
@@ -19573,6 +19754,10 @@ function OhhwellsBridge() {
19573
19754
  [data-ohw-editable-state], [data-ohw-editable-state] * { pointer-events: none !important; }
19574
19755
  [data-ohw-editable-state][data-ohw-active-state] [data-ohw-editable] { pointer-events: auto !important; }
19575
19756
  [data-ohw-editable-state][data-ohw-active-state][data-ohw-editable] { pointer-events: auto !important; }
19757
+ /* SVG hit-tests painted glyphs only, so an editable curved-text badge answered clicks
19758
+ on its letter strokes alone (and a spinning one barely at all). In edit mode the
19759
+ whole box takes the click; the handler resolves it to the editable inside. */
19760
+ svg:has([data-ohw-editable]) { pointer-events: bounding-box !important; }
19576
19761
  `;
19577
19762
  if (!existing) {
19578
19763
  const base = document.createElement("style");
@@ -19611,9 +19796,6 @@ function OhhwellsBridge() {
19611
19796
  if (target.closest("[data-ohw-state-toggle]")) return;
19612
19797
  if (target.closest("[data-ohw-max-badge]")) return;
19613
19798
  if (isInsideLinkEditor(target)) return;
19614
- if (selectedMediaElRef.current && !selectedMediaElRef.current.contains(target) && !target.closest("[data-ohw-media-overlay], [data-ohw-edit-chrome], [data-ohw-item-interaction]")) {
19615
- clearMediaSelectionRef.current();
19616
- }
19617
19799
  if (isInsideFloatingPanel(target)) return;
19618
19800
  if (target.closest("[data-ohw-form-toolbar]")) return;
19619
19801
  if (target.closest(
@@ -19621,6 +19803,9 @@ function OhhwellsBridge() {
19621
19803
  )) {
19622
19804
  return;
19623
19805
  }
19806
+ if (selectedMediaElRef.current && !selectedMediaElRef.current.contains(target) && !target.closest("[data-ohw-media-overlay], [data-ohw-edit-chrome], [data-ohw-item-interaction]")) {
19807
+ clearMediaSelectionRef.current();
19808
+ }
19624
19809
  {
19625
19810
  const formEl = getFormElement(target);
19626
19811
  const onSuccessText = target.closest(`[${SUCCESS_TEXT_ATTR}]`);
@@ -19743,7 +19928,19 @@ function OhhwellsBridge() {
19743
19928
  openLogoSizePanelRef.current(logoEl);
19744
19929
  return;
19745
19930
  }
19746
- const editable = target.closest("[data-ohw-editable]");
19931
+ let editable = target.closest("[data-ohw-editable]");
19932
+ if (!editable && target instanceof SVGElement) {
19933
+ const inner = target.closest("svg")?.querySelectorAll("[data-ohw-editable]");
19934
+ if (inner && inner.length === 1) editable = inner[0];
19935
+ }
19936
+ if (editable && !(editable instanceof HTMLElement)) {
19937
+ e.preventDefault();
19938
+ e.stopPropagation();
19939
+ deselectRef.current();
19940
+ deactivateRef.current();
19941
+ aiSectionApiRef.current?.selectFromElement(editable);
19942
+ return;
19943
+ }
19747
19944
  if (editable) {
19748
19945
  if (editable.dataset.ohwEditable === "link") {
19749
19946
  e.preventDefault();
@@ -19772,14 +19969,6 @@ function OhhwellsBridge() {
19772
19969
  }
19773
19970
  const clickedButton = findClosestButtonLike(target);
19774
19971
  const buttonOnMedia = Boolean(clickedButton && isMediaEditable(editable) && editable.contains(clickedButton));
19775
- console.log("[click-debug]", {
19776
- editableType: editable.dataset.ohwEditable,
19777
- editableTag: editable.tagName,
19778
- targetTag: target.tagName,
19779
- clickedButtonTag: clickedButton?.tagName ?? null,
19780
- buttonOnMedia,
19781
- isMediaEditableEditable: isMediaEditable(editable)
19782
- });
19783
19972
  if (isMediaEditable(editable) && !buttonOnMedia) {
19784
19973
  e.preventDefault();
19785
19974
  e.stopPropagation();
@@ -19806,11 +19995,6 @@ function OhhwellsBridge() {
19806
19995
  const hrefLookupTarget = buttonOnMedia ? clickedButton : editable;
19807
19996
  const hrefCtx = getHrefKeyFromElement(hrefLookupTarget);
19808
19997
  const navAnchor = hrefCtx ? getNavigationItemAnchor(hrefCtx.anchor) : null;
19809
- console.log("[click-debug 2]", {
19810
- hrefLookupTargetTag: hrefLookupTarget.tagName,
19811
- hrefCtx: hrefCtx ? { key: hrefCtx.key } : null,
19812
- navAnchorTag: navAnchor?.tagName ?? null
19813
- });
19814
19998
  if (navAnchor) {
19815
19999
  e.preventDefault();
19816
20000
  e.stopPropagation();
@@ -19980,6 +20164,9 @@ function OhhwellsBridge() {
19980
20164
  setHoveredItemRect(null);
19981
20165
  hoveredNavContainerRef.current = null;
19982
20166
  setHoveredNavContainerRect(null);
20167
+ siblingHintElRef.current = null;
20168
+ setSiblingHintRect(null);
20169
+ setSiblingHintRects([]);
19983
20170
  return;
19984
20171
  }
19985
20172
  {
@@ -20100,7 +20287,6 @@ function OhhwellsBridge() {
20100
20287
  hoveredNavContainerRef.current = null;
20101
20288
  setHoveredNavContainerRect(null);
20102
20289
  hoveredItemElRef.current = editable;
20103
- setHoveredItemRect(isSelectedForHoverRef.current(editable) ? null : editable.getBoundingClientRect());
20104
20290
  }
20105
20291
  }
20106
20292
  }
@@ -20397,7 +20583,7 @@ function OhhwellsBridge() {
20397
20583
  }
20398
20584
  };
20399
20585
  const probeImageAt = (clientX, clientY, isDragOver = false, fromParentViewport = false) => {
20400
- if (linkPopoverOpenRef.current) {
20586
+ if (linkPopoverOpenRef.current || floatingPanelOpenRef.current && isPointOverFloatingPanel(clientX, clientY)) {
20401
20587
  if (hoveredImageRef.current) {
20402
20588
  hoveredImageRef.current = null;
20403
20589
  hoveredImageHasTextOverlapRef.current = false;
@@ -20486,6 +20672,15 @@ function OhhwellsBridge() {
20486
20672
  }
20487
20673
  return;
20488
20674
  }
20675
+ const cardHit = !isDragOver && Array.from(document.querySelectorAll(".card, [data-ohw-card]")).some((card) => {
20676
+ if (card.contains(imgEl)) return false;
20677
+ const r2 = card.getBoundingClientRect();
20678
+ return x2 >= r2.left && x2 <= r2.right && y2 >= r2.top && y2 <= r2.bottom;
20679
+ });
20680
+ if (cardHit) {
20681
+ dismissImageHover();
20682
+ return;
20683
+ }
20489
20684
  const topEl = document.elementFromPoint(x2, y2);
20490
20685
  if (topEl?.closest('[data-ohw-toolbar], [data-ohw-edit-chrome], [data-ohw-item-interaction], [data-ohw-drag-handle-container], [data-slot="drag-handle"], [data-ohw-more-menu], [data-slot="dropdown-menu-content"], [data-slot="dropdown-menu-item"]')) {
20491
20686
  if (hoveredImageRef.current) {
@@ -20761,8 +20956,7 @@ function OhhwellsBridge() {
20761
20956
  };
20762
20957
  const handleMouseMove = (e) => {
20763
20958
  const { clientX, clientY } = e;
20764
- if (pointOwnedByFloatingPanel(clientX, clientY)) return;
20765
- if (isOverEditorChrome(clientX, clientY)) {
20959
+ if (document.documentElement.hasAttribute("data-ohw-panel-dragging") || floatingPanelOpenRef.current && isPointOverFloatingPanel(clientX, clientY) || isOverEditorChrome(clientX, clientY)) {
20766
20960
  document.querySelectorAll("[data-ohw-hovered]").forEach((el) => el.removeAttribute("data-ohw-hovered"));
20767
20961
  formHoverElRef.current = null;
20768
20962
  setFormHoverRect(null);
@@ -20770,6 +20964,12 @@ function OhhwellsBridge() {
20770
20964
  setHoveredItemRect(null);
20771
20965
  hoveredNavContainerRef.current = null;
20772
20966
  setHoveredNavContainerRect(null);
20967
+ siblingHintElRef.current = null;
20968
+ setSiblingHintRect(null);
20969
+ setSiblingHintRects([]);
20970
+ dismissImageHover();
20971
+ clearImageHover();
20972
+ setSectionGap(null);
20773
20973
  return;
20774
20974
  }
20775
20975
  if (probeSocialsRowAt(clientX, clientY)) return;
@@ -20781,7 +20981,11 @@ function OhhwellsBridge() {
20781
20981
  if (e.data?.type !== "ow:pointer-sync") return;
20782
20982
  const { clientX, clientY } = e.data;
20783
20983
  if (typeof clientX !== "number" || typeof clientY !== "number") return;
20784
- if (pointOwnedByFloatingPanel(clientX, clientY)) return;
20984
+ if (document.documentElement.hasAttribute("data-ohw-panel-dragging") || floatingPanelOpenRef.current && isPointOverFloatingPanel(clientX, clientY)) {
20985
+ dismissImageHover();
20986
+ clearImageHover();
20987
+ return;
20988
+ }
20785
20989
  if (probeSocialsRowAt(clientX, clientY)) return;
20786
20990
  probeSectionGapAt(clientX, clientY);
20787
20991
  probeImageAt(clientX, clientY);
@@ -21120,6 +21324,7 @@ function OhhwellsBridge() {
21120
21324
  }
21121
21325
  if (typeof content[STYLE_STORE_KEY] === "string") {
21122
21326
  stylesRef.current = content[STYLE_STORE_KEY];
21327
+ document.querySelectorAll('[data-ohw-editable="form"]').forEach((f) => markFormFields(f));
21123
21328
  applyStylesToDom(parseStyleStore(content[STYLE_STORE_KEY]));
21124
21329
  }
21125
21330
  applyBrandChrome(content);
@@ -21131,11 +21336,11 @@ function OhhwellsBridge() {
21131
21336
  continue;
21132
21337
  }
21133
21338
  if (key === AI_SECTIONS_KEY) continue;
21339
+ if (key === LOGO_PLACEHOLDER_KEY) continue;
21340
+ if (LOGO_IMAGE_KEYS.includes(key) && !String(val ?? "").trim()) continue;
21134
21341
  if (key === BRAND_KIT_KEY) continue;
21135
21342
  if (key === STYLE_STORE_KEY) continue;
21136
21343
  if (BRAND_CHROME_KEYS.has(key)) continue;
21137
- if (key === LOGO_PLACEHOLDER_KEY) continue;
21138
- if (LOGO_IMAGE_KEYS.includes(key) && !String(val ?? "").trim()) continue;
21139
21344
  if (applyVideoSettingNode(key, val)) continue;
21140
21345
  if (applyCarouselNode(key, val)) continue;
21141
21346
  document.querySelectorAll(`[data-ohw-key="${key}"]`).forEach((el) => {
@@ -21356,10 +21561,26 @@ function OhhwellsBridge() {
21356
21561
  const handleGetBrand = (e) => {
21357
21562
  if (e.data?.type !== "ow:get-brand") return;
21358
21563
  const template = deriveTemplateBrand();
21359
- const value = brandKitRef.current || (template ? JSON.stringify(template) : "");
21564
+ const fallback = template ?? (() => {
21565
+ const fonts = deriveTemplateFonts();
21566
+ return fonts ? { palette: AI_DEFAULT_BRAND.palette, fonts } : null;
21567
+ })();
21568
+ const value = brandKitRef.current || (fallback ? JSON.stringify(fallback) : "");
21360
21569
  postToParentRef.current({ type: "ow:brand-value", value });
21361
21570
  };
21362
21571
  window.addEventListener("message", handleGetBrand);
21572
+ const handleGetTemplateFonts = (e) => {
21573
+ if (e.data?.type !== "ow:get-template-fonts") return;
21574
+ const fonts = deriveTemplateFonts();
21575
+ postToParentRef.current({ type: "ow:template-fonts-value", value: fonts ? JSON.stringify(fonts) : "" });
21576
+ };
21577
+ window.addEventListener("message", handleGetTemplateFonts);
21578
+ const handleGetTemplateBrand = (e) => {
21579
+ if (e.data?.type !== "ow:get-template-brand") return;
21580
+ const brand = deriveTemplateBrand();
21581
+ postToParentRef.current({ type: "ow:template-brand-value", value: brand ? JSON.stringify(brand) : "" });
21582
+ };
21583
+ window.addEventListener("message", handleGetTemplateBrand);
21363
21584
  const handleMoveSection = (e) => {
21364
21585
  if (e.data?.type !== "ow:move-section") return;
21365
21586
  const instanceId = typeof e.data.instanceId === "string" ? e.data.instanceId : "";
@@ -21552,6 +21773,12 @@ function OhhwellsBridge() {
21552
21773
  closeLinkPopoverRef.current();
21553
21774
  return;
21554
21775
  }
21776
+ if (floatingPanelOpenRef.current) {
21777
+ setFloatingPanelRef.current(null);
21778
+ deselectRef.current();
21779
+ deactivateRef.current();
21780
+ return;
21781
+ }
21555
21782
  deselectRef.current();
21556
21783
  deactivateRef.current();
21557
21784
  clearMediaSelectionRef.current();
@@ -21840,8 +22067,12 @@ function OhhwellsBridge() {
21840
22067
  if (inserted) {
21841
22068
  const tracker = getSectionsTracker();
21842
22069
  postToParentRef.current({ type: "ow:change", nodes: [{ key: "__ohw_sections", text: tracker.textContent ?? "[]" }] });
21843
- const h = document.body.scrollHeight;
21844
- if (h > 50) postToParentRef.current({ type: "ow:height", height: h });
22070
+ const reportHeight = () => {
22071
+ const h = document.body.scrollHeight;
22072
+ if (h > 50) postToParentRef.current({ type: "ow:height", height: h });
22073
+ };
22074
+ reportHeight();
22075
+ setTimeout(reportHeight, 500);
21845
22076
  }
21846
22077
  };
21847
22078
  const handleSwitchSchedule = (e) => {
@@ -22018,14 +22249,20 @@ function OhhwellsBridge() {
22018
22249
  };
22019
22250
  const applyToolbarPos = (rect) => {
22020
22251
  const ps = parentScrollRef.current;
22252
+ const hidden = isRectOutsideClip(rect, getIframeVisibleClip(ps));
22021
22253
  const measuredW = toolbarElRef.current?.offsetWidth || 330;
22022
22254
  if (toolbarElRef.current) {
22023
- const { top, left, transform } = calcToolbarPos(rect, ps, measuredW);
22024
- toolbarElRef.current.style.top = `${top}px`;
22025
- toolbarElRef.current.style.left = `${left}px`;
22026
- toolbarElRef.current.style.transform = transform;
22255
+ toolbarElRef.current.style.display = hidden ? "none" : "";
22256
+ if (!hidden) {
22257
+ const { top, left, transform } = calcToolbarPos(rect, ps, measuredW);
22258
+ toolbarElRef.current.style.top = `${top}px`;
22259
+ toolbarElRef.current.style.left = `${left}px`;
22260
+ toolbarElRef.current.style.transform = transform;
22261
+ }
22027
22262
  }
22028
22263
  if (glowElRef.current) {
22264
+ glowElRef.current.style.display = hidden ? "none" : "";
22265
+ if (hidden) return;
22029
22266
  const GAP = SELECTION_CHROME_GAP2;
22030
22267
  glowElRef.current.style.top = `${rect.top - GAP}px`;
22031
22268
  glowElRef.current.style.left = `${rect.left - GAP}px`;
@@ -22256,15 +22493,17 @@ function OhhwellsBridge() {
22256
22493
  window.removeEventListener("message", handleAiSetBrand);
22257
22494
  window.removeEventListener("message", handleAiSetStyles);
22258
22495
  window.removeEventListener("message", handleGetBrand);
22496
+ window.removeEventListener("message", handleGetTemplateFonts);
22497
+ window.removeEventListener("message", handleGetTemplateBrand);
22259
22498
  window.removeEventListener("message", handleMoveSection);
22260
22499
  window.removeEventListener("message", handlePanelDragging);
22261
22500
  window.removeEventListener("message", handleDeleteSection);
22262
22501
  window.removeEventListener("message", handleDuplicateSection);
22263
22502
  window.removeEventListener("message", handleDeactivate);
22264
- document.documentElement.removeAttribute("data-ohw-panel-dragging");
22265
22503
  window.removeEventListener("message", handleToastAction);
22266
22504
  window.removeEventListener("message", handleFormCount);
22267
22505
  window.removeEventListener("message", handleUiEscape);
22506
+ document.documentElement.removeAttribute("data-ohw-panel-dragging");
22268
22507
  autoSaveTimers.current.forEach(clearTimeout);
22269
22508
  autoSaveTimers.current.clear();
22270
22509
  if (imageUnhoverTimerRef.current) clearTimeout(imageUnhoverTimerRef.current);
@@ -22467,7 +22706,7 @@ function OhhwellsBridge() {
22467
22706
  postToParent2({
22468
22707
  type: "ow:ready",
22469
22708
  version: "1",
22470
- bridgeVersion: "0.1.106",
22709
+ bridgeVersion: "0.1.108",
22471
22710
  path: pathname,
22472
22711
  nodes: collectEditableNodes(editContentRef.current),
22473
22712
  sections
@@ -23083,7 +23322,7 @@ function OhhwellsBridge() {
23083
23322
  "span",
23084
23323
  {
23085
23324
  "data-ohw-form-count": "",
23086
- className: "ml-0.5 inline-flex h-[18px] min-w-[18px] items-center justify-center rounded-full bg-primary px-1.5 text-[11px] font-semibold text-primary-foreground",
23325
+ className: "ml-0.5 inline-flex h-[18px] min-w-[18px] items-center justify-center rounded-full bg-bridge-primary px-1.5 text-[11px] font-semibold text-primary-foreground",
23087
23326
  children: formPickCount
23088
23327
  }
23089
23328
  )
@@ -23254,7 +23493,7 @@ function OhhwellsBridge() {
23254
23493
  ) : void 0
23255
23494
  }
23256
23495
  ),
23257
- toolbarRect && toolbarVariant === "rich-text" && !linkPopover && /* @__PURE__ */ jsxs20(Fragment8, { children: [
23496
+ toolbarRect && toolbarVariant === "rich-text" && !linkPopover && !isRectOutsideClip(toolbarRect, getIframeVisibleClip(parentScrollRef.current)) && /* @__PURE__ */ jsxs20(Fragment8, { children: [
23258
23497
  /* @__PURE__ */ jsx33(
23259
23498
  EditGlowChrome,
23260
23499
  {
@@ -23320,11 +23559,11 @@ function OhhwellsBridge() {
23320
23559
  className: "fixed left-0 w-full z-2147483646 flex items-center pointer-events-none",
23321
23560
  style: { top: sectionGap.y, transform: "translateY(-50%)" },
23322
23561
  children: [
23323
- /* @__PURE__ */ jsx33("div", { className: "flex-1 bg-primary", style: { height: 3 } }),
23562
+ /* @__PURE__ */ jsx33("div", { className: "flex-1 bg-bridge-primary", style: { height: 3 } }),
23324
23563
  /* @__PURE__ */ jsx33(
23325
23564
  Badge,
23326
23565
  {
23327
- className: "px-8 py-1 bg-primary hover:bg-primary text-primary-foreground text-xs font-medium shrink-0 rounded-full cursor-pointer pointer-events-auto",
23566
+ className: "px-8 py-1 bg-bridge-primary hover:bg-bridge-primary text-primary-foreground text-xs font-medium shrink-0 rounded-full cursor-pointer pointer-events-auto",
23328
23567
  onClick: () => {
23329
23568
  window.parent.postMessage(
23330
23569
  {
@@ -23338,7 +23577,7 @@ function OhhwellsBridge() {
23338
23577
  children: "Add Section"
23339
23578
  }
23340
23579
  ),
23341
- /* @__PURE__ */ jsx33("div", { className: "flex-1 bg-primary", style: { height: 3 } })
23580
+ /* @__PURE__ */ jsx33("div", { className: "flex-1 bg-bridge-primary", style: { height: 3 } })
23342
23581
  ]
23343
23582
  }
23344
23583
  ),
@@ -23386,6 +23625,59 @@ function OhhwellsBridge() {
23386
23625
  ) : null
23387
23626
  ] });
23388
23627
  }
23628
+
23629
+ // src/ui/EmptySection.tsx
23630
+ import Link3 from "next/link";
23631
+ import { Fragment as Fragment9, jsx as jsx34, jsxs as jsxs21 } from "react/jsx-runtime";
23632
+ function EmptySection({ title, homeHref = "/", eyebrowKey, titleKey, subtitleKey }) {
23633
+ return /* @__PURE__ */ jsxs21(Fragment9, { children: [
23634
+ /* @__PURE__ */ jsx34(
23635
+ "p",
23636
+ {
23637
+ style: {
23638
+ fontFamily: "var(--brand-font-body)",
23639
+ fontSize: "0.75rem",
23640
+ fontWeight: 500,
23641
+ letterSpacing: "0.15em",
23642
+ textTransform: "uppercase",
23643
+ color: "var(--brand-accent)",
23644
+ marginBottom: "1.5rem"
23645
+ },
23646
+ children: /* @__PURE__ */ jsx34(Link3, { href: homeHref, style: { color: "inherit" }, children: /* @__PURE__ */ jsx34("span", { ...eyebrowKey ? { "data-ohw-editable": "text", "data-ohw-key": eyebrowKey } : {}, children: "Home" }) })
23647
+ }
23648
+ ),
23649
+ /* @__PURE__ */ jsx34(
23650
+ "h1",
23651
+ {
23652
+ style: {
23653
+ fontFamily: "var(--brand-font-heading)",
23654
+ fontSize: "clamp(2rem, 3.5vw, 2.75rem)",
23655
+ lineHeight: 1.1,
23656
+ letterSpacing: "-0.025em",
23657
+ color: "var(--brand-text)",
23658
+ marginBottom: "1rem"
23659
+ },
23660
+ ...titleKey ? { "data-ohw-editable": "text", "data-ohw-key": titleKey, "data-ohw-max-length": 80 } : {},
23661
+ children: title
23662
+ }
23663
+ ),
23664
+ /* @__PURE__ */ jsx34(
23665
+ "p",
23666
+ {
23667
+ style: {
23668
+ fontFamily: "var(--brand-font-body)",
23669
+ fontSize: "1rem",
23670
+ lineHeight: 1.7,
23671
+ fontWeight: 300,
23672
+ color: "var(--brand-text-muted)",
23673
+ maxWidth: "340px"
23674
+ },
23675
+ ...subtitleKey ? { "data-ohw-editable": "text", "data-ohw-key": subtitleKey, "data-ohw-max-length": 160 } : {},
23676
+ children: "This page doesn't have any content yet."
23677
+ }
23678
+ )
23679
+ ] });
23680
+ }
23389
23681
  export {
23390
23682
  AI_DEFAULT_BRAND,
23391
23683
  AI_TREE_SCHEMA_VERSIONS,
@@ -23402,6 +23694,7 @@ export {
23402
23694
  DropdownMenuItem,
23403
23695
  DropdownMenuSeparator,
23404
23696
  DropdownMenuTrigger,
23697
+ EmptySection,
23405
23698
  ItemActionToolbar,
23406
23699
  ItemInteractionLayer,
23407
23700
  LinkEditorPanel,