@ohhwells/bridge 0.1.68 → 0.1.69-next.208

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -46,6 +46,7 @@ __export(index_exports, {
46
46
  DropdownMenuItem: () => DropdownMenuItem,
47
47
  DropdownMenuSeparator: () => DropdownMenuSeparator,
48
48
  DropdownMenuTrigger: () => DropdownMenuTrigger,
49
+ EmptySection: () => EmptySection,
49
50
  ItemActionToolbar: () => ItemActionToolbar,
50
51
  ItemInteractionLayer: () => ItemInteractionLayer,
51
52
  LinkEditorPanel: () => LinkEditorPanel,
@@ -74,7 +75,7 @@ __export(index_exports, {
74
75
  module.exports = __toCommonJS(index_exports);
75
76
 
76
77
  // src/OhhwellsBridge.tsx
77
- var import_react16 = __toESM(require("react"), 1);
78
+ var import_react17 = __toESM(require("react"), 1);
78
79
  var import_client2 = require("react-dom/client");
79
80
  var import_react_dom3 = require("react-dom");
80
81
 
@@ -156,7 +157,12 @@ function parseAiSectionsState(raw) {
156
157
  media: entry.media && typeof entry.media === "object" ? entry.media : {}
157
158
  }));
158
159
  const removed = Array.isArray(parsed.removed) ? parsed.removed.filter((id) => typeof id === "string" && id.length > 0) : [];
159
- return { v: 1, sections, ...removed.length ? { removed } : {} };
160
+ return {
161
+ v: 1,
162
+ sections,
163
+ ...removed.length ? { removed } : {},
164
+ ...parsed.hideTemplate === true ? { hideTemplate: true } : {}
165
+ };
160
166
  } catch {
161
167
  return EMPTY_AI_SECTIONS;
162
168
  }
@@ -169,6 +175,7 @@ function applyTreeToState(state, payload) {
169
175
  const entry = {
170
176
  id: payload.id,
171
177
  label: payload.label ?? "Generated section",
178
+ ...typeof payload.path === "string" && payload.path ? { path: payload.path } : {},
172
179
  afterSection: payload.mode === "insert" && !insertBefore ? payload.targetSectionId ?? null : null,
173
180
  ...insertBefore && payload.targetSectionId ? { beforeSection: payload.targetSectionId } : {},
174
181
  ...payload.mode === "replace" && payload.targetSectionId ? { replaces: payload.targetSectionId } : {},
@@ -191,6 +198,317 @@ function deleteSectionFromState(state, sectionId) {
191
198
  return { ...state, v: 1, sections: state.sections, removed: [...removed, sectionId] };
192
199
  }
193
200
 
201
+ // src/lib/brand-chrome.ts
202
+ var BRAND_NAME_KEY = "__ohw_brand_name";
203
+ var BRAND_TITLE_KEY = "__ohw_site_title";
204
+ var BRAND_FAVICON_LETTER_KEY = "__ohw_favicon_letter";
205
+ var BRAND_CHROME_KEYS = /* @__PURE__ */ new Set([
206
+ BRAND_NAME_KEY,
207
+ BRAND_TITLE_KEY,
208
+ BRAND_FAVICON_LETTER_KEY
209
+ ]);
210
+ function upsertMeta(selector, attr, token, value) {
211
+ let el = document.head.querySelector(selector);
212
+ if (!el) {
213
+ el = document.createElement("meta");
214
+ el.setAttribute(attr, token);
215
+ document.head.appendChild(el);
216
+ }
217
+ if (el.getAttribute("content") !== value) el.setAttribute("content", value);
218
+ }
219
+ function escapeXml(value) {
220
+ return value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;");
221
+ }
222
+ function applyLetterFavicon(letter) {
223
+ 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>`;
224
+ const href = `data:image/svg+xml,${encodeURIComponent(svg)}`;
225
+ let link = document.head.querySelector('link[rel="icon"]');
226
+ if (!link) {
227
+ link = document.createElement("link");
228
+ link.rel = "icon";
229
+ document.head.appendChild(link);
230
+ }
231
+ link.type = "image/svg+xml";
232
+ if (link.href !== href) link.href = href;
233
+ }
234
+ function applyBrandChrome(content) {
235
+ const name = content[BRAND_NAME_KEY];
236
+ if (typeof name === "string" && name.length > 0) {
237
+ document.querySelectorAll("[data-ohw-wordmark]").forEach((el) => {
238
+ if (el.textContent !== name) el.textContent = name;
239
+ if (el.getAttribute("title") !== name) el.setAttribute("title", name);
240
+ });
241
+ }
242
+ const title = content[BRAND_TITLE_KEY];
243
+ if (typeof title === "string" && title.length > 0) {
244
+ if (document.title !== title) document.title = title;
245
+ upsertMeta('meta[property="og:title"]', "property", "og:title", title);
246
+ upsertMeta('meta[name="twitter:title"]', "name", "twitter:title", title);
247
+ }
248
+ const letter = content[BRAND_FAVICON_LETTER_KEY];
249
+ if (typeof letter === "string" && letter.length > 0) {
250
+ applyLetterFavicon(letter);
251
+ }
252
+ }
253
+
254
+ // src/lib/brand-kit.ts
255
+ var BRAND_KIT_KEY = "__ohw_brand";
256
+ var BRAND_VAR_PREFIX = "--ohw-brand-";
257
+ var BRAND_VAR_NAMES = ["primary", "accent", "light", "dark", "surface", "border", "muted"].map(
258
+ (role) => `${BRAND_VAR_PREFIX}${role}`
259
+ );
260
+ var FONT_VARS = {
261
+ heading: ["--font-heading", "--font-display", "--brand-font-heading"],
262
+ body: ["--font-body", "--brand-font-body"]
263
+ };
264
+ var BRAND_FONT_LINK_ID = "ohw-brand-fonts";
265
+ function brandColorVars(kit) {
266
+ const { dark, primary, accent, light } = kit.palette;
267
+ const mix = (a, aPct, b) => `color-mix(in srgb, ${a} ${aPct}%, ${b})`;
268
+ return {
269
+ [`${BRAND_VAR_PREFIX}primary`]: primary,
270
+ [`${BRAND_VAR_PREFIX}accent`]: accent,
271
+ [`${BRAND_VAR_PREFIX}light`]: light,
272
+ [`${BRAND_VAR_PREFIX}dark`]: dark,
273
+ [`${BRAND_VAR_PREFIX}surface`]: mix(light, 95, dark),
274
+ [`${BRAND_VAR_PREFIX}border`]: mix(light, 85, dark),
275
+ [`${BRAND_VAR_PREFIX}muted`]: mix(dark, 62, light)
276
+ };
277
+ }
278
+ function parseBrandKit(raw) {
279
+ if (!raw) return null;
280
+ try {
281
+ const parsed = JSON.parse(raw);
282
+ const p = parsed?.palette;
283
+ const f = parsed?.fonts;
284
+ 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") {
285
+ return null;
286
+ }
287
+ return {
288
+ palette: { dark: p.dark, primary: p.primary, accent: p.accent, light: p.light },
289
+ fonts: { heading: f.heading, body: f.body }
290
+ };
291
+ } catch {
292
+ return null;
293
+ }
294
+ }
295
+ function familyOf(stack) {
296
+ const first = stack.split(",")[0]?.trim() ?? "";
297
+ return first.replace(/^['"]|['"]$/g, "");
298
+ }
299
+ function loadBrandFonts(families) {
300
+ const unique = [...new Set(families.map((f) => f.trim()).filter(Boolean))];
301
+ if (unique.length === 0) return;
302
+ const spec = unique.map((f) => `${f.replace(/ /g, "+")}:400,500,600,700`).join("|");
303
+ const href = `https://fonts.googleapis.com/css?family=${spec}&display=swap`;
304
+ let link = document.getElementById(BRAND_FONT_LINK_ID);
305
+ if (!link) {
306
+ link = document.createElement("link");
307
+ link.id = BRAND_FONT_LINK_ID;
308
+ link.rel = "stylesheet";
309
+ document.head.appendChild(link);
310
+ }
311
+ if (link.href !== href) link.href = href;
312
+ }
313
+ function applyBrandToDom(kit) {
314
+ const root = document.documentElement;
315
+ if (!kit) {
316
+ for (const name of BRAND_VAR_NAMES) root.style.removeProperty(name);
317
+ for (const name of [...FONT_VARS.heading, ...FONT_VARS.body]) root.style.removeProperty(name);
318
+ document.getElementById(BRAND_FONT_LINK_ID)?.remove();
319
+ return;
320
+ }
321
+ for (const [name, value] of Object.entries(brandColorVars(kit))) root.style.setProperty(name, value);
322
+ for (const name of FONT_VARS.heading) root.style.setProperty(name, kit.fonts.heading);
323
+ for (const name of FONT_VARS.body) root.style.setProperty(name, kit.fonts.body);
324
+ loadBrandFonts([familyOf(kit.fonts.heading), familyOf(kit.fonts.body)]);
325
+ }
326
+
327
+ // src/lib/section-styles.ts
328
+ var STYLE_STORE_KEY = "__ohw_styles";
329
+ var STYLE_SHEET_ID = "ohw-section-styles";
330
+ function parseStyleStore(raw) {
331
+ if (!raw) return null;
332
+ try {
333
+ const parsed = JSON.parse(raw);
334
+ if (parsed?.v !== 1) return null;
335
+ return {
336
+ v: 1,
337
+ sections: typeof parsed.sections === "object" && parsed.sections ? parsed.sections : {},
338
+ nodes: typeof parsed.nodes === "object" && parsed.nodes ? parsed.nodes : {}
339
+ };
340
+ } catch {
341
+ return null;
342
+ }
343
+ }
344
+ var BG_VALUES = {
345
+ surface: "color-mix(in srgb, var(--ohw-brand-light, var(--color-light, #ECECEB)) 94%, var(--ohw-brand-dark, var(--color-dark, #0C0A09)))",
346
+ accent: "var(--ohw-brand-primary, var(--color-primary, #0078E5))",
347
+ "accent-soft": "color-mix(in srgb, var(--ohw-brand-primary, var(--color-primary, #0078E5)) 12%, var(--ohw-brand-light, var(--color-light, #FFFFFF)))"
348
+ };
349
+ var HEADLINE_SIZES = { md: 24, lg: 32, xl: 40, display: 56 };
350
+ function styleSheetCss() {
351
+ const rules = [];
352
+ for (const [tone, value] of Object.entries(BG_VALUES)) {
353
+ rules.push(`[data-ohw-style-bg="${tone}"] { background: ${value} !important; }`);
354
+ }
355
+ rules.push(
356
+ `[data-ohw-style-bg="accent"] { color: var(--ohw-brand-light, var(--color-light, #FFFFFF)) !important; }`
357
+ );
358
+ rules.push(
359
+ `[data-ohw-style-distribution="space-between"] { display: flex !important; flex-direction: column; justify-content: space-between; }`,
360
+ `[data-ohw-style-distribution="center"] { display: flex !important; flex-direction: column; justify-content: center; }`
361
+ );
362
+ for (const [scale, size] of Object.entries(HEADLINE_SIZES)) {
363
+ rules.push(
364
+ `[data-ohw-style-headline="${scale}"] :is(h1, h2, h3) { font-size: ${size}px !important; line-height: 1.15 !important; }`
365
+ );
366
+ }
367
+ rules.push(
368
+ `[data-ohw-style-aspect="fill-height"] img { height: 100% !important; aspect-ratio: auto !important; object-fit: cover; }`
369
+ );
370
+ for (const aspect of ["1:1", "4:5", "3:4", "3:2", "16:9", "2:1", "3:1"]) {
371
+ rules.push(
372
+ `[data-ohw-style-aspect="${aspect.replace(":", "-")}"] img { aspect-ratio: ${aspect.replace(":", " / ")} !important; height: auto !important; object-fit: cover; }`
373
+ );
374
+ }
375
+ const pad = { tight: 40, balanced: 64, airy: 96 };
376
+ for (const [spacing, px] of Object.entries(pad)) {
377
+ rules.push(
378
+ `[data-ohw-style-spacing="${spacing}"] { padding-top: ${px}px !important; padding-bottom: ${px}px !important; }`
379
+ );
380
+ }
381
+ return rules.join("\n");
382
+ }
383
+ var STYLE_FONT_LINK_ID = "ohw-style-fonts";
384
+ function loadStyleFonts(families) {
385
+ const unique = [...new Set(families.map((f) => f.trim()).filter(Boolean))];
386
+ const existing = document.getElementById(STYLE_FONT_LINK_ID);
387
+ if (unique.length === 0) {
388
+ existing?.remove();
389
+ return;
390
+ }
391
+ const spec = unique.map((f) => `${f.replace(/ /g, "+")}:400,500,600,700`).join("|");
392
+ const href = `https://fonts.googleapis.com/css?family=${spec}&display=swap`;
393
+ let link = existing;
394
+ if (!link) {
395
+ link = document.createElement("link");
396
+ link.id = STYLE_FONT_LINK_ID;
397
+ link.rel = "stylesheet";
398
+ document.head.appendChild(link);
399
+ }
400
+ if (link.href !== href) link.href = href;
401
+ }
402
+ var SECTION_ATTRS = {
403
+ sectionBackground: "data-ohw-style-bg",
404
+ textDistribution: "data-ohw-style-distribution",
405
+ headlineScale: "data-ohw-style-headline",
406
+ imageAspect: "data-ohw-style-aspect",
407
+ spacing: "data-ohw-style-spacing"
408
+ };
409
+ var NODE_WROTE_ATTR = "data-ohw-style-node";
410
+ var NODE_PROPS = ["color", "font-family", "font-size", "background"];
411
+ function saveInline(el, prop) {
412
+ const attr = `data-ohw-style-prev-${prop}`;
413
+ if (!el.hasAttribute(attr)) el.setAttribute(attr, el.style.getPropertyValue(prop));
414
+ }
415
+ function restoreInline(el, prop) {
416
+ const attr = `data-ohw-style-prev-${prop}`;
417
+ if (!el.hasAttribute(attr)) return;
418
+ const prev = el.getAttribute(attr) ?? "";
419
+ if (prev) el.style.setProperty(prop, prev);
420
+ else el.style.removeProperty(prop);
421
+ el.removeAttribute(attr);
422
+ }
423
+ function ensureStyleSheet() {
424
+ let el = document.getElementById(STYLE_SHEET_ID);
425
+ if (!el) {
426
+ el = document.createElement("style");
427
+ el.id = STYLE_SHEET_ID;
428
+ document.head.appendChild(el);
429
+ }
430
+ const css = styleSheetCss();
431
+ if (el.textContent !== css) el.textContent = css;
432
+ }
433
+ function clearSectionAttrs(root) {
434
+ for (const attr of Object.values(SECTION_ATTRS)) {
435
+ for (const el of Array.from(root.querySelectorAll(`[${attr}]`))) el.removeAttribute(attr);
436
+ }
437
+ for (const el of Array.from(root.querySelectorAll("[data-ohw-style-bgcolor]"))) {
438
+ restoreInline(el, "background");
439
+ el.removeAttribute("data-ohw-style-bgcolor");
440
+ }
441
+ }
442
+ function clearNodeProps(root) {
443
+ for (const el of Array.from(root.querySelectorAll(`[${NODE_WROTE_ATTR}]`))) {
444
+ const h = el;
445
+ for (const prop of NODE_PROPS) restoreInline(h, prop);
446
+ h.removeAttribute(NODE_WROTE_ATTR);
447
+ }
448
+ }
449
+ function buttonSurfaceOf(el) {
450
+ return el.closest("a, button") ?? el;
451
+ }
452
+ function applyStylesToDom(store) {
453
+ ensureStyleSheet();
454
+ clearSectionAttrs(document);
455
+ clearNodeProps(document);
456
+ loadStyleFonts(
457
+ store ? Object.values(store.nodes).flatMap((n) => n.fontFamily ? [n.fontFamily] : []) : []
458
+ );
459
+ if (!store) return;
460
+ for (const [sectionId, override] of Object.entries(store.sections)) {
461
+ const sections = document.querySelectorAll(
462
+ `[data-ohw-section="${CSS.escape(sectionId)}"]`
463
+ );
464
+ for (const section of Array.from(sections)) {
465
+ for (const [prop, attr] of Object.entries(SECTION_ATTRS)) {
466
+ const value = override[prop];
467
+ if (value === void 0) continue;
468
+ if (prop === "sectionBackground" && override.sectionBackgroundColor !== void 0) continue;
469
+ section.setAttribute(attr, String(value).replace(":", "-"));
470
+ }
471
+ if (override.sectionBackgroundColor !== void 0) {
472
+ saveInline(section, "background");
473
+ section.style.setProperty("background", override.sectionBackgroundColor, "important");
474
+ section.setAttribute("data-ohw-style-bgcolor", "");
475
+ }
476
+ }
477
+ }
478
+ for (const [key, override] of Object.entries(store.nodes)) {
479
+ const nodes = document.querySelectorAll(`[data-ohw-key="${CSS.escape(key)}"]`);
480
+ for (const el of Array.from(nodes)) {
481
+ if (override.color !== void 0) {
482
+ saveInline(el, "color");
483
+ el.style.setProperty("color", override.color, "important");
484
+ el.setAttribute(NODE_WROTE_ATTR, "");
485
+ }
486
+ if (override.fontFamily !== void 0) {
487
+ saveInline(el, "font-family");
488
+ el.style.setProperty("font-family", `'${override.fontFamily}'`, "important");
489
+ el.setAttribute(NODE_WROTE_ATTR, "");
490
+ }
491
+ if (override.fontSize !== void 0) {
492
+ saveInline(el, "font-size");
493
+ el.style.setProperty("font-size", `${override.fontSize}px`, "important");
494
+ el.setAttribute(NODE_WROTE_ATTR, "");
495
+ }
496
+ if (override.buttonBackground !== void 0 || override.buttonText !== void 0) {
497
+ const surface = buttonSurfaceOf(el);
498
+ if (override.buttonBackground !== void 0) {
499
+ saveInline(surface, "background");
500
+ surface.style.setProperty("background", override.buttonBackground, "important");
501
+ }
502
+ if (override.buttonText !== void 0) {
503
+ saveInline(surface, "color");
504
+ surface.style.setProperty("color", override.buttonText, "important");
505
+ }
506
+ surface.setAttribute(NODE_WROTE_ATTR, "");
507
+ }
508
+ }
509
+ }
510
+ }
511
+
194
512
  // src/ui/ai-tree/aiSectionsManager.tsx
195
513
  var import_react_dom = require("react-dom");
196
514
  var import_client = require("react-dom/client");
@@ -205,7 +523,8 @@ function lucideByName(name) {
205
523
  }
206
524
  var typeStyle = (spec, font) => ({
207
525
  fontFamily: font,
208
- fontSize: spec.size,
526
+ // Headings shrink with the viewport (reaching full size around ~900px wide); body copy stays put.
527
+ 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,
209
528
  lineHeight: spec.line,
210
529
  fontWeight: spec.weight
211
530
  });
@@ -220,6 +539,36 @@ var FEATURE_LINE_CSS = [
220
539
  `background-color:currentColor;-webkit-mask:url("${CHECK_MASK}") no-repeat 0 0/24px 24px;`,
221
540
  `mask:url("${CHECK_MASK}") no-repeat 0 0/24px 24px}`
222
541
  ].join("");
542
+ function hexLuminance(color) {
543
+ const m = /^#([0-9a-f]{6})$/i.exec(color.trim());
544
+ if (!m) return null;
545
+ const [r2, g, b] = [0, 2, 4].map((i) => {
546
+ const c = parseInt(m[1].slice(i, i + 2), 16) / 255;
547
+ return c <= 0.03928 ? c / 12.92 : Math.pow((c + 0.055) / 1.055, 2.4);
548
+ });
549
+ return 0.2126 * r2 + 0.7152 * g + 0.0722 * b;
550
+ }
551
+ function hexContrast(a, b) {
552
+ const la = hexLuminance(a);
553
+ const lb = hexLuminance(b);
554
+ if (la === null || lb === null) return null;
555
+ const [hi, lo] = la > lb ? [la, lb] : [lb, la];
556
+ return (hi + 0.05) / (lo + 0.05);
557
+ }
558
+ function accentBandContext(brand) {
559
+ const p = brand.palette;
560
+ const lightWins = (hexContrast(p.light, p.primary) ?? 99) >= (hexContrast(p.dark, p.primary) ?? 0);
561
+ if (lightWins) {
562
+ return {
563
+ brand: { ...brand, palette: { dark: p.light, primary: p.light, accent: p.light, light: p.primary } },
564
+ buttonLabel: p.primary
565
+ };
566
+ }
567
+ return {
568
+ brand: { ...brand, palette: { dark: p.dark, primary: p.dark, accent: p.dark, light: p.light } },
569
+ buttonLabel: p.light
570
+ };
571
+ }
223
572
  function textAttrs(ctx, path) {
224
573
  return ctx.keyFor ? { "data-ohw-key": ctx.keyFor(path), "data-ohw-editable": "text" } : {};
225
574
  }
@@ -231,7 +580,12 @@ var AI_RESPONSIVE_CSS = [
231
580
  "@media (max-width: 640px) {",
232
581
  " [data-ai-responsive] [data-ai-grid] { grid-template-columns: 1fr !important; }",
233
582
  " [data-ai-responsive] [data-ai-columns] > * { grid-column: 1 / -1 !important; }",
583
+ // Group containers flatten to a column on phones; span placements come along for free.
584
+ " [data-ai-responsive] [data-ai-group] { display: flex !important; flex-direction: column !important; }",
585
+ " [data-ai-responsive] [data-ai-group] > * { grid-column: auto !important; }",
234
586
  " [data-ai-responsive] [data-ai-section-inner] { padding-left: 20px !important; padding-right: 20px !important; }",
587
+ " [data-ai-responsive] { overflow-x: hidden; }",
588
+ " [data-ai-responsive] img { max-width: 100%; }",
235
589
  "}"
236
590
  ].join("\n");
237
591
  var TRANSPARENT_PX = "data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7";
@@ -309,7 +663,7 @@ function ButtonEl({
309
663
  }) {
310
664
  const secondary = slots.variant === "secondary";
311
665
  const href = str(slots.href);
312
- const linkAttrs = ctx.keyFor && path ? { "data-ohw-href-key": ctx.keyFor(`${path}.href`) } : {};
666
+ const linkAttrs = ctx.keyFor && path ? { "data-ohw-href-key": ctx.keyFor(`${path}.href`), "data-ohw-role": "button" } : {};
313
667
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
314
668
  "a",
315
669
  {
@@ -325,7 +679,7 @@ function ButtonEl({
325
679
  textDecoration: "none",
326
680
  cursor: "pointer",
327
681
  ...typeStyle(AI_TREE_TOKENS.type.button, ctx.brand.fonts.body),
328
- ...secondary ? { border: `1px solid ${ctx.brand.palette.dark}`, color: ctx.brand.palette.dark } : { background: ctx.brand.palette.primary, color: AI_TREE_TOKENS.textPrimaryForeground }
682
+ ...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 }
329
683
  },
330
684
  children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { ...path ? textAttrs(ctx, `${path}.label`) : {}, children: str(slots.label) })
331
685
  }
@@ -831,7 +1185,24 @@ function CardBlock({ node, ctx, path }) {
831
1185
  minWidth: 0
832
1186
  },
833
1187
  children: [
834
- media && (horizontal ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { flex: 1, minWidth: 0, display: "flex", alignItems: "center", padding: mediaInset }, children: media }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1188
+ media && (horizontal ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1189
+ "div",
1190
+ {
1191
+ style: (
1192
+ // An icon hugs its glyph — flex:1 gave a 48px icon half the card and pushed the
1193
+ // text to the far side. Photos keep the half-and-half split. The inset has no
1194
+ // inner padding (the photo split absorbed that), so the icon carries its own gap.
1195
+ /^(lucide|simple):/.test(mediaRef) ? {
1196
+ flexShrink: 0,
1197
+ display: "flex",
1198
+ alignItems: "center",
1199
+ padding: mediaInset,
1200
+ [mediaPosition === "right" ? "marginLeft" : "marginRight"]: 20
1201
+ } : { flex: 1, minWidth: 0, display: "flex", alignItems: "center", padding: mediaInset }
1202
+ ),
1203
+ children: media
1204
+ }
1205
+ ) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
835
1206
  "div",
836
1207
  {
837
1208
  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" },
@@ -1095,6 +1466,49 @@ function renderNode(node, ctx, path) {
1095
1466
  switch (node.type) {
1096
1467
  case "text":
1097
1468
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TextBlock, { slots, ctx, path });
1469
+ // Layout container: arranges child blocks, contributes no content of its own. `grid` is a
1470
+ // nested 12-column grid the children span (a collage is 3–5 media on it, bottom-aligned so
1471
+ // mixed aspects read as a composition); `split` is exactly two children at a ratio; `stack`
1472
+ // is a column. Children render through this same dispatcher, so edit markers, media
1473
+ // resolution, and copy paths all work unchanged inside a group.
1474
+ case "group": {
1475
+ const layout = str(slots.layout);
1476
+ const gap = slots.spacing === "tight" ? AI_TREE_TOKENS.spacing3 : slots.spacing === "airy" ? AI_TREE_TOKENS.spacing8 : AI_TREE_TOKENS.spacing6;
1477
+ const kids = (node.children ?? []).map((child, i) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1478
+ "div",
1479
+ {
1480
+ style: layout === "grid" ? {
1481
+ gridColumn: `span ${typeof child.span === "number" ? Math.min(12, Math.max(1, child.span)) : 12}`,
1482
+ minWidth: 0
1483
+ } : { minWidth: 0 },
1484
+ children: renderNode(child, ctx, `${path}.c${i}`)
1485
+ },
1486
+ i
1487
+ ));
1488
+ if (layout === "grid") {
1489
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1490
+ "div",
1491
+ {
1492
+ "data-ai-group": "grid",
1493
+ style: { display: "grid", gridTemplateColumns: "repeat(12, 1fr)", gap, alignItems: "end" },
1494
+ children: kids
1495
+ }
1496
+ );
1497
+ }
1498
+ if (layout === "split") {
1499
+ const ratio = str(slots.ratio);
1500
+ const cols = ratio === "3:5" ? "3fr 5fr" : ratio === "5:3" ? "5fr 3fr" : "1fr 1fr";
1501
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1502
+ "div",
1503
+ {
1504
+ "data-ai-group": "split",
1505
+ style: { display: "grid", gridTemplateColumns: cols, gap, alignItems: "center" },
1506
+ children: kids
1507
+ }
1508
+ );
1509
+ }
1510
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { "data-ai-group": "stack", style: { display: "flex", flexDirection: "column", gap }, children: kids });
1511
+ }
1098
1512
  case "button":
1099
1513
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ButtonEl, { slots, ctx, path });
1100
1514
  case "button-row":
@@ -1227,11 +1641,14 @@ function AiTreeRenderer({ tree, brand, resolveMedia, editKeyPrefix }) {
1227
1641
  return null;
1228
1642
  }
1229
1643
  const resolvedBrand = brand ?? AI_DEFAULT_BRAND;
1644
+ const band = (tree.settings ?? {}).sectionBackground === "accent" ? accentBandContext(resolvedBrand) : null;
1645
+ const blockBrand = band?.brand ?? resolvedBrand;
1230
1646
  const ctx = {
1231
- brand: resolvedBrand,
1647
+ brand: blockBrand,
1232
1648
  resolveMedia: resolveMedia ?? (() => null),
1233
- cardSurface: resolvedBrand.palette.light.toUpperCase() === AI_DEFAULT_BRAND.palette.light.toUpperCase() ? "#ECECEB" : `color-mix(in srgb, ${resolvedBrand.palette.light} 90%, ${resolvedBrand.palette.dark})`,
1234
- keyFor: editKeyPrefix ? (path) => `${editKeyPrefix}.${path}` : null
1649
+ cardSurface: blockBrand.palette.light.toUpperCase() === AI_DEFAULT_BRAND.palette.light.toUpperCase() ? "#ECECEB" : `color-mix(in srgb, ${blockBrand.palette.light} 90%, ${blockBrand.palette.dark})`,
1650
+ keyFor: editKeyPrefix ? (path) => `${editKeyPrefix}.${path}` : null,
1651
+ ...band ? { buttonLabel: band.buttonLabel } : {}
1235
1652
  };
1236
1653
  const settings = tree.settings ?? {};
1237
1654
  const pad = AI_TREE_TOKENS.sectionPadding[settings.spacing ?? "balanced"];
@@ -1239,6 +1656,20 @@ function AiTreeRenderer({ tree, brand, resolveMedia, editKeyPrefix }) {
1239
1656
  const overlayAlpha = { low: 0.25, medium: 0.45, high: 0.65 }[settings.overlayIntensity ?? "medium"];
1240
1657
  const backgroundUrl = isOverlay && settings.backgroundMedia ? ctx.resolveMedia(settings.backgroundMedia) : null;
1241
1658
  const bgAttrs = ctx.keyFor && isOverlay && settings.backgroundMedia ? { "data-ohw-key": ctx.keyFor("settings.backgroundMedia"), "data-ohw-editable": "bg-image" } : {};
1659
+ const toneBackground = (() => {
1660
+ const { dark, primary, light } = resolvedBrand.palette;
1661
+ switch (settings.sectionBackground) {
1662
+ case "surface":
1663
+ return `color-mix(in srgb, ${light} 94%, ${dark})`;
1664
+ case "accent":
1665
+ return primary;
1666
+ case "accent-soft":
1667
+ return `color-mix(in srgb, ${primary} 12%, ${light})`;
1668
+ default:
1669
+ return void 0;
1670
+ }
1671
+ })();
1672
+ const distributed = !isOverlay && settings.textDistribution;
1242
1673
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
1243
1674
  "section",
1244
1675
  {
@@ -1248,10 +1679,11 @@ function AiTreeRenderer({ tree, brand, resolveMedia, editKeyPrefix }) {
1248
1679
  style: {
1249
1680
  position: "relative",
1250
1681
  padding: `${pad}px 0`,
1251
- background: isOverlay && !backgroundUrl ? "#DBEAFE" : void 0,
1682
+ background: toneBackground ?? (isOverlay && !backgroundUrl ? "#DBEAFE" : void 0),
1252
1683
  backgroundImage: backgroundUrl ? `url(${backgroundUrl})` : void 0,
1253
1684
  backgroundSize: "cover",
1254
- backgroundPosition: "center"
1685
+ backgroundPosition: "center",
1686
+ color: settings.sectionBackground === "accent" ? blockBrand.palette.dark : void 0
1255
1687
  },
1256
1688
  children: [
1257
1689
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("style", { children: AI_RESPONSIVE_CSS }),
@@ -1275,10 +1707,24 @@ function AiTreeRenderer({ tree, brand, resolveMedia, editKeyPrefix }) {
1275
1707
  display: "grid",
1276
1708
  gridTemplateColumns: "repeat(12, 1fr)",
1277
1709
  gap: AI_TREE_TOKENS.spacing6,
1278
- alignItems: settings.verticalPosition === "top" ? "start" : "center",
1710
+ alignItems: distributed === "space-between" ? "stretch" : (distributed ?? settings.verticalPosition) === "top" ? "start" : "center",
1279
1711
  marginTop: r2 > 0 ? AI_TREE_TOKENS.spacing8 : 0
1280
1712
  },
1281
- children: (row.blocks ?? []).map((block, b) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { gridColumn: `span ${Math.min(12, Math.max(1, block.span))}`, minWidth: 0 }, children: renderNode(block, ctx, `r${r2}.b${b}`) }, b))
1713
+ children: (row.blocks ?? []).map((block, b) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1714
+ "div",
1715
+ {
1716
+ "data-ai-cell": "",
1717
+ style: {
1718
+ gridColumn: `span ${Math.min(12, Math.max(1, block.span))}`,
1719
+ minWidth: 0,
1720
+ // space-between: each column becomes a flex column whose content spreads over
1721
+ // the full row height instead of clumping at the top.
1722
+ ...distributed === "space-between" ? { display: "flex", flexDirection: "column", justifyContent: "space-between" } : {}
1723
+ },
1724
+ children: renderNode(block, ctx, `r${r2}.b${b}`)
1725
+ },
1726
+ b
1727
+ ))
1282
1728
  },
1283
1729
  r2
1284
1730
  ))
@@ -1294,17 +1740,36 @@ var import_jsx_runtime2 = require("react/jsx-runtime");
1294
1740
  var CONTAINER_ATTR = "data-ohw-ai-generated";
1295
1741
  var REPLACED_ATTR = "data-ohw-ai-replaced-by";
1296
1742
  var REMOVED_ATTR = "data-ohw-ai-removed";
1743
+ var TEMPLATE_HIDDEN_ATTR = "data-ohw-ai-template-hidden";
1744
+ var CHROME_SECTION_IDS = /* @__PURE__ */ new Set(["navbar", "footer"]);
1745
+ function readRootVar(name) {
1746
+ if (typeof document === "undefined") return "";
1747
+ return getComputedStyle(document.documentElement).getPropertyValue(name).trim();
1748
+ }
1749
+ function deriveBrandOverride() {
1750
+ const dark = readRootVar("--ohw-brand-dark");
1751
+ const primary = readRootVar("--ohw-brand-primary");
1752
+ const light = readRootVar("--ohw-brand-light");
1753
+ if (!dark || !primary || !light) return null;
1754
+ const accent = readRootVar("--ohw-brand-accent");
1755
+ const heading = readRootVar("--font-heading") || readRootVar("--font-display");
1756
+ const body = readRootVar("--font-body");
1757
+ return {
1758
+ palette: { dark, primary, accent: accent || dark, light },
1759
+ fonts: {
1760
+ heading: heading || AI_DEFAULT_BRAND.fonts.heading,
1761
+ body: body || AI_DEFAULT_BRAND.fonts.body
1762
+ }
1763
+ };
1764
+ }
1297
1765
  function deriveTemplateBrand() {
1298
- if (typeof document === "undefined") return null;
1299
- const cs = getComputedStyle(document.documentElement);
1300
- const read = (name) => cs.getPropertyValue(name).trim();
1301
- const dark = read("--color-dark");
1302
- const primary = read("--color-primary");
1303
- const light = read("--color-light");
1766
+ const dark = readRootVar("--color-dark");
1767
+ const primary = readRootVar("--color-primary");
1768
+ const light = readRootVar("--color-light");
1304
1769
  if (!dark || !primary || !light) return null;
1305
- const accent = read("--color-accent");
1306
- const heading = read("--font-heading") || read("--font-display");
1307
- const body = read("--font-body");
1770
+ const accent = readRootVar("--color-accent");
1771
+ const heading = readRootVar("--font-heading") || readRootVar("--font-display");
1772
+ const body = readRootVar("--font-body");
1308
1773
  return {
1309
1774
  palette: { dark, primary, accent: accent || dark, light },
1310
1775
  fonts: {
@@ -1376,6 +1841,24 @@ function syncRemovedSections(state) {
1376
1841
  }
1377
1842
  }
1378
1843
  }
1844
+ function syncTemplateHidden(state, pageHasSections) {
1845
+ const hide = state.hideTemplate === true && pageHasSections;
1846
+ for (const el of Array.from(document.querySelectorAll(`[${TEMPLATE_HIDDEN_ATTR}]`))) {
1847
+ if (!hide) {
1848
+ el.style.removeProperty("display");
1849
+ el.removeAttribute(TEMPLATE_HIDDEN_ATTR);
1850
+ }
1851
+ }
1852
+ if (!hide) return;
1853
+ for (const el of Array.from(document.querySelectorAll("[data-ohw-section]"))) {
1854
+ if (el.hasAttribute(CONTAINER_ATTR)) continue;
1855
+ if (CHROME_SECTION_IDS.has(el.getAttribute("data-ohw-section") ?? "")) continue;
1856
+ if (el.parentElement?.closest("[data-ohw-section]")) continue;
1857
+ if (el.hasAttribute(REPLACED_ATTR) || el.hasAttribute(REMOVED_ATTR)) continue;
1858
+ el.style.display = "none";
1859
+ el.setAttribute(TEMPLATE_HIDDEN_ATTR, "");
1860
+ }
1861
+ }
1379
1862
  function syncReplacedOriginals(state) {
1380
1863
  for (const el of document.querySelectorAll(`[${REPLACED_ATTR}]`)) {
1381
1864
  const byId = el.getAttribute(REPLACED_ATTR) ?? "";
@@ -1396,8 +1879,12 @@ function syncReplacedOriginals(state) {
1396
1879
  }
1397
1880
  function applyAiSectionsToDom(state, options) {
1398
1881
  if (typeof document === "undefined") return;
1882
+ const brandOverride = deriveBrandOverride();
1399
1883
  const templateBrand = deriveTemplateBrand();
1400
- const activeIds = new Set(state.sections.map((entry) => entry.id));
1884
+ const brandKey = brandOverride ? JSON.stringify(brandOverride) : "";
1885
+ const pagePath = window.location.pathname;
1886
+ const pageSections = state.sections.filter((entry) => (entry.path ?? "/") === pagePath);
1887
+ const activeIds = new Set(pageSections.map((entry) => entry.id));
1401
1888
  for (const [id, section] of mounted) {
1402
1889
  if (!activeIds.has(id)) {
1403
1890
  section.root.unmount();
@@ -1405,8 +1892,8 @@ function applyAiSectionsToDom(state, options) {
1405
1892
  mounted.delete(id);
1406
1893
  }
1407
1894
  }
1408
- for (const entry of state.sections) {
1409
- const serialized = JSON.stringify(entry);
1895
+ for (const entry of pageSections) {
1896
+ const serialized = JSON.stringify(entry) + brandKey;
1410
1897
  const existing = mounted.get(entry.id);
1411
1898
  if (existing && existing.serialized === serialized && existing.container.isConnected) {
1412
1899
  continue;
@@ -1420,6 +1907,7 @@ function applyAiSectionsToDom(state, options) {
1420
1907
  mounted.delete(entry.id);
1421
1908
  }
1422
1909
  container.setAttribute("data-ohw-section", entry.id);
1910
+ container.setAttribute("data-ohw-instance", entry.id);
1423
1911
  container.setAttribute("data-ohw-section-label", entry.label);
1424
1912
  placeContainer(container, entry);
1425
1913
  const root = mounted.get(entry.id)?.root ?? (0, import_client.createRoot)(container);
@@ -1430,7 +1918,7 @@ function applyAiSectionsToDom(state, options) {
1430
1918
  AiTreeRenderer,
1431
1919
  {
1432
1920
  tree: entry.tree,
1433
- brand: entry.brand ?? options?.brand ?? templateBrand ?? void 0,
1921
+ brand: brandOverride ?? entry.brand ?? options?.brand ?? templateBrand ?? void 0,
1434
1922
  resolveMedia,
1435
1923
  editKeyPrefix: `ai.${entry.id}`
1436
1924
  }
@@ -1441,6 +1929,7 @@ function applyAiSectionsToDom(state, options) {
1441
1929
  }
1442
1930
  syncReplacedOriginals(state);
1443
1931
  syncRemovedSections(state);
1932
+ syncTemplateHidden(state, pageSections.length > 0);
1444
1933
  }
1445
1934
 
1446
1935
  // src/useLinkHrefGuardian.ts
@@ -2047,7 +2536,7 @@ function SchedulingWidget({ notifyOnConnect = false, initialScheduleId, insertAf
2047
2536
  const autoId = (0, import_react5.useId)();
2048
2537
  const insertAfter = insertAfterProp ?? autoId;
2049
2538
  const [schedule, setSchedule] = (0, import_react5.useState)(null);
2050
- const [loading, setLoading] = (0, import_react5.useState)(true);
2539
+ const [loading, setLoading] = (0, import_react5.useState)(initialScheduleId !== null);
2051
2540
  const [inEditor, setInEditor] = (0, import_react5.useState)(false);
2052
2541
  const [isHovered, setIsHovered] = (0, import_react5.useState)(false);
2053
2542
  const [modalState, setModalState] = (0, import_react5.useState)(null);
@@ -2221,8 +2710,10 @@ function SchedulingWidget({ notifyOnConnect = false, initialScheduleId, insertAf
2221
2710
  "*"
2222
2711
  );
2223
2712
  };
2224
- if (!inEditor && !loading && !schedule) return null;
2225
2713
  const sectionId = `scheduling-${insertAfter}`;
2714
+ if (!inEditor && !loading && !schedule) {
2715
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("section", { "data-ohw-section": sectionId, "data-ohw-scheduling-anchor": insertAfter, style: { display: "none" } });
2716
+ }
2226
2717
  return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
2227
2718
  "section",
2228
2719
  {
@@ -7130,13 +7621,17 @@ function MediaOverlay({
7130
7621
  hover,
7131
7622
  isUploading,
7132
7623
  fadingOut = false,
7624
+ selected = false,
7625
+ hovered = false,
7133
7626
  onFadeOutComplete,
7134
7627
  onReplace,
7628
+ onSelect,
7135
7629
  onVideoSettingsChange
7136
7630
  }) {
7137
7631
  const { rect } = hover;
7138
7632
  const skeletonRef = React7.useRef(null);
7139
7633
  const isVideo = hover.elementType === "video";
7634
+ const showChrome = !selected || hovered;
7140
7635
  const autoplay = hover.videoAutoplay ?? true;
7141
7636
  const muted = hover.videoMuted ?? true;
7142
7637
  const box = {
@@ -7171,7 +7666,7 @@ function MediaOverlay({
7171
7666
  }
7172
7667
  );
7173
7668
  }
7174
- const settingsBar = isVideo && !hover.isDragOver ? /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(
7669
+ const settingsBar = isVideo && !hover.isDragOver && showChrome ? /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(
7175
7670
  "div",
7176
7671
  {
7177
7672
  "data-ohw-bridge": "",
@@ -7241,11 +7736,13 @@ function MediaOverlay({
7241
7736
  // in-document, pointer-events does it natively. The button below opts back in, so
7242
7737
  // Replace still works.
7243
7738
  pointerEvents: hover.hasTextOverlap ? "none" : "auto",
7244
- boxShadow: "inset 0 0 0 1.5px var(--color-primary)",
7245
- background: "color-mix(in srgb, var(--color-primary) 20%, transparent)"
7739
+ // Selected: a firm component ring with no wash, so the image reads as chosen rather
7740
+ // than hovered. Hover keeps the existing tinted preview.
7741
+ boxShadow: selected ? "inset 0 0 0 2px var(--color-primary)" : "inset 0 0 0 1.5px var(--color-primary)",
7742
+ background: selected ? "transparent" : "color-mix(in srgb, var(--color-primary) 20%, transparent)"
7246
7743
  },
7247
- onClick: () => onReplace(hover.key),
7248
- children: /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(
7744
+ onClick: () => onSelect && !selected ? onSelect(hover.key) : onReplace(hover.key),
7745
+ children: showChrome && /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(
7249
7746
  Button,
7250
7747
  {
7251
7748
  "data-ohw-media-overlay": "",
@@ -7264,7 +7761,7 @@ function MediaOverlay({
7264
7761
  },
7265
7762
  children: [
7266
7763
  isVideo ? /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_lucide_react5.Film, { size: 14 }) : /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_lucide_react5.ImageIcon, { size: 14 }),
7267
- isVideo ? "Replace video" : "Replace image"
7764
+ isVideo ? "Replace video" : hover.elementType === "bg-image" ? "Replace background" : "Replace image"
7268
7765
  ]
7269
7766
  }
7270
7767
  )
@@ -7334,6 +7831,9 @@ var import_lucide_react7 = require("lucide-react");
7334
7831
 
7335
7832
  // src/lib/sections.ts
7336
7833
  var LINK_PICKER_EXCLUDED_IDS = /* @__PURE__ */ new Set(["navbar", "footer"]);
7834
+ function isChromeSection(el) {
7835
+ return el.matches("header, nav, footer, aside");
7836
+ }
7337
7837
  function titleCaseSectionId(id) {
7338
7838
  return id.split("-").filter(Boolean).map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join(" ");
7339
7839
  }
@@ -7344,6 +7844,8 @@ function parseSectionsFromRoot(root) {
7344
7844
  const id = el.getAttribute("data-ohw-section") ?? "";
7345
7845
  if (!id || seen.has(id) || LINK_PICKER_EXCLUDED_IDS.has(id)) continue;
7346
7846
  if (el.parentElement?.closest("[data-ohw-section]")) continue;
7847
+ if (el.hasAttribute("data-ohw-ai-template-hidden") || el.hasAttribute("data-ohw-ai-removed") || el.hasAttribute("data-ohw-ai-replaced-by"))
7848
+ continue;
7347
7849
  seen.add(id);
7348
7850
  const label = el.getAttribute("data-ohw-section-label") ?? titleCaseSectionId(id);
7349
7851
  sections.push({ id, label });
@@ -7361,8 +7863,12 @@ function parseSectionsFromHtml(html) {
7361
7863
 
7362
7864
  // src/ui/ai-section/AiSectionOverlay.tsx
7363
7865
  var import_jsx_runtime17 = require("react/jsx-runtime");
7364
- function readRect(sectionId) {
7365
- const el = document.querySelector(`[data-ohw-section="${CSS.escape(sectionId)}"]`);
7866
+ function findSectionElement(instanceId) {
7867
+ const escaped = CSS.escape(instanceId);
7868
+ return document.querySelector(`[data-ohw-instance="${escaped}"]`) ?? document.querySelector(`[data-ohw-section="${escaped}"]:not([data-ohw-instance])`);
7869
+ }
7870
+ function readRect(instanceId) {
7871
+ const el = findSectionElement(instanceId);
7366
7872
  if (!el) return null;
7367
7873
  const r2 = el.getBoundingClientRect();
7368
7874
  if (r2.width <= 0 || r2.height <= 0) return null;
@@ -7385,7 +7891,7 @@ function useLiveSectionRect(sectionId) {
7385
7891
  const opts = { capture: true, passive: true };
7386
7892
  window.addEventListener("scroll", update, opts);
7387
7893
  window.addEventListener("resize", update);
7388
- const el = document.querySelector(`[data-ohw-section="${CSS.escape(sectionId)}"]`);
7894
+ const el = findSectionElement(sectionId);
7389
7895
  const ro = el ? new ResizeObserver(update) : null;
7390
7896
  if (el && ro) ro.observe(el);
7391
7897
  const interval = setInterval(update, 500);
@@ -7398,6 +7904,14 @@ function useLiveSectionRect(sectionId) {
7398
7904
  }, [sectionId]);
7399
7905
  return rect;
7400
7906
  }
7907
+ function computeSectionBoundaryFlags(instanceId) {
7908
+ const topLevel = Array.from(document.querySelectorAll("[data-ohw-section]")).filter(
7909
+ (el) => !el.parentElement?.closest("[data-ohw-section]") && !isChromeSection(el)
7910
+ );
7911
+ const index = topLevel.findIndex((el) => (el.dataset.ohwInstance ?? el.dataset.ohwSection) === instanceId);
7912
+ if (index === -1) return { isFirst: true, isLast: true };
7913
+ return { isFirst: index === 0, isLast: index === topLevel.length - 1 };
7914
+ }
7401
7915
  var PRIMARY2 = "#0885FE";
7402
7916
  function edgeAwareRadius(rect) {
7403
7917
  const container = window.innerWidth <= 480 ? 16 : 24;
@@ -7471,6 +7985,7 @@ function AiSectionOverlay({
7471
7985
  }) {
7472
7986
  const [selectedId, setSelectedId] = (0, import_react8.useState)(null);
7473
7987
  const [reviewId, setReviewId] = (0, import_react8.useState)(null);
7988
+ const [reviewButtonsHidden, setReviewButtonsHidden] = (0, import_react8.useState)(false);
7474
7989
  const reviewIdRef = (0, import_react8.useRef)(null);
7475
7990
  reviewIdRef.current = reviewId;
7476
7991
  const selectedIdRef = (0, import_react8.useRef)(null);
@@ -7479,7 +7994,7 @@ function AiSectionOverlay({
7479
7994
  (el) => {
7480
7995
  postToParent2({
7481
7996
  type: "ow:section-selected",
7482
- sectionId: el?.dataset.ohwSection ?? null,
7997
+ sectionId: el ? el.dataset.ohwInstance ?? el.dataset.ohwSection ?? null : null,
7483
7998
  sectionLabel: el ? el.dataset.ohwSectionLabel ?? titleCaseSectionId(el.dataset.ohwSection ?? "") : null
7484
7999
  });
7485
8000
  },
@@ -7488,7 +8003,7 @@ function AiSectionOverlay({
7488
8003
  const selectFromElement = (0, import_react8.useCallback)(
7489
8004
  (el, options) => {
7490
8005
  const sectionEl = el?.closest("[data-ohw-section]") ?? null;
7491
- const id = sectionEl?.dataset.ohwSection ?? null;
8006
+ const id = sectionEl ? sectionEl.dataset.ohwInstance ?? sectionEl.dataset.ohwSection ?? null : null;
7492
8007
  if (id === selectedIdRef.current) return;
7493
8008
  setSelectedId(id);
7494
8009
  if (options?.report !== false) report(sectionEl);
@@ -7529,9 +8044,10 @@ function AiSectionOverlay({
7529
8044
  }
7530
8045
  const found = readRect(sectionId) != null;
7531
8046
  setReviewId(found ? sectionId : null);
8047
+ setReviewButtonsHidden(e.data.hideButtons === true);
7532
8048
  postToParent2({ type: "ow:ai-review-started", sectionId, found });
7533
8049
  if (found) {
7534
- document.querySelector(`[data-ohw-section="${CSS.escape(sectionId)}"]`)?.scrollIntoView({ behavior: "smooth", block: "nearest" });
8050
+ document.querySelector(`[data-ohw-instance="${CSS.escape(sectionId)}"]`)?.scrollIntoView({ behavior: "smooth", block: "nearest" });
7535
8051
  }
7536
8052
  }
7537
8053
  };
@@ -7550,7 +8066,7 @@ function AiSectionOverlay({
7550
8066
  return;
7551
8067
  }
7552
8068
  const sec = t.closest("[data-ohw-section]");
7553
- setHoveredId(sec?.dataset.ohwSection ?? null);
8069
+ setHoveredId(sec ? sec.dataset.ohwInstance ?? sec.dataset.ohwSection ?? null : null);
7554
8070
  };
7555
8071
  const onLeave = () => setHoveredId(null);
7556
8072
  document.addEventListener("mousemove", onMove, { passive: true });
@@ -7582,9 +8098,30 @@ function AiSectionOverlay({
7582
8098
  },
7583
8099
  [postToParent2]
7584
8100
  );
7585
- const selectionRect = useLiveSectionRect(reviewId ? null : selectedId);
8101
+ const activeSelectionId = reviewId ? null : selectedId;
8102
+ const selectionRect = useLiveSectionRect(activeSelectionId);
7586
8103
  const reviewRect = useLiveSectionRect(reviewId);
7587
8104
  const hoverRect = useLiveSectionRect(reviewId || hoveredId === selectedId ? null : hoveredId);
8105
+ (0, import_react8.useEffect)(() => {
8106
+ const selectedEl = activeSelectionId ? findSectionElement(activeSelectionId) : null;
8107
+ if (!activeSelectionId || !selectionRect || selectedEl && isChromeSection(selectedEl)) {
8108
+ postToParent2({ type: "ow:section-rect", instanceId: null, rect: null });
8109
+ return;
8110
+ }
8111
+ const { isFirst, isLast } = computeSectionBoundaryFlags(activeSelectionId);
8112
+ postToParent2({
8113
+ type: "ow:section-rect",
8114
+ instanceId: activeSelectionId,
8115
+ rect: {
8116
+ top: selectionRect.top + window.scrollY,
8117
+ left: selectionRect.left + window.scrollX,
8118
+ width: selectionRect.width,
8119
+ height: selectionRect.height
8120
+ },
8121
+ isFirst,
8122
+ isLast
8123
+ });
8124
+ }, [activeSelectionId, selectionRect, postToParent2]);
7588
8125
  return /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(import_jsx_runtime17.Fragment, { children: [
7589
8126
  hoverRect && /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
7590
8127
  "div",
@@ -7635,13 +8172,16 @@ function AiSectionOverlay({
7635
8172
  border: `2px solid ${PRIMARY2}`,
7636
8173
  borderRadius: edgeAwareRadius(reviewRect),
7637
8174
  zIndex: 2147483200,
7638
- // The veil itself: swallows clicks so the section stays locked until decided.
8175
+ // The veil itself: swallows clicks so the section stays locked until decided. This
8176
+ // stopPropagation only guards the bubble phase; the bridge's capture-phase click
8177
+ // handlers additionally bail on `[data-ohw-ai-review]`, without which a click on
8178
+ // Accept/Discard resolves to the media beneath and opens the file picker.
7639
8179
  background: "rgba(8, 133, 254, 0.04)",
7640
8180
  pointerEvents: "auto",
7641
8181
  cursor: "default"
7642
8182
  },
7643
8183
  onClick: (e) => e.stopPropagation(),
7644
- children: /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(
8184
+ children: !reviewButtonsHidden && /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(
7645
8185
  "div",
7646
8186
  {
7647
8187
  style: {
@@ -7666,6 +8206,59 @@ function AiSectionOverlay({
7666
8206
 
7667
8207
  // src/lib/section-instances.ts
7668
8208
  var SECTION_ORDER_KEY = "__ohw_section_order";
8209
+ function topLevelSections() {
8210
+ return Array.from(document.querySelectorAll("[data-ohw-section]")).filter(
8211
+ (el) => !el.parentElement?.closest("[data-ohw-section]") && !isChromeSection(el)
8212
+ );
8213
+ }
8214
+ function instanceIdOf(el) {
8215
+ return el.dataset.ohwInstance ?? el.dataset.ohwSection ?? "";
8216
+ }
8217
+ function planSectionMove(instanceId, targetIndex, currentPath) {
8218
+ const sections = topLevelSections();
8219
+ const index = sections.findIndex((el) => instanceIdOf(el) === instanceId);
8220
+ if (index === -1) return null;
8221
+ const dragged = sections[index];
8222
+ const others = sections.filter((_, i) => i !== index);
8223
+ const clamped = Math.max(0, Math.min(targetIndex, others.length));
8224
+ const reordered = [...others.slice(0, clamped), dragged, ...others.slice(clamped)];
8225
+ return reordered.map((el, order) => ({
8226
+ instanceId: instanceIdOf(el),
8227
+ type: el.getAttribute("data-ohw-section") ?? "",
8228
+ order,
8229
+ pagePath: currentPath
8230
+ }));
8231
+ }
8232
+ function moveSectionInstance(instanceId, direction, currentPath) {
8233
+ const sections = topLevelSections();
8234
+ const index = sections.findIndex((el) => instanceIdOf(el) === instanceId);
8235
+ if (index === -1) return null;
8236
+ const siblingIndex = direction === "up" ? index - 1 : index + 1;
8237
+ if (siblingIndex < 0 || siblingIndex >= sections.length) return null;
8238
+ const entries = planSectionMove(instanceId, siblingIndex, currentPath);
8239
+ if (!entries) return null;
8240
+ applyPersistedOrder(entries);
8241
+ return entries;
8242
+ }
8243
+ function applyPersistedOrder(entries) {
8244
+ if (entries.length === 0) return;
8245
+ const sections = topLevelSections();
8246
+ if (sections.length === 0) return;
8247
+ const orderIndex = new Map(entries.map((e) => [e.instanceId, e.order]));
8248
+ const ordered = [...sections].sort((a, b) => {
8249
+ const aOrder = orderIndex.get(instanceIdOf(a));
8250
+ const bOrder = orderIndex.get(instanceIdOf(b));
8251
+ if (aOrder === void 0 && bOrder === void 0) return 0;
8252
+ if (aOrder === void 0) return 1;
8253
+ if (bOrder === void 0) return -1;
8254
+ return aOrder - bOrder;
8255
+ });
8256
+ let prev = null;
8257
+ for (const el of ordered) {
8258
+ if (prev) prev.after(el);
8259
+ prev = el;
8260
+ }
8261
+ }
7669
8262
  function getPageSectionOrderEntries(raw, currentPath) {
7670
8263
  if (!raw) return [];
7671
8264
  try {
@@ -7703,6 +8296,7 @@ function initSectionInstancesFromContent(content, currentPath) {
7703
8296
  rekeySectionSubtree(clone, entry.instanceId);
7704
8297
  original.insertAdjacentElement("afterend", clone);
7705
8298
  }
8299
+ applyPersistedOrder(entries);
7706
8300
  }
7707
8301
 
7708
8302
  // src/OhhwellsBridge.tsx
@@ -10247,8 +10841,13 @@ var GLYPH_SELECTOR = "svg, img";
10247
10841
  function referenceBox(slot) {
10248
10842
  const row = slot.closest("[data-ohw-socials-row]") ?? slot.closest("a")?.parentElement ?? null;
10249
10843
  const neighbour = row ? Array.from(row.querySelectorAll('[data-ohw-editable="icon"]')).find((el) => el !== slot) : null;
10250
- const source = (neighbour ?? slot).querySelector(GLYPH_SELECTOR);
10251
- const box = source?.getBoundingClientRect() ?? null;
10844
+ if (neighbour) {
10845
+ const box2 = neighbour.querySelector(GLYPH_SELECTOR)?.getBoundingClientRect();
10846
+ if (box2?.width && box2.height) return box2;
10847
+ }
10848
+ const own = slot.getBoundingClientRect();
10849
+ if (own.width && own.height) return own;
10850
+ const box = slot.querySelector(GLYPH_SELECTOR)?.getBoundingClientRect() ?? null;
10252
10851
  return box?.width && box.height ? box : null;
10253
10852
  }
10254
10853
  function iconMarkupSizedFor(slot, markup) {
@@ -10327,7 +10926,8 @@ function findSocialsRow(el) {
10327
10926
  }
10328
10927
  function isSocialsRow(el) {
10329
10928
  const anchors = Array.from(el.querySelectorAll("a"));
10330
- return anchors.length > 0 && anchors.every((anchor) => isSocialItem(anchor));
10929
+ if (!anchors.length || !anchors.some((anchor) => isSocialItem(anchor))) return false;
10930
+ return findSocialsRow(anchors[0]) === el;
10331
10931
  }
10332
10932
  var MAX_SOCIAL_ITEMS_PER_ROW = 7;
10333
10933
  function canAddSocialItem(row) {
@@ -10365,6 +10965,7 @@ function markSocialsRows(root = document) {
10365
10965
  });
10366
10966
  listSocialsRows(root).forEach((row) => {
10367
10967
  row.setAttribute(SOCIALS_ROW_ATTR, "");
10968
+ allowRowToWrap(row);
10368
10969
  const items = listSocialItems(row);
10369
10970
  const firstUnit = items[0] ? socialRowUnit(items[0], row) : null;
10370
10971
  if (firstUnit) rowTemplates.set(rowKeyOf(row), firstUnit.outerHTML);
@@ -10659,7 +11260,29 @@ function applySocialsDisplayToRow(row, display) {
10659
11260
  const icon = item.querySelector(ICON_SELECTOR);
10660
11261
  if (label) label.style.display = display.text ? "" : "none";
10661
11262
  if (icon) icon.style.display = display.icon ? "" : "none";
11263
+ layOutIconAndLabel(item, Boolean(display.text && display.icon));
10662
11264
  });
11265
+ allowRowToWrap(row);
11266
+ }
11267
+ function layOutIconAndLabel(item, on) {
11268
+ const hasBoth = Boolean(item.querySelector(ICON_SELECTOR)) && Boolean(socialLabelElement(item));
11269
+ if (!hasBoth) {
11270
+ item.style.display = "";
11271
+ item.style.alignItems = "";
11272
+ item.style.gap = "";
11273
+ item.style.whiteSpace = "";
11274
+ item.style.flex = "";
11275
+ return;
11276
+ }
11277
+ item.style.display = on ? "inline-flex" : "";
11278
+ item.style.alignItems = on ? "center" : "";
11279
+ item.style.gap = on ? "8px" : "";
11280
+ item.style.whiteSpace = on ? "nowrap" : "";
11281
+ item.style.flex = on ? "0 0 auto" : "";
11282
+ }
11283
+ function allowRowToWrap(row) {
11284
+ const display = row.ownerDocument.defaultView?.getComputedStyle(row).display ?? "";
11285
+ if (display === "flex" || display === "inline-flex") row.style.flexWrap = "wrap";
10663
11286
  }
10664
11287
  function applySocialsDisplayFromContent(content, root = document) {
10665
11288
  const stored = parseSocialsDisplay(content[SOCIALS_DISPLAY_KEY]);
@@ -11869,6 +12492,7 @@ function readLogoSizeState(content, placement) {
11869
12492
  function getLogoElement(el) {
11870
12493
  const marked = el.closest('[data-ohw-role="logo"], [data-ohw-logo]');
11871
12494
  if (marked) return marked;
12495
+ if (el.closest('[data-ohw-editable="icon"]')) return null;
11872
12496
  const root = el.closest("nav, [data-ohw-nav-root], footer");
11873
12497
  if (!root) return null;
11874
12498
  const anchor = el.closest("a");
@@ -12742,98 +13366,425 @@ function useNavItemDrag({
12742
13366
  };
12743
13367
  }
12744
13368
 
12745
- // src/ui/footer-container-chrome.tsx
12746
- var import_lucide_react15 = require("lucide-react");
12747
- var import_jsx_runtime29 = require("react/jsx-runtime");
12748
- function FooterContainerChrome({
12749
- rect,
12750
- onAdd,
12751
- addDisabled = false
12752
- }) {
12753
- const chromeGap = 6;
12754
- const buttonMargin = 7;
12755
- return /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(TooltipProvider, { delayDuration: 0, children: /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(
12756
- "div",
12757
- {
12758
- "data-ohw-footer-container-chrome": "",
12759
- "data-ohw-bridge": "",
12760
- className: "pointer-events-none fixed z-[2147483647]",
12761
- style: {
12762
- top: rect.top - chromeGap,
12763
- left: rect.left - chromeGap,
12764
- width: rect.width + chromeGap * 2,
12765
- height: rect.height + chromeGap * 2
12766
- },
12767
- children: /* @__PURE__ */ (0, import_jsx_runtime29.jsxs)(Tooltip, { children: [
12768
- /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(
12769
- "button",
12770
- {
12771
- type: "button",
12772
- "data-ohw-footer-add-button": "",
12773
- disabled: addDisabled,
12774
- className: "pointer-events-auto absolute left-1/2 flex size-7 -translate-x-1/2 -translate-y-full items-center justify-center rounded-[10px] border border-border bg-background p-0.5 shadow-sm transition-colors hover:bg-muted/80 disabled:pointer-events-none disabled:opacity-40",
12775
- style: { top: chromeGap - buttonMargin },
12776
- "aria-label": "Add item",
12777
- onMouseDown: (e) => {
12778
- e.preventDefault();
12779
- e.stopPropagation();
12780
- },
12781
- onClick: (e) => {
12782
- e.preventDefault();
12783
- e.stopPropagation();
12784
- if (addDisabled) return;
12785
- onAdd();
12786
- },
12787
- children: /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(import_lucide_react15.Plus, { className: "size-4 shrink-0 text-foreground", "aria-hidden": true })
12788
- }
12789
- ) }),
12790
- /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(TooltipContent, { side: "bottom", sideOffset: 9, children: addDisabled ? "Maximum columns reached" : "Add item" })
12791
- ] })
12792
- }
12793
- ) });
12794
- }
12795
-
12796
- // src/lib/carousel.ts
13369
+ // src/useSectionDrag.ts
12797
13370
  var import_react15 = require("react");
12798
- var CAROUSEL_ATTR = "data-ohw-carousel";
12799
- var CAROUSEL_VALUE_ATTR = "data-ohw-carousel-value";
12800
- var CAROUSEL_SLIDE_ATTR = "data-ohw-carousel-slide";
12801
- var CAROUSEL_EVENT = "ohw:carousel-change";
12802
- function listCarouselKeys() {
12803
- const keys = /* @__PURE__ */ new Set();
12804
- document.querySelectorAll(`[${CAROUSEL_ATTR}]`).forEach((el) => {
12805
- const key = el.getAttribute("data-ohw-key");
12806
- if (key) keys.add(key);
12807
- });
12808
- return [...keys];
13371
+
13372
+ // src/lib/section-dnd.ts
13373
+ function isFooterSection(el) {
13374
+ return el.dataset.ohwSection === "footer";
12809
13375
  }
12810
- function containersForKey(key) {
12811
- return Array.from(
12812
- document.querySelectorAll(`[${CAROUSEL_ATTR}][data-ohw-key="${CSS.escape(key)}"]`)
13376
+ function buildSectionDropSlots(draggedInstanceId) {
13377
+ const sections = topLevelSections().filter(
13378
+ (el) => instanceIdOf(el) !== draggedInstanceId && !isFooterSection(el)
12813
13379
  );
13380
+ const slots = [];
13381
+ if (sections.length === 0) return slots;
13382
+ const left = 0;
13383
+ const width = document.documentElement.clientWidth;
13384
+ for (let i = 0; i <= sections.length; i++) {
13385
+ let y;
13386
+ if (i === 0) {
13387
+ y = sections[0].getBoundingClientRect().top;
13388
+ } else if (i === sections.length) {
13389
+ y = sections[sections.length - 1].getBoundingClientRect().bottom;
13390
+ } else {
13391
+ const prev = sections[i - 1].getBoundingClientRect();
13392
+ const next = sections[i].getBoundingClientRect();
13393
+ y = (prev.bottom + next.top) / 2;
13394
+ }
13395
+ slots.push({ insertIndex: i, y, left, width });
13396
+ }
13397
+ return slots;
12814
13398
  }
12815
- function isCarouselKey(key) {
12816
- return containersForKey(key).length > 0;
12817
- }
12818
- function parseSlides(raw) {
12819
- if (!raw) return [];
12820
- try {
12821
- const parsed = JSON.parse(raw);
12822
- if (!Array.isArray(parsed)) return [];
12823
- return parsed.filter((s) => Boolean(s) && typeof s === "object").map((s) => ({ src: String(s.src ?? ""), alt: String(s.alt ?? "") }));
12824
- } catch {
12825
- return [];
13399
+ function hitTestSectionDropSlot(y, slots) {
13400
+ let best = null;
13401
+ for (const slot of slots) {
13402
+ const dist = Math.abs(y - slot.y);
13403
+ if (!best || dist < best.dist) best = { slot, dist };
12826
13404
  }
13405
+ return best?.slot ?? null;
12827
13406
  }
12828
- function readCarouselValue(key) {
12829
- const container = containersForKey(key)[0];
12830
- if (!container) return [];
12831
- const fromAttr = parseSlides(container.getAttribute(CAROUSEL_VALUE_ATTR));
12832
- if (fromAttr.length > 0) return fromAttr;
12833
- return Array.from(container.querySelectorAll(`[${CAROUSEL_SLIDE_ATTR}]`)).filter((slide) => slide.closest(`[${CAROUSEL_ATTR}]`) === container).sort((a, b) => slideIndex(a) - slideIndex(b)).map((slide) => {
12834
- const img = slide instanceof HTMLImageElement ? slide : slide.querySelector("img");
12835
- return { src: img?.src ?? "", alt: img?.alt ?? "" };
12836
- });
13407
+
13408
+ // src/useSectionDrag.ts
13409
+ var PRESS_THRESHOLD = 10;
13410
+ var EDGE_ZONE = 60;
13411
+ var MAX_AUTO_SCROLL_SPEED = 18;
13412
+ var SECTION_DRAG_EXCLUDED_SELECTOR = [
13413
+ "[data-ohw-toolbar]",
13414
+ "[data-ohw-edit-chrome]",
13415
+ "[data-ohw-item-interaction]",
13416
+ "[data-ohw-drag-handle-container]",
13417
+ '[data-slot="drag-handle"]',
13418
+ "[data-ohw-item-toolbar-anchor]",
13419
+ "[data-ohw-item-drag-surface]",
13420
+ "[data-ohw-more-menu]",
13421
+ '[data-slot="dropdown-menu-content"]',
13422
+ '[data-slot="dropdown-menu-item"]',
13423
+ "[data-ohw-state-toggle]",
13424
+ "[data-ohw-max-badge]",
13425
+ "[data-ohw-floating-panel]",
13426
+ "[data-ohw-section-picker]",
13427
+ "[data-ohw-link-popover-root]",
13428
+ "[data-ohw-link-modal-root]",
13429
+ "[data-ohw-link-page-dropdown]",
13430
+ '[data-slot="popover-content"]',
13431
+ '[data-slot="dialog-content"]',
13432
+ '[data-slot="dialog-overlay"]',
13433
+ "[data-ohw-ai-review]",
13434
+ "[data-ohw-editable]",
13435
+ "[data-ohw-editable-state]",
13436
+ "[contenteditable]",
13437
+ "[data-ohw-href-key]",
13438
+ "[data-ohw-footer-col]",
13439
+ "[data-ohw-social-label]",
13440
+ "a",
13441
+ "button",
13442
+ '[role="button"]',
13443
+ '[data-ohw-role="navbar-button"]',
13444
+ '[data-ohw-role="button"]',
13445
+ "[data-ohw-carousel]",
13446
+ "[data-ohw-carousel-value]",
13447
+ "[data-ohw-carousel-slide]",
13448
+ "[data-ohw-carousel-overlay]",
13449
+ "[data-ohw-media-chrome]",
13450
+ "[data-ohw-media-overlay]",
13451
+ "[data-ohw-media-skeleton]"
13452
+ ].join(", ");
13453
+ function visibleClip(ps) {
13454
+ if (!ps) return null;
13455
+ const top = Math.max(0, ps.headerH - ps.iframeOffsetTop);
13456
+ const bottom = Math.min(window.innerHeight, ps.headerH + ps.canvasH - ps.iframeOffsetTop);
13457
+ return { top, bottom: Math.max(top, bottom) };
13458
+ }
13459
+ function useSectionDrag({
13460
+ isEditMode,
13461
+ editContentRef,
13462
+ postToParentRef,
13463
+ parentScrollRef,
13464
+ navDragRef,
13465
+ footerDragRef,
13466
+ suppressNextClickRef,
13467
+ suppressClickUntilRef
13468
+ }) {
13469
+ const sectionDragRef = (0, import_react15.useRef)(null);
13470
+ const [sectionDropSlots, setSectionDropSlots] = (0, import_react15.useState)([]);
13471
+ const [activeSectionDropIndex, setActiveSectionDropIndex] = (0, import_react15.useState)(null);
13472
+ const [isSectionDragging, setIsSectionDragging] = (0, import_react15.useState)(false);
13473
+ const sectionPointerDragRef = (0, import_react15.useRef)(null);
13474
+ const autoScrollRafRef = (0, import_react15.useRef)(null);
13475
+ const autoScrollDeltaRef = (0, import_react15.useRef)(0);
13476
+ const stopAutoScroll = (0, import_react15.useCallback)(() => {
13477
+ if (autoScrollRafRef.current != null) {
13478
+ cancelAnimationFrame(autoScrollRafRef.current);
13479
+ autoScrollRafRef.current = null;
13480
+ }
13481
+ autoScrollDeltaRef.current = 0;
13482
+ }, []);
13483
+ const tickAutoScroll = (0, import_react15.useCallback)(() => {
13484
+ if (!sectionDragRef.current) {
13485
+ stopAutoScroll();
13486
+ return;
13487
+ }
13488
+ if (autoScrollDeltaRef.current !== 0) {
13489
+ postToParentRef.current({ type: "ow:request-scroll", deltaY: autoScrollDeltaRef.current });
13490
+ }
13491
+ autoScrollRafRef.current = requestAnimationFrame(tickAutoScroll);
13492
+ }, [postToParentRef, stopAutoScroll]);
13493
+ const updateAutoScroll = (0, import_react15.useCallback)(
13494
+ (clientY) => {
13495
+ const clip = visibleClip(parentScrollRef.current);
13496
+ let delta = 0;
13497
+ if (clip) {
13498
+ const distTop = clientY - clip.top;
13499
+ const distBottom = clip.bottom - clientY;
13500
+ if (distTop >= 0 && distTop < EDGE_ZONE) {
13501
+ delta = -MAX_AUTO_SCROLL_SPEED * (1 - distTop / EDGE_ZONE);
13502
+ } else if (distBottom >= 0 && distBottom < EDGE_ZONE) {
13503
+ delta = MAX_AUTO_SCROLL_SPEED * (1 - distBottom / EDGE_ZONE);
13504
+ }
13505
+ }
13506
+ autoScrollDeltaRef.current = delta;
13507
+ if (delta !== 0 && autoScrollRafRef.current == null) {
13508
+ autoScrollRafRef.current = requestAnimationFrame(tickAutoScroll);
13509
+ } else if (delta === 0) {
13510
+ stopAutoScroll();
13511
+ }
13512
+ },
13513
+ [parentScrollRef, stopAutoScroll, tickAutoScroll]
13514
+ );
13515
+ const clearSectionDragVisuals = (0, import_react15.useCallback)(() => {
13516
+ sectionDragRef.current?.draggedEl.removeAttribute("data-ohw-section-dragging");
13517
+ sectionDragRef.current = null;
13518
+ setSectionDropSlots([]);
13519
+ setActiveSectionDropIndex(null);
13520
+ setIsSectionDragging(false);
13521
+ stopAutoScroll();
13522
+ document.documentElement.removeAttribute("data-ohw-section-dragging-root");
13523
+ unlockItemDragInteraction();
13524
+ }, [stopAutoScroll]);
13525
+ const refreshSectionDragVisuals = (0, import_react15.useCallback)(
13526
+ (session, clientX, clientY) => {
13527
+ session.lastClientX = clientX;
13528
+ session.lastClientY = clientY;
13529
+ const slots = buildSectionDropSlots(session.instanceId);
13530
+ const activeSlot = hitTestSectionDropSlot(clientY, slots);
13531
+ session.activeSlot = activeSlot;
13532
+ setSectionDropSlots(slots);
13533
+ const activeIdx = activeSlot ? slots.findIndex((s) => s.insertIndex === activeSlot.insertIndex) : -1;
13534
+ setActiveSectionDropIndex(activeIdx >= 0 ? activeIdx : null);
13535
+ updateAutoScroll(clientY);
13536
+ },
13537
+ [updateAutoScroll]
13538
+ );
13539
+ const beginSectionDrag = (0, import_react15.useCallback)(
13540
+ (session) => {
13541
+ sectionDragRef.current = session;
13542
+ setIsSectionDragging(true);
13543
+ lockItemDuringDrag();
13544
+ document.documentElement.setAttribute("data-ohw-section-dragging-root", "");
13545
+ session.draggedEl.setAttribute("data-ohw-section-dragging", "");
13546
+ refreshSectionDragVisuals(session, session.lastClientX, session.lastClientY);
13547
+ },
13548
+ [refreshSectionDragVisuals]
13549
+ );
13550
+ const commitSectionDrag = (0, import_react15.useCallback)(() => {
13551
+ const session = sectionDragRef.current;
13552
+ if (!session) {
13553
+ clearSectionDragVisuals();
13554
+ return;
13555
+ }
13556
+ const slot = session.activeSlot ?? hitTestSectionDropSlot(session.lastClientY, buildSectionDropSlots(session.instanceId));
13557
+ const entries = slot ? planSectionMove(session.instanceId, slot.insertIndex, window.location.pathname) : null;
13558
+ if (!entries) {
13559
+ clearSectionDragVisuals();
13560
+ return;
13561
+ }
13562
+ const orderJson = JSON.stringify(entries);
13563
+ editContentRef.current = { ...editContentRef.current, [SECTION_ORDER_KEY]: orderJson };
13564
+ postToParentRef.current({ type: "ow:change", nodes: [{ key: SECTION_ORDER_KEY, text: orderJson }] });
13565
+ applyPersistedOrder(entries);
13566
+ clearSectionDragVisuals();
13567
+ requestAnimationFrame(() => {
13568
+ if (editContentRef.current[SECTION_ORDER_KEY] === orderJson) {
13569
+ applyPersistedOrder(entries);
13570
+ }
13571
+ requestAnimationFrame(() => {
13572
+ window.dispatchEvent(new Event("resize"));
13573
+ });
13574
+ });
13575
+ }, [clearSectionDragVisuals, editContentRef, postToParentRef]);
13576
+ const startSectionPressDrag = (0, import_react15.useCallback)(
13577
+ (el, clientX, clientY, pointerId) => {
13578
+ if (navDragRef.current || footerDragRef.current || sectionDragRef.current) return false;
13579
+ const instanceId = instanceIdOf(el);
13580
+ if (!instanceId) return false;
13581
+ sectionPointerDragRef.current = {
13582
+ el,
13583
+ instanceId,
13584
+ startX: clientX,
13585
+ startY: clientY,
13586
+ pointerId,
13587
+ started: false
13588
+ };
13589
+ return true;
13590
+ },
13591
+ [footerDragRef, navDragRef]
13592
+ );
13593
+ (0, import_react15.useEffect)(() => {
13594
+ if (!isEditMode) return;
13595
+ const onPointerDown = (e) => {
13596
+ if (e.button !== 0) return;
13597
+ if (navDragRef.current || footerDragRef.current || sectionDragRef.current) return;
13598
+ if (sectionPointerDragRef.current) return;
13599
+ const target = e.target;
13600
+ if (!(target instanceof HTMLElement)) return;
13601
+ if (target.closest(SECTION_DRAG_EXCLUDED_SELECTOR)) return;
13602
+ const sectionEl = target.closest("[data-ohw-section]");
13603
+ if (!sectionEl || isChromeSection(sectionEl) || sectionEl.dataset.ohwSection === "footer") return;
13604
+ if (!topLevelSections().includes(sectionEl)) return;
13605
+ startSectionPressDrag(sectionEl, e.clientX, e.clientY, e.pointerId);
13606
+ };
13607
+ const onPointerMove = (e) => {
13608
+ const pending = sectionPointerDragRef.current;
13609
+ if (!pending) return;
13610
+ if (pending.started) {
13611
+ e.preventDefault();
13612
+ clearTextSelection();
13613
+ const session = sectionDragRef.current;
13614
+ if (!session) return;
13615
+ refreshSectionDragVisuals(session, e.clientX, e.clientY);
13616
+ return;
13617
+ }
13618
+ const dx = e.clientX - pending.startX;
13619
+ const dy = e.clientY - pending.startY;
13620
+ if (dx * dx + dy * dy < PRESS_THRESHOLD * PRESS_THRESHOLD) return;
13621
+ e.preventDefault();
13622
+ pending.started = true;
13623
+ armItemPressDrag();
13624
+ clearTextSelection();
13625
+ try {
13626
+ document.body.setPointerCapture(pending.pointerId);
13627
+ } catch {
13628
+ }
13629
+ beginSectionDrag({
13630
+ instanceId: pending.instanceId,
13631
+ draggedEl: pending.el,
13632
+ lastClientX: e.clientX,
13633
+ lastClientY: e.clientY,
13634
+ activeSlot: null
13635
+ });
13636
+ };
13637
+ const endPointerDrag = (e) => {
13638
+ const pending = sectionPointerDragRef.current;
13639
+ sectionPointerDragRef.current = null;
13640
+ try {
13641
+ if (document.body.hasPointerCapture(e.pointerId)) {
13642
+ document.body.releasePointerCapture(e.pointerId);
13643
+ }
13644
+ } catch {
13645
+ }
13646
+ if (!pending) return;
13647
+ if (!pending.started) {
13648
+ unlockItemDragInteraction();
13649
+ return;
13650
+ }
13651
+ suppressNextClickRef.current = true;
13652
+ suppressClickUntilRef.current = Date.now() + 500;
13653
+ commitSectionDrag();
13654
+ };
13655
+ const onKeyDown = (e) => {
13656
+ if (e.key !== "Escape") return;
13657
+ if (!sectionDragRef.current && !sectionPointerDragRef.current) return;
13658
+ sectionPointerDragRef.current = null;
13659
+ clearSectionDragVisuals();
13660
+ };
13661
+ document.addEventListener("pointerdown", onPointerDown, true);
13662
+ document.addEventListener("pointermove", onPointerMove, true);
13663
+ document.addEventListener("pointerup", endPointerDrag, true);
13664
+ document.addEventListener("pointercancel", endPointerDrag, true);
13665
+ document.addEventListener("keydown", onKeyDown, true);
13666
+ return () => {
13667
+ document.removeEventListener("pointerdown", onPointerDown, true);
13668
+ document.removeEventListener("pointermove", onPointerMove, true);
13669
+ document.removeEventListener("pointerup", endPointerDrag, true);
13670
+ document.removeEventListener("pointercancel", endPointerDrag, true);
13671
+ document.removeEventListener("keydown", onKeyDown, true);
13672
+ unlockItemDragInteraction();
13673
+ stopAutoScroll();
13674
+ };
13675
+ }, [
13676
+ beginSectionDrag,
13677
+ clearSectionDragVisuals,
13678
+ commitSectionDrag,
13679
+ footerDragRef,
13680
+ isEditMode,
13681
+ navDragRef,
13682
+ refreshSectionDragVisuals,
13683
+ startSectionPressDrag,
13684
+ stopAutoScroll,
13685
+ suppressClickUntilRef,
13686
+ suppressNextClickRef
13687
+ ]);
13688
+ return {
13689
+ sectionDragRef,
13690
+ sectionDropSlots,
13691
+ activeSectionDropIndex,
13692
+ isSectionDragging
13693
+ };
13694
+ }
13695
+
13696
+ // src/ui/footer-container-chrome.tsx
13697
+ var import_lucide_react15 = require("lucide-react");
13698
+ var import_jsx_runtime29 = require("react/jsx-runtime");
13699
+ function FooterContainerChrome({
13700
+ rect,
13701
+ onAdd,
13702
+ addDisabled = false
13703
+ }) {
13704
+ const chromeGap = 6;
13705
+ const buttonMargin = 7;
13706
+ return /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(TooltipProvider, { delayDuration: 0, children: /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(
13707
+ "div",
13708
+ {
13709
+ "data-ohw-footer-container-chrome": "",
13710
+ "data-ohw-bridge": "",
13711
+ className: "pointer-events-none fixed z-[2147483647]",
13712
+ style: {
13713
+ top: rect.top - chromeGap,
13714
+ left: rect.left - chromeGap,
13715
+ width: rect.width + chromeGap * 2,
13716
+ height: rect.height + chromeGap * 2
13717
+ },
13718
+ children: /* @__PURE__ */ (0, import_jsx_runtime29.jsxs)(Tooltip, { children: [
13719
+ /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(
13720
+ "button",
13721
+ {
13722
+ type: "button",
13723
+ "data-ohw-footer-add-button": "",
13724
+ disabled: addDisabled,
13725
+ className: "pointer-events-auto absolute left-1/2 flex size-7 -translate-x-1/2 -translate-y-full items-center justify-center rounded-[10px] border border-border bg-background p-0.5 shadow-sm transition-colors hover:bg-muted/80 disabled:pointer-events-none disabled:opacity-40",
13726
+ style: { top: chromeGap - buttonMargin },
13727
+ "aria-label": "Add item",
13728
+ onMouseDown: (e) => {
13729
+ e.preventDefault();
13730
+ e.stopPropagation();
13731
+ },
13732
+ onClick: (e) => {
13733
+ e.preventDefault();
13734
+ e.stopPropagation();
13735
+ if (addDisabled) return;
13736
+ onAdd();
13737
+ },
13738
+ children: /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(import_lucide_react15.Plus, { className: "size-4 shrink-0 text-foreground", "aria-hidden": true })
13739
+ }
13740
+ ) }),
13741
+ /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(TooltipContent, { side: "bottom", sideOffset: 9, children: addDisabled ? "Maximum columns reached" : "Add item" })
13742
+ ] })
13743
+ }
13744
+ ) });
13745
+ }
13746
+
13747
+ // src/lib/carousel.ts
13748
+ var import_react16 = require("react");
13749
+ var CAROUSEL_ATTR = "data-ohw-carousel";
13750
+ var CAROUSEL_VALUE_ATTR = "data-ohw-carousel-value";
13751
+ var CAROUSEL_SLIDE_ATTR = "data-ohw-carousel-slide";
13752
+ var CAROUSEL_EVENT = "ohw:carousel-change";
13753
+ function listCarouselKeys() {
13754
+ const keys = /* @__PURE__ */ new Set();
13755
+ document.querySelectorAll(`[${CAROUSEL_ATTR}]`).forEach((el) => {
13756
+ const key = el.getAttribute("data-ohw-key");
13757
+ if (key) keys.add(key);
13758
+ });
13759
+ return [...keys];
13760
+ }
13761
+ function containersForKey(key) {
13762
+ return Array.from(
13763
+ document.querySelectorAll(`[${CAROUSEL_ATTR}][data-ohw-key="${CSS.escape(key)}"]`)
13764
+ );
13765
+ }
13766
+ function isCarouselKey(key) {
13767
+ return containersForKey(key).length > 0;
13768
+ }
13769
+ function parseSlides(raw) {
13770
+ if (!raw) return [];
13771
+ try {
13772
+ const parsed = JSON.parse(raw);
13773
+ if (!Array.isArray(parsed)) return [];
13774
+ return parsed.filter((s) => Boolean(s) && typeof s === "object").map((s) => ({ src: String(s.src ?? ""), alt: String(s.alt ?? "") }));
13775
+ } catch {
13776
+ return [];
13777
+ }
13778
+ }
13779
+ function readCarouselValue(key) {
13780
+ const container = containersForKey(key)[0];
13781
+ if (!container) return [];
13782
+ const fromAttr = parseSlides(container.getAttribute(CAROUSEL_VALUE_ATTR));
13783
+ if (fromAttr.length > 0) return fromAttr;
13784
+ return Array.from(container.querySelectorAll(`[${CAROUSEL_SLIDE_ATTR}]`)).filter((slide) => slide.closest(`[${CAROUSEL_ATTR}]`) === container).sort((a, b) => slideIndex(a) - slideIndex(b)).map((slide) => {
13785
+ const img = slide instanceof HTMLImageElement ? slide : slide.querySelector("img");
13786
+ return { src: img?.src ?? "", alt: img?.alt ?? "" };
13787
+ });
12837
13788
  }
12838
13789
  function slideIndex(el) {
12839
13790
  const raw = el.getAttribute(CAROUSEL_SLIDE_ATTR);
@@ -12856,8 +13807,8 @@ function applyCarouselNode(key, val) {
12856
13807
  return true;
12857
13808
  }
12858
13809
  function useOhwCarousel(key, initial) {
12859
- const [images, setImages] = (0, import_react15.useState)(initial);
12860
- (0, import_react15.useEffect)(() => {
13810
+ const [images, setImages] = (0, import_react16.useState)(initial);
13811
+ (0, import_react16.useEffect)(() => {
12861
13812
  const el = document.querySelector(
12862
13813
  `[${CAROUSEL_ATTR}][data-ohw-key="${CSS.escape(key)}"]`
12863
13814
  );
@@ -12935,7 +13886,7 @@ function collectEditableNodes(extraContent, root = document) {
12935
13886
  nodes.push({ key, type: "link", text: href });
12936
13887
  }
12937
13888
  if (extraContent) {
12938
- for (const key of [NAV_ORDER_KEY, FOOTER_ORDER_KEY, NAV_COUNT_KEY]) {
13889
+ for (const key of [NAV_ORDER_KEY, FOOTER_ORDER_KEY, NAV_COUNT_KEY, SECTION_ORDER_KEY]) {
12939
13890
  const text = extraContent[key];
12940
13891
  if (typeof text === "string" && text.length > 0) {
12941
13892
  nodes.push({ key, type: "meta", text });
@@ -12972,6 +13923,18 @@ function collectEditableNodes(extraContent, root = document) {
12972
13923
  }
12973
13924
  if (extraContent && !isScoped) {
12974
13925
  applyNavFooterDeleteOverrides(byKey, extraContent);
13926
+ for (const key of LOGO_IMAGE_KEYS) {
13927
+ if (!(key in extraContent)) continue;
13928
+ byKey.set(key, { key, type: "image", text: extraContent[key] ?? "" });
13929
+ }
13930
+ for (const key of [LOGO_PLACEHOLDER_KEY, LOGO_ALT_KEY]) {
13931
+ if (!(key in extraContent)) continue;
13932
+ byKey.set(key, { key, type: "meta", text: extraContent[key] ?? "" });
13933
+ }
13934
+ for (const key of LOGO_SIZE_KEYS) {
13935
+ if (!(key in extraContent)) continue;
13936
+ byKey.set(key, { key, type: "meta", text: extraContent[key] ?? "" });
13937
+ }
12975
13938
  }
12976
13939
  return Array.from(byKey.values());
12977
13940
  }
@@ -13376,6 +14339,7 @@ function fadeInImageElement(img, onReady) {
13376
14339
  function applyEditableImageSrc(img, url) {
13377
14340
  img.removeAttribute("srcset");
13378
14341
  img.removeAttribute("sizes");
14342
+ if (img.loading === "lazy") img.loading = "eager";
13379
14343
  img.src = url;
13380
14344
  }
13381
14345
  function fadeInBgImage(el, url, onReady) {
@@ -13440,21 +14404,10 @@ function parseSchedulingInsertAfter(insertAfter) {
13440
14404
  insertBefore: insertAfter.slice(idx + INSERT_BEFORE_MARKER.length)
13441
14405
  };
13442
14406
  }
13443
- function resolveSchedulingInsert(insertAfter, explicitInsertBefore) {
13444
- const parsed = parseSchedulingInsertAfter(insertAfter);
13445
- const insertBefore = explicitInsertBefore ?? parsed.insertBefore;
13446
- const effectiveInsertAfter = insertBefore && parsed.insertBefore === null ? `${insertAfter}${INSERT_BEFORE_MARKER}${insertBefore}` : insertAfter;
13447
- return { effectiveInsertAfter, insertBefore };
13448
- }
13449
- function getSchedulingMountPoint(insertAfter) {
13450
- const { anchor } = parseSchedulingInsertAfter(insertAfter);
13451
- let anchorEl = document.querySelector(`[data-ohw-section="${anchor}"]`);
13452
- if (!anchorEl && anchor === "scheduling") {
13453
- const widgets = Array.from(document.querySelectorAll('[data-ohw-section^="scheduling-"]'));
13454
- anchorEl = widgets.at(-1) ?? null;
13455
- }
13456
- if (!anchorEl) return null;
13457
- return anchorEl.closest('[data-ohw-section-container="scheduling"]') ?? anchorEl;
14407
+ function resolveEntryAnchor(entry) {
14408
+ if (entry.anchorId !== void 0) return { anchorId: entry.anchorId, beforeId: entry.beforeId ?? null };
14409
+ const parsed = parseSchedulingInsertAfter(entry.insertAfter);
14410
+ return { anchorId: parsed.anchor, beforeId: parsed.insertBefore };
13458
14411
  }
13459
14412
  function schedulingMountDepth(insertAfter) {
13460
14413
  if (!insertAfter.includes(INSERT_BEFORE_MARKER)) return 0;
@@ -13471,8 +14424,7 @@ function getPageSchedulingEntries(raw) {
13471
14424
  }
13472
14425
  }
13473
14426
  function isSchedulingWidgetMissing(entry) {
13474
- const { effectiveInsertAfter } = resolveSchedulingInsert(entry.insertAfter);
13475
- return !document.querySelector(`[data-ohw-section="${schedulingSectionId(effectiveInsertAfter)}"]`);
14427
+ return !document.querySelector(`[data-ohw-section="${schedulingSectionId(entry.insertAfter)}"]`);
13476
14428
  }
13477
14429
  function hasMissingSchedulingWidgets(entries) {
13478
14430
  return entries.some(isSchedulingWidgetMissing);
@@ -13502,16 +14454,17 @@ function initSectionsFromContent(content, removeExisting = false) {
13502
14454
  } catch {
13503
14455
  }
13504
14456
  }
13505
- function mountSchedulingWidget(insertAfter, notifyOnConnect = false, scheduleId, explicitInsertBefore) {
13506
- const { effectiveInsertAfter, insertBefore } = resolveSchedulingInsert(insertAfter, explicitInsertBefore);
13507
- const sectionId = schedulingSectionId(effectiveInsertAfter);
14457
+ function mountSchedulingWidget(anchorId, notifyOnConnect = false, scheduleId, beforeId, existingWidgetId) {
14458
+ const widgetId = existingWidgetId ?? (beforeId ? `${anchorId}${INSERT_BEFORE_MARKER}${beforeId}` : anchorId);
14459
+ const sectionId = schedulingSectionId(widgetId);
13508
14460
  if (document.querySelector(`[data-ohw-section="${sectionId}"]`)) return false;
13509
- const mountPoint = getSchedulingMountPoint(effectiveInsertAfter);
13510
- if (!mountPoint) return false;
14461
+ const anchorEl = document.querySelector(`[data-ohw-section="${anchorId}"]`);
14462
+ if (!anchorEl) return false;
14463
+ const mountPoint = anchorEl.closest('[data-ohw-section-container="scheduling"]') ?? anchorEl;
13511
14464
  const container = document.createElement("div");
13512
14465
  container.dataset.ohwSectionContainer = "scheduling";
13513
- if (insertBefore) {
13514
- const beforeAnchor = document.querySelector(`[data-ohw-section="${insertBefore}"]`);
14466
+ if (beforeId) {
14467
+ const beforeAnchor = document.querySelector(`[data-ohw-section="${beforeId}"]`);
13515
14468
  const beforePoint = beforeAnchor?.closest('[data-ohw-section-container="scheduling"]') ?? beforeAnchor;
13516
14469
  if (!beforePoint) return false;
13517
14470
  beforePoint.insertAdjacentElement("beforebegin", container);
@@ -13522,19 +14475,25 @@ function mountSchedulingWidget(insertAfter, notifyOnConnect = false, scheduleId,
13522
14475
  }
13523
14476
  tail.insertAdjacentElement("afterend", container);
13524
14477
  }
13525
- const root = (0, import_client2.createRoot)(container);
13526
- (0, import_react_dom3.flushSync)(() => {
13527
- root.render(
13528
- /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
13529
- SchedulingWidget,
13530
- {
13531
- notifyOnConnect,
13532
- initialScheduleId: scheduleId,
13533
- insertAfter: effectiveInsertAfter
13534
- }
13535
- )
13536
- );
13537
- });
14478
+ try {
14479
+ const root = (0, import_client2.createRoot)(container);
14480
+ (0, import_react_dom3.flushSync)(() => {
14481
+ root.render(
14482
+ /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
14483
+ SchedulingWidget,
14484
+ {
14485
+ notifyOnConnect,
14486
+ initialScheduleId: scheduleId,
14487
+ insertAfter: widgetId
14488
+ }
14489
+ )
14490
+ );
14491
+ });
14492
+ } catch (err) {
14493
+ console.error("[ow:scheduling] render threw", err);
14494
+ container.remove();
14495
+ return false;
14496
+ }
13538
14497
  const tracker = getSectionsTracker();
13539
14498
  let sections = [];
13540
14499
  try {
@@ -13542,10 +14501,12 @@ function mountSchedulingWidget(insertAfter, notifyOnConnect = false, scheduleId,
13542
14501
  } catch {
13543
14502
  }
13544
14503
  const inEditor = typeof window !== "undefined" && window.self !== window.top;
13545
- if (inEditor && !sections.find((s) => s.type === "scheduling" && s.insertAfter === effectiveInsertAfter)) {
14504
+ if (inEditor && !sections.find((s) => s.type === "scheduling" && s.insertAfter === widgetId)) {
13546
14505
  sections.push({
13547
14506
  type: "scheduling",
13548
- insertAfter: effectiveInsertAfter,
14507
+ insertAfter: widgetId,
14508
+ anchorId,
14509
+ beforeId: beforeId ?? null,
13549
14510
  pagePath: window.location.pathname,
13550
14511
  ...scheduleId ? { scheduleId } : {}
13551
14512
  });
@@ -13559,7 +14520,8 @@ function mountSchedulingEntries(entries, notifyOnConnect = false) {
13559
14520
  for (let i = pending.length - 1; i >= 0; i--) {
13560
14521
  const entry = pending[i];
13561
14522
  const shouldNotify = typeof notifyOnConnect === "function" ? notifyOnConnect(entry) : notifyOnConnect;
13562
- if (mountSchedulingWidget(entry.insertAfter, shouldNotify, entry.scheduleId)) {
14523
+ const { anchorId, beforeId } = resolveEntryAnchor(entry);
14524
+ if (mountSchedulingWidget(anchorId, shouldNotify, entry.scheduleId ?? null, beforeId, entry.insertAfter)) {
13563
14525
  pending.splice(i, 1);
13564
14526
  }
13565
14527
  }
@@ -13707,6 +14669,13 @@ function isInsideLinkEditor(target) {
13707
14669
  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"]')
13708
14670
  );
13709
14671
  }
14672
+ function isInsideFloatingPanel(target) {
14673
+ return Boolean(target.closest("[data-ohw-floating-panel]"));
14674
+ }
14675
+ function isPointOverFloatingPanel(clientX, clientY) {
14676
+ const el = document.elementFromPoint(clientX, clientY);
14677
+ return Boolean(el instanceof Element && el.closest("[data-ohw-floating-panel]"));
14678
+ }
13710
14679
  function getHrefKeyFromElement(el) {
13711
14680
  if (!el) return null;
13712
14681
  const anchor = el.closest("[data-ohw-href-key]");
@@ -13952,7 +14921,7 @@ function getNavigationSelectionParent(el) {
13952
14921
  if (!isFooterLinksContainer(el) && (el.hasAttribute("data-ohw-footer-col") || el.hasAttribute("data-ohw-footer-column") || isInferredFooterGroup2(el))) {
13953
14922
  return getFooterLinksContainer();
13954
14923
  }
13955
- if (el.hasAttribute("data-ohw-nav-container") || el.hasAttribute("data-ohw-nav-drawer") || isFooterLinksContainer(el)) {
14924
+ 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)) {
13956
14925
  return getNavigationRoot(el);
13957
14926
  }
13958
14927
  return null;
@@ -14421,9 +15390,9 @@ function FloatingToolbar({
14421
15390
  showEditLink,
14422
15391
  onEditLink
14423
15392
  }) {
14424
- const localRef = import_react16.default.useRef(null);
14425
- const [measuredW, setMeasuredW] = import_react16.default.useState(330);
14426
- const setRefs = import_react16.default.useCallback(
15393
+ const localRef = import_react17.default.useRef(null);
15394
+ const [measuredW, setMeasuredW] = import_react17.default.useState(330);
15395
+ const setRefs = import_react17.default.useCallback(
14427
15396
  (node) => {
14428
15397
  localRef.current = node;
14429
15398
  if (typeof elRef === "function") elRef(node);
@@ -14435,7 +15404,7 @@ function FloatingToolbar({
14435
15404
  },
14436
15405
  [elRef]
14437
15406
  );
14438
- import_react16.default.useLayoutEffect(() => {
15407
+ import_react17.default.useLayoutEffect(() => {
14439
15408
  const node = localRef.current;
14440
15409
  if (!node) return;
14441
15410
  const update = () => {
@@ -14461,7 +15430,7 @@ function FloatingToolbar({
14461
15430
  pointerEvents: "auto"
14462
15431
  },
14463
15432
  children: /* @__PURE__ */ (0, import_jsx_runtime33.jsxs)(CustomToolbar, { children: [
14464
- TOOLBAR_GROUPS.map((btns, gi) => /* @__PURE__ */ (0, import_jsx_runtime33.jsxs)(import_react16.default.Fragment, { children: [
15433
+ TOOLBAR_GROUPS.map((btns, gi) => /* @__PURE__ */ (0, import_jsx_runtime33.jsxs)(import_react17.default.Fragment, { children: [
14465
15434
  gi > 0 && /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(CustomToolbarDivider, {}),
14466
15435
  btns.map((btn) => {
14467
15436
  const isActive = activeCommands.has(btn.cmd);
@@ -14545,6 +15514,45 @@ function StateToggle({
14545
15514
  );
14546
15515
  }
14547
15516
  var contentCache = /* @__PURE__ */ new Map();
15517
+ var fetchedContentPaths = /* @__PURE__ */ new Set();
15518
+ var OHW_LOADER_STYLE = {
15519
+ position: "fixed",
15520
+ inset: 0,
15521
+ background: "#fff",
15522
+ zIndex: 2147483646,
15523
+ display: "flex",
15524
+ alignItems: "center",
15525
+ justifyContent: "center"
15526
+ };
15527
+ function OhwLoaderSpinner() {
15528
+ return /* @__PURE__ */ (0, import_jsx_runtime33.jsxs)("svg", { width: "28", height: "28", viewBox: "0 0 28 28", fill: "none", "aria-hidden": true, children: [
15529
+ /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("circle", { cx: "14", cy: "14", r: "11", stroke: "#E7E5E4", strokeWidth: "3" }),
15530
+ /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
15531
+ "circle",
15532
+ {
15533
+ cx: "14",
15534
+ cy: "14",
15535
+ r: "11",
15536
+ stroke: "#1C1917",
15537
+ strokeWidth: "3",
15538
+ strokeDasharray: "17 52",
15539
+ strokeLinecap: "round",
15540
+ children: /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
15541
+ "animateTransform",
15542
+ {
15543
+ attributeName: "transform",
15544
+ type: "rotate",
15545
+ from: "0 14 14",
15546
+ to: "360 14 14",
15547
+ dur: "0.7s",
15548
+ repeatCount: "indefinite"
15549
+ }
15550
+ )
15551
+ }
15552
+ )
15553
+ ] });
15554
+ }
15555
+ 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){}})();`;
14548
15556
  function resolveSubdomain(subdomainFromQuery) {
14549
15557
  if (subdomainFromQuery) return subdomainFromQuery;
14550
15558
  if (typeof window !== "undefined") {
@@ -14567,8 +15575,8 @@ function OhhwellsBridge() {
14567
15575
  const router = (0, import_navigation3.useRouter)();
14568
15576
  const searchParams = (0, import_navigation3.useSearchParams)();
14569
15577
  const isEditMode = isEditSessionActive();
14570
- const [bridgeRoot, setBridgeRoot] = (0, import_react16.useState)(null);
14571
- (0, import_react16.useEffect)(() => {
15578
+ const [bridgeRoot, setBridgeRoot] = (0, import_react17.useState)(null);
15579
+ (0, import_react17.useEffect)(() => {
14572
15580
  const figtreeFontId = "ohw-figtree-font";
14573
15581
  if (!document.getElementById(figtreeFontId)) {
14574
15582
  const preconnect1 = Object.assign(document.createElement("link"), { rel: "preconnect", href: "https://fonts.googleapis.com" });
@@ -14597,88 +15605,152 @@ function OhhwellsBridge() {
14597
15605
  const subdomain = resolveSubdomain(subdomainFromQuery);
14598
15606
  useLinkHrefGuardian(pathname, subdomain, isEditMode);
14599
15607
  useSavedLinkNavigation(isEditMode);
14600
- const postToParent2 = (0, import_react16.useCallback)((data) => {
15608
+ const postToParent2 = (0, import_react17.useCallback)((data) => {
14601
15609
  if (typeof window !== "undefined" && window.parent !== window) {
14602
15610
  window.parent.postMessage(data, "*");
14603
15611
  }
14604
15612
  }, []);
14605
- const [fetchState, setFetchState] = (0, import_react16.useState)("idle");
14606
- const autoSaveTimers = (0, import_react16.useRef)(/* @__PURE__ */ new Map());
14607
- const activeElRef = (0, import_react16.useRef)(null);
14608
- const pointerHeldRef = (0, import_react16.useRef)(false);
14609
- const selectedElRef = (0, import_react16.useRef)(null);
14610
- const selectedHrefKeyRef = (0, import_react16.useRef)(null);
14611
- const selectedFooterColAttrRef = (0, import_react16.useRef)(null);
14612
- const originalContentRef = (0, import_react16.useRef)(null);
14613
- const activeStateElRef = (0, import_react16.useRef)(null);
14614
- const parentScrollRef = (0, import_react16.useRef)(null);
14615
- const visibleViewportRef = (0, import_react16.useRef)(null);
14616
- const [dialogPortalContainer, setDialogPortalContainer] = (0, import_react16.useState)(null);
14617
- const attachVisibleViewport = (0, import_react16.useCallback)((node) => {
15613
+ const [fetchState, setFetchState] = (0, import_react17.useState)("idle");
15614
+ const autoSaveTimers = (0, import_react17.useRef)(/* @__PURE__ */ new Map());
15615
+ const activeElRef = (0, import_react17.useRef)(null);
15616
+ const pointerHeldRef = (0, import_react17.useRef)(false);
15617
+ const selectedElRef = (0, import_react17.useRef)(null);
15618
+ const selectedHrefKeyRef = (0, import_react17.useRef)(null);
15619
+ const selectedFooterColAttrRef = (0, import_react17.useRef)(null);
15620
+ const originalContentRef = (0, import_react17.useRef)(null);
15621
+ const activeStateElRef = (0, import_react17.useRef)(null);
15622
+ const parentScrollRef = (0, import_react17.useRef)(null);
15623
+ const visibleViewportRef = (0, import_react17.useRef)(null);
15624
+ const [dialogPortalContainer, setDialogPortalContainer] = (0, import_react17.useState)(null);
15625
+ const attachVisibleViewport = (0, import_react17.useCallback)((node) => {
14618
15626
  visibleViewportRef.current = node;
14619
15627
  setDialogPortalContainer(node);
14620
15628
  if (node) applyVisibleViewport(node, parentScrollRef.current);
14621
15629
  }, []);
14622
- const toolbarElRef = (0, import_react16.useRef)(null);
14623
- const glowElRef = (0, import_react16.useRef)(null);
14624
- const hoveredImageRef = (0, import_react16.useRef)(null);
14625
- const hoveredImageHasTextOverlapRef = (0, import_react16.useRef)(false);
14626
- const dragOverElRef = (0, import_react16.useRef)(null);
14627
- const [mediaHover, setMediaHover] = (0, import_react16.useState)(null);
14628
- const [carouselHover, setCarouselHover] = (0, import_react16.useState)(null);
14629
- const [uploadingRects, setUploadingRects] = (0, import_react16.useState)({});
14630
- const hoveredGapRef = (0, import_react16.useRef)(null);
14631
- const imageUnhoverTimerRef = (0, import_react16.useRef)(null);
14632
- const imageShowTimerRef = (0, import_react16.useRef)(null);
14633
- const editStylesRef = (0, import_react16.useRef)(null);
14634
- const activateRef = (0, import_react16.useRef)(() => {
14635
- });
14636
- const deactivateRef = (0, import_react16.useRef)(() => {
14637
- });
14638
- const selectRef = (0, import_react16.useRef)(() => {
14639
- });
14640
- const selectFrameRef = (0, import_react16.useRef)(() => {
14641
- });
14642
- const selectLogoRef = (0, import_react16.useRef)(() => {
14643
- });
14644
- const openLogoSizePanelRef = (0, import_react16.useRef)(() => {
14645
- });
14646
- const deselectRef = (0, import_react16.useRef)(() => {
14647
- });
14648
- const closeFloatingPanelOnlyRef = (0, import_react16.useRef)(() => {
14649
- });
14650
- const reselectNavigationItemRef = (0, import_react16.useRef)(() => {
14651
- });
14652
- const commitNavigationTextEditRef = (0, import_react16.useRef)(() => {
14653
- });
14654
- const handleDeleteSelectedRef = (0, import_react16.useRef)(() => false);
14655
- const runPendingDeleteUndoRef = (0, import_react16.useRef)(() => false);
14656
- const isFooterFrameSelectionRef = (0, import_react16.useRef)(false);
14657
- const refreshActiveCommandsRef = (0, import_react16.useRef)(() => {
14658
- });
14659
- const postToParentRef = (0, import_react16.useRef)(postToParent2);
14660
- postToParentRef.current = postToParent2;
14661
- const aiSectionApiRef = (0, import_react16.useRef)(null);
14662
- const sectionsLoadedRef = (0, import_react16.useRef)(false);
14663
- const pendingScheduleConfigRequests = (0, import_react16.useRef)([]);
14664
- const [toolbarRect, setToolbarRect] = (0, import_react16.useState)(null);
14665
- const [formPickRect, setFormPickRect] = (0, import_react16.useState)(null);
14666
- const formPickElRef = (0, import_react16.useRef)(null);
14667
- const [formViewState, setFormViewStateUi] = (0, import_react16.useState)("default");
14668
- const [formPickCount, setFormPickCount] = (0, import_react16.useState)(null);
14669
- const [formHoverRect, setFormHoverRect] = (0, import_react16.useState)(null);
14670
- const formHoverElRef = (0, import_react16.useRef)(null);
14671
- const [fieldPickRect, setFieldPickRect] = (0, import_react16.useState)(null);
14672
- const fieldPickElRef = (0, import_react16.useRef)(null);
14673
- const [fieldPickState, setFieldPickState] = (0, import_react16.useState)(null);
14674
- const [fieldTypePickerOpen, setFieldTypePickerOpen] = (0, import_react16.useState)(false);
14675
- const clearFormPick = (0, import_react16.useCallback)(() => {
14676
- const form = formPickElRef.current;
14677
- const editing = fieldPickElRef.current;
14678
- if (commitPlaceholderEdit(editing) && editing) {
14679
- const owner = editing.closest('[data-ohw-editable="form"]');
14680
- if (owner) persistFieldsRef.current(owner);
14681
- }
15630
+ const toolbarElRef = (0, import_react17.useRef)(null);
15631
+ const glowElRef = (0, import_react17.useRef)(null);
15632
+ const hoveredImageRef = (0, import_react17.useRef)(null);
15633
+ const hoveredImageHasTextOverlapRef = (0, import_react17.useRef)(false);
15634
+ const dragOverElRef = (0, import_react17.useRef)(null);
15635
+ const [mediaHover, setMediaHover] = (0, import_react17.useState)(null);
15636
+ const [selectedMedia, setSelectedMedia] = (0, import_react17.useState)(null);
15637
+ const selectedMediaElRef = (0, import_react17.useRef)(null);
15638
+ const clearMediaSelection = (0, import_react17.useCallback)(() => {
15639
+ const prev = selectedMediaElRef.current;
15640
+ selectedMediaElRef.current = null;
15641
+ setSelectedMedia(null);
15642
+ const sectionEl = prev?.closest("[data-ohw-section]") ?? null;
15643
+ if (sectionEl) {
15644
+ postToParentRef.current({
15645
+ type: "ow:section-selected",
15646
+ sectionId: sectionEl.dataset.ohwSection ?? null,
15647
+ sectionLabel: sectionEl.dataset.ohwSectionLabel ?? titleCaseSectionId(sectionEl.dataset.ohwSection ?? ""),
15648
+ key: null
15649
+ });
15650
+ }
15651
+ }, []);
15652
+ const clearMediaSelectionRef = (0, import_react17.useRef)(clearMediaSelection);
15653
+ clearMediaSelectionRef.current = clearMediaSelection;
15654
+ const selectMediaElement = (0, import_react17.useCallback)((el) => {
15655
+ const r2 = el.getBoundingClientRect();
15656
+ const video = el.dataset.ohwEditable === "video" ? el.querySelector("video") : null;
15657
+ selectedMediaElRef.current = el;
15658
+ setSelectedMedia({
15659
+ key: el.dataset.ohwKey ?? "",
15660
+ rect: { top: r2.top, left: r2.left, width: r2.width, height: r2.height },
15661
+ elementType: el.dataset.ohwEditable ?? "image",
15662
+ hasTextOverlap: false,
15663
+ isDragOver: false,
15664
+ ...video ? { videoAutoplay: video.autoplay, videoMuted: video.muted } : {}
15665
+ });
15666
+ const sectionEl = el.closest("[data-ohw-section]");
15667
+ aiSectionApiRef.current?.selectFromElement(el, { report: false });
15668
+ postToParentRef.current({
15669
+ type: "ow:section-selected",
15670
+ sectionId: sectionEl?.dataset.ohwSection ?? null,
15671
+ sectionLabel: sectionEl ? sectionEl.dataset.ohwSectionLabel ?? titleCaseSectionId(sectionEl.dataset.ohwSection ?? "") : null,
15672
+ key: el.dataset.ohwKey ?? null,
15673
+ // Display name for the pill — the raw key prettifies into fragments ("Img"); the
15674
+ // bridge knows what the node IS, so it names it.
15675
+ keyLabel: el.dataset.ohwEditable === "video" ? "Video" : el.dataset.ohwEditable === "bg-image" ? "Background" : "Image"
15676
+ });
15677
+ }, []);
15678
+ const selectMediaElementRef = (0, import_react17.useRef)(selectMediaElement);
15679
+ selectMediaElementRef.current = selectMediaElement;
15680
+ (0, import_react17.useEffect)(() => {
15681
+ if (!selectedMedia) return;
15682
+ const update = () => {
15683
+ const el = selectedMediaElRef.current;
15684
+ if (!el || !el.isConnected) {
15685
+ clearMediaSelection();
15686
+ return;
15687
+ }
15688
+ const r2 = el.getBoundingClientRect();
15689
+ setSelectedMedia(
15690
+ (prev) => prev ? { ...prev, rect: { top: r2.top, left: r2.left, width: r2.width, height: r2.height } } : prev
15691
+ );
15692
+ };
15693
+ window.addEventListener("scroll", update, true);
15694
+ window.addEventListener("resize", update);
15695
+ return () => {
15696
+ window.removeEventListener("scroll", update, true);
15697
+ window.removeEventListener("resize", update);
15698
+ };
15699
+ }, [selectedMedia !== null]);
15700
+ const [carouselHover, setCarouselHover] = (0, import_react17.useState)(null);
15701
+ const [uploadingRects, setUploadingRects] = (0, import_react17.useState)({});
15702
+ const hoveredGapRef = (0, import_react17.useRef)(null);
15703
+ const imageUnhoverTimerRef = (0, import_react17.useRef)(null);
15704
+ const imageShowTimerRef = (0, import_react17.useRef)(null);
15705
+ const editStylesRef = (0, import_react17.useRef)(null);
15706
+ const activateRef = (0, import_react17.useRef)(() => {
15707
+ });
15708
+ const deactivateRef = (0, import_react17.useRef)(() => {
15709
+ });
15710
+ const selectRef = (0, import_react17.useRef)(() => {
15711
+ });
15712
+ const selectFrameRef = (0, import_react17.useRef)(() => {
15713
+ });
15714
+ const selectLogoRef = (0, import_react17.useRef)(() => {
15715
+ });
15716
+ const openLogoSizePanelRef = (0, import_react17.useRef)(() => {
15717
+ });
15718
+ const deselectRef = (0, import_react17.useRef)(() => {
15719
+ });
15720
+ const closeFloatingPanelOnlyRef = (0, import_react17.useRef)(() => {
15721
+ });
15722
+ const reselectNavigationItemRef = (0, import_react17.useRef)(() => {
15723
+ });
15724
+ const commitNavigationTextEditRef = (0, import_react17.useRef)(() => {
15725
+ });
15726
+ const handleDeleteSelectedRef = (0, import_react17.useRef)(() => false);
15727
+ const runPendingDeleteUndoRef = (0, import_react17.useRef)(() => false);
15728
+ const isFooterFrameSelectionRef = (0, import_react17.useRef)(false);
15729
+ const refreshActiveCommandsRef = (0, import_react17.useRef)(() => {
15730
+ });
15731
+ const postToParentRef = (0, import_react17.useRef)(postToParent2);
15732
+ postToParentRef.current = postToParent2;
15733
+ const aiSectionApiRef = (0, import_react17.useRef)(null);
15734
+ const sectionsLoadedRef = (0, import_react17.useRef)(false);
15735
+ const pendingScheduleConfigRequests = (0, import_react17.useRef)([]);
15736
+ const [toolbarRect, setToolbarRect] = (0, import_react17.useState)(null);
15737
+ const [formPickRect, setFormPickRect] = (0, import_react17.useState)(null);
15738
+ const formPickElRef = (0, import_react17.useRef)(null);
15739
+ const [formViewState, setFormViewStateUi] = (0, import_react17.useState)("default");
15740
+ const [formPickCount, setFormPickCount] = (0, import_react17.useState)(null);
15741
+ const [formHoverRect, setFormHoverRect] = (0, import_react17.useState)(null);
15742
+ const formHoverElRef = (0, import_react17.useRef)(null);
15743
+ const [fieldPickRect, setFieldPickRect] = (0, import_react17.useState)(null);
15744
+ const fieldPickElRef = (0, import_react17.useRef)(null);
15745
+ const [fieldPickState, setFieldPickState] = (0, import_react17.useState)(null);
15746
+ const [fieldTypePickerOpen, setFieldTypePickerOpen] = (0, import_react17.useState)(false);
15747
+ const clearFormPick = (0, import_react17.useCallback)(() => {
15748
+ const form = formPickElRef.current;
15749
+ const editing = fieldPickElRef.current;
15750
+ if (commitPlaceholderEdit(editing) && editing) {
15751
+ const owner = editing.closest('[data-ohw-editable="form"]');
15752
+ if (owner) persistFieldsRef.current(owner);
15753
+ }
14682
15754
  if (form) {
14683
15755
  const key = formKeyOf(form);
14684
15756
  if (key) setFormViewState(form, key, "default", successInitialFor(form, key, editContentRef.current));
@@ -14692,7 +15764,7 @@ function OhhwellsBridge() {
14692
15764
  formPickElRef.current = null;
14693
15765
  setFormPickRect(null);
14694
15766
  }, []);
14695
- const clearFieldPick = (0, import_react16.useCallback)(() => {
15767
+ const clearFieldPick = (0, import_react17.useCallback)(() => {
14696
15768
  const wrapper = fieldPickElRef.current;
14697
15769
  if (commitPlaceholderEdit(wrapper) && wrapper) {
14698
15770
  const form = wrapper.closest('[data-ohw-editable="form"]');
@@ -14702,9 +15774,9 @@ function OhhwellsBridge() {
14702
15774
  setFieldPickRect(null);
14703
15775
  setFieldPickState(null);
14704
15776
  }, []);
14705
- const persistFieldsRef = (0, import_react16.useRef)(() => {
15777
+ const persistFieldsRef = (0, import_react17.useRef)(() => {
14706
15778
  });
14707
- const persistFields = (0, import_react16.useCallback)(
15779
+ const persistFields = (0, import_react17.useCallback)(
14708
15780
  (form) => {
14709
15781
  const key = formKeyOf(form);
14710
15782
  if (!key) return;
@@ -14715,7 +15787,7 @@ function OhhwellsBridge() {
14715
15787
  []
14716
15788
  );
14717
15789
  persistFieldsRef.current = persistFields;
14718
- const selectField = (0, import_react16.useCallback)((wrapper) => {
15790
+ const selectField = (0, import_react17.useCallback)((wrapper) => {
14719
15791
  if (fieldPickElRef.current && fieldPickElRef.current !== wrapper) {
14720
15792
  commitPlaceholderEdit(fieldPickElRef.current);
14721
15793
  }
@@ -14728,7 +15800,7 @@ function OhhwellsBridge() {
14728
15800
  setFieldPickState({ type: fieldTypeOf(wrapper), required: isFieldRequired(wrapper) });
14729
15801
  setFieldTypePickerOpen(false);
14730
15802
  }, []);
14731
- const withSelectedField = (0, import_react16.useCallback)(
15803
+ const withSelectedField = (0, import_react17.useCallback)(
14732
15804
  (run) => {
14733
15805
  const wrapper = fieldPickElRef.current;
14734
15806
  const form = formPickElRef.current;
@@ -14741,28 +15813,28 @@ function OhhwellsBridge() {
14741
15813
  },
14742
15814
  [persistFields]
14743
15815
  );
14744
- const handleFieldTypeChange = (0, import_react16.useCallback)(
15816
+ const handleFieldTypeChange = (0, import_react17.useCallback)(
14745
15817
  (type) => withSelectedField((_form, wrapper) => {
14746
15818
  applyFieldType(wrapper, type);
14747
15819
  selectField(wrapper);
14748
15820
  }),
14749
15821
  [selectField, withSelectedField]
14750
15822
  );
14751
- const handleFieldRequiredToggle = (0, import_react16.useCallback)(
15823
+ const handleFieldRequiredToggle = (0, import_react17.useCallback)(
14752
15824
  () => withSelectedField((_form, wrapper) => {
14753
15825
  setFieldRequired(wrapper, !isFieldRequired(wrapper));
14754
15826
  selectField(wrapper);
14755
15827
  }),
14756
15828
  [selectField, withSelectedField]
14757
15829
  );
14758
- const handleFieldDuplicate = (0, import_react16.useCallback)(
15830
+ const handleFieldDuplicate = (0, import_react17.useCallback)(
14759
15831
  () => withSelectedField((form, wrapper) => {
14760
15832
  const copy = duplicateField(form, wrapper);
14761
15833
  selectField(copy);
14762
15834
  }),
14763
15835
  [selectField, withSelectedField]
14764
15836
  );
14765
- const handleFieldDelete = (0, import_react16.useCallback)(
15837
+ const handleFieldDelete = (0, import_react17.useCallback)(
14766
15838
  () => withSelectedField((_form, wrapper) => {
14767
15839
  removeField(wrapper);
14768
15840
  clearFieldPick();
@@ -14770,7 +15842,7 @@ function OhhwellsBridge() {
14770
15842
  }),
14771
15843
  [clearFieldPick, withSelectedField]
14772
15844
  );
14773
- const handleAddField = (0, import_react16.useCallback)(
15845
+ const handleAddField = (0, import_react17.useCallback)(
14774
15846
  (type) => {
14775
15847
  const form = formPickElRef.current;
14776
15848
  if (!form) return;
@@ -14786,8 +15858,8 @@ function OhhwellsBridge() {
14786
15858
  },
14787
15859
  [persistFields, selectField]
14788
15860
  );
14789
- const fieldDragRef = (0, import_react16.useRef)(null);
14790
- const buildFieldDropSlots = (0, import_react16.useCallback)((form, draggedKey) => {
15861
+ const fieldDragRef = (0, import_react17.useRef)(null);
15862
+ const buildFieldDropSlots = (0, import_react17.useCallback)((form, draggedKey) => {
14791
15863
  const others = listFieldWrappers(form).filter((el) => fieldKeyOf(el) !== draggedKey);
14792
15864
  const slots = others.map((el) => {
14793
15865
  const rect = el.getBoundingClientRect();
@@ -14800,7 +15872,7 @@ function OhhwellsBridge() {
14800
15872
  }
14801
15873
  return slots;
14802
15874
  }, []);
14803
- const handleFieldDragStart = (0, import_react16.useCallback)(() => {
15875
+ const handleFieldDragStart = (0, import_react17.useCallback)(() => {
14804
15876
  const wrapper = fieldPickElRef.current;
14805
15877
  const form = formPickElRef.current;
14806
15878
  if (!wrapper || !form) return;
@@ -14809,18 +15881,18 @@ function OhhwellsBridge() {
14809
15881
  setFieldDragging(true);
14810
15882
  setFieldDropSlots(buildFieldDropSlots(form, key));
14811
15883
  }, [buildFieldDropSlots]);
14812
- const handleFieldDragEnd = (0, import_react16.useCallback)(() => {
15884
+ const handleFieldDragEnd = (0, import_react17.useCallback)(() => {
14813
15885
  fieldDragRef.current = null;
14814
15886
  setFieldDropIndex(null);
14815
15887
  setFieldDropSlots([]);
14816
15888
  setFieldDragging(false);
14817
15889
  }, []);
14818
- const [fieldDropIndex, setFieldDropIndex] = (0, import_react16.useState)(null);
14819
- const [fieldDropSlots, setFieldDropSlots] = (0, import_react16.useState)([]);
14820
- const [fieldDragging, setFieldDragging] = (0, import_react16.useState)(false);
14821
- const clearFormPickRef = (0, import_react16.useRef)(clearFormPick);
15890
+ const [fieldDropIndex, setFieldDropIndex] = (0, import_react17.useState)(null);
15891
+ const [fieldDropSlots, setFieldDropSlots] = (0, import_react17.useState)([]);
15892
+ const [fieldDragging, setFieldDragging] = (0, import_react17.useState)(false);
15893
+ const clearFormPickRef = (0, import_react17.useRef)(clearFormPick);
14822
15894
  clearFormPickRef.current = clearFormPick;
14823
- (0, import_react16.useEffect)(() => {
15895
+ (0, import_react17.useEffect)(() => {
14824
15896
  const el = fieldPickElRef.current;
14825
15897
  if (!el || fieldPickRect === null) return;
14826
15898
  const observer = new ResizeObserver(() => {
@@ -14829,7 +15901,7 @@ function OhhwellsBridge() {
14829
15901
  observer.observe(el);
14830
15902
  return () => observer.disconnect();
14831
15903
  }, [fieldPickRect !== null, fieldPickState]);
14832
- (0, import_react16.useEffect)(() => {
15904
+ (0, import_react17.useEffect)(() => {
14833
15905
  const el = formPickElRef.current;
14834
15906
  if (!el || formPickRect === null) return;
14835
15907
  const observer = new ResizeObserver(() => {
@@ -14838,25 +15910,25 @@ function OhhwellsBridge() {
14838
15910
  observer.observe(el);
14839
15911
  return () => observer.disconnect();
14840
15912
  }, [formPickRect !== null, formViewState]);
14841
- const [toolbarVariant, setToolbarVariant] = (0, import_react16.useState)("none");
14842
- const toolbarVariantRef = (0, import_react16.useRef)("none");
15913
+ const [toolbarVariant, setToolbarVariant] = (0, import_react17.useState)("none");
15914
+ const toolbarVariantRef = (0, import_react17.useRef)("none");
14843
15915
  toolbarVariantRef.current = toolbarVariant;
14844
- const [selectedIsCta, setSelectedIsCta] = (0, import_react16.useState)(false);
14845
- const [selectedIsSocial, setSelectedIsSocial] = (0, import_react16.useState)(false);
14846
- const [selectedIsSocialsRow, setSelectedIsSocialsRow] = (0, import_react16.useState)(false);
14847
- const [reorderHrefKey, setReorderHrefKey] = (0, import_react16.useState)(null);
14848
- const [reorderDragDisabled, setReorderDragDisabled] = (0, import_react16.useState)(false);
14849
- const [toggleState, setToggleState] = (0, import_react16.useState)(null);
14850
- const [maxBadge, setMaxBadge] = (0, import_react16.useState)(null);
14851
- const [activeCommands, setActiveCommands] = (0, import_react16.useState)(/* @__PURE__ */ new Set());
14852
- const [sectionGap, setSectionGap] = (0, import_react16.useState)(null);
14853
- const [toolbarShowEditLink, setToolbarShowEditLink] = (0, import_react16.useState)(false);
14854
- const hoveredNavContainerRef = (0, import_react16.useRef)(null);
14855
- const [hoveredNavContainerRect, setHoveredNavContainerRect] = (0, import_react16.useState)(null);
14856
- const hoveredItemElRef = (0, import_react16.useRef)(null);
14857
- const [hoveredItemRect, setHoveredItemRect] = (0, import_react16.useState)(null);
14858
- const [hoveredTextRect, setHoveredTextRect] = (0, import_react16.useState)(null);
14859
- (0, import_react16.useEffect)(() => {
15916
+ const [selectedIsCta, setSelectedIsCta] = (0, import_react17.useState)(false);
15917
+ const [selectedIsSocial, setSelectedIsSocial] = (0, import_react17.useState)(false);
15918
+ const [selectedIsSocialsRow, setSelectedIsSocialsRow] = (0, import_react17.useState)(false);
15919
+ const [reorderHrefKey, setReorderHrefKey] = (0, import_react17.useState)(null);
15920
+ const [reorderDragDisabled, setReorderDragDisabled] = (0, import_react17.useState)(false);
15921
+ const [toggleState, setToggleState] = (0, import_react17.useState)(null);
15922
+ const [maxBadge, setMaxBadge] = (0, import_react17.useState)(null);
15923
+ const [activeCommands, setActiveCommands] = (0, import_react17.useState)(/* @__PURE__ */ new Set());
15924
+ const [sectionGap, setSectionGap] = (0, import_react17.useState)(null);
15925
+ const [toolbarShowEditLink, setToolbarShowEditLink] = (0, import_react17.useState)(false);
15926
+ const hoveredNavContainerRef = (0, import_react17.useRef)(null);
15927
+ const [hoveredNavContainerRect, setHoveredNavContainerRect] = (0, import_react17.useState)(null);
15928
+ const hoveredItemElRef = (0, import_react17.useRef)(null);
15929
+ const [hoveredItemRect, setHoveredItemRect] = (0, import_react17.useState)(null);
15930
+ const [hoveredTextRect, setHoveredTextRect] = (0, import_react17.useState)(null);
15931
+ (0, import_react17.useEffect)(() => {
14860
15932
  const sync = () => {
14861
15933
  const el = document.querySelector(
14862
15934
  '[data-ohw-hovered]:not([contenteditable]):not([data-ohw-href-key]):not([data-ohw-editable="form"] *)'
@@ -14877,43 +15949,56 @@ function OhhwellsBridge() {
14877
15949
  });
14878
15950
  return () => observer.disconnect();
14879
15951
  }, []);
14880
- const siblingHintElRef = (0, import_react16.useRef)(null);
14881
- const [siblingHintRect, setSiblingHintRect] = (0, import_react16.useState)(null);
14882
- const [siblingHintRects, setSiblingHintRects] = (0, import_react16.useState)([]);
14883
- const [isItemDragging, setIsItemDragging] = (0, import_react16.useState)(false);
14884
- const [isFooterFrameSelection, setIsFooterFrameSelection] = (0, import_react16.useState)(false);
15952
+ const siblingHintElRef = (0, import_react17.useRef)(null);
15953
+ const [siblingHintRect, setSiblingHintRect] = (0, import_react17.useState)(null);
15954
+ const [siblingHintRects, setSiblingHintRects] = (0, import_react17.useState)([]);
15955
+ const [isItemDragging, setIsItemDragging] = (0, import_react17.useState)(false);
15956
+ const [isFooterFrameSelection, setIsFooterFrameSelection] = (0, import_react17.useState)(false);
14885
15957
  isFooterFrameSelectionRef.current = isFooterFrameSelection;
14886
- const [floatingPanel, setFloatingPanel] = (0, import_react16.useState)(null);
14887
- const floatingPanelOpenRef = (0, import_react16.useRef)(false);
14888
- floatingPanelOpenRef.current = floatingPanel !== null;
14889
- const [floatingPanelPos, setFloatingPanelPos] = (0, import_react16.useState)(null);
14890
- const [logoSizeDraft, setLogoSizeDraft] = (0, import_react16.useState)(null);
14891
- const [editorViewport, setEditorViewport] = (0, import_react16.useState)("desktop");
14892
- const [parentScrollSnap, setParentScrollSnap] = (0, import_react16.useState)(null);
14893
- const [navDropdownPreviewOpen, setNavDropdownPreviewOpen] = (0, import_react16.useState)(null);
14894
- const [footerHeadingVisible, setFooterHeadingVisible] = (0, import_react16.useState)(null);
14895
- const footerDragRef = (0, import_react16.useRef)(null);
14896
- const [footerDropSlots, setFooterDropSlots] = (0, import_react16.useState)([]);
14897
- const [activeFooterDropIndex, setActiveFooterDropIndex] = (0, import_react16.useState)(null);
14898
- const [draggedItemRect, setDraggedItemRect] = (0, import_react16.useState)(null);
14899
- const footerPointerDragRef = (0, import_react16.useRef)(null);
14900
- const suppressNextClickRef = (0, import_react16.useRef)(false);
14901
- const suppressClickUntilRef = (0, import_react16.useRef)(0);
14902
- const [linkPopover, setLinkPopover] = (0, import_react16.useState)(null);
14903
- const linkPopoverSessionRef = (0, import_react16.useRef)(null);
14904
- const addNavAfterAnchorRef = (0, import_react16.useRef)(null);
14905
- const editContentRef = (0, import_react16.useRef)({});
14906
- const aiSectionsRef = (0, import_react16.useRef)("");
14907
- const pendingDeleteUndoRef = (0, import_react16.useRef)(null);
14908
- const [sitePages, setSitePages] = (0, import_react16.useState)([]);
14909
- const [sectionsByPath, setSectionsByPath] = (0, import_react16.useState)({});
14910
- const sectionsPrefetchGenRef = (0, import_react16.useRef)(0);
14911
- const setLinkPopoverRef = (0, import_react16.useRef)(setLinkPopover);
14912
- const linkPopoverPanelRef = (0, import_react16.useRef)(null);
14913
- const linkPopoverOpenRef = (0, import_react16.useRef)(false);
14914
- const linkPopoverGraceUntilRef = (0, import_react16.useRef)(0);
15958
+ const [navDropdownPreviewOpen, setNavDropdownPreviewOpen] = (0, import_react17.useState)(null);
15959
+ const [footerHeadingVisible, setFooterHeadingVisible] = (0, import_react17.useState)(null);
15960
+ const footerDragRef = (0, import_react17.useRef)(null);
15961
+ const [footerDropSlots, setFooterDropSlots] = (0, import_react17.useState)([]);
15962
+ const [activeFooterDropIndex, setActiveFooterDropIndex] = (0, import_react17.useState)(null);
15963
+ const [draggedItemRect, setDraggedItemRect] = (0, import_react17.useState)(null);
15964
+ const footerPointerDragRef = (0, import_react17.useRef)(null);
15965
+ const suppressNextClickRef = (0, import_react17.useRef)(false);
15966
+ const suppressClickUntilRef = (0, import_react17.useRef)(0);
15967
+ const [linkPopover, setLinkPopover] = (0, import_react17.useState)(null);
15968
+ const linkPopoverSessionRef = (0, import_react17.useRef)(null);
15969
+ const addNavAfterAnchorRef = (0, import_react17.useRef)(null);
15970
+ const editContentRef = (0, import_react17.useRef)({});
15971
+ const aiSectionsRef = (0, import_react17.useRef)("");
15972
+ const brandKitRef = (0, import_react17.useRef)("");
15973
+ const stylesRef = (0, import_react17.useRef)("");
15974
+ const pendingDeleteUndoRef = (0, import_react17.useRef)(null);
15975
+ const [floatingPanel, setFloatingPanel] = (0, import_react17.useState)(null);
15976
+ const floatingPanelOpenRef = (0, import_react17.useRef)(false);
15977
+ const setFloatingPanelRef = (0, import_react17.useRef)(setFloatingPanel);
15978
+ const [floatingPanelPos, setFloatingPanelPos] = (0, import_react17.useState)(null);
15979
+ const [logoSizeDraft, setLogoSizeDraft] = (0, import_react17.useState)(null);
15980
+ const [editorViewport, setEditorViewport] = (0, import_react17.useState)("desktop");
15981
+ const [parentScrollSnap, setParentScrollSnap] = (0, import_react17.useState)(null);
15982
+ const [sitePages, setSitePages] = (0, import_react17.useState)([]);
15983
+ const [sectionsByPath, setSectionsByPath] = (0, import_react17.useState)({});
15984
+ const sectionsPrefetchGenRef = (0, import_react17.useRef)(0);
15985
+ const setLinkPopoverRef = (0, import_react17.useRef)(setLinkPopover);
15986
+ const linkPopoverPanelRef = (0, import_react17.useRef)(null);
15987
+ const linkPopoverOpenRef = (0, import_react17.useRef)(false);
15988
+ const linkPopoverGraceUntilRef = (0, import_react17.useRef)(0);
14915
15989
  setLinkPopoverRef.current = setLinkPopover;
15990
+ setFloatingPanelRef.current = setFloatingPanel;
14916
15991
  linkPopoverSessionRef.current = linkPopover;
15992
+ floatingPanelOpenRef.current = Boolean(floatingPanel);
15993
+ (0, import_react17.useEffect)(() => {
15994
+ const syncViewport = () => {
15995
+ const next = window.innerWidth <= 480 ? "mobile" : "desktop";
15996
+ setEditorViewport((prev) => prev === next ? prev : next);
15997
+ };
15998
+ syncViewport();
15999
+ window.addEventListener("resize", syncViewport);
16000
+ return () => window.removeEventListener("resize", syncViewport);
16001
+ }, []);
14917
16002
  const {
14918
16003
  navDragRef,
14919
16004
  navDropSlots,
@@ -14946,10 +16031,20 @@ function OhhwellsBridge() {
14946
16031
  getNavigationItemAnchor,
14947
16032
  isDragHandleDisabled
14948
16033
  });
16034
+ const { sectionDropSlots, activeSectionDropIndex, isSectionDragging } = useSectionDrag({
16035
+ isEditMode,
16036
+ editContentRef,
16037
+ postToParentRef,
16038
+ parentScrollRef,
16039
+ navDragRef,
16040
+ footerDragRef,
16041
+ suppressNextClickRef,
16042
+ suppressClickUntilRef
16043
+ });
14949
16044
  const bumpLinkPopoverGrace = () => {
14950
16045
  linkPopoverGraceUntilRef.current = Date.now() + 350;
14951
16046
  };
14952
- const runSectionsPrefetch = (0, import_react16.useCallback)((pages) => {
16047
+ const runSectionsPrefetch = (0, import_react17.useCallback)((pages) => {
14953
16048
  if (!isEditMode || shouldUseDevFixtures() || pages.length === 0) return;
14954
16049
  const gen = ++sectionsPrefetchGenRef.current;
14955
16050
  const paths = pages.map((p) => p.path);
@@ -14968,9 +16063,9 @@ function OhhwellsBridge() {
14968
16063
  );
14969
16064
  });
14970
16065
  }, [isEditMode, pathname]);
14971
- const runSectionsPrefetchRef = (0, import_react16.useRef)(runSectionsPrefetch);
16066
+ const runSectionsPrefetchRef = (0, import_react17.useRef)(runSectionsPrefetch);
14972
16067
  runSectionsPrefetchRef.current = runSectionsPrefetch;
14973
- (0, import_react16.useEffect)(() => {
16068
+ (0, import_react17.useEffect)(() => {
14974
16069
  if (!linkPopover) {
14975
16070
  document.documentElement.removeAttribute("data-ohw-link-popover-open");
14976
16071
  return;
@@ -14998,7 +16093,7 @@ function OhhwellsBridge() {
14998
16093
  document.documentElement.removeAttribute("data-ohw-link-popover-open");
14999
16094
  };
15000
16095
  }, [linkPopover, postToParent2]);
15001
- (0, import_react16.useEffect)(() => {
16096
+ (0, import_react17.useEffect)(() => {
15002
16097
  if (!isEditMode) return;
15003
16098
  const useFixtures = shouldUseDevFixtures();
15004
16099
  if (useFixtures) {
@@ -15022,14 +16117,14 @@ function OhhwellsBridge() {
15022
16117
  if (!useFixtures) postToParent2({ type: "ow:request-site-pages" });
15023
16118
  return () => window.removeEventListener("message", onSitePages);
15024
16119
  }, [isEditMode, postToParent2]);
15025
- (0, import_react16.useEffect)(() => {
16120
+ (0, import_react17.useEffect)(() => {
15026
16121
  if (!isEditMode || shouldUseDevFixtures()) return;
15027
16122
  void loadAllSectionsManifest().then((manifest) => {
15028
16123
  if (Object.keys(manifest).length === 0) return;
15029
16124
  setSectionsByPath((prev) => ({ ...manifest, ...prev }));
15030
16125
  });
15031
16126
  }, [isEditMode]);
15032
- (0, import_react16.useEffect)(() => {
16127
+ (0, import_react17.useEffect)(() => {
15033
16128
  const update = () => {
15034
16129
  const el = activeElRef.current ?? selectedElRef.current;
15035
16130
  if (el) setToolbarRect(getEditMeasureEl(el).getBoundingClientRect());
@@ -15053,10 +16148,10 @@ function OhhwellsBridge() {
15053
16148
  vvp.removeEventListener("resize", update);
15054
16149
  };
15055
16150
  }, []);
15056
- const refreshStateRules = (0, import_react16.useCallback)(() => {
16151
+ const refreshStateRules = (0, import_react17.useCallback)(() => {
15057
16152
  editStylesRef.current?.forceHover && (editStylesRef.current.forceHover.textContent = collectStateRules());
15058
16153
  }, []);
15059
- const processConfigRequest = (0, import_react16.useCallback)((insertAfterVal) => {
16154
+ const processConfigRequest = (0, import_react17.useCallback)((insertAfterVal) => {
15060
16155
  const tracker = getSectionsTracker();
15061
16156
  let entries = [];
15062
16157
  try {
@@ -15079,7 +16174,7 @@ function OhhwellsBridge() {
15079
16174
  }
15080
16175
  window.postMessage({ type: "ow:schedule-config", insertAfter: insertAfterVal, scheduleId: null }, "*");
15081
16176
  }, [isEditMode]);
15082
- const deactivate = (0, import_react16.useCallback)(() => {
16177
+ const deactivate = (0, import_react17.useCallback)(() => {
15083
16178
  const el = activeElRef.current;
15084
16179
  if (!el) return;
15085
16180
  const isFormBlock = el.dataset.ohwEditable === "form";
@@ -15120,12 +16215,12 @@ function OhhwellsBridge() {
15120
16215
  setToolbarShowEditLink(false);
15121
16216
  postToParent2({ type: "ow:exit-edit" });
15122
16217
  }, [postToParent2]);
15123
- const clearSelectedAttr = (0, import_react16.useCallback)(() => {
16218
+ const clearSelectedAttr = (0, import_react17.useCallback)(() => {
15124
16219
  document.querySelectorAll("[data-ohw-selected]").forEach((el) => {
15125
16220
  el.removeAttribute("data-ohw-selected");
15126
16221
  });
15127
16222
  }, []);
15128
- const deselect = (0, import_react16.useCallback)(() => {
16223
+ const deselect = (0, import_react17.useCallback)(() => {
15129
16224
  clearSelectedAttr();
15130
16225
  selectedElRef.current = null;
15131
16226
  selectedHrefKeyRef.current = null;
@@ -15154,11 +16249,12 @@ function OhhwellsBridge() {
15154
16249
  setToolbarVariant("none");
15155
16250
  }
15156
16251
  }, [clearSelectedAttr]);
15157
- const markSelected = (0, import_react16.useCallback)((el) => {
16252
+ const markSelected = (0, import_react17.useCallback)((el) => {
15158
16253
  clearSelectedAttr();
16254
+ el.removeAttribute("data-ohw-hovered");
15159
16255
  el.setAttribute("data-ohw-selected", "");
15160
16256
  }, [clearSelectedAttr]);
15161
- const resolveHrefKeyElement = (0, import_react16.useCallback)((hrefKey) => {
16257
+ const resolveHrefKeyElement = (0, import_react17.useCallback)((hrefKey) => {
15162
16258
  if (isFooterHrefKey(hrefKey)) {
15163
16259
  return document.querySelector(
15164
16260
  `footer [data-ohw-href-key="${CSS.escape(hrefKey)}"]`
@@ -15173,7 +16269,7 @@ function OhhwellsBridge() {
15173
16269
  `[data-ohw-href-key="${CSS.escape(hrefKey)}"]`
15174
16270
  );
15175
16271
  }, []);
15176
- const resyncSelectedNavigationItem = (0, import_react16.useCallback)(() => {
16272
+ const resyncSelectedNavigationItem = (0, import_react17.useCallback)(() => {
15177
16273
  const hrefKey = selectedHrefKeyRef.current;
15178
16274
  if (hrefKey) {
15179
16275
  const link = resolveHrefKeyElement(hrefKey);
@@ -15211,7 +16307,7 @@ function OhhwellsBridge() {
15211
16307
  );
15212
16308
  }
15213
16309
  }, [resolveHrefKeyElement]);
15214
- const reselectNavigationItem = (0, import_react16.useCallback)((navAnchor) => {
16310
+ const reselectNavigationItem = (0, import_react17.useCallback)((navAnchor) => {
15215
16311
  selectedElRef.current = navAnchor;
15216
16312
  selectedHrefKeyRef.current = navAnchor.getAttribute("data-ohw-href-key");
15217
16313
  selectedFooterColAttrRef.current = null;
@@ -15242,7 +16338,7 @@ function OhhwellsBridge() {
15242
16338
  setToolbarShowEditLink(false);
15243
16339
  setActiveCommands(/* @__PURE__ */ new Set());
15244
16340
  }, [markSelected]);
15245
- const commitNavigationTextEdit = (0, import_react16.useCallback)((navAnchor) => {
16341
+ const commitNavigationTextEdit = (0, import_react17.useCallback)((navAnchor) => {
15246
16342
  const el = activeElRef.current;
15247
16343
  if (!el) return;
15248
16344
  const key = el.dataset.ohwKey;
@@ -15275,7 +16371,7 @@ function OhhwellsBridge() {
15275
16371
  postToParent2({ type: "ow:exit-edit" });
15276
16372
  reselectNavigationItem(navAnchor);
15277
16373
  }, [postToParent2, reselectNavigationItem]);
15278
- const handleAddTopLevelNavItem = (0, import_react16.useCallback)(() => {
16374
+ const handleAddTopLevelNavItem = (0, import_react17.useCallback)(() => {
15279
16375
  const items = listNavbarRootItems();
15280
16376
  addNavAfterAnchorRef.current = items[items.length - 1] ?? null;
15281
16377
  deselectRef.current();
@@ -15287,7 +16383,7 @@ function OhhwellsBridge() {
15287
16383
  intent: "add-nav"
15288
16384
  });
15289
16385
  }, []);
15290
- const maybeWarnNavLinkDropdownConflict = (0, import_react16.useCallback)(
16386
+ const maybeWarnNavLinkDropdownConflict = (0, import_react17.useCallback)(
15291
16387
  (anchor) => {
15292
16388
  if (!isNavbarHrefKey(anchor.getAttribute("data-ohw-href-key"))) return;
15293
16389
  if (!navDropdownsOpenOnClick()) return;
@@ -15300,7 +16396,7 @@ function OhhwellsBridge() {
15300
16396
  },
15301
16397
  [postToParent2]
15302
16398
  );
15303
- const handleNavDropdownOpenChange = (0, import_react16.useCallback)((open) => {
16399
+ const handleNavDropdownOpenChange = (0, import_react17.useCallback)((open) => {
15304
16400
  const selected = selectedElRef.current;
15305
16401
  if (!selected || !isNavigationItem2(selected)) return;
15306
16402
  setNavGroupForceOpen(selected, open);
@@ -15312,7 +16408,7 @@ function OhhwellsBridge() {
15312
16408
  }
15313
16409
  });
15314
16410
  }, []);
15315
- const handleFooterHeadingVisibleChange = (0, import_react16.useCallback)(
16411
+ const handleFooterHeadingVisibleChange = (0, import_react17.useCallback)(
15316
16412
  (visible) => {
15317
16413
  const selected = selectedElRef.current;
15318
16414
  if (!selected || !isFooterFrameSelectionRef.current) return;
@@ -15336,7 +16432,7 @@ function OhhwellsBridge() {
15336
16432
  },
15337
16433
  [postToParent2]
15338
16434
  );
15339
- const enterEditOnNewItem = (0, import_react16.useCallback)((anchor) => {
16435
+ const enterEditOnNewItem = (0, import_react17.useCallback)((anchor) => {
15340
16436
  const label = anchor.querySelector('[data-ohw-editable="text"]');
15341
16437
  if (!label) {
15342
16438
  selectRef.current(anchor);
@@ -15345,7 +16441,7 @@ function OhhwellsBridge() {
15345
16441
  setNavGroupForceOpen(anchor, true);
15346
16442
  activateRef.current(label);
15347
16443
  }, []);
15348
- const handleAddChildItem = (0, import_react16.useCallback)(() => {
16444
+ const handleAddChildItem = (0, import_react17.useCallback)(() => {
15349
16445
  const selected = selectedElRef.current;
15350
16446
  if (!selected) return;
15351
16447
  const socialsRow = isSocialsRow(selected) ? selected : findSocialsRow(selected);
@@ -15457,7 +16553,7 @@ function OhhwellsBridge() {
15457
16553
  enterEditOnNewItem(result.anchor);
15458
16554
  });
15459
16555
  }, [enterEditOnNewItem, isFooterFrameSelection, maybeWarnNavLinkDropdownConflict, postToParent2]);
15460
- const handleAddFooterColumn = (0, import_react16.useCallback)(() => {
16556
+ const handleAddFooterColumn = (0, import_react17.useCallback)(() => {
15461
16557
  if (!canAddFooterColumn()) {
15462
16558
  postToParent2({
15463
16559
  type: "ow:toast",
@@ -15478,7 +16574,7 @@ function OhhwellsBridge() {
15478
16574
  selectRef.current(result.firstLink);
15479
16575
  });
15480
16576
  }, [postToParent2]);
15481
- const clearFooterDragVisuals = (0, import_react16.useCallback)(() => {
16577
+ const clearFooterDragVisuals = (0, import_react17.useCallback)(() => {
15482
16578
  footerDragRef.current = null;
15483
16579
  setSiblingHintRects([]);
15484
16580
  setFooterDropSlots([]);
@@ -15487,7 +16583,7 @@ function OhhwellsBridge() {
15487
16583
  setIsItemDragging(false);
15488
16584
  unlockFooterDragInteraction();
15489
16585
  }, []);
15490
- const refreshFooterDragVisuals = (0, import_react16.useCallback)((session, activeSlot, clientX, clientY) => {
16586
+ const refreshFooterDragVisuals = (0, import_react17.useCallback)((session, activeSlot, clientX, clientY) => {
15491
16587
  const dragged = session.draggedEl;
15492
16588
  setDraggedItemRect(dragged.getBoundingClientRect());
15493
16589
  if (typeof clientX === "number" && typeof clientY === "number") {
@@ -15519,13 +16615,13 @@ function OhhwellsBridge() {
15519
16615
  const activeIdx = activeSlot ? slots.findIndex((s) => s.insertIndex === activeSlot.insertIndex) : -1;
15520
16616
  setActiveFooterDropIndex(activeIdx >= 0 ? activeIdx : null);
15521
16617
  }, []);
15522
- const refreshFooterDragVisualsRef = (0, import_react16.useRef)(refreshFooterDragVisuals);
16618
+ const refreshFooterDragVisualsRef = (0, import_react17.useRef)(refreshFooterDragVisuals);
15523
16619
  refreshFooterDragVisualsRef.current = refreshFooterDragVisuals;
15524
- const commitFooterDragRef = (0, import_react16.useRef)(() => {
16620
+ const commitFooterDragRef = (0, import_react17.useRef)(() => {
15525
16621
  });
15526
- const beginFooterDragRef = (0, import_react16.useRef)(() => {
16622
+ const beginFooterDragRef = (0, import_react17.useRef)(() => {
15527
16623
  });
15528
- const beginFooterDrag = (0, import_react16.useCallback)(
16624
+ const beginFooterDrag = (0, import_react17.useCallback)(
15529
16625
  (session) => {
15530
16626
  const rect = session.draggedEl.getBoundingClientRect();
15531
16627
  session.lastClientX = session.lastClientX || rect.left + rect.width / 2;
@@ -15545,7 +16641,7 @@ function OhhwellsBridge() {
15545
16641
  [refreshFooterDragVisuals]
15546
16642
  );
15547
16643
  beginFooterDragRef.current = beginFooterDrag;
15548
- const commitFooterDrag = (0, import_react16.useCallback)(
16644
+ const commitFooterDrag = (0, import_react17.useCallback)(
15549
16645
  (clientX, clientY) => {
15550
16646
  const session = footerDragRef.current;
15551
16647
  if (!session) {
@@ -15673,7 +16769,7 @@ function OhhwellsBridge() {
15673
16769
  [clearFooterDragVisuals, resolveHrefKeyElement, resyncSelectedNavigationItem]
15674
16770
  );
15675
16771
  commitFooterDragRef.current = commitFooterDrag;
15676
- const startFooterLinkDrag = (0, import_react16.useCallback)(
16772
+ const startFooterLinkDrag = (0, import_react17.useCallback)(
15677
16773
  (anchor, clientX, clientY, wasSelected) => {
15678
16774
  const hrefKey = anchor.getAttribute("data-ohw-href-key");
15679
16775
  if (!hrefKey) return false;
@@ -15709,7 +16805,7 @@ function OhhwellsBridge() {
15709
16805
  },
15710
16806
  [beginFooterDrag]
15711
16807
  );
15712
- const startFooterColumnDrag = (0, import_react16.useCallback)(
16808
+ const startFooterColumnDrag = (0, import_react17.useCallback)(
15713
16809
  (columnEl, clientX, clientY, wasSelected) => {
15714
16810
  const columns = listFooterColumns();
15715
16811
  const idx = columns.indexOf(columnEl);
@@ -15729,7 +16825,7 @@ function OhhwellsBridge() {
15729
16825
  },
15730
16826
  [beginFooterDrag]
15731
16827
  );
15732
- const handleItemDragStart = (0, import_react16.useCallback)(
16828
+ const handleItemDragStart = (0, import_react17.useCallback)(
15733
16829
  (e) => {
15734
16830
  const selected = selectedElRef.current;
15735
16831
  if (!selected) {
@@ -15749,7 +16845,7 @@ function OhhwellsBridge() {
15749
16845
  },
15750
16846
  [startFooterColumnDrag, startFooterLinkDrag, startNavLinkDrag]
15751
16847
  );
15752
- const handleItemDragEnd = (0, import_react16.useCallback)(
16848
+ const handleItemDragEnd = (0, import_react17.useCallback)(
15753
16849
  (e) => {
15754
16850
  if (footerDragRef.current) {
15755
16851
  const x = e?.clientX;
@@ -15775,7 +16871,7 @@ function OhhwellsBridge() {
15775
16871
  },
15776
16872
  [commitFooterDrag, commitNavDrag, navDragRef]
15777
16873
  );
15778
- const handleItemChromePointerDown = (0, import_react16.useCallback)((e) => {
16874
+ const handleItemChromePointerDown = (0, import_react17.useCallback)((e) => {
15779
16875
  if (e.button !== 0) return;
15780
16876
  const selected = selectedElRef.current;
15781
16877
  if (!selected) return;
@@ -15806,7 +16902,7 @@ function OhhwellsBridge() {
15806
16902
  }
15807
16903
  if (armNavPressFromChrome(selected, e.clientX, e.clientY, e.pointerId)) return;
15808
16904
  }, [armNavPressFromChrome]);
15809
- const handleItemChromeClick = (0, import_react16.useCallback)((clientX, clientY) => {
16905
+ const handleItemChromeClick = (0, import_react17.useCallback)((clientX, clientY) => {
15810
16906
  if (suppressNextClickRef.current || Date.now() < suppressClickUntilRef.current) {
15811
16907
  suppressNextClickRef.current = false;
15812
16908
  return;
@@ -15819,7 +16915,7 @@ function OhhwellsBridge() {
15819
16915
  }, []);
15820
16916
  reselectNavigationItemRef.current = reselectNavigationItem;
15821
16917
  commitNavigationTextEditRef.current = commitNavigationTextEdit;
15822
- const select = (0, import_react16.useCallback)((anchor) => {
16918
+ const select = (0, import_react17.useCallback)((anchor) => {
15823
16919
  if (!isNavigationItem2(anchor)) return;
15824
16920
  if (activeElRef.current) deactivate();
15825
16921
  aiSectionApiRef.current?.selectFromElement(anchor);
@@ -15862,7 +16958,7 @@ function OhhwellsBridge() {
15862
16958
  setFloatingPanel(null);
15863
16959
  setLogoSizeDraft(null);
15864
16960
  }, [deactivate, markSelected]);
15865
- const selectFrame = (0, import_react16.useCallback)((el) => {
16961
+ const selectFrame = (0, import_react17.useCallback)((el) => {
15866
16962
  if (!isNavigationContainer(el)) return;
15867
16963
  if (activeElRef.current) deactivate();
15868
16964
  aiSectionApiRef.current?.selectFromElement(el);
@@ -15913,7 +17009,7 @@ function OhhwellsBridge() {
15913
17009
  setFloatingPanel(null);
15914
17010
  setLogoSizeDraft(null);
15915
17011
  }, [deactivate, markSelected, postToParent2]);
15916
- const selectLogo = (0, import_react16.useCallback)(
17012
+ const selectLogo = (0, import_react17.useCallback)(
15917
17013
  (logoEl) => {
15918
17014
  if (activeElRef.current) deactivate();
15919
17015
  selectedElRef.current = logoEl;
@@ -15942,7 +17038,7 @@ function OhhwellsBridge() {
15942
17038
  },
15943
17039
  [deactivate, markSelected]
15944
17040
  );
15945
- const openLogoSizePanel = (0, import_react16.useCallback)((logoEl) => {
17041
+ const openLogoSizePanel = (0, import_react17.useCallback)((logoEl) => {
15946
17042
  const placement = getLogoPlacement(logoEl);
15947
17043
  const draft = readLogoSizeState(editContentRef.current, placement);
15948
17044
  setLogoSizeDraft(draft);
@@ -15955,7 +17051,7 @@ function OhhwellsBridge() {
15955
17051
  placement
15956
17052
  });
15957
17053
  }, []);
15958
- const openSocialsDisplayPanel = (0, import_react16.useCallback)((row) => {
17054
+ const openSocialsDisplayPanel = (0, import_react17.useCallback)((row) => {
15959
17055
  setParentScrollSnap(parentScrollRef.current);
15960
17056
  setFloatingPanel({
15961
17057
  key: "socials-display",
@@ -15965,7 +17061,7 @@ function OhhwellsBridge() {
15965
17061
  row
15966
17062
  });
15967
17063
  }, []);
15968
- const changeSocialsDisplay = (0, import_react16.useCallback)(
17064
+ const changeSocialsDisplay = (0, import_react17.useCallback)(
15969
17065
  (row, next) => {
15970
17066
  if (next.icon) {
15971
17067
  const missing = socialsMissingIcons(row);
@@ -15988,17 +17084,17 @@ function OhhwellsBridge() {
15988
17084
  },
15989
17085
  []
15990
17086
  );
15991
- const closeFloatingPanelOnly = (0, import_react16.useCallback)(() => {
17087
+ const closeFloatingPanelOnly = (0, import_react17.useCallback)(() => {
15992
17088
  setFloatingPanel(null);
15993
17089
  setLogoSizeDraft(null);
15994
17090
  }, []);
15995
17091
  closeFloatingPanelOnlyRef.current = closeFloatingPanelOnly;
15996
- const closeFloatingPanelAndDeselect = (0, import_react16.useCallback)(() => {
17092
+ const closeFloatingPanelAndDeselect = (0, import_react17.useCallback)(() => {
15997
17093
  setFloatingPanel(null);
15998
17094
  setLogoSizeDraft(null);
15999
17095
  deselectRef.current();
16000
17096
  }, []);
16001
- (0, import_react16.useEffect)(() => {
17097
+ (0, import_react17.useEffect)(() => {
16002
17098
  const session = floatingPanel && floatingPanel.kind === "logo-size" ? floatingPanel : null;
16003
17099
  if (!session || !logoSizeDraft) {
16004
17100
  postToParentRef.current({ type: "ow:logo-size-panel", open: false });
@@ -16017,7 +17113,7 @@ function OhhwellsBridge() {
16017
17113
  max: LOGO_SIZE_MAX
16018
17114
  });
16019
17115
  }, [floatingPanel, logoSizeDraft, editorViewport]);
16020
- const persistLogoSizeDraft = (0, import_react16.useCallback)(
17116
+ const persistLogoSizeDraft = (0, import_react17.useCallback)(
16021
17117
  (placement, draft) => {
16022
17118
  const desktopKey = LOGO_SIZE_DESKTOP_KEYS[placement];
16023
17119
  const mobileKey = LOGO_SIZE_MOBILE_KEYS[placement];
@@ -16057,7 +17153,7 @@ function OhhwellsBridge() {
16057
17153
  },
16058
17154
  [postToParent2]
16059
17155
  );
16060
- const activate = (0, import_react16.useCallback)((el, options) => {
17156
+ const activate = (0, import_react17.useCallback)((el, options) => {
16061
17157
  if (activeElRef.current === el) return;
16062
17158
  if (isIconEditable(el)) return;
16063
17159
  if (el.hasAttribute("data-ohw-social-label")) return;
@@ -16141,8 +17237,8 @@ function OhhwellsBridge() {
16141
17237
  openLogoSizePanelRef.current = openLogoSizePanel;
16142
17238
  deselectRef.current = deselect;
16143
17239
  closeFloatingPanelOnlyRef.current = closeFloatingPanelOnly;
16144
- const lastSiteWideScopeRef = (0, import_react16.useRef)(null);
16145
- (0, import_react16.useEffect)(() => {
17240
+ const lastSiteWideScopeRef = (0, import_react17.useRef)(null);
17241
+ (0, import_react17.useEffect)(() => {
16146
17242
  if (!isEditMode) {
16147
17243
  if (lastSiteWideScopeRef.current !== false) {
16148
17244
  lastSiteWideScopeRef.current = false;
@@ -16168,22 +17264,34 @@ function OhhwellsBridge() {
16168
17264
  isFooterFrameSelection,
16169
17265
  postToParent2
16170
17266
  ]);
16171
- (0, import_react16.useLayoutEffect)(() => {
17267
+ (0, import_react17.useLayoutEffect)(() => {
16172
17268
  if (!subdomain || isEditMode) {
16173
17269
  setFetchState("done");
16174
17270
  return;
16175
17271
  }
16176
17272
  const applyContent = (content) => {
16177
17273
  const imageLoads = [];
17274
+ if (typeof content[BRAND_KIT_KEY] === "string") {
17275
+ brandKitRef.current = content[BRAND_KIT_KEY];
17276
+ applyBrandToDom(parseBrandKit(content[BRAND_KIT_KEY]));
17277
+ }
16178
17278
  if (typeof content[AI_SECTIONS_KEY] === "string") {
16179
17279
  aiSectionsRef.current = content[AI_SECTIONS_KEY];
16180
17280
  applyAiSectionsToDom(parseAiSectionsState(content[AI_SECTIONS_KEY]));
16181
17281
  }
17282
+ if (typeof content[STYLE_STORE_KEY] === "string") {
17283
+ stylesRef.current = content[STYLE_STORE_KEY];
17284
+ applyStylesToDom(parseStyleStore(content[STYLE_STORE_KEY]));
17285
+ }
17286
+ applyBrandChrome(content);
16182
17287
  for (const [key, val] of Object.entries(content)) {
16183
17288
  if (key === "__ohw_sections") continue;
16184
17289
  if (key === AI_SECTIONS_KEY) continue;
16185
17290
  if (key === LOGO_PLACEHOLDER_KEY) continue;
16186
17291
  if (LOGO_IMAGE_KEYS.includes(key)) continue;
17292
+ if (key === BRAND_KIT_KEY) continue;
17293
+ if (key === STYLE_STORE_KEY) continue;
17294
+ if (BRAND_CHROME_KEYS.has(key)) continue;
16187
17295
  if (applyVideoSettingNode(key, val)) continue;
16188
17296
  if (applyCarouselNode(key, val)) continue;
16189
17297
  document.querySelectorAll(`[data-ohw-key="${key}"]`).forEach((el) => {
@@ -16241,7 +17349,9 @@ function OhhwellsBridge() {
16241
17349
  let cancelled = false;
16242
17350
  setFetchState("loading");
16243
17351
  const apiUrl = process.env.NEXT_PUBLIC_FLOWOPS_API_URL ?? "https://flowops-backend-staging-2yfdh7pwpq-as.a.run.app";
16244
- fetch(`${apiUrl}/api/public/sites/${subdomain}/content`, { cache: "no-store" }).then((r2) => r2.ok ? r2.json() : null).then((data) => {
17352
+ const initialPath = pathname;
17353
+ fetchedContentPaths.add(`${subdomain}::${initialPath}`);
17354
+ fetch(`${apiUrl}/api/public/sites/${subdomain}/content?path=${encodeURIComponent(initialPath)}`, { cache: "no-store" }).then((r2) => r2.ok ? r2.json() : null).then((data) => {
16245
17355
  if (cancelled) return;
16246
17356
  const content = data?.content ?? {};
16247
17357
  contentCache.set(subdomain, content);
@@ -16254,7 +17364,7 @@ function OhhwellsBridge() {
16254
17364
  cancelled = true;
16255
17365
  };
16256
17366
  }, [subdomain, isEditMode]);
16257
- (0, import_react16.useEffect)(() => {
17367
+ (0, import_react17.useEffect)(() => {
16258
17368
  if (!isEditMode) return;
16259
17369
  const resolveIndex = (form, clientY) => {
16260
17370
  const wrappers = listFieldWrappers(form).filter((el) => fieldKeyOf(el) !== fieldDragRef.current?.key);
@@ -16295,7 +17405,7 @@ function OhhwellsBridge() {
16295
17405
  window.removeEventListener("drop", onDrop, true);
16296
17406
  };
16297
17407
  }, [buildFieldDropSlots, isEditMode, persistFields, selectField]);
16298
- (0, import_react16.useEffect)(() => {
17408
+ (0, import_react17.useEffect)(() => {
16299
17409
  if (!isEditMode) return;
16300
17410
  const mark = () => document.querySelectorAll('[data-ohw-editable="form"]').forEach((form) => {
16301
17411
  markFormFields(form);
@@ -16307,7 +17417,7 @@ function OhhwellsBridge() {
16307
17417
  });
16308
17418
  return () => observer.disconnect();
16309
17419
  }, [isEditMode, fetchState, pathname]);
16310
- (0, import_react16.useEffect)(() => {
17420
+ (0, import_react17.useEffect)(() => {
16311
17421
  if (!isEditMode) return;
16312
17422
  let saveTimer = null;
16313
17423
  const onInput = (e) => {
@@ -16329,14 +17439,14 @@ function OhhwellsBridge() {
16329
17439
  document.addEventListener("input", onInput, true);
16330
17440
  return () => document.removeEventListener("input", onInput, true);
16331
17441
  }, [isEditMode, persistFields]);
16332
- (0, import_react16.useEffect)(() => {
17442
+ (0, import_react17.useEffect)(() => {
16333
17443
  if (isEditMode || fetchState !== "done") return;
16334
17444
  const content = contentCache.get(subdomain) ?? {};
16335
17445
  document.querySelectorAll('[data-ohw-editable="form"]').forEach((form) => {
16336
17446
  reconcileFieldsFromContent(form, content);
16337
17447
  });
16338
17448
  }, [isEditMode, fetchState, subdomain]);
16339
- (0, import_react16.useEffect)(() => {
17449
+ (0, import_react17.useEffect)(() => {
16340
17450
  if (!isEditMode) return;
16341
17451
  const swallow = (e) => {
16342
17452
  const target = e.target;
@@ -16345,12 +17455,12 @@ function OhhwellsBridge() {
16345
17455
  document.addEventListener("submit", swallow, true);
16346
17456
  return () => document.removeEventListener("submit", swallow, true);
16347
17457
  }, [isEditMode]);
16348
- (0, import_react16.useEffect)(() => {
17458
+ (0, import_react17.useEffect)(() => {
16349
17459
  if (isEditMode || fetchState !== "done") return;
16350
17460
  const apiUrl = process.env.NEXT_PUBLIC_FLOWOPS_API_URL ?? "https://flowops-backend-staging-2yfdh7pwpq-as.a.run.app";
16351
17461
  bindPublishedForms(apiUrl, subdomain, contentCache.get(subdomain) ?? {});
16352
17462
  }, [isEditMode, fetchState, subdomain]);
16353
- (0, import_react16.useEffect)(() => {
17463
+ (0, import_react17.useEffect)(() => {
16354
17464
  if (!subdomain || isEditMode) return;
16355
17465
  let debounceTimer = null;
16356
17466
  let observer = null;
@@ -16361,10 +17471,21 @@ function OhhwellsBridge() {
16361
17471
  initSectionInstancesFromContent(content, window.location.pathname);
16362
17472
  observer?.disconnect();
16363
17473
  try {
17474
+ applyBrandChrome(content);
17475
+ if (typeof content[BRAND_KIT_KEY] === "string") {
17476
+ applyBrandToDom(parseBrandKit(content[BRAND_KIT_KEY]));
17477
+ }
17478
+ if (typeof content[STYLE_STORE_KEY] === "string") {
17479
+ applyStylesToDom(parseStyleStore(content[STYLE_STORE_KEY]));
17480
+ }
16364
17481
  for (const [key, val] of Object.entries(content)) {
16365
17482
  if (key === "__ohw_sections") continue;
16366
17483
  if (key === LOGO_PLACEHOLDER_KEY) continue;
16367
17484
  if (LOGO_IMAGE_KEYS.includes(key)) continue;
17485
+ if (key === BRAND_KIT_KEY) continue;
17486
+ if (key === STYLE_STORE_KEY) continue;
17487
+ if (key === STYLE_STORE_KEY) continue;
17488
+ if (BRAND_CHROME_KEYS.has(key)) continue;
16368
17489
  if (applyVideoSettingNode(key, val)) continue;
16369
17490
  if (applyCarouselNode(key, val)) continue;
16370
17491
  document.querySelectorAll(`[data-ohw-key="${key}"]`).forEach((el) => {
@@ -16407,6 +17528,17 @@ function OhhwellsBridge() {
16407
17528
  debounceTimer = setTimeout(applyFromCache, 150);
16408
17529
  };
16409
17530
  applyFromCache();
17531
+ const pathCacheKey = `${subdomain}::${pathname}`;
17532
+ if (!fetchedContentPaths.has(pathCacheKey)) {
17533
+ fetchedContentPaths.add(pathCacheKey);
17534
+ const apiUrl = process.env.NEXT_PUBLIC_FLOWOPS_API_URL ?? "https://flowops-backend-staging-2yfdh7pwpq-as.a.run.app";
17535
+ fetch(`${apiUrl}/api/public/sites/${subdomain}/content?path=${encodeURIComponent(pathname)}`, { cache: "no-store" }).then((r2) => r2.ok ? r2.json() : null).then((data) => {
17536
+ if (!data?.content) return;
17537
+ contentCache.set(subdomain, data.content);
17538
+ applyFromCache();
17539
+ }).catch(() => {
17540
+ });
17541
+ }
16410
17542
  observer = new MutationObserver(scheduleApply);
16411
17543
  observer.observe(document.body, { childList: true, subtree: true });
16412
17544
  return () => {
@@ -16414,16 +17546,16 @@ function OhhwellsBridge() {
16414
17546
  if (debounceTimer) clearTimeout(debounceTimer);
16415
17547
  };
16416
17548
  }, [subdomain, isEditMode, pathname]);
16417
- (0, import_react16.useLayoutEffect)(() => {
17549
+ (0, import_react17.useLayoutEffect)(() => {
16418
17550
  const el = document.getElementById("ohw-loader");
16419
17551
  if (!el) return;
16420
17552
  const visible = Boolean(subdomain) && fetchState !== "done";
16421
17553
  el.style.display = visible ? "flex" : "none";
16422
17554
  }, [subdomain, fetchState]);
16423
- (0, import_react16.useEffect)(() => {
17555
+ (0, import_react17.useEffect)(() => {
16424
17556
  postToParent2({ type: "ow:navigation", path: pathname });
16425
17557
  }, [pathname, postToParent2]);
16426
- (0, import_react16.useEffect)(() => {
17558
+ (0, import_react17.useEffect)(() => {
16427
17559
  if (!isEditMode) return;
16428
17560
  if (linkPopoverSessionRef.current?.intent === "add-nav") return;
16429
17561
  if (document.querySelector("[data-ohw-section-picker]")) return;
@@ -16431,7 +17563,7 @@ function OhhwellsBridge() {
16431
17563
  deselectRef.current();
16432
17564
  deactivateRef.current();
16433
17565
  }, [pathname, isEditMode]);
16434
- (0, import_react16.useEffect)(() => {
17566
+ (0, import_react17.useEffect)(() => {
16435
17567
  const contentForNav = () => {
16436
17568
  if (isEditMode) return editContentRef.current;
16437
17569
  if (!subdomain) return {};
@@ -16498,31 +17630,36 @@ function OhhwellsBridge() {
16498
17630
  observer?.disconnect();
16499
17631
  };
16500
17632
  }, [isEditMode, pathname, subdomain, fetchState, resyncSelectedNavigationItem]);
16501
- (0, import_react16.useEffect)(() => {
17633
+ (0, import_react17.useEffect)(() => {
16502
17634
  if (!isEditMode) return;
17635
+ let lastPosted = 0;
16503
17636
  const measure = () => {
16504
17637
  const h = document.body.scrollHeight;
16505
- if (h > 50) postToParent2({ type: "ow:height", height: h });
17638
+ if (h > 50 && Math.abs(h - lastPosted) > 1) {
17639
+ lastPosted = h;
17640
+ postToParent2({ type: "ow:height", height: h });
17641
+ }
17642
+ };
17643
+ let raf = null;
17644
+ const schedule = () => {
17645
+ if (raf != null) return;
17646
+ raf = requestAnimationFrame(() => {
17647
+ raf = null;
17648
+ measure();
17649
+ });
16506
17650
  };
16507
17651
  const t1 = setTimeout(measure, 50);
16508
17652
  const t2 = setTimeout(measure, 500);
16509
- let lastWidth = window.innerWidth;
16510
- let resizeTimer = null;
16511
- const handleResize = () => {
16512
- if (window.innerWidth === lastWidth) return;
16513
- lastWidth = window.innerWidth;
16514
- if (resizeTimer) clearTimeout(resizeTimer);
16515
- resizeTimer = setTimeout(measure, 150);
16516
- };
16517
- window.addEventListener("resize", handleResize);
17653
+ const ro = new ResizeObserver(schedule);
17654
+ ro.observe(document.body);
16518
17655
  return () => {
16519
17656
  clearTimeout(t1);
16520
17657
  clearTimeout(t2);
16521
- if (resizeTimer) clearTimeout(resizeTimer);
16522
- window.removeEventListener("resize", handleResize);
17658
+ if (raf != null) cancelAnimationFrame(raf);
17659
+ ro.disconnect();
16523
17660
  };
16524
17661
  }, [pathname, isEditMode, postToParent2]);
16525
- (0, import_react16.useEffect)(() => {
17662
+ (0, import_react17.useEffect)(() => {
16526
17663
  if (!subdomainFromQuery || isEditMode) return;
16527
17664
  const handleClick = (e) => {
16528
17665
  const anchor = e.target.closest("a");
@@ -16538,7 +17675,7 @@ function OhhwellsBridge() {
16538
17675
  document.addEventListener("click", handleClick, true);
16539
17676
  return () => document.removeEventListener("click", handleClick, true);
16540
17677
  }, [subdomainFromQuery, isEditMode, router]);
16541
- (0, import_react16.useEffect)(() => {
17678
+ (0, import_react17.useEffect)(() => {
16542
17679
  if (!isEditMode) {
16543
17680
  editStylesRef.current?.base.remove();
16544
17681
  editStylesRef.current?.forceHover.remove();
@@ -16700,16 +17837,21 @@ function OhhwellsBridge() {
16700
17837
  return;
16701
17838
  }
16702
17839
  const target = e.target;
17840
+ if (target.closest("[data-ohw-ai-review]")) return;
16703
17841
  if (target.closest("[data-ohw-toolbar]")) return;
16704
17842
  if (target.closest("[data-ohw-state-toggle]")) return;
16705
17843
  if (target.closest("[data-ohw-max-badge]")) return;
16706
17844
  if (isInsideLinkEditor(target)) return;
17845
+ if (isInsideFloatingPanel(target)) return;
16707
17846
  if (target.closest("[data-ohw-form-toolbar]")) return;
16708
17847
  if (target.closest(
16709
17848
  '[data-ohw-field-toolbar], [data-ohw-field-type-picker], [data-radix-popper-content-wrapper], [role="menu"], [data-slot="dropdown-menu-content"]'
16710
17849
  )) {
16711
17850
  return;
16712
17851
  }
17852
+ if (selectedMediaElRef.current && !selectedMediaElRef.current.contains(target) && !target.closest("[data-ohw-media-overlay], [data-ohw-edit-chrome], [data-ohw-item-interaction]")) {
17853
+ clearMediaSelectionRef.current();
17854
+ }
16713
17855
  {
16714
17856
  const formEl = getFormElement(target);
16715
17857
  const onSuccessText = target.closest(`[${SUCCESS_TEXT_ATTR}]`);
@@ -16857,8 +17999,11 @@ function OhhwellsBridge() {
16857
17999
  if (isMediaEditable(editable) && !buttonOnMedia) {
16858
18000
  e.preventDefault();
16859
18001
  e.stopPropagation();
16860
- aiSectionApiRef.current?.selectFromElement(editable);
16861
- postToParentRef.current({ type: "ow:image-pick", key: editable.dataset.ohwKey ?? "", elementType: editable.dataset.ohwEditable ?? "image" });
18002
+ if (selectedMediaElRef.current === editable) {
18003
+ postToParentRef.current({ type: "ow:image-pick", key: editable.dataset.ohwKey ?? "", elementType: editable.dataset.ohwEditable ?? "image" });
18004
+ } else {
18005
+ selectMediaElementRef.current(editable);
18006
+ }
16862
18007
  return;
16863
18008
  }
16864
18009
  const hrefLookupTarget = buttonOnMedia ? clickedButton : editable;
@@ -16977,10 +18122,12 @@ function OhhwellsBridge() {
16977
18122
  };
16978
18123
  const handleDblClick = (e) => {
16979
18124
  const target = e.target;
18125
+ if (target.closest("[data-ohw-ai-review]")) return;
16980
18126
  if (target.closest("[data-ohw-toolbar]")) return;
16981
18127
  if (target.closest("[data-ohw-state-toggle]")) return;
16982
18128
  if (target.closest("[data-ohw-max-badge]")) return;
16983
18129
  if (isInsideLinkEditor(target)) return;
18130
+ if (isInsideFloatingPanel(target)) return;
16984
18131
  if (target.closest('[data-ohw-edit-chrome], [data-ohw-item-interaction], [data-ohw-drag-handle-container], [data-slot="drag-handle"]')) {
16985
18132
  return;
16986
18133
  }
@@ -17021,11 +18168,14 @@ function OhhwellsBridge() {
17021
18168
  setSiblingHintRects([]);
17022
18169
  return;
17023
18170
  }
17024
- if (document.documentElement.hasAttribute("data-ohw-panel-dragging") || target.closest("[data-ohw-floating-panel]")) {
18171
+ if (document.documentElement.hasAttribute("data-ohw-panel-dragging") || isInsideFloatingPanel(target) || isPointOverFloatingPanel(e.clientX, e.clientY)) {
17025
18172
  hoveredItemElRef.current = null;
17026
18173
  setHoveredItemRect(null);
17027
18174
  hoveredNavContainerRef.current = null;
17028
18175
  setHoveredNavContainerRect(null);
18176
+ siblingHintElRef.current = null;
18177
+ setSiblingHintRect(null);
18178
+ setSiblingHintRects([]);
17029
18179
  return;
17030
18180
  }
17031
18181
  {
@@ -17043,6 +18193,16 @@ function OhhwellsBridge() {
17043
18193
  return;
17044
18194
  }
17045
18195
  }
18196
+ if (allowNavContainerHover) {
18197
+ const socialsRow = isSocialsRow(target) ? target : null;
18198
+ if (socialsRow && !getSocialItem(target) && selected2 !== socialsRow) {
18199
+ hoveredNavContainerRef.current = socialsRow;
18200
+ setHoveredNavContainerRect(socialsRow.getBoundingClientRect());
18201
+ hoveredItemElRef.current = null;
18202
+ setHoveredItemRect(null);
18203
+ return;
18204
+ }
18205
+ }
17046
18206
  if (allowFooterLinksHover) {
17047
18207
  const navContainer = target.closest("[data-ohw-nav-container]");
17048
18208
  if (!navContainer) {
@@ -17128,13 +18288,12 @@ function OhhwellsBridge() {
17128
18288
  clearHrefKeyHover(hoverTarget);
17129
18289
  hoveredItemElRef.current = hoverTarget;
17130
18290
  setHoveredItemRect(hoverTarget.getBoundingClientRect());
17131
- } else if (!isInsideNavigationItem(editable)) {
18291
+ } else if (!isInsideNavigationItem(editable) && hoverTarget !== selectedElRef.current) {
17132
18292
  hoverTarget.setAttribute("data-ohw-hovered", "");
17133
18293
  if (editable.closest("footer") || editable.closest('[data-ohw-section="footer"]')) {
17134
18294
  hoveredNavContainerRef.current = null;
17135
18295
  setHoveredNavContainerRect(null);
17136
18296
  hoveredItemElRef.current = editable;
17137
- setHoveredItemRect(editable.getBoundingClientRect());
17138
18297
  }
17139
18298
  }
17140
18299
  }
@@ -17431,7 +18590,7 @@ function OhhwellsBridge() {
17431
18590
  }
17432
18591
  };
17433
18592
  const probeImageAt = (clientX, clientY, isDragOver = false, fromParentViewport = false) => {
17434
- if (linkPopoverOpenRef.current) {
18593
+ if (linkPopoverOpenRef.current || floatingPanelOpenRef.current && isPointOverFloatingPanel(clientX, clientY)) {
17435
18594
  if (hoveredImageRef.current) {
17436
18595
  hoveredImageRef.current = null;
17437
18596
  hoveredImageHasTextOverlapRef.current = false;
@@ -17690,7 +18849,7 @@ function OhhwellsBridge() {
17690
18849
  }
17691
18850
  };
17692
18851
  const probeHoverCardsAt = (clientX, clientY, fromParentViewport = false) => {
17693
- if (linkPopoverOpenRef.current || document.documentElement.hasAttribute("data-ohw-section-picking")) {
18852
+ if (linkPopoverOpenRef.current || floatingPanelOpenRef.current && isPointOverFloatingPanel(clientX, clientY) || document.documentElement.hasAttribute("data-ohw-section-picking")) {
17694
18853
  if (activeStateElRef.current) {
17695
18854
  activeStateElRef.current.removeAttribute("data-ohw-state-hovered");
17696
18855
  activeStateElRef.current = null;
@@ -17727,6 +18886,35 @@ function OhhwellsBridge() {
17727
18886
  }
17728
18887
  }
17729
18888
  };
18889
+ const probeSocialsRowAt = (clientX, clientY, fromParentViewport = false) => {
18890
+ const wasSocialsRow = Boolean(hoveredNavContainerRef.current?.hasAttribute(SOCIALS_ROW_ATTR));
18891
+ const clear = () => {
18892
+ if (!wasSocialsRow) return false;
18893
+ hoveredNavContainerRef.current = null;
18894
+ setHoveredNavContainerRect(null);
18895
+ return false;
18896
+ };
18897
+ if (linkPopoverOpenRef.current || toolbarVariantRef.current === "select-frame") return clear();
18898
+ const { x, y } = toProbeCoords(clientX, clientY, fromParentViewport);
18899
+ const SLACK = 6;
18900
+ for (const row of Array.from(document.querySelectorAll(`[${SOCIALS_ROW_ATTR}]`))) {
18901
+ const rect = row.getBoundingClientRect();
18902
+ const inside = x >= rect.left - SLACK && x <= rect.right + SLACK && y >= rect.top - SLACK && y <= rect.bottom + SLACK;
18903
+ if (!inside) continue;
18904
+ if (selectedElRef.current === row) return clear();
18905
+ const overItem = listSocialItems(row).some((item) => {
18906
+ const box = item.getBoundingClientRect();
18907
+ return x >= box.left && x <= box.right && y >= box.top && y <= box.bottom;
18908
+ });
18909
+ if (overItem) return clear();
18910
+ hoveredNavContainerRef.current = row;
18911
+ setHoveredNavContainerRect(rect);
18912
+ hoveredItemElRef.current = null;
18913
+ setHoveredItemRect(null);
18914
+ return true;
18915
+ }
18916
+ return clear();
18917
+ };
17730
18918
  const probeSectionGapAt = (clientX, clientY, fromParentViewport = false) => {
17731
18919
  if (linkPopoverOpenRef.current) {
17732
18920
  if (hoveredGapRef.current) {
@@ -17736,7 +18924,9 @@ function OhhwellsBridge() {
17736
18924
  return;
17737
18925
  }
17738
18926
  const { y } = toProbeCoords(clientX, clientY, fromParentViewport);
17739
- const sections = Array.from(document.querySelectorAll("[data-ohw-section]")).sort((a, b) => a.getBoundingClientRect().top - b.getBoundingClientRect().top);
18927
+ const sections = Array.from(document.querySelectorAll("[data-ohw-section]")).filter(
18928
+ (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
18929
+ ).sort((a, b) => a.getBoundingClientRect().top - b.getBoundingClientRect().top);
17740
18930
  const ZONE = 20;
17741
18931
  for (let i = 0; i < sections.length; i++) {
17742
18932
  const a = sections[i];
@@ -17765,8 +18955,7 @@ function OhhwellsBridge() {
17765
18955
  };
17766
18956
  const handleMouseMove = (e) => {
17767
18957
  const { clientX, clientY } = e;
17768
- if (pointOwnedByFloatingPanel(clientX, clientY)) return;
17769
- if (isOverEditorChrome(clientX, clientY)) {
18958
+ if (document.documentElement.hasAttribute("data-ohw-panel-dragging") || floatingPanelOpenRef.current && isPointOverFloatingPanel(clientX, clientY) || isOverEditorChrome(clientX, clientY)) {
17770
18959
  document.querySelectorAll("[data-ohw-hovered]").forEach((el) => el.removeAttribute("data-ohw-hovered"));
17771
18960
  formHoverElRef.current = null;
17772
18961
  setFormHoverRect(null);
@@ -17774,8 +18963,15 @@ function OhhwellsBridge() {
17774
18963
  setHoveredItemRect(null);
17775
18964
  hoveredNavContainerRef.current = null;
17776
18965
  setHoveredNavContainerRect(null);
18966
+ siblingHintElRef.current = null;
18967
+ setSiblingHintRect(null);
18968
+ setSiblingHintRects([]);
18969
+ dismissImageHover();
18970
+ clearImageHover();
18971
+ setSectionGap(null);
17777
18972
  return;
17778
18973
  }
18974
+ if (probeSocialsRowAt(clientX, clientY)) return;
17779
18975
  probeSectionGapAt(clientX, clientY);
17780
18976
  probeImageAt(clientX, clientY);
17781
18977
  probeHoverCardsAt(clientX, clientY);
@@ -17784,7 +18980,12 @@ function OhhwellsBridge() {
17784
18980
  if (e.data?.type !== "ow:pointer-sync") return;
17785
18981
  const { clientX, clientY } = e.data;
17786
18982
  if (typeof clientX !== "number" || typeof clientY !== "number") return;
17787
- if (pointOwnedByFloatingPanel(clientX, clientY)) return;
18983
+ if (document.documentElement.hasAttribute("data-ohw-panel-dragging") || floatingPanelOpenRef.current && isPointOverFloatingPanel(clientX, clientY)) {
18984
+ dismissImageHover();
18985
+ clearImageHover();
18986
+ return;
18987
+ }
18988
+ if (probeSocialsRowAt(clientX, clientY)) return;
17788
18989
  probeSectionGapAt(clientX, clientY);
17789
18990
  probeImageAt(clientX, clientY);
17790
18991
  probeHoverCardsAt(clientX, clientY);
@@ -18030,10 +19231,19 @@ function OhhwellsBridge() {
18030
19231
  if (e.data?.type !== "ow:hydrate") return;
18031
19232
  const content = e.data.content;
18032
19233
  if (!content) return;
19234
+ if (typeof content[BRAND_KIT_KEY] === "string") {
19235
+ brandKitRef.current = content[BRAND_KIT_KEY];
19236
+ applyBrandToDom(parseBrandKit(content[BRAND_KIT_KEY]));
19237
+ }
18033
19238
  if (typeof content[AI_SECTIONS_KEY] === "string") {
18034
19239
  aiSectionsRef.current = content[AI_SECTIONS_KEY];
18035
19240
  applyAiSectionsToDom(parseAiSectionsState(content[AI_SECTIONS_KEY]));
18036
19241
  }
19242
+ if (typeof content[STYLE_STORE_KEY] === "string") {
19243
+ stylesRef.current = content[STYLE_STORE_KEY];
19244
+ applyStylesToDom(parseStyleStore(content[STYLE_STORE_KEY]));
19245
+ }
19246
+ applyBrandChrome(content);
18037
19247
  let sectionsJson = null;
18038
19248
  for (const [key, val] of Object.entries(content)) {
18039
19249
  if (key === "__ohw_sections") {
@@ -18043,6 +19253,9 @@ function OhhwellsBridge() {
18043
19253
  if (key === AI_SECTIONS_KEY) continue;
18044
19254
  if (key === LOGO_PLACEHOLDER_KEY) continue;
18045
19255
  if (LOGO_IMAGE_KEYS.includes(key) && !String(val ?? "").trim()) continue;
19256
+ if (key === BRAND_KIT_KEY) continue;
19257
+ if (key === STYLE_STORE_KEY) continue;
19258
+ if (BRAND_CHROME_KEYS.has(key)) continue;
18046
19259
  if (applyVideoSettingNode(key, val)) continue;
18047
19260
  if (applyCarouselNode(key, val)) continue;
18048
19261
  document.querySelectorAll(`[data-ohw-key="${key}"]`).forEach((el) => {
@@ -18056,6 +19269,8 @@ function OhhwellsBridge() {
18056
19269
  if (video && video.src !== val) applyVideoSrc(video, val);
18057
19270
  } else if (el.dataset.ohwEditable === "link") {
18058
19271
  applyLinkHref(el, val);
19272
+ } else if (el.dataset.ohwEditable === "icon") {
19273
+ applyIconMarkup(el, val);
18059
19274
  } else {
18060
19275
  el.innerHTML = val;
18061
19276
  }
@@ -18139,12 +19354,21 @@ function OhhwellsBridge() {
18139
19354
  nodes: collectEditableNodes(editContentRef.current)
18140
19355
  });
18141
19356
  };
19357
+ const clearInteractionChrome = () => {
19358
+ deactivateRef.current();
19359
+ deselectRef.current();
19360
+ clearMediaSelectionRef.current();
19361
+ };
18142
19362
  const handleAiApplyTree = (e) => {
18143
19363
  if (e.data?.type !== "ow:ai-apply-tree") return;
18144
19364
  const payload = e.data.payload;
18145
19365
  if (!payload || typeof payload.id !== "string" || !isRenderableTree(payload.tree)) return;
19366
+ clearInteractionChrome();
18146
19367
  const previous = aiSectionsRef.current;
18147
- const nextState = applyTreeToState(parseAiSectionsState(previous), payload);
19368
+ const nextState = applyTreeToState(parseAiSectionsState(previous), {
19369
+ ...payload,
19370
+ path: payload.path ?? window.location.pathname
19371
+ });
18148
19372
  const nextValue = serializeAiSectionsState(nextState);
18149
19373
  aiSectionsRef.current = nextValue;
18150
19374
  applyAiSectionsToDom(nextState);
@@ -18164,6 +19388,7 @@ function OhhwellsBridge() {
18164
19388
  if (!sectionId || sectionId === "navbar" || sectionId === "footer") return;
18165
19389
  const exists = document.querySelector(`[data-ohw-section="${CSS.escape(sectionId)}"]`);
18166
19390
  if (!exists) return;
19391
+ clearInteractionChrome();
18167
19392
  const previous = aiSectionsRef.current;
18168
19393
  const nextState = deleteSectionFromState(parseAiSectionsState(previous), sectionId);
18169
19394
  const nextValue = serializeAiSectionsState(nextState);
@@ -18179,14 +19404,58 @@ function OhhwellsBridge() {
18179
19404
  const handleAiSetSections = (e) => {
18180
19405
  if (e.data?.type !== "ow:ai-set-sections") return;
18181
19406
  const value = typeof e.data.value === "string" ? e.data.value : "";
19407
+ clearInteractionChrome();
18182
19408
  aiSectionsRef.current = value;
18183
19409
  applyAiSectionsToDom(parseAiSectionsState(value));
19410
+ applyStylesToDom(parseStyleStore(stylesRef.current));
18184
19411
  const restoredHeight = document.documentElement.scrollHeight;
18185
19412
  if (restoredHeight > 50) postToParentRef.current({ type: "ow:height", height: restoredHeight });
18186
19413
  postToParentRef.current({ type: "ow:change", nodes: [{ key: AI_SECTIONS_KEY, text: value }] });
18187
19414
  postAiSectionsChanged();
18188
19415
  };
18189
19416
  window.addEventListener("message", handleAiSetSections);
19417
+ const handleMoveSection = (e) => {
19418
+ if (e.data?.type !== "ow:move-section") return;
19419
+ const instanceId = typeof e.data.instanceId === "string" ? e.data.instanceId : "";
19420
+ const direction = e.data.direction === "up" || e.data.direction === "down" ? e.data.direction : null;
19421
+ if (!instanceId || !direction) return;
19422
+ const entries = moveSectionInstance(instanceId, direction, window.location.pathname);
19423
+ if (!entries) return;
19424
+ const orderJson = JSON.stringify(entries);
19425
+ editContentRef.current = { ...editContentRef.current, [SECTION_ORDER_KEY]: orderJson };
19426
+ postToParentRef.current({ type: "ow:change", nodes: [{ key: SECTION_ORDER_KEY, text: orderJson }] });
19427
+ window.dispatchEvent(new Event("resize"));
19428
+ };
19429
+ window.addEventListener("message", handleMoveSection);
19430
+ const handleAiSetBrand = (e) => {
19431
+ if (e.data?.type !== "ow:ai-set-brand") return;
19432
+ const value = typeof e.data.value === "string" ? e.data.value : "";
19433
+ const previous = brandKitRef.current;
19434
+ brandKitRef.current = value;
19435
+ applyBrandToDom(parseBrandKit(value));
19436
+ if (aiSectionsRef.current) applyAiSectionsToDom(parseAiSectionsState(aiSectionsRef.current));
19437
+ applyStylesToDom(parseStyleStore(stylesRef.current));
19438
+ postToParentRef.current({ type: "ow:change", nodes: [{ key: BRAND_KIT_KEY, text: value }] });
19439
+ postToParentRef.current({ type: "ow:ai-brand-applied", previous, value });
19440
+ };
19441
+ window.addEventListener("message", handleAiSetBrand);
19442
+ const handleAiSetStyles = (e) => {
19443
+ if (e.data?.type !== "ow:ai-set-styles") return;
19444
+ const value = typeof e.data.value === "string" ? e.data.value : "";
19445
+ const previous = stylesRef.current;
19446
+ stylesRef.current = value;
19447
+ applyStylesToDom(parseStyleStore(value));
19448
+ postToParentRef.current({ type: "ow:change", nodes: [{ key: STYLE_STORE_KEY, text: value }] });
19449
+ postToParentRef.current({ type: "ow:ai-styles-applied", previous, value });
19450
+ };
19451
+ window.addEventListener("message", handleAiSetStyles);
19452
+ const handleGetBrand = (e) => {
19453
+ if (e.data?.type !== "ow:get-brand") return;
19454
+ const template = deriveTemplateBrand();
19455
+ const value = brandKitRef.current || (template ? JSON.stringify(template) : "");
19456
+ postToParentRef.current({ type: "ow:brand-value", value });
19457
+ };
19458
+ window.addEventListener("message", handleGetBrand);
18190
19459
  const handlePanelDragging = (e) => {
18191
19460
  if (e.data?.type !== "ow:panel-dragging") return;
18192
19461
  if (e.data.dragging) document.documentElement.setAttribute("data-ohw-panel-dragging", "");
@@ -18202,8 +19471,15 @@ function OhhwellsBridge() {
18202
19471
  closeLinkPopoverRef.current();
18203
19472
  return;
18204
19473
  }
19474
+ if (floatingPanelOpenRef.current) {
19475
+ setFloatingPanelRef.current(null);
19476
+ deselectRef.current();
19477
+ deactivateRef.current();
19478
+ return;
19479
+ }
18205
19480
  deselectRef.current();
18206
19481
  deactivateRef.current();
19482
+ clearMediaSelectionRef.current();
18207
19483
  };
18208
19484
  window.addEventListener("message", handleDeactivate);
18209
19485
  const handleToastAction = (e) => {
@@ -18289,6 +19565,10 @@ function OhhwellsBridge() {
18289
19565
  const handleKeyDown = (e) => {
18290
19566
  if (e.key === "Escape" && document.querySelector("[data-ohw-section-picker]")) return;
18291
19567
  if (e.key === "Escape" && document.querySelector("[data-ohw-more-menu]")) return;
19568
+ if (e.key === "Escape" && selectedMediaElRef.current) {
19569
+ clearMediaSelectionRef.current();
19570
+ return;
19571
+ }
18292
19572
  if ((e.ctrlKey || e.metaKey) && e.key.toLowerCase() === "a" && activeElRef.current) {
18293
19573
  e.preventDefault();
18294
19574
  selectAllTextInEditable(activeElRef.current);
@@ -18448,6 +19728,12 @@ function OhhwellsBridge() {
18448
19728
  if (aiSectionsRef.current) {
18449
19729
  nodes.push({ key: AI_SECTIONS_KEY, type: "sections", text: aiSectionsRef.current });
18450
19730
  }
19731
+ if (stylesRef.current) {
19732
+ nodes.push({ key: STYLE_STORE_KEY, type: "styles", text: stylesRef.current });
19733
+ }
19734
+ if (brandKitRef.current) {
19735
+ nodes.push({ key: BRAND_KIT_KEY, type: "brand", text: brandKitRef.current });
19736
+ }
18451
19737
  document.querySelectorAll('[data-ohw-editable="form"]').forEach((form) => {
18452
19738
  const formKey = formKeyOf(form);
18453
19739
  if (!formKey) return;
@@ -18465,8 +19751,12 @@ function OhhwellsBridge() {
18465
19751
  if (inserted) {
18466
19752
  const tracker = getSectionsTracker();
18467
19753
  postToParentRef.current({ type: "ow:change", nodes: [{ key: "__ohw_sections", text: tracker.textContent ?? "[]" }] });
18468
- const h = document.documentElement.scrollHeight;
18469
- if (h > 50) postToParentRef.current({ type: "ow:height", height: h });
19754
+ const reportHeight = () => {
19755
+ const h = document.body.scrollHeight;
19756
+ if (h > 50) postToParentRef.current({ type: "ow:height", height: h });
19757
+ };
19758
+ reportHeight();
19759
+ setTimeout(reportHeight, 500);
18470
19760
  }
18471
19761
  };
18472
19762
  const handleSwitchSchedule = (e) => {
@@ -18853,19 +20143,23 @@ function OhhwellsBridge() {
18853
20143
  window.removeEventListener("message", handleAiApplyTree);
18854
20144
  window.removeEventListener("message", handleAiDeleteSection);
18855
20145
  window.removeEventListener("message", handleAiSetSections);
20146
+ window.removeEventListener("message", handleMoveSection);
20147
+ window.removeEventListener("message", handleAiSetBrand);
20148
+ window.removeEventListener("message", handleAiSetStyles);
20149
+ window.removeEventListener("message", handleGetBrand);
18856
20150
  window.removeEventListener("message", handlePanelDragging);
18857
20151
  window.removeEventListener("message", handleDeactivate);
18858
- document.documentElement.removeAttribute("data-ohw-panel-dragging");
18859
20152
  window.removeEventListener("message", handleToastAction);
18860
20153
  window.removeEventListener("message", handleFormCount);
18861
20154
  window.removeEventListener("message", handleUiEscape);
20155
+ document.documentElement.removeAttribute("data-ohw-panel-dragging");
18862
20156
  autoSaveTimers.current.forEach(clearTimeout);
18863
20157
  autoSaveTimers.current.clear();
18864
20158
  if (imageUnhoverTimerRef.current) clearTimeout(imageUnhoverTimerRef.current);
18865
20159
  if (imageShowTimerRef.current) clearTimeout(imageShowTimerRef.current);
18866
20160
  };
18867
20161
  }, [isEditMode, refreshStateRules]);
18868
- (0, import_react16.useEffect)(() => {
20162
+ (0, import_react17.useEffect)(() => {
18869
20163
  if (!isEditMode) return;
18870
20164
  const THRESHOLD = 10;
18871
20165
  const resolveWasSelected = (el) => {
@@ -19021,7 +20315,7 @@ function OhhwellsBridge() {
19021
20315
  unlockFooterDragInteraction();
19022
20316
  };
19023
20317
  }, [isEditMode]);
19024
- (0, import_react16.useEffect)(() => {
20318
+ (0, import_react17.useEffect)(() => {
19025
20319
  const handler = (e) => {
19026
20320
  if (e.data?.type !== "ow:request-schedule-config") return;
19027
20321
  const insertAfterVal = e.data.insertAfter;
@@ -19037,7 +20331,7 @@ function OhhwellsBridge() {
19037
20331
  window.addEventListener("message", handler);
19038
20332
  return () => window.removeEventListener("message", handler);
19039
20333
  }, [processConfigRequest]);
19040
- (0, import_react16.useEffect)(() => {
20334
+ (0, import_react17.useEffect)(() => {
19041
20335
  if (!isEditMode) return;
19042
20336
  document.querySelectorAll("[data-ohw-active-state]").forEach((el) => {
19043
20337
  el.removeAttribute("data-ohw-active-state");
@@ -19061,7 +20355,7 @@ function OhhwellsBridge() {
19061
20355
  postToParent2({
19062
20356
  type: "ow:ready",
19063
20357
  version: "1",
19064
- bridgeVersion: "0.1.67",
20358
+ bridgeVersion: "0.1.69",
19065
20359
  path: pathname,
19066
20360
  nodes: collectEditableNodes(editContentRef.current),
19067
20361
  sections
@@ -19073,13 +20367,13 @@ function OhhwellsBridge() {
19073
20367
  clearTimeout(timer);
19074
20368
  };
19075
20369
  }, [pathname, isEditMode, refreshStateRules, postToParent2]);
19076
- (0, import_react16.useEffect)(() => {
20370
+ (0, import_react17.useEffect)(() => {
19077
20371
  scrollToHashSectionWhenReady();
19078
20372
  const onHashChange = () => scrollToHashSectionWhenReady();
19079
20373
  window.addEventListener("hashchange", onHashChange);
19080
20374
  return () => window.removeEventListener("hashchange", onHashChange);
19081
20375
  }, [pathname]);
19082
- const handleCommand = (0, import_react16.useCallback)((cmd) => {
20376
+ const handleCommand = (0, import_react17.useCallback)((cmd) => {
19083
20377
  const el = activeElRef.current;
19084
20378
  const selBefore = window.getSelection();
19085
20379
  let savedOffsets = null;
@@ -19115,7 +20409,7 @@ function OhhwellsBridge() {
19115
20409
  if (el) setToolbarRect(getEditMeasureEl(el).getBoundingClientRect());
19116
20410
  refreshActiveCommandsRef.current();
19117
20411
  }, []);
19118
- (0, import_react16.useEffect)(() => {
20412
+ (0, import_react17.useEffect)(() => {
19119
20413
  const session = floatingPanel && floatingPanel.kind === "logo-size" ? floatingPanel : null;
19120
20414
  if (!session || !logoSizeDraft) return;
19121
20415
  const onPanelAction = (e) => {
@@ -19153,7 +20447,7 @@ function OhhwellsBridge() {
19153
20447
  window.addEventListener("message", onPanelAction);
19154
20448
  return () => window.removeEventListener("message", onPanelAction);
19155
20449
  }, [floatingPanel, logoSizeDraft, editorViewport, persistLogoSizeDraft, closeFloatingPanelAndDeselect]);
19156
- const handleStateChange = (0, import_react16.useCallback)((state) => {
20450
+ const handleStateChange = (0, import_react17.useCallback)((state) => {
19157
20451
  if (!activeStateElRef.current) return;
19158
20452
  const el = activeStateElRef.current;
19159
20453
  if (state === "Default") {
@@ -19166,7 +20460,7 @@ function OhhwellsBridge() {
19166
20460
  }
19167
20461
  setToggleState((prev) => prev ? { ...prev, activeState: state } : null);
19168
20462
  }, [deactivate]);
19169
- const reselectAfterLinkPopover = (0, import_react16.useCallback)(
20463
+ const reselectAfterLinkPopover = (0, import_react17.useCallback)(
19170
20464
  (hrefKey) => {
19171
20465
  requestAnimationFrame(() => {
19172
20466
  const el = resolveHrefKeyElement(hrefKey);
@@ -19175,7 +20469,7 @@ function OhhwellsBridge() {
19175
20469
  },
19176
20470
  [resolveHrefKeyElement]
19177
20471
  );
19178
- const closeLinkPopover = (0, import_react16.useCallback)(() => {
20472
+ const closeLinkPopover = (0, import_react17.useCallback)(() => {
19179
20473
  const session = linkPopoverSessionRef.current;
19180
20474
  addNavAfterAnchorRef.current = null;
19181
20475
  setLinkPopover(null);
@@ -19183,9 +20477,9 @@ function OhhwellsBridge() {
19183
20477
  reselectAfterLinkPopover(session.key);
19184
20478
  }
19185
20479
  }, [reselectAfterLinkPopover]);
19186
- const closeLinkPopoverRef = (0, import_react16.useRef)(closeLinkPopover);
20480
+ const closeLinkPopoverRef = (0, import_react17.useRef)(closeLinkPopover);
19187
20481
  closeLinkPopoverRef.current = closeLinkPopover;
19188
- const openLinkPopoverForActive = (0, import_react16.useCallback)(() => {
20482
+ const openLinkPopoverForActive = (0, import_react17.useCallback)(() => {
19189
20483
  const hrefCtx = getHrefKeyFromElement(activeElRef.current);
19190
20484
  if (!hrefCtx) return;
19191
20485
  bumpLinkPopoverGrace();
@@ -19196,7 +20490,7 @@ function OhhwellsBridge() {
19196
20490
  });
19197
20491
  deactivate();
19198
20492
  }, [deactivate]);
19199
- const openLinkPopoverForSelected = (0, import_react16.useCallback)(() => {
20493
+ const openLinkPopoverForSelected = (0, import_react17.useCallback)(() => {
19200
20494
  const anchor = selectedElRef.current;
19201
20495
  if (!anchor) return;
19202
20496
  const key = anchor.getAttribute("data-ohw-href-key");
@@ -19213,7 +20507,7 @@ function OhhwellsBridge() {
19213
20507
  });
19214
20508
  deselect();
19215
20509
  }, [deselect]);
19216
- const handleSelectParent = (0, import_react16.useCallback)(() => {
20510
+ const handleSelectParent = (0, import_react17.useCallback)(() => {
19217
20511
  const selected = selectedElRef.current;
19218
20512
  if (!selected) return;
19219
20513
  if (toolbarVariantRef.current === "select-frame") {
@@ -19240,7 +20534,7 @@ function OhhwellsBridge() {
19240
20534
  }
19241
20535
  deselectRef.current();
19242
20536
  }, []);
19243
- const handleDuplicateSelected = (0, import_react16.useCallback)(() => {
20537
+ const handleDuplicateSelected = (0, import_react17.useCallback)(() => {
19244
20538
  const selected = selectedElRef.current;
19245
20539
  if (!selected || !isNavigationItem2(selected)) return;
19246
20540
  const hrefKey = selected.getAttribute("data-ohw-href-key");
@@ -19356,7 +20650,7 @@ function OhhwellsBridge() {
19356
20650
  });
19357
20651
  }
19358
20652
  }, [postToParent2]);
19359
- const runPendingDeleteUndo = (0, import_react16.useCallback)(() => {
20653
+ const runPendingDeleteUndo = (0, import_react17.useCallback)(() => {
19360
20654
  const pending = pendingDeleteUndoRef.current;
19361
20655
  if (!pending) return false;
19362
20656
  pendingDeleteUndoRef.current = null;
@@ -19364,7 +20658,7 @@ function OhhwellsBridge() {
19364
20658
  enforceLinkHrefs();
19365
20659
  return true;
19366
20660
  }, []);
19367
- const handleDeleteSelected = (0, import_react16.useCallback)(() => {
20661
+ const handleDeleteSelected = (0, import_react17.useCallback)(() => {
19368
20662
  const selected = selectedElRef.current;
19369
20663
  if (!selected) return false;
19370
20664
  return deleteSelectedNavFooterItem({
@@ -19385,7 +20679,7 @@ function OhhwellsBridge() {
19385
20679
  }, [postToParent2]);
19386
20680
  handleDeleteSelectedRef.current = handleDeleteSelected;
19387
20681
  runPendingDeleteUndoRef.current = runPendingDeleteUndo;
19388
- const handleLinkPopoverSubmit = (0, import_react16.useCallback)(
20682
+ const handleLinkPopoverSubmit = (0, import_react17.useCallback)(
19389
20683
  (target) => {
19390
20684
  const session = linkPopoverSessionRef.current;
19391
20685
  if (!session) return;
@@ -19451,19 +20745,30 @@ function OhhwellsBridge() {
19451
20745
  const showEditLink = toolbarShowEditLink;
19452
20746
  const currentSections = sectionsByPath[pathname] ?? [];
19453
20747
  linkPopoverOpenRef.current = linkPopover !== null;
19454
- const handleMediaReplace = (0, import_react16.useCallback)(
20748
+ const handleMediaSelect = (0, import_react17.useCallback)((key) => {
20749
+ const el = hoveredImageRef.current?.dataset.ohwKey === key ? hoveredImageRef.current : Array.from(document.querySelectorAll(MEDIA_SELECTOR)).find(
20750
+ (m) => (m.dataset.ohwKey ?? "") === key
20751
+ ) ?? null;
20752
+ if (!el) return;
20753
+ selectMediaElementRef.current(el);
20754
+ }, []);
20755
+ const handleMediaReplace = (0, import_react17.useCallback)(
19455
20756
  (key) => {
19456
- postToParent2({ type: "ow:image-pick", key, elementType: mediaHover?.elementType ?? "image" });
20757
+ postToParent2({
20758
+ type: "ow:image-pick",
20759
+ key,
20760
+ elementType: mediaHover?.elementType ?? selectedMedia?.elementType ?? "image"
20761
+ });
19457
20762
  },
19458
- [postToParent2, mediaHover?.elementType]
20763
+ [postToParent2, mediaHover?.elementType, selectedMedia?.elementType]
19459
20764
  );
19460
- const handleEditCarousel = (0, import_react16.useCallback)(
20765
+ const handleEditCarousel = (0, import_react17.useCallback)(
19461
20766
  (key) => {
19462
20767
  postToParent2({ type: "ow:carousel-open", key, images: readCarouselValue(key) });
19463
20768
  },
19464
20769
  [postToParent2]
19465
20770
  );
19466
- const handleMediaFadeOutComplete = (0, import_react16.useCallback)((key) => {
20771
+ const handleMediaFadeOutComplete = (0, import_react17.useCallback)((key) => {
19467
20772
  setUploadingRects((prev) => {
19468
20773
  if (!(key in prev)) return prev;
19469
20774
  const next = { ...prev };
@@ -19471,7 +20776,7 @@ function OhhwellsBridge() {
19471
20776
  return next;
19472
20777
  });
19473
20778
  }, []);
19474
- const handleVideoSettingsChange = (0, import_react16.useCallback)(
20779
+ const handleVideoSettingsChange = (0, import_react17.useCallback)(
19475
20780
  (key, settings) => {
19476
20781
  document.querySelectorAll(`[data-ohw-key="${key}"]`).forEach((el) => {
19477
20782
  const video = getVideoEl2(el);
@@ -19493,420 +20798,506 @@ function OhhwellsBridge() {
19493
20798
  },
19494
20799
  [postToParent2]
19495
20800
  );
19496
- return bridgeRoot ? (0, import_react_dom4.createPortal)(
19497
- /* @__PURE__ */ (0, import_jsx_runtime33.jsxs)(import_jsx_runtime33.Fragment, { children: [
19498
- /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("div", { ref: attachVisibleViewport, "data-ohw-visible-viewport": "", "aria-hidden": true }),
19499
- isEditMode && /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(AiSectionOverlay, { postToParent: postToParent2, apiRef: aiSectionApiRef }),
19500
- Object.entries(uploadingRects).map(([key, { rect, fadingOut }]) => /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
19501
- MediaOverlay,
19502
- {
19503
- hover: { key, rect, elementType: "image", isDragOver: false, hasTextOverlap: false },
19504
- isUploading: true,
19505
- fadingOut,
19506
- onFadeOutComplete: handleMediaFadeOutComplete,
19507
- onReplace: handleMediaReplace
19508
- },
19509
- `uploading-${key}`
19510
- )),
19511
- mediaHover && !(mediaHover.key in uploadingRects) && /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
19512
- MediaOverlay,
19513
- {
19514
- hover: mediaHover,
19515
- isUploading: false,
19516
- onReplace: handleMediaReplace,
19517
- onVideoSettingsChange: handleVideoSettingsChange
19518
- }
19519
- ),
19520
- carouselHover && !linkPopover && !isItemDragging && /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(CarouselOverlay, { hover: carouselHover, onEdit: handleEditCarousel }),
19521
- siblingHintRect && !linkPopover && !isItemDragging && siblingHintRects.length === 0 && /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(ItemInteractionLayer, { rect: siblingHintRect, state: "sibling-hint" }),
19522
- siblingHintRects.length > 0 && !linkPopover && !isItemDragging && siblingHintRects.map((rect, i) => /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(ItemInteractionLayer, { rect, state: "sibling-hint" }, `sibling-hint-${i}`)),
19523
- isItemDragging && draggedItemRect && selectedElRef.current !== footerDragRef.current?.draggedEl && selectedElRef.current !== navDragRef.current?.draggedEl && /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(ItemInteractionLayer, { rect: draggedItemRect, state: "dragging" }),
19524
- isItemDragging && footerDropSlots.map((slot, i) => /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
19525
- "div",
19526
- {
19527
- className: "pointer-events-none fixed z-2147483646",
19528
- style: {
19529
- left: slot.left,
19530
- top: slot.top,
19531
- width: slot.width,
19532
- height: slot.height
20801
+ return /* @__PURE__ */ (0, import_jsx_runtime33.jsxs)(import_jsx_runtime33.Fragment, { children: [
20802
+ /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("div", { id: "ohw-loader", suppressHydrationWarning: true, style: { ...OHW_LOADER_STYLE, display: "none" }, children: /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(OhwLoaderSpinner, {}) }),
20803
+ /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("script", { suppressHydrationWarning: true, dangerouslySetInnerHTML: { __html: OHW_LOADER_PREHYDRATE_SCRIPT } }),
20804
+ bridgeRoot ? (0, import_react_dom4.createPortal)(
20805
+ /* @__PURE__ */ (0, import_jsx_runtime33.jsxs)(import_jsx_runtime33.Fragment, { children: [
20806
+ /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("div", { ref: attachVisibleViewport, "data-ohw-visible-viewport": "", "aria-hidden": true }),
20807
+ isEditMode && /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(AiSectionOverlay, { postToParent: postToParent2, apiRef: aiSectionApiRef }),
20808
+ isSectionDragging && sectionDropSlots.map((slot, i) => /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
20809
+ "div",
20810
+ {
20811
+ className: "pointer-events-none fixed z-2147483646",
20812
+ style: { left: slot.left, top: slot.y, width: slot.width, height: 3, transform: "translateY(-50%)" },
20813
+ children: /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
20814
+ DropIndicator,
20815
+ {
20816
+ direction: "horizontal",
20817
+ state: activeSectionDropIndex === i ? "dragActive" : "dragIdle",
20818
+ className: "!h-full !w-full"
20819
+ }
20820
+ )
19533
20821
  },
19534
- children: /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
19535
- DropIndicator,
19536
- {
19537
- direction: slot.direction,
19538
- state: activeFooterDropIndex === i ? "dragActive" : "dragIdle",
19539
- className: "!h-full !w-full"
19540
- }
19541
- )
19542
- },
19543
- `footer-drop-${slot.direction}-${slot.columnIndex}-${slot.insertIndex}-${i}`
19544
- )),
19545
- isItemDragging && navDropSlots.map((slot, i) => /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
19546
- "div",
19547
- {
19548
- className: "pointer-events-none fixed z-2147483646",
19549
- style: {
19550
- left: slot.left,
19551
- top: slot.top,
19552
- width: slot.width,
19553
- height: slot.height
20822
+ `section-drop-${slot.insertIndex}-${i}`
20823
+ )),
20824
+ Object.entries(uploadingRects).map(([key, { rect, fadingOut }]) => /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
20825
+ MediaOverlay,
20826
+ {
20827
+ hover: { key, rect, elementType: "image", isDragOver: false, hasTextOverlap: false },
20828
+ isUploading: true,
20829
+ fadingOut,
20830
+ onFadeOutComplete: handleMediaFadeOutComplete,
20831
+ onReplace: handleMediaReplace
19554
20832
  },
19555
- children: /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
19556
- DropIndicator,
19557
- {
19558
- direction: slot.direction,
19559
- state: activeNavDropIndex === i ? "dragActive" : "dragIdle",
19560
- className: "!h-full !w-full"
19561
- }
19562
- )
19563
- },
19564
- `nav-drop-${slot.direction}-${slot.parentId ?? "root"}-${slot.insertIndex}-${i}`
19565
- )),
19566
- hoveredNavContainerRect && (toolbarVariant !== "select-frame" || isFooterFrameSelection) && !linkPopover && !isItemDragging && /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(ItemInteractionLayer, { rect: hoveredNavContainerRect, state: "hover" }),
19567
- hoveredItemRect && !linkPopover && !hoveredNavContainerRect && !isItemDragging && hoveredItemElRef.current !== selectedElRef.current && /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(ItemInteractionLayer, { rect: hoveredItemRect, state: "hover" }),
19568
- hoveredTextRect && !isItemDragging && toolbarVariant !== "rich-text" && /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(ItemInteractionLayer, { rect: hoveredTextRect, state: "hover" }),
19569
- formPickRect && !isItemDragging && /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
19570
- ItemInteractionLayer,
19571
- {
19572
- rect: formPickRect,
19573
- state: "active-top",
19574
- itemDragSurface: false,
19575
- toolbarAlign: "left",
19576
- chromeGap: 24,
19577
- toolbar: fieldPickRect ? void 0 : /* @__PURE__ */ (0, import_jsx_runtime33.jsxs)(
20833
+ `uploading-${key}`
20834
+ )),
20835
+ mediaHover && !(mediaHover.key in uploadingRects) && mediaHover.key !== selectedMedia?.key && /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
20836
+ MediaOverlay,
20837
+ {
20838
+ hover: mediaHover,
20839
+ isUploading: false,
20840
+ onReplace: handleMediaReplace,
20841
+ onSelect: handleMediaSelect,
20842
+ onVideoSettingsChange: handleVideoSettingsChange
20843
+ }
20844
+ ),
20845
+ selectedMedia && !(selectedMedia.key in uploadingRects) && !linkPopover && /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
20846
+ MediaOverlay,
20847
+ {
20848
+ hover: selectedMedia,
20849
+ selected: true,
20850
+ hovered: mediaHover?.key === selectedMedia.key,
20851
+ isUploading: false,
20852
+ onReplace: handleMediaReplace,
20853
+ onSelect: handleMediaSelect,
20854
+ onVideoSettingsChange: handleVideoSettingsChange
20855
+ }
20856
+ ),
20857
+ carouselHover && !linkPopover && !isItemDragging && /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(CarouselOverlay, { hover: carouselHover, onEdit: handleEditCarousel }),
20858
+ siblingHintRect && !linkPopover && !isItemDragging && siblingHintRects.length === 0 && /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(ItemInteractionLayer, { rect: siblingHintRect, state: "sibling-hint" }),
20859
+ siblingHintRects.length > 0 && !linkPopover && !isItemDragging && siblingHintRects.map((rect, i) => /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(ItemInteractionLayer, { rect, state: "sibling-hint" }, `sibling-hint-${i}`)),
20860
+ isItemDragging && draggedItemRect && selectedElRef.current !== footerDragRef.current?.draggedEl && selectedElRef.current !== navDragRef.current?.draggedEl && /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(ItemInteractionLayer, { rect: draggedItemRect, state: "dragging" }),
20861
+ isItemDragging && footerDropSlots.map((slot, i) => /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
20862
+ "div",
20863
+ {
20864
+ className: "pointer-events-none fixed z-2147483646",
20865
+ style: {
20866
+ left: slot.left,
20867
+ top: slot.top,
20868
+ width: slot.width,
20869
+ height: slot.height
20870
+ },
20871
+ children: /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
20872
+ DropIndicator,
20873
+ {
20874
+ direction: slot.direction,
20875
+ state: activeFooterDropIndex === i ? "dragActive" : "dragIdle",
20876
+ className: "!h-full !w-full"
20877
+ }
20878
+ )
20879
+ },
20880
+ `footer-drop-${slot.direction}-${slot.columnIndex}-${slot.insertIndex}-${i}`
20881
+ )),
20882
+ isItemDragging && navDropSlots.map((slot, i) => /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
20883
+ "div",
20884
+ {
20885
+ className: "pointer-events-none fixed z-2147483646",
20886
+ style: {
20887
+ left: slot.left,
20888
+ top: slot.top,
20889
+ width: slot.width,
20890
+ height: slot.height
20891
+ },
20892
+ children: /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
20893
+ DropIndicator,
20894
+ {
20895
+ direction: slot.direction,
20896
+ state: activeNavDropIndex === i ? "dragActive" : "dragIdle",
20897
+ className: "!h-full !w-full"
20898
+ }
20899
+ )
20900
+ },
20901
+ `nav-drop-${slot.direction}-${slot.parentId ?? "root"}-${slot.insertIndex}-${i}`
20902
+ )),
20903
+ hoveredNavContainerRect && (toolbarVariant !== "select-frame" || isFooterFrameSelection) && !linkPopover && !isItemDragging && /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(ItemInteractionLayer, { rect: hoveredNavContainerRect, state: "hover" }),
20904
+ hoveredItemRect && !linkPopover && !hoveredNavContainerRect && !isItemDragging && hoveredItemElRef.current !== selectedElRef.current && /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(ItemInteractionLayer, { rect: hoveredItemRect, state: "hover" }),
20905
+ hoveredTextRect && !hoveredNavContainerRect && !hoveredItemRect && !isItemDragging && toolbarVariant !== "rich-text" && /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(ItemInteractionLayer, { rect: hoveredTextRect, state: "hover" }),
20906
+ formPickRect && !isItemDragging && /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
20907
+ ItemInteractionLayer,
20908
+ {
20909
+ rect: formPickRect,
20910
+ state: "active-top",
20911
+ itemDragSurface: false,
20912
+ toolbarAlign: "left",
20913
+ chromeGap: 24,
20914
+ toolbar: fieldPickRect ? void 0 : /* @__PURE__ */ (0, import_jsx_runtime33.jsxs)(
20915
+ "div",
20916
+ {
20917
+ "data-ohw-form-toolbar": "",
20918
+ className: "pointer-events-auto flex items-center gap-0.5 whitespace-nowrap rounded-lg border border-border bg-background p-1 shadow-md",
20919
+ children: [
20920
+ /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
20921
+ "button",
20922
+ {
20923
+ type: "button",
20924
+ "aria-label": "Add field",
20925
+ className: "flex h-7 w-7 items-center justify-center rounded-md text-foreground transition-colors hover:bg-muted/80",
20926
+ onClick: () => setFieldTypePickerOpen((open) => !open),
20927
+ "data-ohw-add-field": "",
20928
+ children: /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(import_lucide_react17.Plus, { size: 15, "aria-hidden": true })
20929
+ }
20930
+ ),
20931
+ /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("div", { className: "mx-0.5 h-5 w-px bg-border" }),
20932
+ /* @__PURE__ */ (0, import_jsx_runtime33.jsxs)(
20933
+ "button",
20934
+ {
20935
+ type: "button",
20936
+ 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",
20937
+ onClick: () => {
20938
+ const form = formPickElRef.current;
20939
+ if (!form) return;
20940
+ postToParent2({
20941
+ type: "ow:form-pick",
20942
+ formKey: formKeyOf(form),
20943
+ hasLongText: formHasLongText(form)
20944
+ });
20945
+ },
20946
+ children: [
20947
+ /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(import_lucide_react17.Settings, { size: 14, "aria-hidden": true }),
20948
+ "Form settings",
20949
+ formPickCount ? (
20950
+ // Counter pill, per the design — not a text suffix.
20951
+ /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
20952
+ "span",
20953
+ {
20954
+ "data-ohw-form-count": "",
20955
+ 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",
20956
+ children: formPickCount
20957
+ }
20958
+ )
20959
+ ) : null
20960
+ ]
20961
+ }
20962
+ ),
20963
+ /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("div", { className: "mx-0.5 h-5 w-px bg-border" }),
20964
+ /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("div", { className: "flex items-center rounded-md bg-muted/70 p-0.5", children: ["default", "success"].map((state) => /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
20965
+ "button",
20966
+ {
20967
+ type: "button",
20968
+ "aria-pressed": formViewState === state,
20969
+ 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"),
20970
+ onClick: () => {
20971
+ const form = formPickElRef.current;
20972
+ const key = form ? formKeyOf(form) : null;
20973
+ if (!form || !key) return;
20974
+ const initial = successInitialFor(form, key, editContentRef.current);
20975
+ setFormViewState(form, key, state, initial);
20976
+ setFormViewStateUi(state);
20977
+ setFormPickRect(form.getBoundingClientRect());
20978
+ if (state === "success") {
20979
+ const successEl = form.querySelector(`[${SUCCESS_TEXT_ATTR}]`);
20980
+ if (successEl) requestAnimationFrame(() => activateRef.current(successEl));
20981
+ } else {
20982
+ deactivateRef.current();
20983
+ }
20984
+ },
20985
+ children: state
20986
+ },
20987
+ state
20988
+ )) })
20989
+ ]
20990
+ }
20991
+ )
20992
+ }
20993
+ ),
20994
+ formHoverRect && !isItemDragging && /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
20995
+ ItemInteractionLayer,
20996
+ {
20997
+ rect: formHoverRect,
20998
+ state: "hover",
20999
+ chromeGap: formHoverElRef.current && getFieldWrapper(formHoverElRef.current) ? 8 : 24
21000
+ }
21001
+ ),
21002
+ fieldPickRect && fieldPickState && !isItemDragging && toolbarVariant !== "rich-text" && /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
21003
+ ItemInteractionLayer,
21004
+ {
21005
+ rect: fieldPickRect,
21006
+ state: fieldDragging ? "dragging" : "active-top",
21007
+ itemDragSurface: false,
21008
+ toolbarAlign: "left",
21009
+ chromeGap: 10,
21010
+ showHandle: true,
21011
+ dragHandleLabel: "Reorder field",
21012
+ onDragHandleDragStart: handleFieldDragStart,
21013
+ onDragHandleDragEnd: handleFieldDragEnd,
21014
+ toolbar: /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
21015
+ FormFieldToolbar,
21016
+ {
21017
+ type: fieldPickState.type,
21018
+ required: fieldPickState.required,
21019
+ onTypeChange: handleFieldTypeChange,
21020
+ onRequiredToggle: handleFieldRequiredToggle,
21021
+ onDuplicate: handleFieldDuplicate,
21022
+ onDelete: handleFieldDelete
21023
+ }
21024
+ )
21025
+ }
21026
+ ),
21027
+ fieldDragging ? fieldDropSlots.map((slot, i) => /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
21028
+ "div",
21029
+ {
21030
+ className: "pointer-events-none fixed z-[2147483644]",
21031
+ style: { top: slot.top, left: slot.left, width: slot.width },
21032
+ children: /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
21033
+ DropIndicator,
21034
+ {
21035
+ direction: "horizontal",
21036
+ state: fieldDropIndex === i ? "dragActive" : "dragIdle",
21037
+ className: "!w-full"
21038
+ }
21039
+ )
21040
+ },
21041
+ `field-drop-${i}`
21042
+ )) : null,
21043
+ fieldTypePickerOpen && formPickRect ? (() => {
21044
+ const toolbar = document.querySelector("[data-ohw-form-toolbar]")?.getBoundingClientRect();
21045
+ return /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
19578
21046
  "div",
19579
21047
  {
19580
- "data-ohw-form-toolbar": "",
19581
- className: "pointer-events-auto flex items-center gap-0.5 whitespace-nowrap rounded-lg border border-border bg-background p-1 shadow-md",
19582
- children: [
19583
- /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
19584
- "button",
19585
- {
19586
- type: "button",
19587
- "aria-label": "Add field",
19588
- className: "flex h-7 w-7 items-center justify-center rounded-md text-foreground transition-colors hover:bg-muted/80",
19589
- onClick: () => setFieldTypePickerOpen((open) => !open),
19590
- "data-ohw-add-field": "",
19591
- children: /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(import_lucide_react17.Plus, { size: 15, "aria-hidden": true })
19592
- }
19593
- ),
19594
- /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("div", { className: "mx-0.5 h-5 w-px bg-border" }),
19595
- /* @__PURE__ */ (0, import_jsx_runtime33.jsxs)(
19596
- "button",
19597
- {
19598
- type: "button",
19599
- 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",
19600
- onClick: () => {
19601
- const form = formPickElRef.current;
19602
- if (!form) return;
19603
- postToParent2({
19604
- type: "ow:form-pick",
19605
- formKey: formKeyOf(form),
19606
- hasLongText: formHasLongText(form)
19607
- });
19608
- },
19609
- children: [
19610
- /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(import_lucide_react17.Settings, { size: 14, "aria-hidden": true }),
19611
- "Form settings",
19612
- formPickCount ? (
19613
- // Counter pill, per the design — not a text suffix.
19614
- /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
19615
- "span",
19616
- {
19617
- "data-ohw-form-count": "",
19618
- 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",
19619
- children: formPickCount
19620
- }
19621
- )
19622
- ) : null
19623
- ]
19624
- }
19625
- ),
19626
- /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("div", { className: "mx-0.5 h-5 w-px bg-border" }),
19627
- /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("div", { className: "flex items-center rounded-md bg-muted/70 p-0.5", children: ["default", "success"].map((state) => /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
19628
- "button",
19629
- {
19630
- type: "button",
19631
- "aria-pressed": formViewState === state,
19632
- 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"),
19633
- onClick: () => {
19634
- const form = formPickElRef.current;
19635
- const key = form ? formKeyOf(form) : null;
19636
- if (!form || !key) return;
19637
- const initial = successInitialFor(form, key, editContentRef.current);
19638
- setFormViewState(form, key, state, initial);
19639
- setFormViewStateUi(state);
19640
- setFormPickRect(form.getBoundingClientRect());
19641
- if (state === "success") {
19642
- const successEl = form.querySelector(`[${SUCCESS_TEXT_ATTR}]`);
19643
- if (successEl) requestAnimationFrame(() => activateRef.current(successEl));
19644
- } else {
19645
- deactivateRef.current();
19646
- }
19647
- },
19648
- children: state
19649
- },
19650
- state
19651
- )) })
19652
- ]
21048
+ className: "pointer-events-none fixed z-[2147483645]",
21049
+ style: {
21050
+ top: toolbar ? toolbar.bottom + 6 : formPickRect.top + 16,
21051
+ left: toolbar ? toolbar.left : formPickRect.left + 24
21052
+ },
21053
+ children: /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(FieldTypePicker, { onPick: handleAddField })
19653
21054
  }
19654
- )
19655
- }
19656
- ),
19657
- formHoverRect && !isItemDragging && /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
19658
- ItemInteractionLayer,
19659
- {
19660
- rect: formHoverRect,
19661
- state: "hover",
19662
- chromeGap: formHoverElRef.current && getFieldWrapper(formHoverElRef.current) ? 8 : 24
19663
- }
19664
- ),
19665
- fieldPickRect && fieldPickState && !isItemDragging && toolbarVariant !== "rich-text" && /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
19666
- ItemInteractionLayer,
19667
- {
19668
- rect: fieldPickRect,
19669
- state: fieldDragging ? "dragging" : "active-top",
19670
- itemDragSurface: false,
19671
- toolbarAlign: "left",
19672
- chromeGap: 10,
19673
- showHandle: true,
19674
- dragHandleLabel: "Reorder field",
19675
- onDragHandleDragStart: handleFieldDragStart,
19676
- onDragHandleDragEnd: handleFieldDragEnd,
19677
- toolbar: /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
19678
- FormFieldToolbar,
21055
+ );
21056
+ })() : null,
21057
+ toolbarVariant === "select-frame" && toolbarRect && !linkPopover && !isItemDragging && !isFooterFrameSelection && selectedElRef.current && isNavbarLinksContainer2(selectedElRef.current) && /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(NavbarContainerChrome, { rect: toolbarRect, onAdd: handleAddTopLevelNavItem }),
21058
+ toolbarVariant === "select-frame" && toolbarRect && !linkPopover && !isItemDragging && !isFooterFrameSelection && selectedElRef.current && isFooterLinksContainer(selectedElRef.current) && /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
21059
+ FooterContainerChrome,
21060
+ {
21061
+ rect: toolbarRect,
21062
+ onAdd: handleAddFooterColumn,
21063
+ addDisabled: !canAddFooterColumn()
21064
+ }
21065
+ ),
21066
+ toolbarRect && !linkPopover && (toolbarVariant === "link-action" || toolbarVariant === "select-frame" || toolbarVariant === "logo") && /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
21067
+ ItemInteractionLayer,
21068
+ {
21069
+ rect: isItemDragging && draggedItemRect && (footerDragRef.current?.wasSelected || navDragRef.current?.wasSelected) ? draggedItemRect : toolbarRect,
21070
+ toolbarBelowRect: selectedElRef.current && isNavigationItem2(selectedElRef.current) ? getOpenNavDropdownPanelRect(selectedElRef.current) : null,
21071
+ elRef: glowElRef,
21072
+ state: isItemDragging ? "dragging" : selectedElRef.current && isNavigationItem2(selectedElRef.current) && getOpenNavDropdownPanelRect(selectedElRef.current) ? "active-bottom" : resolveItemInteractionState(toolbarRect, parentScrollRef.current),
21073
+ showHandle: toolbarVariant === "link-action" && Boolean(reorderHrefKey) || toolbarVariant === "select-frame" && isFooterFrameSelection,
21074
+ dragDisabled: reorderDragDisabled,
21075
+ dragHandleLabel: reorderHrefKey ? `Reorder ${reorderHrefKey}` : "Reorder item",
21076
+ onDragHandleDragStart: handleItemDragStart,
21077
+ onDragHandleDragEnd: handleItemDragEnd,
21078
+ onItemPointerDown: toolbarVariant === "logo" ? void 0 : handleItemChromePointerDown,
21079
+ onItemClick: toolbarVariant === "logo" ? void 0 : handleItemChromeClick,
21080
+ itemDragSurface: toolbarVariant !== "logo" && !isFooterFrameSelection,
21081
+ toolbar: toolbarVariant === "link-action" && !isItemDragging || toolbarVariant === "select-frame" && (isFooterFrameSelection || selectedIsSocialsRow) && !isItemDragging ? /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
21082
+ ItemActionToolbar,
21083
+ {
21084
+ onEditLink: openLinkPopoverForSelected,
21085
+ onStyle: () => {
21086
+ const row = selectedElRef.current;
21087
+ if (!row) return;
21088
+ if (floatingPanel?.kind === "socials-display") closeFloatingPanelOnly();
21089
+ else openSocialsDisplayPanel(row);
21090
+ },
21091
+ showStyle: selectedIsSocialsRow,
21092
+ styleActive: floatingPanel?.kind === "socials-display",
21093
+ onAddItem: handleAddChildItem,
21094
+ onSelectParent: handleSelectParent,
21095
+ onDuplicate: handleDuplicateSelected,
21096
+ onDelete: handleDeleteSelected,
21097
+ addItemDisabled: isFooterFrameSelection && isFooterAddItemDisabled(selectedElRef.current) || selectedElRef.current !== null && (() => {
21098
+ const row = isSocialsRow(selectedElRef.current) ? selectedElRef.current : findSocialsRow(selectedElRef.current);
21099
+ return row ? !canAddSocialItem(row) : false;
21100
+ })(),
21101
+ editLinkDisabled: false,
21102
+ moreDisabled: false,
21103
+ duplicateDisabled: isFooterFrameSelection || selectedIsSocialsRow,
21104
+ showEditLink: !isFooterFrameSelection && !selectedIsSocialsRow && navDropdownPreviewOpen === null,
21105
+ showAddItem: isFooterFrameSelection || selectedIsSocialsRow || !selectedIsCta && Boolean(
21106
+ selectedElRef.current && isNavbarHrefKey(selectedElRef.current.getAttribute("data-ohw-href-key")) && !isNestedNavChild(selectedElRef.current)
21107
+ ),
21108
+ showMore: (!selectedIsCta || isFooterFrameSelection) && !selectedIsSocialsRow,
21109
+ dropdownOpen: navDropdownPreviewOpen,
21110
+ onDropdownOpenChange: handleNavDropdownOpenChange,
21111
+ headingVisible: footerHeadingVisible,
21112
+ onHeadingVisibleChange: handleFooterHeadingVisibleChange
21113
+ }
21114
+ ) : void 0
21115
+ }
21116
+ ),
21117
+ toolbarRect && toolbarVariant === "rich-text" && !linkPopover && /* @__PURE__ */ (0, import_jsx_runtime33.jsxs)(import_jsx_runtime33.Fragment, { children: [
21118
+ /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
21119
+ EditGlowChrome,
19679
21120
  {
19680
- type: fieldPickState.type,
19681
- required: fieldPickState.required,
19682
- onTypeChange: handleFieldTypeChange,
19683
- onRequiredToggle: handleFieldRequiredToggle,
19684
- onDuplicate: handleFieldDuplicate,
19685
- onDelete: handleFieldDelete
21121
+ rect: toolbarRect,
21122
+ elRef: glowElRef,
21123
+ reorderHrefKey,
21124
+ dragDisabled: reorderDragDisabled,
21125
+ hideHandle: isItemDragging
19686
21126
  }
19687
- )
19688
- }
19689
- ),
19690
- fieldDragging ? fieldDropSlots.map((slot, i) => /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
19691
- "div",
19692
- {
19693
- className: "pointer-events-none fixed z-[2147483644]",
19694
- style: { top: slot.top, left: slot.left, width: slot.width },
19695
- children: /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
19696
- DropIndicator,
21127
+ ),
21128
+ /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
21129
+ FloatingToolbar,
19697
21130
  {
19698
- direction: "horizontal",
19699
- state: fieldDropIndex === i ? "dragActive" : "dragIdle",
19700
- className: "!w-full"
21131
+ rect: toolbarRect,
21132
+ parentScroll: parentScrollRef.current,
21133
+ elRef: toolbarElRef,
21134
+ onCommand: handleCommand,
21135
+ activeCommands,
21136
+ showEditLink,
21137
+ onEditLink: openLinkPopoverForActive
19701
21138
  }
19702
21139
  )
19703
- },
19704
- `field-drop-${i}`
19705
- )) : null,
19706
- fieldTypePickerOpen && formPickRect ? (() => {
19707
- const toolbar = document.querySelector("[data-ohw-form-toolbar]")?.getBoundingClientRect();
19708
- return /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
21140
+ ] }),
21141
+ maxBadge && /* @__PURE__ */ (0, import_jsx_runtime33.jsxs)(
19709
21142
  "div",
19710
21143
  {
19711
- className: "pointer-events-none fixed z-[2147483645]",
21144
+ "data-ohw-max-badge": "",
19712
21145
  style: {
19713
- top: toolbar ? toolbar.bottom + 6 : formPickRect.top + 16,
19714
- left: toolbar ? toolbar.left : formPickRect.left + 24
21146
+ position: "fixed",
21147
+ top: maxBadge.rect.bottom + 4,
21148
+ left: maxBadge.rect.right,
21149
+ transform: "translateX(-100%)",
21150
+ zIndex: 2147483647,
21151
+ background: maxBadge.current > maxBadge.max ? "#FEF2F2" : "#F5F5F4",
21152
+ color: maxBadge.current > maxBadge.max ? "#DC2626" : "#78716C",
21153
+ border: `1px solid ${maxBadge.current > maxBadge.max ? "#FECACA" : "#E7E5E4"}`,
21154
+ borderRadius: 4,
21155
+ padding: "2px 6px",
21156
+ fontSize: 11,
21157
+ fontWeight: 500,
21158
+ pointerEvents: "none"
19715
21159
  },
19716
- children: /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(FieldTypePicker, { onPick: handleAddField })
21160
+ children: [
21161
+ maxBadge.current,
21162
+ "/",
21163
+ maxBadge.max
21164
+ ]
19717
21165
  }
19718
- );
19719
- })() : null,
19720
- toolbarVariant === "select-frame" && toolbarRect && !linkPopover && !isItemDragging && !isFooterFrameSelection && selectedElRef.current && isNavbarLinksContainer2(selectedElRef.current) && /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(NavbarContainerChrome, { rect: toolbarRect, onAdd: handleAddTopLevelNavItem }),
19721
- toolbarVariant === "select-frame" && toolbarRect && !linkPopover && !isItemDragging && !isFooterFrameSelection && selectedElRef.current && isFooterLinksContainer(selectedElRef.current) && /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
19722
- FooterContainerChrome,
19723
- {
19724
- rect: toolbarRect,
19725
- onAdd: handleAddFooterColumn,
19726
- addDisabled: !canAddFooterColumn()
19727
- }
19728
- ),
19729
- toolbarRect && !linkPopover && (toolbarVariant === "link-action" || toolbarVariant === "select-frame" || toolbarVariant === "logo") && /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
19730
- ItemInteractionLayer,
19731
- {
19732
- rect: isItemDragging && draggedItemRect && (footerDragRef.current?.wasSelected || navDragRef.current?.wasSelected) ? draggedItemRect : toolbarRect,
19733
- toolbarBelowRect: selectedElRef.current && isNavigationItem2(selectedElRef.current) ? getOpenNavDropdownPanelRect(selectedElRef.current) : null,
19734
- elRef: glowElRef,
19735
- state: isItemDragging ? "dragging" : selectedElRef.current && isNavigationItem2(selectedElRef.current) && getOpenNavDropdownPanelRect(selectedElRef.current) ? "active-bottom" : resolveItemInteractionState(toolbarRect, parentScrollRef.current),
19736
- showHandle: toolbarVariant === "link-action" && Boolean(reorderHrefKey) || toolbarVariant === "select-frame" && isFooterFrameSelection,
19737
- dragDisabled: reorderDragDisabled,
19738
- dragHandleLabel: reorderHrefKey ? `Reorder ${reorderHrefKey}` : "Reorder item",
19739
- onDragHandleDragStart: handleItemDragStart,
19740
- onDragHandleDragEnd: handleItemDragEnd,
19741
- onItemPointerDown: toolbarVariant === "logo" ? void 0 : handleItemChromePointerDown,
19742
- onItemClick: toolbarVariant === "logo" ? void 0 : handleItemChromeClick,
19743
- itemDragSurface: toolbarVariant !== "logo" && !isFooterFrameSelection,
19744
- toolbar: toolbarVariant === "link-action" && !isItemDragging || toolbarVariant === "select-frame" && (isFooterFrameSelection || selectedIsSocialsRow) && !isItemDragging ? /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
19745
- ItemActionToolbar,
19746
- {
19747
- onEditLink: openLinkPopoverForSelected,
19748
- onStyle: () => {
19749
- const row = selectedElRef.current;
19750
- if (!row) return;
19751
- if (floatingPanel?.kind === "socials-display") closeFloatingPanelOnly();
19752
- else openSocialsDisplayPanel(row);
19753
- },
19754
- showStyle: selectedIsSocialsRow,
19755
- styleActive: floatingPanel?.kind === "socials-display",
19756
- onAddItem: handleAddChildItem,
19757
- onSelectParent: handleSelectParent,
19758
- onDuplicate: handleDuplicateSelected,
19759
- onDelete: handleDeleteSelected,
19760
- addItemDisabled: isFooterFrameSelection && isFooterAddItemDisabled(selectedElRef.current) || selectedElRef.current !== null && (() => {
19761
- const row = isSocialsRow(selectedElRef.current) ? selectedElRef.current : findSocialsRow(selectedElRef.current);
19762
- return row ? !canAddSocialItem(row) : false;
19763
- })(),
19764
- editLinkDisabled: false,
19765
- moreDisabled: false,
19766
- duplicateDisabled: isFooterFrameSelection || selectedIsSocialsRow,
19767
- showEditLink: !isFooterFrameSelection && !selectedIsSocialsRow && navDropdownPreviewOpen === null,
19768
- showAddItem: isFooterFrameSelection || selectedIsSocialsRow || !selectedIsCta && Boolean(
19769
- selectedElRef.current && isNavbarHrefKey(selectedElRef.current.getAttribute("data-ohw-href-key")) && !isNestedNavChild(selectedElRef.current)
19770
- ),
19771
- showMore: (!selectedIsCta || isFooterFrameSelection) && !selectedIsSocialsRow,
19772
- dropdownOpen: navDropdownPreviewOpen,
19773
- onDropdownOpenChange: handleNavDropdownOpenChange,
19774
- headingVisible: footerHeadingVisible,
19775
- onHeadingVisibleChange: handleFooterHeadingVisibleChange
19776
- }
19777
- ) : void 0
19778
- }
19779
- ),
19780
- toolbarRect && toolbarVariant === "rich-text" && !linkPopover && /* @__PURE__ */ (0, import_jsx_runtime33.jsxs)(import_jsx_runtime33.Fragment, { children: [
19781
- /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
19782
- EditGlowChrome,
21166
+ ),
21167
+ toggleState && !linkPopover && /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
21168
+ StateToggle,
19783
21169
  {
19784
- rect: toolbarRect,
19785
- elRef: glowElRef,
19786
- reorderHrefKey,
19787
- dragDisabled: reorderDragDisabled,
19788
- hideHandle: isItemDragging
21170
+ rect: toggleState.rect,
21171
+ activeState: toggleState.activeState,
21172
+ states: toggleState.states,
21173
+ onStateChange: handleStateChange
19789
21174
  }
19790
21175
  ),
19791
- /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
19792
- FloatingToolbar,
21176
+ sectionGap && !linkPopover && /* @__PURE__ */ (0, import_jsx_runtime33.jsxs)(
21177
+ "div",
19793
21178
  {
19794
- rect: toolbarRect,
19795
- parentScroll: parentScrollRef.current,
19796
- elRef: toolbarElRef,
19797
- onCommand: handleCommand,
19798
- activeCommands,
19799
- showEditLink,
19800
- onEditLink: openLinkPopoverForActive
21179
+ "data-ohw-section-insert-line": "",
21180
+ className: "fixed left-0 w-full z-2147483646 flex items-center pointer-events-none",
21181
+ style: { top: sectionGap.y, transform: "translateY(-50%)" },
21182
+ children: [
21183
+ /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("div", { className: "flex-1 bg-primary", style: { height: 3 } }),
21184
+ /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
21185
+ Badge,
21186
+ {
21187
+ 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",
21188
+ onClick: () => {
21189
+ window.parent.postMessage(
21190
+ {
21191
+ type: "ow:add-section",
21192
+ insertAfter: sectionGap.insertAfter,
21193
+ insertBefore: sectionGap.insertBefore
21194
+ },
21195
+ "*"
21196
+ );
21197
+ },
21198
+ children: "Add Section"
21199
+ }
21200
+ ),
21201
+ /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("div", { className: "flex-1 bg-primary", style: { height: 3 } })
21202
+ ]
19801
21203
  }
19802
- )
19803
- ] }),
19804
- maxBadge && /* @__PURE__ */ (0, import_jsx_runtime33.jsxs)(
19805
- "div",
19806
- {
19807
- "data-ohw-max-badge": "",
19808
- style: {
19809
- position: "fixed",
19810
- top: maxBadge.rect.bottom + 4,
19811
- left: maxBadge.rect.right,
19812
- transform: "translateX(-100%)",
19813
- zIndex: 2147483647,
19814
- background: maxBadge.current > maxBadge.max ? "#FEF2F2" : "#F5F5F4",
19815
- color: maxBadge.current > maxBadge.max ? "#DC2626" : "#78716C",
19816
- border: `1px solid ${maxBadge.current > maxBadge.max ? "#FECACA" : "#E7E5E4"}`,
19817
- borderRadius: 4,
19818
- padding: "2px 6px",
19819
- fontSize: 11,
19820
- fontWeight: 500,
19821
- pointerEvents: "none"
21204
+ ),
21205
+ linkPopover && dialogPortalContainer ? /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
21206
+ LinkPopover,
21207
+ {
21208
+ panelRef: linkPopoverPanelRef,
21209
+ portalContainer: dialogPortalContainer,
21210
+ open: true,
21211
+ mode: linkPopover.mode ?? "edit",
21212
+ pages: sitePages,
21213
+ sections: currentSections,
21214
+ sectionsByPath,
21215
+ initialTarget: linkPopover.target,
21216
+ existingTargets: linkPopover.intent === "add-nav" ? getNavbarExistingTargets() : [],
21217
+ onClose: closeLinkPopover,
21218
+ onSubmit: handleLinkPopoverSubmit
19822
21219
  },
19823
- children: [
19824
- maxBadge.current,
19825
- "/",
19826
- maxBadge.max
19827
- ]
19828
- }
19829
- ),
19830
- toggleState && !linkPopover && /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
19831
- StateToggle,
19832
- {
19833
- rect: toggleState.rect,
19834
- activeState: toggleState.activeState,
19835
- states: toggleState.states,
19836
- onStateChange: handleStateChange
19837
- }
19838
- ),
19839
- sectionGap && !linkPopover && /* @__PURE__ */ (0, import_jsx_runtime33.jsxs)(
19840
- "div",
19841
- {
19842
- "data-ohw-section-insert-line": "",
19843
- className: "fixed left-0 w-full z-2147483646 flex items-center pointer-events-none",
19844
- style: { top: sectionGap.y, transform: "translateY(-50%)" },
19845
- children: [
19846
- /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("div", { className: "flex-1 bg-primary", style: { height: 3 } }),
19847
- /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
19848
- Badge,
21220
+ linkPopover.key
21221
+ ) : null,
21222
+ floatingPanel && floatingPanel.kind === "socials-display" ? /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
21223
+ FloatingPanel,
21224
+ {
21225
+ open: true,
21226
+ title: floatingPanel.title,
21227
+ context: floatingPanel.context,
21228
+ position: floatingPanelPos,
21229
+ onPositionChange: setFloatingPanelPos,
21230
+ parentScroll: parentScrollSnap ?? parentScrollRef.current,
21231
+ onClose: closeFloatingPanelOnly,
21232
+ children: /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
21233
+ SocialsDisplayPanel,
19849
21234
  {
19850
- 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",
19851
- onClick: () => {
19852
- window.parent.postMessage(
19853
- {
19854
- type: "ow:add-section",
19855
- insertAfter: sectionGap.insertAfter,
19856
- insertBefore: sectionGap.insertBefore
19857
- },
19858
- "*"
19859
- );
19860
- },
19861
- children: "Add Section"
21235
+ display: socialsDisplayFor(floatingPanel.row, editContentRef.current),
21236
+ onChange: (next) => {
21237
+ changeSocialsDisplay(floatingPanel.row, next);
21238
+ setFloatingPanel({ ...floatingPanel });
21239
+ }
19862
21240
  }
19863
- ),
19864
- /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("div", { className: "flex-1 bg-primary", style: { height: 3 } })
19865
- ]
19866
- }
19867
- ),
19868
- linkPopover && dialogPortalContainer ? /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
19869
- LinkPopover,
19870
- {
19871
- panelRef: linkPopoverPanelRef,
19872
- portalContainer: dialogPortalContainer,
19873
- open: true,
19874
- mode: linkPopover.mode ?? "edit",
19875
- pages: sitePages,
19876
- sections: currentSections,
19877
- sectionsByPath,
19878
- initialTarget: linkPopover.target,
19879
- existingTargets: linkPopover.intent === "add-nav" ? getNavbarExistingTargets() : [],
19880
- onClose: closeLinkPopover,
19881
- onSubmit: handleLinkPopoverSubmit
21241
+ )
21242
+ }
21243
+ ) : null
21244
+ ] }),
21245
+ bridgeRoot
21246
+ ) : null
21247
+ ] });
21248
+ }
21249
+
21250
+ // src/ui/EmptySection.tsx
21251
+ var import_link = __toESM(require("next/link"), 1);
21252
+ var import_jsx_runtime34 = require("react/jsx-runtime");
21253
+ function EmptySection({ title, homeHref = "/", eyebrowKey, titleKey, subtitleKey }) {
21254
+ return /* @__PURE__ */ (0, import_jsx_runtime34.jsxs)(import_jsx_runtime34.Fragment, { children: [
21255
+ /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(
21256
+ "p",
21257
+ {
21258
+ style: {
21259
+ fontFamily: "var(--brand-font-body)",
21260
+ fontSize: "0.75rem",
21261
+ fontWeight: 500,
21262
+ letterSpacing: "0.15em",
21263
+ textTransform: "uppercase",
21264
+ color: "var(--brand-accent)",
21265
+ marginBottom: "1.5rem"
19882
21266
  },
19883
- linkPopover.key
19884
- ) : null,
19885
- floatingPanel && floatingPanel.kind === "socials-display" ? /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
19886
- FloatingPanel,
19887
- {
19888
- open: true,
19889
- title: floatingPanel.title,
19890
- context: floatingPanel.context,
19891
- position: floatingPanelPos,
19892
- onPositionChange: setFloatingPanelPos,
19893
- parentScroll: parentScrollSnap ?? parentScrollRef.current,
19894
- onClose: closeFloatingPanelOnly,
19895
- children: /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
19896
- SocialsDisplayPanel,
19897
- {
19898
- display: socialsDisplayFor(floatingPanel.row, editContentRef.current),
19899
- onChange: (next) => {
19900
- changeSocialsDisplay(floatingPanel.row, next);
19901
- setFloatingPanel({ ...floatingPanel });
19902
- }
19903
- }
19904
- )
19905
- }
19906
- ) : null
19907
- ] }),
19908
- bridgeRoot
19909
- ) : null;
21267
+ children: /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(import_link.default, { href: homeHref, style: { color: "inherit" }, children: /* @__PURE__ */ (0, import_jsx_runtime34.jsx)("span", { ...eyebrowKey ? { "data-ohw-editable": "text", "data-ohw-key": eyebrowKey } : {}, children: "Home" }) })
21268
+ }
21269
+ ),
21270
+ /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(
21271
+ "h1",
21272
+ {
21273
+ style: {
21274
+ fontFamily: "var(--brand-font-heading)",
21275
+ fontSize: "clamp(2rem, 3.5vw, 2.75rem)",
21276
+ lineHeight: 1.1,
21277
+ letterSpacing: "-0.025em",
21278
+ color: "var(--brand-text)",
21279
+ marginBottom: "1rem"
21280
+ },
21281
+ ...titleKey ? { "data-ohw-editable": "text", "data-ohw-key": titleKey, "data-ohw-max-length": 80 } : {},
21282
+ children: title
21283
+ }
21284
+ ),
21285
+ /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(
21286
+ "p",
21287
+ {
21288
+ style: {
21289
+ fontFamily: "var(--brand-font-body)",
21290
+ fontSize: "1rem",
21291
+ lineHeight: 1.7,
21292
+ fontWeight: 300,
21293
+ color: "var(--brand-text-muted)",
21294
+ maxWidth: "340px"
21295
+ },
21296
+ ...subtitleKey ? { "data-ohw-editable": "text", "data-ohw-key": subtitleKey, "data-ohw-max-length": 160 } : {},
21297
+ children: "This page doesn't have any content yet."
21298
+ }
21299
+ )
21300
+ ] });
19910
21301
  }
19911
21302
  // Annotate the CommonJS export names for ESM import in node:
19912
21303
  0 && (module.exports = {
@@ -19925,6 +21316,7 @@ function OhhwellsBridge() {
19925
21316
  DropdownMenuItem,
19926
21317
  DropdownMenuSeparator,
19927
21318
  DropdownMenuTrigger,
21319
+ EmptySection,
19928
21320
  ItemActionToolbar,
19929
21321
  ItemInteractionLayer,
19930
21322
  LinkEditorPanel,