@ohhwells/bridge 0.1.132 → 0.1.134-next.362

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
@@ -2656,6 +2656,19 @@ function quotedFont(font) {
2656
2656
  const cleaned = font.replace(/["']/g, "").trim();
2657
2657
  return cleaned ? `"${cleaned}"` : "sans-serif";
2658
2658
  }
2659
+ function themeFont(role, fallback) {
2660
+ const quoted = quotedFont(fallback);
2661
+ return role === "heading" ? `var(--brand-font-heading, var(--font-heading, var(--font-display, ${quoted})))` : `var(--brand-font-body, var(--font-body, ${quoted}))`;
2662
+ }
2663
+ function withThemeFonts(brand) {
2664
+ return {
2665
+ ...brand,
2666
+ fonts: {
2667
+ heading: themeFont("heading", brand.fonts.heading),
2668
+ body: themeFont("body", brand.fonts.body)
2669
+ }
2670
+ };
2671
+ }
2659
2672
  function footerLogoStyle(fallbackFont, fallbackColor) {
2660
2673
  return {
2661
2674
  fontFamily: `var(--brand-font-heading, var(--font-heading, var(--font-display, ${quotedFont(fallbackFont)})))`,
@@ -2727,7 +2740,7 @@ var AI_MOBILE_CSS = [
2727
2740
  // page's first section. Top only (bottom needs no nav clearance); ~96px approximates the
2728
2741
  // template nav's own height (measured ~94px) — there's no shared `--ohw-header-h` var to read
2729
2742
  // an exact figure from (no live measurement exists), so this is a safe approximation, not a
2730
- // pixel-exact fit. Horizontal spacing already comes from `data-ai-container`.
2743
+ // pixel-exact fit. Horizontal spacing already comes from `data-ai-section-inner`.
2731
2744
  '[data-ai-overlay-clearance] [data-ai-overlap-layer="overlay"]{padding-top:96px}',
2732
2745
  // A full-bleed carousel's height is pure arithmetic: it spans the whole window, so 16/9 turns
2733
2746
  // window width straight into height. Measured at 16/9 it came out ~90% of the viewport at EVERY
@@ -2743,8 +2756,10 @@ var AI_MOBILE_CSS = [
2743
2756
  "[data-ai-section]{overflow-x:hidden}",
2744
2757
  // `:not([data-ai-full-bleed])`: this `!important` gutter beat the inline `padding: 0` that a
2745
2758
  // full-bleed section's container sets, so a carousel/media hero that is edge-to-edge on desktop
2746
- // still got a 20px band of section background down both sides on a phone.
2747
- "[data-ai-container]:not([data-ai-full-bleed]){padding:0 20px !important}",
2759
+ // still got a 20px band of section background down both sides on a phone. The footer's own
2760
+ // container keeps the unguarded rule below — `data-ai-full-bleed` can never appear on it.
2761
+ "[data-ai-section-inner]:not([data-ai-full-bleed]){padding:0 20px !important}",
2762
+ "[data-ai-container]{padding:0 20px !important}",
2748
2763
  "[data-ai-row]{display:flex !important;flex-direction:column !important;align-items:stretch !important}",
2749
2764
  "[data-ai-cell]{width:100%;min-width:0}",
2750
2765
  // The rule above turns every row into a COLUMN flex container on phones, which silently breaks
@@ -2945,6 +2960,31 @@ function pricingCardEmphasis(featured, ctx, hasBg) {
2945
2960
  function textAttrs(ctx, path) {
2946
2961
  return ctx.keyFor ? { "data-ohw-key": ctx.keyFor(path), "data-ohw-editable": "text" } : {};
2947
2962
  }
2963
+ var AI_RESPONSIVE_CSS = [
2964
+ "@media (max-width: 960px) {",
2965
+ " [data-ai-responsive] [data-ai-grid] { grid-template-columns: repeat(2, 1fr) !important; }",
2966
+ ' [data-ai-responsive] [data-ai-grid="1"] { grid-template-columns: 1fr !important; }',
2967
+ "}",
2968
+ "@media (max-width: 640px) {",
2969
+ " [data-ai-responsive] [data-ai-grid] { grid-template-columns: 1fr !important; }",
2970
+ " [data-ai-responsive] [data-ai-columns] > * { grid-column: 1 / -1 !important; }",
2971
+ // Group containers flatten to a column on phones; span placements come along for free.
2972
+ // `:not([data-ai-group="overlap"])` for the same reason as the twin pair in AI_MOBILE_CSS — see
2973
+ // the comment there. This sheet's copy is the narrower selector, so without the exemption it
2974
+ // flattens an `overlap` hero below 640px even though the other rule stands down: measured a 390px
2975
+ // viewport with the heading/CTA in flow *under* the image, 219px of section background where the
2976
+ // layered text used to be.
2977
+ ' [data-ai-responsive] [data-ai-group]:not([data-ai-group="overlap"]) { display: flex !important; flex-direction: column !important; }',
2978
+ ' [data-ai-responsive] [data-ai-group]:not([data-ai-group="overlap"]) > * { grid-column: auto !important; }',
2979
+ // `:not([data-ai-full-bleed])` for the same reason as the twin rule in AI_MOBILE_CSS: this one
2980
+ // is narrower (0,2,0 vs 0,1,0) and also `!important`, so without the guard it wins outright and
2981
+ // a full-bleed hero gets its 20px side band back below 640px only — the one width where the
2982
+ // other rule had already been taught to stand down.
2983
+ " [data-ai-responsive] [data-ai-section-inner]:not([data-ai-full-bleed]) { padding-left: 20px !important; padding-right: 20px !important; }",
2984
+ " [data-ai-responsive] { overflow-x: hidden; }",
2985
+ " [data-ai-responsive] img { max-width: 100%; }",
2986
+ "}"
2987
+ ].join("\n");
2948
2988
  var TRANSPARENT_PX = "data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7";
2949
2989
  function MediaBox({
2950
2990
  refValue,
@@ -2965,9 +3005,9 @@ function MediaBox({
2965
3005
  const url = refValue ? ctx.resolveMedia(refValue) : null;
2966
3006
  const isIcon = /^(lucide|simple):/.test(refValue);
2967
3007
  const aspectRatio = aspect && /^\d+:\d+$/.test(aspect) ? aspect.replace(":", " / ") : void 0;
2968
- const editAttrs = ctx.keyFor && editPath && !isIcon ? {
3008
+ const editAttrs = ctx.keyFor && editPath ? {
2969
3009
  "data-ohw-key": ctx.keyFor(editPath),
2970
- ...genericImageEdit ? { "data-ohw-editable": "image" } : {}
3010
+ ...isIcon ? { "data-ohw-editable": "icon" } : genericImageEdit ? { "data-ohw-editable": "image" } : {}
2971
3011
  } : {};
2972
3012
  if (isIcon) {
2973
3013
  const Icon = refValue.startsWith("lucide:") ? lucideByName(refValue.slice(7)) : null;
@@ -2975,6 +3015,7 @@ function MediaBox({
2975
3015
  "span",
2976
3016
  {
2977
3017
  "data-ai-icon": refValue,
3018
+ ...editAttrs,
2978
3019
  style: {
2979
3020
  display: "inline-flex",
2980
3021
  width: 48,
@@ -4159,18 +4200,25 @@ function FadeSlideshow({ node, ctx, path }) {
4159
4200
  (dir) => onInteract(stepViableSlide(index, failed, count, dir, loop)),
4160
4201
  [count, failed, index, loop, onInteract]
4161
4202
  );
4203
+ const prefetchRef = import_react.default.useRef(prefetch);
4204
+ import_react.default.useEffect(() => {
4205
+ prefetchRef.current = prefetch;
4206
+ });
4207
+ import_react.default.useEffect(() => {
4208
+ if (autoplay) hasInteractedRef.current = false;
4209
+ }, [autoplay]);
4162
4210
  import_react.default.useEffect(() => {
4163
4211
  if (!autoplay) return;
4164
4212
  const id = window.setInterval(() => {
4165
4213
  if (hasInteractedRef.current || hoveredRef.current) return;
4166
4214
  setIndex((i) => loop ? (i + 1) % count : Math.min(count - 1, i + 1));
4167
4215
  }, intervalMs);
4168
- const warmId = window.setTimeout(() => prefetch(1 % count), Math.min(1500, intervalMs / 2));
4216
+ const warmId = window.setTimeout(() => prefetchRef.current(1 % count), Math.min(1500, intervalMs / 2));
4169
4217
  return () => {
4170
4218
  window.clearInterval(id);
4171
4219
  window.clearTimeout(warmId);
4172
4220
  };
4173
- }, [autoplay, count, intervalMs, loop, prefetch]);
4221
+ }, [autoplay, count, intervalMs, loop]);
4174
4222
  const hasMovedRef = import_react.default.useRef(false);
4175
4223
  import_react.default.useEffect(() => {
4176
4224
  if (!hasMovedRef.current) {
@@ -4461,7 +4509,7 @@ function CollectionBlock({ node, ctx, path }) {
4461
4509
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
4462
4510
  "div",
4463
4511
  {
4464
- "data-ai-grid": "",
4512
+ "data-ai-grid": String(itemsPerRow),
4465
4513
  "data-ohw-pricing-collection": isPricing ? "" : void 0,
4466
4514
  style: {
4467
4515
  display: "grid",
@@ -4721,7 +4769,7 @@ function renderNode(node, ctx, path, ambientAlign) {
4721
4769
  },
4722
4770
  children: [
4723
4771
  i > 0 && ctx.heroOverlapFullBleed ? (
4724
- // The section's `data-ai-container` gave up its max-width/gutter so the base
4772
+ // The section's `data-ai-section-inner` gave up its max-width/gutter so the base
4725
4773
  // layer (this group's i===0 sibling) can span edge-to-edge — reintroduced here,
4726
4774
  // scoped to just the overlay text, so a heading/copy/CTA stack doesn't stretch
4727
4775
  // the full section width instead of reading at the usual content width.
@@ -5044,7 +5092,7 @@ function AiTreeRenderer({
5044
5092
  if (!isRenderableTree(tree)) {
5045
5093
  return null;
5046
5094
  }
5047
- const resolvedBrand = brand ?? AI_DEFAULT_BRAND;
5095
+ const resolvedBrand = withThemeFonts(brand ?? AI_DEFAULT_BRAND);
5048
5096
  const band = (tree.settings ?? {}).sectionBackground === "accent" ? accentBandContext(resolvedBrand) : null;
5049
5097
  const blockBrand = band?.brand ?? resolvedBrand;
5050
5098
  const placeholderMap = buildPlaceholderMap(tree);
@@ -5114,6 +5162,7 @@ function AiTreeRenderer({
5114
5162
  ...fillHeight ? { "data-ai-fill-height": "" } : {},
5115
5163
  ...needsOverlayClearance ? { "data-ai-overlay-clearance": "" } : {},
5116
5164
  ...bgAttrs,
5165
+ "data-ai-responsive": "",
5117
5166
  style: {
5118
5167
  position: "relative",
5119
5168
  padding: `${padCss} 0`,
@@ -5127,12 +5176,13 @@ function AiTreeRenderer({
5127
5176
  ...fillHeight ? { display: "grid" } : {}
5128
5177
  },
5129
5178
  children: [
5130
- isOverlay && backgroundUrl && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { position: "absolute", inset: 0, background: `rgba(255,255,255,${overlayAlpha})` } }),
5179
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("style", { children: AI_RESPONSIVE_CSS }),
5131
5180
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("style", { children: AI_MOBILE_CSS }),
5181
+ isOverlay && backgroundUrl && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { position: "absolute", inset: 0, background: `rgba(255,255,255,${overlayAlpha})` } }),
5132
5182
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
5133
5183
  "div",
5134
5184
  {
5135
- "data-ai-container": "",
5185
+ "data-ai-section-inner": "",
5136
5186
  ...fullBleedMedia ? { "data-ai-full-bleed": "" } : {},
5137
5187
  style: {
5138
5188
  position: "relative",
@@ -5149,7 +5199,7 @@ function AiTreeRenderer({
5149
5199
  children: tree.rows.map((row, r2) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
5150
5200
  "div",
5151
5201
  {
5152
- "data-ai-row": "",
5202
+ "data-ai-columns": "",
5153
5203
  style: {
5154
5204
  display: "grid",
5155
5205
  gridTemplateColumns: "repeat(12, 1fr)",
@@ -5200,7 +5250,7 @@ function AiFooterRenderer({
5200
5250
  if (!isRenderableFooterTree(tree)) {
5201
5251
  return null;
5202
5252
  }
5203
- const resolvedBrand = brand ?? AI_DEFAULT_BRAND;
5253
+ const resolvedBrand = withThemeFonts(brand ?? AI_DEFAULT_BRAND);
5204
5254
  const bandBg = surface?.background ?? resolvedBrand.palette.dark;
5205
5255
  const bandText = surface?.text ?? resolvedBrand.palette.light;
5206
5256
  const onDark = surface ? isDarkColor(surface.background) : true;
@@ -5805,9 +5855,10 @@ function applyAiSectionsToDom(state, options) {
5805
5855
  if (existing && existing.serialized === serialized && existing.container.isConnected) {
5806
5856
  continue;
5807
5857
  }
5808
- const isNewMount = !existing || !existing.container.isConnected;
5809
5858
  let container = existing?.container;
5859
+ let isNewMount = false;
5810
5860
  if (!container || !container.isConnected) {
5861
+ isNewMount = true;
5811
5862
  existing?.root.unmount();
5812
5863
  container?.remove();
5813
5864
  container = document.createElement("div");
@@ -9023,7 +9074,7 @@ var isLabelWeight = (label) => label === "number" || label === "weight";
9023
9074
  var isLabelShadow = (label) => label === "shadow";
9024
9075
  var getDefaultConfig = () => {
9025
9076
  const themeColor = fromTheme("color");
9026
- const themeFont = fromTheme("font");
9077
+ const themeFont2 = fromTheme("font");
9027
9078
  const themeText = fromTheme("text");
9028
9079
  const themeFontWeight = fromTheme("font-weight");
9029
9080
  const themeTracking = fromTheme("tracking");
@@ -9945,7 +9996,7 @@ var getDefaultConfig = () => {
9945
9996
  * @see https://tailwindcss.com/docs/font-family
9946
9997
  */
9947
9998
  "font-family": [{
9948
- font: [isArbitraryVariableFamilyName, isArbitraryFamilyName, themeFont]
9999
+ font: [isArbitraryVariableFamilyName, isArbitraryFamilyName, themeFont2]
9949
10000
  }],
9950
10001
  /**
9951
10002
  * Font Feature Settings
@@ -13375,17 +13426,9 @@ var Button = React7.forwardRef(
13375
13426
  );
13376
13427
  Button.displayName = "Button";
13377
13428
 
13378
- // src/ui/MediaOverlay.tsx
13379
- var import_jsx_runtime16 = require("react/jsx-runtime");
13429
+ // src/ui/media-skeleton.ts
13380
13430
  var MEDIA_UPLOAD_FADE_MS = 300;
13381
- var VIDEO_SETTINGS_BAR_INSET = 8;
13382
- var OVERLAY_BUTTON_STYLE = {
13383
- pointerEvents: "auto",
13384
- fontFamily: "Inter, sans-serif",
13385
- fontSize: 12,
13386
- color: "#000"
13387
- };
13388
- var SKELETON_CSS = `
13431
+ var MEDIA_SKELETON_CSS = `
13389
13432
  @keyframes ohw-media-shimmer {
13390
13433
  0% { transform: translateX(-100%); }
13391
13434
  100% { transform: translateX(100%); }
@@ -13402,6 +13445,16 @@ var SKELETON_CSS = `
13402
13445
  animation: ohw-media-shimmer 1.4s infinite;
13403
13446
  }
13404
13447
  `;
13448
+
13449
+ // src/ui/MediaOverlay.tsx
13450
+ var import_jsx_runtime16 = require("react/jsx-runtime");
13451
+ var VIDEO_SETTINGS_BAR_INSET = 8;
13452
+ var OVERLAY_BUTTON_STYLE = {
13453
+ pointerEvents: "auto",
13454
+ fontFamily: "Inter, sans-serif",
13455
+ fontSize: 12,
13456
+ color: "#000"
13457
+ };
13405
13458
  var REPLACE_BUTTON_INSET = 12;
13406
13459
  function replaceButtonMode(image, button) {
13407
13460
  if (!button.fullWidth || !button.height) return "none";
@@ -13440,6 +13493,7 @@ function MediaOverlay({
13440
13493
  (prev) => prev && prev.fullWidth === width && prev.height === height ? prev : { fullWidth: width, height }
13441
13494
  );
13442
13495
  }, [isVideo]);
13496
+ const replaceLabel = isVideo ? "Replace video" : hover.elementType === "bg-image" ? "Replace background" : "Replace image";
13443
13497
  const replaceMode = buttonSize ? replaceButtonMode({ width: rect.width, height: rect.height }, buttonSize) : "none";
13444
13498
  const box = {
13445
13499
  position: "fixed",
@@ -13469,7 +13523,7 @@ function MediaOverlay({
13469
13523
  "data-ohw-media-skeleton": "",
13470
13524
  "aria-hidden": true,
13471
13525
  style: { ...box, pointerEvents: "none" },
13472
- children: /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("style", { children: SKELETON_CSS })
13526
+ children: /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("style", { children: MEDIA_SKELETON_CSS })
13473
13527
  }
13474
13528
  );
13475
13529
  }
@@ -13538,11 +13592,10 @@ function MediaOverlay({
13538
13592
  className: "flex items-center justify-center cursor-pointer",
13539
13593
  style: {
13540
13594
  ...box,
13541
- // When text overlays this media, let clicks fall THROUGH to the text underneath. In the
13542
- // parent this needed a whole `ow:click-at` round-trip to re-hit-test inside the iframe;
13543
- // in-document, pointer-events does it natively. The button below opts back in, so
13544
- // Replace still works.
13545
- pointerEvents: hover.hasTextOverlap || cueOnly ? "none" : "auto",
13595
+ // Visual only. `pointer-events: auto` on this full-bleed rect covered the navbar
13596
+ // (hero bg includes the absolute TopNav) and swallowed CTA press-drag. Text overlay
13597
+ // and cueOnly also need clicks to fall through — Replace / video controls opt back in.
13598
+ pointerEvents: "none",
13546
13599
  // Selected: a firm component ring with no wash, so the image reads as chosen rather
13547
13600
  // than hovered. Hover keeps the existing tinted preview.
13548
13601
  boxShadow: selected ? "inset 0 0 0 2px var(--color-bridge-primary)" : "inset 0 0 0 1.5px var(--color-bridge-primary)",
@@ -13569,17 +13622,17 @@ function MediaOverlay({
13569
13622
  },
13570
13623
  children: [
13571
13624
  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 }),
13572
- isVideo ? "Replace video" : "Replace image"
13625
+ replaceLabel
13573
13626
  ]
13574
13627
  }
13575
13628
  ),
13576
- replaceMode === "none" || cueOnly ? null : /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(
13629
+ showChrome && replaceMode !== "none" && /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(
13577
13630
  Button,
13578
13631
  {
13579
13632
  "data-ohw-media-overlay": "",
13580
13633
  variant: "outline",
13581
13634
  size: "sm",
13582
- "aria-label": isVideo ? "Replace video" : "Replace image",
13635
+ "aria-label": replaceLabel,
13583
13636
  className: "gap-1.5 cursor-pointer hover:bg-background",
13584
13637
  style: {
13585
13638
  ...OVERLAY_BUTTON_STYLE,
@@ -13602,7 +13655,7 @@ function MediaOverlay({
13602
13655
  },
13603
13656
  children: [
13604
13657
  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 }),
13605
- replaceMode === "full" ? isVideo ? "Replace video" : hover.elementType === "bg-image" ? "Replace background" : "Replace image" : null
13658
+ replaceMode === "full" ? replaceLabel : null
13606
13659
  ]
13607
13660
  }
13608
13661
  )
@@ -15899,6 +15952,17 @@ function setCarouselSlideAsset(tree, index, assetId) {
15899
15952
  return true;
15900
15953
  });
15901
15954
  }
15955
+ function carouselAutoplay(tree) {
15956
+ return findCarouselBlock(tree)?.slots?.autoplay === true;
15957
+ }
15958
+ function setCarouselAutoplay(tree, on) {
15959
+ return withCarousel(tree, (block) => {
15960
+ const slots = { ...block.slots, autoplay: on };
15961
+ if (!on) delete slots.interval;
15962
+ block.slots = slots;
15963
+ return true;
15964
+ });
15965
+ }
15902
15966
 
15903
15967
  // src/ui/ai-section/AiFadeCarouselOverlay.tsx
15904
15968
  var import_jsx_runtime22 = require("react/jsx-runtime");
@@ -16012,10 +16076,85 @@ function Pill4({ label, onClick, disabled }) {
16012
16076
  }
16013
16077
  );
16014
16078
  }
16079
+ function TogglePill({
16080
+ label,
16081
+ on,
16082
+ onToggle,
16083
+ disabled
16084
+ }) {
16085
+ return /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(
16086
+ "button",
16087
+ {
16088
+ type: "button",
16089
+ role: "switch",
16090
+ "aria-checked": on,
16091
+ onClick: onToggle,
16092
+ disabled,
16093
+ title: on ? `${label} on \u2014 click to turn off` : `${label} off \u2014 click to turn on`,
16094
+ style: {
16095
+ height: 28,
16096
+ padding: "0 8px 0 10px",
16097
+ display: "inline-flex",
16098
+ alignItems: "center",
16099
+ gap: 8,
16100
+ borderRadius: 999,
16101
+ border: "none",
16102
+ cursor: disabled ? "default" : "pointer",
16103
+ background: "#ffffff",
16104
+ color: OHW_BLUE_DARK4,
16105
+ opacity: disabled ? 0.4 : 1,
16106
+ fontSize: 12,
16107
+ lineHeight: "16px",
16108
+ fontFamily: "Figtree, ui-sans-serif, system-ui, sans-serif",
16109
+ whiteSpace: "nowrap",
16110
+ flex: "0 0 auto"
16111
+ },
16112
+ children: [
16113
+ label,
16114
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
16115
+ "span",
16116
+ {
16117
+ "aria-hidden": true,
16118
+ style: {
16119
+ position: "relative",
16120
+ width: 26,
16121
+ height: 15,
16122
+ borderRadius: 999,
16123
+ flex: "0 0 auto",
16124
+ // Off is a grey track, not a washed-out blue: the pill's own white background already
16125
+ // carries the brand colour in the text, and a pale blue track next to it read as
16126
+ // "disabled control" rather than "switch that is off".
16127
+ background: on ? OHW_BLUE_DARK4 : "#CBD5E1",
16128
+ transition: "background 120ms ease"
16129
+ },
16130
+ children: /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
16131
+ "span",
16132
+ {
16133
+ style: {
16134
+ position: "absolute",
16135
+ top: 2,
16136
+ left: on ? 13 : 2,
16137
+ width: 11,
16138
+ height: 11,
16139
+ borderRadius: "50%",
16140
+ background: "#ffffff",
16141
+ boxShadow: "0 1px 2px rgba(0,0,0,0.3)",
16142
+ transition: "left 120ms ease"
16143
+ }
16144
+ }
16145
+ )
16146
+ }
16147
+ )
16148
+ ]
16149
+ }
16150
+ );
16151
+ }
16015
16152
  function CarouselSlideToolbar({
16016
16153
  pending,
16017
16154
  error,
16018
- onChangeImage
16155
+ autoplay,
16156
+ onChangeImage,
16157
+ onToggleAutoplay
16019
16158
  }) {
16020
16159
  return /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(
16021
16160
  "div",
@@ -16032,11 +16171,18 @@ function CarouselSlideToolbar({
16032
16171
  padding: 8,
16033
16172
  borderRadius: 12,
16034
16173
  boxShadow: "0 8px 24px rgba(0,0,0,0.25)",
16035
- pointerEvents: pending ? "none" : "auto",
16036
- opacity: pending ? 0.6 : 1
16174
+ // Not dimmed while pending, only inert. The toolbar used to drop to 0.6 opacity for the
16175
+ // length of the validate round-trip, which on a local backend is short enough that the
16176
+ // autoplay toggle just flashed — a dim and an un-dim with nothing in between. Progress is
16177
+ // reported where the change is: the switch moves at once (optimistically), and an image
16178
+ // swap raises the skeleton over the slide.
16179
+ pointerEvents: pending ? "none" : "auto"
16037
16180
  },
16038
16181
  children: [
16039
- /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(Pill4, { label: "Change Image", onClick: onChangeImage }),
16182
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("div", { style: { display: "flex", gap: 6, alignItems: "center" }, children: [
16183
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(Pill4, { label: "Change Image", onClick: onChangeImage }),
16184
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(TogglePill, { label: "Autoplay", on: autoplay, onToggle: onToggleAutoplay })
16185
+ ] }),
16040
16186
  error && /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("div", { style: { color: "#FCA5A5", fontSize: 11, maxWidth: 220, textAlign: "center" }, children: error })
16041
16187
  ]
16042
16188
  }
@@ -16052,8 +16198,24 @@ function AiFadeCarouselOverlay({
16052
16198
  const [active, setActive] = (0, import_react12.useState)(null);
16053
16199
  const [pending, setPending] = (0, import_react12.useState)(false);
16054
16200
  const [error, setError] = (0, import_react12.useState)(null);
16201
+ const [autoplayOverride, setAutoplayOverride] = (0, import_react12.useState)(null);
16202
+ const [swapping, setSwapping] = (0, import_react12.useState)(null);
16203
+ const skeletonRef = (0, import_react12.useRef)(null);
16055
16204
  const activeRef = (0, import_react12.useRef)(null);
16056
16205
  activeRef.current = active;
16206
+ (0, import_react12.useEffect)(() => {
16207
+ setAutoplayOverride(null);
16208
+ }, [active?.sectionId, active?.index]);
16209
+ (0, import_react12.useEffect)(() => {
16210
+ if (!swapping?.fadingOut || !skeletonRef.current) return;
16211
+ const anim = skeletonRef.current.animate([{ opacity: 1 }, { opacity: 0 }], {
16212
+ duration: MEDIA_UPLOAD_FADE_MS,
16213
+ easing: "ease-out",
16214
+ fill: "forwards"
16215
+ });
16216
+ anim.finished.then(() => setSwapping(null)).catch(() => setSwapping(null));
16217
+ return () => anim.cancel();
16218
+ }, [swapping?.fadingOut]);
16057
16219
  (0, import_react12.useEffect)(() => {
16058
16220
  const onMove = (e) => {
16059
16221
  const t = e.target;
@@ -16194,6 +16356,27 @@ function AiFadeCarouselOverlay({
16194
16356
  children: /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(import_lucide_react11.Star, { size: 14, style: { pointerEvents: "none" } })
16195
16357
  }
16196
16358
  ),
16359
+ swapping && /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
16360
+ "div",
16361
+ {
16362
+ ref: skeletonRef,
16363
+ "data-ohw-bridge": "",
16364
+ "data-ohw-media-skeleton": "",
16365
+ "aria-hidden": true,
16366
+ style: {
16367
+ position: "fixed",
16368
+ top: swapping.rect.top,
16369
+ left: swapping.rect.left,
16370
+ width: swapping.rect.width,
16371
+ height: swapping.rect.height,
16372
+ // Under the toolbar (2147483647) so its controls stay visible and clickable over the
16373
+ // slide being swapped, and inert so it cannot eat the click that dismisses them.
16374
+ zIndex: 2147483646,
16375
+ pointerEvents: "none"
16376
+ },
16377
+ children: /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("style", { children: MEDIA_SKELETON_CSS })
16378
+ }
16379
+ ),
16197
16380
  activeItemRect && active && activeEntry && /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
16198
16381
  ItemInteractionLayer,
16199
16382
  {
@@ -16204,14 +16387,36 @@ function AiFadeCarouselOverlay({
16204
16387
  {
16205
16388
  pending,
16206
16389
  error,
16390
+ autoplay: autoplayOverride ?? carouselAutoplay(activeEntry.tree),
16391
+ onToggleAutoplay: () => {
16392
+ const next = !(autoplayOverride ?? carouselAutoplay(activeEntry.tree));
16393
+ setAutoplayOverride(next);
16394
+ void runMutation(active.sectionId, (tree) => setCarouselAutoplay(tree, next)).then(
16395
+ (ok) => {
16396
+ if (!ok) setAutoplayOverride(null);
16397
+ }
16398
+ );
16399
+ },
16207
16400
  onChangeImage: () => {
16208
- void requestAssetPick2(postToParent2).then((picked) => {
16401
+ void requestAssetPick2(postToParent2).then(async (picked) => {
16209
16402
  if (!picked) return;
16210
- void runMutation(
16403
+ const slideRect = readRect5(active);
16404
+ if (slideRect) setSwapping({ rect: slideRect, fadingOut: false });
16405
+ const ok = await runMutation(
16211
16406
  active.sectionId,
16212
16407
  (tree) => setCarouselSlideAsset(tree, active.index, picked.assetId),
16213
16408
  { [picked.assetId]: picked.url }
16214
16409
  );
16410
+ if (!ok) {
16411
+ setSwapping(null);
16412
+ return;
16413
+ }
16414
+ const preload = new Image();
16415
+ const done = () => setSwapping((s) => s ? { ...s, fadingOut: true } : s);
16416
+ preload.onload = done;
16417
+ preload.onerror = done;
16418
+ preload.src = picked.url;
16419
+ if (preload.complete) done();
16215
16420
  });
16216
16421
  }
16217
16422
  }
@@ -19682,6 +19887,7 @@ function readLogoSizeState(content, placement) {
19682
19887
  function getLogoElement(el) {
19683
19888
  const marked = el.closest('[data-ohw-role="logo"], [data-ohw-logo]');
19684
19889
  if (marked) return marked;
19890
+ if (el.closest('[data-ohw-editable="icon"]')) return null;
19685
19891
  const root = el.closest("nav, [data-ohw-nav-root], footer");
19686
19892
  if (!root) return null;
19687
19893
  const anchor = el.closest("a");
@@ -23173,7 +23379,7 @@ function getNavigationSelectionParent(el) {
23173
23379
  if (!isFooterLinksContainer(el) && (el.hasAttribute("data-ohw-footer-col") || el.hasAttribute("data-ohw-footer-column") || isInferredFooterGroup2(el))) {
23174
23380
  return resolveFooterLinksContainerFrom(el);
23175
23381
  }
23176
- if (el.hasAttribute("data-ohw-nav-container") || el.hasAttribute("data-ohw-nav-drawer") || isFooterLinksContainer(el)) {
23382
+ 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)) {
23177
23383
  return getNavigationRoot(el);
23178
23384
  }
23179
23385
  return null;
@@ -23395,7 +23601,6 @@ var ICONS = {
23395
23601
  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"/>',
23396
23602
  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"/>'
23397
23603
  };
23398
- var HEIGHT_SETTLE_DELAYS = [150, 400, 800];
23399
23604
  var SELECTION_CHROME_GAP2 = 4;
23400
23605
  var TOOLBAR_STROKE_GAP2 = 4;
23401
23606
  var TOOLBAR_OFFSET_FROM_ELEMENT = SELECTION_CHROME_GAP2 + TOOLBAR_STROKE_GAP2;
@@ -24375,21 +24580,6 @@ function OhhwellsBridge() {
24375
24580
  const [isItemDragging, setIsItemDragging] = (0, import_react21.useState)(false);
24376
24581
  const [isFooterFrameSelection, setIsFooterFrameSelection] = (0, import_react21.useState)(false);
24377
24582
  isFooterFrameSelectionRef.current = isFooterFrameSelection;
24378
- const [floatingPanel, setFloatingPanel] = (0, import_react21.useState)(null);
24379
- const floatingPanelOpenRef = (0, import_react21.useRef)(false);
24380
- floatingPanelOpenRef.current = floatingPanel !== null;
24381
- const floatingPanelRef = (0, import_react21.useRef)(null);
24382
- floatingPanelRef.current = floatingPanel;
24383
- const [floatingPanelPos, setFloatingPanelPos] = (0, import_react21.useState)(null);
24384
- const buttonCollectionDragRef = (0, import_react21.useRef)(null);
24385
- const buttonDropInsertIndexRef = (0, import_react21.useRef)(null);
24386
- const [buttonDropSlots, setButtonDropSlots] = (0, import_react21.useState)([]);
24387
- const [activeButtonDropIndex, setActiveButtonDropIndex] = (0, import_react21.useState)(null);
24388
- const buttonPointerDragRef = (0, import_react21.useRef)(null);
24389
- const [buttonStyleTick, setButtonStyleTick] = (0, import_react21.useState)(0);
24390
- const [logoSizeDraft, setLogoSizeDraft] = (0, import_react21.useState)(null);
24391
- const [editorViewport, setEditorViewport] = (0, import_react21.useState)("desktop");
24392
- const [parentScrollSnap, setParentScrollSnap] = (0, import_react21.useState)(null);
24393
24583
  const [navDropdownPreviewOpen, setNavDropdownPreviewOpen] = (0, import_react21.useState)(null);
24394
24584
  const [footerHeadingVisible, setFooterHeadingVisible] = (0, import_react21.useState)(null);
24395
24585
  const footerDragRef = (0, import_react21.useRef)(null);
@@ -24415,6 +24605,21 @@ function OhhwellsBridge() {
24415
24605
  const stylesRef = (0, import_react21.useRef)("");
24416
24606
  const appliedDrawerRef = (0, import_react21.useRef)(null);
24417
24607
  const pendingDeleteUndoRef = (0, import_react21.useRef)(null);
24608
+ const [floatingPanel, setFloatingPanel] = (0, import_react21.useState)(null);
24609
+ const floatingPanelOpenRef = (0, import_react21.useRef)(false);
24610
+ const floatingPanelRef = (0, import_react21.useRef)(null);
24611
+ floatingPanelRef.current = floatingPanel;
24612
+ const setFloatingPanelRef = (0, import_react21.useRef)(setFloatingPanel);
24613
+ const [floatingPanelPos, setFloatingPanelPos] = (0, import_react21.useState)(null);
24614
+ const buttonCollectionDragRef = (0, import_react21.useRef)(null);
24615
+ const buttonDropInsertIndexRef = (0, import_react21.useRef)(null);
24616
+ const [buttonDropSlots, setButtonDropSlots] = (0, import_react21.useState)([]);
24617
+ const [activeButtonDropIndex, setActiveButtonDropIndex] = (0, import_react21.useState)(null);
24618
+ const buttonPointerDragRef = (0, import_react21.useRef)(null);
24619
+ const [buttonStyleTick, setButtonStyleTick] = (0, import_react21.useState)(0);
24620
+ const [logoSizeDraft, setLogoSizeDraft] = (0, import_react21.useState)(null);
24621
+ const [editorViewport, setEditorViewport] = (0, import_react21.useState)("desktop");
24622
+ const [parentScrollSnap, setParentScrollSnap] = (0, import_react21.useState)(null);
24418
24623
  const [sitePages, setSitePages] = (0, import_react21.useState)([]);
24419
24624
  const [sectionsByPath, setSectionsByPath] = (0, import_react21.useState)({});
24420
24625
  const sectionsPrefetchGenRef = (0, import_react21.useRef)(0);
@@ -24423,7 +24628,18 @@ function OhhwellsBridge() {
24423
24628
  const linkPopoverOpenRef = (0, import_react21.useRef)(false);
24424
24629
  const linkPopoverGraceUntilRef = (0, import_react21.useRef)(0);
24425
24630
  setLinkPopoverRef.current = setLinkPopover;
24631
+ setFloatingPanelRef.current = setFloatingPanel;
24426
24632
  linkPopoverSessionRef.current = linkPopover;
24633
+ floatingPanelOpenRef.current = Boolean(floatingPanel);
24634
+ (0, import_react21.useEffect)(() => {
24635
+ const syncViewport = () => {
24636
+ const next = window.innerWidth <= 480 ? "mobile" : "desktop";
24637
+ setEditorViewport((prev) => prev === next ? prev : next);
24638
+ };
24639
+ syncViewport();
24640
+ window.addEventListener("resize", syncViewport);
24641
+ return () => window.removeEventListener("resize", syncViewport);
24642
+ }, []);
24427
24643
  (0, import_react21.useEffect)(() => {
24428
24644
  setSectionOrderPersistHandler((entries) => {
24429
24645
  const currentPath = window.location.pathname;
@@ -26102,6 +26318,7 @@ function OhhwellsBridge() {
26102
26318
  applyAiFooterFromContent(content);
26103
26319
  if (typeof content[STYLE_STORE_KEY] === "string") {
26104
26320
  stylesRef.current = content[STYLE_STORE_KEY];
26321
+ document.querySelectorAll('[data-ohw-editable="form"]').forEach((f) => markFormFields(f));
26105
26322
  applyStylesToDom(parseStyleStore(content[STYLE_STORE_KEY]));
26106
26323
  }
26107
26324
  applyBrandChrome(content);
@@ -26111,12 +26328,12 @@ function OhhwellsBridge() {
26111
26328
  if (key === "__ohw_sections") continue;
26112
26329
  if (key === AI_SECTIONS_KEY) continue;
26113
26330
  if (key === AI_FOOTER_KEY) continue;
26331
+ if (key === LOGO_PLACEHOLDER_KEY) continue;
26332
+ if (LOGO_IMAGE_KEYS.includes(key)) continue;
26114
26333
  if (key === BRAND_KIT_KEY) continue;
26115
26334
  if (key === STYLE_STORE_KEY) continue;
26116
26335
  if (BRAND_CHROME_KEYS.has(key)) continue;
26117
- if (key === LOGO_PLACEHOLDER_KEY) continue;
26118
26336
  if (key === CUSTOM_CODE_AFTER_HEAD_KEY || key === CUSTOM_CODE_BEFORE_HEAD_KEY) continue;
26119
- if (LOGO_IMAGE_KEYS.includes(key)) continue;
26120
26337
  if (typeof content[AI_FOOTER_KEY] === "string" && content[AI_FOOTER_KEY] && isAiFooterOwnedContentKey(key)) {
26121
26338
  continue;
26122
26339
  }
@@ -26330,17 +26547,18 @@ function OhhwellsBridge() {
26330
26547
  }
26331
26548
  applyAiFooterFromContent(content);
26332
26549
  if (typeof content[STYLE_STORE_KEY] === "string") {
26550
+ document.querySelectorAll('[data-ohw-editable="form"]').forEach((f) => markFormFields(f));
26333
26551
  applyStylesToDom(parseStyleStore(content[STYLE_STORE_KEY]));
26334
26552
  }
26335
26553
  for (const [key, val] of Object.entries(content)) {
26336
26554
  if (key === "__ohw_sections") continue;
26337
26555
  if (key === AI_SECTIONS_KEY) continue;
26338
26556
  if (key === AI_FOOTER_KEY) continue;
26557
+ if (key === LOGO_PLACEHOLDER_KEY) continue;
26558
+ if (LOGO_IMAGE_KEYS.includes(key)) continue;
26339
26559
  if (key === BRAND_KIT_KEY) continue;
26340
26560
  if (key === STYLE_STORE_KEY) continue;
26341
26561
  if (BRAND_CHROME_KEYS.has(key)) continue;
26342
- if (key === LOGO_PLACEHOLDER_KEY) continue;
26343
- if (LOGO_IMAGE_KEYS.includes(key)) continue;
26344
26562
  if (typeof content[AI_FOOTER_KEY] === "string" && content[AI_FOOTER_KEY] && isAiFooterOwnedContentKey(key)) {
26345
26563
  continue;
26346
26564
  }
@@ -26596,26 +26814,11 @@ function OhhwellsBridge() {
26596
26814
  const t2 = setTimeout(measure, 500);
26597
26815
  const ro = new ResizeObserver(schedule);
26598
26816
  ro.observe(document.body);
26599
- let lastWidth = window.innerWidth;
26600
- let resizeTimers = [];
26601
- const clearResizeTimers = () => {
26602
- resizeTimers.forEach(clearTimeout);
26603
- resizeTimers = [];
26604
- };
26605
- const handleResize = () => {
26606
- if (window.innerWidth === lastWidth) return;
26607
- lastWidth = window.innerWidth;
26608
- clearResizeTimers();
26609
- resizeTimers = HEIGHT_SETTLE_DELAYS.map((delay) => setTimeout(measure, delay));
26610
- };
26611
- window.addEventListener("resize", handleResize);
26612
26817
  return () => {
26613
26818
  clearTimeout(t1);
26614
26819
  clearTimeout(t2);
26615
26820
  if (raf != null) cancelAnimationFrame(raf);
26616
26821
  ro.disconnect();
26617
- clearResizeTimers();
26618
- window.removeEventListener("resize", handleResize);
26619
26822
  };
26620
26823
  }, [pathname, isEditMode, postToParent2]);
26621
26824
  (0, import_react21.useEffect)(() => {
@@ -26910,6 +27113,13 @@ function OhhwellsBridge() {
26910
27113
  if (target.closest("[data-ohw-state-toggle]")) return;
26911
27114
  if (target.closest("[data-ohw-max-badge]")) return;
26912
27115
  if (isInsideLinkEditor(target)) return;
27116
+ if (isInsideFloatingPanel(target)) return;
27117
+ if (target.closest("[data-ohw-form-toolbar]")) return;
27118
+ if (target.closest(
27119
+ '[data-ohw-field-toolbar], [data-ohw-field-type-picker], [data-radix-popper-content-wrapper], [role="menu"], [data-slot="dropdown-menu-content"]'
27120
+ )) {
27121
+ return;
27122
+ }
26913
27123
  if (selectedMediaElRef.current && !selectedMediaElRef.current.contains(target) && !target.closest("[data-ohw-media-overlay], [data-ohw-edit-chrome], [data-ohw-item-interaction]")) {
26914
27124
  clearMediaSelectionRef.current();
26915
27125
  }
@@ -26930,13 +27140,6 @@ function OhhwellsBridge() {
26930
27140
  return;
26931
27141
  }
26932
27142
  }
26933
- if (isInsideFloatingPanel(target)) return;
26934
- if (target.closest("[data-ohw-form-toolbar]")) return;
26935
- if (target.closest(
26936
- '[data-ohw-field-toolbar], [data-ohw-field-type-picker], [data-radix-popper-content-wrapper], [role="menu"], [data-slot="dropdown-menu-content"]'
26937
- )) {
26938
- return;
26939
- }
26940
27143
  {
26941
27144
  const formEl = getFormElement(target);
26942
27145
  const onSuccessText = target.closest(`[${SUCCESS_TEXT_ATTR}]`);
@@ -27316,6 +27519,9 @@ function OhhwellsBridge() {
27316
27519
  setHoveredItemRect(null);
27317
27520
  hoveredNavContainerRef.current = null;
27318
27521
  setHoveredNavContainerRect(null);
27522
+ siblingHintElRef.current = null;
27523
+ setSiblingHintRect(null);
27524
+ setSiblingHintRects([]);
27319
27525
  return;
27320
27526
  }
27321
27527
  {
@@ -27436,7 +27642,6 @@ function OhhwellsBridge() {
27436
27642
  hoveredNavContainerRef.current = null;
27437
27643
  setHoveredNavContainerRect(null);
27438
27644
  hoveredItemElRef.current = editable;
27439
- setHoveredItemRect(isSelectedForHoverRef.current(editable) ? null : editable.getBoundingClientRect());
27440
27645
  }
27441
27646
  }
27442
27647
  }
@@ -28146,7 +28351,11 @@ function OhhwellsBridge() {
28146
28351
  };
28147
28352
  const handleMouseMove = (e) => {
28148
28353
  const { clientX, clientY } = e;
28149
- if (pointOwnedByFloatingPanel(clientX, clientY)) return;
28354
+ if (pointOwnedByFloatingPanel(clientX, clientY)) {
28355
+ dismissImageHover();
28356
+ clearImageHover();
28357
+ return;
28358
+ }
28150
28359
  if (isNavDrawerOpen() && isPointOutsideOpenDrawer(clientX, clientY)) {
28151
28360
  document.querySelectorAll("[data-ohw-hovered]").forEach((el) => el.removeAttribute("data-ohw-hovered"));
28152
28361
  formHoverElRef.current = null;
@@ -28175,7 +28384,12 @@ function OhhwellsBridge() {
28175
28384
  setHoveredItemRect(null);
28176
28385
  hoveredNavContainerRef.current = null;
28177
28386
  setHoveredNavContainerRect(null);
28387
+ siblingHintElRef.current = null;
28388
+ setSiblingHintRect(null);
28389
+ setSiblingHintRects([]);
28178
28390
  dismissImageHover();
28391
+ clearImageHover();
28392
+ setSectionGap(null);
28179
28393
  return;
28180
28394
  }
28181
28395
  if (probeSocialsRowAt(clientX, clientY)) return;
@@ -28187,7 +28401,11 @@ function OhhwellsBridge() {
28187
28401
  if (e.data?.type !== "ow:pointer-sync") return;
28188
28402
  const { clientX, clientY } = e.data;
28189
28403
  if (typeof clientX !== "number" || typeof clientY !== "number") return;
28190
- if (pointOwnedByFloatingPanel(clientX, clientY)) return;
28404
+ if (pointOwnedByFloatingPanel(clientX, clientY)) {
28405
+ dismissImageHover();
28406
+ clearImageHover();
28407
+ return;
28408
+ }
28191
28409
  if (isNavDrawerOpen() && isPointOutsideOpenDrawer(clientX, clientY)) return;
28192
28410
  if (probeSocialsRowAt(clientX, clientY)) return;
28193
28411
  probeSectionGapAt(clientX, clientY);
@@ -28539,6 +28757,7 @@ function OhhwellsBridge() {
28539
28757
  applyAiFooterFromContent(content);
28540
28758
  if (typeof content[STYLE_STORE_KEY] === "string") {
28541
28759
  stylesRef.current = content[STYLE_STORE_KEY];
28760
+ document.querySelectorAll('[data-ohw-editable="form"]').forEach((f) => markFormFields(f));
28542
28761
  applyStylesToDom(parseStyleStore(content[STYLE_STORE_KEY]));
28543
28762
  }
28544
28763
  applyBrandChrome(content);
@@ -28552,12 +28771,12 @@ function OhhwellsBridge() {
28552
28771
  }
28553
28772
  if (key === AI_SECTIONS_KEY) continue;
28554
28773
  if (key === AI_FOOTER_KEY) continue;
28774
+ if (key === LOGO_PLACEHOLDER_KEY) continue;
28775
+ if (LOGO_IMAGE_KEYS.includes(key) && !String(val ?? "").trim()) continue;
28555
28776
  if (key === BRAND_KIT_KEY) continue;
28556
28777
  if (key === STYLE_STORE_KEY) continue;
28557
28778
  if (BRAND_CHROME_KEYS.has(key)) continue;
28558
- if (key === LOGO_PLACEHOLDER_KEY) continue;
28559
28779
  if (key === CUSTOM_CODE_AFTER_HEAD_KEY || key === CUSTOM_CODE_BEFORE_HEAD_KEY) continue;
28560
- if (LOGO_IMAGE_KEYS.includes(key) && !String(val ?? "").trim()) continue;
28561
28780
  if (typeof content[AI_FOOTER_KEY] === "string" && content[AI_FOOTER_KEY] && isAiFooterOwnedContentKey(key)) {
28562
28781
  continue;
28563
28782
  }
@@ -29094,6 +29313,12 @@ function OhhwellsBridge() {
29094
29313
  closeLinkPopoverRef.current();
29095
29314
  return;
29096
29315
  }
29316
+ if (floatingPanelOpenRef.current) {
29317
+ setFloatingPanelRef.current(null);
29318
+ deselectRef.current();
29319
+ deactivateRef.current();
29320
+ return;
29321
+ }
29097
29322
  deselectRef.current();
29098
29323
  deactivateRef.current();
29099
29324
  clearMediaSelectionRef.current();
@@ -29856,10 +30081,10 @@ function OhhwellsBridge() {
29856
30081
  window.removeEventListener("message", handleDeleteSection);
29857
30082
  window.removeEventListener("message", handleDuplicateSection);
29858
30083
  window.removeEventListener("message", handleDeactivate);
29859
- document.documentElement.removeAttribute("data-ohw-panel-dragging");
29860
30084
  window.removeEventListener("message", handleToastAction);
29861
30085
  window.removeEventListener("message", handleFormCount);
29862
30086
  window.removeEventListener("message", handleUiEscape);
30087
+ document.documentElement.removeAttribute("data-ohw-panel-dragging");
29863
30088
  autoSaveTimers.current.forEach(clearTimeout);
29864
30089
  autoSaveTimers.current.clear();
29865
30090
  if (imageUnhoverTimerRef.current) clearTimeout(imageUnhoverTimerRef.current);
@@ -30153,7 +30378,7 @@ function OhhwellsBridge() {
30153
30378
  postToParent2({
30154
30379
  type: "ow:ready",
30155
30380
  version: "1",
30156
- bridgeVersion: "0.1.130",
30381
+ bridgeVersion: "0.1.134",
30157
30382
  path: pathname,
30158
30383
  nodes: collectEditableNodes(editContentRef.current),
30159
30384
  sections