@ohhwells/bridge 0.1.68 → 0.1.69-next.208

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
@@ -1,7 +1,7 @@
1
1
  "use client";
2
2
 
3
3
  // src/OhhwellsBridge.tsx
4
- import React11, { useCallback as useCallback7, useEffect as useEffect12, useLayoutEffect as useLayoutEffect4, useRef as useRef9, useState as useState11 } from "react";
4
+ import React11, { useCallback as useCallback8, useEffect as useEffect13, useLayoutEffect as useLayoutEffect4, useRef as useRef10, useState as useState12 } from "react";
5
5
  import { createRoot as createRoot2 } from "react-dom/client";
6
6
  import { flushSync as flushSync2 } from "react-dom";
7
7
 
@@ -83,7 +83,12 @@ function parseAiSectionsState(raw) {
83
83
  media: entry.media && typeof entry.media === "object" ? entry.media : {}
84
84
  }));
85
85
  const removed = Array.isArray(parsed.removed) ? parsed.removed.filter((id) => typeof id === "string" && id.length > 0) : [];
86
- return { v: 1, sections, ...removed.length ? { removed } : {} };
86
+ return {
87
+ v: 1,
88
+ sections,
89
+ ...removed.length ? { removed } : {},
90
+ ...parsed.hideTemplate === true ? { hideTemplate: true } : {}
91
+ };
87
92
  } catch {
88
93
  return EMPTY_AI_SECTIONS;
89
94
  }
@@ -96,6 +101,7 @@ function applyTreeToState(state, payload) {
96
101
  const entry = {
97
102
  id: payload.id,
98
103
  label: payload.label ?? "Generated section",
104
+ ...typeof payload.path === "string" && payload.path ? { path: payload.path } : {},
99
105
  afterSection: payload.mode === "insert" && !insertBefore ? payload.targetSectionId ?? null : null,
100
106
  ...insertBefore && payload.targetSectionId ? { beforeSection: payload.targetSectionId } : {},
101
107
  ...payload.mode === "replace" && payload.targetSectionId ? { replaces: payload.targetSectionId } : {},
@@ -118,6 +124,317 @@ function deleteSectionFromState(state, sectionId) {
118
124
  return { ...state, v: 1, sections: state.sections, removed: [...removed, sectionId] };
119
125
  }
120
126
 
127
+ // src/lib/brand-chrome.ts
128
+ var BRAND_NAME_KEY = "__ohw_brand_name";
129
+ var BRAND_TITLE_KEY = "__ohw_site_title";
130
+ var BRAND_FAVICON_LETTER_KEY = "__ohw_favicon_letter";
131
+ var BRAND_CHROME_KEYS = /* @__PURE__ */ new Set([
132
+ BRAND_NAME_KEY,
133
+ BRAND_TITLE_KEY,
134
+ BRAND_FAVICON_LETTER_KEY
135
+ ]);
136
+ function upsertMeta(selector, attr, token, value) {
137
+ let el = document.head.querySelector(selector);
138
+ if (!el) {
139
+ el = document.createElement("meta");
140
+ el.setAttribute(attr, token);
141
+ document.head.appendChild(el);
142
+ }
143
+ if (el.getAttribute("content") !== value) el.setAttribute("content", value);
144
+ }
145
+ function escapeXml(value) {
146
+ return value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;");
147
+ }
148
+ function applyLetterFavicon(letter) {
149
+ const svg = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64"><rect width="64" height="64" rx="12" fill="#111827"/><text x="32" y="46" font-family="system-ui,-apple-system,Segoe UI,sans-serif" font-size="40" font-weight="700" text-anchor="middle" fill="#ffffff">${escapeXml(letter)}</text></svg>`;
150
+ const href = `data:image/svg+xml,${encodeURIComponent(svg)}`;
151
+ let link = document.head.querySelector('link[rel="icon"]');
152
+ if (!link) {
153
+ link = document.createElement("link");
154
+ link.rel = "icon";
155
+ document.head.appendChild(link);
156
+ }
157
+ link.type = "image/svg+xml";
158
+ if (link.href !== href) link.href = href;
159
+ }
160
+ function applyBrandChrome(content) {
161
+ const name = content[BRAND_NAME_KEY];
162
+ if (typeof name === "string" && name.length > 0) {
163
+ document.querySelectorAll("[data-ohw-wordmark]").forEach((el) => {
164
+ if (el.textContent !== name) el.textContent = name;
165
+ if (el.getAttribute("title") !== name) el.setAttribute("title", name);
166
+ });
167
+ }
168
+ const title = content[BRAND_TITLE_KEY];
169
+ if (typeof title === "string" && title.length > 0) {
170
+ if (document.title !== title) document.title = title;
171
+ upsertMeta('meta[property="og:title"]', "property", "og:title", title);
172
+ upsertMeta('meta[name="twitter:title"]', "name", "twitter:title", title);
173
+ }
174
+ const letter = content[BRAND_FAVICON_LETTER_KEY];
175
+ if (typeof letter === "string" && letter.length > 0) {
176
+ applyLetterFavicon(letter);
177
+ }
178
+ }
179
+
180
+ // src/lib/brand-kit.ts
181
+ var BRAND_KIT_KEY = "__ohw_brand";
182
+ var BRAND_VAR_PREFIX = "--ohw-brand-";
183
+ var BRAND_VAR_NAMES = ["primary", "accent", "light", "dark", "surface", "border", "muted"].map(
184
+ (role) => `${BRAND_VAR_PREFIX}${role}`
185
+ );
186
+ var FONT_VARS = {
187
+ heading: ["--font-heading", "--font-display", "--brand-font-heading"],
188
+ body: ["--font-body", "--brand-font-body"]
189
+ };
190
+ var BRAND_FONT_LINK_ID = "ohw-brand-fonts";
191
+ function brandColorVars(kit) {
192
+ const { dark, primary, accent, light } = kit.palette;
193
+ const mix = (a, aPct, b) => `color-mix(in srgb, ${a} ${aPct}%, ${b})`;
194
+ return {
195
+ [`${BRAND_VAR_PREFIX}primary`]: primary,
196
+ [`${BRAND_VAR_PREFIX}accent`]: accent,
197
+ [`${BRAND_VAR_PREFIX}light`]: light,
198
+ [`${BRAND_VAR_PREFIX}dark`]: dark,
199
+ [`${BRAND_VAR_PREFIX}surface`]: mix(light, 95, dark),
200
+ [`${BRAND_VAR_PREFIX}border`]: mix(light, 85, dark),
201
+ [`${BRAND_VAR_PREFIX}muted`]: mix(dark, 62, light)
202
+ };
203
+ }
204
+ function parseBrandKit(raw) {
205
+ if (!raw) return null;
206
+ try {
207
+ const parsed = JSON.parse(raw);
208
+ const p = parsed?.palette;
209
+ const f = parsed?.fonts;
210
+ if (!p || !f || typeof p.dark !== "string" || typeof p.primary !== "string" || typeof p.accent !== "string" || typeof p.light !== "string" || typeof f.heading !== "string" || typeof f.body !== "string") {
211
+ return null;
212
+ }
213
+ return {
214
+ palette: { dark: p.dark, primary: p.primary, accent: p.accent, light: p.light },
215
+ fonts: { heading: f.heading, body: f.body }
216
+ };
217
+ } catch {
218
+ return null;
219
+ }
220
+ }
221
+ function familyOf(stack) {
222
+ const first = stack.split(",")[0]?.trim() ?? "";
223
+ return first.replace(/^['"]|['"]$/g, "");
224
+ }
225
+ function loadBrandFonts(families) {
226
+ const unique = [...new Set(families.map((f) => f.trim()).filter(Boolean))];
227
+ if (unique.length === 0) return;
228
+ const spec = unique.map((f) => `${f.replace(/ /g, "+")}:400,500,600,700`).join("|");
229
+ const href = `https://fonts.googleapis.com/css?family=${spec}&display=swap`;
230
+ let link = document.getElementById(BRAND_FONT_LINK_ID);
231
+ if (!link) {
232
+ link = document.createElement("link");
233
+ link.id = BRAND_FONT_LINK_ID;
234
+ link.rel = "stylesheet";
235
+ document.head.appendChild(link);
236
+ }
237
+ if (link.href !== href) link.href = href;
238
+ }
239
+ function applyBrandToDom(kit) {
240
+ const root = document.documentElement;
241
+ if (!kit) {
242
+ for (const name of BRAND_VAR_NAMES) root.style.removeProperty(name);
243
+ for (const name of [...FONT_VARS.heading, ...FONT_VARS.body]) root.style.removeProperty(name);
244
+ document.getElementById(BRAND_FONT_LINK_ID)?.remove();
245
+ return;
246
+ }
247
+ for (const [name, value] of Object.entries(brandColorVars(kit))) root.style.setProperty(name, value);
248
+ for (const name of FONT_VARS.heading) root.style.setProperty(name, kit.fonts.heading);
249
+ for (const name of FONT_VARS.body) root.style.setProperty(name, kit.fonts.body);
250
+ loadBrandFonts([familyOf(kit.fonts.heading), familyOf(kit.fonts.body)]);
251
+ }
252
+
253
+ // src/lib/section-styles.ts
254
+ var STYLE_STORE_KEY = "__ohw_styles";
255
+ var STYLE_SHEET_ID = "ohw-section-styles";
256
+ function parseStyleStore(raw) {
257
+ if (!raw) return null;
258
+ try {
259
+ const parsed = JSON.parse(raw);
260
+ if (parsed?.v !== 1) return null;
261
+ return {
262
+ v: 1,
263
+ sections: typeof parsed.sections === "object" && parsed.sections ? parsed.sections : {},
264
+ nodes: typeof parsed.nodes === "object" && parsed.nodes ? parsed.nodes : {}
265
+ };
266
+ } catch {
267
+ return null;
268
+ }
269
+ }
270
+ var BG_VALUES = {
271
+ surface: "color-mix(in srgb, var(--ohw-brand-light, var(--color-light, #ECECEB)) 94%, var(--ohw-brand-dark, var(--color-dark, #0C0A09)))",
272
+ accent: "var(--ohw-brand-primary, var(--color-primary, #0078E5))",
273
+ "accent-soft": "color-mix(in srgb, var(--ohw-brand-primary, var(--color-primary, #0078E5)) 12%, var(--ohw-brand-light, var(--color-light, #FFFFFF)))"
274
+ };
275
+ var HEADLINE_SIZES = { md: 24, lg: 32, xl: 40, display: 56 };
276
+ function styleSheetCss() {
277
+ const rules = [];
278
+ for (const [tone, value] of Object.entries(BG_VALUES)) {
279
+ rules.push(`[data-ohw-style-bg="${tone}"] { background: ${value} !important; }`);
280
+ }
281
+ rules.push(
282
+ `[data-ohw-style-bg="accent"] { color: var(--ohw-brand-light, var(--color-light, #FFFFFF)) !important; }`
283
+ );
284
+ rules.push(
285
+ `[data-ohw-style-distribution="space-between"] { display: flex !important; flex-direction: column; justify-content: space-between; }`,
286
+ `[data-ohw-style-distribution="center"] { display: flex !important; flex-direction: column; justify-content: center; }`
287
+ );
288
+ for (const [scale, size] of Object.entries(HEADLINE_SIZES)) {
289
+ rules.push(
290
+ `[data-ohw-style-headline="${scale}"] :is(h1, h2, h3) { font-size: ${size}px !important; line-height: 1.15 !important; }`
291
+ );
292
+ }
293
+ rules.push(
294
+ `[data-ohw-style-aspect="fill-height"] img { height: 100% !important; aspect-ratio: auto !important; object-fit: cover; }`
295
+ );
296
+ for (const aspect of ["1:1", "4:5", "3:4", "3:2", "16:9", "2:1", "3:1"]) {
297
+ rules.push(
298
+ `[data-ohw-style-aspect="${aspect.replace(":", "-")}"] img { aspect-ratio: ${aspect.replace(":", " / ")} !important; height: auto !important; object-fit: cover; }`
299
+ );
300
+ }
301
+ const pad = { tight: 40, balanced: 64, airy: 96 };
302
+ for (const [spacing, px] of Object.entries(pad)) {
303
+ rules.push(
304
+ `[data-ohw-style-spacing="${spacing}"] { padding-top: ${px}px !important; padding-bottom: ${px}px !important; }`
305
+ );
306
+ }
307
+ return rules.join("\n");
308
+ }
309
+ var STYLE_FONT_LINK_ID = "ohw-style-fonts";
310
+ function loadStyleFonts(families) {
311
+ const unique = [...new Set(families.map((f) => f.trim()).filter(Boolean))];
312
+ const existing = document.getElementById(STYLE_FONT_LINK_ID);
313
+ if (unique.length === 0) {
314
+ existing?.remove();
315
+ return;
316
+ }
317
+ const spec = unique.map((f) => `${f.replace(/ /g, "+")}:400,500,600,700`).join("|");
318
+ const href = `https://fonts.googleapis.com/css?family=${spec}&display=swap`;
319
+ let link = existing;
320
+ if (!link) {
321
+ link = document.createElement("link");
322
+ link.id = STYLE_FONT_LINK_ID;
323
+ link.rel = "stylesheet";
324
+ document.head.appendChild(link);
325
+ }
326
+ if (link.href !== href) link.href = href;
327
+ }
328
+ var SECTION_ATTRS = {
329
+ sectionBackground: "data-ohw-style-bg",
330
+ textDistribution: "data-ohw-style-distribution",
331
+ headlineScale: "data-ohw-style-headline",
332
+ imageAspect: "data-ohw-style-aspect",
333
+ spacing: "data-ohw-style-spacing"
334
+ };
335
+ var NODE_WROTE_ATTR = "data-ohw-style-node";
336
+ var NODE_PROPS = ["color", "font-family", "font-size", "background"];
337
+ function saveInline(el, prop) {
338
+ const attr = `data-ohw-style-prev-${prop}`;
339
+ if (!el.hasAttribute(attr)) el.setAttribute(attr, el.style.getPropertyValue(prop));
340
+ }
341
+ function restoreInline(el, prop) {
342
+ const attr = `data-ohw-style-prev-${prop}`;
343
+ if (!el.hasAttribute(attr)) return;
344
+ const prev = el.getAttribute(attr) ?? "";
345
+ if (prev) el.style.setProperty(prop, prev);
346
+ else el.style.removeProperty(prop);
347
+ el.removeAttribute(attr);
348
+ }
349
+ function ensureStyleSheet() {
350
+ let el = document.getElementById(STYLE_SHEET_ID);
351
+ if (!el) {
352
+ el = document.createElement("style");
353
+ el.id = STYLE_SHEET_ID;
354
+ document.head.appendChild(el);
355
+ }
356
+ const css = styleSheetCss();
357
+ if (el.textContent !== css) el.textContent = css;
358
+ }
359
+ function clearSectionAttrs(root) {
360
+ for (const attr of Object.values(SECTION_ATTRS)) {
361
+ for (const el of Array.from(root.querySelectorAll(`[${attr}]`))) el.removeAttribute(attr);
362
+ }
363
+ for (const el of Array.from(root.querySelectorAll("[data-ohw-style-bgcolor]"))) {
364
+ restoreInline(el, "background");
365
+ el.removeAttribute("data-ohw-style-bgcolor");
366
+ }
367
+ }
368
+ function clearNodeProps(root) {
369
+ for (const el of Array.from(root.querySelectorAll(`[${NODE_WROTE_ATTR}]`))) {
370
+ const h = el;
371
+ for (const prop of NODE_PROPS) restoreInline(h, prop);
372
+ h.removeAttribute(NODE_WROTE_ATTR);
373
+ }
374
+ }
375
+ function buttonSurfaceOf(el) {
376
+ return el.closest("a, button") ?? el;
377
+ }
378
+ function applyStylesToDom(store) {
379
+ ensureStyleSheet();
380
+ clearSectionAttrs(document);
381
+ clearNodeProps(document);
382
+ loadStyleFonts(
383
+ store ? Object.values(store.nodes).flatMap((n) => n.fontFamily ? [n.fontFamily] : []) : []
384
+ );
385
+ if (!store) return;
386
+ for (const [sectionId, override] of Object.entries(store.sections)) {
387
+ const sections = document.querySelectorAll(
388
+ `[data-ohw-section="${CSS.escape(sectionId)}"]`
389
+ );
390
+ for (const section of Array.from(sections)) {
391
+ for (const [prop, attr] of Object.entries(SECTION_ATTRS)) {
392
+ const value = override[prop];
393
+ if (value === void 0) continue;
394
+ if (prop === "sectionBackground" && override.sectionBackgroundColor !== void 0) continue;
395
+ section.setAttribute(attr, String(value).replace(":", "-"));
396
+ }
397
+ if (override.sectionBackgroundColor !== void 0) {
398
+ saveInline(section, "background");
399
+ section.style.setProperty("background", override.sectionBackgroundColor, "important");
400
+ section.setAttribute("data-ohw-style-bgcolor", "");
401
+ }
402
+ }
403
+ }
404
+ for (const [key, override] of Object.entries(store.nodes)) {
405
+ const nodes = document.querySelectorAll(`[data-ohw-key="${CSS.escape(key)}"]`);
406
+ for (const el of Array.from(nodes)) {
407
+ if (override.color !== void 0) {
408
+ saveInline(el, "color");
409
+ el.style.setProperty("color", override.color, "important");
410
+ el.setAttribute(NODE_WROTE_ATTR, "");
411
+ }
412
+ if (override.fontFamily !== void 0) {
413
+ saveInline(el, "font-family");
414
+ el.style.setProperty("font-family", `'${override.fontFamily}'`, "important");
415
+ el.setAttribute(NODE_WROTE_ATTR, "");
416
+ }
417
+ if (override.fontSize !== void 0) {
418
+ saveInline(el, "font-size");
419
+ el.style.setProperty("font-size", `${override.fontSize}px`, "important");
420
+ el.setAttribute(NODE_WROTE_ATTR, "");
421
+ }
422
+ if (override.buttonBackground !== void 0 || override.buttonText !== void 0) {
423
+ const surface = buttonSurfaceOf(el);
424
+ if (override.buttonBackground !== void 0) {
425
+ saveInline(surface, "background");
426
+ surface.style.setProperty("background", override.buttonBackground, "important");
427
+ }
428
+ if (override.buttonText !== void 0) {
429
+ saveInline(surface, "color");
430
+ surface.style.setProperty("color", override.buttonText, "important");
431
+ }
432
+ surface.setAttribute(NODE_WROTE_ATTR, "");
433
+ }
434
+ }
435
+ }
436
+ }
437
+
121
438
  // src/ui/ai-tree/aiSectionsManager.tsx
122
439
  import { flushSync } from "react-dom";
123
440
  import { createRoot } from "react-dom/client";
@@ -132,7 +449,8 @@ function lucideByName(name) {
132
449
  }
133
450
  var typeStyle = (spec, font) => ({
134
451
  fontFamily: font,
135
- fontSize: spec.size,
452
+ // Headings shrink with the viewport (reaching full size around ~900px wide); body copy stays put.
453
+ fontSize: spec.size >= 24 ? `clamp(${Math.max(18, Math.round(spec.size * 0.6))}px, ${(spec.size / 9).toFixed(2)}vw, ${spec.size}px)` : spec.size,
136
454
  lineHeight: spec.line,
137
455
  fontWeight: spec.weight
138
456
  });
@@ -147,6 +465,36 @@ var FEATURE_LINE_CSS = [
147
465
  `background-color:currentColor;-webkit-mask:url("${CHECK_MASK}") no-repeat 0 0/24px 24px;`,
148
466
  `mask:url("${CHECK_MASK}") no-repeat 0 0/24px 24px}`
149
467
  ].join("");
468
+ function hexLuminance(color) {
469
+ const m = /^#([0-9a-f]{6})$/i.exec(color.trim());
470
+ if (!m) return null;
471
+ const [r2, g, b] = [0, 2, 4].map((i) => {
472
+ const c = parseInt(m[1].slice(i, i + 2), 16) / 255;
473
+ return c <= 0.03928 ? c / 12.92 : Math.pow((c + 0.055) / 1.055, 2.4);
474
+ });
475
+ return 0.2126 * r2 + 0.7152 * g + 0.0722 * b;
476
+ }
477
+ function hexContrast(a, b) {
478
+ const la = hexLuminance(a);
479
+ const lb = hexLuminance(b);
480
+ if (la === null || lb === null) return null;
481
+ const [hi, lo] = la > lb ? [la, lb] : [lb, la];
482
+ return (hi + 0.05) / (lo + 0.05);
483
+ }
484
+ function accentBandContext(brand) {
485
+ const p = brand.palette;
486
+ const lightWins = (hexContrast(p.light, p.primary) ?? 99) >= (hexContrast(p.dark, p.primary) ?? 0);
487
+ if (lightWins) {
488
+ return {
489
+ brand: { ...brand, palette: { dark: p.light, primary: p.light, accent: p.light, light: p.primary } },
490
+ buttonLabel: p.primary
491
+ };
492
+ }
493
+ return {
494
+ brand: { ...brand, palette: { dark: p.dark, primary: p.dark, accent: p.dark, light: p.light } },
495
+ buttonLabel: p.light
496
+ };
497
+ }
150
498
  function textAttrs(ctx, path) {
151
499
  return ctx.keyFor ? { "data-ohw-key": ctx.keyFor(path), "data-ohw-editable": "text" } : {};
152
500
  }
@@ -158,7 +506,12 @@ var AI_RESPONSIVE_CSS = [
158
506
  "@media (max-width: 640px) {",
159
507
  " [data-ai-responsive] [data-ai-grid] { grid-template-columns: 1fr !important; }",
160
508
  " [data-ai-responsive] [data-ai-columns] > * { grid-column: 1 / -1 !important; }",
509
+ // Group containers flatten to a column on phones; span placements come along for free.
510
+ " [data-ai-responsive] [data-ai-group] { display: flex !important; flex-direction: column !important; }",
511
+ " [data-ai-responsive] [data-ai-group] > * { grid-column: auto !important; }",
161
512
  " [data-ai-responsive] [data-ai-section-inner] { padding-left: 20px !important; padding-right: 20px !important; }",
513
+ " [data-ai-responsive] { overflow-x: hidden; }",
514
+ " [data-ai-responsive] img { max-width: 100%; }",
162
515
  "}"
163
516
  ].join("\n");
164
517
  var TRANSPARENT_PX = "data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7";
@@ -236,7 +589,7 @@ function ButtonEl({
236
589
  }) {
237
590
  const secondary = slots.variant === "secondary";
238
591
  const href = str(slots.href);
239
- const linkAttrs = ctx.keyFor && path ? { "data-ohw-href-key": ctx.keyFor(`${path}.href`) } : {};
592
+ const linkAttrs = ctx.keyFor && path ? { "data-ohw-href-key": ctx.keyFor(`${path}.href`), "data-ohw-role": "button" } : {};
240
593
  return /* @__PURE__ */ jsx(
241
594
  "a",
242
595
  {
@@ -252,7 +605,7 @@ function ButtonEl({
252
605
  textDecoration: "none",
253
606
  cursor: "pointer",
254
607
  ...typeStyle(AI_TREE_TOKENS.type.button, ctx.brand.fonts.body),
255
- ...secondary ? { border: `1px solid ${ctx.brand.palette.dark}`, color: ctx.brand.palette.dark } : { background: ctx.brand.palette.primary, color: AI_TREE_TOKENS.textPrimaryForeground }
608
+ ...secondary ? { border: `1px solid ${ctx.brand.palette.dark}`, color: ctx.brand.palette.dark } : { background: ctx.brand.palette.primary, color: ctx.buttonLabel ?? AI_TREE_TOKENS.textPrimaryForeground }
256
609
  },
257
610
  children: /* @__PURE__ */ jsx("span", { ...path ? textAttrs(ctx, `${path}.label`) : {}, children: str(slots.label) })
258
611
  }
@@ -758,7 +1111,24 @@ function CardBlock({ node, ctx, path }) {
758
1111
  minWidth: 0
759
1112
  },
760
1113
  children: [
761
- media && (horizontal ? /* @__PURE__ */ jsx("div", { style: { flex: 1, minWidth: 0, display: "flex", alignItems: "center", padding: mediaInset }, children: media }) : /* @__PURE__ */ jsx(
1114
+ media && (horizontal ? /* @__PURE__ */ jsx(
1115
+ "div",
1116
+ {
1117
+ style: (
1118
+ // An icon hugs its glyph — flex:1 gave a 48px icon half the card and pushed the
1119
+ // text to the far side. Photos keep the half-and-half split. The inset has no
1120
+ // inner padding (the photo split absorbed that), so the icon carries its own gap.
1121
+ /^(lucide|simple):/.test(mediaRef) ? {
1122
+ flexShrink: 0,
1123
+ display: "flex",
1124
+ alignItems: "center",
1125
+ padding: mediaInset,
1126
+ [mediaPosition === "right" ? "marginLeft" : "marginRight"]: 20
1127
+ } : { flex: 1, minWidth: 0, display: "flex", alignItems: "center", padding: mediaInset }
1128
+ ),
1129
+ children: media
1130
+ }
1131
+ ) : /* @__PURE__ */ jsx(
762
1132
  "div",
763
1133
  {
764
1134
  style: /^(lucide|simple):/.test(mediaRef) ? { padding: `${AI_TREE_TOKENS.paddingBlock}px ${AI_TREE_TOKENS.paddingBlock}px 0` } : hasBg ? void 0 : { borderRadius: AI_TREE_TOKENS.radiusCard, overflow: "hidden" },
@@ -1022,6 +1392,49 @@ function renderNode(node, ctx, path) {
1022
1392
  switch (node.type) {
1023
1393
  case "text":
1024
1394
  return /* @__PURE__ */ jsx(TextBlock, { slots, ctx, path });
1395
+ // Layout container: arranges child blocks, contributes no content of its own. `grid` is a
1396
+ // nested 12-column grid the children span (a collage is 3–5 media on it, bottom-aligned so
1397
+ // mixed aspects read as a composition); `split` is exactly two children at a ratio; `stack`
1398
+ // is a column. Children render through this same dispatcher, so edit markers, media
1399
+ // resolution, and copy paths all work unchanged inside a group.
1400
+ case "group": {
1401
+ const layout = str(slots.layout);
1402
+ const gap = slots.spacing === "tight" ? AI_TREE_TOKENS.spacing3 : slots.spacing === "airy" ? AI_TREE_TOKENS.spacing8 : AI_TREE_TOKENS.spacing6;
1403
+ const kids = (node.children ?? []).map((child, i) => /* @__PURE__ */ jsx(
1404
+ "div",
1405
+ {
1406
+ style: layout === "grid" ? {
1407
+ gridColumn: `span ${typeof child.span === "number" ? Math.min(12, Math.max(1, child.span)) : 12}`,
1408
+ minWidth: 0
1409
+ } : { minWidth: 0 },
1410
+ children: renderNode(child, ctx, `${path}.c${i}`)
1411
+ },
1412
+ i
1413
+ ));
1414
+ if (layout === "grid") {
1415
+ return /* @__PURE__ */ jsx(
1416
+ "div",
1417
+ {
1418
+ "data-ai-group": "grid",
1419
+ style: { display: "grid", gridTemplateColumns: "repeat(12, 1fr)", gap, alignItems: "end" },
1420
+ children: kids
1421
+ }
1422
+ );
1423
+ }
1424
+ if (layout === "split") {
1425
+ const ratio = str(slots.ratio);
1426
+ const cols = ratio === "3:5" ? "3fr 5fr" : ratio === "5:3" ? "5fr 3fr" : "1fr 1fr";
1427
+ return /* @__PURE__ */ jsx(
1428
+ "div",
1429
+ {
1430
+ "data-ai-group": "split",
1431
+ style: { display: "grid", gridTemplateColumns: cols, gap, alignItems: "center" },
1432
+ children: kids
1433
+ }
1434
+ );
1435
+ }
1436
+ return /* @__PURE__ */ jsx("div", { "data-ai-group": "stack", style: { display: "flex", flexDirection: "column", gap }, children: kids });
1437
+ }
1025
1438
  case "button":
1026
1439
  return /* @__PURE__ */ jsx(ButtonEl, { slots, ctx, path });
1027
1440
  case "button-row":
@@ -1154,11 +1567,14 @@ function AiTreeRenderer({ tree, brand, resolveMedia, editKeyPrefix }) {
1154
1567
  return null;
1155
1568
  }
1156
1569
  const resolvedBrand = brand ?? AI_DEFAULT_BRAND;
1570
+ const band = (tree.settings ?? {}).sectionBackground === "accent" ? accentBandContext(resolvedBrand) : null;
1571
+ const blockBrand = band?.brand ?? resolvedBrand;
1157
1572
  const ctx = {
1158
- brand: resolvedBrand,
1573
+ brand: blockBrand,
1159
1574
  resolveMedia: resolveMedia ?? (() => null),
1160
- cardSurface: resolvedBrand.palette.light.toUpperCase() === AI_DEFAULT_BRAND.palette.light.toUpperCase() ? "#ECECEB" : `color-mix(in srgb, ${resolvedBrand.palette.light} 90%, ${resolvedBrand.palette.dark})`,
1161
- keyFor: editKeyPrefix ? (path) => `${editKeyPrefix}.${path}` : null
1575
+ cardSurface: blockBrand.palette.light.toUpperCase() === AI_DEFAULT_BRAND.palette.light.toUpperCase() ? "#ECECEB" : `color-mix(in srgb, ${blockBrand.palette.light} 90%, ${blockBrand.palette.dark})`,
1576
+ keyFor: editKeyPrefix ? (path) => `${editKeyPrefix}.${path}` : null,
1577
+ ...band ? { buttonLabel: band.buttonLabel } : {}
1162
1578
  };
1163
1579
  const settings = tree.settings ?? {};
1164
1580
  const pad = AI_TREE_TOKENS.sectionPadding[settings.spacing ?? "balanced"];
@@ -1166,6 +1582,20 @@ function AiTreeRenderer({ tree, brand, resolveMedia, editKeyPrefix }) {
1166
1582
  const overlayAlpha = { low: 0.25, medium: 0.45, high: 0.65 }[settings.overlayIntensity ?? "medium"];
1167
1583
  const backgroundUrl = isOverlay && settings.backgroundMedia ? ctx.resolveMedia(settings.backgroundMedia) : null;
1168
1584
  const bgAttrs = ctx.keyFor && isOverlay && settings.backgroundMedia ? { "data-ohw-key": ctx.keyFor("settings.backgroundMedia"), "data-ohw-editable": "bg-image" } : {};
1585
+ const toneBackground = (() => {
1586
+ const { dark, primary, light } = resolvedBrand.palette;
1587
+ switch (settings.sectionBackground) {
1588
+ case "surface":
1589
+ return `color-mix(in srgb, ${light} 94%, ${dark})`;
1590
+ case "accent":
1591
+ return primary;
1592
+ case "accent-soft":
1593
+ return `color-mix(in srgb, ${primary} 12%, ${light})`;
1594
+ default:
1595
+ return void 0;
1596
+ }
1597
+ })();
1598
+ const distributed = !isOverlay && settings.textDistribution;
1169
1599
  return /* @__PURE__ */ jsxs(
1170
1600
  "section",
1171
1601
  {
@@ -1175,10 +1605,11 @@ function AiTreeRenderer({ tree, brand, resolveMedia, editKeyPrefix }) {
1175
1605
  style: {
1176
1606
  position: "relative",
1177
1607
  padding: `${pad}px 0`,
1178
- background: isOverlay && !backgroundUrl ? "#DBEAFE" : void 0,
1608
+ background: toneBackground ?? (isOverlay && !backgroundUrl ? "#DBEAFE" : void 0),
1179
1609
  backgroundImage: backgroundUrl ? `url(${backgroundUrl})` : void 0,
1180
1610
  backgroundSize: "cover",
1181
- backgroundPosition: "center"
1611
+ backgroundPosition: "center",
1612
+ color: settings.sectionBackground === "accent" ? blockBrand.palette.dark : void 0
1182
1613
  },
1183
1614
  children: [
1184
1615
  /* @__PURE__ */ jsx("style", { children: AI_RESPONSIVE_CSS }),
@@ -1202,10 +1633,24 @@ function AiTreeRenderer({ tree, brand, resolveMedia, editKeyPrefix }) {
1202
1633
  display: "grid",
1203
1634
  gridTemplateColumns: "repeat(12, 1fr)",
1204
1635
  gap: AI_TREE_TOKENS.spacing6,
1205
- alignItems: settings.verticalPosition === "top" ? "start" : "center",
1636
+ alignItems: distributed === "space-between" ? "stretch" : (distributed ?? settings.verticalPosition) === "top" ? "start" : "center",
1206
1637
  marginTop: r2 > 0 ? AI_TREE_TOKENS.spacing8 : 0
1207
1638
  },
1208
- children: (row.blocks ?? []).map((block, b) => /* @__PURE__ */ jsx("div", { style: { gridColumn: `span ${Math.min(12, Math.max(1, block.span))}`, minWidth: 0 }, children: renderNode(block, ctx, `r${r2}.b${b}`) }, b))
1639
+ children: (row.blocks ?? []).map((block, b) => /* @__PURE__ */ jsx(
1640
+ "div",
1641
+ {
1642
+ "data-ai-cell": "",
1643
+ style: {
1644
+ gridColumn: `span ${Math.min(12, Math.max(1, block.span))}`,
1645
+ minWidth: 0,
1646
+ // space-between: each column becomes a flex column whose content spreads over
1647
+ // the full row height instead of clumping at the top.
1648
+ ...distributed === "space-between" ? { display: "flex", flexDirection: "column", justifyContent: "space-between" } : {}
1649
+ },
1650
+ children: renderNode(block, ctx, `r${r2}.b${b}`)
1651
+ },
1652
+ b
1653
+ ))
1209
1654
  },
1210
1655
  r2
1211
1656
  ))
@@ -1221,17 +1666,36 @@ import { jsx as jsx2 } from "react/jsx-runtime";
1221
1666
  var CONTAINER_ATTR = "data-ohw-ai-generated";
1222
1667
  var REPLACED_ATTR = "data-ohw-ai-replaced-by";
1223
1668
  var REMOVED_ATTR = "data-ohw-ai-removed";
1669
+ var TEMPLATE_HIDDEN_ATTR = "data-ohw-ai-template-hidden";
1670
+ var CHROME_SECTION_IDS = /* @__PURE__ */ new Set(["navbar", "footer"]);
1671
+ function readRootVar(name) {
1672
+ if (typeof document === "undefined") return "";
1673
+ return getComputedStyle(document.documentElement).getPropertyValue(name).trim();
1674
+ }
1675
+ function deriveBrandOverride() {
1676
+ const dark = readRootVar("--ohw-brand-dark");
1677
+ const primary = readRootVar("--ohw-brand-primary");
1678
+ const light = readRootVar("--ohw-brand-light");
1679
+ if (!dark || !primary || !light) return null;
1680
+ const accent = readRootVar("--ohw-brand-accent");
1681
+ const heading = readRootVar("--font-heading") || readRootVar("--font-display");
1682
+ const body = readRootVar("--font-body");
1683
+ return {
1684
+ palette: { dark, primary, accent: accent || dark, light },
1685
+ fonts: {
1686
+ heading: heading || AI_DEFAULT_BRAND.fonts.heading,
1687
+ body: body || AI_DEFAULT_BRAND.fonts.body
1688
+ }
1689
+ };
1690
+ }
1224
1691
  function deriveTemplateBrand() {
1225
- if (typeof document === "undefined") return null;
1226
- const cs = getComputedStyle(document.documentElement);
1227
- const read = (name) => cs.getPropertyValue(name).trim();
1228
- const dark = read("--color-dark");
1229
- const primary = read("--color-primary");
1230
- const light = read("--color-light");
1692
+ const dark = readRootVar("--color-dark");
1693
+ const primary = readRootVar("--color-primary");
1694
+ const light = readRootVar("--color-light");
1231
1695
  if (!dark || !primary || !light) return null;
1232
- const accent = read("--color-accent");
1233
- const heading = read("--font-heading") || read("--font-display");
1234
- const body = read("--font-body");
1696
+ const accent = readRootVar("--color-accent");
1697
+ const heading = readRootVar("--font-heading") || readRootVar("--font-display");
1698
+ const body = readRootVar("--font-body");
1235
1699
  return {
1236
1700
  palette: { dark, primary, accent: accent || dark, light },
1237
1701
  fonts: {
@@ -1303,6 +1767,24 @@ function syncRemovedSections(state) {
1303
1767
  }
1304
1768
  }
1305
1769
  }
1770
+ function syncTemplateHidden(state, pageHasSections) {
1771
+ const hide = state.hideTemplate === true && pageHasSections;
1772
+ for (const el of Array.from(document.querySelectorAll(`[${TEMPLATE_HIDDEN_ATTR}]`))) {
1773
+ if (!hide) {
1774
+ el.style.removeProperty("display");
1775
+ el.removeAttribute(TEMPLATE_HIDDEN_ATTR);
1776
+ }
1777
+ }
1778
+ if (!hide) return;
1779
+ for (const el of Array.from(document.querySelectorAll("[data-ohw-section]"))) {
1780
+ if (el.hasAttribute(CONTAINER_ATTR)) continue;
1781
+ if (CHROME_SECTION_IDS.has(el.getAttribute("data-ohw-section") ?? "")) continue;
1782
+ if (el.parentElement?.closest("[data-ohw-section]")) continue;
1783
+ if (el.hasAttribute(REPLACED_ATTR) || el.hasAttribute(REMOVED_ATTR)) continue;
1784
+ el.style.display = "none";
1785
+ el.setAttribute(TEMPLATE_HIDDEN_ATTR, "");
1786
+ }
1787
+ }
1306
1788
  function syncReplacedOriginals(state) {
1307
1789
  for (const el of document.querySelectorAll(`[${REPLACED_ATTR}]`)) {
1308
1790
  const byId = el.getAttribute(REPLACED_ATTR) ?? "";
@@ -1323,8 +1805,12 @@ function syncReplacedOriginals(state) {
1323
1805
  }
1324
1806
  function applyAiSectionsToDom(state, options) {
1325
1807
  if (typeof document === "undefined") return;
1808
+ const brandOverride = deriveBrandOverride();
1326
1809
  const templateBrand = deriveTemplateBrand();
1327
- const activeIds = new Set(state.sections.map((entry) => entry.id));
1810
+ const brandKey = brandOverride ? JSON.stringify(brandOverride) : "";
1811
+ const pagePath = window.location.pathname;
1812
+ const pageSections = state.sections.filter((entry) => (entry.path ?? "/") === pagePath);
1813
+ const activeIds = new Set(pageSections.map((entry) => entry.id));
1328
1814
  for (const [id, section] of mounted) {
1329
1815
  if (!activeIds.has(id)) {
1330
1816
  section.root.unmount();
@@ -1332,8 +1818,8 @@ function applyAiSectionsToDom(state, options) {
1332
1818
  mounted.delete(id);
1333
1819
  }
1334
1820
  }
1335
- for (const entry of state.sections) {
1336
- const serialized = JSON.stringify(entry);
1821
+ for (const entry of pageSections) {
1822
+ const serialized = JSON.stringify(entry) + brandKey;
1337
1823
  const existing = mounted.get(entry.id);
1338
1824
  if (existing && existing.serialized === serialized && existing.container.isConnected) {
1339
1825
  continue;
@@ -1347,6 +1833,7 @@ function applyAiSectionsToDom(state, options) {
1347
1833
  mounted.delete(entry.id);
1348
1834
  }
1349
1835
  container.setAttribute("data-ohw-section", entry.id);
1836
+ container.setAttribute("data-ohw-instance", entry.id);
1350
1837
  container.setAttribute("data-ohw-section-label", entry.label);
1351
1838
  placeContainer(container, entry);
1352
1839
  const root = mounted.get(entry.id)?.root ?? createRoot(container);
@@ -1357,7 +1844,7 @@ function applyAiSectionsToDom(state, options) {
1357
1844
  AiTreeRenderer,
1358
1845
  {
1359
1846
  tree: entry.tree,
1360
- brand: entry.brand ?? options?.brand ?? templateBrand ?? void 0,
1847
+ brand: brandOverride ?? entry.brand ?? options?.brand ?? templateBrand ?? void 0,
1361
1848
  resolveMedia,
1362
1849
  editKeyPrefix: `ai.${entry.id}`
1363
1850
  }
@@ -1368,6 +1855,7 @@ function applyAiSectionsToDom(state, options) {
1368
1855
  }
1369
1856
  syncReplacedOriginals(state);
1370
1857
  syncRemovedSections(state);
1858
+ syncTemplateHidden(state, pageSections.length > 0);
1371
1859
  }
1372
1860
 
1373
1861
  // src/useLinkHrefGuardian.ts
@@ -1974,7 +2462,7 @@ function SchedulingWidget({ notifyOnConnect = false, initialScheduleId, insertAf
1974
2462
  const autoId = useId();
1975
2463
  const insertAfter = insertAfterProp ?? autoId;
1976
2464
  const [schedule, setSchedule] = useState2(null);
1977
- const [loading, setLoading] = useState2(true);
2465
+ const [loading, setLoading] = useState2(initialScheduleId !== null);
1978
2466
  const [inEditor, setInEditor] = useState2(false);
1979
2467
  const [isHovered, setIsHovered] = useState2(false);
1980
2468
  const [modalState, setModalState] = useState2(null);
@@ -2148,8 +2636,10 @@ function SchedulingWidget({ notifyOnConnect = false, initialScheduleId, insertAf
2148
2636
  "*"
2149
2637
  );
2150
2638
  };
2151
- if (!inEditor && !loading && !schedule) return null;
2152
2639
  const sectionId = `scheduling-${insertAfter}`;
2640
+ if (!inEditor && !loading && !schedule) {
2641
+ return /* @__PURE__ */ jsx4("section", { "data-ohw-section": sectionId, "data-ohw-scheduling-anchor": insertAfter, style: { display: "none" } });
2642
+ }
2153
2643
  return /* @__PURE__ */ jsxs3(
2154
2644
  "section",
2155
2645
  {
@@ -7057,13 +7547,17 @@ function MediaOverlay({
7057
7547
  hover,
7058
7548
  isUploading,
7059
7549
  fadingOut = false,
7550
+ selected = false,
7551
+ hovered = false,
7060
7552
  onFadeOutComplete,
7061
7553
  onReplace,
7554
+ onSelect,
7062
7555
  onVideoSettingsChange
7063
7556
  }) {
7064
7557
  const { rect } = hover;
7065
7558
  const skeletonRef = React7.useRef(null);
7066
7559
  const isVideo = hover.elementType === "video";
7560
+ const showChrome = !selected || hovered;
7067
7561
  const autoplay = hover.videoAutoplay ?? true;
7068
7562
  const muted = hover.videoMuted ?? true;
7069
7563
  const box = {
@@ -7098,7 +7592,7 @@ function MediaOverlay({
7098
7592
  }
7099
7593
  );
7100
7594
  }
7101
- const settingsBar = isVideo && !hover.isDragOver ? /* @__PURE__ */ jsxs7(
7595
+ const settingsBar = isVideo && !hover.isDragOver && showChrome ? /* @__PURE__ */ jsxs7(
7102
7596
  "div",
7103
7597
  {
7104
7598
  "data-ohw-bridge": "",
@@ -7168,11 +7662,13 @@ function MediaOverlay({
7168
7662
  // in-document, pointer-events does it natively. The button below opts back in, so
7169
7663
  // Replace still works.
7170
7664
  pointerEvents: hover.hasTextOverlap ? "none" : "auto",
7171
- boxShadow: "inset 0 0 0 1.5px var(--color-primary)",
7172
- background: "color-mix(in srgb, var(--color-primary) 20%, transparent)"
7665
+ // Selected: a firm component ring with no wash, so the image reads as chosen rather
7666
+ // than hovered. Hover keeps the existing tinted preview.
7667
+ boxShadow: selected ? "inset 0 0 0 2px var(--color-primary)" : "inset 0 0 0 1.5px var(--color-primary)",
7668
+ background: selected ? "transparent" : "color-mix(in srgb, var(--color-primary) 20%, transparent)"
7173
7669
  },
7174
- onClick: () => onReplace(hover.key),
7175
- children: /* @__PURE__ */ jsxs7(
7670
+ onClick: () => onSelect && !selected ? onSelect(hover.key) : onReplace(hover.key),
7671
+ children: showChrome && /* @__PURE__ */ jsxs7(
7176
7672
  Button,
7177
7673
  {
7178
7674
  "data-ohw-media-overlay": "",
@@ -7191,7 +7687,7 @@ function MediaOverlay({
7191
7687
  },
7192
7688
  children: [
7193
7689
  isVideo ? /* @__PURE__ */ jsx15(Film, { size: 14 }) : /* @__PURE__ */ jsx15(ImageIcon, { size: 14 }),
7194
- isVideo ? "Replace video" : "Replace image"
7690
+ isVideo ? "Replace video" : hover.elementType === "bg-image" ? "Replace background" : "Replace image"
7195
7691
  ]
7196
7692
  }
7197
7693
  )
@@ -7261,6 +7757,9 @@ import { Check, X } from "lucide-react";
7261
7757
 
7262
7758
  // src/lib/sections.ts
7263
7759
  var LINK_PICKER_EXCLUDED_IDS = /* @__PURE__ */ new Set(["navbar", "footer"]);
7760
+ function isChromeSection(el) {
7761
+ return el.matches("header, nav, footer, aside");
7762
+ }
7264
7763
  function titleCaseSectionId(id) {
7265
7764
  return id.split("-").filter(Boolean).map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join(" ");
7266
7765
  }
@@ -7271,6 +7770,8 @@ function parseSectionsFromRoot(root) {
7271
7770
  const id = el.getAttribute("data-ohw-section") ?? "";
7272
7771
  if (!id || seen.has(id) || LINK_PICKER_EXCLUDED_IDS.has(id)) continue;
7273
7772
  if (el.parentElement?.closest("[data-ohw-section]")) continue;
7773
+ if (el.hasAttribute("data-ohw-ai-template-hidden") || el.hasAttribute("data-ohw-ai-removed") || el.hasAttribute("data-ohw-ai-replaced-by"))
7774
+ continue;
7274
7775
  seen.add(id);
7275
7776
  const label = el.getAttribute("data-ohw-section-label") ?? titleCaseSectionId(id);
7276
7777
  sections.push({ id, label });
@@ -7288,8 +7789,12 @@ function parseSectionsFromHtml(html) {
7288
7789
 
7289
7790
  // src/ui/ai-section/AiSectionOverlay.tsx
7290
7791
  import { Fragment as Fragment5, jsx as jsx17, jsxs as jsxs9 } from "react/jsx-runtime";
7291
- function readRect(sectionId) {
7292
- const el = document.querySelector(`[data-ohw-section="${CSS.escape(sectionId)}"]`);
7792
+ function findSectionElement(instanceId) {
7793
+ const escaped = CSS.escape(instanceId);
7794
+ return document.querySelector(`[data-ohw-instance="${escaped}"]`) ?? document.querySelector(`[data-ohw-section="${escaped}"]:not([data-ohw-instance])`);
7795
+ }
7796
+ function readRect(instanceId) {
7797
+ const el = findSectionElement(instanceId);
7293
7798
  if (!el) return null;
7294
7799
  const r2 = el.getBoundingClientRect();
7295
7800
  if (r2.width <= 0 || r2.height <= 0) return null;
@@ -7312,7 +7817,7 @@ function useLiveSectionRect(sectionId) {
7312
7817
  const opts = { capture: true, passive: true };
7313
7818
  window.addEventListener("scroll", update, opts);
7314
7819
  window.addEventListener("resize", update);
7315
- const el = document.querySelector(`[data-ohw-section="${CSS.escape(sectionId)}"]`);
7820
+ const el = findSectionElement(sectionId);
7316
7821
  const ro = el ? new ResizeObserver(update) : null;
7317
7822
  if (el && ro) ro.observe(el);
7318
7823
  const interval = setInterval(update, 500);
@@ -7325,6 +7830,14 @@ function useLiveSectionRect(sectionId) {
7325
7830
  }, [sectionId]);
7326
7831
  return rect;
7327
7832
  }
7833
+ function computeSectionBoundaryFlags(instanceId) {
7834
+ const topLevel = Array.from(document.querySelectorAll("[data-ohw-section]")).filter(
7835
+ (el) => !el.parentElement?.closest("[data-ohw-section]") && !isChromeSection(el)
7836
+ );
7837
+ const index = topLevel.findIndex((el) => (el.dataset.ohwInstance ?? el.dataset.ohwSection) === instanceId);
7838
+ if (index === -1) return { isFirst: true, isLast: true };
7839
+ return { isFirst: index === 0, isLast: index === topLevel.length - 1 };
7840
+ }
7328
7841
  var PRIMARY2 = "#0885FE";
7329
7842
  function edgeAwareRadius(rect) {
7330
7843
  const container = window.innerWidth <= 480 ? 16 : 24;
@@ -7398,6 +7911,7 @@ function AiSectionOverlay({
7398
7911
  }) {
7399
7912
  const [selectedId, setSelectedId] = useState4(null);
7400
7913
  const [reviewId, setReviewId] = useState4(null);
7914
+ const [reviewButtonsHidden, setReviewButtonsHidden] = useState4(false);
7401
7915
  const reviewIdRef = useRef4(null);
7402
7916
  reviewIdRef.current = reviewId;
7403
7917
  const selectedIdRef = useRef4(null);
@@ -7406,7 +7920,7 @@ function AiSectionOverlay({
7406
7920
  (el) => {
7407
7921
  postToParent2({
7408
7922
  type: "ow:section-selected",
7409
- sectionId: el?.dataset.ohwSection ?? null,
7923
+ sectionId: el ? el.dataset.ohwInstance ?? el.dataset.ohwSection ?? null : null,
7410
7924
  sectionLabel: el ? el.dataset.ohwSectionLabel ?? titleCaseSectionId(el.dataset.ohwSection ?? "") : null
7411
7925
  });
7412
7926
  },
@@ -7415,7 +7929,7 @@ function AiSectionOverlay({
7415
7929
  const selectFromElement = useCallback2(
7416
7930
  (el, options) => {
7417
7931
  const sectionEl = el?.closest("[data-ohw-section]") ?? null;
7418
- const id = sectionEl?.dataset.ohwSection ?? null;
7932
+ const id = sectionEl ? sectionEl.dataset.ohwInstance ?? sectionEl.dataset.ohwSection ?? null : null;
7419
7933
  if (id === selectedIdRef.current) return;
7420
7934
  setSelectedId(id);
7421
7935
  if (options?.report !== false) report(sectionEl);
@@ -7456,9 +7970,10 @@ function AiSectionOverlay({
7456
7970
  }
7457
7971
  const found = readRect(sectionId) != null;
7458
7972
  setReviewId(found ? sectionId : null);
7973
+ setReviewButtonsHidden(e.data.hideButtons === true);
7459
7974
  postToParent2({ type: "ow:ai-review-started", sectionId, found });
7460
7975
  if (found) {
7461
- document.querySelector(`[data-ohw-section="${CSS.escape(sectionId)}"]`)?.scrollIntoView({ behavior: "smooth", block: "nearest" });
7976
+ document.querySelector(`[data-ohw-instance="${CSS.escape(sectionId)}"]`)?.scrollIntoView({ behavior: "smooth", block: "nearest" });
7462
7977
  }
7463
7978
  }
7464
7979
  };
@@ -7477,7 +7992,7 @@ function AiSectionOverlay({
7477
7992
  return;
7478
7993
  }
7479
7994
  const sec = t.closest("[data-ohw-section]");
7480
- setHoveredId(sec?.dataset.ohwSection ?? null);
7995
+ setHoveredId(sec ? sec.dataset.ohwInstance ?? sec.dataset.ohwSection ?? null : null);
7481
7996
  };
7482
7997
  const onLeave = () => setHoveredId(null);
7483
7998
  document.addEventListener("mousemove", onMove, { passive: true });
@@ -7509,9 +8024,30 @@ function AiSectionOverlay({
7509
8024
  },
7510
8025
  [postToParent2]
7511
8026
  );
7512
- const selectionRect = useLiveSectionRect(reviewId ? null : selectedId);
8027
+ const activeSelectionId = reviewId ? null : selectedId;
8028
+ const selectionRect = useLiveSectionRect(activeSelectionId);
7513
8029
  const reviewRect = useLiveSectionRect(reviewId);
7514
8030
  const hoverRect = useLiveSectionRect(reviewId || hoveredId === selectedId ? null : hoveredId);
8031
+ useEffect4(() => {
8032
+ const selectedEl = activeSelectionId ? findSectionElement(activeSelectionId) : null;
8033
+ if (!activeSelectionId || !selectionRect || selectedEl && isChromeSection(selectedEl)) {
8034
+ postToParent2({ type: "ow:section-rect", instanceId: null, rect: null });
8035
+ return;
8036
+ }
8037
+ const { isFirst, isLast } = computeSectionBoundaryFlags(activeSelectionId);
8038
+ postToParent2({
8039
+ type: "ow:section-rect",
8040
+ instanceId: activeSelectionId,
8041
+ rect: {
8042
+ top: selectionRect.top + window.scrollY,
8043
+ left: selectionRect.left + window.scrollX,
8044
+ width: selectionRect.width,
8045
+ height: selectionRect.height
8046
+ },
8047
+ isFirst,
8048
+ isLast
8049
+ });
8050
+ }, [activeSelectionId, selectionRect, postToParent2]);
7515
8051
  return /* @__PURE__ */ jsxs9(Fragment5, { children: [
7516
8052
  hoverRect && /* @__PURE__ */ jsx17(
7517
8053
  "div",
@@ -7562,13 +8098,16 @@ function AiSectionOverlay({
7562
8098
  border: `2px solid ${PRIMARY2}`,
7563
8099
  borderRadius: edgeAwareRadius(reviewRect),
7564
8100
  zIndex: 2147483200,
7565
- // The veil itself: swallows clicks so the section stays locked until decided.
8101
+ // The veil itself: swallows clicks so the section stays locked until decided. This
8102
+ // stopPropagation only guards the bubble phase; the bridge's capture-phase click
8103
+ // handlers additionally bail on `[data-ohw-ai-review]`, without which a click on
8104
+ // Accept/Discard resolves to the media beneath and opens the file picker.
7566
8105
  background: "rgba(8, 133, 254, 0.04)",
7567
8106
  pointerEvents: "auto",
7568
8107
  cursor: "default"
7569
8108
  },
7570
8109
  onClick: (e) => e.stopPropagation(),
7571
- children: /* @__PURE__ */ jsxs9(
8110
+ children: !reviewButtonsHidden && /* @__PURE__ */ jsxs9(
7572
8111
  "div",
7573
8112
  {
7574
8113
  style: {
@@ -7593,6 +8132,59 @@ function AiSectionOverlay({
7593
8132
 
7594
8133
  // src/lib/section-instances.ts
7595
8134
  var SECTION_ORDER_KEY = "__ohw_section_order";
8135
+ function topLevelSections() {
8136
+ return Array.from(document.querySelectorAll("[data-ohw-section]")).filter(
8137
+ (el) => !el.parentElement?.closest("[data-ohw-section]") && !isChromeSection(el)
8138
+ );
8139
+ }
8140
+ function instanceIdOf(el) {
8141
+ return el.dataset.ohwInstance ?? el.dataset.ohwSection ?? "";
8142
+ }
8143
+ function planSectionMove(instanceId, targetIndex, currentPath) {
8144
+ const sections = topLevelSections();
8145
+ const index = sections.findIndex((el) => instanceIdOf(el) === instanceId);
8146
+ if (index === -1) return null;
8147
+ const dragged = sections[index];
8148
+ const others = sections.filter((_, i) => i !== index);
8149
+ const clamped = Math.max(0, Math.min(targetIndex, others.length));
8150
+ const reordered = [...others.slice(0, clamped), dragged, ...others.slice(clamped)];
8151
+ return reordered.map((el, order) => ({
8152
+ instanceId: instanceIdOf(el),
8153
+ type: el.getAttribute("data-ohw-section") ?? "",
8154
+ order,
8155
+ pagePath: currentPath
8156
+ }));
8157
+ }
8158
+ function moveSectionInstance(instanceId, direction, currentPath) {
8159
+ const sections = topLevelSections();
8160
+ const index = sections.findIndex((el) => instanceIdOf(el) === instanceId);
8161
+ if (index === -1) return null;
8162
+ const siblingIndex = direction === "up" ? index - 1 : index + 1;
8163
+ if (siblingIndex < 0 || siblingIndex >= sections.length) return null;
8164
+ const entries = planSectionMove(instanceId, siblingIndex, currentPath);
8165
+ if (!entries) return null;
8166
+ applyPersistedOrder(entries);
8167
+ return entries;
8168
+ }
8169
+ function applyPersistedOrder(entries) {
8170
+ if (entries.length === 0) return;
8171
+ const sections = topLevelSections();
8172
+ if (sections.length === 0) return;
8173
+ const orderIndex = new Map(entries.map((e) => [e.instanceId, e.order]));
8174
+ const ordered = [...sections].sort((a, b) => {
8175
+ const aOrder = orderIndex.get(instanceIdOf(a));
8176
+ const bOrder = orderIndex.get(instanceIdOf(b));
8177
+ if (aOrder === void 0 && bOrder === void 0) return 0;
8178
+ if (aOrder === void 0) return 1;
8179
+ if (bOrder === void 0) return -1;
8180
+ return aOrder - bOrder;
8181
+ });
8182
+ let prev = null;
8183
+ for (const el of ordered) {
8184
+ if (prev) prev.after(el);
8185
+ prev = el;
8186
+ }
8187
+ }
7596
8188
  function getPageSectionOrderEntries(raw, currentPath) {
7597
8189
  if (!raw) return [];
7598
8190
  try {
@@ -7630,6 +8222,7 @@ function initSectionInstancesFromContent(content, currentPath) {
7630
8222
  rekeySectionSubtree(clone, entry.instanceId);
7631
8223
  original.insertAdjacentElement("afterend", clone);
7632
8224
  }
8225
+ applyPersistedOrder(entries);
7633
8226
  }
7634
8227
 
7635
8228
  // src/OhhwellsBridge.tsx
@@ -10174,8 +10767,13 @@ var GLYPH_SELECTOR = "svg, img";
10174
10767
  function referenceBox(slot) {
10175
10768
  const row = slot.closest("[data-ohw-socials-row]") ?? slot.closest("a")?.parentElement ?? null;
10176
10769
  const neighbour = row ? Array.from(row.querySelectorAll('[data-ohw-editable="icon"]')).find((el) => el !== slot) : null;
10177
- const source = (neighbour ?? slot).querySelector(GLYPH_SELECTOR);
10178
- const box = source?.getBoundingClientRect() ?? null;
10770
+ if (neighbour) {
10771
+ const box2 = neighbour.querySelector(GLYPH_SELECTOR)?.getBoundingClientRect();
10772
+ if (box2?.width && box2.height) return box2;
10773
+ }
10774
+ const own = slot.getBoundingClientRect();
10775
+ if (own.width && own.height) return own;
10776
+ const box = slot.querySelector(GLYPH_SELECTOR)?.getBoundingClientRect() ?? null;
10179
10777
  return box?.width && box.height ? box : null;
10180
10778
  }
10181
10779
  function iconMarkupSizedFor(slot, markup) {
@@ -10254,7 +10852,8 @@ function findSocialsRow(el) {
10254
10852
  }
10255
10853
  function isSocialsRow(el) {
10256
10854
  const anchors = Array.from(el.querySelectorAll("a"));
10257
- return anchors.length > 0 && anchors.every((anchor) => isSocialItem(anchor));
10855
+ if (!anchors.length || !anchors.some((anchor) => isSocialItem(anchor))) return false;
10856
+ return findSocialsRow(anchors[0]) === el;
10258
10857
  }
10259
10858
  var MAX_SOCIAL_ITEMS_PER_ROW = 7;
10260
10859
  function canAddSocialItem(row) {
@@ -10292,6 +10891,7 @@ function markSocialsRows(root = document) {
10292
10891
  });
10293
10892
  listSocialsRows(root).forEach((row) => {
10294
10893
  row.setAttribute(SOCIALS_ROW_ATTR, "");
10894
+ allowRowToWrap(row);
10295
10895
  const items = listSocialItems(row);
10296
10896
  const firstUnit = items[0] ? socialRowUnit(items[0], row) : null;
10297
10897
  if (firstUnit) rowTemplates.set(rowKeyOf(row), firstUnit.outerHTML);
@@ -10586,7 +11186,29 @@ function applySocialsDisplayToRow(row, display) {
10586
11186
  const icon = item.querySelector(ICON_SELECTOR);
10587
11187
  if (label) label.style.display = display.text ? "" : "none";
10588
11188
  if (icon) icon.style.display = display.icon ? "" : "none";
11189
+ layOutIconAndLabel(item, Boolean(display.text && display.icon));
10589
11190
  });
11191
+ allowRowToWrap(row);
11192
+ }
11193
+ function layOutIconAndLabel(item, on) {
11194
+ const hasBoth = Boolean(item.querySelector(ICON_SELECTOR)) && Boolean(socialLabelElement(item));
11195
+ if (!hasBoth) {
11196
+ item.style.display = "";
11197
+ item.style.alignItems = "";
11198
+ item.style.gap = "";
11199
+ item.style.whiteSpace = "";
11200
+ item.style.flex = "";
11201
+ return;
11202
+ }
11203
+ item.style.display = on ? "inline-flex" : "";
11204
+ item.style.alignItems = on ? "center" : "";
11205
+ item.style.gap = on ? "8px" : "";
11206
+ item.style.whiteSpace = on ? "nowrap" : "";
11207
+ item.style.flex = on ? "0 0 auto" : "";
11208
+ }
11209
+ function allowRowToWrap(row) {
11210
+ const display = row.ownerDocument.defaultView?.getComputedStyle(row).display ?? "";
11211
+ if (display === "flex" || display === "inline-flex") row.style.flexWrap = "wrap";
10590
11212
  }
10591
11213
  function applySocialsDisplayFromContent(content, root = document) {
10592
11214
  const stored = parseSocialsDisplay(content[SOCIALS_DISPLAY_KEY]);
@@ -11796,6 +12418,7 @@ function readLogoSizeState(content, placement) {
11796
12418
  function getLogoElement(el) {
11797
12419
  const marked = el.closest('[data-ohw-role="logo"], [data-ohw-logo]');
11798
12420
  if (marked) return marked;
12421
+ if (el.closest('[data-ohw-editable="icon"]')) return null;
11799
12422
  const root = el.closest("nav, [data-ohw-nav-root], footer");
11800
12423
  if (!root) return null;
11801
12424
  const anchor = el.closest("a");
@@ -12675,101 +13298,428 @@ function useNavItemDrag({
12675
13298
  };
12676
13299
  }
12677
13300
 
12678
- // src/ui/footer-container-chrome.tsx
12679
- import { Plus as Plus2 } from "lucide-react";
12680
- import { jsx as jsx29, jsxs as jsxs19 } from "react/jsx-runtime";
12681
- function FooterContainerChrome({
12682
- rect,
12683
- onAdd,
12684
- addDisabled = false
12685
- }) {
12686
- const chromeGap = 6;
12687
- const buttonMargin = 7;
12688
- return /* @__PURE__ */ jsx29(TooltipProvider, { delayDuration: 0, children: /* @__PURE__ */ jsx29(
12689
- "div",
12690
- {
12691
- "data-ohw-footer-container-chrome": "",
12692
- "data-ohw-bridge": "",
12693
- className: "pointer-events-none fixed z-[2147483647]",
12694
- style: {
12695
- top: rect.top - chromeGap,
12696
- left: rect.left - chromeGap,
12697
- width: rect.width + chromeGap * 2,
12698
- height: rect.height + chromeGap * 2
12699
- },
12700
- children: /* @__PURE__ */ jsxs19(Tooltip, { children: [
12701
- /* @__PURE__ */ jsx29(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsx29(
12702
- "button",
12703
- {
12704
- type: "button",
12705
- "data-ohw-footer-add-button": "",
12706
- disabled: addDisabled,
12707
- className: "pointer-events-auto absolute left-1/2 flex size-7 -translate-x-1/2 -translate-y-full items-center justify-center rounded-[10px] border border-border bg-background p-0.5 shadow-sm transition-colors hover:bg-muted/80 disabled:pointer-events-none disabled:opacity-40",
12708
- style: { top: chromeGap - buttonMargin },
12709
- "aria-label": "Add item",
12710
- onMouseDown: (e) => {
12711
- e.preventDefault();
12712
- e.stopPropagation();
12713
- },
12714
- onClick: (e) => {
12715
- e.preventDefault();
12716
- e.stopPropagation();
12717
- if (addDisabled) return;
12718
- onAdd();
12719
- },
12720
- children: /* @__PURE__ */ jsx29(Plus2, { className: "size-4 shrink-0 text-foreground", "aria-hidden": true })
12721
- }
12722
- ) }),
12723
- /* @__PURE__ */ jsx29(TooltipContent, { side: "bottom", sideOffset: 9, children: addDisabled ? "Maximum columns reached" : "Add item" })
12724
- ] })
12725
- }
12726
- ) });
12727
- }
13301
+ // src/useSectionDrag.ts
13302
+ import { useCallback as useCallback7, useEffect as useEffect11, useRef as useRef9, useState as useState10 } from "react";
12728
13303
 
12729
- // src/lib/carousel.ts
12730
- import { useEffect as useEffect11, useState as useState10 } from "react";
12731
- var CAROUSEL_ATTR = "data-ohw-carousel";
12732
- var CAROUSEL_VALUE_ATTR = "data-ohw-carousel-value";
12733
- var CAROUSEL_SLIDE_ATTR = "data-ohw-carousel-slide";
12734
- var CAROUSEL_EVENT = "ohw:carousel-change";
12735
- function listCarouselKeys() {
12736
- const keys = /* @__PURE__ */ new Set();
12737
- document.querySelectorAll(`[${CAROUSEL_ATTR}]`).forEach((el) => {
12738
- const key = el.getAttribute("data-ohw-key");
12739
- if (key) keys.add(key);
12740
- });
12741
- return [...keys];
13304
+ // src/lib/section-dnd.ts
13305
+ function isFooterSection(el) {
13306
+ return el.dataset.ohwSection === "footer";
12742
13307
  }
12743
- function containersForKey(key) {
12744
- return Array.from(
12745
- document.querySelectorAll(`[${CAROUSEL_ATTR}][data-ohw-key="${CSS.escape(key)}"]`)
13308
+ function buildSectionDropSlots(draggedInstanceId) {
13309
+ const sections = topLevelSections().filter(
13310
+ (el) => instanceIdOf(el) !== draggedInstanceId && !isFooterSection(el)
12746
13311
  );
13312
+ const slots = [];
13313
+ if (sections.length === 0) return slots;
13314
+ const left = 0;
13315
+ const width = document.documentElement.clientWidth;
13316
+ for (let i = 0; i <= sections.length; i++) {
13317
+ let y;
13318
+ if (i === 0) {
13319
+ y = sections[0].getBoundingClientRect().top;
13320
+ } else if (i === sections.length) {
13321
+ y = sections[sections.length - 1].getBoundingClientRect().bottom;
13322
+ } else {
13323
+ const prev = sections[i - 1].getBoundingClientRect();
13324
+ const next = sections[i].getBoundingClientRect();
13325
+ y = (prev.bottom + next.top) / 2;
13326
+ }
13327
+ slots.push({ insertIndex: i, y, left, width });
13328
+ }
13329
+ return slots;
12747
13330
  }
12748
- function isCarouselKey(key) {
12749
- return containersForKey(key).length > 0;
12750
- }
12751
- function parseSlides(raw) {
12752
- if (!raw) return [];
12753
- try {
12754
- const parsed = JSON.parse(raw);
12755
- if (!Array.isArray(parsed)) return [];
12756
- return parsed.filter((s) => Boolean(s) && typeof s === "object").map((s) => ({ src: String(s.src ?? ""), alt: String(s.alt ?? "") }));
12757
- } catch {
12758
- return [];
13331
+ function hitTestSectionDropSlot(y, slots) {
13332
+ let best = null;
13333
+ for (const slot of slots) {
13334
+ const dist = Math.abs(y - slot.y);
13335
+ if (!best || dist < best.dist) best = { slot, dist };
12759
13336
  }
13337
+ return best?.slot ?? null;
12760
13338
  }
12761
- function readCarouselValue(key) {
12762
- const container = containersForKey(key)[0];
12763
- if (!container) return [];
12764
- const fromAttr = parseSlides(container.getAttribute(CAROUSEL_VALUE_ATTR));
12765
- if (fromAttr.length > 0) return fromAttr;
12766
- return Array.from(container.querySelectorAll(`[${CAROUSEL_SLIDE_ATTR}]`)).filter((slide) => slide.closest(`[${CAROUSEL_ATTR}]`) === container).sort((a, b) => slideIndex(a) - slideIndex(b)).map((slide) => {
12767
- const img = slide instanceof HTMLImageElement ? slide : slide.querySelector("img");
12768
- return { src: img?.src ?? "", alt: img?.alt ?? "" };
12769
- });
13339
+
13340
+ // src/useSectionDrag.ts
13341
+ var PRESS_THRESHOLD = 10;
13342
+ var EDGE_ZONE = 60;
13343
+ var MAX_AUTO_SCROLL_SPEED = 18;
13344
+ var SECTION_DRAG_EXCLUDED_SELECTOR = [
13345
+ "[data-ohw-toolbar]",
13346
+ "[data-ohw-edit-chrome]",
13347
+ "[data-ohw-item-interaction]",
13348
+ "[data-ohw-drag-handle-container]",
13349
+ '[data-slot="drag-handle"]',
13350
+ "[data-ohw-item-toolbar-anchor]",
13351
+ "[data-ohw-item-drag-surface]",
13352
+ "[data-ohw-more-menu]",
13353
+ '[data-slot="dropdown-menu-content"]',
13354
+ '[data-slot="dropdown-menu-item"]',
13355
+ "[data-ohw-state-toggle]",
13356
+ "[data-ohw-max-badge]",
13357
+ "[data-ohw-floating-panel]",
13358
+ "[data-ohw-section-picker]",
13359
+ "[data-ohw-link-popover-root]",
13360
+ "[data-ohw-link-modal-root]",
13361
+ "[data-ohw-link-page-dropdown]",
13362
+ '[data-slot="popover-content"]',
13363
+ '[data-slot="dialog-content"]',
13364
+ '[data-slot="dialog-overlay"]',
13365
+ "[data-ohw-ai-review]",
13366
+ "[data-ohw-editable]",
13367
+ "[data-ohw-editable-state]",
13368
+ "[contenteditable]",
13369
+ "[data-ohw-href-key]",
13370
+ "[data-ohw-footer-col]",
13371
+ "[data-ohw-social-label]",
13372
+ "a",
13373
+ "button",
13374
+ '[role="button"]',
13375
+ '[data-ohw-role="navbar-button"]',
13376
+ '[data-ohw-role="button"]',
13377
+ "[data-ohw-carousel]",
13378
+ "[data-ohw-carousel-value]",
13379
+ "[data-ohw-carousel-slide]",
13380
+ "[data-ohw-carousel-overlay]",
13381
+ "[data-ohw-media-chrome]",
13382
+ "[data-ohw-media-overlay]",
13383
+ "[data-ohw-media-skeleton]"
13384
+ ].join(", ");
13385
+ function visibleClip(ps) {
13386
+ if (!ps) return null;
13387
+ const top = Math.max(0, ps.headerH - ps.iframeOffsetTop);
13388
+ const bottom = Math.min(window.innerHeight, ps.headerH + ps.canvasH - ps.iframeOffsetTop);
13389
+ return { top, bottom: Math.max(top, bottom) };
12770
13390
  }
12771
- function slideIndex(el) {
12772
- const raw = el.getAttribute(CAROUSEL_SLIDE_ATTR);
13391
+ function useSectionDrag({
13392
+ isEditMode,
13393
+ editContentRef,
13394
+ postToParentRef,
13395
+ parentScrollRef,
13396
+ navDragRef,
13397
+ footerDragRef,
13398
+ suppressNextClickRef,
13399
+ suppressClickUntilRef
13400
+ }) {
13401
+ const sectionDragRef = useRef9(null);
13402
+ const [sectionDropSlots, setSectionDropSlots] = useState10([]);
13403
+ const [activeSectionDropIndex, setActiveSectionDropIndex] = useState10(null);
13404
+ const [isSectionDragging, setIsSectionDragging] = useState10(false);
13405
+ const sectionPointerDragRef = useRef9(null);
13406
+ const autoScrollRafRef = useRef9(null);
13407
+ const autoScrollDeltaRef = useRef9(0);
13408
+ const stopAutoScroll = useCallback7(() => {
13409
+ if (autoScrollRafRef.current != null) {
13410
+ cancelAnimationFrame(autoScrollRafRef.current);
13411
+ autoScrollRafRef.current = null;
13412
+ }
13413
+ autoScrollDeltaRef.current = 0;
13414
+ }, []);
13415
+ const tickAutoScroll = useCallback7(() => {
13416
+ if (!sectionDragRef.current) {
13417
+ stopAutoScroll();
13418
+ return;
13419
+ }
13420
+ if (autoScrollDeltaRef.current !== 0) {
13421
+ postToParentRef.current({ type: "ow:request-scroll", deltaY: autoScrollDeltaRef.current });
13422
+ }
13423
+ autoScrollRafRef.current = requestAnimationFrame(tickAutoScroll);
13424
+ }, [postToParentRef, stopAutoScroll]);
13425
+ const updateAutoScroll = useCallback7(
13426
+ (clientY) => {
13427
+ const clip = visibleClip(parentScrollRef.current);
13428
+ let delta = 0;
13429
+ if (clip) {
13430
+ const distTop = clientY - clip.top;
13431
+ const distBottom = clip.bottom - clientY;
13432
+ if (distTop >= 0 && distTop < EDGE_ZONE) {
13433
+ delta = -MAX_AUTO_SCROLL_SPEED * (1 - distTop / EDGE_ZONE);
13434
+ } else if (distBottom >= 0 && distBottom < EDGE_ZONE) {
13435
+ delta = MAX_AUTO_SCROLL_SPEED * (1 - distBottom / EDGE_ZONE);
13436
+ }
13437
+ }
13438
+ autoScrollDeltaRef.current = delta;
13439
+ if (delta !== 0 && autoScrollRafRef.current == null) {
13440
+ autoScrollRafRef.current = requestAnimationFrame(tickAutoScroll);
13441
+ } else if (delta === 0) {
13442
+ stopAutoScroll();
13443
+ }
13444
+ },
13445
+ [parentScrollRef, stopAutoScroll, tickAutoScroll]
13446
+ );
13447
+ const clearSectionDragVisuals = useCallback7(() => {
13448
+ sectionDragRef.current?.draggedEl.removeAttribute("data-ohw-section-dragging");
13449
+ sectionDragRef.current = null;
13450
+ setSectionDropSlots([]);
13451
+ setActiveSectionDropIndex(null);
13452
+ setIsSectionDragging(false);
13453
+ stopAutoScroll();
13454
+ document.documentElement.removeAttribute("data-ohw-section-dragging-root");
13455
+ unlockItemDragInteraction();
13456
+ }, [stopAutoScroll]);
13457
+ const refreshSectionDragVisuals = useCallback7(
13458
+ (session, clientX, clientY) => {
13459
+ session.lastClientX = clientX;
13460
+ session.lastClientY = clientY;
13461
+ const slots = buildSectionDropSlots(session.instanceId);
13462
+ const activeSlot = hitTestSectionDropSlot(clientY, slots);
13463
+ session.activeSlot = activeSlot;
13464
+ setSectionDropSlots(slots);
13465
+ const activeIdx = activeSlot ? slots.findIndex((s) => s.insertIndex === activeSlot.insertIndex) : -1;
13466
+ setActiveSectionDropIndex(activeIdx >= 0 ? activeIdx : null);
13467
+ updateAutoScroll(clientY);
13468
+ },
13469
+ [updateAutoScroll]
13470
+ );
13471
+ const beginSectionDrag = useCallback7(
13472
+ (session) => {
13473
+ sectionDragRef.current = session;
13474
+ setIsSectionDragging(true);
13475
+ lockItemDuringDrag();
13476
+ document.documentElement.setAttribute("data-ohw-section-dragging-root", "");
13477
+ session.draggedEl.setAttribute("data-ohw-section-dragging", "");
13478
+ refreshSectionDragVisuals(session, session.lastClientX, session.lastClientY);
13479
+ },
13480
+ [refreshSectionDragVisuals]
13481
+ );
13482
+ const commitSectionDrag = useCallback7(() => {
13483
+ const session = sectionDragRef.current;
13484
+ if (!session) {
13485
+ clearSectionDragVisuals();
13486
+ return;
13487
+ }
13488
+ const slot = session.activeSlot ?? hitTestSectionDropSlot(session.lastClientY, buildSectionDropSlots(session.instanceId));
13489
+ const entries = slot ? planSectionMove(session.instanceId, slot.insertIndex, window.location.pathname) : null;
13490
+ if (!entries) {
13491
+ clearSectionDragVisuals();
13492
+ return;
13493
+ }
13494
+ const orderJson = JSON.stringify(entries);
13495
+ editContentRef.current = { ...editContentRef.current, [SECTION_ORDER_KEY]: orderJson };
13496
+ postToParentRef.current({ type: "ow:change", nodes: [{ key: SECTION_ORDER_KEY, text: orderJson }] });
13497
+ applyPersistedOrder(entries);
13498
+ clearSectionDragVisuals();
13499
+ requestAnimationFrame(() => {
13500
+ if (editContentRef.current[SECTION_ORDER_KEY] === orderJson) {
13501
+ applyPersistedOrder(entries);
13502
+ }
13503
+ requestAnimationFrame(() => {
13504
+ window.dispatchEvent(new Event("resize"));
13505
+ });
13506
+ });
13507
+ }, [clearSectionDragVisuals, editContentRef, postToParentRef]);
13508
+ const startSectionPressDrag = useCallback7(
13509
+ (el, clientX, clientY, pointerId) => {
13510
+ if (navDragRef.current || footerDragRef.current || sectionDragRef.current) return false;
13511
+ const instanceId = instanceIdOf(el);
13512
+ if (!instanceId) return false;
13513
+ sectionPointerDragRef.current = {
13514
+ el,
13515
+ instanceId,
13516
+ startX: clientX,
13517
+ startY: clientY,
13518
+ pointerId,
13519
+ started: false
13520
+ };
13521
+ return true;
13522
+ },
13523
+ [footerDragRef, navDragRef]
13524
+ );
13525
+ useEffect11(() => {
13526
+ if (!isEditMode) return;
13527
+ const onPointerDown = (e) => {
13528
+ if (e.button !== 0) return;
13529
+ if (navDragRef.current || footerDragRef.current || sectionDragRef.current) return;
13530
+ if (sectionPointerDragRef.current) return;
13531
+ const target = e.target;
13532
+ if (!(target instanceof HTMLElement)) return;
13533
+ if (target.closest(SECTION_DRAG_EXCLUDED_SELECTOR)) return;
13534
+ const sectionEl = target.closest("[data-ohw-section]");
13535
+ if (!sectionEl || isChromeSection(sectionEl) || sectionEl.dataset.ohwSection === "footer") return;
13536
+ if (!topLevelSections().includes(sectionEl)) return;
13537
+ startSectionPressDrag(sectionEl, e.clientX, e.clientY, e.pointerId);
13538
+ };
13539
+ const onPointerMove = (e) => {
13540
+ const pending = sectionPointerDragRef.current;
13541
+ if (!pending) return;
13542
+ if (pending.started) {
13543
+ e.preventDefault();
13544
+ clearTextSelection();
13545
+ const session = sectionDragRef.current;
13546
+ if (!session) return;
13547
+ refreshSectionDragVisuals(session, e.clientX, e.clientY);
13548
+ return;
13549
+ }
13550
+ const dx = e.clientX - pending.startX;
13551
+ const dy = e.clientY - pending.startY;
13552
+ if (dx * dx + dy * dy < PRESS_THRESHOLD * PRESS_THRESHOLD) return;
13553
+ e.preventDefault();
13554
+ pending.started = true;
13555
+ armItemPressDrag();
13556
+ clearTextSelection();
13557
+ try {
13558
+ document.body.setPointerCapture(pending.pointerId);
13559
+ } catch {
13560
+ }
13561
+ beginSectionDrag({
13562
+ instanceId: pending.instanceId,
13563
+ draggedEl: pending.el,
13564
+ lastClientX: e.clientX,
13565
+ lastClientY: e.clientY,
13566
+ activeSlot: null
13567
+ });
13568
+ };
13569
+ const endPointerDrag = (e) => {
13570
+ const pending = sectionPointerDragRef.current;
13571
+ sectionPointerDragRef.current = null;
13572
+ try {
13573
+ if (document.body.hasPointerCapture(e.pointerId)) {
13574
+ document.body.releasePointerCapture(e.pointerId);
13575
+ }
13576
+ } catch {
13577
+ }
13578
+ if (!pending) return;
13579
+ if (!pending.started) {
13580
+ unlockItemDragInteraction();
13581
+ return;
13582
+ }
13583
+ suppressNextClickRef.current = true;
13584
+ suppressClickUntilRef.current = Date.now() + 500;
13585
+ commitSectionDrag();
13586
+ };
13587
+ const onKeyDown = (e) => {
13588
+ if (e.key !== "Escape") return;
13589
+ if (!sectionDragRef.current && !sectionPointerDragRef.current) return;
13590
+ sectionPointerDragRef.current = null;
13591
+ clearSectionDragVisuals();
13592
+ };
13593
+ document.addEventListener("pointerdown", onPointerDown, true);
13594
+ document.addEventListener("pointermove", onPointerMove, true);
13595
+ document.addEventListener("pointerup", endPointerDrag, true);
13596
+ document.addEventListener("pointercancel", endPointerDrag, true);
13597
+ document.addEventListener("keydown", onKeyDown, true);
13598
+ return () => {
13599
+ document.removeEventListener("pointerdown", onPointerDown, true);
13600
+ document.removeEventListener("pointermove", onPointerMove, true);
13601
+ document.removeEventListener("pointerup", endPointerDrag, true);
13602
+ document.removeEventListener("pointercancel", endPointerDrag, true);
13603
+ document.removeEventListener("keydown", onKeyDown, true);
13604
+ unlockItemDragInteraction();
13605
+ stopAutoScroll();
13606
+ };
13607
+ }, [
13608
+ beginSectionDrag,
13609
+ clearSectionDragVisuals,
13610
+ commitSectionDrag,
13611
+ footerDragRef,
13612
+ isEditMode,
13613
+ navDragRef,
13614
+ refreshSectionDragVisuals,
13615
+ startSectionPressDrag,
13616
+ stopAutoScroll,
13617
+ suppressClickUntilRef,
13618
+ suppressNextClickRef
13619
+ ]);
13620
+ return {
13621
+ sectionDragRef,
13622
+ sectionDropSlots,
13623
+ activeSectionDropIndex,
13624
+ isSectionDragging
13625
+ };
13626
+ }
13627
+
13628
+ // src/ui/footer-container-chrome.tsx
13629
+ import { Plus as Plus2 } from "lucide-react";
13630
+ import { jsx as jsx29, jsxs as jsxs19 } from "react/jsx-runtime";
13631
+ function FooterContainerChrome({
13632
+ rect,
13633
+ onAdd,
13634
+ addDisabled = false
13635
+ }) {
13636
+ const chromeGap = 6;
13637
+ const buttonMargin = 7;
13638
+ return /* @__PURE__ */ jsx29(TooltipProvider, { delayDuration: 0, children: /* @__PURE__ */ jsx29(
13639
+ "div",
13640
+ {
13641
+ "data-ohw-footer-container-chrome": "",
13642
+ "data-ohw-bridge": "",
13643
+ className: "pointer-events-none fixed z-[2147483647]",
13644
+ style: {
13645
+ top: rect.top - chromeGap,
13646
+ left: rect.left - chromeGap,
13647
+ width: rect.width + chromeGap * 2,
13648
+ height: rect.height + chromeGap * 2
13649
+ },
13650
+ children: /* @__PURE__ */ jsxs19(Tooltip, { children: [
13651
+ /* @__PURE__ */ jsx29(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsx29(
13652
+ "button",
13653
+ {
13654
+ type: "button",
13655
+ "data-ohw-footer-add-button": "",
13656
+ disabled: addDisabled,
13657
+ className: "pointer-events-auto absolute left-1/2 flex size-7 -translate-x-1/2 -translate-y-full items-center justify-center rounded-[10px] border border-border bg-background p-0.5 shadow-sm transition-colors hover:bg-muted/80 disabled:pointer-events-none disabled:opacity-40",
13658
+ style: { top: chromeGap - buttonMargin },
13659
+ "aria-label": "Add item",
13660
+ onMouseDown: (e) => {
13661
+ e.preventDefault();
13662
+ e.stopPropagation();
13663
+ },
13664
+ onClick: (e) => {
13665
+ e.preventDefault();
13666
+ e.stopPropagation();
13667
+ if (addDisabled) return;
13668
+ onAdd();
13669
+ },
13670
+ children: /* @__PURE__ */ jsx29(Plus2, { className: "size-4 shrink-0 text-foreground", "aria-hidden": true })
13671
+ }
13672
+ ) }),
13673
+ /* @__PURE__ */ jsx29(TooltipContent, { side: "bottom", sideOffset: 9, children: addDisabled ? "Maximum columns reached" : "Add item" })
13674
+ ] })
13675
+ }
13676
+ ) });
13677
+ }
13678
+
13679
+ // src/lib/carousel.ts
13680
+ import { useEffect as useEffect12, useState as useState11 } from "react";
13681
+ var CAROUSEL_ATTR = "data-ohw-carousel";
13682
+ var CAROUSEL_VALUE_ATTR = "data-ohw-carousel-value";
13683
+ var CAROUSEL_SLIDE_ATTR = "data-ohw-carousel-slide";
13684
+ var CAROUSEL_EVENT = "ohw:carousel-change";
13685
+ function listCarouselKeys() {
13686
+ const keys = /* @__PURE__ */ new Set();
13687
+ document.querySelectorAll(`[${CAROUSEL_ATTR}]`).forEach((el) => {
13688
+ const key = el.getAttribute("data-ohw-key");
13689
+ if (key) keys.add(key);
13690
+ });
13691
+ return [...keys];
13692
+ }
13693
+ function containersForKey(key) {
13694
+ return Array.from(
13695
+ document.querySelectorAll(`[${CAROUSEL_ATTR}][data-ohw-key="${CSS.escape(key)}"]`)
13696
+ );
13697
+ }
13698
+ function isCarouselKey(key) {
13699
+ return containersForKey(key).length > 0;
13700
+ }
13701
+ function parseSlides(raw) {
13702
+ if (!raw) return [];
13703
+ try {
13704
+ const parsed = JSON.parse(raw);
13705
+ if (!Array.isArray(parsed)) return [];
13706
+ return parsed.filter((s) => Boolean(s) && typeof s === "object").map((s) => ({ src: String(s.src ?? ""), alt: String(s.alt ?? "") }));
13707
+ } catch {
13708
+ return [];
13709
+ }
13710
+ }
13711
+ function readCarouselValue(key) {
13712
+ const container = containersForKey(key)[0];
13713
+ if (!container) return [];
13714
+ const fromAttr = parseSlides(container.getAttribute(CAROUSEL_VALUE_ATTR));
13715
+ if (fromAttr.length > 0) return fromAttr;
13716
+ return Array.from(container.querySelectorAll(`[${CAROUSEL_SLIDE_ATTR}]`)).filter((slide) => slide.closest(`[${CAROUSEL_ATTR}]`) === container).sort((a, b) => slideIndex(a) - slideIndex(b)).map((slide) => {
13717
+ const img = slide instanceof HTMLImageElement ? slide : slide.querySelector("img");
13718
+ return { src: img?.src ?? "", alt: img?.alt ?? "" };
13719
+ });
13720
+ }
13721
+ function slideIndex(el) {
13722
+ const raw = el.getAttribute(CAROUSEL_SLIDE_ATTR);
12773
13723
  const n = raw ? parseInt(raw, 10) : NaN;
12774
13724
  return Number.isFinite(n) ? n : 0;
12775
13725
  }
@@ -12789,8 +13739,8 @@ function applyCarouselNode(key, val) {
12789
13739
  return true;
12790
13740
  }
12791
13741
  function useOhwCarousel(key, initial) {
12792
- const [images, setImages] = useState10(initial);
12793
- useEffect11(() => {
13742
+ const [images, setImages] = useState11(initial);
13743
+ useEffect12(() => {
12794
13744
  const el = document.querySelector(
12795
13745
  `[${CAROUSEL_ATTR}][data-ohw-key="${CSS.escape(key)}"]`
12796
13746
  );
@@ -12868,7 +13818,7 @@ function collectEditableNodes(extraContent, root = document) {
12868
13818
  nodes.push({ key, type: "link", text: href });
12869
13819
  }
12870
13820
  if (extraContent) {
12871
- for (const key of [NAV_ORDER_KEY, FOOTER_ORDER_KEY, NAV_COUNT_KEY]) {
13821
+ for (const key of [NAV_ORDER_KEY, FOOTER_ORDER_KEY, NAV_COUNT_KEY, SECTION_ORDER_KEY]) {
12872
13822
  const text = extraContent[key];
12873
13823
  if (typeof text === "string" && text.length > 0) {
12874
13824
  nodes.push({ key, type: "meta", text });
@@ -12905,6 +13855,18 @@ function collectEditableNodes(extraContent, root = document) {
12905
13855
  }
12906
13856
  if (extraContent && !isScoped) {
12907
13857
  applyNavFooterDeleteOverrides(byKey, extraContent);
13858
+ for (const key of LOGO_IMAGE_KEYS) {
13859
+ if (!(key in extraContent)) continue;
13860
+ byKey.set(key, { key, type: "image", text: extraContent[key] ?? "" });
13861
+ }
13862
+ for (const key of [LOGO_PLACEHOLDER_KEY, LOGO_ALT_KEY]) {
13863
+ if (!(key in extraContent)) continue;
13864
+ byKey.set(key, { key, type: "meta", text: extraContent[key] ?? "" });
13865
+ }
13866
+ for (const key of LOGO_SIZE_KEYS) {
13867
+ if (!(key in extraContent)) continue;
13868
+ byKey.set(key, { key, type: "meta", text: extraContent[key] ?? "" });
13869
+ }
12908
13870
  }
12909
13871
  return Array.from(byKey.values());
12910
13872
  }
@@ -13309,6 +14271,7 @@ function fadeInImageElement(img, onReady) {
13309
14271
  function applyEditableImageSrc(img, url) {
13310
14272
  img.removeAttribute("srcset");
13311
14273
  img.removeAttribute("sizes");
14274
+ if (img.loading === "lazy") img.loading = "eager";
13312
14275
  img.src = url;
13313
14276
  }
13314
14277
  function fadeInBgImage(el, url, onReady) {
@@ -13373,21 +14336,10 @@ function parseSchedulingInsertAfter(insertAfter) {
13373
14336
  insertBefore: insertAfter.slice(idx + INSERT_BEFORE_MARKER.length)
13374
14337
  };
13375
14338
  }
13376
- function resolveSchedulingInsert(insertAfter, explicitInsertBefore) {
13377
- const parsed = parseSchedulingInsertAfter(insertAfter);
13378
- const insertBefore = explicitInsertBefore ?? parsed.insertBefore;
13379
- const effectiveInsertAfter = insertBefore && parsed.insertBefore === null ? `${insertAfter}${INSERT_BEFORE_MARKER}${insertBefore}` : insertAfter;
13380
- return { effectiveInsertAfter, insertBefore };
13381
- }
13382
- function getSchedulingMountPoint(insertAfter) {
13383
- const { anchor } = parseSchedulingInsertAfter(insertAfter);
13384
- let anchorEl = document.querySelector(`[data-ohw-section="${anchor}"]`);
13385
- if (!anchorEl && anchor === "scheduling") {
13386
- const widgets = Array.from(document.querySelectorAll('[data-ohw-section^="scheduling-"]'));
13387
- anchorEl = widgets.at(-1) ?? null;
13388
- }
13389
- if (!anchorEl) return null;
13390
- return anchorEl.closest('[data-ohw-section-container="scheduling"]') ?? anchorEl;
14339
+ function resolveEntryAnchor(entry) {
14340
+ if (entry.anchorId !== void 0) return { anchorId: entry.anchorId, beforeId: entry.beforeId ?? null };
14341
+ const parsed = parseSchedulingInsertAfter(entry.insertAfter);
14342
+ return { anchorId: parsed.anchor, beforeId: parsed.insertBefore };
13391
14343
  }
13392
14344
  function schedulingMountDepth(insertAfter) {
13393
14345
  if (!insertAfter.includes(INSERT_BEFORE_MARKER)) return 0;
@@ -13404,8 +14356,7 @@ function getPageSchedulingEntries(raw) {
13404
14356
  }
13405
14357
  }
13406
14358
  function isSchedulingWidgetMissing(entry) {
13407
- const { effectiveInsertAfter } = resolveSchedulingInsert(entry.insertAfter);
13408
- return !document.querySelector(`[data-ohw-section="${schedulingSectionId(effectiveInsertAfter)}"]`);
14359
+ return !document.querySelector(`[data-ohw-section="${schedulingSectionId(entry.insertAfter)}"]`);
13409
14360
  }
13410
14361
  function hasMissingSchedulingWidgets(entries) {
13411
14362
  return entries.some(isSchedulingWidgetMissing);
@@ -13435,16 +14386,17 @@ function initSectionsFromContent(content, removeExisting = false) {
13435
14386
  } catch {
13436
14387
  }
13437
14388
  }
13438
- function mountSchedulingWidget(insertAfter, notifyOnConnect = false, scheduleId, explicitInsertBefore) {
13439
- const { effectiveInsertAfter, insertBefore } = resolveSchedulingInsert(insertAfter, explicitInsertBefore);
13440
- const sectionId = schedulingSectionId(effectiveInsertAfter);
14389
+ function mountSchedulingWidget(anchorId, notifyOnConnect = false, scheduleId, beforeId, existingWidgetId) {
14390
+ const widgetId = existingWidgetId ?? (beforeId ? `${anchorId}${INSERT_BEFORE_MARKER}${beforeId}` : anchorId);
14391
+ const sectionId = schedulingSectionId(widgetId);
13441
14392
  if (document.querySelector(`[data-ohw-section="${sectionId}"]`)) return false;
13442
- const mountPoint = getSchedulingMountPoint(effectiveInsertAfter);
13443
- if (!mountPoint) return false;
14393
+ const anchorEl = document.querySelector(`[data-ohw-section="${anchorId}"]`);
14394
+ if (!anchorEl) return false;
14395
+ const mountPoint = anchorEl.closest('[data-ohw-section-container="scheduling"]') ?? anchorEl;
13444
14396
  const container = document.createElement("div");
13445
14397
  container.dataset.ohwSectionContainer = "scheduling";
13446
- if (insertBefore) {
13447
- const beforeAnchor = document.querySelector(`[data-ohw-section="${insertBefore}"]`);
14398
+ if (beforeId) {
14399
+ const beforeAnchor = document.querySelector(`[data-ohw-section="${beforeId}"]`);
13448
14400
  const beforePoint = beforeAnchor?.closest('[data-ohw-section-container="scheduling"]') ?? beforeAnchor;
13449
14401
  if (!beforePoint) return false;
13450
14402
  beforePoint.insertAdjacentElement("beforebegin", container);
@@ -13455,19 +14407,25 @@ function mountSchedulingWidget(insertAfter, notifyOnConnect = false, scheduleId,
13455
14407
  }
13456
14408
  tail.insertAdjacentElement("afterend", container);
13457
14409
  }
13458
- const root = createRoot2(container);
13459
- flushSync2(() => {
13460
- root.render(
13461
- /* @__PURE__ */ jsx33(
13462
- SchedulingWidget,
13463
- {
13464
- notifyOnConnect,
13465
- initialScheduleId: scheduleId,
13466
- insertAfter: effectiveInsertAfter
13467
- }
13468
- )
13469
- );
13470
- });
14410
+ try {
14411
+ const root = createRoot2(container);
14412
+ flushSync2(() => {
14413
+ root.render(
14414
+ /* @__PURE__ */ jsx33(
14415
+ SchedulingWidget,
14416
+ {
14417
+ notifyOnConnect,
14418
+ initialScheduleId: scheduleId,
14419
+ insertAfter: widgetId
14420
+ }
14421
+ )
14422
+ );
14423
+ });
14424
+ } catch (err) {
14425
+ console.error("[ow:scheduling] render threw", err);
14426
+ container.remove();
14427
+ return false;
14428
+ }
13471
14429
  const tracker = getSectionsTracker();
13472
14430
  let sections = [];
13473
14431
  try {
@@ -13475,10 +14433,12 @@ function mountSchedulingWidget(insertAfter, notifyOnConnect = false, scheduleId,
13475
14433
  } catch {
13476
14434
  }
13477
14435
  const inEditor = typeof window !== "undefined" && window.self !== window.top;
13478
- if (inEditor && !sections.find((s) => s.type === "scheduling" && s.insertAfter === effectiveInsertAfter)) {
14436
+ if (inEditor && !sections.find((s) => s.type === "scheduling" && s.insertAfter === widgetId)) {
13479
14437
  sections.push({
13480
14438
  type: "scheduling",
13481
- insertAfter: effectiveInsertAfter,
14439
+ insertAfter: widgetId,
14440
+ anchorId,
14441
+ beforeId: beforeId ?? null,
13482
14442
  pagePath: window.location.pathname,
13483
14443
  ...scheduleId ? { scheduleId } : {}
13484
14444
  });
@@ -13492,7 +14452,8 @@ function mountSchedulingEntries(entries, notifyOnConnect = false) {
13492
14452
  for (let i = pending.length - 1; i >= 0; i--) {
13493
14453
  const entry = pending[i];
13494
14454
  const shouldNotify = typeof notifyOnConnect === "function" ? notifyOnConnect(entry) : notifyOnConnect;
13495
- if (mountSchedulingWidget(entry.insertAfter, shouldNotify, entry.scheduleId)) {
14455
+ const { anchorId, beforeId } = resolveEntryAnchor(entry);
14456
+ if (mountSchedulingWidget(anchorId, shouldNotify, entry.scheduleId ?? null, beforeId, entry.insertAfter)) {
13496
14457
  pending.splice(i, 1);
13497
14458
  }
13498
14459
  }
@@ -13640,6 +14601,13 @@ function isInsideLinkEditor(target) {
13640
14601
  target.closest("[data-ohw-link-popover-root]") || target.closest("[data-ohw-link-modal-root]") || target.closest("[data-ohw-link-page-dropdown]") || target.closest("[data-ohw-section-picker]") || target.closest("[data-ohw-navbar-container-chrome]") || target.closest("[data-ohw-navbar-add-button]") || target.closest("[data-ohw-footer-container-chrome]") || target.closest("[data-ohw-footer-add-button]") || target.closest("[data-ohw-more-menu]") || target.closest('[data-slot="dropdown-menu-content"]') || target.closest('[data-slot="popover-content"]') || target.closest('[data-slot="dialog-content"]') || target.closest('[data-slot="dialog-overlay"]')
13641
14602
  );
13642
14603
  }
14604
+ function isInsideFloatingPanel(target) {
14605
+ return Boolean(target.closest("[data-ohw-floating-panel]"));
14606
+ }
14607
+ function isPointOverFloatingPanel(clientX, clientY) {
14608
+ const el = document.elementFromPoint(clientX, clientY);
14609
+ return Boolean(el instanceof Element && el.closest("[data-ohw-floating-panel]"));
14610
+ }
13643
14611
  function getHrefKeyFromElement(el) {
13644
14612
  if (!el) return null;
13645
14613
  const anchor = el.closest("[data-ohw-href-key]");
@@ -13885,7 +14853,7 @@ function getNavigationSelectionParent(el) {
13885
14853
  if (!isFooterLinksContainer(el) && (el.hasAttribute("data-ohw-footer-col") || el.hasAttribute("data-ohw-footer-column") || isInferredFooterGroup2(el))) {
13886
14854
  return getFooterLinksContainer();
13887
14855
  }
13888
- if (el.hasAttribute("data-ohw-nav-container") || el.hasAttribute("data-ohw-nav-drawer") || isFooterLinksContainer(el)) {
14856
+ 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)) {
13889
14857
  return getNavigationRoot(el);
13890
14858
  }
13891
14859
  return null;
@@ -14478,6 +15446,45 @@ function StateToggle({
14478
15446
  );
14479
15447
  }
14480
15448
  var contentCache = /* @__PURE__ */ new Map();
15449
+ var fetchedContentPaths = /* @__PURE__ */ new Set();
15450
+ var OHW_LOADER_STYLE = {
15451
+ position: "fixed",
15452
+ inset: 0,
15453
+ background: "#fff",
15454
+ zIndex: 2147483646,
15455
+ display: "flex",
15456
+ alignItems: "center",
15457
+ justifyContent: "center"
15458
+ };
15459
+ function OhwLoaderSpinner() {
15460
+ return /* @__PURE__ */ jsxs20("svg", { width: "28", height: "28", viewBox: "0 0 28 28", fill: "none", "aria-hidden": true, children: [
15461
+ /* @__PURE__ */ jsx33("circle", { cx: "14", cy: "14", r: "11", stroke: "#E7E5E4", strokeWidth: "3" }),
15462
+ /* @__PURE__ */ jsx33(
15463
+ "circle",
15464
+ {
15465
+ cx: "14",
15466
+ cy: "14",
15467
+ r: "11",
15468
+ stroke: "#1C1917",
15469
+ strokeWidth: "3",
15470
+ strokeDasharray: "17 52",
15471
+ strokeLinecap: "round",
15472
+ children: /* @__PURE__ */ jsx33(
15473
+ "animateTransform",
15474
+ {
15475
+ attributeName: "transform",
15476
+ type: "rotate",
15477
+ from: "0 14 14",
15478
+ to: "360 14 14",
15479
+ dur: "0.7s",
15480
+ repeatCount: "indefinite"
15481
+ }
15482
+ )
15483
+ }
15484
+ )
15485
+ ] });
15486
+ }
15487
+ var OHW_LOADER_PREHYDRATE_SCRIPT = `(function(){try{var p=location.hostname.split(".");var fromHost=p.length>=3&&p[0]!=="www"?p[0]:"";var fromQuery=new URLSearchParams(location.search).get("subdomain")||"";if(!fromHost&&!fromQuery)return;var e=document.getElementById("ohw-loader");if(e)e.style.display="flex"}catch(e){}})();`;
14481
15488
  function resolveSubdomain(subdomainFromQuery) {
14482
15489
  if (subdomainFromQuery) return subdomainFromQuery;
14483
15490
  if (typeof window !== "undefined") {
@@ -14500,8 +15507,8 @@ function OhhwellsBridge() {
14500
15507
  const router = useRouter3();
14501
15508
  const searchParams = useSearchParams();
14502
15509
  const isEditMode = isEditSessionActive();
14503
- const [bridgeRoot, setBridgeRoot] = useState11(null);
14504
- useEffect12(() => {
15510
+ const [bridgeRoot, setBridgeRoot] = useState12(null);
15511
+ useEffect13(() => {
14505
15512
  const figtreeFontId = "ohw-figtree-font";
14506
15513
  if (!document.getElementById(figtreeFontId)) {
14507
15514
  const preconnect1 = Object.assign(document.createElement("link"), { rel: "preconnect", href: "https://fonts.googleapis.com" });
@@ -14530,86 +15537,150 @@ function OhhwellsBridge() {
14530
15537
  const subdomain = resolveSubdomain(subdomainFromQuery);
14531
15538
  useLinkHrefGuardian(pathname, subdomain, isEditMode);
14532
15539
  useSavedLinkNavigation(isEditMode);
14533
- const postToParent2 = useCallback7((data) => {
15540
+ const postToParent2 = useCallback8((data) => {
14534
15541
  if (typeof window !== "undefined" && window.parent !== window) {
14535
15542
  window.parent.postMessage(data, "*");
14536
15543
  }
14537
15544
  }, []);
14538
- const [fetchState, setFetchState] = useState11("idle");
14539
- const autoSaveTimers = useRef9(/* @__PURE__ */ new Map());
14540
- const activeElRef = useRef9(null);
14541
- const pointerHeldRef = useRef9(false);
14542
- const selectedElRef = useRef9(null);
14543
- const selectedHrefKeyRef = useRef9(null);
14544
- const selectedFooterColAttrRef = useRef9(null);
14545
- const originalContentRef = useRef9(null);
14546
- const activeStateElRef = useRef9(null);
14547
- const parentScrollRef = useRef9(null);
14548
- const visibleViewportRef = useRef9(null);
14549
- const [dialogPortalContainer, setDialogPortalContainer] = useState11(null);
14550
- const attachVisibleViewport = useCallback7((node) => {
15545
+ const [fetchState, setFetchState] = useState12("idle");
15546
+ const autoSaveTimers = useRef10(/* @__PURE__ */ new Map());
15547
+ const activeElRef = useRef10(null);
15548
+ const pointerHeldRef = useRef10(false);
15549
+ const selectedElRef = useRef10(null);
15550
+ const selectedHrefKeyRef = useRef10(null);
15551
+ const selectedFooterColAttrRef = useRef10(null);
15552
+ const originalContentRef = useRef10(null);
15553
+ const activeStateElRef = useRef10(null);
15554
+ const parentScrollRef = useRef10(null);
15555
+ const visibleViewportRef = useRef10(null);
15556
+ const [dialogPortalContainer, setDialogPortalContainer] = useState12(null);
15557
+ const attachVisibleViewport = useCallback8((node) => {
14551
15558
  visibleViewportRef.current = node;
14552
15559
  setDialogPortalContainer(node);
14553
15560
  if (node) applyVisibleViewport(node, parentScrollRef.current);
14554
15561
  }, []);
14555
- const toolbarElRef = useRef9(null);
14556
- const glowElRef = useRef9(null);
14557
- const hoveredImageRef = useRef9(null);
14558
- const hoveredImageHasTextOverlapRef = useRef9(false);
14559
- const dragOverElRef = useRef9(null);
14560
- const [mediaHover, setMediaHover] = useState11(null);
14561
- const [carouselHover, setCarouselHover] = useState11(null);
14562
- const [uploadingRects, setUploadingRects] = useState11({});
14563
- const hoveredGapRef = useRef9(null);
14564
- const imageUnhoverTimerRef = useRef9(null);
14565
- const imageShowTimerRef = useRef9(null);
14566
- const editStylesRef = useRef9(null);
14567
- const activateRef = useRef9(() => {
14568
- });
14569
- const deactivateRef = useRef9(() => {
14570
- });
14571
- const selectRef = useRef9(() => {
14572
- });
14573
- const selectFrameRef = useRef9(() => {
14574
- });
14575
- const selectLogoRef = useRef9(() => {
14576
- });
14577
- const openLogoSizePanelRef = useRef9(() => {
14578
- });
14579
- const deselectRef = useRef9(() => {
14580
- });
14581
- const closeFloatingPanelOnlyRef = useRef9(() => {
14582
- });
14583
- const reselectNavigationItemRef = useRef9(() => {
14584
- });
14585
- const commitNavigationTextEditRef = useRef9(() => {
14586
- });
14587
- const handleDeleteSelectedRef = useRef9(() => false);
14588
- const runPendingDeleteUndoRef = useRef9(() => false);
14589
- const isFooterFrameSelectionRef = useRef9(false);
14590
- const refreshActiveCommandsRef = useRef9(() => {
14591
- });
14592
- const postToParentRef = useRef9(postToParent2);
14593
- postToParentRef.current = postToParent2;
14594
- const aiSectionApiRef = useRef9(null);
14595
- const sectionsLoadedRef = useRef9(false);
14596
- const pendingScheduleConfigRequests = useRef9([]);
14597
- const [toolbarRect, setToolbarRect] = useState11(null);
14598
- const [formPickRect, setFormPickRect] = useState11(null);
14599
- const formPickElRef = useRef9(null);
14600
- const [formViewState, setFormViewStateUi] = useState11("default");
14601
- const [formPickCount, setFormPickCount] = useState11(null);
14602
- const [formHoverRect, setFormHoverRect] = useState11(null);
14603
- const formHoverElRef = useRef9(null);
14604
- const [fieldPickRect, setFieldPickRect] = useState11(null);
14605
- const fieldPickElRef = useRef9(null);
14606
- const [fieldPickState, setFieldPickState] = useState11(null);
14607
- const [fieldTypePickerOpen, setFieldTypePickerOpen] = useState11(false);
14608
- const clearFormPick = useCallback7(() => {
14609
- const form = formPickElRef.current;
14610
- const editing = fieldPickElRef.current;
14611
- if (commitPlaceholderEdit(editing) && editing) {
14612
- const owner = editing.closest('[data-ohw-editable="form"]');
15562
+ const toolbarElRef = useRef10(null);
15563
+ const glowElRef = useRef10(null);
15564
+ const hoveredImageRef = useRef10(null);
15565
+ const hoveredImageHasTextOverlapRef = useRef10(false);
15566
+ const dragOverElRef = useRef10(null);
15567
+ const [mediaHover, setMediaHover] = useState12(null);
15568
+ const [selectedMedia, setSelectedMedia] = useState12(null);
15569
+ const selectedMediaElRef = useRef10(null);
15570
+ const clearMediaSelection = useCallback8(() => {
15571
+ const prev = selectedMediaElRef.current;
15572
+ selectedMediaElRef.current = null;
15573
+ setSelectedMedia(null);
15574
+ const sectionEl = prev?.closest("[data-ohw-section]") ?? null;
15575
+ if (sectionEl) {
15576
+ postToParentRef.current({
15577
+ type: "ow:section-selected",
15578
+ sectionId: sectionEl.dataset.ohwSection ?? null,
15579
+ sectionLabel: sectionEl.dataset.ohwSectionLabel ?? titleCaseSectionId(sectionEl.dataset.ohwSection ?? ""),
15580
+ key: null
15581
+ });
15582
+ }
15583
+ }, []);
15584
+ const clearMediaSelectionRef = useRef10(clearMediaSelection);
15585
+ clearMediaSelectionRef.current = clearMediaSelection;
15586
+ const selectMediaElement = useCallback8((el) => {
15587
+ const r2 = el.getBoundingClientRect();
15588
+ const video = el.dataset.ohwEditable === "video" ? el.querySelector("video") : null;
15589
+ selectedMediaElRef.current = el;
15590
+ setSelectedMedia({
15591
+ key: el.dataset.ohwKey ?? "",
15592
+ rect: { top: r2.top, left: r2.left, width: r2.width, height: r2.height },
15593
+ elementType: el.dataset.ohwEditable ?? "image",
15594
+ hasTextOverlap: false,
15595
+ isDragOver: false,
15596
+ ...video ? { videoAutoplay: video.autoplay, videoMuted: video.muted } : {}
15597
+ });
15598
+ const sectionEl = el.closest("[data-ohw-section]");
15599
+ aiSectionApiRef.current?.selectFromElement(el, { report: false });
15600
+ postToParentRef.current({
15601
+ type: "ow:section-selected",
15602
+ sectionId: sectionEl?.dataset.ohwSection ?? null,
15603
+ sectionLabel: sectionEl ? sectionEl.dataset.ohwSectionLabel ?? titleCaseSectionId(sectionEl.dataset.ohwSection ?? "") : null,
15604
+ key: el.dataset.ohwKey ?? null,
15605
+ // Display name for the pill — the raw key prettifies into fragments ("Img"); the
15606
+ // bridge knows what the node IS, so it names it.
15607
+ keyLabel: el.dataset.ohwEditable === "video" ? "Video" : el.dataset.ohwEditable === "bg-image" ? "Background" : "Image"
15608
+ });
15609
+ }, []);
15610
+ const selectMediaElementRef = useRef10(selectMediaElement);
15611
+ selectMediaElementRef.current = selectMediaElement;
15612
+ useEffect13(() => {
15613
+ if (!selectedMedia) return;
15614
+ const update = () => {
15615
+ const el = selectedMediaElRef.current;
15616
+ if (!el || !el.isConnected) {
15617
+ clearMediaSelection();
15618
+ return;
15619
+ }
15620
+ const r2 = el.getBoundingClientRect();
15621
+ setSelectedMedia(
15622
+ (prev) => prev ? { ...prev, rect: { top: r2.top, left: r2.left, width: r2.width, height: r2.height } } : prev
15623
+ );
15624
+ };
15625
+ window.addEventListener("scroll", update, true);
15626
+ window.addEventListener("resize", update);
15627
+ return () => {
15628
+ window.removeEventListener("scroll", update, true);
15629
+ window.removeEventListener("resize", update);
15630
+ };
15631
+ }, [selectedMedia !== null]);
15632
+ const [carouselHover, setCarouselHover] = useState12(null);
15633
+ const [uploadingRects, setUploadingRects] = useState12({});
15634
+ const hoveredGapRef = useRef10(null);
15635
+ const imageUnhoverTimerRef = useRef10(null);
15636
+ const imageShowTimerRef = useRef10(null);
15637
+ const editStylesRef = useRef10(null);
15638
+ const activateRef = useRef10(() => {
15639
+ });
15640
+ const deactivateRef = useRef10(() => {
15641
+ });
15642
+ const selectRef = useRef10(() => {
15643
+ });
15644
+ const selectFrameRef = useRef10(() => {
15645
+ });
15646
+ const selectLogoRef = useRef10(() => {
15647
+ });
15648
+ const openLogoSizePanelRef = useRef10(() => {
15649
+ });
15650
+ const deselectRef = useRef10(() => {
15651
+ });
15652
+ const closeFloatingPanelOnlyRef = useRef10(() => {
15653
+ });
15654
+ const reselectNavigationItemRef = useRef10(() => {
15655
+ });
15656
+ const commitNavigationTextEditRef = useRef10(() => {
15657
+ });
15658
+ const handleDeleteSelectedRef = useRef10(() => false);
15659
+ const runPendingDeleteUndoRef = useRef10(() => false);
15660
+ const isFooterFrameSelectionRef = useRef10(false);
15661
+ const refreshActiveCommandsRef = useRef10(() => {
15662
+ });
15663
+ const postToParentRef = useRef10(postToParent2);
15664
+ postToParentRef.current = postToParent2;
15665
+ const aiSectionApiRef = useRef10(null);
15666
+ const sectionsLoadedRef = useRef10(false);
15667
+ const pendingScheduleConfigRequests = useRef10([]);
15668
+ const [toolbarRect, setToolbarRect] = useState12(null);
15669
+ const [formPickRect, setFormPickRect] = useState12(null);
15670
+ const formPickElRef = useRef10(null);
15671
+ const [formViewState, setFormViewStateUi] = useState12("default");
15672
+ const [formPickCount, setFormPickCount] = useState12(null);
15673
+ const [formHoverRect, setFormHoverRect] = useState12(null);
15674
+ const formHoverElRef = useRef10(null);
15675
+ const [fieldPickRect, setFieldPickRect] = useState12(null);
15676
+ const fieldPickElRef = useRef10(null);
15677
+ const [fieldPickState, setFieldPickState] = useState12(null);
15678
+ const [fieldTypePickerOpen, setFieldTypePickerOpen] = useState12(false);
15679
+ const clearFormPick = useCallback8(() => {
15680
+ const form = formPickElRef.current;
15681
+ const editing = fieldPickElRef.current;
15682
+ if (commitPlaceholderEdit(editing) && editing) {
15683
+ const owner = editing.closest('[data-ohw-editable="form"]');
14613
15684
  if (owner) persistFieldsRef.current(owner);
14614
15685
  }
14615
15686
  if (form) {
@@ -14625,7 +15696,7 @@ function OhhwellsBridge() {
14625
15696
  formPickElRef.current = null;
14626
15697
  setFormPickRect(null);
14627
15698
  }, []);
14628
- const clearFieldPick = useCallback7(() => {
15699
+ const clearFieldPick = useCallback8(() => {
14629
15700
  const wrapper = fieldPickElRef.current;
14630
15701
  if (commitPlaceholderEdit(wrapper) && wrapper) {
14631
15702
  const form = wrapper.closest('[data-ohw-editable="form"]');
@@ -14635,9 +15706,9 @@ function OhhwellsBridge() {
14635
15706
  setFieldPickRect(null);
14636
15707
  setFieldPickState(null);
14637
15708
  }, []);
14638
- const persistFieldsRef = useRef9(() => {
15709
+ const persistFieldsRef = useRef10(() => {
14639
15710
  });
14640
- const persistFields = useCallback7(
15711
+ const persistFields = useCallback8(
14641
15712
  (form) => {
14642
15713
  const key = formKeyOf(form);
14643
15714
  if (!key) return;
@@ -14648,7 +15719,7 @@ function OhhwellsBridge() {
14648
15719
  []
14649
15720
  );
14650
15721
  persistFieldsRef.current = persistFields;
14651
- const selectField = useCallback7((wrapper) => {
15722
+ const selectField = useCallback8((wrapper) => {
14652
15723
  if (fieldPickElRef.current && fieldPickElRef.current !== wrapper) {
14653
15724
  commitPlaceholderEdit(fieldPickElRef.current);
14654
15725
  }
@@ -14661,7 +15732,7 @@ function OhhwellsBridge() {
14661
15732
  setFieldPickState({ type: fieldTypeOf(wrapper), required: isFieldRequired(wrapper) });
14662
15733
  setFieldTypePickerOpen(false);
14663
15734
  }, []);
14664
- const withSelectedField = useCallback7(
15735
+ const withSelectedField = useCallback8(
14665
15736
  (run) => {
14666
15737
  const wrapper = fieldPickElRef.current;
14667
15738
  const form = formPickElRef.current;
@@ -14674,28 +15745,28 @@ function OhhwellsBridge() {
14674
15745
  },
14675
15746
  [persistFields]
14676
15747
  );
14677
- const handleFieldTypeChange = useCallback7(
15748
+ const handleFieldTypeChange = useCallback8(
14678
15749
  (type) => withSelectedField((_form, wrapper) => {
14679
15750
  applyFieldType(wrapper, type);
14680
15751
  selectField(wrapper);
14681
15752
  }),
14682
15753
  [selectField, withSelectedField]
14683
15754
  );
14684
- const handleFieldRequiredToggle = useCallback7(
15755
+ const handleFieldRequiredToggle = useCallback8(
14685
15756
  () => withSelectedField((_form, wrapper) => {
14686
15757
  setFieldRequired(wrapper, !isFieldRequired(wrapper));
14687
15758
  selectField(wrapper);
14688
15759
  }),
14689
15760
  [selectField, withSelectedField]
14690
15761
  );
14691
- const handleFieldDuplicate = useCallback7(
15762
+ const handleFieldDuplicate = useCallback8(
14692
15763
  () => withSelectedField((form, wrapper) => {
14693
15764
  const copy = duplicateField(form, wrapper);
14694
15765
  selectField(copy);
14695
15766
  }),
14696
15767
  [selectField, withSelectedField]
14697
15768
  );
14698
- const handleFieldDelete = useCallback7(
15769
+ const handleFieldDelete = useCallback8(
14699
15770
  () => withSelectedField((_form, wrapper) => {
14700
15771
  removeField(wrapper);
14701
15772
  clearFieldPick();
@@ -14703,7 +15774,7 @@ function OhhwellsBridge() {
14703
15774
  }),
14704
15775
  [clearFieldPick, withSelectedField]
14705
15776
  );
14706
- const handleAddField = useCallback7(
15777
+ const handleAddField = useCallback8(
14707
15778
  (type) => {
14708
15779
  const form = formPickElRef.current;
14709
15780
  if (!form) return;
@@ -14719,8 +15790,8 @@ function OhhwellsBridge() {
14719
15790
  },
14720
15791
  [persistFields, selectField]
14721
15792
  );
14722
- const fieldDragRef = useRef9(null);
14723
- const buildFieldDropSlots = useCallback7((form, draggedKey) => {
15793
+ const fieldDragRef = useRef10(null);
15794
+ const buildFieldDropSlots = useCallback8((form, draggedKey) => {
14724
15795
  const others = listFieldWrappers(form).filter((el) => fieldKeyOf(el) !== draggedKey);
14725
15796
  const slots = others.map((el) => {
14726
15797
  const rect = el.getBoundingClientRect();
@@ -14733,7 +15804,7 @@ function OhhwellsBridge() {
14733
15804
  }
14734
15805
  return slots;
14735
15806
  }, []);
14736
- const handleFieldDragStart = useCallback7(() => {
15807
+ const handleFieldDragStart = useCallback8(() => {
14737
15808
  const wrapper = fieldPickElRef.current;
14738
15809
  const form = formPickElRef.current;
14739
15810
  if (!wrapper || !form) return;
@@ -14742,18 +15813,18 @@ function OhhwellsBridge() {
14742
15813
  setFieldDragging(true);
14743
15814
  setFieldDropSlots(buildFieldDropSlots(form, key));
14744
15815
  }, [buildFieldDropSlots]);
14745
- const handleFieldDragEnd = useCallback7(() => {
15816
+ const handleFieldDragEnd = useCallback8(() => {
14746
15817
  fieldDragRef.current = null;
14747
15818
  setFieldDropIndex(null);
14748
15819
  setFieldDropSlots([]);
14749
15820
  setFieldDragging(false);
14750
15821
  }, []);
14751
- const [fieldDropIndex, setFieldDropIndex] = useState11(null);
14752
- const [fieldDropSlots, setFieldDropSlots] = useState11([]);
14753
- const [fieldDragging, setFieldDragging] = useState11(false);
14754
- const clearFormPickRef = useRef9(clearFormPick);
15822
+ const [fieldDropIndex, setFieldDropIndex] = useState12(null);
15823
+ const [fieldDropSlots, setFieldDropSlots] = useState12([]);
15824
+ const [fieldDragging, setFieldDragging] = useState12(false);
15825
+ const clearFormPickRef = useRef10(clearFormPick);
14755
15826
  clearFormPickRef.current = clearFormPick;
14756
- useEffect12(() => {
15827
+ useEffect13(() => {
14757
15828
  const el = fieldPickElRef.current;
14758
15829
  if (!el || fieldPickRect === null) return;
14759
15830
  const observer = new ResizeObserver(() => {
@@ -14762,7 +15833,7 @@ function OhhwellsBridge() {
14762
15833
  observer.observe(el);
14763
15834
  return () => observer.disconnect();
14764
15835
  }, [fieldPickRect !== null, fieldPickState]);
14765
- useEffect12(() => {
15836
+ useEffect13(() => {
14766
15837
  const el = formPickElRef.current;
14767
15838
  if (!el || formPickRect === null) return;
14768
15839
  const observer = new ResizeObserver(() => {
@@ -14771,25 +15842,25 @@ function OhhwellsBridge() {
14771
15842
  observer.observe(el);
14772
15843
  return () => observer.disconnect();
14773
15844
  }, [formPickRect !== null, formViewState]);
14774
- const [toolbarVariant, setToolbarVariant] = useState11("none");
14775
- const toolbarVariantRef = useRef9("none");
15845
+ const [toolbarVariant, setToolbarVariant] = useState12("none");
15846
+ const toolbarVariantRef = useRef10("none");
14776
15847
  toolbarVariantRef.current = toolbarVariant;
14777
- const [selectedIsCta, setSelectedIsCta] = useState11(false);
14778
- const [selectedIsSocial, setSelectedIsSocial] = useState11(false);
14779
- const [selectedIsSocialsRow, setSelectedIsSocialsRow] = useState11(false);
14780
- const [reorderHrefKey, setReorderHrefKey] = useState11(null);
14781
- const [reorderDragDisabled, setReorderDragDisabled] = useState11(false);
14782
- const [toggleState, setToggleState] = useState11(null);
14783
- const [maxBadge, setMaxBadge] = useState11(null);
14784
- const [activeCommands, setActiveCommands] = useState11(/* @__PURE__ */ new Set());
14785
- const [sectionGap, setSectionGap] = useState11(null);
14786
- const [toolbarShowEditLink, setToolbarShowEditLink] = useState11(false);
14787
- const hoveredNavContainerRef = useRef9(null);
14788
- const [hoveredNavContainerRect, setHoveredNavContainerRect] = useState11(null);
14789
- const hoveredItemElRef = useRef9(null);
14790
- const [hoveredItemRect, setHoveredItemRect] = useState11(null);
14791
- const [hoveredTextRect, setHoveredTextRect] = useState11(null);
14792
- useEffect12(() => {
15848
+ const [selectedIsCta, setSelectedIsCta] = useState12(false);
15849
+ const [selectedIsSocial, setSelectedIsSocial] = useState12(false);
15850
+ const [selectedIsSocialsRow, setSelectedIsSocialsRow] = useState12(false);
15851
+ const [reorderHrefKey, setReorderHrefKey] = useState12(null);
15852
+ const [reorderDragDisabled, setReorderDragDisabled] = useState12(false);
15853
+ const [toggleState, setToggleState] = useState12(null);
15854
+ const [maxBadge, setMaxBadge] = useState12(null);
15855
+ const [activeCommands, setActiveCommands] = useState12(/* @__PURE__ */ new Set());
15856
+ const [sectionGap, setSectionGap] = useState12(null);
15857
+ const [toolbarShowEditLink, setToolbarShowEditLink] = useState12(false);
15858
+ const hoveredNavContainerRef = useRef10(null);
15859
+ const [hoveredNavContainerRect, setHoveredNavContainerRect] = useState12(null);
15860
+ const hoveredItemElRef = useRef10(null);
15861
+ const [hoveredItemRect, setHoveredItemRect] = useState12(null);
15862
+ const [hoveredTextRect, setHoveredTextRect] = useState12(null);
15863
+ useEffect13(() => {
14793
15864
  const sync = () => {
14794
15865
  const el = document.querySelector(
14795
15866
  '[data-ohw-hovered]:not([contenteditable]):not([data-ohw-href-key]):not([data-ohw-editable="form"] *)'
@@ -14810,43 +15881,56 @@ function OhhwellsBridge() {
14810
15881
  });
14811
15882
  return () => observer.disconnect();
14812
15883
  }, []);
14813
- const siblingHintElRef = useRef9(null);
14814
- const [siblingHintRect, setSiblingHintRect] = useState11(null);
14815
- const [siblingHintRects, setSiblingHintRects] = useState11([]);
14816
- const [isItemDragging, setIsItemDragging] = useState11(false);
14817
- const [isFooterFrameSelection, setIsFooterFrameSelection] = useState11(false);
15884
+ const siblingHintElRef = useRef10(null);
15885
+ const [siblingHintRect, setSiblingHintRect] = useState12(null);
15886
+ const [siblingHintRects, setSiblingHintRects] = useState12([]);
15887
+ const [isItemDragging, setIsItemDragging] = useState12(false);
15888
+ const [isFooterFrameSelection, setIsFooterFrameSelection] = useState12(false);
14818
15889
  isFooterFrameSelectionRef.current = isFooterFrameSelection;
14819
- const [floatingPanel, setFloatingPanel] = useState11(null);
14820
- const floatingPanelOpenRef = useRef9(false);
14821
- floatingPanelOpenRef.current = floatingPanel !== null;
14822
- const [floatingPanelPos, setFloatingPanelPos] = useState11(null);
14823
- const [logoSizeDraft, setLogoSizeDraft] = useState11(null);
14824
- const [editorViewport, setEditorViewport] = useState11("desktop");
14825
- const [parentScrollSnap, setParentScrollSnap] = useState11(null);
14826
- const [navDropdownPreviewOpen, setNavDropdownPreviewOpen] = useState11(null);
14827
- const [footerHeadingVisible, setFooterHeadingVisible] = useState11(null);
14828
- const footerDragRef = useRef9(null);
14829
- const [footerDropSlots, setFooterDropSlots] = useState11([]);
14830
- const [activeFooterDropIndex, setActiveFooterDropIndex] = useState11(null);
14831
- const [draggedItemRect, setDraggedItemRect] = useState11(null);
14832
- const footerPointerDragRef = useRef9(null);
14833
- const suppressNextClickRef = useRef9(false);
14834
- const suppressClickUntilRef = useRef9(0);
14835
- const [linkPopover, setLinkPopover] = useState11(null);
14836
- const linkPopoverSessionRef = useRef9(null);
14837
- const addNavAfterAnchorRef = useRef9(null);
14838
- const editContentRef = useRef9({});
14839
- const aiSectionsRef = useRef9("");
14840
- const pendingDeleteUndoRef = useRef9(null);
14841
- const [sitePages, setSitePages] = useState11([]);
14842
- const [sectionsByPath, setSectionsByPath] = useState11({});
14843
- const sectionsPrefetchGenRef = useRef9(0);
14844
- const setLinkPopoverRef = useRef9(setLinkPopover);
14845
- const linkPopoverPanelRef = useRef9(null);
14846
- const linkPopoverOpenRef = useRef9(false);
14847
- const linkPopoverGraceUntilRef = useRef9(0);
15890
+ const [navDropdownPreviewOpen, setNavDropdownPreviewOpen] = useState12(null);
15891
+ const [footerHeadingVisible, setFooterHeadingVisible] = useState12(null);
15892
+ const footerDragRef = useRef10(null);
15893
+ const [footerDropSlots, setFooterDropSlots] = useState12([]);
15894
+ const [activeFooterDropIndex, setActiveFooterDropIndex] = useState12(null);
15895
+ const [draggedItemRect, setDraggedItemRect] = useState12(null);
15896
+ const footerPointerDragRef = useRef10(null);
15897
+ const suppressNextClickRef = useRef10(false);
15898
+ const suppressClickUntilRef = useRef10(0);
15899
+ const [linkPopover, setLinkPopover] = useState12(null);
15900
+ const linkPopoverSessionRef = useRef10(null);
15901
+ const addNavAfterAnchorRef = useRef10(null);
15902
+ const editContentRef = useRef10({});
15903
+ const aiSectionsRef = useRef10("");
15904
+ const brandKitRef = useRef10("");
15905
+ const stylesRef = useRef10("");
15906
+ const pendingDeleteUndoRef = useRef10(null);
15907
+ const [floatingPanel, setFloatingPanel] = useState12(null);
15908
+ const floatingPanelOpenRef = useRef10(false);
15909
+ const setFloatingPanelRef = useRef10(setFloatingPanel);
15910
+ const [floatingPanelPos, setFloatingPanelPos] = useState12(null);
15911
+ const [logoSizeDraft, setLogoSizeDraft] = useState12(null);
15912
+ const [editorViewport, setEditorViewport] = useState12("desktop");
15913
+ const [parentScrollSnap, setParentScrollSnap] = useState12(null);
15914
+ const [sitePages, setSitePages] = useState12([]);
15915
+ const [sectionsByPath, setSectionsByPath] = useState12({});
15916
+ const sectionsPrefetchGenRef = useRef10(0);
15917
+ const setLinkPopoverRef = useRef10(setLinkPopover);
15918
+ const linkPopoverPanelRef = useRef10(null);
15919
+ const linkPopoverOpenRef = useRef10(false);
15920
+ const linkPopoverGraceUntilRef = useRef10(0);
14848
15921
  setLinkPopoverRef.current = setLinkPopover;
15922
+ setFloatingPanelRef.current = setFloatingPanel;
14849
15923
  linkPopoverSessionRef.current = linkPopover;
15924
+ floatingPanelOpenRef.current = Boolean(floatingPanel);
15925
+ useEffect13(() => {
15926
+ const syncViewport = () => {
15927
+ const next = window.innerWidth <= 480 ? "mobile" : "desktop";
15928
+ setEditorViewport((prev) => prev === next ? prev : next);
15929
+ };
15930
+ syncViewport();
15931
+ window.addEventListener("resize", syncViewport);
15932
+ return () => window.removeEventListener("resize", syncViewport);
15933
+ }, []);
14850
15934
  const {
14851
15935
  navDragRef,
14852
15936
  navDropSlots,
@@ -14879,10 +15963,20 @@ function OhhwellsBridge() {
14879
15963
  getNavigationItemAnchor,
14880
15964
  isDragHandleDisabled
14881
15965
  });
15966
+ const { sectionDropSlots, activeSectionDropIndex, isSectionDragging } = useSectionDrag({
15967
+ isEditMode,
15968
+ editContentRef,
15969
+ postToParentRef,
15970
+ parentScrollRef,
15971
+ navDragRef,
15972
+ footerDragRef,
15973
+ suppressNextClickRef,
15974
+ suppressClickUntilRef
15975
+ });
14882
15976
  const bumpLinkPopoverGrace = () => {
14883
15977
  linkPopoverGraceUntilRef.current = Date.now() + 350;
14884
15978
  };
14885
- const runSectionsPrefetch = useCallback7((pages) => {
15979
+ const runSectionsPrefetch = useCallback8((pages) => {
14886
15980
  if (!isEditMode || shouldUseDevFixtures() || pages.length === 0) return;
14887
15981
  const gen = ++sectionsPrefetchGenRef.current;
14888
15982
  const paths = pages.map((p) => p.path);
@@ -14901,9 +15995,9 @@ function OhhwellsBridge() {
14901
15995
  );
14902
15996
  });
14903
15997
  }, [isEditMode, pathname]);
14904
- const runSectionsPrefetchRef = useRef9(runSectionsPrefetch);
15998
+ const runSectionsPrefetchRef = useRef10(runSectionsPrefetch);
14905
15999
  runSectionsPrefetchRef.current = runSectionsPrefetch;
14906
- useEffect12(() => {
16000
+ useEffect13(() => {
14907
16001
  if (!linkPopover) {
14908
16002
  document.documentElement.removeAttribute("data-ohw-link-popover-open");
14909
16003
  return;
@@ -14931,7 +16025,7 @@ function OhhwellsBridge() {
14931
16025
  document.documentElement.removeAttribute("data-ohw-link-popover-open");
14932
16026
  };
14933
16027
  }, [linkPopover, postToParent2]);
14934
- useEffect12(() => {
16028
+ useEffect13(() => {
14935
16029
  if (!isEditMode) return;
14936
16030
  const useFixtures = shouldUseDevFixtures();
14937
16031
  if (useFixtures) {
@@ -14955,14 +16049,14 @@ function OhhwellsBridge() {
14955
16049
  if (!useFixtures) postToParent2({ type: "ow:request-site-pages" });
14956
16050
  return () => window.removeEventListener("message", onSitePages);
14957
16051
  }, [isEditMode, postToParent2]);
14958
- useEffect12(() => {
16052
+ useEffect13(() => {
14959
16053
  if (!isEditMode || shouldUseDevFixtures()) return;
14960
16054
  void loadAllSectionsManifest().then((manifest) => {
14961
16055
  if (Object.keys(manifest).length === 0) return;
14962
16056
  setSectionsByPath((prev) => ({ ...manifest, ...prev }));
14963
16057
  });
14964
16058
  }, [isEditMode]);
14965
- useEffect12(() => {
16059
+ useEffect13(() => {
14966
16060
  const update = () => {
14967
16061
  const el = activeElRef.current ?? selectedElRef.current;
14968
16062
  if (el) setToolbarRect(getEditMeasureEl(el).getBoundingClientRect());
@@ -14986,10 +16080,10 @@ function OhhwellsBridge() {
14986
16080
  vvp.removeEventListener("resize", update);
14987
16081
  };
14988
16082
  }, []);
14989
- const refreshStateRules = useCallback7(() => {
16083
+ const refreshStateRules = useCallback8(() => {
14990
16084
  editStylesRef.current?.forceHover && (editStylesRef.current.forceHover.textContent = collectStateRules());
14991
16085
  }, []);
14992
- const processConfigRequest = useCallback7((insertAfterVal) => {
16086
+ const processConfigRequest = useCallback8((insertAfterVal) => {
14993
16087
  const tracker = getSectionsTracker();
14994
16088
  let entries = [];
14995
16089
  try {
@@ -15012,7 +16106,7 @@ function OhhwellsBridge() {
15012
16106
  }
15013
16107
  window.postMessage({ type: "ow:schedule-config", insertAfter: insertAfterVal, scheduleId: null }, "*");
15014
16108
  }, [isEditMode]);
15015
- const deactivate = useCallback7(() => {
16109
+ const deactivate = useCallback8(() => {
15016
16110
  const el = activeElRef.current;
15017
16111
  if (!el) return;
15018
16112
  const isFormBlock = el.dataset.ohwEditable === "form";
@@ -15053,12 +16147,12 @@ function OhhwellsBridge() {
15053
16147
  setToolbarShowEditLink(false);
15054
16148
  postToParent2({ type: "ow:exit-edit" });
15055
16149
  }, [postToParent2]);
15056
- const clearSelectedAttr = useCallback7(() => {
16150
+ const clearSelectedAttr = useCallback8(() => {
15057
16151
  document.querySelectorAll("[data-ohw-selected]").forEach((el) => {
15058
16152
  el.removeAttribute("data-ohw-selected");
15059
16153
  });
15060
16154
  }, []);
15061
- const deselect = useCallback7(() => {
16155
+ const deselect = useCallback8(() => {
15062
16156
  clearSelectedAttr();
15063
16157
  selectedElRef.current = null;
15064
16158
  selectedHrefKeyRef.current = null;
@@ -15087,11 +16181,12 @@ function OhhwellsBridge() {
15087
16181
  setToolbarVariant("none");
15088
16182
  }
15089
16183
  }, [clearSelectedAttr]);
15090
- const markSelected = useCallback7((el) => {
16184
+ const markSelected = useCallback8((el) => {
15091
16185
  clearSelectedAttr();
16186
+ el.removeAttribute("data-ohw-hovered");
15092
16187
  el.setAttribute("data-ohw-selected", "");
15093
16188
  }, [clearSelectedAttr]);
15094
- const resolveHrefKeyElement = useCallback7((hrefKey) => {
16189
+ const resolveHrefKeyElement = useCallback8((hrefKey) => {
15095
16190
  if (isFooterHrefKey(hrefKey)) {
15096
16191
  return document.querySelector(
15097
16192
  `footer [data-ohw-href-key="${CSS.escape(hrefKey)}"]`
@@ -15106,7 +16201,7 @@ function OhhwellsBridge() {
15106
16201
  `[data-ohw-href-key="${CSS.escape(hrefKey)}"]`
15107
16202
  );
15108
16203
  }, []);
15109
- const resyncSelectedNavigationItem = useCallback7(() => {
16204
+ const resyncSelectedNavigationItem = useCallback8(() => {
15110
16205
  const hrefKey = selectedHrefKeyRef.current;
15111
16206
  if (hrefKey) {
15112
16207
  const link = resolveHrefKeyElement(hrefKey);
@@ -15144,7 +16239,7 @@ function OhhwellsBridge() {
15144
16239
  );
15145
16240
  }
15146
16241
  }, [resolveHrefKeyElement]);
15147
- const reselectNavigationItem = useCallback7((navAnchor) => {
16242
+ const reselectNavigationItem = useCallback8((navAnchor) => {
15148
16243
  selectedElRef.current = navAnchor;
15149
16244
  selectedHrefKeyRef.current = navAnchor.getAttribute("data-ohw-href-key");
15150
16245
  selectedFooterColAttrRef.current = null;
@@ -15175,7 +16270,7 @@ function OhhwellsBridge() {
15175
16270
  setToolbarShowEditLink(false);
15176
16271
  setActiveCommands(/* @__PURE__ */ new Set());
15177
16272
  }, [markSelected]);
15178
- const commitNavigationTextEdit = useCallback7((navAnchor) => {
16273
+ const commitNavigationTextEdit = useCallback8((navAnchor) => {
15179
16274
  const el = activeElRef.current;
15180
16275
  if (!el) return;
15181
16276
  const key = el.dataset.ohwKey;
@@ -15208,7 +16303,7 @@ function OhhwellsBridge() {
15208
16303
  postToParent2({ type: "ow:exit-edit" });
15209
16304
  reselectNavigationItem(navAnchor);
15210
16305
  }, [postToParent2, reselectNavigationItem]);
15211
- const handleAddTopLevelNavItem = useCallback7(() => {
16306
+ const handleAddTopLevelNavItem = useCallback8(() => {
15212
16307
  const items = listNavbarRootItems();
15213
16308
  addNavAfterAnchorRef.current = items[items.length - 1] ?? null;
15214
16309
  deselectRef.current();
@@ -15220,7 +16315,7 @@ function OhhwellsBridge() {
15220
16315
  intent: "add-nav"
15221
16316
  });
15222
16317
  }, []);
15223
- const maybeWarnNavLinkDropdownConflict = useCallback7(
16318
+ const maybeWarnNavLinkDropdownConflict = useCallback8(
15224
16319
  (anchor) => {
15225
16320
  if (!isNavbarHrefKey(anchor.getAttribute("data-ohw-href-key"))) return;
15226
16321
  if (!navDropdownsOpenOnClick()) return;
@@ -15233,7 +16328,7 @@ function OhhwellsBridge() {
15233
16328
  },
15234
16329
  [postToParent2]
15235
16330
  );
15236
- const handleNavDropdownOpenChange = useCallback7((open) => {
16331
+ const handleNavDropdownOpenChange = useCallback8((open) => {
15237
16332
  const selected = selectedElRef.current;
15238
16333
  if (!selected || !isNavigationItem2(selected)) return;
15239
16334
  setNavGroupForceOpen(selected, open);
@@ -15245,7 +16340,7 @@ function OhhwellsBridge() {
15245
16340
  }
15246
16341
  });
15247
16342
  }, []);
15248
- const handleFooterHeadingVisibleChange = useCallback7(
16343
+ const handleFooterHeadingVisibleChange = useCallback8(
15249
16344
  (visible) => {
15250
16345
  const selected = selectedElRef.current;
15251
16346
  if (!selected || !isFooterFrameSelectionRef.current) return;
@@ -15269,7 +16364,7 @@ function OhhwellsBridge() {
15269
16364
  },
15270
16365
  [postToParent2]
15271
16366
  );
15272
- const enterEditOnNewItem = useCallback7((anchor) => {
16367
+ const enterEditOnNewItem = useCallback8((anchor) => {
15273
16368
  const label = anchor.querySelector('[data-ohw-editable="text"]');
15274
16369
  if (!label) {
15275
16370
  selectRef.current(anchor);
@@ -15278,7 +16373,7 @@ function OhhwellsBridge() {
15278
16373
  setNavGroupForceOpen(anchor, true);
15279
16374
  activateRef.current(label);
15280
16375
  }, []);
15281
- const handleAddChildItem = useCallback7(() => {
16376
+ const handleAddChildItem = useCallback8(() => {
15282
16377
  const selected = selectedElRef.current;
15283
16378
  if (!selected) return;
15284
16379
  const socialsRow = isSocialsRow(selected) ? selected : findSocialsRow(selected);
@@ -15390,7 +16485,7 @@ function OhhwellsBridge() {
15390
16485
  enterEditOnNewItem(result.anchor);
15391
16486
  });
15392
16487
  }, [enterEditOnNewItem, isFooterFrameSelection, maybeWarnNavLinkDropdownConflict, postToParent2]);
15393
- const handleAddFooterColumn = useCallback7(() => {
16488
+ const handleAddFooterColumn = useCallback8(() => {
15394
16489
  if (!canAddFooterColumn()) {
15395
16490
  postToParent2({
15396
16491
  type: "ow:toast",
@@ -15411,7 +16506,7 @@ function OhhwellsBridge() {
15411
16506
  selectRef.current(result.firstLink);
15412
16507
  });
15413
16508
  }, [postToParent2]);
15414
- const clearFooterDragVisuals = useCallback7(() => {
16509
+ const clearFooterDragVisuals = useCallback8(() => {
15415
16510
  footerDragRef.current = null;
15416
16511
  setSiblingHintRects([]);
15417
16512
  setFooterDropSlots([]);
@@ -15420,7 +16515,7 @@ function OhhwellsBridge() {
15420
16515
  setIsItemDragging(false);
15421
16516
  unlockFooterDragInteraction();
15422
16517
  }, []);
15423
- const refreshFooterDragVisuals = useCallback7((session, activeSlot, clientX, clientY) => {
16518
+ const refreshFooterDragVisuals = useCallback8((session, activeSlot, clientX, clientY) => {
15424
16519
  const dragged = session.draggedEl;
15425
16520
  setDraggedItemRect(dragged.getBoundingClientRect());
15426
16521
  if (typeof clientX === "number" && typeof clientY === "number") {
@@ -15452,13 +16547,13 @@ function OhhwellsBridge() {
15452
16547
  const activeIdx = activeSlot ? slots.findIndex((s) => s.insertIndex === activeSlot.insertIndex) : -1;
15453
16548
  setActiveFooterDropIndex(activeIdx >= 0 ? activeIdx : null);
15454
16549
  }, []);
15455
- const refreshFooterDragVisualsRef = useRef9(refreshFooterDragVisuals);
16550
+ const refreshFooterDragVisualsRef = useRef10(refreshFooterDragVisuals);
15456
16551
  refreshFooterDragVisualsRef.current = refreshFooterDragVisuals;
15457
- const commitFooterDragRef = useRef9(() => {
16552
+ const commitFooterDragRef = useRef10(() => {
15458
16553
  });
15459
- const beginFooterDragRef = useRef9(() => {
16554
+ const beginFooterDragRef = useRef10(() => {
15460
16555
  });
15461
- const beginFooterDrag = useCallback7(
16556
+ const beginFooterDrag = useCallback8(
15462
16557
  (session) => {
15463
16558
  const rect = session.draggedEl.getBoundingClientRect();
15464
16559
  session.lastClientX = session.lastClientX || rect.left + rect.width / 2;
@@ -15478,7 +16573,7 @@ function OhhwellsBridge() {
15478
16573
  [refreshFooterDragVisuals]
15479
16574
  );
15480
16575
  beginFooterDragRef.current = beginFooterDrag;
15481
- const commitFooterDrag = useCallback7(
16576
+ const commitFooterDrag = useCallback8(
15482
16577
  (clientX, clientY) => {
15483
16578
  const session = footerDragRef.current;
15484
16579
  if (!session) {
@@ -15606,7 +16701,7 @@ function OhhwellsBridge() {
15606
16701
  [clearFooterDragVisuals, resolveHrefKeyElement, resyncSelectedNavigationItem]
15607
16702
  );
15608
16703
  commitFooterDragRef.current = commitFooterDrag;
15609
- const startFooterLinkDrag = useCallback7(
16704
+ const startFooterLinkDrag = useCallback8(
15610
16705
  (anchor, clientX, clientY, wasSelected) => {
15611
16706
  const hrefKey = anchor.getAttribute("data-ohw-href-key");
15612
16707
  if (!hrefKey) return false;
@@ -15642,7 +16737,7 @@ function OhhwellsBridge() {
15642
16737
  },
15643
16738
  [beginFooterDrag]
15644
16739
  );
15645
- const startFooterColumnDrag = useCallback7(
16740
+ const startFooterColumnDrag = useCallback8(
15646
16741
  (columnEl, clientX, clientY, wasSelected) => {
15647
16742
  const columns = listFooterColumns();
15648
16743
  const idx = columns.indexOf(columnEl);
@@ -15662,7 +16757,7 @@ function OhhwellsBridge() {
15662
16757
  },
15663
16758
  [beginFooterDrag]
15664
16759
  );
15665
- const handleItemDragStart = useCallback7(
16760
+ const handleItemDragStart = useCallback8(
15666
16761
  (e) => {
15667
16762
  const selected = selectedElRef.current;
15668
16763
  if (!selected) {
@@ -15682,7 +16777,7 @@ function OhhwellsBridge() {
15682
16777
  },
15683
16778
  [startFooterColumnDrag, startFooterLinkDrag, startNavLinkDrag]
15684
16779
  );
15685
- const handleItemDragEnd = useCallback7(
16780
+ const handleItemDragEnd = useCallback8(
15686
16781
  (e) => {
15687
16782
  if (footerDragRef.current) {
15688
16783
  const x = e?.clientX;
@@ -15708,7 +16803,7 @@ function OhhwellsBridge() {
15708
16803
  },
15709
16804
  [commitFooterDrag, commitNavDrag, navDragRef]
15710
16805
  );
15711
- const handleItemChromePointerDown = useCallback7((e) => {
16806
+ const handleItemChromePointerDown = useCallback8((e) => {
15712
16807
  if (e.button !== 0) return;
15713
16808
  const selected = selectedElRef.current;
15714
16809
  if (!selected) return;
@@ -15739,7 +16834,7 @@ function OhhwellsBridge() {
15739
16834
  }
15740
16835
  if (armNavPressFromChrome(selected, e.clientX, e.clientY, e.pointerId)) return;
15741
16836
  }, [armNavPressFromChrome]);
15742
- const handleItemChromeClick = useCallback7((clientX, clientY) => {
16837
+ const handleItemChromeClick = useCallback8((clientX, clientY) => {
15743
16838
  if (suppressNextClickRef.current || Date.now() < suppressClickUntilRef.current) {
15744
16839
  suppressNextClickRef.current = false;
15745
16840
  return;
@@ -15752,7 +16847,7 @@ function OhhwellsBridge() {
15752
16847
  }, []);
15753
16848
  reselectNavigationItemRef.current = reselectNavigationItem;
15754
16849
  commitNavigationTextEditRef.current = commitNavigationTextEdit;
15755
- const select = useCallback7((anchor) => {
16850
+ const select = useCallback8((anchor) => {
15756
16851
  if (!isNavigationItem2(anchor)) return;
15757
16852
  if (activeElRef.current) deactivate();
15758
16853
  aiSectionApiRef.current?.selectFromElement(anchor);
@@ -15795,7 +16890,7 @@ function OhhwellsBridge() {
15795
16890
  setFloatingPanel(null);
15796
16891
  setLogoSizeDraft(null);
15797
16892
  }, [deactivate, markSelected]);
15798
- const selectFrame = useCallback7((el) => {
16893
+ const selectFrame = useCallback8((el) => {
15799
16894
  if (!isNavigationContainer(el)) return;
15800
16895
  if (activeElRef.current) deactivate();
15801
16896
  aiSectionApiRef.current?.selectFromElement(el);
@@ -15846,7 +16941,7 @@ function OhhwellsBridge() {
15846
16941
  setFloatingPanel(null);
15847
16942
  setLogoSizeDraft(null);
15848
16943
  }, [deactivate, markSelected, postToParent2]);
15849
- const selectLogo = useCallback7(
16944
+ const selectLogo = useCallback8(
15850
16945
  (logoEl) => {
15851
16946
  if (activeElRef.current) deactivate();
15852
16947
  selectedElRef.current = logoEl;
@@ -15875,7 +16970,7 @@ function OhhwellsBridge() {
15875
16970
  },
15876
16971
  [deactivate, markSelected]
15877
16972
  );
15878
- const openLogoSizePanel = useCallback7((logoEl) => {
16973
+ const openLogoSizePanel = useCallback8((logoEl) => {
15879
16974
  const placement = getLogoPlacement(logoEl);
15880
16975
  const draft = readLogoSizeState(editContentRef.current, placement);
15881
16976
  setLogoSizeDraft(draft);
@@ -15888,7 +16983,7 @@ function OhhwellsBridge() {
15888
16983
  placement
15889
16984
  });
15890
16985
  }, []);
15891
- const openSocialsDisplayPanel = useCallback7((row) => {
16986
+ const openSocialsDisplayPanel = useCallback8((row) => {
15892
16987
  setParentScrollSnap(parentScrollRef.current);
15893
16988
  setFloatingPanel({
15894
16989
  key: "socials-display",
@@ -15898,7 +16993,7 @@ function OhhwellsBridge() {
15898
16993
  row
15899
16994
  });
15900
16995
  }, []);
15901
- const changeSocialsDisplay = useCallback7(
16996
+ const changeSocialsDisplay = useCallback8(
15902
16997
  (row, next) => {
15903
16998
  if (next.icon) {
15904
16999
  const missing = socialsMissingIcons(row);
@@ -15921,17 +17016,17 @@ function OhhwellsBridge() {
15921
17016
  },
15922
17017
  []
15923
17018
  );
15924
- const closeFloatingPanelOnly = useCallback7(() => {
17019
+ const closeFloatingPanelOnly = useCallback8(() => {
15925
17020
  setFloatingPanel(null);
15926
17021
  setLogoSizeDraft(null);
15927
17022
  }, []);
15928
17023
  closeFloatingPanelOnlyRef.current = closeFloatingPanelOnly;
15929
- const closeFloatingPanelAndDeselect = useCallback7(() => {
17024
+ const closeFloatingPanelAndDeselect = useCallback8(() => {
15930
17025
  setFloatingPanel(null);
15931
17026
  setLogoSizeDraft(null);
15932
17027
  deselectRef.current();
15933
17028
  }, []);
15934
- useEffect12(() => {
17029
+ useEffect13(() => {
15935
17030
  const session = floatingPanel && floatingPanel.kind === "logo-size" ? floatingPanel : null;
15936
17031
  if (!session || !logoSizeDraft) {
15937
17032
  postToParentRef.current({ type: "ow:logo-size-panel", open: false });
@@ -15950,7 +17045,7 @@ function OhhwellsBridge() {
15950
17045
  max: LOGO_SIZE_MAX
15951
17046
  });
15952
17047
  }, [floatingPanel, logoSizeDraft, editorViewport]);
15953
- const persistLogoSizeDraft = useCallback7(
17048
+ const persistLogoSizeDraft = useCallback8(
15954
17049
  (placement, draft) => {
15955
17050
  const desktopKey = LOGO_SIZE_DESKTOP_KEYS[placement];
15956
17051
  const mobileKey = LOGO_SIZE_MOBILE_KEYS[placement];
@@ -15990,7 +17085,7 @@ function OhhwellsBridge() {
15990
17085
  },
15991
17086
  [postToParent2]
15992
17087
  );
15993
- const activate = useCallback7((el, options) => {
17088
+ const activate = useCallback8((el, options) => {
15994
17089
  if (activeElRef.current === el) return;
15995
17090
  if (isIconEditable(el)) return;
15996
17091
  if (el.hasAttribute("data-ohw-social-label")) return;
@@ -16074,8 +17169,8 @@ function OhhwellsBridge() {
16074
17169
  openLogoSizePanelRef.current = openLogoSizePanel;
16075
17170
  deselectRef.current = deselect;
16076
17171
  closeFloatingPanelOnlyRef.current = closeFloatingPanelOnly;
16077
- const lastSiteWideScopeRef = useRef9(null);
16078
- useEffect12(() => {
17172
+ const lastSiteWideScopeRef = useRef10(null);
17173
+ useEffect13(() => {
16079
17174
  if (!isEditMode) {
16080
17175
  if (lastSiteWideScopeRef.current !== false) {
16081
17176
  lastSiteWideScopeRef.current = false;
@@ -16108,15 +17203,27 @@ function OhhwellsBridge() {
16108
17203
  }
16109
17204
  const applyContent = (content) => {
16110
17205
  const imageLoads = [];
17206
+ if (typeof content[BRAND_KIT_KEY] === "string") {
17207
+ brandKitRef.current = content[BRAND_KIT_KEY];
17208
+ applyBrandToDom(parseBrandKit(content[BRAND_KIT_KEY]));
17209
+ }
16111
17210
  if (typeof content[AI_SECTIONS_KEY] === "string") {
16112
17211
  aiSectionsRef.current = content[AI_SECTIONS_KEY];
16113
17212
  applyAiSectionsToDom(parseAiSectionsState(content[AI_SECTIONS_KEY]));
16114
17213
  }
17214
+ if (typeof content[STYLE_STORE_KEY] === "string") {
17215
+ stylesRef.current = content[STYLE_STORE_KEY];
17216
+ applyStylesToDom(parseStyleStore(content[STYLE_STORE_KEY]));
17217
+ }
17218
+ applyBrandChrome(content);
16115
17219
  for (const [key, val] of Object.entries(content)) {
16116
17220
  if (key === "__ohw_sections") continue;
16117
17221
  if (key === AI_SECTIONS_KEY) continue;
16118
17222
  if (key === LOGO_PLACEHOLDER_KEY) continue;
16119
17223
  if (LOGO_IMAGE_KEYS.includes(key)) continue;
17224
+ if (key === BRAND_KIT_KEY) continue;
17225
+ if (key === STYLE_STORE_KEY) continue;
17226
+ if (BRAND_CHROME_KEYS.has(key)) continue;
16120
17227
  if (applyVideoSettingNode(key, val)) continue;
16121
17228
  if (applyCarouselNode(key, val)) continue;
16122
17229
  document.querySelectorAll(`[data-ohw-key="${key}"]`).forEach((el) => {
@@ -16174,7 +17281,9 @@ function OhhwellsBridge() {
16174
17281
  let cancelled = false;
16175
17282
  setFetchState("loading");
16176
17283
  const apiUrl = process.env.NEXT_PUBLIC_FLOWOPS_API_URL ?? "https://flowops-backend-staging-2yfdh7pwpq-as.a.run.app";
16177
- fetch(`${apiUrl}/api/public/sites/${subdomain}/content`, { cache: "no-store" }).then((r2) => r2.ok ? r2.json() : null).then((data) => {
17284
+ const initialPath = pathname;
17285
+ fetchedContentPaths.add(`${subdomain}::${initialPath}`);
17286
+ fetch(`${apiUrl}/api/public/sites/${subdomain}/content?path=${encodeURIComponent(initialPath)}`, { cache: "no-store" }).then((r2) => r2.ok ? r2.json() : null).then((data) => {
16178
17287
  if (cancelled) return;
16179
17288
  const content = data?.content ?? {};
16180
17289
  contentCache.set(subdomain, content);
@@ -16187,7 +17296,7 @@ function OhhwellsBridge() {
16187
17296
  cancelled = true;
16188
17297
  };
16189
17298
  }, [subdomain, isEditMode]);
16190
- useEffect12(() => {
17299
+ useEffect13(() => {
16191
17300
  if (!isEditMode) return;
16192
17301
  const resolveIndex = (form, clientY) => {
16193
17302
  const wrappers = listFieldWrappers(form).filter((el) => fieldKeyOf(el) !== fieldDragRef.current?.key);
@@ -16228,7 +17337,7 @@ function OhhwellsBridge() {
16228
17337
  window.removeEventListener("drop", onDrop, true);
16229
17338
  };
16230
17339
  }, [buildFieldDropSlots, isEditMode, persistFields, selectField]);
16231
- useEffect12(() => {
17340
+ useEffect13(() => {
16232
17341
  if (!isEditMode) return;
16233
17342
  const mark = () => document.querySelectorAll('[data-ohw-editable="form"]').forEach((form) => {
16234
17343
  markFormFields(form);
@@ -16240,7 +17349,7 @@ function OhhwellsBridge() {
16240
17349
  });
16241
17350
  return () => observer.disconnect();
16242
17351
  }, [isEditMode, fetchState, pathname]);
16243
- useEffect12(() => {
17352
+ useEffect13(() => {
16244
17353
  if (!isEditMode) return;
16245
17354
  let saveTimer = null;
16246
17355
  const onInput = (e) => {
@@ -16262,14 +17371,14 @@ function OhhwellsBridge() {
16262
17371
  document.addEventListener("input", onInput, true);
16263
17372
  return () => document.removeEventListener("input", onInput, true);
16264
17373
  }, [isEditMode, persistFields]);
16265
- useEffect12(() => {
17374
+ useEffect13(() => {
16266
17375
  if (isEditMode || fetchState !== "done") return;
16267
17376
  const content = contentCache.get(subdomain) ?? {};
16268
17377
  document.querySelectorAll('[data-ohw-editable="form"]').forEach((form) => {
16269
17378
  reconcileFieldsFromContent(form, content);
16270
17379
  });
16271
17380
  }, [isEditMode, fetchState, subdomain]);
16272
- useEffect12(() => {
17381
+ useEffect13(() => {
16273
17382
  if (!isEditMode) return;
16274
17383
  const swallow = (e) => {
16275
17384
  const target = e.target;
@@ -16278,12 +17387,12 @@ function OhhwellsBridge() {
16278
17387
  document.addEventListener("submit", swallow, true);
16279
17388
  return () => document.removeEventListener("submit", swallow, true);
16280
17389
  }, [isEditMode]);
16281
- useEffect12(() => {
17390
+ useEffect13(() => {
16282
17391
  if (isEditMode || fetchState !== "done") return;
16283
17392
  const apiUrl = process.env.NEXT_PUBLIC_FLOWOPS_API_URL ?? "https://flowops-backend-staging-2yfdh7pwpq-as.a.run.app";
16284
17393
  bindPublishedForms(apiUrl, subdomain, contentCache.get(subdomain) ?? {});
16285
17394
  }, [isEditMode, fetchState, subdomain]);
16286
- useEffect12(() => {
17395
+ useEffect13(() => {
16287
17396
  if (!subdomain || isEditMode) return;
16288
17397
  let debounceTimer = null;
16289
17398
  let observer = null;
@@ -16294,10 +17403,21 @@ function OhhwellsBridge() {
16294
17403
  initSectionInstancesFromContent(content, window.location.pathname);
16295
17404
  observer?.disconnect();
16296
17405
  try {
17406
+ applyBrandChrome(content);
17407
+ if (typeof content[BRAND_KIT_KEY] === "string") {
17408
+ applyBrandToDom(parseBrandKit(content[BRAND_KIT_KEY]));
17409
+ }
17410
+ if (typeof content[STYLE_STORE_KEY] === "string") {
17411
+ applyStylesToDom(parseStyleStore(content[STYLE_STORE_KEY]));
17412
+ }
16297
17413
  for (const [key, val] of Object.entries(content)) {
16298
17414
  if (key === "__ohw_sections") continue;
16299
17415
  if (key === LOGO_PLACEHOLDER_KEY) continue;
16300
17416
  if (LOGO_IMAGE_KEYS.includes(key)) continue;
17417
+ if (key === BRAND_KIT_KEY) continue;
17418
+ if (key === STYLE_STORE_KEY) continue;
17419
+ if (key === STYLE_STORE_KEY) continue;
17420
+ if (BRAND_CHROME_KEYS.has(key)) continue;
16301
17421
  if (applyVideoSettingNode(key, val)) continue;
16302
17422
  if (applyCarouselNode(key, val)) continue;
16303
17423
  document.querySelectorAll(`[data-ohw-key="${key}"]`).forEach((el) => {
@@ -16340,6 +17460,17 @@ function OhhwellsBridge() {
16340
17460
  debounceTimer = setTimeout(applyFromCache, 150);
16341
17461
  };
16342
17462
  applyFromCache();
17463
+ const pathCacheKey = `${subdomain}::${pathname}`;
17464
+ if (!fetchedContentPaths.has(pathCacheKey)) {
17465
+ fetchedContentPaths.add(pathCacheKey);
17466
+ const apiUrl = process.env.NEXT_PUBLIC_FLOWOPS_API_URL ?? "https://flowops-backend-staging-2yfdh7pwpq-as.a.run.app";
17467
+ fetch(`${apiUrl}/api/public/sites/${subdomain}/content?path=${encodeURIComponent(pathname)}`, { cache: "no-store" }).then((r2) => r2.ok ? r2.json() : null).then((data) => {
17468
+ if (!data?.content) return;
17469
+ contentCache.set(subdomain, data.content);
17470
+ applyFromCache();
17471
+ }).catch(() => {
17472
+ });
17473
+ }
16343
17474
  observer = new MutationObserver(scheduleApply);
16344
17475
  observer.observe(document.body, { childList: true, subtree: true });
16345
17476
  return () => {
@@ -16353,10 +17484,10 @@ function OhhwellsBridge() {
16353
17484
  const visible = Boolean(subdomain) && fetchState !== "done";
16354
17485
  el.style.display = visible ? "flex" : "none";
16355
17486
  }, [subdomain, fetchState]);
16356
- useEffect12(() => {
17487
+ useEffect13(() => {
16357
17488
  postToParent2({ type: "ow:navigation", path: pathname });
16358
17489
  }, [pathname, postToParent2]);
16359
- useEffect12(() => {
17490
+ useEffect13(() => {
16360
17491
  if (!isEditMode) return;
16361
17492
  if (linkPopoverSessionRef.current?.intent === "add-nav") return;
16362
17493
  if (document.querySelector("[data-ohw-section-picker]")) return;
@@ -16364,7 +17495,7 @@ function OhhwellsBridge() {
16364
17495
  deselectRef.current();
16365
17496
  deactivateRef.current();
16366
17497
  }, [pathname, isEditMode]);
16367
- useEffect12(() => {
17498
+ useEffect13(() => {
16368
17499
  const contentForNav = () => {
16369
17500
  if (isEditMode) return editContentRef.current;
16370
17501
  if (!subdomain) return {};
@@ -16431,31 +17562,36 @@ function OhhwellsBridge() {
16431
17562
  observer?.disconnect();
16432
17563
  };
16433
17564
  }, [isEditMode, pathname, subdomain, fetchState, resyncSelectedNavigationItem]);
16434
- useEffect12(() => {
17565
+ useEffect13(() => {
16435
17566
  if (!isEditMode) return;
17567
+ let lastPosted = 0;
16436
17568
  const measure = () => {
16437
17569
  const h = document.body.scrollHeight;
16438
- if (h > 50) postToParent2({ type: "ow:height", height: h });
17570
+ if (h > 50 && Math.abs(h - lastPosted) > 1) {
17571
+ lastPosted = h;
17572
+ postToParent2({ type: "ow:height", height: h });
17573
+ }
17574
+ };
17575
+ let raf = null;
17576
+ const schedule = () => {
17577
+ if (raf != null) return;
17578
+ raf = requestAnimationFrame(() => {
17579
+ raf = null;
17580
+ measure();
17581
+ });
16439
17582
  };
16440
17583
  const t1 = setTimeout(measure, 50);
16441
17584
  const t2 = setTimeout(measure, 500);
16442
- let lastWidth = window.innerWidth;
16443
- let resizeTimer = null;
16444
- const handleResize = () => {
16445
- if (window.innerWidth === lastWidth) return;
16446
- lastWidth = window.innerWidth;
16447
- if (resizeTimer) clearTimeout(resizeTimer);
16448
- resizeTimer = setTimeout(measure, 150);
16449
- };
16450
- window.addEventListener("resize", handleResize);
17585
+ const ro = new ResizeObserver(schedule);
17586
+ ro.observe(document.body);
16451
17587
  return () => {
16452
17588
  clearTimeout(t1);
16453
17589
  clearTimeout(t2);
16454
- if (resizeTimer) clearTimeout(resizeTimer);
16455
- window.removeEventListener("resize", handleResize);
17590
+ if (raf != null) cancelAnimationFrame(raf);
17591
+ ro.disconnect();
16456
17592
  };
16457
17593
  }, [pathname, isEditMode, postToParent2]);
16458
- useEffect12(() => {
17594
+ useEffect13(() => {
16459
17595
  if (!subdomainFromQuery || isEditMode) return;
16460
17596
  const handleClick = (e) => {
16461
17597
  const anchor = e.target.closest("a");
@@ -16471,7 +17607,7 @@ function OhhwellsBridge() {
16471
17607
  document.addEventListener("click", handleClick, true);
16472
17608
  return () => document.removeEventListener("click", handleClick, true);
16473
17609
  }, [subdomainFromQuery, isEditMode, router]);
16474
- useEffect12(() => {
17610
+ useEffect13(() => {
16475
17611
  if (!isEditMode) {
16476
17612
  editStylesRef.current?.base.remove();
16477
17613
  editStylesRef.current?.forceHover.remove();
@@ -16633,16 +17769,21 @@ function OhhwellsBridge() {
16633
17769
  return;
16634
17770
  }
16635
17771
  const target = e.target;
17772
+ if (target.closest("[data-ohw-ai-review]")) return;
16636
17773
  if (target.closest("[data-ohw-toolbar]")) return;
16637
17774
  if (target.closest("[data-ohw-state-toggle]")) return;
16638
17775
  if (target.closest("[data-ohw-max-badge]")) return;
16639
17776
  if (isInsideLinkEditor(target)) return;
17777
+ if (isInsideFloatingPanel(target)) return;
16640
17778
  if (target.closest("[data-ohw-form-toolbar]")) return;
16641
17779
  if (target.closest(
16642
17780
  '[data-ohw-field-toolbar], [data-ohw-field-type-picker], [data-radix-popper-content-wrapper], [role="menu"], [data-slot="dropdown-menu-content"]'
16643
17781
  )) {
16644
17782
  return;
16645
17783
  }
17784
+ if (selectedMediaElRef.current && !selectedMediaElRef.current.contains(target) && !target.closest("[data-ohw-media-overlay], [data-ohw-edit-chrome], [data-ohw-item-interaction]")) {
17785
+ clearMediaSelectionRef.current();
17786
+ }
16646
17787
  {
16647
17788
  const formEl = getFormElement(target);
16648
17789
  const onSuccessText = target.closest(`[${SUCCESS_TEXT_ATTR}]`);
@@ -16790,8 +17931,11 @@ function OhhwellsBridge() {
16790
17931
  if (isMediaEditable(editable) && !buttonOnMedia) {
16791
17932
  e.preventDefault();
16792
17933
  e.stopPropagation();
16793
- aiSectionApiRef.current?.selectFromElement(editable);
16794
- postToParentRef.current({ type: "ow:image-pick", key: editable.dataset.ohwKey ?? "", elementType: editable.dataset.ohwEditable ?? "image" });
17934
+ if (selectedMediaElRef.current === editable) {
17935
+ postToParentRef.current({ type: "ow:image-pick", key: editable.dataset.ohwKey ?? "", elementType: editable.dataset.ohwEditable ?? "image" });
17936
+ } else {
17937
+ selectMediaElementRef.current(editable);
17938
+ }
16795
17939
  return;
16796
17940
  }
16797
17941
  const hrefLookupTarget = buttonOnMedia ? clickedButton : editable;
@@ -16910,10 +18054,12 @@ function OhhwellsBridge() {
16910
18054
  };
16911
18055
  const handleDblClick = (e) => {
16912
18056
  const target = e.target;
18057
+ if (target.closest("[data-ohw-ai-review]")) return;
16913
18058
  if (target.closest("[data-ohw-toolbar]")) return;
16914
18059
  if (target.closest("[data-ohw-state-toggle]")) return;
16915
18060
  if (target.closest("[data-ohw-max-badge]")) return;
16916
18061
  if (isInsideLinkEditor(target)) return;
18062
+ if (isInsideFloatingPanel(target)) return;
16917
18063
  if (target.closest('[data-ohw-edit-chrome], [data-ohw-item-interaction], [data-ohw-drag-handle-container], [data-slot="drag-handle"]')) {
16918
18064
  return;
16919
18065
  }
@@ -16954,11 +18100,14 @@ function OhhwellsBridge() {
16954
18100
  setSiblingHintRects([]);
16955
18101
  return;
16956
18102
  }
16957
- if (document.documentElement.hasAttribute("data-ohw-panel-dragging") || target.closest("[data-ohw-floating-panel]")) {
18103
+ if (document.documentElement.hasAttribute("data-ohw-panel-dragging") || isInsideFloatingPanel(target) || isPointOverFloatingPanel(e.clientX, e.clientY)) {
16958
18104
  hoveredItemElRef.current = null;
16959
18105
  setHoveredItemRect(null);
16960
18106
  hoveredNavContainerRef.current = null;
16961
18107
  setHoveredNavContainerRect(null);
18108
+ siblingHintElRef.current = null;
18109
+ setSiblingHintRect(null);
18110
+ setSiblingHintRects([]);
16962
18111
  return;
16963
18112
  }
16964
18113
  {
@@ -16976,6 +18125,16 @@ function OhhwellsBridge() {
16976
18125
  return;
16977
18126
  }
16978
18127
  }
18128
+ if (allowNavContainerHover) {
18129
+ const socialsRow = isSocialsRow(target) ? target : null;
18130
+ if (socialsRow && !getSocialItem(target) && selected2 !== socialsRow) {
18131
+ hoveredNavContainerRef.current = socialsRow;
18132
+ setHoveredNavContainerRect(socialsRow.getBoundingClientRect());
18133
+ hoveredItemElRef.current = null;
18134
+ setHoveredItemRect(null);
18135
+ return;
18136
+ }
18137
+ }
16979
18138
  if (allowFooterLinksHover) {
16980
18139
  const navContainer = target.closest("[data-ohw-nav-container]");
16981
18140
  if (!navContainer) {
@@ -17061,13 +18220,12 @@ function OhhwellsBridge() {
17061
18220
  clearHrefKeyHover(hoverTarget);
17062
18221
  hoveredItemElRef.current = hoverTarget;
17063
18222
  setHoveredItemRect(hoverTarget.getBoundingClientRect());
17064
- } else if (!isInsideNavigationItem(editable)) {
18223
+ } else if (!isInsideNavigationItem(editable) && hoverTarget !== selectedElRef.current) {
17065
18224
  hoverTarget.setAttribute("data-ohw-hovered", "");
17066
18225
  if (editable.closest("footer") || editable.closest('[data-ohw-section="footer"]')) {
17067
18226
  hoveredNavContainerRef.current = null;
17068
18227
  setHoveredNavContainerRect(null);
17069
18228
  hoveredItemElRef.current = editable;
17070
- setHoveredItemRect(editable.getBoundingClientRect());
17071
18229
  }
17072
18230
  }
17073
18231
  }
@@ -17364,7 +18522,7 @@ function OhhwellsBridge() {
17364
18522
  }
17365
18523
  };
17366
18524
  const probeImageAt = (clientX, clientY, isDragOver = false, fromParentViewport = false) => {
17367
- if (linkPopoverOpenRef.current) {
18525
+ if (linkPopoverOpenRef.current || floatingPanelOpenRef.current && isPointOverFloatingPanel(clientX, clientY)) {
17368
18526
  if (hoveredImageRef.current) {
17369
18527
  hoveredImageRef.current = null;
17370
18528
  hoveredImageHasTextOverlapRef.current = false;
@@ -17623,7 +18781,7 @@ function OhhwellsBridge() {
17623
18781
  }
17624
18782
  };
17625
18783
  const probeHoverCardsAt = (clientX, clientY, fromParentViewport = false) => {
17626
- if (linkPopoverOpenRef.current || document.documentElement.hasAttribute("data-ohw-section-picking")) {
18784
+ if (linkPopoverOpenRef.current || floatingPanelOpenRef.current && isPointOverFloatingPanel(clientX, clientY) || document.documentElement.hasAttribute("data-ohw-section-picking")) {
17627
18785
  if (activeStateElRef.current) {
17628
18786
  activeStateElRef.current.removeAttribute("data-ohw-state-hovered");
17629
18787
  activeStateElRef.current = null;
@@ -17660,6 +18818,35 @@ function OhhwellsBridge() {
17660
18818
  }
17661
18819
  }
17662
18820
  };
18821
+ const probeSocialsRowAt = (clientX, clientY, fromParentViewport = false) => {
18822
+ const wasSocialsRow = Boolean(hoveredNavContainerRef.current?.hasAttribute(SOCIALS_ROW_ATTR));
18823
+ const clear = () => {
18824
+ if (!wasSocialsRow) return false;
18825
+ hoveredNavContainerRef.current = null;
18826
+ setHoveredNavContainerRect(null);
18827
+ return false;
18828
+ };
18829
+ if (linkPopoverOpenRef.current || toolbarVariantRef.current === "select-frame") return clear();
18830
+ const { x, y } = toProbeCoords(clientX, clientY, fromParentViewport);
18831
+ const SLACK = 6;
18832
+ for (const row of Array.from(document.querySelectorAll(`[${SOCIALS_ROW_ATTR}]`))) {
18833
+ const rect = row.getBoundingClientRect();
18834
+ const inside = x >= rect.left - SLACK && x <= rect.right + SLACK && y >= rect.top - SLACK && y <= rect.bottom + SLACK;
18835
+ if (!inside) continue;
18836
+ if (selectedElRef.current === row) return clear();
18837
+ const overItem = listSocialItems(row).some((item) => {
18838
+ const box = item.getBoundingClientRect();
18839
+ return x >= box.left && x <= box.right && y >= box.top && y <= box.bottom;
18840
+ });
18841
+ if (overItem) return clear();
18842
+ hoveredNavContainerRef.current = row;
18843
+ setHoveredNavContainerRect(rect);
18844
+ hoveredItemElRef.current = null;
18845
+ setHoveredItemRect(null);
18846
+ return true;
18847
+ }
18848
+ return clear();
18849
+ };
17663
18850
  const probeSectionGapAt = (clientX, clientY, fromParentViewport = false) => {
17664
18851
  if (linkPopoverOpenRef.current) {
17665
18852
  if (hoveredGapRef.current) {
@@ -17669,7 +18856,9 @@ function OhhwellsBridge() {
17669
18856
  return;
17670
18857
  }
17671
18858
  const { y } = toProbeCoords(clientX, clientY, fromParentViewport);
17672
- const sections = Array.from(document.querySelectorAll("[data-ohw-section]")).sort((a, b) => a.getBoundingClientRect().top - b.getBoundingClientRect().top);
18859
+ const sections = Array.from(document.querySelectorAll("[data-ohw-section]")).filter(
18860
+ (el) => !el.hasAttribute("data-ohw-ai-template-hidden") && !el.hasAttribute("data-ohw-ai-removed") && !el.hasAttribute("data-ohw-ai-replaced-by") && el.getBoundingClientRect().height > 0
18861
+ ).sort((a, b) => a.getBoundingClientRect().top - b.getBoundingClientRect().top);
17673
18862
  const ZONE = 20;
17674
18863
  for (let i = 0; i < sections.length; i++) {
17675
18864
  const a = sections[i];
@@ -17698,8 +18887,7 @@ function OhhwellsBridge() {
17698
18887
  };
17699
18888
  const handleMouseMove = (e) => {
17700
18889
  const { clientX, clientY } = e;
17701
- if (pointOwnedByFloatingPanel(clientX, clientY)) return;
17702
- if (isOverEditorChrome(clientX, clientY)) {
18890
+ if (document.documentElement.hasAttribute("data-ohw-panel-dragging") || floatingPanelOpenRef.current && isPointOverFloatingPanel(clientX, clientY) || isOverEditorChrome(clientX, clientY)) {
17703
18891
  document.querySelectorAll("[data-ohw-hovered]").forEach((el) => el.removeAttribute("data-ohw-hovered"));
17704
18892
  formHoverElRef.current = null;
17705
18893
  setFormHoverRect(null);
@@ -17707,8 +18895,15 @@ function OhhwellsBridge() {
17707
18895
  setHoveredItemRect(null);
17708
18896
  hoveredNavContainerRef.current = null;
17709
18897
  setHoveredNavContainerRect(null);
18898
+ siblingHintElRef.current = null;
18899
+ setSiblingHintRect(null);
18900
+ setSiblingHintRects([]);
18901
+ dismissImageHover();
18902
+ clearImageHover();
18903
+ setSectionGap(null);
17710
18904
  return;
17711
18905
  }
18906
+ if (probeSocialsRowAt(clientX, clientY)) return;
17712
18907
  probeSectionGapAt(clientX, clientY);
17713
18908
  probeImageAt(clientX, clientY);
17714
18909
  probeHoverCardsAt(clientX, clientY);
@@ -17717,7 +18912,12 @@ function OhhwellsBridge() {
17717
18912
  if (e.data?.type !== "ow:pointer-sync") return;
17718
18913
  const { clientX, clientY } = e.data;
17719
18914
  if (typeof clientX !== "number" || typeof clientY !== "number") return;
17720
- if (pointOwnedByFloatingPanel(clientX, clientY)) return;
18915
+ if (document.documentElement.hasAttribute("data-ohw-panel-dragging") || floatingPanelOpenRef.current && isPointOverFloatingPanel(clientX, clientY)) {
18916
+ dismissImageHover();
18917
+ clearImageHover();
18918
+ return;
18919
+ }
18920
+ if (probeSocialsRowAt(clientX, clientY)) return;
17721
18921
  probeSectionGapAt(clientX, clientY);
17722
18922
  probeImageAt(clientX, clientY);
17723
18923
  probeHoverCardsAt(clientX, clientY);
@@ -17963,10 +19163,19 @@ function OhhwellsBridge() {
17963
19163
  if (e.data?.type !== "ow:hydrate") return;
17964
19164
  const content = e.data.content;
17965
19165
  if (!content) return;
19166
+ if (typeof content[BRAND_KIT_KEY] === "string") {
19167
+ brandKitRef.current = content[BRAND_KIT_KEY];
19168
+ applyBrandToDom(parseBrandKit(content[BRAND_KIT_KEY]));
19169
+ }
17966
19170
  if (typeof content[AI_SECTIONS_KEY] === "string") {
17967
19171
  aiSectionsRef.current = content[AI_SECTIONS_KEY];
17968
19172
  applyAiSectionsToDom(parseAiSectionsState(content[AI_SECTIONS_KEY]));
17969
19173
  }
19174
+ if (typeof content[STYLE_STORE_KEY] === "string") {
19175
+ stylesRef.current = content[STYLE_STORE_KEY];
19176
+ applyStylesToDom(parseStyleStore(content[STYLE_STORE_KEY]));
19177
+ }
19178
+ applyBrandChrome(content);
17970
19179
  let sectionsJson = null;
17971
19180
  for (const [key, val] of Object.entries(content)) {
17972
19181
  if (key === "__ohw_sections") {
@@ -17976,6 +19185,9 @@ function OhhwellsBridge() {
17976
19185
  if (key === AI_SECTIONS_KEY) continue;
17977
19186
  if (key === LOGO_PLACEHOLDER_KEY) continue;
17978
19187
  if (LOGO_IMAGE_KEYS.includes(key) && !String(val ?? "").trim()) continue;
19188
+ if (key === BRAND_KIT_KEY) continue;
19189
+ if (key === STYLE_STORE_KEY) continue;
19190
+ if (BRAND_CHROME_KEYS.has(key)) continue;
17979
19191
  if (applyVideoSettingNode(key, val)) continue;
17980
19192
  if (applyCarouselNode(key, val)) continue;
17981
19193
  document.querySelectorAll(`[data-ohw-key="${key}"]`).forEach((el) => {
@@ -17989,6 +19201,8 @@ function OhhwellsBridge() {
17989
19201
  if (video && video.src !== val) applyVideoSrc(video, val);
17990
19202
  } else if (el.dataset.ohwEditable === "link") {
17991
19203
  applyLinkHref(el, val);
19204
+ } else if (el.dataset.ohwEditable === "icon") {
19205
+ applyIconMarkup(el, val);
17992
19206
  } else {
17993
19207
  el.innerHTML = val;
17994
19208
  }
@@ -18072,12 +19286,21 @@ function OhhwellsBridge() {
18072
19286
  nodes: collectEditableNodes(editContentRef.current)
18073
19287
  });
18074
19288
  };
19289
+ const clearInteractionChrome = () => {
19290
+ deactivateRef.current();
19291
+ deselectRef.current();
19292
+ clearMediaSelectionRef.current();
19293
+ };
18075
19294
  const handleAiApplyTree = (e) => {
18076
19295
  if (e.data?.type !== "ow:ai-apply-tree") return;
18077
19296
  const payload = e.data.payload;
18078
19297
  if (!payload || typeof payload.id !== "string" || !isRenderableTree(payload.tree)) return;
19298
+ clearInteractionChrome();
18079
19299
  const previous = aiSectionsRef.current;
18080
- const nextState = applyTreeToState(parseAiSectionsState(previous), payload);
19300
+ const nextState = applyTreeToState(parseAiSectionsState(previous), {
19301
+ ...payload,
19302
+ path: payload.path ?? window.location.pathname
19303
+ });
18081
19304
  const nextValue = serializeAiSectionsState(nextState);
18082
19305
  aiSectionsRef.current = nextValue;
18083
19306
  applyAiSectionsToDom(nextState);
@@ -18097,6 +19320,7 @@ function OhhwellsBridge() {
18097
19320
  if (!sectionId || sectionId === "navbar" || sectionId === "footer") return;
18098
19321
  const exists = document.querySelector(`[data-ohw-section="${CSS.escape(sectionId)}"]`);
18099
19322
  if (!exists) return;
19323
+ clearInteractionChrome();
18100
19324
  const previous = aiSectionsRef.current;
18101
19325
  const nextState = deleteSectionFromState(parseAiSectionsState(previous), sectionId);
18102
19326
  const nextValue = serializeAiSectionsState(nextState);
@@ -18112,14 +19336,58 @@ function OhhwellsBridge() {
18112
19336
  const handleAiSetSections = (e) => {
18113
19337
  if (e.data?.type !== "ow:ai-set-sections") return;
18114
19338
  const value = typeof e.data.value === "string" ? e.data.value : "";
19339
+ clearInteractionChrome();
18115
19340
  aiSectionsRef.current = value;
18116
19341
  applyAiSectionsToDom(parseAiSectionsState(value));
19342
+ applyStylesToDom(parseStyleStore(stylesRef.current));
18117
19343
  const restoredHeight = document.documentElement.scrollHeight;
18118
19344
  if (restoredHeight > 50) postToParentRef.current({ type: "ow:height", height: restoredHeight });
18119
19345
  postToParentRef.current({ type: "ow:change", nodes: [{ key: AI_SECTIONS_KEY, text: value }] });
18120
19346
  postAiSectionsChanged();
18121
19347
  };
18122
19348
  window.addEventListener("message", handleAiSetSections);
19349
+ const handleMoveSection = (e) => {
19350
+ if (e.data?.type !== "ow:move-section") return;
19351
+ const instanceId = typeof e.data.instanceId === "string" ? e.data.instanceId : "";
19352
+ const direction = e.data.direction === "up" || e.data.direction === "down" ? e.data.direction : null;
19353
+ if (!instanceId || !direction) return;
19354
+ const entries = moveSectionInstance(instanceId, direction, window.location.pathname);
19355
+ if (!entries) return;
19356
+ const orderJson = JSON.stringify(entries);
19357
+ editContentRef.current = { ...editContentRef.current, [SECTION_ORDER_KEY]: orderJson };
19358
+ postToParentRef.current({ type: "ow:change", nodes: [{ key: SECTION_ORDER_KEY, text: orderJson }] });
19359
+ window.dispatchEvent(new Event("resize"));
19360
+ };
19361
+ window.addEventListener("message", handleMoveSection);
19362
+ const handleAiSetBrand = (e) => {
19363
+ if (e.data?.type !== "ow:ai-set-brand") return;
19364
+ const value = typeof e.data.value === "string" ? e.data.value : "";
19365
+ const previous = brandKitRef.current;
19366
+ brandKitRef.current = value;
19367
+ applyBrandToDom(parseBrandKit(value));
19368
+ if (aiSectionsRef.current) applyAiSectionsToDom(parseAiSectionsState(aiSectionsRef.current));
19369
+ applyStylesToDom(parseStyleStore(stylesRef.current));
19370
+ postToParentRef.current({ type: "ow:change", nodes: [{ key: BRAND_KIT_KEY, text: value }] });
19371
+ postToParentRef.current({ type: "ow:ai-brand-applied", previous, value });
19372
+ };
19373
+ window.addEventListener("message", handleAiSetBrand);
19374
+ const handleAiSetStyles = (e) => {
19375
+ if (e.data?.type !== "ow:ai-set-styles") return;
19376
+ const value = typeof e.data.value === "string" ? e.data.value : "";
19377
+ const previous = stylesRef.current;
19378
+ stylesRef.current = value;
19379
+ applyStylesToDom(parseStyleStore(value));
19380
+ postToParentRef.current({ type: "ow:change", nodes: [{ key: STYLE_STORE_KEY, text: value }] });
19381
+ postToParentRef.current({ type: "ow:ai-styles-applied", previous, value });
19382
+ };
19383
+ window.addEventListener("message", handleAiSetStyles);
19384
+ const handleGetBrand = (e) => {
19385
+ if (e.data?.type !== "ow:get-brand") return;
19386
+ const template = deriveTemplateBrand();
19387
+ const value = brandKitRef.current || (template ? JSON.stringify(template) : "");
19388
+ postToParentRef.current({ type: "ow:brand-value", value });
19389
+ };
19390
+ window.addEventListener("message", handleGetBrand);
18123
19391
  const handlePanelDragging = (e) => {
18124
19392
  if (e.data?.type !== "ow:panel-dragging") return;
18125
19393
  if (e.data.dragging) document.documentElement.setAttribute("data-ohw-panel-dragging", "");
@@ -18135,8 +19403,15 @@ function OhhwellsBridge() {
18135
19403
  closeLinkPopoverRef.current();
18136
19404
  return;
18137
19405
  }
19406
+ if (floatingPanelOpenRef.current) {
19407
+ setFloatingPanelRef.current(null);
19408
+ deselectRef.current();
19409
+ deactivateRef.current();
19410
+ return;
19411
+ }
18138
19412
  deselectRef.current();
18139
19413
  deactivateRef.current();
19414
+ clearMediaSelectionRef.current();
18140
19415
  };
18141
19416
  window.addEventListener("message", handleDeactivate);
18142
19417
  const handleToastAction = (e) => {
@@ -18222,6 +19497,10 @@ function OhhwellsBridge() {
18222
19497
  const handleKeyDown = (e) => {
18223
19498
  if (e.key === "Escape" && document.querySelector("[data-ohw-section-picker]")) return;
18224
19499
  if (e.key === "Escape" && document.querySelector("[data-ohw-more-menu]")) return;
19500
+ if (e.key === "Escape" && selectedMediaElRef.current) {
19501
+ clearMediaSelectionRef.current();
19502
+ return;
19503
+ }
18225
19504
  if ((e.ctrlKey || e.metaKey) && e.key.toLowerCase() === "a" && activeElRef.current) {
18226
19505
  e.preventDefault();
18227
19506
  selectAllTextInEditable(activeElRef.current);
@@ -18381,6 +19660,12 @@ function OhhwellsBridge() {
18381
19660
  if (aiSectionsRef.current) {
18382
19661
  nodes.push({ key: AI_SECTIONS_KEY, type: "sections", text: aiSectionsRef.current });
18383
19662
  }
19663
+ if (stylesRef.current) {
19664
+ nodes.push({ key: STYLE_STORE_KEY, type: "styles", text: stylesRef.current });
19665
+ }
19666
+ if (brandKitRef.current) {
19667
+ nodes.push({ key: BRAND_KIT_KEY, type: "brand", text: brandKitRef.current });
19668
+ }
18384
19669
  document.querySelectorAll('[data-ohw-editable="form"]').forEach((form) => {
18385
19670
  const formKey = formKeyOf(form);
18386
19671
  if (!formKey) return;
@@ -18398,8 +19683,12 @@ function OhhwellsBridge() {
18398
19683
  if (inserted) {
18399
19684
  const tracker = getSectionsTracker();
18400
19685
  postToParentRef.current({ type: "ow:change", nodes: [{ key: "__ohw_sections", text: tracker.textContent ?? "[]" }] });
18401
- const h = document.documentElement.scrollHeight;
18402
- if (h > 50) postToParentRef.current({ type: "ow:height", height: h });
19686
+ const reportHeight = () => {
19687
+ const h = document.body.scrollHeight;
19688
+ if (h > 50) postToParentRef.current({ type: "ow:height", height: h });
19689
+ };
19690
+ reportHeight();
19691
+ setTimeout(reportHeight, 500);
18403
19692
  }
18404
19693
  };
18405
19694
  const handleSwitchSchedule = (e) => {
@@ -18786,19 +20075,23 @@ function OhhwellsBridge() {
18786
20075
  window.removeEventListener("message", handleAiApplyTree);
18787
20076
  window.removeEventListener("message", handleAiDeleteSection);
18788
20077
  window.removeEventListener("message", handleAiSetSections);
20078
+ window.removeEventListener("message", handleMoveSection);
20079
+ window.removeEventListener("message", handleAiSetBrand);
20080
+ window.removeEventListener("message", handleAiSetStyles);
20081
+ window.removeEventListener("message", handleGetBrand);
18789
20082
  window.removeEventListener("message", handlePanelDragging);
18790
20083
  window.removeEventListener("message", handleDeactivate);
18791
- document.documentElement.removeAttribute("data-ohw-panel-dragging");
18792
20084
  window.removeEventListener("message", handleToastAction);
18793
20085
  window.removeEventListener("message", handleFormCount);
18794
20086
  window.removeEventListener("message", handleUiEscape);
20087
+ document.documentElement.removeAttribute("data-ohw-panel-dragging");
18795
20088
  autoSaveTimers.current.forEach(clearTimeout);
18796
20089
  autoSaveTimers.current.clear();
18797
20090
  if (imageUnhoverTimerRef.current) clearTimeout(imageUnhoverTimerRef.current);
18798
20091
  if (imageShowTimerRef.current) clearTimeout(imageShowTimerRef.current);
18799
20092
  };
18800
20093
  }, [isEditMode, refreshStateRules]);
18801
- useEffect12(() => {
20094
+ useEffect13(() => {
18802
20095
  if (!isEditMode) return;
18803
20096
  const THRESHOLD = 10;
18804
20097
  const resolveWasSelected = (el) => {
@@ -18954,7 +20247,7 @@ function OhhwellsBridge() {
18954
20247
  unlockFooterDragInteraction();
18955
20248
  };
18956
20249
  }, [isEditMode]);
18957
- useEffect12(() => {
20250
+ useEffect13(() => {
18958
20251
  const handler = (e) => {
18959
20252
  if (e.data?.type !== "ow:request-schedule-config") return;
18960
20253
  const insertAfterVal = e.data.insertAfter;
@@ -18970,7 +20263,7 @@ function OhhwellsBridge() {
18970
20263
  window.addEventListener("message", handler);
18971
20264
  return () => window.removeEventListener("message", handler);
18972
20265
  }, [processConfigRequest]);
18973
- useEffect12(() => {
20266
+ useEffect13(() => {
18974
20267
  if (!isEditMode) return;
18975
20268
  document.querySelectorAll("[data-ohw-active-state]").forEach((el) => {
18976
20269
  el.removeAttribute("data-ohw-active-state");
@@ -18994,7 +20287,7 @@ function OhhwellsBridge() {
18994
20287
  postToParent2({
18995
20288
  type: "ow:ready",
18996
20289
  version: "1",
18997
- bridgeVersion: "0.1.67",
20290
+ bridgeVersion: "0.1.69",
18998
20291
  path: pathname,
18999
20292
  nodes: collectEditableNodes(editContentRef.current),
19000
20293
  sections
@@ -19006,13 +20299,13 @@ function OhhwellsBridge() {
19006
20299
  clearTimeout(timer);
19007
20300
  };
19008
20301
  }, [pathname, isEditMode, refreshStateRules, postToParent2]);
19009
- useEffect12(() => {
20302
+ useEffect13(() => {
19010
20303
  scrollToHashSectionWhenReady();
19011
20304
  const onHashChange = () => scrollToHashSectionWhenReady();
19012
20305
  window.addEventListener("hashchange", onHashChange);
19013
20306
  return () => window.removeEventListener("hashchange", onHashChange);
19014
20307
  }, [pathname]);
19015
- const handleCommand = useCallback7((cmd) => {
20308
+ const handleCommand = useCallback8((cmd) => {
19016
20309
  const el = activeElRef.current;
19017
20310
  const selBefore = window.getSelection();
19018
20311
  let savedOffsets = null;
@@ -19048,7 +20341,7 @@ function OhhwellsBridge() {
19048
20341
  if (el) setToolbarRect(getEditMeasureEl(el).getBoundingClientRect());
19049
20342
  refreshActiveCommandsRef.current();
19050
20343
  }, []);
19051
- useEffect12(() => {
20344
+ useEffect13(() => {
19052
20345
  const session = floatingPanel && floatingPanel.kind === "logo-size" ? floatingPanel : null;
19053
20346
  if (!session || !logoSizeDraft) return;
19054
20347
  const onPanelAction = (e) => {
@@ -19086,7 +20379,7 @@ function OhhwellsBridge() {
19086
20379
  window.addEventListener("message", onPanelAction);
19087
20380
  return () => window.removeEventListener("message", onPanelAction);
19088
20381
  }, [floatingPanel, logoSizeDraft, editorViewport, persistLogoSizeDraft, closeFloatingPanelAndDeselect]);
19089
- const handleStateChange = useCallback7((state) => {
20382
+ const handleStateChange = useCallback8((state) => {
19090
20383
  if (!activeStateElRef.current) return;
19091
20384
  const el = activeStateElRef.current;
19092
20385
  if (state === "Default") {
@@ -19099,7 +20392,7 @@ function OhhwellsBridge() {
19099
20392
  }
19100
20393
  setToggleState((prev) => prev ? { ...prev, activeState: state } : null);
19101
20394
  }, [deactivate]);
19102
- const reselectAfterLinkPopover = useCallback7(
20395
+ const reselectAfterLinkPopover = useCallback8(
19103
20396
  (hrefKey) => {
19104
20397
  requestAnimationFrame(() => {
19105
20398
  const el = resolveHrefKeyElement(hrefKey);
@@ -19108,7 +20401,7 @@ function OhhwellsBridge() {
19108
20401
  },
19109
20402
  [resolveHrefKeyElement]
19110
20403
  );
19111
- const closeLinkPopover = useCallback7(() => {
20404
+ const closeLinkPopover = useCallback8(() => {
19112
20405
  const session = linkPopoverSessionRef.current;
19113
20406
  addNavAfterAnchorRef.current = null;
19114
20407
  setLinkPopover(null);
@@ -19116,9 +20409,9 @@ function OhhwellsBridge() {
19116
20409
  reselectAfterLinkPopover(session.key);
19117
20410
  }
19118
20411
  }, [reselectAfterLinkPopover]);
19119
- const closeLinkPopoverRef = useRef9(closeLinkPopover);
20412
+ const closeLinkPopoverRef = useRef10(closeLinkPopover);
19120
20413
  closeLinkPopoverRef.current = closeLinkPopover;
19121
- const openLinkPopoverForActive = useCallback7(() => {
20414
+ const openLinkPopoverForActive = useCallback8(() => {
19122
20415
  const hrefCtx = getHrefKeyFromElement(activeElRef.current);
19123
20416
  if (!hrefCtx) return;
19124
20417
  bumpLinkPopoverGrace();
@@ -19129,7 +20422,7 @@ function OhhwellsBridge() {
19129
20422
  });
19130
20423
  deactivate();
19131
20424
  }, [deactivate]);
19132
- const openLinkPopoverForSelected = useCallback7(() => {
20425
+ const openLinkPopoverForSelected = useCallback8(() => {
19133
20426
  const anchor = selectedElRef.current;
19134
20427
  if (!anchor) return;
19135
20428
  const key = anchor.getAttribute("data-ohw-href-key");
@@ -19146,7 +20439,7 @@ function OhhwellsBridge() {
19146
20439
  });
19147
20440
  deselect();
19148
20441
  }, [deselect]);
19149
- const handleSelectParent = useCallback7(() => {
20442
+ const handleSelectParent = useCallback8(() => {
19150
20443
  const selected = selectedElRef.current;
19151
20444
  if (!selected) return;
19152
20445
  if (toolbarVariantRef.current === "select-frame") {
@@ -19173,7 +20466,7 @@ function OhhwellsBridge() {
19173
20466
  }
19174
20467
  deselectRef.current();
19175
20468
  }, []);
19176
- const handleDuplicateSelected = useCallback7(() => {
20469
+ const handleDuplicateSelected = useCallback8(() => {
19177
20470
  const selected = selectedElRef.current;
19178
20471
  if (!selected || !isNavigationItem2(selected)) return;
19179
20472
  const hrefKey = selected.getAttribute("data-ohw-href-key");
@@ -19289,7 +20582,7 @@ function OhhwellsBridge() {
19289
20582
  });
19290
20583
  }
19291
20584
  }, [postToParent2]);
19292
- const runPendingDeleteUndo = useCallback7(() => {
20585
+ const runPendingDeleteUndo = useCallback8(() => {
19293
20586
  const pending = pendingDeleteUndoRef.current;
19294
20587
  if (!pending) return false;
19295
20588
  pendingDeleteUndoRef.current = null;
@@ -19297,7 +20590,7 @@ function OhhwellsBridge() {
19297
20590
  enforceLinkHrefs();
19298
20591
  return true;
19299
20592
  }, []);
19300
- const handleDeleteSelected = useCallback7(() => {
20593
+ const handleDeleteSelected = useCallback8(() => {
19301
20594
  const selected = selectedElRef.current;
19302
20595
  if (!selected) return false;
19303
20596
  return deleteSelectedNavFooterItem({
@@ -19318,7 +20611,7 @@ function OhhwellsBridge() {
19318
20611
  }, [postToParent2]);
19319
20612
  handleDeleteSelectedRef.current = handleDeleteSelected;
19320
20613
  runPendingDeleteUndoRef.current = runPendingDeleteUndo;
19321
- const handleLinkPopoverSubmit = useCallback7(
20614
+ const handleLinkPopoverSubmit = useCallback8(
19322
20615
  (target) => {
19323
20616
  const session = linkPopoverSessionRef.current;
19324
20617
  if (!session) return;
@@ -19384,19 +20677,30 @@ function OhhwellsBridge() {
19384
20677
  const showEditLink = toolbarShowEditLink;
19385
20678
  const currentSections = sectionsByPath[pathname] ?? [];
19386
20679
  linkPopoverOpenRef.current = linkPopover !== null;
19387
- const handleMediaReplace = useCallback7(
20680
+ const handleMediaSelect = useCallback8((key) => {
20681
+ const el = hoveredImageRef.current?.dataset.ohwKey === key ? hoveredImageRef.current : Array.from(document.querySelectorAll(MEDIA_SELECTOR)).find(
20682
+ (m) => (m.dataset.ohwKey ?? "") === key
20683
+ ) ?? null;
20684
+ if (!el) return;
20685
+ selectMediaElementRef.current(el);
20686
+ }, []);
20687
+ const handleMediaReplace = useCallback8(
19388
20688
  (key) => {
19389
- postToParent2({ type: "ow:image-pick", key, elementType: mediaHover?.elementType ?? "image" });
20689
+ postToParent2({
20690
+ type: "ow:image-pick",
20691
+ key,
20692
+ elementType: mediaHover?.elementType ?? selectedMedia?.elementType ?? "image"
20693
+ });
19390
20694
  },
19391
- [postToParent2, mediaHover?.elementType]
20695
+ [postToParent2, mediaHover?.elementType, selectedMedia?.elementType]
19392
20696
  );
19393
- const handleEditCarousel = useCallback7(
20697
+ const handleEditCarousel = useCallback8(
19394
20698
  (key) => {
19395
20699
  postToParent2({ type: "ow:carousel-open", key, images: readCarouselValue(key) });
19396
20700
  },
19397
20701
  [postToParent2]
19398
20702
  );
19399
- const handleMediaFadeOutComplete = useCallback7((key) => {
20703
+ const handleMediaFadeOutComplete = useCallback8((key) => {
19400
20704
  setUploadingRects((prev) => {
19401
20705
  if (!(key in prev)) return prev;
19402
20706
  const next = { ...prev };
@@ -19404,7 +20708,7 @@ function OhhwellsBridge() {
19404
20708
  return next;
19405
20709
  });
19406
20710
  }, []);
19407
- const handleVideoSettingsChange = useCallback7(
20711
+ const handleVideoSettingsChange = useCallback8(
19408
20712
  (key, settings) => {
19409
20713
  document.querySelectorAll(`[data-ohw-key="${key}"]`).forEach((el) => {
19410
20714
  const video = getVideoEl2(el);
@@ -19426,420 +20730,506 @@ function OhhwellsBridge() {
19426
20730
  },
19427
20731
  [postToParent2]
19428
20732
  );
19429
- return bridgeRoot ? createPortal2(
19430
- /* @__PURE__ */ jsxs20(Fragment8, { children: [
19431
- /* @__PURE__ */ jsx33("div", { ref: attachVisibleViewport, "data-ohw-visible-viewport": "", "aria-hidden": true }),
19432
- isEditMode && /* @__PURE__ */ jsx33(AiSectionOverlay, { postToParent: postToParent2, apiRef: aiSectionApiRef }),
19433
- Object.entries(uploadingRects).map(([key, { rect, fadingOut }]) => /* @__PURE__ */ jsx33(
19434
- MediaOverlay,
19435
- {
19436
- hover: { key, rect, elementType: "image", isDragOver: false, hasTextOverlap: false },
19437
- isUploading: true,
19438
- fadingOut,
19439
- onFadeOutComplete: handleMediaFadeOutComplete,
19440
- onReplace: handleMediaReplace
19441
- },
19442
- `uploading-${key}`
19443
- )),
19444
- mediaHover && !(mediaHover.key in uploadingRects) && /* @__PURE__ */ jsx33(
19445
- MediaOverlay,
19446
- {
19447
- hover: mediaHover,
19448
- isUploading: false,
19449
- onReplace: handleMediaReplace,
19450
- onVideoSettingsChange: handleVideoSettingsChange
19451
- }
19452
- ),
19453
- carouselHover && !linkPopover && !isItemDragging && /* @__PURE__ */ jsx33(CarouselOverlay, { hover: carouselHover, onEdit: handleEditCarousel }),
19454
- siblingHintRect && !linkPopover && !isItemDragging && siblingHintRects.length === 0 && /* @__PURE__ */ jsx33(ItemInteractionLayer, { rect: siblingHintRect, state: "sibling-hint" }),
19455
- siblingHintRects.length > 0 && !linkPopover && !isItemDragging && siblingHintRects.map((rect, i) => /* @__PURE__ */ jsx33(ItemInteractionLayer, { rect, state: "sibling-hint" }, `sibling-hint-${i}`)),
19456
- isItemDragging && draggedItemRect && selectedElRef.current !== footerDragRef.current?.draggedEl && selectedElRef.current !== navDragRef.current?.draggedEl && /* @__PURE__ */ jsx33(ItemInteractionLayer, { rect: draggedItemRect, state: "dragging" }),
19457
- isItemDragging && footerDropSlots.map((slot, i) => /* @__PURE__ */ jsx33(
19458
- "div",
19459
- {
19460
- className: "pointer-events-none fixed z-2147483646",
19461
- style: {
19462
- left: slot.left,
19463
- top: slot.top,
19464
- width: slot.width,
19465
- height: slot.height
20733
+ return /* @__PURE__ */ jsxs20(Fragment8, { children: [
20734
+ /* @__PURE__ */ jsx33("div", { id: "ohw-loader", suppressHydrationWarning: true, style: { ...OHW_LOADER_STYLE, display: "none" }, children: /* @__PURE__ */ jsx33(OhwLoaderSpinner, {}) }),
20735
+ /* @__PURE__ */ jsx33("script", { suppressHydrationWarning: true, dangerouslySetInnerHTML: { __html: OHW_LOADER_PREHYDRATE_SCRIPT } }),
20736
+ bridgeRoot ? createPortal2(
20737
+ /* @__PURE__ */ jsxs20(Fragment8, { children: [
20738
+ /* @__PURE__ */ jsx33("div", { ref: attachVisibleViewport, "data-ohw-visible-viewport": "", "aria-hidden": true }),
20739
+ isEditMode && /* @__PURE__ */ jsx33(AiSectionOverlay, { postToParent: postToParent2, apiRef: aiSectionApiRef }),
20740
+ isSectionDragging && sectionDropSlots.map((slot, i) => /* @__PURE__ */ jsx33(
20741
+ "div",
20742
+ {
20743
+ className: "pointer-events-none fixed z-2147483646",
20744
+ style: { left: slot.left, top: slot.y, width: slot.width, height: 3, transform: "translateY(-50%)" },
20745
+ children: /* @__PURE__ */ jsx33(
20746
+ DropIndicator,
20747
+ {
20748
+ direction: "horizontal",
20749
+ state: activeSectionDropIndex === i ? "dragActive" : "dragIdle",
20750
+ className: "!h-full !w-full"
20751
+ }
20752
+ )
19466
20753
  },
19467
- children: /* @__PURE__ */ jsx33(
19468
- DropIndicator,
19469
- {
19470
- direction: slot.direction,
19471
- state: activeFooterDropIndex === i ? "dragActive" : "dragIdle",
19472
- className: "!h-full !w-full"
19473
- }
19474
- )
19475
- },
19476
- `footer-drop-${slot.direction}-${slot.columnIndex}-${slot.insertIndex}-${i}`
19477
- )),
19478
- isItemDragging && navDropSlots.map((slot, i) => /* @__PURE__ */ jsx33(
19479
- "div",
19480
- {
19481
- className: "pointer-events-none fixed z-2147483646",
19482
- style: {
19483
- left: slot.left,
19484
- top: slot.top,
19485
- width: slot.width,
19486
- height: slot.height
20754
+ `section-drop-${slot.insertIndex}-${i}`
20755
+ )),
20756
+ Object.entries(uploadingRects).map(([key, { rect, fadingOut }]) => /* @__PURE__ */ jsx33(
20757
+ MediaOverlay,
20758
+ {
20759
+ hover: { key, rect, elementType: "image", isDragOver: false, hasTextOverlap: false },
20760
+ isUploading: true,
20761
+ fadingOut,
20762
+ onFadeOutComplete: handleMediaFadeOutComplete,
20763
+ onReplace: handleMediaReplace
19487
20764
  },
19488
- children: /* @__PURE__ */ jsx33(
19489
- DropIndicator,
19490
- {
19491
- direction: slot.direction,
19492
- state: activeNavDropIndex === i ? "dragActive" : "dragIdle",
19493
- className: "!h-full !w-full"
19494
- }
19495
- )
19496
- },
19497
- `nav-drop-${slot.direction}-${slot.parentId ?? "root"}-${slot.insertIndex}-${i}`
19498
- )),
19499
- hoveredNavContainerRect && (toolbarVariant !== "select-frame" || isFooterFrameSelection) && !linkPopover && !isItemDragging && /* @__PURE__ */ jsx33(ItemInteractionLayer, { rect: hoveredNavContainerRect, state: "hover" }),
19500
- hoveredItemRect && !linkPopover && !hoveredNavContainerRect && !isItemDragging && hoveredItemElRef.current !== selectedElRef.current && /* @__PURE__ */ jsx33(ItemInteractionLayer, { rect: hoveredItemRect, state: "hover" }),
19501
- hoveredTextRect && !isItemDragging && toolbarVariant !== "rich-text" && /* @__PURE__ */ jsx33(ItemInteractionLayer, { rect: hoveredTextRect, state: "hover" }),
19502
- formPickRect && !isItemDragging && /* @__PURE__ */ jsx33(
19503
- ItemInteractionLayer,
19504
- {
19505
- rect: formPickRect,
19506
- state: "active-top",
19507
- itemDragSurface: false,
19508
- toolbarAlign: "left",
19509
- chromeGap: 24,
19510
- toolbar: fieldPickRect ? void 0 : /* @__PURE__ */ jsxs20(
20765
+ `uploading-${key}`
20766
+ )),
20767
+ mediaHover && !(mediaHover.key in uploadingRects) && mediaHover.key !== selectedMedia?.key && /* @__PURE__ */ jsx33(
20768
+ MediaOverlay,
20769
+ {
20770
+ hover: mediaHover,
20771
+ isUploading: false,
20772
+ onReplace: handleMediaReplace,
20773
+ onSelect: handleMediaSelect,
20774
+ onVideoSettingsChange: handleVideoSettingsChange
20775
+ }
20776
+ ),
20777
+ selectedMedia && !(selectedMedia.key in uploadingRects) && !linkPopover && /* @__PURE__ */ jsx33(
20778
+ MediaOverlay,
20779
+ {
20780
+ hover: selectedMedia,
20781
+ selected: true,
20782
+ hovered: mediaHover?.key === selectedMedia.key,
20783
+ isUploading: false,
20784
+ onReplace: handleMediaReplace,
20785
+ onSelect: handleMediaSelect,
20786
+ onVideoSettingsChange: handleVideoSettingsChange
20787
+ }
20788
+ ),
20789
+ carouselHover && !linkPopover && !isItemDragging && /* @__PURE__ */ jsx33(CarouselOverlay, { hover: carouselHover, onEdit: handleEditCarousel }),
20790
+ siblingHintRect && !linkPopover && !isItemDragging && siblingHintRects.length === 0 && /* @__PURE__ */ jsx33(ItemInteractionLayer, { rect: siblingHintRect, state: "sibling-hint" }),
20791
+ siblingHintRects.length > 0 && !linkPopover && !isItemDragging && siblingHintRects.map((rect, i) => /* @__PURE__ */ jsx33(ItemInteractionLayer, { rect, state: "sibling-hint" }, `sibling-hint-${i}`)),
20792
+ isItemDragging && draggedItemRect && selectedElRef.current !== footerDragRef.current?.draggedEl && selectedElRef.current !== navDragRef.current?.draggedEl && /* @__PURE__ */ jsx33(ItemInteractionLayer, { rect: draggedItemRect, state: "dragging" }),
20793
+ isItemDragging && footerDropSlots.map((slot, i) => /* @__PURE__ */ jsx33(
20794
+ "div",
20795
+ {
20796
+ className: "pointer-events-none fixed z-2147483646",
20797
+ style: {
20798
+ left: slot.left,
20799
+ top: slot.top,
20800
+ width: slot.width,
20801
+ height: slot.height
20802
+ },
20803
+ children: /* @__PURE__ */ jsx33(
20804
+ DropIndicator,
20805
+ {
20806
+ direction: slot.direction,
20807
+ state: activeFooterDropIndex === i ? "dragActive" : "dragIdle",
20808
+ className: "!h-full !w-full"
20809
+ }
20810
+ )
20811
+ },
20812
+ `footer-drop-${slot.direction}-${slot.columnIndex}-${slot.insertIndex}-${i}`
20813
+ )),
20814
+ isItemDragging && navDropSlots.map((slot, i) => /* @__PURE__ */ jsx33(
20815
+ "div",
20816
+ {
20817
+ className: "pointer-events-none fixed z-2147483646",
20818
+ style: {
20819
+ left: slot.left,
20820
+ top: slot.top,
20821
+ width: slot.width,
20822
+ height: slot.height
20823
+ },
20824
+ children: /* @__PURE__ */ jsx33(
20825
+ DropIndicator,
20826
+ {
20827
+ direction: slot.direction,
20828
+ state: activeNavDropIndex === i ? "dragActive" : "dragIdle",
20829
+ className: "!h-full !w-full"
20830
+ }
20831
+ )
20832
+ },
20833
+ `nav-drop-${slot.direction}-${slot.parentId ?? "root"}-${slot.insertIndex}-${i}`
20834
+ )),
20835
+ hoveredNavContainerRect && (toolbarVariant !== "select-frame" || isFooterFrameSelection) && !linkPopover && !isItemDragging && /* @__PURE__ */ jsx33(ItemInteractionLayer, { rect: hoveredNavContainerRect, state: "hover" }),
20836
+ hoveredItemRect && !linkPopover && !hoveredNavContainerRect && !isItemDragging && hoveredItemElRef.current !== selectedElRef.current && /* @__PURE__ */ jsx33(ItemInteractionLayer, { rect: hoveredItemRect, state: "hover" }),
20837
+ hoveredTextRect && !hoveredNavContainerRect && !hoveredItemRect && !isItemDragging && toolbarVariant !== "rich-text" && /* @__PURE__ */ jsx33(ItemInteractionLayer, { rect: hoveredTextRect, state: "hover" }),
20838
+ formPickRect && !isItemDragging && /* @__PURE__ */ jsx33(
20839
+ ItemInteractionLayer,
20840
+ {
20841
+ rect: formPickRect,
20842
+ state: "active-top",
20843
+ itemDragSurface: false,
20844
+ toolbarAlign: "left",
20845
+ chromeGap: 24,
20846
+ toolbar: fieldPickRect ? void 0 : /* @__PURE__ */ jsxs20(
20847
+ "div",
20848
+ {
20849
+ "data-ohw-form-toolbar": "",
20850
+ className: "pointer-events-auto flex items-center gap-0.5 whitespace-nowrap rounded-lg border border-border bg-background p-1 shadow-md",
20851
+ children: [
20852
+ /* @__PURE__ */ jsx33(
20853
+ "button",
20854
+ {
20855
+ type: "button",
20856
+ "aria-label": "Add field",
20857
+ className: "flex h-7 w-7 items-center justify-center rounded-md text-foreground transition-colors hover:bg-muted/80",
20858
+ onClick: () => setFieldTypePickerOpen((open) => !open),
20859
+ "data-ohw-add-field": "",
20860
+ children: /* @__PURE__ */ jsx33(Plus4, { size: 15, "aria-hidden": true })
20861
+ }
20862
+ ),
20863
+ /* @__PURE__ */ jsx33("div", { className: "mx-0.5 h-5 w-px bg-border" }),
20864
+ /* @__PURE__ */ jsxs20(
20865
+ "button",
20866
+ {
20867
+ type: "button",
20868
+ className: "flex h-7 items-center gap-1.5 whitespace-nowrap rounded-md px-2 text-[13px] font-semibold text-foreground transition-colors hover:bg-muted/80",
20869
+ onClick: () => {
20870
+ const form = formPickElRef.current;
20871
+ if (!form) return;
20872
+ postToParent2({
20873
+ type: "ow:form-pick",
20874
+ formKey: formKeyOf(form),
20875
+ hasLongText: formHasLongText(form)
20876
+ });
20877
+ },
20878
+ children: [
20879
+ /* @__PURE__ */ jsx33(Settings, { size: 14, "aria-hidden": true }),
20880
+ "Form settings",
20881
+ formPickCount ? (
20882
+ // Counter pill, per the design — not a text suffix.
20883
+ /* @__PURE__ */ jsx33(
20884
+ "span",
20885
+ {
20886
+ "data-ohw-form-count": "",
20887
+ className: "ml-0.5 inline-flex h-[18px] min-w-[18px] items-center justify-center rounded-full bg-primary px-1.5 text-[11px] font-semibold text-primary-foreground",
20888
+ children: formPickCount
20889
+ }
20890
+ )
20891
+ ) : null
20892
+ ]
20893
+ }
20894
+ ),
20895
+ /* @__PURE__ */ jsx33("div", { className: "mx-0.5 h-5 w-px bg-border" }),
20896
+ /* @__PURE__ */ jsx33("div", { className: "flex items-center rounded-md bg-muted/70 p-0.5", children: ["default", "success"].map((state) => /* @__PURE__ */ jsx33(
20897
+ "button",
20898
+ {
20899
+ type: "button",
20900
+ "aria-pressed": formViewState === state,
20901
+ className: "rounded-md px-2.5 py-1 text-[13px] font-semibold capitalize transition-colors " + (formViewState === state ? "bg-background text-foreground shadow-sm" : "text-muted-foreground hover:text-foreground"),
20902
+ onClick: () => {
20903
+ const form = formPickElRef.current;
20904
+ const key = form ? formKeyOf(form) : null;
20905
+ if (!form || !key) return;
20906
+ const initial = successInitialFor(form, key, editContentRef.current);
20907
+ setFormViewState(form, key, state, initial);
20908
+ setFormViewStateUi(state);
20909
+ setFormPickRect(form.getBoundingClientRect());
20910
+ if (state === "success") {
20911
+ const successEl = form.querySelector(`[${SUCCESS_TEXT_ATTR}]`);
20912
+ if (successEl) requestAnimationFrame(() => activateRef.current(successEl));
20913
+ } else {
20914
+ deactivateRef.current();
20915
+ }
20916
+ },
20917
+ children: state
20918
+ },
20919
+ state
20920
+ )) })
20921
+ ]
20922
+ }
20923
+ )
20924
+ }
20925
+ ),
20926
+ formHoverRect && !isItemDragging && /* @__PURE__ */ jsx33(
20927
+ ItemInteractionLayer,
20928
+ {
20929
+ rect: formHoverRect,
20930
+ state: "hover",
20931
+ chromeGap: formHoverElRef.current && getFieldWrapper(formHoverElRef.current) ? 8 : 24
20932
+ }
20933
+ ),
20934
+ fieldPickRect && fieldPickState && !isItemDragging && toolbarVariant !== "rich-text" && /* @__PURE__ */ jsx33(
20935
+ ItemInteractionLayer,
20936
+ {
20937
+ rect: fieldPickRect,
20938
+ state: fieldDragging ? "dragging" : "active-top",
20939
+ itemDragSurface: false,
20940
+ toolbarAlign: "left",
20941
+ chromeGap: 10,
20942
+ showHandle: true,
20943
+ dragHandleLabel: "Reorder field",
20944
+ onDragHandleDragStart: handleFieldDragStart,
20945
+ onDragHandleDragEnd: handleFieldDragEnd,
20946
+ toolbar: /* @__PURE__ */ jsx33(
20947
+ FormFieldToolbar,
20948
+ {
20949
+ type: fieldPickState.type,
20950
+ required: fieldPickState.required,
20951
+ onTypeChange: handleFieldTypeChange,
20952
+ onRequiredToggle: handleFieldRequiredToggle,
20953
+ onDuplicate: handleFieldDuplicate,
20954
+ onDelete: handleFieldDelete
20955
+ }
20956
+ )
20957
+ }
20958
+ ),
20959
+ fieldDragging ? fieldDropSlots.map((slot, i) => /* @__PURE__ */ jsx33(
20960
+ "div",
20961
+ {
20962
+ className: "pointer-events-none fixed z-[2147483644]",
20963
+ style: { top: slot.top, left: slot.left, width: slot.width },
20964
+ children: /* @__PURE__ */ jsx33(
20965
+ DropIndicator,
20966
+ {
20967
+ direction: "horizontal",
20968
+ state: fieldDropIndex === i ? "dragActive" : "dragIdle",
20969
+ className: "!w-full"
20970
+ }
20971
+ )
20972
+ },
20973
+ `field-drop-${i}`
20974
+ )) : null,
20975
+ fieldTypePickerOpen && formPickRect ? (() => {
20976
+ const toolbar = document.querySelector("[data-ohw-form-toolbar]")?.getBoundingClientRect();
20977
+ return /* @__PURE__ */ jsx33(
19511
20978
  "div",
19512
20979
  {
19513
- "data-ohw-form-toolbar": "",
19514
- className: "pointer-events-auto flex items-center gap-0.5 whitespace-nowrap rounded-lg border border-border bg-background p-1 shadow-md",
19515
- children: [
19516
- /* @__PURE__ */ jsx33(
19517
- "button",
19518
- {
19519
- type: "button",
19520
- "aria-label": "Add field",
19521
- className: "flex h-7 w-7 items-center justify-center rounded-md text-foreground transition-colors hover:bg-muted/80",
19522
- onClick: () => setFieldTypePickerOpen((open) => !open),
19523
- "data-ohw-add-field": "",
19524
- children: /* @__PURE__ */ jsx33(Plus4, { size: 15, "aria-hidden": true })
19525
- }
19526
- ),
19527
- /* @__PURE__ */ jsx33("div", { className: "mx-0.5 h-5 w-px bg-border" }),
19528
- /* @__PURE__ */ jsxs20(
19529
- "button",
19530
- {
19531
- type: "button",
19532
- className: "flex h-7 items-center gap-1.5 whitespace-nowrap rounded-md px-2 text-[13px] font-semibold text-foreground transition-colors hover:bg-muted/80",
19533
- onClick: () => {
19534
- const form = formPickElRef.current;
19535
- if (!form) return;
19536
- postToParent2({
19537
- type: "ow:form-pick",
19538
- formKey: formKeyOf(form),
19539
- hasLongText: formHasLongText(form)
19540
- });
19541
- },
19542
- children: [
19543
- /* @__PURE__ */ jsx33(Settings, { size: 14, "aria-hidden": true }),
19544
- "Form settings",
19545
- formPickCount ? (
19546
- // Counter pill, per the design — not a text suffix.
19547
- /* @__PURE__ */ jsx33(
19548
- "span",
19549
- {
19550
- "data-ohw-form-count": "",
19551
- className: "ml-0.5 inline-flex h-[18px] min-w-[18px] items-center justify-center rounded-full bg-primary px-1.5 text-[11px] font-semibold text-primary-foreground",
19552
- children: formPickCount
19553
- }
19554
- )
19555
- ) : null
19556
- ]
19557
- }
19558
- ),
19559
- /* @__PURE__ */ jsx33("div", { className: "mx-0.5 h-5 w-px bg-border" }),
19560
- /* @__PURE__ */ jsx33("div", { className: "flex items-center rounded-md bg-muted/70 p-0.5", children: ["default", "success"].map((state) => /* @__PURE__ */ jsx33(
19561
- "button",
19562
- {
19563
- type: "button",
19564
- "aria-pressed": formViewState === state,
19565
- className: "rounded-md px-2.5 py-1 text-[13px] font-semibold capitalize transition-colors " + (formViewState === state ? "bg-background text-foreground shadow-sm" : "text-muted-foreground hover:text-foreground"),
19566
- onClick: () => {
19567
- const form = formPickElRef.current;
19568
- const key = form ? formKeyOf(form) : null;
19569
- if (!form || !key) return;
19570
- const initial = successInitialFor(form, key, editContentRef.current);
19571
- setFormViewState(form, key, state, initial);
19572
- setFormViewStateUi(state);
19573
- setFormPickRect(form.getBoundingClientRect());
19574
- if (state === "success") {
19575
- const successEl = form.querySelector(`[${SUCCESS_TEXT_ATTR}]`);
19576
- if (successEl) requestAnimationFrame(() => activateRef.current(successEl));
19577
- } else {
19578
- deactivateRef.current();
19579
- }
19580
- },
19581
- children: state
19582
- },
19583
- state
19584
- )) })
19585
- ]
20980
+ className: "pointer-events-none fixed z-[2147483645]",
20981
+ style: {
20982
+ top: toolbar ? toolbar.bottom + 6 : formPickRect.top + 16,
20983
+ left: toolbar ? toolbar.left : formPickRect.left + 24
20984
+ },
20985
+ children: /* @__PURE__ */ jsx33(FieldTypePicker, { onPick: handleAddField })
19586
20986
  }
19587
- )
19588
- }
19589
- ),
19590
- formHoverRect && !isItemDragging && /* @__PURE__ */ jsx33(
19591
- ItemInteractionLayer,
19592
- {
19593
- rect: formHoverRect,
19594
- state: "hover",
19595
- chromeGap: formHoverElRef.current && getFieldWrapper(formHoverElRef.current) ? 8 : 24
19596
- }
19597
- ),
19598
- fieldPickRect && fieldPickState && !isItemDragging && toolbarVariant !== "rich-text" && /* @__PURE__ */ jsx33(
19599
- ItemInteractionLayer,
19600
- {
19601
- rect: fieldPickRect,
19602
- state: fieldDragging ? "dragging" : "active-top",
19603
- itemDragSurface: false,
19604
- toolbarAlign: "left",
19605
- chromeGap: 10,
19606
- showHandle: true,
19607
- dragHandleLabel: "Reorder field",
19608
- onDragHandleDragStart: handleFieldDragStart,
19609
- onDragHandleDragEnd: handleFieldDragEnd,
19610
- toolbar: /* @__PURE__ */ jsx33(
19611
- FormFieldToolbar,
20987
+ );
20988
+ })() : null,
20989
+ toolbarVariant === "select-frame" && toolbarRect && !linkPopover && !isItemDragging && !isFooterFrameSelection && selectedElRef.current && isNavbarLinksContainer2(selectedElRef.current) && /* @__PURE__ */ jsx33(NavbarContainerChrome, { rect: toolbarRect, onAdd: handleAddTopLevelNavItem }),
20990
+ toolbarVariant === "select-frame" && toolbarRect && !linkPopover && !isItemDragging && !isFooterFrameSelection && selectedElRef.current && isFooterLinksContainer(selectedElRef.current) && /* @__PURE__ */ jsx33(
20991
+ FooterContainerChrome,
20992
+ {
20993
+ rect: toolbarRect,
20994
+ onAdd: handleAddFooterColumn,
20995
+ addDisabled: !canAddFooterColumn()
20996
+ }
20997
+ ),
20998
+ toolbarRect && !linkPopover && (toolbarVariant === "link-action" || toolbarVariant === "select-frame" || toolbarVariant === "logo") && /* @__PURE__ */ jsx33(
20999
+ ItemInteractionLayer,
21000
+ {
21001
+ rect: isItemDragging && draggedItemRect && (footerDragRef.current?.wasSelected || navDragRef.current?.wasSelected) ? draggedItemRect : toolbarRect,
21002
+ toolbarBelowRect: selectedElRef.current && isNavigationItem2(selectedElRef.current) ? getOpenNavDropdownPanelRect(selectedElRef.current) : null,
21003
+ elRef: glowElRef,
21004
+ state: isItemDragging ? "dragging" : selectedElRef.current && isNavigationItem2(selectedElRef.current) && getOpenNavDropdownPanelRect(selectedElRef.current) ? "active-bottom" : resolveItemInteractionState(toolbarRect, parentScrollRef.current),
21005
+ showHandle: toolbarVariant === "link-action" && Boolean(reorderHrefKey) || toolbarVariant === "select-frame" && isFooterFrameSelection,
21006
+ dragDisabled: reorderDragDisabled,
21007
+ dragHandleLabel: reorderHrefKey ? `Reorder ${reorderHrefKey}` : "Reorder item",
21008
+ onDragHandleDragStart: handleItemDragStart,
21009
+ onDragHandleDragEnd: handleItemDragEnd,
21010
+ onItemPointerDown: toolbarVariant === "logo" ? void 0 : handleItemChromePointerDown,
21011
+ onItemClick: toolbarVariant === "logo" ? void 0 : handleItemChromeClick,
21012
+ itemDragSurface: toolbarVariant !== "logo" && !isFooterFrameSelection,
21013
+ toolbar: toolbarVariant === "link-action" && !isItemDragging || toolbarVariant === "select-frame" && (isFooterFrameSelection || selectedIsSocialsRow) && !isItemDragging ? /* @__PURE__ */ jsx33(
21014
+ ItemActionToolbar,
21015
+ {
21016
+ onEditLink: openLinkPopoverForSelected,
21017
+ onStyle: () => {
21018
+ const row = selectedElRef.current;
21019
+ if (!row) return;
21020
+ if (floatingPanel?.kind === "socials-display") closeFloatingPanelOnly();
21021
+ else openSocialsDisplayPanel(row);
21022
+ },
21023
+ showStyle: selectedIsSocialsRow,
21024
+ styleActive: floatingPanel?.kind === "socials-display",
21025
+ onAddItem: handleAddChildItem,
21026
+ onSelectParent: handleSelectParent,
21027
+ onDuplicate: handleDuplicateSelected,
21028
+ onDelete: handleDeleteSelected,
21029
+ addItemDisabled: isFooterFrameSelection && isFooterAddItemDisabled(selectedElRef.current) || selectedElRef.current !== null && (() => {
21030
+ const row = isSocialsRow(selectedElRef.current) ? selectedElRef.current : findSocialsRow(selectedElRef.current);
21031
+ return row ? !canAddSocialItem(row) : false;
21032
+ })(),
21033
+ editLinkDisabled: false,
21034
+ moreDisabled: false,
21035
+ duplicateDisabled: isFooterFrameSelection || selectedIsSocialsRow,
21036
+ showEditLink: !isFooterFrameSelection && !selectedIsSocialsRow && navDropdownPreviewOpen === null,
21037
+ showAddItem: isFooterFrameSelection || selectedIsSocialsRow || !selectedIsCta && Boolean(
21038
+ selectedElRef.current && isNavbarHrefKey(selectedElRef.current.getAttribute("data-ohw-href-key")) && !isNestedNavChild(selectedElRef.current)
21039
+ ),
21040
+ showMore: (!selectedIsCta || isFooterFrameSelection) && !selectedIsSocialsRow,
21041
+ dropdownOpen: navDropdownPreviewOpen,
21042
+ onDropdownOpenChange: handleNavDropdownOpenChange,
21043
+ headingVisible: footerHeadingVisible,
21044
+ onHeadingVisibleChange: handleFooterHeadingVisibleChange
21045
+ }
21046
+ ) : void 0
21047
+ }
21048
+ ),
21049
+ toolbarRect && toolbarVariant === "rich-text" && !linkPopover && /* @__PURE__ */ jsxs20(Fragment8, { children: [
21050
+ /* @__PURE__ */ jsx33(
21051
+ EditGlowChrome,
19612
21052
  {
19613
- type: fieldPickState.type,
19614
- required: fieldPickState.required,
19615
- onTypeChange: handleFieldTypeChange,
19616
- onRequiredToggle: handleFieldRequiredToggle,
19617
- onDuplicate: handleFieldDuplicate,
19618
- onDelete: handleFieldDelete
21053
+ rect: toolbarRect,
21054
+ elRef: glowElRef,
21055
+ reorderHrefKey,
21056
+ dragDisabled: reorderDragDisabled,
21057
+ hideHandle: isItemDragging
19619
21058
  }
19620
- )
19621
- }
19622
- ),
19623
- fieldDragging ? fieldDropSlots.map((slot, i) => /* @__PURE__ */ jsx33(
19624
- "div",
19625
- {
19626
- className: "pointer-events-none fixed z-[2147483644]",
19627
- style: { top: slot.top, left: slot.left, width: slot.width },
19628
- children: /* @__PURE__ */ jsx33(
19629
- DropIndicator,
21059
+ ),
21060
+ /* @__PURE__ */ jsx33(
21061
+ FloatingToolbar,
19630
21062
  {
19631
- direction: "horizontal",
19632
- state: fieldDropIndex === i ? "dragActive" : "dragIdle",
19633
- className: "!w-full"
21063
+ rect: toolbarRect,
21064
+ parentScroll: parentScrollRef.current,
21065
+ elRef: toolbarElRef,
21066
+ onCommand: handleCommand,
21067
+ activeCommands,
21068
+ showEditLink,
21069
+ onEditLink: openLinkPopoverForActive
19634
21070
  }
19635
21071
  )
19636
- },
19637
- `field-drop-${i}`
19638
- )) : null,
19639
- fieldTypePickerOpen && formPickRect ? (() => {
19640
- const toolbar = document.querySelector("[data-ohw-form-toolbar]")?.getBoundingClientRect();
19641
- return /* @__PURE__ */ jsx33(
21072
+ ] }),
21073
+ maxBadge && /* @__PURE__ */ jsxs20(
19642
21074
  "div",
19643
21075
  {
19644
- className: "pointer-events-none fixed z-[2147483645]",
21076
+ "data-ohw-max-badge": "",
19645
21077
  style: {
19646
- top: toolbar ? toolbar.bottom + 6 : formPickRect.top + 16,
19647
- left: toolbar ? toolbar.left : formPickRect.left + 24
21078
+ position: "fixed",
21079
+ top: maxBadge.rect.bottom + 4,
21080
+ left: maxBadge.rect.right,
21081
+ transform: "translateX(-100%)",
21082
+ zIndex: 2147483647,
21083
+ background: maxBadge.current > maxBadge.max ? "#FEF2F2" : "#F5F5F4",
21084
+ color: maxBadge.current > maxBadge.max ? "#DC2626" : "#78716C",
21085
+ border: `1px solid ${maxBadge.current > maxBadge.max ? "#FECACA" : "#E7E5E4"}`,
21086
+ borderRadius: 4,
21087
+ padding: "2px 6px",
21088
+ fontSize: 11,
21089
+ fontWeight: 500,
21090
+ pointerEvents: "none"
19648
21091
  },
19649
- children: /* @__PURE__ */ jsx33(FieldTypePicker, { onPick: handleAddField })
21092
+ children: [
21093
+ maxBadge.current,
21094
+ "/",
21095
+ maxBadge.max
21096
+ ]
19650
21097
  }
19651
- );
19652
- })() : null,
19653
- toolbarVariant === "select-frame" && toolbarRect && !linkPopover && !isItemDragging && !isFooterFrameSelection && selectedElRef.current && isNavbarLinksContainer2(selectedElRef.current) && /* @__PURE__ */ jsx33(NavbarContainerChrome, { rect: toolbarRect, onAdd: handleAddTopLevelNavItem }),
19654
- toolbarVariant === "select-frame" && toolbarRect && !linkPopover && !isItemDragging && !isFooterFrameSelection && selectedElRef.current && isFooterLinksContainer(selectedElRef.current) && /* @__PURE__ */ jsx33(
19655
- FooterContainerChrome,
19656
- {
19657
- rect: toolbarRect,
19658
- onAdd: handleAddFooterColumn,
19659
- addDisabled: !canAddFooterColumn()
19660
- }
19661
- ),
19662
- toolbarRect && !linkPopover && (toolbarVariant === "link-action" || toolbarVariant === "select-frame" || toolbarVariant === "logo") && /* @__PURE__ */ jsx33(
19663
- ItemInteractionLayer,
19664
- {
19665
- rect: isItemDragging && draggedItemRect && (footerDragRef.current?.wasSelected || navDragRef.current?.wasSelected) ? draggedItemRect : toolbarRect,
19666
- toolbarBelowRect: selectedElRef.current && isNavigationItem2(selectedElRef.current) ? getOpenNavDropdownPanelRect(selectedElRef.current) : null,
19667
- elRef: glowElRef,
19668
- state: isItemDragging ? "dragging" : selectedElRef.current && isNavigationItem2(selectedElRef.current) && getOpenNavDropdownPanelRect(selectedElRef.current) ? "active-bottom" : resolveItemInteractionState(toolbarRect, parentScrollRef.current),
19669
- showHandle: toolbarVariant === "link-action" && Boolean(reorderHrefKey) || toolbarVariant === "select-frame" && isFooterFrameSelection,
19670
- dragDisabled: reorderDragDisabled,
19671
- dragHandleLabel: reorderHrefKey ? `Reorder ${reorderHrefKey}` : "Reorder item",
19672
- onDragHandleDragStart: handleItemDragStart,
19673
- onDragHandleDragEnd: handleItemDragEnd,
19674
- onItemPointerDown: toolbarVariant === "logo" ? void 0 : handleItemChromePointerDown,
19675
- onItemClick: toolbarVariant === "logo" ? void 0 : handleItemChromeClick,
19676
- itemDragSurface: toolbarVariant !== "logo" && !isFooterFrameSelection,
19677
- toolbar: toolbarVariant === "link-action" && !isItemDragging || toolbarVariant === "select-frame" && (isFooterFrameSelection || selectedIsSocialsRow) && !isItemDragging ? /* @__PURE__ */ jsx33(
19678
- ItemActionToolbar,
19679
- {
19680
- onEditLink: openLinkPopoverForSelected,
19681
- onStyle: () => {
19682
- const row = selectedElRef.current;
19683
- if (!row) return;
19684
- if (floatingPanel?.kind === "socials-display") closeFloatingPanelOnly();
19685
- else openSocialsDisplayPanel(row);
19686
- },
19687
- showStyle: selectedIsSocialsRow,
19688
- styleActive: floatingPanel?.kind === "socials-display",
19689
- onAddItem: handleAddChildItem,
19690
- onSelectParent: handleSelectParent,
19691
- onDuplicate: handleDuplicateSelected,
19692
- onDelete: handleDeleteSelected,
19693
- addItemDisabled: isFooterFrameSelection && isFooterAddItemDisabled(selectedElRef.current) || selectedElRef.current !== null && (() => {
19694
- const row = isSocialsRow(selectedElRef.current) ? selectedElRef.current : findSocialsRow(selectedElRef.current);
19695
- return row ? !canAddSocialItem(row) : false;
19696
- })(),
19697
- editLinkDisabled: false,
19698
- moreDisabled: false,
19699
- duplicateDisabled: isFooterFrameSelection || selectedIsSocialsRow,
19700
- showEditLink: !isFooterFrameSelection && !selectedIsSocialsRow && navDropdownPreviewOpen === null,
19701
- showAddItem: isFooterFrameSelection || selectedIsSocialsRow || !selectedIsCta && Boolean(
19702
- selectedElRef.current && isNavbarHrefKey(selectedElRef.current.getAttribute("data-ohw-href-key")) && !isNestedNavChild(selectedElRef.current)
19703
- ),
19704
- showMore: (!selectedIsCta || isFooterFrameSelection) && !selectedIsSocialsRow,
19705
- dropdownOpen: navDropdownPreviewOpen,
19706
- onDropdownOpenChange: handleNavDropdownOpenChange,
19707
- headingVisible: footerHeadingVisible,
19708
- onHeadingVisibleChange: handleFooterHeadingVisibleChange
19709
- }
19710
- ) : void 0
19711
- }
19712
- ),
19713
- toolbarRect && toolbarVariant === "rich-text" && !linkPopover && /* @__PURE__ */ jsxs20(Fragment8, { children: [
19714
- /* @__PURE__ */ jsx33(
19715
- EditGlowChrome,
21098
+ ),
21099
+ toggleState && !linkPopover && /* @__PURE__ */ jsx33(
21100
+ StateToggle,
19716
21101
  {
19717
- rect: toolbarRect,
19718
- elRef: glowElRef,
19719
- reorderHrefKey,
19720
- dragDisabled: reorderDragDisabled,
19721
- hideHandle: isItemDragging
21102
+ rect: toggleState.rect,
21103
+ activeState: toggleState.activeState,
21104
+ states: toggleState.states,
21105
+ onStateChange: handleStateChange
19722
21106
  }
19723
21107
  ),
19724
- /* @__PURE__ */ jsx33(
19725
- FloatingToolbar,
21108
+ sectionGap && !linkPopover && /* @__PURE__ */ jsxs20(
21109
+ "div",
19726
21110
  {
19727
- rect: toolbarRect,
19728
- parentScroll: parentScrollRef.current,
19729
- elRef: toolbarElRef,
19730
- onCommand: handleCommand,
19731
- activeCommands,
19732
- showEditLink,
19733
- onEditLink: openLinkPopoverForActive
21111
+ "data-ohw-section-insert-line": "",
21112
+ className: "fixed left-0 w-full z-2147483646 flex items-center pointer-events-none",
21113
+ style: { top: sectionGap.y, transform: "translateY(-50%)" },
21114
+ children: [
21115
+ /* @__PURE__ */ jsx33("div", { className: "flex-1 bg-primary", style: { height: 3 } }),
21116
+ /* @__PURE__ */ jsx33(
21117
+ Badge,
21118
+ {
21119
+ className: "px-8 py-1 bg-primary hover:bg-primary text-primary-foreground text-xs font-medium shrink-0 rounded-full cursor-pointer pointer-events-auto",
21120
+ onClick: () => {
21121
+ window.parent.postMessage(
21122
+ {
21123
+ type: "ow:add-section",
21124
+ insertAfter: sectionGap.insertAfter,
21125
+ insertBefore: sectionGap.insertBefore
21126
+ },
21127
+ "*"
21128
+ );
21129
+ },
21130
+ children: "Add Section"
21131
+ }
21132
+ ),
21133
+ /* @__PURE__ */ jsx33("div", { className: "flex-1 bg-primary", style: { height: 3 } })
21134
+ ]
19734
21135
  }
19735
- )
19736
- ] }),
19737
- maxBadge && /* @__PURE__ */ jsxs20(
19738
- "div",
19739
- {
19740
- "data-ohw-max-badge": "",
19741
- style: {
19742
- position: "fixed",
19743
- top: maxBadge.rect.bottom + 4,
19744
- left: maxBadge.rect.right,
19745
- transform: "translateX(-100%)",
19746
- zIndex: 2147483647,
19747
- background: maxBadge.current > maxBadge.max ? "#FEF2F2" : "#F5F5F4",
19748
- color: maxBadge.current > maxBadge.max ? "#DC2626" : "#78716C",
19749
- border: `1px solid ${maxBadge.current > maxBadge.max ? "#FECACA" : "#E7E5E4"}`,
19750
- borderRadius: 4,
19751
- padding: "2px 6px",
19752
- fontSize: 11,
19753
- fontWeight: 500,
19754
- pointerEvents: "none"
21136
+ ),
21137
+ linkPopover && dialogPortalContainer ? /* @__PURE__ */ jsx33(
21138
+ LinkPopover,
21139
+ {
21140
+ panelRef: linkPopoverPanelRef,
21141
+ portalContainer: dialogPortalContainer,
21142
+ open: true,
21143
+ mode: linkPopover.mode ?? "edit",
21144
+ pages: sitePages,
21145
+ sections: currentSections,
21146
+ sectionsByPath,
21147
+ initialTarget: linkPopover.target,
21148
+ existingTargets: linkPopover.intent === "add-nav" ? getNavbarExistingTargets() : [],
21149
+ onClose: closeLinkPopover,
21150
+ onSubmit: handleLinkPopoverSubmit
19755
21151
  },
19756
- children: [
19757
- maxBadge.current,
19758
- "/",
19759
- maxBadge.max
19760
- ]
19761
- }
19762
- ),
19763
- toggleState && !linkPopover && /* @__PURE__ */ jsx33(
19764
- StateToggle,
19765
- {
19766
- rect: toggleState.rect,
19767
- activeState: toggleState.activeState,
19768
- states: toggleState.states,
19769
- onStateChange: handleStateChange
19770
- }
19771
- ),
19772
- sectionGap && !linkPopover && /* @__PURE__ */ jsxs20(
19773
- "div",
19774
- {
19775
- "data-ohw-section-insert-line": "",
19776
- className: "fixed left-0 w-full z-2147483646 flex items-center pointer-events-none",
19777
- style: { top: sectionGap.y, transform: "translateY(-50%)" },
19778
- children: [
19779
- /* @__PURE__ */ jsx33("div", { className: "flex-1 bg-primary", style: { height: 3 } }),
19780
- /* @__PURE__ */ jsx33(
19781
- Badge,
21152
+ linkPopover.key
21153
+ ) : null,
21154
+ floatingPanel && floatingPanel.kind === "socials-display" ? /* @__PURE__ */ jsx33(
21155
+ FloatingPanel,
21156
+ {
21157
+ open: true,
21158
+ title: floatingPanel.title,
21159
+ context: floatingPanel.context,
21160
+ position: floatingPanelPos,
21161
+ onPositionChange: setFloatingPanelPos,
21162
+ parentScroll: parentScrollSnap ?? parentScrollRef.current,
21163
+ onClose: closeFloatingPanelOnly,
21164
+ children: /* @__PURE__ */ jsx33(
21165
+ SocialsDisplayPanel,
19782
21166
  {
19783
- className: "px-8 py-1 bg-primary hover:bg-primary text-primary-foreground text-xs font-medium shrink-0 rounded-full cursor-pointer pointer-events-auto",
19784
- onClick: () => {
19785
- window.parent.postMessage(
19786
- {
19787
- type: "ow:add-section",
19788
- insertAfter: sectionGap.insertAfter,
19789
- insertBefore: sectionGap.insertBefore
19790
- },
19791
- "*"
19792
- );
19793
- },
19794
- children: "Add Section"
21167
+ display: socialsDisplayFor(floatingPanel.row, editContentRef.current),
21168
+ onChange: (next) => {
21169
+ changeSocialsDisplay(floatingPanel.row, next);
21170
+ setFloatingPanel({ ...floatingPanel });
21171
+ }
19795
21172
  }
19796
- ),
19797
- /* @__PURE__ */ jsx33("div", { className: "flex-1 bg-primary", style: { height: 3 } })
19798
- ]
19799
- }
19800
- ),
19801
- linkPopover && dialogPortalContainer ? /* @__PURE__ */ jsx33(
19802
- LinkPopover,
19803
- {
19804
- panelRef: linkPopoverPanelRef,
19805
- portalContainer: dialogPortalContainer,
19806
- open: true,
19807
- mode: linkPopover.mode ?? "edit",
19808
- pages: sitePages,
19809
- sections: currentSections,
19810
- sectionsByPath,
19811
- initialTarget: linkPopover.target,
19812
- existingTargets: linkPopover.intent === "add-nav" ? getNavbarExistingTargets() : [],
19813
- onClose: closeLinkPopover,
19814
- onSubmit: handleLinkPopoverSubmit
21173
+ )
21174
+ }
21175
+ ) : null
21176
+ ] }),
21177
+ bridgeRoot
21178
+ ) : null
21179
+ ] });
21180
+ }
21181
+
21182
+ // src/ui/EmptySection.tsx
21183
+ import Link3 from "next/link";
21184
+ import { Fragment as Fragment9, jsx as jsx34, jsxs as jsxs21 } from "react/jsx-runtime";
21185
+ function EmptySection({ title, homeHref = "/", eyebrowKey, titleKey, subtitleKey }) {
21186
+ return /* @__PURE__ */ jsxs21(Fragment9, { children: [
21187
+ /* @__PURE__ */ jsx34(
21188
+ "p",
21189
+ {
21190
+ style: {
21191
+ fontFamily: "var(--brand-font-body)",
21192
+ fontSize: "0.75rem",
21193
+ fontWeight: 500,
21194
+ letterSpacing: "0.15em",
21195
+ textTransform: "uppercase",
21196
+ color: "var(--brand-accent)",
21197
+ marginBottom: "1.5rem"
19815
21198
  },
19816
- linkPopover.key
19817
- ) : null,
19818
- floatingPanel && floatingPanel.kind === "socials-display" ? /* @__PURE__ */ jsx33(
19819
- FloatingPanel,
19820
- {
19821
- open: true,
19822
- title: floatingPanel.title,
19823
- context: floatingPanel.context,
19824
- position: floatingPanelPos,
19825
- onPositionChange: setFloatingPanelPos,
19826
- parentScroll: parentScrollSnap ?? parentScrollRef.current,
19827
- onClose: closeFloatingPanelOnly,
19828
- children: /* @__PURE__ */ jsx33(
19829
- SocialsDisplayPanel,
19830
- {
19831
- display: socialsDisplayFor(floatingPanel.row, editContentRef.current),
19832
- onChange: (next) => {
19833
- changeSocialsDisplay(floatingPanel.row, next);
19834
- setFloatingPanel({ ...floatingPanel });
19835
- }
19836
- }
19837
- )
19838
- }
19839
- ) : null
19840
- ] }),
19841
- bridgeRoot
19842
- ) : null;
21199
+ children: /* @__PURE__ */ jsx34(Link3, { href: homeHref, style: { color: "inherit" }, children: /* @__PURE__ */ jsx34("span", { ...eyebrowKey ? { "data-ohw-editable": "text", "data-ohw-key": eyebrowKey } : {}, children: "Home" }) })
21200
+ }
21201
+ ),
21202
+ /* @__PURE__ */ jsx34(
21203
+ "h1",
21204
+ {
21205
+ style: {
21206
+ fontFamily: "var(--brand-font-heading)",
21207
+ fontSize: "clamp(2rem, 3.5vw, 2.75rem)",
21208
+ lineHeight: 1.1,
21209
+ letterSpacing: "-0.025em",
21210
+ color: "var(--brand-text)",
21211
+ marginBottom: "1rem"
21212
+ },
21213
+ ...titleKey ? { "data-ohw-editable": "text", "data-ohw-key": titleKey, "data-ohw-max-length": 80 } : {},
21214
+ children: title
21215
+ }
21216
+ ),
21217
+ /* @__PURE__ */ jsx34(
21218
+ "p",
21219
+ {
21220
+ style: {
21221
+ fontFamily: "var(--brand-font-body)",
21222
+ fontSize: "1rem",
21223
+ lineHeight: 1.7,
21224
+ fontWeight: 300,
21225
+ color: "var(--brand-text-muted)",
21226
+ maxWidth: "340px"
21227
+ },
21228
+ ...subtitleKey ? { "data-ohw-editable": "text", "data-ohw-key": subtitleKey, "data-ohw-max-length": 160 } : {},
21229
+ children: "This page doesn't have any content yet."
21230
+ }
21231
+ )
21232
+ ] });
19843
21233
  }
19844
21234
  export {
19845
21235
  AI_DEFAULT_BRAND,
@@ -19857,6 +21247,7 @@ export {
19857
21247
  DropdownMenuItem,
19858
21248
  DropdownMenuSeparator,
19859
21249
  DropdownMenuTrigger,
21250
+ EmptySection,
19860
21251
  ItemActionToolbar,
19861
21252
  ItemInteractionLayer,
19862
21253
  LinkEditorPanel,