@ohhwells/bridge 0.1.131 → 0.1.132-next.358

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
@@ -2727,7 +2727,7 @@ var AI_MOBILE_CSS = [
2727
2727
  // page's first section. Top only (bottom needs no nav clearance); ~96px approximates the
2728
2728
  // template nav's own height (measured ~94px) — there's no shared `--ohw-header-h` var to read
2729
2729
  // 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`.
2730
+ // pixel-exact fit. Horizontal spacing already comes from `data-ai-section-inner`.
2731
2731
  '[data-ai-overlay-clearance] [data-ai-overlap-layer="overlay"]{padding-top:96px}',
2732
2732
  // A full-bleed carousel's height is pure arithmetic: it spans the whole window, so 16/9 turns
2733
2733
  // window width straight into height. Measured at 16/9 it came out ~90% of the viewport at EVERY
@@ -2743,8 +2743,10 @@ var AI_MOBILE_CSS = [
2743
2743
  "[data-ai-section]{overflow-x:hidden}",
2744
2744
  // `:not([data-ai-full-bleed])`: this `!important` gutter beat the inline `padding: 0` that a
2745
2745
  // 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}",
2746
+ // still got a 20px band of section background down both sides on a phone. The footer's own
2747
+ // container keeps the unguarded rule below — `data-ai-full-bleed` can never appear on it.
2748
+ "[data-ai-section-inner]:not([data-ai-full-bleed]){padding:0 20px !important}",
2749
+ "[data-ai-container]{padding:0 20px !important}",
2748
2750
  "[data-ai-row]{display:flex !important;flex-direction:column !important;align-items:stretch !important}",
2749
2751
  "[data-ai-cell]{width:100%;min-width:0}",
2750
2752
  // The rule above turns every row into a COLUMN flex container on phones, which silently breaks
@@ -2945,6 +2947,31 @@ function pricingCardEmphasis(featured, ctx, hasBg) {
2945
2947
  function textAttrs(ctx, path) {
2946
2948
  return ctx.keyFor ? { "data-ohw-key": ctx.keyFor(path), "data-ohw-editable": "text" } : {};
2947
2949
  }
2950
+ var AI_RESPONSIVE_CSS = [
2951
+ "@media (max-width: 960px) {",
2952
+ " [data-ai-responsive] [data-ai-grid] { grid-template-columns: repeat(2, 1fr) !important; }",
2953
+ ' [data-ai-responsive] [data-ai-grid="1"] { grid-template-columns: 1fr !important; }',
2954
+ "}",
2955
+ "@media (max-width: 640px) {",
2956
+ " [data-ai-responsive] [data-ai-grid] { grid-template-columns: 1fr !important; }",
2957
+ " [data-ai-responsive] [data-ai-columns] > * { grid-column: 1 / -1 !important; }",
2958
+ // Group containers flatten to a column on phones; span placements come along for free.
2959
+ // `:not([data-ai-group="overlap"])` for the same reason as the twin pair in AI_MOBILE_CSS — see
2960
+ // the comment there. This sheet's copy is the narrower selector, so without the exemption it
2961
+ // flattens an `overlap` hero below 640px even though the other rule stands down: measured a 390px
2962
+ // viewport with the heading/CTA in flow *under* the image, 219px of section background where the
2963
+ // layered text used to be.
2964
+ ' [data-ai-responsive] [data-ai-group]:not([data-ai-group="overlap"]) { display: flex !important; flex-direction: column !important; }',
2965
+ ' [data-ai-responsive] [data-ai-group]:not([data-ai-group="overlap"]) > * { grid-column: auto !important; }',
2966
+ // `:not([data-ai-full-bleed])` for the same reason as the twin rule in AI_MOBILE_CSS: this one
2967
+ // is narrower (0,2,0 vs 0,1,0) and also `!important`, so without the guard it wins outright and
2968
+ // a full-bleed hero gets its 20px side band back below 640px only — the one width where the
2969
+ // other rule had already been taught to stand down.
2970
+ " [data-ai-responsive] [data-ai-section-inner]:not([data-ai-full-bleed]) { padding-left: 20px !important; padding-right: 20px !important; }",
2971
+ " [data-ai-responsive] { overflow-x: hidden; }",
2972
+ " [data-ai-responsive] img { max-width: 100%; }",
2973
+ "}"
2974
+ ].join("\n");
2948
2975
  var TRANSPARENT_PX = "data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7";
2949
2976
  function MediaBox({
2950
2977
  refValue,
@@ -2965,9 +2992,9 @@ function MediaBox({
2965
2992
  const url = refValue ? ctx.resolveMedia(refValue) : null;
2966
2993
  const isIcon = /^(lucide|simple):/.test(refValue);
2967
2994
  const aspectRatio = aspect && /^\d+:\d+$/.test(aspect) ? aspect.replace(":", " / ") : void 0;
2968
- const editAttrs = ctx.keyFor && editPath && !isIcon ? {
2995
+ const editAttrs = ctx.keyFor && editPath ? {
2969
2996
  "data-ohw-key": ctx.keyFor(editPath),
2970
- ...genericImageEdit ? { "data-ohw-editable": "image" } : {}
2997
+ ...isIcon ? { "data-ohw-editable": "icon" } : genericImageEdit ? { "data-ohw-editable": "image" } : {}
2971
2998
  } : {};
2972
2999
  if (isIcon) {
2973
3000
  const Icon = refValue.startsWith("lucide:") ? lucideByName(refValue.slice(7)) : null;
@@ -2975,6 +3002,7 @@ function MediaBox({
2975
3002
  "span",
2976
3003
  {
2977
3004
  "data-ai-icon": refValue,
3005
+ ...editAttrs,
2978
3006
  style: {
2979
3007
  display: "inline-flex",
2980
3008
  width: 48,
@@ -4461,7 +4489,7 @@ function CollectionBlock({ node, ctx, path }) {
4461
4489
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
4462
4490
  "div",
4463
4491
  {
4464
- "data-ai-grid": "",
4492
+ "data-ai-grid": String(itemsPerRow),
4465
4493
  "data-ohw-pricing-collection": isPricing ? "" : void 0,
4466
4494
  style: {
4467
4495
  display: "grid",
@@ -4721,7 +4749,7 @@ function renderNode(node, ctx, path, ambientAlign) {
4721
4749
  },
4722
4750
  children: [
4723
4751
  i > 0 && ctx.heroOverlapFullBleed ? (
4724
- // The section's `data-ai-container` gave up its max-width/gutter so the base
4752
+ // The section's `data-ai-section-inner` gave up its max-width/gutter so the base
4725
4753
  // layer (this group's i===0 sibling) can span edge-to-edge — reintroduced here,
4726
4754
  // scoped to just the overlay text, so a heading/copy/CTA stack doesn't stretch
4727
4755
  // the full section width instead of reading at the usual content width.
@@ -5114,6 +5142,7 @@ function AiTreeRenderer({
5114
5142
  ...fillHeight ? { "data-ai-fill-height": "" } : {},
5115
5143
  ...needsOverlayClearance ? { "data-ai-overlay-clearance": "" } : {},
5116
5144
  ...bgAttrs,
5145
+ "data-ai-responsive": "",
5117
5146
  style: {
5118
5147
  position: "relative",
5119
5148
  padding: `${padCss} 0`,
@@ -5127,12 +5156,13 @@ function AiTreeRenderer({
5127
5156
  ...fillHeight ? { display: "grid" } : {}
5128
5157
  },
5129
5158
  children: [
5130
- isOverlay && backgroundUrl && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { position: "absolute", inset: 0, background: `rgba(255,255,255,${overlayAlpha})` } }),
5159
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("style", { children: AI_RESPONSIVE_CSS }),
5131
5160
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("style", { children: AI_MOBILE_CSS }),
5161
+ isOverlay && backgroundUrl && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { position: "absolute", inset: 0, background: `rgba(255,255,255,${overlayAlpha})` } }),
5132
5162
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
5133
5163
  "div",
5134
5164
  {
5135
- "data-ai-container": "",
5165
+ "data-ai-section-inner": "",
5136
5166
  ...fullBleedMedia ? { "data-ai-full-bleed": "" } : {},
5137
5167
  style: {
5138
5168
  position: "relative",
@@ -5149,7 +5179,7 @@ function AiTreeRenderer({
5149
5179
  children: tree.rows.map((row, r2) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
5150
5180
  "div",
5151
5181
  {
5152
- "data-ai-row": "",
5182
+ "data-ai-columns": "",
5153
5183
  style: {
5154
5184
  display: "grid",
5155
5185
  gridTemplateColumns: "repeat(12, 1fr)",
@@ -5805,9 +5835,10 @@ function applyAiSectionsToDom(state, options) {
5805
5835
  if (existing && existing.serialized === serialized && existing.container.isConnected) {
5806
5836
  continue;
5807
5837
  }
5808
- const isNewMount = !existing || !existing.container.isConnected;
5809
5838
  let container = existing?.container;
5839
+ let isNewMount = false;
5810
5840
  if (!container || !container.isConnected) {
5841
+ isNewMount = true;
5811
5842
  existing?.root.unmount();
5812
5843
  container?.remove();
5813
5844
  container = document.createElement("div");
@@ -13375,17 +13406,9 @@ var Button = React7.forwardRef(
13375
13406
  );
13376
13407
  Button.displayName = "Button";
13377
13408
 
13378
- // src/ui/MediaOverlay.tsx
13379
- var import_jsx_runtime16 = require("react/jsx-runtime");
13409
+ // src/ui/media-skeleton.ts
13380
13410
  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 = `
13411
+ var MEDIA_SKELETON_CSS = `
13389
13412
  @keyframes ohw-media-shimmer {
13390
13413
  0% { transform: translateX(-100%); }
13391
13414
  100% { transform: translateX(100%); }
@@ -13402,6 +13425,16 @@ var SKELETON_CSS = `
13402
13425
  animation: ohw-media-shimmer 1.4s infinite;
13403
13426
  }
13404
13427
  `;
13428
+
13429
+ // src/ui/MediaOverlay.tsx
13430
+ var import_jsx_runtime16 = require("react/jsx-runtime");
13431
+ var VIDEO_SETTINGS_BAR_INSET = 8;
13432
+ var OVERLAY_BUTTON_STYLE = {
13433
+ pointerEvents: "auto",
13434
+ fontFamily: "Inter, sans-serif",
13435
+ fontSize: 12,
13436
+ color: "#000"
13437
+ };
13405
13438
  var REPLACE_BUTTON_INSET = 12;
13406
13439
  function replaceButtonMode(image, button) {
13407
13440
  if (!button.fullWidth || !button.height) return "none";
@@ -13440,6 +13473,7 @@ function MediaOverlay({
13440
13473
  (prev) => prev && prev.fullWidth === width && prev.height === height ? prev : { fullWidth: width, height }
13441
13474
  );
13442
13475
  }, [isVideo]);
13476
+ const replaceLabel = isVideo ? "Replace video" : hover.elementType === "bg-image" ? "Replace background" : "Replace image";
13443
13477
  const replaceMode = buttonSize ? replaceButtonMode({ width: rect.width, height: rect.height }, buttonSize) : "none";
13444
13478
  const box = {
13445
13479
  position: "fixed",
@@ -13469,7 +13503,7 @@ function MediaOverlay({
13469
13503
  "data-ohw-media-skeleton": "",
13470
13504
  "aria-hidden": true,
13471
13505
  style: { ...box, pointerEvents: "none" },
13472
- children: /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("style", { children: SKELETON_CSS })
13506
+ children: /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("style", { children: MEDIA_SKELETON_CSS })
13473
13507
  }
13474
13508
  );
13475
13509
  }
@@ -13538,11 +13572,10 @@ function MediaOverlay({
13538
13572
  className: "flex items-center justify-center cursor-pointer",
13539
13573
  style: {
13540
13574
  ...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",
13575
+ // Visual only. `pointer-events: auto` on this full-bleed rect covered the navbar
13576
+ // (hero bg includes the absolute TopNav) and swallowed CTA press-drag. Text overlay
13577
+ // and cueOnly also need clicks to fall through — Replace / video controls opt back in.
13578
+ pointerEvents: "none",
13546
13579
  // Selected: a firm component ring with no wash, so the image reads as chosen rather
13547
13580
  // than hovered. Hover keeps the existing tinted preview.
13548
13581
  boxShadow: selected ? "inset 0 0 0 2px var(--color-bridge-primary)" : "inset 0 0 0 1.5px var(--color-bridge-primary)",
@@ -13569,17 +13602,17 @@ function MediaOverlay({
13569
13602
  },
13570
13603
  children: [
13571
13604
  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"
13605
+ replaceLabel
13573
13606
  ]
13574
13607
  }
13575
13608
  ),
13576
- replaceMode === "none" || cueOnly ? null : /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(
13609
+ showChrome && replaceMode !== "none" && /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(
13577
13610
  Button,
13578
13611
  {
13579
13612
  "data-ohw-media-overlay": "",
13580
13613
  variant: "outline",
13581
13614
  size: "sm",
13582
- "aria-label": isVideo ? "Replace video" : "Replace image",
13615
+ "aria-label": replaceLabel,
13583
13616
  className: "gap-1.5 cursor-pointer hover:bg-background",
13584
13617
  style: {
13585
13618
  ...OVERLAY_BUTTON_STYLE,
@@ -13602,7 +13635,7 @@ function MediaOverlay({
13602
13635
  },
13603
13636
  children: [
13604
13637
  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
13638
+ replaceMode === "full" ? replaceLabel : null
13606
13639
  ]
13607
13640
  }
13608
13641
  )
@@ -15899,6 +15932,17 @@ function setCarouselSlideAsset(tree, index, assetId) {
15899
15932
  return true;
15900
15933
  });
15901
15934
  }
15935
+ function carouselAutoplay(tree) {
15936
+ return findCarouselBlock(tree)?.slots?.autoplay === true;
15937
+ }
15938
+ function setCarouselAutoplay(tree, on) {
15939
+ return withCarousel(tree, (block) => {
15940
+ const slots = { ...block.slots, autoplay: on };
15941
+ if (!on) delete slots.interval;
15942
+ block.slots = slots;
15943
+ return true;
15944
+ });
15945
+ }
15902
15946
 
15903
15947
  // src/ui/ai-section/AiFadeCarouselOverlay.tsx
15904
15948
  var import_jsx_runtime22 = require("react/jsx-runtime");
@@ -16012,10 +16056,85 @@ function Pill4({ label, onClick, disabled }) {
16012
16056
  }
16013
16057
  );
16014
16058
  }
16059
+ function TogglePill({
16060
+ label,
16061
+ on,
16062
+ onToggle,
16063
+ disabled
16064
+ }) {
16065
+ return /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(
16066
+ "button",
16067
+ {
16068
+ type: "button",
16069
+ role: "switch",
16070
+ "aria-checked": on,
16071
+ onClick: onToggle,
16072
+ disabled,
16073
+ title: on ? `${label} on \u2014 click to turn off` : `${label} off \u2014 click to turn on`,
16074
+ style: {
16075
+ height: 28,
16076
+ padding: "0 8px 0 10px",
16077
+ display: "inline-flex",
16078
+ alignItems: "center",
16079
+ gap: 8,
16080
+ borderRadius: 999,
16081
+ border: "none",
16082
+ cursor: disabled ? "default" : "pointer",
16083
+ background: "#ffffff",
16084
+ color: OHW_BLUE_DARK4,
16085
+ opacity: disabled ? 0.4 : 1,
16086
+ fontSize: 12,
16087
+ lineHeight: "16px",
16088
+ fontFamily: "Figtree, ui-sans-serif, system-ui, sans-serif",
16089
+ whiteSpace: "nowrap",
16090
+ flex: "0 0 auto"
16091
+ },
16092
+ children: [
16093
+ label,
16094
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
16095
+ "span",
16096
+ {
16097
+ "aria-hidden": true,
16098
+ style: {
16099
+ position: "relative",
16100
+ width: 26,
16101
+ height: 15,
16102
+ borderRadius: 999,
16103
+ flex: "0 0 auto",
16104
+ // Off is a grey track, not a washed-out blue: the pill's own white background already
16105
+ // carries the brand colour in the text, and a pale blue track next to it read as
16106
+ // "disabled control" rather than "switch that is off".
16107
+ background: on ? OHW_BLUE_DARK4 : "#CBD5E1",
16108
+ transition: "background 120ms ease"
16109
+ },
16110
+ children: /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
16111
+ "span",
16112
+ {
16113
+ style: {
16114
+ position: "absolute",
16115
+ top: 2,
16116
+ left: on ? 13 : 2,
16117
+ width: 11,
16118
+ height: 11,
16119
+ borderRadius: "50%",
16120
+ background: "#ffffff",
16121
+ boxShadow: "0 1px 2px rgba(0,0,0,0.3)",
16122
+ transition: "left 120ms ease"
16123
+ }
16124
+ }
16125
+ )
16126
+ }
16127
+ )
16128
+ ]
16129
+ }
16130
+ );
16131
+ }
16015
16132
  function CarouselSlideToolbar({
16016
16133
  pending,
16017
16134
  error,
16018
- onChangeImage
16135
+ autoplay,
16136
+ onChangeImage,
16137
+ onToggleAutoplay
16019
16138
  }) {
16020
16139
  return /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(
16021
16140
  "div",
@@ -16032,11 +16151,18 @@ function CarouselSlideToolbar({
16032
16151
  padding: 8,
16033
16152
  borderRadius: 12,
16034
16153
  boxShadow: "0 8px 24px rgba(0,0,0,0.25)",
16035
- pointerEvents: pending ? "none" : "auto",
16036
- opacity: pending ? 0.6 : 1
16154
+ // Not dimmed while pending, only inert. The toolbar used to drop to 0.6 opacity for the
16155
+ // length of the validate round-trip, which on a local backend is short enough that the
16156
+ // autoplay toggle just flashed — a dim and an un-dim with nothing in between. Progress is
16157
+ // reported where the change is: the switch moves at once (optimistically), and an image
16158
+ // swap raises the skeleton over the slide.
16159
+ pointerEvents: pending ? "none" : "auto"
16037
16160
  },
16038
16161
  children: [
16039
- /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(Pill4, { label: "Change Image", onClick: onChangeImage }),
16162
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("div", { style: { display: "flex", gap: 6, alignItems: "center" }, children: [
16163
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(Pill4, { label: "Change Image", onClick: onChangeImage }),
16164
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(TogglePill, { label: "Autoplay", on: autoplay, onToggle: onToggleAutoplay })
16165
+ ] }),
16040
16166
  error && /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("div", { style: { color: "#FCA5A5", fontSize: 11, maxWidth: 220, textAlign: "center" }, children: error })
16041
16167
  ]
16042
16168
  }
@@ -16052,8 +16178,24 @@ function AiFadeCarouselOverlay({
16052
16178
  const [active, setActive] = (0, import_react12.useState)(null);
16053
16179
  const [pending, setPending] = (0, import_react12.useState)(false);
16054
16180
  const [error, setError] = (0, import_react12.useState)(null);
16181
+ const [autoplayOverride, setAutoplayOverride] = (0, import_react12.useState)(null);
16182
+ const [swapping, setSwapping] = (0, import_react12.useState)(null);
16183
+ const skeletonRef = (0, import_react12.useRef)(null);
16055
16184
  const activeRef = (0, import_react12.useRef)(null);
16056
16185
  activeRef.current = active;
16186
+ (0, import_react12.useEffect)(() => {
16187
+ setAutoplayOverride(null);
16188
+ }, [active?.sectionId, active?.index]);
16189
+ (0, import_react12.useEffect)(() => {
16190
+ if (!swapping?.fadingOut || !skeletonRef.current) return;
16191
+ const anim = skeletonRef.current.animate([{ opacity: 1 }, { opacity: 0 }], {
16192
+ duration: MEDIA_UPLOAD_FADE_MS,
16193
+ easing: "ease-out",
16194
+ fill: "forwards"
16195
+ });
16196
+ anim.finished.then(() => setSwapping(null)).catch(() => setSwapping(null));
16197
+ return () => anim.cancel();
16198
+ }, [swapping?.fadingOut]);
16057
16199
  (0, import_react12.useEffect)(() => {
16058
16200
  const onMove = (e) => {
16059
16201
  const t = e.target;
@@ -16194,6 +16336,27 @@ function AiFadeCarouselOverlay({
16194
16336
  children: /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(import_lucide_react11.Star, { size: 14, style: { pointerEvents: "none" } })
16195
16337
  }
16196
16338
  ),
16339
+ swapping && /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
16340
+ "div",
16341
+ {
16342
+ ref: skeletonRef,
16343
+ "data-ohw-bridge": "",
16344
+ "data-ohw-media-skeleton": "",
16345
+ "aria-hidden": true,
16346
+ style: {
16347
+ position: "fixed",
16348
+ top: swapping.rect.top,
16349
+ left: swapping.rect.left,
16350
+ width: swapping.rect.width,
16351
+ height: swapping.rect.height,
16352
+ // Under the toolbar (2147483647) so its controls stay visible and clickable over the
16353
+ // slide being swapped, and inert so it cannot eat the click that dismisses them.
16354
+ zIndex: 2147483646,
16355
+ pointerEvents: "none"
16356
+ },
16357
+ children: /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("style", { children: MEDIA_SKELETON_CSS })
16358
+ }
16359
+ ),
16197
16360
  activeItemRect && active && activeEntry && /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
16198
16361
  ItemInteractionLayer,
16199
16362
  {
@@ -16204,14 +16367,36 @@ function AiFadeCarouselOverlay({
16204
16367
  {
16205
16368
  pending,
16206
16369
  error,
16370
+ autoplay: autoplayOverride ?? carouselAutoplay(activeEntry.tree),
16371
+ onToggleAutoplay: () => {
16372
+ const next = !(autoplayOverride ?? carouselAutoplay(activeEntry.tree));
16373
+ setAutoplayOverride(next);
16374
+ void runMutation(active.sectionId, (tree) => setCarouselAutoplay(tree, next)).then(
16375
+ (ok) => {
16376
+ if (!ok) setAutoplayOverride(null);
16377
+ }
16378
+ );
16379
+ },
16207
16380
  onChangeImage: () => {
16208
- void requestAssetPick2(postToParent2).then((picked) => {
16381
+ void requestAssetPick2(postToParent2).then(async (picked) => {
16209
16382
  if (!picked) return;
16210
- void runMutation(
16383
+ const slideRect = readRect5(active);
16384
+ if (slideRect) setSwapping({ rect: slideRect, fadingOut: false });
16385
+ const ok = await runMutation(
16211
16386
  active.sectionId,
16212
16387
  (tree) => setCarouselSlideAsset(tree, active.index, picked.assetId),
16213
16388
  { [picked.assetId]: picked.url }
16214
16389
  );
16390
+ if (!ok) {
16391
+ setSwapping(null);
16392
+ return;
16393
+ }
16394
+ const preload = new Image();
16395
+ const done = () => setSwapping((s) => s ? { ...s, fadingOut: true } : s);
16396
+ preload.onload = done;
16397
+ preload.onerror = done;
16398
+ preload.src = picked.url;
16399
+ if (preload.complete) done();
16215
16400
  });
16216
16401
  }
16217
16402
  }
@@ -19682,6 +19867,7 @@ function readLogoSizeState(content, placement) {
19682
19867
  function getLogoElement(el) {
19683
19868
  const marked = el.closest('[data-ohw-role="logo"], [data-ohw-logo]');
19684
19869
  if (marked) return marked;
19870
+ if (el.closest('[data-ohw-editable="icon"]')) return null;
19685
19871
  const root = el.closest("nav, [data-ohw-nav-root], footer");
19686
19872
  if (!root) return null;
19687
19873
  const anchor = el.closest("a");
@@ -23173,7 +23359,7 @@ function getNavigationSelectionParent(el) {
23173
23359
  if (!isFooterLinksContainer(el) && (el.hasAttribute("data-ohw-footer-col") || el.hasAttribute("data-ohw-footer-column") || isInferredFooterGroup2(el))) {
23174
23360
  return resolveFooterLinksContainerFrom(el);
23175
23361
  }
23176
- if (el.hasAttribute("data-ohw-nav-container") || el.hasAttribute("data-ohw-nav-drawer") || isFooterLinksContainer(el)) {
23362
+ 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
23363
  return getNavigationRoot(el);
23178
23364
  }
23179
23365
  return null;
@@ -23395,7 +23581,6 @@ var ICONS = {
23395
23581
  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
23582
  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
23583
  };
23398
- var HEIGHT_SETTLE_DELAYS = [150, 400, 800];
23399
23584
  var SELECTION_CHROME_GAP2 = 4;
23400
23585
  var TOOLBAR_STROKE_GAP2 = 4;
23401
23586
  var TOOLBAR_OFFSET_FROM_ELEMENT = SELECTION_CHROME_GAP2 + TOOLBAR_STROKE_GAP2;
@@ -24375,21 +24560,6 @@ function OhhwellsBridge() {
24375
24560
  const [isItemDragging, setIsItemDragging] = (0, import_react21.useState)(false);
24376
24561
  const [isFooterFrameSelection, setIsFooterFrameSelection] = (0, import_react21.useState)(false);
24377
24562
  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
24563
  const [navDropdownPreviewOpen, setNavDropdownPreviewOpen] = (0, import_react21.useState)(null);
24394
24564
  const [footerHeadingVisible, setFooterHeadingVisible] = (0, import_react21.useState)(null);
24395
24565
  const footerDragRef = (0, import_react21.useRef)(null);
@@ -24415,6 +24585,21 @@ function OhhwellsBridge() {
24415
24585
  const stylesRef = (0, import_react21.useRef)("");
24416
24586
  const appliedDrawerRef = (0, import_react21.useRef)(null);
24417
24587
  const pendingDeleteUndoRef = (0, import_react21.useRef)(null);
24588
+ const [floatingPanel, setFloatingPanel] = (0, import_react21.useState)(null);
24589
+ const floatingPanelOpenRef = (0, import_react21.useRef)(false);
24590
+ const floatingPanelRef = (0, import_react21.useRef)(null);
24591
+ floatingPanelRef.current = floatingPanel;
24592
+ const setFloatingPanelRef = (0, import_react21.useRef)(setFloatingPanel);
24593
+ const [floatingPanelPos, setFloatingPanelPos] = (0, import_react21.useState)(null);
24594
+ const buttonCollectionDragRef = (0, import_react21.useRef)(null);
24595
+ const buttonDropInsertIndexRef = (0, import_react21.useRef)(null);
24596
+ const [buttonDropSlots, setButtonDropSlots] = (0, import_react21.useState)([]);
24597
+ const [activeButtonDropIndex, setActiveButtonDropIndex] = (0, import_react21.useState)(null);
24598
+ const buttonPointerDragRef = (0, import_react21.useRef)(null);
24599
+ const [buttonStyleTick, setButtonStyleTick] = (0, import_react21.useState)(0);
24600
+ const [logoSizeDraft, setLogoSizeDraft] = (0, import_react21.useState)(null);
24601
+ const [editorViewport, setEditorViewport] = (0, import_react21.useState)("desktop");
24602
+ const [parentScrollSnap, setParentScrollSnap] = (0, import_react21.useState)(null);
24418
24603
  const [sitePages, setSitePages] = (0, import_react21.useState)([]);
24419
24604
  const [sectionsByPath, setSectionsByPath] = (0, import_react21.useState)({});
24420
24605
  const sectionsPrefetchGenRef = (0, import_react21.useRef)(0);
@@ -24423,7 +24608,18 @@ function OhhwellsBridge() {
24423
24608
  const linkPopoverOpenRef = (0, import_react21.useRef)(false);
24424
24609
  const linkPopoverGraceUntilRef = (0, import_react21.useRef)(0);
24425
24610
  setLinkPopoverRef.current = setLinkPopover;
24611
+ setFloatingPanelRef.current = setFloatingPanel;
24426
24612
  linkPopoverSessionRef.current = linkPopover;
24613
+ floatingPanelOpenRef.current = Boolean(floatingPanel);
24614
+ (0, import_react21.useEffect)(() => {
24615
+ const syncViewport = () => {
24616
+ const next = window.innerWidth <= 480 ? "mobile" : "desktop";
24617
+ setEditorViewport((prev) => prev === next ? prev : next);
24618
+ };
24619
+ syncViewport();
24620
+ window.addEventListener("resize", syncViewport);
24621
+ return () => window.removeEventListener("resize", syncViewport);
24622
+ }, []);
24427
24623
  (0, import_react21.useEffect)(() => {
24428
24624
  setSectionOrderPersistHandler((entries) => {
24429
24625
  const currentPath = window.location.pathname;
@@ -26102,6 +26298,7 @@ function OhhwellsBridge() {
26102
26298
  applyAiFooterFromContent(content);
26103
26299
  if (typeof content[STYLE_STORE_KEY] === "string") {
26104
26300
  stylesRef.current = content[STYLE_STORE_KEY];
26301
+ document.querySelectorAll('[data-ohw-editable="form"]').forEach((f) => markFormFields(f));
26105
26302
  applyStylesToDom(parseStyleStore(content[STYLE_STORE_KEY]));
26106
26303
  }
26107
26304
  applyBrandChrome(content);
@@ -26111,12 +26308,12 @@ function OhhwellsBridge() {
26111
26308
  if (key === "__ohw_sections") continue;
26112
26309
  if (key === AI_SECTIONS_KEY) continue;
26113
26310
  if (key === AI_FOOTER_KEY) continue;
26311
+ if (key === LOGO_PLACEHOLDER_KEY) continue;
26312
+ if (LOGO_IMAGE_KEYS.includes(key)) continue;
26114
26313
  if (key === BRAND_KIT_KEY) continue;
26115
26314
  if (key === STYLE_STORE_KEY) continue;
26116
26315
  if (BRAND_CHROME_KEYS.has(key)) continue;
26117
- if (key === LOGO_PLACEHOLDER_KEY) continue;
26118
26316
  if (key === CUSTOM_CODE_AFTER_HEAD_KEY || key === CUSTOM_CODE_BEFORE_HEAD_KEY) continue;
26119
- if (LOGO_IMAGE_KEYS.includes(key)) continue;
26120
26317
  if (typeof content[AI_FOOTER_KEY] === "string" && content[AI_FOOTER_KEY] && isAiFooterOwnedContentKey(key)) {
26121
26318
  continue;
26122
26319
  }
@@ -26330,17 +26527,18 @@ function OhhwellsBridge() {
26330
26527
  }
26331
26528
  applyAiFooterFromContent(content);
26332
26529
  if (typeof content[STYLE_STORE_KEY] === "string") {
26530
+ document.querySelectorAll('[data-ohw-editable="form"]').forEach((f) => markFormFields(f));
26333
26531
  applyStylesToDom(parseStyleStore(content[STYLE_STORE_KEY]));
26334
26532
  }
26335
26533
  for (const [key, val] of Object.entries(content)) {
26336
26534
  if (key === "__ohw_sections") continue;
26337
26535
  if (key === AI_SECTIONS_KEY) continue;
26338
26536
  if (key === AI_FOOTER_KEY) continue;
26537
+ if (key === LOGO_PLACEHOLDER_KEY) continue;
26538
+ if (LOGO_IMAGE_KEYS.includes(key)) continue;
26339
26539
  if (key === BRAND_KIT_KEY) continue;
26340
26540
  if (key === STYLE_STORE_KEY) continue;
26341
26541
  if (BRAND_CHROME_KEYS.has(key)) continue;
26342
- if (key === LOGO_PLACEHOLDER_KEY) continue;
26343
- if (LOGO_IMAGE_KEYS.includes(key)) continue;
26344
26542
  if (typeof content[AI_FOOTER_KEY] === "string" && content[AI_FOOTER_KEY] && isAiFooterOwnedContentKey(key)) {
26345
26543
  continue;
26346
26544
  }
@@ -26596,26 +26794,11 @@ function OhhwellsBridge() {
26596
26794
  const t2 = setTimeout(measure, 500);
26597
26795
  const ro = new ResizeObserver(schedule);
26598
26796
  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
26797
  return () => {
26613
26798
  clearTimeout(t1);
26614
26799
  clearTimeout(t2);
26615
26800
  if (raf != null) cancelAnimationFrame(raf);
26616
26801
  ro.disconnect();
26617
- clearResizeTimers();
26618
- window.removeEventListener("resize", handleResize);
26619
26802
  };
26620
26803
  }, [pathname, isEditMode, postToParent2]);
26621
26804
  (0, import_react21.useEffect)(() => {
@@ -26910,6 +27093,13 @@ function OhhwellsBridge() {
26910
27093
  if (target.closest("[data-ohw-state-toggle]")) return;
26911
27094
  if (target.closest("[data-ohw-max-badge]")) return;
26912
27095
  if (isInsideLinkEditor(target)) return;
27096
+ if (isInsideFloatingPanel(target)) return;
27097
+ if (target.closest("[data-ohw-form-toolbar]")) return;
27098
+ if (target.closest(
27099
+ '[data-ohw-field-toolbar], [data-ohw-field-type-picker], [data-radix-popper-content-wrapper], [role="menu"], [data-slot="dropdown-menu-content"]'
27100
+ )) {
27101
+ return;
27102
+ }
26913
27103
  if (selectedMediaElRef.current && !selectedMediaElRef.current.contains(target) && !target.closest("[data-ohw-media-overlay], [data-ohw-edit-chrome], [data-ohw-item-interaction]")) {
26914
27104
  clearMediaSelectionRef.current();
26915
27105
  }
@@ -26930,13 +27120,6 @@ function OhhwellsBridge() {
26930
27120
  return;
26931
27121
  }
26932
27122
  }
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
27123
  {
26941
27124
  const formEl = getFormElement(target);
26942
27125
  const onSuccessText = target.closest(`[${SUCCESS_TEXT_ATTR}]`);
@@ -27316,6 +27499,9 @@ function OhhwellsBridge() {
27316
27499
  setHoveredItemRect(null);
27317
27500
  hoveredNavContainerRef.current = null;
27318
27501
  setHoveredNavContainerRect(null);
27502
+ siblingHintElRef.current = null;
27503
+ setSiblingHintRect(null);
27504
+ setSiblingHintRects([]);
27319
27505
  return;
27320
27506
  }
27321
27507
  {
@@ -27436,7 +27622,6 @@ function OhhwellsBridge() {
27436
27622
  hoveredNavContainerRef.current = null;
27437
27623
  setHoveredNavContainerRect(null);
27438
27624
  hoveredItemElRef.current = editable;
27439
- setHoveredItemRect(isSelectedForHoverRef.current(editable) ? null : editable.getBoundingClientRect());
27440
27625
  }
27441
27626
  }
27442
27627
  }
@@ -28146,7 +28331,11 @@ function OhhwellsBridge() {
28146
28331
  };
28147
28332
  const handleMouseMove = (e) => {
28148
28333
  const { clientX, clientY } = e;
28149
- if (pointOwnedByFloatingPanel(clientX, clientY)) return;
28334
+ if (pointOwnedByFloatingPanel(clientX, clientY)) {
28335
+ dismissImageHover();
28336
+ clearImageHover();
28337
+ return;
28338
+ }
28150
28339
  if (isNavDrawerOpen() && isPointOutsideOpenDrawer(clientX, clientY)) {
28151
28340
  document.querySelectorAll("[data-ohw-hovered]").forEach((el) => el.removeAttribute("data-ohw-hovered"));
28152
28341
  formHoverElRef.current = null;
@@ -28175,7 +28364,12 @@ function OhhwellsBridge() {
28175
28364
  setHoveredItemRect(null);
28176
28365
  hoveredNavContainerRef.current = null;
28177
28366
  setHoveredNavContainerRect(null);
28367
+ siblingHintElRef.current = null;
28368
+ setSiblingHintRect(null);
28369
+ setSiblingHintRects([]);
28178
28370
  dismissImageHover();
28371
+ clearImageHover();
28372
+ setSectionGap(null);
28179
28373
  return;
28180
28374
  }
28181
28375
  if (probeSocialsRowAt(clientX, clientY)) return;
@@ -28187,7 +28381,11 @@ function OhhwellsBridge() {
28187
28381
  if (e.data?.type !== "ow:pointer-sync") return;
28188
28382
  const { clientX, clientY } = e.data;
28189
28383
  if (typeof clientX !== "number" || typeof clientY !== "number") return;
28190
- if (pointOwnedByFloatingPanel(clientX, clientY)) return;
28384
+ if (pointOwnedByFloatingPanel(clientX, clientY)) {
28385
+ dismissImageHover();
28386
+ clearImageHover();
28387
+ return;
28388
+ }
28191
28389
  if (isNavDrawerOpen() && isPointOutsideOpenDrawer(clientX, clientY)) return;
28192
28390
  if (probeSocialsRowAt(clientX, clientY)) return;
28193
28391
  probeSectionGapAt(clientX, clientY);
@@ -28539,6 +28737,7 @@ function OhhwellsBridge() {
28539
28737
  applyAiFooterFromContent(content);
28540
28738
  if (typeof content[STYLE_STORE_KEY] === "string") {
28541
28739
  stylesRef.current = content[STYLE_STORE_KEY];
28740
+ document.querySelectorAll('[data-ohw-editable="form"]').forEach((f) => markFormFields(f));
28542
28741
  applyStylesToDom(parseStyleStore(content[STYLE_STORE_KEY]));
28543
28742
  }
28544
28743
  applyBrandChrome(content);
@@ -28552,12 +28751,12 @@ function OhhwellsBridge() {
28552
28751
  }
28553
28752
  if (key === AI_SECTIONS_KEY) continue;
28554
28753
  if (key === AI_FOOTER_KEY) continue;
28754
+ if (key === LOGO_PLACEHOLDER_KEY) continue;
28755
+ if (LOGO_IMAGE_KEYS.includes(key) && !String(val ?? "").trim()) continue;
28555
28756
  if (key === BRAND_KIT_KEY) continue;
28556
28757
  if (key === STYLE_STORE_KEY) continue;
28557
28758
  if (BRAND_CHROME_KEYS.has(key)) continue;
28558
- if (key === LOGO_PLACEHOLDER_KEY) continue;
28559
28759
  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
28760
  if (typeof content[AI_FOOTER_KEY] === "string" && content[AI_FOOTER_KEY] && isAiFooterOwnedContentKey(key)) {
28562
28761
  continue;
28563
28762
  }
@@ -29094,6 +29293,12 @@ function OhhwellsBridge() {
29094
29293
  closeLinkPopoverRef.current();
29095
29294
  return;
29096
29295
  }
29296
+ if (floatingPanelOpenRef.current) {
29297
+ setFloatingPanelRef.current(null);
29298
+ deselectRef.current();
29299
+ deactivateRef.current();
29300
+ return;
29301
+ }
29097
29302
  deselectRef.current();
29098
29303
  deactivateRef.current();
29099
29304
  clearMediaSelectionRef.current();
@@ -29856,10 +30061,10 @@ function OhhwellsBridge() {
29856
30061
  window.removeEventListener("message", handleDeleteSection);
29857
30062
  window.removeEventListener("message", handleDuplicateSection);
29858
30063
  window.removeEventListener("message", handleDeactivate);
29859
- document.documentElement.removeAttribute("data-ohw-panel-dragging");
29860
30064
  window.removeEventListener("message", handleToastAction);
29861
30065
  window.removeEventListener("message", handleFormCount);
29862
30066
  window.removeEventListener("message", handleUiEscape);
30067
+ document.documentElement.removeAttribute("data-ohw-panel-dragging");
29863
30068
  autoSaveTimers.current.forEach(clearTimeout);
29864
30069
  autoSaveTimers.current.clear();
29865
30070
  if (imageUnhoverTimerRef.current) clearTimeout(imageUnhoverTimerRef.current);
@@ -30153,7 +30358,7 @@ function OhhwellsBridge() {
30153
30358
  postToParent2({
30154
30359
  type: "ow:ready",
30155
30360
  version: "1",
30156
- bridgeVersion: "0.1.130",
30361
+ bridgeVersion: "0.1.132",
30157
30362
  path: pathname,
30158
30363
  nodes: collectEditableNodes(editContentRef.current),
30159
30364
  sections