@ohhwells/bridge 0.1.111 → 0.1.112-next.291

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
@@ -459,6 +522,20 @@ var BG_VALUES = {
459
522
  accent: "var(--ohw-brand-primary, var(--color-primary, #0078E5))",
460
523
  "accent-soft": "color-mix(in srgb, var(--ohw-brand-primary, var(--color-primary, #0078E5)) 12%, var(--ohw-brand-light, var(--color-light, #FFFFFF)))"
461
524
  };
525
+ var NAVBAR_COLOR_TOKENS = ["dark", "primary", "accent", "light"];
526
+ function navbarColorVar(token) {
527
+ const fallback = {
528
+ dark: "#0C0A09",
529
+ primary: "#0078E5",
530
+ accent: "#0078E5",
531
+ light: "#FFFFFF"
532
+ };
533
+ return `var(--ohw-brand-${token}, var(--color-${token}, ${fallback[token]}))`;
534
+ }
535
+ function resolveNavbarValue(value) {
536
+ if (value === void 0) return void 0;
537
+ return NAVBAR_COLOR_TOKENS.includes(value) ? navbarColorVar(value) : value;
538
+ }
462
539
  var HEADLINE_SIZES = { md: 24, lg: 32, xl: 40, display: 56 };
463
540
  function styleSheetCss() {
464
541
  const rules = [];
@@ -628,8 +705,16 @@ function applyStylesToDom(store) {
628
705
  for (const [sectionId, override] of Object.entries(store.sections)) {
629
706
  const escaped = CSS.escape(sectionId);
630
707
  const byInstance = document.querySelectorAll(`[data-ohw-instance="${escaped}"]`);
631
- const sections = byInstance.length ? byInstance : document.querySelectorAll(`[data-ohw-section="${escaped}"]`);
708
+ const sections = Array.from(
709
+ byInstance.length ? byInstance : document.querySelectorAll(`[data-ohw-section="${escaped}"]`)
710
+ );
711
+ const isNavbar = sectionId === "navbar";
712
+ if (isNavbar) {
713
+ const drawer = document.querySelector("[data-ohw-nav-drawer]");
714
+ if (drawer && !sections.includes(drawer)) sections.push(drawer);
715
+ }
632
716
  for (const marker of Array.from(sections)) {
717
+ const isDrawerMarker = isNavbar && marker.hasAttribute("data-ohw-nav-drawer");
633
718
  const section = marker.querySelector(":scope > [data-ai-section]") ?? marker;
634
719
  for (const [prop, attr] of Object.entries(SECTION_ATTRS)) {
635
720
  const value = override[prop];
@@ -637,19 +722,23 @@ function applyStylesToDom(store) {
637
722
  if (prop === "sectionBackground" && override.sectionBackgroundColor !== void 0) continue;
638
723
  section.setAttribute(attr, String(value).replace(":", "-"));
639
724
  }
640
- if (override.sectionBackgroundColor !== void 0) {
725
+ const bgOverride = isDrawerMarker ? override.navbarMobileBackground ?? override.sectionBackgroundColor : override.sectionBackgroundColor;
726
+ const textOverride = isDrawerMarker ? override.navbarMobileTextColor ?? override.textColor : override.textColor;
727
+ const bgValue = isNavbar ? resolveNavbarValue(bgOverride) : bgOverride;
728
+ const textValue = isNavbar ? resolveNavbarValue(textOverride) : textOverride;
729
+ if (bgValue !== void 0) {
641
730
  saveInline(section, "background");
642
- section.style.setProperty("background", override.sectionBackgroundColor, "important");
731
+ section.style.setProperty("background", bgValue, "important");
643
732
  section.setAttribute("data-ohw-style-bgcolor", "");
644
733
  }
645
- if (override.textColor !== void 0 || override.fontFamily !== void 0) {
734
+ if (textValue !== void 0 || override.fontFamily !== void 0) {
646
735
  const textEls = marker.querySelectorAll(
647
736
  '[data-ohw-editable="text"], [data-ohw-editable="plain"], [data-ohw-field-label]'
648
737
  );
649
738
  for (const el of Array.from(textEls)) {
650
- if (override.textColor !== void 0) {
739
+ if (textValue !== void 0) {
651
740
  saveInline(el, "color");
652
- el.style.setProperty("color", override.textColor, "important");
741
+ el.style.setProperty("color", textValue, "important");
653
742
  el.setAttribute(NODE_WROTE_ATTR, "");
654
743
  }
655
744
  if (override.fontFamily !== void 0) {
@@ -1355,6 +1444,22 @@ function accentBandContext(brand) {
1355
1444
  function textAttrs(ctx, path) {
1356
1445
  return ctx.keyFor ? { "data-ohw-key": ctx.keyFor(path), "data-ohw-editable": "text" } : {};
1357
1446
  }
1447
+ var AI_RESPONSIVE_CSS = [
1448
+ "@media (max-width: 960px) {",
1449
+ " [data-ai-responsive] [data-ai-grid] { grid-template-columns: repeat(2, 1fr) !important; }",
1450
+ ' [data-ai-responsive] [data-ai-grid="1"] { grid-template-columns: 1fr !important; }',
1451
+ "}",
1452
+ "@media (max-width: 640px) {",
1453
+ " [data-ai-responsive] [data-ai-grid] { grid-template-columns: 1fr !important; }",
1454
+ " [data-ai-responsive] [data-ai-columns] > * { grid-column: 1 / -1 !important; }",
1455
+ // Group containers flatten to a column on phones; span placements come along for free.
1456
+ " [data-ai-responsive] [data-ai-group] { display: flex !important; flex-direction: column !important; }",
1457
+ " [data-ai-responsive] [data-ai-group] > * { grid-column: auto !important; }",
1458
+ " [data-ai-responsive] [data-ai-section-inner] { padding-left: 20px !important; padding-right: 20px !important; }",
1459
+ " [data-ai-responsive] { overflow-x: hidden; }",
1460
+ " [data-ai-responsive] img { max-width: 100%; }",
1461
+ "}"
1462
+ ].join("\n");
1358
1463
  var TRANSPARENT_PX = "data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7";
1359
1464
  function MediaBox({
1360
1465
  refValue,
@@ -1367,13 +1472,17 @@ function MediaBox({
1367
1472
  const url = refValue ? ctx.resolveMedia(refValue) : null;
1368
1473
  const isIcon = /^(lucide|simple):/.test(refValue);
1369
1474
  const aspectRatio = aspect && /^\d+:\d+$/.test(aspect) ? aspect.replace(":", " / ") : void 0;
1370
- const editAttrs = ctx.keyFor && editPath && !isIcon ? { "data-ohw-key": ctx.keyFor(editPath), "data-ohw-editable": "image" } : {};
1475
+ const editAttrs = ctx.keyFor && editPath ? {
1476
+ "data-ohw-key": ctx.keyFor(editPath),
1477
+ "data-ohw-editable": isIcon ? "icon" : "image"
1478
+ } : {};
1371
1479
  if (isIcon) {
1372
1480
  const Icon = refValue.startsWith("lucide:") ? lucideByName(refValue.slice(7)) : null;
1373
1481
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1374
1482
  "span",
1375
1483
  {
1376
1484
  "data-ai-icon": refValue,
1485
+ ...editAttrs,
1377
1486
  style: {
1378
1487
  display: "inline-flex",
1379
1488
  width: 48,
@@ -2266,7 +2375,7 @@ function CollectionBlock({ node, ctx, path }) {
2266
2375
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
2267
2376
  "div",
2268
2377
  {
2269
- "data-ai-grid": "",
2378
+ "data-ai-grid": String(itemsPerRow),
2270
2379
  style: {
2271
2380
  display: "grid",
2272
2381
  gridTemplateColumns: `repeat(${itemsPerRow}, 1fr)`,
@@ -2619,6 +2728,7 @@ function AiTreeRenderer({
2619
2728
  {
2620
2729
  "data-ai-section": tree.tag ?? "",
2621
2730
  ...bgAttrs,
2731
+ "data-ai-responsive": "",
2622
2732
  style: {
2623
2733
  position: "relative",
2624
2734
  padding: `${pad}px 0`,
@@ -2629,12 +2739,13 @@ function AiTreeRenderer({
2629
2739
  color: settings.sectionBackground === "accent" ? blockBrand.palette.dark : void 0
2630
2740
  },
2631
2741
  children: [
2632
- isOverlay && backgroundUrl && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { position: "absolute", inset: 0, background: `rgba(255,255,255,${overlayAlpha})` } }),
2742
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("style", { children: AI_RESPONSIVE_CSS }),
2633
2743
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("style", { children: AI_MOBILE_CSS }),
2744
+ isOverlay && backgroundUrl && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { position: "absolute", inset: 0, background: `rgba(255,255,255,${overlayAlpha})` } }),
2634
2745
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
2635
2746
  "div",
2636
2747
  {
2637
- "data-ai-container": "",
2748
+ "data-ai-section-inner": "",
2638
2749
  style: {
2639
2750
  position: "relative",
2640
2751
  maxWidth: AI_TREE_TOKENS.sectionMaxWidth,
@@ -2645,7 +2756,7 @@ function AiTreeRenderer({
2645
2756
  children: tree.rows.map((row, r2) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
2646
2757
  "div",
2647
2758
  {
2648
- "data-ai-row": "",
2759
+ "data-ai-columns": "",
2649
2760
  style: {
2650
2761
  display: "grid",
2651
2762
  gridTemplateColumns: "repeat(12, 1fr)",
@@ -2695,14 +2806,52 @@ function readRootVar(name) {
2695
2806
  if (typeof document === "undefined") return "";
2696
2807
  return getComputedStyle(document.documentElement).getPropertyValue(name).trim();
2697
2808
  }
2809
+ function normalizeColorToHex(value) {
2810
+ if (!value || typeof document === "undefined") return value;
2811
+ const canvas = document.createElement("canvas");
2812
+ canvas.width = 1;
2813
+ canvas.height = 1;
2814
+ const ctx = canvas.getContext("2d");
2815
+ if (!ctx) return value;
2816
+ ctx.fillStyle = value;
2817
+ ctx.fillRect(0, 0, 1, 1);
2818
+ const [r2, g, b] = ctx.getImageData(0, 0, 1, 1).data;
2819
+ const toHex = (n) => n.toString(16).padStart(2, "0");
2820
+ return `#${toHex(r2)}${toHex(g)}${toHex(b)}`;
2821
+ }
2822
+ var GENERIC_FONT_KEYWORDS = /* @__PURE__ */ new Set([
2823
+ "serif",
2824
+ "sans-serif",
2825
+ "monospace",
2826
+ "cursive",
2827
+ "fantasy",
2828
+ "system-ui",
2829
+ "ui-serif",
2830
+ "ui-sans-serif",
2831
+ "ui-monospace",
2832
+ "ui-rounded",
2833
+ "math",
2834
+ "emoji",
2835
+ "fangsong"
2836
+ ]);
2837
+ function primaryFontFamily(stack) {
2838
+ const first = stack.split(",").map((part) => part.trim().replace(/^["']|["']$/g, "")).find((part) => part && !GENERIC_FONT_KEYWORDS.has(part.toLowerCase()));
2839
+ return first ?? "";
2840
+ }
2841
+ function humanizeFontFamily(name) {
2842
+ 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());
2843
+ }
2844
+ function readFontVar(name) {
2845
+ return humanizeFontFamily(primaryFontFamily(readRootVar(name)));
2846
+ }
2698
2847
  function deriveBrandOverride() {
2699
2848
  const dark = readRootVar("--ohw-brand-dark");
2700
2849
  const primary = readRootVar("--ohw-brand-primary");
2701
2850
  const light = readRootVar("--ohw-brand-light");
2702
2851
  if (!dark || !primary || !light) return null;
2703
2852
  const accent = readRootVar("--ohw-brand-accent");
2704
- const heading = readRootVar("--font-heading") || readRootVar("--font-display");
2705
- const body = readRootVar("--font-body");
2853
+ const heading = readFontVar("--font-heading") || readFontVar("--font-display");
2854
+ const body = readFontVar("--font-body");
2706
2855
  return {
2707
2856
  palette: { dark, primary, accent: accent || dark, light },
2708
2857
  fonts: {
@@ -2711,22 +2860,55 @@ function deriveBrandOverride() {
2711
2860
  }
2712
2861
  };
2713
2862
  }
2714
- function deriveTemplateBrand() {
2863
+ function deriveTemplateBrandLive() {
2715
2864
  const primary = readRootVar("--brand-primary") || readRootVar("--color-primary");
2716
2865
  const dark = readRootVar("--brand-text") || readRootVar("--color-dark");
2717
2866
  const light = readRootVar("--brand-background") || readRootVar("--color-light");
2718
2867
  if (!dark || !primary || !light) return null;
2719
2868
  const accent = readRootVar("--brand-accent") || readRootVar("--color-accent");
2720
- const heading = readRootVar("--brand-font-heading") || readRootVar("--font-heading") || readRootVar("--font-display");
2721
- const body = readRootVar("--brand-font-body") || readRootVar("--font-body");
2869
+ const heading = readFontVar("--brand-font-heading") || readFontVar("--font-heading") || readFontVar("--font-display");
2870
+ const body = readFontVar("--brand-font-body") || readFontVar("--font-body");
2722
2871
  return {
2723
- palette: { dark, primary, accent: accent || dark, light },
2872
+ palette: {
2873
+ dark: normalizeColorToHex(dark),
2874
+ primary: normalizeColorToHex(primary),
2875
+ accent: normalizeColorToHex(accent || dark),
2876
+ light: normalizeColorToHex(light)
2877
+ },
2724
2878
  fonts: {
2725
2879
  heading: heading || AI_DEFAULT_BRAND.fonts.heading,
2726
2880
  body: body || AI_DEFAULT_BRAND.fonts.body
2727
2881
  }
2728
2882
  };
2729
2883
  }
2884
+ function deriveTemplateFontsLive() {
2885
+ const heading = readFontVar("--brand-font-heading") || readFontVar("--font-heading") || readFontVar("--font-display");
2886
+ const body = readFontVar("--brand-font-body") || readFontVar("--font-body");
2887
+ if (!heading && !body) return null;
2888
+ return {
2889
+ heading: heading || AI_DEFAULT_BRAND.fonts.heading,
2890
+ body: body || AI_DEFAULT_BRAND.fonts.body
2891
+ };
2892
+ }
2893
+ var OVERRIDE_VAR_NAMES = [...BRAND_VAR_NAMES, ...LEGACY_BRAND_VAR_NAMES, ...FONT_VARS.heading, ...FONT_VARS.body];
2894
+ function withOverrideStripped(read) {
2895
+ const root = document.documentElement;
2896
+ const restore = OVERRIDE_VAR_NAMES.map((name) => [name, root.style.getPropertyValue(name)]);
2897
+ for (const name of OVERRIDE_VAR_NAMES) root.style.removeProperty(name);
2898
+ try {
2899
+ return read();
2900
+ } finally {
2901
+ for (const [name, value] of restore) if (value) root.style.setProperty(name, value);
2902
+ }
2903
+ }
2904
+ var TEMPLATE_BRAND_SNAPSHOT = typeof document === "undefined" ? null : withOverrideStripped(deriveTemplateBrandLive);
2905
+ var TEMPLATE_FONTS_SNAPSHOT = typeof document === "undefined" ? null : withOverrideStripped(deriveTemplateFontsLive);
2906
+ function deriveTemplateBrand() {
2907
+ return TEMPLATE_BRAND_SNAPSHOT;
2908
+ }
2909
+ function deriveTemplateFonts() {
2910
+ return TEMPLATE_FONTS_SNAPSHOT;
2911
+ }
2730
2912
  function deriveTemplateButtonStyle() {
2731
2913
  if (typeof document === "undefined") return null;
2732
2914
  const candidates = Array.from(
@@ -7224,12 +7406,12 @@ var cva = (base, config) => (props) => {
7224
7406
  var import_radix_ui2 = require("radix-ui");
7225
7407
  var import_jsx_runtime5 = require("react/jsx-runtime");
7226
7408
  var toggleVariants = cva(
7227
- "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",
7409
+ "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",
7228
7410
  {
7229
7411
  variants: {
7230
7412
  variant: {
7231
7413
  default: "bg-transparent border-0",
7232
- outline: "border border-input bg-transparent shadow-xs hover:bg-accent hover:text-accent-foreground"
7414
+ outline: "border border-input bg-transparent shadow-xs hover:bg-bridge-accent hover:text-accent-foreground"
7233
7415
  },
7234
7416
  size: {
7235
7417
  default: "px-2 py-1",
@@ -7322,10 +7504,10 @@ var DragHandle = React4.forwardRef(
7322
7504
  type,
7323
7505
  "data-slot": "drag-handle",
7324
7506
  className: cn(
7325
- "inline-flex h-7 w-4 shrink-0 items-center justify-center rounded-md transition-all duration-200 max-h-[30px]",
7507
+ "inline-flex h-7 w-4 shrink-0 items-center justify-center rounded-md transition-all duration-200 max-h-7.5",
7326
7508
  "bg-white border border-transparent text-stone-500 shadow-md cursor-grab",
7327
7509
  "enabled:hover:border enabled:hover:border-stone-200 enabled:hover:text-stone-950",
7328
- "enabled:active:border enabled:active:border-primary enabled:active:bg-primary-50 enabled:active:text-stone-950 enabled:active:shadow enabled:active:cursor-grabbing",
7510
+ "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",
7329
7511
  "disabled:cursor-not-allowed disabled:opacity-40 disabled:text-stone-950 disabled:pointer-events-none",
7330
7512
  className
7331
7513
  ),
@@ -7347,7 +7529,7 @@ var CustomToolbar = React5.forwardRef(({ className, onMouseDown, ...props }, ref
7347
7529
  "data-ohw-toolbar": "",
7348
7530
  className: cn(
7349
7531
  // Figma: bg background, radius 8, gap-1, p-0.5, shadow-md — no border
7350
- "inline-flex h-8 items-center gap-1 rounded-[var(--radius,0.5rem)] bg-background p-0.5 font-sans whitespace-nowrap shadow-md",
7532
+ "inline-flex h-8 items-center gap-1 rounded-(--radius,0.5rem) bg-background p-0.5 font-sans whitespace-nowrap shadow-md",
7351
7533
  className
7352
7534
  ),
7353
7535
  onMouseDown: (e) => {
@@ -7376,7 +7558,7 @@ var CustomToolbarButton = React5.forwardRef(
7376
7558
  type,
7377
7559
  className: cn(
7378
7560
  "inline-flex size-7 shrink-0 items-center justify-center rounded-[calc(var(--radius,0.5rem)-2px)] text-foreground transition-colors",
7379
- active ? "bg-primary text-primary-foreground" : "bg-transparent hover:bg-muted disabled:cursor-not-allowed disabled:text-muted-foreground disabled:opacity-60",
7561
+ active ? "bg-bridge-primary text-primary-foreground" : "bg-transparent hover:bg-muted disabled:cursor-not-allowed disabled:text-muted-foreground disabled:opacity-60",
7380
7562
  className
7381
7563
  ),
7382
7564
  ...props
@@ -8640,13 +8822,13 @@ function FormFieldToolbar({
8640
8822
  ]
8641
8823
  }
8642
8824
  ) }),
8643
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(DropdownMenuContent, { align: "start", sideOffset: 8, className: "min-w-[190px] p-1", children: FIELD_TYPES.map((entry) => {
8825
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(DropdownMenuContent, { align: "start", sideOffset: 8, className: "min-w-47.5 p-1", children: FIELD_TYPES.map((entry) => {
8644
8826
  const Icon = TYPE_ICONS[entry.type];
8645
8827
  return /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(
8646
8828
  DropdownMenuItem,
8647
8829
  {
8648
8830
  onSelect: () => onTypeChange(entry.type),
8649
- className: "rounded-md py-2 text-[13px] " + (entry.type === type ? "bg-primary/10" : ""),
8831
+ className: "rounded-md py-2 text-[13px] " + (entry.type === type ? "bg-bridge-primary/10" : ""),
8650
8832
  children: [
8651
8833
  /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(Icon, { size: 14, strokeWidth: 1.75, className: "shrink-0", "aria-hidden": true }),
8652
8834
  entry.label
@@ -8663,7 +8845,7 @@ function FormFieldToolbar({
8663
8845
  type: "button",
8664
8846
  "aria-pressed": required,
8665
8847
  onClick: onRequiredToggle,
8666
- 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"),
8848
+ 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"),
8667
8849
  "data-ohw-field-required-toggle": "",
8668
8850
  children: [
8669
8851
  /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(import_lucide_react4.Asterisk, { size: 14, strokeWidth: 2, "aria-hidden": true }),
@@ -8683,7 +8865,7 @@ function FormFieldToolbar({
8683
8865
  children: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(import_lucide_react4.MoreHorizontal, { size: 15, "aria-hidden": true })
8684
8866
  }
8685
8867
  ) }),
8686
- /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(DropdownMenuContent, { align: "start", sideOffset: 8, className: "min-w-[170px] p-1", children: [
8868
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(DropdownMenuContent, { align: "start", sideOffset: 8, className: "min-w-42.5 p-1", children: [
8687
8869
  /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(DropdownMenuItem, { onSelect: onDuplicate, className: "rounded-md py-2 text-[13px]", children: [
8688
8870
  /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(import_lucide_react4.Copy, { size: 14, strokeWidth: 1.75, className: "shrink-0", "aria-hidden": true }),
8689
8871
  "Duplicate"
@@ -8711,7 +8893,7 @@ function FieldTypePicker({ onPick }) {
8711
8893
  {
8712
8894
  type: "button",
8713
8895
  onClick: () => onPick(entry.type),
8714
- 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",
8896
+ className: "flex h-[76px] flex-col items-center justify-center gap-2 rounded-xl border border-border text-[15px] font-medium text-foreground transition-colors hover:border-bridge-primary hover:bg-bridge-primary/5",
8715
8897
  children: [
8716
8898
  /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(Icon, { size: 26, strokeWidth: 1.5, "aria-hidden": true }),
8717
8899
  entry.label
@@ -8737,7 +8919,7 @@ var buttonVariants = cva(
8737
8919
  {
8738
8920
  variants: {
8739
8921
  variant: {
8740
- default: "bg-primary text-primary-foreground hover:opacity-90",
8922
+ default: "bg-bridge-primary text-primary-foreground hover:opacity-90",
8741
8923
  outline: "border border-border bg-background text-foreground shadow-sm hover:bg-muted/80",
8742
8924
  ghost: "min-w-0 px-3 py-2 text-foreground hover:bg-muted/50"
8743
8925
  },
@@ -8832,6 +9014,7 @@ function MediaOverlay({
8832
9014
  (prev) => prev && prev.fullWidth === width && prev.height === height ? prev : { fullWidth: width, height }
8833
9015
  );
8834
9016
  }, [isVideo]);
9017
+ const replaceLabel = isVideo ? "Replace video" : hover.elementType === "bg-image" ? "Replace background" : "Replace image";
8835
9018
  const replaceMode = buttonSize ? replaceButtonMode({ width: rect.width, height: rect.height }, buttonSize) : "none";
8836
9019
  const box = {
8837
9020
  position: "fixed",
@@ -8937,8 +9120,8 @@ function MediaOverlay({
8937
9120
  pointerEvents: hover.hasTextOverlap ? "none" : "auto",
8938
9121
  // Selected: a firm component ring with no wash, so the image reads as chosen rather
8939
9122
  // than hovered. Hover keeps the existing tinted preview.
8940
- boxShadow: selected ? "inset 0 0 0 2px var(--color-primary)" : "inset 0 0 0 1.5px var(--color-primary)",
8941
- background: selected ? "transparent" : "color-mix(in srgb, var(--color-primary) 20%, transparent)"
9123
+ boxShadow: selected ? "inset 0 0 0 2px var(--color-bridge-primary)" : "inset 0 0 0 1.5px var(--color-bridge-primary)",
9124
+ background: selected ? "transparent" : "color-mix(in srgb, var(--color-bridge-primary) 20%, transparent)"
8942
9125
  },
8943
9126
  onClick: () => onSelect && !selected ? onSelect(hover.key) : onReplace(hover.key),
8944
9127
  children: [
@@ -8961,17 +9144,17 @@ function MediaOverlay({
8961
9144
  },
8962
9145
  children: [
8963
9146
  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 }),
8964
- isVideo ? "Replace video" : "Replace image"
9147
+ replaceLabel
8965
9148
  ]
8966
9149
  }
8967
9150
  ),
8968
- replaceMode === "none" ? null : /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(
9151
+ showChrome && replaceMode !== "none" && /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(
8969
9152
  Button,
8970
9153
  {
8971
9154
  "data-ohw-media-overlay": "",
8972
9155
  variant: "outline",
8973
9156
  size: "sm",
8974
- "aria-label": isVideo ? "Replace video" : "Replace image",
9157
+ "aria-label": replaceLabel,
8975
9158
  className: "gap-1.5 cursor-pointer hover:bg-background",
8976
9159
  style: {
8977
9160
  ...OVERLAY_BUTTON_STYLE,
@@ -8994,7 +9177,7 @@ function MediaOverlay({
8994
9177
  },
8995
9178
  children: [
8996
9179
  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 }),
8997
- replaceMode === "full" ? isVideo ? "Replace video" : hover.elementType === "bg-image" ? "Replace background" : "Replace image" : null
9180
+ replaceMode === "full" ? replaceLabel : null
8998
9181
  ]
8999
9182
  }
9000
9183
  )
@@ -9032,8 +9215,8 @@ function CarouselOverlay({
9032
9215
  height: rect.height,
9033
9216
  zIndex: 2147483646,
9034
9217
  pointerEvents: "auto",
9035
- boxShadow: "inset 0 0 0 1.5px var(--color-primary)",
9036
- background: "color-mix(in srgb, var(--color-primary) 20%, transparent)"
9218
+ boxShadow: "inset 0 0 0 1.5px var(--color-bridge-primary)",
9219
+ background: "color-mix(in srgb, var(--color-bridge-primary) 20%, transparent)"
9037
9220
  },
9038
9221
  onClick: () => onEdit(hover.key),
9039
9222
  children: /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(
@@ -9886,7 +10069,7 @@ function SectionTreeItem({
9886
10069
  /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
9887
10070
  "div",
9888
10071
  {
9889
- className: "mr-[-1px] h-9 w-2 shrink-0 rounded-bl-sm border-b border-l border-border mb-4",
10072
+ className: "-mr-px h-9 w-2 shrink-0 rounded-bl-sm border-b border-l border-border mb-4",
9890
10073
  "aria-hidden": true
9891
10074
  }
9892
10075
  ),
@@ -9905,7 +10088,7 @@ function SectionTreeItem({
9905
10088
  className: cn(
9906
10089
  "flex h-9 min-w-0 flex-1 items-center gap-2 rounded-md border border-border bg-background p-3",
9907
10090
  interactive && "cursor-pointer hover:bg-muted/30",
9908
- interactive && selected && "border-primary"
10091
+ interactive && selected && "border-bridge-primary"
9909
10092
  ),
9910
10093
  children: [
9911
10094
  /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
@@ -10035,7 +10218,7 @@ function UrlOrPageInput({
10035
10218
  };
10036
10219
  const fieldClassName = cn(
10037
10220
  "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]",
10038
- urlError ? "border-destructive shadow-[0_0_0_1px_var(--ohw-destructive)]" : isFocused ? "border-primary shadow-[0_0_0_1px_var(--ohw-primary)]" : "border-input"
10221
+ 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"
10039
10222
  );
10040
10223
  return /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)("div", { className: "flex w-full flex-col gap-2 p-0", children: [
10041
10224
  /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(Label, { htmlFor: inputId, className: cn(urlError && "text-destructive"), children: "Destination" }),
@@ -13942,6 +14125,7 @@ function readLogoSizeState(content, placement) {
13942
14125
  function getLogoElement(el) {
13943
14126
  const marked = el.closest('[data-ohw-role="logo"], [data-ohw-logo]');
13944
14127
  if (marked) return marked;
14128
+ if (el.closest('[data-ohw-editable="icon"]')) return null;
13945
14129
  const root = el.closest("nav, [data-ohw-nav-root], footer");
13946
14130
  if (!root) return null;
13947
14131
  const anchor = el.closest("a");
@@ -14229,7 +14413,7 @@ function DisplaySwitch({
14229
14413
  onClick: () => onChange(!checked),
14230
14414
  className: cn(
14231
14415
  "relative h-5 w-9 shrink-0 rounded-full transition-colors",
14232
- checked ? "bg-primary" : "bg-primary-50",
14416
+ checked ? "bg-bridge-primary" : "bg-primary-50",
14233
14417
  disabled ? "cursor-default opacity-50" : "cursor-pointer"
14234
14418
  ),
14235
14419
  children: /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(
@@ -14237,7 +14421,7 @@ function DisplaySwitch({
14237
14421
  {
14238
14422
  className: cn(
14239
14423
  "absolute top-0.5 h-4 w-4 rounded-full bg-white transition-all",
14240
- checked ? "left-[1.125rem]" : "left-0.5"
14424
+ checked ? "left-4.5" : "left-0.5"
14241
14425
  )
14242
14426
  }
14243
14427
  )
@@ -15872,7 +16056,7 @@ var badgeVariants = cva(
15872
16056
  {
15873
16057
  variants: {
15874
16058
  variant: {
15875
- default: "border-transparent bg-primary text-primary-foreground",
16059
+ default: "border-transparent bg-bridge-primary text-primary-foreground",
15876
16060
  secondary: "border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80",
15877
16061
  destructive: "border-transparent bg-destructive text-destructive-foreground hover:bg-destructive/80",
15878
16062
  outline: "text-foreground"
@@ -16054,21 +16238,10 @@ function parseSchedulingInsertAfter(insertAfter) {
16054
16238
  insertBefore: insertAfter.slice(idx + INSERT_BEFORE_MARKER.length)
16055
16239
  };
16056
16240
  }
16057
- function resolveSchedulingInsert(insertAfter, explicitInsertBefore) {
16058
- const parsed = parseSchedulingInsertAfter(insertAfter);
16059
- const insertBefore = explicitInsertBefore ?? parsed.insertBefore;
16060
- const effectiveInsertAfter = insertBefore && parsed.insertBefore === null ? `${insertAfter}${INSERT_BEFORE_MARKER}${insertBefore}` : insertAfter;
16061
- return { effectiveInsertAfter, insertBefore };
16062
- }
16063
- function getSchedulingMountPoint(insertAfter) {
16064
- const { anchor } = parseSchedulingInsertAfter(insertAfter);
16065
- let anchorEl = document.querySelector(`[data-ohw-section="${anchor}"]`);
16066
- if (!anchorEl && anchor === "scheduling") {
16067
- const widgets = Array.from(document.querySelectorAll('[data-ohw-section^="scheduling-"]'));
16068
- anchorEl = widgets.at(-1) ?? null;
16069
- }
16070
- if (!anchorEl) return null;
16071
- return anchorEl.closest('[data-ohw-section-container="scheduling"]') ?? anchorEl;
16241
+ function resolveEntryAnchor(entry) {
16242
+ if (entry.anchorId !== void 0) return { anchorId: entry.anchorId, beforeId: entry.beforeId ?? null };
16243
+ const parsed = parseSchedulingInsertAfter(entry.insertAfter);
16244
+ return { anchorId: parsed.anchor, beforeId: parsed.insertBefore };
16072
16245
  }
16073
16246
  function schedulingMountDepth(insertAfter) {
16074
16247
  if (!insertAfter.includes(INSERT_BEFORE_MARKER)) return 0;
@@ -16085,8 +16258,7 @@ function getPageSchedulingEntries(raw) {
16085
16258
  }
16086
16259
  }
16087
16260
  function isSchedulingWidgetMissing(entry) {
16088
- const { effectiveInsertAfter } = resolveSchedulingInsert(entry.insertAfter);
16089
- return !document.querySelector(`[data-ohw-section="${schedulingSectionId(effectiveInsertAfter)}"]`);
16261
+ return !document.querySelector(`[data-ohw-section="${schedulingSectionId(entry.insertAfter)}"]`);
16090
16262
  }
16091
16263
  function hasMissingSchedulingWidgets(entries) {
16092
16264
  return entries.some(isSchedulingWidgetMissing);
@@ -16124,17 +16296,18 @@ function initSectionsFromContent(content, removeExisting = false, currentPath =
16124
16296
  } catch {
16125
16297
  }
16126
16298
  }
16127
- function mountSchedulingWidget(insertAfter, notifyOnConnect = false, scheduleId, explicitInsertBefore) {
16128
- const { effectiveInsertAfter, insertBefore } = resolveSchedulingInsert(insertAfter, explicitInsertBefore);
16129
- const sectionId = schedulingSectionId(effectiveInsertAfter);
16299
+ function mountSchedulingWidget(anchorId, notifyOnConnect = false, scheduleId, beforeId, existingWidgetId) {
16300
+ const widgetId = existingWidgetId ?? (beforeId ? `${anchorId}${INSERT_BEFORE_MARKER}${beforeId}` : anchorId);
16301
+ const sectionId = schedulingSectionId(widgetId);
16130
16302
  if (document.querySelector(`[data-ohw-section="${sectionId}"]`)) return false;
16131
- const mountPoint = getSchedulingMountPoint(effectiveInsertAfter);
16132
- if (!mountPoint) return false;
16303
+ const anchorEl = document.querySelector(`[data-ohw-section="${anchorId}"]`);
16304
+ if (!anchorEl) return false;
16305
+ const mountPoint = anchorEl.closest('[data-ohw-section-container="scheduling"]') ?? anchorEl;
16133
16306
  const container = document.createElement("div");
16134
16307
  container.dataset.ohwSectionContainer = "scheduling";
16135
16308
  container.dataset.ohwInstance = sectionId;
16136
- if (insertBefore) {
16137
- const beforeAnchor = document.querySelector(`[data-ohw-section="${insertBefore}"]`);
16309
+ if (beforeId) {
16310
+ const beforeAnchor = document.querySelector(`[data-ohw-section="${beforeId}"]`);
16138
16311
  const beforePoint = beforeAnchor?.closest('[data-ohw-section-container="scheduling"]') ?? beforeAnchor;
16139
16312
  if (!beforePoint) return false;
16140
16313
  beforePoint.insertAdjacentElement("beforebegin", container);
@@ -16145,20 +16318,26 @@ function mountSchedulingWidget(insertAfter, notifyOnConnect = false, scheduleId,
16145
16318
  }
16146
16319
  tail.insertAdjacentElement("afterend", container);
16147
16320
  }
16148
- const root = (0, import_client2.createRoot)(container);
16149
- schedulingRoots.set(container, root);
16150
- (0, import_react_dom4.flushSync)(() => {
16151
- root.render(
16152
- /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
16153
- SchedulingWidget,
16154
- {
16155
- notifyOnConnect,
16156
- initialScheduleId: scheduleId,
16157
- insertAfter: effectiveInsertAfter
16158
- }
16159
- )
16160
- );
16161
- });
16321
+ try {
16322
+ const root = (0, import_client2.createRoot)(container);
16323
+ schedulingRoots.set(container, root);
16324
+ (0, import_react_dom4.flushSync)(() => {
16325
+ root.render(
16326
+ /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
16327
+ SchedulingWidget,
16328
+ {
16329
+ notifyOnConnect,
16330
+ initialScheduleId: scheduleId,
16331
+ insertAfter: widgetId
16332
+ }
16333
+ )
16334
+ );
16335
+ });
16336
+ } catch (err) {
16337
+ console.error("[ow:scheduling] render threw", err);
16338
+ container.remove();
16339
+ return false;
16340
+ }
16162
16341
  const tracker = getSectionsTracker();
16163
16342
  let sections = [];
16164
16343
  try {
@@ -16166,10 +16345,12 @@ function mountSchedulingWidget(insertAfter, notifyOnConnect = false, scheduleId,
16166
16345
  } catch {
16167
16346
  }
16168
16347
  const inEditor = typeof window !== "undefined" && window.self !== window.top;
16169
- if (inEditor && !sections.find((s) => s.type === "scheduling" && s.insertAfter === effectiveInsertAfter)) {
16348
+ if (inEditor && !sections.find((s) => s.type === "scheduling" && s.insertAfter === widgetId)) {
16170
16349
  sections.push({
16171
16350
  type: "scheduling",
16172
- insertAfter: effectiveInsertAfter,
16351
+ insertAfter: widgetId,
16352
+ anchorId,
16353
+ beforeId: beforeId ?? null,
16173
16354
  pagePath: window.location.pathname,
16174
16355
  ...scheduleId ? { scheduleId } : {}
16175
16356
  });
@@ -16183,7 +16364,8 @@ function mountSchedulingEntries(entries, notifyOnConnect = false) {
16183
16364
  for (let i = pending.length - 1; i >= 0; i--) {
16184
16365
  const entry = pending[i];
16185
16366
  const shouldNotify = typeof notifyOnConnect === "function" ? notifyOnConnect(entry) : notifyOnConnect;
16186
- if (mountSchedulingWidget(entry.insertAfter, shouldNotify, entry.scheduleId ?? null)) {
16367
+ const { anchorId, beforeId } = resolveEntryAnchor(entry);
16368
+ if (mountSchedulingWidget(anchorId, shouldNotify, entry.scheduleId ?? null, beforeId, entry.insertAfter)) {
16187
16369
  pending.splice(i, 1);
16188
16370
  }
16189
16371
  }
@@ -16347,6 +16529,11 @@ function applyLinkByKey(key, val) {
16347
16529
  hrefAnchors.forEach((el) => applyLinkHref(el, val));
16348
16530
  }
16349
16531
  }
16532
+ function isInsideLinkEditor(target) {
16533
+ return Boolean(
16534
+ 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"]')
16535
+ );
16536
+ }
16350
16537
  function isInsideFloatingPanel(target) {
16351
16538
  return Boolean(target.closest("[data-ohw-floating-panel]"));
16352
16539
  }
@@ -16354,11 +16541,6 @@ function isPointOverFloatingPanel(clientX, clientY) {
16354
16541
  const el = document.elementFromPoint(clientX, clientY);
16355
16542
  return Boolean(el instanceof Element && el.closest("[data-ohw-floating-panel]"));
16356
16543
  }
16357
- function isInsideLinkEditor(target) {
16358
- return Boolean(
16359
- 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"]')
16360
- );
16361
- }
16362
16544
  function getHrefKeyFromElement(el) {
16363
16545
  if (!el) return null;
16364
16546
  const anchor = el.closest("[data-ohw-href-key]");
@@ -16518,6 +16700,7 @@ function isFooterAddItemDisabled(selected) {
16518
16700
  }
16519
16701
  function resolveFooterColumnSelectionTarget(target, clientX, clientY) {
16520
16702
  if (getNavigationItemAnchor(target)) return null;
16703
+ if (target.closest("[data-ohw-editable]")) return null;
16521
16704
  const column = getFooterColumn(target);
16522
16705
  if (!column) return null;
16523
16706
  if (isPointOverNavItem(column, clientX, clientY)) return null;
@@ -16627,7 +16810,7 @@ function getNavigationSelectionParent(el) {
16627
16810
  if (!isFooterLinksContainer(el) && (el.hasAttribute("data-ohw-footer-col") || el.hasAttribute("data-ohw-footer-column") || isInferredFooterGroup2(el))) {
16628
16811
  return getFooterLinksContainer();
16629
16812
  }
16630
- if (el.hasAttribute("data-ohw-nav-container") || el.hasAttribute("data-ohw-nav-drawer") || isFooterLinksContainer(el)) {
16813
+ 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)) {
16631
16814
  return getNavigationRoot(el);
16632
16815
  }
16633
16816
  return null;
@@ -16659,6 +16842,12 @@ function collectNavigationItemSiblingHintRects(selected) {
16659
16842
  return [];
16660
16843
  }).filter((el) => isNavigationItem2(el) && el !== selected).map((el) => el.getBoundingClientRect());
16661
16844
  }
16845
+ if (selected.closest('footer, [data-ohw-section="footer"]')) {
16846
+ const group = findFooterItemGroup2(selected);
16847
+ if (group) {
16848
+ return Array.from(group.querySelectorAll("[data-ohw-href-key]")).filter((el) => isNavigationItem2(el) && el !== selected).map((el) => el.getBoundingClientRect());
16849
+ }
16850
+ }
16662
16851
  return listNavigationItems().filter((el) => el !== selected && !el.closest("[data-ohw-nav-children]")).map((el) => el.getBoundingClientRect());
16663
16852
  }
16664
16853
  function placeCaretAtPoint(el, x, y) {
@@ -16843,7 +17032,6 @@ var ICONS = {
16843
17032
  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"/>',
16844
17033
  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"/>'
16845
17034
  };
16846
- var HEIGHT_SETTLE_DELAYS = [150, 400, 800];
16847
17035
  var SELECTION_CHROME_GAP2 = 4;
16848
17036
  var TOOLBAR_STROKE_GAP2 = 4;
16849
17037
  var TOOLBAR_OFFSET_FROM_ELEMENT = SELECTION_CHROME_GAP2 + TOOLBAR_STROKE_GAP2;
@@ -17227,6 +17415,7 @@ function StateToggle({
17227
17415
  );
17228
17416
  }
17229
17417
  var contentCache = /* @__PURE__ */ new Map();
17418
+ var fetchedContentPaths = /* @__PURE__ */ new Set();
17230
17419
  var brandingCache = /* @__PURE__ */ new Map();
17231
17420
  var OHW_LOADER_STYLE = {
17232
17421
  position: "fixed",
@@ -17775,13 +17964,6 @@ function OhhwellsBridge() {
17775
17964
  const [isItemDragging, setIsItemDragging] = (0, import_react17.useState)(false);
17776
17965
  const [isFooterFrameSelection, setIsFooterFrameSelection] = (0, import_react17.useState)(false);
17777
17966
  isFooterFrameSelectionRef.current = isFooterFrameSelection;
17778
- const [floatingPanel, setFloatingPanel] = (0, import_react17.useState)(null);
17779
- const floatingPanelOpenRef = (0, import_react17.useRef)(false);
17780
- floatingPanelOpenRef.current = floatingPanel !== null;
17781
- const [floatingPanelPos, setFloatingPanelPos] = (0, import_react17.useState)(null);
17782
- const [logoSizeDraft, setLogoSizeDraft] = (0, import_react17.useState)(null);
17783
- const [editorViewport, setEditorViewport] = (0, import_react17.useState)("desktop");
17784
- const [parentScrollSnap, setParentScrollSnap] = (0, import_react17.useState)(null);
17785
17967
  const [navDropdownPreviewOpen, setNavDropdownPreviewOpen] = (0, import_react17.useState)(null);
17786
17968
  const [footerHeadingVisible, setFooterHeadingVisible] = (0, import_react17.useState)(null);
17787
17969
  const footerDragRef = (0, import_react17.useRef)(null);
@@ -17798,7 +17980,15 @@ function OhhwellsBridge() {
17798
17980
  const aiSectionsRef = (0, import_react17.useRef)("");
17799
17981
  const brandKitRef = (0, import_react17.useRef)("");
17800
17982
  const stylesRef = (0, import_react17.useRef)("");
17983
+ const appliedDrawerRef = (0, import_react17.useRef)(null);
17801
17984
  const pendingDeleteUndoRef = (0, import_react17.useRef)(null);
17985
+ const [floatingPanel, setFloatingPanel] = (0, import_react17.useState)(null);
17986
+ const floatingPanelOpenRef = (0, import_react17.useRef)(false);
17987
+ const setFloatingPanelRef = (0, import_react17.useRef)(setFloatingPanel);
17988
+ const [floatingPanelPos, setFloatingPanelPos] = (0, import_react17.useState)(null);
17989
+ const [logoSizeDraft, setLogoSizeDraft] = (0, import_react17.useState)(null);
17990
+ const [editorViewport, setEditorViewport] = (0, import_react17.useState)("desktop");
17991
+ const [parentScrollSnap, setParentScrollSnap] = (0, import_react17.useState)(null);
17802
17992
  const [sitePages, setSitePages] = (0, import_react17.useState)([]);
17803
17993
  const [sectionsByPath, setSectionsByPath] = (0, import_react17.useState)({});
17804
17994
  const sectionsPrefetchGenRef = (0, import_react17.useRef)(0);
@@ -17807,7 +17997,18 @@ function OhhwellsBridge() {
17807
17997
  const linkPopoverOpenRef = (0, import_react17.useRef)(false);
17808
17998
  const linkPopoverGraceUntilRef = (0, import_react17.useRef)(0);
17809
17999
  setLinkPopoverRef.current = setLinkPopover;
18000
+ setFloatingPanelRef.current = setFloatingPanel;
17810
18001
  linkPopoverSessionRef.current = linkPopover;
18002
+ floatingPanelOpenRef.current = Boolean(floatingPanel);
18003
+ (0, import_react17.useEffect)(() => {
18004
+ const syncViewport = () => {
18005
+ const next = window.innerWidth <= 480 ? "mobile" : "desktop";
18006
+ setEditorViewport((prev) => prev === next ? prev : next);
18007
+ };
18008
+ syncViewport();
18009
+ window.addEventListener("resize", syncViewport);
18010
+ return () => window.removeEventListener("resize", syncViewport);
18011
+ }, []);
17811
18012
  const {
17812
18013
  navDragRef,
17813
18014
  navPointerDragRef,
@@ -19143,6 +19344,7 @@ function OhhwellsBridge() {
19143
19344
  }
19144
19345
  if (typeof content[STYLE_STORE_KEY] === "string") {
19145
19346
  stylesRef.current = content[STYLE_STORE_KEY];
19347
+ document.querySelectorAll('[data-ohw-editable="form"]').forEach((f) => markFormFields(f));
19146
19348
  applyStylesToDom(parseStyleStore(content[STYLE_STORE_KEY]));
19147
19349
  }
19148
19350
  applyBrandChrome(content);
@@ -19150,11 +19352,11 @@ function OhhwellsBridge() {
19150
19352
  for (const [key, val] of Object.entries(content)) {
19151
19353
  if (key === "__ohw_sections") continue;
19152
19354
  if (key === AI_SECTIONS_KEY) continue;
19355
+ if (key === LOGO_PLACEHOLDER_KEY) continue;
19356
+ if (LOGO_IMAGE_KEYS.includes(key)) continue;
19153
19357
  if (key === BRAND_KIT_KEY) continue;
19154
19358
  if (key === STYLE_STORE_KEY) continue;
19155
19359
  if (BRAND_CHROME_KEYS.has(key)) continue;
19156
- if (key === LOGO_PLACEHOLDER_KEY) continue;
19157
- if (LOGO_IMAGE_KEYS.includes(key)) continue;
19158
19360
  if (applyVideoSettingNode(key, val)) continue;
19159
19361
  if (applyCarouselNode(key, val)) continue;
19160
19362
  document.querySelectorAll(`[data-ohw-key="${key}"]`).forEach((el) => {
@@ -19225,7 +19427,9 @@ function OhhwellsBridge() {
19225
19427
  let cancelled = false;
19226
19428
  setFetchState("loading");
19227
19429
  const apiUrl = process.env.NEXT_PUBLIC_FLOWOPS_API_URL ?? "https://flowops-backend-staging-2yfdh7pwpq-as.a.run.app";
19228
- fetch(`${apiUrl}/api/public/sites/${subdomain}/content`, { cache: "no-store" }).then((r2) => r2.ok ? r2.json() : null).then((data) => {
19430
+ const initialPath = pathname;
19431
+ fetchedContentPaths.add(`${subdomain}::${initialPath}`);
19432
+ fetch(`${apiUrl}/api/public/sites/${subdomain}/content?path=${encodeURIComponent(initialPath)}`, { cache: "no-store" }).then((r2) => r2.ok ? r2.json() : null).then((data) => {
19229
19433
  if (cancelled) return;
19230
19434
  const content = data?.content ?? {};
19231
19435
  const branding = Boolean(data?.showBranding);
@@ -19334,7 +19538,9 @@ function OhhwellsBridge() {
19334
19538
  }, [isEditMode]);
19335
19539
  (0, import_react17.useEffect)(() => {
19336
19540
  if (isEditMode || fetchState !== "done") return;
19541
+ console.log("env", process.env.NEXT_PUBLIC_FLOWOPS_API_URL);
19337
19542
  const apiUrl = process.env.NEXT_PUBLIC_FLOWOPS_API_URL ?? "https://flowops-backend-staging-2yfdh7pwpq-as.a.run.app";
19543
+ console.log({ apiUrl, subdomain });
19338
19544
  bindPublishedForms(apiUrl, subdomain, contentCache.get(subdomain) ?? {});
19339
19545
  }, [isEditMode, fetchState, subdomain]);
19340
19546
  (0, import_react17.useEffect)(() => {
@@ -19359,16 +19565,17 @@ function OhhwellsBridge() {
19359
19565
  applyAiSectionsToDom(parseAiSectionsState(content[AI_SECTIONS_KEY]));
19360
19566
  }
19361
19567
  if (typeof content[STYLE_STORE_KEY] === "string") {
19568
+ document.querySelectorAll('[data-ohw-editable="form"]').forEach((f) => markFormFields(f));
19362
19569
  applyStylesToDom(parseStyleStore(content[STYLE_STORE_KEY]));
19363
19570
  }
19364
19571
  for (const [key, val] of Object.entries(content)) {
19365
19572
  if (key === "__ohw_sections") continue;
19366
19573
  if (key === AI_SECTIONS_KEY) continue;
19574
+ if (key === LOGO_PLACEHOLDER_KEY) continue;
19575
+ if (LOGO_IMAGE_KEYS.includes(key)) continue;
19367
19576
  if (key === BRAND_KIT_KEY) continue;
19368
19577
  if (key === STYLE_STORE_KEY) continue;
19369
19578
  if (BRAND_CHROME_KEYS.has(key)) continue;
19370
- if (key === LOGO_PLACEHOLDER_KEY) continue;
19371
- if (LOGO_IMAGE_KEYS.includes(key)) continue;
19372
19579
  if (applyVideoSettingNode(key, val)) continue;
19373
19580
  if (applyCarouselNode(key, val)) continue;
19374
19581
  document.querySelectorAll(`[data-ohw-key="${key}"]`).forEach((el) => {
@@ -19419,6 +19626,17 @@ function OhhwellsBridge() {
19419
19626
  debounceTimer = setTimeout(applyFromCache, 150);
19420
19627
  };
19421
19628
  applyFromCache();
19629
+ const pathCacheKey = `${subdomain}::${pathname}`;
19630
+ if (!fetchedContentPaths.has(pathCacheKey)) {
19631
+ fetchedContentPaths.add(pathCacheKey);
19632
+ const apiUrl = process.env.NEXT_PUBLIC_FLOWOPS_API_URL ?? "https://flowops-backend-staging-2yfdh7pwpq-as.a.run.app";
19633
+ fetch(`${apiUrl}/api/public/sites/${subdomain}/content?path=${encodeURIComponent(pathname)}`, { cache: "no-store" }).then((r2) => r2.ok ? r2.json() : null).then((data) => {
19634
+ if (!data?.content) return;
19635
+ contentCache.set(subdomain, data.content);
19636
+ applyFromCache();
19637
+ }).catch(() => {
19638
+ });
19639
+ }
19422
19640
  observer = new MutationObserver(scheduleApply);
19423
19641
  observer.observe(document.body, { childList: true, subtree: true });
19424
19642
  return () => {
@@ -19463,6 +19681,17 @@ function OhhwellsBridge() {
19463
19681
  if (!isEditMode) return;
19464
19682
  initSectionInstancesFromContent(editContentRef.current, pathname);
19465
19683
  }, [pathname, isEditMode]);
19684
+ (0, import_react17.useEffect)(() => {
19685
+ if (!isEditMode) return;
19686
+ const observer = new MutationObserver(() => {
19687
+ const drawer = document.querySelector("[data-ohw-nav-drawer]");
19688
+ if (!drawer || drawer === appliedDrawerRef.current) return;
19689
+ appliedDrawerRef.current = drawer;
19690
+ applyStylesToDom(parseStyleStore(stylesRef.current));
19691
+ });
19692
+ observer.observe(document.body, { childList: true, subtree: true });
19693
+ return () => observer.disconnect();
19694
+ }, [isEditMode, pathname]);
19466
19695
  (0, import_react17.useEffect)(() => {
19467
19696
  const contentForNav = () => {
19468
19697
  if (isEditMode) return editContentRef.current;
@@ -19554,26 +19783,11 @@ function OhhwellsBridge() {
19554
19783
  const t2 = setTimeout(measure, 500);
19555
19784
  const ro = new ResizeObserver(schedule);
19556
19785
  ro.observe(document.body);
19557
- let lastWidth = window.innerWidth;
19558
- let resizeTimers = [];
19559
- const clearResizeTimers = () => {
19560
- resizeTimers.forEach(clearTimeout);
19561
- resizeTimers = [];
19562
- };
19563
- const handleResize = () => {
19564
- if (window.innerWidth === lastWidth) return;
19565
- lastWidth = window.innerWidth;
19566
- clearResizeTimers();
19567
- resizeTimers = HEIGHT_SETTLE_DELAYS.map((delay) => setTimeout(measure, delay));
19568
- };
19569
- window.addEventListener("resize", handleResize);
19570
19786
  return () => {
19571
19787
  clearTimeout(t1);
19572
19788
  clearTimeout(t2);
19573
19789
  if (raf != null) cancelAnimationFrame(raf);
19574
19790
  ro.disconnect();
19575
- clearResizeTimers();
19576
- window.removeEventListener("resize", handleResize);
19577
19791
  };
19578
19792
  }, [pathname, isEditMode, postToParent2]);
19579
19793
  (0, import_react17.useEffect)(() => {
@@ -19756,7 +19970,10 @@ function OhhwellsBridge() {
19756
19970
  flex-wrap: wrap !important;
19757
19971
  align-items: flex-end !important;
19758
19972
  }
19759
- [data-ohw-editable="form"] [data-ohw-form-field] {
19973
+ /* Generated forms are excluded: they are always a single column, so the 12rem
19974
+ flex BASIS \u2014 a minimum width in a horizontal row \u2014 reads as a 12rem HEIGHT
19975
+ there and inflates every field to the tallest one's size. */
19976
+ [data-ohw-editable="form"]:not([data-ai-form]) [data-ohw-form-field] {
19760
19977
  flex: 1 1 12rem !important;
19761
19978
  min-width: min(12rem, 100%) !important;
19762
19979
  max-width: 100% !important;
@@ -19865,9 +20082,6 @@ function OhhwellsBridge() {
19865
20082
  if (target.closest("[data-ohw-state-toggle]")) return;
19866
20083
  if (target.closest("[data-ohw-max-badge]")) return;
19867
20084
  if (isInsideLinkEditor(target)) return;
19868
- if (selectedMediaElRef.current && !selectedMediaElRef.current.contains(target) && !target.closest("[data-ohw-media-overlay], [data-ohw-edit-chrome], [data-ohw-item-interaction]")) {
19869
- clearMediaSelectionRef.current();
19870
- }
19871
20085
  if (isInsideFloatingPanel(target)) return;
19872
20086
  if (target.closest("[data-ohw-form-toolbar]")) return;
19873
20087
  if (target.closest(
@@ -19875,6 +20089,9 @@ function OhhwellsBridge() {
19875
20089
  )) {
19876
20090
  return;
19877
20091
  }
20092
+ if (selectedMediaElRef.current && !selectedMediaElRef.current.contains(target) && !target.closest("[data-ohw-media-overlay], [data-ohw-edit-chrome], [data-ohw-item-interaction]")) {
20093
+ clearMediaSelectionRef.current();
20094
+ }
19878
20095
  {
19879
20096
  const formEl = getFormElement(target);
19880
20097
  const onSuccessText = target.closest(`[${SUCCESS_TEXT_ATTR}]`);
@@ -20038,14 +20255,6 @@ function OhhwellsBridge() {
20038
20255
  }
20039
20256
  const clickedButton = findClosestButtonLike(target);
20040
20257
  const buttonOnMedia = Boolean(clickedButton && isMediaEditable(editable) && editable.contains(clickedButton));
20041
- console.log("[click-debug]", {
20042
- editableType: editable.dataset.ohwEditable,
20043
- editableTag: editable.tagName,
20044
- targetTag: target.tagName,
20045
- clickedButtonTag: clickedButton?.tagName ?? null,
20046
- buttonOnMedia,
20047
- isMediaEditableEditable: isMediaEditable(editable)
20048
- });
20049
20258
  if (isMediaEditable(editable) && !buttonOnMedia) {
20050
20259
  e.preventDefault();
20051
20260
  e.stopPropagation();
@@ -20072,11 +20281,6 @@ function OhhwellsBridge() {
20072
20281
  const hrefLookupTarget = buttonOnMedia ? clickedButton : editable;
20073
20282
  const hrefCtx = getHrefKeyFromElement(hrefLookupTarget);
20074
20283
  const navAnchor = hrefCtx ? getNavigationItemAnchor(hrefCtx.anchor) : null;
20075
- console.log("[click-debug 2]", {
20076
- hrefLookupTargetTag: hrefLookupTarget.tagName,
20077
- hrefCtx: hrefCtx ? { key: hrefCtx.key } : null,
20078
- navAnchorTag: navAnchor?.tagName ?? null
20079
- });
20080
20284
  if (navAnchor) {
20081
20285
  e.preventDefault();
20082
20286
  e.stopPropagation();
@@ -20118,7 +20322,7 @@ function OhhwellsBridge() {
20118
20322
  return;
20119
20323
  }
20120
20324
  const footerColClick = target.closest("[data-ohw-footer-col]");
20121
- if (footerColClick) {
20325
+ if (footerColClick && !target.closest("[data-ohw-editable]")) {
20122
20326
  e.preventDefault();
20123
20327
  e.stopPropagation();
20124
20328
  selectFrameRef.current(footerColClick);
@@ -20246,6 +20450,9 @@ function OhhwellsBridge() {
20246
20450
  setHoveredItemRect(null);
20247
20451
  hoveredNavContainerRef.current = null;
20248
20452
  setHoveredNavContainerRect(null);
20453
+ siblingHintElRef.current = null;
20454
+ setSiblingHintRect(null);
20455
+ setSiblingHintRects([]);
20249
20456
  return;
20250
20457
  }
20251
20458
  {
@@ -20277,7 +20484,7 @@ function OhhwellsBridge() {
20277
20484
  const navContainer = target.closest("[data-ohw-nav-container]");
20278
20485
  if (!navContainer) {
20279
20486
  const footerLinks = target.closest("[data-ohw-footer-links], .rb-footer-links") ?? null;
20280
- if (footerLinks && selected2 !== footerLinks && !getNavigationItemAnchor(target) && !target.closest("[data-ohw-footer-col], [data-ohw-footer-column]")) {
20487
+ if (footerLinks && selected2 !== footerLinks && !getNavigationItemAnchor(target) && !target.closest("[data-ohw-footer-col], [data-ohw-footer-column]") && !target.closest("[data-ohw-editable]")) {
20281
20488
  hoveredNavContainerRef.current = footerLinks;
20282
20489
  setHoveredNavContainerRect(footerLinks.getBoundingClientRect());
20283
20490
  hoveredItemElRef.current = null;
@@ -20315,7 +20522,7 @@ function OhhwellsBridge() {
20315
20522
  return;
20316
20523
  }
20317
20524
  const footerCol = target.closest("[data-ohw-footer-col]") ?? target.closest("[data-ohw-footer-column]");
20318
- if (footerCol) {
20525
+ if (footerCol && !target.closest("[data-ohw-editable]")) {
20319
20526
  hoveredNavContainerRef.current = null;
20320
20527
  setHoveredNavContainerRect(null);
20321
20528
  if (selectedElRef.current === footerCol) return;
@@ -20366,7 +20573,6 @@ function OhhwellsBridge() {
20366
20573
  hoveredNavContainerRef.current = null;
20367
20574
  setHoveredNavContainerRect(null);
20368
20575
  hoveredItemElRef.current = editable;
20369
- setHoveredItemRect(isSelectedForHoverRef.current(editable) ? null : editable.getBoundingClientRect());
20370
20576
  }
20371
20577
  }
20372
20578
  }
@@ -20663,7 +20869,7 @@ function OhhwellsBridge() {
20663
20869
  }
20664
20870
  };
20665
20871
  const probeImageAt = (clientX, clientY, isDragOver = false, fromParentViewport = false) => {
20666
- if (linkPopoverOpenRef.current) {
20872
+ if (linkPopoverOpenRef.current || floatingPanelOpenRef.current && isPointOverFloatingPanel(clientX, clientY)) {
20667
20873
  if (hoveredImageRef.current) {
20668
20874
  hoveredImageRef.current = null;
20669
20875
  hoveredImageHasTextOverlapRef.current = false;
@@ -21036,8 +21242,7 @@ function OhhwellsBridge() {
21036
21242
  };
21037
21243
  const handleMouseMove = (e) => {
21038
21244
  const { clientX, clientY } = e;
21039
- if (pointOwnedByFloatingPanel(clientX, clientY)) return;
21040
- if (isOverEditorChrome(clientX, clientY)) {
21245
+ if (document.documentElement.hasAttribute("data-ohw-panel-dragging") || floatingPanelOpenRef.current && isPointOverFloatingPanel(clientX, clientY) || isOverEditorChrome(clientX, clientY)) {
21041
21246
  document.querySelectorAll("[data-ohw-hovered]").forEach((el) => el.removeAttribute("data-ohw-hovered"));
21042
21247
  formHoverElRef.current = null;
21043
21248
  setFormHoverRect(null);
@@ -21045,7 +21250,12 @@ function OhhwellsBridge() {
21045
21250
  setHoveredItemRect(null);
21046
21251
  hoveredNavContainerRef.current = null;
21047
21252
  setHoveredNavContainerRect(null);
21253
+ siblingHintElRef.current = null;
21254
+ setSiblingHintRect(null);
21255
+ setSiblingHintRects([]);
21048
21256
  dismissImageHover();
21257
+ clearImageHover();
21258
+ setSectionGap(null);
21049
21259
  return;
21050
21260
  }
21051
21261
  if (probeSocialsRowAt(clientX, clientY)) return;
@@ -21057,7 +21267,11 @@ function OhhwellsBridge() {
21057
21267
  if (e.data?.type !== "ow:pointer-sync") return;
21058
21268
  const { clientX, clientY } = e.data;
21059
21269
  if (typeof clientX !== "number" || typeof clientY !== "number") return;
21060
- if (pointOwnedByFloatingPanel(clientX, clientY)) return;
21270
+ if (document.documentElement.hasAttribute("data-ohw-panel-dragging") || floatingPanelOpenRef.current && isPointOverFloatingPanel(clientX, clientY)) {
21271
+ dismissImageHover();
21272
+ clearImageHover();
21273
+ return;
21274
+ }
21061
21275
  if (probeSocialsRowAt(clientX, clientY)) return;
21062
21276
  probeSectionGapAt(clientX, clientY);
21063
21277
  probeImageAt(clientX, clientY);
@@ -21396,6 +21610,7 @@ function OhhwellsBridge() {
21396
21610
  }
21397
21611
  if (typeof content[STYLE_STORE_KEY] === "string") {
21398
21612
  stylesRef.current = content[STYLE_STORE_KEY];
21613
+ document.querySelectorAll('[data-ohw-editable="form"]').forEach((f) => markFormFields(f));
21399
21614
  applyStylesToDom(parseStyleStore(content[STYLE_STORE_KEY]));
21400
21615
  }
21401
21616
  applyBrandChrome(content);
@@ -21407,11 +21622,11 @@ function OhhwellsBridge() {
21407
21622
  continue;
21408
21623
  }
21409
21624
  if (key === AI_SECTIONS_KEY) continue;
21625
+ if (key === LOGO_PLACEHOLDER_KEY) continue;
21626
+ if (LOGO_IMAGE_KEYS.includes(key) && !String(val ?? "").trim()) continue;
21410
21627
  if (key === BRAND_KIT_KEY) continue;
21411
21628
  if (key === STYLE_STORE_KEY) continue;
21412
21629
  if (BRAND_CHROME_KEYS.has(key)) continue;
21413
- if (key === LOGO_PLACEHOLDER_KEY) continue;
21414
- if (LOGO_IMAGE_KEYS.includes(key) && !String(val ?? "").trim()) continue;
21415
21630
  if (applyVideoSettingNode(key, val)) continue;
21416
21631
  if (applyCarouselNode(key, val)) continue;
21417
21632
  document.querySelectorAll(`[data-ohw-key="${key}"]`).forEach((el) => {
@@ -21636,10 +21851,26 @@ function OhhwellsBridge() {
21636
21851
  const handleGetBrand = (e) => {
21637
21852
  if (e.data?.type !== "ow:get-brand") return;
21638
21853
  const template = deriveTemplateBrand();
21639
- const value = brandKitRef.current || (template ? JSON.stringify(template) : "");
21854
+ const fallback = template ?? (() => {
21855
+ const fonts = deriveTemplateFonts();
21856
+ return fonts ? { palette: AI_DEFAULT_BRAND.palette, fonts } : null;
21857
+ })();
21858
+ const value = brandKitRef.current || (fallback ? JSON.stringify(fallback) : "");
21640
21859
  postToParentRef.current({ type: "ow:brand-value", value });
21641
21860
  };
21642
21861
  window.addEventListener("message", handleGetBrand);
21862
+ const handleGetTemplateFonts = (e) => {
21863
+ if (e.data?.type !== "ow:get-template-fonts") return;
21864
+ const fonts = deriveTemplateFonts();
21865
+ postToParentRef.current({ type: "ow:template-fonts-value", value: fonts ? JSON.stringify(fonts) : "" });
21866
+ };
21867
+ window.addEventListener("message", handleGetTemplateFonts);
21868
+ const handleGetTemplateBrand = (e) => {
21869
+ if (e.data?.type !== "ow:get-template-brand") return;
21870
+ const brand = deriveTemplateBrand();
21871
+ postToParentRef.current({ type: "ow:template-brand-value", value: brand ? JSON.stringify(brand) : "" });
21872
+ };
21873
+ window.addEventListener("message", handleGetTemplateBrand);
21643
21874
  const handleMoveSection = (e) => {
21644
21875
  if (e.data?.type !== "ow:move-section") return;
21645
21876
  const instanceId = typeof e.data.instanceId === "string" ? e.data.instanceId : "";
@@ -21832,6 +22063,12 @@ function OhhwellsBridge() {
21832
22063
  closeLinkPopoverRef.current();
21833
22064
  return;
21834
22065
  }
22066
+ if (floatingPanelOpenRef.current) {
22067
+ setFloatingPanelRef.current(null);
22068
+ deselectRef.current();
22069
+ deactivateRef.current();
22070
+ return;
22071
+ }
21835
22072
  deselectRef.current();
21836
22073
  deactivateRef.current();
21837
22074
  clearMediaSelectionRef.current();
@@ -22120,8 +22357,12 @@ function OhhwellsBridge() {
22120
22357
  if (inserted) {
22121
22358
  const tracker = getSectionsTracker();
22122
22359
  postToParentRef.current({ type: "ow:change", nodes: [{ key: "__ohw_sections", text: tracker.textContent ?? "[]" }] });
22123
- const h = document.body.scrollHeight;
22124
- if (h > 50) postToParentRef.current({ type: "ow:height", height: h });
22360
+ const reportHeight = () => {
22361
+ const h = document.body.scrollHeight;
22362
+ if (h > 50) postToParentRef.current({ type: "ow:height", height: h });
22363
+ };
22364
+ reportHeight();
22365
+ setTimeout(reportHeight, 500);
22125
22366
  }
22126
22367
  };
22127
22368
  const handleSwitchSchedule = (e) => {
@@ -22542,15 +22783,17 @@ function OhhwellsBridge() {
22542
22783
  window.removeEventListener("message", handleAiSetBrand);
22543
22784
  window.removeEventListener("message", handleAiSetStyles);
22544
22785
  window.removeEventListener("message", handleGetBrand);
22786
+ window.removeEventListener("message", handleGetTemplateFonts);
22787
+ window.removeEventListener("message", handleGetTemplateBrand);
22545
22788
  window.removeEventListener("message", handleMoveSection);
22546
22789
  window.removeEventListener("message", handlePanelDragging);
22547
22790
  window.removeEventListener("message", handleDeleteSection);
22548
22791
  window.removeEventListener("message", handleDuplicateSection);
22549
22792
  window.removeEventListener("message", handleDeactivate);
22550
- document.documentElement.removeAttribute("data-ohw-panel-dragging");
22551
22793
  window.removeEventListener("message", handleToastAction);
22552
22794
  window.removeEventListener("message", handleFormCount);
22553
22795
  window.removeEventListener("message", handleUiEscape);
22796
+ document.documentElement.removeAttribute("data-ohw-panel-dragging");
22554
22797
  autoSaveTimers.current.forEach(clearTimeout);
22555
22798
  autoSaveTimers.current.clear();
22556
22799
  if (imageUnhoverTimerRef.current) clearTimeout(imageUnhoverTimerRef.current);
@@ -22753,7 +22996,7 @@ function OhhwellsBridge() {
22753
22996
  postToParent2({
22754
22997
  type: "ow:ready",
22755
22998
  version: "1",
22756
- bridgeVersion: "0.1.110",
22999
+ bridgeVersion: "0.1.112",
22757
23000
  path: pathname,
22758
23001
  nodes: collectEditableNodes(editContentRef.current),
22759
23002
  sections
@@ -23369,7 +23612,7 @@ function OhhwellsBridge() {
23369
23612
  "span",
23370
23613
  {
23371
23614
  "data-ohw-form-count": "",
23372
- 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",
23615
+ 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",
23373
23616
  children: formPickCount
23374
23617
  }
23375
23618
  )
@@ -23607,11 +23850,11 @@ function OhhwellsBridge() {
23607
23850
  className: "fixed left-0 w-full z-2147483646 flex items-center pointer-events-none",
23608
23851
  style: { top: sectionGap.y, transform: "translateY(-50%)" },
23609
23852
  children: [
23610
- /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("div", { className: "flex-1 bg-primary", style: { height: 3 } }),
23853
+ /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("div", { className: "flex-1 bg-bridge-primary", style: { height: 3 } }),
23611
23854
  /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
23612
23855
  Badge,
23613
23856
  {
23614
- 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",
23857
+ 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",
23615
23858
  onClick: () => {
23616
23859
  window.parent.postMessage(
23617
23860
  {
@@ -23625,7 +23868,7 @@ function OhhwellsBridge() {
23625
23868
  children: "Add Section"
23626
23869
  }
23627
23870
  ),
23628
- /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("div", { className: "flex-1 bg-primary", style: { height: 3 } })
23871
+ /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("div", { className: "flex-1 bg-bridge-primary", style: { height: 3 } })
23629
23872
  ]
23630
23873
  }
23631
23874
  ),
@@ -23673,6 +23916,59 @@ function OhhwellsBridge() {
23673
23916
  ) : null
23674
23917
  ] });
23675
23918
  }
23919
+
23920
+ // src/ui/EmptySection.tsx
23921
+ var import_link = __toESM(require("next/link"), 1);
23922
+ var import_jsx_runtime34 = require("react/jsx-runtime");
23923
+ function EmptySection({ title, homeHref = "/", eyebrowKey, titleKey, subtitleKey }) {
23924
+ return /* @__PURE__ */ (0, import_jsx_runtime34.jsxs)(import_jsx_runtime34.Fragment, { children: [
23925
+ /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(
23926
+ "p",
23927
+ {
23928
+ style: {
23929
+ fontFamily: "var(--brand-font-body)",
23930
+ fontSize: "0.75rem",
23931
+ fontWeight: 500,
23932
+ letterSpacing: "0.15em",
23933
+ textTransform: "uppercase",
23934
+ color: "var(--brand-accent)",
23935
+ marginBottom: "1.5rem"
23936
+ },
23937
+ 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" }) })
23938
+ }
23939
+ ),
23940
+ /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(
23941
+ "h1",
23942
+ {
23943
+ style: {
23944
+ fontFamily: "var(--brand-font-heading)",
23945
+ fontSize: "clamp(2rem, 3.5vw, 2.75rem)",
23946
+ lineHeight: 1.1,
23947
+ letterSpacing: "-0.025em",
23948
+ color: "var(--brand-text)",
23949
+ marginBottom: "1rem"
23950
+ },
23951
+ ...titleKey ? { "data-ohw-editable": "text", "data-ohw-key": titleKey, "data-ohw-max-length": 80 } : {},
23952
+ children: title
23953
+ }
23954
+ ),
23955
+ /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(
23956
+ "p",
23957
+ {
23958
+ style: {
23959
+ fontFamily: "var(--brand-font-body)",
23960
+ fontSize: "1rem",
23961
+ lineHeight: 1.7,
23962
+ fontWeight: 300,
23963
+ color: "var(--brand-text-muted)",
23964
+ maxWidth: "340px"
23965
+ },
23966
+ ...subtitleKey ? { "data-ohw-editable": "text", "data-ohw-key": subtitleKey, "data-ohw-max-length": 160 } : {},
23967
+ children: "This page doesn't have any content yet."
23968
+ }
23969
+ )
23970
+ ] });
23971
+ }
23676
23972
  // Annotate the CommonJS export names for ESM import in node:
23677
23973
  0 && (module.exports = {
23678
23974
  AI_DEFAULT_BRAND,
@@ -23690,6 +23986,7 @@ function OhhwellsBridge() {
23690
23986
  DropdownMenuItem,
23691
23987
  DropdownMenuSeparator,
23692
23988
  DropdownMenuTrigger,
23989
+ EmptySection,
23693
23990
  ItemActionToolbar,
23694
23991
  ItemInteractionLayer,
23695
23992
  LinkEditorPanel,