@ohhwells/bridge 0.1.110 → 0.1.112-next.289

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
@@ -8251,6 +8433,7 @@ var FIELD_TYPES = [
8251
8433
  var FIELD_ATTR = "data-ohw-form-field";
8252
8434
  var FIELD_TYPE_ATTR = "data-ohw-field-type";
8253
8435
  var PLACEHOLDER_EDIT_ATTR = "data-ohw-placeholder-edit";
8436
+ var seedFieldByForm = /* @__PURE__ */ new WeakMap();
8254
8437
  function fieldsKey(formKey) {
8255
8438
  return `${formKey}-fields`;
8256
8439
  }
@@ -8336,7 +8519,11 @@ function markFormFields(form) {
8336
8519
  ensureFieldLabel(wrapper, key);
8337
8520
  syncRequiredMark(wrapper);
8338
8521
  });
8339
- return listFieldWrappers(form);
8522
+ const wrappers = listFieldWrappers(form);
8523
+ if (wrappers.length > 0 && !seedFieldByForm.has(form)) {
8524
+ seedFieldByForm.set(form, wrappers[0].cloneNode(true));
8525
+ }
8526
+ return wrappers;
8340
8527
  }
8341
8528
  function readFieldsFromDom(form) {
8342
8529
  return listFieldWrappers(form).map((wrapper) => {
@@ -8480,12 +8667,27 @@ function insertField(form, type) {
8480
8667
  wrapper = source.cloneNode(true);
8481
8668
  source.after(wrapper);
8482
8669
  } else {
8483
- wrapper = document.createElement("div");
8484
- const label2 = document.createElement("label");
8485
- const input2 = document.createElement("input");
8486
- wrapper.append(label2, input2);
8670
+ const seed = seedFieldByForm.get(form);
8671
+ if (seed) {
8672
+ wrapper = seed.cloneNode(true);
8673
+ } else {
8674
+ wrapper = document.createElement("div");
8675
+ const input2 = document.createElement("input");
8676
+ input2.style.width = "100%";
8677
+ input2.style.boxSizing = "border-box";
8678
+ input2.style.padding = "12px";
8679
+ input2.style.border = "1px solid color-mix(in srgb, currentColor 35%, transparent)";
8680
+ input2.style.borderRadius = "8px";
8681
+ input2.style.background = "#fff";
8682
+ input2.style.fontFamily = "inherit";
8683
+ input2.style.fontSize = "inherit";
8684
+ input2.style.color = "inherit";
8685
+ wrapper.append(input2);
8686
+ }
8487
8687
  const submit = form.querySelector('button, input[type="submit"]');
8488
- if (submit) submit.before(wrapper);
8688
+ let anchor = submit;
8689
+ while (anchor && anchor.parentElement !== form) anchor = anchor.parentElement;
8690
+ if (anchor) anchor.before(wrapper);
8489
8691
  else form.appendChild(wrapper);
8490
8692
  }
8491
8693
  wrapper.setAttribute(FIELD_ATTR, key);
@@ -8620,13 +8822,13 @@ function FormFieldToolbar({
8620
8822
  ]
8621
8823
  }
8622
8824
  ) }),
8623
- /* @__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) => {
8624
8826
  const Icon = TYPE_ICONS[entry.type];
8625
8827
  return /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(
8626
8828
  DropdownMenuItem,
8627
8829
  {
8628
8830
  onSelect: () => onTypeChange(entry.type),
8629
- 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" : ""),
8630
8832
  children: [
8631
8833
  /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(Icon, { size: 14, strokeWidth: 1.75, className: "shrink-0", "aria-hidden": true }),
8632
8834
  entry.label
@@ -8643,7 +8845,7 @@ function FormFieldToolbar({
8643
8845
  type: "button",
8644
8846
  "aria-pressed": required,
8645
8847
  onClick: onRequiredToggle,
8646
- 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"),
8647
8849
  "data-ohw-field-required-toggle": "",
8648
8850
  children: [
8649
8851
  /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(import_lucide_react4.Asterisk, { size: 14, strokeWidth: 2, "aria-hidden": true }),
@@ -8663,7 +8865,7 @@ function FormFieldToolbar({
8663
8865
  children: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(import_lucide_react4.MoreHorizontal, { size: 15, "aria-hidden": true })
8664
8866
  }
8665
8867
  ) }),
8666
- /* @__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: [
8667
8869
  /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(DropdownMenuItem, { onSelect: onDuplicate, className: "rounded-md py-2 text-[13px]", children: [
8668
8870
  /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(import_lucide_react4.Copy, { size: 14, strokeWidth: 1.75, className: "shrink-0", "aria-hidden": true }),
8669
8871
  "Duplicate"
@@ -8691,7 +8893,7 @@ function FieldTypePicker({ onPick }) {
8691
8893
  {
8692
8894
  type: "button",
8693
8895
  onClick: () => onPick(entry.type),
8694
- 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",
8695
8897
  children: [
8696
8898
  /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(Icon, { size: 26, strokeWidth: 1.5, "aria-hidden": true }),
8697
8899
  entry.label
@@ -8717,7 +8919,7 @@ var buttonVariants = cva(
8717
8919
  {
8718
8920
  variants: {
8719
8921
  variant: {
8720
- default: "bg-primary text-primary-foreground hover:opacity-90",
8922
+ default: "bg-bridge-primary text-primary-foreground hover:opacity-90",
8721
8923
  outline: "border border-border bg-background text-foreground shadow-sm hover:bg-muted/80",
8722
8924
  ghost: "min-w-0 px-3 py-2 text-foreground hover:bg-muted/50"
8723
8925
  },
@@ -8812,6 +9014,7 @@ function MediaOverlay({
8812
9014
  (prev) => prev && prev.fullWidth === width && prev.height === height ? prev : { fullWidth: width, height }
8813
9015
  );
8814
9016
  }, [isVideo]);
9017
+ const replaceLabel = isVideo ? "Replace video" : hover.elementType === "bg-image" ? "Replace background" : "Replace image";
8815
9018
  const replaceMode = buttonSize ? replaceButtonMode({ width: rect.width, height: rect.height }, buttonSize) : "none";
8816
9019
  const box = {
8817
9020
  position: "fixed",
@@ -8917,8 +9120,8 @@ function MediaOverlay({
8917
9120
  pointerEvents: hover.hasTextOverlap ? "none" : "auto",
8918
9121
  // Selected: a firm component ring with no wash, so the image reads as chosen rather
8919
9122
  // than hovered. Hover keeps the existing tinted preview.
8920
- boxShadow: selected ? "inset 0 0 0 2px var(--color-primary)" : "inset 0 0 0 1.5px var(--color-primary)",
8921
- 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)"
8922
9125
  },
8923
9126
  onClick: () => onSelect && !selected ? onSelect(hover.key) : onReplace(hover.key),
8924
9127
  children: [
@@ -8941,17 +9144,17 @@ function MediaOverlay({
8941
9144
  },
8942
9145
  children: [
8943
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 }),
8944
- isVideo ? "Replace video" : "Replace image"
9147
+ replaceLabel
8945
9148
  ]
8946
9149
  }
8947
9150
  ),
8948
- replaceMode === "none" ? null : /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(
9151
+ showChrome && replaceMode !== "none" && /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(
8949
9152
  Button,
8950
9153
  {
8951
9154
  "data-ohw-media-overlay": "",
8952
9155
  variant: "outline",
8953
9156
  size: "sm",
8954
- "aria-label": isVideo ? "Replace video" : "Replace image",
9157
+ "aria-label": replaceLabel,
8955
9158
  className: "gap-1.5 cursor-pointer hover:bg-background",
8956
9159
  style: {
8957
9160
  ...OVERLAY_BUTTON_STYLE,
@@ -8974,7 +9177,7 @@ function MediaOverlay({
8974
9177
  },
8975
9178
  children: [
8976
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 }),
8977
- replaceMode === "full" ? isVideo ? "Replace video" : hover.elementType === "bg-image" ? "Replace background" : "Replace image" : null
9180
+ replaceMode === "full" ? replaceLabel : null
8978
9181
  ]
8979
9182
  }
8980
9183
  )
@@ -9012,8 +9215,8 @@ function CarouselOverlay({
9012
9215
  height: rect.height,
9013
9216
  zIndex: 2147483646,
9014
9217
  pointerEvents: "auto",
9015
- boxShadow: "inset 0 0 0 1.5px var(--color-primary)",
9016
- 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)"
9017
9220
  },
9018
9221
  onClick: () => onEdit(hover.key),
9019
9222
  children: /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(
@@ -9866,7 +10069,7 @@ function SectionTreeItem({
9866
10069
  /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
9867
10070
  "div",
9868
10071
  {
9869
- 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",
9870
10073
  "aria-hidden": true
9871
10074
  }
9872
10075
  ),
@@ -9885,7 +10088,7 @@ function SectionTreeItem({
9885
10088
  className: cn(
9886
10089
  "flex h-9 min-w-0 flex-1 items-center gap-2 rounded-md border border-border bg-background p-3",
9887
10090
  interactive && "cursor-pointer hover:bg-muted/30",
9888
- interactive && selected && "border-primary"
10091
+ interactive && selected && "border-bridge-primary"
9889
10092
  ),
9890
10093
  children: [
9891
10094
  /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
@@ -10015,7 +10218,7 @@ function UrlOrPageInput({
10015
10218
  };
10016
10219
  const fieldClassName = cn(
10017
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]",
10018
- 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"
10019
10222
  );
10020
10223
  return /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)("div", { className: "flex w-full flex-col gap-2 p-0", children: [
10021
10224
  /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(Label, { htmlFor: inputId, className: cn(urlError && "text-destructive"), children: "Destination" }),
@@ -13922,6 +14125,7 @@ function readLogoSizeState(content, placement) {
13922
14125
  function getLogoElement(el) {
13923
14126
  const marked = el.closest('[data-ohw-role="logo"], [data-ohw-logo]');
13924
14127
  if (marked) return marked;
14128
+ if (el.closest('[data-ohw-editable="icon"]')) return null;
13925
14129
  const root = el.closest("nav, [data-ohw-nav-root], footer");
13926
14130
  if (!root) return null;
13927
14131
  const anchor = el.closest("a");
@@ -14209,7 +14413,7 @@ function DisplaySwitch({
14209
14413
  onClick: () => onChange(!checked),
14210
14414
  className: cn(
14211
14415
  "relative h-5 w-9 shrink-0 rounded-full transition-colors",
14212
- checked ? "bg-primary" : "bg-primary-50",
14416
+ checked ? "bg-bridge-primary" : "bg-primary-50",
14213
14417
  disabled ? "cursor-default opacity-50" : "cursor-pointer"
14214
14418
  ),
14215
14419
  children: /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(
@@ -14217,7 +14421,7 @@ function DisplaySwitch({
14217
14421
  {
14218
14422
  className: cn(
14219
14423
  "absolute top-0.5 h-4 w-4 rounded-full bg-white transition-all",
14220
- checked ? "left-[1.125rem]" : "left-0.5"
14424
+ checked ? "left-4.5" : "left-0.5"
14221
14425
  )
14222
14426
  }
14223
14427
  )
@@ -15852,7 +16056,7 @@ var badgeVariants = cva(
15852
16056
  {
15853
16057
  variants: {
15854
16058
  variant: {
15855
- default: "border-transparent bg-primary text-primary-foreground",
16059
+ default: "border-transparent bg-bridge-primary text-primary-foreground",
15856
16060
  secondary: "border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80",
15857
16061
  destructive: "border-transparent bg-destructive text-destructive-foreground hover:bg-destructive/80",
15858
16062
  outline: "text-foreground"
@@ -16034,21 +16238,10 @@ function parseSchedulingInsertAfter(insertAfter) {
16034
16238
  insertBefore: insertAfter.slice(idx + INSERT_BEFORE_MARKER.length)
16035
16239
  };
16036
16240
  }
16037
- function resolveSchedulingInsert(insertAfter, explicitInsertBefore) {
16038
- const parsed = parseSchedulingInsertAfter(insertAfter);
16039
- const insertBefore = explicitInsertBefore ?? parsed.insertBefore;
16040
- const effectiveInsertAfter = insertBefore && parsed.insertBefore === null ? `${insertAfter}${INSERT_BEFORE_MARKER}${insertBefore}` : insertAfter;
16041
- return { effectiveInsertAfter, insertBefore };
16042
- }
16043
- function getSchedulingMountPoint(insertAfter) {
16044
- const { anchor } = parseSchedulingInsertAfter(insertAfter);
16045
- let anchorEl = document.querySelector(`[data-ohw-section="${anchor}"]`);
16046
- if (!anchorEl && anchor === "scheduling") {
16047
- const widgets = Array.from(document.querySelectorAll('[data-ohw-section^="scheduling-"]'));
16048
- anchorEl = widgets.at(-1) ?? null;
16049
- }
16050
- if (!anchorEl) return null;
16051
- 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 };
16052
16245
  }
16053
16246
  function schedulingMountDepth(insertAfter) {
16054
16247
  if (!insertAfter.includes(INSERT_BEFORE_MARKER)) return 0;
@@ -16065,8 +16258,7 @@ function getPageSchedulingEntries(raw) {
16065
16258
  }
16066
16259
  }
16067
16260
  function isSchedulingWidgetMissing(entry) {
16068
- const { effectiveInsertAfter } = resolveSchedulingInsert(entry.insertAfter);
16069
- return !document.querySelector(`[data-ohw-section="${schedulingSectionId(effectiveInsertAfter)}"]`);
16261
+ return !document.querySelector(`[data-ohw-section="${schedulingSectionId(entry.insertAfter)}"]`);
16070
16262
  }
16071
16263
  function hasMissingSchedulingWidgets(entries) {
16072
16264
  return entries.some(isSchedulingWidgetMissing);
@@ -16104,17 +16296,18 @@ function initSectionsFromContent(content, removeExisting = false, currentPath =
16104
16296
  } catch {
16105
16297
  }
16106
16298
  }
16107
- function mountSchedulingWidget(insertAfter, notifyOnConnect = false, scheduleId, explicitInsertBefore) {
16108
- const { effectiveInsertAfter, insertBefore } = resolveSchedulingInsert(insertAfter, explicitInsertBefore);
16109
- 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);
16110
16302
  if (document.querySelector(`[data-ohw-section="${sectionId}"]`)) return false;
16111
- const mountPoint = getSchedulingMountPoint(effectiveInsertAfter);
16112
- 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;
16113
16306
  const container = document.createElement("div");
16114
16307
  container.dataset.ohwSectionContainer = "scheduling";
16115
16308
  container.dataset.ohwInstance = sectionId;
16116
- if (insertBefore) {
16117
- const beforeAnchor = document.querySelector(`[data-ohw-section="${insertBefore}"]`);
16309
+ if (beforeId) {
16310
+ const beforeAnchor = document.querySelector(`[data-ohw-section="${beforeId}"]`);
16118
16311
  const beforePoint = beforeAnchor?.closest('[data-ohw-section-container="scheduling"]') ?? beforeAnchor;
16119
16312
  if (!beforePoint) return false;
16120
16313
  beforePoint.insertAdjacentElement("beforebegin", container);
@@ -16125,20 +16318,26 @@ function mountSchedulingWidget(insertAfter, notifyOnConnect = false, scheduleId,
16125
16318
  }
16126
16319
  tail.insertAdjacentElement("afterend", container);
16127
16320
  }
16128
- const root = (0, import_client2.createRoot)(container);
16129
- schedulingRoots.set(container, root);
16130
- (0, import_react_dom4.flushSync)(() => {
16131
- root.render(
16132
- /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
16133
- SchedulingWidget,
16134
- {
16135
- notifyOnConnect,
16136
- initialScheduleId: scheduleId,
16137
- insertAfter: effectiveInsertAfter
16138
- }
16139
- )
16140
- );
16141
- });
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
+ }
16142
16341
  const tracker = getSectionsTracker();
16143
16342
  let sections = [];
16144
16343
  try {
@@ -16146,10 +16345,12 @@ function mountSchedulingWidget(insertAfter, notifyOnConnect = false, scheduleId,
16146
16345
  } catch {
16147
16346
  }
16148
16347
  const inEditor = typeof window !== "undefined" && window.self !== window.top;
16149
- if (inEditor && !sections.find((s) => s.type === "scheduling" && s.insertAfter === effectiveInsertAfter)) {
16348
+ if (inEditor && !sections.find((s) => s.type === "scheduling" && s.insertAfter === widgetId)) {
16150
16349
  sections.push({
16151
16350
  type: "scheduling",
16152
- insertAfter: effectiveInsertAfter,
16351
+ insertAfter: widgetId,
16352
+ anchorId,
16353
+ beforeId: beforeId ?? null,
16153
16354
  pagePath: window.location.pathname,
16154
16355
  ...scheduleId ? { scheduleId } : {}
16155
16356
  });
@@ -16163,7 +16364,8 @@ function mountSchedulingEntries(entries, notifyOnConnect = false) {
16163
16364
  for (let i = pending.length - 1; i >= 0; i--) {
16164
16365
  const entry = pending[i];
16165
16366
  const shouldNotify = typeof notifyOnConnect === "function" ? notifyOnConnect(entry) : notifyOnConnect;
16166
- 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)) {
16167
16369
  pending.splice(i, 1);
16168
16370
  }
16169
16371
  }
@@ -16327,6 +16529,11 @@ function applyLinkByKey(key, val) {
16327
16529
  hrefAnchors.forEach((el) => applyLinkHref(el, val));
16328
16530
  }
16329
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
+ }
16330
16537
  function isInsideFloatingPanel(target) {
16331
16538
  return Boolean(target.closest("[data-ohw-floating-panel]"));
16332
16539
  }
@@ -16334,11 +16541,6 @@ function isPointOverFloatingPanel(clientX, clientY) {
16334
16541
  const el = document.elementFromPoint(clientX, clientY);
16335
16542
  return Boolean(el instanceof Element && el.closest("[data-ohw-floating-panel]"));
16336
16543
  }
16337
- function isInsideLinkEditor(target) {
16338
- return Boolean(
16339
- 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"]')
16340
- );
16341
- }
16342
16544
  function getHrefKeyFromElement(el) {
16343
16545
  if (!el) return null;
16344
16546
  const anchor = el.closest("[data-ohw-href-key]");
@@ -16607,7 +16809,7 @@ function getNavigationSelectionParent(el) {
16607
16809
  if (!isFooterLinksContainer(el) && (el.hasAttribute("data-ohw-footer-col") || el.hasAttribute("data-ohw-footer-column") || isInferredFooterGroup2(el))) {
16608
16810
  return getFooterLinksContainer();
16609
16811
  }
16610
- if (el.hasAttribute("data-ohw-nav-container") || el.hasAttribute("data-ohw-nav-drawer") || isFooterLinksContainer(el)) {
16812
+ 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)) {
16611
16813
  return getNavigationRoot(el);
16612
16814
  }
16613
16815
  return null;
@@ -16823,7 +17025,6 @@ var ICONS = {
16823
17025
  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"/>',
16824
17026
  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"/>'
16825
17027
  };
16826
- var HEIGHT_SETTLE_DELAYS = [150, 400, 800];
16827
17028
  var SELECTION_CHROME_GAP2 = 4;
16828
17029
  var TOOLBAR_STROKE_GAP2 = 4;
16829
17030
  var TOOLBAR_OFFSET_FROM_ELEMENT = SELECTION_CHROME_GAP2 + TOOLBAR_STROKE_GAP2;
@@ -17207,6 +17408,7 @@ function StateToggle({
17207
17408
  );
17208
17409
  }
17209
17410
  var contentCache = /* @__PURE__ */ new Map();
17411
+ var fetchedContentPaths = /* @__PURE__ */ new Set();
17210
17412
  var brandingCache = /* @__PURE__ */ new Map();
17211
17413
  var OHW_LOADER_STYLE = {
17212
17414
  position: "fixed",
@@ -17755,13 +17957,6 @@ function OhhwellsBridge() {
17755
17957
  const [isItemDragging, setIsItemDragging] = (0, import_react17.useState)(false);
17756
17958
  const [isFooterFrameSelection, setIsFooterFrameSelection] = (0, import_react17.useState)(false);
17757
17959
  isFooterFrameSelectionRef.current = isFooterFrameSelection;
17758
- const [floatingPanel, setFloatingPanel] = (0, import_react17.useState)(null);
17759
- const floatingPanelOpenRef = (0, import_react17.useRef)(false);
17760
- floatingPanelOpenRef.current = floatingPanel !== null;
17761
- const [floatingPanelPos, setFloatingPanelPos] = (0, import_react17.useState)(null);
17762
- const [logoSizeDraft, setLogoSizeDraft] = (0, import_react17.useState)(null);
17763
- const [editorViewport, setEditorViewport] = (0, import_react17.useState)("desktop");
17764
- const [parentScrollSnap, setParentScrollSnap] = (0, import_react17.useState)(null);
17765
17960
  const [navDropdownPreviewOpen, setNavDropdownPreviewOpen] = (0, import_react17.useState)(null);
17766
17961
  const [footerHeadingVisible, setFooterHeadingVisible] = (0, import_react17.useState)(null);
17767
17962
  const footerDragRef = (0, import_react17.useRef)(null);
@@ -17778,7 +17973,15 @@ function OhhwellsBridge() {
17778
17973
  const aiSectionsRef = (0, import_react17.useRef)("");
17779
17974
  const brandKitRef = (0, import_react17.useRef)("");
17780
17975
  const stylesRef = (0, import_react17.useRef)("");
17976
+ const appliedDrawerRef = (0, import_react17.useRef)(null);
17781
17977
  const pendingDeleteUndoRef = (0, import_react17.useRef)(null);
17978
+ const [floatingPanel, setFloatingPanel] = (0, import_react17.useState)(null);
17979
+ const floatingPanelOpenRef = (0, import_react17.useRef)(false);
17980
+ const setFloatingPanelRef = (0, import_react17.useRef)(setFloatingPanel);
17981
+ const [floatingPanelPos, setFloatingPanelPos] = (0, import_react17.useState)(null);
17982
+ const [logoSizeDraft, setLogoSizeDraft] = (0, import_react17.useState)(null);
17983
+ const [editorViewport, setEditorViewport] = (0, import_react17.useState)("desktop");
17984
+ const [parentScrollSnap, setParentScrollSnap] = (0, import_react17.useState)(null);
17782
17985
  const [sitePages, setSitePages] = (0, import_react17.useState)([]);
17783
17986
  const [sectionsByPath, setSectionsByPath] = (0, import_react17.useState)({});
17784
17987
  const sectionsPrefetchGenRef = (0, import_react17.useRef)(0);
@@ -17787,7 +17990,18 @@ function OhhwellsBridge() {
17787
17990
  const linkPopoverOpenRef = (0, import_react17.useRef)(false);
17788
17991
  const linkPopoverGraceUntilRef = (0, import_react17.useRef)(0);
17789
17992
  setLinkPopoverRef.current = setLinkPopover;
17993
+ setFloatingPanelRef.current = setFloatingPanel;
17790
17994
  linkPopoverSessionRef.current = linkPopover;
17995
+ floatingPanelOpenRef.current = Boolean(floatingPanel);
17996
+ (0, import_react17.useEffect)(() => {
17997
+ const syncViewport = () => {
17998
+ const next = window.innerWidth <= 480 ? "mobile" : "desktop";
17999
+ setEditorViewport((prev) => prev === next ? prev : next);
18000
+ };
18001
+ syncViewport();
18002
+ window.addEventListener("resize", syncViewport);
18003
+ return () => window.removeEventListener("resize", syncViewport);
18004
+ }, []);
17791
18005
  const {
17792
18006
  navDragRef,
17793
18007
  navPointerDragRef,
@@ -19123,6 +19337,7 @@ function OhhwellsBridge() {
19123
19337
  }
19124
19338
  if (typeof content[STYLE_STORE_KEY] === "string") {
19125
19339
  stylesRef.current = content[STYLE_STORE_KEY];
19340
+ document.querySelectorAll('[data-ohw-editable="form"]').forEach((f) => markFormFields(f));
19126
19341
  applyStylesToDom(parseStyleStore(content[STYLE_STORE_KEY]));
19127
19342
  }
19128
19343
  applyBrandChrome(content);
@@ -19130,11 +19345,11 @@ function OhhwellsBridge() {
19130
19345
  for (const [key, val] of Object.entries(content)) {
19131
19346
  if (key === "__ohw_sections") continue;
19132
19347
  if (key === AI_SECTIONS_KEY) continue;
19348
+ if (key === LOGO_PLACEHOLDER_KEY) continue;
19349
+ if (LOGO_IMAGE_KEYS.includes(key)) continue;
19133
19350
  if (key === BRAND_KIT_KEY) continue;
19134
19351
  if (key === STYLE_STORE_KEY) continue;
19135
19352
  if (BRAND_CHROME_KEYS.has(key)) continue;
19136
- if (key === LOGO_PLACEHOLDER_KEY) continue;
19137
- if (LOGO_IMAGE_KEYS.includes(key)) continue;
19138
19353
  if (applyVideoSettingNode(key, val)) continue;
19139
19354
  if (applyCarouselNode(key, val)) continue;
19140
19355
  document.querySelectorAll(`[data-ohw-key="${key}"]`).forEach((el) => {
@@ -19205,7 +19420,9 @@ function OhhwellsBridge() {
19205
19420
  let cancelled = false;
19206
19421
  setFetchState("loading");
19207
19422
  const apiUrl = process.env.NEXT_PUBLIC_FLOWOPS_API_URL ?? "https://flowops-backend-staging-2yfdh7pwpq-as.a.run.app";
19208
- fetch(`${apiUrl}/api/public/sites/${subdomain}/content`, { cache: "no-store" }).then((r2) => r2.ok ? r2.json() : null).then((data) => {
19423
+ const initialPath = pathname;
19424
+ fetchedContentPaths.add(`${subdomain}::${initialPath}`);
19425
+ fetch(`${apiUrl}/api/public/sites/${subdomain}/content?path=${encodeURIComponent(initialPath)}`, { cache: "no-store" }).then((r2) => r2.ok ? r2.json() : null).then((data) => {
19209
19426
  if (cancelled) return;
19210
19427
  const content = data?.content ?? {};
19211
19428
  const branding = Boolean(data?.showBranding);
@@ -19314,7 +19531,9 @@ function OhhwellsBridge() {
19314
19531
  }, [isEditMode]);
19315
19532
  (0, import_react17.useEffect)(() => {
19316
19533
  if (isEditMode || fetchState !== "done") return;
19534
+ console.log("env", process.env.NEXT_PUBLIC_FLOWOPS_API_URL);
19317
19535
  const apiUrl = process.env.NEXT_PUBLIC_FLOWOPS_API_URL ?? "https://flowops-backend-staging-2yfdh7pwpq-as.a.run.app";
19536
+ console.log({ apiUrl, subdomain });
19318
19537
  bindPublishedForms(apiUrl, subdomain, contentCache.get(subdomain) ?? {});
19319
19538
  }, [isEditMode, fetchState, subdomain]);
19320
19539
  (0, import_react17.useEffect)(() => {
@@ -19339,16 +19558,17 @@ function OhhwellsBridge() {
19339
19558
  applyAiSectionsToDom(parseAiSectionsState(content[AI_SECTIONS_KEY]));
19340
19559
  }
19341
19560
  if (typeof content[STYLE_STORE_KEY] === "string") {
19561
+ document.querySelectorAll('[data-ohw-editable="form"]').forEach((f) => markFormFields(f));
19342
19562
  applyStylesToDom(parseStyleStore(content[STYLE_STORE_KEY]));
19343
19563
  }
19344
19564
  for (const [key, val] of Object.entries(content)) {
19345
19565
  if (key === "__ohw_sections") continue;
19346
19566
  if (key === AI_SECTIONS_KEY) continue;
19567
+ if (key === LOGO_PLACEHOLDER_KEY) continue;
19568
+ if (LOGO_IMAGE_KEYS.includes(key)) continue;
19347
19569
  if (key === BRAND_KIT_KEY) continue;
19348
19570
  if (key === STYLE_STORE_KEY) continue;
19349
19571
  if (BRAND_CHROME_KEYS.has(key)) continue;
19350
- if (key === LOGO_PLACEHOLDER_KEY) continue;
19351
- if (LOGO_IMAGE_KEYS.includes(key)) continue;
19352
19572
  if (applyVideoSettingNode(key, val)) continue;
19353
19573
  if (applyCarouselNode(key, val)) continue;
19354
19574
  document.querySelectorAll(`[data-ohw-key="${key}"]`).forEach((el) => {
@@ -19399,6 +19619,17 @@ function OhhwellsBridge() {
19399
19619
  debounceTimer = setTimeout(applyFromCache, 150);
19400
19620
  };
19401
19621
  applyFromCache();
19622
+ const pathCacheKey = `${subdomain}::${pathname}`;
19623
+ if (!fetchedContentPaths.has(pathCacheKey)) {
19624
+ fetchedContentPaths.add(pathCacheKey);
19625
+ const apiUrl = process.env.NEXT_PUBLIC_FLOWOPS_API_URL ?? "https://flowops-backend-staging-2yfdh7pwpq-as.a.run.app";
19626
+ fetch(`${apiUrl}/api/public/sites/${subdomain}/content?path=${encodeURIComponent(pathname)}`, { cache: "no-store" }).then((r2) => r2.ok ? r2.json() : null).then((data) => {
19627
+ if (!data?.content) return;
19628
+ contentCache.set(subdomain, data.content);
19629
+ applyFromCache();
19630
+ }).catch(() => {
19631
+ });
19632
+ }
19402
19633
  observer = new MutationObserver(scheduleApply);
19403
19634
  observer.observe(document.body, { childList: true, subtree: true });
19404
19635
  return () => {
@@ -19443,6 +19674,17 @@ function OhhwellsBridge() {
19443
19674
  if (!isEditMode) return;
19444
19675
  initSectionInstancesFromContent(editContentRef.current, pathname);
19445
19676
  }, [pathname, isEditMode]);
19677
+ (0, import_react17.useEffect)(() => {
19678
+ if (!isEditMode) return;
19679
+ const observer = new MutationObserver(() => {
19680
+ const drawer = document.querySelector("[data-ohw-nav-drawer]");
19681
+ if (!drawer || drawer === appliedDrawerRef.current) return;
19682
+ appliedDrawerRef.current = drawer;
19683
+ applyStylesToDom(parseStyleStore(stylesRef.current));
19684
+ });
19685
+ observer.observe(document.body, { childList: true, subtree: true });
19686
+ return () => observer.disconnect();
19687
+ }, [isEditMode, pathname]);
19446
19688
  (0, import_react17.useEffect)(() => {
19447
19689
  const contentForNav = () => {
19448
19690
  if (isEditMode) return editContentRef.current;
@@ -19534,26 +19776,11 @@ function OhhwellsBridge() {
19534
19776
  const t2 = setTimeout(measure, 500);
19535
19777
  const ro = new ResizeObserver(schedule);
19536
19778
  ro.observe(document.body);
19537
- let lastWidth = window.innerWidth;
19538
- let resizeTimers = [];
19539
- const clearResizeTimers = () => {
19540
- resizeTimers.forEach(clearTimeout);
19541
- resizeTimers = [];
19542
- };
19543
- const handleResize = () => {
19544
- if (window.innerWidth === lastWidth) return;
19545
- lastWidth = window.innerWidth;
19546
- clearResizeTimers();
19547
- resizeTimers = HEIGHT_SETTLE_DELAYS.map((delay) => setTimeout(measure, delay));
19548
- };
19549
- window.addEventListener("resize", handleResize);
19550
19779
  return () => {
19551
19780
  clearTimeout(t1);
19552
19781
  clearTimeout(t2);
19553
19782
  if (raf != null) cancelAnimationFrame(raf);
19554
19783
  ro.disconnect();
19555
- clearResizeTimers();
19556
- window.removeEventListener("resize", handleResize);
19557
19784
  };
19558
19785
  }, [pathname, isEditMode, postToParent2]);
19559
19786
  (0, import_react17.useEffect)(() => {
@@ -19663,8 +19890,10 @@ function OhhwellsBridge() {
19663
19890
  crushed its fields together (OHH-490). Not media (image/bg-image/video/icon/map)
19664
19891
  either: those can be their own flex containers centering fallback content (a
19665
19892
  placeholder label, a mascot emoji) \u2014 forcing block broke that centering while the
19666
- image was unset (OHH-758-ish). */
19667
- [data-ohw-editable]:not([data-ohw-editable="form"]):not([data-ohw-editable="image"]):not([data-ohw-editable="bg-image"]):not([data-ohw-editable="video"]):not([data-ohw-editable="icon"]):not([data-ohw-editable="map"]) {
19893
+ image was unset (OHH-758-ish). Not inside a container that declares its editables
19894
+ inline (data-ohw-inline-editables): a row of inline links \u2014 a footer's legal line \u2014
19895
+ keeps its line; blockified labels put each link on its own row in the editor only. */
19896
+ [data-ohw-editable]:not([data-ohw-editable="form"]):not([data-ohw-editable="image"]):not([data-ohw-editable="bg-image"]):not([data-ohw-editable="video"]):not([data-ohw-editable="icon"]):not([data-ohw-editable="map"]):not([data-ohw-inline-editables] *) {
19668
19897
  display: block;
19669
19898
  }
19670
19899
  /* The block-force above is an author rule, so it beats the browser's own
@@ -19734,7 +19963,10 @@ function OhhwellsBridge() {
19734
19963
  flex-wrap: wrap !important;
19735
19964
  align-items: flex-end !important;
19736
19965
  }
19737
- [data-ohw-editable="form"] [data-ohw-form-field] {
19966
+ /* Generated forms are excluded: they are always a single column, so the 12rem
19967
+ flex BASIS \u2014 a minimum width in a horizontal row \u2014 reads as a 12rem HEIGHT
19968
+ there and inflates every field to the tallest one's size. */
19969
+ [data-ohw-editable="form"]:not([data-ai-form]) [data-ohw-form-field] {
19738
19970
  flex: 1 1 12rem !important;
19739
19971
  min-width: min(12rem, 100%) !important;
19740
19972
  max-width: 100% !important;
@@ -19843,9 +20075,6 @@ function OhhwellsBridge() {
19843
20075
  if (target.closest("[data-ohw-state-toggle]")) return;
19844
20076
  if (target.closest("[data-ohw-max-badge]")) return;
19845
20077
  if (isInsideLinkEditor(target)) return;
19846
- if (selectedMediaElRef.current && !selectedMediaElRef.current.contains(target) && !target.closest("[data-ohw-media-overlay], [data-ohw-edit-chrome], [data-ohw-item-interaction]")) {
19847
- clearMediaSelectionRef.current();
19848
- }
19849
20078
  if (isInsideFloatingPanel(target)) return;
19850
20079
  if (target.closest("[data-ohw-form-toolbar]")) return;
19851
20080
  if (target.closest(
@@ -19853,6 +20082,9 @@ function OhhwellsBridge() {
19853
20082
  )) {
19854
20083
  return;
19855
20084
  }
20085
+ if (selectedMediaElRef.current && !selectedMediaElRef.current.contains(target) && !target.closest("[data-ohw-media-overlay], [data-ohw-edit-chrome], [data-ohw-item-interaction]")) {
20086
+ clearMediaSelectionRef.current();
20087
+ }
19856
20088
  {
19857
20089
  const formEl = getFormElement(target);
19858
20090
  const onSuccessText = target.closest(`[${SUCCESS_TEXT_ATTR}]`);
@@ -20016,14 +20248,6 @@ function OhhwellsBridge() {
20016
20248
  }
20017
20249
  const clickedButton = findClosestButtonLike(target);
20018
20250
  const buttonOnMedia = Boolean(clickedButton && isMediaEditable(editable) && editable.contains(clickedButton));
20019
- console.log("[click-debug]", {
20020
- editableType: editable.dataset.ohwEditable,
20021
- editableTag: editable.tagName,
20022
- targetTag: target.tagName,
20023
- clickedButtonTag: clickedButton?.tagName ?? null,
20024
- buttonOnMedia,
20025
- isMediaEditableEditable: isMediaEditable(editable)
20026
- });
20027
20251
  if (isMediaEditable(editable) && !buttonOnMedia) {
20028
20252
  e.preventDefault();
20029
20253
  e.stopPropagation();
@@ -20050,11 +20274,6 @@ function OhhwellsBridge() {
20050
20274
  const hrefLookupTarget = buttonOnMedia ? clickedButton : editable;
20051
20275
  const hrefCtx = getHrefKeyFromElement(hrefLookupTarget);
20052
20276
  const navAnchor = hrefCtx ? getNavigationItemAnchor(hrefCtx.anchor) : null;
20053
- console.log("[click-debug 2]", {
20054
- hrefLookupTargetTag: hrefLookupTarget.tagName,
20055
- hrefCtx: hrefCtx ? { key: hrefCtx.key } : null,
20056
- navAnchorTag: navAnchor?.tagName ?? null
20057
- });
20058
20277
  if (navAnchor) {
20059
20278
  e.preventDefault();
20060
20279
  e.stopPropagation();
@@ -20224,6 +20443,9 @@ function OhhwellsBridge() {
20224
20443
  setHoveredItemRect(null);
20225
20444
  hoveredNavContainerRef.current = null;
20226
20445
  setHoveredNavContainerRect(null);
20446
+ siblingHintElRef.current = null;
20447
+ setSiblingHintRect(null);
20448
+ setSiblingHintRects([]);
20227
20449
  return;
20228
20450
  }
20229
20451
  {
@@ -20344,7 +20566,6 @@ function OhhwellsBridge() {
20344
20566
  hoveredNavContainerRef.current = null;
20345
20567
  setHoveredNavContainerRect(null);
20346
20568
  hoveredItemElRef.current = editable;
20347
- setHoveredItemRect(isSelectedForHoverRef.current(editable) ? null : editable.getBoundingClientRect());
20348
20569
  }
20349
20570
  }
20350
20571
  }
@@ -20641,7 +20862,7 @@ function OhhwellsBridge() {
20641
20862
  }
20642
20863
  };
20643
20864
  const probeImageAt = (clientX, clientY, isDragOver = false, fromParentViewport = false) => {
20644
- if (linkPopoverOpenRef.current) {
20865
+ if (linkPopoverOpenRef.current || floatingPanelOpenRef.current && isPointOverFloatingPanel(clientX, clientY)) {
20645
20866
  if (hoveredImageRef.current) {
20646
20867
  hoveredImageRef.current = null;
20647
20868
  hoveredImageHasTextOverlapRef.current = false;
@@ -21014,8 +21235,7 @@ function OhhwellsBridge() {
21014
21235
  };
21015
21236
  const handleMouseMove = (e) => {
21016
21237
  const { clientX, clientY } = e;
21017
- if (pointOwnedByFloatingPanel(clientX, clientY)) return;
21018
- if (isOverEditorChrome(clientX, clientY)) {
21238
+ if (document.documentElement.hasAttribute("data-ohw-panel-dragging") || floatingPanelOpenRef.current && isPointOverFloatingPanel(clientX, clientY) || isOverEditorChrome(clientX, clientY)) {
21019
21239
  document.querySelectorAll("[data-ohw-hovered]").forEach((el) => el.removeAttribute("data-ohw-hovered"));
21020
21240
  formHoverElRef.current = null;
21021
21241
  setFormHoverRect(null);
@@ -21023,7 +21243,12 @@ function OhhwellsBridge() {
21023
21243
  setHoveredItemRect(null);
21024
21244
  hoveredNavContainerRef.current = null;
21025
21245
  setHoveredNavContainerRect(null);
21246
+ siblingHintElRef.current = null;
21247
+ setSiblingHintRect(null);
21248
+ setSiblingHintRects([]);
21026
21249
  dismissImageHover();
21250
+ clearImageHover();
21251
+ setSectionGap(null);
21027
21252
  return;
21028
21253
  }
21029
21254
  if (probeSocialsRowAt(clientX, clientY)) return;
@@ -21035,7 +21260,11 @@ function OhhwellsBridge() {
21035
21260
  if (e.data?.type !== "ow:pointer-sync") return;
21036
21261
  const { clientX, clientY } = e.data;
21037
21262
  if (typeof clientX !== "number" || typeof clientY !== "number") return;
21038
- if (pointOwnedByFloatingPanel(clientX, clientY)) return;
21263
+ if (document.documentElement.hasAttribute("data-ohw-panel-dragging") || floatingPanelOpenRef.current && isPointOverFloatingPanel(clientX, clientY)) {
21264
+ dismissImageHover();
21265
+ clearImageHover();
21266
+ return;
21267
+ }
21039
21268
  if (probeSocialsRowAt(clientX, clientY)) return;
21040
21269
  probeSectionGapAt(clientX, clientY);
21041
21270
  probeImageAt(clientX, clientY);
@@ -21374,6 +21603,7 @@ function OhhwellsBridge() {
21374
21603
  }
21375
21604
  if (typeof content[STYLE_STORE_KEY] === "string") {
21376
21605
  stylesRef.current = content[STYLE_STORE_KEY];
21606
+ document.querySelectorAll('[data-ohw-editable="form"]').forEach((f) => markFormFields(f));
21377
21607
  applyStylesToDom(parseStyleStore(content[STYLE_STORE_KEY]));
21378
21608
  }
21379
21609
  applyBrandChrome(content);
@@ -21385,11 +21615,11 @@ function OhhwellsBridge() {
21385
21615
  continue;
21386
21616
  }
21387
21617
  if (key === AI_SECTIONS_KEY) continue;
21618
+ if (key === LOGO_PLACEHOLDER_KEY) continue;
21619
+ if (LOGO_IMAGE_KEYS.includes(key) && !String(val ?? "").trim()) continue;
21388
21620
  if (key === BRAND_KIT_KEY) continue;
21389
21621
  if (key === STYLE_STORE_KEY) continue;
21390
21622
  if (BRAND_CHROME_KEYS.has(key)) continue;
21391
- if (key === LOGO_PLACEHOLDER_KEY) continue;
21392
- if (LOGO_IMAGE_KEYS.includes(key) && !String(val ?? "").trim()) continue;
21393
21623
  if (applyVideoSettingNode(key, val)) continue;
21394
21624
  if (applyCarouselNode(key, val)) continue;
21395
21625
  document.querySelectorAll(`[data-ohw-key="${key}"]`).forEach((el) => {
@@ -21407,6 +21637,7 @@ function OhhwellsBridge() {
21407
21637
  applyMapQuery(el, val);
21408
21638
  } else if (el.dataset.ohwEditable === "icon") {
21409
21639
  applyIconMarkup(el, val);
21640
+ } else if (el.dataset.ohwEditable === "form") {
21410
21641
  } else if (isIconMarkupValue(val)) {
21411
21642
  } else {
21412
21643
  el.innerHTML = val;
@@ -21416,6 +21647,9 @@ function OhhwellsBridge() {
21416
21647
  }
21417
21648
  applyLogoFromContent(content);
21418
21649
  applyLogoSizes(content);
21650
+ document.querySelectorAll('[data-ohw-editable="form"]').forEach((form) => {
21651
+ reconcileFieldsFromContent(form, content);
21652
+ });
21419
21653
  if (sectionsJson) {
21420
21654
  initSectionsFromContent({ __ohw_sections: sectionsJson }, true);
21421
21655
  sectionsLoadedRef.current = true;
@@ -21610,10 +21844,26 @@ function OhhwellsBridge() {
21610
21844
  const handleGetBrand = (e) => {
21611
21845
  if (e.data?.type !== "ow:get-brand") return;
21612
21846
  const template = deriveTemplateBrand();
21613
- const value = brandKitRef.current || (template ? JSON.stringify(template) : "");
21847
+ const fallback = template ?? (() => {
21848
+ const fonts = deriveTemplateFonts();
21849
+ return fonts ? { palette: AI_DEFAULT_BRAND.palette, fonts } : null;
21850
+ })();
21851
+ const value = brandKitRef.current || (fallback ? JSON.stringify(fallback) : "");
21614
21852
  postToParentRef.current({ type: "ow:brand-value", value });
21615
21853
  };
21616
21854
  window.addEventListener("message", handleGetBrand);
21855
+ const handleGetTemplateFonts = (e) => {
21856
+ if (e.data?.type !== "ow:get-template-fonts") return;
21857
+ const fonts = deriveTemplateFonts();
21858
+ postToParentRef.current({ type: "ow:template-fonts-value", value: fonts ? JSON.stringify(fonts) : "" });
21859
+ };
21860
+ window.addEventListener("message", handleGetTemplateFonts);
21861
+ const handleGetTemplateBrand = (e) => {
21862
+ if (e.data?.type !== "ow:get-template-brand") return;
21863
+ const brand = deriveTemplateBrand();
21864
+ postToParentRef.current({ type: "ow:template-brand-value", value: brand ? JSON.stringify(brand) : "" });
21865
+ };
21866
+ window.addEventListener("message", handleGetTemplateBrand);
21617
21867
  const handleMoveSection = (e) => {
21618
21868
  if (e.data?.type !== "ow:move-section") return;
21619
21869
  const instanceId = typeof e.data.instanceId === "string" ? e.data.instanceId : "";
@@ -21806,6 +22056,12 @@ function OhhwellsBridge() {
21806
22056
  closeLinkPopoverRef.current();
21807
22057
  return;
21808
22058
  }
22059
+ if (floatingPanelOpenRef.current) {
22060
+ setFloatingPanelRef.current(null);
22061
+ deselectRef.current();
22062
+ deactivateRef.current();
22063
+ return;
22064
+ }
21809
22065
  deselectRef.current();
21810
22066
  deactivateRef.current();
21811
22067
  clearMediaSelectionRef.current();
@@ -22094,8 +22350,12 @@ function OhhwellsBridge() {
22094
22350
  if (inserted) {
22095
22351
  const tracker = getSectionsTracker();
22096
22352
  postToParentRef.current({ type: "ow:change", nodes: [{ key: "__ohw_sections", text: tracker.textContent ?? "[]" }] });
22097
- const h = document.body.scrollHeight;
22098
- if (h > 50) postToParentRef.current({ type: "ow:height", height: h });
22353
+ const reportHeight = () => {
22354
+ const h = document.body.scrollHeight;
22355
+ if (h > 50) postToParentRef.current({ type: "ow:height", height: h });
22356
+ };
22357
+ reportHeight();
22358
+ setTimeout(reportHeight, 500);
22099
22359
  }
22100
22360
  };
22101
22361
  const handleSwitchSchedule = (e) => {
@@ -22516,15 +22776,17 @@ function OhhwellsBridge() {
22516
22776
  window.removeEventListener("message", handleAiSetBrand);
22517
22777
  window.removeEventListener("message", handleAiSetStyles);
22518
22778
  window.removeEventListener("message", handleGetBrand);
22779
+ window.removeEventListener("message", handleGetTemplateFonts);
22780
+ window.removeEventListener("message", handleGetTemplateBrand);
22519
22781
  window.removeEventListener("message", handleMoveSection);
22520
22782
  window.removeEventListener("message", handlePanelDragging);
22521
22783
  window.removeEventListener("message", handleDeleteSection);
22522
22784
  window.removeEventListener("message", handleDuplicateSection);
22523
22785
  window.removeEventListener("message", handleDeactivate);
22524
- document.documentElement.removeAttribute("data-ohw-panel-dragging");
22525
22786
  window.removeEventListener("message", handleToastAction);
22526
22787
  window.removeEventListener("message", handleFormCount);
22527
22788
  window.removeEventListener("message", handleUiEscape);
22789
+ document.documentElement.removeAttribute("data-ohw-panel-dragging");
22528
22790
  autoSaveTimers.current.forEach(clearTimeout);
22529
22791
  autoSaveTimers.current.clear();
22530
22792
  if (imageUnhoverTimerRef.current) clearTimeout(imageUnhoverTimerRef.current);
@@ -22727,7 +22989,7 @@ function OhhwellsBridge() {
22727
22989
  postToParent2({
22728
22990
  type: "ow:ready",
22729
22991
  version: "1",
22730
- bridgeVersion: "0.1.109",
22992
+ bridgeVersion: "0.1.112",
22731
22993
  path: pathname,
22732
22994
  nodes: collectEditableNodes(editContentRef.current),
22733
22995
  sections
@@ -23343,7 +23605,7 @@ function OhhwellsBridge() {
23343
23605
  "span",
23344
23606
  {
23345
23607
  "data-ohw-form-count": "",
23346
- 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",
23608
+ 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",
23347
23609
  children: formPickCount
23348
23610
  }
23349
23611
  )
@@ -23581,11 +23843,11 @@ function OhhwellsBridge() {
23581
23843
  className: "fixed left-0 w-full z-2147483646 flex items-center pointer-events-none",
23582
23844
  style: { top: sectionGap.y, transform: "translateY(-50%)" },
23583
23845
  children: [
23584
- /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("div", { className: "flex-1 bg-primary", style: { height: 3 } }),
23846
+ /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("div", { className: "flex-1 bg-bridge-primary", style: { height: 3 } }),
23585
23847
  /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
23586
23848
  Badge,
23587
23849
  {
23588
- 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",
23850
+ 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",
23589
23851
  onClick: () => {
23590
23852
  window.parent.postMessage(
23591
23853
  {
@@ -23599,7 +23861,7 @@ function OhhwellsBridge() {
23599
23861
  children: "Add Section"
23600
23862
  }
23601
23863
  ),
23602
- /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("div", { className: "flex-1 bg-primary", style: { height: 3 } })
23864
+ /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("div", { className: "flex-1 bg-bridge-primary", style: { height: 3 } })
23603
23865
  ]
23604
23866
  }
23605
23867
  ),
@@ -23647,6 +23909,59 @@ function OhhwellsBridge() {
23647
23909
  ) : null
23648
23910
  ] });
23649
23911
  }
23912
+
23913
+ // src/ui/EmptySection.tsx
23914
+ var import_link = __toESM(require("next/link"), 1);
23915
+ var import_jsx_runtime34 = require("react/jsx-runtime");
23916
+ function EmptySection({ title, homeHref = "/", eyebrowKey, titleKey, subtitleKey }) {
23917
+ return /* @__PURE__ */ (0, import_jsx_runtime34.jsxs)(import_jsx_runtime34.Fragment, { children: [
23918
+ /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(
23919
+ "p",
23920
+ {
23921
+ style: {
23922
+ fontFamily: "var(--brand-font-body)",
23923
+ fontSize: "0.75rem",
23924
+ fontWeight: 500,
23925
+ letterSpacing: "0.15em",
23926
+ textTransform: "uppercase",
23927
+ color: "var(--brand-accent)",
23928
+ marginBottom: "1.5rem"
23929
+ },
23930
+ 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" }) })
23931
+ }
23932
+ ),
23933
+ /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(
23934
+ "h1",
23935
+ {
23936
+ style: {
23937
+ fontFamily: "var(--brand-font-heading)",
23938
+ fontSize: "clamp(2rem, 3.5vw, 2.75rem)",
23939
+ lineHeight: 1.1,
23940
+ letterSpacing: "-0.025em",
23941
+ color: "var(--brand-text)",
23942
+ marginBottom: "1rem"
23943
+ },
23944
+ ...titleKey ? { "data-ohw-editable": "text", "data-ohw-key": titleKey, "data-ohw-max-length": 80 } : {},
23945
+ children: title
23946
+ }
23947
+ ),
23948
+ /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(
23949
+ "p",
23950
+ {
23951
+ style: {
23952
+ fontFamily: "var(--brand-font-body)",
23953
+ fontSize: "1rem",
23954
+ lineHeight: 1.7,
23955
+ fontWeight: 300,
23956
+ color: "var(--brand-text-muted)",
23957
+ maxWidth: "340px"
23958
+ },
23959
+ ...subtitleKey ? { "data-ohw-editable": "text", "data-ohw-key": subtitleKey, "data-ohw-max-length": 160 } : {},
23960
+ children: "This page doesn't have any content yet."
23961
+ }
23962
+ )
23963
+ ] });
23964
+ }
23650
23965
  // Annotate the CommonJS export names for ESM import in node:
23651
23966
  0 && (module.exports = {
23652
23967
  AI_DEFAULT_BRAND,
@@ -23664,6 +23979,7 @@ function OhhwellsBridge() {
23664
23979
  DropdownMenuItem,
23665
23980
  DropdownMenuSeparator,
23666
23981
  DropdownMenuTrigger,
23982
+ EmptySection,
23667
23983
  ItemActionToolbar,
23668
23984
  ItemInteractionLayer,
23669
23985
  LinkEditorPanel,