@ohhwells/bridge 0.1.106 → 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; }`);
@@ -593,6 +658,19 @@ function alignSubjectOf(el) {
593
658
  while (subject.parentElement && !subject.parentElement.hasAttribute("data-ohw-section") && /^inline/.test(getComputedStyle(subject).display)) {
594
659
  subject = subject.parentElement;
595
660
  }
661
+ const TEXT_ROW = /^(P|LI|H[1-6]|BLOCKQUOTE|FIGCAPTION|DT|DD)$/;
662
+ const parent = subject.parentElement;
663
+ if (parent && TEXT_ROW.test(parent.tagName) && /^(inline-)?flex$/.test(getComputedStyle(parent).display)) {
664
+ return parent;
665
+ }
666
+ if (parent && !parent.hasAttribute("data-ohw-section")) {
667
+ const pcs = getComputedStyle(parent);
668
+ const children = Array.from(parent.children);
669
+ const textRowChild = (c) => c.hasAttribute("data-ohw-editable") || (c.textContent ?? "").trim().length === 0 || c.getAttribute("aria-hidden") === "true";
670
+ if (/^(inline-)?flex$/.test(pcs.display) && pcs.flexDirection.startsWith("row") && children.length > 0 && children.some((c) => c.hasAttribute("data-ohw-editable")) && children.every(textRowChild)) {
671
+ return parent;
672
+ }
673
+ }
596
674
  const row = subject.closest("li");
597
675
  if (row && row !== subject && /^(inline-)?flex$/.test(getComputedStyle(row).display)) {
598
676
  return row;
@@ -1340,6 +1418,22 @@ function accentBandContext(brand) {
1340
1418
  function textAttrs(ctx, path) {
1341
1419
  return ctx.keyFor ? { "data-ohw-key": ctx.keyFor(path), "data-ohw-editable": "text" } : {};
1342
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");
1343
1437
  var TRANSPARENT_PX = "data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7";
1344
1438
  function MediaBox({
1345
1439
  refValue,
@@ -1352,13 +1446,17 @@ function MediaBox({
1352
1446
  const url = refValue ? ctx.resolveMedia(refValue) : null;
1353
1447
  const isIcon = /^(lucide|simple):/.test(refValue);
1354
1448
  const aspectRatio = aspect && /^\d+:\d+$/.test(aspect) ? aspect.replace(":", " / ") : void 0;
1355
- 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
+ } : {};
1356
1453
  if (isIcon) {
1357
1454
  const Icon = refValue.startsWith("lucide:") ? lucideByName(refValue.slice(7)) : null;
1358
1455
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1359
1456
  "span",
1360
1457
  {
1361
1458
  "data-ai-icon": refValue,
1459
+ ...editAttrs,
1362
1460
  style: {
1363
1461
  display: "inline-flex",
1364
1462
  width: 48,
@@ -2251,7 +2349,7 @@ function CollectionBlock({ node, ctx, path }) {
2251
2349
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
2252
2350
  "div",
2253
2351
  {
2254
- "data-ai-grid": "",
2352
+ "data-ai-grid": String(itemsPerRow),
2255
2353
  style: {
2256
2354
  display: "grid",
2257
2355
  gridTemplateColumns: `repeat(${itemsPerRow}, 1fr)`,
@@ -2604,6 +2702,7 @@ function AiTreeRenderer({
2604
2702
  {
2605
2703
  "data-ai-section": tree.tag ?? "",
2606
2704
  ...bgAttrs,
2705
+ "data-ai-responsive": "",
2607
2706
  style: {
2608
2707
  position: "relative",
2609
2708
  padding: `${pad}px 0`,
@@ -2614,12 +2713,13 @@ function AiTreeRenderer({
2614
2713
  color: settings.sectionBackground === "accent" ? blockBrand.palette.dark : void 0
2615
2714
  },
2616
2715
  children: [
2617
- 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 }),
2618
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})` } }),
2619
2719
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
2620
2720
  "div",
2621
2721
  {
2622
- "data-ai-container": "",
2722
+ "data-ai-section-inner": "",
2623
2723
  style: {
2624
2724
  position: "relative",
2625
2725
  maxWidth: AI_TREE_TOKENS.sectionMaxWidth,
@@ -2630,7 +2730,7 @@ function AiTreeRenderer({
2630
2730
  children: tree.rows.map((row, r2) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
2631
2731
  "div",
2632
2732
  {
2633
- "data-ai-row": "",
2733
+ "data-ai-columns": "",
2634
2734
  style: {
2635
2735
  display: "grid",
2636
2736
  gridTemplateColumns: "repeat(12, 1fr)",
@@ -2680,14 +2780,52 @@ function readRootVar(name) {
2680
2780
  if (typeof document === "undefined") return "";
2681
2781
  return getComputedStyle(document.documentElement).getPropertyValue(name).trim();
2682
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
+ }
2683
2821
  function deriveBrandOverride() {
2684
2822
  const dark = readRootVar("--ohw-brand-dark");
2685
2823
  const primary = readRootVar("--ohw-brand-primary");
2686
2824
  const light = readRootVar("--ohw-brand-light");
2687
2825
  if (!dark || !primary || !light) return null;
2688
2826
  const accent = readRootVar("--ohw-brand-accent");
2689
- const heading = readRootVar("--font-heading") || readRootVar("--font-display");
2690
- const body = readRootVar("--font-body");
2827
+ const heading = readFontVar("--font-heading") || readFontVar("--font-display");
2828
+ const body = readFontVar("--font-body");
2691
2829
  return {
2692
2830
  palette: { dark, primary, accent: accent || dark, light },
2693
2831
  fonts: {
@@ -2696,27 +2834,65 @@ function deriveBrandOverride() {
2696
2834
  }
2697
2835
  };
2698
2836
  }
2699
- function deriveTemplateBrand() {
2700
- const primary = readRootVar("--brand-primary") || readRootVar("--color-primary");
2701
- const dark = readRootVar("--brand-text") || readRootVar("--color-dark");
2702
- 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");
2703
2841
  if (!dark || !primary || !light) return null;
2704
- const accent = readRootVar("--brand-accent") || readRootVar("--color-accent");
2705
- const heading = readRootVar("--brand-font-heading") || readRootVar("--font-heading") || readRootVar("--font-display");
2706
- 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");
2707
2845
  return {
2708
- 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
+ },
2709
2852
  fonts: {
2710
2853
  heading: heading || AI_DEFAULT_BRAND.fonts.heading,
2711
2854
  body: body || AI_DEFAULT_BRAND.fonts.body
2712
2855
  }
2713
2856
  };
2714
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
+ }
2715
2886
  function deriveTemplateButtonStyle() {
2716
2887
  if (typeof document === "undefined") return null;
2717
2888
  const candidates = Array.from(
2718
2889
  document.querySelectorAll('[data-ohw-role="button"]')
2719
- ).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
+ );
2720
2896
  const isFilled = (el) => {
2721
2897
  const bg = getComputedStyle(el).backgroundColor;
2722
2898
  if (!bg || bg === "transparent") return false;
@@ -7204,12 +7380,12 @@ var cva = (base, config) => (props) => {
7204
7380
  var import_radix_ui2 = require("radix-ui");
7205
7381
  var import_jsx_runtime5 = require("react/jsx-runtime");
7206
7382
  var toggleVariants = cva(
7207
- "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",
7208
7384
  {
7209
7385
  variants: {
7210
7386
  variant: {
7211
7387
  default: "bg-transparent border-0",
7212
- 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"
7213
7389
  },
7214
7390
  size: {
7215
7391
  default: "px-2 py-1",
@@ -7302,10 +7478,10 @@ var DragHandle = React4.forwardRef(
7302
7478
  type,
7303
7479
  "data-slot": "drag-handle",
7304
7480
  className: cn(
7305
- "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",
7306
7482
  "bg-white border border-transparent text-stone-500 shadow-md cursor-grab",
7307
7483
  "enabled:hover:border enabled:hover:border-stone-200 enabled:hover:text-stone-950",
7308
- "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",
7309
7485
  "disabled:cursor-not-allowed disabled:opacity-40 disabled:text-stone-950 disabled:pointer-events-none",
7310
7486
  className
7311
7487
  ),
@@ -7327,7 +7503,7 @@ var CustomToolbar = React5.forwardRef(({ className, onMouseDown, ...props }, ref
7327
7503
  "data-ohw-toolbar": "",
7328
7504
  className: cn(
7329
7505
  // Figma: bg background, radius 8, gap-1, p-0.5, shadow-md — no border
7330
- "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",
7331
7507
  className
7332
7508
  ),
7333
7509
  onMouseDown: (e) => {
@@ -7356,7 +7532,7 @@ var CustomToolbarButton = React5.forwardRef(
7356
7532
  type,
7357
7533
  className: cn(
7358
7534
  "inline-flex size-7 shrink-0 items-center justify-center rounded-[calc(var(--radius,0.5rem)-2px)] text-foreground transition-colors",
7359
- 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",
7360
7536
  className
7361
7537
  ),
7362
7538
  ...props
@@ -8558,13 +8734,13 @@ function FormFieldToolbar({
8558
8734
  ]
8559
8735
  }
8560
8736
  ) }),
8561
- /* @__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) => {
8562
8738
  const Icon = TYPE_ICONS[entry.type];
8563
8739
  return /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(
8564
8740
  DropdownMenuItem,
8565
8741
  {
8566
8742
  onSelect: () => onTypeChange(entry.type),
8567
- 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" : ""),
8568
8744
  children: [
8569
8745
  /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(Icon, { size: 14, strokeWidth: 1.75, className: "shrink-0", "aria-hidden": true }),
8570
8746
  entry.label
@@ -8581,7 +8757,7 @@ function FormFieldToolbar({
8581
8757
  type: "button",
8582
8758
  "aria-pressed": required,
8583
8759
  onClick: onRequiredToggle,
8584
- 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"),
8585
8761
  "data-ohw-field-required-toggle": "",
8586
8762
  children: [
8587
8763
  /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(import_lucide_react4.Asterisk, { size: 14, strokeWidth: 2, "aria-hidden": true }),
@@ -8601,7 +8777,7 @@ function FormFieldToolbar({
8601
8777
  children: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(import_lucide_react4.MoreHorizontal, { size: 15, "aria-hidden": true })
8602
8778
  }
8603
8779
  ) }),
8604
- /* @__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: [
8605
8781
  /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(DropdownMenuItem, { onSelect: onDuplicate, className: "rounded-md py-2 text-[13px]", children: [
8606
8782
  /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(import_lucide_react4.Copy, { size: 14, strokeWidth: 1.75, className: "shrink-0", "aria-hidden": true }),
8607
8783
  "Duplicate"
@@ -8621,7 +8797,7 @@ function FieldTypePicker({ onPick }) {
8621
8797
  "div",
8622
8798
  {
8623
8799
  "data-ohw-field-type-picker": "",
8624
- 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",
8625
8801
  children: FIELD_TYPES.map((entry) => {
8626
8802
  const Icon = TYPE_ICONS[entry.type];
8627
8803
  return /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(
@@ -8629,7 +8805,7 @@ function FieldTypePicker({ onPick }) {
8629
8805
  {
8630
8806
  type: "button",
8631
8807
  onClick: () => onPick(entry.type),
8632
- 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",
8633
8809
  children: [
8634
8810
  /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(Icon, { size: 26, strokeWidth: 1.5, "aria-hidden": true }),
8635
8811
  entry.label
@@ -8655,7 +8831,7 @@ var buttonVariants = cva(
8655
8831
  {
8656
8832
  variants: {
8657
8833
  variant: {
8658
- default: "bg-primary text-primary-foreground hover:opacity-90",
8834
+ default: "bg-bridge-primary text-primary-foreground hover:opacity-90",
8659
8835
  outline: "border border-border bg-background text-foreground shadow-sm hover:bg-muted/80",
8660
8836
  ghost: "min-w-0 px-3 py-2 text-foreground hover:bg-muted/50"
8661
8837
  },
@@ -8750,6 +8926,7 @@ function MediaOverlay({
8750
8926
  (prev) => prev && prev.fullWidth === width && prev.height === height ? prev : { fullWidth: width, height }
8751
8927
  );
8752
8928
  }, [isVideo]);
8929
+ const replaceLabel = isVideo ? "Replace video" : hover.elementType === "bg-image" ? "Replace background" : "Replace image";
8753
8930
  const replaceMode = buttonSize ? replaceButtonMode({ width: rect.width, height: rect.height }, buttonSize) : "none";
8754
8931
  const box = {
8755
8932
  position: "fixed",
@@ -8855,8 +9032,8 @@ function MediaOverlay({
8855
9032
  pointerEvents: hover.hasTextOverlap ? "none" : "auto",
8856
9033
  // Selected: a firm component ring with no wash, so the image reads as chosen rather
8857
9034
  // than hovered. Hover keeps the existing tinted preview.
8858
- boxShadow: selected ? "inset 0 0 0 2px var(--color-primary)" : "inset 0 0 0 1.5px var(--color-primary)",
8859
- 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)"
8860
9037
  },
8861
9038
  onClick: () => onSelect && !selected ? onSelect(hover.key) : onReplace(hover.key),
8862
9039
  children: [
@@ -8879,17 +9056,17 @@ function MediaOverlay({
8879
9056
  },
8880
9057
  children: [
8881
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 }),
8882
- isVideo ? "Replace video" : "Replace image"
9059
+ replaceLabel
8883
9060
  ]
8884
9061
  }
8885
9062
  ),
8886
- replaceMode === "none" ? null : /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(
9063
+ showChrome && replaceMode !== "none" && /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(
8887
9064
  Button,
8888
9065
  {
8889
9066
  "data-ohw-media-overlay": "",
8890
9067
  variant: "outline",
8891
9068
  size: "sm",
8892
- "aria-label": isVideo ? "Replace video" : "Replace image",
9069
+ "aria-label": replaceLabel,
8893
9070
  className: "gap-1.5 cursor-pointer hover:bg-background",
8894
9071
  style: {
8895
9072
  ...OVERLAY_BUTTON_STYLE,
@@ -8912,7 +9089,7 @@ function MediaOverlay({
8912
9089
  },
8913
9090
  children: [
8914
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 }),
8915
- replaceMode === "full" ? isVideo ? "Replace video" : hover.elementType === "bg-image" ? "Replace background" : "Replace image" : null
9092
+ replaceMode === "full" ? replaceLabel : null
8916
9093
  ]
8917
9094
  }
8918
9095
  )
@@ -8950,8 +9127,8 @@ function CarouselOverlay({
8950
9127
  height: rect.height,
8951
9128
  zIndex: 2147483646,
8952
9129
  pointerEvents: "auto",
8953
- boxShadow: "inset 0 0 0 1.5px var(--color-primary)",
8954
- 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)"
8955
9132
  },
8956
9133
  onClick: () => onEdit(hover.key),
8957
9134
  children: /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(
@@ -9789,7 +9966,7 @@ function SectionTreeItem({
9789
9966
  /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
9790
9967
  "div",
9791
9968
  {
9792
- 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",
9793
9970
  "aria-hidden": true
9794
9971
  }
9795
9972
  ),
@@ -9808,7 +9985,7 @@ function SectionTreeItem({
9808
9985
  className: cn(
9809
9986
  "flex h-9 min-w-0 flex-1 items-center gap-2 rounded-md border border-border bg-background p-3",
9810
9987
  interactive && "cursor-pointer hover:bg-muted/30",
9811
- interactive && selected && "border-primary"
9988
+ interactive && selected && "border-bridge-primary"
9812
9989
  ),
9813
9990
  children: [
9814
9991
  /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
@@ -9938,7 +10115,7 @@ function UrlOrPageInput({
9938
10115
  };
9939
10116
  const fieldClassName = cn(
9940
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]",
9941
- 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"
9942
10119
  );
9943
10120
  return /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)("div", { className: "flex w-full flex-col gap-2 p-0", children: [
9944
10121
  /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(Label, { htmlFor: inputId, className: cn(urlError && "text-destructive"), children: "Destination" }),
@@ -13787,6 +13964,7 @@ function readLogoSizeState(content, placement) {
13787
13964
  function getLogoElement(el) {
13788
13965
  const marked = el.closest('[data-ohw-role="logo"], [data-ohw-logo]');
13789
13966
  if (marked) return marked;
13967
+ if (el.closest('[data-ohw-editable="icon"]')) return null;
13790
13968
  const root = el.closest("nav, [data-ohw-nav-root], footer");
13791
13969
  if (!root) return null;
13792
13970
  const anchor = el.closest("a");
@@ -14074,7 +14252,7 @@ function DisplaySwitch({
14074
14252
  onClick: () => onChange(!checked),
14075
14253
  className: cn(
14076
14254
  "relative h-5 w-9 shrink-0 rounded-full transition-colors",
14077
- checked ? "bg-primary" : "bg-primary-50",
14255
+ checked ? "bg-bridge-primary" : "bg-primary-50",
14078
14256
  disabled ? "cursor-default opacity-50" : "cursor-pointer"
14079
14257
  ),
14080
14258
  children: /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(
@@ -14082,7 +14260,7 @@ function DisplaySwitch({
14082
14260
  {
14083
14261
  className: cn(
14084
14262
  "absolute top-0.5 h-4 w-4 rounded-full bg-white transition-all",
14085
- checked ? "left-[1.125rem]" : "left-0.5"
14263
+ checked ? "left-4.5" : "left-0.5"
14086
14264
  )
14087
14265
  }
14088
14266
  )
@@ -15261,7 +15439,9 @@ function collectEditableNodes(extraContent, root = document) {
15261
15439
  return {
15262
15440
  key: el.dataset.ohwKey ?? "",
15263
15441
  type: el.dataset.ohwEditable ?? "text",
15264
- 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
15265
15445
  };
15266
15446
  });
15267
15447
  const hrefEls = Array.from(root.querySelectorAll("[data-ohw-href-key]"));
@@ -15698,7 +15878,7 @@ var badgeVariants = cva(
15698
15878
  {
15699
15879
  variants: {
15700
15880
  variant: {
15701
- default: "border-transparent bg-primary text-primary-foreground",
15881
+ default: "border-transparent bg-bridge-primary text-primary-foreground",
15702
15882
  secondary: "border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80",
15703
15883
  destructive: "border-transparent bg-destructive text-destructive-foreground hover:bg-destructive/80",
15704
15884
  outline: "text-foreground"
@@ -15880,21 +16060,10 @@ function parseSchedulingInsertAfter(insertAfter) {
15880
16060
  insertBefore: insertAfter.slice(idx + INSERT_BEFORE_MARKER.length)
15881
16061
  };
15882
16062
  }
15883
- function resolveSchedulingInsert(insertAfter, explicitInsertBefore) {
15884
- const parsed = parseSchedulingInsertAfter(insertAfter);
15885
- const insertBefore = explicitInsertBefore ?? parsed.insertBefore;
15886
- const effectiveInsertAfter = insertBefore && parsed.insertBefore === null ? `${insertAfter}${INSERT_BEFORE_MARKER}${insertBefore}` : insertAfter;
15887
- return { effectiveInsertAfter, insertBefore };
15888
- }
15889
- function getSchedulingMountPoint(insertAfter) {
15890
- const { anchor } = parseSchedulingInsertAfter(insertAfter);
15891
- let anchorEl = document.querySelector(`[data-ohw-section="${anchor}"]`);
15892
- if (!anchorEl && anchor === "scheduling") {
15893
- const widgets = Array.from(document.querySelectorAll('[data-ohw-section^="scheduling-"]'));
15894
- anchorEl = widgets.at(-1) ?? null;
15895
- }
15896
- if (!anchorEl) return null;
15897
- 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 };
15898
16067
  }
15899
16068
  function schedulingMountDepth(insertAfter) {
15900
16069
  if (!insertAfter.includes(INSERT_BEFORE_MARKER)) return 0;
@@ -15911,8 +16080,7 @@ function getPageSchedulingEntries(raw) {
15911
16080
  }
15912
16081
  }
15913
16082
  function isSchedulingWidgetMissing(entry) {
15914
- const { effectiveInsertAfter } = resolveSchedulingInsert(entry.insertAfter);
15915
- return !document.querySelector(`[data-ohw-section="${schedulingSectionId(effectiveInsertAfter)}"]`);
16083
+ return !document.querySelector(`[data-ohw-section="${schedulingSectionId(entry.insertAfter)}"]`);
15916
16084
  }
15917
16085
  function hasMissingSchedulingWidgets(entries) {
15918
16086
  return entries.some(isSchedulingWidgetMissing);
@@ -15950,17 +16118,18 @@ function initSectionsFromContent(content, removeExisting = false, currentPath =
15950
16118
  } catch {
15951
16119
  }
15952
16120
  }
15953
- function mountSchedulingWidget(insertAfter, notifyOnConnect = false, scheduleId, explicitInsertBefore) {
15954
- const { effectiveInsertAfter, insertBefore } = resolveSchedulingInsert(insertAfter, explicitInsertBefore);
15955
- 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);
15956
16124
  if (document.querySelector(`[data-ohw-section="${sectionId}"]`)) return false;
15957
- const mountPoint = getSchedulingMountPoint(effectiveInsertAfter);
15958
- 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;
15959
16128
  const container = document.createElement("div");
15960
16129
  container.dataset.ohwSectionContainer = "scheduling";
15961
16130
  container.dataset.ohwInstance = sectionId;
15962
- if (insertBefore) {
15963
- const beforeAnchor = document.querySelector(`[data-ohw-section="${insertBefore}"]`);
16131
+ if (beforeId) {
16132
+ const beforeAnchor = document.querySelector(`[data-ohw-section="${beforeId}"]`);
15964
16133
  const beforePoint = beforeAnchor?.closest('[data-ohw-section-container="scheduling"]') ?? beforeAnchor;
15965
16134
  if (!beforePoint) return false;
15966
16135
  beforePoint.insertAdjacentElement("beforebegin", container);
@@ -15971,20 +16140,26 @@ function mountSchedulingWidget(insertAfter, notifyOnConnect = false, scheduleId,
15971
16140
  }
15972
16141
  tail.insertAdjacentElement("afterend", container);
15973
16142
  }
15974
- const root = (0, import_client2.createRoot)(container);
15975
- schedulingRoots.set(container, root);
15976
- (0, import_react_dom3.flushSync)(() => {
15977
- root.render(
15978
- /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
15979
- SchedulingWidget,
15980
- {
15981
- notifyOnConnect,
15982
- initialScheduleId: scheduleId,
15983
- insertAfter: effectiveInsertAfter
15984
- }
15985
- )
15986
- );
15987
- });
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
+ }
15988
16163
  const tracker = getSectionsTracker();
15989
16164
  let sections = [];
15990
16165
  try {
@@ -15992,10 +16167,12 @@ function mountSchedulingWidget(insertAfter, notifyOnConnect = false, scheduleId,
15992
16167
  } catch {
15993
16168
  }
15994
16169
  const inEditor = typeof window !== "undefined" && window.self !== window.top;
15995
- if (inEditor && !sections.find((s) => s.type === "scheduling" && s.insertAfter === effectiveInsertAfter)) {
16170
+ if (inEditor && !sections.find((s) => s.type === "scheduling" && s.insertAfter === widgetId)) {
15996
16171
  sections.push({
15997
16172
  type: "scheduling",
15998
- insertAfter: effectiveInsertAfter,
16173
+ insertAfter: widgetId,
16174
+ anchorId,
16175
+ beforeId: beforeId ?? null,
15999
16176
  pagePath: window.location.pathname,
16000
16177
  ...scheduleId ? { scheduleId } : {}
16001
16178
  });
@@ -16009,7 +16186,8 @@ function mountSchedulingEntries(entries, notifyOnConnect = false) {
16009
16186
  for (let i = pending.length - 1; i >= 0; i--) {
16010
16187
  const entry = pending[i];
16011
16188
  const shouldNotify = typeof notifyOnConnect === "function" ? notifyOnConnect(entry) : notifyOnConnect;
16012
- 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)) {
16013
16191
  pending.splice(i, 1);
16014
16192
  }
16015
16193
  }
@@ -16173,6 +16351,11 @@ function applyLinkByKey(key, val) {
16173
16351
  hrefAnchors.forEach((el) => applyLinkHref(el, val));
16174
16352
  }
16175
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
+ }
16176
16359
  function isInsideFloatingPanel(target) {
16177
16360
  return Boolean(target.closest("[data-ohw-floating-panel]"));
16178
16361
  }
@@ -16180,11 +16363,6 @@ function isPointOverFloatingPanel(clientX, clientY) {
16180
16363
  const el = document.elementFromPoint(clientX, clientY);
16181
16364
  return Boolean(el instanceof Element && el.closest("[data-ohw-floating-panel]"));
16182
16365
  }
16183
- function isInsideLinkEditor(target) {
16184
- return Boolean(
16185
- 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"]')
16186
- );
16187
- }
16188
16366
  function getHrefKeyFromElement(el) {
16189
16367
  if (!el) return null;
16190
16368
  const anchor = el.closest("[data-ohw-href-key]");
@@ -16449,7 +16627,7 @@ function getNavigationSelectionParent(el) {
16449
16627
  if (!isFooterLinksContainer(el) && (el.hasAttribute("data-ohw-footer-col") || el.hasAttribute("data-ohw-footer-column") || isInferredFooterGroup2(el))) {
16450
16628
  return getFooterLinksContainer();
16451
16629
  }
16452
- 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)) {
16453
16631
  return getNavigationRoot(el);
16454
16632
  }
16455
16633
  return null;
@@ -16665,7 +16843,6 @@ var ICONS = {
16665
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"/>',
16666
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"/>'
16667
16845
  };
16668
- var HEIGHT_SETTLE_DELAYS = [150, 400, 800];
16669
16846
  var SELECTION_CHROME_GAP2 = 4;
16670
16847
  var TOOLBAR_STROKE_GAP2 = 4;
16671
16848
  var TOOLBAR_OFFSET_FROM_ELEMENT = SELECTION_CHROME_GAP2 + TOOLBAR_STROKE_GAP2;
@@ -16762,6 +16939,10 @@ function getIframeVisibleClip(parentScroll) {
16762
16939
  );
16763
16940
  return { top: clipTop, bottom: Math.max(clipTop, clipBottom) };
16764
16941
  }
16942
+ function isRectOutsideClip(rect, clip) {
16943
+ if (!clip) return false;
16944
+ return rect.bottom < clip.top || rect.top > clip.bottom;
16945
+ }
16765
16946
  function applyVisibleViewport(el, parentScroll) {
16766
16947
  const clip = getIframeVisibleClip(parentScroll);
16767
16948
  const top = clip?.top ?? 0;
@@ -17045,6 +17226,7 @@ function StateToggle({
17045
17226
  );
17046
17227
  }
17047
17228
  var contentCache = /* @__PURE__ */ new Map();
17229
+ var fetchedContentPaths = /* @__PURE__ */ new Set();
17048
17230
  var brandingCache = /* @__PURE__ */ new Map();
17049
17231
  var OHW_LOADER_STYLE = {
17050
17232
  position: "fixed",
@@ -17593,13 +17775,6 @@ function OhhwellsBridge() {
17593
17775
  const [isItemDragging, setIsItemDragging] = (0, import_react17.useState)(false);
17594
17776
  const [isFooterFrameSelection, setIsFooterFrameSelection] = (0, import_react17.useState)(false);
17595
17777
  isFooterFrameSelectionRef.current = isFooterFrameSelection;
17596
- const [floatingPanel, setFloatingPanel] = (0, import_react17.useState)(null);
17597
- const floatingPanelOpenRef = (0, import_react17.useRef)(false);
17598
- floatingPanelOpenRef.current = floatingPanel !== null;
17599
- const [floatingPanelPos, setFloatingPanelPos] = (0, import_react17.useState)(null);
17600
- const [logoSizeDraft, setLogoSizeDraft] = (0, import_react17.useState)(null);
17601
- const [editorViewport, setEditorViewport] = (0, import_react17.useState)("desktop");
17602
- const [parentScrollSnap, setParentScrollSnap] = (0, import_react17.useState)(null);
17603
17778
  const [navDropdownPreviewOpen, setNavDropdownPreviewOpen] = (0, import_react17.useState)(null);
17604
17779
  const [footerHeadingVisible, setFooterHeadingVisible] = (0, import_react17.useState)(null);
17605
17780
  const footerDragRef = (0, import_react17.useRef)(null);
@@ -17617,6 +17792,13 @@ function OhhwellsBridge() {
17617
17792
  const brandKitRef = (0, import_react17.useRef)("");
17618
17793
  const stylesRef = (0, import_react17.useRef)("");
17619
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);
17620
17802
  const [sitePages, setSitePages] = (0, import_react17.useState)([]);
17621
17803
  const [sectionsByPath, setSectionsByPath] = (0, import_react17.useState)({});
17622
17804
  const sectionsPrefetchGenRef = (0, import_react17.useRef)(0);
@@ -17625,7 +17807,18 @@ function OhhwellsBridge() {
17625
17807
  const linkPopoverOpenRef = (0, import_react17.useRef)(false);
17626
17808
  const linkPopoverGraceUntilRef = (0, import_react17.useRef)(0);
17627
17809
  setLinkPopoverRef.current = setLinkPopover;
17810
+ setFloatingPanelRef.current = setFloatingPanel;
17628
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
+ }, []);
17629
17822
  const {
17630
17823
  navDragRef,
17631
17824
  navPointerDragRef,
@@ -18966,6 +19159,7 @@ function OhhwellsBridge() {
18966
19159
  }
18967
19160
  if (typeof content[STYLE_STORE_KEY] === "string") {
18968
19161
  stylesRef.current = content[STYLE_STORE_KEY];
19162
+ document.querySelectorAll('[data-ohw-editable="form"]').forEach((f) => markFormFields(f));
18969
19163
  applyStylesToDom(parseStyleStore(content[STYLE_STORE_KEY]));
18970
19164
  }
18971
19165
  applyBrandChrome(content);
@@ -18973,11 +19167,11 @@ function OhhwellsBridge() {
18973
19167
  for (const [key, val] of Object.entries(content)) {
18974
19168
  if (key === "__ohw_sections") continue;
18975
19169
  if (key === AI_SECTIONS_KEY) continue;
19170
+ if (key === LOGO_PLACEHOLDER_KEY) continue;
19171
+ if (LOGO_IMAGE_KEYS.includes(key)) continue;
18976
19172
  if (key === BRAND_KIT_KEY) continue;
18977
19173
  if (key === STYLE_STORE_KEY) continue;
18978
19174
  if (BRAND_CHROME_KEYS.has(key)) continue;
18979
- if (key === LOGO_PLACEHOLDER_KEY) continue;
18980
- if (LOGO_IMAGE_KEYS.includes(key)) continue;
18981
19175
  if (applyVideoSettingNode(key, val)) continue;
18982
19176
  if (applyCarouselNode(key, val)) continue;
18983
19177
  document.querySelectorAll(`[data-ohw-key="${key}"]`).forEach((el) => {
@@ -19048,7 +19242,9 @@ function OhhwellsBridge() {
19048
19242
  let cancelled = false;
19049
19243
  setFetchState("loading");
19050
19244
  const apiUrl = process.env.NEXT_PUBLIC_FLOWOPS_API_URL ?? "https://flowops-backend-staging-2yfdh7pwpq-as.a.run.app";
19051
- 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) => {
19052
19248
  if (cancelled) return;
19053
19249
  const content = data?.content ?? {};
19054
19250
  const branding = Boolean(data?.showBranding);
@@ -19157,7 +19353,9 @@ function OhhwellsBridge() {
19157
19353
  }, [isEditMode]);
19158
19354
  (0, import_react17.useEffect)(() => {
19159
19355
  if (isEditMode || fetchState !== "done") return;
19356
+ console.log("env", process.env.NEXT_PUBLIC_FLOWOPS_API_URL);
19160
19357
  const apiUrl = process.env.NEXT_PUBLIC_FLOWOPS_API_URL ?? "https://flowops-backend-staging-2yfdh7pwpq-as.a.run.app";
19358
+ console.log({ apiUrl, subdomain });
19161
19359
  bindPublishedForms(apiUrl, subdomain, contentCache.get(subdomain) ?? {});
19162
19360
  }, [isEditMode, fetchState, subdomain]);
19163
19361
  (0, import_react17.useEffect)(() => {
@@ -19182,16 +19380,17 @@ function OhhwellsBridge() {
19182
19380
  applyAiSectionsToDom(parseAiSectionsState(content[AI_SECTIONS_KEY]));
19183
19381
  }
19184
19382
  if (typeof content[STYLE_STORE_KEY] === "string") {
19383
+ document.querySelectorAll('[data-ohw-editable="form"]').forEach((f) => markFormFields(f));
19185
19384
  applyStylesToDom(parseStyleStore(content[STYLE_STORE_KEY]));
19186
19385
  }
19187
19386
  for (const [key, val] of Object.entries(content)) {
19188
19387
  if (key === "__ohw_sections") continue;
19189
19388
  if (key === AI_SECTIONS_KEY) continue;
19389
+ if (key === LOGO_PLACEHOLDER_KEY) continue;
19390
+ if (LOGO_IMAGE_KEYS.includes(key)) continue;
19190
19391
  if (key === BRAND_KIT_KEY) continue;
19191
19392
  if (key === STYLE_STORE_KEY) continue;
19192
19393
  if (BRAND_CHROME_KEYS.has(key)) continue;
19193
- if (key === LOGO_PLACEHOLDER_KEY) continue;
19194
- if (LOGO_IMAGE_KEYS.includes(key)) continue;
19195
19394
  if (applyVideoSettingNode(key, val)) continue;
19196
19395
  if (applyCarouselNode(key, val)) continue;
19197
19396
  document.querySelectorAll(`[data-ohw-key="${key}"]`).forEach((el) => {
@@ -19242,6 +19441,17 @@ function OhhwellsBridge() {
19242
19441
  debounceTimer = setTimeout(applyFromCache, 150);
19243
19442
  };
19244
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
+ }
19245
19455
  observer = new MutationObserver(scheduleApply);
19246
19456
  observer.observe(document.body, { childList: true, subtree: true });
19247
19457
  return () => {
@@ -19377,26 +19587,11 @@ function OhhwellsBridge() {
19377
19587
  const t2 = setTimeout(measure, 500);
19378
19588
  const ro = new ResizeObserver(schedule);
19379
19589
  ro.observe(document.body);
19380
- let lastWidth = window.innerWidth;
19381
- let resizeTimers = [];
19382
- const clearResizeTimers = () => {
19383
- resizeTimers.forEach(clearTimeout);
19384
- resizeTimers = [];
19385
- };
19386
- const handleResize = () => {
19387
- if (window.innerWidth === lastWidth) return;
19388
- lastWidth = window.innerWidth;
19389
- clearResizeTimers();
19390
- resizeTimers = HEIGHT_SETTLE_DELAYS.map((delay) => setTimeout(measure, delay));
19391
- };
19392
- window.addEventListener("resize", handleResize);
19393
19590
  return () => {
19394
19591
  clearTimeout(t1);
19395
19592
  clearTimeout(t2);
19396
19593
  if (raf != null) cancelAnimationFrame(raf);
19397
19594
  ro.disconnect();
19398
- clearResizeTimers();
19399
- window.removeEventListener("resize", handleResize);
19400
19595
  };
19401
19596
  }, [pathname, isEditMode, postToParent2]);
19402
19597
  (0, import_react17.useEffect)(() => {
@@ -19627,6 +19822,10 @@ function OhhwellsBridge() {
19627
19822
  [data-ohw-editable-state], [data-ohw-editable-state] * { pointer-events: none !important; }
19628
19823
  [data-ohw-editable-state][data-ohw-active-state] [data-ohw-editable] { pointer-events: auto !important; }
19629
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; }
19630
19829
  `;
19631
19830
  if (!existing) {
19632
19831
  const base = document.createElement("style");
@@ -19665,9 +19864,6 @@ function OhhwellsBridge() {
19665
19864
  if (target.closest("[data-ohw-state-toggle]")) return;
19666
19865
  if (target.closest("[data-ohw-max-badge]")) return;
19667
19866
  if (isInsideLinkEditor(target)) return;
19668
- if (selectedMediaElRef.current && !selectedMediaElRef.current.contains(target) && !target.closest("[data-ohw-media-overlay], [data-ohw-edit-chrome], [data-ohw-item-interaction]")) {
19669
- clearMediaSelectionRef.current();
19670
- }
19671
19867
  if (isInsideFloatingPanel(target)) return;
19672
19868
  if (target.closest("[data-ohw-form-toolbar]")) return;
19673
19869
  if (target.closest(
@@ -19675,6 +19871,9 @@ function OhhwellsBridge() {
19675
19871
  )) {
19676
19872
  return;
19677
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
+ }
19678
19877
  {
19679
19878
  const formEl = getFormElement(target);
19680
19879
  const onSuccessText = target.closest(`[${SUCCESS_TEXT_ATTR}]`);
@@ -19797,7 +19996,19 @@ function OhhwellsBridge() {
19797
19996
  openLogoSizePanelRef.current(logoEl);
19798
19997
  return;
19799
19998
  }
19800
- 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
+ }
19801
20012
  if (editable) {
19802
20013
  if (editable.dataset.ohwEditable === "link") {
19803
20014
  e.preventDefault();
@@ -19826,14 +20037,6 @@ function OhhwellsBridge() {
19826
20037
  }
19827
20038
  const clickedButton = findClosestButtonLike(target);
19828
20039
  const buttonOnMedia = Boolean(clickedButton && isMediaEditable(editable) && editable.contains(clickedButton));
19829
- console.log("[click-debug]", {
19830
- editableType: editable.dataset.ohwEditable,
19831
- editableTag: editable.tagName,
19832
- targetTag: target.tagName,
19833
- clickedButtonTag: clickedButton?.tagName ?? null,
19834
- buttonOnMedia,
19835
- isMediaEditableEditable: isMediaEditable(editable)
19836
- });
19837
20040
  if (isMediaEditable(editable) && !buttonOnMedia) {
19838
20041
  e.preventDefault();
19839
20042
  e.stopPropagation();
@@ -19860,11 +20063,6 @@ function OhhwellsBridge() {
19860
20063
  const hrefLookupTarget = buttonOnMedia ? clickedButton : editable;
19861
20064
  const hrefCtx = getHrefKeyFromElement(hrefLookupTarget);
19862
20065
  const navAnchor = hrefCtx ? getNavigationItemAnchor(hrefCtx.anchor) : null;
19863
- console.log("[click-debug 2]", {
19864
- hrefLookupTargetTag: hrefLookupTarget.tagName,
19865
- hrefCtx: hrefCtx ? { key: hrefCtx.key } : null,
19866
- navAnchorTag: navAnchor?.tagName ?? null
19867
- });
19868
20066
  if (navAnchor) {
19869
20067
  e.preventDefault();
19870
20068
  e.stopPropagation();
@@ -20034,6 +20232,9 @@ function OhhwellsBridge() {
20034
20232
  setHoveredItemRect(null);
20035
20233
  hoveredNavContainerRef.current = null;
20036
20234
  setHoveredNavContainerRect(null);
20235
+ siblingHintElRef.current = null;
20236
+ setSiblingHintRect(null);
20237
+ setSiblingHintRects([]);
20037
20238
  return;
20038
20239
  }
20039
20240
  {
@@ -20154,7 +20355,6 @@ function OhhwellsBridge() {
20154
20355
  hoveredNavContainerRef.current = null;
20155
20356
  setHoveredNavContainerRect(null);
20156
20357
  hoveredItemElRef.current = editable;
20157
- setHoveredItemRect(isSelectedForHoverRef.current(editable) ? null : editable.getBoundingClientRect());
20158
20358
  }
20159
20359
  }
20160
20360
  }
@@ -20451,7 +20651,7 @@ function OhhwellsBridge() {
20451
20651
  }
20452
20652
  };
20453
20653
  const probeImageAt = (clientX, clientY, isDragOver = false, fromParentViewport = false) => {
20454
- if (linkPopoverOpenRef.current) {
20654
+ if (linkPopoverOpenRef.current || floatingPanelOpenRef.current && isPointOverFloatingPanel(clientX, clientY)) {
20455
20655
  if (hoveredImageRef.current) {
20456
20656
  hoveredImageRef.current = null;
20457
20657
  hoveredImageHasTextOverlapRef.current = false;
@@ -20540,6 +20740,15 @@ function OhhwellsBridge() {
20540
20740
  }
20541
20741
  return;
20542
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
+ }
20543
20752
  const topEl = document.elementFromPoint(x2, y2);
20544
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"]')) {
20545
20754
  if (hoveredImageRef.current) {
@@ -20815,8 +21024,7 @@ function OhhwellsBridge() {
20815
21024
  };
20816
21025
  const handleMouseMove = (e) => {
20817
21026
  const { clientX, clientY } = e;
20818
- if (pointOwnedByFloatingPanel(clientX, clientY)) return;
20819
- if (isOverEditorChrome(clientX, clientY)) {
21027
+ if (document.documentElement.hasAttribute("data-ohw-panel-dragging") || floatingPanelOpenRef.current && isPointOverFloatingPanel(clientX, clientY) || isOverEditorChrome(clientX, clientY)) {
20820
21028
  document.querySelectorAll("[data-ohw-hovered]").forEach((el) => el.removeAttribute("data-ohw-hovered"));
20821
21029
  formHoverElRef.current = null;
20822
21030
  setFormHoverRect(null);
@@ -20824,6 +21032,12 @@ function OhhwellsBridge() {
20824
21032
  setHoveredItemRect(null);
20825
21033
  hoveredNavContainerRef.current = null;
20826
21034
  setHoveredNavContainerRect(null);
21035
+ siblingHintElRef.current = null;
21036
+ setSiblingHintRect(null);
21037
+ setSiblingHintRects([]);
21038
+ dismissImageHover();
21039
+ clearImageHover();
21040
+ setSectionGap(null);
20827
21041
  return;
20828
21042
  }
20829
21043
  if (probeSocialsRowAt(clientX, clientY)) return;
@@ -20835,7 +21049,11 @@ function OhhwellsBridge() {
20835
21049
  if (e.data?.type !== "ow:pointer-sync") return;
20836
21050
  const { clientX, clientY } = e.data;
20837
21051
  if (typeof clientX !== "number" || typeof clientY !== "number") return;
20838
- 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
+ }
20839
21057
  if (probeSocialsRowAt(clientX, clientY)) return;
20840
21058
  probeSectionGapAt(clientX, clientY);
20841
21059
  probeImageAt(clientX, clientY);
@@ -21174,6 +21392,7 @@ function OhhwellsBridge() {
21174
21392
  }
21175
21393
  if (typeof content[STYLE_STORE_KEY] === "string") {
21176
21394
  stylesRef.current = content[STYLE_STORE_KEY];
21395
+ document.querySelectorAll('[data-ohw-editable="form"]').forEach((f) => markFormFields(f));
21177
21396
  applyStylesToDom(parseStyleStore(content[STYLE_STORE_KEY]));
21178
21397
  }
21179
21398
  applyBrandChrome(content);
@@ -21185,11 +21404,11 @@ function OhhwellsBridge() {
21185
21404
  continue;
21186
21405
  }
21187
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;
21188
21409
  if (key === BRAND_KIT_KEY) continue;
21189
21410
  if (key === STYLE_STORE_KEY) continue;
21190
21411
  if (BRAND_CHROME_KEYS.has(key)) continue;
21191
- if (key === LOGO_PLACEHOLDER_KEY) continue;
21192
- if (LOGO_IMAGE_KEYS.includes(key) && !String(val ?? "").trim()) continue;
21193
21412
  if (applyVideoSettingNode(key, val)) continue;
21194
21413
  if (applyCarouselNode(key, val)) continue;
21195
21414
  document.querySelectorAll(`[data-ohw-key="${key}"]`).forEach((el) => {
@@ -21410,10 +21629,26 @@ function OhhwellsBridge() {
21410
21629
  const handleGetBrand = (e) => {
21411
21630
  if (e.data?.type !== "ow:get-brand") return;
21412
21631
  const template = deriveTemplateBrand();
21413
- 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) : "");
21414
21637
  postToParentRef.current({ type: "ow:brand-value", value });
21415
21638
  };
21416
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);
21417
21652
  const handleMoveSection = (e) => {
21418
21653
  if (e.data?.type !== "ow:move-section") return;
21419
21654
  const instanceId = typeof e.data.instanceId === "string" ? e.data.instanceId : "";
@@ -21606,6 +21841,12 @@ function OhhwellsBridge() {
21606
21841
  closeLinkPopoverRef.current();
21607
21842
  return;
21608
21843
  }
21844
+ if (floatingPanelOpenRef.current) {
21845
+ setFloatingPanelRef.current(null);
21846
+ deselectRef.current();
21847
+ deactivateRef.current();
21848
+ return;
21849
+ }
21609
21850
  deselectRef.current();
21610
21851
  deactivateRef.current();
21611
21852
  clearMediaSelectionRef.current();
@@ -21894,8 +22135,12 @@ function OhhwellsBridge() {
21894
22135
  if (inserted) {
21895
22136
  const tracker = getSectionsTracker();
21896
22137
  postToParentRef.current({ type: "ow:change", nodes: [{ key: "__ohw_sections", text: tracker.textContent ?? "[]" }] });
21897
- const h = document.body.scrollHeight;
21898
- 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);
21899
22144
  }
21900
22145
  };
21901
22146
  const handleSwitchSchedule = (e) => {
@@ -22072,14 +22317,20 @@ function OhhwellsBridge() {
22072
22317
  };
22073
22318
  const applyToolbarPos = (rect) => {
22074
22319
  const ps = parentScrollRef.current;
22320
+ const hidden = isRectOutsideClip(rect, getIframeVisibleClip(ps));
22075
22321
  const measuredW = toolbarElRef.current?.offsetWidth || 330;
22076
22322
  if (toolbarElRef.current) {
22077
- const { top, left, transform } = calcToolbarPos(rect, ps, measuredW);
22078
- toolbarElRef.current.style.top = `${top}px`;
22079
- toolbarElRef.current.style.left = `${left}px`;
22080
- 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
+ }
22081
22330
  }
22082
22331
  if (glowElRef.current) {
22332
+ glowElRef.current.style.display = hidden ? "none" : "";
22333
+ if (hidden) return;
22083
22334
  const GAP = SELECTION_CHROME_GAP2;
22084
22335
  glowElRef.current.style.top = `${rect.top - GAP}px`;
22085
22336
  glowElRef.current.style.left = `${rect.left - GAP}px`;
@@ -22310,15 +22561,17 @@ function OhhwellsBridge() {
22310
22561
  window.removeEventListener("message", handleAiSetBrand);
22311
22562
  window.removeEventListener("message", handleAiSetStyles);
22312
22563
  window.removeEventListener("message", handleGetBrand);
22564
+ window.removeEventListener("message", handleGetTemplateFonts);
22565
+ window.removeEventListener("message", handleGetTemplateBrand);
22313
22566
  window.removeEventListener("message", handleMoveSection);
22314
22567
  window.removeEventListener("message", handlePanelDragging);
22315
22568
  window.removeEventListener("message", handleDeleteSection);
22316
22569
  window.removeEventListener("message", handleDuplicateSection);
22317
22570
  window.removeEventListener("message", handleDeactivate);
22318
- document.documentElement.removeAttribute("data-ohw-panel-dragging");
22319
22571
  window.removeEventListener("message", handleToastAction);
22320
22572
  window.removeEventListener("message", handleFormCount);
22321
22573
  window.removeEventListener("message", handleUiEscape);
22574
+ document.documentElement.removeAttribute("data-ohw-panel-dragging");
22322
22575
  autoSaveTimers.current.forEach(clearTimeout);
22323
22576
  autoSaveTimers.current.clear();
22324
22577
  if (imageUnhoverTimerRef.current) clearTimeout(imageUnhoverTimerRef.current);
@@ -22521,7 +22774,7 @@ function OhhwellsBridge() {
22521
22774
  postToParent2({
22522
22775
  type: "ow:ready",
22523
22776
  version: "1",
22524
- bridgeVersion: "0.1.105",
22777
+ bridgeVersion: "0.1.108",
22525
22778
  path: pathname,
22526
22779
  nodes: collectEditableNodes(editContentRef.current),
22527
22780
  sections
@@ -23137,7 +23390,7 @@ function OhhwellsBridge() {
23137
23390
  "span",
23138
23391
  {
23139
23392
  "data-ohw-form-count": "",
23140
- 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",
23141
23394
  children: formPickCount
23142
23395
  }
23143
23396
  )
@@ -23308,7 +23561,7 @@ function OhhwellsBridge() {
23308
23561
  ) : void 0
23309
23562
  }
23310
23563
  ),
23311
- 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: [
23312
23565
  /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
23313
23566
  EditGlowChrome,
23314
23567
  {
@@ -23374,11 +23627,11 @@ function OhhwellsBridge() {
23374
23627
  className: "fixed left-0 w-full z-2147483646 flex items-center pointer-events-none",
23375
23628
  style: { top: sectionGap.y, transform: "translateY(-50%)" },
23376
23629
  children: [
23377
- /* @__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 } }),
23378
23631
  /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
23379
23632
  Badge,
23380
23633
  {
23381
- 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",
23382
23635
  onClick: () => {
23383
23636
  window.parent.postMessage(
23384
23637
  {
@@ -23392,7 +23645,7 @@ function OhhwellsBridge() {
23392
23645
  children: "Add Section"
23393
23646
  }
23394
23647
  ),
23395
- /* @__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 } })
23396
23649
  ]
23397
23650
  }
23398
23651
  ),
@@ -23440,6 +23693,59 @@ function OhhwellsBridge() {
23440
23693
  ) : null
23441
23694
  ] });
23442
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
+ }
23443
23749
  // Annotate the CommonJS export names for ESM import in node:
23444
23750
  0 && (module.exports = {
23445
23751
  AI_DEFAULT_BRAND,
@@ -23457,6 +23763,7 @@ function OhhwellsBridge() {
23457
23763
  DropdownMenuItem,
23458
23764
  DropdownMenuSeparator,
23459
23765
  DropdownMenuTrigger,
23766
+ EmptySection,
23460
23767
  ItemActionToolbar,
23461
23768
  ItemInteractionLayer,
23462
23769
  LinkEditorPanel,