@ohhwells/bridge 0.1.107 → 0.1.108-next.282

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -46,6 +46,7 @@ __export(index_exports, {
46
46
  DropdownMenuItem: () => DropdownMenuItem,
47
47
  DropdownMenuSeparator: () => DropdownMenuSeparator,
48
48
  DropdownMenuTrigger: () => DropdownMenuTrigger,
49
+ EmptySection: () => EmptySection,
49
50
  ItemActionToolbar: () => ItemActionToolbar,
50
51
  ItemInteractionLayer: () => ItemInteractionLayer,
51
52
  LinkEditorPanel: () => LinkEditorPanel,
@@ -352,16 +353,17 @@ var LEGACY_BRAND_VAR_NAMES = [
352
353
  "text-muted",
353
354
  "surface",
354
355
  "border",
355
- "on-primary",
356
- "navbar-background"
356
+ "on-primary"
357
357
  ].map((role) => `--brand-${role}`);
358
358
  var FONT_VARS = {
359
359
  heading: ["--font-heading", "--font-display", "--brand-font-heading"],
360
360
  body: ["--font-body", "--brand-font-body"]
361
361
  };
362
362
  var BRAND_FONT_LINK_ID = "ohw-brand-fonts";
363
- function brandColorVars(kit) {
364
- const { dark, primary, accent, light } = kit.palette;
363
+ var CUSTOM_FONT_STYLE_ID = "ohw-brand-custom-fonts";
364
+ var CUSTOM_FONT_FORMATS = /* @__PURE__ */ new Set(["woff2", "woff", "truetype", "opentype"]);
365
+ function brandColorVars(palette) {
366
+ const { dark, primary, accent, light } = palette;
365
367
  const mix = (a, aPct, b) => `color-mix(in srgb, ${a} ${aPct}%, ${b})`;
366
368
  const surface = mix(light, 95, dark);
367
369
  const border = mix(light, 85, dark);
@@ -384,22 +386,42 @@ function brandColorVars(kit) {
384
386
  "--brand-border": border,
385
387
  // Buttons/bands painted in the primary colour assume it's dark/saturated enough to need
386
388
  // light text on top — the same assumption LOGO_IMAGE's light-on-dark navbar mark makes.
387
- "--brand-on-primary": light,
388
- "--brand-navbar-background": dark
389
+ "--brand-on-primary": light
389
390
  };
390
391
  }
392
+ function parseCustomFontWeight(raw) {
393
+ if (typeof raw !== "object" || raw === null) return null;
394
+ const w = raw;
395
+ if (typeof w.weight !== "number" || !Number.isFinite(w.weight)) return null;
396
+ if (typeof w.url !== "string" || !w.url) return null;
397
+ if (typeof w.format !== "string" || !CUSTOM_FONT_FORMATS.has(w.format)) return null;
398
+ const weightEnd = typeof w.weightEnd === "number" && Number.isFinite(w.weightEnd) && w.weightEnd > w.weight ? w.weightEnd : void 0;
399
+ return { weight: w.weight, ...weightEnd !== void 0 ? { weightEnd } : {}, url: w.url, format: w.format };
400
+ }
401
+ function parseCustomFont(raw) {
402
+ if (typeof raw !== "object" || raw === null) return null;
403
+ const f = raw;
404
+ if (typeof f.family !== "string" || !f.family) return null;
405
+ if (typeof f.label !== "string" || !f.label) return null;
406
+ if (!Array.isArray(f.weights)) return null;
407
+ const weights = f.weights.map(parseCustomFontWeight).filter((w) => w !== null);
408
+ if (weights.length === 0) return null;
409
+ return { family: f.family, label: f.label, weights };
410
+ }
391
411
  function parseBrandKit(raw) {
392
412
  if (!raw) return null;
393
413
  try {
394
414
  const parsed = JSON.parse(raw);
395
415
  const p = parsed?.palette;
396
416
  const f = parsed?.fonts;
397
- 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") {
398
- return null;
399
- }
417
+ 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;
418
+ const fonts = f && typeof f.heading === "string" && typeof f.body === "string" ? { heading: f.heading, body: f.body } : void 0;
419
+ const customFonts = Array.isArray(parsed?.customFonts) ? parsed.customFonts.map(parseCustomFont).filter((c) => c !== null) : void 0;
420
+ if (!palette && !fonts && !customFonts) return null;
400
421
  return {
401
- palette: { dark: p.dark, primary: p.primary, accent: p.accent, light: p.light },
402
- fonts: { heading: f.heading, body: f.body }
422
+ ...palette ? { palette } : {},
423
+ ...fonts ? { fonts } : {},
424
+ ...customFonts ? { customFonts } : {}
403
425
  };
404
426
  } catch {
405
427
  return null;
@@ -409,11 +431,16 @@ function familyOf(stack) {
409
431
  const first = stack.split(",")[0]?.trim() ?? "";
410
432
  return first.replace(/^['"]|['"]$/g, "");
411
433
  }
434
+ function quoteFamily(family) {
435
+ return `'${family.replace(/\\/g, "\\\\").replace(/'/g, "\\'")}'`;
436
+ }
437
+ var WEIGHT_SCALE = [100, 200, 300, 400, 500, 600, 700, 800, 900];
412
438
  function loadBrandFonts(families) {
413
439
  const unique = [...new Set(families.map((f) => f.trim()).filter(Boolean))];
414
440
  if (unique.length === 0) return;
415
- const spec = unique.map((f) => `${f.replace(/ /g, "+")}:400,500,600,700`).join("|");
416
- const href = `https://fonts.googleapis.com/css?family=${spec}&display=swap`;
441
+ const weights = WEIGHT_SCALE.join(";");
442
+ const spec = unique.map((f) => `family=${f.replace(/ /g, "+")}:wght@${weights}`).join("&");
443
+ const href = `https://fonts.googleapis.com/css2?${spec}&display=swap`;
417
444
  let link = document.getElementById(BRAND_FONT_LINK_ID);
418
445
  if (!link) {
419
446
  link = document.createElement("link");
@@ -423,18 +450,54 @@ function loadBrandFonts(families) {
423
450
  }
424
451
  if (link.href !== href) link.href = href;
425
452
  }
453
+ function escapeCssString(value) {
454
+ return value.replace(/\\/g, "\\\\").replace(/'/g, "\\'");
455
+ }
456
+ function loadCustomFonts(fonts) {
457
+ const list = fonts ?? [];
458
+ let style = document.getElementById(CUSTOM_FONT_STYLE_ID);
459
+ if (list.length === 0) {
460
+ style?.remove();
461
+ return;
462
+ }
463
+ if (!style) {
464
+ style = document.createElement("style");
465
+ style.id = CUSTOM_FONT_STYLE_ID;
466
+ document.head.appendChild(style);
467
+ }
468
+ style.textContent = list.flatMap(
469
+ (font) => font.weights.map((w) => {
470
+ const weightDescriptor = w.weightEnd ? `${w.weight} ${w.weightEnd}` : `${w.weight}`;
471
+ return `@font-face { font-family: '${escapeCssString(font.family)}'; src: url('${escapeCssString(w.url)}') format('${w.format}'); font-weight: ${weightDescriptor}; font-display: swap; }`;
472
+ })
473
+ ).join("\n");
474
+ }
426
475
  function applyBrandToDom(kit) {
427
476
  const root = document.documentElement;
428
477
  if (!kit) {
429
478
  for (const name of [...BRAND_VAR_NAMES, ...LEGACY_BRAND_VAR_NAMES]) root.style.removeProperty(name);
430
479
  for (const name of [...FONT_VARS.heading, ...FONT_VARS.body]) root.style.removeProperty(name);
431
480
  document.getElementById(BRAND_FONT_LINK_ID)?.remove();
481
+ document.getElementById(CUSTOM_FONT_STYLE_ID)?.remove();
432
482
  return;
433
483
  }
434
- for (const [name, value] of Object.entries(brandColorVars(kit))) root.style.setProperty(name, value);
435
- for (const name of FONT_VARS.heading) root.style.setProperty(name, kit.fonts.heading);
436
- for (const name of FONT_VARS.body) root.style.setProperty(name, kit.fonts.body);
437
- loadBrandFonts([familyOf(kit.fonts.heading), familyOf(kit.fonts.body)]);
484
+ if (kit.palette) {
485
+ for (const [name, value] of Object.entries(brandColorVars(kit.palette))) root.style.setProperty(name, value);
486
+ }
487
+ if (kit.customFonts) {
488
+ loadCustomFonts(kit.customFonts);
489
+ }
490
+ if (kit.fonts) {
491
+ const heading = quoteFamily(kit.fonts.heading);
492
+ const body = quoteFamily(kit.fonts.body);
493
+ for (const name of FONT_VARS.heading) root.style.setProperty(name, heading);
494
+ for (const name of FONT_VARS.body) root.style.setProperty(name, body);
495
+ const customFamilies = new Set((kit.customFonts ?? []).map((f) => f.family));
496
+ const googleFamilies = [familyOf(kit.fonts.heading), familyOf(kit.fonts.body)].filter(
497
+ (f) => !customFamilies.has(f)
498
+ );
499
+ loadBrandFonts(googleFamilies);
500
+ }
438
501
  }
439
502
 
440
503
  // src/lib/section-styles.ts
@@ -493,7 +556,9 @@ function styleSheetCss() {
493
556
  }
494
557
  rules.push(
495
558
  `[data-ohw-style-corners="sharp"] :is(.card, [data-ohw-card], img, picture, figure, [data-ohw-editable="bg-image"]) { border-radius: 0 !important; }`,
496
- `[data-ohw-style-corners="sharp"] :has(> img) { border-radius: 0 !important; }`
559
+ `[data-ohw-style-corners="sharp"] :has(> img) { border-radius: 0 !important; }`,
560
+ `[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; }`,
561
+ `[data-ohw-style-corners="rounded"] :has(> img) { border-radius: max(0.75rem, var(--radius, 0px)) !important; }`
497
562
  );
498
563
  for (const align of ["left", "center", "right"]) {
499
564
  rules.push(`[data-ohw-style-align="${align}"] { text-align: ${align} !important; }`);
@@ -1353,6 +1418,22 @@ function accentBandContext(brand) {
1353
1418
  function textAttrs(ctx, path) {
1354
1419
  return ctx.keyFor ? { "data-ohw-key": ctx.keyFor(path), "data-ohw-editable": "text" } : {};
1355
1420
  }
1421
+ var AI_RESPONSIVE_CSS = [
1422
+ "@media (max-width: 960px) {",
1423
+ " [data-ai-responsive] [data-ai-grid] { grid-template-columns: repeat(2, 1fr) !important; }",
1424
+ ' [data-ai-responsive] [data-ai-grid="1"] { grid-template-columns: 1fr !important; }',
1425
+ "}",
1426
+ "@media (max-width: 640px) {",
1427
+ " [data-ai-responsive] [data-ai-grid] { grid-template-columns: 1fr !important; }",
1428
+ " [data-ai-responsive] [data-ai-columns] > * { grid-column: 1 / -1 !important; }",
1429
+ // Group containers flatten to a column on phones; span placements come along for free.
1430
+ " [data-ai-responsive] [data-ai-group] { display: flex !important; flex-direction: column !important; }",
1431
+ " [data-ai-responsive] [data-ai-group] > * { grid-column: auto !important; }",
1432
+ " [data-ai-responsive] [data-ai-section-inner] { padding-left: 20px !important; padding-right: 20px !important; }",
1433
+ " [data-ai-responsive] { overflow-x: hidden; }",
1434
+ " [data-ai-responsive] img { max-width: 100%; }",
1435
+ "}"
1436
+ ].join("\n");
1356
1437
  var TRANSPARENT_PX = "data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7";
1357
1438
  function MediaBox({
1358
1439
  refValue,
@@ -1365,13 +1446,17 @@ function MediaBox({
1365
1446
  const url = refValue ? ctx.resolveMedia(refValue) : null;
1366
1447
  const isIcon = /^(lucide|simple):/.test(refValue);
1367
1448
  const aspectRatio = aspect && /^\d+:\d+$/.test(aspect) ? aspect.replace(":", " / ") : void 0;
1368
- const editAttrs = ctx.keyFor && editPath && !isIcon ? { "data-ohw-key": ctx.keyFor(editPath), "data-ohw-editable": "image" } : {};
1449
+ const editAttrs = ctx.keyFor && editPath ? {
1450
+ "data-ohw-key": ctx.keyFor(editPath),
1451
+ "data-ohw-editable": isIcon ? "icon" : "image"
1452
+ } : {};
1369
1453
  if (isIcon) {
1370
1454
  const Icon = refValue.startsWith("lucide:") ? lucideByName(refValue.slice(7)) : null;
1371
1455
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1372
1456
  "span",
1373
1457
  {
1374
1458
  "data-ai-icon": refValue,
1459
+ ...editAttrs,
1375
1460
  style: {
1376
1461
  display: "inline-flex",
1377
1462
  width: 48,
@@ -2264,7 +2349,7 @@ function CollectionBlock({ node, ctx, path }) {
2264
2349
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
2265
2350
  "div",
2266
2351
  {
2267
- "data-ai-grid": "",
2352
+ "data-ai-grid": String(itemsPerRow),
2268
2353
  style: {
2269
2354
  display: "grid",
2270
2355
  gridTemplateColumns: `repeat(${itemsPerRow}, 1fr)`,
@@ -2617,6 +2702,7 @@ function AiTreeRenderer({
2617
2702
  {
2618
2703
  "data-ai-section": tree.tag ?? "",
2619
2704
  ...bgAttrs,
2705
+ "data-ai-responsive": "",
2620
2706
  style: {
2621
2707
  position: "relative",
2622
2708
  padding: `${pad}px 0`,
@@ -2627,12 +2713,13 @@ function AiTreeRenderer({
2627
2713
  color: settings.sectionBackground === "accent" ? blockBrand.palette.dark : void 0
2628
2714
  },
2629
2715
  children: [
2630
- isOverlay && backgroundUrl && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { position: "absolute", inset: 0, background: `rgba(255,255,255,${overlayAlpha})` } }),
2716
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("style", { children: AI_RESPONSIVE_CSS }),
2631
2717
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("style", { children: AI_MOBILE_CSS }),
2718
+ isOverlay && backgroundUrl && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { position: "absolute", inset: 0, background: `rgba(255,255,255,${overlayAlpha})` } }),
2632
2719
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
2633
2720
  "div",
2634
2721
  {
2635
- "data-ai-container": "",
2722
+ "data-ai-section-inner": "",
2636
2723
  style: {
2637
2724
  position: "relative",
2638
2725
  maxWidth: AI_TREE_TOKENS.sectionMaxWidth,
@@ -2643,7 +2730,7 @@ function AiTreeRenderer({
2643
2730
  children: tree.rows.map((row, r2) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
2644
2731
  "div",
2645
2732
  {
2646
- "data-ai-row": "",
2733
+ "data-ai-columns": "",
2647
2734
  style: {
2648
2735
  display: "grid",
2649
2736
  gridTemplateColumns: "repeat(12, 1fr)",
@@ -2693,14 +2780,52 @@ function readRootVar(name) {
2693
2780
  if (typeof document === "undefined") return "";
2694
2781
  return getComputedStyle(document.documentElement).getPropertyValue(name).trim();
2695
2782
  }
2783
+ function normalizeColorToHex(value) {
2784
+ if (!value || typeof document === "undefined") return value;
2785
+ const canvas = document.createElement("canvas");
2786
+ canvas.width = 1;
2787
+ canvas.height = 1;
2788
+ const ctx = canvas.getContext("2d");
2789
+ if (!ctx) return value;
2790
+ ctx.fillStyle = value;
2791
+ ctx.fillRect(0, 0, 1, 1);
2792
+ const [r2, g, b] = ctx.getImageData(0, 0, 1, 1).data;
2793
+ const toHex = (n) => n.toString(16).padStart(2, "0");
2794
+ return `#${toHex(r2)}${toHex(g)}${toHex(b)}`;
2795
+ }
2796
+ var GENERIC_FONT_KEYWORDS = /* @__PURE__ */ new Set([
2797
+ "serif",
2798
+ "sans-serif",
2799
+ "monospace",
2800
+ "cursive",
2801
+ "fantasy",
2802
+ "system-ui",
2803
+ "ui-serif",
2804
+ "ui-sans-serif",
2805
+ "ui-monospace",
2806
+ "ui-rounded",
2807
+ "math",
2808
+ "emoji",
2809
+ "fangsong"
2810
+ ]);
2811
+ function primaryFontFamily(stack) {
2812
+ const first = stack.split(",").map((part) => part.trim().replace(/^["']|["']$/g, "")).find((part) => part && !GENERIC_FONT_KEYWORDS.has(part.toLowerCase()));
2813
+ return first ?? "";
2814
+ }
2815
+ function humanizeFontFamily(name) {
2816
+ 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());
2817
+ }
2818
+ function readFontVar(name) {
2819
+ return humanizeFontFamily(primaryFontFamily(readRootVar(name)));
2820
+ }
2696
2821
  function deriveBrandOverride() {
2697
2822
  const dark = readRootVar("--ohw-brand-dark");
2698
2823
  const primary = readRootVar("--ohw-brand-primary");
2699
2824
  const light = readRootVar("--ohw-brand-light");
2700
2825
  if (!dark || !primary || !light) return null;
2701
2826
  const accent = readRootVar("--ohw-brand-accent");
2702
- const heading = readRootVar("--font-heading") || readRootVar("--font-display");
2703
- const body = readRootVar("--font-body");
2827
+ const heading = readFontVar("--font-heading") || readFontVar("--font-display");
2828
+ const body = readFontVar("--font-body");
2704
2829
  return {
2705
2830
  palette: { dark, primary, accent: accent || dark, light },
2706
2831
  fonts: {
@@ -2709,27 +2834,65 @@ function deriveBrandOverride() {
2709
2834
  }
2710
2835
  };
2711
2836
  }
2712
- function deriveTemplateBrand() {
2713
- const primary = readRootVar("--brand-primary") || readRootVar("--color-primary");
2714
- const dark = readRootVar("--brand-text") || readRootVar("--color-dark");
2715
- const light = readRootVar("--brand-background") || readRootVar("--color-light");
2837
+ function deriveTemplateBrandLive() {
2838
+ const dark = readRootVar("--color-dark") || readRootVar("--brand-text");
2839
+ const primary = readRootVar("--color-primary") || readRootVar("--brand-primary");
2840
+ const light = readRootVar("--color-light") || readRootVar("--brand-background");
2716
2841
  if (!dark || !primary || !light) return null;
2717
- const accent = readRootVar("--brand-accent") || readRootVar("--color-accent");
2718
- const heading = readRootVar("--brand-font-heading") || readRootVar("--font-heading") || readRootVar("--font-display");
2719
- const body = readRootVar("--brand-font-body") || readRootVar("--font-body");
2842
+ const accent = readRootVar("--color-accent") || readRootVar("--brand-accent");
2843
+ const heading = readFontVar("--brand-font-heading") || readFontVar("--font-heading") || readFontVar("--font-display");
2844
+ const body = readFontVar("--brand-font-body") || readFontVar("--font-body");
2720
2845
  return {
2721
- palette: { dark, primary, accent: accent || dark, light },
2846
+ palette: {
2847
+ dark: normalizeColorToHex(dark),
2848
+ primary: normalizeColorToHex(primary),
2849
+ accent: normalizeColorToHex(accent || dark),
2850
+ light: normalizeColorToHex(light)
2851
+ },
2722
2852
  fonts: {
2723
2853
  heading: heading || AI_DEFAULT_BRAND.fonts.heading,
2724
2854
  body: body || AI_DEFAULT_BRAND.fonts.body
2725
2855
  }
2726
2856
  };
2727
2857
  }
2858
+ function deriveTemplateFontsLive() {
2859
+ const heading = readFontVar("--brand-font-heading") || readFontVar("--font-heading") || readFontVar("--font-display");
2860
+ const body = readFontVar("--brand-font-body") || readFontVar("--font-body");
2861
+ if (!heading && !body) return null;
2862
+ return {
2863
+ heading: heading || AI_DEFAULT_BRAND.fonts.heading,
2864
+ body: body || AI_DEFAULT_BRAND.fonts.body
2865
+ };
2866
+ }
2867
+ var OVERRIDE_VAR_NAMES = [...BRAND_VAR_NAMES, ...LEGACY_BRAND_VAR_NAMES, ...FONT_VARS.heading, ...FONT_VARS.body];
2868
+ function withOverrideStripped(read) {
2869
+ const root = document.documentElement;
2870
+ const restore = OVERRIDE_VAR_NAMES.map((name) => [name, root.style.getPropertyValue(name)]);
2871
+ for (const name of OVERRIDE_VAR_NAMES) root.style.removeProperty(name);
2872
+ try {
2873
+ return read();
2874
+ } finally {
2875
+ for (const [name, value] of restore) if (value) root.style.setProperty(name, value);
2876
+ }
2877
+ }
2878
+ var TEMPLATE_BRAND_SNAPSHOT = typeof document === "undefined" ? null : withOverrideStripped(deriveTemplateBrandLive);
2879
+ var TEMPLATE_FONTS_SNAPSHOT = typeof document === "undefined" ? null : withOverrideStripped(deriveTemplateFontsLive);
2880
+ function deriveTemplateBrand() {
2881
+ return TEMPLATE_BRAND_SNAPSHOT;
2882
+ }
2883
+ function deriveTemplateFonts() {
2884
+ return TEMPLATE_FONTS_SNAPSHOT;
2885
+ }
2728
2886
  function deriveTemplateButtonStyle() {
2729
2887
  if (typeof document === "undefined") return null;
2730
2888
  const candidates = Array.from(
2731
2889
  document.querySelectorAll('[data-ohw-role="button"]')
2732
- ).filter((el) => !el.closest(`[${CONTAINER_ATTR}]`));
2890
+ ).filter(
2891
+ (el) => !el.closest(`[${CONTAINER_ATTR}]`) && // Whole-card links legitimately carry the button role (serene's clickable treatment
2892
+ // cards) but must never donate the "button look" — a real button holds at most its
2893
+ // one editable label, a card holds a headline, copy and prices.
2894
+ el.querySelectorAll("[data-ohw-editable]").length <= 1
2895
+ );
2733
2896
  const isFilled = (el) => {
2734
2897
  const bg = getComputedStyle(el).backgroundColor;
2735
2898
  if (!bg || bg === "transparent") return false;
@@ -7217,12 +7380,12 @@ var cva = (base, config) => (props) => {
7217
7380
  var import_radix_ui2 = require("radix-ui");
7218
7381
  var import_jsx_runtime5 = require("react/jsx-runtime");
7219
7382
  var toggleVariants = cva(
7220
- "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",
7383
+ "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",
7221
7384
  {
7222
7385
  variants: {
7223
7386
  variant: {
7224
7387
  default: "bg-transparent border-0",
7225
- outline: "border border-input bg-transparent shadow-xs hover:bg-accent hover:text-accent-foreground"
7388
+ outline: "border border-input bg-transparent shadow-xs hover:bg-bridge-accent hover:text-accent-foreground"
7226
7389
  },
7227
7390
  size: {
7228
7391
  default: "px-2 py-1",
@@ -7315,10 +7478,10 @@ var DragHandle = React4.forwardRef(
7315
7478
  type,
7316
7479
  "data-slot": "drag-handle",
7317
7480
  className: cn(
7318
- "inline-flex h-7 w-4 shrink-0 items-center justify-center rounded-md transition-all duration-200 max-h-[30px]",
7481
+ "inline-flex h-7 w-4 shrink-0 items-center justify-center rounded-md transition-all duration-200 max-h-7.5",
7319
7482
  "bg-white border border-transparent text-stone-500 shadow-md cursor-grab",
7320
7483
  "enabled:hover:border enabled:hover:border-stone-200 enabled:hover:text-stone-950",
7321
- "enabled:active:border enabled:active:border-primary enabled:active:bg-primary-50 enabled:active:text-stone-950 enabled:active:shadow enabled:active:cursor-grabbing",
7484
+ "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",
7322
7485
  "disabled:cursor-not-allowed disabled:opacity-40 disabled:text-stone-950 disabled:pointer-events-none",
7323
7486
  className
7324
7487
  ),
@@ -7340,7 +7503,7 @@ var CustomToolbar = React5.forwardRef(({ className, onMouseDown, ...props }, ref
7340
7503
  "data-ohw-toolbar": "",
7341
7504
  className: cn(
7342
7505
  // Figma: bg background, radius 8, gap-1, p-0.5, shadow-md — no border
7343
- "inline-flex h-8 items-center gap-1 rounded-[var(--radius,0.5rem)] bg-background p-0.5 font-sans whitespace-nowrap shadow-md",
7506
+ "inline-flex h-8 items-center gap-1 rounded-(--radius,0.5rem) bg-background p-0.5 font-sans whitespace-nowrap shadow-md",
7344
7507
  className
7345
7508
  ),
7346
7509
  onMouseDown: (e) => {
@@ -7369,7 +7532,7 @@ var CustomToolbarButton = React5.forwardRef(
7369
7532
  type,
7370
7533
  className: cn(
7371
7534
  "inline-flex size-7 shrink-0 items-center justify-center rounded-[calc(var(--radius,0.5rem)-2px)] text-foreground transition-colors",
7372
- active ? "bg-primary text-primary-foreground" : "bg-transparent hover:bg-muted disabled:cursor-not-allowed disabled:text-muted-foreground disabled:opacity-60",
7535
+ active ? "bg-bridge-primary text-primary-foreground" : "bg-transparent hover:bg-muted disabled:cursor-not-allowed disabled:text-muted-foreground disabled:opacity-60",
7373
7536
  className
7374
7537
  ),
7375
7538
  ...props
@@ -8571,13 +8734,13 @@ function FormFieldToolbar({
8571
8734
  ]
8572
8735
  }
8573
8736
  ) }),
8574
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(DropdownMenuContent, { align: "start", sideOffset: 8, className: "min-w-[190px] p-1", children: FIELD_TYPES.map((entry) => {
8737
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(DropdownMenuContent, { align: "start", sideOffset: 8, className: "min-w-47.5 p-1", children: FIELD_TYPES.map((entry) => {
8575
8738
  const Icon = TYPE_ICONS[entry.type];
8576
8739
  return /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(
8577
8740
  DropdownMenuItem,
8578
8741
  {
8579
8742
  onSelect: () => onTypeChange(entry.type),
8580
- className: "rounded-md py-2 text-[13px] " + (entry.type === type ? "bg-primary/10" : ""),
8743
+ className: "rounded-md py-2 text-[13px] " + (entry.type === type ? "bg-bridge-primary/10" : ""),
8581
8744
  children: [
8582
8745
  /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(Icon, { size: 14, strokeWidth: 1.75, className: "shrink-0", "aria-hidden": true }),
8583
8746
  entry.label
@@ -8594,7 +8757,7 @@ function FormFieldToolbar({
8594
8757
  type: "button",
8595
8758
  "aria-pressed": required,
8596
8759
  onClick: onRequiredToggle,
8597
- 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"),
8760
+ 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"),
8598
8761
  "data-ohw-field-required-toggle": "",
8599
8762
  children: [
8600
8763
  /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(import_lucide_react4.Asterisk, { size: 14, strokeWidth: 2, "aria-hidden": true }),
@@ -8614,7 +8777,7 @@ function FormFieldToolbar({
8614
8777
  children: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(import_lucide_react4.MoreHorizontal, { size: 15, "aria-hidden": true })
8615
8778
  }
8616
8779
  ) }),
8617
- /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(DropdownMenuContent, { align: "start", sideOffset: 8, className: "min-w-[170px] p-1", children: [
8780
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(DropdownMenuContent, { align: "start", sideOffset: 8, className: "min-w-42.5 p-1", children: [
8618
8781
  /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(DropdownMenuItem, { onSelect: onDuplicate, className: "rounded-md py-2 text-[13px]", children: [
8619
8782
  /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(import_lucide_react4.Copy, { size: 14, strokeWidth: 1.75, className: "shrink-0", "aria-hidden": true }),
8620
8783
  "Duplicate"
@@ -8634,7 +8797,7 @@ function FieldTypePicker({ onPick }) {
8634
8797
  "div",
8635
8798
  {
8636
8799
  "data-ohw-field-type-picker": "",
8637
- className: "pointer-events-auto grid w-[280px] grid-cols-2 gap-2 rounded-xl border border-border bg-background p-3 shadow-lg",
8800
+ className: "pointer-events-auto grid w-105 grid-cols-3 gap-3 rounded-xl border border-border bg-background p-4 shadow-lg",
8638
8801
  children: FIELD_TYPES.map((entry) => {
8639
8802
  const Icon = TYPE_ICONS[entry.type];
8640
8803
  return /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(
@@ -8642,7 +8805,7 @@ function FieldTypePicker({ onPick }) {
8642
8805
  {
8643
8806
  type: "button",
8644
8807
  onClick: () => onPick(entry.type),
8645
- 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",
8808
+ className: "flex h-26 flex-col items-center justify-center gap-3 rounded-xl border border-border text-[15px] font-medium text-foreground transition-colors hover:border-bridge-primary hover:bg-bridge-primary/5",
8646
8809
  children: [
8647
8810
  /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(Icon, { size: 26, strokeWidth: 1.5, "aria-hidden": true }),
8648
8811
  entry.label
@@ -8668,7 +8831,7 @@ var buttonVariants = cva(
8668
8831
  {
8669
8832
  variants: {
8670
8833
  variant: {
8671
- default: "bg-primary text-primary-foreground hover:opacity-90",
8834
+ default: "bg-bridge-primary text-primary-foreground hover:opacity-90",
8672
8835
  outline: "border border-border bg-background text-foreground shadow-sm hover:bg-muted/80",
8673
8836
  ghost: "min-w-0 px-3 py-2 text-foreground hover:bg-muted/50"
8674
8837
  },
@@ -8763,6 +8926,7 @@ function MediaOverlay({
8763
8926
  (prev) => prev && prev.fullWidth === width && prev.height === height ? prev : { fullWidth: width, height }
8764
8927
  );
8765
8928
  }, [isVideo]);
8929
+ const replaceLabel = isVideo ? "Replace video" : hover.elementType === "bg-image" ? "Replace background" : "Replace image";
8766
8930
  const replaceMode = buttonSize ? replaceButtonMode({ width: rect.width, height: rect.height }, buttonSize) : "none";
8767
8931
  const box = {
8768
8932
  position: "fixed",
@@ -8868,8 +9032,8 @@ function MediaOverlay({
8868
9032
  pointerEvents: hover.hasTextOverlap ? "none" : "auto",
8869
9033
  // Selected: a firm component ring with no wash, so the image reads as chosen rather
8870
9034
  // than hovered. Hover keeps the existing tinted preview.
8871
- boxShadow: selected ? "inset 0 0 0 2px var(--color-primary)" : "inset 0 0 0 1.5px var(--color-primary)",
8872
- background: selected ? "transparent" : "color-mix(in srgb, var(--color-primary) 20%, transparent)"
9035
+ boxShadow: selected ? "inset 0 0 0 2px var(--color-bridge-primary)" : "inset 0 0 0 1.5px var(--color-bridge-primary)",
9036
+ background: selected ? "transparent" : "color-mix(in srgb, var(--color-bridge-primary) 20%, transparent)"
8873
9037
  },
8874
9038
  onClick: () => onSelect && !selected ? onSelect(hover.key) : onReplace(hover.key),
8875
9039
  children: [
@@ -8892,17 +9056,17 @@ function MediaOverlay({
8892
9056
  },
8893
9057
  children: [
8894
9058
  isVideo ? /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_lucide_react5.Film, { size: 14 }) : /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_lucide_react5.ImageIcon, { size: 14 }),
8895
- isVideo ? "Replace video" : "Replace image"
9059
+ replaceLabel
8896
9060
  ]
8897
9061
  }
8898
9062
  ),
8899
- replaceMode === "none" ? null : /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(
9063
+ showChrome && replaceMode !== "none" && /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(
8900
9064
  Button,
8901
9065
  {
8902
9066
  "data-ohw-media-overlay": "",
8903
9067
  variant: "outline",
8904
9068
  size: "sm",
8905
- "aria-label": isVideo ? "Replace video" : "Replace image",
9069
+ "aria-label": replaceLabel,
8906
9070
  className: "gap-1.5 cursor-pointer hover:bg-background",
8907
9071
  style: {
8908
9072
  ...OVERLAY_BUTTON_STYLE,
@@ -8925,7 +9089,7 @@ function MediaOverlay({
8925
9089
  },
8926
9090
  children: [
8927
9091
  isVideo ? /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_lucide_react5.Film, { size: 14 }) : /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_lucide_react5.ImageIcon, { size: 14 }),
8928
- replaceMode === "full" ? isVideo ? "Replace video" : hover.elementType === "bg-image" ? "Replace background" : "Replace image" : null
9092
+ replaceMode === "full" ? replaceLabel : null
8929
9093
  ]
8930
9094
  }
8931
9095
  )
@@ -8963,8 +9127,8 @@ function CarouselOverlay({
8963
9127
  height: rect.height,
8964
9128
  zIndex: 2147483646,
8965
9129
  pointerEvents: "auto",
8966
- boxShadow: "inset 0 0 0 1.5px var(--color-primary)",
8967
- background: "color-mix(in srgb, var(--color-primary) 20%, transparent)"
9130
+ boxShadow: "inset 0 0 0 1.5px var(--color-bridge-primary)",
9131
+ background: "color-mix(in srgb, var(--color-bridge-primary) 20%, transparent)"
8968
9132
  },
8969
9133
  onClick: () => onEdit(hover.key),
8970
9134
  children: /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(
@@ -9802,7 +9966,7 @@ function SectionTreeItem({
9802
9966
  /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
9803
9967
  "div",
9804
9968
  {
9805
- className: "mr-[-1px] h-9 w-2 shrink-0 rounded-bl-sm border-b border-l border-border mb-4",
9969
+ className: "-mr-px h-9 w-2 shrink-0 rounded-bl-sm border-b border-l border-border mb-4",
9806
9970
  "aria-hidden": true
9807
9971
  }
9808
9972
  ),
@@ -9821,7 +9985,7 @@ function SectionTreeItem({
9821
9985
  className: cn(
9822
9986
  "flex h-9 min-w-0 flex-1 items-center gap-2 rounded-md border border-border bg-background p-3",
9823
9987
  interactive && "cursor-pointer hover:bg-muted/30",
9824
- interactive && selected && "border-primary"
9988
+ interactive && selected && "border-bridge-primary"
9825
9989
  ),
9826
9990
  children: [
9827
9991
  /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
@@ -9951,7 +10115,7 @@ function UrlOrPageInput({
9951
10115
  };
9952
10116
  const fieldClassName = cn(
9953
10117
  "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]",
9954
- urlError ? "border-destructive shadow-[0_0_0_1px_var(--ohw-destructive)]" : isFocused ? "border-primary shadow-[0_0_0_1px_var(--ohw-primary)]" : "border-input"
10118
+ 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"
9955
10119
  );
9956
10120
  return /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)("div", { className: "flex w-full flex-col gap-2 p-0", children: [
9957
10121
  /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(Label, { htmlFor: inputId, className: cn(urlError && "text-destructive"), children: "Destination" }),
@@ -13800,6 +13964,7 @@ function readLogoSizeState(content, placement) {
13800
13964
  function getLogoElement(el) {
13801
13965
  const marked = el.closest('[data-ohw-role="logo"], [data-ohw-logo]');
13802
13966
  if (marked) return marked;
13967
+ if (el.closest('[data-ohw-editable="icon"]')) return null;
13803
13968
  const root = el.closest("nav, [data-ohw-nav-root], footer");
13804
13969
  if (!root) return null;
13805
13970
  const anchor = el.closest("a");
@@ -14087,7 +14252,7 @@ function DisplaySwitch({
14087
14252
  onClick: () => onChange(!checked),
14088
14253
  className: cn(
14089
14254
  "relative h-5 w-9 shrink-0 rounded-full transition-colors",
14090
- checked ? "bg-primary" : "bg-primary-50",
14255
+ checked ? "bg-bridge-primary" : "bg-primary-50",
14091
14256
  disabled ? "cursor-default opacity-50" : "cursor-pointer"
14092
14257
  ),
14093
14258
  children: /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(
@@ -14095,7 +14260,7 @@ function DisplaySwitch({
14095
14260
  {
14096
14261
  className: cn(
14097
14262
  "absolute top-0.5 h-4 w-4 rounded-full bg-white transition-all",
14098
- checked ? "left-[1.125rem]" : "left-0.5"
14263
+ checked ? "left-4.5" : "left-0.5"
14099
14264
  )
14100
14265
  }
14101
14266
  )
@@ -15274,7 +15439,9 @@ function collectEditableNodes(extraContent, root = document) {
15274
15439
  return {
15275
15440
  key: el.dataset.ohwKey ?? "",
15276
15441
  type: el.dataset.ohwEditable ?? "text",
15277
- text: el.dataset.ohwEditable === "plain" ? el.innerText ?? "" : el.innerHTML
15442
+ // innerText does not exist on SVG elements (a circular-badge <textPath> is a legitimate
15443
+ // plain editable) — textContent is the value there.
15444
+ text: el.dataset.ohwEditable === "plain" ? el.innerText ?? el.textContent ?? "" : el.innerHTML
15278
15445
  };
15279
15446
  });
15280
15447
  const hrefEls = Array.from(root.querySelectorAll("[data-ohw-href-key]"));
@@ -15711,7 +15878,7 @@ var badgeVariants = cva(
15711
15878
  {
15712
15879
  variants: {
15713
15880
  variant: {
15714
- default: "border-transparent bg-primary text-primary-foreground",
15881
+ default: "border-transparent bg-bridge-primary text-primary-foreground",
15715
15882
  secondary: "border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80",
15716
15883
  destructive: "border-transparent bg-destructive text-destructive-foreground hover:bg-destructive/80",
15717
15884
  outline: "text-foreground"
@@ -15893,21 +16060,10 @@ function parseSchedulingInsertAfter(insertAfter) {
15893
16060
  insertBefore: insertAfter.slice(idx + INSERT_BEFORE_MARKER.length)
15894
16061
  };
15895
16062
  }
15896
- function resolveSchedulingInsert(insertAfter, explicitInsertBefore) {
15897
- const parsed = parseSchedulingInsertAfter(insertAfter);
15898
- const insertBefore = explicitInsertBefore ?? parsed.insertBefore;
15899
- const effectiveInsertAfter = insertBefore && parsed.insertBefore === null ? `${insertAfter}${INSERT_BEFORE_MARKER}${insertBefore}` : insertAfter;
15900
- return { effectiveInsertAfter, insertBefore };
15901
- }
15902
- function getSchedulingMountPoint(insertAfter) {
15903
- const { anchor } = parseSchedulingInsertAfter(insertAfter);
15904
- let anchorEl = document.querySelector(`[data-ohw-section="${anchor}"]`);
15905
- if (!anchorEl && anchor === "scheduling") {
15906
- const widgets = Array.from(document.querySelectorAll('[data-ohw-section^="scheduling-"]'));
15907
- anchorEl = widgets.at(-1) ?? null;
15908
- }
15909
- if (!anchorEl) return null;
15910
- return anchorEl.closest('[data-ohw-section-container="scheduling"]') ?? anchorEl;
16063
+ function resolveEntryAnchor(entry) {
16064
+ if (entry.anchorId !== void 0) return { anchorId: entry.anchorId, beforeId: entry.beforeId ?? null };
16065
+ const parsed = parseSchedulingInsertAfter(entry.insertAfter);
16066
+ return { anchorId: parsed.anchor, beforeId: parsed.insertBefore };
15911
16067
  }
15912
16068
  function schedulingMountDepth(insertAfter) {
15913
16069
  if (!insertAfter.includes(INSERT_BEFORE_MARKER)) return 0;
@@ -15924,8 +16080,7 @@ function getPageSchedulingEntries(raw) {
15924
16080
  }
15925
16081
  }
15926
16082
  function isSchedulingWidgetMissing(entry) {
15927
- const { effectiveInsertAfter } = resolveSchedulingInsert(entry.insertAfter);
15928
- return !document.querySelector(`[data-ohw-section="${schedulingSectionId(effectiveInsertAfter)}"]`);
16083
+ return !document.querySelector(`[data-ohw-section="${schedulingSectionId(entry.insertAfter)}"]`);
15929
16084
  }
15930
16085
  function hasMissingSchedulingWidgets(entries) {
15931
16086
  return entries.some(isSchedulingWidgetMissing);
@@ -15963,17 +16118,18 @@ function initSectionsFromContent(content, removeExisting = false, currentPath =
15963
16118
  } catch {
15964
16119
  }
15965
16120
  }
15966
- function mountSchedulingWidget(insertAfter, notifyOnConnect = false, scheduleId, explicitInsertBefore) {
15967
- const { effectiveInsertAfter, insertBefore } = resolveSchedulingInsert(insertAfter, explicitInsertBefore);
15968
- const sectionId = schedulingSectionId(effectiveInsertAfter);
16121
+ function mountSchedulingWidget(anchorId, notifyOnConnect = false, scheduleId, beforeId, existingWidgetId) {
16122
+ const widgetId = existingWidgetId ?? (beforeId ? `${anchorId}${INSERT_BEFORE_MARKER}${beforeId}` : anchorId);
16123
+ const sectionId = schedulingSectionId(widgetId);
15969
16124
  if (document.querySelector(`[data-ohw-section="${sectionId}"]`)) return false;
15970
- const mountPoint = getSchedulingMountPoint(effectiveInsertAfter);
15971
- if (!mountPoint) return false;
16125
+ const anchorEl = document.querySelector(`[data-ohw-section="${anchorId}"]`);
16126
+ if (!anchorEl) return false;
16127
+ const mountPoint = anchorEl.closest('[data-ohw-section-container="scheduling"]') ?? anchorEl;
15972
16128
  const container = document.createElement("div");
15973
16129
  container.dataset.ohwSectionContainer = "scheduling";
15974
16130
  container.dataset.ohwInstance = sectionId;
15975
- if (insertBefore) {
15976
- const beforeAnchor = document.querySelector(`[data-ohw-section="${insertBefore}"]`);
16131
+ if (beforeId) {
16132
+ const beforeAnchor = document.querySelector(`[data-ohw-section="${beforeId}"]`);
15977
16133
  const beforePoint = beforeAnchor?.closest('[data-ohw-section-container="scheduling"]') ?? beforeAnchor;
15978
16134
  if (!beforePoint) return false;
15979
16135
  beforePoint.insertAdjacentElement("beforebegin", container);
@@ -15984,20 +16140,26 @@ function mountSchedulingWidget(insertAfter, notifyOnConnect = false, scheduleId,
15984
16140
  }
15985
16141
  tail.insertAdjacentElement("afterend", container);
15986
16142
  }
15987
- const root = (0, import_client2.createRoot)(container);
15988
- schedulingRoots.set(container, root);
15989
- (0, import_react_dom3.flushSync)(() => {
15990
- root.render(
15991
- /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
15992
- SchedulingWidget,
15993
- {
15994
- notifyOnConnect,
15995
- initialScheduleId: scheduleId,
15996
- insertAfter: effectiveInsertAfter
15997
- }
15998
- )
15999
- );
16000
- });
16143
+ try {
16144
+ const root = (0, import_client2.createRoot)(container);
16145
+ schedulingRoots.set(container, root);
16146
+ (0, import_react_dom3.flushSync)(() => {
16147
+ root.render(
16148
+ /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
16149
+ SchedulingWidget,
16150
+ {
16151
+ notifyOnConnect,
16152
+ initialScheduleId: scheduleId,
16153
+ insertAfter: widgetId
16154
+ }
16155
+ )
16156
+ );
16157
+ });
16158
+ } catch (err) {
16159
+ console.error("[ow:scheduling] render threw", err);
16160
+ container.remove();
16161
+ return false;
16162
+ }
16001
16163
  const tracker = getSectionsTracker();
16002
16164
  let sections = [];
16003
16165
  try {
@@ -16005,10 +16167,12 @@ function mountSchedulingWidget(insertAfter, notifyOnConnect = false, scheduleId,
16005
16167
  } catch {
16006
16168
  }
16007
16169
  const inEditor = typeof window !== "undefined" && window.self !== window.top;
16008
- if (inEditor && !sections.find((s) => s.type === "scheduling" && s.insertAfter === effectiveInsertAfter)) {
16170
+ if (inEditor && !sections.find((s) => s.type === "scheduling" && s.insertAfter === widgetId)) {
16009
16171
  sections.push({
16010
16172
  type: "scheduling",
16011
- insertAfter: effectiveInsertAfter,
16173
+ insertAfter: widgetId,
16174
+ anchorId,
16175
+ beforeId: beforeId ?? null,
16012
16176
  pagePath: window.location.pathname,
16013
16177
  ...scheduleId ? { scheduleId } : {}
16014
16178
  });
@@ -16022,7 +16186,8 @@ function mountSchedulingEntries(entries, notifyOnConnect = false) {
16022
16186
  for (let i = pending.length - 1; i >= 0; i--) {
16023
16187
  const entry = pending[i];
16024
16188
  const shouldNotify = typeof notifyOnConnect === "function" ? notifyOnConnect(entry) : notifyOnConnect;
16025
- if (mountSchedulingWidget(entry.insertAfter, shouldNotify, entry.scheduleId ?? null)) {
16189
+ const { anchorId, beforeId } = resolveEntryAnchor(entry);
16190
+ if (mountSchedulingWidget(anchorId, shouldNotify, entry.scheduleId ?? null, beforeId, entry.insertAfter)) {
16026
16191
  pending.splice(i, 1);
16027
16192
  }
16028
16193
  }
@@ -16186,6 +16351,11 @@ function applyLinkByKey(key, val) {
16186
16351
  hrefAnchors.forEach((el) => applyLinkHref(el, val));
16187
16352
  }
16188
16353
  }
16354
+ function isInsideLinkEditor(target) {
16355
+ return Boolean(
16356
+ 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"]')
16357
+ );
16358
+ }
16189
16359
  function isInsideFloatingPanel(target) {
16190
16360
  return Boolean(target.closest("[data-ohw-floating-panel]"));
16191
16361
  }
@@ -16193,11 +16363,6 @@ function isPointOverFloatingPanel(clientX, clientY) {
16193
16363
  const el = document.elementFromPoint(clientX, clientY);
16194
16364
  return Boolean(el instanceof Element && el.closest("[data-ohw-floating-panel]"));
16195
16365
  }
16196
- function isInsideLinkEditor(target) {
16197
- return Boolean(
16198
- 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"]')
16199
- );
16200
- }
16201
16366
  function getHrefKeyFromElement(el) {
16202
16367
  if (!el) return null;
16203
16368
  const anchor = el.closest("[data-ohw-href-key]");
@@ -16462,7 +16627,7 @@ function getNavigationSelectionParent(el) {
16462
16627
  if (!isFooterLinksContainer(el) && (el.hasAttribute("data-ohw-footer-col") || el.hasAttribute("data-ohw-footer-column") || isInferredFooterGroup2(el))) {
16463
16628
  return getFooterLinksContainer();
16464
16629
  }
16465
- if (el.hasAttribute("data-ohw-nav-container") || el.hasAttribute("data-ohw-nav-drawer") || isFooterLinksContainer(el)) {
16630
+ 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)) {
16466
16631
  return getNavigationRoot(el);
16467
16632
  }
16468
16633
  return null;
@@ -16678,7 +16843,6 @@ var ICONS = {
16678
16843
  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"/>',
16679
16844
  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"/>'
16680
16845
  };
16681
- var HEIGHT_SETTLE_DELAYS = [150, 400, 800];
16682
16846
  var SELECTION_CHROME_GAP2 = 4;
16683
16847
  var TOOLBAR_STROKE_GAP2 = 4;
16684
16848
  var TOOLBAR_OFFSET_FROM_ELEMENT = SELECTION_CHROME_GAP2 + TOOLBAR_STROKE_GAP2;
@@ -16775,6 +16939,10 @@ function getIframeVisibleClip(parentScroll) {
16775
16939
  );
16776
16940
  return { top: clipTop, bottom: Math.max(clipTop, clipBottom) };
16777
16941
  }
16942
+ function isRectOutsideClip(rect, clip) {
16943
+ if (!clip) return false;
16944
+ return rect.bottom < clip.top || rect.top > clip.bottom;
16945
+ }
16778
16946
  function applyVisibleViewport(el, parentScroll) {
16779
16947
  const clip = getIframeVisibleClip(parentScroll);
16780
16948
  const top = clip?.top ?? 0;
@@ -17058,6 +17226,7 @@ function StateToggle({
17058
17226
  );
17059
17227
  }
17060
17228
  var contentCache = /* @__PURE__ */ new Map();
17229
+ var fetchedContentPaths = /* @__PURE__ */ new Set();
17061
17230
  var brandingCache = /* @__PURE__ */ new Map();
17062
17231
  var OHW_LOADER_STYLE = {
17063
17232
  position: "fixed",
@@ -17606,13 +17775,6 @@ function OhhwellsBridge() {
17606
17775
  const [isItemDragging, setIsItemDragging] = (0, import_react17.useState)(false);
17607
17776
  const [isFooterFrameSelection, setIsFooterFrameSelection] = (0, import_react17.useState)(false);
17608
17777
  isFooterFrameSelectionRef.current = isFooterFrameSelection;
17609
- const [floatingPanel, setFloatingPanel] = (0, import_react17.useState)(null);
17610
- const floatingPanelOpenRef = (0, import_react17.useRef)(false);
17611
- floatingPanelOpenRef.current = floatingPanel !== null;
17612
- const [floatingPanelPos, setFloatingPanelPos] = (0, import_react17.useState)(null);
17613
- const [logoSizeDraft, setLogoSizeDraft] = (0, import_react17.useState)(null);
17614
- const [editorViewport, setEditorViewport] = (0, import_react17.useState)("desktop");
17615
- const [parentScrollSnap, setParentScrollSnap] = (0, import_react17.useState)(null);
17616
17778
  const [navDropdownPreviewOpen, setNavDropdownPreviewOpen] = (0, import_react17.useState)(null);
17617
17779
  const [footerHeadingVisible, setFooterHeadingVisible] = (0, import_react17.useState)(null);
17618
17780
  const footerDragRef = (0, import_react17.useRef)(null);
@@ -17630,6 +17792,13 @@ function OhhwellsBridge() {
17630
17792
  const brandKitRef = (0, import_react17.useRef)("");
17631
17793
  const stylesRef = (0, import_react17.useRef)("");
17632
17794
  const pendingDeleteUndoRef = (0, import_react17.useRef)(null);
17795
+ const [floatingPanel, setFloatingPanel] = (0, import_react17.useState)(null);
17796
+ const floatingPanelOpenRef = (0, import_react17.useRef)(false);
17797
+ const setFloatingPanelRef = (0, import_react17.useRef)(setFloatingPanel);
17798
+ const [floatingPanelPos, setFloatingPanelPos] = (0, import_react17.useState)(null);
17799
+ const [logoSizeDraft, setLogoSizeDraft] = (0, import_react17.useState)(null);
17800
+ const [editorViewport, setEditorViewport] = (0, import_react17.useState)("desktop");
17801
+ const [parentScrollSnap, setParentScrollSnap] = (0, import_react17.useState)(null);
17633
17802
  const [sitePages, setSitePages] = (0, import_react17.useState)([]);
17634
17803
  const [sectionsByPath, setSectionsByPath] = (0, import_react17.useState)({});
17635
17804
  const sectionsPrefetchGenRef = (0, import_react17.useRef)(0);
@@ -17638,7 +17807,18 @@ function OhhwellsBridge() {
17638
17807
  const linkPopoverOpenRef = (0, import_react17.useRef)(false);
17639
17808
  const linkPopoverGraceUntilRef = (0, import_react17.useRef)(0);
17640
17809
  setLinkPopoverRef.current = setLinkPopover;
17810
+ setFloatingPanelRef.current = setFloatingPanel;
17641
17811
  linkPopoverSessionRef.current = linkPopover;
17812
+ floatingPanelOpenRef.current = Boolean(floatingPanel);
17813
+ (0, import_react17.useEffect)(() => {
17814
+ const syncViewport = () => {
17815
+ const next = window.innerWidth <= 480 ? "mobile" : "desktop";
17816
+ setEditorViewport((prev) => prev === next ? prev : next);
17817
+ };
17818
+ syncViewport();
17819
+ window.addEventListener("resize", syncViewport);
17820
+ return () => window.removeEventListener("resize", syncViewport);
17821
+ }, []);
17642
17822
  const {
17643
17823
  navDragRef,
17644
17824
  navPointerDragRef,
@@ -18979,6 +19159,7 @@ function OhhwellsBridge() {
18979
19159
  }
18980
19160
  if (typeof content[STYLE_STORE_KEY] === "string") {
18981
19161
  stylesRef.current = content[STYLE_STORE_KEY];
19162
+ document.querySelectorAll('[data-ohw-editable="form"]').forEach((f) => markFormFields(f));
18982
19163
  applyStylesToDom(parseStyleStore(content[STYLE_STORE_KEY]));
18983
19164
  }
18984
19165
  applyBrandChrome(content);
@@ -18986,11 +19167,11 @@ function OhhwellsBridge() {
18986
19167
  for (const [key, val] of Object.entries(content)) {
18987
19168
  if (key === "__ohw_sections") continue;
18988
19169
  if (key === AI_SECTIONS_KEY) continue;
19170
+ if (key === LOGO_PLACEHOLDER_KEY) continue;
19171
+ if (LOGO_IMAGE_KEYS.includes(key)) continue;
18989
19172
  if (key === BRAND_KIT_KEY) continue;
18990
19173
  if (key === STYLE_STORE_KEY) continue;
18991
19174
  if (BRAND_CHROME_KEYS.has(key)) continue;
18992
- if (key === LOGO_PLACEHOLDER_KEY) continue;
18993
- if (LOGO_IMAGE_KEYS.includes(key)) continue;
18994
19175
  if (applyVideoSettingNode(key, val)) continue;
18995
19176
  if (applyCarouselNode(key, val)) continue;
18996
19177
  document.querySelectorAll(`[data-ohw-key="${key}"]`).forEach((el) => {
@@ -19061,7 +19242,9 @@ function OhhwellsBridge() {
19061
19242
  let cancelled = false;
19062
19243
  setFetchState("loading");
19063
19244
  const apiUrl = process.env.NEXT_PUBLIC_FLOWOPS_API_URL ?? "https://flowops-backend-staging-2yfdh7pwpq-as.a.run.app";
19064
- fetch(`${apiUrl}/api/public/sites/${subdomain}/content`, { cache: "no-store" }).then((r2) => r2.ok ? r2.json() : null).then((data) => {
19245
+ const initialPath = pathname;
19246
+ fetchedContentPaths.add(`${subdomain}::${initialPath}`);
19247
+ fetch(`${apiUrl}/api/public/sites/${subdomain}/content?path=${encodeURIComponent(initialPath)}`, { cache: "no-store" }).then((r2) => r2.ok ? r2.json() : null).then((data) => {
19065
19248
  if (cancelled) return;
19066
19249
  const content = data?.content ?? {};
19067
19250
  const branding = Boolean(data?.showBranding);
@@ -19170,7 +19353,9 @@ function OhhwellsBridge() {
19170
19353
  }, [isEditMode]);
19171
19354
  (0, import_react17.useEffect)(() => {
19172
19355
  if (isEditMode || fetchState !== "done") return;
19356
+ console.log("env", process.env.NEXT_PUBLIC_FLOWOPS_API_URL);
19173
19357
  const apiUrl = process.env.NEXT_PUBLIC_FLOWOPS_API_URL ?? "https://flowops-backend-staging-2yfdh7pwpq-as.a.run.app";
19358
+ console.log({ apiUrl, subdomain });
19174
19359
  bindPublishedForms(apiUrl, subdomain, contentCache.get(subdomain) ?? {});
19175
19360
  }, [isEditMode, fetchState, subdomain]);
19176
19361
  (0, import_react17.useEffect)(() => {
@@ -19195,16 +19380,17 @@ function OhhwellsBridge() {
19195
19380
  applyAiSectionsToDom(parseAiSectionsState(content[AI_SECTIONS_KEY]));
19196
19381
  }
19197
19382
  if (typeof content[STYLE_STORE_KEY] === "string") {
19383
+ document.querySelectorAll('[data-ohw-editable="form"]').forEach((f) => markFormFields(f));
19198
19384
  applyStylesToDom(parseStyleStore(content[STYLE_STORE_KEY]));
19199
19385
  }
19200
19386
  for (const [key, val] of Object.entries(content)) {
19201
19387
  if (key === "__ohw_sections") continue;
19202
19388
  if (key === AI_SECTIONS_KEY) continue;
19389
+ if (key === LOGO_PLACEHOLDER_KEY) continue;
19390
+ if (LOGO_IMAGE_KEYS.includes(key)) continue;
19203
19391
  if (key === BRAND_KIT_KEY) continue;
19204
19392
  if (key === STYLE_STORE_KEY) continue;
19205
19393
  if (BRAND_CHROME_KEYS.has(key)) continue;
19206
- if (key === LOGO_PLACEHOLDER_KEY) continue;
19207
- if (LOGO_IMAGE_KEYS.includes(key)) continue;
19208
19394
  if (applyVideoSettingNode(key, val)) continue;
19209
19395
  if (applyCarouselNode(key, val)) continue;
19210
19396
  document.querySelectorAll(`[data-ohw-key="${key}"]`).forEach((el) => {
@@ -19255,6 +19441,17 @@ function OhhwellsBridge() {
19255
19441
  debounceTimer = setTimeout(applyFromCache, 150);
19256
19442
  };
19257
19443
  applyFromCache();
19444
+ const pathCacheKey = `${subdomain}::${pathname}`;
19445
+ if (!fetchedContentPaths.has(pathCacheKey)) {
19446
+ fetchedContentPaths.add(pathCacheKey);
19447
+ const apiUrl = process.env.NEXT_PUBLIC_FLOWOPS_API_URL ?? "https://flowops-backend-staging-2yfdh7pwpq-as.a.run.app";
19448
+ fetch(`${apiUrl}/api/public/sites/${subdomain}/content?path=${encodeURIComponent(pathname)}`, { cache: "no-store" }).then((r2) => r2.ok ? r2.json() : null).then((data) => {
19449
+ if (!data?.content) return;
19450
+ contentCache.set(subdomain, data.content);
19451
+ applyFromCache();
19452
+ }).catch(() => {
19453
+ });
19454
+ }
19258
19455
  observer = new MutationObserver(scheduleApply);
19259
19456
  observer.observe(document.body, { childList: true, subtree: true });
19260
19457
  return () => {
@@ -19390,26 +19587,11 @@ function OhhwellsBridge() {
19390
19587
  const t2 = setTimeout(measure, 500);
19391
19588
  const ro = new ResizeObserver(schedule);
19392
19589
  ro.observe(document.body);
19393
- let lastWidth = window.innerWidth;
19394
- let resizeTimers = [];
19395
- const clearResizeTimers = () => {
19396
- resizeTimers.forEach(clearTimeout);
19397
- resizeTimers = [];
19398
- };
19399
- const handleResize = () => {
19400
- if (window.innerWidth === lastWidth) return;
19401
- lastWidth = window.innerWidth;
19402
- clearResizeTimers();
19403
- resizeTimers = HEIGHT_SETTLE_DELAYS.map((delay) => setTimeout(measure, delay));
19404
- };
19405
- window.addEventListener("resize", handleResize);
19406
19590
  return () => {
19407
19591
  clearTimeout(t1);
19408
19592
  clearTimeout(t2);
19409
19593
  if (raf != null) cancelAnimationFrame(raf);
19410
19594
  ro.disconnect();
19411
- clearResizeTimers();
19412
- window.removeEventListener("resize", handleResize);
19413
19595
  };
19414
19596
  }, [pathname, isEditMode, postToParent2]);
19415
19597
  (0, import_react17.useEffect)(() => {
@@ -19640,6 +19822,10 @@ function OhhwellsBridge() {
19640
19822
  [data-ohw-editable-state], [data-ohw-editable-state] * { pointer-events: none !important; }
19641
19823
  [data-ohw-editable-state][data-ohw-active-state] [data-ohw-editable] { pointer-events: auto !important; }
19642
19824
  [data-ohw-editable-state][data-ohw-active-state][data-ohw-editable] { pointer-events: auto !important; }
19825
+ /* SVG hit-tests painted glyphs only, so an editable curved-text badge answered clicks
19826
+ on its letter strokes alone (and a spinning one barely at all). In edit mode the
19827
+ whole box takes the click; the handler resolves it to the editable inside. */
19828
+ svg:has([data-ohw-editable]) { pointer-events: bounding-box !important; }
19643
19829
  `;
19644
19830
  if (!existing) {
19645
19831
  const base = document.createElement("style");
@@ -19678,9 +19864,6 @@ function OhhwellsBridge() {
19678
19864
  if (target.closest("[data-ohw-state-toggle]")) return;
19679
19865
  if (target.closest("[data-ohw-max-badge]")) return;
19680
19866
  if (isInsideLinkEditor(target)) return;
19681
- if (selectedMediaElRef.current && !selectedMediaElRef.current.contains(target) && !target.closest("[data-ohw-media-overlay], [data-ohw-edit-chrome], [data-ohw-item-interaction]")) {
19682
- clearMediaSelectionRef.current();
19683
- }
19684
19867
  if (isInsideFloatingPanel(target)) return;
19685
19868
  if (target.closest("[data-ohw-form-toolbar]")) return;
19686
19869
  if (target.closest(
@@ -19688,6 +19871,9 @@ function OhhwellsBridge() {
19688
19871
  )) {
19689
19872
  return;
19690
19873
  }
19874
+ if (selectedMediaElRef.current && !selectedMediaElRef.current.contains(target) && !target.closest("[data-ohw-media-overlay], [data-ohw-edit-chrome], [data-ohw-item-interaction]")) {
19875
+ clearMediaSelectionRef.current();
19876
+ }
19691
19877
  {
19692
19878
  const formEl = getFormElement(target);
19693
19879
  const onSuccessText = target.closest(`[${SUCCESS_TEXT_ATTR}]`);
@@ -19810,7 +19996,19 @@ function OhhwellsBridge() {
19810
19996
  openLogoSizePanelRef.current(logoEl);
19811
19997
  return;
19812
19998
  }
19813
- const editable = target.closest("[data-ohw-editable]");
19999
+ let editable = target.closest("[data-ohw-editable]");
20000
+ if (!editable && target instanceof SVGElement) {
20001
+ const inner = target.closest("svg")?.querySelectorAll("[data-ohw-editable]");
20002
+ if (inner && inner.length === 1) editable = inner[0];
20003
+ }
20004
+ if (editable && !(editable instanceof HTMLElement)) {
20005
+ e.preventDefault();
20006
+ e.stopPropagation();
20007
+ deselectRef.current();
20008
+ deactivateRef.current();
20009
+ aiSectionApiRef.current?.selectFromElement(editable);
20010
+ return;
20011
+ }
19814
20012
  if (editable) {
19815
20013
  if (editable.dataset.ohwEditable === "link") {
19816
20014
  e.preventDefault();
@@ -19839,14 +20037,6 @@ function OhhwellsBridge() {
19839
20037
  }
19840
20038
  const clickedButton = findClosestButtonLike(target);
19841
20039
  const buttonOnMedia = Boolean(clickedButton && isMediaEditable(editable) && editable.contains(clickedButton));
19842
- console.log("[click-debug]", {
19843
- editableType: editable.dataset.ohwEditable,
19844
- editableTag: editable.tagName,
19845
- targetTag: target.tagName,
19846
- clickedButtonTag: clickedButton?.tagName ?? null,
19847
- buttonOnMedia,
19848
- isMediaEditableEditable: isMediaEditable(editable)
19849
- });
19850
20040
  if (isMediaEditable(editable) && !buttonOnMedia) {
19851
20041
  e.preventDefault();
19852
20042
  e.stopPropagation();
@@ -19873,11 +20063,6 @@ function OhhwellsBridge() {
19873
20063
  const hrefLookupTarget = buttonOnMedia ? clickedButton : editable;
19874
20064
  const hrefCtx = getHrefKeyFromElement(hrefLookupTarget);
19875
20065
  const navAnchor = hrefCtx ? getNavigationItemAnchor(hrefCtx.anchor) : null;
19876
- console.log("[click-debug 2]", {
19877
- hrefLookupTargetTag: hrefLookupTarget.tagName,
19878
- hrefCtx: hrefCtx ? { key: hrefCtx.key } : null,
19879
- navAnchorTag: navAnchor?.tagName ?? null
19880
- });
19881
20066
  if (navAnchor) {
19882
20067
  e.preventDefault();
19883
20068
  e.stopPropagation();
@@ -20047,6 +20232,9 @@ function OhhwellsBridge() {
20047
20232
  setHoveredItemRect(null);
20048
20233
  hoveredNavContainerRef.current = null;
20049
20234
  setHoveredNavContainerRect(null);
20235
+ siblingHintElRef.current = null;
20236
+ setSiblingHintRect(null);
20237
+ setSiblingHintRects([]);
20050
20238
  return;
20051
20239
  }
20052
20240
  {
@@ -20167,7 +20355,6 @@ function OhhwellsBridge() {
20167
20355
  hoveredNavContainerRef.current = null;
20168
20356
  setHoveredNavContainerRect(null);
20169
20357
  hoveredItemElRef.current = editable;
20170
- setHoveredItemRect(isSelectedForHoverRef.current(editable) ? null : editable.getBoundingClientRect());
20171
20358
  }
20172
20359
  }
20173
20360
  }
@@ -20464,7 +20651,7 @@ function OhhwellsBridge() {
20464
20651
  }
20465
20652
  };
20466
20653
  const probeImageAt = (clientX, clientY, isDragOver = false, fromParentViewport = false) => {
20467
- if (linkPopoverOpenRef.current) {
20654
+ if (linkPopoverOpenRef.current || floatingPanelOpenRef.current && isPointOverFloatingPanel(clientX, clientY)) {
20468
20655
  if (hoveredImageRef.current) {
20469
20656
  hoveredImageRef.current = null;
20470
20657
  hoveredImageHasTextOverlapRef.current = false;
@@ -20553,6 +20740,15 @@ function OhhwellsBridge() {
20553
20740
  }
20554
20741
  return;
20555
20742
  }
20743
+ const cardHit = !isDragOver && Array.from(document.querySelectorAll(".card, [data-ohw-card]")).some((card) => {
20744
+ if (card.contains(imgEl)) return false;
20745
+ const r2 = card.getBoundingClientRect();
20746
+ return x2 >= r2.left && x2 <= r2.right && y2 >= r2.top && y2 <= r2.bottom;
20747
+ });
20748
+ if (cardHit) {
20749
+ dismissImageHover();
20750
+ return;
20751
+ }
20556
20752
  const topEl = document.elementFromPoint(x2, y2);
20557
20753
  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"]')) {
20558
20754
  if (hoveredImageRef.current) {
@@ -20828,8 +21024,7 @@ function OhhwellsBridge() {
20828
21024
  };
20829
21025
  const handleMouseMove = (e) => {
20830
21026
  const { clientX, clientY } = e;
20831
- if (pointOwnedByFloatingPanel(clientX, clientY)) return;
20832
- if (isOverEditorChrome(clientX, clientY)) {
21027
+ if (document.documentElement.hasAttribute("data-ohw-panel-dragging") || floatingPanelOpenRef.current && isPointOverFloatingPanel(clientX, clientY) || isOverEditorChrome(clientX, clientY)) {
20833
21028
  document.querySelectorAll("[data-ohw-hovered]").forEach((el) => el.removeAttribute("data-ohw-hovered"));
20834
21029
  formHoverElRef.current = null;
20835
21030
  setFormHoverRect(null);
@@ -20837,6 +21032,12 @@ function OhhwellsBridge() {
20837
21032
  setHoveredItemRect(null);
20838
21033
  hoveredNavContainerRef.current = null;
20839
21034
  setHoveredNavContainerRect(null);
21035
+ siblingHintElRef.current = null;
21036
+ setSiblingHintRect(null);
21037
+ setSiblingHintRects([]);
21038
+ dismissImageHover();
21039
+ clearImageHover();
21040
+ setSectionGap(null);
20840
21041
  return;
20841
21042
  }
20842
21043
  if (probeSocialsRowAt(clientX, clientY)) return;
@@ -20848,7 +21049,11 @@ function OhhwellsBridge() {
20848
21049
  if (e.data?.type !== "ow:pointer-sync") return;
20849
21050
  const { clientX, clientY } = e.data;
20850
21051
  if (typeof clientX !== "number" || typeof clientY !== "number") return;
20851
- if (pointOwnedByFloatingPanel(clientX, clientY)) return;
21052
+ if (document.documentElement.hasAttribute("data-ohw-panel-dragging") || floatingPanelOpenRef.current && isPointOverFloatingPanel(clientX, clientY)) {
21053
+ dismissImageHover();
21054
+ clearImageHover();
21055
+ return;
21056
+ }
20852
21057
  if (probeSocialsRowAt(clientX, clientY)) return;
20853
21058
  probeSectionGapAt(clientX, clientY);
20854
21059
  probeImageAt(clientX, clientY);
@@ -21187,6 +21392,7 @@ function OhhwellsBridge() {
21187
21392
  }
21188
21393
  if (typeof content[STYLE_STORE_KEY] === "string") {
21189
21394
  stylesRef.current = content[STYLE_STORE_KEY];
21395
+ document.querySelectorAll('[data-ohw-editable="form"]').forEach((f) => markFormFields(f));
21190
21396
  applyStylesToDom(parseStyleStore(content[STYLE_STORE_KEY]));
21191
21397
  }
21192
21398
  applyBrandChrome(content);
@@ -21198,11 +21404,11 @@ function OhhwellsBridge() {
21198
21404
  continue;
21199
21405
  }
21200
21406
  if (key === AI_SECTIONS_KEY) continue;
21407
+ if (key === LOGO_PLACEHOLDER_KEY) continue;
21408
+ if (LOGO_IMAGE_KEYS.includes(key) && !String(val ?? "").trim()) continue;
21201
21409
  if (key === BRAND_KIT_KEY) continue;
21202
21410
  if (key === STYLE_STORE_KEY) continue;
21203
21411
  if (BRAND_CHROME_KEYS.has(key)) continue;
21204
- if (key === LOGO_PLACEHOLDER_KEY) continue;
21205
- if (LOGO_IMAGE_KEYS.includes(key) && !String(val ?? "").trim()) continue;
21206
21412
  if (applyVideoSettingNode(key, val)) continue;
21207
21413
  if (applyCarouselNode(key, val)) continue;
21208
21414
  document.querySelectorAll(`[data-ohw-key="${key}"]`).forEach((el) => {
@@ -21423,10 +21629,26 @@ function OhhwellsBridge() {
21423
21629
  const handleGetBrand = (e) => {
21424
21630
  if (e.data?.type !== "ow:get-brand") return;
21425
21631
  const template = deriveTemplateBrand();
21426
- const value = brandKitRef.current || (template ? JSON.stringify(template) : "");
21632
+ const fallback = template ?? (() => {
21633
+ const fonts = deriveTemplateFonts();
21634
+ return fonts ? { palette: AI_DEFAULT_BRAND.palette, fonts } : null;
21635
+ })();
21636
+ const value = brandKitRef.current || (fallback ? JSON.stringify(fallback) : "");
21427
21637
  postToParentRef.current({ type: "ow:brand-value", value });
21428
21638
  };
21429
21639
  window.addEventListener("message", handleGetBrand);
21640
+ const handleGetTemplateFonts = (e) => {
21641
+ if (e.data?.type !== "ow:get-template-fonts") return;
21642
+ const fonts = deriveTemplateFonts();
21643
+ postToParentRef.current({ type: "ow:template-fonts-value", value: fonts ? JSON.stringify(fonts) : "" });
21644
+ };
21645
+ window.addEventListener("message", handleGetTemplateFonts);
21646
+ const handleGetTemplateBrand = (e) => {
21647
+ if (e.data?.type !== "ow:get-template-brand") return;
21648
+ const brand = deriveTemplateBrand();
21649
+ postToParentRef.current({ type: "ow:template-brand-value", value: brand ? JSON.stringify(brand) : "" });
21650
+ };
21651
+ window.addEventListener("message", handleGetTemplateBrand);
21430
21652
  const handleMoveSection = (e) => {
21431
21653
  if (e.data?.type !== "ow:move-section") return;
21432
21654
  const instanceId = typeof e.data.instanceId === "string" ? e.data.instanceId : "";
@@ -21619,6 +21841,12 @@ function OhhwellsBridge() {
21619
21841
  closeLinkPopoverRef.current();
21620
21842
  return;
21621
21843
  }
21844
+ if (floatingPanelOpenRef.current) {
21845
+ setFloatingPanelRef.current(null);
21846
+ deselectRef.current();
21847
+ deactivateRef.current();
21848
+ return;
21849
+ }
21622
21850
  deselectRef.current();
21623
21851
  deactivateRef.current();
21624
21852
  clearMediaSelectionRef.current();
@@ -21907,8 +22135,12 @@ function OhhwellsBridge() {
21907
22135
  if (inserted) {
21908
22136
  const tracker = getSectionsTracker();
21909
22137
  postToParentRef.current({ type: "ow:change", nodes: [{ key: "__ohw_sections", text: tracker.textContent ?? "[]" }] });
21910
- const h = document.body.scrollHeight;
21911
- if (h > 50) postToParentRef.current({ type: "ow:height", height: h });
22138
+ const reportHeight = () => {
22139
+ const h = document.body.scrollHeight;
22140
+ if (h > 50) postToParentRef.current({ type: "ow:height", height: h });
22141
+ };
22142
+ reportHeight();
22143
+ setTimeout(reportHeight, 500);
21912
22144
  }
21913
22145
  };
21914
22146
  const handleSwitchSchedule = (e) => {
@@ -22085,14 +22317,20 @@ function OhhwellsBridge() {
22085
22317
  };
22086
22318
  const applyToolbarPos = (rect) => {
22087
22319
  const ps = parentScrollRef.current;
22320
+ const hidden = isRectOutsideClip(rect, getIframeVisibleClip(ps));
22088
22321
  const measuredW = toolbarElRef.current?.offsetWidth || 330;
22089
22322
  if (toolbarElRef.current) {
22090
- const { top, left, transform } = calcToolbarPos(rect, ps, measuredW);
22091
- toolbarElRef.current.style.top = `${top}px`;
22092
- toolbarElRef.current.style.left = `${left}px`;
22093
- toolbarElRef.current.style.transform = transform;
22323
+ toolbarElRef.current.style.display = hidden ? "none" : "";
22324
+ if (!hidden) {
22325
+ const { top, left, transform } = calcToolbarPos(rect, ps, measuredW);
22326
+ toolbarElRef.current.style.top = `${top}px`;
22327
+ toolbarElRef.current.style.left = `${left}px`;
22328
+ toolbarElRef.current.style.transform = transform;
22329
+ }
22094
22330
  }
22095
22331
  if (glowElRef.current) {
22332
+ glowElRef.current.style.display = hidden ? "none" : "";
22333
+ if (hidden) return;
22096
22334
  const GAP = SELECTION_CHROME_GAP2;
22097
22335
  glowElRef.current.style.top = `${rect.top - GAP}px`;
22098
22336
  glowElRef.current.style.left = `${rect.left - GAP}px`;
@@ -22323,15 +22561,17 @@ function OhhwellsBridge() {
22323
22561
  window.removeEventListener("message", handleAiSetBrand);
22324
22562
  window.removeEventListener("message", handleAiSetStyles);
22325
22563
  window.removeEventListener("message", handleGetBrand);
22564
+ window.removeEventListener("message", handleGetTemplateFonts);
22565
+ window.removeEventListener("message", handleGetTemplateBrand);
22326
22566
  window.removeEventListener("message", handleMoveSection);
22327
22567
  window.removeEventListener("message", handlePanelDragging);
22328
22568
  window.removeEventListener("message", handleDeleteSection);
22329
22569
  window.removeEventListener("message", handleDuplicateSection);
22330
22570
  window.removeEventListener("message", handleDeactivate);
22331
- document.documentElement.removeAttribute("data-ohw-panel-dragging");
22332
22571
  window.removeEventListener("message", handleToastAction);
22333
22572
  window.removeEventListener("message", handleFormCount);
22334
22573
  window.removeEventListener("message", handleUiEscape);
22574
+ document.documentElement.removeAttribute("data-ohw-panel-dragging");
22335
22575
  autoSaveTimers.current.forEach(clearTimeout);
22336
22576
  autoSaveTimers.current.clear();
22337
22577
  if (imageUnhoverTimerRef.current) clearTimeout(imageUnhoverTimerRef.current);
@@ -22534,7 +22774,7 @@ function OhhwellsBridge() {
22534
22774
  postToParent2({
22535
22775
  type: "ow:ready",
22536
22776
  version: "1",
22537
- bridgeVersion: "0.1.106",
22777
+ bridgeVersion: "0.1.108",
22538
22778
  path: pathname,
22539
22779
  nodes: collectEditableNodes(editContentRef.current),
22540
22780
  sections
@@ -23150,7 +23390,7 @@ function OhhwellsBridge() {
23150
23390
  "span",
23151
23391
  {
23152
23392
  "data-ohw-form-count": "",
23153
- 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",
23393
+ 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",
23154
23394
  children: formPickCount
23155
23395
  }
23156
23396
  )
@@ -23321,7 +23561,7 @@ function OhhwellsBridge() {
23321
23561
  ) : void 0
23322
23562
  }
23323
23563
  ),
23324
- toolbarRect && toolbarVariant === "rich-text" && !linkPopover && /* @__PURE__ */ (0, import_jsx_runtime33.jsxs)(import_jsx_runtime33.Fragment, { children: [
23564
+ toolbarRect && toolbarVariant === "rich-text" && !linkPopover && !isRectOutsideClip(toolbarRect, getIframeVisibleClip(parentScrollRef.current)) && /* @__PURE__ */ (0, import_jsx_runtime33.jsxs)(import_jsx_runtime33.Fragment, { children: [
23325
23565
  /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
23326
23566
  EditGlowChrome,
23327
23567
  {
@@ -23387,11 +23627,11 @@ function OhhwellsBridge() {
23387
23627
  className: "fixed left-0 w-full z-2147483646 flex items-center pointer-events-none",
23388
23628
  style: { top: sectionGap.y, transform: "translateY(-50%)" },
23389
23629
  children: [
23390
- /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("div", { className: "flex-1 bg-primary", style: { height: 3 } }),
23630
+ /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("div", { className: "flex-1 bg-bridge-primary", style: { height: 3 } }),
23391
23631
  /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
23392
23632
  Badge,
23393
23633
  {
23394
- 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",
23634
+ 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",
23395
23635
  onClick: () => {
23396
23636
  window.parent.postMessage(
23397
23637
  {
@@ -23405,7 +23645,7 @@ function OhhwellsBridge() {
23405
23645
  children: "Add Section"
23406
23646
  }
23407
23647
  ),
23408
- /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("div", { className: "flex-1 bg-primary", style: { height: 3 } })
23648
+ /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("div", { className: "flex-1 bg-bridge-primary", style: { height: 3 } })
23409
23649
  ]
23410
23650
  }
23411
23651
  ),
@@ -23453,6 +23693,59 @@ function OhhwellsBridge() {
23453
23693
  ) : null
23454
23694
  ] });
23455
23695
  }
23696
+
23697
+ // src/ui/EmptySection.tsx
23698
+ var import_link = __toESM(require("next/link"), 1);
23699
+ var import_jsx_runtime34 = require("react/jsx-runtime");
23700
+ function EmptySection({ title, homeHref = "/", eyebrowKey, titleKey, subtitleKey }) {
23701
+ return /* @__PURE__ */ (0, import_jsx_runtime34.jsxs)(import_jsx_runtime34.Fragment, { children: [
23702
+ /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(
23703
+ "p",
23704
+ {
23705
+ style: {
23706
+ fontFamily: "var(--brand-font-body)",
23707
+ fontSize: "0.75rem",
23708
+ fontWeight: 500,
23709
+ letterSpacing: "0.15em",
23710
+ textTransform: "uppercase",
23711
+ color: "var(--brand-accent)",
23712
+ marginBottom: "1.5rem"
23713
+ },
23714
+ children: /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(import_link.default, { href: homeHref, style: { color: "inherit" }, children: /* @__PURE__ */ (0, import_jsx_runtime34.jsx)("span", { ...eyebrowKey ? { "data-ohw-editable": "text", "data-ohw-key": eyebrowKey } : {}, children: "Home" }) })
23715
+ }
23716
+ ),
23717
+ /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(
23718
+ "h1",
23719
+ {
23720
+ style: {
23721
+ fontFamily: "var(--brand-font-heading)",
23722
+ fontSize: "clamp(2rem, 3.5vw, 2.75rem)",
23723
+ lineHeight: 1.1,
23724
+ letterSpacing: "-0.025em",
23725
+ color: "var(--brand-text)",
23726
+ marginBottom: "1rem"
23727
+ },
23728
+ ...titleKey ? { "data-ohw-editable": "text", "data-ohw-key": titleKey, "data-ohw-max-length": 80 } : {},
23729
+ children: title
23730
+ }
23731
+ ),
23732
+ /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(
23733
+ "p",
23734
+ {
23735
+ style: {
23736
+ fontFamily: "var(--brand-font-body)",
23737
+ fontSize: "1rem",
23738
+ lineHeight: 1.7,
23739
+ fontWeight: 300,
23740
+ color: "var(--brand-text-muted)",
23741
+ maxWidth: "340px"
23742
+ },
23743
+ ...subtitleKey ? { "data-ohw-editable": "text", "data-ohw-key": subtitleKey, "data-ohw-max-length": 160 } : {},
23744
+ children: "This page doesn't have any content yet."
23745
+ }
23746
+ )
23747
+ ] });
23748
+ }
23456
23749
  // Annotate the CommonJS export names for ESM import in node:
23457
23750
  0 && (module.exports = {
23458
23751
  AI_DEFAULT_BRAND,
@@ -23470,6 +23763,7 @@ function OhhwellsBridge() {
23470
23763
  DropdownMenuItem,
23471
23764
  DropdownMenuSeparator,
23472
23765
  DropdownMenuTrigger,
23766
+ EmptySection,
23473
23767
  ItemActionToolbar,
23474
23768
  ItemInteractionLayer,
23475
23769
  LinkEditorPanel,