@ohhwells/bridge 0.1.74 → 0.1.76-next.230

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,167 @@ 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 findByInstanceId(instanceId) {
8055
+ const escapedId = CSS.escape(instanceId);
8056
+ return document.querySelector(`[data-ohw-instance="${escapedId}"]`) ?? document.querySelector(`[data-ohw-section="${escapedId}"]:not([data-ohw-instance])`);
8057
+ }
8058
+ function planSectionMove(instanceId, targetIndex, currentPath) {
8059
+ const sections = topLevelSections();
8060
+ const index = sections.findIndex((el) => instanceIdOf(el) === instanceId);
8061
+ if (index === -1) return null;
8062
+ const dragged = sections[index];
8063
+ const others = sections.filter((_, i) => i !== index);
8064
+ const clamped = Math.max(0, Math.min(targetIndex, others.length));
8065
+ const reordered = [...others.slice(0, clamped), dragged, ...others.slice(clamped)];
8066
+ return reordered.map((el, order) => ({
8067
+ instanceId: instanceIdOf(el),
8068
+ type: el.getAttribute("data-ohw-section") ?? "",
8069
+ order,
8070
+ pagePath: currentPath
8071
+ }));
8072
+ }
8073
+ function moveSectionInstance(instanceId, direction, currentPath) {
8074
+ const sections = topLevelSections();
8075
+ const index = sections.findIndex((el) => instanceIdOf(el) === instanceId);
8076
+ if (index === -1) return null;
8077
+ const siblingIndex = direction === "up" ? index - 1 : index + 1;
8078
+ if (siblingIndex < 0 || siblingIndex >= sections.length) return null;
8079
+ const entries = planSectionMove(instanceId, siblingIndex, currentPath);
8080
+ if (!entries) return null;
8081
+ applyPersistedOrder(entries);
8082
+ return entries;
8083
+ }
8084
+ function syncRemovedFlags(entries) {
8085
+ const removedIds = new Set(entries.filter((e) => e.removed).map((e) => e.instanceId));
8086
+ document.querySelectorAll(`[${REMOVED_ATTR2}]`).forEach((el) => {
8087
+ if (!removedIds.has(instanceIdOf(el))) {
8088
+ el.style.removeProperty("display");
8089
+ el.removeAttribute(REMOVED_ATTR2);
8090
+ }
8091
+ });
8092
+ for (const id of removedIds) {
8093
+ const el = findByInstanceId(id);
8094
+ if (el) {
8095
+ el.style.display = "none";
8096
+ el.setAttribute(REMOVED_ATTR2, "");
8097
+ }
8098
+ }
8099
+ }
8100
+ function applyPersistedOrder(entries) {
8101
+ syncRemovedFlags(entries);
8102
+ if (entries.length === 0) return;
8103
+ const sections = topLevelSections();
8104
+ if (sections.length === 0) return;
8105
+ const orderIndex = new Map(entries.map((e) => [e.instanceId, e.order]));
8106
+ const ordered = [...sections].sort((a, b) => {
8107
+ const aOrder = orderIndex.get(instanceIdOf(a));
8108
+ const bOrder = orderIndex.get(instanceIdOf(b));
8109
+ if (aOrder === void 0 && bOrder === void 0) return 0;
8110
+ if (aOrder === void 0) return 1;
8111
+ if (bOrder === void 0) return -1;
8112
+ return aOrder - bOrder;
8113
+ });
8114
+ let prev = null;
8115
+ for (const el of ordered) {
8116
+ if (prev) prev.after(el);
8117
+ prev = el;
8118
+ }
8119
+ }
8120
+ function setSectionRemoved(instanceId, currentPath, existingEntries, removed) {
8121
+ if (!findByInstanceId(instanceId)) return null;
8122
+ const byId = new Map(existingEntries.map((e) => [e.instanceId, e]));
8123
+ const allSections = Array.from(document.querySelectorAll("[data-ohw-section]")).filter(
8124
+ (el) => !el.parentElement?.closest("[data-ohw-section]") && !isChromeSection(el)
8125
+ );
8126
+ allSections.forEach((el, order) => {
8127
+ const id = instanceIdOf(el);
8128
+ if (!byId.has(id)) {
8129
+ byId.set(id, { instanceId: id, type: el.getAttribute("data-ohw-section") ?? "", order, pagePath: currentPath });
8130
+ }
8131
+ });
8132
+ const target = byId.get(instanceId);
8133
+ if (!target) return null;
8134
+ byId.set(instanceId, { ...target, removed });
8135
+ const entries = Array.from(byId.values());
8136
+ applyPersistedOrder(entries);
8137
+ return entries;
8138
+ }
8139
+ function deleteSectionInstance(instanceId, currentPath, existingEntries) {
8140
+ return setSectionRemoved(instanceId, currentPath, existingEntries, true);
8141
+ }
8142
+ function restoreSectionInstance(instanceId, currentPath, existingEntries) {
8143
+ return setSectionRemoved(instanceId, currentPath, existingEntries, false);
8144
+ }
8145
+ function newInstanceId() {
8146
+ return typeof crypto !== "undefined" && "randomUUID" in crypto ? crypto.randomUUID() : `instance-${Date.now()}-${Math.random().toString(36).slice(2)}`;
8147
+ }
8148
+ function getPageSectionOrderEntries(raw, currentPath) {
8149
+ if (!raw) return [];
8150
+ try {
8151
+ const entries = JSON.parse(raw);
8152
+ return entries.filter((e) => !e.pagePath || e.pagePath === currentPath);
8153
+ } catch {
8154
+ return [];
8155
+ }
8156
+ }
8157
+ function rekeySectionSubtree(root, instanceId) {
8158
+ const suffix = `::${instanceId}`;
8159
+ const rekey = (el, attr) => {
8160
+ const current = el.getAttribute(attr);
8161
+ if (current) el.setAttribute(attr, `${current}${suffix}`);
8162
+ };
8163
+ if (root.hasAttribute("data-ohw-key")) rekey(root, "data-ohw-key");
8164
+ if (root.hasAttribute("data-ohw-href-key")) rekey(root, "data-ohw-href-key");
8165
+ root.querySelectorAll("[data-ohw-key]").forEach((el) => rekey(el, "data-ohw-key"));
8166
+ root.querySelectorAll("[data-ohw-href-key]").forEach((el) => rekey(el, "data-ohw-href-key"));
8167
+ }
8168
+ function initSectionInstancesFromContent(content, currentPath) {
8169
+ document.querySelectorAll("[data-ohw-section]:not([data-ohw-instance])").forEach((el) => {
8170
+ el.setAttribute("data-ohw-instance", el.getAttribute("data-ohw-section") ?? "");
8171
+ });
8172
+ const entries = getPageSectionOrderEntries(content[SECTION_ORDER_KEY], currentPath);
8173
+ for (const entry of entries) {
8174
+ if (entry.instanceId === entry.type) continue;
8175
+ if (document.querySelector(`[data-ohw-instance="${CSS.escape(entry.instanceId)}"]`)) continue;
8176
+ const original = document.querySelector(
8177
+ `[data-ohw-section="${CSS.escape(entry.type)}"][data-ohw-instance="${CSS.escape(entry.type)}"]`
8178
+ );
8179
+ if (!original) continue;
8180
+ const clone = original.cloneNode(true);
8181
+ clone.setAttribute("data-ohw-instance", entry.instanceId);
8182
+ rekeySectionSubtree(clone, entry.instanceId);
8183
+ original.insertAdjacentElement("afterend", clone);
8184
+ }
8185
+ applyPersistedOrder(entries);
8186
+ }
8187
+
8188
+ // src/ui/ai-section/AiSectionOverlay.tsx
8189
+ import { Fragment as Fragment5, jsx as jsx17, jsxs as jsxs9 } from "react/jsx-runtime";
8190
+ function findSectionElement(instanceId) {
8191
+ const escaped = CSS.escape(instanceId);
8192
+ return document.querySelector(`[data-ohw-instance="${escaped}"]`) ?? document.querySelector(`[data-ohw-section="${escaped}"]:not([data-ohw-instance])`);
8193
+ }
8194
+ function readRect(instanceId) {
8195
+ const el = findSectionElement(instanceId);
8196
+ if (!el) return null;
8197
+ const r2 = el.getBoundingClientRect();
8198
+ if (r2.width <= 0 || r2.height <= 0) return null;
8199
+ return { top: r2.top, left: r2.left, width: r2.width, height: r2.height };
8200
+ }
7354
8201
  function useLiveSectionRect(sectionId) {
7355
8202
  const [rect, setRect] = useState5(null);
7356
8203
  useEffect4(() => {
@@ -7368,7 +8215,7 @@ function useLiveSectionRect(sectionId) {
7368
8215
  const opts = { capture: true, passive: true };
7369
8216
  window.addEventListener("scroll", update, opts);
7370
8217
  window.addEventListener("resize", update);
7371
- const el = document.querySelector(`[data-ohw-section="${CSS.escape(sectionId)}"]`);
8218
+ const el = findSectionElement(sectionId);
7372
8219
  const ro = el ? new ResizeObserver(update) : null;
7373
8220
  if (el && ro) ro.observe(el);
7374
8221
  const interval = setInterval(update, 500);
@@ -7381,6 +8228,12 @@ function useLiveSectionRect(sectionId) {
7381
8228
  }, [sectionId]);
7382
8229
  return rect;
7383
8230
  }
8231
+ function computeSectionBoundaryFlags(instanceId) {
8232
+ const topLevel = topLevelSections();
8233
+ const index = topLevel.findIndex((el) => (el.dataset.ohwInstance ?? el.dataset.ohwSection) === instanceId);
8234
+ if (index === -1) return { isFirst: true, isLast: true };
8235
+ return { isFirst: index === 0, isLast: index === topLevel.length - 1 };
8236
+ }
7384
8237
  var PRIMARY2 = "#0885FE";
7385
8238
  function edgeAwareRadius(rect) {
7386
8239
  const container = window.innerWidth <= 480 ? 16 : 24;
@@ -7454,6 +8307,7 @@ function AiSectionOverlay({
7454
8307
  }) {
7455
8308
  const [selectedId, setSelectedId] = useState5(null);
7456
8309
  const [reviewId, setReviewId] = useState5(null);
8310
+ const [reviewButtonsHidden, setReviewButtonsHidden] = useState5(false);
7457
8311
  const reviewIdRef = useRef4(null);
7458
8312
  reviewIdRef.current = reviewId;
7459
8313
  const selectedIdRef = useRef4(null);
@@ -7462,7 +8316,7 @@ function AiSectionOverlay({
7462
8316
  (el) => {
7463
8317
  postToParent2({
7464
8318
  type: "ow:section-selected",
7465
- sectionId: el?.dataset.ohwSection ?? null,
8319
+ sectionId: el ? el.dataset.ohwInstance ?? el.dataset.ohwSection ?? null : null,
7466
8320
  sectionLabel: el ? el.dataset.ohwSectionLabel ?? titleCaseSectionId(el.dataset.ohwSection ?? "") : null
7467
8321
  });
7468
8322
  },
@@ -7471,7 +8325,7 @@ function AiSectionOverlay({
7471
8325
  const selectFromElement = useCallback2(
7472
8326
  (el, options) => {
7473
8327
  const sectionEl = el?.closest("[data-ohw-section]") ?? null;
7474
- const id = sectionEl?.dataset.ohwSection ?? null;
8328
+ const id = sectionEl ? sectionEl.dataset.ohwInstance ?? sectionEl.dataset.ohwSection ?? null : null;
7475
8329
  if (id === selectedIdRef.current) return;
7476
8330
  setSelectedId(id);
7477
8331
  if (options?.report !== false) report(sectionEl);
@@ -7492,12 +8346,15 @@ function AiSectionOverlay({
7492
8346
  selectFromElement(sectionEl);
7493
8347
  return sectionEl != null;
7494
8348
  },
7495
- clear: () => setSelectedId(null)
8349
+ clear: () => {
8350
+ setSelectedId(null);
8351
+ report(null);
8352
+ }
7496
8353
  };
7497
8354
  return () => {
7498
8355
  apiRef.current = null;
7499
8356
  };
7500
- }, [apiRef, selectFromElement]);
8357
+ }, [apiRef, selectFromElement, report]);
7501
8358
  useEffect4(() => {
7502
8359
  const onMessage = (e) => {
7503
8360
  if (e.data?.type === "ow:ai-select" && e.data.sectionId === null) {
@@ -7512,9 +8369,10 @@ function AiSectionOverlay({
7512
8369
  }
7513
8370
  const found = readRect(sectionId) != null;
7514
8371
  setReviewId(found ? sectionId : null);
8372
+ setReviewButtonsHidden(e.data.hideButtons === true);
7515
8373
  postToParent2({ type: "ow:ai-review-started", sectionId, found });
7516
8374
  if (found) {
7517
- document.querySelector(`[data-ohw-section="${CSS.escape(sectionId)}"]`)?.scrollIntoView({ behavior: "smooth", block: "nearest" });
8375
+ document.querySelector(`[data-ohw-instance="${CSS.escape(sectionId)}"]`)?.scrollIntoView({ behavior: "smooth", block: "nearest" });
7518
8376
  }
7519
8377
  }
7520
8378
  };
@@ -7533,7 +8391,7 @@ function AiSectionOverlay({
7533
8391
  return;
7534
8392
  }
7535
8393
  const sec = t.closest("[data-ohw-section]");
7536
- setHoveredId(sec?.dataset.ohwSection ?? null);
8394
+ setHoveredId(sec ? sec.dataset.ohwInstance ?? sec.dataset.ohwSection ?? null : null);
7537
8395
  };
7538
8396
  const onLeave = () => setHoveredId(null);
7539
8397
  document.addEventListener("mousemove", onMove, { passive: true });
@@ -7565,9 +8423,30 @@ function AiSectionOverlay({
7565
8423
  },
7566
8424
  [postToParent2]
7567
8425
  );
7568
- const selectionRect = useLiveSectionRect(reviewId ? null : selectedId);
8426
+ const activeSelectionId = reviewId ? null : selectedId;
8427
+ const selectionRect = useLiveSectionRect(activeSelectionId);
7569
8428
  const reviewRect = useLiveSectionRect(reviewId);
7570
8429
  const hoverRect = useLiveSectionRect(reviewId || hoveredId === selectedId ? null : hoveredId);
8430
+ useEffect4(() => {
8431
+ const selectedEl = activeSelectionId ? findSectionElement(activeSelectionId) : null;
8432
+ if (!activeSelectionId || !selectionRect || selectedEl && isChromeSection(selectedEl)) {
8433
+ postToParent2({ type: "ow:section-rect", instanceId: null, rect: null });
8434
+ return;
8435
+ }
8436
+ const { isFirst, isLast } = computeSectionBoundaryFlags(activeSelectionId);
8437
+ postToParent2({
8438
+ type: "ow:section-rect",
8439
+ instanceId: activeSelectionId,
8440
+ rect: {
8441
+ top: selectionRect.top + window.scrollY,
8442
+ left: selectionRect.left + window.scrollX,
8443
+ width: selectionRect.width,
8444
+ height: selectionRect.height
8445
+ },
8446
+ isFirst,
8447
+ isLast
8448
+ });
8449
+ }, [activeSelectionId, selectionRect, postToParent2]);
7571
8450
  return /* @__PURE__ */ jsxs9(Fragment5, { children: [
7572
8451
  hoverRect && /* @__PURE__ */ jsx17(
7573
8452
  "div",
@@ -7618,13 +8497,16 @@ function AiSectionOverlay({
7618
8497
  border: `2px solid ${PRIMARY2}`,
7619
8498
  borderRadius: edgeAwareRadius(reviewRect),
7620
8499
  zIndex: 2147483200,
7621
- // The veil itself: swallows clicks so the section stays locked until decided.
8500
+ // The veil itself: swallows clicks so the section stays locked until decided. This
8501
+ // stopPropagation only guards the bubble phase; the bridge's capture-phase click
8502
+ // handlers additionally bail on `[data-ohw-ai-review]`, without which a click on
8503
+ // Accept/Discard resolves to the media beneath and opens the file picker.
7622
8504
  background: "rgba(8, 133, 254, 0.04)",
7623
8505
  pointerEvents: "auto",
7624
8506
  cursor: "default"
7625
8507
  },
7626
8508
  onClick: (e) => e.stopPropagation(),
7627
- children: /* @__PURE__ */ jsxs9(
8509
+ children: !reviewButtonsHidden && /* @__PURE__ */ jsxs9(
7628
8510
  "div",
7629
8511
  {
7630
8512
  style: {
@@ -7647,47 +8529,6 @@ function AiSectionOverlay({
7647
8529
  ] });
7648
8530
  }
7649
8531
 
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
8532
  // src/OhhwellsBridge.tsx
7692
8533
  import { createPortal as createPortal2 } from "react-dom";
7693
8534
  import { usePathname as usePathname2, useRouter as useRouter3, useSearchParams } from "next/navigation";
@@ -10232,8 +11073,13 @@ function referenceBox(slot) {
10232
11073
  const neighbour = row ? Array.from(row.querySelectorAll('[data-ohw-editable="icon"]')).find(
10233
11074
  (el) => el !== slot && !el.hasAttribute("data-ohw-social-icon-placeholder")
10234
11075
  ) : null;
10235
- const source = (neighbour ?? slot).querySelector(GLYPH_SELECTOR);
10236
- const box = source?.getBoundingClientRect() ?? null;
11076
+ if (neighbour) {
11077
+ const box2 = neighbour.querySelector(GLYPH_SELECTOR)?.getBoundingClientRect();
11078
+ if (box2?.width && box2.height) return box2;
11079
+ }
11080
+ const own = slot.getBoundingClientRect();
11081
+ if (own.width && own.height) return own;
11082
+ const box = slot.querySelector(GLYPH_SELECTOR)?.getBoundingClientRect() ?? null;
10237
11083
  return box?.width && box.height ? box : null;
10238
11084
  }
10239
11085
  function iconMarkupSizedFor(slot, markup) {
@@ -12034,6 +12880,7 @@ function readLogoSizeState(content, placement) {
12034
12880
  function getLogoElement(el) {
12035
12881
  const marked = el.closest('[data-ohw-role="logo"], [data-ohw-logo]');
12036
12882
  if (marked) return marked;
12883
+ if (el.closest('[data-ohw-editable="icon"]')) return null;
12037
12884
  const root = el.closest("nav, [data-ohw-nav-root], footer");
12038
12885
  if (!root) return null;
12039
12886
  const anchor = el.closest("a");
@@ -12913,6 +13760,334 @@ function useNavItemDrag({
12913
13760
  };
12914
13761
  }
12915
13762
 
13763
+ // src/useSectionDrag.ts
13764
+ import { useCallback as useCallback7, useEffect as useEffect11, useRef as useRef9, useState as useState11 } from "react";
13765
+
13766
+ // src/lib/section-dnd.ts
13767
+ function isFooterSection(el) {
13768
+ return el.dataset.ohwSection === "footer";
13769
+ }
13770
+ function buildSectionDropSlots(draggedInstanceId) {
13771
+ const sections = topLevelSections().filter(
13772
+ (el) => instanceIdOf(el) !== draggedInstanceId && !isFooterSection(el)
13773
+ );
13774
+ const slots = [];
13775
+ if (sections.length === 0) return slots;
13776
+ const left = 0;
13777
+ const width = document.documentElement.clientWidth;
13778
+ for (let i = 0; i <= sections.length; i++) {
13779
+ let y;
13780
+ if (i === 0) {
13781
+ y = sections[0].getBoundingClientRect().top;
13782
+ } else if (i === sections.length) {
13783
+ y = sections[sections.length - 1].getBoundingClientRect().bottom;
13784
+ } else {
13785
+ const prev = sections[i - 1].getBoundingClientRect();
13786
+ const next = sections[i].getBoundingClientRect();
13787
+ y = (prev.bottom + next.top) / 2;
13788
+ }
13789
+ slots.push({ insertIndex: i, y, left, width });
13790
+ }
13791
+ return slots;
13792
+ }
13793
+ function hitTestSectionDropSlot(y, slots) {
13794
+ let best = null;
13795
+ for (const slot of slots) {
13796
+ const dist = Math.abs(y - slot.y);
13797
+ if (!best || dist < best.dist) best = { slot, dist };
13798
+ }
13799
+ return best?.slot ?? null;
13800
+ }
13801
+
13802
+ // src/useSectionDrag.ts
13803
+ var PRESS_THRESHOLD = 10;
13804
+ var EDGE_ZONE = 60;
13805
+ var MAX_AUTO_SCROLL_SPEED = 18;
13806
+ var SECTION_DRAG_EXCLUDED_SELECTOR = [
13807
+ "[data-ohw-toolbar]",
13808
+ "[data-ohw-edit-chrome]",
13809
+ "[data-ohw-item-interaction]",
13810
+ "[data-ohw-drag-handle-container]",
13811
+ '[data-slot="drag-handle"]',
13812
+ "[data-ohw-item-toolbar-anchor]",
13813
+ "[data-ohw-item-drag-surface]",
13814
+ "[data-ohw-more-menu]",
13815
+ '[data-slot="dropdown-menu-content"]',
13816
+ '[data-slot="dropdown-menu-item"]',
13817
+ "[data-ohw-state-toggle]",
13818
+ "[data-ohw-max-badge]",
13819
+ "[data-ohw-floating-panel]",
13820
+ "[data-ohw-section-picker]",
13821
+ "[data-ohw-link-popover-root]",
13822
+ "[data-ohw-link-modal-root]",
13823
+ "[data-ohw-link-page-dropdown]",
13824
+ '[data-slot="popover-content"]',
13825
+ '[data-slot="dialog-content"]',
13826
+ '[data-slot="dialog-overlay"]',
13827
+ "[data-ohw-ai-review]",
13828
+ "[data-ohw-editable]",
13829
+ "[data-ohw-editable-state]",
13830
+ "[contenteditable]",
13831
+ "[data-ohw-href-key]",
13832
+ "[data-ohw-footer-col]",
13833
+ "[data-ohw-social-label]",
13834
+ "a",
13835
+ "button",
13836
+ '[role="button"]',
13837
+ '[data-ohw-role="navbar-button"]',
13838
+ '[data-ohw-role="button"]',
13839
+ "[data-ohw-carousel]",
13840
+ "[data-ohw-carousel-value]",
13841
+ "[data-ohw-carousel-slide]",
13842
+ "[data-ohw-carousel-overlay]",
13843
+ "[data-ohw-media-chrome]",
13844
+ "[data-ohw-media-overlay]",
13845
+ "[data-ohw-media-skeleton]"
13846
+ ].join(", ");
13847
+ function visibleClip(ps) {
13848
+ if (!ps) return null;
13849
+ const top = Math.max(0, ps.headerH - ps.iframeOffsetTop);
13850
+ const bottom = Math.min(window.innerHeight, ps.headerH + ps.canvasH - ps.iframeOffsetTop);
13851
+ return { top, bottom: Math.max(top, bottom) };
13852
+ }
13853
+ function useSectionDrag({
13854
+ isEditMode,
13855
+ editContentRef,
13856
+ postToParentRef,
13857
+ parentScrollRef,
13858
+ navDragRef,
13859
+ footerDragRef,
13860
+ suppressNextClickRef,
13861
+ suppressClickUntilRef
13862
+ }) {
13863
+ const sectionDragRef = useRef9(null);
13864
+ const [sectionDropSlots, setSectionDropSlots] = useState11([]);
13865
+ const [activeSectionDropIndex, setActiveSectionDropIndex] = useState11(null);
13866
+ const [isSectionDragging, setIsSectionDragging] = useState11(false);
13867
+ const sectionPointerDragRef = useRef9(null);
13868
+ const autoScrollRafRef = useRef9(null);
13869
+ const autoScrollDeltaRef = useRef9(0);
13870
+ const stopAutoScroll = useCallback7(() => {
13871
+ if (autoScrollRafRef.current != null) {
13872
+ cancelAnimationFrame(autoScrollRafRef.current);
13873
+ autoScrollRafRef.current = null;
13874
+ }
13875
+ autoScrollDeltaRef.current = 0;
13876
+ }, []);
13877
+ const tickAutoScroll = useCallback7(() => {
13878
+ if (!sectionDragRef.current) {
13879
+ stopAutoScroll();
13880
+ return;
13881
+ }
13882
+ if (autoScrollDeltaRef.current !== 0) {
13883
+ postToParentRef.current({ type: "ow:request-scroll", deltaY: autoScrollDeltaRef.current });
13884
+ }
13885
+ autoScrollRafRef.current = requestAnimationFrame(tickAutoScroll);
13886
+ }, [postToParentRef, stopAutoScroll]);
13887
+ const updateAutoScroll = useCallback7(
13888
+ (clientY) => {
13889
+ const clip = visibleClip(parentScrollRef.current);
13890
+ let delta = 0;
13891
+ if (clip) {
13892
+ const distTop = clientY - clip.top;
13893
+ const distBottom = clip.bottom - clientY;
13894
+ if (distTop >= 0 && distTop < EDGE_ZONE) {
13895
+ delta = -MAX_AUTO_SCROLL_SPEED * (1 - distTop / EDGE_ZONE);
13896
+ } else if (distBottom >= 0 && distBottom < EDGE_ZONE) {
13897
+ delta = MAX_AUTO_SCROLL_SPEED * (1 - distBottom / EDGE_ZONE);
13898
+ }
13899
+ }
13900
+ autoScrollDeltaRef.current = delta;
13901
+ if (delta !== 0 && autoScrollRafRef.current == null) {
13902
+ autoScrollRafRef.current = requestAnimationFrame(tickAutoScroll);
13903
+ } else if (delta === 0) {
13904
+ stopAutoScroll();
13905
+ }
13906
+ },
13907
+ [parentScrollRef, stopAutoScroll, tickAutoScroll]
13908
+ );
13909
+ const clearSectionDragVisuals = useCallback7(() => {
13910
+ sectionDragRef.current?.draggedEl.removeAttribute("data-ohw-section-dragging");
13911
+ sectionDragRef.current = null;
13912
+ setSectionDropSlots([]);
13913
+ setActiveSectionDropIndex(null);
13914
+ setIsSectionDragging(false);
13915
+ stopAutoScroll();
13916
+ document.documentElement.removeAttribute("data-ohw-section-dragging-root");
13917
+ unlockItemDragInteraction();
13918
+ }, [stopAutoScroll]);
13919
+ const refreshSectionDragVisuals = useCallback7(
13920
+ (session, clientX, clientY) => {
13921
+ session.lastClientX = clientX;
13922
+ session.lastClientY = clientY;
13923
+ const slots = buildSectionDropSlots(session.instanceId);
13924
+ const activeSlot = hitTestSectionDropSlot(clientY, slots);
13925
+ session.activeSlot = activeSlot;
13926
+ setSectionDropSlots(slots);
13927
+ const activeIdx = activeSlot ? slots.findIndex((s) => s.insertIndex === activeSlot.insertIndex) : -1;
13928
+ setActiveSectionDropIndex(activeIdx >= 0 ? activeIdx : null);
13929
+ updateAutoScroll(clientY);
13930
+ },
13931
+ [updateAutoScroll]
13932
+ );
13933
+ const beginSectionDrag = useCallback7(
13934
+ (session) => {
13935
+ sectionDragRef.current = session;
13936
+ setIsSectionDragging(true);
13937
+ lockItemDuringDrag();
13938
+ document.documentElement.setAttribute("data-ohw-section-dragging-root", "");
13939
+ session.draggedEl.setAttribute("data-ohw-section-dragging", "");
13940
+ refreshSectionDragVisuals(session, session.lastClientX, session.lastClientY);
13941
+ },
13942
+ [refreshSectionDragVisuals]
13943
+ );
13944
+ const commitSectionDrag = useCallback7(() => {
13945
+ const session = sectionDragRef.current;
13946
+ if (!session) {
13947
+ clearSectionDragVisuals();
13948
+ return;
13949
+ }
13950
+ const slot = session.activeSlot ?? hitTestSectionDropSlot(session.lastClientY, buildSectionDropSlots(session.instanceId));
13951
+ const entries = slot ? planSectionMove(session.instanceId, slot.insertIndex, window.location.pathname) : null;
13952
+ if (!entries) {
13953
+ clearSectionDragVisuals();
13954
+ return;
13955
+ }
13956
+ const orderJson = JSON.stringify(entries);
13957
+ editContentRef.current = { ...editContentRef.current, [SECTION_ORDER_KEY]: orderJson };
13958
+ setAiSectionOrder(orderJson, window.location.pathname);
13959
+ postToParentRef.current({ type: "ow:change", nodes: [{ key: SECTION_ORDER_KEY, text: orderJson }] });
13960
+ applyPersistedOrder(entries);
13961
+ clearSectionDragVisuals();
13962
+ requestAnimationFrame(() => {
13963
+ if (editContentRef.current[SECTION_ORDER_KEY] === orderJson) {
13964
+ applyPersistedOrder(entries);
13965
+ }
13966
+ requestAnimationFrame(() => {
13967
+ window.dispatchEvent(new Event("resize"));
13968
+ });
13969
+ });
13970
+ }, [clearSectionDragVisuals, editContentRef, postToParentRef]);
13971
+ const startSectionPressDrag = useCallback7(
13972
+ (el, clientX, clientY, pointerId) => {
13973
+ if (navDragRef.current || footerDragRef.current || sectionDragRef.current) return false;
13974
+ const instanceId = instanceIdOf(el);
13975
+ if (!instanceId) return false;
13976
+ sectionPointerDragRef.current = {
13977
+ el,
13978
+ instanceId,
13979
+ startX: clientX,
13980
+ startY: clientY,
13981
+ pointerId,
13982
+ started: false
13983
+ };
13984
+ return true;
13985
+ },
13986
+ [footerDragRef, navDragRef]
13987
+ );
13988
+ useEffect11(() => {
13989
+ if (!isEditMode) return;
13990
+ const onPointerDown = (e) => {
13991
+ if (e.button !== 0) return;
13992
+ if (navDragRef.current || footerDragRef.current || sectionDragRef.current) return;
13993
+ if (sectionPointerDragRef.current) return;
13994
+ const target = e.target;
13995
+ if (!(target instanceof HTMLElement)) return;
13996
+ if (target.closest(SECTION_DRAG_EXCLUDED_SELECTOR)) return;
13997
+ const sectionEl = target.closest("[data-ohw-section]");
13998
+ if (!sectionEl || isChromeSection(sectionEl) || sectionEl.dataset.ohwSection === "footer") return;
13999
+ if (!topLevelSections().includes(sectionEl)) return;
14000
+ startSectionPressDrag(sectionEl, e.clientX, e.clientY, e.pointerId);
14001
+ };
14002
+ const onPointerMove = (e) => {
14003
+ const pending = sectionPointerDragRef.current;
14004
+ if (!pending) return;
14005
+ if (pending.started) {
14006
+ e.preventDefault();
14007
+ clearTextSelection();
14008
+ const session = sectionDragRef.current;
14009
+ if (!session) return;
14010
+ refreshSectionDragVisuals(session, e.clientX, e.clientY);
14011
+ return;
14012
+ }
14013
+ const dx = e.clientX - pending.startX;
14014
+ const dy = e.clientY - pending.startY;
14015
+ if (dx * dx + dy * dy < PRESS_THRESHOLD * PRESS_THRESHOLD) return;
14016
+ e.preventDefault();
14017
+ pending.started = true;
14018
+ armItemPressDrag();
14019
+ clearTextSelection();
14020
+ try {
14021
+ document.body.setPointerCapture(pending.pointerId);
14022
+ } catch {
14023
+ }
14024
+ beginSectionDrag({
14025
+ instanceId: pending.instanceId,
14026
+ draggedEl: pending.el,
14027
+ lastClientX: e.clientX,
14028
+ lastClientY: e.clientY,
14029
+ activeSlot: null
14030
+ });
14031
+ };
14032
+ const endPointerDrag = (e) => {
14033
+ const pending = sectionPointerDragRef.current;
14034
+ sectionPointerDragRef.current = null;
14035
+ try {
14036
+ if (document.body.hasPointerCapture(e.pointerId)) {
14037
+ document.body.releasePointerCapture(e.pointerId);
14038
+ }
14039
+ } catch {
14040
+ }
14041
+ if (!pending) return;
14042
+ if (!pending.started) {
14043
+ unlockItemDragInteraction();
14044
+ return;
14045
+ }
14046
+ suppressNextClickRef.current = true;
14047
+ suppressClickUntilRef.current = Date.now() + 500;
14048
+ commitSectionDrag();
14049
+ };
14050
+ const onKeyDown = (e) => {
14051
+ if (e.key !== "Escape") return;
14052
+ if (!sectionDragRef.current && !sectionPointerDragRef.current) return;
14053
+ sectionPointerDragRef.current = null;
14054
+ clearSectionDragVisuals();
14055
+ };
14056
+ document.addEventListener("pointerdown", onPointerDown, true);
14057
+ document.addEventListener("pointermove", onPointerMove, true);
14058
+ document.addEventListener("pointerup", endPointerDrag, true);
14059
+ document.addEventListener("pointercancel", endPointerDrag, true);
14060
+ document.addEventListener("keydown", onKeyDown, true);
14061
+ return () => {
14062
+ document.removeEventListener("pointerdown", onPointerDown, true);
14063
+ document.removeEventListener("pointermove", onPointerMove, true);
14064
+ document.removeEventListener("pointerup", endPointerDrag, true);
14065
+ document.removeEventListener("pointercancel", endPointerDrag, true);
14066
+ document.removeEventListener("keydown", onKeyDown, true);
14067
+ unlockItemDragInteraction();
14068
+ stopAutoScroll();
14069
+ };
14070
+ }, [
14071
+ beginSectionDrag,
14072
+ clearSectionDragVisuals,
14073
+ commitSectionDrag,
14074
+ footerDragRef,
14075
+ isEditMode,
14076
+ navDragRef,
14077
+ refreshSectionDragVisuals,
14078
+ startSectionPressDrag,
14079
+ stopAutoScroll,
14080
+ suppressClickUntilRef,
14081
+ suppressNextClickRef
14082
+ ]);
14083
+ return {
14084
+ sectionDragRef,
14085
+ sectionDropSlots,
14086
+ activeSectionDropIndex,
14087
+ isSectionDragging
14088
+ };
14089
+ }
14090
+
12916
14091
  // src/ui/footer-container-chrome.tsx
12917
14092
  import { Plus as Plus2 } from "lucide-react";
12918
14093
  import { jsx as jsx29, jsxs as jsxs19 } from "react/jsx-runtime";
@@ -12965,7 +14140,7 @@ function FooterContainerChrome({
12965
14140
  }
12966
14141
 
12967
14142
  // src/lib/carousel.ts
12968
- import { useEffect as useEffect11, useState as useState11 } from "react";
14143
+ import { useEffect as useEffect12, useState as useState12 } from "react";
12969
14144
  var CAROUSEL_ATTR = "data-ohw-carousel";
12970
14145
  var CAROUSEL_VALUE_ATTR = "data-ohw-carousel-value";
12971
14146
  var CAROUSEL_SLIDE_ATTR = "data-ohw-carousel-slide";
@@ -13027,8 +14202,8 @@ function applyCarouselNode(key, val) {
13027
14202
  return true;
13028
14203
  }
13029
14204
  function useOhwCarousel(key, initial) {
13030
- const [images, setImages] = useState11(initial);
13031
- useEffect11(() => {
14205
+ const [images, setImages] = useState12(initial);
14206
+ useEffect12(() => {
13032
14207
  const el = document.querySelector(
13033
14208
  `[${CAROUSEL_ATTR}][data-ohw-key="${CSS.escape(key)}"]`
13034
14209
  );
@@ -13110,6 +14285,7 @@ function collectEditableNodes(extraContent, root = document) {
13110
14285
  NAV_ORDER_KEY,
13111
14286
  FOOTER_ORDER_KEY,
13112
14287
  NAV_COUNT_KEY,
14288
+ SECTION_ORDER_KEY,
13113
14289
  // A socials row's order and its icons-vs-words setting live under keys no element carries,
13114
14290
  // so collecting the DOM alone left them behind: the draft knew the row was showing icons and
13115
14291
  // had gained an item, and the published page went back to the template's own (OHH-736).
@@ -13574,6 +14750,7 @@ function fadeInImageElement(img, onReady) {
13574
14750
  function applyEditableImageSrc(img, url) {
13575
14751
  img.removeAttribute("srcset");
13576
14752
  img.removeAttribute("sizes");
14753
+ if (img.loading === "lazy") img.loading = "eager";
13577
14754
  img.src = url;
13578
14755
  }
13579
14756
  function fadeInBgImage(el, url, onReady) {
@@ -13638,21 +14815,10 @@ function parseSchedulingInsertAfter(insertAfter) {
13638
14815
  insertBefore: insertAfter.slice(idx + INSERT_BEFORE_MARKER.length)
13639
14816
  };
13640
14817
  }
13641
- function resolveSchedulingInsert(insertAfter, explicitInsertBefore) {
13642
- const parsed = parseSchedulingInsertAfter(insertAfter);
13643
- const insertBefore = explicitInsertBefore ?? parsed.insertBefore;
13644
- const effectiveInsertAfter = insertBefore && parsed.insertBefore === null ? `${insertAfter}${INSERT_BEFORE_MARKER}${insertBefore}` : insertAfter;
13645
- return { effectiveInsertAfter, insertBefore };
13646
- }
13647
- function getSchedulingMountPoint(insertAfter) {
13648
- const { anchor } = parseSchedulingInsertAfter(insertAfter);
13649
- let anchorEl = document.querySelector(`[data-ohw-section="${anchor}"]`);
13650
- if (!anchorEl && anchor === "scheduling") {
13651
- const widgets = Array.from(document.querySelectorAll('[data-ohw-section^="scheduling-"]'));
13652
- anchorEl = widgets.at(-1) ?? null;
13653
- }
13654
- if (!anchorEl) return null;
13655
- return anchorEl.closest('[data-ohw-section-container="scheduling"]') ?? anchorEl;
14818
+ function resolveEntryAnchor(entry) {
14819
+ if (entry.anchorId !== void 0) return { anchorId: entry.anchorId, beforeId: entry.beforeId ?? null };
14820
+ const parsed = parseSchedulingInsertAfter(entry.insertAfter);
14821
+ return { anchorId: parsed.anchor, beforeId: parsed.insertBefore };
13656
14822
  }
13657
14823
  function schedulingMountDepth(insertAfter) {
13658
14824
  if (!insertAfter.includes(INSERT_BEFORE_MARKER)) return 0;
@@ -13669,8 +14835,7 @@ function getPageSchedulingEntries(raw) {
13669
14835
  }
13670
14836
  }
13671
14837
  function isSchedulingWidgetMissing(entry) {
13672
- const { effectiveInsertAfter } = resolveSchedulingInsert(entry.insertAfter);
13673
- return !document.querySelector(`[data-ohw-section="${schedulingSectionId(effectiveInsertAfter)}"]`);
14838
+ return !document.querySelector(`[data-ohw-section="${schedulingSectionId(entry.insertAfter)}"]`);
13674
14839
  }
13675
14840
  function hasMissingSchedulingWidgets(entries) {
13676
14841
  return entries.some(isSchedulingWidgetMissing);
@@ -13700,16 +14865,17 @@ function initSectionsFromContent(content, removeExisting = false) {
13700
14865
  } catch {
13701
14866
  }
13702
14867
  }
13703
- function mountSchedulingWidget(insertAfter, notifyOnConnect = false, scheduleId, explicitInsertBefore) {
13704
- const { effectiveInsertAfter, insertBefore } = resolveSchedulingInsert(insertAfter, explicitInsertBefore);
13705
- const sectionId = schedulingSectionId(effectiveInsertAfter);
14868
+ function mountSchedulingWidget(anchorId, notifyOnConnect = false, scheduleId, beforeId, existingWidgetId) {
14869
+ const widgetId = existingWidgetId ?? (beforeId ? `${anchorId}${INSERT_BEFORE_MARKER}${beforeId}` : anchorId);
14870
+ const sectionId = schedulingSectionId(widgetId);
13706
14871
  if (document.querySelector(`[data-ohw-section="${sectionId}"]`)) return false;
13707
- const mountPoint = getSchedulingMountPoint(effectiveInsertAfter);
13708
- if (!mountPoint) return false;
14872
+ const anchorEl = document.querySelector(`[data-ohw-section="${anchorId}"]`);
14873
+ if (!anchorEl) return false;
14874
+ const mountPoint = anchorEl.closest('[data-ohw-section-container="scheduling"]') ?? anchorEl;
13709
14875
  const container = document.createElement("div");
13710
14876
  container.dataset.ohwSectionContainer = "scheduling";
13711
- if (insertBefore) {
13712
- const beforeAnchor = document.querySelector(`[data-ohw-section="${insertBefore}"]`);
14877
+ if (beforeId) {
14878
+ const beforeAnchor = document.querySelector(`[data-ohw-section="${beforeId}"]`);
13713
14879
  const beforePoint = beforeAnchor?.closest('[data-ohw-section-container="scheduling"]') ?? beforeAnchor;
13714
14880
  if (!beforePoint) return false;
13715
14881
  beforePoint.insertAdjacentElement("beforebegin", container);
@@ -13720,19 +14886,25 @@ function mountSchedulingWidget(insertAfter, notifyOnConnect = false, scheduleId,
13720
14886
  }
13721
14887
  tail.insertAdjacentElement("afterend", container);
13722
14888
  }
13723
- const root = createRoot2(container);
13724
- flushSync2(() => {
13725
- root.render(
13726
- /* @__PURE__ */ jsx33(
13727
- SchedulingWidget,
13728
- {
13729
- notifyOnConnect,
13730
- initialScheduleId: scheduleId,
13731
- insertAfter: effectiveInsertAfter
13732
- }
13733
- )
13734
- );
13735
- });
14889
+ try {
14890
+ const root = createRoot2(container);
14891
+ flushSync2(() => {
14892
+ root.render(
14893
+ /* @__PURE__ */ jsx33(
14894
+ SchedulingWidget,
14895
+ {
14896
+ notifyOnConnect,
14897
+ initialScheduleId: scheduleId,
14898
+ insertAfter: widgetId
14899
+ }
14900
+ )
14901
+ );
14902
+ });
14903
+ } catch (err) {
14904
+ console.error("[ow:scheduling] render threw", err);
14905
+ container.remove();
14906
+ return false;
14907
+ }
13736
14908
  const tracker = getSectionsTracker();
13737
14909
  let sections = [];
13738
14910
  try {
@@ -13740,10 +14912,12 @@ function mountSchedulingWidget(insertAfter, notifyOnConnect = false, scheduleId,
13740
14912
  } catch {
13741
14913
  }
13742
14914
  const inEditor = typeof window !== "undefined" && window.self !== window.top;
13743
- if (inEditor && !sections.find((s) => s.type === "scheduling" && s.insertAfter === effectiveInsertAfter)) {
14915
+ if (inEditor && !sections.find((s) => s.type === "scheduling" && s.insertAfter === widgetId)) {
13744
14916
  sections.push({
13745
14917
  type: "scheduling",
13746
- insertAfter: effectiveInsertAfter,
14918
+ insertAfter: widgetId,
14919
+ anchorId,
14920
+ beforeId: beforeId ?? null,
13747
14921
  pagePath: window.location.pathname,
13748
14922
  ...scheduleId ? { scheduleId } : {}
13749
14923
  });
@@ -13757,7 +14931,8 @@ function mountSchedulingEntries(entries, notifyOnConnect = false) {
13757
14931
  for (let i = pending.length - 1; i >= 0; i--) {
13758
14932
  const entry = pending[i];
13759
14933
  const shouldNotify = typeof notifyOnConnect === "function" ? notifyOnConnect(entry) : notifyOnConnect;
13760
- if (mountSchedulingWidget(entry.insertAfter, shouldNotify, entry.scheduleId)) {
14934
+ const { anchorId, beforeId } = resolveEntryAnchor(entry);
14935
+ if (mountSchedulingWidget(anchorId, shouldNotify, entry.scheduleId ?? null, beforeId, entry.insertAfter)) {
13761
14936
  pending.splice(i, 1);
13762
14937
  }
13763
14938
  }
@@ -13915,6 +15090,11 @@ function applyLinkByKey(key, val) {
13915
15090
  hrefAnchors.forEach((el) => applyLinkHref(el, val));
13916
15091
  }
13917
15092
  }
15093
+ function isInsideLinkEditor(target) {
15094
+ return Boolean(
15095
+ 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"]')
15096
+ );
15097
+ }
13918
15098
  function isInsideFloatingPanel(target) {
13919
15099
  return Boolean(target.closest("[data-ohw-floating-panel]"));
13920
15100
  }
@@ -13922,11 +15102,6 @@ function isPointOverFloatingPanel(clientX, clientY) {
13922
15102
  const el = document.elementFromPoint(clientX, clientY);
13923
15103
  return Boolean(el instanceof Element && el.closest("[data-ohw-floating-panel]"));
13924
15104
  }
13925
- function isInsideLinkEditor(target) {
13926
- return Boolean(
13927
- 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"]')
13928
- );
13929
- }
13930
15105
  function getHrefKeyFromElement(el) {
13931
15106
  if (!el) return null;
13932
15107
  const anchor = el.closest("[data-ohw-href-key]");
@@ -14185,7 +15360,7 @@ function getNavigationSelectionParent(el) {
14185
15360
  if (!isFooterLinksContainer(el) && (el.hasAttribute("data-ohw-footer-col") || el.hasAttribute("data-ohw-footer-column") || isInferredFooterGroup2(el))) {
14186
15361
  return getFooterLinksContainer();
14187
15362
  }
14188
- if (el.hasAttribute("data-ohw-nav-container") || el.hasAttribute("data-ohw-nav-drawer") || isFooterLinksContainer(el)) {
15363
+ 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)) {
14189
15364
  return getNavigationRoot(el);
14190
15365
  }
14191
15366
  return null;
@@ -14400,7 +15575,6 @@ var ICONS = {
14400
15575
  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"/>',
14401
15576
  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"/>'
14402
15577
  };
14403
- var HEIGHT_SETTLE_DELAYS = [150, 400, 800];
14404
15578
  var SELECTION_CHROME_GAP2 = 4;
14405
15579
  var TOOLBAR_STROKE_GAP2 = 4;
14406
15580
  var TOOLBAR_OFFSET_FROM_ELEMENT = SELECTION_CHROME_GAP2 + TOOLBAR_STROKE_GAP2;
@@ -14780,6 +15954,45 @@ function StateToggle({
14780
15954
  );
14781
15955
  }
14782
15956
  var contentCache = /* @__PURE__ */ new Map();
15957
+ var fetchedContentPaths = /* @__PURE__ */ new Set();
15958
+ var OHW_LOADER_STYLE = {
15959
+ position: "fixed",
15960
+ inset: 0,
15961
+ background: "#fff",
15962
+ zIndex: 2147483646,
15963
+ display: "flex",
15964
+ alignItems: "center",
15965
+ justifyContent: "center"
15966
+ };
15967
+ function OhwLoaderSpinner() {
15968
+ return /* @__PURE__ */ jsxs20("svg", { width: "28", height: "28", viewBox: "0 0 28 28", fill: "none", "aria-hidden": true, children: [
15969
+ /* @__PURE__ */ jsx33("circle", { cx: "14", cy: "14", r: "11", stroke: "#E7E5E4", strokeWidth: "3" }),
15970
+ /* @__PURE__ */ jsx33(
15971
+ "circle",
15972
+ {
15973
+ cx: "14",
15974
+ cy: "14",
15975
+ r: "11",
15976
+ stroke: "#1C1917",
15977
+ strokeWidth: "3",
15978
+ strokeDasharray: "17 52",
15979
+ strokeLinecap: "round",
15980
+ children: /* @__PURE__ */ jsx33(
15981
+ "animateTransform",
15982
+ {
15983
+ attributeName: "transform",
15984
+ type: "rotate",
15985
+ from: "0 14 14",
15986
+ to: "360 14 14",
15987
+ dur: "0.7s",
15988
+ repeatCount: "indefinite"
15989
+ }
15990
+ )
15991
+ }
15992
+ )
15993
+ ] });
15994
+ }
15995
+ 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){}})();`;
14783
15996
  function resolveSubdomain(subdomainFromQuery) {
14784
15997
  if (subdomainFromQuery) return subdomainFromQuery;
14785
15998
  if (typeof window !== "undefined") {
@@ -14802,8 +16015,8 @@ function OhhwellsBridge() {
14802
16015
  const router = useRouter3();
14803
16016
  const searchParams = useSearchParams();
14804
16017
  const isEditMode = isEditSessionActive();
14805
- const [bridgeRoot, setBridgeRoot] = useState12(null);
14806
- useEffect12(() => {
16018
+ const [bridgeRoot, setBridgeRoot] = useState13(null);
16019
+ useEffect13(() => {
14807
16020
  const figtreeFontId = "ohw-figtree-font";
14808
16021
  if (!document.getElementById(figtreeFontId)) {
14809
16022
  const preconnect1 = Object.assign(document.createElement("link"), { rel: "preconnect", href: "https://fonts.googleapis.com" });
@@ -14832,82 +16045,146 @@ function OhhwellsBridge() {
14832
16045
  const subdomain = resolveSubdomain(subdomainFromQuery);
14833
16046
  useLinkHrefGuardian(pathname, subdomain, isEditMode);
14834
16047
  useSavedLinkNavigation(isEditMode);
14835
- const postToParent2 = useCallback7((data) => {
16048
+ const postToParent2 = useCallback8((data) => {
14836
16049
  if (typeof window !== "undefined" && window.parent !== window) {
14837
16050
  window.parent.postMessage(data, "*");
14838
16051
  }
14839
16052
  }, []);
14840
- const [fetchState, setFetchState] = useState12("idle");
14841
- const autoSaveTimers = useRef9(/* @__PURE__ */ new Map());
14842
- const activeElRef = useRef9(null);
14843
- const pointerHeldRef = useRef9(false);
14844
- const selectedElRef = useRef9(null);
14845
- const selectedHrefKeyRef = useRef9(null);
14846
- const selectedFooterColAttrRef = useRef9(null);
14847
- const originalContentRef = useRef9(null);
14848
- const activeStateElRef = useRef9(null);
14849
- const parentScrollRef = useRef9(null);
14850
- const visibleViewportRef = useRef9(null);
14851
- const [dialogPortalContainer, setDialogPortalContainer] = useState12(null);
14852
- const attachVisibleViewport = useCallback7((node) => {
16053
+ const [fetchState, setFetchState] = useState13("idle");
16054
+ const autoSaveTimers = useRef10(/* @__PURE__ */ new Map());
16055
+ const activeElRef = useRef10(null);
16056
+ const pointerHeldRef = useRef10(false);
16057
+ const selectedElRef = useRef10(null);
16058
+ const selectedHrefKeyRef = useRef10(null);
16059
+ const selectedFooterColAttrRef = useRef10(null);
16060
+ const originalContentRef = useRef10(null);
16061
+ const activeStateElRef = useRef10(null);
16062
+ const parentScrollRef = useRef10(null);
16063
+ const visibleViewportRef = useRef10(null);
16064
+ const [dialogPortalContainer, setDialogPortalContainer] = useState13(null);
16065
+ const attachVisibleViewport = useCallback8((node) => {
14853
16066
  visibleViewportRef.current = node;
14854
16067
  setDialogPortalContainer(node);
14855
16068
  if (node) applyVisibleViewport(node, parentScrollRef.current);
14856
16069
  }, []);
14857
- const toolbarElRef = useRef9(null);
14858
- const glowElRef = useRef9(null);
14859
- const hoveredImageRef = useRef9(null);
14860
- const hoveredImageHasTextOverlapRef = useRef9(false);
14861
- const dragOverElRef = useRef9(null);
14862
- const [mediaHover, setMediaHover] = useState12(null);
14863
- const [carouselHover, setCarouselHover] = useState12(null);
14864
- const [uploadingRects, setUploadingRects] = useState12({});
14865
- const hoveredGapRef = useRef9(null);
14866
- const imageUnhoverTimerRef = useRef9(null);
14867
- const imageShowTimerRef = useRef9(null);
14868
- const editStylesRef = useRef9(null);
14869
- const activateRef = useRef9(() => {
16070
+ const toolbarElRef = useRef10(null);
16071
+ const glowElRef = useRef10(null);
16072
+ const hoveredImageRef = useRef10(null);
16073
+ const hoveredImageHasTextOverlapRef = useRef10(false);
16074
+ const dragOverElRef = useRef10(null);
16075
+ const [mediaHover, setMediaHover] = useState13(null);
16076
+ const [selectedMedia, setSelectedMedia] = useState13(null);
16077
+ const selectedMediaElRef = useRef10(null);
16078
+ const clearMediaSelection = useCallback8(() => {
16079
+ const prev = selectedMediaElRef.current;
16080
+ selectedMediaElRef.current = null;
16081
+ setSelectedMedia(null);
16082
+ const sectionEl = prev?.closest("[data-ohw-section]") ?? null;
16083
+ if (sectionEl) {
16084
+ postToParentRef.current({
16085
+ type: "ow:section-selected",
16086
+ sectionId: sectionEl.dataset.ohwSection ?? null,
16087
+ sectionLabel: sectionEl.dataset.ohwSectionLabel ?? titleCaseSectionId(sectionEl.dataset.ohwSection ?? ""),
16088
+ key: null
16089
+ });
16090
+ }
16091
+ }, []);
16092
+ const clearMediaSelectionRef = useRef10(clearMediaSelection);
16093
+ clearMediaSelectionRef.current = clearMediaSelection;
16094
+ const selectMediaElement = useCallback8((el) => {
16095
+ const r2 = el.getBoundingClientRect();
16096
+ const video = el.dataset.ohwEditable === "video" ? el.querySelector("video") : null;
16097
+ selectedMediaElRef.current = el;
16098
+ setSelectedMedia({
16099
+ key: el.dataset.ohwKey ?? "",
16100
+ rect: { top: r2.top, left: r2.left, width: r2.width, height: r2.height },
16101
+ elementType: el.dataset.ohwEditable ?? "image",
16102
+ hasTextOverlap: false,
16103
+ isDragOver: false,
16104
+ ...video ? { videoAutoplay: video.autoplay, videoMuted: video.muted } : {}
16105
+ });
16106
+ const sectionEl = el.closest("[data-ohw-section]");
16107
+ aiSectionApiRef.current?.selectFromElement(el, { report: false });
16108
+ postToParentRef.current({
16109
+ type: "ow:section-selected",
16110
+ sectionId: sectionEl?.dataset.ohwSection ?? null,
16111
+ sectionLabel: sectionEl ? sectionEl.dataset.ohwSectionLabel ?? titleCaseSectionId(sectionEl.dataset.ohwSection ?? "") : null,
16112
+ key: el.dataset.ohwKey ?? null,
16113
+ // Display name for the pill — the raw key prettifies into fragments ("Img"); the
16114
+ // bridge knows what the node IS, so it names it.
16115
+ keyLabel: el.dataset.ohwEditable === "video" ? "Video" : el.dataset.ohwEditable === "bg-image" ? "Background" : "Image"
16116
+ });
16117
+ }, []);
16118
+ const selectMediaElementRef = useRef10(selectMediaElement);
16119
+ selectMediaElementRef.current = selectMediaElement;
16120
+ useEffect13(() => {
16121
+ if (!selectedMedia) return;
16122
+ const update = () => {
16123
+ const el = selectedMediaElRef.current;
16124
+ if (!el || !el.isConnected) {
16125
+ clearMediaSelection();
16126
+ return;
16127
+ }
16128
+ const r2 = el.getBoundingClientRect();
16129
+ setSelectedMedia(
16130
+ (prev) => prev ? { ...prev, rect: { top: r2.top, left: r2.left, width: r2.width, height: r2.height } } : prev
16131
+ );
16132
+ };
16133
+ window.addEventListener("scroll", update, true);
16134
+ window.addEventListener("resize", update);
16135
+ return () => {
16136
+ window.removeEventListener("scroll", update, true);
16137
+ window.removeEventListener("resize", update);
16138
+ };
16139
+ }, [selectedMedia !== null]);
16140
+ const [carouselHover, setCarouselHover] = useState13(null);
16141
+ const [uploadingRects, setUploadingRects] = useState13({});
16142
+ const hoveredGapRef = useRef10(null);
16143
+ const imageUnhoverTimerRef = useRef10(null);
16144
+ const imageShowTimerRef = useRef10(null);
16145
+ const editStylesRef = useRef10(null);
16146
+ const activateRef = useRef10(() => {
14870
16147
  });
14871
- const deactivateRef = useRef9(() => {
16148
+ const deactivateRef = useRef10(() => {
14872
16149
  });
14873
- const selectRef = useRef9(() => {
16150
+ const selectRef = useRef10(() => {
14874
16151
  });
14875
- const selectFrameRef = useRef9(() => {
16152
+ const selectFrameRef = useRef10(() => {
14876
16153
  });
14877
- const selectLogoRef = useRef9(() => {
16154
+ const selectLogoRef = useRef10(() => {
14878
16155
  });
14879
- const openLogoSizePanelRef = useRef9(() => {
16156
+ const openLogoSizePanelRef = useRef10(() => {
14880
16157
  });
14881
- const deselectRef = useRef9(() => {
16158
+ const deselectRef = useRef10(() => {
14882
16159
  });
14883
- const closeFloatingPanelOnlyRef = useRef9(() => {
16160
+ const closeFloatingPanelOnlyRef = useRef10(() => {
14884
16161
  });
14885
- const reselectNavigationItemRef = useRef9(() => {
16162
+ const reselectNavigationItemRef = useRef10(() => {
14886
16163
  });
14887
- const commitNavigationTextEditRef = useRef9(() => {
16164
+ const commitNavigationTextEditRef = useRef10(() => {
14888
16165
  });
14889
- const handleDeleteSelectedRef = useRef9(() => false);
14890
- const runPendingDeleteUndoRef = useRef9(() => false);
14891
- const isFooterFrameSelectionRef = useRef9(false);
14892
- const refreshActiveCommandsRef = useRef9(() => {
16166
+ const handleDeleteSelectedRef = useRef10(() => false);
16167
+ const runPendingDeleteUndoRef = useRef10(() => false);
16168
+ const isFooterFrameSelectionRef = useRef10(false);
16169
+ const refreshActiveCommandsRef = useRef10(() => {
14893
16170
  });
14894
- const postToParentRef = useRef9(postToParent2);
16171
+ const postToParentRef = useRef10(postToParent2);
14895
16172
  postToParentRef.current = postToParent2;
14896
- const aiSectionApiRef = useRef9(null);
14897
- const sectionsLoadedRef = useRef9(false);
14898
- const pendingScheduleConfigRequests = useRef9([]);
14899
- const [toolbarRect, setToolbarRect] = useState12(null);
14900
- const [formPickRect, setFormPickRect] = useState12(null);
14901
- const formPickElRef = useRef9(null);
14902
- const [formViewState, setFormViewStateUi] = useState12("default");
14903
- const [formPickCount, setFormPickCount] = useState12(null);
14904
- const [formHoverRect, setFormHoverRect] = useState12(null);
14905
- const formHoverElRef = useRef9(null);
14906
- const [fieldPickRect, setFieldPickRect] = useState12(null);
14907
- const fieldPickElRef = useRef9(null);
14908
- const [fieldPickState, setFieldPickState] = useState12(null);
14909
- const [fieldTypePickerOpen, setFieldTypePickerOpen] = useState12(false);
14910
- const clearFormPick = useCallback7(() => {
16173
+ const aiSectionApiRef = useRef10(null);
16174
+ const sectionsLoadedRef = useRef10(false);
16175
+ const pendingScheduleConfigRequests = useRef10([]);
16176
+ const [toolbarRect, setToolbarRect] = useState13(null);
16177
+ const [formPickRect, setFormPickRect] = useState13(null);
16178
+ const formPickElRef = useRef10(null);
16179
+ const [formViewState, setFormViewStateUi] = useState13("default");
16180
+ const [formPickCount, setFormPickCount] = useState13(null);
16181
+ const [formHoverRect, setFormHoverRect] = useState13(null);
16182
+ const formHoverElRef = useRef10(null);
16183
+ const [fieldPickRect, setFieldPickRect] = useState13(null);
16184
+ const fieldPickElRef = useRef10(null);
16185
+ const [fieldPickState, setFieldPickState] = useState13(null);
16186
+ const [fieldTypePickerOpen, setFieldTypePickerOpen] = useState13(false);
16187
+ const clearFormPick = useCallback8(() => {
14911
16188
  const form = formPickElRef.current;
14912
16189
  const editing = fieldPickElRef.current;
14913
16190
  if (commitPlaceholderEdit(editing) && editing) {
@@ -14927,7 +16204,7 @@ function OhhwellsBridge() {
14927
16204
  formPickElRef.current = null;
14928
16205
  setFormPickRect(null);
14929
16206
  }, []);
14930
- const clearFieldPick = useCallback7(() => {
16207
+ const clearFieldPick = useCallback8(() => {
14931
16208
  const wrapper = fieldPickElRef.current;
14932
16209
  if (commitPlaceholderEdit(wrapper) && wrapper) {
14933
16210
  const form = wrapper.closest('[data-ohw-editable="form"]');
@@ -14937,9 +16214,9 @@ function OhhwellsBridge() {
14937
16214
  setFieldPickRect(null);
14938
16215
  setFieldPickState(null);
14939
16216
  }, []);
14940
- const persistFieldsRef = useRef9(() => {
16217
+ const persistFieldsRef = useRef10(() => {
14941
16218
  });
14942
- const persistFields = useCallback7(
16219
+ const persistFields = useCallback8(
14943
16220
  (form) => {
14944
16221
  const key = formKeyOf(form);
14945
16222
  if (!key) return;
@@ -14950,7 +16227,7 @@ function OhhwellsBridge() {
14950
16227
  []
14951
16228
  );
14952
16229
  persistFieldsRef.current = persistFields;
14953
- const selectField = useCallback7((wrapper) => {
16230
+ const selectField = useCallback8((wrapper) => {
14954
16231
  if (fieldPickElRef.current && fieldPickElRef.current !== wrapper) {
14955
16232
  commitPlaceholderEdit(fieldPickElRef.current);
14956
16233
  }
@@ -14963,7 +16240,7 @@ function OhhwellsBridge() {
14963
16240
  setFieldPickState({ type: fieldTypeOf(wrapper), required: isFieldRequired(wrapper) });
14964
16241
  setFieldTypePickerOpen(false);
14965
16242
  }, []);
14966
- const withSelectedField = useCallback7(
16243
+ const withSelectedField = useCallback8(
14967
16244
  (run) => {
14968
16245
  const wrapper = fieldPickElRef.current;
14969
16246
  const form = formPickElRef.current;
@@ -14976,28 +16253,28 @@ function OhhwellsBridge() {
14976
16253
  },
14977
16254
  [persistFields]
14978
16255
  );
14979
- const handleFieldTypeChange = useCallback7(
16256
+ const handleFieldTypeChange = useCallback8(
14980
16257
  (type) => withSelectedField((_form, wrapper) => {
14981
16258
  applyFieldType(wrapper, type);
14982
16259
  selectField(wrapper);
14983
16260
  }),
14984
16261
  [selectField, withSelectedField]
14985
16262
  );
14986
- const handleFieldRequiredToggle = useCallback7(
16263
+ const handleFieldRequiredToggle = useCallback8(
14987
16264
  () => withSelectedField((_form, wrapper) => {
14988
16265
  setFieldRequired(wrapper, !isFieldRequired(wrapper));
14989
16266
  selectField(wrapper);
14990
16267
  }),
14991
16268
  [selectField, withSelectedField]
14992
16269
  );
14993
- const handleFieldDuplicate = useCallback7(
16270
+ const handleFieldDuplicate = useCallback8(
14994
16271
  () => withSelectedField((form, wrapper) => {
14995
16272
  const copy = duplicateField(form, wrapper);
14996
16273
  selectField(copy);
14997
16274
  }),
14998
16275
  [selectField, withSelectedField]
14999
16276
  );
15000
- const handleFieldDelete = useCallback7(
16277
+ const handleFieldDelete = useCallback8(
15001
16278
  () => withSelectedField((_form, wrapper) => {
15002
16279
  removeField(wrapper);
15003
16280
  clearFieldPick();
@@ -15005,7 +16282,7 @@ function OhhwellsBridge() {
15005
16282
  }),
15006
16283
  [clearFieldPick, withSelectedField]
15007
16284
  );
15008
- const handleAddField = useCallback7(
16285
+ const handleAddField = useCallback8(
15009
16286
  (type) => {
15010
16287
  const form = formPickElRef.current;
15011
16288
  if (!form) return;
@@ -15021,8 +16298,8 @@ function OhhwellsBridge() {
15021
16298
  },
15022
16299
  [persistFields, selectField]
15023
16300
  );
15024
- const fieldDragRef = useRef9(null);
15025
- const buildFieldDropSlots = useCallback7((form, draggedKey) => {
16301
+ const fieldDragRef = useRef10(null);
16302
+ const buildFieldDropSlots = useCallback8((form, draggedKey) => {
15026
16303
  const others = listFieldWrappers(form).filter((el) => fieldKeyOf(el) !== draggedKey);
15027
16304
  const slots = others.map((el) => {
15028
16305
  const rect = el.getBoundingClientRect();
@@ -15035,7 +16312,7 @@ function OhhwellsBridge() {
15035
16312
  }
15036
16313
  return slots;
15037
16314
  }, []);
15038
- const handleFieldDragStart = useCallback7(() => {
16315
+ const handleFieldDragStart = useCallback8(() => {
15039
16316
  const wrapper = fieldPickElRef.current;
15040
16317
  const form = formPickElRef.current;
15041
16318
  if (!wrapper || !form) return;
@@ -15044,18 +16321,18 @@ function OhhwellsBridge() {
15044
16321
  setFieldDragging(true);
15045
16322
  setFieldDropSlots(buildFieldDropSlots(form, key));
15046
16323
  }, [buildFieldDropSlots]);
15047
- const handleFieldDragEnd = useCallback7(() => {
16324
+ const handleFieldDragEnd = useCallback8(() => {
15048
16325
  fieldDragRef.current = null;
15049
16326
  setFieldDropIndex(null);
15050
16327
  setFieldDropSlots([]);
15051
16328
  setFieldDragging(false);
15052
16329
  }, []);
15053
- const [fieldDropIndex, setFieldDropIndex] = useState12(null);
15054
- const [fieldDropSlots, setFieldDropSlots] = useState12([]);
15055
- const [fieldDragging, setFieldDragging] = useState12(false);
15056
- const clearFormPickRef = useRef9(clearFormPick);
16330
+ const [fieldDropIndex, setFieldDropIndex] = useState13(null);
16331
+ const [fieldDropSlots, setFieldDropSlots] = useState13([]);
16332
+ const [fieldDragging, setFieldDragging] = useState13(false);
16333
+ const clearFormPickRef = useRef10(clearFormPick);
15057
16334
  clearFormPickRef.current = clearFormPick;
15058
- useEffect12(() => {
16335
+ useEffect13(() => {
15059
16336
  const el = fieldPickElRef.current;
15060
16337
  if (!el || fieldPickRect === null) return;
15061
16338
  const observer = new ResizeObserver(() => {
@@ -15064,7 +16341,7 @@ function OhhwellsBridge() {
15064
16341
  observer.observe(el);
15065
16342
  return () => observer.disconnect();
15066
16343
  }, [fieldPickRect !== null, fieldPickState]);
15067
- useEffect12(() => {
16344
+ useEffect13(() => {
15068
16345
  const el = formPickElRef.current;
15069
16346
  if (!el || formPickRect === null) return;
15070
16347
  const observer = new ResizeObserver(() => {
@@ -15073,25 +16350,25 @@ function OhhwellsBridge() {
15073
16350
  observer.observe(el);
15074
16351
  return () => observer.disconnect();
15075
16352
  }, [formPickRect !== null, formViewState]);
15076
- const [toolbarVariant, setToolbarVariant] = useState12("none");
15077
- const toolbarVariantRef = useRef9("none");
16353
+ const [toolbarVariant, setToolbarVariant] = useState13("none");
16354
+ const toolbarVariantRef = useRef10("none");
15078
16355
  toolbarVariantRef.current = toolbarVariant;
15079
- const [selectedIsCta, setSelectedIsCta] = useState12(false);
15080
- const [selectedIsSocial, setSelectedIsSocial] = useState12(false);
15081
- const [selectedIsSocialsRow, setSelectedIsSocialsRow] = useState12(false);
15082
- const [reorderHrefKey, setReorderHrefKey] = useState12(null);
15083
- const [reorderDragDisabled, setReorderDragDisabled] = useState12(false);
15084
- const [toggleState, setToggleState] = useState12(null);
15085
- const [maxBadge, setMaxBadge] = useState12(null);
15086
- const [activeCommands, setActiveCommands] = useState12(/* @__PURE__ */ new Set());
15087
- const [sectionGap, setSectionGap] = useState12(null);
15088
- const [toolbarShowEditLink, setToolbarShowEditLink] = useState12(false);
15089
- const hoveredNavContainerRef = useRef9(null);
15090
- const [hoveredNavContainerRect, setHoveredNavContainerRect] = useState12(null);
15091
- const hoveredItemElRef = useRef9(null);
15092
- const [hoveredItemRect, setHoveredItemRect] = useState12(null);
15093
- const [hoveredTextRect, setHoveredTextRect] = useState12(null);
15094
- useEffect12(() => {
16356
+ const [selectedIsCta, setSelectedIsCta] = useState13(false);
16357
+ const [selectedIsSocial, setSelectedIsSocial] = useState13(false);
16358
+ const [selectedIsSocialsRow, setSelectedIsSocialsRow] = useState13(false);
16359
+ const [reorderHrefKey, setReorderHrefKey] = useState13(null);
16360
+ const [reorderDragDisabled, setReorderDragDisabled] = useState13(false);
16361
+ const [toggleState, setToggleState] = useState13(null);
16362
+ const [maxBadge, setMaxBadge] = useState13(null);
16363
+ const [activeCommands, setActiveCommands] = useState13(/* @__PURE__ */ new Set());
16364
+ const [sectionGap, setSectionGap] = useState13(null);
16365
+ const [toolbarShowEditLink, setToolbarShowEditLink] = useState13(false);
16366
+ const hoveredNavContainerRef = useRef10(null);
16367
+ const [hoveredNavContainerRect, setHoveredNavContainerRect] = useState13(null);
16368
+ const hoveredItemElRef = useRef10(null);
16369
+ const [hoveredItemRect, setHoveredItemRect] = useState13(null);
16370
+ const [hoveredTextRect, setHoveredTextRect] = useState13(null);
16371
+ useEffect13(() => {
15095
16372
  const sync = () => {
15096
16373
  const el = document.querySelector(
15097
16374
  '[data-ohw-hovered]:not([contenteditable]):not([data-ohw-href-key]):not([data-ohw-editable="form"] *)'
@@ -15116,43 +16393,56 @@ function OhhwellsBridge() {
15116
16393
  });
15117
16394
  return () => observer.disconnect();
15118
16395
  }, []);
15119
- const siblingHintElRef = useRef9(null);
15120
- const [siblingHintRect, setSiblingHintRect] = useState12(null);
15121
- const [siblingHintRects, setSiblingHintRects] = useState12([]);
15122
- const [isItemDragging, setIsItemDragging] = useState12(false);
15123
- const [isFooterFrameSelection, setIsFooterFrameSelection] = useState12(false);
16396
+ const siblingHintElRef = useRef10(null);
16397
+ const [siblingHintRect, setSiblingHintRect] = useState13(null);
16398
+ const [siblingHintRects, setSiblingHintRects] = useState13([]);
16399
+ const [isItemDragging, setIsItemDragging] = useState13(false);
16400
+ const [isFooterFrameSelection, setIsFooterFrameSelection] = useState13(false);
15124
16401
  isFooterFrameSelectionRef.current = isFooterFrameSelection;
15125
- const [floatingPanel, setFloatingPanel] = useState12(null);
15126
- const floatingPanelOpenRef = useRef9(false);
15127
- floatingPanelOpenRef.current = floatingPanel !== null;
15128
- const [floatingPanelPos, setFloatingPanelPos] = useState12(null);
15129
- const [logoSizeDraft, setLogoSizeDraft] = useState12(null);
15130
- const [editorViewport, setEditorViewport] = useState12("desktop");
15131
- const [parentScrollSnap, setParentScrollSnap] = useState12(null);
15132
- const [navDropdownPreviewOpen, setNavDropdownPreviewOpen] = useState12(null);
15133
- const [footerHeadingVisible, setFooterHeadingVisible] = useState12(null);
15134
- const footerDragRef = useRef9(null);
15135
- const [footerDropSlots, setFooterDropSlots] = useState12([]);
15136
- const [activeFooterDropIndex, setActiveFooterDropIndex] = useState12(null);
15137
- const [draggedItemRect, setDraggedItemRect] = useState12(null);
15138
- const footerPointerDragRef = useRef9(null);
15139
- const suppressNextClickRef = useRef9(false);
15140
- const suppressClickUntilRef = useRef9(0);
15141
- const [linkPopover, setLinkPopover] = useState12(null);
15142
- const linkPopoverSessionRef = useRef9(null);
15143
- const addNavAfterAnchorRef = useRef9(null);
15144
- const editContentRef = useRef9({});
15145
- const aiSectionsRef = useRef9("");
15146
- const pendingDeleteUndoRef = useRef9(null);
15147
- const [sitePages, setSitePages] = useState12([]);
15148
- const [sectionsByPath, setSectionsByPath] = useState12({});
15149
- const sectionsPrefetchGenRef = useRef9(0);
15150
- const setLinkPopoverRef = useRef9(setLinkPopover);
15151
- const linkPopoverPanelRef = useRef9(null);
15152
- const linkPopoverOpenRef = useRef9(false);
15153
- const linkPopoverGraceUntilRef = useRef9(0);
16402
+ const [navDropdownPreviewOpen, setNavDropdownPreviewOpen] = useState13(null);
16403
+ const [footerHeadingVisible, setFooterHeadingVisible] = useState13(null);
16404
+ const footerDragRef = useRef10(null);
16405
+ const [footerDropSlots, setFooterDropSlots] = useState13([]);
16406
+ const [activeFooterDropIndex, setActiveFooterDropIndex] = useState13(null);
16407
+ const [draggedItemRect, setDraggedItemRect] = useState13(null);
16408
+ const footerPointerDragRef = useRef10(null);
16409
+ const suppressNextClickRef = useRef10(false);
16410
+ const suppressClickUntilRef = useRef10(0);
16411
+ const [linkPopover, setLinkPopover] = useState13(null);
16412
+ const linkPopoverSessionRef = useRef10(null);
16413
+ const addNavAfterAnchorRef = useRef10(null);
16414
+ const editContentRef = useRef10({});
16415
+ const aiSectionsRef = useRef10("");
16416
+ const brandKitRef = useRef10("");
16417
+ const stylesRef = useRef10("");
16418
+ const pendingDeleteUndoRef = useRef10(null);
16419
+ const [floatingPanel, setFloatingPanel] = useState13(null);
16420
+ const floatingPanelOpenRef = useRef10(false);
16421
+ const setFloatingPanelRef = useRef10(setFloatingPanel);
16422
+ const [floatingPanelPos, setFloatingPanelPos] = useState13(null);
16423
+ const [logoSizeDraft, setLogoSizeDraft] = useState13(null);
16424
+ const [editorViewport, setEditorViewport] = useState13("desktop");
16425
+ const [parentScrollSnap, setParentScrollSnap] = useState13(null);
16426
+ const [sitePages, setSitePages] = useState13([]);
16427
+ const [sectionsByPath, setSectionsByPath] = useState13({});
16428
+ const sectionsPrefetchGenRef = useRef10(0);
16429
+ const setLinkPopoverRef = useRef10(setLinkPopover);
16430
+ const linkPopoverPanelRef = useRef10(null);
16431
+ const linkPopoverOpenRef = useRef10(false);
16432
+ const linkPopoverGraceUntilRef = useRef10(0);
15154
16433
  setLinkPopoverRef.current = setLinkPopover;
16434
+ setFloatingPanelRef.current = setFloatingPanel;
15155
16435
  linkPopoverSessionRef.current = linkPopover;
16436
+ floatingPanelOpenRef.current = Boolean(floatingPanel);
16437
+ useEffect13(() => {
16438
+ const syncViewport = () => {
16439
+ const next = window.innerWidth <= 480 ? "mobile" : "desktop";
16440
+ setEditorViewport((prev) => prev === next ? prev : next);
16441
+ };
16442
+ syncViewport();
16443
+ window.addEventListener("resize", syncViewport);
16444
+ return () => window.removeEventListener("resize", syncViewport);
16445
+ }, []);
15156
16446
  const {
15157
16447
  navDragRef,
15158
16448
  navDropSlots,
@@ -15185,10 +16475,20 @@ function OhhwellsBridge() {
15185
16475
  getNavigationItemAnchor,
15186
16476
  isDragHandleDisabled
15187
16477
  });
16478
+ const { sectionDropSlots, activeSectionDropIndex, isSectionDragging } = useSectionDrag({
16479
+ isEditMode,
16480
+ editContentRef,
16481
+ postToParentRef,
16482
+ parentScrollRef,
16483
+ navDragRef,
16484
+ footerDragRef,
16485
+ suppressNextClickRef,
16486
+ suppressClickUntilRef
16487
+ });
15188
16488
  const bumpLinkPopoverGrace = () => {
15189
16489
  linkPopoverGraceUntilRef.current = Date.now() + 350;
15190
16490
  };
15191
- const runSectionsPrefetch = useCallback7((pages) => {
16491
+ const runSectionsPrefetch = useCallback8((pages) => {
15192
16492
  if (!isEditMode || shouldUseDevFixtures() || pages.length === 0) return;
15193
16493
  const gen = ++sectionsPrefetchGenRef.current;
15194
16494
  const paths = pages.map((p) => p.path);
@@ -15207,9 +16507,9 @@ function OhhwellsBridge() {
15207
16507
  );
15208
16508
  });
15209
16509
  }, [isEditMode, pathname]);
15210
- const runSectionsPrefetchRef = useRef9(runSectionsPrefetch);
16510
+ const runSectionsPrefetchRef = useRef10(runSectionsPrefetch);
15211
16511
  runSectionsPrefetchRef.current = runSectionsPrefetch;
15212
- useEffect12(() => {
16512
+ useEffect13(() => {
15213
16513
  if (!linkPopover) {
15214
16514
  document.documentElement.removeAttribute("data-ohw-link-popover-open");
15215
16515
  return;
@@ -15237,7 +16537,7 @@ function OhhwellsBridge() {
15237
16537
  document.documentElement.removeAttribute("data-ohw-link-popover-open");
15238
16538
  };
15239
16539
  }, [linkPopover, postToParent2]);
15240
- useEffect12(() => {
16540
+ useEffect13(() => {
15241
16541
  if (!isEditMode) return;
15242
16542
  const useFixtures = shouldUseDevFixtures();
15243
16543
  if (useFixtures) {
@@ -15261,14 +16561,14 @@ function OhhwellsBridge() {
15261
16561
  if (!useFixtures) postToParent2({ type: "ow:request-site-pages" });
15262
16562
  return () => window.removeEventListener("message", onSitePages);
15263
16563
  }, [isEditMode, postToParent2]);
15264
- useEffect12(() => {
16564
+ useEffect13(() => {
15265
16565
  if (!isEditMode || shouldUseDevFixtures()) return;
15266
16566
  void loadAllSectionsManifest().then((manifest) => {
15267
16567
  if (Object.keys(manifest).length === 0) return;
15268
16568
  setSectionsByPath((prev) => ({ ...manifest, ...prev }));
15269
16569
  });
15270
16570
  }, [isEditMode]);
15271
- useEffect12(() => {
16571
+ useEffect13(() => {
15272
16572
  const update = () => {
15273
16573
  const el = activeElRef.current ?? selectedElRef.current;
15274
16574
  if (el) setToolbarRect(getEditMeasureEl(el).getBoundingClientRect());
@@ -15292,10 +16592,10 @@ function OhhwellsBridge() {
15292
16592
  vvp.removeEventListener("resize", update);
15293
16593
  };
15294
16594
  }, []);
15295
- const refreshStateRules = useCallback7(() => {
16595
+ const refreshStateRules = useCallback8(() => {
15296
16596
  editStylesRef.current?.forceHover && (editStylesRef.current.forceHover.textContent = collectStateRules());
15297
16597
  }, []);
15298
- const processConfigRequest = useCallback7((insertAfterVal) => {
16598
+ const processConfigRequest = useCallback8((insertAfterVal) => {
15299
16599
  const tracker = getSectionsTracker();
15300
16600
  let entries = [];
15301
16601
  try {
@@ -15318,7 +16618,7 @@ function OhhwellsBridge() {
15318
16618
  }
15319
16619
  window.postMessage({ type: "ow:schedule-config", insertAfter: insertAfterVal, scheduleId: null }, "*");
15320
16620
  }, [isEditMode]);
15321
- const deactivate = useCallback7(() => {
16621
+ const deactivate = useCallback8(() => {
15322
16622
  const el = activeElRef.current;
15323
16623
  if (!el) return;
15324
16624
  const isFormBlock = el.dataset.ohwEditable === "form";
@@ -15334,7 +16634,7 @@ function OhhwellsBridge() {
15334
16634
  const original = originalContentRef.current ?? "";
15335
16635
  if (html !== sanitizeHtml(original)) {
15336
16636
  postToParentRef.current({ type: "ow:change", nodes: [{ key, text: html }] });
15337
- const h = document.documentElement.scrollHeight;
16637
+ const h = document.body.scrollHeight;
15338
16638
  if (h > 50) postToParentRef.current({ type: "ow:height", height: h });
15339
16639
  }
15340
16640
  }
@@ -15359,12 +16659,12 @@ function OhhwellsBridge() {
15359
16659
  setToolbarShowEditLink(false);
15360
16660
  postToParent2({ type: "ow:exit-edit" });
15361
16661
  }, [postToParent2]);
15362
- const clearSelectedAttr = useCallback7(() => {
16662
+ const clearSelectedAttr = useCallback8(() => {
15363
16663
  document.querySelectorAll("[data-ohw-selected]").forEach((el) => {
15364
16664
  el.removeAttribute("data-ohw-selected");
15365
16665
  });
15366
16666
  }, []);
15367
- const deselect = useCallback7(() => {
16667
+ const deselect = useCallback8(() => {
15368
16668
  clearSelectedAttr();
15369
16669
  selectedElRef.current = null;
15370
16670
  selectedHrefKeyRef.current = null;
@@ -15393,20 +16693,20 @@ function OhhwellsBridge() {
15393
16693
  setToolbarVariant("none");
15394
16694
  }
15395
16695
  }, [clearSelectedAttr]);
15396
- const markSelected = useCallback7((el) => {
16696
+ const markSelected = useCallback8((el) => {
15397
16697
  clearSelectedAttr();
15398
16698
  el.removeAttribute("data-ohw-hovered");
15399
16699
  el.setAttribute("data-ohw-selected", "");
15400
16700
  }, [clearSelectedAttr]);
15401
- const isSelectedForHover = useCallback7((el) => {
16701
+ const isSelectedForHover = useCallback8((el) => {
15402
16702
  if (!el) return false;
15403
16703
  return [selectedElRef.current, activeElRef.current].some(
15404
16704
  (busy) => busy && (el === busy || busy.contains(el) || el.contains(busy))
15405
16705
  );
15406
16706
  }, []);
15407
- const isSelectedForHoverRef = useRef9(isSelectedForHover);
16707
+ const isSelectedForHoverRef = useRef10(isSelectedForHover);
15408
16708
  isSelectedForHoverRef.current = isSelectedForHover;
15409
- const resolveHrefKeyElement = useCallback7((hrefKey) => {
16709
+ const resolveHrefKeyElement = useCallback8((hrefKey) => {
15410
16710
  if (isFooterHrefKey(hrefKey)) {
15411
16711
  return document.querySelector(
15412
16712
  `footer [data-ohw-href-key="${CSS.escape(hrefKey)}"]`
@@ -15421,7 +16721,7 @@ function OhhwellsBridge() {
15421
16721
  `[data-ohw-href-key="${CSS.escape(hrefKey)}"]`
15422
16722
  );
15423
16723
  }, []);
15424
- const resyncSelectedNavigationItem = useCallback7(() => {
16724
+ const resyncSelectedNavigationItem = useCallback8(() => {
15425
16725
  const hrefKey = selectedHrefKeyRef.current;
15426
16726
  if (hrefKey) {
15427
16727
  const link = resolveHrefKeyElement(hrefKey);
@@ -15459,7 +16759,7 @@ function OhhwellsBridge() {
15459
16759
  );
15460
16760
  }
15461
16761
  }, [resolveHrefKeyElement]);
15462
- const reselectNavigationItem = useCallback7((navAnchor) => {
16762
+ const reselectNavigationItem = useCallback8((navAnchor) => {
15463
16763
  selectedElRef.current = navAnchor;
15464
16764
  selectedHrefKeyRef.current = navAnchor.getAttribute("data-ohw-href-key");
15465
16765
  selectedFooterColAttrRef.current = null;
@@ -15490,7 +16790,7 @@ function OhhwellsBridge() {
15490
16790
  setToolbarShowEditLink(false);
15491
16791
  setActiveCommands(/* @__PURE__ */ new Set());
15492
16792
  }, [markSelected]);
15493
- const commitNavigationTextEdit = useCallback7((navAnchor) => {
16793
+ const commitNavigationTextEdit = useCallback8((navAnchor) => {
15494
16794
  const el = activeElRef.current;
15495
16795
  if (!el) return;
15496
16796
  const key = el.dataset.ohwKey;
@@ -15504,7 +16804,7 @@ function OhhwellsBridge() {
15504
16804
  const original = originalContentRef.current ?? "";
15505
16805
  if (html !== sanitizeHtml(original)) {
15506
16806
  postToParent2({ type: "ow:change", nodes: [{ key, text: html }] });
15507
- const h = document.documentElement.scrollHeight;
16807
+ const h = document.body.scrollHeight;
15508
16808
  if (h > 50) postToParent2({ type: "ow:height", height: h });
15509
16809
  }
15510
16810
  }
@@ -15523,7 +16823,7 @@ function OhhwellsBridge() {
15523
16823
  postToParent2({ type: "ow:exit-edit" });
15524
16824
  reselectNavigationItem(navAnchor);
15525
16825
  }, [postToParent2, reselectNavigationItem]);
15526
- const handleAddTopLevelNavItem = useCallback7(() => {
16826
+ const handleAddTopLevelNavItem = useCallback8(() => {
15527
16827
  const items = listNavbarRootItems();
15528
16828
  addNavAfterAnchorRef.current = items[items.length - 1] ?? null;
15529
16829
  deselectRef.current();
@@ -15535,7 +16835,7 @@ function OhhwellsBridge() {
15535
16835
  intent: "add-nav"
15536
16836
  });
15537
16837
  }, []);
15538
- const maybeWarnNavLinkDropdownConflict = useCallback7(
16838
+ const maybeWarnNavLinkDropdownConflict = useCallback8(
15539
16839
  (anchor) => {
15540
16840
  if (!isNavbarHrefKey(anchor.getAttribute("data-ohw-href-key"))) return;
15541
16841
  if (!navDropdownsOpenOnClick()) return;
@@ -15548,7 +16848,7 @@ function OhhwellsBridge() {
15548
16848
  },
15549
16849
  [postToParent2]
15550
16850
  );
15551
- const handleNavDropdownOpenChange = useCallback7((open) => {
16851
+ const handleNavDropdownOpenChange = useCallback8((open) => {
15552
16852
  const selected = selectedElRef.current;
15553
16853
  if (!selected || !isNavigationItem2(selected)) return;
15554
16854
  setNavGroupForceOpen(selected, open);
@@ -15560,7 +16860,7 @@ function OhhwellsBridge() {
15560
16860
  }
15561
16861
  });
15562
16862
  }, []);
15563
- const handleFooterHeadingVisibleChange = useCallback7(
16863
+ const handleFooterHeadingVisibleChange = useCallback8(
15564
16864
  (visible) => {
15565
16865
  const selected = selectedElRef.current;
15566
16866
  if (!selected || !isFooterFrameSelectionRef.current) return;
@@ -15584,7 +16884,7 @@ function OhhwellsBridge() {
15584
16884
  },
15585
16885
  [postToParent2]
15586
16886
  );
15587
- const enterEditOnNewItem = useCallback7((anchor) => {
16887
+ const enterEditOnNewItem = useCallback8((anchor) => {
15588
16888
  const label = anchor.querySelector('[data-ohw-editable="text"]');
15589
16889
  if (!label) {
15590
16890
  selectRef.current(anchor);
@@ -15593,8 +16893,8 @@ function OhhwellsBridge() {
15593
16893
  setNavGroupForceOpen(anchor, true);
15594
16894
  activateRef.current(label);
15595
16895
  }, []);
15596
- const pendingSocialAddRef = useRef9(null);
15597
- const handleAddChildItem = useCallback7(() => {
16896
+ const pendingSocialAddRef = useRef10(null);
16897
+ const handleAddChildItem = useCallback8(() => {
15598
16898
  const selected = selectedElRef.current;
15599
16899
  if (!selected) return;
15600
16900
  const socialsRow = isSocialsRow(selected) ? selected : findSocialsRow(selected);
@@ -15703,7 +17003,7 @@ function OhhwellsBridge() {
15703
17003
  enterEditOnNewItem(result.anchor);
15704
17004
  });
15705
17005
  }, [enterEditOnNewItem, isFooterFrameSelection, maybeWarnNavLinkDropdownConflict, postToParent2]);
15706
- const handleAddFooterColumn = useCallback7(() => {
17006
+ const handleAddFooterColumn = useCallback8(() => {
15707
17007
  if (!canAddFooterColumn()) {
15708
17008
  postToParent2({
15709
17009
  type: "ow:toast",
@@ -15724,7 +17024,7 @@ function OhhwellsBridge() {
15724
17024
  selectRef.current(result.firstLink);
15725
17025
  });
15726
17026
  }, [postToParent2]);
15727
- const clearFooterDragVisuals = useCallback7(() => {
17027
+ const clearFooterDragVisuals = useCallback8(() => {
15728
17028
  footerDragRef.current = null;
15729
17029
  setSiblingHintRects([]);
15730
17030
  setFooterDropSlots([]);
@@ -15733,7 +17033,7 @@ function OhhwellsBridge() {
15733
17033
  setIsItemDragging(false);
15734
17034
  unlockFooterDragInteraction();
15735
17035
  }, []);
15736
- const refreshFooterDragVisuals = useCallback7((session, activeSlot, clientX, clientY) => {
17036
+ const refreshFooterDragVisuals = useCallback8((session, activeSlot, clientX, clientY) => {
15737
17037
  const dragged = session.draggedEl;
15738
17038
  setDraggedItemRect(dragged.getBoundingClientRect());
15739
17039
  if (typeof clientX === "number" && typeof clientY === "number") {
@@ -15765,13 +17065,13 @@ function OhhwellsBridge() {
15765
17065
  const activeIdx = activeSlot ? slots.findIndex((s) => s.insertIndex === activeSlot.insertIndex) : -1;
15766
17066
  setActiveFooterDropIndex(activeIdx >= 0 ? activeIdx : null);
15767
17067
  }, []);
15768
- const refreshFooterDragVisualsRef = useRef9(refreshFooterDragVisuals);
17068
+ const refreshFooterDragVisualsRef = useRef10(refreshFooterDragVisuals);
15769
17069
  refreshFooterDragVisualsRef.current = refreshFooterDragVisuals;
15770
- const commitFooterDragRef = useRef9(() => {
17070
+ const commitFooterDragRef = useRef10(() => {
15771
17071
  });
15772
- const beginFooterDragRef = useRef9(() => {
17072
+ const beginFooterDragRef = useRef10(() => {
15773
17073
  });
15774
- const beginFooterDrag = useCallback7(
17074
+ const beginFooterDrag = useCallback8(
15775
17075
  (session) => {
15776
17076
  const rect = session.draggedEl.getBoundingClientRect();
15777
17077
  session.lastClientX = session.lastClientX || rect.left + rect.width / 2;
@@ -15791,7 +17091,7 @@ function OhhwellsBridge() {
15791
17091
  [refreshFooterDragVisuals]
15792
17092
  );
15793
17093
  beginFooterDragRef.current = beginFooterDrag;
15794
- const commitFooterDrag = useCallback7(
17094
+ const commitFooterDrag = useCallback8(
15795
17095
  (clientX, clientY) => {
15796
17096
  const session = footerDragRef.current;
15797
17097
  if (!session) {
@@ -15920,7 +17220,7 @@ function OhhwellsBridge() {
15920
17220
  [clearFooterDragVisuals, resolveHrefKeyElement, resyncSelectedNavigationItem]
15921
17221
  );
15922
17222
  commitFooterDragRef.current = commitFooterDrag;
15923
- const startFooterLinkDrag = useCallback7(
17223
+ const startFooterLinkDrag = useCallback8(
15924
17224
  (anchor, clientX, clientY, wasSelected) => {
15925
17225
  const hrefKey = anchor.getAttribute("data-ohw-href-key");
15926
17226
  if (!hrefKey) return false;
@@ -15956,7 +17256,7 @@ function OhhwellsBridge() {
15956
17256
  },
15957
17257
  [beginFooterDrag]
15958
17258
  );
15959
- const startFooterColumnDrag = useCallback7(
17259
+ const startFooterColumnDrag = useCallback8(
15960
17260
  (columnEl, clientX, clientY, wasSelected) => {
15961
17261
  const columns = listFooterColumns();
15962
17262
  const idx = columns.indexOf(columnEl);
@@ -15976,7 +17276,7 @@ function OhhwellsBridge() {
15976
17276
  },
15977
17277
  [beginFooterDrag]
15978
17278
  );
15979
- const handleItemDragStart = useCallback7(
17279
+ const handleItemDragStart = useCallback8(
15980
17280
  (e) => {
15981
17281
  const selected = selectedElRef.current;
15982
17282
  if (!selected) {
@@ -15996,7 +17296,7 @@ function OhhwellsBridge() {
15996
17296
  },
15997
17297
  [startFooterColumnDrag, startFooterLinkDrag, startNavLinkDrag]
15998
17298
  );
15999
- const handleItemDragEnd = useCallback7(
17299
+ const handleItemDragEnd = useCallback8(
16000
17300
  (e) => {
16001
17301
  if (footerDragRef.current) {
16002
17302
  const x = e?.clientX;
@@ -16022,7 +17322,7 @@ function OhhwellsBridge() {
16022
17322
  },
16023
17323
  [commitFooterDrag, commitNavDrag, navDragRef]
16024
17324
  );
16025
- const handleItemChromePointerDown = useCallback7((e) => {
17325
+ const handleItemChromePointerDown = useCallback8((e) => {
16026
17326
  if (e.button !== 0) return;
16027
17327
  const selected = selectedElRef.current;
16028
17328
  if (!selected) return;
@@ -16053,7 +17353,7 @@ function OhhwellsBridge() {
16053
17353
  }
16054
17354
  if (armNavPressFromChrome(selected, e.clientX, e.clientY, e.pointerId)) return;
16055
17355
  }, [armNavPressFromChrome]);
16056
- const handleItemChromeClick = useCallback7((clientX, clientY) => {
17356
+ const handleItemChromeClick = useCallback8((clientX, clientY) => {
16057
17357
  if (suppressNextClickRef.current || Date.now() < suppressClickUntilRef.current) {
16058
17358
  suppressNextClickRef.current = false;
16059
17359
  return;
@@ -16066,7 +17366,7 @@ function OhhwellsBridge() {
16066
17366
  }, []);
16067
17367
  reselectNavigationItemRef.current = reselectNavigationItem;
16068
17368
  commitNavigationTextEditRef.current = commitNavigationTextEdit;
16069
- const select = useCallback7((anchor) => {
17369
+ const select = useCallback8((anchor) => {
16070
17370
  if (!isNavigationItem2(anchor)) return;
16071
17371
  if (activeElRef.current) deactivate();
16072
17372
  aiSectionApiRef.current?.selectFromElement(anchor);
@@ -16109,7 +17409,7 @@ function OhhwellsBridge() {
16109
17409
  setFloatingPanel(null);
16110
17410
  setLogoSizeDraft(null);
16111
17411
  }, [deactivate, markSelected]);
16112
- const selectFrame = useCallback7((el) => {
17412
+ const selectFrame = useCallback8((el) => {
16113
17413
  if (!isNavigationContainer(el)) return;
16114
17414
  if (activeElRef.current) deactivate();
16115
17415
  aiSectionApiRef.current?.selectFromElement(el);
@@ -16160,7 +17460,7 @@ function OhhwellsBridge() {
16160
17460
  setFloatingPanel(null);
16161
17461
  setLogoSizeDraft(null);
16162
17462
  }, [deactivate, markSelected, postToParent2]);
16163
- const selectLogo = useCallback7(
17463
+ const selectLogo = useCallback8(
16164
17464
  (logoEl) => {
16165
17465
  if (activeElRef.current) deactivate();
16166
17466
  selectedElRef.current = logoEl;
@@ -16189,7 +17489,7 @@ function OhhwellsBridge() {
16189
17489
  },
16190
17490
  [deactivate, markSelected]
16191
17491
  );
16192
- const openLogoSizePanel = useCallback7((logoEl) => {
17492
+ const openLogoSizePanel = useCallback8((logoEl) => {
16193
17493
  const placement = getLogoPlacement(logoEl);
16194
17494
  const draft = readLogoSizeState(editContentRef.current, placement);
16195
17495
  setLogoSizeDraft(draft);
@@ -16202,7 +17502,7 @@ function OhhwellsBridge() {
16202
17502
  placement
16203
17503
  });
16204
17504
  }, []);
16205
- const openSocialsDisplayPanel = useCallback7((row) => {
17505
+ const openSocialsDisplayPanel = useCallback8((row) => {
16206
17506
  setParentScrollSnap(parentScrollRef.current);
16207
17507
  setFloatingPanel({
16208
17508
  key: "socials-display",
@@ -16212,11 +17512,11 @@ function OhhwellsBridge() {
16212
17512
  row
16213
17513
  });
16214
17514
  }, []);
16215
- const isEditModeRef = useRef9(false);
16216
- const requestMissingSocialIconsRef = useRef9(() => {
17515
+ const isEditModeRef = useRef10(false);
17516
+ const requestMissingSocialIconsRef = useRef10(() => {
16217
17517
  });
16218
- const askedSocialIconsRef = useRef9(/* @__PURE__ */ new Set());
16219
- const requestMissingSocialIcons = useCallback7(() => {
17518
+ const askedSocialIconsRef = useRef10(/* @__PURE__ */ new Set());
17519
+ const requestMissingSocialIcons = useCallback8(() => {
16220
17520
  const items = Array.from(document.querySelectorAll(`[${SOCIALS_ROW_ATTR}]`)).filter((row) => socialsDisplayFor(row, editContentRef.current).icon).flatMap((row) => {
16221
17521
  const missing = socialsMissingIcons(row);
16222
17522
  listSocialItems(row).forEach((item) => ensureIconSlot(item));
@@ -16228,7 +17528,7 @@ function OhhwellsBridge() {
16228
17528
  }, []);
16229
17529
  requestMissingSocialIconsRef.current = requestMissingSocialIcons;
16230
17530
  isEditModeRef.current = isEditMode;
16231
- const changeSocialsDisplay = useCallback7(
17531
+ const changeSocialsDisplay = useCallback8(
16232
17532
  (row, next) => {
16233
17533
  if (next.icon) {
16234
17534
  const missing = socialsMissingIcons(row);
@@ -16252,17 +17552,17 @@ function OhhwellsBridge() {
16252
17552
  },
16253
17553
  []
16254
17554
  );
16255
- const closeFloatingPanelOnly = useCallback7(() => {
17555
+ const closeFloatingPanelOnly = useCallback8(() => {
16256
17556
  setFloatingPanel(null);
16257
17557
  setLogoSizeDraft(null);
16258
17558
  }, []);
16259
17559
  closeFloatingPanelOnlyRef.current = closeFloatingPanelOnly;
16260
- const closeFloatingPanelAndDeselect = useCallback7(() => {
17560
+ const closeFloatingPanelAndDeselect = useCallback8(() => {
16261
17561
  setFloatingPanel(null);
16262
17562
  setLogoSizeDraft(null);
16263
17563
  deselectRef.current();
16264
17564
  }, []);
16265
- useEffect12(() => {
17565
+ useEffect13(() => {
16266
17566
  const session = floatingPanel && floatingPanel.kind === "logo-size" ? floatingPanel : null;
16267
17567
  if (!session || !logoSizeDraft) {
16268
17568
  postToParentRef.current({ type: "ow:logo-size-panel", open: false });
@@ -16281,7 +17581,7 @@ function OhhwellsBridge() {
16281
17581
  max: LOGO_SIZE_MAX
16282
17582
  });
16283
17583
  }, [floatingPanel, logoSizeDraft, editorViewport]);
16284
- const persistLogoSizeDraft = useCallback7(
17584
+ const persistLogoSizeDraft = useCallback8(
16285
17585
  (placement, draft) => {
16286
17586
  const desktopKey = LOGO_SIZE_DESKTOP_KEYS[placement];
16287
17587
  const mobileKey = LOGO_SIZE_MOBILE_KEYS[placement];
@@ -16321,7 +17621,7 @@ function OhhwellsBridge() {
16321
17621
  },
16322
17622
  [postToParent2]
16323
17623
  );
16324
- const activate = useCallback7((el, options) => {
17624
+ const activate = useCallback8((el, options) => {
16325
17625
  if (activeElRef.current === el) return;
16326
17626
  document.querySelectorAll("[data-ohw-hovered]").forEach((hovered) => {
16327
17627
  hovered.removeAttribute("data-ohw-hovered");
@@ -16419,8 +17719,8 @@ function OhhwellsBridge() {
16419
17719
  openLogoSizePanelRef.current = openLogoSizePanel;
16420
17720
  deselectRef.current = deselect;
16421
17721
  closeFloatingPanelOnlyRef.current = closeFloatingPanelOnly;
16422
- const lastSiteWideScopeRef = useRef9(null);
16423
- useEffect12(() => {
17722
+ const lastSiteWideScopeRef = useRef10(null);
17723
+ useEffect13(() => {
16424
17724
  if (!isEditMode) {
16425
17725
  if (lastSiteWideScopeRef.current !== false) {
16426
17726
  lastSiteWideScopeRef.current = false;
@@ -16453,15 +17753,31 @@ function OhhwellsBridge() {
16453
17753
  }
16454
17754
  const applyContent = (content) => {
16455
17755
  const imageLoads = [];
17756
+ if (typeof content[BRAND_KIT_KEY] === "string") {
17757
+ brandKitRef.current = content[BRAND_KIT_KEY];
17758
+ applyBrandToDom(parseBrandKit(content[BRAND_KIT_KEY]));
17759
+ } else {
17760
+ brandKitRef.current = "";
17761
+ applyBrandToDom(null);
17762
+ }
16456
17763
  if (typeof content[AI_SECTIONS_KEY] === "string") {
16457
17764
  aiSectionsRef.current = content[AI_SECTIONS_KEY];
17765
+ setAiSectionOrder(content[SECTION_ORDER_KEY], window.location.pathname);
16458
17766
  applyAiSectionsToDom(parseAiSectionsState(content[AI_SECTIONS_KEY]));
16459
17767
  }
17768
+ if (typeof content[STYLE_STORE_KEY] === "string") {
17769
+ stylesRef.current = content[STYLE_STORE_KEY];
17770
+ applyStylesToDom(parseStyleStore(content[STYLE_STORE_KEY]));
17771
+ }
17772
+ applyBrandChrome(content);
16460
17773
  for (const [key, val] of Object.entries(content)) {
16461
17774
  if (key === "__ohw_sections") continue;
16462
17775
  if (key === AI_SECTIONS_KEY) continue;
16463
17776
  if (key === LOGO_PLACEHOLDER_KEY) continue;
16464
17777
  if (LOGO_IMAGE_KEYS.includes(key)) continue;
17778
+ if (key === BRAND_KIT_KEY) continue;
17779
+ if (key === STYLE_STORE_KEY) continue;
17780
+ if (BRAND_CHROME_KEYS.has(key)) continue;
16465
17781
  if (applyVideoSettingNode(key, val)) continue;
16466
17782
  if (applyCarouselNode(key, val)) continue;
16467
17783
  document.querySelectorAll(`[data-ohw-key="${key}"]`).forEach((el) => {
@@ -16527,7 +17843,9 @@ function OhhwellsBridge() {
16527
17843
  let cancelled = false;
16528
17844
  setFetchState("loading");
16529
17845
  const apiUrl = process.env.NEXT_PUBLIC_FLOWOPS_API_URL ?? "https://flowops-backend-staging-2yfdh7pwpq-as.a.run.app";
16530
- fetch(`${apiUrl}/api/public/sites/${subdomain}/content`, { cache: "no-store" }).then((r2) => r2.ok ? r2.json() : null).then((data) => {
17846
+ const initialPath = pathname;
17847
+ fetchedContentPaths.add(`${subdomain}::${initialPath}`);
17848
+ fetch(`${apiUrl}/api/public/sites/${subdomain}/content?path=${encodeURIComponent(initialPath)}`, { cache: "no-store" }).then((r2) => r2.ok ? r2.json() : null).then((data) => {
16531
17849
  if (cancelled) return;
16532
17850
  const content = data?.content ?? {};
16533
17851
  contentCache.set(subdomain, content);
@@ -16540,7 +17858,7 @@ function OhhwellsBridge() {
16540
17858
  cancelled = true;
16541
17859
  };
16542
17860
  }, [subdomain, isEditMode]);
16543
- useEffect12(() => {
17861
+ useEffect13(() => {
16544
17862
  if (!isEditMode) return;
16545
17863
  const resolveIndex = (form, clientY) => {
16546
17864
  const wrappers = listFieldWrappers(form).filter((el) => fieldKeyOf(el) !== fieldDragRef.current?.key);
@@ -16581,7 +17899,7 @@ function OhhwellsBridge() {
16581
17899
  window.removeEventListener("drop", onDrop, true);
16582
17900
  };
16583
17901
  }, [buildFieldDropSlots, isEditMode, persistFields, selectField]);
16584
- useEffect12(() => {
17902
+ useEffect13(() => {
16585
17903
  if (!isEditMode) return;
16586
17904
  const mark = () => document.querySelectorAll('[data-ohw-editable="form"]').forEach((form) => {
16587
17905
  markFormFields(form);
@@ -16593,7 +17911,7 @@ function OhhwellsBridge() {
16593
17911
  });
16594
17912
  return () => observer.disconnect();
16595
17913
  }, [isEditMode, fetchState, pathname]);
16596
- useEffect12(() => {
17914
+ useEffect13(() => {
16597
17915
  if (!isEditMode) return;
16598
17916
  let saveTimer = null;
16599
17917
  const onInput = (e) => {
@@ -16615,14 +17933,14 @@ function OhhwellsBridge() {
16615
17933
  document.addEventListener("input", onInput, true);
16616
17934
  return () => document.removeEventListener("input", onInput, true);
16617
17935
  }, [isEditMode, persistFields]);
16618
- useEffect12(() => {
17936
+ useEffect13(() => {
16619
17937
  if (isEditMode || fetchState !== "done") return;
16620
17938
  const content = contentCache.get(subdomain) ?? {};
16621
17939
  document.querySelectorAll('[data-ohw-editable="form"]').forEach((form) => {
16622
17940
  reconcileFieldsFromContent(form, content);
16623
17941
  });
16624
17942
  }, [isEditMode, fetchState, subdomain]);
16625
- useEffect12(() => {
17943
+ useEffect13(() => {
16626
17944
  if (!isEditMode) return;
16627
17945
  const swallow = (e) => {
16628
17946
  const target = e.target;
@@ -16631,12 +17949,12 @@ function OhhwellsBridge() {
16631
17949
  document.addEventListener("submit", swallow, true);
16632
17950
  return () => document.removeEventListener("submit", swallow, true);
16633
17951
  }, [isEditMode]);
16634
- useEffect12(() => {
17952
+ useEffect13(() => {
16635
17953
  if (isEditMode || fetchState !== "done") return;
16636
17954
  const apiUrl = process.env.NEXT_PUBLIC_FLOWOPS_API_URL ?? "https://flowops-backend-staging-2yfdh7pwpq-as.a.run.app";
16637
17955
  bindPublishedForms(apiUrl, subdomain, contentCache.get(subdomain) ?? {});
16638
17956
  }, [isEditMode, fetchState, subdomain]);
16639
- useEffect12(() => {
17957
+ useEffect13(() => {
16640
17958
  if (!subdomain || isEditMode) return;
16641
17959
  let debounceTimer = null;
16642
17960
  let observer = null;
@@ -16647,10 +17965,28 @@ function OhhwellsBridge() {
16647
17965
  initSectionInstancesFromContent(content, window.location.pathname);
16648
17966
  observer?.disconnect();
16649
17967
  try {
17968
+ applyBrandChrome(content);
17969
+ if (typeof content[BRAND_KIT_KEY] === "string") {
17970
+ applyBrandToDom(parseBrandKit(content[BRAND_KIT_KEY]));
17971
+ } else {
17972
+ applyBrandToDom(null);
17973
+ }
17974
+ if (typeof content[AI_SECTIONS_KEY] === "string") {
17975
+ setAiSectionOrder(content[SECTION_ORDER_KEY], window.location.pathname);
17976
+ applyAiSectionsToDom(parseAiSectionsState(content[AI_SECTIONS_KEY]));
17977
+ }
17978
+ if (typeof content[STYLE_STORE_KEY] === "string") {
17979
+ applyStylesToDom(parseStyleStore(content[STYLE_STORE_KEY]));
17980
+ }
16650
17981
  for (const [key, val] of Object.entries(content)) {
16651
17982
  if (key === "__ohw_sections") continue;
17983
+ if (key === AI_SECTIONS_KEY) continue;
16652
17984
  if (key === LOGO_PLACEHOLDER_KEY) continue;
16653
17985
  if (LOGO_IMAGE_KEYS.includes(key)) continue;
17986
+ if (key === BRAND_KIT_KEY) continue;
17987
+ if (key === STYLE_STORE_KEY) continue;
17988
+ if (key === STYLE_STORE_KEY) continue;
17989
+ if (BRAND_CHROME_KEYS.has(key)) continue;
16654
17990
  if (applyVideoSettingNode(key, val)) continue;
16655
17991
  if (applyCarouselNode(key, val)) continue;
16656
17992
  document.querySelectorAll(`[data-ohw-key="${key}"]`).forEach((el) => {
@@ -16696,6 +18032,17 @@ function OhhwellsBridge() {
16696
18032
  debounceTimer = setTimeout(applyFromCache, 150);
16697
18033
  };
16698
18034
  applyFromCache();
18035
+ const pathCacheKey = `${subdomain}::${pathname}`;
18036
+ if (!fetchedContentPaths.has(pathCacheKey)) {
18037
+ fetchedContentPaths.add(pathCacheKey);
18038
+ const apiUrl = process.env.NEXT_PUBLIC_FLOWOPS_API_URL ?? "https://flowops-backend-staging-2yfdh7pwpq-as.a.run.app";
18039
+ fetch(`${apiUrl}/api/public/sites/${subdomain}/content?path=${encodeURIComponent(pathname)}`, { cache: "no-store" }).then((r2) => r2.ok ? r2.json() : null).then((data) => {
18040
+ if (!data?.content) return;
18041
+ contentCache.set(subdomain, data.content);
18042
+ applyFromCache();
18043
+ }).catch(() => {
18044
+ });
18045
+ }
16699
18046
  observer = new MutationObserver(scheduleApply);
16700
18047
  observer.observe(document.body, { childList: true, subtree: true });
16701
18048
  return () => {
@@ -16709,10 +18056,10 @@ function OhhwellsBridge() {
16709
18056
  const visible = Boolean(subdomain) && fetchState !== "done";
16710
18057
  el.style.display = visible ? "flex" : "none";
16711
18058
  }, [subdomain, fetchState]);
16712
- useEffect12(() => {
18059
+ useEffect13(() => {
16713
18060
  postToParent2({ type: "ow:navigation", path: pathname });
16714
18061
  }, [pathname, postToParent2]);
16715
- useEffect12(() => {
18062
+ useEffect13(() => {
16716
18063
  if (!isEditMode) return;
16717
18064
  if (linkPopoverSessionRef.current?.intent === "add-nav") return;
16718
18065
  if (document.querySelector("[data-ohw-section-picker]")) return;
@@ -16720,7 +18067,7 @@ function OhhwellsBridge() {
16720
18067
  deselectRef.current();
16721
18068
  deactivateRef.current();
16722
18069
  }, [pathname, isEditMode]);
16723
- useEffect12(() => {
18070
+ useEffect13(() => {
16724
18071
  const contentForNav = () => {
16725
18072
  if (isEditMode) return editContentRef.current;
16726
18073
  if (!subdomain) return {};
@@ -16789,35 +18136,36 @@ function OhhwellsBridge() {
16789
18136
  observer?.disconnect();
16790
18137
  };
16791
18138
  }, [isEditMode, pathname, subdomain, fetchState, resyncSelectedNavigationItem]);
16792
- useEffect12(() => {
18139
+ useEffect13(() => {
16793
18140
  if (!isEditMode) return;
18141
+ let lastPosted = 0;
16794
18142
  const measure = () => {
16795
18143
  const h = document.body.scrollHeight;
16796
- if (h > 50) postToParent2({ type: "ow:height", height: h });
18144
+ if (h > 50 && Math.abs(h - lastPosted) > 1) {
18145
+ lastPosted = h;
18146
+ postToParent2({ type: "ow:height", height: h });
18147
+ }
18148
+ };
18149
+ let raf = null;
18150
+ const schedule = () => {
18151
+ if (raf != null) return;
18152
+ raf = requestAnimationFrame(() => {
18153
+ raf = null;
18154
+ measure();
18155
+ });
16797
18156
  };
16798
18157
  const t1 = setTimeout(measure, 50);
16799
18158
  const t2 = setTimeout(measure, 500);
16800
- let lastWidth = window.innerWidth;
16801
- let resizeTimers = [];
16802
- const clearResizeTimers = () => {
16803
- resizeTimers.forEach(clearTimeout);
16804
- resizeTimers = [];
16805
- };
16806
- const handleResize = () => {
16807
- if (window.innerWidth === lastWidth) return;
16808
- lastWidth = window.innerWidth;
16809
- clearResizeTimers();
16810
- resizeTimers = HEIGHT_SETTLE_DELAYS.map((delay) => setTimeout(measure, delay));
16811
- };
16812
- window.addEventListener("resize", handleResize);
18159
+ const ro = new ResizeObserver(schedule);
18160
+ ro.observe(document.body);
16813
18161
  return () => {
16814
18162
  clearTimeout(t1);
16815
18163
  clearTimeout(t2);
16816
- clearResizeTimers();
16817
- window.removeEventListener("resize", handleResize);
18164
+ if (raf != null) cancelAnimationFrame(raf);
18165
+ ro.disconnect();
16818
18166
  };
16819
18167
  }, [pathname, isEditMode, postToParent2]);
16820
- useEffect12(() => {
18168
+ useEffect13(() => {
16821
18169
  if (!subdomainFromQuery || isEditMode) return;
16822
18170
  const handleClick = (e) => {
16823
18171
  const anchor = e.target.closest("a");
@@ -16833,7 +18181,7 @@ function OhhwellsBridge() {
16833
18181
  document.addEventListener("click", handleClick, true);
16834
18182
  return () => document.removeEventListener("click", handleClick, true);
16835
18183
  }, [subdomainFromQuery, isEditMode, router]);
16836
- useEffect12(() => {
18184
+ useEffect13(() => {
16837
18185
  if (!isEditMode) {
16838
18186
  editStylesRef.current?.base.remove();
16839
18187
  editStylesRef.current?.forceHover.remove();
@@ -17055,6 +18403,7 @@ function OhhwellsBridge() {
17055
18403
  return;
17056
18404
  }
17057
18405
  const target = e.target;
18406
+ if (target.closest("[data-ohw-ai-review]")) return;
17058
18407
  if (target.closest("[data-ohw-toolbar]")) return;
17059
18408
  if (target.closest("[data-ohw-state-toggle]")) return;
17060
18409
  if (target.closest("[data-ohw-max-badge]")) return;
@@ -17066,6 +18415,9 @@ function OhhwellsBridge() {
17066
18415
  )) {
17067
18416
  return;
17068
18417
  }
18418
+ if (selectedMediaElRef.current && !selectedMediaElRef.current.contains(target) && !target.closest("[data-ohw-media-overlay], [data-ohw-edit-chrome], [data-ohw-item-interaction]")) {
18419
+ clearMediaSelectionRef.current();
18420
+ }
17069
18421
  {
17070
18422
  const formEl = getFormElement(target);
17071
18423
  const onSuccessText = target.closest(`[${SUCCESS_TEXT_ATTR}]`);
@@ -17220,8 +18572,11 @@ function OhhwellsBridge() {
17220
18572
  if (isMediaEditable(editable) && !buttonOnMedia) {
17221
18573
  e.preventDefault();
17222
18574
  e.stopPropagation();
17223
- aiSectionApiRef.current?.selectFromElement(editable);
17224
- postToParentRef.current({ type: "ow:image-pick", key: editable.dataset.ohwKey ?? "", elementType: editable.dataset.ohwEditable ?? "image" });
18575
+ if (selectedMediaElRef.current === editable) {
18576
+ postToParentRef.current({ type: "ow:image-pick", key: editable.dataset.ohwKey ?? "", elementType: editable.dataset.ohwEditable ?? "image" });
18577
+ } else {
18578
+ selectMediaElementRef.current(editable);
18579
+ }
17225
18580
  return;
17226
18581
  }
17227
18582
  const socialItem = getSocialItem(editable);
@@ -17357,6 +18712,7 @@ function OhhwellsBridge() {
17357
18712
  };
17358
18713
  const handleDblClick = (e) => {
17359
18714
  const target = e.target;
18715
+ if (target.closest("[data-ohw-ai-review]")) return;
17360
18716
  if (target.closest("[data-ohw-toolbar]")) return;
17361
18717
  if (target.closest("[data-ohw-state-toggle]")) return;
17362
18718
  if (target.closest("[data-ohw-max-badge]")) return;
@@ -17408,6 +18764,9 @@ function OhhwellsBridge() {
17408
18764
  setHoveredItemRect(null);
17409
18765
  hoveredNavContainerRef.current = null;
17410
18766
  setHoveredNavContainerRect(null);
18767
+ siblingHintElRef.current = null;
18768
+ setSiblingHintRect(null);
18769
+ setSiblingHintRects([]);
17411
18770
  return;
17412
18771
  }
17413
18772
  {
@@ -17526,7 +18885,6 @@ function OhhwellsBridge() {
17526
18885
  hoveredNavContainerRef.current = null;
17527
18886
  setHoveredNavContainerRect(null);
17528
18887
  hoveredItemElRef.current = editable;
17529
- setHoveredItemRect(isSelectedForHoverRef.current(editable) ? null : editable.getBoundingClientRect());
17530
18888
  }
17531
18889
  }
17532
18890
  }
@@ -17823,7 +19181,7 @@ function OhhwellsBridge() {
17823
19181
  }
17824
19182
  };
17825
19183
  const probeImageAt = (clientX, clientY, isDragOver = false, fromParentViewport = false) => {
17826
- if (linkPopoverOpenRef.current) {
19184
+ if (linkPopoverOpenRef.current || floatingPanelOpenRef.current && isPointOverFloatingPanel(clientX, clientY)) {
17827
19185
  if (hoveredImageRef.current) {
17828
19186
  hoveredImageRef.current = null;
17829
19187
  hoveredImageHasTextOverlapRef.current = false;
@@ -18157,7 +19515,9 @@ function OhhwellsBridge() {
18157
19515
  return;
18158
19516
  }
18159
19517
  const { y } = toProbeCoords(clientX, clientY, fromParentViewport);
18160
- const sections = Array.from(document.querySelectorAll("[data-ohw-section]")).sort((a, b) => a.getBoundingClientRect().top - b.getBoundingClientRect().top);
19518
+ const sections = Array.from(document.querySelectorAll("[data-ohw-section]")).filter(
19519
+ (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
19520
+ ).sort((a, b) => a.getBoundingClientRect().top - b.getBoundingClientRect().top);
18161
19521
  const ZONE = 20;
18162
19522
  for (let i = 0; i < sections.length; i++) {
18163
19523
  const a = sections[i];
@@ -18186,8 +19546,7 @@ function OhhwellsBridge() {
18186
19546
  };
18187
19547
  const handleMouseMove = (e) => {
18188
19548
  const { clientX, clientY } = e;
18189
- if (pointOwnedByFloatingPanel(clientX, clientY)) return;
18190
- if (isOverEditorChrome(clientX, clientY)) {
19549
+ if (document.documentElement.hasAttribute("data-ohw-panel-dragging") || floatingPanelOpenRef.current && isPointOverFloatingPanel(clientX, clientY) || isOverEditorChrome(clientX, clientY)) {
18191
19550
  document.querySelectorAll("[data-ohw-hovered]").forEach((el) => el.removeAttribute("data-ohw-hovered"));
18192
19551
  formHoverElRef.current = null;
18193
19552
  setFormHoverRect(null);
@@ -18195,6 +19554,12 @@ function OhhwellsBridge() {
18195
19554
  setHoveredItemRect(null);
18196
19555
  hoveredNavContainerRef.current = null;
18197
19556
  setHoveredNavContainerRect(null);
19557
+ siblingHintElRef.current = null;
19558
+ setSiblingHintRect(null);
19559
+ setSiblingHintRects([]);
19560
+ dismissImageHover();
19561
+ clearImageHover();
19562
+ setSectionGap(null);
18198
19563
  return;
18199
19564
  }
18200
19565
  if (probeSocialsRowAt(clientX, clientY)) return;
@@ -18206,7 +19571,11 @@ function OhhwellsBridge() {
18206
19571
  if (e.data?.type !== "ow:pointer-sync") return;
18207
19572
  const { clientX, clientY } = e.data;
18208
19573
  if (typeof clientX !== "number" || typeof clientY !== "number") return;
18209
- if (pointOwnedByFloatingPanel(clientX, clientY)) return;
19574
+ if (document.documentElement.hasAttribute("data-ohw-panel-dragging") || floatingPanelOpenRef.current && isPointOverFloatingPanel(clientX, clientY)) {
19575
+ dismissImageHover();
19576
+ clearImageHover();
19577
+ return;
19578
+ }
18210
19579
  if (probeSocialsRowAt(clientX, clientY)) return;
18211
19580
  probeSectionGapAt(clientX, clientY);
18212
19581
  probeImageAt(clientX, clientY);
@@ -18481,7 +19850,7 @@ function OhhwellsBridge() {
18481
19850
  timers.set(key, setTimeout(() => {
18482
19851
  timers.delete(key);
18483
19852
  postToParentRef.current({ type: "ow:change", nodes: [{ key, text: html }] });
18484
- const h = document.documentElement.scrollHeight;
19853
+ const h = document.body.scrollHeight;
18485
19854
  if (h > 50) postToParentRef.current({ type: "ow:height", height: h });
18486
19855
  }, 400));
18487
19856
  };
@@ -18489,10 +19858,23 @@ function OhhwellsBridge() {
18489
19858
  if (e.data?.type !== "ow:hydrate") return;
18490
19859
  const content = e.data.content;
18491
19860
  if (!content) return;
19861
+ if (typeof content[BRAND_KIT_KEY] === "string") {
19862
+ brandKitRef.current = content[BRAND_KIT_KEY];
19863
+ applyBrandToDom(parseBrandKit(content[BRAND_KIT_KEY]));
19864
+ } else {
19865
+ brandKitRef.current = "";
19866
+ applyBrandToDom(null);
19867
+ }
18492
19868
  if (typeof content[AI_SECTIONS_KEY] === "string") {
18493
19869
  aiSectionsRef.current = content[AI_SECTIONS_KEY];
19870
+ setAiSectionOrder(content[SECTION_ORDER_KEY], window.location.pathname);
18494
19871
  applyAiSectionsToDom(parseAiSectionsState(content[AI_SECTIONS_KEY]));
18495
19872
  }
19873
+ if (typeof content[STYLE_STORE_KEY] === "string") {
19874
+ stylesRef.current = content[STYLE_STORE_KEY];
19875
+ applyStylesToDom(parseStyleStore(content[STYLE_STORE_KEY]));
19876
+ }
19877
+ applyBrandChrome(content);
18496
19878
  let sectionsJson = null;
18497
19879
  for (const [key, val] of Object.entries(content)) {
18498
19880
  if (key === "__ohw_sections") {
@@ -18502,6 +19884,9 @@ function OhhwellsBridge() {
18502
19884
  if (key === AI_SECTIONS_KEY) continue;
18503
19885
  if (key === LOGO_PLACEHOLDER_KEY) continue;
18504
19886
  if (LOGO_IMAGE_KEYS.includes(key) && !String(val ?? "").trim()) continue;
19887
+ if (key === BRAND_KIT_KEY) continue;
19888
+ if (key === STYLE_STORE_KEY) continue;
19889
+ if (BRAND_CHROME_KEYS.has(key)) continue;
18505
19890
  if (applyVideoSettingNode(key, val)) continue;
18506
19891
  if (applyCarouselNode(key, val)) continue;
18507
19892
  document.querySelectorAll(`[data-ohw-key="${key}"]`).forEach((el) => {
@@ -18515,6 +19900,8 @@ function OhhwellsBridge() {
18515
19900
  if (video && video.src !== val) applyVideoSrc(video, val);
18516
19901
  } else if (el.dataset.ohwEditable === "link") {
18517
19902
  applyLinkHref(el, val);
19903
+ } else if (el.dataset.ohwEditable === "icon") {
19904
+ applyIconMarkup(el, val);
18518
19905
  } else if (isIconMarkupValue(val)) {
18519
19906
  } else {
18520
19907
  el.innerHTML = val;
@@ -18535,7 +19922,7 @@ function OhhwellsBridge() {
18535
19922
  reconcileFooterOrderFromContent(editContentRef.current);
18536
19923
  syncNavigationDragCursorAttrs();
18537
19924
  enforceLinkHrefs();
18538
- const hydratedHeight = document.documentElement.scrollHeight;
19925
+ const hydratedHeight = document.body.scrollHeight;
18539
19926
  if (hydratedHeight > 50) postToParentRef.current({ type: "ow:height", height: hydratedHeight });
18540
19927
  postToParentRef.current({ type: "ow:hydrate-done" });
18541
19928
  };
@@ -18599,16 +19986,25 @@ function OhhwellsBridge() {
18599
19986
  nodes: collectEditableNodes(editContentRef.current)
18600
19987
  });
18601
19988
  };
19989
+ const clearInteractionChrome = () => {
19990
+ deactivateRef.current();
19991
+ deselectRef.current();
19992
+ clearMediaSelectionRef.current();
19993
+ };
18602
19994
  const handleAiApplyTree = (e) => {
18603
19995
  if (e.data?.type !== "ow:ai-apply-tree") return;
18604
19996
  const payload = e.data.payload;
18605
19997
  if (!payload || typeof payload.id !== "string" || !isRenderableTree(payload.tree)) return;
19998
+ clearInteractionChrome();
18606
19999
  const previous = aiSectionsRef.current;
18607
- const nextState = applyTreeToState(parseAiSectionsState(previous), payload);
20000
+ const nextState = applyTreeToState(parseAiSectionsState(previous), {
20001
+ ...payload,
20002
+ path: payload.path ?? window.location.pathname
20003
+ });
18608
20004
  const nextValue = serializeAiSectionsState(nextState);
18609
20005
  aiSectionsRef.current = nextValue;
18610
20006
  applyAiSectionsToDom(nextState);
18611
- const newHeight = document.documentElement.scrollHeight;
20007
+ const newHeight = document.body.scrollHeight;
18612
20008
  if (newHeight > 50) postToParentRef.current({ type: "ow:height", height: newHeight });
18613
20009
  postToParentRef.current({ type: "ow:change", nodes: [{ key: AI_SECTIONS_KEY, text: nextValue }] });
18614
20010
  const appliedEl = document.querySelector(`[data-ohw-section="${CSS.escape(payload.id)}"]`);
@@ -18625,12 +20021,13 @@ function OhhwellsBridge() {
18625
20021
  const exists = document.querySelector(`[data-ohw-section="${CSS.escape(sectionId)}"]`);
18626
20022
  if (!exists) return;
18627
20023
  if (isPageFrameSection(exists)) return;
20024
+ clearInteractionChrome();
18628
20025
  const previous = aiSectionsRef.current;
18629
20026
  const nextState = deleteSectionFromState(parseAiSectionsState(previous), sectionId);
18630
20027
  const nextValue = serializeAiSectionsState(nextState);
18631
20028
  aiSectionsRef.current = nextValue;
18632
20029
  applyAiSectionsToDom(nextState);
18633
- const newHeight = document.documentElement.scrollHeight;
20030
+ const newHeight = document.body.scrollHeight;
18634
20031
  if (newHeight > 50) postToParentRef.current({ type: "ow:height", height: newHeight });
18635
20032
  postToParentRef.current({ type: "ow:change", nodes: [{ key: AI_SECTIONS_KEY, text: nextValue }] });
18636
20033
  postToParentRef.current({ type: "ow:ai-section-deleted", sectionId, previous, value: nextValue });
@@ -18640,20 +20037,107 @@ function OhhwellsBridge() {
18640
20037
  const handleAiSetSections = (e) => {
18641
20038
  if (e.data?.type !== "ow:ai-set-sections") return;
18642
20039
  const value = typeof e.data.value === "string" ? e.data.value : "";
20040
+ clearInteractionChrome();
18643
20041
  aiSectionsRef.current = value;
18644
20042
  applyAiSectionsToDom(parseAiSectionsState(value));
18645
- const restoredHeight = document.documentElement.scrollHeight;
20043
+ applyStylesToDom(parseStyleStore(stylesRef.current));
20044
+ const restoredHeight = document.body.scrollHeight;
18646
20045
  if (restoredHeight > 50) postToParentRef.current({ type: "ow:height", height: restoredHeight });
18647
20046
  postToParentRef.current({ type: "ow:change", nodes: [{ key: AI_SECTIONS_KEY, text: value }] });
18648
20047
  postAiSectionsChanged();
18649
20048
  };
18650
20049
  window.addEventListener("message", handleAiSetSections);
20050
+ const handleMoveSection = (e) => {
20051
+ if (e.data?.type !== "ow:move-section") return;
20052
+ const instanceId = typeof e.data.instanceId === "string" ? e.data.instanceId : "";
20053
+ const direction = e.data.direction === "up" || e.data.direction === "down" ? e.data.direction : null;
20054
+ if (!instanceId || !direction) return;
20055
+ const entries = moveSectionInstance(instanceId, direction, window.location.pathname);
20056
+ if (!entries) return;
20057
+ const orderJson = JSON.stringify(entries);
20058
+ editContentRef.current = { ...editContentRef.current, [SECTION_ORDER_KEY]: orderJson };
20059
+ setAiSectionOrder(orderJson, window.location.pathname);
20060
+ postToParentRef.current({ type: "ow:change", nodes: [{ key: SECTION_ORDER_KEY, text: orderJson }] });
20061
+ window.dispatchEvent(new Event("resize"));
20062
+ };
20063
+ window.addEventListener("message", handleMoveSection);
20064
+ const handleAiSetBrand = (e) => {
20065
+ if (e.data?.type !== "ow:ai-set-brand") return;
20066
+ const value = typeof e.data.value === "string" ? e.data.value : "";
20067
+ const previous = brandKitRef.current;
20068
+ brandKitRef.current = value;
20069
+ applyBrandToDom(parseBrandKit(value));
20070
+ if (aiSectionsRef.current) applyAiSectionsToDom(parseAiSectionsState(aiSectionsRef.current));
20071
+ applyStylesToDom(parseStyleStore(stylesRef.current));
20072
+ postToParentRef.current({ type: "ow:change", nodes: [{ key: BRAND_KIT_KEY, text: value }] });
20073
+ postToParentRef.current({ type: "ow:ai-brand-applied", previous, value });
20074
+ };
20075
+ window.addEventListener("message", handleAiSetBrand);
20076
+ const handleAiSetStyles = (e) => {
20077
+ if (e.data?.type !== "ow:ai-set-styles") return;
20078
+ const value = typeof e.data.value === "string" ? e.data.value : "";
20079
+ const previous = stylesRef.current;
20080
+ stylesRef.current = value;
20081
+ applyStylesToDom(parseStyleStore(value));
20082
+ postToParentRef.current({ type: "ow:change", nodes: [{ key: STYLE_STORE_KEY, text: value }] });
20083
+ postToParentRef.current({ type: "ow:ai-styles-applied", previous, value });
20084
+ };
20085
+ window.addEventListener("message", handleAiSetStyles);
20086
+ const handleGetBrand = (e) => {
20087
+ if (e.data?.type !== "ow:get-brand") return;
20088
+ const template = deriveTemplateBrand();
20089
+ const value = brandKitRef.current || (template ? JSON.stringify(template) : "");
20090
+ postToParentRef.current({ type: "ow:brand-value", value });
20091
+ };
20092
+ window.addEventListener("message", handleGetBrand);
18651
20093
  const handlePanelDragging = (e) => {
18652
20094
  if (e.data?.type !== "ow:panel-dragging") return;
18653
20095
  if (e.data.dragging) document.documentElement.setAttribute("data-ohw-panel-dragging", "");
18654
20096
  else document.documentElement.removeAttribute("data-ohw-panel-dragging");
18655
20097
  };
18656
20098
  window.addEventListener("message", handlePanelDragging);
20099
+ const handleDeleteSection = (e) => {
20100
+ if (e.data?.type !== "ow:delete-section") return;
20101
+ const instanceId = typeof e.data.instanceId === "string" ? e.data.instanceId : "";
20102
+ if (!instanceId) return;
20103
+ const currentEntries = getPageSectionOrderEntries(editContentRef.current[SECTION_ORDER_KEY], window.location.pathname);
20104
+ const entries = deleteSectionInstance(instanceId, window.location.pathname, currentEntries);
20105
+ if (!entries) return;
20106
+ const orderJson = JSON.stringify(entries);
20107
+ editContentRef.current = { ...editContentRef.current, [SECTION_ORDER_KEY]: orderJson };
20108
+ postToParentRef.current({ type: "ow:change", nodes: [{ key: SECTION_ORDER_KEY, text: orderJson }] });
20109
+ aiSectionApiRef.current?.clear();
20110
+ window.dispatchEvent(new Event("resize"));
20111
+ const deleteHeight = document.body.scrollHeight;
20112
+ if (deleteHeight > 50) postToParentRef.current({ type: "ow:height", height: deleteHeight });
20113
+ const actionId = newInstanceId();
20114
+ pendingDeleteUndoRef.current = {
20115
+ actionId,
20116
+ restore: () => {
20117
+ const restoredEntries = getPageSectionOrderEntries(
20118
+ editContentRef.current[SECTION_ORDER_KEY],
20119
+ window.location.pathname
20120
+ );
20121
+ const restored = restoreSectionInstance(instanceId, window.location.pathname, restoredEntries);
20122
+ if (!restored) return;
20123
+ const restoredJson = JSON.stringify(restored);
20124
+ editContentRef.current = { ...editContentRef.current, [SECTION_ORDER_KEY]: restoredJson };
20125
+ postToParentRef.current({ type: "ow:change", nodes: [{ key: SECTION_ORDER_KEY, text: restoredJson }] });
20126
+ window.dispatchEvent(new Event("resize"));
20127
+ const restoreHeight = document.body.scrollHeight;
20128
+ if (restoreHeight > 50) postToParentRef.current({ type: "ow:height", height: restoreHeight });
20129
+ }
20130
+ };
20131
+ postToParentRef.current({
20132
+ type: "ow:toast",
20133
+ title: "Section deleted",
20134
+ toastType: "success",
20135
+ actionLabel: "Undo",
20136
+ actionId,
20137
+ duration: 6e3
20138
+ });
20139
+ };
20140
+ window.addEventListener("message", handleDeleteSection);
18657
20141
  const handleDeactivate = (e) => {
18658
20142
  if (e.data?.type !== "ow:deactivate") return;
18659
20143
  if (document.documentElement.hasAttribute("data-ohw-panel-dragging")) return;
@@ -18663,8 +20147,15 @@ function OhhwellsBridge() {
18663
20147
  closeLinkPopoverRef.current();
18664
20148
  return;
18665
20149
  }
20150
+ if (floatingPanelOpenRef.current) {
20151
+ setFloatingPanelRef.current(null);
20152
+ deselectRef.current();
20153
+ deactivateRef.current();
20154
+ return;
20155
+ }
18666
20156
  deselectRef.current();
18667
20157
  deactivateRef.current();
20158
+ clearMediaSelectionRef.current();
18668
20159
  };
18669
20160
  window.addEventListener("message", handleDeactivate);
18670
20161
  const handleToastAction = (e) => {
@@ -18750,6 +20241,10 @@ function OhhwellsBridge() {
18750
20241
  const handleKeyDown = (e) => {
18751
20242
  if (e.key === "Escape" && document.querySelector("[data-ohw-section-picker]")) return;
18752
20243
  if (e.key === "Escape" && document.querySelector("[data-ohw-more-menu]")) return;
20244
+ if (e.key === "Escape" && selectedMediaElRef.current) {
20245
+ clearMediaSelectionRef.current();
20246
+ return;
20247
+ }
18753
20248
  if ((e.ctrlKey || e.metaKey) && e.key.toLowerCase() === "a" && activeElRef.current) {
18754
20249
  e.preventDefault();
18755
20250
  selectAllTextInEditable(activeElRef.current);
@@ -18909,6 +20404,12 @@ function OhhwellsBridge() {
18909
20404
  if (aiSectionsRef.current) {
18910
20405
  nodes.push({ key: AI_SECTIONS_KEY, type: "sections", text: aiSectionsRef.current });
18911
20406
  }
20407
+ if (stylesRef.current) {
20408
+ nodes.push({ key: STYLE_STORE_KEY, type: "styles", text: stylesRef.current });
20409
+ }
20410
+ if (brandKitRef.current) {
20411
+ nodes.push({ key: BRAND_KIT_KEY, type: "brand", text: brandKitRef.current });
20412
+ }
18912
20413
  document.querySelectorAll('[data-ohw-editable="form"]').forEach((form) => {
18913
20414
  const formKey = formKeyOf(form);
18914
20415
  if (!formKey) return;
@@ -18926,8 +20427,12 @@ function OhhwellsBridge() {
18926
20427
  if (inserted) {
18927
20428
  const tracker = getSectionsTracker();
18928
20429
  postToParentRef.current({ type: "ow:change", nodes: [{ key: "__ohw_sections", text: tracker.textContent ?? "[]" }] });
18929
- const h = document.documentElement.scrollHeight;
18930
- if (h > 50) postToParentRef.current({ type: "ow:height", height: h });
20430
+ const reportHeight = () => {
20431
+ const h = document.body.scrollHeight;
20432
+ if (h > 50) postToParentRef.current({ type: "ow:height", height: h });
20433
+ };
20434
+ reportHeight();
20435
+ setTimeout(reportHeight, 500);
18931
20436
  }
18932
20437
  };
18933
20438
  const handleSwitchSchedule = (e) => {
@@ -18968,7 +20473,7 @@ function OhhwellsBridge() {
18968
20473
  const updated = sections.filter((s) => !(s.type === "scheduling" && s.pagePath === currentPath));
18969
20474
  tracker.textContent = JSON.stringify(updated);
18970
20475
  postToParentRef.current({ type: "ow:change", nodes: [{ key: "__ohw_sections", text: tracker.textContent }] });
18971
- const h = document.documentElement.scrollHeight;
20476
+ const h = document.body.scrollHeight;
18972
20477
  if (h > 50) postToParentRef.current({ type: "ow:height", height: h });
18973
20478
  };
18974
20479
  const handleCollectSection = (e) => {
@@ -19323,19 +20828,24 @@ function OhhwellsBridge() {
19323
20828
  window.removeEventListener("message", handleAiApplyTree);
19324
20829
  window.removeEventListener("message", handleAiDeleteSection);
19325
20830
  window.removeEventListener("message", handleAiSetSections);
20831
+ window.removeEventListener("message", handleMoveSection);
20832
+ window.removeEventListener("message", handleAiSetBrand);
20833
+ window.removeEventListener("message", handleAiSetStyles);
20834
+ window.removeEventListener("message", handleGetBrand);
19326
20835
  window.removeEventListener("message", handlePanelDragging);
20836
+ window.removeEventListener("message", handleDeleteSection);
19327
20837
  window.removeEventListener("message", handleDeactivate);
19328
- document.documentElement.removeAttribute("data-ohw-panel-dragging");
19329
20838
  window.removeEventListener("message", handleToastAction);
19330
20839
  window.removeEventListener("message", handleFormCount);
19331
20840
  window.removeEventListener("message", handleUiEscape);
20841
+ document.documentElement.removeAttribute("data-ohw-panel-dragging");
19332
20842
  autoSaveTimers.current.forEach(clearTimeout);
19333
20843
  autoSaveTimers.current.clear();
19334
20844
  if (imageUnhoverTimerRef.current) clearTimeout(imageUnhoverTimerRef.current);
19335
20845
  if (imageShowTimerRef.current) clearTimeout(imageShowTimerRef.current);
19336
20846
  };
19337
20847
  }, [isEditMode, refreshStateRules]);
19338
- useEffect12(() => {
20848
+ useEffect13(() => {
19339
20849
  if (!isEditMode) return;
19340
20850
  const THRESHOLD = 10;
19341
20851
  const resolveWasSelected = (el) => {
@@ -19491,7 +21001,7 @@ function OhhwellsBridge() {
19491
21001
  unlockFooterDragInteraction();
19492
21002
  };
19493
21003
  }, [isEditMode]);
19494
- useEffect12(() => {
21004
+ useEffect13(() => {
19495
21005
  const handler = (e) => {
19496
21006
  if (e.data?.type !== "ow:request-schedule-config") return;
19497
21007
  const insertAfterVal = e.data.insertAfter;
@@ -19507,7 +21017,7 @@ function OhhwellsBridge() {
19507
21017
  window.addEventListener("message", handler);
19508
21018
  return () => window.removeEventListener("message", handler);
19509
21019
  }, [processConfigRequest]);
19510
- useEffect12(() => {
21020
+ useEffect13(() => {
19511
21021
  if (!isEditMode) return;
19512
21022
  document.querySelectorAll("[data-ohw-active-state]").forEach((el) => {
19513
21023
  el.removeAttribute("data-ohw-active-state");
@@ -19531,7 +21041,7 @@ function OhhwellsBridge() {
19531
21041
  postToParent2({
19532
21042
  type: "ow:ready",
19533
21043
  version: "1",
19534
- bridgeVersion: "0.1.74",
21044
+ bridgeVersion: "0.1.76",
19535
21045
  path: pathname,
19536
21046
  nodes: collectEditableNodes(editContentRef.current),
19537
21047
  sections
@@ -19543,13 +21053,13 @@ function OhhwellsBridge() {
19543
21053
  clearTimeout(timer);
19544
21054
  };
19545
21055
  }, [pathname, isEditMode, refreshStateRules, postToParent2]);
19546
- useEffect12(() => {
21056
+ useEffect13(() => {
19547
21057
  scrollToHashSectionWhenReady();
19548
21058
  const onHashChange = () => scrollToHashSectionWhenReady();
19549
21059
  window.addEventListener("hashchange", onHashChange);
19550
21060
  return () => window.removeEventListener("hashchange", onHashChange);
19551
21061
  }, [pathname]);
19552
- const handleCommand = useCallback7((cmd) => {
21062
+ const handleCommand = useCallback8((cmd) => {
19553
21063
  const el = activeElRef.current;
19554
21064
  const selBefore = window.getSelection();
19555
21065
  let savedOffsets = null;
@@ -19585,7 +21095,7 @@ function OhhwellsBridge() {
19585
21095
  if (el) setToolbarRect(getEditMeasureEl(el).getBoundingClientRect());
19586
21096
  refreshActiveCommandsRef.current();
19587
21097
  }, []);
19588
- useEffect12(() => {
21098
+ useEffect13(() => {
19589
21099
  const session = floatingPanel && floatingPanel.kind === "logo-size" ? floatingPanel : null;
19590
21100
  if (!session || !logoSizeDraft) return;
19591
21101
  const onPanelAction = (e) => {
@@ -19623,7 +21133,7 @@ function OhhwellsBridge() {
19623
21133
  window.addEventListener("message", onPanelAction);
19624
21134
  return () => window.removeEventListener("message", onPanelAction);
19625
21135
  }, [floatingPanel, logoSizeDraft, editorViewport, persistLogoSizeDraft, closeFloatingPanelAndDeselect]);
19626
- const handleStateChange = useCallback7((state) => {
21136
+ const handleStateChange = useCallback8((state) => {
19627
21137
  if (!activeStateElRef.current) return;
19628
21138
  const el = activeStateElRef.current;
19629
21139
  if (state === "Default") {
@@ -19636,7 +21146,7 @@ function OhhwellsBridge() {
19636
21146
  }
19637
21147
  setToggleState((prev) => prev ? { ...prev, activeState: state } : null);
19638
21148
  }, [deactivate]);
19639
- const reselectAfterLinkPopover = useCallback7(
21149
+ const reselectAfterLinkPopover = useCallback8(
19640
21150
  (hrefKey) => {
19641
21151
  requestAnimationFrame(() => {
19642
21152
  const el = resolveHrefKeyElement(hrefKey);
@@ -19645,7 +21155,7 @@ function OhhwellsBridge() {
19645
21155
  },
19646
21156
  [resolveHrefKeyElement]
19647
21157
  );
19648
- const closeLinkPopover = useCallback7(() => {
21158
+ const closeLinkPopover = useCallback8(() => {
19649
21159
  const session = linkPopoverSessionRef.current;
19650
21160
  addNavAfterAnchorRef.current = null;
19651
21161
  setLinkPopover(null);
@@ -19653,9 +21163,9 @@ function OhhwellsBridge() {
19653
21163
  reselectAfterLinkPopover(session.key);
19654
21164
  }
19655
21165
  }, [reselectAfterLinkPopover]);
19656
- const closeLinkPopoverRef = useRef9(closeLinkPopover);
21166
+ const closeLinkPopoverRef = useRef10(closeLinkPopover);
19657
21167
  closeLinkPopoverRef.current = closeLinkPopover;
19658
- const openLinkPopoverForActive = useCallback7(() => {
21168
+ const openLinkPopoverForActive = useCallback8(() => {
19659
21169
  const hrefCtx = getHrefKeyFromElement(activeElRef.current);
19660
21170
  if (!hrefCtx) return;
19661
21171
  bumpLinkPopoverGrace();
@@ -19666,7 +21176,7 @@ function OhhwellsBridge() {
19666
21176
  });
19667
21177
  deactivate();
19668
21178
  }, [deactivate]);
19669
- const openLinkPopoverForSelected = useCallback7(() => {
21179
+ const openLinkPopoverForSelected = useCallback8(() => {
19670
21180
  const anchor = selectedElRef.current;
19671
21181
  if (!anchor) return;
19672
21182
  const key = anchor.getAttribute("data-ohw-href-key");
@@ -19683,7 +21193,7 @@ function OhhwellsBridge() {
19683
21193
  });
19684
21194
  deselect();
19685
21195
  }, [deselect]);
19686
- const handleSelectParent = useCallback7(() => {
21196
+ const handleSelectParent = useCallback8(() => {
19687
21197
  const selected = selectedElRef.current;
19688
21198
  if (!selected) return;
19689
21199
  if (toolbarVariantRef.current === "select-frame") {
@@ -19710,7 +21220,7 @@ function OhhwellsBridge() {
19710
21220
  }
19711
21221
  deselectRef.current();
19712
21222
  }, []);
19713
- const handleDuplicateSelected = useCallback7(() => {
21223
+ const handleDuplicateSelected = useCallback8(() => {
19714
21224
  const selected = selectedElRef.current;
19715
21225
  if (!selected || !isNavigationItem2(selected)) return;
19716
21226
  const hrefKey = selected.getAttribute("data-ohw-href-key");
@@ -19843,7 +21353,7 @@ function OhhwellsBridge() {
19843
21353
  });
19844
21354
  }
19845
21355
  }, [postToParent2]);
19846
- const runPendingDeleteUndo = useCallback7(() => {
21356
+ const runPendingDeleteUndo = useCallback8(() => {
19847
21357
  const pending = pendingDeleteUndoRef.current;
19848
21358
  if (!pending) return false;
19849
21359
  pendingDeleteUndoRef.current = null;
@@ -19851,7 +21361,7 @@ function OhhwellsBridge() {
19851
21361
  enforceLinkHrefs();
19852
21362
  return true;
19853
21363
  }, []);
19854
- const handleDeleteSelected = useCallback7(() => {
21364
+ const handleDeleteSelected = useCallback8(() => {
19855
21365
  const selected = selectedElRef.current;
19856
21366
  if (!selected) return false;
19857
21367
  return deleteSelectedNavFooterItem({
@@ -19872,7 +21382,7 @@ function OhhwellsBridge() {
19872
21382
  }, [postToParent2]);
19873
21383
  handleDeleteSelectedRef.current = handleDeleteSelected;
19874
21384
  runPendingDeleteUndoRef.current = runPendingDeleteUndo;
19875
- const handleLinkPopoverSubmit = useCallback7(
21385
+ const handleLinkPopoverSubmit = useCallback8(
19876
21386
  (target) => {
19877
21387
  const session = linkPopoverSessionRef.current;
19878
21388
  if (!session) return;
@@ -19938,19 +21448,30 @@ function OhhwellsBridge() {
19938
21448
  const showEditLink = toolbarShowEditLink;
19939
21449
  const currentSections = sectionsByPath[pathname] ?? [];
19940
21450
  linkPopoverOpenRef.current = linkPopover !== null;
19941
- const handleMediaReplace = useCallback7(
21451
+ const handleMediaSelect = useCallback8((key) => {
21452
+ const el = hoveredImageRef.current?.dataset.ohwKey === key ? hoveredImageRef.current : Array.from(document.querySelectorAll(MEDIA_SELECTOR)).find(
21453
+ (m) => (m.dataset.ohwKey ?? "") === key
21454
+ ) ?? null;
21455
+ if (!el) return;
21456
+ selectMediaElementRef.current(el);
21457
+ }, []);
21458
+ const handleMediaReplace = useCallback8(
19942
21459
  (key) => {
19943
- postToParent2({ type: "ow:image-pick", key, elementType: mediaHover?.elementType ?? "image" });
21460
+ postToParent2({
21461
+ type: "ow:image-pick",
21462
+ key,
21463
+ elementType: mediaHover?.elementType ?? selectedMedia?.elementType ?? "image"
21464
+ });
19944
21465
  },
19945
- [postToParent2, mediaHover?.elementType]
21466
+ [postToParent2, mediaHover?.elementType, selectedMedia?.elementType]
19946
21467
  );
19947
- const handleEditCarousel = useCallback7(
21468
+ const handleEditCarousel = useCallback8(
19948
21469
  (key) => {
19949
21470
  postToParent2({ type: "ow:carousel-open", key, images: readCarouselValue(key) });
19950
21471
  },
19951
21472
  [postToParent2]
19952
21473
  );
19953
- const handleMediaFadeOutComplete = useCallback7((key) => {
21474
+ const handleMediaFadeOutComplete = useCallback8((key) => {
19954
21475
  setUploadingRects((prev) => {
19955
21476
  if (!(key in prev)) return prev;
19956
21477
  const next = { ...prev };
@@ -19958,7 +21479,7 @@ function OhhwellsBridge() {
19958
21479
  return next;
19959
21480
  });
19960
21481
  }, []);
19961
- const handleVideoSettingsChange = useCallback7(
21482
+ const handleVideoSettingsChange = useCallback8(
19962
21483
  (key, settings) => {
19963
21484
  document.querySelectorAll(`[data-ohw-key="${key}"]`).forEach((el) => {
19964
21485
  const video = getVideoEl2(el);
@@ -19980,430 +21501,516 @@ function OhhwellsBridge() {
19980
21501
  },
19981
21502
  [postToParent2]
19982
21503
  );
19983
- return bridgeRoot ? createPortal2(
19984
- /* @__PURE__ */ jsxs20(Fragment8, { children: [
19985
- /* @__PURE__ */ jsx33("div", { ref: attachVisibleViewport, "data-ohw-visible-viewport": "", "aria-hidden": true }),
19986
- isEditMode && /* @__PURE__ */ jsx33(AiSectionOverlay, { postToParent: postToParent2, apiRef: aiSectionApiRef }),
19987
- Object.entries(uploadingRects).map(([key, { rect, fadingOut }]) => /* @__PURE__ */ jsx33(
19988
- MediaOverlay,
19989
- {
19990
- hover: { key, rect, elementType: "image", isDragOver: false, hasTextOverlap: false },
19991
- isUploading: true,
19992
- fadingOut,
19993
- onFadeOutComplete: handleMediaFadeOutComplete,
19994
- onReplace: handleMediaReplace
19995
- },
19996
- `uploading-${key}`
19997
- )),
19998
- mediaHover && !(mediaHover.key in uploadingRects) && /* @__PURE__ */ jsx33(
19999
- MediaOverlay,
20000
- {
20001
- hover: mediaHover,
20002
- isUploading: false,
20003
- onReplace: handleMediaReplace,
20004
- onVideoSettingsChange: handleVideoSettingsChange
20005
- }
20006
- ),
20007
- carouselHover && !linkPopover && !isItemDragging && /* @__PURE__ */ jsx33(CarouselOverlay, { hover: carouselHover, onEdit: handleEditCarousel }),
20008
- siblingHintRect && !linkPopover && !isItemDragging && siblingHintRects.length === 0 && /* @__PURE__ */ jsx33(ItemInteractionLayer, { rect: siblingHintRect, state: "sibling-hint" }),
20009
- siblingHintRects.length > 0 && !linkPopover && !isItemDragging && siblingHintRects.map((rect, i) => /* @__PURE__ */ jsx33(ItemInteractionLayer, { rect, state: "sibling-hint" }, `sibling-hint-${i}`)),
20010
- isItemDragging && draggedItemRect && selectedElRef.current !== footerDragRef.current?.draggedEl && selectedElRef.current !== navDragRef.current?.draggedEl && /* @__PURE__ */ jsx33(ItemInteractionLayer, { rect: draggedItemRect, state: "dragging" }),
20011
- isItemDragging && footerDropSlots.map((slot, i) => /* @__PURE__ */ jsx33(
20012
- "div",
20013
- {
20014
- className: "pointer-events-none fixed z-2147483646",
20015
- style: {
20016
- left: slot.left,
20017
- top: slot.top,
20018
- width: slot.width,
20019
- height: slot.height
21504
+ return /* @__PURE__ */ jsxs20(Fragment8, { children: [
21505
+ /* @__PURE__ */ jsx33("div", { id: "ohw-loader", suppressHydrationWarning: true, style: { ...OHW_LOADER_STYLE, display: "none" }, children: /* @__PURE__ */ jsx33(OhwLoaderSpinner, {}) }),
21506
+ /* @__PURE__ */ jsx33("script", { suppressHydrationWarning: true, dangerouslySetInnerHTML: { __html: OHW_LOADER_PREHYDRATE_SCRIPT } }),
21507
+ bridgeRoot ? createPortal2(
21508
+ /* @__PURE__ */ jsxs20(Fragment8, { children: [
21509
+ /* @__PURE__ */ jsx33("div", { ref: attachVisibleViewport, "data-ohw-visible-viewport": "", "aria-hidden": true }),
21510
+ isEditMode && /* @__PURE__ */ jsx33(AiSectionOverlay, { postToParent: postToParent2, apiRef: aiSectionApiRef }),
21511
+ isSectionDragging && sectionDropSlots.map((slot, i) => /* @__PURE__ */ jsx33(
21512
+ "div",
21513
+ {
21514
+ className: "pointer-events-none fixed z-2147483646",
21515
+ style: { left: slot.left, top: slot.y, width: slot.width, height: 3, transform: "translateY(-50%)" },
21516
+ children: /* @__PURE__ */ jsx33(
21517
+ DropIndicator,
21518
+ {
21519
+ direction: "horizontal",
21520
+ state: activeSectionDropIndex === i ? "dragActive" : "dragIdle",
21521
+ className: "!h-full !w-full"
21522
+ }
21523
+ )
20020
21524
  },
20021
- children: /* @__PURE__ */ jsx33(
20022
- DropIndicator,
20023
- {
20024
- direction: slot.direction,
20025
- state: activeFooterDropIndex === i ? "dragActive" : "dragIdle",
20026
- className: "!h-full !w-full"
20027
- }
20028
- )
20029
- },
20030
- `footer-drop-${slot.direction}-${slot.columnIndex}-${slot.insertIndex}-${i}`
20031
- )),
20032
- isItemDragging && navDropSlots.map((slot, i) => /* @__PURE__ */ jsx33(
20033
- "div",
20034
- {
20035
- className: "pointer-events-none fixed z-2147483646",
20036
- style: {
20037
- left: slot.left,
20038
- top: slot.top,
20039
- width: slot.width,
20040
- height: slot.height
21525
+ `section-drop-${slot.insertIndex}-${i}`
21526
+ )),
21527
+ Object.entries(uploadingRects).map(([key, { rect, fadingOut }]) => /* @__PURE__ */ jsx33(
21528
+ MediaOverlay,
21529
+ {
21530
+ hover: { key, rect, elementType: "image", isDragOver: false, hasTextOverlap: false },
21531
+ isUploading: true,
21532
+ fadingOut,
21533
+ onFadeOutComplete: handleMediaFadeOutComplete,
21534
+ onReplace: handleMediaReplace
20041
21535
  },
20042
- children: /* @__PURE__ */ jsx33(
20043
- DropIndicator,
20044
- {
20045
- direction: slot.direction,
20046
- state: activeNavDropIndex === i ? "dragActive" : "dragIdle",
20047
- className: "!h-full !w-full"
20048
- }
20049
- )
20050
- },
20051
- `nav-drop-${slot.direction}-${slot.parentId ?? "root"}-${slot.insertIndex}-${i}`
20052
- )),
20053
- hoveredNavContainerRect && (toolbarVariant !== "select-frame" || isFooterFrameSelection) && !linkPopover && !isItemDragging && /* @__PURE__ */ jsx33(ItemInteractionLayer, { rect: hoveredNavContainerRect, state: "hover" }),
20054
- hoveredItemRect && !linkPopover && !hoveredNavContainerRect && !isItemDragging && hoveredItemElRef.current !== selectedElRef.current && /* @__PURE__ */ jsx33(ItemInteractionLayer, { rect: hoveredItemRect, state: "hover" }),
20055
- hoveredTextRect && !hoveredNavContainerRect && !hoveredItemRect && !isItemDragging && toolbarVariant !== "rich-text" && /* @__PURE__ */ jsx33(ItemInteractionLayer, { rect: hoveredTextRect, state: "hover" }),
20056
- formPickRect && !isItemDragging && /* @__PURE__ */ jsx33(
20057
- ItemInteractionLayer,
20058
- {
20059
- rect: formPickRect,
20060
- state: "active-top",
20061
- itemDragSurface: false,
20062
- toolbarAlign: "left",
20063
- chromeGap: 24,
20064
- toolbar: fieldPickRect ? void 0 : /* @__PURE__ */ jsxs20(
21536
+ `uploading-${key}`
21537
+ )),
21538
+ mediaHover && !(mediaHover.key in uploadingRects) && mediaHover.key !== selectedMedia?.key && /* @__PURE__ */ jsx33(
21539
+ MediaOverlay,
21540
+ {
21541
+ hover: mediaHover,
21542
+ isUploading: false,
21543
+ onReplace: handleMediaReplace,
21544
+ onSelect: handleMediaSelect,
21545
+ onVideoSettingsChange: handleVideoSettingsChange
21546
+ }
21547
+ ),
21548
+ selectedMedia && !(selectedMedia.key in uploadingRects) && !linkPopover && /* @__PURE__ */ jsx33(
21549
+ MediaOverlay,
21550
+ {
21551
+ hover: selectedMedia,
21552
+ selected: true,
21553
+ hovered: mediaHover?.key === selectedMedia.key,
21554
+ isUploading: false,
21555
+ onReplace: handleMediaReplace,
21556
+ onSelect: handleMediaSelect,
21557
+ onVideoSettingsChange: handleVideoSettingsChange
21558
+ }
21559
+ ),
21560
+ carouselHover && !linkPopover && !isItemDragging && /* @__PURE__ */ jsx33(CarouselOverlay, { hover: carouselHover, onEdit: handleEditCarousel }),
21561
+ siblingHintRect && !linkPopover && !isItemDragging && siblingHintRects.length === 0 && /* @__PURE__ */ jsx33(ItemInteractionLayer, { rect: siblingHintRect, state: "sibling-hint" }),
21562
+ siblingHintRects.length > 0 && !linkPopover && !isItemDragging && siblingHintRects.map((rect, i) => /* @__PURE__ */ jsx33(ItemInteractionLayer, { rect, state: "sibling-hint" }, `sibling-hint-${i}`)),
21563
+ isItemDragging && draggedItemRect && selectedElRef.current !== footerDragRef.current?.draggedEl && selectedElRef.current !== navDragRef.current?.draggedEl && /* @__PURE__ */ jsx33(ItemInteractionLayer, { rect: draggedItemRect, state: "dragging" }),
21564
+ isItemDragging && footerDropSlots.map((slot, i) => /* @__PURE__ */ jsx33(
21565
+ "div",
21566
+ {
21567
+ className: "pointer-events-none fixed z-2147483646",
21568
+ style: {
21569
+ left: slot.left,
21570
+ top: slot.top,
21571
+ width: slot.width,
21572
+ height: slot.height
21573
+ },
21574
+ children: /* @__PURE__ */ jsx33(
21575
+ DropIndicator,
21576
+ {
21577
+ direction: slot.direction,
21578
+ state: activeFooterDropIndex === i ? "dragActive" : "dragIdle",
21579
+ className: "!h-full !w-full"
21580
+ }
21581
+ )
21582
+ },
21583
+ `footer-drop-${slot.direction}-${slot.columnIndex}-${slot.insertIndex}-${i}`
21584
+ )),
21585
+ isItemDragging && navDropSlots.map((slot, i) => /* @__PURE__ */ jsx33(
21586
+ "div",
21587
+ {
21588
+ className: "pointer-events-none fixed z-2147483646",
21589
+ style: {
21590
+ left: slot.left,
21591
+ top: slot.top,
21592
+ width: slot.width,
21593
+ height: slot.height
21594
+ },
21595
+ children: /* @__PURE__ */ jsx33(
21596
+ DropIndicator,
21597
+ {
21598
+ direction: slot.direction,
21599
+ state: activeNavDropIndex === i ? "dragActive" : "dragIdle",
21600
+ className: "!h-full !w-full"
21601
+ }
21602
+ )
21603
+ },
21604
+ `nav-drop-${slot.direction}-${slot.parentId ?? "root"}-${slot.insertIndex}-${i}`
21605
+ )),
21606
+ hoveredNavContainerRect && (toolbarVariant !== "select-frame" || isFooterFrameSelection) && !linkPopover && !isItemDragging && /* @__PURE__ */ jsx33(ItemInteractionLayer, { rect: hoveredNavContainerRect, state: "hover" }),
21607
+ hoveredItemRect && !linkPopover && !hoveredNavContainerRect && !isItemDragging && hoveredItemElRef.current !== selectedElRef.current && /* @__PURE__ */ jsx33(ItemInteractionLayer, { rect: hoveredItemRect, state: "hover" }),
21608
+ hoveredTextRect && !hoveredNavContainerRect && !hoveredItemRect && !isItemDragging && toolbarVariant !== "rich-text" && /* @__PURE__ */ jsx33(ItemInteractionLayer, { rect: hoveredTextRect, state: "hover" }),
21609
+ formPickRect && !isItemDragging && /* @__PURE__ */ jsx33(
21610
+ ItemInteractionLayer,
21611
+ {
21612
+ rect: formPickRect,
21613
+ state: "active-top",
21614
+ itemDragSurface: false,
21615
+ toolbarAlign: "left",
21616
+ chromeGap: 24,
21617
+ toolbar: fieldPickRect ? void 0 : /* @__PURE__ */ jsxs20(
21618
+ "div",
21619
+ {
21620
+ "data-ohw-form-toolbar": "",
21621
+ className: "pointer-events-auto flex items-center gap-0.5 whitespace-nowrap rounded-lg border border-border bg-background p-1 shadow-md",
21622
+ children: [
21623
+ /* @__PURE__ */ jsx33(
21624
+ "button",
21625
+ {
21626
+ type: "button",
21627
+ "aria-label": "Add field",
21628
+ className: "flex h-7 w-7 items-center justify-center rounded-md text-foreground transition-colors hover:bg-muted/80",
21629
+ onClick: () => setFieldTypePickerOpen((open) => !open),
21630
+ "data-ohw-add-field": "",
21631
+ children: /* @__PURE__ */ jsx33(Plus4, { size: 15, "aria-hidden": true })
21632
+ }
21633
+ ),
21634
+ /* @__PURE__ */ jsx33("div", { className: "mx-0.5 h-5 w-px bg-border" }),
21635
+ /* @__PURE__ */ jsxs20(
21636
+ "button",
21637
+ {
21638
+ type: "button",
21639
+ 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",
21640
+ onClick: () => {
21641
+ setFieldTypePickerOpen(false);
21642
+ const form = formPickElRef.current;
21643
+ if (!form) return;
21644
+ postToParent2({
21645
+ type: "ow:form-pick",
21646
+ formKey: formKeyOf(form),
21647
+ hasLongText: formHasLongText(form)
21648
+ });
21649
+ },
21650
+ children: [
21651
+ /* @__PURE__ */ jsx33(Settings, { size: 14, "aria-hidden": true }),
21652
+ "Form settings",
21653
+ formPickCount ? (
21654
+ // Counter pill, per the design — not a text suffix.
21655
+ /* @__PURE__ */ jsx33(
21656
+ "span",
21657
+ {
21658
+ "data-ohw-form-count": "",
21659
+ 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",
21660
+ children: formPickCount
21661
+ }
21662
+ )
21663
+ ) : null
21664
+ ]
21665
+ }
21666
+ ),
21667
+ /* @__PURE__ */ jsx33("div", { className: "mx-0.5 h-5 w-px bg-border" }),
21668
+ /* @__PURE__ */ jsx33("div", { className: "flex items-center rounded-md bg-muted/70 p-0.5", children: ["default", "success"].map((state) => /* @__PURE__ */ jsx33(
21669
+ "button",
21670
+ {
21671
+ type: "button",
21672
+ "aria-pressed": formViewState === state,
21673
+ 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"),
21674
+ onClick: () => {
21675
+ setFieldTypePickerOpen(false);
21676
+ const form = formPickElRef.current;
21677
+ const key = form ? formKeyOf(form) : null;
21678
+ if (!form || !key) return;
21679
+ const initial = successInitialFor(form, key, editContentRef.current);
21680
+ setFormViewState(form, key, state, initial);
21681
+ setFormViewStateUi(state);
21682
+ setFormPickRect(form.getBoundingClientRect());
21683
+ if (state === "success") {
21684
+ const successEl = form.querySelector(`[${SUCCESS_TEXT_ATTR}]`);
21685
+ if (successEl) requestAnimationFrame(() => activateRef.current(successEl));
21686
+ } else {
21687
+ deactivateRef.current();
21688
+ }
21689
+ },
21690
+ children: state
21691
+ },
21692
+ state
21693
+ )) })
21694
+ ]
21695
+ }
21696
+ )
21697
+ }
21698
+ ),
21699
+ formHoverRect && !isItemDragging && /* @__PURE__ */ jsx33(
21700
+ ItemInteractionLayer,
21701
+ {
21702
+ rect: formHoverRect,
21703
+ state: "hover",
21704
+ chromeGap: formHoverElRef.current && getFieldWrapper(formHoverElRef.current) ? 8 : 24
21705
+ }
21706
+ ),
21707
+ fieldPickRect && fieldPickState && !isItemDragging && toolbarVariant !== "rich-text" && /* @__PURE__ */ jsx33(
21708
+ ItemInteractionLayer,
21709
+ {
21710
+ rect: fieldPickRect,
21711
+ state: fieldDragging ? "dragging" : "active-top",
21712
+ itemDragSurface: false,
21713
+ toolbarAlign: "left",
21714
+ chromeGap: 10,
21715
+ showHandle: true,
21716
+ dragHandleLabel: "Reorder field",
21717
+ onDragHandleDragStart: handleFieldDragStart,
21718
+ onDragHandleDragEnd: handleFieldDragEnd,
21719
+ toolbar: /* @__PURE__ */ jsx33(
21720
+ FormFieldToolbar,
21721
+ {
21722
+ type: fieldPickState.type,
21723
+ required: fieldPickState.required,
21724
+ onTypeChange: handleFieldTypeChange,
21725
+ onRequiredToggle: handleFieldRequiredToggle,
21726
+ onDuplicate: handleFieldDuplicate,
21727
+ onDelete: handleFieldDelete
21728
+ }
21729
+ )
21730
+ }
21731
+ ),
21732
+ fieldDragging ? fieldDropSlots.map((slot, i) => /* @__PURE__ */ jsx33(
21733
+ "div",
21734
+ {
21735
+ className: "pointer-events-none fixed z-[2147483644]",
21736
+ style: { top: slot.top, left: slot.left, width: slot.width },
21737
+ children: /* @__PURE__ */ jsx33(
21738
+ DropIndicator,
21739
+ {
21740
+ direction: "horizontal",
21741
+ state: fieldDropIndex === i ? "dragActive" : "dragIdle",
21742
+ className: "!w-full"
21743
+ }
21744
+ )
21745
+ },
21746
+ `field-drop-${i}`
21747
+ )) : null,
21748
+ fieldTypePickerOpen && formPickRect ? (() => {
21749
+ const toolbar = document.querySelector("[data-ohw-form-toolbar]")?.getBoundingClientRect();
21750
+ return /* @__PURE__ */ jsx33(
20065
21751
  "div",
20066
21752
  {
20067
- "data-ohw-form-toolbar": "",
20068
- className: "pointer-events-auto flex items-center gap-0.5 whitespace-nowrap rounded-lg border border-border bg-background p-1 shadow-md",
20069
- children: [
20070
- /* @__PURE__ */ jsx33(
20071
- "button",
20072
- {
20073
- type: "button",
20074
- "aria-label": "Add field",
20075
- className: "flex h-7 w-7 items-center justify-center rounded-md text-foreground transition-colors hover:bg-muted/80",
20076
- onClick: () => setFieldTypePickerOpen((open) => !open),
20077
- "data-ohw-add-field": "",
20078
- children: /* @__PURE__ */ jsx33(Plus4, { size: 15, "aria-hidden": true })
20079
- }
20080
- ),
20081
- /* @__PURE__ */ jsx33("div", { className: "mx-0.5 h-5 w-px bg-border" }),
20082
- /* @__PURE__ */ jsxs20(
20083
- "button",
20084
- {
20085
- type: "button",
20086
- 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",
20087
- onClick: () => {
20088
- setFieldTypePickerOpen(false);
20089
- const form = formPickElRef.current;
20090
- if (!form) return;
20091
- postToParent2({
20092
- type: "ow:form-pick",
20093
- formKey: formKeyOf(form),
20094
- hasLongText: formHasLongText(form)
20095
- });
20096
- },
20097
- children: [
20098
- /* @__PURE__ */ jsx33(Settings, { size: 14, "aria-hidden": true }),
20099
- "Form settings",
20100
- formPickCount ? (
20101
- // Counter pill, per the design — not a text suffix.
20102
- /* @__PURE__ */ jsx33(
20103
- "span",
20104
- {
20105
- "data-ohw-form-count": "",
20106
- 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",
20107
- children: formPickCount
20108
- }
20109
- )
20110
- ) : null
20111
- ]
20112
- }
20113
- ),
20114
- /* @__PURE__ */ jsx33("div", { className: "mx-0.5 h-5 w-px bg-border" }),
20115
- /* @__PURE__ */ jsx33("div", { className: "flex items-center rounded-md bg-muted/70 p-0.5", children: ["default", "success"].map((state) => /* @__PURE__ */ jsx33(
20116
- "button",
20117
- {
20118
- type: "button",
20119
- "aria-pressed": formViewState === state,
20120
- 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"),
20121
- onClick: () => {
20122
- setFieldTypePickerOpen(false);
20123
- const form = formPickElRef.current;
20124
- const key = form ? formKeyOf(form) : null;
20125
- if (!form || !key) return;
20126
- const initial = successInitialFor(form, key, editContentRef.current);
20127
- setFormViewState(form, key, state, initial);
20128
- setFormViewStateUi(state);
20129
- setFormPickRect(form.getBoundingClientRect());
20130
- if (state === "success") {
20131
- const successEl = form.querySelector(`[${SUCCESS_TEXT_ATTR}]`);
20132
- if (successEl) requestAnimationFrame(() => activateRef.current(successEl));
20133
- } else {
20134
- deactivateRef.current();
20135
- }
20136
- },
20137
- children: state
20138
- },
20139
- state
20140
- )) })
20141
- ]
21753
+ className: "pointer-events-none fixed z-[2147483645]",
21754
+ style: {
21755
+ top: toolbar ? toolbar.bottom + 6 : formPickRect.top + 16,
21756
+ left: toolbar ? toolbar.left : formPickRect.left + 24
21757
+ },
21758
+ children: /* @__PURE__ */ jsx33(FieldTypePicker, { onPick: handleAddField })
20142
21759
  }
20143
- )
20144
- }
20145
- ),
20146
- formHoverRect && !isItemDragging && /* @__PURE__ */ jsx33(
20147
- ItemInteractionLayer,
20148
- {
20149
- rect: formHoverRect,
20150
- state: "hover",
20151
- chromeGap: formHoverElRef.current && getFieldWrapper(formHoverElRef.current) ? 8 : 24
20152
- }
20153
- ),
20154
- fieldPickRect && fieldPickState && !isItemDragging && toolbarVariant !== "rich-text" && /* @__PURE__ */ jsx33(
20155
- ItemInteractionLayer,
20156
- {
20157
- rect: fieldPickRect,
20158
- state: fieldDragging ? "dragging" : "active-top",
20159
- itemDragSurface: false,
20160
- toolbarAlign: "left",
20161
- chromeGap: 10,
20162
- showHandle: true,
20163
- dragHandleLabel: "Reorder field",
20164
- onDragHandleDragStart: handleFieldDragStart,
20165
- onDragHandleDragEnd: handleFieldDragEnd,
20166
- toolbar: /* @__PURE__ */ jsx33(
20167
- FormFieldToolbar,
21760
+ );
21761
+ })() : null,
21762
+ toolbarVariant === "select-frame" && toolbarRect && !linkPopover && !isItemDragging && !isFooterFrameSelection && selectedElRef.current && isNavbarLinksContainer2(selectedElRef.current) && /* @__PURE__ */ jsx33(NavbarContainerChrome, { rect: toolbarRect, onAdd: handleAddTopLevelNavItem }),
21763
+ toolbarVariant === "select-frame" && toolbarRect && !linkPopover && !isItemDragging && !isFooterFrameSelection && selectedElRef.current && isFooterLinksContainer(selectedElRef.current) && /* @__PURE__ */ jsx33(
21764
+ FooterContainerChrome,
21765
+ {
21766
+ rect: toolbarRect,
21767
+ onAdd: handleAddFooterColumn,
21768
+ addDisabled: !canAddFooterColumn()
21769
+ }
21770
+ ),
21771
+ toolbarRect && !linkPopover && (toolbarVariant === "link-action" || toolbarVariant === "select-frame" || toolbarVariant === "logo") && /* @__PURE__ */ jsx33(
21772
+ ItemInteractionLayer,
21773
+ {
21774
+ rect: isItemDragging && draggedItemRect && (footerDragRef.current?.wasSelected || navDragRef.current?.wasSelected) ? draggedItemRect : toolbarRect,
21775
+ toolbarBelowRect: selectedElRef.current && isNavigationItem2(selectedElRef.current) ? getOpenNavDropdownPanelRect(selectedElRef.current) : null,
21776
+ elRef: glowElRef,
21777
+ state: isItemDragging ? "dragging" : selectedElRef.current && isNavigationItem2(selectedElRef.current) && getOpenNavDropdownPanelRect(selectedElRef.current) ? "active-bottom" : resolveItemInteractionState(toolbarRect, parentScrollRef.current),
21778
+ showHandle: toolbarVariant === "link-action" && Boolean(reorderHrefKey) || toolbarVariant === "select-frame" && isFooterFrameSelection,
21779
+ dragDisabled: reorderDragDisabled,
21780
+ dragHandleLabel: reorderHrefKey ? `Reorder ${reorderHrefKey}` : "Reorder item",
21781
+ onDragHandleDragStart: handleItemDragStart,
21782
+ onDragHandleDragEnd: handleItemDragEnd,
21783
+ onItemPointerDown: toolbarVariant === "logo" ? void 0 : handleItemChromePointerDown,
21784
+ onItemClick: toolbarVariant === "logo" ? void 0 : handleItemChromeClick,
21785
+ itemDragSurface: toolbarVariant !== "logo" && !isFooterFrameSelection && !selectedIsSocialsRow,
21786
+ toolbar: toolbarVariant === "link-action" && !isItemDragging || toolbarVariant === "select-frame" && (isFooterFrameSelection || selectedIsSocialsRow) && !isItemDragging ? /* @__PURE__ */ jsx33(
21787
+ ItemActionToolbar,
21788
+ {
21789
+ onEditLink: openLinkPopoverForSelected,
21790
+ onStyle: () => {
21791
+ const row = selectedElRef.current;
21792
+ if (!row) return;
21793
+ if (floatingPanel?.kind === "socials-display") closeFloatingPanelOnly();
21794
+ else openSocialsDisplayPanel(row);
21795
+ },
21796
+ showStyle: selectedIsSocialsRow,
21797
+ styleActive: floatingPanel?.kind === "socials-display",
21798
+ onAddItem: handleAddChildItem,
21799
+ onSelectParent: handleSelectParent,
21800
+ onDuplicate: handleDuplicateSelected,
21801
+ onDelete: handleDeleteSelected,
21802
+ addItemDisabled: isFooterFrameSelection && isFooterAddItemDisabled(selectedElRef.current) || selectedElRef.current !== null && (() => {
21803
+ const row = isSocialsRow(selectedElRef.current) ? selectedElRef.current : findSocialsRow(selectedElRef.current);
21804
+ return row ? !canAddSocialItem(row) : false;
21805
+ })(),
21806
+ editLinkDisabled: false,
21807
+ moreDisabled: false,
21808
+ deleteDisabled: selectedElRef.current !== null && (() => {
21809
+ const social = getSocialItem(selectedElRef.current);
21810
+ return social ? !canRemoveSocialItem(social) : false;
21811
+ })(),
21812
+ duplicateDisabled: isFooterFrameSelection || selectedIsSocialsRow || selectedElRef.current !== null && (() => {
21813
+ const social = getSocialItem(selectedElRef.current);
21814
+ const row = social ? findSocialsRow(social) : null;
21815
+ return row ? !canAddSocialItem(row) : false;
21816
+ })(),
21817
+ showEditLink: !isFooterFrameSelection && !selectedIsSocialsRow && navDropdownPreviewOpen === null,
21818
+ showAddItem: isFooterFrameSelection || selectedIsSocialsRow || !selectedIsCta && Boolean(
21819
+ selectedElRef.current && isNavbarHrefKey(selectedElRef.current.getAttribute("data-ohw-href-key")) && !isNestedNavChild(selectedElRef.current)
21820
+ ),
21821
+ showMore: (!selectedIsCta || isFooterFrameSelection) && !selectedIsSocialsRow,
21822
+ dropdownOpen: navDropdownPreviewOpen,
21823
+ onDropdownOpenChange: handleNavDropdownOpenChange,
21824
+ headingVisible: footerHeadingVisible,
21825
+ onHeadingVisibleChange: handleFooterHeadingVisibleChange
21826
+ }
21827
+ ) : void 0
21828
+ }
21829
+ ),
21830
+ toolbarRect && toolbarVariant === "rich-text" && !linkPopover && /* @__PURE__ */ jsxs20(Fragment8, { children: [
21831
+ /* @__PURE__ */ jsx33(
21832
+ EditGlowChrome,
20168
21833
  {
20169
- type: fieldPickState.type,
20170
- required: fieldPickState.required,
20171
- onTypeChange: handleFieldTypeChange,
20172
- onRequiredToggle: handleFieldRequiredToggle,
20173
- onDuplicate: handleFieldDuplicate,
20174
- onDelete: handleFieldDelete
21834
+ rect: toolbarRect,
21835
+ elRef: glowElRef,
21836
+ reorderHrefKey,
21837
+ dragDisabled: reorderDragDisabled,
21838
+ hideHandle: isItemDragging
20175
21839
  }
20176
- )
20177
- }
20178
- ),
20179
- fieldDragging ? fieldDropSlots.map((slot, i) => /* @__PURE__ */ jsx33(
20180
- "div",
20181
- {
20182
- className: "pointer-events-none fixed z-[2147483644]",
20183
- style: { top: slot.top, left: slot.left, width: slot.width },
20184
- children: /* @__PURE__ */ jsx33(
20185
- DropIndicator,
21840
+ ),
21841
+ /* @__PURE__ */ jsx33(
21842
+ FloatingToolbar,
20186
21843
  {
20187
- direction: "horizontal",
20188
- state: fieldDropIndex === i ? "dragActive" : "dragIdle",
20189
- className: "!w-full"
21844
+ rect: toolbarRect,
21845
+ parentScroll: parentScrollRef.current,
21846
+ elRef: toolbarElRef,
21847
+ onCommand: handleCommand,
21848
+ activeCommands,
21849
+ showEditLink,
21850
+ onEditLink: openLinkPopoverForActive
20190
21851
  }
20191
21852
  )
20192
- },
20193
- `field-drop-${i}`
20194
- )) : null,
20195
- fieldTypePickerOpen && formPickRect ? (() => {
20196
- const toolbar = document.querySelector("[data-ohw-form-toolbar]")?.getBoundingClientRect();
20197
- return /* @__PURE__ */ jsx33(
21853
+ ] }),
21854
+ maxBadge && /* @__PURE__ */ jsxs20(
20198
21855
  "div",
20199
21856
  {
20200
- className: "pointer-events-none fixed z-[2147483645]",
21857
+ "data-ohw-max-badge": "",
20201
21858
  style: {
20202
- top: toolbar ? toolbar.bottom + 6 : formPickRect.top + 16,
20203
- left: toolbar ? toolbar.left : formPickRect.left + 24
21859
+ position: "fixed",
21860
+ top: maxBadge.rect.bottom + 4,
21861
+ left: maxBadge.rect.right,
21862
+ transform: "translateX(-100%)",
21863
+ zIndex: 2147483647,
21864
+ background: maxBadge.current > maxBadge.max ? "#FEF2F2" : "#F5F5F4",
21865
+ color: maxBadge.current > maxBadge.max ? "#DC2626" : "#78716C",
21866
+ border: `1px solid ${maxBadge.current > maxBadge.max ? "#FECACA" : "#E7E5E4"}`,
21867
+ borderRadius: 4,
21868
+ padding: "2px 6px",
21869
+ fontSize: 11,
21870
+ fontWeight: 500,
21871
+ pointerEvents: "none"
20204
21872
  },
20205
- children: /* @__PURE__ */ jsx33(FieldTypePicker, { onPick: handleAddField })
21873
+ children: [
21874
+ maxBadge.current,
21875
+ "/",
21876
+ maxBadge.max
21877
+ ]
20206
21878
  }
20207
- );
20208
- })() : null,
20209
- toolbarVariant === "select-frame" && toolbarRect && !linkPopover && !isItemDragging && !isFooterFrameSelection && selectedElRef.current && isNavbarLinksContainer2(selectedElRef.current) && /* @__PURE__ */ jsx33(NavbarContainerChrome, { rect: toolbarRect, onAdd: handleAddTopLevelNavItem }),
20210
- toolbarVariant === "select-frame" && toolbarRect && !linkPopover && !isItemDragging && !isFooterFrameSelection && selectedElRef.current && isFooterLinksContainer(selectedElRef.current) && /* @__PURE__ */ jsx33(
20211
- FooterContainerChrome,
20212
- {
20213
- rect: toolbarRect,
20214
- onAdd: handleAddFooterColumn,
20215
- addDisabled: !canAddFooterColumn()
20216
- }
20217
- ),
20218
- toolbarRect && !linkPopover && (toolbarVariant === "link-action" || toolbarVariant === "select-frame" || toolbarVariant === "logo") && /* @__PURE__ */ jsx33(
20219
- ItemInteractionLayer,
20220
- {
20221
- rect: isItemDragging && draggedItemRect && (footerDragRef.current?.wasSelected || navDragRef.current?.wasSelected) ? draggedItemRect : toolbarRect,
20222
- toolbarBelowRect: selectedElRef.current && isNavigationItem2(selectedElRef.current) ? getOpenNavDropdownPanelRect(selectedElRef.current) : null,
20223
- elRef: glowElRef,
20224
- state: isItemDragging ? "dragging" : selectedElRef.current && isNavigationItem2(selectedElRef.current) && getOpenNavDropdownPanelRect(selectedElRef.current) ? "active-bottom" : resolveItemInteractionState(toolbarRect, parentScrollRef.current),
20225
- showHandle: toolbarVariant === "link-action" && Boolean(reorderHrefKey) || toolbarVariant === "select-frame" && isFooterFrameSelection,
20226
- dragDisabled: reorderDragDisabled,
20227
- dragHandleLabel: reorderHrefKey ? `Reorder ${reorderHrefKey}` : "Reorder item",
20228
- onDragHandleDragStart: handleItemDragStart,
20229
- onDragHandleDragEnd: handleItemDragEnd,
20230
- onItemPointerDown: toolbarVariant === "logo" ? void 0 : handleItemChromePointerDown,
20231
- onItemClick: toolbarVariant === "logo" ? void 0 : handleItemChromeClick,
20232
- itemDragSurface: toolbarVariant !== "logo" && !isFooterFrameSelection && !selectedIsSocialsRow,
20233
- toolbar: toolbarVariant === "link-action" && !isItemDragging || toolbarVariant === "select-frame" && (isFooterFrameSelection || selectedIsSocialsRow) && !isItemDragging ? /* @__PURE__ */ jsx33(
20234
- ItemActionToolbar,
20235
- {
20236
- onEditLink: openLinkPopoverForSelected,
20237
- onStyle: () => {
20238
- const row = selectedElRef.current;
20239
- if (!row) return;
20240
- if (floatingPanel?.kind === "socials-display") closeFloatingPanelOnly();
20241
- else openSocialsDisplayPanel(row);
20242
- },
20243
- showStyle: selectedIsSocialsRow,
20244
- styleActive: floatingPanel?.kind === "socials-display",
20245
- onAddItem: handleAddChildItem,
20246
- onSelectParent: handleSelectParent,
20247
- onDuplicate: handleDuplicateSelected,
20248
- onDelete: handleDeleteSelected,
20249
- addItemDisabled: isFooterFrameSelection && isFooterAddItemDisabled(selectedElRef.current) || selectedElRef.current !== null && (() => {
20250
- const row = isSocialsRow(selectedElRef.current) ? selectedElRef.current : findSocialsRow(selectedElRef.current);
20251
- return row ? !canAddSocialItem(row) : false;
20252
- })(),
20253
- editLinkDisabled: false,
20254
- moreDisabled: false,
20255
- deleteDisabled: selectedElRef.current !== null && (() => {
20256
- const social = getSocialItem(selectedElRef.current);
20257
- return social ? !canRemoveSocialItem(social) : false;
20258
- })(),
20259
- duplicateDisabled: isFooterFrameSelection || selectedIsSocialsRow || selectedElRef.current !== null && (() => {
20260
- const social = getSocialItem(selectedElRef.current);
20261
- const row = social ? findSocialsRow(social) : null;
20262
- return row ? !canAddSocialItem(row) : false;
20263
- })(),
20264
- showEditLink: !isFooterFrameSelection && !selectedIsSocialsRow && navDropdownPreviewOpen === null,
20265
- showAddItem: isFooterFrameSelection || selectedIsSocialsRow || !selectedIsCta && Boolean(
20266
- selectedElRef.current && isNavbarHrefKey(selectedElRef.current.getAttribute("data-ohw-href-key")) && !isNestedNavChild(selectedElRef.current)
20267
- ),
20268
- showMore: (!selectedIsCta || isFooterFrameSelection) && !selectedIsSocialsRow,
20269
- dropdownOpen: navDropdownPreviewOpen,
20270
- onDropdownOpenChange: handleNavDropdownOpenChange,
20271
- headingVisible: footerHeadingVisible,
20272
- onHeadingVisibleChange: handleFooterHeadingVisibleChange
20273
- }
20274
- ) : void 0
20275
- }
20276
- ),
20277
- toolbarRect && toolbarVariant === "rich-text" && !linkPopover && /* @__PURE__ */ jsxs20(Fragment8, { children: [
20278
- /* @__PURE__ */ jsx33(
20279
- EditGlowChrome,
21879
+ ),
21880
+ toggleState && !linkPopover && /* @__PURE__ */ jsx33(
21881
+ StateToggle,
20280
21882
  {
20281
- rect: toolbarRect,
20282
- elRef: glowElRef,
20283
- reorderHrefKey,
20284
- dragDisabled: reorderDragDisabled,
20285
- hideHandle: isItemDragging
21883
+ rect: toggleState.rect,
21884
+ activeState: toggleState.activeState,
21885
+ states: toggleState.states,
21886
+ onStateChange: handleStateChange
20286
21887
  }
20287
21888
  ),
20288
- /* @__PURE__ */ jsx33(
20289
- FloatingToolbar,
21889
+ sectionGap && !linkPopover && /* @__PURE__ */ jsxs20(
21890
+ "div",
20290
21891
  {
20291
- rect: toolbarRect,
20292
- parentScroll: parentScrollRef.current,
20293
- elRef: toolbarElRef,
20294
- onCommand: handleCommand,
20295
- activeCommands,
20296
- showEditLink,
20297
- onEditLink: openLinkPopoverForActive
21892
+ "data-ohw-section-insert-line": "",
21893
+ className: "fixed left-0 w-full z-2147483646 flex items-center pointer-events-none",
21894
+ style: { top: sectionGap.y, transform: "translateY(-50%)" },
21895
+ children: [
21896
+ /* @__PURE__ */ jsx33("div", { className: "flex-1 bg-primary", style: { height: 3 } }),
21897
+ /* @__PURE__ */ jsx33(
21898
+ Badge,
21899
+ {
21900
+ 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",
21901
+ onClick: () => {
21902
+ window.parent.postMessage(
21903
+ {
21904
+ type: "ow:add-section",
21905
+ insertAfter: sectionGap.insertAfter,
21906
+ insertBefore: sectionGap.insertBefore
21907
+ },
21908
+ "*"
21909
+ );
21910
+ },
21911
+ children: "Add Section"
21912
+ }
21913
+ ),
21914
+ /* @__PURE__ */ jsx33("div", { className: "flex-1 bg-primary", style: { height: 3 } })
21915
+ ]
20298
21916
  }
20299
- )
20300
- ] }),
20301
- maxBadge && /* @__PURE__ */ jsxs20(
20302
- "div",
20303
- {
20304
- "data-ohw-max-badge": "",
20305
- style: {
20306
- position: "fixed",
20307
- top: maxBadge.rect.bottom + 4,
20308
- left: maxBadge.rect.right,
20309
- transform: "translateX(-100%)",
20310
- zIndex: 2147483647,
20311
- background: maxBadge.current > maxBadge.max ? "#FEF2F2" : "#F5F5F4",
20312
- color: maxBadge.current > maxBadge.max ? "#DC2626" : "#78716C",
20313
- border: `1px solid ${maxBadge.current > maxBadge.max ? "#FECACA" : "#E7E5E4"}`,
20314
- borderRadius: 4,
20315
- padding: "2px 6px",
20316
- fontSize: 11,
20317
- fontWeight: 500,
20318
- pointerEvents: "none"
21917
+ ),
21918
+ linkPopover && dialogPortalContainer ? /* @__PURE__ */ jsx33(
21919
+ LinkPopover,
21920
+ {
21921
+ panelRef: linkPopoverPanelRef,
21922
+ portalContainer: dialogPortalContainer,
21923
+ open: true,
21924
+ mode: linkPopover.mode ?? "edit",
21925
+ pages: sitePages,
21926
+ sections: currentSections,
21927
+ sectionsByPath,
21928
+ initialTarget: linkPopover.target,
21929
+ existingTargets: linkPopover.intent === "add-nav" ? getNavbarExistingTargets() : [],
21930
+ onClose: closeLinkPopover,
21931
+ onSubmit: handleLinkPopoverSubmit
20319
21932
  },
20320
- children: [
20321
- maxBadge.current,
20322
- "/",
20323
- maxBadge.max
20324
- ]
20325
- }
20326
- ),
20327
- toggleState && !linkPopover && /* @__PURE__ */ jsx33(
20328
- StateToggle,
20329
- {
20330
- rect: toggleState.rect,
20331
- activeState: toggleState.activeState,
20332
- states: toggleState.states,
20333
- onStateChange: handleStateChange
20334
- }
20335
- ),
20336
- sectionGap && !linkPopover && /* @__PURE__ */ jsxs20(
20337
- "div",
20338
- {
20339
- "data-ohw-section-insert-line": "",
20340
- className: "fixed left-0 w-full z-2147483646 flex items-center pointer-events-none",
20341
- style: { top: sectionGap.y, transform: "translateY(-50%)" },
20342
- children: [
20343
- /* @__PURE__ */ jsx33("div", { className: "flex-1 bg-primary", style: { height: 3 } }),
20344
- /* @__PURE__ */ jsx33(
20345
- Badge,
21933
+ linkPopover.key
21934
+ ) : null,
21935
+ floatingPanel && floatingPanel.kind === "socials-display" ? /* @__PURE__ */ jsx33(
21936
+ FloatingPanel,
21937
+ {
21938
+ open: true,
21939
+ title: floatingPanel.title,
21940
+ context: floatingPanel.context,
21941
+ position: floatingPanelPos,
21942
+ onPositionChange: setFloatingPanelPos,
21943
+ parentScroll: parentScrollSnap ?? parentScrollRef.current,
21944
+ onClose: closeFloatingPanelOnly,
21945
+ children: /* @__PURE__ */ jsx33(
21946
+ SocialsDisplayPanel,
20346
21947
  {
20347
- 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",
20348
- onClick: () => {
20349
- window.parent.postMessage(
20350
- {
20351
- type: "ow:add-section",
20352
- insertAfter: sectionGap.insertAfter,
20353
- insertBefore: sectionGap.insertBefore
20354
- },
20355
- "*"
20356
- );
20357
- },
20358
- children: "Add Section"
21948
+ display: socialsDisplayFor(floatingPanel.row, editContentRef.current),
21949
+ onChange: (next) => {
21950
+ changeSocialsDisplay(floatingPanel.row, next);
21951
+ setFloatingPanel({ ...floatingPanel });
21952
+ }
20359
21953
  }
20360
- ),
20361
- /* @__PURE__ */ jsx33("div", { className: "flex-1 bg-primary", style: { height: 3 } })
20362
- ]
20363
- }
20364
- ),
20365
- linkPopover && dialogPortalContainer ? /* @__PURE__ */ jsx33(
20366
- LinkPopover,
20367
- {
20368
- panelRef: linkPopoverPanelRef,
20369
- portalContainer: dialogPortalContainer,
20370
- open: true,
20371
- mode: linkPopover.mode ?? "edit",
20372
- pages: sitePages,
20373
- sections: currentSections,
20374
- sectionsByPath,
20375
- initialTarget: linkPopover.target,
20376
- existingTargets: linkPopover.intent === "add-nav" ? getNavbarExistingTargets() : [],
20377
- onClose: closeLinkPopover,
20378
- onSubmit: handleLinkPopoverSubmit
21954
+ )
21955
+ }
21956
+ ) : null
21957
+ ] }),
21958
+ bridgeRoot
21959
+ ) : null
21960
+ ] });
21961
+ }
21962
+
21963
+ // src/ui/EmptySection.tsx
21964
+ import Link3 from "next/link";
21965
+ import { Fragment as Fragment9, jsx as jsx34, jsxs as jsxs21 } from "react/jsx-runtime";
21966
+ function EmptySection({ title, homeHref = "/", eyebrowKey, titleKey, subtitleKey }) {
21967
+ return /* @__PURE__ */ jsxs21(Fragment9, { children: [
21968
+ /* @__PURE__ */ jsx34(
21969
+ "p",
21970
+ {
21971
+ style: {
21972
+ fontFamily: "var(--brand-font-body)",
21973
+ fontSize: "0.75rem",
21974
+ fontWeight: 500,
21975
+ letterSpacing: "0.15em",
21976
+ textTransform: "uppercase",
21977
+ color: "var(--brand-accent)",
21978
+ marginBottom: "1.5rem"
20379
21979
  },
20380
- linkPopover.key
20381
- ) : null,
20382
- floatingPanel && floatingPanel.kind === "socials-display" ? /* @__PURE__ */ jsx33(
20383
- FloatingPanel,
20384
- {
20385
- open: true,
20386
- title: floatingPanel.title,
20387
- context: floatingPanel.context,
20388
- position: floatingPanelPos,
20389
- onPositionChange: setFloatingPanelPos,
20390
- parentScroll: parentScrollSnap ?? parentScrollRef.current,
20391
- onClose: closeFloatingPanelOnly,
20392
- children: /* @__PURE__ */ jsx33(
20393
- SocialsDisplayPanel,
20394
- {
20395
- display: socialsDisplayFor(floatingPanel.row, editContentRef.current),
20396
- onChange: (next) => {
20397
- changeSocialsDisplay(floatingPanel.row, next);
20398
- setFloatingPanel({ ...floatingPanel });
20399
- }
20400
- }
20401
- )
20402
- }
20403
- ) : null
20404
- ] }),
20405
- bridgeRoot
20406
- ) : null;
21980
+ children: /* @__PURE__ */ jsx34(Link3, { href: homeHref, style: { color: "inherit" }, children: /* @__PURE__ */ jsx34("span", { ...eyebrowKey ? { "data-ohw-editable": "text", "data-ohw-key": eyebrowKey } : {}, children: "Home" }) })
21981
+ }
21982
+ ),
21983
+ /* @__PURE__ */ jsx34(
21984
+ "h1",
21985
+ {
21986
+ style: {
21987
+ fontFamily: "var(--brand-font-heading)",
21988
+ fontSize: "clamp(2rem, 3.5vw, 2.75rem)",
21989
+ lineHeight: 1.1,
21990
+ letterSpacing: "-0.025em",
21991
+ color: "var(--brand-text)",
21992
+ marginBottom: "1rem"
21993
+ },
21994
+ ...titleKey ? { "data-ohw-editable": "text", "data-ohw-key": titleKey, "data-ohw-max-length": 80 } : {},
21995
+ children: title
21996
+ }
21997
+ ),
21998
+ /* @__PURE__ */ jsx34(
21999
+ "p",
22000
+ {
22001
+ style: {
22002
+ fontFamily: "var(--brand-font-body)",
22003
+ fontSize: "1rem",
22004
+ lineHeight: 1.7,
22005
+ fontWeight: 300,
22006
+ color: "var(--brand-text-muted)",
22007
+ maxWidth: "340px"
22008
+ },
22009
+ ...subtitleKey ? { "data-ohw-editable": "text", "data-ohw-key": subtitleKey, "data-ohw-max-length": 160 } : {},
22010
+ children: "This page doesn't have any content yet."
22011
+ }
22012
+ )
22013
+ ] });
20407
22014
  }
20408
22015
  export {
20409
22016
  AI_DEFAULT_BRAND,
@@ -20421,6 +22028,7 @@ export {
20421
22028
  DropdownMenuItem,
20422
22029
  DropdownMenuSeparator,
20423
22030
  DropdownMenuTrigger,
22031
+ EmptySection,
20424
22032
  ItemActionToolbar,
20425
22033
  ItemInteractionLayer,
20426
22034
  LinkEditorPanel,