@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.js CHANGED
@@ -279,16 +279,17 @@ var LEGACY_BRAND_VAR_NAMES = [
279
279
  "text-muted",
280
280
  "surface",
281
281
  "border",
282
- "on-primary",
283
- "navbar-background"
282
+ "on-primary"
284
283
  ].map((role) => `--brand-${role}`);
285
284
  var FONT_VARS = {
286
285
  heading: ["--font-heading", "--font-display", "--brand-font-heading"],
287
286
  body: ["--font-body", "--brand-font-body"]
288
287
  };
289
288
  var BRAND_FONT_LINK_ID = "ohw-brand-fonts";
290
- function brandColorVars(kit) {
291
- const { dark, primary, accent, light } = kit.palette;
289
+ var CUSTOM_FONT_STYLE_ID = "ohw-brand-custom-fonts";
290
+ var CUSTOM_FONT_FORMATS = /* @__PURE__ */ new Set(["woff2", "woff", "truetype", "opentype"]);
291
+ function brandColorVars(palette) {
292
+ const { dark, primary, accent, light } = palette;
292
293
  const mix = (a, aPct, b) => `color-mix(in srgb, ${a} ${aPct}%, ${b})`;
293
294
  const surface = mix(light, 95, dark);
294
295
  const border = mix(light, 85, dark);
@@ -311,22 +312,42 @@ function brandColorVars(kit) {
311
312
  "--brand-border": border,
312
313
  // Buttons/bands painted in the primary colour assume it's dark/saturated enough to need
313
314
  // light text on top — the same assumption LOGO_IMAGE's light-on-dark navbar mark makes.
314
- "--brand-on-primary": light,
315
- "--brand-navbar-background": dark
315
+ "--brand-on-primary": light
316
316
  };
317
317
  }
318
+ function parseCustomFontWeight(raw) {
319
+ if (typeof raw !== "object" || raw === null) return null;
320
+ const w = raw;
321
+ if (typeof w.weight !== "number" || !Number.isFinite(w.weight)) return null;
322
+ if (typeof w.url !== "string" || !w.url) return null;
323
+ if (typeof w.format !== "string" || !CUSTOM_FONT_FORMATS.has(w.format)) return null;
324
+ const weightEnd = typeof w.weightEnd === "number" && Number.isFinite(w.weightEnd) && w.weightEnd > w.weight ? w.weightEnd : void 0;
325
+ return { weight: w.weight, ...weightEnd !== void 0 ? { weightEnd } : {}, url: w.url, format: w.format };
326
+ }
327
+ function parseCustomFont(raw) {
328
+ if (typeof raw !== "object" || raw === null) return null;
329
+ const f = raw;
330
+ if (typeof f.family !== "string" || !f.family) return null;
331
+ if (typeof f.label !== "string" || !f.label) return null;
332
+ if (!Array.isArray(f.weights)) return null;
333
+ const weights = f.weights.map(parseCustomFontWeight).filter((w) => w !== null);
334
+ if (weights.length === 0) return null;
335
+ return { family: f.family, label: f.label, weights };
336
+ }
318
337
  function parseBrandKit(raw) {
319
338
  if (!raw) return null;
320
339
  try {
321
340
  const parsed = JSON.parse(raw);
322
341
  const p = parsed?.palette;
323
342
  const f = parsed?.fonts;
324
- if (!p || !f || typeof p.dark !== "string" || typeof p.primary !== "string" || typeof p.accent !== "string" || typeof p.light !== "string" || typeof f.heading !== "string" || typeof f.body !== "string") {
325
- return null;
326
- }
343
+ const palette = p && typeof p.dark === "string" && typeof p.primary === "string" && typeof p.accent === "string" && typeof p.light === "string" ? { dark: p.dark, primary: p.primary, accent: p.accent, light: p.light } : void 0;
344
+ const fonts = f && typeof f.heading === "string" && typeof f.body === "string" ? { heading: f.heading, body: f.body } : void 0;
345
+ const customFonts = Array.isArray(parsed?.customFonts) ? parsed.customFonts.map(parseCustomFont).filter((c) => c !== null) : void 0;
346
+ if (!palette && !fonts && !customFonts) return null;
327
347
  return {
328
- palette: { dark: p.dark, primary: p.primary, accent: p.accent, light: p.light },
329
- fonts: { heading: f.heading, body: f.body }
348
+ ...palette ? { palette } : {},
349
+ ...fonts ? { fonts } : {},
350
+ ...customFonts ? { customFonts } : {}
330
351
  };
331
352
  } catch {
332
353
  return null;
@@ -336,11 +357,16 @@ function familyOf(stack) {
336
357
  const first = stack.split(",")[0]?.trim() ?? "";
337
358
  return first.replace(/^['"]|['"]$/g, "");
338
359
  }
360
+ function quoteFamily(family) {
361
+ return `'${family.replace(/\\/g, "\\\\").replace(/'/g, "\\'")}'`;
362
+ }
363
+ var WEIGHT_SCALE = [100, 200, 300, 400, 500, 600, 700, 800, 900];
339
364
  function loadBrandFonts(families) {
340
365
  const unique = [...new Set(families.map((f) => f.trim()).filter(Boolean))];
341
366
  if (unique.length === 0) return;
342
- const spec = unique.map((f) => `${f.replace(/ /g, "+")}:400,500,600,700`).join("|");
343
- const href = `https://fonts.googleapis.com/css?family=${spec}&display=swap`;
367
+ const weights = WEIGHT_SCALE.join(";");
368
+ const spec = unique.map((f) => `family=${f.replace(/ /g, "+")}:wght@${weights}`).join("&");
369
+ const href = `https://fonts.googleapis.com/css2?${spec}&display=swap`;
344
370
  let link = document.getElementById(BRAND_FONT_LINK_ID);
345
371
  if (!link) {
346
372
  link = document.createElement("link");
@@ -350,18 +376,54 @@ function loadBrandFonts(families) {
350
376
  }
351
377
  if (link.href !== href) link.href = href;
352
378
  }
379
+ function escapeCssString(value) {
380
+ return value.replace(/\\/g, "\\\\").replace(/'/g, "\\'");
381
+ }
382
+ function loadCustomFonts(fonts) {
383
+ const list = fonts ?? [];
384
+ let style = document.getElementById(CUSTOM_FONT_STYLE_ID);
385
+ if (list.length === 0) {
386
+ style?.remove();
387
+ return;
388
+ }
389
+ if (!style) {
390
+ style = document.createElement("style");
391
+ style.id = CUSTOM_FONT_STYLE_ID;
392
+ document.head.appendChild(style);
393
+ }
394
+ style.textContent = list.flatMap(
395
+ (font) => font.weights.map((w) => {
396
+ const weightDescriptor = w.weightEnd ? `${w.weight} ${w.weightEnd}` : `${w.weight}`;
397
+ return `@font-face { font-family: '${escapeCssString(font.family)}'; src: url('${escapeCssString(w.url)}') format('${w.format}'); font-weight: ${weightDescriptor}; font-display: swap; }`;
398
+ })
399
+ ).join("\n");
400
+ }
353
401
  function applyBrandToDom(kit) {
354
402
  const root = document.documentElement;
355
403
  if (!kit) {
356
404
  for (const name of [...BRAND_VAR_NAMES, ...LEGACY_BRAND_VAR_NAMES]) root.style.removeProperty(name);
357
405
  for (const name of [...FONT_VARS.heading, ...FONT_VARS.body]) root.style.removeProperty(name);
358
406
  document.getElementById(BRAND_FONT_LINK_ID)?.remove();
407
+ document.getElementById(CUSTOM_FONT_STYLE_ID)?.remove();
359
408
  return;
360
409
  }
361
- for (const [name, value] of Object.entries(brandColorVars(kit))) root.style.setProperty(name, value);
362
- for (const name of FONT_VARS.heading) root.style.setProperty(name, kit.fonts.heading);
363
- for (const name of FONT_VARS.body) root.style.setProperty(name, kit.fonts.body);
364
- loadBrandFonts([familyOf(kit.fonts.heading), familyOf(kit.fonts.body)]);
410
+ if (kit.palette) {
411
+ for (const [name, value] of Object.entries(brandColorVars(kit.palette))) root.style.setProperty(name, value);
412
+ }
413
+ if (kit.customFonts) {
414
+ loadCustomFonts(kit.customFonts);
415
+ }
416
+ if (kit.fonts) {
417
+ const heading = quoteFamily(kit.fonts.heading);
418
+ const body = quoteFamily(kit.fonts.body);
419
+ for (const name of FONT_VARS.heading) root.style.setProperty(name, heading);
420
+ for (const name of FONT_VARS.body) root.style.setProperty(name, body);
421
+ const customFamilies = new Set((kit.customFonts ?? []).map((f) => f.family));
422
+ const googleFamilies = [familyOf(kit.fonts.heading), familyOf(kit.fonts.body)].filter(
423
+ (f) => !customFamilies.has(f)
424
+ );
425
+ loadBrandFonts(googleFamilies);
426
+ }
365
427
  }
366
428
 
367
429
  // src/lib/section-styles.ts
@@ -386,6 +448,20 @@ var BG_VALUES = {
386
448
  accent: "var(--ohw-brand-primary, var(--color-primary, #0078E5))",
387
449
  "accent-soft": "color-mix(in srgb, var(--ohw-brand-primary, var(--color-primary, #0078E5)) 12%, var(--ohw-brand-light, var(--color-light, #FFFFFF)))"
388
450
  };
451
+ var NAVBAR_COLOR_TOKENS = ["dark", "primary", "accent", "light"];
452
+ function navbarColorVar(token) {
453
+ const fallback = {
454
+ dark: "#0C0A09",
455
+ primary: "#0078E5",
456
+ accent: "#0078E5",
457
+ light: "#FFFFFF"
458
+ };
459
+ return `var(--ohw-brand-${token}, var(--color-${token}, ${fallback[token]}))`;
460
+ }
461
+ function resolveNavbarValue(value) {
462
+ if (value === void 0) return void 0;
463
+ return NAVBAR_COLOR_TOKENS.includes(value) ? navbarColorVar(value) : value;
464
+ }
389
465
  var HEADLINE_SIZES = { md: 24, lg: 32, xl: 40, display: 56 };
390
466
  function styleSheetCss() {
391
467
  const rules = [];
@@ -555,8 +631,16 @@ function applyStylesToDom(store) {
555
631
  for (const [sectionId, override] of Object.entries(store.sections)) {
556
632
  const escaped = CSS.escape(sectionId);
557
633
  const byInstance = document.querySelectorAll(`[data-ohw-instance="${escaped}"]`);
558
- const sections = byInstance.length ? byInstance : document.querySelectorAll(`[data-ohw-section="${escaped}"]`);
634
+ const sections = Array.from(
635
+ byInstance.length ? byInstance : document.querySelectorAll(`[data-ohw-section="${escaped}"]`)
636
+ );
637
+ const isNavbar = sectionId === "navbar";
638
+ if (isNavbar) {
639
+ const drawer = document.querySelector("[data-ohw-nav-drawer]");
640
+ if (drawer && !sections.includes(drawer)) sections.push(drawer);
641
+ }
559
642
  for (const marker of Array.from(sections)) {
643
+ const isDrawerMarker = isNavbar && marker.hasAttribute("data-ohw-nav-drawer");
560
644
  const section = marker.querySelector(":scope > [data-ai-section]") ?? marker;
561
645
  for (const [prop, attr] of Object.entries(SECTION_ATTRS)) {
562
646
  const value = override[prop];
@@ -564,19 +648,23 @@ function applyStylesToDom(store) {
564
648
  if (prop === "sectionBackground" && override.sectionBackgroundColor !== void 0) continue;
565
649
  section.setAttribute(attr, String(value).replace(":", "-"));
566
650
  }
567
- if (override.sectionBackgroundColor !== void 0) {
651
+ const bgOverride = isDrawerMarker ? override.navbarMobileBackground ?? override.sectionBackgroundColor : override.sectionBackgroundColor;
652
+ const textOverride = isDrawerMarker ? override.navbarMobileTextColor ?? override.textColor : override.textColor;
653
+ const bgValue = isNavbar ? resolveNavbarValue(bgOverride) : bgOverride;
654
+ const textValue = isNavbar ? resolveNavbarValue(textOverride) : textOverride;
655
+ if (bgValue !== void 0) {
568
656
  saveInline(section, "background");
569
- section.style.setProperty("background", override.sectionBackgroundColor, "important");
657
+ section.style.setProperty("background", bgValue, "important");
570
658
  section.setAttribute("data-ohw-style-bgcolor", "");
571
659
  }
572
- if (override.textColor !== void 0 || override.fontFamily !== void 0) {
660
+ if (textValue !== void 0 || override.fontFamily !== void 0) {
573
661
  const textEls = marker.querySelectorAll(
574
662
  '[data-ohw-editable="text"], [data-ohw-editable="plain"], [data-ohw-field-label]'
575
663
  );
576
664
  for (const el of Array.from(textEls)) {
577
- if (override.textColor !== void 0) {
665
+ if (textValue !== void 0) {
578
666
  saveInline(el, "color");
579
- el.style.setProperty("color", override.textColor, "important");
667
+ el.style.setProperty("color", textValue, "important");
580
668
  el.setAttribute(NODE_WROTE_ATTR, "");
581
669
  }
582
670
  if (override.fontFamily !== void 0) {
@@ -1282,6 +1370,22 @@ function accentBandContext(brand) {
1282
1370
  function textAttrs(ctx, path) {
1283
1371
  return ctx.keyFor ? { "data-ohw-key": ctx.keyFor(path), "data-ohw-editable": "text" } : {};
1284
1372
  }
1373
+ var AI_RESPONSIVE_CSS = [
1374
+ "@media (max-width: 960px) {",
1375
+ " [data-ai-responsive] [data-ai-grid] { grid-template-columns: repeat(2, 1fr) !important; }",
1376
+ ' [data-ai-responsive] [data-ai-grid="1"] { grid-template-columns: 1fr !important; }',
1377
+ "}",
1378
+ "@media (max-width: 640px) {",
1379
+ " [data-ai-responsive] [data-ai-grid] { grid-template-columns: 1fr !important; }",
1380
+ " [data-ai-responsive] [data-ai-columns] > * { grid-column: 1 / -1 !important; }",
1381
+ // Group containers flatten to a column on phones; span placements come along for free.
1382
+ " [data-ai-responsive] [data-ai-group] { display: flex !important; flex-direction: column !important; }",
1383
+ " [data-ai-responsive] [data-ai-group] > * { grid-column: auto !important; }",
1384
+ " [data-ai-responsive] [data-ai-section-inner] { padding-left: 20px !important; padding-right: 20px !important; }",
1385
+ " [data-ai-responsive] { overflow-x: hidden; }",
1386
+ " [data-ai-responsive] img { max-width: 100%; }",
1387
+ "}"
1388
+ ].join("\n");
1285
1389
  var TRANSPARENT_PX = "data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7";
1286
1390
  function MediaBox({
1287
1391
  refValue,
@@ -1294,13 +1398,17 @@ function MediaBox({
1294
1398
  const url = refValue ? ctx.resolveMedia(refValue) : null;
1295
1399
  const isIcon = /^(lucide|simple):/.test(refValue);
1296
1400
  const aspectRatio = aspect && /^\d+:\d+$/.test(aspect) ? aspect.replace(":", " / ") : void 0;
1297
- const editAttrs = ctx.keyFor && editPath && !isIcon ? { "data-ohw-key": ctx.keyFor(editPath), "data-ohw-editable": "image" } : {};
1401
+ const editAttrs = ctx.keyFor && editPath ? {
1402
+ "data-ohw-key": ctx.keyFor(editPath),
1403
+ "data-ohw-editable": isIcon ? "icon" : "image"
1404
+ } : {};
1298
1405
  if (isIcon) {
1299
1406
  const Icon = refValue.startsWith("lucide:") ? lucideByName(refValue.slice(7)) : null;
1300
1407
  return /* @__PURE__ */ jsx(
1301
1408
  "span",
1302
1409
  {
1303
1410
  "data-ai-icon": refValue,
1411
+ ...editAttrs,
1304
1412
  style: {
1305
1413
  display: "inline-flex",
1306
1414
  width: 48,
@@ -2193,7 +2301,7 @@ function CollectionBlock({ node, ctx, path }) {
2193
2301
  return /* @__PURE__ */ jsx(
2194
2302
  "div",
2195
2303
  {
2196
- "data-ai-grid": "",
2304
+ "data-ai-grid": String(itemsPerRow),
2197
2305
  style: {
2198
2306
  display: "grid",
2199
2307
  gridTemplateColumns: `repeat(${itemsPerRow}, 1fr)`,
@@ -2546,6 +2654,7 @@ function AiTreeRenderer({
2546
2654
  {
2547
2655
  "data-ai-section": tree.tag ?? "",
2548
2656
  ...bgAttrs,
2657
+ "data-ai-responsive": "",
2549
2658
  style: {
2550
2659
  position: "relative",
2551
2660
  padding: `${pad}px 0`,
@@ -2556,12 +2665,13 @@ function AiTreeRenderer({
2556
2665
  color: settings.sectionBackground === "accent" ? blockBrand.palette.dark : void 0
2557
2666
  },
2558
2667
  children: [
2559
- isOverlay && backgroundUrl && /* @__PURE__ */ jsx("div", { style: { position: "absolute", inset: 0, background: `rgba(255,255,255,${overlayAlpha})` } }),
2668
+ /* @__PURE__ */ jsx("style", { children: AI_RESPONSIVE_CSS }),
2560
2669
  /* @__PURE__ */ jsx("style", { children: AI_MOBILE_CSS }),
2670
+ isOverlay && backgroundUrl && /* @__PURE__ */ jsx("div", { style: { position: "absolute", inset: 0, background: `rgba(255,255,255,${overlayAlpha})` } }),
2561
2671
  /* @__PURE__ */ jsx(
2562
2672
  "div",
2563
2673
  {
2564
- "data-ai-container": "",
2674
+ "data-ai-section-inner": "",
2565
2675
  style: {
2566
2676
  position: "relative",
2567
2677
  maxWidth: AI_TREE_TOKENS.sectionMaxWidth,
@@ -2572,7 +2682,7 @@ function AiTreeRenderer({
2572
2682
  children: tree.rows.map((row, r2) => /* @__PURE__ */ jsx(
2573
2683
  "div",
2574
2684
  {
2575
- "data-ai-row": "",
2685
+ "data-ai-columns": "",
2576
2686
  style: {
2577
2687
  display: "grid",
2578
2688
  gridTemplateColumns: "repeat(12, 1fr)",
@@ -2622,14 +2732,52 @@ function readRootVar(name) {
2622
2732
  if (typeof document === "undefined") return "";
2623
2733
  return getComputedStyle(document.documentElement).getPropertyValue(name).trim();
2624
2734
  }
2735
+ function normalizeColorToHex(value) {
2736
+ if (!value || typeof document === "undefined") return value;
2737
+ const canvas = document.createElement("canvas");
2738
+ canvas.width = 1;
2739
+ canvas.height = 1;
2740
+ const ctx = canvas.getContext("2d");
2741
+ if (!ctx) return value;
2742
+ ctx.fillStyle = value;
2743
+ ctx.fillRect(0, 0, 1, 1);
2744
+ const [r2, g, b] = ctx.getImageData(0, 0, 1, 1).data;
2745
+ const toHex = (n) => n.toString(16).padStart(2, "0");
2746
+ return `#${toHex(r2)}${toHex(g)}${toHex(b)}`;
2747
+ }
2748
+ var GENERIC_FONT_KEYWORDS = /* @__PURE__ */ new Set([
2749
+ "serif",
2750
+ "sans-serif",
2751
+ "monospace",
2752
+ "cursive",
2753
+ "fantasy",
2754
+ "system-ui",
2755
+ "ui-serif",
2756
+ "ui-sans-serif",
2757
+ "ui-monospace",
2758
+ "ui-rounded",
2759
+ "math",
2760
+ "emoji",
2761
+ "fangsong"
2762
+ ]);
2763
+ function primaryFontFamily(stack) {
2764
+ const first = stack.split(",").map((part) => part.trim().replace(/^["']|["']$/g, "")).find((part) => part && !GENERIC_FONT_KEYWORDS.has(part.toLowerCase()));
2765
+ return first ?? "";
2766
+ }
2767
+ function humanizeFontFamily(name) {
2768
+ 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());
2769
+ }
2770
+ function readFontVar(name) {
2771
+ return humanizeFontFamily(primaryFontFamily(readRootVar(name)));
2772
+ }
2625
2773
  function deriveBrandOverride() {
2626
2774
  const dark = readRootVar("--ohw-brand-dark");
2627
2775
  const primary = readRootVar("--ohw-brand-primary");
2628
2776
  const light = readRootVar("--ohw-brand-light");
2629
2777
  if (!dark || !primary || !light) return null;
2630
2778
  const accent = readRootVar("--ohw-brand-accent");
2631
- const heading = readRootVar("--font-heading") || readRootVar("--font-display");
2632
- const body = readRootVar("--font-body");
2779
+ const heading = readFontVar("--font-heading") || readFontVar("--font-display");
2780
+ const body = readFontVar("--font-body");
2633
2781
  return {
2634
2782
  palette: { dark, primary, accent: accent || dark, light },
2635
2783
  fonts: {
@@ -2638,22 +2786,55 @@ function deriveBrandOverride() {
2638
2786
  }
2639
2787
  };
2640
2788
  }
2641
- function deriveTemplateBrand() {
2789
+ function deriveTemplateBrandLive() {
2642
2790
  const primary = readRootVar("--brand-primary") || readRootVar("--color-primary");
2643
2791
  const dark = readRootVar("--brand-text") || readRootVar("--color-dark");
2644
2792
  const light = readRootVar("--brand-background") || readRootVar("--color-light");
2645
2793
  if (!dark || !primary || !light) return null;
2646
2794
  const accent = readRootVar("--brand-accent") || readRootVar("--color-accent");
2647
- const heading = readRootVar("--brand-font-heading") || readRootVar("--font-heading") || readRootVar("--font-display");
2648
- const body = readRootVar("--brand-font-body") || readRootVar("--font-body");
2795
+ const heading = readFontVar("--brand-font-heading") || readFontVar("--font-heading") || readFontVar("--font-display");
2796
+ const body = readFontVar("--brand-font-body") || readFontVar("--font-body");
2649
2797
  return {
2650
- palette: { dark, primary, accent: accent || dark, light },
2798
+ palette: {
2799
+ dark: normalizeColorToHex(dark),
2800
+ primary: normalizeColorToHex(primary),
2801
+ accent: normalizeColorToHex(accent || dark),
2802
+ light: normalizeColorToHex(light)
2803
+ },
2651
2804
  fonts: {
2652
2805
  heading: heading || AI_DEFAULT_BRAND.fonts.heading,
2653
2806
  body: body || AI_DEFAULT_BRAND.fonts.body
2654
2807
  }
2655
2808
  };
2656
2809
  }
2810
+ function deriveTemplateFontsLive() {
2811
+ const heading = readFontVar("--brand-font-heading") || readFontVar("--font-heading") || readFontVar("--font-display");
2812
+ const body = readFontVar("--brand-font-body") || readFontVar("--font-body");
2813
+ if (!heading && !body) return null;
2814
+ return {
2815
+ heading: heading || AI_DEFAULT_BRAND.fonts.heading,
2816
+ body: body || AI_DEFAULT_BRAND.fonts.body
2817
+ };
2818
+ }
2819
+ var OVERRIDE_VAR_NAMES = [...BRAND_VAR_NAMES, ...LEGACY_BRAND_VAR_NAMES, ...FONT_VARS.heading, ...FONT_VARS.body];
2820
+ function withOverrideStripped(read) {
2821
+ const root = document.documentElement;
2822
+ const restore = OVERRIDE_VAR_NAMES.map((name) => [name, root.style.getPropertyValue(name)]);
2823
+ for (const name of OVERRIDE_VAR_NAMES) root.style.removeProperty(name);
2824
+ try {
2825
+ return read();
2826
+ } finally {
2827
+ for (const [name, value] of restore) if (value) root.style.setProperty(name, value);
2828
+ }
2829
+ }
2830
+ var TEMPLATE_BRAND_SNAPSHOT = typeof document === "undefined" ? null : withOverrideStripped(deriveTemplateBrandLive);
2831
+ var TEMPLATE_FONTS_SNAPSHOT = typeof document === "undefined" ? null : withOverrideStripped(deriveTemplateFontsLive);
2832
+ function deriveTemplateBrand() {
2833
+ return TEMPLATE_BRAND_SNAPSHOT;
2834
+ }
2835
+ function deriveTemplateFonts() {
2836
+ return TEMPLATE_FONTS_SNAPSHOT;
2837
+ }
2657
2838
  function deriveTemplateButtonStyle() {
2658
2839
  if (typeof document === "undefined") return null;
2659
2840
  const candidates = Array.from(
@@ -7151,12 +7332,12 @@ var cva = (base, config) => (props) => {
7151
7332
  import { Toggle as TogglePrimitive } from "radix-ui";
7152
7333
  import { jsx as jsx5 } from "react/jsx-runtime";
7153
7334
  var toggleVariants = cva(
7154
- "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",
7335
+ "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",
7155
7336
  {
7156
7337
  variants: {
7157
7338
  variant: {
7158
7339
  default: "bg-transparent border-0",
7159
- outline: "border border-input bg-transparent shadow-xs hover:bg-accent hover:text-accent-foreground"
7340
+ outline: "border border-input bg-transparent shadow-xs hover:bg-bridge-accent hover:text-accent-foreground"
7160
7341
  },
7161
7342
  size: {
7162
7343
  default: "px-2 py-1",
@@ -7249,10 +7430,10 @@ var DragHandle = React4.forwardRef(
7249
7430
  type,
7250
7431
  "data-slot": "drag-handle",
7251
7432
  className: cn(
7252
- "inline-flex h-7 w-4 shrink-0 items-center justify-center rounded-md transition-all duration-200 max-h-[30px]",
7433
+ "inline-flex h-7 w-4 shrink-0 items-center justify-center rounded-md transition-all duration-200 max-h-7.5",
7253
7434
  "bg-white border border-transparent text-stone-500 shadow-md cursor-grab",
7254
7435
  "enabled:hover:border enabled:hover:border-stone-200 enabled:hover:text-stone-950",
7255
- "enabled:active:border enabled:active:border-primary enabled:active:bg-primary-50 enabled:active:text-stone-950 enabled:active:shadow enabled:active:cursor-grabbing",
7436
+ "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",
7256
7437
  "disabled:cursor-not-allowed disabled:opacity-40 disabled:text-stone-950 disabled:pointer-events-none",
7257
7438
  className
7258
7439
  ),
@@ -7274,7 +7455,7 @@ var CustomToolbar = React5.forwardRef(({ className, onMouseDown, ...props }, ref
7274
7455
  "data-ohw-toolbar": "",
7275
7456
  className: cn(
7276
7457
  // Figma: bg background, radius 8, gap-1, p-0.5, shadow-md — no border
7277
- "inline-flex h-8 items-center gap-1 rounded-[var(--radius,0.5rem)] bg-background p-0.5 font-sans whitespace-nowrap shadow-md",
7458
+ "inline-flex h-8 items-center gap-1 rounded-(--radius,0.5rem) bg-background p-0.5 font-sans whitespace-nowrap shadow-md",
7278
7459
  className
7279
7460
  ),
7280
7461
  onMouseDown: (e) => {
@@ -7303,7 +7484,7 @@ var CustomToolbarButton = React5.forwardRef(
7303
7484
  type,
7304
7485
  className: cn(
7305
7486
  "inline-flex size-7 shrink-0 items-center justify-center rounded-[calc(var(--radius,0.5rem)-2px)] text-foreground transition-colors",
7306
- active ? "bg-primary text-primary-foreground" : "bg-transparent hover:bg-muted disabled:cursor-not-allowed disabled:text-muted-foreground disabled:opacity-60",
7487
+ active ? "bg-bridge-primary text-primary-foreground" : "bg-transparent hover:bg-muted disabled:cursor-not-allowed disabled:text-muted-foreground disabled:opacity-60",
7307
7488
  className
7308
7489
  ),
7309
7490
  ...props
@@ -8567,13 +8748,13 @@ function FormFieldToolbar({
8567
8748
  ]
8568
8749
  }
8569
8750
  ) }),
8570
- /* @__PURE__ */ jsx13(DropdownMenuContent, { align: "start", sideOffset: 8, className: "min-w-[190px] p-1", children: FIELD_TYPES.map((entry) => {
8751
+ /* @__PURE__ */ jsx13(DropdownMenuContent, { align: "start", sideOffset: 8, className: "min-w-47.5 p-1", children: FIELD_TYPES.map((entry) => {
8571
8752
  const Icon = TYPE_ICONS[entry.type];
8572
8753
  return /* @__PURE__ */ jsxs6(
8573
8754
  DropdownMenuItem,
8574
8755
  {
8575
8756
  onSelect: () => onTypeChange(entry.type),
8576
- className: "rounded-md py-2 text-[13px] " + (entry.type === type ? "bg-primary/10" : ""),
8757
+ className: "rounded-md py-2 text-[13px] " + (entry.type === type ? "bg-bridge-primary/10" : ""),
8577
8758
  children: [
8578
8759
  /* @__PURE__ */ jsx13(Icon, { size: 14, strokeWidth: 1.75, className: "shrink-0", "aria-hidden": true }),
8579
8760
  entry.label
@@ -8590,7 +8771,7 @@ function FormFieldToolbar({
8590
8771
  type: "button",
8591
8772
  "aria-pressed": required,
8592
8773
  onClick: onRequiredToggle,
8593
- 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"),
8774
+ 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"),
8594
8775
  "data-ohw-field-required-toggle": "",
8595
8776
  children: [
8596
8777
  /* @__PURE__ */ jsx13(Asterisk, { size: 14, strokeWidth: 2, "aria-hidden": true }),
@@ -8610,7 +8791,7 @@ function FormFieldToolbar({
8610
8791
  children: /* @__PURE__ */ jsx13(MoreHorizontal2, { size: 15, "aria-hidden": true })
8611
8792
  }
8612
8793
  ) }),
8613
- /* @__PURE__ */ jsxs6(DropdownMenuContent, { align: "start", sideOffset: 8, className: "min-w-[170px] p-1", children: [
8794
+ /* @__PURE__ */ jsxs6(DropdownMenuContent, { align: "start", sideOffset: 8, className: "min-w-42.5 p-1", children: [
8614
8795
  /* @__PURE__ */ jsxs6(DropdownMenuItem, { onSelect: onDuplicate, className: "rounded-md py-2 text-[13px]", children: [
8615
8796
  /* @__PURE__ */ jsx13(Copy2, { size: 14, strokeWidth: 1.75, className: "shrink-0", "aria-hidden": true }),
8616
8797
  "Duplicate"
@@ -8638,7 +8819,7 @@ function FieldTypePicker({ onPick }) {
8638
8819
  {
8639
8820
  type: "button",
8640
8821
  onClick: () => onPick(entry.type),
8641
- 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",
8822
+ 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",
8642
8823
  children: [
8643
8824
  /* @__PURE__ */ jsx13(Icon, { size: 26, strokeWidth: 1.5, "aria-hidden": true }),
8644
8825
  entry.label
@@ -8664,7 +8845,7 @@ var buttonVariants = cva(
8664
8845
  {
8665
8846
  variants: {
8666
8847
  variant: {
8667
- default: "bg-primary text-primary-foreground hover:opacity-90",
8848
+ default: "bg-bridge-primary text-primary-foreground hover:opacity-90",
8668
8849
  outline: "border border-border bg-background text-foreground shadow-sm hover:bg-muted/80",
8669
8850
  ghost: "min-w-0 px-3 py-2 text-foreground hover:bg-muted/50"
8670
8851
  },
@@ -8759,6 +8940,7 @@ function MediaOverlay({
8759
8940
  (prev) => prev && prev.fullWidth === width && prev.height === height ? prev : { fullWidth: width, height }
8760
8941
  );
8761
8942
  }, [isVideo]);
8943
+ const replaceLabel = isVideo ? "Replace video" : hover.elementType === "bg-image" ? "Replace background" : "Replace image";
8762
8944
  const replaceMode = buttonSize ? replaceButtonMode({ width: rect.width, height: rect.height }, buttonSize) : "none";
8763
8945
  const box = {
8764
8946
  position: "fixed",
@@ -8864,8 +9046,8 @@ function MediaOverlay({
8864
9046
  pointerEvents: hover.hasTextOverlap ? "none" : "auto",
8865
9047
  // Selected: a firm component ring with no wash, so the image reads as chosen rather
8866
9048
  // than hovered. Hover keeps the existing tinted preview.
8867
- boxShadow: selected ? "inset 0 0 0 2px var(--color-primary)" : "inset 0 0 0 1.5px var(--color-primary)",
8868
- background: selected ? "transparent" : "color-mix(in srgb, var(--color-primary) 20%, transparent)"
9049
+ boxShadow: selected ? "inset 0 0 0 2px var(--color-bridge-primary)" : "inset 0 0 0 1.5px var(--color-bridge-primary)",
9050
+ background: selected ? "transparent" : "color-mix(in srgb, var(--color-bridge-primary) 20%, transparent)"
8869
9051
  },
8870
9052
  onClick: () => onSelect && !selected ? onSelect(hover.key) : onReplace(hover.key),
8871
9053
  children: [
@@ -8888,17 +9070,17 @@ function MediaOverlay({
8888
9070
  },
8889
9071
  children: [
8890
9072
  isVideo ? /* @__PURE__ */ jsx15(Film, { size: 14 }) : /* @__PURE__ */ jsx15(ImageIcon, { size: 14 }),
8891
- isVideo ? "Replace video" : "Replace image"
9073
+ replaceLabel
8892
9074
  ]
8893
9075
  }
8894
9076
  ),
8895
- replaceMode === "none" ? null : /* @__PURE__ */ jsxs7(
9077
+ showChrome && replaceMode !== "none" && /* @__PURE__ */ jsxs7(
8896
9078
  Button,
8897
9079
  {
8898
9080
  "data-ohw-media-overlay": "",
8899
9081
  variant: "outline",
8900
9082
  size: "sm",
8901
- "aria-label": isVideo ? "Replace video" : "Replace image",
9083
+ "aria-label": replaceLabel,
8902
9084
  className: "gap-1.5 cursor-pointer hover:bg-background",
8903
9085
  style: {
8904
9086
  ...OVERLAY_BUTTON_STYLE,
@@ -8921,7 +9103,7 @@ function MediaOverlay({
8921
9103
  },
8922
9104
  children: [
8923
9105
  isVideo ? /* @__PURE__ */ jsx15(Film, { size: 14 }) : /* @__PURE__ */ jsx15(ImageIcon, { size: 14 }),
8924
- replaceMode === "full" ? isVideo ? "Replace video" : hover.elementType === "bg-image" ? "Replace background" : "Replace image" : null
9106
+ replaceMode === "full" ? replaceLabel : null
8925
9107
  ]
8926
9108
  }
8927
9109
  )
@@ -8959,8 +9141,8 @@ function CarouselOverlay({
8959
9141
  height: rect.height,
8960
9142
  zIndex: 2147483646,
8961
9143
  pointerEvents: "auto",
8962
- boxShadow: "inset 0 0 0 1.5px var(--color-primary)",
8963
- background: "color-mix(in srgb, var(--color-primary) 20%, transparent)"
9144
+ boxShadow: "inset 0 0 0 1.5px var(--color-bridge-primary)",
9145
+ background: "color-mix(in srgb, var(--color-bridge-primary) 20%, transparent)"
8964
9146
  },
8965
9147
  onClick: () => onEdit(hover.key),
8966
9148
  children: /* @__PURE__ */ jsxs8(
@@ -9813,7 +9995,7 @@ function SectionTreeItem({
9813
9995
  /* @__PURE__ */ jsx20(
9814
9996
  "div",
9815
9997
  {
9816
- className: "mr-[-1px] h-9 w-2 shrink-0 rounded-bl-sm border-b border-l border-border mb-4",
9998
+ className: "-mr-px h-9 w-2 shrink-0 rounded-bl-sm border-b border-l border-border mb-4",
9817
9999
  "aria-hidden": true
9818
10000
  }
9819
10001
  ),
@@ -9832,7 +10014,7 @@ function SectionTreeItem({
9832
10014
  className: cn(
9833
10015
  "flex h-9 min-w-0 flex-1 items-center gap-2 rounded-md border border-border bg-background p-3",
9834
10016
  interactive && "cursor-pointer hover:bg-muted/30",
9835
- interactive && selected && "border-primary"
10017
+ interactive && selected && "border-bridge-primary"
9836
10018
  ),
9837
10019
  children: [
9838
10020
  /* @__PURE__ */ jsx20(
@@ -9962,7 +10144,7 @@ function UrlOrPageInput({
9962
10144
  };
9963
10145
  const fieldClassName = cn(
9964
10146
  "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]",
9965
- urlError ? "border-destructive shadow-[0_0_0_1px_var(--ohw-destructive)]" : isFocused ? "border-primary shadow-[0_0_0_1px_var(--ohw-primary)]" : "border-input"
10147
+ 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"
9966
10148
  );
9967
10149
  return /* @__PURE__ */ jsxs13("div", { className: "flex w-full flex-col gap-2 p-0", children: [
9968
10150
  /* @__PURE__ */ jsx23(Label, { htmlFor: inputId, className: cn(urlError && "text-destructive"), children: "Destination" }),
@@ -13869,6 +14051,7 @@ function readLogoSizeState(content, placement) {
13869
14051
  function getLogoElement(el) {
13870
14052
  const marked = el.closest('[data-ohw-role="logo"], [data-ohw-logo]');
13871
14053
  if (marked) return marked;
14054
+ if (el.closest('[data-ohw-editable="icon"]')) return null;
13872
14055
  const root = el.closest("nav, [data-ohw-nav-root], footer");
13873
14056
  if (!root) return null;
13874
14057
  const anchor = el.closest("a");
@@ -14162,7 +14345,7 @@ function DisplaySwitch({
14162
14345
  onClick: () => onChange(!checked),
14163
14346
  className: cn(
14164
14347
  "relative h-5 w-9 shrink-0 rounded-full transition-colors",
14165
- checked ? "bg-primary" : "bg-primary-50",
14348
+ checked ? "bg-bridge-primary" : "bg-primary-50",
14166
14349
  disabled ? "cursor-default opacity-50" : "cursor-pointer"
14167
14350
  ),
14168
14351
  children: /* @__PURE__ */ jsx28(
@@ -14170,7 +14353,7 @@ function DisplaySwitch({
14170
14353
  {
14171
14354
  className: cn(
14172
14355
  "absolute top-0.5 h-4 w-4 rounded-full bg-white transition-all",
14173
- checked ? "left-[1.125rem]" : "left-0.5"
14356
+ checked ? "left-4.5" : "left-0.5"
14174
14357
  )
14175
14358
  }
14176
14359
  )
@@ -15805,7 +15988,7 @@ var badgeVariants = cva(
15805
15988
  {
15806
15989
  variants: {
15807
15990
  variant: {
15808
- default: "border-transparent bg-primary text-primary-foreground",
15991
+ default: "border-transparent bg-bridge-primary text-primary-foreground",
15809
15992
  secondary: "border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80",
15810
15993
  destructive: "border-transparent bg-destructive text-destructive-foreground hover:bg-destructive/80",
15811
15994
  outline: "text-foreground"
@@ -15987,21 +16170,10 @@ function parseSchedulingInsertAfter(insertAfter) {
15987
16170
  insertBefore: insertAfter.slice(idx + INSERT_BEFORE_MARKER.length)
15988
16171
  };
15989
16172
  }
15990
- function resolveSchedulingInsert(insertAfter, explicitInsertBefore) {
15991
- const parsed = parseSchedulingInsertAfter(insertAfter);
15992
- const insertBefore = explicitInsertBefore ?? parsed.insertBefore;
15993
- const effectiveInsertAfter = insertBefore && parsed.insertBefore === null ? `${insertAfter}${INSERT_BEFORE_MARKER}${insertBefore}` : insertAfter;
15994
- return { effectiveInsertAfter, insertBefore };
15995
- }
15996
- function getSchedulingMountPoint(insertAfter) {
15997
- const { anchor } = parseSchedulingInsertAfter(insertAfter);
15998
- let anchorEl = document.querySelector(`[data-ohw-section="${anchor}"]`);
15999
- if (!anchorEl && anchor === "scheduling") {
16000
- const widgets = Array.from(document.querySelectorAll('[data-ohw-section^="scheduling-"]'));
16001
- anchorEl = widgets.at(-1) ?? null;
16002
- }
16003
- if (!anchorEl) return null;
16004
- return anchorEl.closest('[data-ohw-section-container="scheduling"]') ?? anchorEl;
16173
+ function resolveEntryAnchor(entry) {
16174
+ if (entry.anchorId !== void 0) return { anchorId: entry.anchorId, beforeId: entry.beforeId ?? null };
16175
+ const parsed = parseSchedulingInsertAfter(entry.insertAfter);
16176
+ return { anchorId: parsed.anchor, beforeId: parsed.insertBefore };
16005
16177
  }
16006
16178
  function schedulingMountDepth(insertAfter) {
16007
16179
  if (!insertAfter.includes(INSERT_BEFORE_MARKER)) return 0;
@@ -16018,8 +16190,7 @@ function getPageSchedulingEntries(raw) {
16018
16190
  }
16019
16191
  }
16020
16192
  function isSchedulingWidgetMissing(entry) {
16021
- const { effectiveInsertAfter } = resolveSchedulingInsert(entry.insertAfter);
16022
- return !document.querySelector(`[data-ohw-section="${schedulingSectionId(effectiveInsertAfter)}"]`);
16193
+ return !document.querySelector(`[data-ohw-section="${schedulingSectionId(entry.insertAfter)}"]`);
16023
16194
  }
16024
16195
  function hasMissingSchedulingWidgets(entries) {
16025
16196
  return entries.some(isSchedulingWidgetMissing);
@@ -16057,17 +16228,18 @@ function initSectionsFromContent(content, removeExisting = false, currentPath =
16057
16228
  } catch {
16058
16229
  }
16059
16230
  }
16060
- function mountSchedulingWidget(insertAfter, notifyOnConnect = false, scheduleId, explicitInsertBefore) {
16061
- const { effectiveInsertAfter, insertBefore } = resolveSchedulingInsert(insertAfter, explicitInsertBefore);
16062
- const sectionId = schedulingSectionId(effectiveInsertAfter);
16231
+ function mountSchedulingWidget(anchorId, notifyOnConnect = false, scheduleId, beforeId, existingWidgetId) {
16232
+ const widgetId = existingWidgetId ?? (beforeId ? `${anchorId}${INSERT_BEFORE_MARKER}${beforeId}` : anchorId);
16233
+ const sectionId = schedulingSectionId(widgetId);
16063
16234
  if (document.querySelector(`[data-ohw-section="${sectionId}"]`)) return false;
16064
- const mountPoint = getSchedulingMountPoint(effectiveInsertAfter);
16065
- if (!mountPoint) return false;
16235
+ const anchorEl = document.querySelector(`[data-ohw-section="${anchorId}"]`);
16236
+ if (!anchorEl) return false;
16237
+ const mountPoint = anchorEl.closest('[data-ohw-section-container="scheduling"]') ?? anchorEl;
16066
16238
  const container = document.createElement("div");
16067
16239
  container.dataset.ohwSectionContainer = "scheduling";
16068
16240
  container.dataset.ohwInstance = sectionId;
16069
- if (insertBefore) {
16070
- const beforeAnchor = document.querySelector(`[data-ohw-section="${insertBefore}"]`);
16241
+ if (beforeId) {
16242
+ const beforeAnchor = document.querySelector(`[data-ohw-section="${beforeId}"]`);
16071
16243
  const beforePoint = beforeAnchor?.closest('[data-ohw-section-container="scheduling"]') ?? beforeAnchor;
16072
16244
  if (!beforePoint) return false;
16073
16245
  beforePoint.insertAdjacentElement("beforebegin", container);
@@ -16078,20 +16250,26 @@ function mountSchedulingWidget(insertAfter, notifyOnConnect = false, scheduleId,
16078
16250
  }
16079
16251
  tail.insertAdjacentElement("afterend", container);
16080
16252
  }
16081
- const root = createRoot2(container);
16082
- schedulingRoots.set(container, root);
16083
- flushSync2(() => {
16084
- root.render(
16085
- /* @__PURE__ */ jsx33(
16086
- SchedulingWidget,
16087
- {
16088
- notifyOnConnect,
16089
- initialScheduleId: scheduleId,
16090
- insertAfter: effectiveInsertAfter
16091
- }
16092
- )
16093
- );
16094
- });
16253
+ try {
16254
+ const root = createRoot2(container);
16255
+ schedulingRoots.set(container, root);
16256
+ flushSync2(() => {
16257
+ root.render(
16258
+ /* @__PURE__ */ jsx33(
16259
+ SchedulingWidget,
16260
+ {
16261
+ notifyOnConnect,
16262
+ initialScheduleId: scheduleId,
16263
+ insertAfter: widgetId
16264
+ }
16265
+ )
16266
+ );
16267
+ });
16268
+ } catch (err) {
16269
+ console.error("[ow:scheduling] render threw", err);
16270
+ container.remove();
16271
+ return false;
16272
+ }
16095
16273
  const tracker = getSectionsTracker();
16096
16274
  let sections = [];
16097
16275
  try {
@@ -16099,10 +16277,12 @@ function mountSchedulingWidget(insertAfter, notifyOnConnect = false, scheduleId,
16099
16277
  } catch {
16100
16278
  }
16101
16279
  const inEditor = typeof window !== "undefined" && window.self !== window.top;
16102
- if (inEditor && !sections.find((s) => s.type === "scheduling" && s.insertAfter === effectiveInsertAfter)) {
16280
+ if (inEditor && !sections.find((s) => s.type === "scheduling" && s.insertAfter === widgetId)) {
16103
16281
  sections.push({
16104
16282
  type: "scheduling",
16105
- insertAfter: effectiveInsertAfter,
16283
+ insertAfter: widgetId,
16284
+ anchorId,
16285
+ beforeId: beforeId ?? null,
16106
16286
  pagePath: window.location.pathname,
16107
16287
  ...scheduleId ? { scheduleId } : {}
16108
16288
  });
@@ -16116,7 +16296,8 @@ function mountSchedulingEntries(entries, notifyOnConnect = false) {
16116
16296
  for (let i = pending.length - 1; i >= 0; i--) {
16117
16297
  const entry = pending[i];
16118
16298
  const shouldNotify = typeof notifyOnConnect === "function" ? notifyOnConnect(entry) : notifyOnConnect;
16119
- if (mountSchedulingWidget(entry.insertAfter, shouldNotify, entry.scheduleId ?? null)) {
16299
+ const { anchorId, beforeId } = resolveEntryAnchor(entry);
16300
+ if (mountSchedulingWidget(anchorId, shouldNotify, entry.scheduleId ?? null, beforeId, entry.insertAfter)) {
16120
16301
  pending.splice(i, 1);
16121
16302
  }
16122
16303
  }
@@ -16280,6 +16461,11 @@ function applyLinkByKey(key, val) {
16280
16461
  hrefAnchors.forEach((el) => applyLinkHref(el, val));
16281
16462
  }
16282
16463
  }
16464
+ function isInsideLinkEditor(target) {
16465
+ return Boolean(
16466
+ 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"]')
16467
+ );
16468
+ }
16283
16469
  function isInsideFloatingPanel(target) {
16284
16470
  return Boolean(target.closest("[data-ohw-floating-panel]"));
16285
16471
  }
@@ -16287,11 +16473,6 @@ function isPointOverFloatingPanel(clientX, clientY) {
16287
16473
  const el = document.elementFromPoint(clientX, clientY);
16288
16474
  return Boolean(el instanceof Element && el.closest("[data-ohw-floating-panel]"));
16289
16475
  }
16290
- function isInsideLinkEditor(target) {
16291
- return Boolean(
16292
- 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"]')
16293
- );
16294
- }
16295
16476
  function getHrefKeyFromElement(el) {
16296
16477
  if (!el) return null;
16297
16478
  const anchor = el.closest("[data-ohw-href-key]");
@@ -16451,6 +16632,7 @@ function isFooterAddItemDisabled(selected) {
16451
16632
  }
16452
16633
  function resolveFooterColumnSelectionTarget(target, clientX, clientY) {
16453
16634
  if (getNavigationItemAnchor(target)) return null;
16635
+ if (target.closest("[data-ohw-editable]")) return null;
16454
16636
  const column = getFooterColumn(target);
16455
16637
  if (!column) return null;
16456
16638
  if (isPointOverNavItem(column, clientX, clientY)) return null;
@@ -16560,7 +16742,7 @@ function getNavigationSelectionParent(el) {
16560
16742
  if (!isFooterLinksContainer(el) && (el.hasAttribute("data-ohw-footer-col") || el.hasAttribute("data-ohw-footer-column") || isInferredFooterGroup2(el))) {
16561
16743
  return getFooterLinksContainer();
16562
16744
  }
16563
- if (el.hasAttribute("data-ohw-nav-container") || el.hasAttribute("data-ohw-nav-drawer") || isFooterLinksContainer(el)) {
16745
+ 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)) {
16564
16746
  return getNavigationRoot(el);
16565
16747
  }
16566
16748
  return null;
@@ -16592,6 +16774,12 @@ function collectNavigationItemSiblingHintRects(selected) {
16592
16774
  return [];
16593
16775
  }).filter((el) => isNavigationItem2(el) && el !== selected).map((el) => el.getBoundingClientRect());
16594
16776
  }
16777
+ if (selected.closest('footer, [data-ohw-section="footer"]')) {
16778
+ const group = findFooterItemGroup2(selected);
16779
+ if (group) {
16780
+ return Array.from(group.querySelectorAll("[data-ohw-href-key]")).filter((el) => isNavigationItem2(el) && el !== selected).map((el) => el.getBoundingClientRect());
16781
+ }
16782
+ }
16595
16783
  return listNavigationItems().filter((el) => el !== selected && !el.closest("[data-ohw-nav-children]")).map((el) => el.getBoundingClientRect());
16596
16784
  }
16597
16785
  function placeCaretAtPoint(el, x, y) {
@@ -16776,7 +16964,6 @@ var ICONS = {
16776
16964
  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"/>',
16777
16965
  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"/>'
16778
16966
  };
16779
- var HEIGHT_SETTLE_DELAYS = [150, 400, 800];
16780
16967
  var SELECTION_CHROME_GAP2 = 4;
16781
16968
  var TOOLBAR_STROKE_GAP2 = 4;
16782
16969
  var TOOLBAR_OFFSET_FROM_ELEMENT = SELECTION_CHROME_GAP2 + TOOLBAR_STROKE_GAP2;
@@ -17160,6 +17347,7 @@ function StateToggle({
17160
17347
  );
17161
17348
  }
17162
17349
  var contentCache = /* @__PURE__ */ new Map();
17350
+ var fetchedContentPaths = /* @__PURE__ */ new Set();
17163
17351
  var brandingCache = /* @__PURE__ */ new Map();
17164
17352
  var OHW_LOADER_STYLE = {
17165
17353
  position: "fixed",
@@ -17708,13 +17896,6 @@ function OhhwellsBridge() {
17708
17896
  const [isItemDragging, setIsItemDragging] = useState14(false);
17709
17897
  const [isFooterFrameSelection, setIsFooterFrameSelection] = useState14(false);
17710
17898
  isFooterFrameSelectionRef.current = isFooterFrameSelection;
17711
- const [floatingPanel, setFloatingPanel] = useState14(null);
17712
- const floatingPanelOpenRef = useRef10(false);
17713
- floatingPanelOpenRef.current = floatingPanel !== null;
17714
- const [floatingPanelPos, setFloatingPanelPos] = useState14(null);
17715
- const [logoSizeDraft, setLogoSizeDraft] = useState14(null);
17716
- const [editorViewport, setEditorViewport] = useState14("desktop");
17717
- const [parentScrollSnap, setParentScrollSnap] = useState14(null);
17718
17899
  const [navDropdownPreviewOpen, setNavDropdownPreviewOpen] = useState14(null);
17719
17900
  const [footerHeadingVisible, setFooterHeadingVisible] = useState14(null);
17720
17901
  const footerDragRef = useRef10(null);
@@ -17731,7 +17912,15 @@ function OhhwellsBridge() {
17731
17912
  const aiSectionsRef = useRef10("");
17732
17913
  const brandKitRef = useRef10("");
17733
17914
  const stylesRef = useRef10("");
17915
+ const appliedDrawerRef = useRef10(null);
17734
17916
  const pendingDeleteUndoRef = useRef10(null);
17917
+ const [floatingPanel, setFloatingPanel] = useState14(null);
17918
+ const floatingPanelOpenRef = useRef10(false);
17919
+ const setFloatingPanelRef = useRef10(setFloatingPanel);
17920
+ const [floatingPanelPos, setFloatingPanelPos] = useState14(null);
17921
+ const [logoSizeDraft, setLogoSizeDraft] = useState14(null);
17922
+ const [editorViewport, setEditorViewport] = useState14("desktop");
17923
+ const [parentScrollSnap, setParentScrollSnap] = useState14(null);
17735
17924
  const [sitePages, setSitePages] = useState14([]);
17736
17925
  const [sectionsByPath, setSectionsByPath] = useState14({});
17737
17926
  const sectionsPrefetchGenRef = useRef10(0);
@@ -17740,7 +17929,18 @@ function OhhwellsBridge() {
17740
17929
  const linkPopoverOpenRef = useRef10(false);
17741
17930
  const linkPopoverGraceUntilRef = useRef10(0);
17742
17931
  setLinkPopoverRef.current = setLinkPopover;
17932
+ setFloatingPanelRef.current = setFloatingPanel;
17743
17933
  linkPopoverSessionRef.current = linkPopover;
17934
+ floatingPanelOpenRef.current = Boolean(floatingPanel);
17935
+ useEffect13(() => {
17936
+ const syncViewport = () => {
17937
+ const next = window.innerWidth <= 480 ? "mobile" : "desktop";
17938
+ setEditorViewport((prev) => prev === next ? prev : next);
17939
+ };
17940
+ syncViewport();
17941
+ window.addEventListener("resize", syncViewport);
17942
+ return () => window.removeEventListener("resize", syncViewport);
17943
+ }, []);
17744
17944
  const {
17745
17945
  navDragRef,
17746
17946
  navPointerDragRef,
@@ -19076,6 +19276,7 @@ function OhhwellsBridge() {
19076
19276
  }
19077
19277
  if (typeof content[STYLE_STORE_KEY] === "string") {
19078
19278
  stylesRef.current = content[STYLE_STORE_KEY];
19279
+ document.querySelectorAll('[data-ohw-editable="form"]').forEach((f) => markFormFields(f));
19079
19280
  applyStylesToDom(parseStyleStore(content[STYLE_STORE_KEY]));
19080
19281
  }
19081
19282
  applyBrandChrome(content);
@@ -19083,11 +19284,11 @@ function OhhwellsBridge() {
19083
19284
  for (const [key, val] of Object.entries(content)) {
19084
19285
  if (key === "__ohw_sections") continue;
19085
19286
  if (key === AI_SECTIONS_KEY) continue;
19287
+ if (key === LOGO_PLACEHOLDER_KEY) continue;
19288
+ if (LOGO_IMAGE_KEYS.includes(key)) continue;
19086
19289
  if (key === BRAND_KIT_KEY) continue;
19087
19290
  if (key === STYLE_STORE_KEY) continue;
19088
19291
  if (BRAND_CHROME_KEYS.has(key)) continue;
19089
- if (key === LOGO_PLACEHOLDER_KEY) continue;
19090
- if (LOGO_IMAGE_KEYS.includes(key)) continue;
19091
19292
  if (applyVideoSettingNode(key, val)) continue;
19092
19293
  if (applyCarouselNode(key, val)) continue;
19093
19294
  document.querySelectorAll(`[data-ohw-key="${key}"]`).forEach((el) => {
@@ -19158,7 +19359,9 @@ function OhhwellsBridge() {
19158
19359
  let cancelled = false;
19159
19360
  setFetchState("loading");
19160
19361
  const apiUrl = process.env.NEXT_PUBLIC_FLOWOPS_API_URL ?? "https://flowops-backend-staging-2yfdh7pwpq-as.a.run.app";
19161
- fetch(`${apiUrl}/api/public/sites/${subdomain}/content`, { cache: "no-store" }).then((r2) => r2.ok ? r2.json() : null).then((data) => {
19362
+ const initialPath = pathname;
19363
+ fetchedContentPaths.add(`${subdomain}::${initialPath}`);
19364
+ fetch(`${apiUrl}/api/public/sites/${subdomain}/content?path=${encodeURIComponent(initialPath)}`, { cache: "no-store" }).then((r2) => r2.ok ? r2.json() : null).then((data) => {
19162
19365
  if (cancelled) return;
19163
19366
  const content = data?.content ?? {};
19164
19367
  const branding = Boolean(data?.showBranding);
@@ -19267,7 +19470,9 @@ function OhhwellsBridge() {
19267
19470
  }, [isEditMode]);
19268
19471
  useEffect13(() => {
19269
19472
  if (isEditMode || fetchState !== "done") return;
19473
+ console.log("env", process.env.NEXT_PUBLIC_FLOWOPS_API_URL);
19270
19474
  const apiUrl = process.env.NEXT_PUBLIC_FLOWOPS_API_URL ?? "https://flowops-backend-staging-2yfdh7pwpq-as.a.run.app";
19475
+ console.log({ apiUrl, subdomain });
19271
19476
  bindPublishedForms(apiUrl, subdomain, contentCache.get(subdomain) ?? {});
19272
19477
  }, [isEditMode, fetchState, subdomain]);
19273
19478
  useEffect13(() => {
@@ -19292,16 +19497,17 @@ function OhhwellsBridge() {
19292
19497
  applyAiSectionsToDom(parseAiSectionsState(content[AI_SECTIONS_KEY]));
19293
19498
  }
19294
19499
  if (typeof content[STYLE_STORE_KEY] === "string") {
19500
+ document.querySelectorAll('[data-ohw-editable="form"]').forEach((f) => markFormFields(f));
19295
19501
  applyStylesToDom(parseStyleStore(content[STYLE_STORE_KEY]));
19296
19502
  }
19297
19503
  for (const [key, val] of Object.entries(content)) {
19298
19504
  if (key === "__ohw_sections") continue;
19299
19505
  if (key === AI_SECTIONS_KEY) continue;
19506
+ if (key === LOGO_PLACEHOLDER_KEY) continue;
19507
+ if (LOGO_IMAGE_KEYS.includes(key)) continue;
19300
19508
  if (key === BRAND_KIT_KEY) continue;
19301
19509
  if (key === STYLE_STORE_KEY) continue;
19302
19510
  if (BRAND_CHROME_KEYS.has(key)) continue;
19303
- if (key === LOGO_PLACEHOLDER_KEY) continue;
19304
- if (LOGO_IMAGE_KEYS.includes(key)) continue;
19305
19511
  if (applyVideoSettingNode(key, val)) continue;
19306
19512
  if (applyCarouselNode(key, val)) continue;
19307
19513
  document.querySelectorAll(`[data-ohw-key="${key}"]`).forEach((el) => {
@@ -19352,6 +19558,17 @@ function OhhwellsBridge() {
19352
19558
  debounceTimer = setTimeout(applyFromCache, 150);
19353
19559
  };
19354
19560
  applyFromCache();
19561
+ const pathCacheKey = `${subdomain}::${pathname}`;
19562
+ if (!fetchedContentPaths.has(pathCacheKey)) {
19563
+ fetchedContentPaths.add(pathCacheKey);
19564
+ const apiUrl = process.env.NEXT_PUBLIC_FLOWOPS_API_URL ?? "https://flowops-backend-staging-2yfdh7pwpq-as.a.run.app";
19565
+ fetch(`${apiUrl}/api/public/sites/${subdomain}/content?path=${encodeURIComponent(pathname)}`, { cache: "no-store" }).then((r2) => r2.ok ? r2.json() : null).then((data) => {
19566
+ if (!data?.content) return;
19567
+ contentCache.set(subdomain, data.content);
19568
+ applyFromCache();
19569
+ }).catch(() => {
19570
+ });
19571
+ }
19355
19572
  observer = new MutationObserver(scheduleApply);
19356
19573
  observer.observe(document.body, { childList: true, subtree: true });
19357
19574
  return () => {
@@ -19396,6 +19613,17 @@ function OhhwellsBridge() {
19396
19613
  if (!isEditMode) return;
19397
19614
  initSectionInstancesFromContent(editContentRef.current, pathname);
19398
19615
  }, [pathname, isEditMode]);
19616
+ useEffect13(() => {
19617
+ if (!isEditMode) return;
19618
+ const observer = new MutationObserver(() => {
19619
+ const drawer = document.querySelector("[data-ohw-nav-drawer]");
19620
+ if (!drawer || drawer === appliedDrawerRef.current) return;
19621
+ appliedDrawerRef.current = drawer;
19622
+ applyStylesToDom(parseStyleStore(stylesRef.current));
19623
+ });
19624
+ observer.observe(document.body, { childList: true, subtree: true });
19625
+ return () => observer.disconnect();
19626
+ }, [isEditMode, pathname]);
19399
19627
  useEffect13(() => {
19400
19628
  const contentForNav = () => {
19401
19629
  if (isEditMode) return editContentRef.current;
@@ -19487,26 +19715,11 @@ function OhhwellsBridge() {
19487
19715
  const t2 = setTimeout(measure, 500);
19488
19716
  const ro = new ResizeObserver(schedule);
19489
19717
  ro.observe(document.body);
19490
- let lastWidth = window.innerWidth;
19491
- let resizeTimers = [];
19492
- const clearResizeTimers = () => {
19493
- resizeTimers.forEach(clearTimeout);
19494
- resizeTimers = [];
19495
- };
19496
- const handleResize = () => {
19497
- if (window.innerWidth === lastWidth) return;
19498
- lastWidth = window.innerWidth;
19499
- clearResizeTimers();
19500
- resizeTimers = HEIGHT_SETTLE_DELAYS.map((delay) => setTimeout(measure, delay));
19501
- };
19502
- window.addEventListener("resize", handleResize);
19503
19718
  return () => {
19504
19719
  clearTimeout(t1);
19505
19720
  clearTimeout(t2);
19506
19721
  if (raf != null) cancelAnimationFrame(raf);
19507
19722
  ro.disconnect();
19508
- clearResizeTimers();
19509
- window.removeEventListener("resize", handleResize);
19510
19723
  };
19511
19724
  }, [pathname, isEditMode, postToParent2]);
19512
19725
  useEffect13(() => {
@@ -19689,7 +19902,10 @@ function OhhwellsBridge() {
19689
19902
  flex-wrap: wrap !important;
19690
19903
  align-items: flex-end !important;
19691
19904
  }
19692
- [data-ohw-editable="form"] [data-ohw-form-field] {
19905
+ /* Generated forms are excluded: they are always a single column, so the 12rem
19906
+ flex BASIS \u2014 a minimum width in a horizontal row \u2014 reads as a 12rem HEIGHT
19907
+ there and inflates every field to the tallest one's size. */
19908
+ [data-ohw-editable="form"]:not([data-ai-form]) [data-ohw-form-field] {
19693
19909
  flex: 1 1 12rem !important;
19694
19910
  min-width: min(12rem, 100%) !important;
19695
19911
  max-width: 100% !important;
@@ -19798,9 +20014,6 @@ function OhhwellsBridge() {
19798
20014
  if (target.closest("[data-ohw-state-toggle]")) return;
19799
20015
  if (target.closest("[data-ohw-max-badge]")) return;
19800
20016
  if (isInsideLinkEditor(target)) return;
19801
- if (selectedMediaElRef.current && !selectedMediaElRef.current.contains(target) && !target.closest("[data-ohw-media-overlay], [data-ohw-edit-chrome], [data-ohw-item-interaction]")) {
19802
- clearMediaSelectionRef.current();
19803
- }
19804
20017
  if (isInsideFloatingPanel(target)) return;
19805
20018
  if (target.closest("[data-ohw-form-toolbar]")) return;
19806
20019
  if (target.closest(
@@ -19808,6 +20021,9 @@ function OhhwellsBridge() {
19808
20021
  )) {
19809
20022
  return;
19810
20023
  }
20024
+ if (selectedMediaElRef.current && !selectedMediaElRef.current.contains(target) && !target.closest("[data-ohw-media-overlay], [data-ohw-edit-chrome], [data-ohw-item-interaction]")) {
20025
+ clearMediaSelectionRef.current();
20026
+ }
19811
20027
  {
19812
20028
  const formEl = getFormElement(target);
19813
20029
  const onSuccessText = target.closest(`[${SUCCESS_TEXT_ATTR}]`);
@@ -19971,14 +20187,6 @@ function OhhwellsBridge() {
19971
20187
  }
19972
20188
  const clickedButton = findClosestButtonLike(target);
19973
20189
  const buttonOnMedia = Boolean(clickedButton && isMediaEditable(editable) && editable.contains(clickedButton));
19974
- console.log("[click-debug]", {
19975
- editableType: editable.dataset.ohwEditable,
19976
- editableTag: editable.tagName,
19977
- targetTag: target.tagName,
19978
- clickedButtonTag: clickedButton?.tagName ?? null,
19979
- buttonOnMedia,
19980
- isMediaEditableEditable: isMediaEditable(editable)
19981
- });
19982
20190
  if (isMediaEditable(editable) && !buttonOnMedia) {
19983
20191
  e.preventDefault();
19984
20192
  e.stopPropagation();
@@ -20005,11 +20213,6 @@ function OhhwellsBridge() {
20005
20213
  const hrefLookupTarget = buttonOnMedia ? clickedButton : editable;
20006
20214
  const hrefCtx = getHrefKeyFromElement(hrefLookupTarget);
20007
20215
  const navAnchor = hrefCtx ? getNavigationItemAnchor(hrefCtx.anchor) : null;
20008
- console.log("[click-debug 2]", {
20009
- hrefLookupTargetTag: hrefLookupTarget.tagName,
20010
- hrefCtx: hrefCtx ? { key: hrefCtx.key } : null,
20011
- navAnchorTag: navAnchor?.tagName ?? null
20012
- });
20013
20216
  if (navAnchor) {
20014
20217
  e.preventDefault();
20015
20218
  e.stopPropagation();
@@ -20051,7 +20254,7 @@ function OhhwellsBridge() {
20051
20254
  return;
20052
20255
  }
20053
20256
  const footerColClick = target.closest("[data-ohw-footer-col]");
20054
- if (footerColClick) {
20257
+ if (footerColClick && !target.closest("[data-ohw-editable]")) {
20055
20258
  e.preventDefault();
20056
20259
  e.stopPropagation();
20057
20260
  selectFrameRef.current(footerColClick);
@@ -20179,6 +20382,9 @@ function OhhwellsBridge() {
20179
20382
  setHoveredItemRect(null);
20180
20383
  hoveredNavContainerRef.current = null;
20181
20384
  setHoveredNavContainerRect(null);
20385
+ siblingHintElRef.current = null;
20386
+ setSiblingHintRect(null);
20387
+ setSiblingHintRects([]);
20182
20388
  return;
20183
20389
  }
20184
20390
  {
@@ -20210,7 +20416,7 @@ function OhhwellsBridge() {
20210
20416
  const navContainer = target.closest("[data-ohw-nav-container]");
20211
20417
  if (!navContainer) {
20212
20418
  const footerLinks = target.closest("[data-ohw-footer-links], .rb-footer-links") ?? null;
20213
- if (footerLinks && selected2 !== footerLinks && !getNavigationItemAnchor(target) && !target.closest("[data-ohw-footer-col], [data-ohw-footer-column]")) {
20419
+ if (footerLinks && selected2 !== footerLinks && !getNavigationItemAnchor(target) && !target.closest("[data-ohw-footer-col], [data-ohw-footer-column]") && !target.closest("[data-ohw-editable]")) {
20214
20420
  hoveredNavContainerRef.current = footerLinks;
20215
20421
  setHoveredNavContainerRect(footerLinks.getBoundingClientRect());
20216
20422
  hoveredItemElRef.current = null;
@@ -20248,7 +20454,7 @@ function OhhwellsBridge() {
20248
20454
  return;
20249
20455
  }
20250
20456
  const footerCol = target.closest("[data-ohw-footer-col]") ?? target.closest("[data-ohw-footer-column]");
20251
- if (footerCol) {
20457
+ if (footerCol && !target.closest("[data-ohw-editable]")) {
20252
20458
  hoveredNavContainerRef.current = null;
20253
20459
  setHoveredNavContainerRect(null);
20254
20460
  if (selectedElRef.current === footerCol) return;
@@ -20299,7 +20505,6 @@ function OhhwellsBridge() {
20299
20505
  hoveredNavContainerRef.current = null;
20300
20506
  setHoveredNavContainerRect(null);
20301
20507
  hoveredItemElRef.current = editable;
20302
- setHoveredItemRect(isSelectedForHoverRef.current(editable) ? null : editable.getBoundingClientRect());
20303
20508
  }
20304
20509
  }
20305
20510
  }
@@ -20596,7 +20801,7 @@ function OhhwellsBridge() {
20596
20801
  }
20597
20802
  };
20598
20803
  const probeImageAt = (clientX, clientY, isDragOver = false, fromParentViewport = false) => {
20599
- if (linkPopoverOpenRef.current) {
20804
+ if (linkPopoverOpenRef.current || floatingPanelOpenRef.current && isPointOverFloatingPanel(clientX, clientY)) {
20600
20805
  if (hoveredImageRef.current) {
20601
20806
  hoveredImageRef.current = null;
20602
20807
  hoveredImageHasTextOverlapRef.current = false;
@@ -20969,8 +21174,7 @@ function OhhwellsBridge() {
20969
21174
  };
20970
21175
  const handleMouseMove = (e) => {
20971
21176
  const { clientX, clientY } = e;
20972
- if (pointOwnedByFloatingPanel(clientX, clientY)) return;
20973
- if (isOverEditorChrome(clientX, clientY)) {
21177
+ if (document.documentElement.hasAttribute("data-ohw-panel-dragging") || floatingPanelOpenRef.current && isPointOverFloatingPanel(clientX, clientY) || isOverEditorChrome(clientX, clientY)) {
20974
21178
  document.querySelectorAll("[data-ohw-hovered]").forEach((el) => el.removeAttribute("data-ohw-hovered"));
20975
21179
  formHoverElRef.current = null;
20976
21180
  setFormHoverRect(null);
@@ -20978,7 +21182,12 @@ function OhhwellsBridge() {
20978
21182
  setHoveredItemRect(null);
20979
21183
  hoveredNavContainerRef.current = null;
20980
21184
  setHoveredNavContainerRect(null);
21185
+ siblingHintElRef.current = null;
21186
+ setSiblingHintRect(null);
21187
+ setSiblingHintRects([]);
20981
21188
  dismissImageHover();
21189
+ clearImageHover();
21190
+ setSectionGap(null);
20982
21191
  return;
20983
21192
  }
20984
21193
  if (probeSocialsRowAt(clientX, clientY)) return;
@@ -20990,7 +21199,11 @@ function OhhwellsBridge() {
20990
21199
  if (e.data?.type !== "ow:pointer-sync") return;
20991
21200
  const { clientX, clientY } = e.data;
20992
21201
  if (typeof clientX !== "number" || typeof clientY !== "number") return;
20993
- if (pointOwnedByFloatingPanel(clientX, clientY)) return;
21202
+ if (document.documentElement.hasAttribute("data-ohw-panel-dragging") || floatingPanelOpenRef.current && isPointOverFloatingPanel(clientX, clientY)) {
21203
+ dismissImageHover();
21204
+ clearImageHover();
21205
+ return;
21206
+ }
20994
21207
  if (probeSocialsRowAt(clientX, clientY)) return;
20995
21208
  probeSectionGapAt(clientX, clientY);
20996
21209
  probeImageAt(clientX, clientY);
@@ -21329,6 +21542,7 @@ function OhhwellsBridge() {
21329
21542
  }
21330
21543
  if (typeof content[STYLE_STORE_KEY] === "string") {
21331
21544
  stylesRef.current = content[STYLE_STORE_KEY];
21545
+ document.querySelectorAll('[data-ohw-editable="form"]').forEach((f) => markFormFields(f));
21332
21546
  applyStylesToDom(parseStyleStore(content[STYLE_STORE_KEY]));
21333
21547
  }
21334
21548
  applyBrandChrome(content);
@@ -21340,11 +21554,11 @@ function OhhwellsBridge() {
21340
21554
  continue;
21341
21555
  }
21342
21556
  if (key === AI_SECTIONS_KEY) continue;
21557
+ if (key === LOGO_PLACEHOLDER_KEY) continue;
21558
+ if (LOGO_IMAGE_KEYS.includes(key) && !String(val ?? "").trim()) continue;
21343
21559
  if (key === BRAND_KIT_KEY) continue;
21344
21560
  if (key === STYLE_STORE_KEY) continue;
21345
21561
  if (BRAND_CHROME_KEYS.has(key)) continue;
21346
- if (key === LOGO_PLACEHOLDER_KEY) continue;
21347
- if (LOGO_IMAGE_KEYS.includes(key) && !String(val ?? "").trim()) continue;
21348
21562
  if (applyVideoSettingNode(key, val)) continue;
21349
21563
  if (applyCarouselNode(key, val)) continue;
21350
21564
  document.querySelectorAll(`[data-ohw-key="${key}"]`).forEach((el) => {
@@ -21569,10 +21783,26 @@ function OhhwellsBridge() {
21569
21783
  const handleGetBrand = (e) => {
21570
21784
  if (e.data?.type !== "ow:get-brand") return;
21571
21785
  const template = deriveTemplateBrand();
21572
- const value = brandKitRef.current || (template ? JSON.stringify(template) : "");
21786
+ const fallback = template ?? (() => {
21787
+ const fonts = deriveTemplateFonts();
21788
+ return fonts ? { palette: AI_DEFAULT_BRAND.palette, fonts } : null;
21789
+ })();
21790
+ const value = brandKitRef.current || (fallback ? JSON.stringify(fallback) : "");
21573
21791
  postToParentRef.current({ type: "ow:brand-value", value });
21574
21792
  };
21575
21793
  window.addEventListener("message", handleGetBrand);
21794
+ const handleGetTemplateFonts = (e) => {
21795
+ if (e.data?.type !== "ow:get-template-fonts") return;
21796
+ const fonts = deriveTemplateFonts();
21797
+ postToParentRef.current({ type: "ow:template-fonts-value", value: fonts ? JSON.stringify(fonts) : "" });
21798
+ };
21799
+ window.addEventListener("message", handleGetTemplateFonts);
21800
+ const handleGetTemplateBrand = (e) => {
21801
+ if (e.data?.type !== "ow:get-template-brand") return;
21802
+ const brand = deriveTemplateBrand();
21803
+ postToParentRef.current({ type: "ow:template-brand-value", value: brand ? JSON.stringify(brand) : "" });
21804
+ };
21805
+ window.addEventListener("message", handleGetTemplateBrand);
21576
21806
  const handleMoveSection = (e) => {
21577
21807
  if (e.data?.type !== "ow:move-section") return;
21578
21808
  const instanceId = typeof e.data.instanceId === "string" ? e.data.instanceId : "";
@@ -21765,6 +21995,12 @@ function OhhwellsBridge() {
21765
21995
  closeLinkPopoverRef.current();
21766
21996
  return;
21767
21997
  }
21998
+ if (floatingPanelOpenRef.current) {
21999
+ setFloatingPanelRef.current(null);
22000
+ deselectRef.current();
22001
+ deactivateRef.current();
22002
+ return;
22003
+ }
21768
22004
  deselectRef.current();
21769
22005
  deactivateRef.current();
21770
22006
  clearMediaSelectionRef.current();
@@ -22053,8 +22289,12 @@ function OhhwellsBridge() {
22053
22289
  if (inserted) {
22054
22290
  const tracker = getSectionsTracker();
22055
22291
  postToParentRef.current({ type: "ow:change", nodes: [{ key: "__ohw_sections", text: tracker.textContent ?? "[]" }] });
22056
- const h = document.body.scrollHeight;
22057
- if (h > 50) postToParentRef.current({ type: "ow:height", height: h });
22292
+ const reportHeight = () => {
22293
+ const h = document.body.scrollHeight;
22294
+ if (h > 50) postToParentRef.current({ type: "ow:height", height: h });
22295
+ };
22296
+ reportHeight();
22297
+ setTimeout(reportHeight, 500);
22058
22298
  }
22059
22299
  };
22060
22300
  const handleSwitchSchedule = (e) => {
@@ -22475,15 +22715,17 @@ function OhhwellsBridge() {
22475
22715
  window.removeEventListener("message", handleAiSetBrand);
22476
22716
  window.removeEventListener("message", handleAiSetStyles);
22477
22717
  window.removeEventListener("message", handleGetBrand);
22718
+ window.removeEventListener("message", handleGetTemplateFonts);
22719
+ window.removeEventListener("message", handleGetTemplateBrand);
22478
22720
  window.removeEventListener("message", handleMoveSection);
22479
22721
  window.removeEventListener("message", handlePanelDragging);
22480
22722
  window.removeEventListener("message", handleDeleteSection);
22481
22723
  window.removeEventListener("message", handleDuplicateSection);
22482
22724
  window.removeEventListener("message", handleDeactivate);
22483
- document.documentElement.removeAttribute("data-ohw-panel-dragging");
22484
22725
  window.removeEventListener("message", handleToastAction);
22485
22726
  window.removeEventListener("message", handleFormCount);
22486
22727
  window.removeEventListener("message", handleUiEscape);
22728
+ document.documentElement.removeAttribute("data-ohw-panel-dragging");
22487
22729
  autoSaveTimers.current.forEach(clearTimeout);
22488
22730
  autoSaveTimers.current.clear();
22489
22731
  if (imageUnhoverTimerRef.current) clearTimeout(imageUnhoverTimerRef.current);
@@ -22686,7 +22928,7 @@ function OhhwellsBridge() {
22686
22928
  postToParent2({
22687
22929
  type: "ow:ready",
22688
22930
  version: "1",
22689
- bridgeVersion: "0.1.110",
22931
+ bridgeVersion: "0.1.112",
22690
22932
  path: pathname,
22691
22933
  nodes: collectEditableNodes(editContentRef.current),
22692
22934
  sections
@@ -23302,7 +23544,7 @@ function OhhwellsBridge() {
23302
23544
  "span",
23303
23545
  {
23304
23546
  "data-ohw-form-count": "",
23305
- 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",
23547
+ 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",
23306
23548
  children: formPickCount
23307
23549
  }
23308
23550
  )
@@ -23540,11 +23782,11 @@ function OhhwellsBridge() {
23540
23782
  className: "fixed left-0 w-full z-2147483646 flex items-center pointer-events-none",
23541
23783
  style: { top: sectionGap.y, transform: "translateY(-50%)" },
23542
23784
  children: [
23543
- /* @__PURE__ */ jsx33("div", { className: "flex-1 bg-primary", style: { height: 3 } }),
23785
+ /* @__PURE__ */ jsx33("div", { className: "flex-1 bg-bridge-primary", style: { height: 3 } }),
23544
23786
  /* @__PURE__ */ jsx33(
23545
23787
  Badge,
23546
23788
  {
23547
- 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",
23789
+ 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",
23548
23790
  onClick: () => {
23549
23791
  window.parent.postMessage(
23550
23792
  {
@@ -23558,7 +23800,7 @@ function OhhwellsBridge() {
23558
23800
  children: "Add Section"
23559
23801
  }
23560
23802
  ),
23561
- /* @__PURE__ */ jsx33("div", { className: "flex-1 bg-primary", style: { height: 3 } })
23803
+ /* @__PURE__ */ jsx33("div", { className: "flex-1 bg-bridge-primary", style: { height: 3 } })
23562
23804
  ]
23563
23805
  }
23564
23806
  ),
@@ -23606,6 +23848,59 @@ function OhhwellsBridge() {
23606
23848
  ) : null
23607
23849
  ] });
23608
23850
  }
23851
+
23852
+ // src/ui/EmptySection.tsx
23853
+ import Link3 from "next/link";
23854
+ import { Fragment as Fragment9, jsx as jsx34, jsxs as jsxs21 } from "react/jsx-runtime";
23855
+ function EmptySection({ title, homeHref = "/", eyebrowKey, titleKey, subtitleKey }) {
23856
+ return /* @__PURE__ */ jsxs21(Fragment9, { children: [
23857
+ /* @__PURE__ */ jsx34(
23858
+ "p",
23859
+ {
23860
+ style: {
23861
+ fontFamily: "var(--brand-font-body)",
23862
+ fontSize: "0.75rem",
23863
+ fontWeight: 500,
23864
+ letterSpacing: "0.15em",
23865
+ textTransform: "uppercase",
23866
+ color: "var(--brand-accent)",
23867
+ marginBottom: "1.5rem"
23868
+ },
23869
+ children: /* @__PURE__ */ jsx34(Link3, { href: homeHref, style: { color: "inherit" }, children: /* @__PURE__ */ jsx34("span", { ...eyebrowKey ? { "data-ohw-editable": "text", "data-ohw-key": eyebrowKey } : {}, children: "Home" }) })
23870
+ }
23871
+ ),
23872
+ /* @__PURE__ */ jsx34(
23873
+ "h1",
23874
+ {
23875
+ style: {
23876
+ fontFamily: "var(--brand-font-heading)",
23877
+ fontSize: "clamp(2rem, 3.5vw, 2.75rem)",
23878
+ lineHeight: 1.1,
23879
+ letterSpacing: "-0.025em",
23880
+ color: "var(--brand-text)",
23881
+ marginBottom: "1rem"
23882
+ },
23883
+ ...titleKey ? { "data-ohw-editable": "text", "data-ohw-key": titleKey, "data-ohw-max-length": 80 } : {},
23884
+ children: title
23885
+ }
23886
+ ),
23887
+ /* @__PURE__ */ jsx34(
23888
+ "p",
23889
+ {
23890
+ style: {
23891
+ fontFamily: "var(--brand-font-body)",
23892
+ fontSize: "1rem",
23893
+ lineHeight: 1.7,
23894
+ fontWeight: 300,
23895
+ color: "var(--brand-text-muted)",
23896
+ maxWidth: "340px"
23897
+ },
23898
+ ...subtitleKey ? { "data-ohw-editable": "text", "data-ohw-key": subtitleKey, "data-ohw-max-length": 160 } : {},
23899
+ children: "This page doesn't have any content yet."
23900
+ }
23901
+ )
23902
+ ] });
23903
+ }
23609
23904
  export {
23610
23905
  AI_DEFAULT_BRAND,
23611
23906
  AI_TREE_SCHEMA_VERSIONS,
@@ -23622,6 +23917,7 @@ export {
23622
23917
  DropdownMenuItem,
23623
23918
  DropdownMenuSeparator,
23624
23919
  DropdownMenuTrigger,
23920
+ EmptySection,
23625
23921
  ItemActionToolbar,
23626
23922
  ItemInteractionLayer,
23627
23923
  LinkEditorPanel,