@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.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
@@ -8178,6 +8359,7 @@ var FIELD_TYPES = [
8178
8359
  var FIELD_ATTR = "data-ohw-form-field";
8179
8360
  var FIELD_TYPE_ATTR = "data-ohw-field-type";
8180
8361
  var PLACEHOLDER_EDIT_ATTR = "data-ohw-placeholder-edit";
8362
+ var seedFieldByForm = /* @__PURE__ */ new WeakMap();
8181
8363
  function fieldsKey(formKey) {
8182
8364
  return `${formKey}-fields`;
8183
8365
  }
@@ -8263,7 +8445,11 @@ function markFormFields(form) {
8263
8445
  ensureFieldLabel(wrapper, key);
8264
8446
  syncRequiredMark(wrapper);
8265
8447
  });
8266
- return listFieldWrappers(form);
8448
+ const wrappers = listFieldWrappers(form);
8449
+ if (wrappers.length > 0 && !seedFieldByForm.has(form)) {
8450
+ seedFieldByForm.set(form, wrappers[0].cloneNode(true));
8451
+ }
8452
+ return wrappers;
8267
8453
  }
8268
8454
  function readFieldsFromDom(form) {
8269
8455
  return listFieldWrappers(form).map((wrapper) => {
@@ -8407,12 +8593,27 @@ function insertField(form, type) {
8407
8593
  wrapper = source.cloneNode(true);
8408
8594
  source.after(wrapper);
8409
8595
  } else {
8410
- wrapper = document.createElement("div");
8411
- const label2 = document.createElement("label");
8412
- const input2 = document.createElement("input");
8413
- wrapper.append(label2, input2);
8596
+ const seed = seedFieldByForm.get(form);
8597
+ if (seed) {
8598
+ wrapper = seed.cloneNode(true);
8599
+ } else {
8600
+ wrapper = document.createElement("div");
8601
+ const input2 = document.createElement("input");
8602
+ input2.style.width = "100%";
8603
+ input2.style.boxSizing = "border-box";
8604
+ input2.style.padding = "12px";
8605
+ input2.style.border = "1px solid color-mix(in srgb, currentColor 35%, transparent)";
8606
+ input2.style.borderRadius = "8px";
8607
+ input2.style.background = "#fff";
8608
+ input2.style.fontFamily = "inherit";
8609
+ input2.style.fontSize = "inherit";
8610
+ input2.style.color = "inherit";
8611
+ wrapper.append(input2);
8612
+ }
8414
8613
  const submit = form.querySelector('button, input[type="submit"]');
8415
- if (submit) submit.before(wrapper);
8614
+ let anchor = submit;
8615
+ while (anchor && anchor.parentElement !== form) anchor = anchor.parentElement;
8616
+ if (anchor) anchor.before(wrapper);
8416
8617
  else form.appendChild(wrapper);
8417
8618
  }
8418
8619
  wrapper.setAttribute(FIELD_ATTR, key);
@@ -8547,13 +8748,13 @@ function FormFieldToolbar({
8547
8748
  ]
8548
8749
  }
8549
8750
  ) }),
8550
- /* @__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) => {
8551
8752
  const Icon = TYPE_ICONS[entry.type];
8552
8753
  return /* @__PURE__ */ jsxs6(
8553
8754
  DropdownMenuItem,
8554
8755
  {
8555
8756
  onSelect: () => onTypeChange(entry.type),
8556
- 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" : ""),
8557
8758
  children: [
8558
8759
  /* @__PURE__ */ jsx13(Icon, { size: 14, strokeWidth: 1.75, className: "shrink-0", "aria-hidden": true }),
8559
8760
  entry.label
@@ -8570,7 +8771,7 @@ function FormFieldToolbar({
8570
8771
  type: "button",
8571
8772
  "aria-pressed": required,
8572
8773
  onClick: onRequiredToggle,
8573
- 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"),
8574
8775
  "data-ohw-field-required-toggle": "",
8575
8776
  children: [
8576
8777
  /* @__PURE__ */ jsx13(Asterisk, { size: 14, strokeWidth: 2, "aria-hidden": true }),
@@ -8590,7 +8791,7 @@ function FormFieldToolbar({
8590
8791
  children: /* @__PURE__ */ jsx13(MoreHorizontal2, { size: 15, "aria-hidden": true })
8591
8792
  }
8592
8793
  ) }),
8593
- /* @__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: [
8594
8795
  /* @__PURE__ */ jsxs6(DropdownMenuItem, { onSelect: onDuplicate, className: "rounded-md py-2 text-[13px]", children: [
8595
8796
  /* @__PURE__ */ jsx13(Copy2, { size: 14, strokeWidth: 1.75, className: "shrink-0", "aria-hidden": true }),
8596
8797
  "Duplicate"
@@ -8618,7 +8819,7 @@ function FieldTypePicker({ onPick }) {
8618
8819
  {
8619
8820
  type: "button",
8620
8821
  onClick: () => onPick(entry.type),
8621
- 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",
8622
8823
  children: [
8623
8824
  /* @__PURE__ */ jsx13(Icon, { size: 26, strokeWidth: 1.5, "aria-hidden": true }),
8624
8825
  entry.label
@@ -8644,7 +8845,7 @@ var buttonVariants = cva(
8644
8845
  {
8645
8846
  variants: {
8646
8847
  variant: {
8647
- default: "bg-primary text-primary-foreground hover:opacity-90",
8848
+ default: "bg-bridge-primary text-primary-foreground hover:opacity-90",
8648
8849
  outline: "border border-border bg-background text-foreground shadow-sm hover:bg-muted/80",
8649
8850
  ghost: "min-w-0 px-3 py-2 text-foreground hover:bg-muted/50"
8650
8851
  },
@@ -8739,6 +8940,7 @@ function MediaOverlay({
8739
8940
  (prev) => prev && prev.fullWidth === width && prev.height === height ? prev : { fullWidth: width, height }
8740
8941
  );
8741
8942
  }, [isVideo]);
8943
+ const replaceLabel = isVideo ? "Replace video" : hover.elementType === "bg-image" ? "Replace background" : "Replace image";
8742
8944
  const replaceMode = buttonSize ? replaceButtonMode({ width: rect.width, height: rect.height }, buttonSize) : "none";
8743
8945
  const box = {
8744
8946
  position: "fixed",
@@ -8844,8 +9046,8 @@ function MediaOverlay({
8844
9046
  pointerEvents: hover.hasTextOverlap ? "none" : "auto",
8845
9047
  // Selected: a firm component ring with no wash, so the image reads as chosen rather
8846
9048
  // than hovered. Hover keeps the existing tinted preview.
8847
- boxShadow: selected ? "inset 0 0 0 2px var(--color-primary)" : "inset 0 0 0 1.5px var(--color-primary)",
8848
- 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)"
8849
9051
  },
8850
9052
  onClick: () => onSelect && !selected ? onSelect(hover.key) : onReplace(hover.key),
8851
9053
  children: [
@@ -8868,17 +9070,17 @@ function MediaOverlay({
8868
9070
  },
8869
9071
  children: [
8870
9072
  isVideo ? /* @__PURE__ */ jsx15(Film, { size: 14 }) : /* @__PURE__ */ jsx15(ImageIcon, { size: 14 }),
8871
- isVideo ? "Replace video" : "Replace image"
9073
+ replaceLabel
8872
9074
  ]
8873
9075
  }
8874
9076
  ),
8875
- replaceMode === "none" ? null : /* @__PURE__ */ jsxs7(
9077
+ showChrome && replaceMode !== "none" && /* @__PURE__ */ jsxs7(
8876
9078
  Button,
8877
9079
  {
8878
9080
  "data-ohw-media-overlay": "",
8879
9081
  variant: "outline",
8880
9082
  size: "sm",
8881
- "aria-label": isVideo ? "Replace video" : "Replace image",
9083
+ "aria-label": replaceLabel,
8882
9084
  className: "gap-1.5 cursor-pointer hover:bg-background",
8883
9085
  style: {
8884
9086
  ...OVERLAY_BUTTON_STYLE,
@@ -8901,7 +9103,7 @@ function MediaOverlay({
8901
9103
  },
8902
9104
  children: [
8903
9105
  isVideo ? /* @__PURE__ */ jsx15(Film, { size: 14 }) : /* @__PURE__ */ jsx15(ImageIcon, { size: 14 }),
8904
- replaceMode === "full" ? isVideo ? "Replace video" : hover.elementType === "bg-image" ? "Replace background" : "Replace image" : null
9106
+ replaceMode === "full" ? replaceLabel : null
8905
9107
  ]
8906
9108
  }
8907
9109
  )
@@ -8939,8 +9141,8 @@ function CarouselOverlay({
8939
9141
  height: rect.height,
8940
9142
  zIndex: 2147483646,
8941
9143
  pointerEvents: "auto",
8942
- boxShadow: "inset 0 0 0 1.5px var(--color-primary)",
8943
- 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)"
8944
9146
  },
8945
9147
  onClick: () => onEdit(hover.key),
8946
9148
  children: /* @__PURE__ */ jsxs8(
@@ -9793,7 +9995,7 @@ function SectionTreeItem({
9793
9995
  /* @__PURE__ */ jsx20(
9794
9996
  "div",
9795
9997
  {
9796
- 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",
9797
9999
  "aria-hidden": true
9798
10000
  }
9799
10001
  ),
@@ -9812,7 +10014,7 @@ function SectionTreeItem({
9812
10014
  className: cn(
9813
10015
  "flex h-9 min-w-0 flex-1 items-center gap-2 rounded-md border border-border bg-background p-3",
9814
10016
  interactive && "cursor-pointer hover:bg-muted/30",
9815
- interactive && selected && "border-primary"
10017
+ interactive && selected && "border-bridge-primary"
9816
10018
  ),
9817
10019
  children: [
9818
10020
  /* @__PURE__ */ jsx20(
@@ -9942,7 +10144,7 @@ function UrlOrPageInput({
9942
10144
  };
9943
10145
  const fieldClassName = cn(
9944
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]",
9945
- 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"
9946
10148
  );
9947
10149
  return /* @__PURE__ */ jsxs13("div", { className: "flex w-full flex-col gap-2 p-0", children: [
9948
10150
  /* @__PURE__ */ jsx23(Label, { htmlFor: inputId, className: cn(urlError && "text-destructive"), children: "Destination" }),
@@ -13849,6 +14051,7 @@ function readLogoSizeState(content, placement) {
13849
14051
  function getLogoElement(el) {
13850
14052
  const marked = el.closest('[data-ohw-role="logo"], [data-ohw-logo]');
13851
14053
  if (marked) return marked;
14054
+ if (el.closest('[data-ohw-editable="icon"]')) return null;
13852
14055
  const root = el.closest("nav, [data-ohw-nav-root], footer");
13853
14056
  if (!root) return null;
13854
14057
  const anchor = el.closest("a");
@@ -14142,7 +14345,7 @@ function DisplaySwitch({
14142
14345
  onClick: () => onChange(!checked),
14143
14346
  className: cn(
14144
14347
  "relative h-5 w-9 shrink-0 rounded-full transition-colors",
14145
- checked ? "bg-primary" : "bg-primary-50",
14348
+ checked ? "bg-bridge-primary" : "bg-primary-50",
14146
14349
  disabled ? "cursor-default opacity-50" : "cursor-pointer"
14147
14350
  ),
14148
14351
  children: /* @__PURE__ */ jsx28(
@@ -14150,7 +14353,7 @@ function DisplaySwitch({
14150
14353
  {
14151
14354
  className: cn(
14152
14355
  "absolute top-0.5 h-4 w-4 rounded-full bg-white transition-all",
14153
- checked ? "left-[1.125rem]" : "left-0.5"
14356
+ checked ? "left-4.5" : "left-0.5"
14154
14357
  )
14155
14358
  }
14156
14359
  )
@@ -15785,7 +15988,7 @@ var badgeVariants = cva(
15785
15988
  {
15786
15989
  variants: {
15787
15990
  variant: {
15788
- default: "border-transparent bg-primary text-primary-foreground",
15991
+ default: "border-transparent bg-bridge-primary text-primary-foreground",
15789
15992
  secondary: "border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80",
15790
15993
  destructive: "border-transparent bg-destructive text-destructive-foreground hover:bg-destructive/80",
15791
15994
  outline: "text-foreground"
@@ -15967,21 +16170,10 @@ function parseSchedulingInsertAfter(insertAfter) {
15967
16170
  insertBefore: insertAfter.slice(idx + INSERT_BEFORE_MARKER.length)
15968
16171
  };
15969
16172
  }
15970
- function resolveSchedulingInsert(insertAfter, explicitInsertBefore) {
15971
- const parsed = parseSchedulingInsertAfter(insertAfter);
15972
- const insertBefore = explicitInsertBefore ?? parsed.insertBefore;
15973
- const effectiveInsertAfter = insertBefore && parsed.insertBefore === null ? `${insertAfter}${INSERT_BEFORE_MARKER}${insertBefore}` : insertAfter;
15974
- return { effectiveInsertAfter, insertBefore };
15975
- }
15976
- function getSchedulingMountPoint(insertAfter) {
15977
- const { anchor } = parseSchedulingInsertAfter(insertAfter);
15978
- let anchorEl = document.querySelector(`[data-ohw-section="${anchor}"]`);
15979
- if (!anchorEl && anchor === "scheduling") {
15980
- const widgets = Array.from(document.querySelectorAll('[data-ohw-section^="scheduling-"]'));
15981
- anchorEl = widgets.at(-1) ?? null;
15982
- }
15983
- if (!anchorEl) return null;
15984
- 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 };
15985
16177
  }
15986
16178
  function schedulingMountDepth(insertAfter) {
15987
16179
  if (!insertAfter.includes(INSERT_BEFORE_MARKER)) return 0;
@@ -15998,8 +16190,7 @@ function getPageSchedulingEntries(raw) {
15998
16190
  }
15999
16191
  }
16000
16192
  function isSchedulingWidgetMissing(entry) {
16001
- const { effectiveInsertAfter } = resolveSchedulingInsert(entry.insertAfter);
16002
- return !document.querySelector(`[data-ohw-section="${schedulingSectionId(effectiveInsertAfter)}"]`);
16193
+ return !document.querySelector(`[data-ohw-section="${schedulingSectionId(entry.insertAfter)}"]`);
16003
16194
  }
16004
16195
  function hasMissingSchedulingWidgets(entries) {
16005
16196
  return entries.some(isSchedulingWidgetMissing);
@@ -16037,17 +16228,18 @@ function initSectionsFromContent(content, removeExisting = false, currentPath =
16037
16228
  } catch {
16038
16229
  }
16039
16230
  }
16040
- function mountSchedulingWidget(insertAfter, notifyOnConnect = false, scheduleId, explicitInsertBefore) {
16041
- const { effectiveInsertAfter, insertBefore } = resolveSchedulingInsert(insertAfter, explicitInsertBefore);
16042
- 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);
16043
16234
  if (document.querySelector(`[data-ohw-section="${sectionId}"]`)) return false;
16044
- const mountPoint = getSchedulingMountPoint(effectiveInsertAfter);
16045
- 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;
16046
16238
  const container = document.createElement("div");
16047
16239
  container.dataset.ohwSectionContainer = "scheduling";
16048
16240
  container.dataset.ohwInstance = sectionId;
16049
- if (insertBefore) {
16050
- const beforeAnchor = document.querySelector(`[data-ohw-section="${insertBefore}"]`);
16241
+ if (beforeId) {
16242
+ const beforeAnchor = document.querySelector(`[data-ohw-section="${beforeId}"]`);
16051
16243
  const beforePoint = beforeAnchor?.closest('[data-ohw-section-container="scheduling"]') ?? beforeAnchor;
16052
16244
  if (!beforePoint) return false;
16053
16245
  beforePoint.insertAdjacentElement("beforebegin", container);
@@ -16058,20 +16250,26 @@ function mountSchedulingWidget(insertAfter, notifyOnConnect = false, scheduleId,
16058
16250
  }
16059
16251
  tail.insertAdjacentElement("afterend", container);
16060
16252
  }
16061
- const root = createRoot2(container);
16062
- schedulingRoots.set(container, root);
16063
- flushSync2(() => {
16064
- root.render(
16065
- /* @__PURE__ */ jsx33(
16066
- SchedulingWidget,
16067
- {
16068
- notifyOnConnect,
16069
- initialScheduleId: scheduleId,
16070
- insertAfter: effectiveInsertAfter
16071
- }
16072
- )
16073
- );
16074
- });
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
+ }
16075
16273
  const tracker = getSectionsTracker();
16076
16274
  let sections = [];
16077
16275
  try {
@@ -16079,10 +16277,12 @@ function mountSchedulingWidget(insertAfter, notifyOnConnect = false, scheduleId,
16079
16277
  } catch {
16080
16278
  }
16081
16279
  const inEditor = typeof window !== "undefined" && window.self !== window.top;
16082
- if (inEditor && !sections.find((s) => s.type === "scheduling" && s.insertAfter === effectiveInsertAfter)) {
16280
+ if (inEditor && !sections.find((s) => s.type === "scheduling" && s.insertAfter === widgetId)) {
16083
16281
  sections.push({
16084
16282
  type: "scheduling",
16085
- insertAfter: effectiveInsertAfter,
16283
+ insertAfter: widgetId,
16284
+ anchorId,
16285
+ beforeId: beforeId ?? null,
16086
16286
  pagePath: window.location.pathname,
16087
16287
  ...scheduleId ? { scheduleId } : {}
16088
16288
  });
@@ -16096,7 +16296,8 @@ function mountSchedulingEntries(entries, notifyOnConnect = false) {
16096
16296
  for (let i = pending.length - 1; i >= 0; i--) {
16097
16297
  const entry = pending[i];
16098
16298
  const shouldNotify = typeof notifyOnConnect === "function" ? notifyOnConnect(entry) : notifyOnConnect;
16099
- 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)) {
16100
16301
  pending.splice(i, 1);
16101
16302
  }
16102
16303
  }
@@ -16260,6 +16461,11 @@ function applyLinkByKey(key, val) {
16260
16461
  hrefAnchors.forEach((el) => applyLinkHref(el, val));
16261
16462
  }
16262
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
+ }
16263
16469
  function isInsideFloatingPanel(target) {
16264
16470
  return Boolean(target.closest("[data-ohw-floating-panel]"));
16265
16471
  }
@@ -16267,11 +16473,6 @@ function isPointOverFloatingPanel(clientX, clientY) {
16267
16473
  const el = document.elementFromPoint(clientX, clientY);
16268
16474
  return Boolean(el instanceof Element && el.closest("[data-ohw-floating-panel]"));
16269
16475
  }
16270
- function isInsideLinkEditor(target) {
16271
- return Boolean(
16272
- 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"]')
16273
- );
16274
- }
16275
16476
  function getHrefKeyFromElement(el) {
16276
16477
  if (!el) return null;
16277
16478
  const anchor = el.closest("[data-ohw-href-key]");
@@ -16540,7 +16741,7 @@ function getNavigationSelectionParent(el) {
16540
16741
  if (!isFooterLinksContainer(el) && (el.hasAttribute("data-ohw-footer-col") || el.hasAttribute("data-ohw-footer-column") || isInferredFooterGroup2(el))) {
16541
16742
  return getFooterLinksContainer();
16542
16743
  }
16543
- if (el.hasAttribute("data-ohw-nav-container") || el.hasAttribute("data-ohw-nav-drawer") || isFooterLinksContainer(el)) {
16744
+ 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)) {
16544
16745
  return getNavigationRoot(el);
16545
16746
  }
16546
16747
  return null;
@@ -16756,7 +16957,6 @@ var ICONS = {
16756
16957
  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"/>',
16757
16958
  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"/>'
16758
16959
  };
16759
- var HEIGHT_SETTLE_DELAYS = [150, 400, 800];
16760
16960
  var SELECTION_CHROME_GAP2 = 4;
16761
16961
  var TOOLBAR_STROKE_GAP2 = 4;
16762
16962
  var TOOLBAR_OFFSET_FROM_ELEMENT = SELECTION_CHROME_GAP2 + TOOLBAR_STROKE_GAP2;
@@ -17140,6 +17340,7 @@ function StateToggle({
17140
17340
  );
17141
17341
  }
17142
17342
  var contentCache = /* @__PURE__ */ new Map();
17343
+ var fetchedContentPaths = /* @__PURE__ */ new Set();
17143
17344
  var brandingCache = /* @__PURE__ */ new Map();
17144
17345
  var OHW_LOADER_STYLE = {
17145
17346
  position: "fixed",
@@ -17688,13 +17889,6 @@ function OhhwellsBridge() {
17688
17889
  const [isItemDragging, setIsItemDragging] = useState14(false);
17689
17890
  const [isFooterFrameSelection, setIsFooterFrameSelection] = useState14(false);
17690
17891
  isFooterFrameSelectionRef.current = isFooterFrameSelection;
17691
- const [floatingPanel, setFloatingPanel] = useState14(null);
17692
- const floatingPanelOpenRef = useRef10(false);
17693
- floatingPanelOpenRef.current = floatingPanel !== null;
17694
- const [floatingPanelPos, setFloatingPanelPos] = useState14(null);
17695
- const [logoSizeDraft, setLogoSizeDraft] = useState14(null);
17696
- const [editorViewport, setEditorViewport] = useState14("desktop");
17697
- const [parentScrollSnap, setParentScrollSnap] = useState14(null);
17698
17892
  const [navDropdownPreviewOpen, setNavDropdownPreviewOpen] = useState14(null);
17699
17893
  const [footerHeadingVisible, setFooterHeadingVisible] = useState14(null);
17700
17894
  const footerDragRef = useRef10(null);
@@ -17711,7 +17905,15 @@ function OhhwellsBridge() {
17711
17905
  const aiSectionsRef = useRef10("");
17712
17906
  const brandKitRef = useRef10("");
17713
17907
  const stylesRef = useRef10("");
17908
+ const appliedDrawerRef = useRef10(null);
17714
17909
  const pendingDeleteUndoRef = useRef10(null);
17910
+ const [floatingPanel, setFloatingPanel] = useState14(null);
17911
+ const floatingPanelOpenRef = useRef10(false);
17912
+ const setFloatingPanelRef = useRef10(setFloatingPanel);
17913
+ const [floatingPanelPos, setFloatingPanelPos] = useState14(null);
17914
+ const [logoSizeDraft, setLogoSizeDraft] = useState14(null);
17915
+ const [editorViewport, setEditorViewport] = useState14("desktop");
17916
+ const [parentScrollSnap, setParentScrollSnap] = useState14(null);
17715
17917
  const [sitePages, setSitePages] = useState14([]);
17716
17918
  const [sectionsByPath, setSectionsByPath] = useState14({});
17717
17919
  const sectionsPrefetchGenRef = useRef10(0);
@@ -17720,7 +17922,18 @@ function OhhwellsBridge() {
17720
17922
  const linkPopoverOpenRef = useRef10(false);
17721
17923
  const linkPopoverGraceUntilRef = useRef10(0);
17722
17924
  setLinkPopoverRef.current = setLinkPopover;
17925
+ setFloatingPanelRef.current = setFloatingPanel;
17723
17926
  linkPopoverSessionRef.current = linkPopover;
17927
+ floatingPanelOpenRef.current = Boolean(floatingPanel);
17928
+ useEffect13(() => {
17929
+ const syncViewport = () => {
17930
+ const next = window.innerWidth <= 480 ? "mobile" : "desktop";
17931
+ setEditorViewport((prev) => prev === next ? prev : next);
17932
+ };
17933
+ syncViewport();
17934
+ window.addEventListener("resize", syncViewport);
17935
+ return () => window.removeEventListener("resize", syncViewport);
17936
+ }, []);
17724
17937
  const {
17725
17938
  navDragRef,
17726
17939
  navPointerDragRef,
@@ -19056,6 +19269,7 @@ function OhhwellsBridge() {
19056
19269
  }
19057
19270
  if (typeof content[STYLE_STORE_KEY] === "string") {
19058
19271
  stylesRef.current = content[STYLE_STORE_KEY];
19272
+ document.querySelectorAll('[data-ohw-editable="form"]').forEach((f) => markFormFields(f));
19059
19273
  applyStylesToDom(parseStyleStore(content[STYLE_STORE_KEY]));
19060
19274
  }
19061
19275
  applyBrandChrome(content);
@@ -19063,11 +19277,11 @@ function OhhwellsBridge() {
19063
19277
  for (const [key, val] of Object.entries(content)) {
19064
19278
  if (key === "__ohw_sections") continue;
19065
19279
  if (key === AI_SECTIONS_KEY) continue;
19280
+ if (key === LOGO_PLACEHOLDER_KEY) continue;
19281
+ if (LOGO_IMAGE_KEYS.includes(key)) continue;
19066
19282
  if (key === BRAND_KIT_KEY) continue;
19067
19283
  if (key === STYLE_STORE_KEY) continue;
19068
19284
  if (BRAND_CHROME_KEYS.has(key)) continue;
19069
- if (key === LOGO_PLACEHOLDER_KEY) continue;
19070
- if (LOGO_IMAGE_KEYS.includes(key)) continue;
19071
19285
  if (applyVideoSettingNode(key, val)) continue;
19072
19286
  if (applyCarouselNode(key, val)) continue;
19073
19287
  document.querySelectorAll(`[data-ohw-key="${key}"]`).forEach((el) => {
@@ -19138,7 +19352,9 @@ function OhhwellsBridge() {
19138
19352
  let cancelled = false;
19139
19353
  setFetchState("loading");
19140
19354
  const apiUrl = process.env.NEXT_PUBLIC_FLOWOPS_API_URL ?? "https://flowops-backend-staging-2yfdh7pwpq-as.a.run.app";
19141
- fetch(`${apiUrl}/api/public/sites/${subdomain}/content`, { cache: "no-store" }).then((r2) => r2.ok ? r2.json() : null).then((data) => {
19355
+ const initialPath = pathname;
19356
+ fetchedContentPaths.add(`${subdomain}::${initialPath}`);
19357
+ fetch(`${apiUrl}/api/public/sites/${subdomain}/content?path=${encodeURIComponent(initialPath)}`, { cache: "no-store" }).then((r2) => r2.ok ? r2.json() : null).then((data) => {
19142
19358
  if (cancelled) return;
19143
19359
  const content = data?.content ?? {};
19144
19360
  const branding = Boolean(data?.showBranding);
@@ -19247,7 +19463,9 @@ function OhhwellsBridge() {
19247
19463
  }, [isEditMode]);
19248
19464
  useEffect13(() => {
19249
19465
  if (isEditMode || fetchState !== "done") return;
19466
+ console.log("env", process.env.NEXT_PUBLIC_FLOWOPS_API_URL);
19250
19467
  const apiUrl = process.env.NEXT_PUBLIC_FLOWOPS_API_URL ?? "https://flowops-backend-staging-2yfdh7pwpq-as.a.run.app";
19468
+ console.log({ apiUrl, subdomain });
19251
19469
  bindPublishedForms(apiUrl, subdomain, contentCache.get(subdomain) ?? {});
19252
19470
  }, [isEditMode, fetchState, subdomain]);
19253
19471
  useEffect13(() => {
@@ -19272,16 +19490,17 @@ function OhhwellsBridge() {
19272
19490
  applyAiSectionsToDom(parseAiSectionsState(content[AI_SECTIONS_KEY]));
19273
19491
  }
19274
19492
  if (typeof content[STYLE_STORE_KEY] === "string") {
19493
+ document.querySelectorAll('[data-ohw-editable="form"]').forEach((f) => markFormFields(f));
19275
19494
  applyStylesToDom(parseStyleStore(content[STYLE_STORE_KEY]));
19276
19495
  }
19277
19496
  for (const [key, val] of Object.entries(content)) {
19278
19497
  if (key === "__ohw_sections") continue;
19279
19498
  if (key === AI_SECTIONS_KEY) continue;
19499
+ if (key === LOGO_PLACEHOLDER_KEY) continue;
19500
+ if (LOGO_IMAGE_KEYS.includes(key)) continue;
19280
19501
  if (key === BRAND_KIT_KEY) continue;
19281
19502
  if (key === STYLE_STORE_KEY) continue;
19282
19503
  if (BRAND_CHROME_KEYS.has(key)) continue;
19283
- if (key === LOGO_PLACEHOLDER_KEY) continue;
19284
- if (LOGO_IMAGE_KEYS.includes(key)) continue;
19285
19504
  if (applyVideoSettingNode(key, val)) continue;
19286
19505
  if (applyCarouselNode(key, val)) continue;
19287
19506
  document.querySelectorAll(`[data-ohw-key="${key}"]`).forEach((el) => {
@@ -19332,6 +19551,17 @@ function OhhwellsBridge() {
19332
19551
  debounceTimer = setTimeout(applyFromCache, 150);
19333
19552
  };
19334
19553
  applyFromCache();
19554
+ const pathCacheKey = `${subdomain}::${pathname}`;
19555
+ if (!fetchedContentPaths.has(pathCacheKey)) {
19556
+ fetchedContentPaths.add(pathCacheKey);
19557
+ const apiUrl = process.env.NEXT_PUBLIC_FLOWOPS_API_URL ?? "https://flowops-backend-staging-2yfdh7pwpq-as.a.run.app";
19558
+ fetch(`${apiUrl}/api/public/sites/${subdomain}/content?path=${encodeURIComponent(pathname)}`, { cache: "no-store" }).then((r2) => r2.ok ? r2.json() : null).then((data) => {
19559
+ if (!data?.content) return;
19560
+ contentCache.set(subdomain, data.content);
19561
+ applyFromCache();
19562
+ }).catch(() => {
19563
+ });
19564
+ }
19335
19565
  observer = new MutationObserver(scheduleApply);
19336
19566
  observer.observe(document.body, { childList: true, subtree: true });
19337
19567
  return () => {
@@ -19376,6 +19606,17 @@ function OhhwellsBridge() {
19376
19606
  if (!isEditMode) return;
19377
19607
  initSectionInstancesFromContent(editContentRef.current, pathname);
19378
19608
  }, [pathname, isEditMode]);
19609
+ useEffect13(() => {
19610
+ if (!isEditMode) return;
19611
+ const observer = new MutationObserver(() => {
19612
+ const drawer = document.querySelector("[data-ohw-nav-drawer]");
19613
+ if (!drawer || drawer === appliedDrawerRef.current) return;
19614
+ appliedDrawerRef.current = drawer;
19615
+ applyStylesToDom(parseStyleStore(stylesRef.current));
19616
+ });
19617
+ observer.observe(document.body, { childList: true, subtree: true });
19618
+ return () => observer.disconnect();
19619
+ }, [isEditMode, pathname]);
19379
19620
  useEffect13(() => {
19380
19621
  const contentForNav = () => {
19381
19622
  if (isEditMode) return editContentRef.current;
@@ -19467,26 +19708,11 @@ function OhhwellsBridge() {
19467
19708
  const t2 = setTimeout(measure, 500);
19468
19709
  const ro = new ResizeObserver(schedule);
19469
19710
  ro.observe(document.body);
19470
- let lastWidth = window.innerWidth;
19471
- let resizeTimers = [];
19472
- const clearResizeTimers = () => {
19473
- resizeTimers.forEach(clearTimeout);
19474
- resizeTimers = [];
19475
- };
19476
- const handleResize = () => {
19477
- if (window.innerWidth === lastWidth) return;
19478
- lastWidth = window.innerWidth;
19479
- clearResizeTimers();
19480
- resizeTimers = HEIGHT_SETTLE_DELAYS.map((delay) => setTimeout(measure, delay));
19481
- };
19482
- window.addEventListener("resize", handleResize);
19483
19711
  return () => {
19484
19712
  clearTimeout(t1);
19485
19713
  clearTimeout(t2);
19486
19714
  if (raf != null) cancelAnimationFrame(raf);
19487
19715
  ro.disconnect();
19488
- clearResizeTimers();
19489
- window.removeEventListener("resize", handleResize);
19490
19716
  };
19491
19717
  }, [pathname, isEditMode, postToParent2]);
19492
19718
  useEffect13(() => {
@@ -19596,8 +19822,10 @@ function OhhwellsBridge() {
19596
19822
  crushed its fields together (OHH-490). Not media (image/bg-image/video/icon/map)
19597
19823
  either: those can be their own flex containers centering fallback content (a
19598
19824
  placeholder label, a mascot emoji) \u2014 forcing block broke that centering while the
19599
- image was unset (OHH-758-ish). */
19600
- [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"]) {
19825
+ image was unset (OHH-758-ish). Not inside a container that declares its editables
19826
+ inline (data-ohw-inline-editables): a row of inline links \u2014 a footer's legal line \u2014
19827
+ keeps its line; blockified labels put each link on its own row in the editor only. */
19828
+ [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] *) {
19601
19829
  display: block;
19602
19830
  }
19603
19831
  /* The block-force above is an author rule, so it beats the browser's own
@@ -19667,7 +19895,10 @@ function OhhwellsBridge() {
19667
19895
  flex-wrap: wrap !important;
19668
19896
  align-items: flex-end !important;
19669
19897
  }
19670
- [data-ohw-editable="form"] [data-ohw-form-field] {
19898
+ /* Generated forms are excluded: they are always a single column, so the 12rem
19899
+ flex BASIS \u2014 a minimum width in a horizontal row \u2014 reads as a 12rem HEIGHT
19900
+ there and inflates every field to the tallest one's size. */
19901
+ [data-ohw-editable="form"]:not([data-ai-form]) [data-ohw-form-field] {
19671
19902
  flex: 1 1 12rem !important;
19672
19903
  min-width: min(12rem, 100%) !important;
19673
19904
  max-width: 100% !important;
@@ -19776,9 +20007,6 @@ function OhhwellsBridge() {
19776
20007
  if (target.closest("[data-ohw-state-toggle]")) return;
19777
20008
  if (target.closest("[data-ohw-max-badge]")) return;
19778
20009
  if (isInsideLinkEditor(target)) return;
19779
- if (selectedMediaElRef.current && !selectedMediaElRef.current.contains(target) && !target.closest("[data-ohw-media-overlay], [data-ohw-edit-chrome], [data-ohw-item-interaction]")) {
19780
- clearMediaSelectionRef.current();
19781
- }
19782
20010
  if (isInsideFloatingPanel(target)) return;
19783
20011
  if (target.closest("[data-ohw-form-toolbar]")) return;
19784
20012
  if (target.closest(
@@ -19786,6 +20014,9 @@ function OhhwellsBridge() {
19786
20014
  )) {
19787
20015
  return;
19788
20016
  }
20017
+ if (selectedMediaElRef.current && !selectedMediaElRef.current.contains(target) && !target.closest("[data-ohw-media-overlay], [data-ohw-edit-chrome], [data-ohw-item-interaction]")) {
20018
+ clearMediaSelectionRef.current();
20019
+ }
19789
20020
  {
19790
20021
  const formEl = getFormElement(target);
19791
20022
  const onSuccessText = target.closest(`[${SUCCESS_TEXT_ATTR}]`);
@@ -19949,14 +20180,6 @@ function OhhwellsBridge() {
19949
20180
  }
19950
20181
  const clickedButton = findClosestButtonLike(target);
19951
20182
  const buttonOnMedia = Boolean(clickedButton && isMediaEditable(editable) && editable.contains(clickedButton));
19952
- console.log("[click-debug]", {
19953
- editableType: editable.dataset.ohwEditable,
19954
- editableTag: editable.tagName,
19955
- targetTag: target.tagName,
19956
- clickedButtonTag: clickedButton?.tagName ?? null,
19957
- buttonOnMedia,
19958
- isMediaEditableEditable: isMediaEditable(editable)
19959
- });
19960
20183
  if (isMediaEditable(editable) && !buttonOnMedia) {
19961
20184
  e.preventDefault();
19962
20185
  e.stopPropagation();
@@ -19983,11 +20206,6 @@ function OhhwellsBridge() {
19983
20206
  const hrefLookupTarget = buttonOnMedia ? clickedButton : editable;
19984
20207
  const hrefCtx = getHrefKeyFromElement(hrefLookupTarget);
19985
20208
  const navAnchor = hrefCtx ? getNavigationItemAnchor(hrefCtx.anchor) : null;
19986
- console.log("[click-debug 2]", {
19987
- hrefLookupTargetTag: hrefLookupTarget.tagName,
19988
- hrefCtx: hrefCtx ? { key: hrefCtx.key } : null,
19989
- navAnchorTag: navAnchor?.tagName ?? null
19990
- });
19991
20209
  if (navAnchor) {
19992
20210
  e.preventDefault();
19993
20211
  e.stopPropagation();
@@ -20157,6 +20375,9 @@ function OhhwellsBridge() {
20157
20375
  setHoveredItemRect(null);
20158
20376
  hoveredNavContainerRef.current = null;
20159
20377
  setHoveredNavContainerRect(null);
20378
+ siblingHintElRef.current = null;
20379
+ setSiblingHintRect(null);
20380
+ setSiblingHintRects([]);
20160
20381
  return;
20161
20382
  }
20162
20383
  {
@@ -20277,7 +20498,6 @@ function OhhwellsBridge() {
20277
20498
  hoveredNavContainerRef.current = null;
20278
20499
  setHoveredNavContainerRect(null);
20279
20500
  hoveredItemElRef.current = editable;
20280
- setHoveredItemRect(isSelectedForHoverRef.current(editable) ? null : editable.getBoundingClientRect());
20281
20501
  }
20282
20502
  }
20283
20503
  }
@@ -20574,7 +20794,7 @@ function OhhwellsBridge() {
20574
20794
  }
20575
20795
  };
20576
20796
  const probeImageAt = (clientX, clientY, isDragOver = false, fromParentViewport = false) => {
20577
- if (linkPopoverOpenRef.current) {
20797
+ if (linkPopoverOpenRef.current || floatingPanelOpenRef.current && isPointOverFloatingPanel(clientX, clientY)) {
20578
20798
  if (hoveredImageRef.current) {
20579
20799
  hoveredImageRef.current = null;
20580
20800
  hoveredImageHasTextOverlapRef.current = false;
@@ -20947,8 +21167,7 @@ function OhhwellsBridge() {
20947
21167
  };
20948
21168
  const handleMouseMove = (e) => {
20949
21169
  const { clientX, clientY } = e;
20950
- if (pointOwnedByFloatingPanel(clientX, clientY)) return;
20951
- if (isOverEditorChrome(clientX, clientY)) {
21170
+ if (document.documentElement.hasAttribute("data-ohw-panel-dragging") || floatingPanelOpenRef.current && isPointOverFloatingPanel(clientX, clientY) || isOverEditorChrome(clientX, clientY)) {
20952
21171
  document.querySelectorAll("[data-ohw-hovered]").forEach((el) => el.removeAttribute("data-ohw-hovered"));
20953
21172
  formHoverElRef.current = null;
20954
21173
  setFormHoverRect(null);
@@ -20956,7 +21175,12 @@ function OhhwellsBridge() {
20956
21175
  setHoveredItemRect(null);
20957
21176
  hoveredNavContainerRef.current = null;
20958
21177
  setHoveredNavContainerRect(null);
21178
+ siblingHintElRef.current = null;
21179
+ setSiblingHintRect(null);
21180
+ setSiblingHintRects([]);
20959
21181
  dismissImageHover();
21182
+ clearImageHover();
21183
+ setSectionGap(null);
20960
21184
  return;
20961
21185
  }
20962
21186
  if (probeSocialsRowAt(clientX, clientY)) return;
@@ -20968,7 +21192,11 @@ function OhhwellsBridge() {
20968
21192
  if (e.data?.type !== "ow:pointer-sync") return;
20969
21193
  const { clientX, clientY } = e.data;
20970
21194
  if (typeof clientX !== "number" || typeof clientY !== "number") return;
20971
- if (pointOwnedByFloatingPanel(clientX, clientY)) return;
21195
+ if (document.documentElement.hasAttribute("data-ohw-panel-dragging") || floatingPanelOpenRef.current && isPointOverFloatingPanel(clientX, clientY)) {
21196
+ dismissImageHover();
21197
+ clearImageHover();
21198
+ return;
21199
+ }
20972
21200
  if (probeSocialsRowAt(clientX, clientY)) return;
20973
21201
  probeSectionGapAt(clientX, clientY);
20974
21202
  probeImageAt(clientX, clientY);
@@ -21307,6 +21535,7 @@ function OhhwellsBridge() {
21307
21535
  }
21308
21536
  if (typeof content[STYLE_STORE_KEY] === "string") {
21309
21537
  stylesRef.current = content[STYLE_STORE_KEY];
21538
+ document.querySelectorAll('[data-ohw-editable="form"]').forEach((f) => markFormFields(f));
21310
21539
  applyStylesToDom(parseStyleStore(content[STYLE_STORE_KEY]));
21311
21540
  }
21312
21541
  applyBrandChrome(content);
@@ -21318,11 +21547,11 @@ function OhhwellsBridge() {
21318
21547
  continue;
21319
21548
  }
21320
21549
  if (key === AI_SECTIONS_KEY) continue;
21550
+ if (key === LOGO_PLACEHOLDER_KEY) continue;
21551
+ if (LOGO_IMAGE_KEYS.includes(key) && !String(val ?? "").trim()) continue;
21321
21552
  if (key === BRAND_KIT_KEY) continue;
21322
21553
  if (key === STYLE_STORE_KEY) continue;
21323
21554
  if (BRAND_CHROME_KEYS.has(key)) continue;
21324
- if (key === LOGO_PLACEHOLDER_KEY) continue;
21325
- if (LOGO_IMAGE_KEYS.includes(key) && !String(val ?? "").trim()) continue;
21326
21555
  if (applyVideoSettingNode(key, val)) continue;
21327
21556
  if (applyCarouselNode(key, val)) continue;
21328
21557
  document.querySelectorAll(`[data-ohw-key="${key}"]`).forEach((el) => {
@@ -21340,6 +21569,7 @@ function OhhwellsBridge() {
21340
21569
  applyMapQuery(el, val);
21341
21570
  } else if (el.dataset.ohwEditable === "icon") {
21342
21571
  applyIconMarkup(el, val);
21572
+ } else if (el.dataset.ohwEditable === "form") {
21343
21573
  } else if (isIconMarkupValue(val)) {
21344
21574
  } else {
21345
21575
  el.innerHTML = val;
@@ -21349,6 +21579,9 @@ function OhhwellsBridge() {
21349
21579
  }
21350
21580
  applyLogoFromContent(content);
21351
21581
  applyLogoSizes(content);
21582
+ document.querySelectorAll('[data-ohw-editable="form"]').forEach((form) => {
21583
+ reconcileFieldsFromContent(form, content);
21584
+ });
21352
21585
  if (sectionsJson) {
21353
21586
  initSectionsFromContent({ __ohw_sections: sectionsJson }, true);
21354
21587
  sectionsLoadedRef.current = true;
@@ -21543,10 +21776,26 @@ function OhhwellsBridge() {
21543
21776
  const handleGetBrand = (e) => {
21544
21777
  if (e.data?.type !== "ow:get-brand") return;
21545
21778
  const template = deriveTemplateBrand();
21546
- const value = brandKitRef.current || (template ? JSON.stringify(template) : "");
21779
+ const fallback = template ?? (() => {
21780
+ const fonts = deriveTemplateFonts();
21781
+ return fonts ? { palette: AI_DEFAULT_BRAND.palette, fonts } : null;
21782
+ })();
21783
+ const value = brandKitRef.current || (fallback ? JSON.stringify(fallback) : "");
21547
21784
  postToParentRef.current({ type: "ow:brand-value", value });
21548
21785
  };
21549
21786
  window.addEventListener("message", handleGetBrand);
21787
+ const handleGetTemplateFonts = (e) => {
21788
+ if (e.data?.type !== "ow:get-template-fonts") return;
21789
+ const fonts = deriveTemplateFonts();
21790
+ postToParentRef.current({ type: "ow:template-fonts-value", value: fonts ? JSON.stringify(fonts) : "" });
21791
+ };
21792
+ window.addEventListener("message", handleGetTemplateFonts);
21793
+ const handleGetTemplateBrand = (e) => {
21794
+ if (e.data?.type !== "ow:get-template-brand") return;
21795
+ const brand = deriveTemplateBrand();
21796
+ postToParentRef.current({ type: "ow:template-brand-value", value: brand ? JSON.stringify(brand) : "" });
21797
+ };
21798
+ window.addEventListener("message", handleGetTemplateBrand);
21550
21799
  const handleMoveSection = (e) => {
21551
21800
  if (e.data?.type !== "ow:move-section") return;
21552
21801
  const instanceId = typeof e.data.instanceId === "string" ? e.data.instanceId : "";
@@ -21739,6 +21988,12 @@ function OhhwellsBridge() {
21739
21988
  closeLinkPopoverRef.current();
21740
21989
  return;
21741
21990
  }
21991
+ if (floatingPanelOpenRef.current) {
21992
+ setFloatingPanelRef.current(null);
21993
+ deselectRef.current();
21994
+ deactivateRef.current();
21995
+ return;
21996
+ }
21742
21997
  deselectRef.current();
21743
21998
  deactivateRef.current();
21744
21999
  clearMediaSelectionRef.current();
@@ -22027,8 +22282,12 @@ function OhhwellsBridge() {
22027
22282
  if (inserted) {
22028
22283
  const tracker = getSectionsTracker();
22029
22284
  postToParentRef.current({ type: "ow:change", nodes: [{ key: "__ohw_sections", text: tracker.textContent ?? "[]" }] });
22030
- const h = document.body.scrollHeight;
22031
- if (h > 50) postToParentRef.current({ type: "ow:height", height: h });
22285
+ const reportHeight = () => {
22286
+ const h = document.body.scrollHeight;
22287
+ if (h > 50) postToParentRef.current({ type: "ow:height", height: h });
22288
+ };
22289
+ reportHeight();
22290
+ setTimeout(reportHeight, 500);
22032
22291
  }
22033
22292
  };
22034
22293
  const handleSwitchSchedule = (e) => {
@@ -22449,15 +22708,17 @@ function OhhwellsBridge() {
22449
22708
  window.removeEventListener("message", handleAiSetBrand);
22450
22709
  window.removeEventListener("message", handleAiSetStyles);
22451
22710
  window.removeEventListener("message", handleGetBrand);
22711
+ window.removeEventListener("message", handleGetTemplateFonts);
22712
+ window.removeEventListener("message", handleGetTemplateBrand);
22452
22713
  window.removeEventListener("message", handleMoveSection);
22453
22714
  window.removeEventListener("message", handlePanelDragging);
22454
22715
  window.removeEventListener("message", handleDeleteSection);
22455
22716
  window.removeEventListener("message", handleDuplicateSection);
22456
22717
  window.removeEventListener("message", handleDeactivate);
22457
- document.documentElement.removeAttribute("data-ohw-panel-dragging");
22458
22718
  window.removeEventListener("message", handleToastAction);
22459
22719
  window.removeEventListener("message", handleFormCount);
22460
22720
  window.removeEventListener("message", handleUiEscape);
22721
+ document.documentElement.removeAttribute("data-ohw-panel-dragging");
22461
22722
  autoSaveTimers.current.forEach(clearTimeout);
22462
22723
  autoSaveTimers.current.clear();
22463
22724
  if (imageUnhoverTimerRef.current) clearTimeout(imageUnhoverTimerRef.current);
@@ -22660,7 +22921,7 @@ function OhhwellsBridge() {
22660
22921
  postToParent2({
22661
22922
  type: "ow:ready",
22662
22923
  version: "1",
22663
- bridgeVersion: "0.1.109",
22924
+ bridgeVersion: "0.1.112",
22664
22925
  path: pathname,
22665
22926
  nodes: collectEditableNodes(editContentRef.current),
22666
22927
  sections
@@ -23276,7 +23537,7 @@ function OhhwellsBridge() {
23276
23537
  "span",
23277
23538
  {
23278
23539
  "data-ohw-form-count": "",
23279
- 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",
23540
+ 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",
23280
23541
  children: formPickCount
23281
23542
  }
23282
23543
  )
@@ -23514,11 +23775,11 @@ function OhhwellsBridge() {
23514
23775
  className: "fixed left-0 w-full z-2147483646 flex items-center pointer-events-none",
23515
23776
  style: { top: sectionGap.y, transform: "translateY(-50%)" },
23516
23777
  children: [
23517
- /* @__PURE__ */ jsx33("div", { className: "flex-1 bg-primary", style: { height: 3 } }),
23778
+ /* @__PURE__ */ jsx33("div", { className: "flex-1 bg-bridge-primary", style: { height: 3 } }),
23518
23779
  /* @__PURE__ */ jsx33(
23519
23780
  Badge,
23520
23781
  {
23521
- 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",
23782
+ 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",
23522
23783
  onClick: () => {
23523
23784
  window.parent.postMessage(
23524
23785
  {
@@ -23532,7 +23793,7 @@ function OhhwellsBridge() {
23532
23793
  children: "Add Section"
23533
23794
  }
23534
23795
  ),
23535
- /* @__PURE__ */ jsx33("div", { className: "flex-1 bg-primary", style: { height: 3 } })
23796
+ /* @__PURE__ */ jsx33("div", { className: "flex-1 bg-bridge-primary", style: { height: 3 } })
23536
23797
  ]
23537
23798
  }
23538
23799
  ),
@@ -23580,6 +23841,59 @@ function OhhwellsBridge() {
23580
23841
  ) : null
23581
23842
  ] });
23582
23843
  }
23844
+
23845
+ // src/ui/EmptySection.tsx
23846
+ import Link3 from "next/link";
23847
+ import { Fragment as Fragment9, jsx as jsx34, jsxs as jsxs21 } from "react/jsx-runtime";
23848
+ function EmptySection({ title, homeHref = "/", eyebrowKey, titleKey, subtitleKey }) {
23849
+ return /* @__PURE__ */ jsxs21(Fragment9, { children: [
23850
+ /* @__PURE__ */ jsx34(
23851
+ "p",
23852
+ {
23853
+ style: {
23854
+ fontFamily: "var(--brand-font-body)",
23855
+ fontSize: "0.75rem",
23856
+ fontWeight: 500,
23857
+ letterSpacing: "0.15em",
23858
+ textTransform: "uppercase",
23859
+ color: "var(--brand-accent)",
23860
+ marginBottom: "1.5rem"
23861
+ },
23862
+ children: /* @__PURE__ */ jsx34(Link3, { href: homeHref, style: { color: "inherit" }, children: /* @__PURE__ */ jsx34("span", { ...eyebrowKey ? { "data-ohw-editable": "text", "data-ohw-key": eyebrowKey } : {}, children: "Home" }) })
23863
+ }
23864
+ ),
23865
+ /* @__PURE__ */ jsx34(
23866
+ "h1",
23867
+ {
23868
+ style: {
23869
+ fontFamily: "var(--brand-font-heading)",
23870
+ fontSize: "clamp(2rem, 3.5vw, 2.75rem)",
23871
+ lineHeight: 1.1,
23872
+ letterSpacing: "-0.025em",
23873
+ color: "var(--brand-text)",
23874
+ marginBottom: "1rem"
23875
+ },
23876
+ ...titleKey ? { "data-ohw-editable": "text", "data-ohw-key": titleKey, "data-ohw-max-length": 80 } : {},
23877
+ children: title
23878
+ }
23879
+ ),
23880
+ /* @__PURE__ */ jsx34(
23881
+ "p",
23882
+ {
23883
+ style: {
23884
+ fontFamily: "var(--brand-font-body)",
23885
+ fontSize: "1rem",
23886
+ lineHeight: 1.7,
23887
+ fontWeight: 300,
23888
+ color: "var(--brand-text-muted)",
23889
+ maxWidth: "340px"
23890
+ },
23891
+ ...subtitleKey ? { "data-ohw-editable": "text", "data-ohw-key": subtitleKey, "data-ohw-max-length": 160 } : {},
23892
+ children: "This page doesn't have any content yet."
23893
+ }
23894
+ )
23895
+ ] });
23896
+ }
23583
23897
  export {
23584
23898
  AI_DEFAULT_BRAND,
23585
23899
  AI_TREE_SCHEMA_VERSIONS,
@@ -23596,6 +23910,7 @@ export {
23596
23910
  DropdownMenuItem,
23597
23911
  DropdownMenuSeparator,
23598
23912
  DropdownMenuTrigger,
23913
+ EmptySection,
23599
23914
  ItemActionToolbar,
23600
23915
  ItemInteractionLayer,
23601
23916
  LinkEditorPanel,