@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.js CHANGED
@@ -2827,14 +2827,22 @@ function isInactiveFooterChrome(el) {
2827
2827
  if (footer && footer.style.display === "none") return true;
2828
2828
  return false;
2829
2829
  }
2830
+ function isIncidentalFooter(el) {
2831
+ return Boolean(el.closest("blockquote, article, figure"));
2832
+ }
2830
2833
  function getFooterRoot() {
2831
2834
  const ai = document.querySelector("footer[data-ai-footer]");
2832
2835
  if (ai) return ai;
2833
2836
  for (const el of Array.from(
2834
- document.querySelectorAll('footer[data-ohw-section="footer"], footer')
2837
+ document.querySelectorAll('footer[data-ohw-section="footer"]')
2835
2838
  )) {
2839
+ if (isInactiveFooterChrome(el)) continue;
2840
+ return el;
2841
+ }
2842
+ for (const el of Array.from(document.querySelectorAll("footer"))) {
2836
2843
  if (el.hasAttribute("data-ai-footer")) continue;
2837
2844
  if (isInactiveFooterChrome(el)) continue;
2845
+ if (isIncidentalFooter(el)) continue;
2838
2846
  return el;
2839
2847
  }
2840
2848
  return null;
@@ -3245,8 +3253,8 @@ function buildFooterLink(colIndex, itemIndex, href, label, template) {
3245
3253
  return anchor;
3246
3254
  }
3247
3255
  function footerColumnExistsInDom(colIndex) {
3248
- const root = getFooterRoot() ?? document;
3249
- 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;
3256
+ const scope = getFooterLinksContainer() ?? getFooterRoot() ?? document;
3257
+ 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;
3250
3258
  }
3251
3259
  function getFooterLinkTemplate() {
3252
3260
  for (const col of listFooterColumns()) {
@@ -6263,6 +6271,20 @@ function applyBrandChrome(content) {
6263
6271
 
6264
6272
  // src/lib/brand-kit.ts
6265
6273
  var BRAND_KIT_KEY = "__ohw_brand";
6274
+ var appliedPalette = null;
6275
+ var appliedFonts = null;
6276
+ function getAppliedBrand() {
6277
+ if (!appliedPalette) return null;
6278
+ return appliedFonts ? { palette: appliedPalette, fonts: appliedFonts } : { palette: appliedPalette };
6279
+ }
6280
+ function brandForGenerated(fallback) {
6281
+ const applied = getAppliedBrand();
6282
+ if (!applied) return fallback ?? void 0;
6283
+ return {
6284
+ palette: applied.palette,
6285
+ fonts: applied.fonts ?? fallback?.fonts ?? AI_DEFAULT_BRAND.fonts
6286
+ };
6287
+ }
6266
6288
  var BRAND_VAR_PREFIX = "--ohw-brand-";
6267
6289
  var BRAND_VAR_NAMES = ["primary", "accent", "light", "dark", "surface", "border", "muted"].map(
6268
6290
  (role) => `${BRAND_VAR_PREFIX}${role}`
@@ -6566,6 +6588,8 @@ function loadCustomFonts(fonts) {
6566
6588
  function applyBrandToDom(kit) {
6567
6589
  const root = document.documentElement;
6568
6590
  if (!kit) {
6591
+ appliedPalette = null;
6592
+ appliedFonts = null;
6569
6593
  for (const name of [...BRAND_VAR_NAMES, ...LEGACY_BRAND_VAR_NAMES, ...BUTTON_VAR_NAMES]) {
6570
6594
  root.style.removeProperty(name);
6571
6595
  }
@@ -6579,11 +6603,16 @@ function applyBrandToDom(kit) {
6579
6603
  const applyFonts = kit.touched ? kit.touched.fonts === true : true;
6580
6604
  if (kit.palette) {
6581
6605
  if (applyPalette) {
6606
+ appliedPalette = kit.palette;
6582
6607
  for (const [name, value] of Object.entries(brandColorVars(kit.palette))) root.style.setProperty(name, value);
6583
6608
  } else {
6609
+ appliedPalette = null;
6584
6610
  for (const name of [...BRAND_VAR_NAMES, ...LEGACY_BRAND_VAR_NAMES]) root.style.removeProperty(name);
6585
6611
  }
6586
6612
  }
6613
+ if (kit.fonts) {
6614
+ appliedFonts = applyFonts ? kit.fonts : null;
6615
+ }
6587
6616
  if (kit.buttons) {
6588
6617
  for (const name of BUTTON_VAR_NAMES) root.style.removeProperty(name);
6589
6618
  for (const [name, value] of Object.entries(buttonStyleVars({ ...kit, buttons: kit.buttons }))) root.style.setProperty(name, value);
@@ -7116,6 +7145,35 @@ function withThemeFonts(brand) {
7116
7145
  }
7117
7146
  };
7118
7147
  }
7148
+ function liveBrandColor(role, fallback) {
7149
+ if (role === "dark") {
7150
+ return `var(--brand-text, var(--ohw-brand-dark, var(--color-dark, ${fallback})))`;
7151
+ }
7152
+ if (role === "light") {
7153
+ return `var(--brand-background, var(--ohw-brand-light, var(--color-light, ${fallback})))`;
7154
+ }
7155
+ return `var(--brand-${role}, var(--ohw-brand-${role}, var(--color-${role}, ${fallback})))`;
7156
+ }
7157
+ function withLivePalette(brand, source) {
7158
+ const sourceOf = (color) => {
7159
+ const value = color.trim().toLowerCase();
7160
+ const entries = Object.entries(source.palette);
7161
+ return entries.find(([, hex]) => hex.trim().toLowerCase() === value)?.[0] ?? null;
7162
+ };
7163
+ const map = (color) => {
7164
+ const role = sourceOf(color);
7165
+ return role ? liveBrandColor(role, source.palette[role]) : color;
7166
+ };
7167
+ return {
7168
+ ...brand,
7169
+ palette: {
7170
+ dark: map(brand.palette.dark),
7171
+ primary: map(brand.palette.primary),
7172
+ accent: map(brand.palette.accent),
7173
+ light: map(brand.palette.light)
7174
+ }
7175
+ };
7176
+ }
7119
7177
  function footerLogoStyle(fallbackFont, fallbackColor) {
7120
7178
  return {
7121
7179
  fontFamily: `var(--brand-font-heading, var(--font-heading, var(--font-display, ${quotedFont(fallbackFont)})))`,
@@ -7187,7 +7245,7 @@ var AI_MOBILE_CSS = [
7187
7245
  // page's first section. Top only (bottom needs no nav clearance); ~96px approximates the
7188
7246
  // template nav's own height (measured ~94px) — there's no shared `--ohw-header-h` var to read
7189
7247
  // an exact figure from (no live measurement exists), so this is a safe approximation, not a
7190
- // pixel-exact fit. Horizontal spacing already comes from `data-ai-container`.
7248
+ // pixel-exact fit. Horizontal spacing already comes from `data-ai-section-inner`.
7191
7249
  '[data-ai-overlay-clearance] [data-ai-overlap-layer="overlay"]{padding-top:96px}',
7192
7250
  // A full-bleed carousel's height is pure arithmetic: it spans the whole window, so 16/9 turns
7193
7251
  // window width straight into height. Measured at 16/9 it came out ~90% of the viewport at EVERY
@@ -7203,8 +7261,10 @@ var AI_MOBILE_CSS = [
7203
7261
  "[data-ai-section]{overflow-x:hidden}",
7204
7262
  // `:not([data-ai-full-bleed])`: this `!important` gutter beat the inline `padding: 0` that a
7205
7263
  // full-bleed section's container sets, so a carousel/media hero that is edge-to-edge on desktop
7206
- // still got a 20px band of section background down both sides on a phone.
7207
- "[data-ai-container]:not([data-ai-full-bleed]){padding:0 20px !important}",
7264
+ // still got a 20px band of section background down both sides on a phone. The footer's own
7265
+ // container keeps the unguarded rule below — `data-ai-full-bleed` can never appear on it.
7266
+ "[data-ai-section-inner]:not([data-ai-full-bleed]){padding:0 20px !important}",
7267
+ "[data-ai-container]{padding:0 20px !important}",
7208
7268
  "[data-ai-row]{display:flex !important;flex-direction:column !important;align-items:stretch !important}",
7209
7269
  "[data-ai-cell]{width:100%;min-width:0}",
7210
7270
  // The rule above turns every row into a COLUMN flex container on phones, which silently breaks
@@ -7333,7 +7393,7 @@ function hexContrast(a, b) {
7333
7393
  function primaryButtonLabel(brand) {
7334
7394
  const darkC = hexContrast(brand.palette.primary, brand.palette.dark);
7335
7395
  const lightC = hexContrast(brand.palette.primary, AI_TREE_TOKENS.textPrimaryForeground);
7336
- if (darkC === null || lightC === null) return AI_TREE_TOKENS.textPrimaryForeground;
7396
+ if (darkC === null || lightC === null) return `var(--brand-on-primary, ${AI_TREE_TOKENS.textPrimaryForeground})`;
7337
7397
  return darkC > lightC ? brand.palette.dark : AI_TREE_TOKENS.textPrimaryForeground;
7338
7398
  }
7339
7399
  function accentBandContext(brand) {
@@ -7357,7 +7417,11 @@ function readableForeground(bg, brand) {
7357
7417
  return darkC > lightC ? brand.palette.dark : AI_TREE_TOKENS.textPrimaryForeground;
7358
7418
  }
7359
7419
  function hexWithAlpha(hex, alpha) {
7360
- const m = /^#([0-9a-f]{6})$/i.exec(hex.trim());
7420
+ const value = hex.trim();
7421
+ if (value.startsWith("var(") || value.startsWith("color-mix(")) {
7422
+ return `color-mix(in srgb, ${value} ${Math.round(alpha * 100)}%, transparent)`;
7423
+ }
7424
+ const m = /^#([0-9a-f]{6})$/i.exec(value);
7361
7425
  if (!m) return hex;
7362
7426
  const r2 = parseInt(m[1].slice(0, 2), 16);
7363
7427
  const g = parseInt(m[1].slice(2, 4), 16);
@@ -7405,6 +7469,31 @@ function pricingCardEmphasis(featured, ctx, hasBg) {
7405
7469
  function textAttrs(ctx, path) {
7406
7470
  return ctx.keyFor ? { "data-ohw-key": ctx.keyFor(path), "data-ohw-editable": "text" } : {};
7407
7471
  }
7472
+ var AI_RESPONSIVE_CSS = [
7473
+ "@media (max-width: 960px) {",
7474
+ " [data-ai-responsive] [data-ai-grid] { grid-template-columns: repeat(2, 1fr) !important; }",
7475
+ ' [data-ai-responsive] [data-ai-grid="1"] { grid-template-columns: 1fr !important; }',
7476
+ "}",
7477
+ "@media (max-width: 640px) {",
7478
+ " [data-ai-responsive] [data-ai-grid] { grid-template-columns: 1fr !important; }",
7479
+ " [data-ai-responsive] [data-ai-columns] > * { grid-column: 1 / -1 !important; }",
7480
+ // Group containers flatten to a column on phones; span placements come along for free.
7481
+ // `:not([data-ai-group="overlap"])` for the same reason as the twin pair in AI_MOBILE_CSS — see
7482
+ // the comment there. This sheet's copy is the narrower selector, so without the exemption it
7483
+ // flattens an `overlap` hero below 640px even though the other rule stands down: measured a 390px
7484
+ // viewport with the heading/CTA in flow *under* the image, 219px of section background where the
7485
+ // layered text used to be.
7486
+ ' [data-ai-responsive] [data-ai-group]:not([data-ai-group="overlap"]) { display: flex !important; flex-direction: column !important; }',
7487
+ ' [data-ai-responsive] [data-ai-group]:not([data-ai-group="overlap"]) > * { grid-column: auto !important; }',
7488
+ // `:not([data-ai-full-bleed])` for the same reason as the twin rule in AI_MOBILE_CSS: this one
7489
+ // is narrower (0,2,0 vs 0,1,0) and also `!important`, so without the guard it wins outright and
7490
+ // a full-bleed hero gets its 20px side band back below 640px only — the one width where the
7491
+ // other rule had already been taught to stand down.
7492
+ " [data-ai-responsive] [data-ai-section-inner]:not([data-ai-full-bleed]) { padding-left: 20px !important; padding-right: 20px !important; }",
7493
+ " [data-ai-responsive] { overflow-x: hidden; }",
7494
+ " [data-ai-responsive] img { max-width: 100%; }",
7495
+ "}"
7496
+ ].join("\n");
7408
7497
  var TRANSPARENT_PX = "data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7";
7409
7498
  function MediaBox({
7410
7499
  refValue,
@@ -7425,9 +7514,9 @@ function MediaBox({
7425
7514
  const url = refValue ? ctx.resolveMedia(refValue) : null;
7426
7515
  const isIcon = /^(lucide|simple):/.test(refValue);
7427
7516
  const aspectRatio = aspect && /^\d+:\d+$/.test(aspect) ? aspect.replace(":", " / ") : void 0;
7428
- const editAttrs = ctx.keyFor && editPath && !isIcon ? {
7517
+ const editAttrs = ctx.keyFor && editPath ? {
7429
7518
  "data-ohw-key": ctx.keyFor(editPath),
7430
- ...genericImageEdit ? { "data-ohw-editable": "image" } : {}
7519
+ ...isIcon ? { "data-ohw-editable": "icon" } : genericImageEdit ? { "data-ohw-editable": "image" } : {}
7431
7520
  } : {};
7432
7521
  if (isIcon) {
7433
7522
  const Icon = refValue.startsWith("lucide:") ? lucideByName(refValue.slice(7)) : null;
@@ -7435,6 +7524,7 @@ function MediaBox({
7435
7524
  "span",
7436
7525
  {
7437
7526
  "data-ai-icon": refValue,
7527
+ ...editAttrs,
7438
7528
  style: {
7439
7529
  display: "inline-flex",
7440
7530
  width: 48,
@@ -7630,12 +7720,10 @@ function ButtonEl({
7630
7720
  cursor: "pointer",
7631
7721
  whiteSpace: "nowrap",
7632
7722
  ...buttonShellStyle(ctx, variant, fullWidth),
7633
- ...tokenColorStyle ?? (secondary ? { border: `1px solid ${secondaryColor ?? ctx.brand.palette.dark}`, color: secondaryColor ?? ctx.brand.palette.dark } : (
7634
- // Off-band, prefer the template button's measured fill/label pair — a template may
7635
- // fill its CTAs with any token (hvac: accent bg, primary text). On an accent band
7636
- // the flipped palette keeps contrast, so the brand-driven colours stay.
7637
- !ctx.buttonLabel && ctx.buttonStyle?.background ? { background: ctx.buttonStyle.background, color: ctx.buttonStyle.color } : { background: ctx.brand.palette.primary, color: ctx.buttonLabel ?? primaryButtonLabel(ctx.brand) }
7638
- ))
7723
+ ...tokenColorStyle ?? (secondary ? { border: `1px solid ${secondaryColor ?? ctx.brand.palette.dark}`, color: secondaryColor ?? ctx.brand.palette.dark } : {
7724
+ background: ctx.brand.palette.primary,
7725
+ color: ctx.buttonLabel ?? primaryButtonLabel(ctx.brand)
7726
+ })
7639
7727
  },
7640
7728
  children: /* @__PURE__ */ jsx("span", { ...path ? textAttrs(ctx, `${path}.label`) : {}, children: str(slots.label) })
7641
7729
  }
@@ -8942,7 +9030,7 @@ function CollectionBlock({ node, ctx, path }) {
8942
9030
  return /* @__PURE__ */ jsx(
8943
9031
  "div",
8944
9032
  {
8945
- "data-ai-grid": "",
9033
+ "data-ai-grid": String(itemsPerRow),
8946
9034
  "data-ohw-pricing-collection": isPricing ? "" : void 0,
8947
9035
  style: {
8948
9036
  display: "grid",
@@ -9202,7 +9290,7 @@ function renderNode(node, ctx, path, ambientAlign) {
9202
9290
  },
9203
9291
  children: [
9204
9292
  i > 0 && ctx.heroOverlapFullBleed ? (
9205
- // The section's `data-ai-container` gave up its max-width/gutter so the base
9293
+ // The section's `data-ai-section-inner` gave up its max-width/gutter so the base
9206
9294
  // layer (this group's i===0 sibling) can span edge-to-edge — reintroduced here,
9207
9295
  // scoped to just the overlay text, so a heading/copy/CTA stack doesn't stretch
9208
9296
  // the full section width instead of reading at the usual content width.
@@ -9480,10 +9568,10 @@ function renderNode(node, ctx, path, ambientAlign) {
9480
9568
  ...buttonShellStyle(ctx, "primary"),
9481
9569
  // Brand-styled: the template button's measured fill/label pair off-band, else
9482
9570
  // primary fill with a brand-derived label so it reads on custom palettes.
9483
- ...buttonColorStyle(ctx, "primary") ?? (!ctx.buttonLabel && ctx.buttonStyle?.background ? { background: ctx.buttonStyle.background, color: ctx.buttonStyle.color } : {
9571
+ ...buttonColorStyle(ctx, "primary") ?? {
9484
9572
  background: ctx.brand.palette.primary,
9485
9573
  color: ctx.buttonLabel ?? primaryButtonLabel(ctx.brand)
9486
- })
9574
+ }
9487
9575
  },
9488
9576
  children: /* @__PURE__ */ jsx("span", { ...textAttrs(ctx, `${path}.c${i}.label`), children: str(cs.label) })
9489
9577
  },
@@ -9527,7 +9615,7 @@ function AiTreeRenderer({
9527
9615
  }
9528
9616
  const resolvedBrand = withThemeFonts(brand ?? AI_DEFAULT_BRAND);
9529
9617
  const band = (tree.settings ?? {}).sectionBackground === "accent" ? accentBandContext(resolvedBrand) : null;
9530
- const blockBrand = band?.brand ?? resolvedBrand;
9618
+ const blockBrand = withLivePalette(band?.brand ?? resolvedBrand, resolvedBrand);
9531
9619
  const placeholderMap = buildPlaceholderMap(tree);
9532
9620
  const hasOverlapBackground = tree.scaffold === "overlay" && tree.rows.some(
9533
9621
  (row) => (row.blocks ?? []).some((b) => b.type === "group" && str(b.slots?.layout) === "overlap")
@@ -9543,7 +9631,7 @@ function AiTreeRenderer({
9543
9631
  // standalone resolve), so generated galleries, image rows, and overlay backgrounds arrive with
9544
9632
  // photos instead of grey boxes.
9545
9633
  resolveMedia: (ref) => resolveMedia?.(ref) ?? placeholderMap[ref] ?? resolvePlaceholderRef(ref),
9546
- cardSurface: blockBrand.palette.light.toUpperCase() === AI_DEFAULT_BRAND.palette.light.toUpperCase() ? "#ECECEB" : `color-mix(in srgb, ${blockBrand.palette.light} 90%, ${blockBrand.palette.dark})`,
9634
+ cardSurface: resolvedBrand.palette.light.toUpperCase() === AI_DEFAULT_BRAND.palette.light.toUpperCase() ? "#ECECEB" : `color-mix(in srgb, ${blockBrand.palette.light} 90%, ${blockBrand.palette.dark})`,
9547
9635
  keyFor: editKeyPrefix ? (path) => `${editKeyPrefix}.${path}` : null,
9548
9636
  sectionAlignment: (tree.settings ?? {}).alignment === "center" ? "center" : "left",
9549
9637
  buttonStyle,
@@ -9560,7 +9648,7 @@ function AiTreeRenderer({
9560
9648
  const backgroundUrl = isOverlay && settings.backgroundMedia ? ctx.resolveMedia(settings.backgroundMedia) : null;
9561
9649
  const bgAttrs = ctx.keyFor && isOverlay && settings.backgroundMedia ? { "data-ohw-key": ctx.keyFor("settings.backgroundMedia"), "data-ohw-editable": "bg-image" } : {};
9562
9650
  const toneBackground = (() => {
9563
- const { dark, primary, light } = resolvedBrand.palette;
9651
+ const { dark, primary, light } = blockBrand.palette;
9564
9652
  switch (settings.sectionBackground) {
9565
9653
  case "surface":
9566
9654
  return `color-mix(in srgb, ${light} 94%, ${dark})`;
@@ -9595,6 +9683,7 @@ function AiTreeRenderer({
9595
9683
  ...fillHeight ? { "data-ai-fill-height": "" } : {},
9596
9684
  ...needsOverlayClearance ? { "data-ai-overlay-clearance": "" } : {},
9597
9685
  ...bgAttrs,
9686
+ "data-ai-responsive": "",
9598
9687
  style: {
9599
9688
  position: "relative",
9600
9689
  padding: `${padCss} 0`,
@@ -9608,12 +9697,13 @@ function AiTreeRenderer({
9608
9697
  ...fillHeight ? { display: "grid" } : {}
9609
9698
  },
9610
9699
  children: [
9611
- isOverlay && backgroundUrl && /* @__PURE__ */ jsx("div", { style: { position: "absolute", inset: 0, background: `rgba(255,255,255,${overlayAlpha})` } }),
9700
+ /* @__PURE__ */ jsx("style", { children: AI_RESPONSIVE_CSS }),
9612
9701
  /* @__PURE__ */ jsx("style", { children: AI_MOBILE_CSS }),
9702
+ isOverlay && backgroundUrl && /* @__PURE__ */ jsx("div", { style: { position: "absolute", inset: 0, background: `rgba(255,255,255,${overlayAlpha})` } }),
9613
9703
  /* @__PURE__ */ jsx(
9614
9704
  "div",
9615
9705
  {
9616
- "data-ai-container": "",
9706
+ "data-ai-section-inner": "",
9617
9707
  ...fullBleedMedia ? { "data-ai-full-bleed": "" } : {},
9618
9708
  style: {
9619
9709
  position: "relative",
@@ -9630,7 +9720,7 @@ function AiTreeRenderer({
9630
9720
  children: tree.rows.map((row, r2) => /* @__PURE__ */ jsx(
9631
9721
  "div",
9632
9722
  {
9633
- "data-ai-row": "",
9723
+ "data-ai-columns": "",
9634
9724
  style: {
9635
9725
  display: "grid",
9636
9726
  gridTemplateColumns: "repeat(12, 1fr)",
@@ -9681,14 +9771,15 @@ function AiFooterRenderer({
9681
9771
  if (!isRenderableFooterTree(tree)) {
9682
9772
  return null;
9683
9773
  }
9684
- const resolvedBrand = withThemeFonts(brand ?? AI_DEFAULT_BRAND);
9774
+ const hexBrand = withThemeFonts(brand ?? AI_DEFAULT_BRAND);
9775
+ const resolvedBrand = withLivePalette(hexBrand, brand ?? AI_DEFAULT_BRAND);
9685
9776
  const bandBg = surface?.background ?? resolvedBrand.palette.dark;
9686
9777
  const bandText = surface?.text ?? resolvedBrand.palette.light;
9687
9778
  const onDark = surface ? isDarkColor(surface.background) : true;
9688
9779
  const ctx = {
9689
9780
  brand: resolvedBrand,
9690
9781
  resolveMedia: (ref) => resolveMedia?.(ref) ?? resolvePlaceholderRef(ref),
9691
- cardSurface: resolvedBrand.palette.light.toUpperCase() === AI_DEFAULT_BRAND.palette.light.toUpperCase() ? "#ECECEB" : `color-mix(in srgb, ${resolvedBrand.palette.light} 90%, ${resolvedBrand.palette.dark})`,
9782
+ cardSurface: hexBrand.palette.light.toUpperCase() === AI_DEFAULT_BRAND.palette.light.toUpperCase() ? "#ECECEB" : `color-mix(in srgb, ${resolvedBrand.palette.light} 90%, ${resolvedBrand.palette.dark})`,
9692
9783
  keyFor: editKeyPrefix ? (path) => `${editKeyPrefix}.${path}` : null,
9693
9784
  logoUrl: logoUrl ?? null,
9694
9785
  resolvePageHref
@@ -10264,10 +10355,9 @@ function syncSoftRemovedGenerated() {
10264
10355
  }
10265
10356
  function applyAiSectionsToDom(state, options) {
10266
10357
  if (typeof document === "undefined") return;
10267
- const brandOverride = deriveBrandOverride();
10268
10358
  const templateBrand = deriveTemplateBrand();
10269
10359
  const templateButtonStyle = deriveTemplateButtonStyle();
10270
- const brandKey = brandOverride ? JSON.stringify(brandOverride) : "";
10360
+ const brandKey = JSON.stringify(getAppliedBrand() ?? deriveBrandOverride() ?? null);
10271
10361
  const pagePath = window.location.pathname;
10272
10362
  const pageSections = state.sections.filter((entry) => (entry.path ?? "/") === pagePath);
10273
10363
  const activeIds = new Set(pageSections.map((entry) => entry.id));
@@ -10286,9 +10376,10 @@ function applyAiSectionsToDom(state, options) {
10286
10376
  if (existing && existing.serialized === serialized && existing.container.isConnected) {
10287
10377
  continue;
10288
10378
  }
10289
- const isNewMount = !existing || !existing.container.isConnected;
10290
10379
  let container = existing?.container;
10380
+ let isNewMount = false;
10291
10381
  if (!container || !container.isConnected) {
10382
+ isNewMount = true;
10292
10383
  existing?.root.unmount();
10293
10384
  container?.remove();
10294
10385
  container = document.createElement("div");
@@ -10310,7 +10401,7 @@ function applyAiSectionsToDom(state, options) {
10310
10401
  AiTreeRenderer,
10311
10402
  {
10312
10403
  tree: entry.tree,
10313
- brand: brandOverride ?? entry.brand ?? options?.brand ?? templateBrand ?? void 0,
10404
+ brand: brandForGenerated(deriveBrandOverride() ?? entry.brand ?? options?.brand ?? templateBrand),
10314
10405
  buttonStyle: templateButtonStyle,
10315
10406
  resolveMedia,
10316
10407
  editKeyPrefix: `ai.${entry.id}`
@@ -10476,7 +10567,7 @@ function applyAiFooterToDom(state, content) {
10476
10567
  const templateFooter = findTemplateFooter();
10477
10568
  captureTemplateFooterSurface(templateFooter);
10478
10569
  const surface = capturedSurface ?? null;
10479
- const brand = mountOptions.brand ?? deriveTemplateBrand();
10570
+ const brand = brandForGenerated(mountOptions.brand ?? deriveTemplateBrand());
10480
10571
  const logoUrl = resolveLogoFromContent(content, templateFooter);
10481
10572
  const serialized = JSON.stringify(state) + JSON.stringify(brand ?? null) + (logoUrl ?? "") + JSON.stringify(surface) + JSON.stringify(defaultLegal());
10482
10573
  if (mounted2 && mounted2.serialized === serialized && mounted2.container.isConnected) {
@@ -16730,6 +16821,7 @@ function MediaOverlay({
16730
16821
  (prev) => prev && prev.fullWidth === width && prev.height === height ? prev : { fullWidth: width, height }
16731
16822
  );
16732
16823
  }, [isVideo]);
16824
+ const replaceLabel = isVideo ? "Replace video" : hover.elementType === "bg-image" ? "Replace background" : "Replace image";
16733
16825
  const replaceMode = buttonSize ? replaceButtonMode({ width: rect.width, height: rect.height }, buttonSize) : "none";
16734
16826
  const box = {
16735
16827
  position: "fixed",
@@ -16828,11 +16920,10 @@ function MediaOverlay({
16828
16920
  className: "flex items-center justify-center cursor-pointer",
16829
16921
  style: {
16830
16922
  ...box,
16831
- // When text overlays this media, let clicks fall THROUGH to the text underneath. In the
16832
- // parent this needed a whole `ow:click-at` round-trip to re-hit-test inside the iframe;
16833
- // in-document, pointer-events does it natively. The button below opts back in, so
16834
- // Replace still works.
16835
- pointerEvents: hover.hasTextOverlap || cueOnly ? "none" : "auto",
16923
+ // Visual only. `pointer-events: auto` on this full-bleed rect covered the navbar
16924
+ // (hero bg includes the absolute TopNav) and swallowed CTA press-drag. Text overlay
16925
+ // and cueOnly also need clicks to fall through — Replace / video controls opt back in.
16926
+ pointerEvents: "none",
16836
16927
  // Selected: a firm component ring with no wash, so the image reads as chosen rather
16837
16928
  // than hovered. Hover keeps the existing tinted preview.
16838
16929
  boxShadow: selected ? "inset 0 0 0 2px var(--color-bridge-primary)" : "inset 0 0 0 1.5px var(--color-bridge-primary)",
@@ -16859,17 +16950,17 @@ function MediaOverlay({
16859
16950
  },
16860
16951
  children: [
16861
16952
  isVideo ? /* @__PURE__ */ jsx16(Film, { size: 14 }) : /* @__PURE__ */ jsx16(ImageIcon, { size: 14 }),
16862
- isVideo ? "Replace video" : "Replace image"
16953
+ replaceLabel
16863
16954
  ]
16864
16955
  }
16865
16956
  ),
16866
- replaceMode === "none" || cueOnly ? null : /* @__PURE__ */ jsxs7(
16957
+ showChrome && replaceMode !== "none" && /* @__PURE__ */ jsxs7(
16867
16958
  Button,
16868
16959
  {
16869
16960
  "data-ohw-media-overlay": "",
16870
16961
  variant: "outline",
16871
16962
  size: "sm",
16872
- "aria-label": isVideo ? "Replace video" : "Replace image",
16963
+ "aria-label": replaceLabel,
16873
16964
  className: "gap-1.5 cursor-pointer hover:bg-background",
16874
16965
  style: {
16875
16966
  ...OVERLAY_BUTTON_STYLE,
@@ -16892,7 +16983,7 @@ function MediaOverlay({
16892
16983
  },
16893
16984
  children: [
16894
16985
  isVideo ? /* @__PURE__ */ jsx16(Film, { size: 14 }) : /* @__PURE__ */ jsx16(ImageIcon, { size: 14 }),
16895
- replaceMode === "full" ? isVideo ? "Replace video" : hover.elementType === "bg-image" ? "Replace background" : "Replace image" : null
16986
+ replaceMode === "full" ? replaceLabel : null
16896
16987
  ]
16897
16988
  }
16898
16989
  )
@@ -18198,8 +18289,20 @@ function withPricingCollection(tree, mutate) {
18198
18289
  const next = JSON.parse(JSON.stringify(tree));
18199
18290
  const collection = findPricingCollection(next);
18200
18291
  if (!collection || !collection.children) return null;
18292
+ for (const card of collection.children) {
18293
+ if (isPricingCard(card)) upgradeLegacyPricingSlots(card);
18294
+ }
18201
18295
  return mutate(collection) ? next : null;
18202
18296
  }
18297
+ function upgradeLegacyPricingSlots(card) {
18298
+ const slots = card.slots = card.slots ?? {};
18299
+ if (typeof slots.features === "string") {
18300
+ const rows = pricingFeatureList(card);
18301
+ if (rows.length > 0) slots.features = rows.slice(0, PRICING_FEATURES_MAX);
18302
+ }
18303
+ if (slots.featured === true) slots.featured = { enabled: true };
18304
+ else if (slots.featured === false) delete slots.featured;
18305
+ }
18203
18306
  function addPricingTier(tree) {
18204
18307
  return withPricingCollection(tree, (collection) => {
18205
18308
  const children = collection.children;
@@ -18321,7 +18424,7 @@ function pricingFeatureList(target) {
18321
18424
  }));
18322
18425
  }
18323
18426
  if (typeof raw === "string") {
18324
- return raw.split("\n").map((line) => line.replace(/^[-*•]\s*/, "").trim()).filter(Boolean).map((text) => ({ text }));
18427
+ return raw.split(/\n|<br\s*\/?>/i).map((line) => line.replace(/<[^>]+>/g, "").replace(/^[-*•]\s*/, "").trim().slice(0, 120)).filter(Boolean).map((text) => ({ text }));
18325
18428
  }
18326
18429
  return [];
18327
18430
  }
@@ -21315,6 +21418,7 @@ function shouldUseDevFixtures() {
21315
21418
  function getLogoElement(el) {
21316
21419
  const marked = el.closest('[data-ohw-role="logo"], [data-ohw-logo]');
21317
21420
  if (marked) return marked;
21421
+ if (el.closest('[data-ohw-editable="icon"]')) return null;
21318
21422
  const root = el.closest("nav, [data-ohw-nav-root], footer");
21319
21423
  if (!root) return null;
21320
21424
  const anchor = el.closest("a");
@@ -23884,7 +23988,7 @@ function getNavigationSelectionParent(el) {
23884
23988
  if (!isFooterLinksContainer(el) && (el.hasAttribute("data-ohw-footer-col") || el.hasAttribute("data-ohw-footer-column") || isInferredFooterGroup2(el))) {
23885
23989
  return resolveFooterLinksContainerFrom(el);
23886
23990
  }
23887
- if (el.hasAttribute("data-ohw-nav-container") || el.hasAttribute("data-ohw-nav-drawer") || isFooterLinksContainer(el)) {
23991
+ 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)) {
23888
23992
  return getNavigationRoot(el);
23889
23993
  }
23890
23994
  return null;
@@ -24106,7 +24210,6 @@ var ICONS = {
24106
24210
  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"/>',
24107
24211
  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"/>'
24108
24212
  };
24109
- var HEIGHT_SETTLE_DELAYS = [150, 400, 800];
24110
24213
  var SELECTION_CHROME_GAP2 = 4;
24111
24214
  var TOOLBAR_STROKE_GAP2 = 4;
24112
24215
  var TOOLBAR_OFFSET_FROM_ELEMENT = SELECTION_CHROME_GAP2 + TOOLBAR_STROKE_GAP2;
@@ -25086,21 +25189,6 @@ function OhhwellsBridge() {
25086
25189
  const [isItemDragging, setIsItemDragging] = useState18(false);
25087
25190
  const [isFooterFrameSelection, setIsFooterFrameSelection] = useState18(false);
25088
25191
  isFooterFrameSelectionRef.current = isFooterFrameSelection;
25089
- const [floatingPanel, setFloatingPanel] = useState18(null);
25090
- const floatingPanelOpenRef = useRef14(false);
25091
- floatingPanelOpenRef.current = floatingPanel !== null;
25092
- const floatingPanelRef = useRef14(null);
25093
- floatingPanelRef.current = floatingPanel;
25094
- const [floatingPanelPos, setFloatingPanelPos] = useState18(null);
25095
- const buttonCollectionDragRef = useRef14(null);
25096
- const buttonDropInsertIndexRef = useRef14(null);
25097
- const [buttonDropSlots, setButtonDropSlots] = useState18([]);
25098
- const [activeButtonDropIndex, setActiveButtonDropIndex] = useState18(null);
25099
- const buttonPointerDragRef = useRef14(null);
25100
- const [buttonStyleTick, setButtonStyleTick] = useState18(0);
25101
- const [logoSizeDraft, setLogoSizeDraft] = useState18(null);
25102
- const [editorViewport, setEditorViewport] = useState18("desktop");
25103
- const [parentScrollSnap, setParentScrollSnap] = useState18(null);
25104
25192
  const [navDropdownPreviewOpen, setNavDropdownPreviewOpen] = useState18(null);
25105
25193
  const [footerHeadingVisible, setFooterHeadingVisible] = useState18(null);
25106
25194
  const footerDragRef = useRef14(null);
@@ -25126,6 +25214,21 @@ function OhhwellsBridge() {
25126
25214
  const stylesRef = useRef14("");
25127
25215
  const appliedDrawerRef = useRef14(null);
25128
25216
  const pendingDeleteUndoRef = useRef14(null);
25217
+ const [floatingPanel, setFloatingPanel] = useState18(null);
25218
+ const floatingPanelOpenRef = useRef14(false);
25219
+ const floatingPanelRef = useRef14(null);
25220
+ floatingPanelRef.current = floatingPanel;
25221
+ const setFloatingPanelRef = useRef14(setFloatingPanel);
25222
+ const [floatingPanelPos, setFloatingPanelPos] = useState18(null);
25223
+ const buttonCollectionDragRef = useRef14(null);
25224
+ const buttonDropInsertIndexRef = useRef14(null);
25225
+ const [buttonDropSlots, setButtonDropSlots] = useState18([]);
25226
+ const [activeButtonDropIndex, setActiveButtonDropIndex] = useState18(null);
25227
+ const buttonPointerDragRef = useRef14(null);
25228
+ const [buttonStyleTick, setButtonStyleTick] = useState18(0);
25229
+ const [logoSizeDraft, setLogoSizeDraft] = useState18(null);
25230
+ const [editorViewport, setEditorViewport] = useState18("desktop");
25231
+ const [parentScrollSnap, setParentScrollSnap] = useState18(null);
25129
25232
  const [sitePages, setSitePages] = useState18([]);
25130
25233
  const [sectionsByPath, setSectionsByPath] = useState18({});
25131
25234
  const sectionsPrefetchGenRef = useRef14(0);
@@ -25134,7 +25237,18 @@ function OhhwellsBridge() {
25134
25237
  const linkPopoverOpenRef = useRef14(false);
25135
25238
  const linkPopoverGraceUntilRef = useRef14(0);
25136
25239
  setLinkPopoverRef.current = setLinkPopover;
25240
+ setFloatingPanelRef.current = setFloatingPanel;
25137
25241
  linkPopoverSessionRef.current = linkPopover;
25242
+ floatingPanelOpenRef.current = Boolean(floatingPanel);
25243
+ useEffect17(() => {
25244
+ const syncViewport = () => {
25245
+ const next = window.innerWidth <= 480 ? "mobile" : "desktop";
25246
+ setEditorViewport((prev) => prev === next ? prev : next);
25247
+ };
25248
+ syncViewport();
25249
+ window.addEventListener("resize", syncViewport);
25250
+ return () => window.removeEventListener("resize", syncViewport);
25251
+ }, []);
25138
25252
  useEffect17(() => {
25139
25253
  setSectionOrderPersistHandler((entries) => {
25140
25254
  const currentPath = window.location.pathname;
@@ -26813,6 +26927,7 @@ function OhhwellsBridge() {
26813
26927
  applyAiFooterFromContent(content);
26814
26928
  if (typeof content[STYLE_STORE_KEY] === "string") {
26815
26929
  stylesRef.current = content[STYLE_STORE_KEY];
26930
+ document.querySelectorAll('[data-ohw-editable="form"]').forEach((f) => markFormFields(f));
26816
26931
  applyStylesToDom(parseStyleStore(content[STYLE_STORE_KEY]));
26817
26932
  }
26818
26933
  applyBrandChrome(content);
@@ -26823,12 +26938,12 @@ function OhhwellsBridge() {
26823
26938
  if (key === "__ohw_sections") continue;
26824
26939
  if (key === AI_SECTIONS_KEY) continue;
26825
26940
  if (key === AI_FOOTER_KEY) continue;
26941
+ if (key === LOGO_PLACEHOLDER_KEY) continue;
26942
+ if (LOGO_IMAGE_KEYS.includes(key)) continue;
26826
26943
  if (key === BRAND_KIT_KEY) continue;
26827
26944
  if (key === STYLE_STORE_KEY) continue;
26828
26945
  if (BRAND_CHROME_KEYS.has(key)) continue;
26829
- if (key === LOGO_PLACEHOLDER_KEY) continue;
26830
26946
  if (key === CUSTOM_CODE_AFTER_HEAD_KEY || key === CUSTOM_CODE_BEFORE_HEAD_KEY) continue;
26831
- if (LOGO_IMAGE_KEYS.includes(key)) continue;
26832
26947
  if (typeof content[AI_FOOTER_KEY] === "string" && content[AI_FOOTER_KEY] && isAiFooterOwnedContentKey(key)) {
26833
26948
  continue;
26834
26949
  }
@@ -27043,17 +27158,18 @@ function OhhwellsBridge() {
27043
27158
  }
27044
27159
  applyAiFooterFromContent(content);
27045
27160
  if (typeof content[STYLE_STORE_KEY] === "string") {
27161
+ document.querySelectorAll('[data-ohw-editable="form"]').forEach((f) => markFormFields(f));
27046
27162
  applyStylesToDom(parseStyleStore(content[STYLE_STORE_KEY]));
27047
27163
  }
27048
27164
  for (const [key, val] of Object.entries(content)) {
27049
27165
  if (key === "__ohw_sections") continue;
27050
27166
  if (key === AI_SECTIONS_KEY) continue;
27051
27167
  if (key === AI_FOOTER_KEY) continue;
27168
+ if (key === LOGO_PLACEHOLDER_KEY) continue;
27169
+ if (LOGO_IMAGE_KEYS.includes(key)) continue;
27052
27170
  if (key === BRAND_KIT_KEY) continue;
27053
27171
  if (key === STYLE_STORE_KEY) continue;
27054
27172
  if (BRAND_CHROME_KEYS.has(key)) continue;
27055
- if (key === LOGO_PLACEHOLDER_KEY) continue;
27056
- if (LOGO_IMAGE_KEYS.includes(key)) continue;
27057
27173
  if (typeof content[AI_FOOTER_KEY] === "string" && content[AI_FOOTER_KEY] && isAiFooterOwnedContentKey(key)) {
27058
27174
  continue;
27059
27175
  }
@@ -27183,7 +27299,7 @@ function OhhwellsBridge() {
27183
27299
  if (!subdomain) return {};
27184
27300
  return contentCache.get(subdomain) ?? {};
27185
27301
  };
27186
- const observeRoots = () => [document.querySelector("nav"), document.querySelector("footer")].filter(
27302
+ const observeRoots = () => [document.querySelector("nav"), getFooterRoot()].filter(
27187
27303
  (el) => Boolean(el)
27188
27304
  );
27189
27305
  const roots = observeRoots();
@@ -27310,26 +27426,11 @@ function OhhwellsBridge() {
27310
27426
  const t2 = setTimeout(measure, 500);
27311
27427
  const ro = new ResizeObserver(schedule);
27312
27428
  ro.observe(document.body);
27313
- let lastWidth = window.innerWidth;
27314
- let resizeTimers = [];
27315
- const clearResizeTimers = () => {
27316
- resizeTimers.forEach(clearTimeout);
27317
- resizeTimers = [];
27318
- };
27319
- const handleResize = () => {
27320
- if (window.innerWidth === lastWidth) return;
27321
- lastWidth = window.innerWidth;
27322
- clearResizeTimers();
27323
- resizeTimers = HEIGHT_SETTLE_DELAYS.map((delay) => setTimeout(measure, delay));
27324
- };
27325
- window.addEventListener("resize", handleResize);
27326
27429
  return () => {
27327
27430
  clearTimeout(t1);
27328
27431
  clearTimeout(t2);
27329
27432
  if (raf != null) cancelAnimationFrame(raf);
27330
27433
  ro.disconnect();
27331
- clearResizeTimers();
27332
- window.removeEventListener("resize", handleResize);
27333
27434
  };
27334
27435
  }, [pathname, isEditMode, postToParent2]);
27335
27436
  useEffect17(() => {
@@ -27624,6 +27725,13 @@ function OhhwellsBridge() {
27624
27725
  if (target.closest("[data-ohw-state-toggle]")) return;
27625
27726
  if (target.closest("[data-ohw-max-badge]")) return;
27626
27727
  if (isInsideLinkEditor(target)) return;
27728
+ if (isInsideFloatingPanel(target)) return;
27729
+ if (target.closest("[data-ohw-form-toolbar]")) return;
27730
+ if (target.closest(
27731
+ '[data-ohw-field-toolbar], [data-ohw-field-type-picker], [data-radix-popper-content-wrapper], [role="menu"], [data-slot="dropdown-menu-content"]'
27732
+ )) {
27733
+ return;
27734
+ }
27627
27735
  if (selectedMediaElRef.current && !selectedMediaElRef.current.contains(target) && !target.closest("[data-ohw-media-overlay], [data-ohw-edit-chrome], [data-ohw-item-interaction]")) {
27628
27736
  clearMediaSelectionRef.current();
27629
27737
  }
@@ -27644,17 +27752,10 @@ function OhhwellsBridge() {
27644
27752
  return;
27645
27753
  }
27646
27754
  }
27647
- if (isInsideFloatingPanel(target)) return;
27648
- if (target.closest("[data-ohw-form-toolbar]")) return;
27649
- if (target.closest(
27650
- '[data-ohw-field-toolbar], [data-ohw-field-type-picker], [data-radix-popper-content-wrapper], [role="menu"], [data-slot="dropdown-menu-content"]'
27651
- )) {
27652
- return;
27653
- }
27654
27755
  {
27655
27756
  const formEl = getFormElement(target);
27656
27757
  const onSuccessText = target.closest(`[${SUCCESS_TEXT_ATTR}]`);
27657
- if (formEl && formKeyOf(formEl) && !onSuccessText) {
27758
+ if (formEl && formKeyOf(formEl) && !onSuccessText && !isCtaButton(target)) {
27658
27759
  const fieldEl = getFieldWrapper(target);
27659
27760
  if (fieldEl) {
27660
27761
  const label = target.closest("label");
@@ -28030,6 +28131,9 @@ function OhhwellsBridge() {
28030
28131
  setHoveredItemRect(null);
28031
28132
  hoveredNavContainerRef.current = null;
28032
28133
  setHoveredNavContainerRect(null);
28134
+ siblingHintElRef.current = null;
28135
+ setSiblingHintRect(null);
28136
+ setSiblingHintRects([]);
28033
28137
  return;
28034
28138
  }
28035
28139
  {
@@ -28150,7 +28254,6 @@ function OhhwellsBridge() {
28150
28254
  hoveredNavContainerRef.current = null;
28151
28255
  setHoveredNavContainerRect(null);
28152
28256
  hoveredItemElRef.current = editable;
28153
- setHoveredItemRect(isSelectedForHoverRef.current(editable) ? null : editable.getBoundingClientRect());
28154
28257
  }
28155
28258
  }
28156
28259
  }
@@ -28860,7 +28963,11 @@ function OhhwellsBridge() {
28860
28963
  };
28861
28964
  const handleMouseMove = (e) => {
28862
28965
  const { clientX, clientY } = e;
28863
- if (pointOwnedByFloatingPanel(clientX, clientY)) return;
28966
+ if (pointOwnedByFloatingPanel(clientX, clientY)) {
28967
+ dismissImageHover();
28968
+ clearImageHover();
28969
+ return;
28970
+ }
28864
28971
  if (isNavDrawerOpen() && isPointOutsideOpenDrawer(clientX, clientY)) {
28865
28972
  document.querySelectorAll("[data-ohw-hovered]").forEach((el) => el.removeAttribute("data-ohw-hovered"));
28866
28973
  formHoverElRef.current = null;
@@ -28889,7 +28996,12 @@ function OhhwellsBridge() {
28889
28996
  setHoveredItemRect(null);
28890
28997
  hoveredNavContainerRef.current = null;
28891
28998
  setHoveredNavContainerRect(null);
28999
+ siblingHintElRef.current = null;
29000
+ setSiblingHintRect(null);
29001
+ setSiblingHintRects([]);
28892
29002
  dismissImageHover();
29003
+ clearImageHover();
29004
+ setSectionGap(null);
28893
29005
  return;
28894
29006
  }
28895
29007
  if (probeSocialsRowAt(clientX, clientY)) return;
@@ -28901,7 +29013,11 @@ function OhhwellsBridge() {
28901
29013
  if (e.data?.type !== "ow:pointer-sync") return;
28902
29014
  const { clientX, clientY } = e.data;
28903
29015
  if (typeof clientX !== "number" || typeof clientY !== "number") return;
28904
- if (pointOwnedByFloatingPanel(clientX, clientY)) return;
29016
+ if (pointOwnedByFloatingPanel(clientX, clientY)) {
29017
+ dismissImageHover();
29018
+ clearImageHover();
29019
+ return;
29020
+ }
28905
29021
  if (isNavDrawerOpen() && isPointOutsideOpenDrawer(clientX, clientY)) return;
28906
29022
  if (probeSocialsRowAt(clientX, clientY)) return;
28907
29023
  probeSectionGapAt(clientX, clientY);
@@ -29253,6 +29369,7 @@ function OhhwellsBridge() {
29253
29369
  applyAiFooterFromContent(content);
29254
29370
  if (typeof content[STYLE_STORE_KEY] === "string") {
29255
29371
  stylesRef.current = content[STYLE_STORE_KEY];
29372
+ document.querySelectorAll('[data-ohw-editable="form"]').forEach((f) => markFormFields(f));
29256
29373
  applyStylesToDom(parseStyleStore(content[STYLE_STORE_KEY]));
29257
29374
  }
29258
29375
  applyBrandChrome(content);
@@ -29267,12 +29384,12 @@ function OhhwellsBridge() {
29267
29384
  }
29268
29385
  if (key === AI_SECTIONS_KEY) continue;
29269
29386
  if (key === AI_FOOTER_KEY) continue;
29387
+ if (key === LOGO_PLACEHOLDER_KEY) continue;
29388
+ if (LOGO_IMAGE_KEYS.includes(key) && !String(val ?? "").trim()) continue;
29270
29389
  if (key === BRAND_KIT_KEY) continue;
29271
29390
  if (key === STYLE_STORE_KEY) continue;
29272
29391
  if (BRAND_CHROME_KEYS.has(key)) continue;
29273
- if (key === LOGO_PLACEHOLDER_KEY) continue;
29274
29392
  if (key === CUSTOM_CODE_AFTER_HEAD_KEY || key === CUSTOM_CODE_BEFORE_HEAD_KEY) continue;
29275
- if (LOGO_IMAGE_KEYS.includes(key) && !String(val ?? "").trim()) continue;
29276
29393
  if (typeof content[AI_FOOTER_KEY] === "string" && content[AI_FOOTER_KEY] && isAiFooterOwnedContentKey(key)) {
29277
29394
  continue;
29278
29395
  }
@@ -29547,6 +29664,7 @@ function OhhwellsBridge() {
29547
29664
  brandKitRef.current = value;
29548
29665
  applyBrandToDom(parseBrandKit(value));
29549
29666
  if (aiSectionsRef.current) applyAiSectionsToDom(parseAiSectionsState(aiSectionsRef.current));
29667
+ applyAiFooterFromContent(editContentRef.current);
29550
29668
  applyStylesToDom(parseStyleStore(stylesRef.current));
29551
29669
  postToParentRef.current({ type: "ow:change", nodes: [{ key: BRAND_KIT_KEY, text: value }] });
29552
29670
  postToParentRef.current({ type: "ow:ai-brand-applied", previous, value });
@@ -29809,6 +29927,12 @@ function OhhwellsBridge() {
29809
29927
  closeLinkPopoverRef.current();
29810
29928
  return;
29811
29929
  }
29930
+ if (floatingPanelOpenRef.current) {
29931
+ setFloatingPanelRef.current(null);
29932
+ deselectRef.current();
29933
+ deactivateRef.current();
29934
+ return;
29935
+ }
29812
29936
  deselectRef.current();
29813
29937
  deactivateRef.current();
29814
29938
  clearMediaSelectionRef.current();
@@ -30622,10 +30746,10 @@ function OhhwellsBridge() {
30622
30746
  window.removeEventListener("message", handleDeleteSection);
30623
30747
  window.removeEventListener("message", handleDuplicateSection);
30624
30748
  window.removeEventListener("message", handleDeactivate);
30625
- document.documentElement.removeAttribute("data-ohw-panel-dragging");
30626
30749
  window.removeEventListener("message", handleToastAction);
30627
30750
  window.removeEventListener("message", handleFormCount);
30628
30751
  window.removeEventListener("message", handleUiEscape);
30752
+ document.documentElement.removeAttribute("data-ohw-panel-dragging");
30629
30753
  autoSaveTimers.current.forEach(clearTimeout);
30630
30754
  autoSaveTimers.current.clear();
30631
30755
  if (imageUnhoverTimerRef.current) clearTimeout(imageUnhoverTimerRef.current);
@@ -30919,7 +31043,7 @@ function OhhwellsBridge() {
30919
31043
  postToParent2({
30920
31044
  type: "ow:ready",
30921
31045
  version: "1",
30922
- bridgeVersion: "0.1.137",
31046
+ bridgeVersion: "0.1.139",
30923
31047
  path: pathname,
30924
31048
  nodes: collectEditableNodes(editContentRef.current),
30925
31049
  sections