@ohhwells/bridge 0.1.138 → 0.1.139-next.374

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -2919,14 +2919,22 @@ function isInactiveFooterChrome(el) {
2919
2919
  if (footer && footer.style.display === "none") return true;
2920
2920
  return false;
2921
2921
  }
2922
+ function isIncidentalFooter(el) {
2923
+ return Boolean(el.closest("blockquote, article, figure"));
2924
+ }
2922
2925
  function getFooterRoot() {
2923
2926
  const ai = document.querySelector("footer[data-ai-footer]");
2924
2927
  if (ai) return ai;
2925
2928
  for (const el of Array.from(
2926
- document.querySelectorAll('footer[data-ohw-section="footer"], footer')
2929
+ document.querySelectorAll('footer[data-ohw-section="footer"]')
2927
2930
  )) {
2931
+ if (isInactiveFooterChrome(el)) continue;
2932
+ return el;
2933
+ }
2934
+ for (const el of Array.from(document.querySelectorAll("footer"))) {
2928
2935
  if (el.hasAttribute("data-ai-footer")) continue;
2929
2936
  if (isInactiveFooterChrome(el)) continue;
2937
+ if (isIncidentalFooter(el)) continue;
2930
2938
  return el;
2931
2939
  }
2932
2940
  return null;
@@ -3337,8 +3345,8 @@ function buildFooterLink(colIndex, itemIndex, href, label, template) {
3337
3345
  return anchor;
3338
3346
  }
3339
3347
  function footerColumnExistsInDom(colIndex) {
3340
- const root = getFooterRoot() ?? document;
3341
- return root.querySelector(`[data-ohw-footer-col="${colIndex}"]`) !== null || root.querySelector(`[data-ohw-href-key^="footer-${colIndex}-"]`) !== null || root.querySelector(`[data-ohw-key="footer-${colIndex}-heading"]`) !== null;
3348
+ const scope = getFooterLinksContainer() ?? getFooterRoot() ?? document;
3349
+ return scope.querySelector(`[data-ohw-footer-col="${colIndex}"]`) !== null || scope.querySelector(`[data-ohw-href-key^="footer-${colIndex}-"]`) !== null || scope.querySelector(`[data-ohw-key="footer-${colIndex}-heading"]`) !== null;
3342
3350
  }
3343
3351
  function getFooterLinkTemplate() {
3344
3352
  for (const col of listFooterColumns()) {
@@ -6355,6 +6363,20 @@ function applyBrandChrome(content) {
6355
6363
 
6356
6364
  // src/lib/brand-kit.ts
6357
6365
  var BRAND_KIT_KEY = "__ohw_brand";
6366
+ var appliedPalette = null;
6367
+ var appliedFonts = null;
6368
+ function getAppliedBrand() {
6369
+ if (!appliedPalette) return null;
6370
+ return appliedFonts ? { palette: appliedPalette, fonts: appliedFonts } : { palette: appliedPalette };
6371
+ }
6372
+ function brandForGenerated(fallback) {
6373
+ const applied = getAppliedBrand();
6374
+ if (!applied) return fallback ?? void 0;
6375
+ return {
6376
+ palette: applied.palette,
6377
+ fonts: applied.fonts ?? fallback?.fonts ?? AI_DEFAULT_BRAND.fonts
6378
+ };
6379
+ }
6358
6380
  var BRAND_VAR_PREFIX = "--ohw-brand-";
6359
6381
  var BRAND_VAR_NAMES = ["primary", "accent", "light", "dark", "surface", "border", "muted"].map(
6360
6382
  (role) => `${BRAND_VAR_PREFIX}${role}`
@@ -6658,6 +6680,8 @@ function loadCustomFonts(fonts) {
6658
6680
  function applyBrandToDom(kit) {
6659
6681
  const root = document.documentElement;
6660
6682
  if (!kit) {
6683
+ appliedPalette = null;
6684
+ appliedFonts = null;
6661
6685
  for (const name of [...BRAND_VAR_NAMES, ...LEGACY_BRAND_VAR_NAMES, ...BUTTON_VAR_NAMES]) {
6662
6686
  root.style.removeProperty(name);
6663
6687
  }
@@ -6671,11 +6695,16 @@ function applyBrandToDom(kit) {
6671
6695
  const applyFonts = kit.touched ? kit.touched.fonts === true : true;
6672
6696
  if (kit.palette) {
6673
6697
  if (applyPalette) {
6698
+ appliedPalette = kit.palette;
6674
6699
  for (const [name, value] of Object.entries(brandColorVars(kit.palette))) root.style.setProperty(name, value);
6675
6700
  } else {
6701
+ appliedPalette = null;
6676
6702
  for (const name of [...BRAND_VAR_NAMES, ...LEGACY_BRAND_VAR_NAMES]) root.style.removeProperty(name);
6677
6703
  }
6678
6704
  }
6705
+ if (kit.fonts) {
6706
+ appliedFonts = applyFonts ? kit.fonts : null;
6707
+ }
6679
6708
  if (kit.buttons) {
6680
6709
  for (const name of BUTTON_VAR_NAMES) root.style.removeProperty(name);
6681
6710
  for (const [name, value] of Object.entries(buttonStyleVars({ ...kit, buttons: kit.buttons }))) root.style.setProperty(name, value);
@@ -7208,6 +7237,35 @@ function withThemeFonts(brand) {
7208
7237
  }
7209
7238
  };
7210
7239
  }
7240
+ function liveBrandColor(role, fallback) {
7241
+ if (role === "dark") {
7242
+ return `var(--brand-text, var(--ohw-brand-dark, var(--color-dark, ${fallback})))`;
7243
+ }
7244
+ if (role === "light") {
7245
+ return `var(--brand-background, var(--ohw-brand-light, var(--color-light, ${fallback})))`;
7246
+ }
7247
+ return `var(--brand-${role}, var(--ohw-brand-${role}, var(--color-${role}, ${fallback})))`;
7248
+ }
7249
+ function withLivePalette(brand, source) {
7250
+ const sourceOf = (color) => {
7251
+ const value = color.trim().toLowerCase();
7252
+ const entries = Object.entries(source.palette);
7253
+ return entries.find(([, hex]) => hex.trim().toLowerCase() === value)?.[0] ?? null;
7254
+ };
7255
+ const map = (color) => {
7256
+ const role = sourceOf(color);
7257
+ return role ? liveBrandColor(role, source.palette[role]) : color;
7258
+ };
7259
+ return {
7260
+ ...brand,
7261
+ palette: {
7262
+ dark: map(brand.palette.dark),
7263
+ primary: map(brand.palette.primary),
7264
+ accent: map(brand.palette.accent),
7265
+ light: map(brand.palette.light)
7266
+ }
7267
+ };
7268
+ }
7211
7269
  function footerLogoStyle(fallbackFont, fallbackColor) {
7212
7270
  return {
7213
7271
  fontFamily: `var(--brand-font-heading, var(--font-heading, var(--font-display, ${quotedFont(fallbackFont)})))`,
@@ -7279,7 +7337,7 @@ var AI_MOBILE_CSS = [
7279
7337
  // page's first section. Top only (bottom needs no nav clearance); ~96px approximates the
7280
7338
  // template nav's own height (measured ~94px) — there's no shared `--ohw-header-h` var to read
7281
7339
  // an exact figure from (no live measurement exists), so this is a safe approximation, not a
7282
- // pixel-exact fit. Horizontal spacing already comes from `data-ai-container`.
7340
+ // pixel-exact fit. Horizontal spacing already comes from `data-ai-section-inner`.
7283
7341
  '[data-ai-overlay-clearance] [data-ai-overlap-layer="overlay"]{padding-top:96px}',
7284
7342
  // A full-bleed carousel's height is pure arithmetic: it spans the whole window, so 16/9 turns
7285
7343
  // window width straight into height. Measured at 16/9 it came out ~90% of the viewport at EVERY
@@ -7295,8 +7353,10 @@ var AI_MOBILE_CSS = [
7295
7353
  "[data-ai-section]{overflow-x:hidden}",
7296
7354
  // `:not([data-ai-full-bleed])`: this `!important` gutter beat the inline `padding: 0` that a
7297
7355
  // full-bleed section's container sets, so a carousel/media hero that is edge-to-edge on desktop
7298
- // still got a 20px band of section background down both sides on a phone.
7299
- "[data-ai-container]:not([data-ai-full-bleed]){padding:0 20px !important}",
7356
+ // still got a 20px band of section background down both sides on a phone. The footer's own
7357
+ // container keeps the unguarded rule below — `data-ai-full-bleed` can never appear on it.
7358
+ "[data-ai-section-inner]:not([data-ai-full-bleed]){padding:0 20px !important}",
7359
+ "[data-ai-container]{padding:0 20px !important}",
7300
7360
  "[data-ai-row]{display:flex !important;flex-direction:column !important;align-items:stretch !important}",
7301
7361
  "[data-ai-cell]{width:100%;min-width:0}",
7302
7362
  // The rule above turns every row into a COLUMN flex container on phones, which silently breaks
@@ -7425,7 +7485,7 @@ function hexContrast(a, b) {
7425
7485
  function primaryButtonLabel(brand) {
7426
7486
  const darkC = hexContrast(brand.palette.primary, brand.palette.dark);
7427
7487
  const lightC = hexContrast(brand.palette.primary, AI_TREE_TOKENS.textPrimaryForeground);
7428
- if (darkC === null || lightC === null) return AI_TREE_TOKENS.textPrimaryForeground;
7488
+ if (darkC === null || lightC === null) return `var(--brand-on-primary, ${AI_TREE_TOKENS.textPrimaryForeground})`;
7429
7489
  return darkC > lightC ? brand.palette.dark : AI_TREE_TOKENS.textPrimaryForeground;
7430
7490
  }
7431
7491
  function accentBandContext(brand) {
@@ -7449,7 +7509,11 @@ function readableForeground(bg, brand) {
7449
7509
  return darkC > lightC ? brand.palette.dark : AI_TREE_TOKENS.textPrimaryForeground;
7450
7510
  }
7451
7511
  function hexWithAlpha(hex, alpha) {
7452
- const m = /^#([0-9a-f]{6})$/i.exec(hex.trim());
7512
+ const value = hex.trim();
7513
+ if (value.startsWith("var(") || value.startsWith("color-mix(")) {
7514
+ return `color-mix(in srgb, ${value} ${Math.round(alpha * 100)}%, transparent)`;
7515
+ }
7516
+ const m = /^#([0-9a-f]{6})$/i.exec(value);
7453
7517
  if (!m) return hex;
7454
7518
  const r2 = parseInt(m[1].slice(0, 2), 16);
7455
7519
  const g = parseInt(m[1].slice(2, 4), 16);
@@ -7497,6 +7561,31 @@ function pricingCardEmphasis(featured, ctx, hasBg) {
7497
7561
  function textAttrs(ctx, path) {
7498
7562
  return ctx.keyFor ? { "data-ohw-key": ctx.keyFor(path), "data-ohw-editable": "text" } : {};
7499
7563
  }
7564
+ var AI_RESPONSIVE_CSS = [
7565
+ "@media (max-width: 960px) {",
7566
+ " [data-ai-responsive] [data-ai-grid] { grid-template-columns: repeat(2, 1fr) !important; }",
7567
+ ' [data-ai-responsive] [data-ai-grid="1"] { grid-template-columns: 1fr !important; }',
7568
+ "}",
7569
+ "@media (max-width: 640px) {",
7570
+ " [data-ai-responsive] [data-ai-grid] { grid-template-columns: 1fr !important; }",
7571
+ " [data-ai-responsive] [data-ai-columns] > * { grid-column: 1 / -1 !important; }",
7572
+ // Group containers flatten to a column on phones; span placements come along for free.
7573
+ // `:not([data-ai-group="overlap"])` for the same reason as the twin pair in AI_MOBILE_CSS — see
7574
+ // the comment there. This sheet's copy is the narrower selector, so without the exemption it
7575
+ // flattens an `overlap` hero below 640px even though the other rule stands down: measured a 390px
7576
+ // viewport with the heading/CTA in flow *under* the image, 219px of section background where the
7577
+ // layered text used to be.
7578
+ ' [data-ai-responsive] [data-ai-group]:not([data-ai-group="overlap"]) { display: flex !important; flex-direction: column !important; }',
7579
+ ' [data-ai-responsive] [data-ai-group]:not([data-ai-group="overlap"]) > * { grid-column: auto !important; }',
7580
+ // `:not([data-ai-full-bleed])` for the same reason as the twin rule in AI_MOBILE_CSS: this one
7581
+ // is narrower (0,2,0 vs 0,1,0) and also `!important`, so without the guard it wins outright and
7582
+ // a full-bleed hero gets its 20px side band back below 640px only — the one width where the
7583
+ // other rule had already been taught to stand down.
7584
+ " [data-ai-responsive] [data-ai-section-inner]:not([data-ai-full-bleed]) { padding-left: 20px !important; padding-right: 20px !important; }",
7585
+ " [data-ai-responsive] { overflow-x: hidden; }",
7586
+ " [data-ai-responsive] img { max-width: 100%; }",
7587
+ "}"
7588
+ ].join("\n");
7500
7589
  var TRANSPARENT_PX = "data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7";
7501
7590
  function MediaBox({
7502
7591
  refValue,
@@ -7517,9 +7606,9 @@ function MediaBox({
7517
7606
  const url = refValue ? ctx.resolveMedia(refValue) : null;
7518
7607
  const isIcon = /^(lucide|simple):/.test(refValue);
7519
7608
  const aspectRatio = aspect && /^\d+:\d+$/.test(aspect) ? aspect.replace(":", " / ") : void 0;
7520
- const editAttrs = ctx.keyFor && editPath && !isIcon ? {
7609
+ const editAttrs = ctx.keyFor && editPath ? {
7521
7610
  "data-ohw-key": ctx.keyFor(editPath),
7522
- ...genericImageEdit ? { "data-ohw-editable": "image" } : {}
7611
+ ...isIcon ? { "data-ohw-editable": "icon" } : genericImageEdit ? { "data-ohw-editable": "image" } : {}
7523
7612
  } : {};
7524
7613
  if (isIcon) {
7525
7614
  const Icon = refValue.startsWith("lucide:") ? lucideByName(refValue.slice(7)) : null;
@@ -7527,6 +7616,7 @@ function MediaBox({
7527
7616
  "span",
7528
7617
  {
7529
7618
  "data-ai-icon": refValue,
7619
+ ...editAttrs,
7530
7620
  style: {
7531
7621
  display: "inline-flex",
7532
7622
  width: 48,
@@ -7722,12 +7812,10 @@ function ButtonEl({
7722
7812
  cursor: "pointer",
7723
7813
  whiteSpace: "nowrap",
7724
7814
  ...buttonShellStyle(ctx, variant, fullWidth),
7725
- ...tokenColorStyle ?? (secondary ? { border: `1px solid ${secondaryColor ?? ctx.brand.palette.dark}`, color: secondaryColor ?? ctx.brand.palette.dark } : (
7726
- // Off-band, prefer the template button's measured fill/label pair — a template may
7727
- // fill its CTAs with any token (hvac: accent bg, primary text). On an accent band
7728
- // the flipped palette keeps contrast, so the brand-driven colours stay.
7729
- !ctx.buttonLabel && ctx.buttonStyle?.background ? { background: ctx.buttonStyle.background, color: ctx.buttonStyle.color } : { background: ctx.brand.palette.primary, color: ctx.buttonLabel ?? primaryButtonLabel(ctx.brand) }
7730
- ))
7815
+ ...tokenColorStyle ?? (secondary ? { border: `1px solid ${secondaryColor ?? ctx.brand.palette.dark}`, color: secondaryColor ?? ctx.brand.palette.dark } : {
7816
+ background: ctx.brand.palette.primary,
7817
+ color: ctx.buttonLabel ?? primaryButtonLabel(ctx.brand)
7818
+ })
7731
7819
  },
7732
7820
  children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { ...path ? textAttrs(ctx, `${path}.label`) : {}, children: str(slots.label) })
7733
7821
  }
@@ -9034,7 +9122,7 @@ function CollectionBlock({ node, ctx, path }) {
9034
9122
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
9035
9123
  "div",
9036
9124
  {
9037
- "data-ai-grid": "",
9125
+ "data-ai-grid": String(itemsPerRow),
9038
9126
  "data-ohw-pricing-collection": isPricing ? "" : void 0,
9039
9127
  style: {
9040
9128
  display: "grid",
@@ -9294,7 +9382,7 @@ function renderNode(node, ctx, path, ambientAlign) {
9294
9382
  },
9295
9383
  children: [
9296
9384
  i > 0 && ctx.heroOverlapFullBleed ? (
9297
- // The section's `data-ai-container` gave up its max-width/gutter so the base
9385
+ // The section's `data-ai-section-inner` gave up its max-width/gutter so the base
9298
9386
  // layer (this group's i===0 sibling) can span edge-to-edge — reintroduced here,
9299
9387
  // scoped to just the overlay text, so a heading/copy/CTA stack doesn't stretch
9300
9388
  // the full section width instead of reading at the usual content width.
@@ -9572,10 +9660,10 @@ function renderNode(node, ctx, path, ambientAlign) {
9572
9660
  ...buttonShellStyle(ctx, "primary"),
9573
9661
  // Brand-styled: the template button's measured fill/label pair off-band, else
9574
9662
  // primary fill with a brand-derived label so it reads on custom palettes.
9575
- ...buttonColorStyle(ctx, "primary") ?? (!ctx.buttonLabel && ctx.buttonStyle?.background ? { background: ctx.buttonStyle.background, color: ctx.buttonStyle.color } : {
9663
+ ...buttonColorStyle(ctx, "primary") ?? {
9576
9664
  background: ctx.brand.palette.primary,
9577
9665
  color: ctx.buttonLabel ?? primaryButtonLabel(ctx.brand)
9578
- })
9666
+ }
9579
9667
  },
9580
9668
  children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { ...textAttrs(ctx, `${path}.c${i}.label`), children: str(cs.label) })
9581
9669
  },
@@ -9619,7 +9707,7 @@ function AiTreeRenderer({
9619
9707
  }
9620
9708
  const resolvedBrand = withThemeFonts(brand ?? AI_DEFAULT_BRAND);
9621
9709
  const band = (tree.settings ?? {}).sectionBackground === "accent" ? accentBandContext(resolvedBrand) : null;
9622
- const blockBrand = band?.brand ?? resolvedBrand;
9710
+ const blockBrand = withLivePalette(band?.brand ?? resolvedBrand, resolvedBrand);
9623
9711
  const placeholderMap = buildPlaceholderMap(tree);
9624
9712
  const hasOverlapBackground = tree.scaffold === "overlay" && tree.rows.some(
9625
9713
  (row) => (row.blocks ?? []).some((b) => b.type === "group" && str(b.slots?.layout) === "overlap")
@@ -9635,7 +9723,7 @@ function AiTreeRenderer({
9635
9723
  // standalone resolve), so generated galleries, image rows, and overlay backgrounds arrive with
9636
9724
  // photos instead of grey boxes.
9637
9725
  resolveMedia: (ref) => resolveMedia?.(ref) ?? placeholderMap[ref] ?? resolvePlaceholderRef(ref),
9638
- cardSurface: blockBrand.palette.light.toUpperCase() === AI_DEFAULT_BRAND.palette.light.toUpperCase() ? "#ECECEB" : `color-mix(in srgb, ${blockBrand.palette.light} 90%, ${blockBrand.palette.dark})`,
9726
+ cardSurface: resolvedBrand.palette.light.toUpperCase() === AI_DEFAULT_BRAND.palette.light.toUpperCase() ? "#ECECEB" : `color-mix(in srgb, ${blockBrand.palette.light} 90%, ${blockBrand.palette.dark})`,
9639
9727
  keyFor: editKeyPrefix ? (path) => `${editKeyPrefix}.${path}` : null,
9640
9728
  sectionAlignment: (tree.settings ?? {}).alignment === "center" ? "center" : "left",
9641
9729
  buttonStyle,
@@ -9652,7 +9740,7 @@ function AiTreeRenderer({
9652
9740
  const backgroundUrl = isOverlay && settings.backgroundMedia ? ctx.resolveMedia(settings.backgroundMedia) : null;
9653
9741
  const bgAttrs = ctx.keyFor && isOverlay && settings.backgroundMedia ? { "data-ohw-key": ctx.keyFor("settings.backgroundMedia"), "data-ohw-editable": "bg-image" } : {};
9654
9742
  const toneBackground = (() => {
9655
- const { dark, primary, light } = resolvedBrand.palette;
9743
+ const { dark, primary, light } = blockBrand.palette;
9656
9744
  switch (settings.sectionBackground) {
9657
9745
  case "surface":
9658
9746
  return `color-mix(in srgb, ${light} 94%, ${dark})`;
@@ -9687,6 +9775,7 @@ function AiTreeRenderer({
9687
9775
  ...fillHeight ? { "data-ai-fill-height": "" } : {},
9688
9776
  ...needsOverlayClearance ? { "data-ai-overlay-clearance": "" } : {},
9689
9777
  ...bgAttrs,
9778
+ "data-ai-responsive": "",
9690
9779
  style: {
9691
9780
  position: "relative",
9692
9781
  padding: `${padCss} 0`,
@@ -9700,12 +9789,13 @@ function AiTreeRenderer({
9700
9789
  ...fillHeight ? { display: "grid" } : {}
9701
9790
  },
9702
9791
  children: [
9703
- isOverlay && backgroundUrl && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { position: "absolute", inset: 0, background: `rgba(255,255,255,${overlayAlpha})` } }),
9792
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("style", { children: AI_RESPONSIVE_CSS }),
9704
9793
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("style", { children: AI_MOBILE_CSS }),
9794
+ isOverlay && backgroundUrl && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { position: "absolute", inset: 0, background: `rgba(255,255,255,${overlayAlpha})` } }),
9705
9795
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
9706
9796
  "div",
9707
9797
  {
9708
- "data-ai-container": "",
9798
+ "data-ai-section-inner": "",
9709
9799
  ...fullBleedMedia ? { "data-ai-full-bleed": "" } : {},
9710
9800
  style: {
9711
9801
  position: "relative",
@@ -9722,7 +9812,7 @@ function AiTreeRenderer({
9722
9812
  children: tree.rows.map((row, r2) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
9723
9813
  "div",
9724
9814
  {
9725
- "data-ai-row": "",
9815
+ "data-ai-columns": "",
9726
9816
  style: {
9727
9817
  display: "grid",
9728
9818
  gridTemplateColumns: "repeat(12, 1fr)",
@@ -9773,14 +9863,15 @@ function AiFooterRenderer({
9773
9863
  if (!isRenderableFooterTree(tree)) {
9774
9864
  return null;
9775
9865
  }
9776
- const resolvedBrand = withThemeFonts(brand ?? AI_DEFAULT_BRAND);
9866
+ const hexBrand = withThemeFonts(brand ?? AI_DEFAULT_BRAND);
9867
+ const resolvedBrand = withLivePalette(hexBrand, brand ?? AI_DEFAULT_BRAND);
9777
9868
  const bandBg = surface?.background ?? resolvedBrand.palette.dark;
9778
9869
  const bandText = surface?.text ?? resolvedBrand.palette.light;
9779
9870
  const onDark = surface ? isDarkColor(surface.background) : true;
9780
9871
  const ctx = {
9781
9872
  brand: resolvedBrand,
9782
9873
  resolveMedia: (ref) => resolveMedia?.(ref) ?? resolvePlaceholderRef(ref),
9783
- cardSurface: resolvedBrand.palette.light.toUpperCase() === AI_DEFAULT_BRAND.palette.light.toUpperCase() ? "#ECECEB" : `color-mix(in srgb, ${resolvedBrand.palette.light} 90%, ${resolvedBrand.palette.dark})`,
9874
+ cardSurface: hexBrand.palette.light.toUpperCase() === AI_DEFAULT_BRAND.palette.light.toUpperCase() ? "#ECECEB" : `color-mix(in srgb, ${resolvedBrand.palette.light} 90%, ${resolvedBrand.palette.dark})`,
9784
9875
  keyFor: editKeyPrefix ? (path) => `${editKeyPrefix}.${path}` : null,
9785
9876
  logoUrl: logoUrl ?? null,
9786
9877
  resolvePageHref
@@ -10356,10 +10447,9 @@ function syncSoftRemovedGenerated() {
10356
10447
  }
10357
10448
  function applyAiSectionsToDom(state, options) {
10358
10449
  if (typeof document === "undefined") return;
10359
- const brandOverride = deriveBrandOverride();
10360
10450
  const templateBrand = deriveTemplateBrand();
10361
10451
  const templateButtonStyle = deriveTemplateButtonStyle();
10362
- const brandKey = brandOverride ? JSON.stringify(brandOverride) : "";
10452
+ const brandKey = JSON.stringify(getAppliedBrand() ?? deriveBrandOverride() ?? null);
10363
10453
  const pagePath = window.location.pathname;
10364
10454
  const pageSections = state.sections.filter((entry) => (entry.path ?? "/") === pagePath);
10365
10455
  const activeIds = new Set(pageSections.map((entry) => entry.id));
@@ -10378,9 +10468,10 @@ function applyAiSectionsToDom(state, options) {
10378
10468
  if (existing && existing.serialized === serialized && existing.container.isConnected) {
10379
10469
  continue;
10380
10470
  }
10381
- const isNewMount = !existing || !existing.container.isConnected;
10382
10471
  let container = existing?.container;
10472
+ let isNewMount = false;
10383
10473
  if (!container || !container.isConnected) {
10474
+ isNewMount = true;
10384
10475
  existing?.root.unmount();
10385
10476
  container?.remove();
10386
10477
  container = document.createElement("div");
@@ -10402,7 +10493,7 @@ function applyAiSectionsToDom(state, options) {
10402
10493
  AiTreeRenderer,
10403
10494
  {
10404
10495
  tree: entry.tree,
10405
- brand: brandOverride ?? entry.brand ?? options?.brand ?? templateBrand ?? void 0,
10496
+ brand: brandForGenerated(deriveBrandOverride() ?? entry.brand ?? options?.brand ?? templateBrand),
10406
10497
  buttonStyle: templateButtonStyle,
10407
10498
  resolveMedia,
10408
10499
  editKeyPrefix: `ai.${entry.id}`
@@ -10568,7 +10659,7 @@ function applyAiFooterToDom(state, content) {
10568
10659
  const templateFooter = findTemplateFooter();
10569
10660
  captureTemplateFooterSurface(templateFooter);
10570
10661
  const surface = capturedSurface ?? null;
10571
- const brand = mountOptions.brand ?? deriveTemplateBrand();
10662
+ const brand = brandForGenerated(mountOptions.brand ?? deriveTemplateBrand());
10572
10663
  const logoUrl = resolveLogoFromContent(content, templateFooter);
10573
10664
  const serialized = JSON.stringify(state) + JSON.stringify(brand ?? null) + (logoUrl ?? "") + JSON.stringify(surface) + JSON.stringify(defaultLegal());
10574
10665
  if (mounted2 && mounted2.serialized === serialized && mounted2.container.isConnected) {
@@ -16822,6 +16913,7 @@ function MediaOverlay({
16822
16913
  (prev) => prev && prev.fullWidth === width && prev.height === height ? prev : { fullWidth: width, height }
16823
16914
  );
16824
16915
  }, [isVideo]);
16916
+ const replaceLabel = isVideo ? "Replace video" : hover.elementType === "bg-image" ? "Replace background" : "Replace image";
16825
16917
  const replaceMode = buttonSize ? replaceButtonMode({ width: rect.width, height: rect.height }, buttonSize) : "none";
16826
16918
  const box = {
16827
16919
  position: "fixed",
@@ -16920,11 +17012,10 @@ function MediaOverlay({
16920
17012
  className: "flex items-center justify-center cursor-pointer",
16921
17013
  style: {
16922
17014
  ...box,
16923
- // When text overlays this media, let clicks fall THROUGH to the text underneath. In the
16924
- // parent this needed a whole `ow:click-at` round-trip to re-hit-test inside the iframe;
16925
- // in-document, pointer-events does it natively. The button below opts back in, so
16926
- // Replace still works.
16927
- pointerEvents: hover.hasTextOverlap || cueOnly ? "none" : "auto",
17015
+ // Visual only. `pointer-events: auto` on this full-bleed rect covered the navbar
17016
+ // (hero bg includes the absolute TopNav) and swallowed CTA press-drag. Text overlay
17017
+ // and cueOnly also need clicks to fall through — Replace / video controls opt back in.
17018
+ pointerEvents: "none",
16928
17019
  // Selected: a firm component ring with no wash, so the image reads as chosen rather
16929
17020
  // than hovered. Hover keeps the existing tinted preview.
16930
17021
  boxShadow: selected ? "inset 0 0 0 2px var(--color-bridge-primary)" : "inset 0 0 0 1.5px var(--color-bridge-primary)",
@@ -16951,17 +17042,17 @@ function MediaOverlay({
16951
17042
  },
16952
17043
  children: [
16953
17044
  isVideo ? /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(import_lucide_react5.Film, { size: 14 }) : /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(import_lucide_react5.ImageIcon, { size: 14 }),
16954
- isVideo ? "Replace video" : "Replace image"
17045
+ replaceLabel
16955
17046
  ]
16956
17047
  }
16957
17048
  ),
16958
- replaceMode === "none" || cueOnly ? null : /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(
17049
+ showChrome && replaceMode !== "none" && /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(
16959
17050
  Button,
16960
17051
  {
16961
17052
  "data-ohw-media-overlay": "",
16962
17053
  variant: "outline",
16963
17054
  size: "sm",
16964
- "aria-label": isVideo ? "Replace video" : "Replace image",
17055
+ "aria-label": replaceLabel,
16965
17056
  className: "gap-1.5 cursor-pointer hover:bg-background",
16966
17057
  style: {
16967
17058
  ...OVERLAY_BUTTON_STYLE,
@@ -16984,7 +17075,7 @@ function MediaOverlay({
16984
17075
  },
16985
17076
  children: [
16986
17077
  isVideo ? /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(import_lucide_react5.Film, { size: 14 }) : /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(import_lucide_react5.ImageIcon, { size: 14 }),
16987
- replaceMode === "full" ? isVideo ? "Replace video" : hover.elementType === "bg-image" ? "Replace background" : "Replace image" : null
17078
+ replaceMode === "full" ? replaceLabel : null
16988
17079
  ]
16989
17080
  }
16990
17081
  )
@@ -18290,8 +18381,20 @@ function withPricingCollection(tree, mutate) {
18290
18381
  const next = JSON.parse(JSON.stringify(tree));
18291
18382
  const collection = findPricingCollection(next);
18292
18383
  if (!collection || !collection.children) return null;
18384
+ for (const card of collection.children) {
18385
+ if (isPricingCard(card)) upgradeLegacyPricingSlots(card);
18386
+ }
18293
18387
  return mutate(collection) ? next : null;
18294
18388
  }
18389
+ function upgradeLegacyPricingSlots(card) {
18390
+ const slots = card.slots = card.slots ?? {};
18391
+ if (typeof slots.features === "string") {
18392
+ const rows = pricingFeatureList(card);
18393
+ if (rows.length > 0) slots.features = rows.slice(0, PRICING_FEATURES_MAX);
18394
+ }
18395
+ if (slots.featured === true) slots.featured = { enabled: true };
18396
+ else if (slots.featured === false) delete slots.featured;
18397
+ }
18295
18398
  function addPricingTier(tree) {
18296
18399
  return withPricingCollection(tree, (collection) => {
18297
18400
  const children = collection.children;
@@ -18413,7 +18516,7 @@ function pricingFeatureList(target) {
18413
18516
  }));
18414
18517
  }
18415
18518
  if (typeof raw === "string") {
18416
- return raw.split("\n").map((line) => line.replace(/^[-*•]\s*/, "").trim()).filter(Boolean).map((text) => ({ text }));
18519
+ return raw.split(/\n|<br\s*\/?>/i).map((line) => line.replace(/<[^>]+>/g, "").replace(/^[-*•]\s*/, "").trim().slice(0, 120)).filter(Boolean).map((text) => ({ text }));
18417
18520
  }
18418
18521
  return [];
18419
18522
  }
@@ -21407,6 +21510,7 @@ function shouldUseDevFixtures() {
21407
21510
  function getLogoElement(el) {
21408
21511
  const marked = el.closest('[data-ohw-role="logo"], [data-ohw-logo]');
21409
21512
  if (marked) return marked;
21513
+ if (el.closest('[data-ohw-editable="icon"]')) return null;
21410
21514
  const root = el.closest("nav, [data-ohw-nav-root], footer");
21411
21515
  if (!root) return null;
21412
21516
  const anchor = el.closest("a");
@@ -23970,7 +24074,7 @@ function getNavigationSelectionParent(el) {
23970
24074
  if (!isFooterLinksContainer(el) && (el.hasAttribute("data-ohw-footer-col") || el.hasAttribute("data-ohw-footer-column") || isInferredFooterGroup2(el))) {
23971
24075
  return resolveFooterLinksContainerFrom(el);
23972
24076
  }
23973
- if (el.hasAttribute("data-ohw-nav-container") || el.hasAttribute("data-ohw-nav-drawer") || isFooterLinksContainer(el)) {
24077
+ 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)) {
23974
24078
  return getNavigationRoot(el);
23975
24079
  }
23976
24080
  return null;
@@ -24192,7 +24296,6 @@ var ICONS = {
24192
24296
  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"/>',
24193
24297
  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"/>'
24194
24298
  };
24195
- var HEIGHT_SETTLE_DELAYS = [150, 400, 800];
24196
24299
  var SELECTION_CHROME_GAP2 = 4;
24197
24300
  var TOOLBAR_STROKE_GAP2 = 4;
24198
24301
  var TOOLBAR_OFFSET_FROM_ELEMENT = SELECTION_CHROME_GAP2 + TOOLBAR_STROKE_GAP2;
@@ -25172,21 +25275,6 @@ function OhhwellsBridge() {
25172
25275
  const [isItemDragging, setIsItemDragging] = (0, import_react21.useState)(false);
25173
25276
  const [isFooterFrameSelection, setIsFooterFrameSelection] = (0, import_react21.useState)(false);
25174
25277
  isFooterFrameSelectionRef.current = isFooterFrameSelection;
25175
- const [floatingPanel, setFloatingPanel] = (0, import_react21.useState)(null);
25176
- const floatingPanelOpenRef = (0, import_react21.useRef)(false);
25177
- floatingPanelOpenRef.current = floatingPanel !== null;
25178
- const floatingPanelRef = (0, import_react21.useRef)(null);
25179
- floatingPanelRef.current = floatingPanel;
25180
- const [floatingPanelPos, setFloatingPanelPos] = (0, import_react21.useState)(null);
25181
- const buttonCollectionDragRef = (0, import_react21.useRef)(null);
25182
- const buttonDropInsertIndexRef = (0, import_react21.useRef)(null);
25183
- const [buttonDropSlots, setButtonDropSlots] = (0, import_react21.useState)([]);
25184
- const [activeButtonDropIndex, setActiveButtonDropIndex] = (0, import_react21.useState)(null);
25185
- const buttonPointerDragRef = (0, import_react21.useRef)(null);
25186
- const [buttonStyleTick, setButtonStyleTick] = (0, import_react21.useState)(0);
25187
- const [logoSizeDraft, setLogoSizeDraft] = (0, import_react21.useState)(null);
25188
- const [editorViewport, setEditorViewport] = (0, import_react21.useState)("desktop");
25189
- const [parentScrollSnap, setParentScrollSnap] = (0, import_react21.useState)(null);
25190
25278
  const [navDropdownPreviewOpen, setNavDropdownPreviewOpen] = (0, import_react21.useState)(null);
25191
25279
  const [footerHeadingVisible, setFooterHeadingVisible] = (0, import_react21.useState)(null);
25192
25280
  const footerDragRef = (0, import_react21.useRef)(null);
@@ -25212,6 +25300,21 @@ function OhhwellsBridge() {
25212
25300
  const stylesRef = (0, import_react21.useRef)("");
25213
25301
  const appliedDrawerRef = (0, import_react21.useRef)(null);
25214
25302
  const pendingDeleteUndoRef = (0, import_react21.useRef)(null);
25303
+ const [floatingPanel, setFloatingPanel] = (0, import_react21.useState)(null);
25304
+ const floatingPanelOpenRef = (0, import_react21.useRef)(false);
25305
+ const floatingPanelRef = (0, import_react21.useRef)(null);
25306
+ floatingPanelRef.current = floatingPanel;
25307
+ const setFloatingPanelRef = (0, import_react21.useRef)(setFloatingPanel);
25308
+ const [floatingPanelPos, setFloatingPanelPos] = (0, import_react21.useState)(null);
25309
+ const buttonCollectionDragRef = (0, import_react21.useRef)(null);
25310
+ const buttonDropInsertIndexRef = (0, import_react21.useRef)(null);
25311
+ const [buttonDropSlots, setButtonDropSlots] = (0, import_react21.useState)([]);
25312
+ const [activeButtonDropIndex, setActiveButtonDropIndex] = (0, import_react21.useState)(null);
25313
+ const buttonPointerDragRef = (0, import_react21.useRef)(null);
25314
+ const [buttonStyleTick, setButtonStyleTick] = (0, import_react21.useState)(0);
25315
+ const [logoSizeDraft, setLogoSizeDraft] = (0, import_react21.useState)(null);
25316
+ const [editorViewport, setEditorViewport] = (0, import_react21.useState)("desktop");
25317
+ const [parentScrollSnap, setParentScrollSnap] = (0, import_react21.useState)(null);
25215
25318
  const [sitePages, setSitePages] = (0, import_react21.useState)([]);
25216
25319
  const [sectionsByPath, setSectionsByPath] = (0, import_react21.useState)({});
25217
25320
  const sectionsPrefetchGenRef = (0, import_react21.useRef)(0);
@@ -25220,7 +25323,18 @@ function OhhwellsBridge() {
25220
25323
  const linkPopoverOpenRef = (0, import_react21.useRef)(false);
25221
25324
  const linkPopoverGraceUntilRef = (0, import_react21.useRef)(0);
25222
25325
  setLinkPopoverRef.current = setLinkPopover;
25326
+ setFloatingPanelRef.current = setFloatingPanel;
25223
25327
  linkPopoverSessionRef.current = linkPopover;
25328
+ floatingPanelOpenRef.current = Boolean(floatingPanel);
25329
+ (0, import_react21.useEffect)(() => {
25330
+ const syncViewport = () => {
25331
+ const next = window.innerWidth <= 480 ? "mobile" : "desktop";
25332
+ setEditorViewport((prev) => prev === next ? prev : next);
25333
+ };
25334
+ syncViewport();
25335
+ window.addEventListener("resize", syncViewport);
25336
+ return () => window.removeEventListener("resize", syncViewport);
25337
+ }, []);
25224
25338
  (0, import_react21.useEffect)(() => {
25225
25339
  setSectionOrderPersistHandler((entries) => {
25226
25340
  const currentPath = window.location.pathname;
@@ -26899,6 +27013,7 @@ function OhhwellsBridge() {
26899
27013
  applyAiFooterFromContent(content);
26900
27014
  if (typeof content[STYLE_STORE_KEY] === "string") {
26901
27015
  stylesRef.current = content[STYLE_STORE_KEY];
27016
+ document.querySelectorAll('[data-ohw-editable="form"]').forEach((f) => markFormFields(f));
26902
27017
  applyStylesToDom(parseStyleStore(content[STYLE_STORE_KEY]));
26903
27018
  }
26904
27019
  applyBrandChrome(content);
@@ -26909,12 +27024,12 @@ function OhhwellsBridge() {
26909
27024
  if (key === "__ohw_sections") continue;
26910
27025
  if (key === AI_SECTIONS_KEY) continue;
26911
27026
  if (key === AI_FOOTER_KEY) continue;
27027
+ if (key === LOGO_PLACEHOLDER_KEY) continue;
27028
+ if (LOGO_IMAGE_KEYS.includes(key)) continue;
26912
27029
  if (key === BRAND_KIT_KEY) continue;
26913
27030
  if (key === STYLE_STORE_KEY) continue;
26914
27031
  if (BRAND_CHROME_KEYS.has(key)) continue;
26915
- if (key === LOGO_PLACEHOLDER_KEY) continue;
26916
27032
  if (key === CUSTOM_CODE_AFTER_HEAD_KEY || key === CUSTOM_CODE_BEFORE_HEAD_KEY) continue;
26917
- if (LOGO_IMAGE_KEYS.includes(key)) continue;
26918
27033
  if (typeof content[AI_FOOTER_KEY] === "string" && content[AI_FOOTER_KEY] && isAiFooterOwnedContentKey(key)) {
26919
27034
  continue;
26920
27035
  }
@@ -27129,17 +27244,18 @@ function OhhwellsBridge() {
27129
27244
  }
27130
27245
  applyAiFooterFromContent(content);
27131
27246
  if (typeof content[STYLE_STORE_KEY] === "string") {
27247
+ document.querySelectorAll('[data-ohw-editable="form"]').forEach((f) => markFormFields(f));
27132
27248
  applyStylesToDom(parseStyleStore(content[STYLE_STORE_KEY]));
27133
27249
  }
27134
27250
  for (const [key, val] of Object.entries(content)) {
27135
27251
  if (key === "__ohw_sections") continue;
27136
27252
  if (key === AI_SECTIONS_KEY) continue;
27137
27253
  if (key === AI_FOOTER_KEY) continue;
27254
+ if (key === LOGO_PLACEHOLDER_KEY) continue;
27255
+ if (LOGO_IMAGE_KEYS.includes(key)) continue;
27138
27256
  if (key === BRAND_KIT_KEY) continue;
27139
27257
  if (key === STYLE_STORE_KEY) continue;
27140
27258
  if (BRAND_CHROME_KEYS.has(key)) continue;
27141
- if (key === LOGO_PLACEHOLDER_KEY) continue;
27142
- if (LOGO_IMAGE_KEYS.includes(key)) continue;
27143
27259
  if (typeof content[AI_FOOTER_KEY] === "string" && content[AI_FOOTER_KEY] && isAiFooterOwnedContentKey(key)) {
27144
27260
  continue;
27145
27261
  }
@@ -27269,7 +27385,7 @@ function OhhwellsBridge() {
27269
27385
  if (!subdomain) return {};
27270
27386
  return contentCache.get(subdomain) ?? {};
27271
27387
  };
27272
- const observeRoots = () => [document.querySelector("nav"), document.querySelector("footer")].filter(
27388
+ const observeRoots = () => [document.querySelector("nav"), getFooterRoot()].filter(
27273
27389
  (el) => Boolean(el)
27274
27390
  );
27275
27391
  const roots = observeRoots();
@@ -27396,26 +27512,11 @@ function OhhwellsBridge() {
27396
27512
  const t2 = setTimeout(measure, 500);
27397
27513
  const ro = new ResizeObserver(schedule);
27398
27514
  ro.observe(document.body);
27399
- let lastWidth = window.innerWidth;
27400
- let resizeTimers = [];
27401
- const clearResizeTimers = () => {
27402
- resizeTimers.forEach(clearTimeout);
27403
- resizeTimers = [];
27404
- };
27405
- const handleResize = () => {
27406
- if (window.innerWidth === lastWidth) return;
27407
- lastWidth = window.innerWidth;
27408
- clearResizeTimers();
27409
- resizeTimers = HEIGHT_SETTLE_DELAYS.map((delay) => setTimeout(measure, delay));
27410
- };
27411
- window.addEventListener("resize", handleResize);
27412
27515
  return () => {
27413
27516
  clearTimeout(t1);
27414
27517
  clearTimeout(t2);
27415
27518
  if (raf != null) cancelAnimationFrame(raf);
27416
27519
  ro.disconnect();
27417
- clearResizeTimers();
27418
- window.removeEventListener("resize", handleResize);
27419
27520
  };
27420
27521
  }, [pathname, isEditMode, postToParent2]);
27421
27522
  (0, import_react21.useEffect)(() => {
@@ -27710,6 +27811,13 @@ function OhhwellsBridge() {
27710
27811
  if (target.closest("[data-ohw-state-toggle]")) return;
27711
27812
  if (target.closest("[data-ohw-max-badge]")) return;
27712
27813
  if (isInsideLinkEditor(target)) return;
27814
+ if (isInsideFloatingPanel(target)) return;
27815
+ if (target.closest("[data-ohw-form-toolbar]")) return;
27816
+ if (target.closest(
27817
+ '[data-ohw-field-toolbar], [data-ohw-field-type-picker], [data-radix-popper-content-wrapper], [role="menu"], [data-slot="dropdown-menu-content"]'
27818
+ )) {
27819
+ return;
27820
+ }
27713
27821
  if (selectedMediaElRef.current && !selectedMediaElRef.current.contains(target) && !target.closest("[data-ohw-media-overlay], [data-ohw-edit-chrome], [data-ohw-item-interaction]")) {
27714
27822
  clearMediaSelectionRef.current();
27715
27823
  }
@@ -27730,17 +27838,10 @@ function OhhwellsBridge() {
27730
27838
  return;
27731
27839
  }
27732
27840
  }
27733
- if (isInsideFloatingPanel(target)) return;
27734
- if (target.closest("[data-ohw-form-toolbar]")) return;
27735
- if (target.closest(
27736
- '[data-ohw-field-toolbar], [data-ohw-field-type-picker], [data-radix-popper-content-wrapper], [role="menu"], [data-slot="dropdown-menu-content"]'
27737
- )) {
27738
- return;
27739
- }
27740
27841
  {
27741
27842
  const formEl = getFormElement(target);
27742
27843
  const onSuccessText = target.closest(`[${SUCCESS_TEXT_ATTR}]`);
27743
- if (formEl && formKeyOf(formEl) && !onSuccessText) {
27844
+ if (formEl && formKeyOf(formEl) && !onSuccessText && !isCtaButton(target)) {
27744
27845
  const fieldEl = getFieldWrapper(target);
27745
27846
  if (fieldEl) {
27746
27847
  const label = target.closest("label");
@@ -28116,6 +28217,9 @@ function OhhwellsBridge() {
28116
28217
  setHoveredItemRect(null);
28117
28218
  hoveredNavContainerRef.current = null;
28118
28219
  setHoveredNavContainerRect(null);
28220
+ siblingHintElRef.current = null;
28221
+ setSiblingHintRect(null);
28222
+ setSiblingHintRects([]);
28119
28223
  return;
28120
28224
  }
28121
28225
  {
@@ -28236,7 +28340,6 @@ function OhhwellsBridge() {
28236
28340
  hoveredNavContainerRef.current = null;
28237
28341
  setHoveredNavContainerRect(null);
28238
28342
  hoveredItemElRef.current = editable;
28239
- setHoveredItemRect(isSelectedForHoverRef.current(editable) ? null : editable.getBoundingClientRect());
28240
28343
  }
28241
28344
  }
28242
28345
  }
@@ -28946,7 +29049,11 @@ function OhhwellsBridge() {
28946
29049
  };
28947
29050
  const handleMouseMove = (e) => {
28948
29051
  const { clientX, clientY } = e;
28949
- if (pointOwnedByFloatingPanel(clientX, clientY)) return;
29052
+ if (pointOwnedByFloatingPanel(clientX, clientY)) {
29053
+ dismissImageHover();
29054
+ clearImageHover();
29055
+ return;
29056
+ }
28950
29057
  if (isNavDrawerOpen() && isPointOutsideOpenDrawer(clientX, clientY)) {
28951
29058
  document.querySelectorAll("[data-ohw-hovered]").forEach((el) => el.removeAttribute("data-ohw-hovered"));
28952
29059
  formHoverElRef.current = null;
@@ -28975,7 +29082,12 @@ function OhhwellsBridge() {
28975
29082
  setHoveredItemRect(null);
28976
29083
  hoveredNavContainerRef.current = null;
28977
29084
  setHoveredNavContainerRect(null);
29085
+ siblingHintElRef.current = null;
29086
+ setSiblingHintRect(null);
29087
+ setSiblingHintRects([]);
28978
29088
  dismissImageHover();
29089
+ clearImageHover();
29090
+ setSectionGap(null);
28979
29091
  return;
28980
29092
  }
28981
29093
  if (probeSocialsRowAt(clientX, clientY)) return;
@@ -28987,7 +29099,11 @@ function OhhwellsBridge() {
28987
29099
  if (e.data?.type !== "ow:pointer-sync") return;
28988
29100
  const { clientX, clientY } = e.data;
28989
29101
  if (typeof clientX !== "number" || typeof clientY !== "number") return;
28990
- if (pointOwnedByFloatingPanel(clientX, clientY)) return;
29102
+ if (pointOwnedByFloatingPanel(clientX, clientY)) {
29103
+ dismissImageHover();
29104
+ clearImageHover();
29105
+ return;
29106
+ }
28991
29107
  if (isNavDrawerOpen() && isPointOutsideOpenDrawer(clientX, clientY)) return;
28992
29108
  if (probeSocialsRowAt(clientX, clientY)) return;
28993
29109
  probeSectionGapAt(clientX, clientY);
@@ -29339,6 +29455,7 @@ function OhhwellsBridge() {
29339
29455
  applyAiFooterFromContent(content);
29340
29456
  if (typeof content[STYLE_STORE_KEY] === "string") {
29341
29457
  stylesRef.current = content[STYLE_STORE_KEY];
29458
+ document.querySelectorAll('[data-ohw-editable="form"]').forEach((f) => markFormFields(f));
29342
29459
  applyStylesToDom(parseStyleStore(content[STYLE_STORE_KEY]));
29343
29460
  }
29344
29461
  applyBrandChrome(content);
@@ -29353,12 +29470,12 @@ function OhhwellsBridge() {
29353
29470
  }
29354
29471
  if (key === AI_SECTIONS_KEY) continue;
29355
29472
  if (key === AI_FOOTER_KEY) continue;
29473
+ if (key === LOGO_PLACEHOLDER_KEY) continue;
29474
+ if (LOGO_IMAGE_KEYS.includes(key) && !String(val ?? "").trim()) continue;
29356
29475
  if (key === BRAND_KIT_KEY) continue;
29357
29476
  if (key === STYLE_STORE_KEY) continue;
29358
29477
  if (BRAND_CHROME_KEYS.has(key)) continue;
29359
- if (key === LOGO_PLACEHOLDER_KEY) continue;
29360
29478
  if (key === CUSTOM_CODE_AFTER_HEAD_KEY || key === CUSTOM_CODE_BEFORE_HEAD_KEY) continue;
29361
- if (LOGO_IMAGE_KEYS.includes(key) && !String(val ?? "").trim()) continue;
29362
29479
  if (typeof content[AI_FOOTER_KEY] === "string" && content[AI_FOOTER_KEY] && isAiFooterOwnedContentKey(key)) {
29363
29480
  continue;
29364
29481
  }
@@ -29633,6 +29750,7 @@ function OhhwellsBridge() {
29633
29750
  brandKitRef.current = value;
29634
29751
  applyBrandToDom(parseBrandKit(value));
29635
29752
  if (aiSectionsRef.current) applyAiSectionsToDom(parseAiSectionsState(aiSectionsRef.current));
29753
+ applyAiFooterFromContent(editContentRef.current);
29636
29754
  applyStylesToDom(parseStyleStore(stylesRef.current));
29637
29755
  postToParentRef.current({ type: "ow:change", nodes: [{ key: BRAND_KIT_KEY, text: value }] });
29638
29756
  postToParentRef.current({ type: "ow:ai-brand-applied", previous, value });
@@ -29895,6 +30013,12 @@ function OhhwellsBridge() {
29895
30013
  closeLinkPopoverRef.current();
29896
30014
  return;
29897
30015
  }
30016
+ if (floatingPanelOpenRef.current) {
30017
+ setFloatingPanelRef.current(null);
30018
+ deselectRef.current();
30019
+ deactivateRef.current();
30020
+ return;
30021
+ }
29898
30022
  deselectRef.current();
29899
30023
  deactivateRef.current();
29900
30024
  clearMediaSelectionRef.current();
@@ -30708,10 +30832,10 @@ function OhhwellsBridge() {
30708
30832
  window.removeEventListener("message", handleDeleteSection);
30709
30833
  window.removeEventListener("message", handleDuplicateSection);
30710
30834
  window.removeEventListener("message", handleDeactivate);
30711
- document.documentElement.removeAttribute("data-ohw-panel-dragging");
30712
30835
  window.removeEventListener("message", handleToastAction);
30713
30836
  window.removeEventListener("message", handleFormCount);
30714
30837
  window.removeEventListener("message", handleUiEscape);
30838
+ document.documentElement.removeAttribute("data-ohw-panel-dragging");
30715
30839
  autoSaveTimers.current.forEach(clearTimeout);
30716
30840
  autoSaveTimers.current.clear();
30717
30841
  if (imageUnhoverTimerRef.current) clearTimeout(imageUnhoverTimerRef.current);
@@ -31005,7 +31129,7 @@ function OhhwellsBridge() {
31005
31129
  postToParent2({
31006
31130
  type: "ow:ready",
31007
31131
  version: "1",
31008
- bridgeVersion: "0.1.137",
31132
+ bridgeVersion: "0.1.139",
31009
31133
  path: pathname,
31010
31134
  nodes: collectEditableNodes(editContentRef.current),
31011
31135
  sections