@ohhwells/bridge 0.1.72 → 0.1.73-next.227

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 React12, { useCallback as useCallback7, useEffect as useEffect12, useLayoutEffect as useLayoutEffect5, useRef as useRef9, useState as useState12 } from "react";
4
+ import React12, { useCallback as useCallback8, useEffect as useEffect13, useLayoutEffect as useLayoutEffect5, useRef as useRef10, useState as useState13 } 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
  });
@@ -141,12 +459,60 @@ var featureLines = (value) => value.split(/\n|<br\s*\/?>/i).map((line) => line.t
141
459
  var CHECK_MASK = `data:image/svg+xml,${encodeURIComponent(
142
460
  '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="black" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M20 6 9 17l-5-5"/></svg>'
143
461
  )}`;
462
+ var AI_MOBILE_CSS = [
463
+ "@media (max-width: 768px){",
464
+ "[data-ai-section]{overflow-x:hidden}",
465
+ "[data-ai-container]{padding:0 20px !important}",
466
+ "[data-ai-row]{display:flex !important;flex-direction:column !important;align-items:stretch !important}",
467
+ "[data-ai-cell]{width:100%;min-width:0}",
468
+ "[data-ai-grid]{grid-template-columns:1fr !important}",
469
+ // Group containers flatten to a column on phones; span placements come along for free.
470
+ "[data-ai-group]{display:flex !important;flex-direction:column !important}",
471
+ "[data-ai-group] > *{grid-column:auto !important}",
472
+ // The 50:50 form collapses to a single stacked column on phones.
473
+ "[data-ai-form]{grid-template-columns:1fr !important}",
474
+ "[data-ai-section] img{max-width:100%}",
475
+ "}",
476
+ "@media (min-width: 769px) and (max-width: 1024px){",
477
+ "[data-ai-grid]{grid-template-columns:repeat(2, 1fr) !important}",
478
+ "}"
479
+ ].join("");
144
480
  var FEATURE_LINE_CSS = [
145
481
  "[data-ai-features]>div{position:relative;padding-left:40px;min-height:24px}",
146
482
  '[data-ai-features]>div::before{content:"";position:absolute;left:0;top:1px;width:24px;height:24px;',
147
483
  `background-color:currentColor;-webkit-mask:url("${CHECK_MASK}") no-repeat 0 0/24px 24px;`,
148
484
  `mask:url("${CHECK_MASK}") no-repeat 0 0/24px 24px}`
149
485
  ].join("");
486
+ function hexLuminance(color) {
487
+ const m = /^#([0-9a-f]{6})$/i.exec(color.trim());
488
+ if (!m) return null;
489
+ const [r2, g, b] = [0, 2, 4].map((i) => {
490
+ const c = parseInt(m[1].slice(i, i + 2), 16) / 255;
491
+ return c <= 0.03928 ? c / 12.92 : Math.pow((c + 0.055) / 1.055, 2.4);
492
+ });
493
+ return 0.2126 * r2 + 0.7152 * g + 0.0722 * b;
494
+ }
495
+ function hexContrast(a, b) {
496
+ const la = hexLuminance(a);
497
+ const lb = hexLuminance(b);
498
+ if (la === null || lb === null) return null;
499
+ const [hi, lo] = la > lb ? [la, lb] : [lb, la];
500
+ return (hi + 0.05) / (lo + 0.05);
501
+ }
502
+ function accentBandContext(brand) {
503
+ const p = brand.palette;
504
+ const lightWins = (hexContrast(p.light, p.primary) ?? 99) >= (hexContrast(p.dark, p.primary) ?? 0);
505
+ if (lightWins) {
506
+ return {
507
+ brand: { ...brand, palette: { dark: p.light, primary: p.light, accent: p.light, light: p.primary } },
508
+ buttonLabel: p.primary
509
+ };
510
+ }
511
+ return {
512
+ brand: { ...brand, palette: { dark: p.dark, primary: p.dark, accent: p.dark, light: p.light } },
513
+ buttonLabel: p.light
514
+ };
515
+ }
150
516
  function textAttrs(ctx, path) {
151
517
  return ctx.keyFor ? { "data-ohw-key": ctx.keyFor(path), "data-ohw-editable": "text" } : {};
152
518
  }
@@ -158,7 +524,12 @@ var AI_RESPONSIVE_CSS = [
158
524
  "@media (max-width: 640px) {",
159
525
  " [data-ai-responsive] [data-ai-grid] { grid-template-columns: 1fr !important; }",
160
526
  " [data-ai-responsive] [data-ai-columns] > * { grid-column: 1 / -1 !important; }",
527
+ // Group containers flatten to a column on phones; span placements come along for free.
528
+ " [data-ai-responsive] [data-ai-group] { display: flex !important; flex-direction: column !important; }",
529
+ " [data-ai-responsive] [data-ai-group] > * { grid-column: auto !important; }",
161
530
  " [data-ai-responsive] [data-ai-section-inner] { padding-left: 20px !important; padding-right: 20px !important; }",
531
+ " [data-ai-responsive] { overflow-x: hidden; }",
532
+ " [data-ai-responsive] img { max-width: 100%; }",
162
533
  "}"
163
534
  ].join("\n");
164
535
  var TRANSPARENT_PX = "data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7";
@@ -236,7 +607,7 @@ function ButtonEl({
236
607
  }) {
237
608
  const secondary = slots.variant === "secondary";
238
609
  const href = str(slots.href);
239
- const linkAttrs = ctx.keyFor && path ? { "data-ohw-href-key": ctx.keyFor(`${path}.href`) } : {};
610
+ const linkAttrs = ctx.keyFor && path ? { "data-ohw-href-key": ctx.keyFor(`${path}.href`), "data-ohw-role": "button" } : {};
240
611
  return /* @__PURE__ */ jsx(
241
612
  "a",
242
613
  {
@@ -252,7 +623,7 @@ function ButtonEl({
252
623
  textDecoration: "none",
253
624
  cursor: "pointer",
254
625
  ...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 }
626
+ ...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
627
  },
257
628
  children: /* @__PURE__ */ jsx("span", { ...path ? textAttrs(ctx, `${path}.label`) : {}, children: str(slots.label) })
258
629
  }
@@ -758,7 +1129,24 @@ function CardBlock({ node, ctx, path }) {
758
1129
  minWidth: 0
759
1130
  },
760
1131
  children: [
761
- media && (horizontal ? /* @__PURE__ */ jsx("div", { style: { flex: 1, minWidth: 0, display: "flex", alignItems: "center", padding: mediaInset }, children: media }) : /* @__PURE__ */ jsx(
1132
+ media && (horizontal ? /* @__PURE__ */ jsx(
1133
+ "div",
1134
+ {
1135
+ style: (
1136
+ // An icon hugs its glyph — flex:1 gave a 48px icon half the card and pushed the
1137
+ // text to the far side. Photos keep the half-and-half split. The inset has no
1138
+ // inner padding (the photo split absorbed that), so the icon carries its own gap.
1139
+ /^(lucide|simple):/.test(mediaRef) ? {
1140
+ flexShrink: 0,
1141
+ display: "flex",
1142
+ alignItems: "center",
1143
+ padding: mediaInset,
1144
+ [mediaPosition === "right" ? "marginLeft" : "marginRight"]: 20
1145
+ } : { flex: 1, minWidth: 0, display: "flex", alignItems: "center", padding: mediaInset }
1146
+ ),
1147
+ children: media
1148
+ }
1149
+ ) : /* @__PURE__ */ jsx(
762
1150
  "div",
763
1151
  {
764
1152
  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" },
@@ -849,13 +1237,44 @@ function AccordionBlock({ node, ctx, path }) {
849
1237
  ) })
850
1238
  ] }, i)) });
851
1239
  }
1240
+ function useIsMobile() {
1241
+ const [mobile, setMobile] = React.useState(
1242
+ () => typeof window !== "undefined" && window.matchMedia("(max-width: 768px)").matches
1243
+ );
1244
+ React.useEffect(() => {
1245
+ const mq = window.matchMedia("(max-width: 768px)");
1246
+ const update = () => setMobile(mq.matches);
1247
+ update();
1248
+ mq.addEventListener("change", update);
1249
+ return () => mq.removeEventListener("change", update);
1250
+ }, []);
1251
+ return mobile;
1252
+ }
852
1253
  function Carousel({ items, itemsPerRow, ctx }) {
1254
+ const isMobile = useIsMobile();
1255
+ const perPage = isMobile ? 1 : itemsPerRow;
1256
+ const pages = Math.max(1, Math.ceil(items.length / perPage));
853
1257
  const [page, setPage] = React.useState(0);
854
- const pages = Math.max(1, Math.ceil(items.length / itemsPerRow));
855
1258
  const current = Math.min(page, pages - 1);
1259
+ if (pages <= 1) {
1260
+ const cols = Math.max(1, Math.min(items.length, itemsPerRow));
1261
+ return /* @__PURE__ */ jsx(
1262
+ "div",
1263
+ {
1264
+ "data-ai-grid": String(cols),
1265
+ style: {
1266
+ display: "grid",
1267
+ gridTemplateColumns: `repeat(${cols}, 1fr)`,
1268
+ gap: AI_TREE_TOKENS.spacing8,
1269
+ alignItems: "start"
1270
+ },
1271
+ children: items
1272
+ }
1273
+ );
1274
+ }
856
1275
  const pageGroups = Array.from(
857
1276
  { length: pages },
858
- (_, p) => items.slice(p * itemsPerRow, (p + 1) * itemsPerRow)
1277
+ (_, p) => items.slice(p * perPage, (p + 1) * perPage)
859
1278
  );
860
1279
  const chrome = (enabled) => ({
861
1280
  border: `1px solid ${ctx.brand.palette.dark}`,
@@ -880,55 +1299,69 @@ function Carousel({ items, itemsPerRow, ctx }) {
880
1299
  cursor: "pointer",
881
1300
  padding: 0
882
1301
  });
883
- return /* @__PURE__ */ jsxs("div", { "data-ai-carousel": "", style: { display: "flex", flexDirection: "column", gap: AI_TREE_TOKENS.spacing8, alignItems: "center" }, children: [
884
- /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", gap: AI_TREE_TOKENS.spacing6, width: "100%" }, children: [
885
- /* @__PURE__ */ jsx(
886
- "button",
887
- {
888
- type: "button",
889
- "aria-label": "Previous",
890
- onClick: () => setPage((p) => Math.max(0, p - 1)),
891
- style: chrome(current > 0),
892
- children: /* @__PURE__ */ jsx(ArrowLeft, { size: 24, strokeWidth: 1.5, "aria-hidden": true })
893
- }
894
- ),
895
- /* @__PURE__ */ jsx("div", { style: { flex: 1, minWidth: 0, overflow: "hidden" }, children: /* @__PURE__ */ jsx(
1302
+ const viewport = /* @__PURE__ */ jsx("div", { style: { flex: isMobile ? "0 0 auto" : 1, minWidth: 0, width: "100%", overflow: "hidden" }, children: /* @__PURE__ */ jsx(
1303
+ "div",
1304
+ {
1305
+ style: {
1306
+ display: "flex",
1307
+ transform: `translateX(-${current * 100}%)`,
1308
+ transition: "transform 0.4s ease"
1309
+ },
1310
+ children: pageGroups.map((group, p) => /* @__PURE__ */ jsx(
896
1311
  "div",
897
1312
  {
1313
+ "data-ai-grid": String(perPage),
898
1314
  style: {
899
- display: "flex",
900
- transform: `translateX(-${current * 100}%)`,
901
- transition: "transform 0.4s ease"
1315
+ flex: "0 0 100%",
1316
+ display: "grid",
1317
+ gridTemplateColumns: `repeat(${perPage}, 1fr)`,
1318
+ gap: AI_TREE_TOKENS.spacing8,
1319
+ alignItems: "start"
902
1320
  },
903
- children: pageGroups.map((group, p) => /* @__PURE__ */ jsx(
904
- "div",
905
- {
906
- "data-ai-grid": String(itemsPerRow),
907
- style: {
908
- flex: "0 0 100%",
909
- display: "grid",
910
- gridTemplateColumns: `repeat(${itemsPerRow}, 1fr)`,
911
- gap: AI_TREE_TOKENS.spacing8,
912
- alignItems: "start"
913
- },
914
- children: group
915
- },
916
- p
917
- ))
918
- }
919
- ) }),
920
- /* @__PURE__ */ jsx(
921
- "button",
922
- {
923
- type: "button",
924
- "aria-label": "Next",
925
- onClick: () => setPage((p) => Math.min(pages - 1, p + 1)),
926
- style: chrome(current < pages - 1),
927
- children: /* @__PURE__ */ jsx(ArrowRight, { size: 24, strokeWidth: 1.5, "aria-hidden": true })
928
- }
929
- )
1321
+ children: group
1322
+ },
1323
+ p
1324
+ ))
1325
+ }
1326
+ ) });
1327
+ const prevBtn = /* @__PURE__ */ jsx(
1328
+ "button",
1329
+ {
1330
+ type: "button",
1331
+ "aria-label": "Previous",
1332
+ onClick: () => setPage((p) => Math.max(0, p - 1)),
1333
+ style: chrome(current > 0),
1334
+ children: /* @__PURE__ */ jsx(ArrowLeft, { size: 24, strokeWidth: 1.5, "aria-hidden": true })
1335
+ }
1336
+ );
1337
+ const nextBtn = /* @__PURE__ */ jsx(
1338
+ "button",
1339
+ {
1340
+ type: "button",
1341
+ "aria-label": "Next",
1342
+ onClick: () => setPage((p) => Math.min(pages - 1, p + 1)),
1343
+ style: chrome(current < pages - 1),
1344
+ children: /* @__PURE__ */ jsx(ArrowRight, { size: 24, strokeWidth: 1.5, "aria-hidden": true })
1345
+ }
1346
+ );
1347
+ const dots = /* @__PURE__ */ jsx("div", { style: { display: "flex", gap: 9, justifyContent: "center" }, children: pageGroups.map((_, p) => /* @__PURE__ */ jsx("button", { type: "button", "aria-label": `Page ${p + 1}`, onClick: () => setPage(p), style: dot(p === current) }, p)) });
1348
+ if (isMobile) {
1349
+ return /* @__PURE__ */ jsxs("div", { "data-ai-carousel": "", style: { display: "flex", flexDirection: "column", gap: AI_TREE_TOKENS.spacing6, alignItems: "center" }, children: [
1350
+ viewport,
1351
+ /* @__PURE__ */ jsxs("div", { style: { display: "flex", gap: AI_TREE_TOKENS.spacing6, justifyContent: "center" }, children: [
1352
+ prevBtn,
1353
+ nextBtn
1354
+ ] }),
1355
+ dots
1356
+ ] });
1357
+ }
1358
+ return /* @__PURE__ */ jsxs("div", { "data-ai-carousel": "", style: { display: "flex", flexDirection: "column", gap: AI_TREE_TOKENS.spacing8, alignItems: "center" }, children: [
1359
+ /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", gap: AI_TREE_TOKENS.spacing6, width: "100%" }, children: [
1360
+ prevBtn,
1361
+ viewport,
1362
+ nextBtn
930
1363
  ] }),
931
- pages > 1 && /* @__PURE__ */ jsx("div", { style: { display: "flex", gap: 9, justifyContent: "center" }, children: pageGroups.map((_, p) => /* @__PURE__ */ jsx("button", { type: "button", "aria-label": `Page ${p + 1}`, onClick: () => setPage(p), style: dot(p === current) }, p)) })
1364
+ dots
932
1365
  ] });
933
1366
  }
934
1367
  function CollectionBlock({ node, ctx, path }) {
@@ -1022,6 +1455,49 @@ function renderNode(node, ctx, path) {
1022
1455
  switch (node.type) {
1023
1456
  case "text":
1024
1457
  return /* @__PURE__ */ jsx(TextBlock, { slots, ctx, path });
1458
+ // Layout container: arranges child blocks, contributes no content of its own. `grid` is a
1459
+ // nested 12-column grid the children span (a collage is 3–5 media on it, bottom-aligned so
1460
+ // mixed aspects read as a composition); `split` is exactly two children at a ratio; `stack`
1461
+ // is a column. Children render through this same dispatcher, so edit markers, media
1462
+ // resolution, and copy paths all work unchanged inside a group.
1463
+ case "group": {
1464
+ const layout = str(slots.layout);
1465
+ const gap = slots.spacing === "tight" ? AI_TREE_TOKENS.spacing3 : slots.spacing === "airy" ? AI_TREE_TOKENS.spacing8 : AI_TREE_TOKENS.spacing6;
1466
+ const kids = (node.children ?? []).map((child, i) => /* @__PURE__ */ jsx(
1467
+ "div",
1468
+ {
1469
+ style: layout === "grid" ? {
1470
+ gridColumn: `span ${typeof child.span === "number" ? Math.min(12, Math.max(1, child.span)) : 12}`,
1471
+ minWidth: 0
1472
+ } : { minWidth: 0 },
1473
+ children: renderNode(child, ctx, `${path}.c${i}`)
1474
+ },
1475
+ i
1476
+ ));
1477
+ if (layout === "grid") {
1478
+ return /* @__PURE__ */ jsx(
1479
+ "div",
1480
+ {
1481
+ "data-ai-group": "grid",
1482
+ style: { display: "grid", gridTemplateColumns: "repeat(12, 1fr)", gap, alignItems: "end" },
1483
+ children: kids
1484
+ }
1485
+ );
1486
+ }
1487
+ if (layout === "split") {
1488
+ const ratio = str(slots.ratio);
1489
+ const cols = ratio === "3:5" ? "3fr 5fr" : ratio === "5:3" ? "5fr 3fr" : "1fr 1fr";
1490
+ return /* @__PURE__ */ jsx(
1491
+ "div",
1492
+ {
1493
+ "data-ai-group": "split",
1494
+ style: { display: "grid", gridTemplateColumns: cols, gap, alignItems: "center" },
1495
+ children: kids
1496
+ }
1497
+ );
1498
+ }
1499
+ return /* @__PURE__ */ jsx("div", { "data-ai-group": "stack", style: { display: "flex", flexDirection: "column", gap }, children: kids });
1500
+ }
1025
1501
  case "button":
1026
1502
  return /* @__PURE__ */ jsx(ButtonEl, { slots, ctx, path });
1027
1503
  case "button-row":
@@ -1102,33 +1578,102 @@ function renderNode(node, ctx, path) {
1102
1578
  }
1103
1579
  );
1104
1580
  }
1105
- case "form":
1106
- return /* @__PURE__ */ jsx("div", { style: { display: "flex", flexDirection: "column", gap: AI_TREE_TOKENS.spacing4 }, children: (node.children ?? []).map((child, i) => {
1107
- if (child.type === "input") {
1108
- const cs = child.slots ?? {};
1109
- return /* @__PURE__ */ jsxs("div", { children: [
1110
- /* @__PURE__ */ jsx(
1111
- "div",
1112
- {
1113
- ...textAttrs(ctx, `${path}.c${i}.label`),
1114
- style: { ...typeStyle(AI_TREE_TOKENS.type.bodyMBold, ctx.brand.fonts.body), color: ctx.brand.palette.dark, marginBottom: 6 },
1115
- children: str(cs.label)
1116
- }
1117
- ),
1118
- /* @__PURE__ */ jsx(
1119
- "div",
1581
+ case "form": {
1582
+ const formAttrs = ctx.keyFor ? {
1583
+ "data-ohw-editable": "form",
1584
+ "data-ohw-key": ctx.keyFor(`${path}.form`),
1585
+ "data-ohw-success-text": "Thanks \u2014 we'll be in touch shortly."
1586
+ } : {};
1587
+ const fieldStyle = {
1588
+ width: "100%",
1589
+ boxSizing: "border-box",
1590
+ border: `1px solid ${ctx.brand.palette.accent}`,
1591
+ borderRadius: AI_TREE_TOKENS.radiusButton,
1592
+ padding: "12px 14px",
1593
+ background: "#fff",
1594
+ color: ctx.brand.palette.dark,
1595
+ outline: "none",
1596
+ ...typeStyle(AI_TREE_TOKENS.type.bodyM, ctx.brand.fonts.body)
1597
+ };
1598
+ const labelStyle = {
1599
+ ...typeStyle(AI_TREE_TOKENS.type.bodyMBold, ctx.brand.fonts.body),
1600
+ color: ctx.brand.palette.dark
1601
+ };
1602
+ return /* @__PURE__ */ jsx(
1603
+ "form",
1604
+ {
1605
+ ...formAttrs,
1606
+ "data-ai-form": "",
1607
+ style: {
1608
+ display: "grid",
1609
+ gridTemplateColumns: "repeat(2, minmax(0, 1fr))",
1610
+ columnGap: 64,
1611
+ rowGap: AI_TREE_TOKENS.spacing4
1612
+ },
1613
+ children: (node.children ?? []).map((child, i) => {
1614
+ if (child.type === "input") {
1615
+ const cs2 = child.slots ?? {};
1616
+ const kind = str(cs2.kind);
1617
+ const label = str(cs2.label);
1618
+ const placeholder = str(cs2.placeholder);
1619
+ const name = label.toLowerCase().replace(/[^a-z0-9]+/gu, "-").replace(/^-+|-+$/gu, "") || `field-${i}`;
1620
+ const isTextarea = kind === "textarea";
1621
+ return /* @__PURE__ */ jsxs(
1622
+ "div",
1623
+ {
1624
+ style: {
1625
+ display: "flex",
1626
+ flexDirection: "column",
1627
+ gap: 8,
1628
+ ...isTextarea ? { gridColumn: "1 / -1", maxWidth: 780 } : {}
1629
+ },
1630
+ children: [
1631
+ /* @__PURE__ */ jsx("label", { ...textAttrs(ctx, `${path}.c${i}.label`), style: labelStyle, children: label }),
1632
+ isTextarea ? /* @__PURE__ */ jsx(
1633
+ "textarea",
1634
+ {
1635
+ name,
1636
+ placeholder,
1637
+ style: { ...fieldStyle, height: 140, resize: "vertical" }
1638
+ }
1639
+ ) : /* @__PURE__ */ jsx(
1640
+ "input",
1641
+ {
1642
+ name,
1643
+ type: kind === "email" ? "email" : "text",
1644
+ placeholder,
1645
+ style: { ...fieldStyle, height: 48 }
1646
+ }
1647
+ )
1648
+ ]
1649
+ },
1650
+ i
1651
+ );
1652
+ }
1653
+ const cs = child.slots ?? {};
1654
+ return /* @__PURE__ */ jsx(
1655
+ "button",
1120
1656
  {
1657
+ type: "submit",
1121
1658
  style: {
1122
- border: `1px solid ${ctx.brand.palette.accent}`,
1659
+ gridColumn: "1 / -1",
1660
+ justifySelf: "start",
1661
+ border: "none",
1662
+ cursor: "pointer",
1663
+ padding: `${AI_TREE_TOKENS.spacing3}px ${AI_TREE_TOKENS.spacing6}px`,
1123
1664
  borderRadius: AI_TREE_TOKENS.radiusButton,
1124
- height: cs.kind === "textarea" ? 96 : 42
1125
- }
1126
- }
1127
- )
1128
- ] }, i);
1665
+ background: ctx.brand.palette.primary,
1666
+ color: AI_TREE_TOKENS.textPrimaryForeground,
1667
+ ...typeStyle(AI_TREE_TOKENS.type.button, ctx.brand.fonts.body)
1668
+ },
1669
+ children: /* @__PURE__ */ jsx("span", { ...textAttrs(ctx, `${path}.c${i}.label`), children: str(cs.label) })
1670
+ },
1671
+ i
1672
+ );
1673
+ })
1129
1674
  }
1130
- return /* @__PURE__ */ jsx("div", { children: /* @__PURE__ */ jsx(ButtonEl, { slots: child.slots ?? {}, ctx, path: `${path}.c${i}` }) }, i);
1131
- }) });
1675
+ );
1676
+ }
1132
1677
  case "schedule-widget":
1133
1678
  return /* @__PURE__ */ jsx(
1134
1679
  "div",
@@ -1154,11 +1699,14 @@ function AiTreeRenderer({ tree, brand, resolveMedia, editKeyPrefix }) {
1154
1699
  return null;
1155
1700
  }
1156
1701
  const resolvedBrand = brand ?? AI_DEFAULT_BRAND;
1702
+ const band = (tree.settings ?? {}).sectionBackground === "accent" ? accentBandContext(resolvedBrand) : null;
1703
+ const blockBrand = band?.brand ?? resolvedBrand;
1157
1704
  const ctx = {
1158
- brand: resolvedBrand,
1705
+ brand: blockBrand,
1159
1706
  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
1707
+ cardSurface: blockBrand.palette.light.toUpperCase() === AI_DEFAULT_BRAND.palette.light.toUpperCase() ? "#ECECEB" : `color-mix(in srgb, ${blockBrand.palette.light} 90%, ${blockBrand.palette.dark})`,
1708
+ keyFor: editKeyPrefix ? (path) => `${editKeyPrefix}.${path}` : null,
1709
+ ...band ? { buttonLabel: band.buttonLabel } : {}
1162
1710
  };
1163
1711
  const settings = tree.settings ?? {};
1164
1712
  const pad = AI_TREE_TOKENS.sectionPadding[settings.spacing ?? "balanced"];
@@ -1166,6 +1714,20 @@ function AiTreeRenderer({ tree, brand, resolveMedia, editKeyPrefix }) {
1166
1714
  const overlayAlpha = { low: 0.25, medium: 0.45, high: 0.65 }[settings.overlayIntensity ?? "medium"];
1167
1715
  const backgroundUrl = isOverlay && settings.backgroundMedia ? ctx.resolveMedia(settings.backgroundMedia) : null;
1168
1716
  const bgAttrs = ctx.keyFor && isOverlay && settings.backgroundMedia ? { "data-ohw-key": ctx.keyFor("settings.backgroundMedia"), "data-ohw-editable": "bg-image" } : {};
1717
+ const toneBackground = (() => {
1718
+ const { dark, primary, light } = resolvedBrand.palette;
1719
+ switch (settings.sectionBackground) {
1720
+ case "surface":
1721
+ return `color-mix(in srgb, ${light} 94%, ${dark})`;
1722
+ case "accent":
1723
+ return primary;
1724
+ case "accent-soft":
1725
+ return `color-mix(in srgb, ${primary} 12%, ${light})`;
1726
+ default:
1727
+ return void 0;
1728
+ }
1729
+ })();
1730
+ const distributed = !isOverlay && settings.textDistribution;
1169
1731
  return /* @__PURE__ */ jsxs(
1170
1732
  "section",
1171
1733
  {
@@ -1175,13 +1737,15 @@ function AiTreeRenderer({ tree, brand, resolveMedia, editKeyPrefix }) {
1175
1737
  style: {
1176
1738
  position: "relative",
1177
1739
  padding: `${pad}px 0`,
1178
- background: isOverlay && !backgroundUrl ? "#DBEAFE" : void 0,
1740
+ background: toneBackground ?? (isOverlay && !backgroundUrl ? "#DBEAFE" : void 0),
1179
1741
  backgroundImage: backgroundUrl ? `url(${backgroundUrl})` : void 0,
1180
1742
  backgroundSize: "cover",
1181
- backgroundPosition: "center"
1743
+ backgroundPosition: "center",
1744
+ color: settings.sectionBackground === "accent" ? blockBrand.palette.dark : void 0
1182
1745
  },
1183
1746
  children: [
1184
1747
  /* @__PURE__ */ jsx("style", { children: AI_RESPONSIVE_CSS }),
1748
+ /* @__PURE__ */ jsx("style", { children: AI_MOBILE_CSS }),
1185
1749
  isOverlay && backgroundUrl && /* @__PURE__ */ jsx("div", { style: { position: "absolute", inset: 0, background: `rgba(255,255,255,${overlayAlpha})` } }),
1186
1750
  /* @__PURE__ */ jsx(
1187
1751
  "div",
@@ -1202,10 +1766,24 @@ function AiTreeRenderer({ tree, brand, resolveMedia, editKeyPrefix }) {
1202
1766
  display: "grid",
1203
1767
  gridTemplateColumns: "repeat(12, 1fr)",
1204
1768
  gap: AI_TREE_TOKENS.spacing6,
1205
- alignItems: settings.verticalPosition === "top" ? "start" : "center",
1769
+ alignItems: distributed === "space-between" ? "stretch" : (distributed ?? settings.verticalPosition) === "top" ? "start" : "center",
1206
1770
  marginTop: r2 > 0 ? AI_TREE_TOKENS.spacing8 : 0
1207
1771
  },
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))
1772
+ children: (row.blocks ?? []).map((block, b) => /* @__PURE__ */ jsx(
1773
+ "div",
1774
+ {
1775
+ "data-ai-cell": "",
1776
+ style: {
1777
+ gridColumn: `span ${Math.min(12, Math.max(1, block.span))}`,
1778
+ minWidth: 0,
1779
+ // space-between: each column becomes a flex column whose content spreads over
1780
+ // the full row height instead of clumping at the top.
1781
+ ...distributed === "space-between" ? { display: "flex", flexDirection: "column", justifyContent: "space-between" } : {}
1782
+ },
1783
+ children: renderNode(block, ctx, `r${r2}.b${b}`)
1784
+ },
1785
+ b
1786
+ ))
1209
1787
  },
1210
1788
  r2
1211
1789
  ))
@@ -1221,17 +1799,36 @@ import { jsx as jsx2 } from "react/jsx-runtime";
1221
1799
  var CONTAINER_ATTR = "data-ohw-ai-generated";
1222
1800
  var REPLACED_ATTR = "data-ohw-ai-replaced-by";
1223
1801
  var REMOVED_ATTR = "data-ohw-ai-removed";
1802
+ var TEMPLATE_HIDDEN_ATTR = "data-ohw-ai-template-hidden";
1803
+ var CHROME_SECTION_IDS = /* @__PURE__ */ new Set(["navbar", "footer"]);
1804
+ function readRootVar(name) {
1805
+ if (typeof document === "undefined") return "";
1806
+ return getComputedStyle(document.documentElement).getPropertyValue(name).trim();
1807
+ }
1808
+ function deriveBrandOverride() {
1809
+ const dark = readRootVar("--ohw-brand-dark");
1810
+ const primary = readRootVar("--ohw-brand-primary");
1811
+ const light = readRootVar("--ohw-brand-light");
1812
+ if (!dark || !primary || !light) return null;
1813
+ const accent = readRootVar("--ohw-brand-accent");
1814
+ const heading = readRootVar("--font-heading") || readRootVar("--font-display");
1815
+ const body = readRootVar("--font-body");
1816
+ return {
1817
+ palette: { dark, primary, accent: accent || dark, light },
1818
+ fonts: {
1819
+ heading: heading || AI_DEFAULT_BRAND.fonts.heading,
1820
+ body: body || AI_DEFAULT_BRAND.fonts.body
1821
+ }
1822
+ };
1823
+ }
1224
1824
  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");
1825
+ const dark = readRootVar("--color-dark");
1826
+ const primary = readRootVar("--color-primary");
1827
+ const light = readRootVar("--color-light");
1231
1828
  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");
1829
+ const accent = readRootVar("--color-accent");
1830
+ const heading = readRootVar("--font-heading") || readRootVar("--font-display");
1831
+ const body = readRootVar("--font-body");
1235
1832
  return {
1236
1833
  palette: { dark, primary, accent: accent || dark, light },
1237
1834
  fonts: {
@@ -1303,6 +1900,24 @@ function syncRemovedSections(state) {
1303
1900
  }
1304
1901
  }
1305
1902
  }
1903
+ function syncTemplateHidden(state, pageHasSections) {
1904
+ const hide = state.hideTemplate === true && pageHasSections;
1905
+ for (const el of Array.from(document.querySelectorAll(`[${TEMPLATE_HIDDEN_ATTR}]`))) {
1906
+ if (!hide) {
1907
+ el.style.removeProperty("display");
1908
+ el.removeAttribute(TEMPLATE_HIDDEN_ATTR);
1909
+ }
1910
+ }
1911
+ if (!hide) return;
1912
+ for (const el of Array.from(document.querySelectorAll("[data-ohw-section]"))) {
1913
+ if (el.hasAttribute(CONTAINER_ATTR)) continue;
1914
+ if (CHROME_SECTION_IDS.has(el.getAttribute("data-ohw-section") ?? "")) continue;
1915
+ if (el.parentElement?.closest("[data-ohw-section]")) continue;
1916
+ if (el.hasAttribute(REPLACED_ATTR) || el.hasAttribute(REMOVED_ATTR)) continue;
1917
+ el.style.display = "none";
1918
+ el.setAttribute(TEMPLATE_HIDDEN_ATTR, "");
1919
+ }
1920
+ }
1306
1921
  function syncReplacedOriginals(state) {
1307
1922
  for (const el of document.querySelectorAll(`[${REPLACED_ATTR}]`)) {
1308
1923
  const byId = el.getAttribute(REPLACED_ATTR) ?? "";
@@ -1321,10 +1936,63 @@ function syncReplacedOriginals(state) {
1321
1936
  }
1322
1937
  }
1323
1938
  }
1939
+ var sectionOrderIndex = /* @__PURE__ */ new Map();
1940
+ function setAiSectionOrder(raw, currentPath) {
1941
+ const next = /* @__PURE__ */ new Map();
1942
+ if (raw) {
1943
+ try {
1944
+ const entries = JSON.parse(raw);
1945
+ for (const entry of entries) {
1946
+ if (!entry.pagePath || entry.pagePath === currentPath) next.set(entry.instanceId, entry.order);
1947
+ }
1948
+ } catch {
1949
+ }
1950
+ }
1951
+ sectionOrderIndex = next;
1952
+ }
1953
+ function applyExplicitOrder(entries) {
1954
+ if (sectionOrderIndex.size === 0) return entries;
1955
+ return entries.map((entry, index) => ({ entry, index, order: sectionOrderIndex.get(entry.id) })).sort((a, b) => {
1956
+ if (a.order === void 0 && b.order === void 0) return a.index - b.index;
1957
+ if (a.order === void 0) return 1;
1958
+ if (b.order === void 0) return -1;
1959
+ return a.order - b.order;
1960
+ }).map((item) => item.entry);
1961
+ }
1962
+ function orderByChain(sections) {
1963
+ const ids = new Set(sections.map((entry) => entry.id));
1964
+ const after = /* @__PURE__ */ new Map();
1965
+ const roots = [];
1966
+ for (const entry of sections) {
1967
+ const anchor = entry.replaces ?? entry.beforeSection ?? entry.afterSection ?? null;
1968
+ if (anchor && ids.has(anchor)) {
1969
+ const bucket = after.get(anchor);
1970
+ if (bucket) bucket.push(entry);
1971
+ else after.set(anchor, [entry]);
1972
+ } else {
1973
+ roots.push(entry);
1974
+ }
1975
+ }
1976
+ const out = [];
1977
+ const seen = /* @__PURE__ */ new Set();
1978
+ const visit = (entry) => {
1979
+ if (seen.has(entry.id)) return;
1980
+ seen.add(entry.id);
1981
+ out.push(entry);
1982
+ for (const child of after.get(entry.id) ?? []) visit(child);
1983
+ };
1984
+ for (const root of roots) visit(root);
1985
+ return out.length === sections.length ? out : sections;
1986
+ }
1324
1987
  function applyAiSectionsToDom(state, options) {
1325
1988
  if (typeof document === "undefined") return;
1989
+ const brandOverride = deriveBrandOverride();
1326
1990
  const templateBrand = deriveTemplateBrand();
1327
- const activeIds = new Set(state.sections.map((entry) => entry.id));
1991
+ const brandKey = brandOverride ? JSON.stringify(brandOverride) : "";
1992
+ const pagePath = window.location.pathname;
1993
+ const pageSections = state.sections.filter((entry) => (entry.path ?? "/") === pagePath);
1994
+ const activeIds = new Set(pageSections.map((entry) => entry.id));
1995
+ const ordered = state.hideTemplate === true ? applyExplicitOrder(orderByChain(pageSections)) : pageSections;
1328
1996
  for (const [id, section] of mounted) {
1329
1997
  if (!activeIds.has(id)) {
1330
1998
  section.root.unmount();
@@ -1332,8 +2000,8 @@ function applyAiSectionsToDom(state, options) {
1332
2000
  mounted.delete(id);
1333
2001
  }
1334
2002
  }
1335
- for (const entry of state.sections) {
1336
- const serialized = JSON.stringify(entry);
2003
+ for (const entry of ordered) {
2004
+ const serialized = JSON.stringify(entry) + brandKey;
1337
2005
  const existing = mounted.get(entry.id);
1338
2006
  if (existing && existing.serialized === serialized && existing.container.isConnected) {
1339
2007
  continue;
@@ -1347,6 +2015,7 @@ function applyAiSectionsToDom(state, options) {
1347
2015
  mounted.delete(entry.id);
1348
2016
  }
1349
2017
  container.setAttribute("data-ohw-section", entry.id);
2018
+ container.setAttribute("data-ohw-instance", entry.id);
1350
2019
  container.setAttribute("data-ohw-section-label", entry.label);
1351
2020
  placeContainer(container, entry);
1352
2021
  const root = mounted.get(entry.id)?.root ?? createRoot(container);
@@ -1357,7 +2026,7 @@ function applyAiSectionsToDom(state, options) {
1357
2026
  AiTreeRenderer,
1358
2027
  {
1359
2028
  tree: entry.tree,
1360
- brand: entry.brand ?? options?.brand ?? templateBrand ?? void 0,
2029
+ brand: brandOverride ?? entry.brand ?? options?.brand ?? templateBrand ?? void 0,
1361
2030
  resolveMedia,
1362
2031
  editKeyPrefix: `ai.${entry.id}`
1363
2032
  }
@@ -1366,8 +2035,20 @@ function applyAiSectionsToDom(state, options) {
1366
2035
  });
1367
2036
  mounted.set(entry.id, { root, container, serialized });
1368
2037
  }
2038
+ if (state.hideTemplate === true) {
2039
+ let prev = null;
2040
+ for (const entry of ordered) {
2041
+ const el = mounted.get(entry.id)?.container;
2042
+ if (!el) continue;
2043
+ if (prev && !(prev.compareDocumentPosition(el) & Node.DOCUMENT_POSITION_FOLLOWING)) {
2044
+ prev.insertAdjacentElement("afterend", el);
2045
+ }
2046
+ prev = el;
2047
+ }
2048
+ }
1369
2049
  syncReplacedOriginals(state);
1370
2050
  syncRemovedSections(state);
2051
+ syncTemplateHidden(state, pageSections.length > 0);
1371
2052
  }
1372
2053
 
1373
2054
  // src/useLinkHrefGuardian.ts
@@ -1974,7 +2655,7 @@ function SchedulingWidget({ notifyOnConnect = false, initialScheduleId, insertAf
1974
2655
  const autoId = useId();
1975
2656
  const insertAfter = insertAfterProp ?? autoId;
1976
2657
  const [schedule, setSchedule] = useState2(null);
1977
- const [loading, setLoading] = useState2(true);
2658
+ const [loading, setLoading] = useState2(initialScheduleId !== null);
1978
2659
  const [inEditor, setInEditor] = useState2(false);
1979
2660
  const [isHovered, setIsHovered] = useState2(false);
1980
2661
  const [modalState, setModalState] = useState2(null);
@@ -2148,8 +2829,10 @@ function SchedulingWidget({ notifyOnConnect = false, initialScheduleId, insertAf
2148
2829
  "*"
2149
2830
  );
2150
2831
  };
2151
- if (!inEditor && !loading && !schedule) return null;
2152
2832
  const sectionId = `scheduling-${insertAfter}`;
2833
+ if (!inEditor && !loading && !schedule) {
2834
+ return /* @__PURE__ */ jsx4("section", { "data-ohw-section": sectionId, "data-ohw-scheduling-anchor": insertAfter, style: { display: "none" } });
2835
+ }
2153
2836
  return /* @__PURE__ */ jsxs3(
2154
2837
  "section",
2155
2838
  {
@@ -7066,13 +7749,17 @@ function MediaOverlay({
7066
7749
  hover,
7067
7750
  isUploading,
7068
7751
  fadingOut = false,
7752
+ selected = false,
7753
+ hovered = false,
7069
7754
  onFadeOutComplete,
7070
7755
  onReplace,
7756
+ onSelect,
7071
7757
  onVideoSettingsChange
7072
7758
  }) {
7073
7759
  const { rect } = hover;
7074
7760
  const skeletonRef = React8.useRef(null);
7075
7761
  const isVideo = hover.elementType === "video";
7762
+ const showChrome = !selected || hovered;
7076
7763
  const autoplay = hover.videoAutoplay ?? true;
7077
7764
  const muted = hover.videoMuted ?? true;
7078
7765
  const probeRef = React8.useRef(null);
@@ -7086,6 +7773,7 @@ function MediaOverlay({
7086
7773
  (prev) => prev && prev.fullWidth === width && prev.height === height ? prev : { fullWidth: width, height }
7087
7774
  );
7088
7775
  }, [isVideo]);
7776
+ const replaceLabel = isVideo ? "Replace video" : hover.elementType === "bg-image" ? "Replace background" : "Replace image";
7089
7777
  const replaceMode = buttonSize ? replaceButtonMode({ width: rect.width, height: rect.height }, buttonSize) : "none";
7090
7778
  const box = {
7091
7779
  position: "fixed",
@@ -7119,7 +7807,7 @@ function MediaOverlay({
7119
7807
  }
7120
7808
  );
7121
7809
  }
7122
- const settingsBar = isVideo && !hover.isDragOver ? /* @__PURE__ */ jsxs7(
7810
+ const settingsBar = isVideo && !hover.isDragOver && showChrome ? /* @__PURE__ */ jsxs7(
7123
7811
  "div",
7124
7812
  {
7125
7813
  "data-ohw-bridge": "",
@@ -7189,10 +7877,12 @@ function MediaOverlay({
7189
7877
  // in-document, pointer-events does it natively. The button below opts back in, so
7190
7878
  // Replace still works.
7191
7879
  pointerEvents: hover.hasTextOverlap ? "none" : "auto",
7192
- boxShadow: "inset 0 0 0 1.5px var(--color-primary)",
7193
- background: "color-mix(in srgb, var(--color-primary) 20%, transparent)"
7880
+ // Selected: a firm component ring with no wash, so the image reads as chosen rather
7881
+ // than hovered. Hover keeps the existing tinted preview.
7882
+ boxShadow: selected ? "inset 0 0 0 2px var(--color-primary)" : "inset 0 0 0 1.5px var(--color-primary)",
7883
+ background: selected ? "transparent" : "color-mix(in srgb, var(--color-primary) 20%, transparent)"
7194
7884
  },
7195
- onClick: () => onReplace(hover.key),
7885
+ onClick: () => onSelect && !selected ? onSelect(hover.key) : onReplace(hover.key),
7196
7886
  children: [
7197
7887
  /* @__PURE__ */ jsxs7(
7198
7888
  Button,
@@ -7213,17 +7903,17 @@ function MediaOverlay({
7213
7903
  },
7214
7904
  children: [
7215
7905
  isVideo ? /* @__PURE__ */ jsx15(Film, { size: 14 }) : /* @__PURE__ */ jsx15(ImageIcon, { size: 14 }),
7216
- isVideo ? "Replace video" : "Replace image"
7906
+ replaceLabel
7217
7907
  ]
7218
7908
  }
7219
7909
  ),
7220
- replaceMode === "none" ? null : /* @__PURE__ */ jsxs7(
7910
+ showChrome && replaceMode !== "none" && /* @__PURE__ */ jsxs7(
7221
7911
  Button,
7222
7912
  {
7223
7913
  "data-ohw-media-overlay": "",
7224
7914
  variant: "outline",
7225
7915
  size: "sm",
7226
- "aria-label": isVideo ? "Replace video" : "Replace image",
7916
+ "aria-label": replaceLabel,
7227
7917
  className: "gap-1.5 cursor-pointer hover:bg-background",
7228
7918
  style: {
7229
7919
  ...OVERLAY_BUTTON_STYLE,
@@ -7246,7 +7936,7 @@ function MediaOverlay({
7246
7936
  },
7247
7937
  children: [
7248
7938
  isVideo ? /* @__PURE__ */ jsx15(Film, { size: 14 }) : /* @__PURE__ */ jsx15(ImageIcon, { size: 14 }),
7249
- replaceMode === "full" ? isVideo ? "Replace video" : "Replace image" : null
7939
+ replaceMode === "full" ? replaceLabel : null
7250
7940
  ]
7251
7941
  }
7252
7942
  )
@@ -7317,6 +8007,9 @@ import { Check, X } from "lucide-react";
7317
8007
 
7318
8008
  // src/lib/sections.ts
7319
8009
  var LINK_PICKER_EXCLUDED_IDS = /* @__PURE__ */ new Set(["navbar", "footer"]);
8010
+ function isChromeSection(el) {
8011
+ return el.matches("header, nav, footer, aside");
8012
+ }
7320
8013
  function titleCaseSectionId(id) {
7321
8014
  return id.split("-").filter(Boolean).map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join(" ");
7322
8015
  }
@@ -7327,6 +8020,8 @@ function parseSectionsFromRoot(root) {
7327
8020
  const id = el.getAttribute("data-ohw-section") ?? "";
7328
8021
  if (!id || seen.has(id) || LINK_PICKER_EXCLUDED_IDS.has(id)) continue;
7329
8022
  if (el.parentElement?.closest("[data-ohw-section]")) continue;
8023
+ if (el.hasAttribute("data-ohw-ai-template-hidden") || el.hasAttribute("data-ohw-ai-removed") || el.hasAttribute("data-ohw-ai-replaced-by"))
8024
+ continue;
7330
8025
  seen.add(id);
7331
8026
  const label = el.getAttribute("data-ohw-section-label") ?? titleCaseSectionId(id);
7332
8027
  sections.push({ id, label });
@@ -7342,15 +8037,163 @@ function parseSectionsFromHtml(html) {
7342
8037
  return parseSectionsFromRoot(doc);
7343
8038
  }
7344
8039
 
7345
- // src/ui/ai-section/AiSectionOverlay.tsx
7346
- import { Fragment as Fragment5, jsx as jsx17, jsxs as jsxs9 } from "react/jsx-runtime";
7347
- function readRect(sectionId) {
7348
- const el = document.querySelector(`[data-ohw-section="${CSS.escape(sectionId)}"]`);
7349
- if (!el) return null;
7350
- const r2 = el.getBoundingClientRect();
7351
- if (r2.width <= 0 || r2.height <= 0) return null;
7352
- return { top: r2.top, left: r2.left, width: r2.width, height: r2.height };
7353
- }
8040
+ // src/lib/section-instances.ts
8041
+ var SECTION_ORDER_KEY = "__ohw_section_order";
8042
+ var REMOVED_ATTR2 = "data-ohw-section-removed";
8043
+ function isRemovedSection(el) {
8044
+ return el.hasAttribute(REMOVED_ATTR2);
8045
+ }
8046
+ function topLevelSections() {
8047
+ return Array.from(document.querySelectorAll("[data-ohw-section]")).filter(
8048
+ (el) => !el.parentElement?.closest("[data-ohw-section]") && !isChromeSection(el) && !isRemovedSection(el)
8049
+ );
8050
+ }
8051
+ function instanceIdOf(el) {
8052
+ return el.dataset.ohwInstance ?? el.dataset.ohwSection ?? "";
8053
+ }
8054
+ function planSectionMove(instanceId, targetIndex, currentPath) {
8055
+ const sections = topLevelSections();
8056
+ const index = sections.findIndex((el) => instanceIdOf(el) === instanceId);
8057
+ if (index === -1) return null;
8058
+ const dragged = sections[index];
8059
+ const others = sections.filter((_, i) => i !== index);
8060
+ const clamped = Math.max(0, Math.min(targetIndex, others.length));
8061
+ const reordered = [...others.slice(0, clamped), dragged, ...others.slice(clamped)];
8062
+ return reordered.map((el, order) => ({
8063
+ instanceId: instanceIdOf(el),
8064
+ type: el.getAttribute("data-ohw-section") ?? "",
8065
+ order,
8066
+ pagePath: currentPath
8067
+ }));
8068
+ }
8069
+ function moveSectionInstance(instanceId, direction, currentPath) {
8070
+ const sections = topLevelSections();
8071
+ const index = sections.findIndex((el) => instanceIdOf(el) === instanceId);
8072
+ if (index === -1) return null;
8073
+ const siblingIndex = direction === "up" ? index - 1 : index + 1;
8074
+ if (siblingIndex < 0 || siblingIndex >= sections.length) return null;
8075
+ const entries = planSectionMove(instanceId, siblingIndex, currentPath);
8076
+ if (!entries) return null;
8077
+ applyPersistedOrder(entries);
8078
+ return entries;
8079
+ }
8080
+ function syncRemovedFlags(entries) {
8081
+ const removedIds = new Set(entries.filter((e) => e.removed).map((e) => e.instanceId));
8082
+ document.querySelectorAll(`[${REMOVED_ATTR2}]`).forEach((el) => {
8083
+ if (!removedIds.has(instanceIdOf(el))) {
8084
+ el.style.removeProperty("display");
8085
+ el.removeAttribute(REMOVED_ATTR2);
8086
+ }
8087
+ });
8088
+ for (const id of removedIds) {
8089
+ const el = document.querySelector(`[data-ohw-instance="${CSS.escape(id)}"]`);
8090
+ if (el) {
8091
+ el.style.display = "none";
8092
+ el.setAttribute(REMOVED_ATTR2, "");
8093
+ }
8094
+ }
8095
+ }
8096
+ function applyPersistedOrder(entries) {
8097
+ syncRemovedFlags(entries);
8098
+ if (entries.length === 0) return;
8099
+ const sections = topLevelSections();
8100
+ if (sections.length === 0) return;
8101
+ const orderIndex = new Map(entries.map((e) => [e.instanceId, e.order]));
8102
+ const ordered = [...sections].sort((a, b) => {
8103
+ const aOrder = orderIndex.get(instanceIdOf(a));
8104
+ const bOrder = orderIndex.get(instanceIdOf(b));
8105
+ if (aOrder === void 0 && bOrder === void 0) return 0;
8106
+ if (aOrder === void 0) return 1;
8107
+ if (bOrder === void 0) return -1;
8108
+ return aOrder - bOrder;
8109
+ });
8110
+ let prev = null;
8111
+ for (const el of ordered) {
8112
+ if (prev) prev.after(el);
8113
+ prev = el;
8114
+ }
8115
+ }
8116
+ function setSectionRemoved(instanceId, currentPath, existingEntries, removed) {
8117
+ if (!document.querySelector(`[data-ohw-instance="${CSS.escape(instanceId)}"]`)) return null;
8118
+ const byId = new Map(existingEntries.map((e) => [e.instanceId, e]));
8119
+ const allSections = Array.from(document.querySelectorAll("[data-ohw-section]")).filter(
8120
+ (el) => !el.parentElement?.closest("[data-ohw-section]") && !isChromeSection(el)
8121
+ );
8122
+ allSections.forEach((el, order) => {
8123
+ const id = instanceIdOf(el);
8124
+ if (!byId.has(id)) {
8125
+ byId.set(id, { instanceId: id, type: el.getAttribute("data-ohw-section") ?? "", order, pagePath: currentPath });
8126
+ }
8127
+ });
8128
+ const target = byId.get(instanceId);
8129
+ if (!target) return null;
8130
+ byId.set(instanceId, { ...target, removed });
8131
+ const entries = Array.from(byId.values());
8132
+ applyPersistedOrder(entries);
8133
+ return entries;
8134
+ }
8135
+ function deleteSectionInstance(instanceId, currentPath, existingEntries) {
8136
+ return setSectionRemoved(instanceId, currentPath, existingEntries, true);
8137
+ }
8138
+ function restoreSectionInstance(instanceId, currentPath, existingEntries) {
8139
+ return setSectionRemoved(instanceId, currentPath, existingEntries, false);
8140
+ }
8141
+ function newInstanceId() {
8142
+ return typeof crypto !== "undefined" && "randomUUID" in crypto ? crypto.randomUUID() : `instance-${Date.now()}-${Math.random().toString(36).slice(2)}`;
8143
+ }
8144
+ function getPageSectionOrderEntries(raw, currentPath) {
8145
+ if (!raw) return [];
8146
+ try {
8147
+ const entries = JSON.parse(raw);
8148
+ return entries.filter((e) => !e.pagePath || e.pagePath === currentPath);
8149
+ } catch {
8150
+ return [];
8151
+ }
8152
+ }
8153
+ function rekeySectionSubtree(root, instanceId) {
8154
+ const suffix = `::${instanceId}`;
8155
+ const rekey = (el, attr) => {
8156
+ const current = el.getAttribute(attr);
8157
+ if (current) el.setAttribute(attr, `${current}${suffix}`);
8158
+ };
8159
+ if (root.hasAttribute("data-ohw-key")) rekey(root, "data-ohw-key");
8160
+ if (root.hasAttribute("data-ohw-href-key")) rekey(root, "data-ohw-href-key");
8161
+ root.querySelectorAll("[data-ohw-key]").forEach((el) => rekey(el, "data-ohw-key"));
8162
+ root.querySelectorAll("[data-ohw-href-key]").forEach((el) => rekey(el, "data-ohw-href-key"));
8163
+ }
8164
+ function initSectionInstancesFromContent(content, currentPath) {
8165
+ document.querySelectorAll("[data-ohw-section]:not([data-ohw-instance])").forEach((el) => {
8166
+ el.setAttribute("data-ohw-instance", el.getAttribute("data-ohw-section") ?? "");
8167
+ });
8168
+ const entries = getPageSectionOrderEntries(content[SECTION_ORDER_KEY], currentPath);
8169
+ for (const entry of entries) {
8170
+ if (entry.instanceId === entry.type) continue;
8171
+ if (document.querySelector(`[data-ohw-instance="${CSS.escape(entry.instanceId)}"]`)) continue;
8172
+ const original = document.querySelector(
8173
+ `[data-ohw-section="${CSS.escape(entry.type)}"][data-ohw-instance="${CSS.escape(entry.type)}"]`
8174
+ );
8175
+ if (!original) continue;
8176
+ const clone = original.cloneNode(true);
8177
+ clone.setAttribute("data-ohw-instance", entry.instanceId);
8178
+ rekeySectionSubtree(clone, entry.instanceId);
8179
+ original.insertAdjacentElement("afterend", clone);
8180
+ }
8181
+ applyPersistedOrder(entries);
8182
+ }
8183
+
8184
+ // src/ui/ai-section/AiSectionOverlay.tsx
8185
+ import { Fragment as Fragment5, jsx as jsx17, jsxs as jsxs9 } from "react/jsx-runtime";
8186
+ function findSectionElement(instanceId) {
8187
+ const escaped = CSS.escape(instanceId);
8188
+ return document.querySelector(`[data-ohw-instance="${escaped}"]`) ?? document.querySelector(`[data-ohw-section="${escaped}"]:not([data-ohw-instance])`);
8189
+ }
8190
+ function readRect(instanceId) {
8191
+ const el = findSectionElement(instanceId);
8192
+ if (!el) return null;
8193
+ const r2 = el.getBoundingClientRect();
8194
+ if (r2.width <= 0 || r2.height <= 0) return null;
8195
+ return { top: r2.top, left: r2.left, width: r2.width, height: r2.height };
8196
+ }
7354
8197
  function useLiveSectionRect(sectionId) {
7355
8198
  const [rect, setRect] = useState5(null);
7356
8199
  useEffect4(() => {
@@ -7368,7 +8211,7 @@ function useLiveSectionRect(sectionId) {
7368
8211
  const opts = { capture: true, passive: true };
7369
8212
  window.addEventListener("scroll", update, opts);
7370
8213
  window.addEventListener("resize", update);
7371
- const el = document.querySelector(`[data-ohw-section="${CSS.escape(sectionId)}"]`);
8214
+ const el = findSectionElement(sectionId);
7372
8215
  const ro = el ? new ResizeObserver(update) : null;
7373
8216
  if (el && ro) ro.observe(el);
7374
8217
  const interval = setInterval(update, 500);
@@ -7381,6 +8224,12 @@ function useLiveSectionRect(sectionId) {
7381
8224
  }, [sectionId]);
7382
8225
  return rect;
7383
8226
  }
8227
+ function computeSectionBoundaryFlags(instanceId) {
8228
+ const topLevel = topLevelSections();
8229
+ const index = topLevel.findIndex((el) => (el.dataset.ohwInstance ?? el.dataset.ohwSection) === instanceId);
8230
+ if (index === -1) return { isFirst: true, isLast: true };
8231
+ return { isFirst: index === 0, isLast: index === topLevel.length - 1 };
8232
+ }
7384
8233
  var PRIMARY2 = "#0885FE";
7385
8234
  function edgeAwareRadius(rect) {
7386
8235
  const container = window.innerWidth <= 480 ? 16 : 24;
@@ -7454,6 +8303,7 @@ function AiSectionOverlay({
7454
8303
  }) {
7455
8304
  const [selectedId, setSelectedId] = useState5(null);
7456
8305
  const [reviewId, setReviewId] = useState5(null);
8306
+ const [reviewButtonsHidden, setReviewButtonsHidden] = useState5(false);
7457
8307
  const reviewIdRef = useRef4(null);
7458
8308
  reviewIdRef.current = reviewId;
7459
8309
  const selectedIdRef = useRef4(null);
@@ -7462,7 +8312,7 @@ function AiSectionOverlay({
7462
8312
  (el) => {
7463
8313
  postToParent2({
7464
8314
  type: "ow:section-selected",
7465
- sectionId: el?.dataset.ohwSection ?? null,
8315
+ sectionId: el ? el.dataset.ohwInstance ?? el.dataset.ohwSection ?? null : null,
7466
8316
  sectionLabel: el ? el.dataset.ohwSectionLabel ?? titleCaseSectionId(el.dataset.ohwSection ?? "") : null
7467
8317
  });
7468
8318
  },
@@ -7471,7 +8321,7 @@ function AiSectionOverlay({
7471
8321
  const selectFromElement = useCallback2(
7472
8322
  (el, options) => {
7473
8323
  const sectionEl = el?.closest("[data-ohw-section]") ?? null;
7474
- const id = sectionEl?.dataset.ohwSection ?? null;
8324
+ const id = sectionEl ? sectionEl.dataset.ohwInstance ?? sectionEl.dataset.ohwSection ?? null : null;
7475
8325
  if (id === selectedIdRef.current) return;
7476
8326
  setSelectedId(id);
7477
8327
  if (options?.report !== false) report(sectionEl);
@@ -7492,12 +8342,15 @@ function AiSectionOverlay({
7492
8342
  selectFromElement(sectionEl);
7493
8343
  return sectionEl != null;
7494
8344
  },
7495
- clear: () => setSelectedId(null)
8345
+ clear: () => {
8346
+ setSelectedId(null);
8347
+ report(null);
8348
+ }
7496
8349
  };
7497
8350
  return () => {
7498
8351
  apiRef.current = null;
7499
8352
  };
7500
- }, [apiRef, selectFromElement]);
8353
+ }, [apiRef, selectFromElement, report]);
7501
8354
  useEffect4(() => {
7502
8355
  const onMessage = (e) => {
7503
8356
  if (e.data?.type === "ow:ai-select" && e.data.sectionId === null) {
@@ -7512,9 +8365,10 @@ function AiSectionOverlay({
7512
8365
  }
7513
8366
  const found = readRect(sectionId) != null;
7514
8367
  setReviewId(found ? sectionId : null);
8368
+ setReviewButtonsHidden(e.data.hideButtons === true);
7515
8369
  postToParent2({ type: "ow:ai-review-started", sectionId, found });
7516
8370
  if (found) {
7517
- document.querySelector(`[data-ohw-section="${CSS.escape(sectionId)}"]`)?.scrollIntoView({ behavior: "smooth", block: "nearest" });
8371
+ document.querySelector(`[data-ohw-instance="${CSS.escape(sectionId)}"]`)?.scrollIntoView({ behavior: "smooth", block: "nearest" });
7518
8372
  }
7519
8373
  }
7520
8374
  };
@@ -7533,7 +8387,7 @@ function AiSectionOverlay({
7533
8387
  return;
7534
8388
  }
7535
8389
  const sec = t.closest("[data-ohw-section]");
7536
- setHoveredId(sec?.dataset.ohwSection ?? null);
8390
+ setHoveredId(sec ? sec.dataset.ohwInstance ?? sec.dataset.ohwSection ?? null : null);
7537
8391
  };
7538
8392
  const onLeave = () => setHoveredId(null);
7539
8393
  document.addEventListener("mousemove", onMove, { passive: true });
@@ -7565,9 +8419,30 @@ function AiSectionOverlay({
7565
8419
  },
7566
8420
  [postToParent2]
7567
8421
  );
7568
- const selectionRect = useLiveSectionRect(reviewId ? null : selectedId);
8422
+ const activeSelectionId = reviewId ? null : selectedId;
8423
+ const selectionRect = useLiveSectionRect(activeSelectionId);
7569
8424
  const reviewRect = useLiveSectionRect(reviewId);
7570
8425
  const hoverRect = useLiveSectionRect(reviewId || hoveredId === selectedId ? null : hoveredId);
8426
+ useEffect4(() => {
8427
+ const selectedEl = activeSelectionId ? findSectionElement(activeSelectionId) : null;
8428
+ if (!activeSelectionId || !selectionRect || selectedEl && isChromeSection(selectedEl)) {
8429
+ postToParent2({ type: "ow:section-rect", instanceId: null, rect: null });
8430
+ return;
8431
+ }
8432
+ const { isFirst, isLast } = computeSectionBoundaryFlags(activeSelectionId);
8433
+ postToParent2({
8434
+ type: "ow:section-rect",
8435
+ instanceId: activeSelectionId,
8436
+ rect: {
8437
+ top: selectionRect.top + window.scrollY,
8438
+ left: selectionRect.left + window.scrollX,
8439
+ width: selectionRect.width,
8440
+ height: selectionRect.height
8441
+ },
8442
+ isFirst,
8443
+ isLast
8444
+ });
8445
+ }, [activeSelectionId, selectionRect, postToParent2]);
7571
8446
  return /* @__PURE__ */ jsxs9(Fragment5, { children: [
7572
8447
  hoverRect && /* @__PURE__ */ jsx17(
7573
8448
  "div",
@@ -7618,13 +8493,16 @@ function AiSectionOverlay({
7618
8493
  border: `2px solid ${PRIMARY2}`,
7619
8494
  borderRadius: edgeAwareRadius(reviewRect),
7620
8495
  zIndex: 2147483200,
7621
- // The veil itself: swallows clicks so the section stays locked until decided.
8496
+ // The veil itself: swallows clicks so the section stays locked until decided. This
8497
+ // stopPropagation only guards the bubble phase; the bridge's capture-phase click
8498
+ // handlers additionally bail on `[data-ohw-ai-review]`, without which a click on
8499
+ // Accept/Discard resolves to the media beneath and opens the file picker.
7622
8500
  background: "rgba(8, 133, 254, 0.04)",
7623
8501
  pointerEvents: "auto",
7624
8502
  cursor: "default"
7625
8503
  },
7626
8504
  onClick: (e) => e.stopPropagation(),
7627
- children: /* @__PURE__ */ jsxs9(
8505
+ children: !reviewButtonsHidden && /* @__PURE__ */ jsxs9(
7628
8506
  "div",
7629
8507
  {
7630
8508
  style: {
@@ -7647,47 +8525,6 @@ function AiSectionOverlay({
7647
8525
  ] });
7648
8526
  }
7649
8527
 
7650
- // src/lib/section-instances.ts
7651
- var SECTION_ORDER_KEY = "__ohw_section_order";
7652
- function getPageSectionOrderEntries(raw, currentPath) {
7653
- if (!raw) return [];
7654
- try {
7655
- const entries = JSON.parse(raw);
7656
- return entries.filter((e) => !e.pagePath || e.pagePath === currentPath);
7657
- } catch {
7658
- return [];
7659
- }
7660
- }
7661
- function rekeySectionSubtree(root, instanceId) {
7662
- const suffix = `::${instanceId}`;
7663
- const rekey = (el, attr) => {
7664
- const current = el.getAttribute(attr);
7665
- if (current) el.setAttribute(attr, `${current}${suffix}`);
7666
- };
7667
- if (root.hasAttribute("data-ohw-key")) rekey(root, "data-ohw-key");
7668
- if (root.hasAttribute("data-ohw-href-key")) rekey(root, "data-ohw-href-key");
7669
- root.querySelectorAll("[data-ohw-key]").forEach((el) => rekey(el, "data-ohw-key"));
7670
- root.querySelectorAll("[data-ohw-href-key]").forEach((el) => rekey(el, "data-ohw-href-key"));
7671
- }
7672
- function initSectionInstancesFromContent(content, currentPath) {
7673
- document.querySelectorAll("[data-ohw-section]:not([data-ohw-instance])").forEach((el) => {
7674
- el.setAttribute("data-ohw-instance", el.getAttribute("data-ohw-section") ?? "");
7675
- });
7676
- const entries = getPageSectionOrderEntries(content[SECTION_ORDER_KEY], currentPath);
7677
- for (const entry of entries) {
7678
- if (entry.instanceId === entry.type) continue;
7679
- if (document.querySelector(`[data-ohw-instance="${CSS.escape(entry.instanceId)}"]`)) continue;
7680
- const original = document.querySelector(
7681
- `[data-ohw-section="${CSS.escape(entry.type)}"][data-ohw-instance="${CSS.escape(entry.type)}"]`
7682
- );
7683
- if (!original) continue;
7684
- const clone = original.cloneNode(true);
7685
- clone.setAttribute("data-ohw-instance", entry.instanceId);
7686
- rekeySectionSubtree(clone, entry.instanceId);
7687
- original.insertAdjacentElement("afterend", clone);
7688
- }
7689
- }
7690
-
7691
8528
  // src/OhhwellsBridge.tsx
7692
8529
  import { createPortal as createPortal2 } from "react-dom";
7693
8530
  import { usePathname as usePathname2, useRouter as useRouter3, useSearchParams } from "next/navigation";
@@ -10232,8 +11069,13 @@ function referenceBox(slot) {
10232
11069
  const neighbour = row ? Array.from(row.querySelectorAll('[data-ohw-editable="icon"]')).find(
10233
11070
  (el) => el !== slot && !el.hasAttribute("data-ohw-social-icon-placeholder")
10234
11071
  ) : null;
10235
- const source = (neighbour ?? slot).querySelector(GLYPH_SELECTOR);
10236
- const box = source?.getBoundingClientRect() ?? null;
11072
+ if (neighbour) {
11073
+ const box2 = neighbour.querySelector(GLYPH_SELECTOR)?.getBoundingClientRect();
11074
+ if (box2?.width && box2.height) return box2;
11075
+ }
11076
+ const own = slot.getBoundingClientRect();
11077
+ if (own.width && own.height) return own;
11078
+ const box = slot.querySelector(GLYPH_SELECTOR)?.getBoundingClientRect() ?? null;
10237
11079
  return box?.width && box.height ? box : null;
10238
11080
  }
10239
11081
  function iconMarkupSizedFor(slot, markup) {
@@ -12034,6 +12876,7 @@ function readLogoSizeState(content, placement) {
12034
12876
  function getLogoElement(el) {
12035
12877
  const marked = el.closest('[data-ohw-role="logo"], [data-ohw-logo]');
12036
12878
  if (marked) return marked;
12879
+ if (el.closest('[data-ohw-editable="icon"]')) return null;
12037
12880
  const root = el.closest("nav, [data-ohw-nav-root], footer");
12038
12881
  if (!root) return null;
12039
12882
  const anchor = el.closest("a");
@@ -12913,6 +13756,334 @@ function useNavItemDrag({
12913
13756
  };
12914
13757
  }
12915
13758
 
13759
+ // src/useSectionDrag.ts
13760
+ import { useCallback as useCallback7, useEffect as useEffect11, useRef as useRef9, useState as useState11 } from "react";
13761
+
13762
+ // src/lib/section-dnd.ts
13763
+ function isFooterSection(el) {
13764
+ return el.dataset.ohwSection === "footer";
13765
+ }
13766
+ function buildSectionDropSlots(draggedInstanceId) {
13767
+ const sections = topLevelSections().filter(
13768
+ (el) => instanceIdOf(el) !== draggedInstanceId && !isFooterSection(el)
13769
+ );
13770
+ const slots = [];
13771
+ if (sections.length === 0) return slots;
13772
+ const left = 0;
13773
+ const width = document.documentElement.clientWidth;
13774
+ for (let i = 0; i <= sections.length; i++) {
13775
+ let y;
13776
+ if (i === 0) {
13777
+ y = sections[0].getBoundingClientRect().top;
13778
+ } else if (i === sections.length) {
13779
+ y = sections[sections.length - 1].getBoundingClientRect().bottom;
13780
+ } else {
13781
+ const prev = sections[i - 1].getBoundingClientRect();
13782
+ const next = sections[i].getBoundingClientRect();
13783
+ y = (prev.bottom + next.top) / 2;
13784
+ }
13785
+ slots.push({ insertIndex: i, y, left, width });
13786
+ }
13787
+ return slots;
13788
+ }
13789
+ function hitTestSectionDropSlot(y, slots) {
13790
+ let best = null;
13791
+ for (const slot of slots) {
13792
+ const dist = Math.abs(y - slot.y);
13793
+ if (!best || dist < best.dist) best = { slot, dist };
13794
+ }
13795
+ return best?.slot ?? null;
13796
+ }
13797
+
13798
+ // src/useSectionDrag.ts
13799
+ var PRESS_THRESHOLD = 10;
13800
+ var EDGE_ZONE = 60;
13801
+ var MAX_AUTO_SCROLL_SPEED = 18;
13802
+ var SECTION_DRAG_EXCLUDED_SELECTOR = [
13803
+ "[data-ohw-toolbar]",
13804
+ "[data-ohw-edit-chrome]",
13805
+ "[data-ohw-item-interaction]",
13806
+ "[data-ohw-drag-handle-container]",
13807
+ '[data-slot="drag-handle"]',
13808
+ "[data-ohw-item-toolbar-anchor]",
13809
+ "[data-ohw-item-drag-surface]",
13810
+ "[data-ohw-more-menu]",
13811
+ '[data-slot="dropdown-menu-content"]',
13812
+ '[data-slot="dropdown-menu-item"]',
13813
+ "[data-ohw-state-toggle]",
13814
+ "[data-ohw-max-badge]",
13815
+ "[data-ohw-floating-panel]",
13816
+ "[data-ohw-section-picker]",
13817
+ "[data-ohw-link-popover-root]",
13818
+ "[data-ohw-link-modal-root]",
13819
+ "[data-ohw-link-page-dropdown]",
13820
+ '[data-slot="popover-content"]',
13821
+ '[data-slot="dialog-content"]',
13822
+ '[data-slot="dialog-overlay"]',
13823
+ "[data-ohw-ai-review]",
13824
+ "[data-ohw-editable]",
13825
+ "[data-ohw-editable-state]",
13826
+ "[contenteditable]",
13827
+ "[data-ohw-href-key]",
13828
+ "[data-ohw-footer-col]",
13829
+ "[data-ohw-social-label]",
13830
+ "a",
13831
+ "button",
13832
+ '[role="button"]',
13833
+ '[data-ohw-role="navbar-button"]',
13834
+ '[data-ohw-role="button"]',
13835
+ "[data-ohw-carousel]",
13836
+ "[data-ohw-carousel-value]",
13837
+ "[data-ohw-carousel-slide]",
13838
+ "[data-ohw-carousel-overlay]",
13839
+ "[data-ohw-media-chrome]",
13840
+ "[data-ohw-media-overlay]",
13841
+ "[data-ohw-media-skeleton]"
13842
+ ].join(", ");
13843
+ function visibleClip(ps) {
13844
+ if (!ps) return null;
13845
+ const top = Math.max(0, ps.headerH - ps.iframeOffsetTop);
13846
+ const bottom = Math.min(window.innerHeight, ps.headerH + ps.canvasH - ps.iframeOffsetTop);
13847
+ return { top, bottom: Math.max(top, bottom) };
13848
+ }
13849
+ function useSectionDrag({
13850
+ isEditMode,
13851
+ editContentRef,
13852
+ postToParentRef,
13853
+ parentScrollRef,
13854
+ navDragRef,
13855
+ footerDragRef,
13856
+ suppressNextClickRef,
13857
+ suppressClickUntilRef
13858
+ }) {
13859
+ const sectionDragRef = useRef9(null);
13860
+ const [sectionDropSlots, setSectionDropSlots] = useState11([]);
13861
+ const [activeSectionDropIndex, setActiveSectionDropIndex] = useState11(null);
13862
+ const [isSectionDragging, setIsSectionDragging] = useState11(false);
13863
+ const sectionPointerDragRef = useRef9(null);
13864
+ const autoScrollRafRef = useRef9(null);
13865
+ const autoScrollDeltaRef = useRef9(0);
13866
+ const stopAutoScroll = useCallback7(() => {
13867
+ if (autoScrollRafRef.current != null) {
13868
+ cancelAnimationFrame(autoScrollRafRef.current);
13869
+ autoScrollRafRef.current = null;
13870
+ }
13871
+ autoScrollDeltaRef.current = 0;
13872
+ }, []);
13873
+ const tickAutoScroll = useCallback7(() => {
13874
+ if (!sectionDragRef.current) {
13875
+ stopAutoScroll();
13876
+ return;
13877
+ }
13878
+ if (autoScrollDeltaRef.current !== 0) {
13879
+ postToParentRef.current({ type: "ow:request-scroll", deltaY: autoScrollDeltaRef.current });
13880
+ }
13881
+ autoScrollRafRef.current = requestAnimationFrame(tickAutoScroll);
13882
+ }, [postToParentRef, stopAutoScroll]);
13883
+ const updateAutoScroll = useCallback7(
13884
+ (clientY) => {
13885
+ const clip = visibleClip(parentScrollRef.current);
13886
+ let delta = 0;
13887
+ if (clip) {
13888
+ const distTop = clientY - clip.top;
13889
+ const distBottom = clip.bottom - clientY;
13890
+ if (distTop >= 0 && distTop < EDGE_ZONE) {
13891
+ delta = -MAX_AUTO_SCROLL_SPEED * (1 - distTop / EDGE_ZONE);
13892
+ } else if (distBottom >= 0 && distBottom < EDGE_ZONE) {
13893
+ delta = MAX_AUTO_SCROLL_SPEED * (1 - distBottom / EDGE_ZONE);
13894
+ }
13895
+ }
13896
+ autoScrollDeltaRef.current = delta;
13897
+ if (delta !== 0 && autoScrollRafRef.current == null) {
13898
+ autoScrollRafRef.current = requestAnimationFrame(tickAutoScroll);
13899
+ } else if (delta === 0) {
13900
+ stopAutoScroll();
13901
+ }
13902
+ },
13903
+ [parentScrollRef, stopAutoScroll, tickAutoScroll]
13904
+ );
13905
+ const clearSectionDragVisuals = useCallback7(() => {
13906
+ sectionDragRef.current?.draggedEl.removeAttribute("data-ohw-section-dragging");
13907
+ sectionDragRef.current = null;
13908
+ setSectionDropSlots([]);
13909
+ setActiveSectionDropIndex(null);
13910
+ setIsSectionDragging(false);
13911
+ stopAutoScroll();
13912
+ document.documentElement.removeAttribute("data-ohw-section-dragging-root");
13913
+ unlockItemDragInteraction();
13914
+ }, [stopAutoScroll]);
13915
+ const refreshSectionDragVisuals = useCallback7(
13916
+ (session, clientX, clientY) => {
13917
+ session.lastClientX = clientX;
13918
+ session.lastClientY = clientY;
13919
+ const slots = buildSectionDropSlots(session.instanceId);
13920
+ const activeSlot = hitTestSectionDropSlot(clientY, slots);
13921
+ session.activeSlot = activeSlot;
13922
+ setSectionDropSlots(slots);
13923
+ const activeIdx = activeSlot ? slots.findIndex((s) => s.insertIndex === activeSlot.insertIndex) : -1;
13924
+ setActiveSectionDropIndex(activeIdx >= 0 ? activeIdx : null);
13925
+ updateAutoScroll(clientY);
13926
+ },
13927
+ [updateAutoScroll]
13928
+ );
13929
+ const beginSectionDrag = useCallback7(
13930
+ (session) => {
13931
+ sectionDragRef.current = session;
13932
+ setIsSectionDragging(true);
13933
+ lockItemDuringDrag();
13934
+ document.documentElement.setAttribute("data-ohw-section-dragging-root", "");
13935
+ session.draggedEl.setAttribute("data-ohw-section-dragging", "");
13936
+ refreshSectionDragVisuals(session, session.lastClientX, session.lastClientY);
13937
+ },
13938
+ [refreshSectionDragVisuals]
13939
+ );
13940
+ const commitSectionDrag = useCallback7(() => {
13941
+ const session = sectionDragRef.current;
13942
+ if (!session) {
13943
+ clearSectionDragVisuals();
13944
+ return;
13945
+ }
13946
+ const slot = session.activeSlot ?? hitTestSectionDropSlot(session.lastClientY, buildSectionDropSlots(session.instanceId));
13947
+ const entries = slot ? planSectionMove(session.instanceId, slot.insertIndex, window.location.pathname) : null;
13948
+ if (!entries) {
13949
+ clearSectionDragVisuals();
13950
+ return;
13951
+ }
13952
+ const orderJson = JSON.stringify(entries);
13953
+ editContentRef.current = { ...editContentRef.current, [SECTION_ORDER_KEY]: orderJson };
13954
+ setAiSectionOrder(orderJson, window.location.pathname);
13955
+ postToParentRef.current({ type: "ow:change", nodes: [{ key: SECTION_ORDER_KEY, text: orderJson }] });
13956
+ applyPersistedOrder(entries);
13957
+ clearSectionDragVisuals();
13958
+ requestAnimationFrame(() => {
13959
+ if (editContentRef.current[SECTION_ORDER_KEY] === orderJson) {
13960
+ applyPersistedOrder(entries);
13961
+ }
13962
+ requestAnimationFrame(() => {
13963
+ window.dispatchEvent(new Event("resize"));
13964
+ });
13965
+ });
13966
+ }, [clearSectionDragVisuals, editContentRef, postToParentRef]);
13967
+ const startSectionPressDrag = useCallback7(
13968
+ (el, clientX, clientY, pointerId) => {
13969
+ if (navDragRef.current || footerDragRef.current || sectionDragRef.current) return false;
13970
+ const instanceId = instanceIdOf(el);
13971
+ if (!instanceId) return false;
13972
+ sectionPointerDragRef.current = {
13973
+ el,
13974
+ instanceId,
13975
+ startX: clientX,
13976
+ startY: clientY,
13977
+ pointerId,
13978
+ started: false
13979
+ };
13980
+ return true;
13981
+ },
13982
+ [footerDragRef, navDragRef]
13983
+ );
13984
+ useEffect11(() => {
13985
+ if (!isEditMode) return;
13986
+ const onPointerDown = (e) => {
13987
+ if (e.button !== 0) return;
13988
+ if (navDragRef.current || footerDragRef.current || sectionDragRef.current) return;
13989
+ if (sectionPointerDragRef.current) return;
13990
+ const target = e.target;
13991
+ if (!(target instanceof HTMLElement)) return;
13992
+ if (target.closest(SECTION_DRAG_EXCLUDED_SELECTOR)) return;
13993
+ const sectionEl = target.closest("[data-ohw-section]");
13994
+ if (!sectionEl || isChromeSection(sectionEl) || sectionEl.dataset.ohwSection === "footer") return;
13995
+ if (!topLevelSections().includes(sectionEl)) return;
13996
+ startSectionPressDrag(sectionEl, e.clientX, e.clientY, e.pointerId);
13997
+ };
13998
+ const onPointerMove = (e) => {
13999
+ const pending = sectionPointerDragRef.current;
14000
+ if (!pending) return;
14001
+ if (pending.started) {
14002
+ e.preventDefault();
14003
+ clearTextSelection();
14004
+ const session = sectionDragRef.current;
14005
+ if (!session) return;
14006
+ refreshSectionDragVisuals(session, e.clientX, e.clientY);
14007
+ return;
14008
+ }
14009
+ const dx = e.clientX - pending.startX;
14010
+ const dy = e.clientY - pending.startY;
14011
+ if (dx * dx + dy * dy < PRESS_THRESHOLD * PRESS_THRESHOLD) return;
14012
+ e.preventDefault();
14013
+ pending.started = true;
14014
+ armItemPressDrag();
14015
+ clearTextSelection();
14016
+ try {
14017
+ document.body.setPointerCapture(pending.pointerId);
14018
+ } catch {
14019
+ }
14020
+ beginSectionDrag({
14021
+ instanceId: pending.instanceId,
14022
+ draggedEl: pending.el,
14023
+ lastClientX: e.clientX,
14024
+ lastClientY: e.clientY,
14025
+ activeSlot: null
14026
+ });
14027
+ };
14028
+ const endPointerDrag = (e) => {
14029
+ const pending = sectionPointerDragRef.current;
14030
+ sectionPointerDragRef.current = null;
14031
+ try {
14032
+ if (document.body.hasPointerCapture(e.pointerId)) {
14033
+ document.body.releasePointerCapture(e.pointerId);
14034
+ }
14035
+ } catch {
14036
+ }
14037
+ if (!pending) return;
14038
+ if (!pending.started) {
14039
+ unlockItemDragInteraction();
14040
+ return;
14041
+ }
14042
+ suppressNextClickRef.current = true;
14043
+ suppressClickUntilRef.current = Date.now() + 500;
14044
+ commitSectionDrag();
14045
+ };
14046
+ const onKeyDown = (e) => {
14047
+ if (e.key !== "Escape") return;
14048
+ if (!sectionDragRef.current && !sectionPointerDragRef.current) return;
14049
+ sectionPointerDragRef.current = null;
14050
+ clearSectionDragVisuals();
14051
+ };
14052
+ document.addEventListener("pointerdown", onPointerDown, true);
14053
+ document.addEventListener("pointermove", onPointerMove, true);
14054
+ document.addEventListener("pointerup", endPointerDrag, true);
14055
+ document.addEventListener("pointercancel", endPointerDrag, true);
14056
+ document.addEventListener("keydown", onKeyDown, true);
14057
+ return () => {
14058
+ document.removeEventListener("pointerdown", onPointerDown, true);
14059
+ document.removeEventListener("pointermove", onPointerMove, true);
14060
+ document.removeEventListener("pointerup", endPointerDrag, true);
14061
+ document.removeEventListener("pointercancel", endPointerDrag, true);
14062
+ document.removeEventListener("keydown", onKeyDown, true);
14063
+ unlockItemDragInteraction();
14064
+ stopAutoScroll();
14065
+ };
14066
+ }, [
14067
+ beginSectionDrag,
14068
+ clearSectionDragVisuals,
14069
+ commitSectionDrag,
14070
+ footerDragRef,
14071
+ isEditMode,
14072
+ navDragRef,
14073
+ refreshSectionDragVisuals,
14074
+ startSectionPressDrag,
14075
+ stopAutoScroll,
14076
+ suppressClickUntilRef,
14077
+ suppressNextClickRef
14078
+ ]);
14079
+ return {
14080
+ sectionDragRef,
14081
+ sectionDropSlots,
14082
+ activeSectionDropIndex,
14083
+ isSectionDragging
14084
+ };
14085
+ }
14086
+
12916
14087
  // src/ui/footer-container-chrome.tsx
12917
14088
  import { Plus as Plus2 } from "lucide-react";
12918
14089
  import { jsx as jsx29, jsxs as jsxs19 } from "react/jsx-runtime";
@@ -12965,7 +14136,7 @@ function FooterContainerChrome({
12965
14136
  }
12966
14137
 
12967
14138
  // src/lib/carousel.ts
12968
- import { useEffect as useEffect11, useState as useState11 } from "react";
14139
+ import { useEffect as useEffect12, useState as useState12 } from "react";
12969
14140
  var CAROUSEL_ATTR = "data-ohw-carousel";
12970
14141
  var CAROUSEL_VALUE_ATTR = "data-ohw-carousel-value";
12971
14142
  var CAROUSEL_SLIDE_ATTR = "data-ohw-carousel-slide";
@@ -13027,8 +14198,8 @@ function applyCarouselNode(key, val) {
13027
14198
  return true;
13028
14199
  }
13029
14200
  function useOhwCarousel(key, initial) {
13030
- const [images, setImages] = useState11(initial);
13031
- useEffect11(() => {
14201
+ const [images, setImages] = useState12(initial);
14202
+ useEffect12(() => {
13032
14203
  const el = document.querySelector(
13033
14204
  `[${CAROUSEL_ATTR}][data-ohw-key="${CSS.escape(key)}"]`
13034
14205
  );
@@ -13110,6 +14281,7 @@ function collectEditableNodes(extraContent, root = document) {
13110
14281
  NAV_ORDER_KEY,
13111
14282
  FOOTER_ORDER_KEY,
13112
14283
  NAV_COUNT_KEY,
14284
+ SECTION_ORDER_KEY,
13113
14285
  // A socials row's order and its icons-vs-words setting live under keys no element carries,
13114
14286
  // so collecting the DOM alone left them behind: the draft knew the row was showing icons and
13115
14287
  // had gained an item, and the published page went back to the template's own (OHH-736).
@@ -13573,6 +14745,7 @@ function fadeInImageElement(img, onReady) {
13573
14745
  function applyEditableImageSrc(img, url) {
13574
14746
  img.removeAttribute("srcset");
13575
14747
  img.removeAttribute("sizes");
14748
+ if (img.loading === "lazy") img.loading = "eager";
13576
14749
  img.src = url;
13577
14750
  }
13578
14751
  function fadeInBgImage(el, url, onReady) {
@@ -13637,21 +14810,10 @@ function parseSchedulingInsertAfter(insertAfter) {
13637
14810
  insertBefore: insertAfter.slice(idx + INSERT_BEFORE_MARKER.length)
13638
14811
  };
13639
14812
  }
13640
- function resolveSchedulingInsert(insertAfter, explicitInsertBefore) {
13641
- const parsed = parseSchedulingInsertAfter(insertAfter);
13642
- const insertBefore = explicitInsertBefore ?? parsed.insertBefore;
13643
- const effectiveInsertAfter = insertBefore && parsed.insertBefore === null ? `${insertAfter}${INSERT_BEFORE_MARKER}${insertBefore}` : insertAfter;
13644
- return { effectiveInsertAfter, insertBefore };
13645
- }
13646
- function getSchedulingMountPoint(insertAfter) {
13647
- const { anchor } = parseSchedulingInsertAfter(insertAfter);
13648
- let anchorEl = document.querySelector(`[data-ohw-section="${anchor}"]`);
13649
- if (!anchorEl && anchor === "scheduling") {
13650
- const widgets = Array.from(document.querySelectorAll('[data-ohw-section^="scheduling-"]'));
13651
- anchorEl = widgets.at(-1) ?? null;
13652
- }
13653
- if (!anchorEl) return null;
13654
- return anchorEl.closest('[data-ohw-section-container="scheduling"]') ?? anchorEl;
14813
+ function resolveEntryAnchor(entry) {
14814
+ if (entry.anchorId !== void 0) return { anchorId: entry.anchorId, beforeId: entry.beforeId ?? null };
14815
+ const parsed = parseSchedulingInsertAfter(entry.insertAfter);
14816
+ return { anchorId: parsed.anchor, beforeId: parsed.insertBefore };
13655
14817
  }
13656
14818
  function schedulingMountDepth(insertAfter) {
13657
14819
  if (!insertAfter.includes(INSERT_BEFORE_MARKER)) return 0;
@@ -13668,8 +14830,7 @@ function getPageSchedulingEntries(raw) {
13668
14830
  }
13669
14831
  }
13670
14832
  function isSchedulingWidgetMissing(entry) {
13671
- const { effectiveInsertAfter } = resolveSchedulingInsert(entry.insertAfter);
13672
- return !document.querySelector(`[data-ohw-section="${schedulingSectionId(effectiveInsertAfter)}"]`);
14833
+ return !document.querySelector(`[data-ohw-section="${schedulingSectionId(entry.insertAfter)}"]`);
13673
14834
  }
13674
14835
  function hasMissingSchedulingWidgets(entries) {
13675
14836
  return entries.some(isSchedulingWidgetMissing);
@@ -13699,16 +14860,17 @@ function initSectionsFromContent(content, removeExisting = false) {
13699
14860
  } catch {
13700
14861
  }
13701
14862
  }
13702
- function mountSchedulingWidget(insertAfter, notifyOnConnect = false, scheduleId, explicitInsertBefore) {
13703
- const { effectiveInsertAfter, insertBefore } = resolveSchedulingInsert(insertAfter, explicitInsertBefore);
13704
- const sectionId = schedulingSectionId(effectiveInsertAfter);
14863
+ function mountSchedulingWidget(anchorId, notifyOnConnect = false, scheduleId, beforeId, existingWidgetId) {
14864
+ const widgetId = existingWidgetId ?? (beforeId ? `${anchorId}${INSERT_BEFORE_MARKER}${beforeId}` : anchorId);
14865
+ const sectionId = schedulingSectionId(widgetId);
13705
14866
  if (document.querySelector(`[data-ohw-section="${sectionId}"]`)) return false;
13706
- const mountPoint = getSchedulingMountPoint(effectiveInsertAfter);
13707
- if (!mountPoint) return false;
14867
+ const anchorEl = document.querySelector(`[data-ohw-section="${anchorId}"]`);
14868
+ if (!anchorEl) return false;
14869
+ const mountPoint = anchorEl.closest('[data-ohw-section-container="scheduling"]') ?? anchorEl;
13708
14870
  const container = document.createElement("div");
13709
14871
  container.dataset.ohwSectionContainer = "scheduling";
13710
- if (insertBefore) {
13711
- const beforeAnchor = document.querySelector(`[data-ohw-section="${insertBefore}"]`);
14872
+ if (beforeId) {
14873
+ const beforeAnchor = document.querySelector(`[data-ohw-section="${beforeId}"]`);
13712
14874
  const beforePoint = beforeAnchor?.closest('[data-ohw-section-container="scheduling"]') ?? beforeAnchor;
13713
14875
  if (!beforePoint) return false;
13714
14876
  beforePoint.insertAdjacentElement("beforebegin", container);
@@ -13719,19 +14881,25 @@ function mountSchedulingWidget(insertAfter, notifyOnConnect = false, scheduleId,
13719
14881
  }
13720
14882
  tail.insertAdjacentElement("afterend", container);
13721
14883
  }
13722
- const root = createRoot2(container);
13723
- flushSync2(() => {
13724
- root.render(
13725
- /* @__PURE__ */ jsx33(
13726
- SchedulingWidget,
13727
- {
13728
- notifyOnConnect,
13729
- initialScheduleId: scheduleId,
13730
- insertAfter: effectiveInsertAfter
13731
- }
13732
- )
13733
- );
13734
- });
14884
+ try {
14885
+ const root = createRoot2(container);
14886
+ flushSync2(() => {
14887
+ root.render(
14888
+ /* @__PURE__ */ jsx33(
14889
+ SchedulingWidget,
14890
+ {
14891
+ notifyOnConnect,
14892
+ initialScheduleId: scheduleId,
14893
+ insertAfter: widgetId
14894
+ }
14895
+ )
14896
+ );
14897
+ });
14898
+ } catch (err) {
14899
+ console.error("[ow:scheduling] render threw", err);
14900
+ container.remove();
14901
+ return false;
14902
+ }
13735
14903
  const tracker = getSectionsTracker();
13736
14904
  let sections = [];
13737
14905
  try {
@@ -13739,10 +14907,12 @@ function mountSchedulingWidget(insertAfter, notifyOnConnect = false, scheduleId,
13739
14907
  } catch {
13740
14908
  }
13741
14909
  const inEditor = typeof window !== "undefined" && window.self !== window.top;
13742
- if (inEditor && !sections.find((s) => s.type === "scheduling" && s.insertAfter === effectiveInsertAfter)) {
14910
+ if (inEditor && !sections.find((s) => s.type === "scheduling" && s.insertAfter === widgetId)) {
13743
14911
  sections.push({
13744
14912
  type: "scheduling",
13745
- insertAfter: effectiveInsertAfter,
14913
+ insertAfter: widgetId,
14914
+ anchorId,
14915
+ beforeId: beforeId ?? null,
13746
14916
  pagePath: window.location.pathname,
13747
14917
  ...scheduleId ? { scheduleId } : {}
13748
14918
  });
@@ -13756,7 +14926,8 @@ function mountSchedulingEntries(entries, notifyOnConnect = false) {
13756
14926
  for (let i = pending.length - 1; i >= 0; i--) {
13757
14927
  const entry = pending[i];
13758
14928
  const shouldNotify = typeof notifyOnConnect === "function" ? notifyOnConnect(entry) : notifyOnConnect;
13759
- if (mountSchedulingWidget(entry.insertAfter, shouldNotify, entry.scheduleId)) {
14929
+ const { anchorId, beforeId } = resolveEntryAnchor(entry);
14930
+ if (mountSchedulingWidget(anchorId, shouldNotify, entry.scheduleId ?? null, beforeId, entry.insertAfter)) {
13760
14931
  pending.splice(i, 1);
13761
14932
  }
13762
14933
  }
@@ -13914,6 +15085,11 @@ function applyLinkByKey(key, val) {
13914
15085
  hrefAnchors.forEach((el) => applyLinkHref(el, val));
13915
15086
  }
13916
15087
  }
15088
+ function isInsideLinkEditor(target) {
15089
+ return Boolean(
15090
+ 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"]')
15091
+ );
15092
+ }
13917
15093
  function isInsideFloatingPanel(target) {
13918
15094
  return Boolean(target.closest("[data-ohw-floating-panel]"));
13919
15095
  }
@@ -13921,11 +15097,6 @@ function isPointOverFloatingPanel(clientX, clientY) {
13921
15097
  const el = document.elementFromPoint(clientX, clientY);
13922
15098
  return Boolean(el instanceof Element && el.closest("[data-ohw-floating-panel]"));
13923
15099
  }
13924
- function isInsideLinkEditor(target) {
13925
- return Boolean(
13926
- 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"]')
13927
- );
13928
- }
13929
15100
  function getHrefKeyFromElement(el) {
13930
15101
  if (!el) return null;
13931
15102
  const anchor = el.closest("[data-ohw-href-key]");
@@ -14184,7 +15355,7 @@ function getNavigationSelectionParent(el) {
14184
15355
  if (!isFooterLinksContainer(el) && (el.hasAttribute("data-ohw-footer-col") || el.hasAttribute("data-ohw-footer-column") || isInferredFooterGroup2(el))) {
14185
15356
  return getFooterLinksContainer();
14186
15357
  }
14187
- if (el.hasAttribute("data-ohw-nav-container") || el.hasAttribute("data-ohw-nav-drawer") || isFooterLinksContainer(el)) {
15358
+ 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)) {
14188
15359
  return getNavigationRoot(el);
14189
15360
  }
14190
15361
  return null;
@@ -14399,7 +15570,6 @@ var ICONS = {
14399
15570
  insertUnorderedList: '<line x1="8" y1="6" x2="21" y2="6"/><line x1="8" y1="12" x2="21" y2="12"/><line x1="8" y1="18" x2="21" y2="18"/><line x1="3" y1="6" x2="3.01" y2="6"/><line x1="3" y1="12" x2="3.01" y2="12"/><line x1="3" y1="18" x2="3.01" y2="18"/>',
14400
15571
  insertOrderedList: '<line x1="10" y1="6" x2="21" y2="6"/><line x1="10" y1="12" x2="21" y2="12"/><line x1="10" y1="18" x2="21" y2="18"/><path d="M4 6h1v4"/><path d="M4 10h2"/><path d="M6 18H4c0-1 2-2 2-3s-1-1.5-2-1"/>'
14401
15572
  };
14402
- var HEIGHT_SETTLE_DELAYS = [150, 400, 800];
14403
15573
  var SELECTION_CHROME_GAP2 = 4;
14404
15574
  var TOOLBAR_STROKE_GAP2 = 4;
14405
15575
  var TOOLBAR_OFFSET_FROM_ELEMENT = SELECTION_CHROME_GAP2 + TOOLBAR_STROKE_GAP2;
@@ -14779,6 +15949,45 @@ function StateToggle({
14779
15949
  );
14780
15950
  }
14781
15951
  var contentCache = /* @__PURE__ */ new Map();
15952
+ var fetchedContentPaths = /* @__PURE__ */ new Set();
15953
+ var OHW_LOADER_STYLE = {
15954
+ position: "fixed",
15955
+ inset: 0,
15956
+ background: "#fff",
15957
+ zIndex: 2147483646,
15958
+ display: "flex",
15959
+ alignItems: "center",
15960
+ justifyContent: "center"
15961
+ };
15962
+ function OhwLoaderSpinner() {
15963
+ return /* @__PURE__ */ jsxs20("svg", { width: "28", height: "28", viewBox: "0 0 28 28", fill: "none", "aria-hidden": true, children: [
15964
+ /* @__PURE__ */ jsx33("circle", { cx: "14", cy: "14", r: "11", stroke: "#E7E5E4", strokeWidth: "3" }),
15965
+ /* @__PURE__ */ jsx33(
15966
+ "circle",
15967
+ {
15968
+ cx: "14",
15969
+ cy: "14",
15970
+ r: "11",
15971
+ stroke: "#1C1917",
15972
+ strokeWidth: "3",
15973
+ strokeDasharray: "17 52",
15974
+ strokeLinecap: "round",
15975
+ children: /* @__PURE__ */ jsx33(
15976
+ "animateTransform",
15977
+ {
15978
+ attributeName: "transform",
15979
+ type: "rotate",
15980
+ from: "0 14 14",
15981
+ to: "360 14 14",
15982
+ dur: "0.7s",
15983
+ repeatCount: "indefinite"
15984
+ }
15985
+ )
15986
+ }
15987
+ )
15988
+ ] });
15989
+ }
15990
+ 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){}})();`;
14782
15991
  function resolveSubdomain(subdomainFromQuery) {
14783
15992
  if (subdomainFromQuery) return subdomainFromQuery;
14784
15993
  if (typeof window !== "undefined") {
@@ -14801,8 +16010,8 @@ function OhhwellsBridge() {
14801
16010
  const router = useRouter3();
14802
16011
  const searchParams = useSearchParams();
14803
16012
  const isEditMode = isEditSessionActive();
14804
- const [bridgeRoot, setBridgeRoot] = useState12(null);
14805
- useEffect12(() => {
16013
+ const [bridgeRoot, setBridgeRoot] = useState13(null);
16014
+ useEffect13(() => {
14806
16015
  const figtreeFontId = "ohw-figtree-font";
14807
16016
  if (!document.getElementById(figtreeFontId)) {
14808
16017
  const preconnect1 = Object.assign(document.createElement("link"), { rel: "preconnect", href: "https://fonts.googleapis.com" });
@@ -14831,82 +16040,146 @@ function OhhwellsBridge() {
14831
16040
  const subdomain = resolveSubdomain(subdomainFromQuery);
14832
16041
  useLinkHrefGuardian(pathname, subdomain, isEditMode);
14833
16042
  useSavedLinkNavigation(isEditMode);
14834
- const postToParent2 = useCallback7((data) => {
16043
+ const postToParent2 = useCallback8((data) => {
14835
16044
  if (typeof window !== "undefined" && window.parent !== window) {
14836
16045
  window.parent.postMessage(data, "*");
14837
16046
  }
14838
16047
  }, []);
14839
- const [fetchState, setFetchState] = useState12("idle");
14840
- const autoSaveTimers = useRef9(/* @__PURE__ */ new Map());
14841
- const activeElRef = useRef9(null);
14842
- const pointerHeldRef = useRef9(false);
14843
- const selectedElRef = useRef9(null);
14844
- const selectedHrefKeyRef = useRef9(null);
14845
- const selectedFooterColAttrRef = useRef9(null);
14846
- const originalContentRef = useRef9(null);
14847
- const activeStateElRef = useRef9(null);
14848
- const parentScrollRef = useRef9(null);
14849
- const visibleViewportRef = useRef9(null);
14850
- const [dialogPortalContainer, setDialogPortalContainer] = useState12(null);
14851
- const attachVisibleViewport = useCallback7((node) => {
16048
+ const [fetchState, setFetchState] = useState13("idle");
16049
+ const autoSaveTimers = useRef10(/* @__PURE__ */ new Map());
16050
+ const activeElRef = useRef10(null);
16051
+ const pointerHeldRef = useRef10(false);
16052
+ const selectedElRef = useRef10(null);
16053
+ const selectedHrefKeyRef = useRef10(null);
16054
+ const selectedFooterColAttrRef = useRef10(null);
16055
+ const originalContentRef = useRef10(null);
16056
+ const activeStateElRef = useRef10(null);
16057
+ const parentScrollRef = useRef10(null);
16058
+ const visibleViewportRef = useRef10(null);
16059
+ const [dialogPortalContainer, setDialogPortalContainer] = useState13(null);
16060
+ const attachVisibleViewport = useCallback8((node) => {
14852
16061
  visibleViewportRef.current = node;
14853
16062
  setDialogPortalContainer(node);
14854
16063
  if (node) applyVisibleViewport(node, parentScrollRef.current);
14855
16064
  }, []);
14856
- const toolbarElRef = useRef9(null);
14857
- const glowElRef = useRef9(null);
14858
- const hoveredImageRef = useRef9(null);
14859
- const hoveredImageHasTextOverlapRef = useRef9(false);
14860
- const dragOverElRef = useRef9(null);
14861
- const [mediaHover, setMediaHover] = useState12(null);
14862
- const [carouselHover, setCarouselHover] = useState12(null);
14863
- const [uploadingRects, setUploadingRects] = useState12({});
14864
- const hoveredGapRef = useRef9(null);
14865
- const imageUnhoverTimerRef = useRef9(null);
14866
- const imageShowTimerRef = useRef9(null);
14867
- const editStylesRef = useRef9(null);
14868
- const activateRef = useRef9(() => {
16065
+ const toolbarElRef = useRef10(null);
16066
+ const glowElRef = useRef10(null);
16067
+ const hoveredImageRef = useRef10(null);
16068
+ const hoveredImageHasTextOverlapRef = useRef10(false);
16069
+ const dragOverElRef = useRef10(null);
16070
+ const [mediaHover, setMediaHover] = useState13(null);
16071
+ const [selectedMedia, setSelectedMedia] = useState13(null);
16072
+ const selectedMediaElRef = useRef10(null);
16073
+ const clearMediaSelection = useCallback8(() => {
16074
+ const prev = selectedMediaElRef.current;
16075
+ selectedMediaElRef.current = null;
16076
+ setSelectedMedia(null);
16077
+ const sectionEl = prev?.closest("[data-ohw-section]") ?? null;
16078
+ if (sectionEl) {
16079
+ postToParentRef.current({
16080
+ type: "ow:section-selected",
16081
+ sectionId: sectionEl.dataset.ohwSection ?? null,
16082
+ sectionLabel: sectionEl.dataset.ohwSectionLabel ?? titleCaseSectionId(sectionEl.dataset.ohwSection ?? ""),
16083
+ key: null
16084
+ });
16085
+ }
16086
+ }, []);
16087
+ const clearMediaSelectionRef = useRef10(clearMediaSelection);
16088
+ clearMediaSelectionRef.current = clearMediaSelection;
16089
+ const selectMediaElement = useCallback8((el) => {
16090
+ const r2 = el.getBoundingClientRect();
16091
+ const video = el.dataset.ohwEditable === "video" ? el.querySelector("video") : null;
16092
+ selectedMediaElRef.current = el;
16093
+ setSelectedMedia({
16094
+ key: el.dataset.ohwKey ?? "",
16095
+ rect: { top: r2.top, left: r2.left, width: r2.width, height: r2.height },
16096
+ elementType: el.dataset.ohwEditable ?? "image",
16097
+ hasTextOverlap: false,
16098
+ isDragOver: false,
16099
+ ...video ? { videoAutoplay: video.autoplay, videoMuted: video.muted } : {}
16100
+ });
16101
+ const sectionEl = el.closest("[data-ohw-section]");
16102
+ aiSectionApiRef.current?.selectFromElement(el, { report: false });
16103
+ postToParentRef.current({
16104
+ type: "ow:section-selected",
16105
+ sectionId: sectionEl?.dataset.ohwSection ?? null,
16106
+ sectionLabel: sectionEl ? sectionEl.dataset.ohwSectionLabel ?? titleCaseSectionId(sectionEl.dataset.ohwSection ?? "") : null,
16107
+ key: el.dataset.ohwKey ?? null,
16108
+ // Display name for the pill — the raw key prettifies into fragments ("Img"); the
16109
+ // bridge knows what the node IS, so it names it.
16110
+ keyLabel: el.dataset.ohwEditable === "video" ? "Video" : el.dataset.ohwEditable === "bg-image" ? "Background" : "Image"
16111
+ });
16112
+ }, []);
16113
+ const selectMediaElementRef = useRef10(selectMediaElement);
16114
+ selectMediaElementRef.current = selectMediaElement;
16115
+ useEffect13(() => {
16116
+ if (!selectedMedia) return;
16117
+ const update = () => {
16118
+ const el = selectedMediaElRef.current;
16119
+ if (!el || !el.isConnected) {
16120
+ clearMediaSelection();
16121
+ return;
16122
+ }
16123
+ const r2 = el.getBoundingClientRect();
16124
+ setSelectedMedia(
16125
+ (prev) => prev ? { ...prev, rect: { top: r2.top, left: r2.left, width: r2.width, height: r2.height } } : prev
16126
+ );
16127
+ };
16128
+ window.addEventListener("scroll", update, true);
16129
+ window.addEventListener("resize", update);
16130
+ return () => {
16131
+ window.removeEventListener("scroll", update, true);
16132
+ window.removeEventListener("resize", update);
16133
+ };
16134
+ }, [selectedMedia !== null]);
16135
+ const [carouselHover, setCarouselHover] = useState13(null);
16136
+ const [uploadingRects, setUploadingRects] = useState13({});
16137
+ const hoveredGapRef = useRef10(null);
16138
+ const imageUnhoverTimerRef = useRef10(null);
16139
+ const imageShowTimerRef = useRef10(null);
16140
+ const editStylesRef = useRef10(null);
16141
+ const activateRef = useRef10(() => {
14869
16142
  });
14870
- const deactivateRef = useRef9(() => {
16143
+ const deactivateRef = useRef10(() => {
14871
16144
  });
14872
- const selectRef = useRef9(() => {
16145
+ const selectRef = useRef10(() => {
14873
16146
  });
14874
- const selectFrameRef = useRef9(() => {
16147
+ const selectFrameRef = useRef10(() => {
14875
16148
  });
14876
- const selectLogoRef = useRef9(() => {
16149
+ const selectLogoRef = useRef10(() => {
14877
16150
  });
14878
- const openLogoSizePanelRef = useRef9(() => {
16151
+ const openLogoSizePanelRef = useRef10(() => {
14879
16152
  });
14880
- const deselectRef = useRef9(() => {
16153
+ const deselectRef = useRef10(() => {
14881
16154
  });
14882
- const closeFloatingPanelOnlyRef = useRef9(() => {
16155
+ const closeFloatingPanelOnlyRef = useRef10(() => {
14883
16156
  });
14884
- const reselectNavigationItemRef = useRef9(() => {
16157
+ const reselectNavigationItemRef = useRef10(() => {
14885
16158
  });
14886
- const commitNavigationTextEditRef = useRef9(() => {
16159
+ const commitNavigationTextEditRef = useRef10(() => {
14887
16160
  });
14888
- const handleDeleteSelectedRef = useRef9(() => false);
14889
- const runPendingDeleteUndoRef = useRef9(() => false);
14890
- const isFooterFrameSelectionRef = useRef9(false);
14891
- const refreshActiveCommandsRef = useRef9(() => {
16161
+ const handleDeleteSelectedRef = useRef10(() => false);
16162
+ const runPendingDeleteUndoRef = useRef10(() => false);
16163
+ const isFooterFrameSelectionRef = useRef10(false);
16164
+ const refreshActiveCommandsRef = useRef10(() => {
14892
16165
  });
14893
- const postToParentRef = useRef9(postToParent2);
16166
+ const postToParentRef = useRef10(postToParent2);
14894
16167
  postToParentRef.current = postToParent2;
14895
- const aiSectionApiRef = useRef9(null);
14896
- const sectionsLoadedRef = useRef9(false);
14897
- const pendingScheduleConfigRequests = useRef9([]);
14898
- const [toolbarRect, setToolbarRect] = useState12(null);
14899
- const [formPickRect, setFormPickRect] = useState12(null);
14900
- const formPickElRef = useRef9(null);
14901
- const [formViewState, setFormViewStateUi] = useState12("default");
14902
- const [formPickCount, setFormPickCount] = useState12(null);
14903
- const [formHoverRect, setFormHoverRect] = useState12(null);
14904
- const formHoverElRef = useRef9(null);
14905
- const [fieldPickRect, setFieldPickRect] = useState12(null);
14906
- const fieldPickElRef = useRef9(null);
14907
- const [fieldPickState, setFieldPickState] = useState12(null);
14908
- const [fieldTypePickerOpen, setFieldTypePickerOpen] = useState12(false);
14909
- const clearFormPick = useCallback7(() => {
16168
+ const aiSectionApiRef = useRef10(null);
16169
+ const sectionsLoadedRef = useRef10(false);
16170
+ const pendingScheduleConfigRequests = useRef10([]);
16171
+ const [toolbarRect, setToolbarRect] = useState13(null);
16172
+ const [formPickRect, setFormPickRect] = useState13(null);
16173
+ const formPickElRef = useRef10(null);
16174
+ const [formViewState, setFormViewStateUi] = useState13("default");
16175
+ const [formPickCount, setFormPickCount] = useState13(null);
16176
+ const [formHoverRect, setFormHoverRect] = useState13(null);
16177
+ const formHoverElRef = useRef10(null);
16178
+ const [fieldPickRect, setFieldPickRect] = useState13(null);
16179
+ const fieldPickElRef = useRef10(null);
16180
+ const [fieldPickState, setFieldPickState] = useState13(null);
16181
+ const [fieldTypePickerOpen, setFieldTypePickerOpen] = useState13(false);
16182
+ const clearFormPick = useCallback8(() => {
14910
16183
  const form = formPickElRef.current;
14911
16184
  const editing = fieldPickElRef.current;
14912
16185
  if (commitPlaceholderEdit(editing) && editing) {
@@ -14926,7 +16199,7 @@ function OhhwellsBridge() {
14926
16199
  formPickElRef.current = null;
14927
16200
  setFormPickRect(null);
14928
16201
  }, []);
14929
- const clearFieldPick = useCallback7(() => {
16202
+ const clearFieldPick = useCallback8(() => {
14930
16203
  const wrapper = fieldPickElRef.current;
14931
16204
  if (commitPlaceholderEdit(wrapper) && wrapper) {
14932
16205
  const form = wrapper.closest('[data-ohw-editable="form"]');
@@ -14936,9 +16209,9 @@ function OhhwellsBridge() {
14936
16209
  setFieldPickRect(null);
14937
16210
  setFieldPickState(null);
14938
16211
  }, []);
14939
- const persistFieldsRef = useRef9(() => {
16212
+ const persistFieldsRef = useRef10(() => {
14940
16213
  });
14941
- const persistFields = useCallback7(
16214
+ const persistFields = useCallback8(
14942
16215
  (form) => {
14943
16216
  const key = formKeyOf(form);
14944
16217
  if (!key) return;
@@ -14949,7 +16222,7 @@ function OhhwellsBridge() {
14949
16222
  []
14950
16223
  );
14951
16224
  persistFieldsRef.current = persistFields;
14952
- const selectField = useCallback7((wrapper) => {
16225
+ const selectField = useCallback8((wrapper) => {
14953
16226
  if (fieldPickElRef.current && fieldPickElRef.current !== wrapper) {
14954
16227
  commitPlaceholderEdit(fieldPickElRef.current);
14955
16228
  }
@@ -14962,7 +16235,7 @@ function OhhwellsBridge() {
14962
16235
  setFieldPickState({ type: fieldTypeOf(wrapper), required: isFieldRequired(wrapper) });
14963
16236
  setFieldTypePickerOpen(false);
14964
16237
  }, []);
14965
- const withSelectedField = useCallback7(
16238
+ const withSelectedField = useCallback8(
14966
16239
  (run) => {
14967
16240
  const wrapper = fieldPickElRef.current;
14968
16241
  const form = formPickElRef.current;
@@ -14975,28 +16248,28 @@ function OhhwellsBridge() {
14975
16248
  },
14976
16249
  [persistFields]
14977
16250
  );
14978
- const handleFieldTypeChange = useCallback7(
16251
+ const handleFieldTypeChange = useCallback8(
14979
16252
  (type) => withSelectedField((_form, wrapper) => {
14980
16253
  applyFieldType(wrapper, type);
14981
16254
  selectField(wrapper);
14982
16255
  }),
14983
16256
  [selectField, withSelectedField]
14984
16257
  );
14985
- const handleFieldRequiredToggle = useCallback7(
16258
+ const handleFieldRequiredToggle = useCallback8(
14986
16259
  () => withSelectedField((_form, wrapper) => {
14987
16260
  setFieldRequired(wrapper, !isFieldRequired(wrapper));
14988
16261
  selectField(wrapper);
14989
16262
  }),
14990
16263
  [selectField, withSelectedField]
14991
16264
  );
14992
- const handleFieldDuplicate = useCallback7(
16265
+ const handleFieldDuplicate = useCallback8(
14993
16266
  () => withSelectedField((form, wrapper) => {
14994
16267
  const copy = duplicateField(form, wrapper);
14995
16268
  selectField(copy);
14996
16269
  }),
14997
16270
  [selectField, withSelectedField]
14998
16271
  );
14999
- const handleFieldDelete = useCallback7(
16272
+ const handleFieldDelete = useCallback8(
15000
16273
  () => withSelectedField((_form, wrapper) => {
15001
16274
  removeField(wrapper);
15002
16275
  clearFieldPick();
@@ -15004,7 +16277,7 @@ function OhhwellsBridge() {
15004
16277
  }),
15005
16278
  [clearFieldPick, withSelectedField]
15006
16279
  );
15007
- const handleAddField = useCallback7(
16280
+ const handleAddField = useCallback8(
15008
16281
  (type) => {
15009
16282
  const form = formPickElRef.current;
15010
16283
  if (!form) return;
@@ -15020,8 +16293,8 @@ function OhhwellsBridge() {
15020
16293
  },
15021
16294
  [persistFields, selectField]
15022
16295
  );
15023
- const fieldDragRef = useRef9(null);
15024
- const buildFieldDropSlots = useCallback7((form, draggedKey) => {
16296
+ const fieldDragRef = useRef10(null);
16297
+ const buildFieldDropSlots = useCallback8((form, draggedKey) => {
15025
16298
  const others = listFieldWrappers(form).filter((el) => fieldKeyOf(el) !== draggedKey);
15026
16299
  const slots = others.map((el) => {
15027
16300
  const rect = el.getBoundingClientRect();
@@ -15034,7 +16307,7 @@ function OhhwellsBridge() {
15034
16307
  }
15035
16308
  return slots;
15036
16309
  }, []);
15037
- const handleFieldDragStart = useCallback7(() => {
16310
+ const handleFieldDragStart = useCallback8(() => {
15038
16311
  const wrapper = fieldPickElRef.current;
15039
16312
  const form = formPickElRef.current;
15040
16313
  if (!wrapper || !form) return;
@@ -15043,18 +16316,18 @@ function OhhwellsBridge() {
15043
16316
  setFieldDragging(true);
15044
16317
  setFieldDropSlots(buildFieldDropSlots(form, key));
15045
16318
  }, [buildFieldDropSlots]);
15046
- const handleFieldDragEnd = useCallback7(() => {
16319
+ const handleFieldDragEnd = useCallback8(() => {
15047
16320
  fieldDragRef.current = null;
15048
16321
  setFieldDropIndex(null);
15049
16322
  setFieldDropSlots([]);
15050
16323
  setFieldDragging(false);
15051
16324
  }, []);
15052
- const [fieldDropIndex, setFieldDropIndex] = useState12(null);
15053
- const [fieldDropSlots, setFieldDropSlots] = useState12([]);
15054
- const [fieldDragging, setFieldDragging] = useState12(false);
15055
- const clearFormPickRef = useRef9(clearFormPick);
16325
+ const [fieldDropIndex, setFieldDropIndex] = useState13(null);
16326
+ const [fieldDropSlots, setFieldDropSlots] = useState13([]);
16327
+ const [fieldDragging, setFieldDragging] = useState13(false);
16328
+ const clearFormPickRef = useRef10(clearFormPick);
15056
16329
  clearFormPickRef.current = clearFormPick;
15057
- useEffect12(() => {
16330
+ useEffect13(() => {
15058
16331
  const el = fieldPickElRef.current;
15059
16332
  if (!el || fieldPickRect === null) return;
15060
16333
  const observer = new ResizeObserver(() => {
@@ -15063,7 +16336,7 @@ function OhhwellsBridge() {
15063
16336
  observer.observe(el);
15064
16337
  return () => observer.disconnect();
15065
16338
  }, [fieldPickRect !== null, fieldPickState]);
15066
- useEffect12(() => {
16339
+ useEffect13(() => {
15067
16340
  const el = formPickElRef.current;
15068
16341
  if (!el || formPickRect === null) return;
15069
16342
  const observer = new ResizeObserver(() => {
@@ -15072,25 +16345,25 @@ function OhhwellsBridge() {
15072
16345
  observer.observe(el);
15073
16346
  return () => observer.disconnect();
15074
16347
  }, [formPickRect !== null, formViewState]);
15075
- const [toolbarVariant, setToolbarVariant] = useState12("none");
15076
- const toolbarVariantRef = useRef9("none");
16348
+ const [toolbarVariant, setToolbarVariant] = useState13("none");
16349
+ const toolbarVariantRef = useRef10("none");
15077
16350
  toolbarVariantRef.current = toolbarVariant;
15078
- const [selectedIsCta, setSelectedIsCta] = useState12(false);
15079
- const [selectedIsSocial, setSelectedIsSocial] = useState12(false);
15080
- const [selectedIsSocialsRow, setSelectedIsSocialsRow] = useState12(false);
15081
- const [reorderHrefKey, setReorderHrefKey] = useState12(null);
15082
- const [reorderDragDisabled, setReorderDragDisabled] = useState12(false);
15083
- const [toggleState, setToggleState] = useState12(null);
15084
- const [maxBadge, setMaxBadge] = useState12(null);
15085
- const [activeCommands, setActiveCommands] = useState12(/* @__PURE__ */ new Set());
15086
- const [sectionGap, setSectionGap] = useState12(null);
15087
- const [toolbarShowEditLink, setToolbarShowEditLink] = useState12(false);
15088
- const hoveredNavContainerRef = useRef9(null);
15089
- const [hoveredNavContainerRect, setHoveredNavContainerRect] = useState12(null);
15090
- const hoveredItemElRef = useRef9(null);
15091
- const [hoveredItemRect, setHoveredItemRect] = useState12(null);
15092
- const [hoveredTextRect, setHoveredTextRect] = useState12(null);
15093
- useEffect12(() => {
16351
+ const [selectedIsCta, setSelectedIsCta] = useState13(false);
16352
+ const [selectedIsSocial, setSelectedIsSocial] = useState13(false);
16353
+ const [selectedIsSocialsRow, setSelectedIsSocialsRow] = useState13(false);
16354
+ const [reorderHrefKey, setReorderHrefKey] = useState13(null);
16355
+ const [reorderDragDisabled, setReorderDragDisabled] = useState13(false);
16356
+ const [toggleState, setToggleState] = useState13(null);
16357
+ const [maxBadge, setMaxBadge] = useState13(null);
16358
+ const [activeCommands, setActiveCommands] = useState13(/* @__PURE__ */ new Set());
16359
+ const [sectionGap, setSectionGap] = useState13(null);
16360
+ const [toolbarShowEditLink, setToolbarShowEditLink] = useState13(false);
16361
+ const hoveredNavContainerRef = useRef10(null);
16362
+ const [hoveredNavContainerRect, setHoveredNavContainerRect] = useState13(null);
16363
+ const hoveredItemElRef = useRef10(null);
16364
+ const [hoveredItemRect, setHoveredItemRect] = useState13(null);
16365
+ const [hoveredTextRect, setHoveredTextRect] = useState13(null);
16366
+ useEffect13(() => {
15094
16367
  const sync = () => {
15095
16368
  const el = document.querySelector(
15096
16369
  '[data-ohw-hovered]:not([contenteditable]):not([data-ohw-href-key]):not([data-ohw-editable="form"] *)'
@@ -15115,43 +16388,56 @@ function OhhwellsBridge() {
15115
16388
  });
15116
16389
  return () => observer.disconnect();
15117
16390
  }, []);
15118
- const siblingHintElRef = useRef9(null);
15119
- const [siblingHintRect, setSiblingHintRect] = useState12(null);
15120
- const [siblingHintRects, setSiblingHintRects] = useState12([]);
15121
- const [isItemDragging, setIsItemDragging] = useState12(false);
15122
- const [isFooterFrameSelection, setIsFooterFrameSelection] = useState12(false);
16391
+ const siblingHintElRef = useRef10(null);
16392
+ const [siblingHintRect, setSiblingHintRect] = useState13(null);
16393
+ const [siblingHintRects, setSiblingHintRects] = useState13([]);
16394
+ const [isItemDragging, setIsItemDragging] = useState13(false);
16395
+ const [isFooterFrameSelection, setIsFooterFrameSelection] = useState13(false);
15123
16396
  isFooterFrameSelectionRef.current = isFooterFrameSelection;
15124
- const [floatingPanel, setFloatingPanel] = useState12(null);
15125
- const floatingPanelOpenRef = useRef9(false);
15126
- floatingPanelOpenRef.current = floatingPanel !== null;
15127
- const [floatingPanelPos, setFloatingPanelPos] = useState12(null);
15128
- const [logoSizeDraft, setLogoSizeDraft] = useState12(null);
15129
- const [editorViewport, setEditorViewport] = useState12("desktop");
15130
- const [parentScrollSnap, setParentScrollSnap] = useState12(null);
15131
- const [navDropdownPreviewOpen, setNavDropdownPreviewOpen] = useState12(null);
15132
- const [footerHeadingVisible, setFooterHeadingVisible] = useState12(null);
15133
- const footerDragRef = useRef9(null);
15134
- const [footerDropSlots, setFooterDropSlots] = useState12([]);
15135
- const [activeFooterDropIndex, setActiveFooterDropIndex] = useState12(null);
15136
- const [draggedItemRect, setDraggedItemRect] = useState12(null);
15137
- const footerPointerDragRef = useRef9(null);
15138
- const suppressNextClickRef = useRef9(false);
15139
- const suppressClickUntilRef = useRef9(0);
15140
- const [linkPopover, setLinkPopover] = useState12(null);
15141
- const linkPopoverSessionRef = useRef9(null);
15142
- const addNavAfterAnchorRef = useRef9(null);
15143
- const editContentRef = useRef9({});
15144
- const aiSectionsRef = useRef9("");
15145
- const pendingDeleteUndoRef = useRef9(null);
15146
- const [sitePages, setSitePages] = useState12([]);
15147
- const [sectionsByPath, setSectionsByPath] = useState12({});
15148
- const sectionsPrefetchGenRef = useRef9(0);
15149
- const setLinkPopoverRef = useRef9(setLinkPopover);
15150
- const linkPopoverPanelRef = useRef9(null);
15151
- const linkPopoverOpenRef = useRef9(false);
15152
- const linkPopoverGraceUntilRef = useRef9(0);
16397
+ const [navDropdownPreviewOpen, setNavDropdownPreviewOpen] = useState13(null);
16398
+ const [footerHeadingVisible, setFooterHeadingVisible] = useState13(null);
16399
+ const footerDragRef = useRef10(null);
16400
+ const [footerDropSlots, setFooterDropSlots] = useState13([]);
16401
+ const [activeFooterDropIndex, setActiveFooterDropIndex] = useState13(null);
16402
+ const [draggedItemRect, setDraggedItemRect] = useState13(null);
16403
+ const footerPointerDragRef = useRef10(null);
16404
+ const suppressNextClickRef = useRef10(false);
16405
+ const suppressClickUntilRef = useRef10(0);
16406
+ const [linkPopover, setLinkPopover] = useState13(null);
16407
+ const linkPopoverSessionRef = useRef10(null);
16408
+ const addNavAfterAnchorRef = useRef10(null);
16409
+ const editContentRef = useRef10({});
16410
+ const aiSectionsRef = useRef10("");
16411
+ const brandKitRef = useRef10("");
16412
+ const stylesRef = useRef10("");
16413
+ const pendingDeleteUndoRef = useRef10(null);
16414
+ const [floatingPanel, setFloatingPanel] = useState13(null);
16415
+ const floatingPanelOpenRef = useRef10(false);
16416
+ const setFloatingPanelRef = useRef10(setFloatingPanel);
16417
+ const [floatingPanelPos, setFloatingPanelPos] = useState13(null);
16418
+ const [logoSizeDraft, setLogoSizeDraft] = useState13(null);
16419
+ const [editorViewport, setEditorViewport] = useState13("desktop");
16420
+ const [parentScrollSnap, setParentScrollSnap] = useState13(null);
16421
+ const [sitePages, setSitePages] = useState13([]);
16422
+ const [sectionsByPath, setSectionsByPath] = useState13({});
16423
+ const sectionsPrefetchGenRef = useRef10(0);
16424
+ const setLinkPopoverRef = useRef10(setLinkPopover);
16425
+ const linkPopoverPanelRef = useRef10(null);
16426
+ const linkPopoverOpenRef = useRef10(false);
16427
+ const linkPopoverGraceUntilRef = useRef10(0);
15153
16428
  setLinkPopoverRef.current = setLinkPopover;
16429
+ setFloatingPanelRef.current = setFloatingPanel;
15154
16430
  linkPopoverSessionRef.current = linkPopover;
16431
+ floatingPanelOpenRef.current = Boolean(floatingPanel);
16432
+ useEffect13(() => {
16433
+ const syncViewport = () => {
16434
+ const next = window.innerWidth <= 480 ? "mobile" : "desktop";
16435
+ setEditorViewport((prev) => prev === next ? prev : next);
16436
+ };
16437
+ syncViewport();
16438
+ window.addEventListener("resize", syncViewport);
16439
+ return () => window.removeEventListener("resize", syncViewport);
16440
+ }, []);
15155
16441
  const {
15156
16442
  navDragRef,
15157
16443
  navDropSlots,
@@ -15184,10 +16470,20 @@ function OhhwellsBridge() {
15184
16470
  getNavigationItemAnchor,
15185
16471
  isDragHandleDisabled
15186
16472
  });
16473
+ const { sectionDropSlots, activeSectionDropIndex, isSectionDragging } = useSectionDrag({
16474
+ isEditMode,
16475
+ editContentRef,
16476
+ postToParentRef,
16477
+ parentScrollRef,
16478
+ navDragRef,
16479
+ footerDragRef,
16480
+ suppressNextClickRef,
16481
+ suppressClickUntilRef
16482
+ });
15187
16483
  const bumpLinkPopoverGrace = () => {
15188
16484
  linkPopoverGraceUntilRef.current = Date.now() + 350;
15189
16485
  };
15190
- const runSectionsPrefetch = useCallback7((pages) => {
16486
+ const runSectionsPrefetch = useCallback8((pages) => {
15191
16487
  if (!isEditMode || shouldUseDevFixtures() || pages.length === 0) return;
15192
16488
  const gen = ++sectionsPrefetchGenRef.current;
15193
16489
  const paths = pages.map((p) => p.path);
@@ -15206,9 +16502,9 @@ function OhhwellsBridge() {
15206
16502
  );
15207
16503
  });
15208
16504
  }, [isEditMode, pathname]);
15209
- const runSectionsPrefetchRef = useRef9(runSectionsPrefetch);
16505
+ const runSectionsPrefetchRef = useRef10(runSectionsPrefetch);
15210
16506
  runSectionsPrefetchRef.current = runSectionsPrefetch;
15211
- useEffect12(() => {
16507
+ useEffect13(() => {
15212
16508
  if (!linkPopover) {
15213
16509
  document.documentElement.removeAttribute("data-ohw-link-popover-open");
15214
16510
  return;
@@ -15236,7 +16532,7 @@ function OhhwellsBridge() {
15236
16532
  document.documentElement.removeAttribute("data-ohw-link-popover-open");
15237
16533
  };
15238
16534
  }, [linkPopover, postToParent2]);
15239
- useEffect12(() => {
16535
+ useEffect13(() => {
15240
16536
  if (!isEditMode) return;
15241
16537
  const useFixtures = shouldUseDevFixtures();
15242
16538
  if (useFixtures) {
@@ -15260,14 +16556,14 @@ function OhhwellsBridge() {
15260
16556
  if (!useFixtures) postToParent2({ type: "ow:request-site-pages" });
15261
16557
  return () => window.removeEventListener("message", onSitePages);
15262
16558
  }, [isEditMode, postToParent2]);
15263
- useEffect12(() => {
16559
+ useEffect13(() => {
15264
16560
  if (!isEditMode || shouldUseDevFixtures()) return;
15265
16561
  void loadAllSectionsManifest().then((manifest) => {
15266
16562
  if (Object.keys(manifest).length === 0) return;
15267
16563
  setSectionsByPath((prev) => ({ ...manifest, ...prev }));
15268
16564
  });
15269
16565
  }, [isEditMode]);
15270
- useEffect12(() => {
16566
+ useEffect13(() => {
15271
16567
  const update = () => {
15272
16568
  const el = activeElRef.current ?? selectedElRef.current;
15273
16569
  if (el) setToolbarRect(getEditMeasureEl(el).getBoundingClientRect());
@@ -15291,10 +16587,10 @@ function OhhwellsBridge() {
15291
16587
  vvp.removeEventListener("resize", update);
15292
16588
  };
15293
16589
  }, []);
15294
- const refreshStateRules = useCallback7(() => {
16590
+ const refreshStateRules = useCallback8(() => {
15295
16591
  editStylesRef.current?.forceHover && (editStylesRef.current.forceHover.textContent = collectStateRules());
15296
16592
  }, []);
15297
- const processConfigRequest = useCallback7((insertAfterVal) => {
16593
+ const processConfigRequest = useCallback8((insertAfterVal) => {
15298
16594
  const tracker = getSectionsTracker();
15299
16595
  let entries = [];
15300
16596
  try {
@@ -15317,7 +16613,7 @@ function OhhwellsBridge() {
15317
16613
  }
15318
16614
  window.postMessage({ type: "ow:schedule-config", insertAfter: insertAfterVal, scheduleId: null }, "*");
15319
16615
  }, [isEditMode]);
15320
- const deactivate = useCallback7(() => {
16616
+ const deactivate = useCallback8(() => {
15321
16617
  const el = activeElRef.current;
15322
16618
  if (!el) return;
15323
16619
  const isFormBlock = el.dataset.ohwEditable === "form";
@@ -15333,7 +16629,7 @@ function OhhwellsBridge() {
15333
16629
  const original = originalContentRef.current ?? "";
15334
16630
  if (html !== sanitizeHtml(original)) {
15335
16631
  postToParentRef.current({ type: "ow:change", nodes: [{ key, text: html }] });
15336
- const h = document.documentElement.scrollHeight;
16632
+ const h = document.body.scrollHeight;
15337
16633
  if (h > 50) postToParentRef.current({ type: "ow:height", height: h });
15338
16634
  }
15339
16635
  }
@@ -15358,12 +16654,12 @@ function OhhwellsBridge() {
15358
16654
  setToolbarShowEditLink(false);
15359
16655
  postToParent2({ type: "ow:exit-edit" });
15360
16656
  }, [postToParent2]);
15361
- const clearSelectedAttr = useCallback7(() => {
16657
+ const clearSelectedAttr = useCallback8(() => {
15362
16658
  document.querySelectorAll("[data-ohw-selected]").forEach((el) => {
15363
16659
  el.removeAttribute("data-ohw-selected");
15364
16660
  });
15365
16661
  }, []);
15366
- const deselect = useCallback7(() => {
16662
+ const deselect = useCallback8(() => {
15367
16663
  clearSelectedAttr();
15368
16664
  selectedElRef.current = null;
15369
16665
  selectedHrefKeyRef.current = null;
@@ -15392,20 +16688,20 @@ function OhhwellsBridge() {
15392
16688
  setToolbarVariant("none");
15393
16689
  }
15394
16690
  }, [clearSelectedAttr]);
15395
- const markSelected = useCallback7((el) => {
16691
+ const markSelected = useCallback8((el) => {
15396
16692
  clearSelectedAttr();
15397
16693
  el.removeAttribute("data-ohw-hovered");
15398
16694
  el.setAttribute("data-ohw-selected", "");
15399
16695
  }, [clearSelectedAttr]);
15400
- const isSelectedForHover = useCallback7((el) => {
16696
+ const isSelectedForHover = useCallback8((el) => {
15401
16697
  if (!el) return false;
15402
16698
  return [selectedElRef.current, activeElRef.current].some(
15403
16699
  (busy) => busy && (el === busy || busy.contains(el) || el.contains(busy))
15404
16700
  );
15405
16701
  }, []);
15406
- const isSelectedForHoverRef = useRef9(isSelectedForHover);
16702
+ const isSelectedForHoverRef = useRef10(isSelectedForHover);
15407
16703
  isSelectedForHoverRef.current = isSelectedForHover;
15408
- const resolveHrefKeyElement = useCallback7((hrefKey) => {
16704
+ const resolveHrefKeyElement = useCallback8((hrefKey) => {
15409
16705
  if (isFooterHrefKey(hrefKey)) {
15410
16706
  return document.querySelector(
15411
16707
  `footer [data-ohw-href-key="${CSS.escape(hrefKey)}"]`
@@ -15420,7 +16716,7 @@ function OhhwellsBridge() {
15420
16716
  `[data-ohw-href-key="${CSS.escape(hrefKey)}"]`
15421
16717
  );
15422
16718
  }, []);
15423
- const resyncSelectedNavigationItem = useCallback7(() => {
16719
+ const resyncSelectedNavigationItem = useCallback8(() => {
15424
16720
  const hrefKey = selectedHrefKeyRef.current;
15425
16721
  if (hrefKey) {
15426
16722
  const link = resolveHrefKeyElement(hrefKey);
@@ -15458,7 +16754,7 @@ function OhhwellsBridge() {
15458
16754
  );
15459
16755
  }
15460
16756
  }, [resolveHrefKeyElement]);
15461
- const reselectNavigationItem = useCallback7((navAnchor) => {
16757
+ const reselectNavigationItem = useCallback8((navAnchor) => {
15462
16758
  selectedElRef.current = navAnchor;
15463
16759
  selectedHrefKeyRef.current = navAnchor.getAttribute("data-ohw-href-key");
15464
16760
  selectedFooterColAttrRef.current = null;
@@ -15489,7 +16785,7 @@ function OhhwellsBridge() {
15489
16785
  setToolbarShowEditLink(false);
15490
16786
  setActiveCommands(/* @__PURE__ */ new Set());
15491
16787
  }, [markSelected]);
15492
- const commitNavigationTextEdit = useCallback7((navAnchor) => {
16788
+ const commitNavigationTextEdit = useCallback8((navAnchor) => {
15493
16789
  const el = activeElRef.current;
15494
16790
  if (!el) return;
15495
16791
  const key = el.dataset.ohwKey;
@@ -15503,7 +16799,7 @@ function OhhwellsBridge() {
15503
16799
  const original = originalContentRef.current ?? "";
15504
16800
  if (html !== sanitizeHtml(original)) {
15505
16801
  postToParent2({ type: "ow:change", nodes: [{ key, text: html }] });
15506
- const h = document.documentElement.scrollHeight;
16802
+ const h = document.body.scrollHeight;
15507
16803
  if (h > 50) postToParent2({ type: "ow:height", height: h });
15508
16804
  }
15509
16805
  }
@@ -15522,7 +16818,7 @@ function OhhwellsBridge() {
15522
16818
  postToParent2({ type: "ow:exit-edit" });
15523
16819
  reselectNavigationItem(navAnchor);
15524
16820
  }, [postToParent2, reselectNavigationItem]);
15525
- const handleAddTopLevelNavItem = useCallback7(() => {
16821
+ const handleAddTopLevelNavItem = useCallback8(() => {
15526
16822
  const items = listNavbarRootItems();
15527
16823
  addNavAfterAnchorRef.current = items[items.length - 1] ?? null;
15528
16824
  deselectRef.current();
@@ -15534,7 +16830,7 @@ function OhhwellsBridge() {
15534
16830
  intent: "add-nav"
15535
16831
  });
15536
16832
  }, []);
15537
- const maybeWarnNavLinkDropdownConflict = useCallback7(
16833
+ const maybeWarnNavLinkDropdownConflict = useCallback8(
15538
16834
  (anchor) => {
15539
16835
  if (!isNavbarHrefKey(anchor.getAttribute("data-ohw-href-key"))) return;
15540
16836
  if (!navDropdownsOpenOnClick()) return;
@@ -15547,7 +16843,7 @@ function OhhwellsBridge() {
15547
16843
  },
15548
16844
  [postToParent2]
15549
16845
  );
15550
- const handleNavDropdownOpenChange = useCallback7((open) => {
16846
+ const handleNavDropdownOpenChange = useCallback8((open) => {
15551
16847
  const selected = selectedElRef.current;
15552
16848
  if (!selected || !isNavigationItem2(selected)) return;
15553
16849
  setNavGroupForceOpen(selected, open);
@@ -15559,7 +16855,7 @@ function OhhwellsBridge() {
15559
16855
  }
15560
16856
  });
15561
16857
  }, []);
15562
- const handleFooterHeadingVisibleChange = useCallback7(
16858
+ const handleFooterHeadingVisibleChange = useCallback8(
15563
16859
  (visible) => {
15564
16860
  const selected = selectedElRef.current;
15565
16861
  if (!selected || !isFooterFrameSelectionRef.current) return;
@@ -15583,7 +16879,7 @@ function OhhwellsBridge() {
15583
16879
  },
15584
16880
  [postToParent2]
15585
16881
  );
15586
- const enterEditOnNewItem = useCallback7((anchor) => {
16882
+ const enterEditOnNewItem = useCallback8((anchor) => {
15587
16883
  const label = anchor.querySelector('[data-ohw-editable="text"]');
15588
16884
  if (!label) {
15589
16885
  selectRef.current(anchor);
@@ -15592,8 +16888,8 @@ function OhhwellsBridge() {
15592
16888
  setNavGroupForceOpen(anchor, true);
15593
16889
  activateRef.current(label);
15594
16890
  }, []);
15595
- const pendingSocialAddRef = useRef9(null);
15596
- const handleAddChildItem = useCallback7(() => {
16891
+ const pendingSocialAddRef = useRef10(null);
16892
+ const handleAddChildItem = useCallback8(() => {
15597
16893
  const selected = selectedElRef.current;
15598
16894
  if (!selected) return;
15599
16895
  const socialsRow = isSocialsRow(selected) ? selected : findSocialsRow(selected);
@@ -15702,7 +16998,7 @@ function OhhwellsBridge() {
15702
16998
  enterEditOnNewItem(result.anchor);
15703
16999
  });
15704
17000
  }, [enterEditOnNewItem, isFooterFrameSelection, maybeWarnNavLinkDropdownConflict, postToParent2]);
15705
- const handleAddFooterColumn = useCallback7(() => {
17001
+ const handleAddFooterColumn = useCallback8(() => {
15706
17002
  if (!canAddFooterColumn()) {
15707
17003
  postToParent2({
15708
17004
  type: "ow:toast",
@@ -15723,7 +17019,7 @@ function OhhwellsBridge() {
15723
17019
  selectRef.current(result.firstLink);
15724
17020
  });
15725
17021
  }, [postToParent2]);
15726
- const clearFooterDragVisuals = useCallback7(() => {
17022
+ const clearFooterDragVisuals = useCallback8(() => {
15727
17023
  footerDragRef.current = null;
15728
17024
  setSiblingHintRects([]);
15729
17025
  setFooterDropSlots([]);
@@ -15732,7 +17028,7 @@ function OhhwellsBridge() {
15732
17028
  setIsItemDragging(false);
15733
17029
  unlockFooterDragInteraction();
15734
17030
  }, []);
15735
- const refreshFooterDragVisuals = useCallback7((session, activeSlot, clientX, clientY) => {
17031
+ const refreshFooterDragVisuals = useCallback8((session, activeSlot, clientX, clientY) => {
15736
17032
  const dragged = session.draggedEl;
15737
17033
  setDraggedItemRect(dragged.getBoundingClientRect());
15738
17034
  if (typeof clientX === "number" && typeof clientY === "number") {
@@ -15764,13 +17060,13 @@ function OhhwellsBridge() {
15764
17060
  const activeIdx = activeSlot ? slots.findIndex((s) => s.insertIndex === activeSlot.insertIndex) : -1;
15765
17061
  setActiveFooterDropIndex(activeIdx >= 0 ? activeIdx : null);
15766
17062
  }, []);
15767
- const refreshFooterDragVisualsRef = useRef9(refreshFooterDragVisuals);
17063
+ const refreshFooterDragVisualsRef = useRef10(refreshFooterDragVisuals);
15768
17064
  refreshFooterDragVisualsRef.current = refreshFooterDragVisuals;
15769
- const commitFooterDragRef = useRef9(() => {
17065
+ const commitFooterDragRef = useRef10(() => {
15770
17066
  });
15771
- const beginFooterDragRef = useRef9(() => {
17067
+ const beginFooterDragRef = useRef10(() => {
15772
17068
  });
15773
- const beginFooterDrag = useCallback7(
17069
+ const beginFooterDrag = useCallback8(
15774
17070
  (session) => {
15775
17071
  const rect = session.draggedEl.getBoundingClientRect();
15776
17072
  session.lastClientX = session.lastClientX || rect.left + rect.width / 2;
@@ -15790,7 +17086,7 @@ function OhhwellsBridge() {
15790
17086
  [refreshFooterDragVisuals]
15791
17087
  );
15792
17088
  beginFooterDragRef.current = beginFooterDrag;
15793
- const commitFooterDrag = useCallback7(
17089
+ const commitFooterDrag = useCallback8(
15794
17090
  (clientX, clientY) => {
15795
17091
  const session = footerDragRef.current;
15796
17092
  if (!session) {
@@ -15919,7 +17215,7 @@ function OhhwellsBridge() {
15919
17215
  [clearFooterDragVisuals, resolveHrefKeyElement, resyncSelectedNavigationItem]
15920
17216
  );
15921
17217
  commitFooterDragRef.current = commitFooterDrag;
15922
- const startFooterLinkDrag = useCallback7(
17218
+ const startFooterLinkDrag = useCallback8(
15923
17219
  (anchor, clientX, clientY, wasSelected) => {
15924
17220
  const hrefKey = anchor.getAttribute("data-ohw-href-key");
15925
17221
  if (!hrefKey) return false;
@@ -15955,7 +17251,7 @@ function OhhwellsBridge() {
15955
17251
  },
15956
17252
  [beginFooterDrag]
15957
17253
  );
15958
- const startFooterColumnDrag = useCallback7(
17254
+ const startFooterColumnDrag = useCallback8(
15959
17255
  (columnEl, clientX, clientY, wasSelected) => {
15960
17256
  const columns = listFooterColumns();
15961
17257
  const idx = columns.indexOf(columnEl);
@@ -15975,7 +17271,7 @@ function OhhwellsBridge() {
15975
17271
  },
15976
17272
  [beginFooterDrag]
15977
17273
  );
15978
- const handleItemDragStart = useCallback7(
17274
+ const handleItemDragStart = useCallback8(
15979
17275
  (e) => {
15980
17276
  const selected = selectedElRef.current;
15981
17277
  if (!selected) {
@@ -15995,7 +17291,7 @@ function OhhwellsBridge() {
15995
17291
  },
15996
17292
  [startFooterColumnDrag, startFooterLinkDrag, startNavLinkDrag]
15997
17293
  );
15998
- const handleItemDragEnd = useCallback7(
17294
+ const handleItemDragEnd = useCallback8(
15999
17295
  (e) => {
16000
17296
  if (footerDragRef.current) {
16001
17297
  const x = e?.clientX;
@@ -16021,7 +17317,7 @@ function OhhwellsBridge() {
16021
17317
  },
16022
17318
  [commitFooterDrag, commitNavDrag, navDragRef]
16023
17319
  );
16024
- const handleItemChromePointerDown = useCallback7((e) => {
17320
+ const handleItemChromePointerDown = useCallback8((e) => {
16025
17321
  if (e.button !== 0) return;
16026
17322
  const selected = selectedElRef.current;
16027
17323
  if (!selected) return;
@@ -16052,7 +17348,7 @@ function OhhwellsBridge() {
16052
17348
  }
16053
17349
  if (armNavPressFromChrome(selected, e.clientX, e.clientY, e.pointerId)) return;
16054
17350
  }, [armNavPressFromChrome]);
16055
- const handleItemChromeClick = useCallback7((clientX, clientY) => {
17351
+ const handleItemChromeClick = useCallback8((clientX, clientY) => {
16056
17352
  if (suppressNextClickRef.current || Date.now() < suppressClickUntilRef.current) {
16057
17353
  suppressNextClickRef.current = false;
16058
17354
  return;
@@ -16065,7 +17361,7 @@ function OhhwellsBridge() {
16065
17361
  }, []);
16066
17362
  reselectNavigationItemRef.current = reselectNavigationItem;
16067
17363
  commitNavigationTextEditRef.current = commitNavigationTextEdit;
16068
- const select = useCallback7((anchor) => {
17364
+ const select = useCallback8((anchor) => {
16069
17365
  if (!isNavigationItem2(anchor)) return;
16070
17366
  if (activeElRef.current) deactivate();
16071
17367
  aiSectionApiRef.current?.selectFromElement(anchor);
@@ -16108,7 +17404,7 @@ function OhhwellsBridge() {
16108
17404
  setFloatingPanel(null);
16109
17405
  setLogoSizeDraft(null);
16110
17406
  }, [deactivate, markSelected]);
16111
- const selectFrame = useCallback7((el) => {
17407
+ const selectFrame = useCallback8((el) => {
16112
17408
  if (!isNavigationContainer(el)) return;
16113
17409
  if (activeElRef.current) deactivate();
16114
17410
  aiSectionApiRef.current?.selectFromElement(el);
@@ -16159,7 +17455,7 @@ function OhhwellsBridge() {
16159
17455
  setFloatingPanel(null);
16160
17456
  setLogoSizeDraft(null);
16161
17457
  }, [deactivate, markSelected, postToParent2]);
16162
- const selectLogo = useCallback7(
17458
+ const selectLogo = useCallback8(
16163
17459
  (logoEl) => {
16164
17460
  if (activeElRef.current) deactivate();
16165
17461
  selectedElRef.current = logoEl;
@@ -16188,7 +17484,7 @@ function OhhwellsBridge() {
16188
17484
  },
16189
17485
  [deactivate, markSelected]
16190
17486
  );
16191
- const openLogoSizePanel = useCallback7((logoEl) => {
17487
+ const openLogoSizePanel = useCallback8((logoEl) => {
16192
17488
  const placement = getLogoPlacement(logoEl);
16193
17489
  const draft = readLogoSizeState(editContentRef.current, placement);
16194
17490
  setLogoSizeDraft(draft);
@@ -16201,7 +17497,7 @@ function OhhwellsBridge() {
16201
17497
  placement
16202
17498
  });
16203
17499
  }, []);
16204
- const openSocialsDisplayPanel = useCallback7((row) => {
17500
+ const openSocialsDisplayPanel = useCallback8((row) => {
16205
17501
  setParentScrollSnap(parentScrollRef.current);
16206
17502
  setFloatingPanel({
16207
17503
  key: "socials-display",
@@ -16211,11 +17507,11 @@ function OhhwellsBridge() {
16211
17507
  row
16212
17508
  });
16213
17509
  }, []);
16214
- const isEditModeRef = useRef9(false);
16215
- const requestMissingSocialIconsRef = useRef9(() => {
17510
+ const isEditModeRef = useRef10(false);
17511
+ const requestMissingSocialIconsRef = useRef10(() => {
16216
17512
  });
16217
- const askedSocialIconsRef = useRef9(/* @__PURE__ */ new Set());
16218
- const requestMissingSocialIcons = useCallback7(() => {
17513
+ const askedSocialIconsRef = useRef10(/* @__PURE__ */ new Set());
17514
+ const requestMissingSocialIcons = useCallback8(() => {
16219
17515
  const items = Array.from(document.querySelectorAll(`[${SOCIALS_ROW_ATTR}]`)).filter((row) => socialsDisplayFor(row, editContentRef.current).icon).flatMap((row) => {
16220
17516
  const missing = socialsMissingIcons(row);
16221
17517
  listSocialItems(row).forEach((item) => ensureIconSlot(item));
@@ -16227,7 +17523,7 @@ function OhhwellsBridge() {
16227
17523
  }, []);
16228
17524
  requestMissingSocialIconsRef.current = requestMissingSocialIcons;
16229
17525
  isEditModeRef.current = isEditMode;
16230
- const changeSocialsDisplay = useCallback7(
17526
+ const changeSocialsDisplay = useCallback8(
16231
17527
  (row, next) => {
16232
17528
  if (next.icon) {
16233
17529
  const missing = socialsMissingIcons(row);
@@ -16251,17 +17547,17 @@ function OhhwellsBridge() {
16251
17547
  },
16252
17548
  []
16253
17549
  );
16254
- const closeFloatingPanelOnly = useCallback7(() => {
17550
+ const closeFloatingPanelOnly = useCallback8(() => {
16255
17551
  setFloatingPanel(null);
16256
17552
  setLogoSizeDraft(null);
16257
17553
  }, []);
16258
17554
  closeFloatingPanelOnlyRef.current = closeFloatingPanelOnly;
16259
- const closeFloatingPanelAndDeselect = useCallback7(() => {
17555
+ const closeFloatingPanelAndDeselect = useCallback8(() => {
16260
17556
  setFloatingPanel(null);
16261
17557
  setLogoSizeDraft(null);
16262
17558
  deselectRef.current();
16263
17559
  }, []);
16264
- useEffect12(() => {
17560
+ useEffect13(() => {
16265
17561
  const session = floatingPanel && floatingPanel.kind === "logo-size" ? floatingPanel : null;
16266
17562
  if (!session || !logoSizeDraft) {
16267
17563
  postToParentRef.current({ type: "ow:logo-size-panel", open: false });
@@ -16280,7 +17576,7 @@ function OhhwellsBridge() {
16280
17576
  max: LOGO_SIZE_MAX
16281
17577
  });
16282
17578
  }, [floatingPanel, logoSizeDraft, editorViewport]);
16283
- const persistLogoSizeDraft = useCallback7(
17579
+ const persistLogoSizeDraft = useCallback8(
16284
17580
  (placement, draft) => {
16285
17581
  const desktopKey = LOGO_SIZE_DESKTOP_KEYS[placement];
16286
17582
  const mobileKey = LOGO_SIZE_MOBILE_KEYS[placement];
@@ -16320,7 +17616,7 @@ function OhhwellsBridge() {
16320
17616
  },
16321
17617
  [postToParent2]
16322
17618
  );
16323
- const activate = useCallback7((el, options) => {
17619
+ const activate = useCallback8((el, options) => {
16324
17620
  if (activeElRef.current === el) return;
16325
17621
  document.querySelectorAll("[data-ohw-hovered]").forEach((hovered) => {
16326
17622
  hovered.removeAttribute("data-ohw-hovered");
@@ -16418,8 +17714,8 @@ function OhhwellsBridge() {
16418
17714
  openLogoSizePanelRef.current = openLogoSizePanel;
16419
17715
  deselectRef.current = deselect;
16420
17716
  closeFloatingPanelOnlyRef.current = closeFloatingPanelOnly;
16421
- const lastSiteWideScopeRef = useRef9(null);
16422
- useEffect12(() => {
17717
+ const lastSiteWideScopeRef = useRef10(null);
17718
+ useEffect13(() => {
16423
17719
  if (!isEditMode) {
16424
17720
  if (lastSiteWideScopeRef.current !== false) {
16425
17721
  lastSiteWideScopeRef.current = false;
@@ -16452,15 +17748,31 @@ function OhhwellsBridge() {
16452
17748
  }
16453
17749
  const applyContent = (content) => {
16454
17750
  const imageLoads = [];
17751
+ if (typeof content[BRAND_KIT_KEY] === "string") {
17752
+ brandKitRef.current = content[BRAND_KIT_KEY];
17753
+ applyBrandToDom(parseBrandKit(content[BRAND_KIT_KEY]));
17754
+ } else {
17755
+ brandKitRef.current = "";
17756
+ applyBrandToDom(null);
17757
+ }
16455
17758
  if (typeof content[AI_SECTIONS_KEY] === "string") {
16456
17759
  aiSectionsRef.current = content[AI_SECTIONS_KEY];
17760
+ setAiSectionOrder(content[SECTION_ORDER_KEY], window.location.pathname);
16457
17761
  applyAiSectionsToDom(parseAiSectionsState(content[AI_SECTIONS_KEY]));
16458
17762
  }
17763
+ if (typeof content[STYLE_STORE_KEY] === "string") {
17764
+ stylesRef.current = content[STYLE_STORE_KEY];
17765
+ applyStylesToDom(parseStyleStore(content[STYLE_STORE_KEY]));
17766
+ }
17767
+ applyBrandChrome(content);
16459
17768
  for (const [key, val] of Object.entries(content)) {
16460
17769
  if (key === "__ohw_sections") continue;
16461
17770
  if (key === AI_SECTIONS_KEY) continue;
16462
17771
  if (key === LOGO_PLACEHOLDER_KEY) continue;
16463
17772
  if (LOGO_IMAGE_KEYS.includes(key)) continue;
17773
+ if (key === BRAND_KIT_KEY) continue;
17774
+ if (key === STYLE_STORE_KEY) continue;
17775
+ if (BRAND_CHROME_KEYS.has(key)) continue;
16464
17776
  if (applyVideoSettingNode(key, val)) continue;
16465
17777
  if (applyCarouselNode(key, val)) continue;
16466
17778
  document.querySelectorAll(`[data-ohw-key="${key}"]`).forEach((el) => {
@@ -16521,7 +17833,9 @@ function OhhwellsBridge() {
16521
17833
  let cancelled = false;
16522
17834
  setFetchState("loading");
16523
17835
  const apiUrl = process.env.NEXT_PUBLIC_FLOWOPS_API_URL ?? "https://flowops-backend-staging-2yfdh7pwpq-as.a.run.app";
16524
- fetch(`${apiUrl}/api/public/sites/${subdomain}/content`, { cache: "no-store" }).then((r2) => r2.ok ? r2.json() : null).then((data) => {
17836
+ const initialPath = pathname;
17837
+ fetchedContentPaths.add(`${subdomain}::${initialPath}`);
17838
+ fetch(`${apiUrl}/api/public/sites/${subdomain}/content?path=${encodeURIComponent(initialPath)}`, { cache: "no-store" }).then((r2) => r2.ok ? r2.json() : null).then((data) => {
16525
17839
  if (cancelled) return;
16526
17840
  const content = data?.content ?? {};
16527
17841
  contentCache.set(subdomain, content);
@@ -16534,7 +17848,7 @@ function OhhwellsBridge() {
16534
17848
  cancelled = true;
16535
17849
  };
16536
17850
  }, [subdomain, isEditMode]);
16537
- useEffect12(() => {
17851
+ useEffect13(() => {
16538
17852
  if (!isEditMode) return;
16539
17853
  const resolveIndex = (form, clientY) => {
16540
17854
  const wrappers = listFieldWrappers(form).filter((el) => fieldKeyOf(el) !== fieldDragRef.current?.key);
@@ -16575,7 +17889,7 @@ function OhhwellsBridge() {
16575
17889
  window.removeEventListener("drop", onDrop, true);
16576
17890
  };
16577
17891
  }, [buildFieldDropSlots, isEditMode, persistFields, selectField]);
16578
- useEffect12(() => {
17892
+ useEffect13(() => {
16579
17893
  if (!isEditMode) return;
16580
17894
  const mark = () => document.querySelectorAll('[data-ohw-editable="form"]').forEach((form) => {
16581
17895
  markFormFields(form);
@@ -16587,7 +17901,7 @@ function OhhwellsBridge() {
16587
17901
  });
16588
17902
  return () => observer.disconnect();
16589
17903
  }, [isEditMode, fetchState, pathname]);
16590
- useEffect12(() => {
17904
+ useEffect13(() => {
16591
17905
  if (!isEditMode) return;
16592
17906
  let saveTimer = null;
16593
17907
  const onInput = (e) => {
@@ -16609,14 +17923,14 @@ function OhhwellsBridge() {
16609
17923
  document.addEventListener("input", onInput, true);
16610
17924
  return () => document.removeEventListener("input", onInput, true);
16611
17925
  }, [isEditMode, persistFields]);
16612
- useEffect12(() => {
17926
+ useEffect13(() => {
16613
17927
  if (isEditMode || fetchState !== "done") return;
16614
17928
  const content = contentCache.get(subdomain) ?? {};
16615
17929
  document.querySelectorAll('[data-ohw-editable="form"]').forEach((form) => {
16616
17930
  reconcileFieldsFromContent(form, content);
16617
17931
  });
16618
17932
  }, [isEditMode, fetchState, subdomain]);
16619
- useEffect12(() => {
17933
+ useEffect13(() => {
16620
17934
  if (!isEditMode) return;
16621
17935
  const swallow = (e) => {
16622
17936
  const target = e.target;
@@ -16625,12 +17939,12 @@ function OhhwellsBridge() {
16625
17939
  document.addEventListener("submit", swallow, true);
16626
17940
  return () => document.removeEventListener("submit", swallow, true);
16627
17941
  }, [isEditMode]);
16628
- useEffect12(() => {
17942
+ useEffect13(() => {
16629
17943
  if (isEditMode || fetchState !== "done") return;
16630
17944
  const apiUrl = process.env.NEXT_PUBLIC_FLOWOPS_API_URL ?? "https://flowops-backend-staging-2yfdh7pwpq-as.a.run.app";
16631
17945
  bindPublishedForms(apiUrl, subdomain, contentCache.get(subdomain) ?? {});
16632
17946
  }, [isEditMode, fetchState, subdomain]);
16633
- useEffect12(() => {
17947
+ useEffect13(() => {
16634
17948
  if (!subdomain || isEditMode) return;
16635
17949
  let debounceTimer = null;
16636
17950
  let observer = null;
@@ -16641,10 +17955,28 @@ function OhhwellsBridge() {
16641
17955
  initSectionInstancesFromContent(content, window.location.pathname);
16642
17956
  observer?.disconnect();
16643
17957
  try {
17958
+ applyBrandChrome(content);
17959
+ if (typeof content[BRAND_KIT_KEY] === "string") {
17960
+ applyBrandToDom(parseBrandKit(content[BRAND_KIT_KEY]));
17961
+ } else {
17962
+ applyBrandToDom(null);
17963
+ }
17964
+ if (typeof content[AI_SECTIONS_KEY] === "string") {
17965
+ setAiSectionOrder(content[SECTION_ORDER_KEY], window.location.pathname);
17966
+ applyAiSectionsToDom(parseAiSectionsState(content[AI_SECTIONS_KEY]));
17967
+ }
17968
+ if (typeof content[STYLE_STORE_KEY] === "string") {
17969
+ applyStylesToDom(parseStyleStore(content[STYLE_STORE_KEY]));
17970
+ }
16644
17971
  for (const [key, val] of Object.entries(content)) {
16645
17972
  if (key === "__ohw_sections") continue;
17973
+ if (key === AI_SECTIONS_KEY) continue;
16646
17974
  if (key === LOGO_PLACEHOLDER_KEY) continue;
16647
17975
  if (LOGO_IMAGE_KEYS.includes(key)) continue;
17976
+ if (key === BRAND_KIT_KEY) continue;
17977
+ if (key === STYLE_STORE_KEY) continue;
17978
+ if (key === STYLE_STORE_KEY) continue;
17979
+ if (BRAND_CHROME_KEYS.has(key)) continue;
16648
17980
  if (applyVideoSettingNode(key, val)) continue;
16649
17981
  if (applyCarouselNode(key, val)) continue;
16650
17982
  document.querySelectorAll(`[data-ohw-key="${key}"]`).forEach((el) => {
@@ -16690,6 +18022,17 @@ function OhhwellsBridge() {
16690
18022
  debounceTimer = setTimeout(applyFromCache, 150);
16691
18023
  };
16692
18024
  applyFromCache();
18025
+ const pathCacheKey = `${subdomain}::${pathname}`;
18026
+ if (!fetchedContentPaths.has(pathCacheKey)) {
18027
+ fetchedContentPaths.add(pathCacheKey);
18028
+ const apiUrl = process.env.NEXT_PUBLIC_FLOWOPS_API_URL ?? "https://flowops-backend-staging-2yfdh7pwpq-as.a.run.app";
18029
+ fetch(`${apiUrl}/api/public/sites/${subdomain}/content?path=${encodeURIComponent(pathname)}`, { cache: "no-store" }).then((r2) => r2.ok ? r2.json() : null).then((data) => {
18030
+ if (!data?.content) return;
18031
+ contentCache.set(subdomain, data.content);
18032
+ applyFromCache();
18033
+ }).catch(() => {
18034
+ });
18035
+ }
16693
18036
  observer = new MutationObserver(scheduleApply);
16694
18037
  observer.observe(document.body, { childList: true, subtree: true });
16695
18038
  return () => {
@@ -16703,10 +18046,10 @@ function OhhwellsBridge() {
16703
18046
  const visible = Boolean(subdomain) && fetchState !== "done";
16704
18047
  el.style.display = visible ? "flex" : "none";
16705
18048
  }, [subdomain, fetchState]);
16706
- useEffect12(() => {
18049
+ useEffect13(() => {
16707
18050
  postToParent2({ type: "ow:navigation", path: pathname });
16708
18051
  }, [pathname, postToParent2]);
16709
- useEffect12(() => {
18052
+ useEffect13(() => {
16710
18053
  if (!isEditMode) return;
16711
18054
  if (linkPopoverSessionRef.current?.intent === "add-nav") return;
16712
18055
  if (document.querySelector("[data-ohw-section-picker]")) return;
@@ -16714,7 +18057,7 @@ function OhhwellsBridge() {
16714
18057
  deselectRef.current();
16715
18058
  deactivateRef.current();
16716
18059
  }, [pathname, isEditMode]);
16717
- useEffect12(() => {
18060
+ useEffect13(() => {
16718
18061
  const contentForNav = () => {
16719
18062
  if (isEditMode) return editContentRef.current;
16720
18063
  if (!subdomain) return {};
@@ -16783,35 +18126,36 @@ function OhhwellsBridge() {
16783
18126
  observer?.disconnect();
16784
18127
  };
16785
18128
  }, [isEditMode, pathname, subdomain, fetchState, resyncSelectedNavigationItem]);
16786
- useEffect12(() => {
18129
+ useEffect13(() => {
16787
18130
  if (!isEditMode) return;
18131
+ let lastPosted = 0;
16788
18132
  const measure = () => {
16789
18133
  const h = document.body.scrollHeight;
16790
- if (h > 50) postToParent2({ type: "ow:height", height: h });
18134
+ if (h > 50 && Math.abs(h - lastPosted) > 1) {
18135
+ lastPosted = h;
18136
+ postToParent2({ type: "ow:height", height: h });
18137
+ }
18138
+ };
18139
+ let raf = null;
18140
+ const schedule = () => {
18141
+ if (raf != null) return;
18142
+ raf = requestAnimationFrame(() => {
18143
+ raf = null;
18144
+ measure();
18145
+ });
16791
18146
  };
16792
18147
  const t1 = setTimeout(measure, 50);
16793
18148
  const t2 = setTimeout(measure, 500);
16794
- let lastWidth = window.innerWidth;
16795
- let resizeTimers = [];
16796
- const clearResizeTimers = () => {
16797
- resizeTimers.forEach(clearTimeout);
16798
- resizeTimers = [];
16799
- };
16800
- const handleResize = () => {
16801
- if (window.innerWidth === lastWidth) return;
16802
- lastWidth = window.innerWidth;
16803
- clearResizeTimers();
16804
- resizeTimers = HEIGHT_SETTLE_DELAYS.map((delay) => setTimeout(measure, delay));
16805
- };
16806
- window.addEventListener("resize", handleResize);
18149
+ const ro = new ResizeObserver(schedule);
18150
+ ro.observe(document.body);
16807
18151
  return () => {
16808
18152
  clearTimeout(t1);
16809
18153
  clearTimeout(t2);
16810
- clearResizeTimers();
16811
- window.removeEventListener("resize", handleResize);
18154
+ if (raf != null) cancelAnimationFrame(raf);
18155
+ ro.disconnect();
16812
18156
  };
16813
18157
  }, [pathname, isEditMode, postToParent2]);
16814
- useEffect12(() => {
18158
+ useEffect13(() => {
16815
18159
  if (!subdomainFromQuery || isEditMode) return;
16816
18160
  const handleClick = (e) => {
16817
18161
  const anchor = e.target.closest("a");
@@ -16827,7 +18171,7 @@ function OhhwellsBridge() {
16827
18171
  document.addEventListener("click", handleClick, true);
16828
18172
  return () => document.removeEventListener("click", handleClick, true);
16829
18173
  }, [subdomainFromQuery, isEditMode, router]);
16830
- useEffect12(() => {
18174
+ useEffect13(() => {
16831
18175
  if (!isEditMode) {
16832
18176
  editStylesRef.current?.base.remove();
16833
18177
  editStylesRef.current?.forceHover.remove();
@@ -17049,6 +18393,7 @@ function OhhwellsBridge() {
17049
18393
  return;
17050
18394
  }
17051
18395
  const target = e.target;
18396
+ if (target.closest("[data-ohw-ai-review]")) return;
17052
18397
  if (target.closest("[data-ohw-toolbar]")) return;
17053
18398
  if (target.closest("[data-ohw-state-toggle]")) return;
17054
18399
  if (target.closest("[data-ohw-max-badge]")) return;
@@ -17060,6 +18405,9 @@ function OhhwellsBridge() {
17060
18405
  )) {
17061
18406
  return;
17062
18407
  }
18408
+ if (selectedMediaElRef.current && !selectedMediaElRef.current.contains(target) && !target.closest("[data-ohw-media-overlay], [data-ohw-edit-chrome], [data-ohw-item-interaction]")) {
18409
+ clearMediaSelectionRef.current();
18410
+ }
17063
18411
  {
17064
18412
  const formEl = getFormElement(target);
17065
18413
  const onSuccessText = target.closest(`[${SUCCESS_TEXT_ATTR}]`);
@@ -17214,8 +18562,11 @@ function OhhwellsBridge() {
17214
18562
  if (isMediaEditable(editable) && !buttonOnMedia) {
17215
18563
  e.preventDefault();
17216
18564
  e.stopPropagation();
17217
- aiSectionApiRef.current?.selectFromElement(editable);
17218
- postToParentRef.current({ type: "ow:image-pick", key: editable.dataset.ohwKey ?? "", elementType: editable.dataset.ohwEditable ?? "image" });
18565
+ if (selectedMediaElRef.current === editable) {
18566
+ postToParentRef.current({ type: "ow:image-pick", key: editable.dataset.ohwKey ?? "", elementType: editable.dataset.ohwEditable ?? "image" });
18567
+ } else {
18568
+ selectMediaElementRef.current(editable);
18569
+ }
17219
18570
  return;
17220
18571
  }
17221
18572
  const socialItem = getSocialItem(editable);
@@ -17351,6 +18702,7 @@ function OhhwellsBridge() {
17351
18702
  };
17352
18703
  const handleDblClick = (e) => {
17353
18704
  const target = e.target;
18705
+ if (target.closest("[data-ohw-ai-review]")) return;
17354
18706
  if (target.closest("[data-ohw-toolbar]")) return;
17355
18707
  if (target.closest("[data-ohw-state-toggle]")) return;
17356
18708
  if (target.closest("[data-ohw-max-badge]")) return;
@@ -17402,6 +18754,9 @@ function OhhwellsBridge() {
17402
18754
  setHoveredItemRect(null);
17403
18755
  hoveredNavContainerRef.current = null;
17404
18756
  setHoveredNavContainerRect(null);
18757
+ siblingHintElRef.current = null;
18758
+ setSiblingHintRect(null);
18759
+ setSiblingHintRects([]);
17405
18760
  return;
17406
18761
  }
17407
18762
  {
@@ -17520,7 +18875,6 @@ function OhhwellsBridge() {
17520
18875
  hoveredNavContainerRef.current = null;
17521
18876
  setHoveredNavContainerRect(null);
17522
18877
  hoveredItemElRef.current = editable;
17523
- setHoveredItemRect(isSelectedForHoverRef.current(editable) ? null : editable.getBoundingClientRect());
17524
18878
  }
17525
18879
  }
17526
18880
  }
@@ -17817,7 +19171,7 @@ function OhhwellsBridge() {
17817
19171
  }
17818
19172
  };
17819
19173
  const probeImageAt = (clientX, clientY, isDragOver = false, fromParentViewport = false) => {
17820
- if (linkPopoverOpenRef.current) {
19174
+ if (linkPopoverOpenRef.current || floatingPanelOpenRef.current && isPointOverFloatingPanel(clientX, clientY)) {
17821
19175
  if (hoveredImageRef.current) {
17822
19176
  hoveredImageRef.current = null;
17823
19177
  hoveredImageHasTextOverlapRef.current = false;
@@ -18151,7 +19505,9 @@ function OhhwellsBridge() {
18151
19505
  return;
18152
19506
  }
18153
19507
  const { y } = toProbeCoords(clientX, clientY, fromParentViewport);
18154
- const sections = Array.from(document.querySelectorAll("[data-ohw-section]")).sort((a, b) => a.getBoundingClientRect().top - b.getBoundingClientRect().top);
19508
+ const sections = Array.from(document.querySelectorAll("[data-ohw-section]")).filter(
19509
+ (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
19510
+ ).sort((a, b) => a.getBoundingClientRect().top - b.getBoundingClientRect().top);
18155
19511
  const ZONE = 20;
18156
19512
  for (let i = 0; i < sections.length; i++) {
18157
19513
  const a = sections[i];
@@ -18180,8 +19536,7 @@ function OhhwellsBridge() {
18180
19536
  };
18181
19537
  const handleMouseMove = (e) => {
18182
19538
  const { clientX, clientY } = e;
18183
- if (pointOwnedByFloatingPanel(clientX, clientY)) return;
18184
- if (isOverEditorChrome(clientX, clientY)) {
19539
+ if (document.documentElement.hasAttribute("data-ohw-panel-dragging") || floatingPanelOpenRef.current && isPointOverFloatingPanel(clientX, clientY) || isOverEditorChrome(clientX, clientY)) {
18185
19540
  document.querySelectorAll("[data-ohw-hovered]").forEach((el) => el.removeAttribute("data-ohw-hovered"));
18186
19541
  formHoverElRef.current = null;
18187
19542
  setFormHoverRect(null);
@@ -18189,6 +19544,12 @@ function OhhwellsBridge() {
18189
19544
  setHoveredItemRect(null);
18190
19545
  hoveredNavContainerRef.current = null;
18191
19546
  setHoveredNavContainerRect(null);
19547
+ siblingHintElRef.current = null;
19548
+ setSiblingHintRect(null);
19549
+ setSiblingHintRects([]);
19550
+ dismissImageHover();
19551
+ clearImageHover();
19552
+ setSectionGap(null);
18192
19553
  return;
18193
19554
  }
18194
19555
  if (probeSocialsRowAt(clientX, clientY)) return;
@@ -18200,7 +19561,11 @@ function OhhwellsBridge() {
18200
19561
  if (e.data?.type !== "ow:pointer-sync") return;
18201
19562
  const { clientX, clientY } = e.data;
18202
19563
  if (typeof clientX !== "number" || typeof clientY !== "number") return;
18203
- if (pointOwnedByFloatingPanel(clientX, clientY)) return;
19564
+ if (document.documentElement.hasAttribute("data-ohw-panel-dragging") || floatingPanelOpenRef.current && isPointOverFloatingPanel(clientX, clientY)) {
19565
+ dismissImageHover();
19566
+ clearImageHover();
19567
+ return;
19568
+ }
18204
19569
  if (probeSocialsRowAt(clientX, clientY)) return;
18205
19570
  probeSectionGapAt(clientX, clientY);
18206
19571
  probeImageAt(clientX, clientY);
@@ -18475,7 +19840,7 @@ function OhhwellsBridge() {
18475
19840
  timers.set(key, setTimeout(() => {
18476
19841
  timers.delete(key);
18477
19842
  postToParentRef.current({ type: "ow:change", nodes: [{ key, text: html }] });
18478
- const h = document.documentElement.scrollHeight;
19843
+ const h = document.body.scrollHeight;
18479
19844
  if (h > 50) postToParentRef.current({ type: "ow:height", height: h });
18480
19845
  }, 400));
18481
19846
  };
@@ -18483,10 +19848,23 @@ function OhhwellsBridge() {
18483
19848
  if (e.data?.type !== "ow:hydrate") return;
18484
19849
  const content = e.data.content;
18485
19850
  if (!content) return;
19851
+ if (typeof content[BRAND_KIT_KEY] === "string") {
19852
+ brandKitRef.current = content[BRAND_KIT_KEY];
19853
+ applyBrandToDom(parseBrandKit(content[BRAND_KIT_KEY]));
19854
+ } else {
19855
+ brandKitRef.current = "";
19856
+ applyBrandToDom(null);
19857
+ }
18486
19858
  if (typeof content[AI_SECTIONS_KEY] === "string") {
18487
19859
  aiSectionsRef.current = content[AI_SECTIONS_KEY];
19860
+ setAiSectionOrder(content[SECTION_ORDER_KEY], window.location.pathname);
18488
19861
  applyAiSectionsToDom(parseAiSectionsState(content[AI_SECTIONS_KEY]));
18489
19862
  }
19863
+ if (typeof content[STYLE_STORE_KEY] === "string") {
19864
+ stylesRef.current = content[STYLE_STORE_KEY];
19865
+ applyStylesToDom(parseStyleStore(content[STYLE_STORE_KEY]));
19866
+ }
19867
+ applyBrandChrome(content);
18490
19868
  let sectionsJson = null;
18491
19869
  for (const [key, val] of Object.entries(content)) {
18492
19870
  if (key === "__ohw_sections") {
@@ -18496,6 +19874,9 @@ function OhhwellsBridge() {
18496
19874
  if (key === AI_SECTIONS_KEY) continue;
18497
19875
  if (key === LOGO_PLACEHOLDER_KEY) continue;
18498
19876
  if (LOGO_IMAGE_KEYS.includes(key) && !String(val ?? "").trim()) continue;
19877
+ if (key === BRAND_KIT_KEY) continue;
19878
+ if (key === STYLE_STORE_KEY) continue;
19879
+ if (BRAND_CHROME_KEYS.has(key)) continue;
18499
19880
  if (applyVideoSettingNode(key, val)) continue;
18500
19881
  if (applyCarouselNode(key, val)) continue;
18501
19882
  document.querySelectorAll(`[data-ohw-key="${key}"]`).forEach((el) => {
@@ -18509,6 +19890,8 @@ function OhhwellsBridge() {
18509
19890
  if (video && video.src !== val) applyVideoSrc(video, val);
18510
19891
  } else if (el.dataset.ohwEditable === "link") {
18511
19892
  applyLinkHref(el, val);
19893
+ } else if (el.dataset.ohwEditable === "icon") {
19894
+ applyIconMarkup(el, val);
18512
19895
  } else if (isIconMarkupValue(val)) {
18513
19896
  } else {
18514
19897
  el.innerHTML = val;
@@ -18529,7 +19912,7 @@ function OhhwellsBridge() {
18529
19912
  reconcileFooterOrderFromContent(editContentRef.current);
18530
19913
  syncNavigationDragCursorAttrs();
18531
19914
  enforceLinkHrefs();
18532
- const hydratedHeight = document.documentElement.scrollHeight;
19915
+ const hydratedHeight = document.body.scrollHeight;
18533
19916
  if (hydratedHeight > 50) postToParentRef.current({ type: "ow:height", height: hydratedHeight });
18534
19917
  postToParentRef.current({ type: "ow:hydrate-done" });
18535
19918
  };
@@ -18593,16 +19976,25 @@ function OhhwellsBridge() {
18593
19976
  nodes: collectEditableNodes(editContentRef.current)
18594
19977
  });
18595
19978
  };
19979
+ const clearInteractionChrome = () => {
19980
+ deactivateRef.current();
19981
+ deselectRef.current();
19982
+ clearMediaSelectionRef.current();
19983
+ };
18596
19984
  const handleAiApplyTree = (e) => {
18597
19985
  if (e.data?.type !== "ow:ai-apply-tree") return;
18598
19986
  const payload = e.data.payload;
18599
19987
  if (!payload || typeof payload.id !== "string" || !isRenderableTree(payload.tree)) return;
19988
+ clearInteractionChrome();
18600
19989
  const previous = aiSectionsRef.current;
18601
- const nextState = applyTreeToState(parseAiSectionsState(previous), payload);
19990
+ const nextState = applyTreeToState(parseAiSectionsState(previous), {
19991
+ ...payload,
19992
+ path: payload.path ?? window.location.pathname
19993
+ });
18602
19994
  const nextValue = serializeAiSectionsState(nextState);
18603
19995
  aiSectionsRef.current = nextValue;
18604
19996
  applyAiSectionsToDom(nextState);
18605
- const newHeight = document.documentElement.scrollHeight;
19997
+ const newHeight = document.body.scrollHeight;
18606
19998
  if (newHeight > 50) postToParentRef.current({ type: "ow:height", height: newHeight });
18607
19999
  postToParentRef.current({ type: "ow:change", nodes: [{ key: AI_SECTIONS_KEY, text: nextValue }] });
18608
20000
  const appliedEl = document.querySelector(`[data-ohw-section="${CSS.escape(payload.id)}"]`);
@@ -18619,12 +20011,13 @@ function OhhwellsBridge() {
18619
20011
  const exists = document.querySelector(`[data-ohw-section="${CSS.escape(sectionId)}"]`);
18620
20012
  if (!exists) return;
18621
20013
  if (isPageFrameSection(exists)) return;
20014
+ clearInteractionChrome();
18622
20015
  const previous = aiSectionsRef.current;
18623
20016
  const nextState = deleteSectionFromState(parseAiSectionsState(previous), sectionId);
18624
20017
  const nextValue = serializeAiSectionsState(nextState);
18625
20018
  aiSectionsRef.current = nextValue;
18626
20019
  applyAiSectionsToDom(nextState);
18627
- const newHeight = document.documentElement.scrollHeight;
20020
+ const newHeight = document.body.scrollHeight;
18628
20021
  if (newHeight > 50) postToParentRef.current({ type: "ow:height", height: newHeight });
18629
20022
  postToParentRef.current({ type: "ow:change", nodes: [{ key: AI_SECTIONS_KEY, text: nextValue }] });
18630
20023
  postToParentRef.current({ type: "ow:ai-section-deleted", sectionId, previous, value: nextValue });
@@ -18634,20 +20027,107 @@ function OhhwellsBridge() {
18634
20027
  const handleAiSetSections = (e) => {
18635
20028
  if (e.data?.type !== "ow:ai-set-sections") return;
18636
20029
  const value = typeof e.data.value === "string" ? e.data.value : "";
20030
+ clearInteractionChrome();
18637
20031
  aiSectionsRef.current = value;
18638
20032
  applyAiSectionsToDom(parseAiSectionsState(value));
18639
- const restoredHeight = document.documentElement.scrollHeight;
20033
+ applyStylesToDom(parseStyleStore(stylesRef.current));
20034
+ const restoredHeight = document.body.scrollHeight;
18640
20035
  if (restoredHeight > 50) postToParentRef.current({ type: "ow:height", height: restoredHeight });
18641
20036
  postToParentRef.current({ type: "ow:change", nodes: [{ key: AI_SECTIONS_KEY, text: value }] });
18642
20037
  postAiSectionsChanged();
18643
20038
  };
18644
20039
  window.addEventListener("message", handleAiSetSections);
20040
+ const handleMoveSection = (e) => {
20041
+ if (e.data?.type !== "ow:move-section") return;
20042
+ const instanceId = typeof e.data.instanceId === "string" ? e.data.instanceId : "";
20043
+ const direction = e.data.direction === "up" || e.data.direction === "down" ? e.data.direction : null;
20044
+ if (!instanceId || !direction) return;
20045
+ const entries = moveSectionInstance(instanceId, direction, window.location.pathname);
20046
+ if (!entries) return;
20047
+ const orderJson = JSON.stringify(entries);
20048
+ editContentRef.current = { ...editContentRef.current, [SECTION_ORDER_KEY]: orderJson };
20049
+ setAiSectionOrder(orderJson, window.location.pathname);
20050
+ postToParentRef.current({ type: "ow:change", nodes: [{ key: SECTION_ORDER_KEY, text: orderJson }] });
20051
+ window.dispatchEvent(new Event("resize"));
20052
+ };
20053
+ window.addEventListener("message", handleMoveSection);
20054
+ const handleAiSetBrand = (e) => {
20055
+ if (e.data?.type !== "ow:ai-set-brand") return;
20056
+ const value = typeof e.data.value === "string" ? e.data.value : "";
20057
+ const previous = brandKitRef.current;
20058
+ brandKitRef.current = value;
20059
+ applyBrandToDom(parseBrandKit(value));
20060
+ if (aiSectionsRef.current) applyAiSectionsToDom(parseAiSectionsState(aiSectionsRef.current));
20061
+ applyStylesToDom(parseStyleStore(stylesRef.current));
20062
+ postToParentRef.current({ type: "ow:change", nodes: [{ key: BRAND_KIT_KEY, text: value }] });
20063
+ postToParentRef.current({ type: "ow:ai-brand-applied", previous, value });
20064
+ };
20065
+ window.addEventListener("message", handleAiSetBrand);
20066
+ const handleAiSetStyles = (e) => {
20067
+ if (e.data?.type !== "ow:ai-set-styles") return;
20068
+ const value = typeof e.data.value === "string" ? e.data.value : "";
20069
+ const previous = stylesRef.current;
20070
+ stylesRef.current = value;
20071
+ applyStylesToDom(parseStyleStore(value));
20072
+ postToParentRef.current({ type: "ow:change", nodes: [{ key: STYLE_STORE_KEY, text: value }] });
20073
+ postToParentRef.current({ type: "ow:ai-styles-applied", previous, value });
20074
+ };
20075
+ window.addEventListener("message", handleAiSetStyles);
20076
+ const handleGetBrand = (e) => {
20077
+ if (e.data?.type !== "ow:get-brand") return;
20078
+ const template = deriveTemplateBrand();
20079
+ const value = brandKitRef.current || (template ? JSON.stringify(template) : "");
20080
+ postToParentRef.current({ type: "ow:brand-value", value });
20081
+ };
20082
+ window.addEventListener("message", handleGetBrand);
18645
20083
  const handlePanelDragging = (e) => {
18646
20084
  if (e.data?.type !== "ow:panel-dragging") return;
18647
20085
  if (e.data.dragging) document.documentElement.setAttribute("data-ohw-panel-dragging", "");
18648
20086
  else document.documentElement.removeAttribute("data-ohw-panel-dragging");
18649
20087
  };
18650
20088
  window.addEventListener("message", handlePanelDragging);
20089
+ const handleDeleteSection = (e) => {
20090
+ if (e.data?.type !== "ow:delete-section") return;
20091
+ const instanceId = typeof e.data.instanceId === "string" ? e.data.instanceId : "";
20092
+ if (!instanceId) return;
20093
+ const currentEntries = getPageSectionOrderEntries(editContentRef.current[SECTION_ORDER_KEY], window.location.pathname);
20094
+ const entries = deleteSectionInstance(instanceId, window.location.pathname, currentEntries);
20095
+ if (!entries) return;
20096
+ const orderJson = JSON.stringify(entries);
20097
+ editContentRef.current = { ...editContentRef.current, [SECTION_ORDER_KEY]: orderJson };
20098
+ postToParentRef.current({ type: "ow:change", nodes: [{ key: SECTION_ORDER_KEY, text: orderJson }] });
20099
+ aiSectionApiRef.current?.clear();
20100
+ window.dispatchEvent(new Event("resize"));
20101
+ const deleteHeight = document.body.scrollHeight;
20102
+ if (deleteHeight > 50) postToParentRef.current({ type: "ow:height", height: deleteHeight });
20103
+ const actionId = newInstanceId();
20104
+ pendingDeleteUndoRef.current = {
20105
+ actionId,
20106
+ restore: () => {
20107
+ const restoredEntries = getPageSectionOrderEntries(
20108
+ editContentRef.current[SECTION_ORDER_KEY],
20109
+ window.location.pathname
20110
+ );
20111
+ const restored = restoreSectionInstance(instanceId, window.location.pathname, restoredEntries);
20112
+ if (!restored) return;
20113
+ const restoredJson = JSON.stringify(restored);
20114
+ editContentRef.current = { ...editContentRef.current, [SECTION_ORDER_KEY]: restoredJson };
20115
+ postToParentRef.current({ type: "ow:change", nodes: [{ key: SECTION_ORDER_KEY, text: restoredJson }] });
20116
+ window.dispatchEvent(new Event("resize"));
20117
+ const restoreHeight = document.body.scrollHeight;
20118
+ if (restoreHeight > 50) postToParentRef.current({ type: "ow:height", height: restoreHeight });
20119
+ }
20120
+ };
20121
+ postToParentRef.current({
20122
+ type: "ow:toast",
20123
+ title: "Section deleted",
20124
+ toastType: "success",
20125
+ actionLabel: "Undo",
20126
+ actionId,
20127
+ duration: 6e3
20128
+ });
20129
+ };
20130
+ window.addEventListener("message", handleDeleteSection);
18651
20131
  const handleDeactivate = (e) => {
18652
20132
  if (e.data?.type !== "ow:deactivate") return;
18653
20133
  if (document.documentElement.hasAttribute("data-ohw-panel-dragging")) return;
@@ -18657,8 +20137,15 @@ function OhhwellsBridge() {
18657
20137
  closeLinkPopoverRef.current();
18658
20138
  return;
18659
20139
  }
20140
+ if (floatingPanelOpenRef.current) {
20141
+ setFloatingPanelRef.current(null);
20142
+ deselectRef.current();
20143
+ deactivateRef.current();
20144
+ return;
20145
+ }
18660
20146
  deselectRef.current();
18661
20147
  deactivateRef.current();
20148
+ clearMediaSelectionRef.current();
18662
20149
  };
18663
20150
  window.addEventListener("message", handleDeactivate);
18664
20151
  const handleToastAction = (e) => {
@@ -18744,6 +20231,10 @@ function OhhwellsBridge() {
18744
20231
  const handleKeyDown = (e) => {
18745
20232
  if (e.key === "Escape" && document.querySelector("[data-ohw-section-picker]")) return;
18746
20233
  if (e.key === "Escape" && document.querySelector("[data-ohw-more-menu]")) return;
20234
+ if (e.key === "Escape" && selectedMediaElRef.current) {
20235
+ clearMediaSelectionRef.current();
20236
+ return;
20237
+ }
18747
20238
  if ((e.ctrlKey || e.metaKey) && e.key.toLowerCase() === "a" && activeElRef.current) {
18748
20239
  e.preventDefault();
18749
20240
  selectAllTextInEditable(activeElRef.current);
@@ -18903,6 +20394,12 @@ function OhhwellsBridge() {
18903
20394
  if (aiSectionsRef.current) {
18904
20395
  nodes.push({ key: AI_SECTIONS_KEY, type: "sections", text: aiSectionsRef.current });
18905
20396
  }
20397
+ if (stylesRef.current) {
20398
+ nodes.push({ key: STYLE_STORE_KEY, type: "styles", text: stylesRef.current });
20399
+ }
20400
+ if (brandKitRef.current) {
20401
+ nodes.push({ key: BRAND_KIT_KEY, type: "brand", text: brandKitRef.current });
20402
+ }
18906
20403
  document.querySelectorAll('[data-ohw-editable="form"]').forEach((form) => {
18907
20404
  const formKey = formKeyOf(form);
18908
20405
  if (!formKey) return;
@@ -18920,8 +20417,12 @@ function OhhwellsBridge() {
18920
20417
  if (inserted) {
18921
20418
  const tracker = getSectionsTracker();
18922
20419
  postToParentRef.current({ type: "ow:change", nodes: [{ key: "__ohw_sections", text: tracker.textContent ?? "[]" }] });
18923
- const h = document.documentElement.scrollHeight;
18924
- if (h > 50) postToParentRef.current({ type: "ow:height", height: h });
20420
+ const reportHeight = () => {
20421
+ const h = document.body.scrollHeight;
20422
+ if (h > 50) postToParentRef.current({ type: "ow:height", height: h });
20423
+ };
20424
+ reportHeight();
20425
+ setTimeout(reportHeight, 500);
18925
20426
  }
18926
20427
  };
18927
20428
  const handleSwitchSchedule = (e) => {
@@ -18962,7 +20463,7 @@ function OhhwellsBridge() {
18962
20463
  const updated = sections.filter((s) => !(s.type === "scheduling" && s.pagePath === currentPath));
18963
20464
  tracker.textContent = JSON.stringify(updated);
18964
20465
  postToParentRef.current({ type: "ow:change", nodes: [{ key: "__ohw_sections", text: tracker.textContent }] });
18965
- const h = document.documentElement.scrollHeight;
20466
+ const h = document.body.scrollHeight;
18966
20467
  if (h > 50) postToParentRef.current({ type: "ow:height", height: h });
18967
20468
  };
18968
20469
  const handleCollectSection = (e) => {
@@ -19317,19 +20818,24 @@ function OhhwellsBridge() {
19317
20818
  window.removeEventListener("message", handleAiApplyTree);
19318
20819
  window.removeEventListener("message", handleAiDeleteSection);
19319
20820
  window.removeEventListener("message", handleAiSetSections);
20821
+ window.removeEventListener("message", handleMoveSection);
20822
+ window.removeEventListener("message", handleAiSetBrand);
20823
+ window.removeEventListener("message", handleAiSetStyles);
20824
+ window.removeEventListener("message", handleGetBrand);
19320
20825
  window.removeEventListener("message", handlePanelDragging);
20826
+ window.removeEventListener("message", handleDeleteSection);
19321
20827
  window.removeEventListener("message", handleDeactivate);
19322
- document.documentElement.removeAttribute("data-ohw-panel-dragging");
19323
20828
  window.removeEventListener("message", handleToastAction);
19324
20829
  window.removeEventListener("message", handleFormCount);
19325
20830
  window.removeEventListener("message", handleUiEscape);
20831
+ document.documentElement.removeAttribute("data-ohw-panel-dragging");
19326
20832
  autoSaveTimers.current.forEach(clearTimeout);
19327
20833
  autoSaveTimers.current.clear();
19328
20834
  if (imageUnhoverTimerRef.current) clearTimeout(imageUnhoverTimerRef.current);
19329
20835
  if (imageShowTimerRef.current) clearTimeout(imageShowTimerRef.current);
19330
20836
  };
19331
20837
  }, [isEditMode, refreshStateRules]);
19332
- useEffect12(() => {
20838
+ useEffect13(() => {
19333
20839
  if (!isEditMode) return;
19334
20840
  const THRESHOLD = 10;
19335
20841
  const resolveWasSelected = (el) => {
@@ -19485,7 +20991,7 @@ function OhhwellsBridge() {
19485
20991
  unlockFooterDragInteraction();
19486
20992
  };
19487
20993
  }, [isEditMode]);
19488
- useEffect12(() => {
20994
+ useEffect13(() => {
19489
20995
  const handler = (e) => {
19490
20996
  if (e.data?.type !== "ow:request-schedule-config") return;
19491
20997
  const insertAfterVal = e.data.insertAfter;
@@ -19501,7 +21007,7 @@ function OhhwellsBridge() {
19501
21007
  window.addEventListener("message", handler);
19502
21008
  return () => window.removeEventListener("message", handler);
19503
21009
  }, [processConfigRequest]);
19504
- useEffect12(() => {
21010
+ useEffect13(() => {
19505
21011
  if (!isEditMode) return;
19506
21012
  document.querySelectorAll("[data-ohw-active-state]").forEach((el) => {
19507
21013
  el.removeAttribute("data-ohw-active-state");
@@ -19525,7 +21031,7 @@ function OhhwellsBridge() {
19525
21031
  postToParent2({
19526
21032
  type: "ow:ready",
19527
21033
  version: "1",
19528
- bridgeVersion: "0.1.72",
21034
+ bridgeVersion: "0.1.73",
19529
21035
  path: pathname,
19530
21036
  nodes: collectEditableNodes(editContentRef.current),
19531
21037
  sections
@@ -19537,13 +21043,13 @@ function OhhwellsBridge() {
19537
21043
  clearTimeout(timer);
19538
21044
  };
19539
21045
  }, [pathname, isEditMode, refreshStateRules, postToParent2]);
19540
- useEffect12(() => {
21046
+ useEffect13(() => {
19541
21047
  scrollToHashSectionWhenReady();
19542
21048
  const onHashChange = () => scrollToHashSectionWhenReady();
19543
21049
  window.addEventListener("hashchange", onHashChange);
19544
21050
  return () => window.removeEventListener("hashchange", onHashChange);
19545
21051
  }, [pathname]);
19546
- const handleCommand = useCallback7((cmd) => {
21052
+ const handleCommand = useCallback8((cmd) => {
19547
21053
  const el = activeElRef.current;
19548
21054
  const selBefore = window.getSelection();
19549
21055
  let savedOffsets = null;
@@ -19579,7 +21085,7 @@ function OhhwellsBridge() {
19579
21085
  if (el) setToolbarRect(getEditMeasureEl(el).getBoundingClientRect());
19580
21086
  refreshActiveCommandsRef.current();
19581
21087
  }, []);
19582
- useEffect12(() => {
21088
+ useEffect13(() => {
19583
21089
  const session = floatingPanel && floatingPanel.kind === "logo-size" ? floatingPanel : null;
19584
21090
  if (!session || !logoSizeDraft) return;
19585
21091
  const onPanelAction = (e) => {
@@ -19617,7 +21123,7 @@ function OhhwellsBridge() {
19617
21123
  window.addEventListener("message", onPanelAction);
19618
21124
  return () => window.removeEventListener("message", onPanelAction);
19619
21125
  }, [floatingPanel, logoSizeDraft, editorViewport, persistLogoSizeDraft, closeFloatingPanelAndDeselect]);
19620
- const handleStateChange = useCallback7((state) => {
21126
+ const handleStateChange = useCallback8((state) => {
19621
21127
  if (!activeStateElRef.current) return;
19622
21128
  const el = activeStateElRef.current;
19623
21129
  if (state === "Default") {
@@ -19630,7 +21136,7 @@ function OhhwellsBridge() {
19630
21136
  }
19631
21137
  setToggleState((prev) => prev ? { ...prev, activeState: state } : null);
19632
21138
  }, [deactivate]);
19633
- const reselectAfterLinkPopover = useCallback7(
21139
+ const reselectAfterLinkPopover = useCallback8(
19634
21140
  (hrefKey) => {
19635
21141
  requestAnimationFrame(() => {
19636
21142
  const el = resolveHrefKeyElement(hrefKey);
@@ -19639,7 +21145,7 @@ function OhhwellsBridge() {
19639
21145
  },
19640
21146
  [resolveHrefKeyElement]
19641
21147
  );
19642
- const closeLinkPopover = useCallback7(() => {
21148
+ const closeLinkPopover = useCallback8(() => {
19643
21149
  const session = linkPopoverSessionRef.current;
19644
21150
  addNavAfterAnchorRef.current = null;
19645
21151
  setLinkPopover(null);
@@ -19647,9 +21153,9 @@ function OhhwellsBridge() {
19647
21153
  reselectAfterLinkPopover(session.key);
19648
21154
  }
19649
21155
  }, [reselectAfterLinkPopover]);
19650
- const closeLinkPopoverRef = useRef9(closeLinkPopover);
21156
+ const closeLinkPopoverRef = useRef10(closeLinkPopover);
19651
21157
  closeLinkPopoverRef.current = closeLinkPopover;
19652
- const openLinkPopoverForActive = useCallback7(() => {
21158
+ const openLinkPopoverForActive = useCallback8(() => {
19653
21159
  const hrefCtx = getHrefKeyFromElement(activeElRef.current);
19654
21160
  if (!hrefCtx) return;
19655
21161
  bumpLinkPopoverGrace();
@@ -19660,7 +21166,7 @@ function OhhwellsBridge() {
19660
21166
  });
19661
21167
  deactivate();
19662
21168
  }, [deactivate]);
19663
- const openLinkPopoverForSelected = useCallback7(() => {
21169
+ const openLinkPopoverForSelected = useCallback8(() => {
19664
21170
  const anchor = selectedElRef.current;
19665
21171
  if (!anchor) return;
19666
21172
  const key = anchor.getAttribute("data-ohw-href-key");
@@ -19677,7 +21183,7 @@ function OhhwellsBridge() {
19677
21183
  });
19678
21184
  deselect();
19679
21185
  }, [deselect]);
19680
- const handleSelectParent = useCallback7(() => {
21186
+ const handleSelectParent = useCallback8(() => {
19681
21187
  const selected = selectedElRef.current;
19682
21188
  if (!selected) return;
19683
21189
  if (toolbarVariantRef.current === "select-frame") {
@@ -19704,7 +21210,7 @@ function OhhwellsBridge() {
19704
21210
  }
19705
21211
  deselectRef.current();
19706
21212
  }, []);
19707
- const handleDuplicateSelected = useCallback7(() => {
21213
+ const handleDuplicateSelected = useCallback8(() => {
19708
21214
  const selected = selectedElRef.current;
19709
21215
  if (!selected || !isNavigationItem2(selected)) return;
19710
21216
  const hrefKey = selected.getAttribute("data-ohw-href-key");
@@ -19837,7 +21343,7 @@ function OhhwellsBridge() {
19837
21343
  });
19838
21344
  }
19839
21345
  }, [postToParent2]);
19840
- const runPendingDeleteUndo = useCallback7(() => {
21346
+ const runPendingDeleteUndo = useCallback8(() => {
19841
21347
  const pending = pendingDeleteUndoRef.current;
19842
21348
  if (!pending) return false;
19843
21349
  pendingDeleteUndoRef.current = null;
@@ -19845,7 +21351,7 @@ function OhhwellsBridge() {
19845
21351
  enforceLinkHrefs();
19846
21352
  return true;
19847
21353
  }, []);
19848
- const handleDeleteSelected = useCallback7(() => {
21354
+ const handleDeleteSelected = useCallback8(() => {
19849
21355
  const selected = selectedElRef.current;
19850
21356
  if (!selected) return false;
19851
21357
  return deleteSelectedNavFooterItem({
@@ -19866,7 +21372,7 @@ function OhhwellsBridge() {
19866
21372
  }, [postToParent2]);
19867
21373
  handleDeleteSelectedRef.current = handleDeleteSelected;
19868
21374
  runPendingDeleteUndoRef.current = runPendingDeleteUndo;
19869
- const handleLinkPopoverSubmit = useCallback7(
21375
+ const handleLinkPopoverSubmit = useCallback8(
19870
21376
  (target) => {
19871
21377
  const session = linkPopoverSessionRef.current;
19872
21378
  if (!session) return;
@@ -19932,19 +21438,30 @@ function OhhwellsBridge() {
19932
21438
  const showEditLink = toolbarShowEditLink;
19933
21439
  const currentSections = sectionsByPath[pathname] ?? [];
19934
21440
  linkPopoverOpenRef.current = linkPopover !== null;
19935
- const handleMediaReplace = useCallback7(
21441
+ const handleMediaSelect = useCallback8((key) => {
21442
+ const el = hoveredImageRef.current?.dataset.ohwKey === key ? hoveredImageRef.current : Array.from(document.querySelectorAll(MEDIA_SELECTOR)).find(
21443
+ (m) => (m.dataset.ohwKey ?? "") === key
21444
+ ) ?? null;
21445
+ if (!el) return;
21446
+ selectMediaElementRef.current(el);
21447
+ }, []);
21448
+ const handleMediaReplace = useCallback8(
19936
21449
  (key) => {
19937
- postToParent2({ type: "ow:image-pick", key, elementType: mediaHover?.elementType ?? "image" });
21450
+ postToParent2({
21451
+ type: "ow:image-pick",
21452
+ key,
21453
+ elementType: mediaHover?.elementType ?? selectedMedia?.elementType ?? "image"
21454
+ });
19938
21455
  },
19939
- [postToParent2, mediaHover?.elementType]
21456
+ [postToParent2, mediaHover?.elementType, selectedMedia?.elementType]
19940
21457
  );
19941
- const handleEditCarousel = useCallback7(
21458
+ const handleEditCarousel = useCallback8(
19942
21459
  (key) => {
19943
21460
  postToParent2({ type: "ow:carousel-open", key, images: readCarouselValue(key) });
19944
21461
  },
19945
21462
  [postToParent2]
19946
21463
  );
19947
- const handleMediaFadeOutComplete = useCallback7((key) => {
21464
+ const handleMediaFadeOutComplete = useCallback8((key) => {
19948
21465
  setUploadingRects((prev) => {
19949
21466
  if (!(key in prev)) return prev;
19950
21467
  const next = { ...prev };
@@ -19952,7 +21469,7 @@ function OhhwellsBridge() {
19952
21469
  return next;
19953
21470
  });
19954
21471
  }, []);
19955
- const handleVideoSettingsChange = useCallback7(
21472
+ const handleVideoSettingsChange = useCallback8(
19956
21473
  (key, settings) => {
19957
21474
  document.querySelectorAll(`[data-ohw-key="${key}"]`).forEach((el) => {
19958
21475
  const video = getVideoEl2(el);
@@ -19974,430 +21491,516 @@ function OhhwellsBridge() {
19974
21491
  },
19975
21492
  [postToParent2]
19976
21493
  );
19977
- return bridgeRoot ? createPortal2(
19978
- /* @__PURE__ */ jsxs20(Fragment8, { children: [
19979
- /* @__PURE__ */ jsx33("div", { ref: attachVisibleViewport, "data-ohw-visible-viewport": "", "aria-hidden": true }),
19980
- isEditMode && /* @__PURE__ */ jsx33(AiSectionOverlay, { postToParent: postToParent2, apiRef: aiSectionApiRef }),
19981
- Object.entries(uploadingRects).map(([key, { rect, fadingOut }]) => /* @__PURE__ */ jsx33(
19982
- MediaOverlay,
19983
- {
19984
- hover: { key, rect, elementType: "image", isDragOver: false, hasTextOverlap: false },
19985
- isUploading: true,
19986
- fadingOut,
19987
- onFadeOutComplete: handleMediaFadeOutComplete,
19988
- onReplace: handleMediaReplace
19989
- },
19990
- `uploading-${key}`
19991
- )),
19992
- mediaHover && !(mediaHover.key in uploadingRects) && /* @__PURE__ */ jsx33(
19993
- MediaOverlay,
19994
- {
19995
- hover: mediaHover,
19996
- isUploading: false,
19997
- onReplace: handleMediaReplace,
19998
- onVideoSettingsChange: handleVideoSettingsChange
19999
- }
20000
- ),
20001
- carouselHover && !linkPopover && !isItemDragging && /* @__PURE__ */ jsx33(CarouselOverlay, { hover: carouselHover, onEdit: handleEditCarousel }),
20002
- siblingHintRect && !linkPopover && !isItemDragging && siblingHintRects.length === 0 && /* @__PURE__ */ jsx33(ItemInteractionLayer, { rect: siblingHintRect, state: "sibling-hint" }),
20003
- siblingHintRects.length > 0 && !linkPopover && !isItemDragging && siblingHintRects.map((rect, i) => /* @__PURE__ */ jsx33(ItemInteractionLayer, { rect, state: "sibling-hint" }, `sibling-hint-${i}`)),
20004
- isItemDragging && draggedItemRect && selectedElRef.current !== footerDragRef.current?.draggedEl && selectedElRef.current !== navDragRef.current?.draggedEl && /* @__PURE__ */ jsx33(ItemInteractionLayer, { rect: draggedItemRect, state: "dragging" }),
20005
- isItemDragging && footerDropSlots.map((slot, i) => /* @__PURE__ */ jsx33(
20006
- "div",
20007
- {
20008
- className: "pointer-events-none fixed z-2147483646",
20009
- style: {
20010
- left: slot.left,
20011
- top: slot.top,
20012
- width: slot.width,
20013
- height: slot.height
21494
+ return /* @__PURE__ */ jsxs20(Fragment8, { children: [
21495
+ /* @__PURE__ */ jsx33("div", { id: "ohw-loader", suppressHydrationWarning: true, style: { ...OHW_LOADER_STYLE, display: "none" }, children: /* @__PURE__ */ jsx33(OhwLoaderSpinner, {}) }),
21496
+ /* @__PURE__ */ jsx33("script", { suppressHydrationWarning: true, dangerouslySetInnerHTML: { __html: OHW_LOADER_PREHYDRATE_SCRIPT } }),
21497
+ bridgeRoot ? createPortal2(
21498
+ /* @__PURE__ */ jsxs20(Fragment8, { children: [
21499
+ /* @__PURE__ */ jsx33("div", { ref: attachVisibleViewport, "data-ohw-visible-viewport": "", "aria-hidden": true }),
21500
+ isEditMode && /* @__PURE__ */ jsx33(AiSectionOverlay, { postToParent: postToParent2, apiRef: aiSectionApiRef }),
21501
+ isSectionDragging && sectionDropSlots.map((slot, i) => /* @__PURE__ */ jsx33(
21502
+ "div",
21503
+ {
21504
+ className: "pointer-events-none fixed z-2147483646",
21505
+ style: { left: slot.left, top: slot.y, width: slot.width, height: 3, transform: "translateY(-50%)" },
21506
+ children: /* @__PURE__ */ jsx33(
21507
+ DropIndicator,
21508
+ {
21509
+ direction: "horizontal",
21510
+ state: activeSectionDropIndex === i ? "dragActive" : "dragIdle",
21511
+ className: "!h-full !w-full"
21512
+ }
21513
+ )
20014
21514
  },
20015
- children: /* @__PURE__ */ jsx33(
20016
- DropIndicator,
20017
- {
20018
- direction: slot.direction,
20019
- state: activeFooterDropIndex === i ? "dragActive" : "dragIdle",
20020
- className: "!h-full !w-full"
20021
- }
20022
- )
20023
- },
20024
- `footer-drop-${slot.direction}-${slot.columnIndex}-${slot.insertIndex}-${i}`
20025
- )),
20026
- isItemDragging && navDropSlots.map((slot, i) => /* @__PURE__ */ jsx33(
20027
- "div",
20028
- {
20029
- className: "pointer-events-none fixed z-2147483646",
20030
- style: {
20031
- left: slot.left,
20032
- top: slot.top,
20033
- width: slot.width,
20034
- height: slot.height
21515
+ `section-drop-${slot.insertIndex}-${i}`
21516
+ )),
21517
+ Object.entries(uploadingRects).map(([key, { rect, fadingOut }]) => /* @__PURE__ */ jsx33(
21518
+ MediaOverlay,
21519
+ {
21520
+ hover: { key, rect, elementType: "image", isDragOver: false, hasTextOverlap: false },
21521
+ isUploading: true,
21522
+ fadingOut,
21523
+ onFadeOutComplete: handleMediaFadeOutComplete,
21524
+ onReplace: handleMediaReplace
20035
21525
  },
20036
- children: /* @__PURE__ */ jsx33(
20037
- DropIndicator,
20038
- {
20039
- direction: slot.direction,
20040
- state: activeNavDropIndex === i ? "dragActive" : "dragIdle",
20041
- className: "!h-full !w-full"
20042
- }
20043
- )
20044
- },
20045
- `nav-drop-${slot.direction}-${slot.parentId ?? "root"}-${slot.insertIndex}-${i}`
20046
- )),
20047
- hoveredNavContainerRect && (toolbarVariant !== "select-frame" || isFooterFrameSelection) && !linkPopover && !isItemDragging && /* @__PURE__ */ jsx33(ItemInteractionLayer, { rect: hoveredNavContainerRect, state: "hover" }),
20048
- hoveredItemRect && !linkPopover && !hoveredNavContainerRect && !isItemDragging && hoveredItemElRef.current !== selectedElRef.current && /* @__PURE__ */ jsx33(ItemInteractionLayer, { rect: hoveredItemRect, state: "hover" }),
20049
- hoveredTextRect && !hoveredNavContainerRect && !hoveredItemRect && !isItemDragging && toolbarVariant !== "rich-text" && /* @__PURE__ */ jsx33(ItemInteractionLayer, { rect: hoveredTextRect, state: "hover" }),
20050
- formPickRect && !isItemDragging && /* @__PURE__ */ jsx33(
20051
- ItemInteractionLayer,
20052
- {
20053
- rect: formPickRect,
20054
- state: "active-top",
20055
- itemDragSurface: false,
20056
- toolbarAlign: "left",
20057
- chromeGap: 24,
20058
- toolbar: fieldPickRect ? void 0 : /* @__PURE__ */ jsxs20(
21526
+ `uploading-${key}`
21527
+ )),
21528
+ mediaHover && !(mediaHover.key in uploadingRects) && mediaHover.key !== selectedMedia?.key && /* @__PURE__ */ jsx33(
21529
+ MediaOverlay,
21530
+ {
21531
+ hover: mediaHover,
21532
+ isUploading: false,
21533
+ onReplace: handleMediaReplace,
21534
+ onSelect: handleMediaSelect,
21535
+ onVideoSettingsChange: handleVideoSettingsChange
21536
+ }
21537
+ ),
21538
+ selectedMedia && !(selectedMedia.key in uploadingRects) && !linkPopover && /* @__PURE__ */ jsx33(
21539
+ MediaOverlay,
21540
+ {
21541
+ hover: selectedMedia,
21542
+ selected: true,
21543
+ hovered: mediaHover?.key === selectedMedia.key,
21544
+ isUploading: false,
21545
+ onReplace: handleMediaReplace,
21546
+ onSelect: handleMediaSelect,
21547
+ onVideoSettingsChange: handleVideoSettingsChange
21548
+ }
21549
+ ),
21550
+ carouselHover && !linkPopover && !isItemDragging && /* @__PURE__ */ jsx33(CarouselOverlay, { hover: carouselHover, onEdit: handleEditCarousel }),
21551
+ siblingHintRect && !linkPopover && !isItemDragging && siblingHintRects.length === 0 && /* @__PURE__ */ jsx33(ItemInteractionLayer, { rect: siblingHintRect, state: "sibling-hint" }),
21552
+ siblingHintRects.length > 0 && !linkPopover && !isItemDragging && siblingHintRects.map((rect, i) => /* @__PURE__ */ jsx33(ItemInteractionLayer, { rect, state: "sibling-hint" }, `sibling-hint-${i}`)),
21553
+ isItemDragging && draggedItemRect && selectedElRef.current !== footerDragRef.current?.draggedEl && selectedElRef.current !== navDragRef.current?.draggedEl && /* @__PURE__ */ jsx33(ItemInteractionLayer, { rect: draggedItemRect, state: "dragging" }),
21554
+ isItemDragging && footerDropSlots.map((slot, i) => /* @__PURE__ */ jsx33(
21555
+ "div",
21556
+ {
21557
+ className: "pointer-events-none fixed z-2147483646",
21558
+ style: {
21559
+ left: slot.left,
21560
+ top: slot.top,
21561
+ width: slot.width,
21562
+ height: slot.height
21563
+ },
21564
+ children: /* @__PURE__ */ jsx33(
21565
+ DropIndicator,
21566
+ {
21567
+ direction: slot.direction,
21568
+ state: activeFooterDropIndex === i ? "dragActive" : "dragIdle",
21569
+ className: "!h-full !w-full"
21570
+ }
21571
+ )
21572
+ },
21573
+ `footer-drop-${slot.direction}-${slot.columnIndex}-${slot.insertIndex}-${i}`
21574
+ )),
21575
+ isItemDragging && navDropSlots.map((slot, i) => /* @__PURE__ */ jsx33(
21576
+ "div",
21577
+ {
21578
+ className: "pointer-events-none fixed z-2147483646",
21579
+ style: {
21580
+ left: slot.left,
21581
+ top: slot.top,
21582
+ width: slot.width,
21583
+ height: slot.height
21584
+ },
21585
+ children: /* @__PURE__ */ jsx33(
21586
+ DropIndicator,
21587
+ {
21588
+ direction: slot.direction,
21589
+ state: activeNavDropIndex === i ? "dragActive" : "dragIdle",
21590
+ className: "!h-full !w-full"
21591
+ }
21592
+ )
21593
+ },
21594
+ `nav-drop-${slot.direction}-${slot.parentId ?? "root"}-${slot.insertIndex}-${i}`
21595
+ )),
21596
+ hoveredNavContainerRect && (toolbarVariant !== "select-frame" || isFooterFrameSelection) && !linkPopover && !isItemDragging && /* @__PURE__ */ jsx33(ItemInteractionLayer, { rect: hoveredNavContainerRect, state: "hover" }),
21597
+ hoveredItemRect && !linkPopover && !hoveredNavContainerRect && !isItemDragging && hoveredItemElRef.current !== selectedElRef.current && /* @__PURE__ */ jsx33(ItemInteractionLayer, { rect: hoveredItemRect, state: "hover" }),
21598
+ hoveredTextRect && !hoveredNavContainerRect && !hoveredItemRect && !isItemDragging && toolbarVariant !== "rich-text" && /* @__PURE__ */ jsx33(ItemInteractionLayer, { rect: hoveredTextRect, state: "hover" }),
21599
+ formPickRect && !isItemDragging && /* @__PURE__ */ jsx33(
21600
+ ItemInteractionLayer,
21601
+ {
21602
+ rect: formPickRect,
21603
+ state: "active-top",
21604
+ itemDragSurface: false,
21605
+ toolbarAlign: "left",
21606
+ chromeGap: 24,
21607
+ toolbar: fieldPickRect ? void 0 : /* @__PURE__ */ jsxs20(
21608
+ "div",
21609
+ {
21610
+ "data-ohw-form-toolbar": "",
21611
+ className: "pointer-events-auto flex items-center gap-0.5 whitespace-nowrap rounded-lg border border-border bg-background p-1 shadow-md",
21612
+ children: [
21613
+ /* @__PURE__ */ jsx33(
21614
+ "button",
21615
+ {
21616
+ type: "button",
21617
+ "aria-label": "Add field",
21618
+ className: "flex h-7 w-7 items-center justify-center rounded-md text-foreground transition-colors hover:bg-muted/80",
21619
+ onClick: () => setFieldTypePickerOpen((open) => !open),
21620
+ "data-ohw-add-field": "",
21621
+ children: /* @__PURE__ */ jsx33(Plus4, { size: 15, "aria-hidden": true })
21622
+ }
21623
+ ),
21624
+ /* @__PURE__ */ jsx33("div", { className: "mx-0.5 h-5 w-px bg-border" }),
21625
+ /* @__PURE__ */ jsxs20(
21626
+ "button",
21627
+ {
21628
+ type: "button",
21629
+ 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",
21630
+ onClick: () => {
21631
+ setFieldTypePickerOpen(false);
21632
+ const form = formPickElRef.current;
21633
+ if (!form) return;
21634
+ postToParent2({
21635
+ type: "ow:form-pick",
21636
+ formKey: formKeyOf(form),
21637
+ hasLongText: formHasLongText(form)
21638
+ });
21639
+ },
21640
+ children: [
21641
+ /* @__PURE__ */ jsx33(Settings, { size: 14, "aria-hidden": true }),
21642
+ "Form settings",
21643
+ formPickCount ? (
21644
+ // Counter pill, per the design — not a text suffix.
21645
+ /* @__PURE__ */ jsx33(
21646
+ "span",
21647
+ {
21648
+ "data-ohw-form-count": "",
21649
+ 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",
21650
+ children: formPickCount
21651
+ }
21652
+ )
21653
+ ) : null
21654
+ ]
21655
+ }
21656
+ ),
21657
+ /* @__PURE__ */ jsx33("div", { className: "mx-0.5 h-5 w-px bg-border" }),
21658
+ /* @__PURE__ */ jsx33("div", { className: "flex items-center rounded-md bg-muted/70 p-0.5", children: ["default", "success"].map((state) => /* @__PURE__ */ jsx33(
21659
+ "button",
21660
+ {
21661
+ type: "button",
21662
+ "aria-pressed": formViewState === state,
21663
+ 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"),
21664
+ onClick: () => {
21665
+ setFieldTypePickerOpen(false);
21666
+ const form = formPickElRef.current;
21667
+ const key = form ? formKeyOf(form) : null;
21668
+ if (!form || !key) return;
21669
+ const initial = successInitialFor(form, key, editContentRef.current);
21670
+ setFormViewState(form, key, state, initial);
21671
+ setFormViewStateUi(state);
21672
+ setFormPickRect(form.getBoundingClientRect());
21673
+ if (state === "success") {
21674
+ const successEl = form.querySelector(`[${SUCCESS_TEXT_ATTR}]`);
21675
+ if (successEl) requestAnimationFrame(() => activateRef.current(successEl));
21676
+ } else {
21677
+ deactivateRef.current();
21678
+ }
21679
+ },
21680
+ children: state
21681
+ },
21682
+ state
21683
+ )) })
21684
+ ]
21685
+ }
21686
+ )
21687
+ }
21688
+ ),
21689
+ formHoverRect && !isItemDragging && /* @__PURE__ */ jsx33(
21690
+ ItemInteractionLayer,
21691
+ {
21692
+ rect: formHoverRect,
21693
+ state: "hover",
21694
+ chromeGap: formHoverElRef.current && getFieldWrapper(formHoverElRef.current) ? 8 : 24
21695
+ }
21696
+ ),
21697
+ fieldPickRect && fieldPickState && !isItemDragging && toolbarVariant !== "rich-text" && /* @__PURE__ */ jsx33(
21698
+ ItemInteractionLayer,
21699
+ {
21700
+ rect: fieldPickRect,
21701
+ state: fieldDragging ? "dragging" : "active-top",
21702
+ itemDragSurface: false,
21703
+ toolbarAlign: "left",
21704
+ chromeGap: 10,
21705
+ showHandle: true,
21706
+ dragHandleLabel: "Reorder field",
21707
+ onDragHandleDragStart: handleFieldDragStart,
21708
+ onDragHandleDragEnd: handleFieldDragEnd,
21709
+ toolbar: /* @__PURE__ */ jsx33(
21710
+ FormFieldToolbar,
21711
+ {
21712
+ type: fieldPickState.type,
21713
+ required: fieldPickState.required,
21714
+ onTypeChange: handleFieldTypeChange,
21715
+ onRequiredToggle: handleFieldRequiredToggle,
21716
+ onDuplicate: handleFieldDuplicate,
21717
+ onDelete: handleFieldDelete
21718
+ }
21719
+ )
21720
+ }
21721
+ ),
21722
+ fieldDragging ? fieldDropSlots.map((slot, i) => /* @__PURE__ */ jsx33(
21723
+ "div",
21724
+ {
21725
+ className: "pointer-events-none fixed z-[2147483644]",
21726
+ style: { top: slot.top, left: slot.left, width: slot.width },
21727
+ children: /* @__PURE__ */ jsx33(
21728
+ DropIndicator,
21729
+ {
21730
+ direction: "horizontal",
21731
+ state: fieldDropIndex === i ? "dragActive" : "dragIdle",
21732
+ className: "!w-full"
21733
+ }
21734
+ )
21735
+ },
21736
+ `field-drop-${i}`
21737
+ )) : null,
21738
+ fieldTypePickerOpen && formPickRect ? (() => {
21739
+ const toolbar = document.querySelector("[data-ohw-form-toolbar]")?.getBoundingClientRect();
21740
+ return /* @__PURE__ */ jsx33(
20059
21741
  "div",
20060
21742
  {
20061
- "data-ohw-form-toolbar": "",
20062
- className: "pointer-events-auto flex items-center gap-0.5 whitespace-nowrap rounded-lg border border-border bg-background p-1 shadow-md",
20063
- children: [
20064
- /* @__PURE__ */ jsx33(
20065
- "button",
20066
- {
20067
- type: "button",
20068
- "aria-label": "Add field",
20069
- className: "flex h-7 w-7 items-center justify-center rounded-md text-foreground transition-colors hover:bg-muted/80",
20070
- onClick: () => setFieldTypePickerOpen((open) => !open),
20071
- "data-ohw-add-field": "",
20072
- children: /* @__PURE__ */ jsx33(Plus4, { size: 15, "aria-hidden": true })
20073
- }
20074
- ),
20075
- /* @__PURE__ */ jsx33("div", { className: "mx-0.5 h-5 w-px bg-border" }),
20076
- /* @__PURE__ */ jsxs20(
20077
- "button",
20078
- {
20079
- type: "button",
20080
- 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",
20081
- onClick: () => {
20082
- setFieldTypePickerOpen(false);
20083
- const form = formPickElRef.current;
20084
- if (!form) return;
20085
- postToParent2({
20086
- type: "ow:form-pick",
20087
- formKey: formKeyOf(form),
20088
- hasLongText: formHasLongText(form)
20089
- });
20090
- },
20091
- children: [
20092
- /* @__PURE__ */ jsx33(Settings, { size: 14, "aria-hidden": true }),
20093
- "Form settings",
20094
- formPickCount ? (
20095
- // Counter pill, per the design — not a text suffix.
20096
- /* @__PURE__ */ jsx33(
20097
- "span",
20098
- {
20099
- "data-ohw-form-count": "",
20100
- 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",
20101
- children: formPickCount
20102
- }
20103
- )
20104
- ) : null
20105
- ]
20106
- }
20107
- ),
20108
- /* @__PURE__ */ jsx33("div", { className: "mx-0.5 h-5 w-px bg-border" }),
20109
- /* @__PURE__ */ jsx33("div", { className: "flex items-center rounded-md bg-muted/70 p-0.5", children: ["default", "success"].map((state) => /* @__PURE__ */ jsx33(
20110
- "button",
20111
- {
20112
- type: "button",
20113
- "aria-pressed": formViewState === state,
20114
- 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"),
20115
- onClick: () => {
20116
- setFieldTypePickerOpen(false);
20117
- const form = formPickElRef.current;
20118
- const key = form ? formKeyOf(form) : null;
20119
- if (!form || !key) return;
20120
- const initial = successInitialFor(form, key, editContentRef.current);
20121
- setFormViewState(form, key, state, initial);
20122
- setFormViewStateUi(state);
20123
- setFormPickRect(form.getBoundingClientRect());
20124
- if (state === "success") {
20125
- const successEl = form.querySelector(`[${SUCCESS_TEXT_ATTR}]`);
20126
- if (successEl) requestAnimationFrame(() => activateRef.current(successEl));
20127
- } else {
20128
- deactivateRef.current();
20129
- }
20130
- },
20131
- children: state
20132
- },
20133
- state
20134
- )) })
20135
- ]
21743
+ className: "pointer-events-none fixed z-[2147483645]",
21744
+ style: {
21745
+ top: toolbar ? toolbar.bottom + 6 : formPickRect.top + 16,
21746
+ left: toolbar ? toolbar.left : formPickRect.left + 24
21747
+ },
21748
+ children: /* @__PURE__ */ jsx33(FieldTypePicker, { onPick: handleAddField })
20136
21749
  }
20137
- )
20138
- }
20139
- ),
20140
- formHoverRect && !isItemDragging && /* @__PURE__ */ jsx33(
20141
- ItemInteractionLayer,
20142
- {
20143
- rect: formHoverRect,
20144
- state: "hover",
20145
- chromeGap: formHoverElRef.current && getFieldWrapper(formHoverElRef.current) ? 8 : 24
20146
- }
20147
- ),
20148
- fieldPickRect && fieldPickState && !isItemDragging && toolbarVariant !== "rich-text" && /* @__PURE__ */ jsx33(
20149
- ItemInteractionLayer,
20150
- {
20151
- rect: fieldPickRect,
20152
- state: fieldDragging ? "dragging" : "active-top",
20153
- itemDragSurface: false,
20154
- toolbarAlign: "left",
20155
- chromeGap: 10,
20156
- showHandle: true,
20157
- dragHandleLabel: "Reorder field",
20158
- onDragHandleDragStart: handleFieldDragStart,
20159
- onDragHandleDragEnd: handleFieldDragEnd,
20160
- toolbar: /* @__PURE__ */ jsx33(
20161
- FormFieldToolbar,
21750
+ );
21751
+ })() : null,
21752
+ toolbarVariant === "select-frame" && toolbarRect && !linkPopover && !isItemDragging && !isFooterFrameSelection && selectedElRef.current && isNavbarLinksContainer2(selectedElRef.current) && /* @__PURE__ */ jsx33(NavbarContainerChrome, { rect: toolbarRect, onAdd: handleAddTopLevelNavItem }),
21753
+ toolbarVariant === "select-frame" && toolbarRect && !linkPopover && !isItemDragging && !isFooterFrameSelection && selectedElRef.current && isFooterLinksContainer(selectedElRef.current) && /* @__PURE__ */ jsx33(
21754
+ FooterContainerChrome,
21755
+ {
21756
+ rect: toolbarRect,
21757
+ onAdd: handleAddFooterColumn,
21758
+ addDisabled: !canAddFooterColumn()
21759
+ }
21760
+ ),
21761
+ toolbarRect && !linkPopover && (toolbarVariant === "link-action" || toolbarVariant === "select-frame" || toolbarVariant === "logo") && /* @__PURE__ */ jsx33(
21762
+ ItemInteractionLayer,
21763
+ {
21764
+ rect: isItemDragging && draggedItemRect && (footerDragRef.current?.wasSelected || navDragRef.current?.wasSelected) ? draggedItemRect : toolbarRect,
21765
+ toolbarBelowRect: selectedElRef.current && isNavigationItem2(selectedElRef.current) ? getOpenNavDropdownPanelRect(selectedElRef.current) : null,
21766
+ elRef: glowElRef,
21767
+ state: isItemDragging ? "dragging" : selectedElRef.current && isNavigationItem2(selectedElRef.current) && getOpenNavDropdownPanelRect(selectedElRef.current) ? "active-bottom" : resolveItemInteractionState(toolbarRect, parentScrollRef.current),
21768
+ showHandle: toolbarVariant === "link-action" && Boolean(reorderHrefKey) || toolbarVariant === "select-frame" && isFooterFrameSelection,
21769
+ dragDisabled: reorderDragDisabled,
21770
+ dragHandleLabel: reorderHrefKey ? `Reorder ${reorderHrefKey}` : "Reorder item",
21771
+ onDragHandleDragStart: handleItemDragStart,
21772
+ onDragHandleDragEnd: handleItemDragEnd,
21773
+ onItemPointerDown: toolbarVariant === "logo" ? void 0 : handleItemChromePointerDown,
21774
+ onItemClick: toolbarVariant === "logo" ? void 0 : handleItemChromeClick,
21775
+ itemDragSurface: toolbarVariant !== "logo" && !isFooterFrameSelection && !selectedIsSocialsRow,
21776
+ toolbar: toolbarVariant === "link-action" && !isItemDragging || toolbarVariant === "select-frame" && (isFooterFrameSelection || selectedIsSocialsRow) && !isItemDragging ? /* @__PURE__ */ jsx33(
21777
+ ItemActionToolbar,
21778
+ {
21779
+ onEditLink: openLinkPopoverForSelected,
21780
+ onStyle: () => {
21781
+ const row = selectedElRef.current;
21782
+ if (!row) return;
21783
+ if (floatingPanel?.kind === "socials-display") closeFloatingPanelOnly();
21784
+ else openSocialsDisplayPanel(row);
21785
+ },
21786
+ showStyle: selectedIsSocialsRow,
21787
+ styleActive: floatingPanel?.kind === "socials-display",
21788
+ onAddItem: handleAddChildItem,
21789
+ onSelectParent: handleSelectParent,
21790
+ onDuplicate: handleDuplicateSelected,
21791
+ onDelete: handleDeleteSelected,
21792
+ addItemDisabled: isFooterFrameSelection && isFooterAddItemDisabled(selectedElRef.current) || selectedElRef.current !== null && (() => {
21793
+ const row = isSocialsRow(selectedElRef.current) ? selectedElRef.current : findSocialsRow(selectedElRef.current);
21794
+ return row ? !canAddSocialItem(row) : false;
21795
+ })(),
21796
+ editLinkDisabled: false,
21797
+ moreDisabled: false,
21798
+ deleteDisabled: selectedElRef.current !== null && (() => {
21799
+ const social = getSocialItem(selectedElRef.current);
21800
+ return social ? !canRemoveSocialItem(social) : false;
21801
+ })(),
21802
+ duplicateDisabled: isFooterFrameSelection || selectedIsSocialsRow || selectedElRef.current !== null && (() => {
21803
+ const social = getSocialItem(selectedElRef.current);
21804
+ const row = social ? findSocialsRow(social) : null;
21805
+ return row ? !canAddSocialItem(row) : false;
21806
+ })(),
21807
+ showEditLink: !isFooterFrameSelection && !selectedIsSocialsRow && navDropdownPreviewOpen === null,
21808
+ showAddItem: isFooterFrameSelection || selectedIsSocialsRow || !selectedIsCta && Boolean(
21809
+ selectedElRef.current && isNavbarHrefKey(selectedElRef.current.getAttribute("data-ohw-href-key")) && !isNestedNavChild(selectedElRef.current)
21810
+ ),
21811
+ showMore: (!selectedIsCta || isFooterFrameSelection) && !selectedIsSocialsRow,
21812
+ dropdownOpen: navDropdownPreviewOpen,
21813
+ onDropdownOpenChange: handleNavDropdownOpenChange,
21814
+ headingVisible: footerHeadingVisible,
21815
+ onHeadingVisibleChange: handleFooterHeadingVisibleChange
21816
+ }
21817
+ ) : void 0
21818
+ }
21819
+ ),
21820
+ toolbarRect && toolbarVariant === "rich-text" && !linkPopover && /* @__PURE__ */ jsxs20(Fragment8, { children: [
21821
+ /* @__PURE__ */ jsx33(
21822
+ EditGlowChrome,
20162
21823
  {
20163
- type: fieldPickState.type,
20164
- required: fieldPickState.required,
20165
- onTypeChange: handleFieldTypeChange,
20166
- onRequiredToggle: handleFieldRequiredToggle,
20167
- onDuplicate: handleFieldDuplicate,
20168
- onDelete: handleFieldDelete
21824
+ rect: toolbarRect,
21825
+ elRef: glowElRef,
21826
+ reorderHrefKey,
21827
+ dragDisabled: reorderDragDisabled,
21828
+ hideHandle: isItemDragging
20169
21829
  }
20170
- )
20171
- }
20172
- ),
20173
- fieldDragging ? fieldDropSlots.map((slot, i) => /* @__PURE__ */ jsx33(
20174
- "div",
20175
- {
20176
- className: "pointer-events-none fixed z-[2147483644]",
20177
- style: { top: slot.top, left: slot.left, width: slot.width },
20178
- children: /* @__PURE__ */ jsx33(
20179
- DropIndicator,
21830
+ ),
21831
+ /* @__PURE__ */ jsx33(
21832
+ FloatingToolbar,
20180
21833
  {
20181
- direction: "horizontal",
20182
- state: fieldDropIndex === i ? "dragActive" : "dragIdle",
20183
- className: "!w-full"
21834
+ rect: toolbarRect,
21835
+ parentScroll: parentScrollRef.current,
21836
+ elRef: toolbarElRef,
21837
+ onCommand: handleCommand,
21838
+ activeCommands,
21839
+ showEditLink,
21840
+ onEditLink: openLinkPopoverForActive
20184
21841
  }
20185
21842
  )
20186
- },
20187
- `field-drop-${i}`
20188
- )) : null,
20189
- fieldTypePickerOpen && formPickRect ? (() => {
20190
- const toolbar = document.querySelector("[data-ohw-form-toolbar]")?.getBoundingClientRect();
20191
- return /* @__PURE__ */ jsx33(
21843
+ ] }),
21844
+ maxBadge && /* @__PURE__ */ jsxs20(
20192
21845
  "div",
20193
21846
  {
20194
- className: "pointer-events-none fixed z-[2147483645]",
21847
+ "data-ohw-max-badge": "",
20195
21848
  style: {
20196
- top: toolbar ? toolbar.bottom + 6 : formPickRect.top + 16,
20197
- left: toolbar ? toolbar.left : formPickRect.left + 24
21849
+ position: "fixed",
21850
+ top: maxBadge.rect.bottom + 4,
21851
+ left: maxBadge.rect.right,
21852
+ transform: "translateX(-100%)",
21853
+ zIndex: 2147483647,
21854
+ background: maxBadge.current > maxBadge.max ? "#FEF2F2" : "#F5F5F4",
21855
+ color: maxBadge.current > maxBadge.max ? "#DC2626" : "#78716C",
21856
+ border: `1px solid ${maxBadge.current > maxBadge.max ? "#FECACA" : "#E7E5E4"}`,
21857
+ borderRadius: 4,
21858
+ padding: "2px 6px",
21859
+ fontSize: 11,
21860
+ fontWeight: 500,
21861
+ pointerEvents: "none"
20198
21862
  },
20199
- children: /* @__PURE__ */ jsx33(FieldTypePicker, { onPick: handleAddField })
21863
+ children: [
21864
+ maxBadge.current,
21865
+ "/",
21866
+ maxBadge.max
21867
+ ]
20200
21868
  }
20201
- );
20202
- })() : null,
20203
- toolbarVariant === "select-frame" && toolbarRect && !linkPopover && !isItemDragging && !isFooterFrameSelection && selectedElRef.current && isNavbarLinksContainer2(selectedElRef.current) && /* @__PURE__ */ jsx33(NavbarContainerChrome, { rect: toolbarRect, onAdd: handleAddTopLevelNavItem }),
20204
- toolbarVariant === "select-frame" && toolbarRect && !linkPopover && !isItemDragging && !isFooterFrameSelection && selectedElRef.current && isFooterLinksContainer(selectedElRef.current) && /* @__PURE__ */ jsx33(
20205
- FooterContainerChrome,
20206
- {
20207
- rect: toolbarRect,
20208
- onAdd: handleAddFooterColumn,
20209
- addDisabled: !canAddFooterColumn()
20210
- }
20211
- ),
20212
- toolbarRect && !linkPopover && (toolbarVariant === "link-action" || toolbarVariant === "select-frame" || toolbarVariant === "logo") && /* @__PURE__ */ jsx33(
20213
- ItemInteractionLayer,
20214
- {
20215
- rect: isItemDragging && draggedItemRect && (footerDragRef.current?.wasSelected || navDragRef.current?.wasSelected) ? draggedItemRect : toolbarRect,
20216
- toolbarBelowRect: selectedElRef.current && isNavigationItem2(selectedElRef.current) ? getOpenNavDropdownPanelRect(selectedElRef.current) : null,
20217
- elRef: glowElRef,
20218
- state: isItemDragging ? "dragging" : selectedElRef.current && isNavigationItem2(selectedElRef.current) && getOpenNavDropdownPanelRect(selectedElRef.current) ? "active-bottom" : resolveItemInteractionState(toolbarRect, parentScrollRef.current),
20219
- showHandle: toolbarVariant === "link-action" && Boolean(reorderHrefKey) || toolbarVariant === "select-frame" && isFooterFrameSelection,
20220
- dragDisabled: reorderDragDisabled,
20221
- dragHandleLabel: reorderHrefKey ? `Reorder ${reorderHrefKey}` : "Reorder item",
20222
- onDragHandleDragStart: handleItemDragStart,
20223
- onDragHandleDragEnd: handleItemDragEnd,
20224
- onItemPointerDown: toolbarVariant === "logo" ? void 0 : handleItemChromePointerDown,
20225
- onItemClick: toolbarVariant === "logo" ? void 0 : handleItemChromeClick,
20226
- itemDragSurface: toolbarVariant !== "logo" && !isFooterFrameSelection && !selectedIsSocialsRow,
20227
- toolbar: toolbarVariant === "link-action" && !isItemDragging || toolbarVariant === "select-frame" && (isFooterFrameSelection || selectedIsSocialsRow) && !isItemDragging ? /* @__PURE__ */ jsx33(
20228
- ItemActionToolbar,
20229
- {
20230
- onEditLink: openLinkPopoverForSelected,
20231
- onStyle: () => {
20232
- const row = selectedElRef.current;
20233
- if (!row) return;
20234
- if (floatingPanel?.kind === "socials-display") closeFloatingPanelOnly();
20235
- else openSocialsDisplayPanel(row);
20236
- },
20237
- showStyle: selectedIsSocialsRow,
20238
- styleActive: floatingPanel?.kind === "socials-display",
20239
- onAddItem: handleAddChildItem,
20240
- onSelectParent: handleSelectParent,
20241
- onDuplicate: handleDuplicateSelected,
20242
- onDelete: handleDeleteSelected,
20243
- addItemDisabled: isFooterFrameSelection && isFooterAddItemDisabled(selectedElRef.current) || selectedElRef.current !== null && (() => {
20244
- const row = isSocialsRow(selectedElRef.current) ? selectedElRef.current : findSocialsRow(selectedElRef.current);
20245
- return row ? !canAddSocialItem(row) : false;
20246
- })(),
20247
- editLinkDisabled: false,
20248
- moreDisabled: false,
20249
- deleteDisabled: selectedElRef.current !== null && (() => {
20250
- const social = getSocialItem(selectedElRef.current);
20251
- return social ? !canRemoveSocialItem(social) : false;
20252
- })(),
20253
- duplicateDisabled: isFooterFrameSelection || selectedIsSocialsRow || selectedElRef.current !== null && (() => {
20254
- const social = getSocialItem(selectedElRef.current);
20255
- const row = social ? findSocialsRow(social) : null;
20256
- return row ? !canAddSocialItem(row) : false;
20257
- })(),
20258
- showEditLink: !isFooterFrameSelection && !selectedIsSocialsRow && navDropdownPreviewOpen === null,
20259
- showAddItem: isFooterFrameSelection || selectedIsSocialsRow || !selectedIsCta && Boolean(
20260
- selectedElRef.current && isNavbarHrefKey(selectedElRef.current.getAttribute("data-ohw-href-key")) && !isNestedNavChild(selectedElRef.current)
20261
- ),
20262
- showMore: (!selectedIsCta || isFooterFrameSelection) && !selectedIsSocialsRow,
20263
- dropdownOpen: navDropdownPreviewOpen,
20264
- onDropdownOpenChange: handleNavDropdownOpenChange,
20265
- headingVisible: footerHeadingVisible,
20266
- onHeadingVisibleChange: handleFooterHeadingVisibleChange
20267
- }
20268
- ) : void 0
20269
- }
20270
- ),
20271
- toolbarRect && toolbarVariant === "rich-text" && !linkPopover && /* @__PURE__ */ jsxs20(Fragment8, { children: [
20272
- /* @__PURE__ */ jsx33(
20273
- EditGlowChrome,
21869
+ ),
21870
+ toggleState && !linkPopover && /* @__PURE__ */ jsx33(
21871
+ StateToggle,
20274
21872
  {
20275
- rect: toolbarRect,
20276
- elRef: glowElRef,
20277
- reorderHrefKey,
20278
- dragDisabled: reorderDragDisabled,
20279
- hideHandle: isItemDragging
21873
+ rect: toggleState.rect,
21874
+ activeState: toggleState.activeState,
21875
+ states: toggleState.states,
21876
+ onStateChange: handleStateChange
20280
21877
  }
20281
21878
  ),
20282
- /* @__PURE__ */ jsx33(
20283
- FloatingToolbar,
21879
+ sectionGap && !linkPopover && /* @__PURE__ */ jsxs20(
21880
+ "div",
20284
21881
  {
20285
- rect: toolbarRect,
20286
- parentScroll: parentScrollRef.current,
20287
- elRef: toolbarElRef,
20288
- onCommand: handleCommand,
20289
- activeCommands,
20290
- showEditLink,
20291
- onEditLink: openLinkPopoverForActive
21882
+ "data-ohw-section-insert-line": "",
21883
+ className: "fixed left-0 w-full z-2147483646 flex items-center pointer-events-none",
21884
+ style: { top: sectionGap.y, transform: "translateY(-50%)" },
21885
+ children: [
21886
+ /* @__PURE__ */ jsx33("div", { className: "flex-1 bg-primary", style: { height: 3 } }),
21887
+ /* @__PURE__ */ jsx33(
21888
+ Badge,
21889
+ {
21890
+ 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",
21891
+ onClick: () => {
21892
+ window.parent.postMessage(
21893
+ {
21894
+ type: "ow:add-section",
21895
+ insertAfter: sectionGap.insertAfter,
21896
+ insertBefore: sectionGap.insertBefore
21897
+ },
21898
+ "*"
21899
+ );
21900
+ },
21901
+ children: "Add Section"
21902
+ }
21903
+ ),
21904
+ /* @__PURE__ */ jsx33("div", { className: "flex-1 bg-primary", style: { height: 3 } })
21905
+ ]
20292
21906
  }
20293
- )
20294
- ] }),
20295
- maxBadge && /* @__PURE__ */ jsxs20(
20296
- "div",
20297
- {
20298
- "data-ohw-max-badge": "",
20299
- style: {
20300
- position: "fixed",
20301
- top: maxBadge.rect.bottom + 4,
20302
- left: maxBadge.rect.right,
20303
- transform: "translateX(-100%)",
20304
- zIndex: 2147483647,
20305
- background: maxBadge.current > maxBadge.max ? "#FEF2F2" : "#F5F5F4",
20306
- color: maxBadge.current > maxBadge.max ? "#DC2626" : "#78716C",
20307
- border: `1px solid ${maxBadge.current > maxBadge.max ? "#FECACA" : "#E7E5E4"}`,
20308
- borderRadius: 4,
20309
- padding: "2px 6px",
20310
- fontSize: 11,
20311
- fontWeight: 500,
20312
- pointerEvents: "none"
21907
+ ),
21908
+ linkPopover && dialogPortalContainer ? /* @__PURE__ */ jsx33(
21909
+ LinkPopover,
21910
+ {
21911
+ panelRef: linkPopoverPanelRef,
21912
+ portalContainer: dialogPortalContainer,
21913
+ open: true,
21914
+ mode: linkPopover.mode ?? "edit",
21915
+ pages: sitePages,
21916
+ sections: currentSections,
21917
+ sectionsByPath,
21918
+ initialTarget: linkPopover.target,
21919
+ existingTargets: linkPopover.intent === "add-nav" ? getNavbarExistingTargets() : [],
21920
+ onClose: closeLinkPopover,
21921
+ onSubmit: handleLinkPopoverSubmit
20313
21922
  },
20314
- children: [
20315
- maxBadge.current,
20316
- "/",
20317
- maxBadge.max
20318
- ]
20319
- }
20320
- ),
20321
- toggleState && !linkPopover && /* @__PURE__ */ jsx33(
20322
- StateToggle,
20323
- {
20324
- rect: toggleState.rect,
20325
- activeState: toggleState.activeState,
20326
- states: toggleState.states,
20327
- onStateChange: handleStateChange
20328
- }
20329
- ),
20330
- sectionGap && !linkPopover && /* @__PURE__ */ jsxs20(
20331
- "div",
20332
- {
20333
- "data-ohw-section-insert-line": "",
20334
- className: "fixed left-0 w-full z-2147483646 flex items-center pointer-events-none",
20335
- style: { top: sectionGap.y, transform: "translateY(-50%)" },
20336
- children: [
20337
- /* @__PURE__ */ jsx33("div", { className: "flex-1 bg-primary", style: { height: 3 } }),
20338
- /* @__PURE__ */ jsx33(
20339
- Badge,
21923
+ linkPopover.key
21924
+ ) : null,
21925
+ floatingPanel && floatingPanel.kind === "socials-display" ? /* @__PURE__ */ jsx33(
21926
+ FloatingPanel,
21927
+ {
21928
+ open: true,
21929
+ title: floatingPanel.title,
21930
+ context: floatingPanel.context,
21931
+ position: floatingPanelPos,
21932
+ onPositionChange: setFloatingPanelPos,
21933
+ parentScroll: parentScrollSnap ?? parentScrollRef.current,
21934
+ onClose: closeFloatingPanelOnly,
21935
+ children: /* @__PURE__ */ jsx33(
21936
+ SocialsDisplayPanel,
20340
21937
  {
20341
- 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",
20342
- onClick: () => {
20343
- window.parent.postMessage(
20344
- {
20345
- type: "ow:add-section",
20346
- insertAfter: sectionGap.insertAfter,
20347
- insertBefore: sectionGap.insertBefore
20348
- },
20349
- "*"
20350
- );
20351
- },
20352
- children: "Add Section"
21938
+ display: socialsDisplayFor(floatingPanel.row, editContentRef.current),
21939
+ onChange: (next) => {
21940
+ changeSocialsDisplay(floatingPanel.row, next);
21941
+ setFloatingPanel({ ...floatingPanel });
21942
+ }
20353
21943
  }
20354
- ),
20355
- /* @__PURE__ */ jsx33("div", { className: "flex-1 bg-primary", style: { height: 3 } })
20356
- ]
20357
- }
20358
- ),
20359
- linkPopover && dialogPortalContainer ? /* @__PURE__ */ jsx33(
20360
- LinkPopover,
20361
- {
20362
- panelRef: linkPopoverPanelRef,
20363
- portalContainer: dialogPortalContainer,
20364
- open: true,
20365
- mode: linkPopover.mode ?? "edit",
20366
- pages: sitePages,
20367
- sections: currentSections,
20368
- sectionsByPath,
20369
- initialTarget: linkPopover.target,
20370
- existingTargets: linkPopover.intent === "add-nav" ? getNavbarExistingTargets() : [],
20371
- onClose: closeLinkPopover,
20372
- onSubmit: handleLinkPopoverSubmit
21944
+ )
21945
+ }
21946
+ ) : null
21947
+ ] }),
21948
+ bridgeRoot
21949
+ ) : null
21950
+ ] });
21951
+ }
21952
+
21953
+ // src/ui/EmptySection.tsx
21954
+ import Link3 from "next/link";
21955
+ import { Fragment as Fragment9, jsx as jsx34, jsxs as jsxs21 } from "react/jsx-runtime";
21956
+ function EmptySection({ title, homeHref = "/", eyebrowKey, titleKey, subtitleKey }) {
21957
+ return /* @__PURE__ */ jsxs21(Fragment9, { children: [
21958
+ /* @__PURE__ */ jsx34(
21959
+ "p",
21960
+ {
21961
+ style: {
21962
+ fontFamily: "var(--brand-font-body)",
21963
+ fontSize: "0.75rem",
21964
+ fontWeight: 500,
21965
+ letterSpacing: "0.15em",
21966
+ textTransform: "uppercase",
21967
+ color: "var(--brand-accent)",
21968
+ marginBottom: "1.5rem"
20373
21969
  },
20374
- linkPopover.key
20375
- ) : null,
20376
- floatingPanel && floatingPanel.kind === "socials-display" ? /* @__PURE__ */ jsx33(
20377
- FloatingPanel,
20378
- {
20379
- open: true,
20380
- title: floatingPanel.title,
20381
- context: floatingPanel.context,
20382
- position: floatingPanelPos,
20383
- onPositionChange: setFloatingPanelPos,
20384
- parentScroll: parentScrollSnap ?? parentScrollRef.current,
20385
- onClose: closeFloatingPanelOnly,
20386
- children: /* @__PURE__ */ jsx33(
20387
- SocialsDisplayPanel,
20388
- {
20389
- display: socialsDisplayFor(floatingPanel.row, editContentRef.current),
20390
- onChange: (next) => {
20391
- changeSocialsDisplay(floatingPanel.row, next);
20392
- setFloatingPanel({ ...floatingPanel });
20393
- }
20394
- }
20395
- )
20396
- }
20397
- ) : null
20398
- ] }),
20399
- bridgeRoot
20400
- ) : null;
21970
+ children: /* @__PURE__ */ jsx34(Link3, { href: homeHref, style: { color: "inherit" }, children: /* @__PURE__ */ jsx34("span", { ...eyebrowKey ? { "data-ohw-editable": "text", "data-ohw-key": eyebrowKey } : {}, children: "Home" }) })
21971
+ }
21972
+ ),
21973
+ /* @__PURE__ */ jsx34(
21974
+ "h1",
21975
+ {
21976
+ style: {
21977
+ fontFamily: "var(--brand-font-heading)",
21978
+ fontSize: "clamp(2rem, 3.5vw, 2.75rem)",
21979
+ lineHeight: 1.1,
21980
+ letterSpacing: "-0.025em",
21981
+ color: "var(--brand-text)",
21982
+ marginBottom: "1rem"
21983
+ },
21984
+ ...titleKey ? { "data-ohw-editable": "text", "data-ohw-key": titleKey, "data-ohw-max-length": 80 } : {},
21985
+ children: title
21986
+ }
21987
+ ),
21988
+ /* @__PURE__ */ jsx34(
21989
+ "p",
21990
+ {
21991
+ style: {
21992
+ fontFamily: "var(--brand-font-body)",
21993
+ fontSize: "1rem",
21994
+ lineHeight: 1.7,
21995
+ fontWeight: 300,
21996
+ color: "var(--brand-text-muted)",
21997
+ maxWidth: "340px"
21998
+ },
21999
+ ...subtitleKey ? { "data-ohw-editable": "text", "data-ohw-key": subtitleKey, "data-ohw-max-length": 160 } : {},
22000
+ children: "This page doesn't have any content yet."
22001
+ }
22002
+ )
22003
+ ] });
20401
22004
  }
20402
22005
  export {
20403
22006
  AI_DEFAULT_BRAND,
@@ -20415,6 +22018,7 @@ export {
20415
22018
  DropdownMenuItem,
20416
22019
  DropdownMenuSeparator,
20417
22020
  DropdownMenuTrigger,
22021
+ EmptySection,
20418
22022
  ItemActionToolbar,
20419
22023
  ItemInteractionLayer,
20420
22024
  LinkEditorPanel,