@ohhwells/bridge 0.1.76-next.230 → 0.1.77

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,7 +46,6 @@ __export(index_exports, {
46
46
  DropdownMenuItem: () => DropdownMenuItem,
47
47
  DropdownMenuSeparator: () => DropdownMenuSeparator,
48
48
  DropdownMenuTrigger: () => DropdownMenuTrigger,
49
- EmptySection: () => EmptySection,
50
49
  ItemActionToolbar: () => ItemActionToolbar,
51
50
  ItemInteractionLayer: () => ItemInteractionLayer,
52
51
  LinkEditorPanel: () => LinkEditorPanel,
@@ -157,12 +156,7 @@ function parseAiSectionsState(raw) {
157
156
  media: entry.media && typeof entry.media === "object" ? entry.media : {}
158
157
  }));
159
158
  const removed = Array.isArray(parsed.removed) ? parsed.removed.filter((id) => typeof id === "string" && id.length > 0) : [];
160
- return {
161
- v: 1,
162
- sections,
163
- ...removed.length ? { removed } : {},
164
- ...parsed.hideTemplate === true ? { hideTemplate: true } : {}
165
- };
159
+ return { v: 1, sections, ...removed.length ? { removed } : {} };
166
160
  } catch {
167
161
  return EMPTY_AI_SECTIONS;
168
162
  }
@@ -175,7 +169,6 @@ function applyTreeToState(state, payload) {
175
169
  const entry = {
176
170
  id: payload.id,
177
171
  label: payload.label ?? "Generated section",
178
- ...typeof payload.path === "string" && payload.path ? { path: payload.path } : {},
179
172
  afterSection: payload.mode === "insert" && !insertBefore ? payload.targetSectionId ?? null : null,
180
173
  ...insertBefore && payload.targetSectionId ? { beforeSection: payload.targetSectionId } : {},
181
174
  ...payload.mode === "replace" && payload.targetSectionId ? { replaces: payload.targetSectionId } : {},
@@ -198,317 +191,6 @@ function deleteSectionFromState(state, sectionId) {
198
191
  return { ...state, v: 1, sections: state.sections, removed: [...removed, sectionId] };
199
192
  }
200
193
 
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
-
512
194
  // src/ui/ai-tree/aiSectionsManager.tsx
513
195
  var import_react_dom = require("react-dom");
514
196
  var import_client = require("react-dom/client");
@@ -523,8 +205,7 @@ function lucideByName(name) {
523
205
  }
524
206
  var typeStyle = (spec, font) => ({
525
207
  fontFamily: font,
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,
208
+ fontSize: spec.size,
528
209
  lineHeight: spec.line,
529
210
  fontWeight: spec.weight
530
211
  });
@@ -533,60 +214,12 @@ var featureLines = (value) => value.split(/\n|<br\s*\/?>/i).map((line) => line.t
533
214
  var CHECK_MASK = `data:image/svg+xml,${encodeURIComponent(
534
215
  '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="black" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M20 6 9 17l-5-5"/></svg>'
535
216
  )}`;
536
- var AI_MOBILE_CSS = [
537
- "@media (max-width: 768px){",
538
- "[data-ai-section]{overflow-x:hidden}",
539
- "[data-ai-container]{padding:0 20px !important}",
540
- "[data-ai-row]{display:flex !important;flex-direction:column !important;align-items:stretch !important}",
541
- "[data-ai-cell]{width:100%;min-width:0}",
542
- "[data-ai-grid]{grid-template-columns:1fr !important}",
543
- // Group containers flatten to a column on phones; span placements come along for free.
544
- "[data-ai-group]{display:flex !important;flex-direction:column !important}",
545
- "[data-ai-group] > *{grid-column:auto !important}",
546
- // The 50:50 form collapses to a single stacked column on phones.
547
- "[data-ai-form]{grid-template-columns:1fr !important}",
548
- "[data-ai-section] img{max-width:100%}",
549
- "}",
550
- "@media (min-width: 769px) and (max-width: 1024px){",
551
- "[data-ai-grid]{grid-template-columns:repeat(2, 1fr) !important}",
552
- "}"
553
- ].join("");
554
217
  var FEATURE_LINE_CSS = [
555
218
  "[data-ai-features]>div{position:relative;padding-left:40px;min-height:24px}",
556
219
  '[data-ai-features]>div::before{content:"";position:absolute;left:0;top:1px;width:24px;height:24px;',
557
220
  `background-color:currentColor;-webkit-mask:url("${CHECK_MASK}") no-repeat 0 0/24px 24px;`,
558
221
  `mask:url("${CHECK_MASK}") no-repeat 0 0/24px 24px}`
559
222
  ].join("");
560
- function hexLuminance(color) {
561
- const m = /^#([0-9a-f]{6})$/i.exec(color.trim());
562
- if (!m) return null;
563
- const [r2, g, b] = [0, 2, 4].map((i) => {
564
- const c = parseInt(m[1].slice(i, i + 2), 16) / 255;
565
- return c <= 0.03928 ? c / 12.92 : Math.pow((c + 0.055) / 1.055, 2.4);
566
- });
567
- return 0.2126 * r2 + 0.7152 * g + 0.0722 * b;
568
- }
569
- function hexContrast(a, b) {
570
- const la = hexLuminance(a);
571
- const lb = hexLuminance(b);
572
- if (la === null || lb === null) return null;
573
- const [hi, lo] = la > lb ? [la, lb] : [lb, la];
574
- return (hi + 0.05) / (lo + 0.05);
575
- }
576
- function accentBandContext(brand) {
577
- const p = brand.palette;
578
- const lightWins = (hexContrast(p.light, p.primary) ?? 99) >= (hexContrast(p.dark, p.primary) ?? 0);
579
- if (lightWins) {
580
- return {
581
- brand: { ...brand, palette: { dark: p.light, primary: p.light, accent: p.light, light: p.primary } },
582
- buttonLabel: p.primary
583
- };
584
- }
585
- return {
586
- brand: { ...brand, palette: { dark: p.dark, primary: p.dark, accent: p.dark, light: p.light } },
587
- buttonLabel: p.light
588
- };
589
- }
590
223
  function textAttrs(ctx, path) {
591
224
  return ctx.keyFor ? { "data-ohw-key": ctx.keyFor(path), "data-ohw-editable": "text" } : {};
592
225
  }
@@ -598,12 +231,7 @@ var AI_RESPONSIVE_CSS = [
598
231
  "@media (max-width: 640px) {",
599
232
  " [data-ai-responsive] [data-ai-grid] { grid-template-columns: 1fr !important; }",
600
233
  " [data-ai-responsive] [data-ai-columns] > * { grid-column: 1 / -1 !important; }",
601
- // Group containers flatten to a column on phones; span placements come along for free.
602
- " [data-ai-responsive] [data-ai-group] { display: flex !important; flex-direction: column !important; }",
603
- " [data-ai-responsive] [data-ai-group] > * { grid-column: auto !important; }",
604
234
  " [data-ai-responsive] [data-ai-section-inner] { padding-left: 20px !important; padding-right: 20px !important; }",
605
- " [data-ai-responsive] { overflow-x: hidden; }",
606
- " [data-ai-responsive] img { max-width: 100%; }",
607
235
  "}"
608
236
  ].join("\n");
609
237
  var TRANSPARENT_PX = "data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7";
@@ -681,7 +309,7 @@ function ButtonEl({
681
309
  }) {
682
310
  const secondary = slots.variant === "secondary";
683
311
  const href = str(slots.href);
684
- const linkAttrs = ctx.keyFor && path ? { "data-ohw-href-key": ctx.keyFor(`${path}.href`), "data-ohw-role": "button" } : {};
312
+ const linkAttrs = ctx.keyFor && path ? { "data-ohw-href-key": ctx.keyFor(`${path}.href`) } : {};
685
313
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
686
314
  "a",
687
315
  {
@@ -697,7 +325,7 @@ function ButtonEl({
697
325
  textDecoration: "none",
698
326
  cursor: "pointer",
699
327
  ...typeStyle(AI_TREE_TOKENS.type.button, ctx.brand.fonts.body),
700
- ...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 }
328
+ ...secondary ? { border: `1px solid ${ctx.brand.palette.dark}`, color: ctx.brand.palette.dark } : { background: ctx.brand.palette.primary, color: AI_TREE_TOKENS.textPrimaryForeground }
701
329
  },
702
330
  children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { ...path ? textAttrs(ctx, `${path}.label`) : {}, children: str(slots.label) })
703
331
  }
@@ -1203,24 +831,7 @@ function CardBlock({ node, ctx, path }) {
1203
831
  minWidth: 0
1204
832
  },
1205
833
  children: [
1206
- media && (horizontal ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1207
- "div",
1208
- {
1209
- style: (
1210
- // An icon hugs its glyph — flex:1 gave a 48px icon half the card and pushed the
1211
- // text to the far side. Photos keep the half-and-half split. The inset has no
1212
- // inner padding (the photo split absorbed that), so the icon carries its own gap.
1213
- /^(lucide|simple):/.test(mediaRef) ? {
1214
- flexShrink: 0,
1215
- display: "flex",
1216
- alignItems: "center",
1217
- padding: mediaInset,
1218
- [mediaPosition === "right" ? "marginLeft" : "marginRight"]: 20
1219
- } : { flex: 1, minWidth: 0, display: "flex", alignItems: "center", padding: mediaInset }
1220
- ),
1221
- children: media
1222
- }
1223
- ) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
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)(
1224
835
  "div",
1225
836
  {
1226
837
  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" },
@@ -1311,44 +922,13 @@ function AccordionBlock({ node, ctx, path }) {
1311
922
  ) })
1312
923
  ] }, i)) });
1313
924
  }
1314
- function useIsMobile() {
1315
- const [mobile, setMobile] = import_react.default.useState(
1316
- () => typeof window !== "undefined" && window.matchMedia("(max-width: 768px)").matches
1317
- );
1318
- import_react.default.useEffect(() => {
1319
- const mq = window.matchMedia("(max-width: 768px)");
1320
- const update = () => setMobile(mq.matches);
1321
- update();
1322
- mq.addEventListener("change", update);
1323
- return () => mq.removeEventListener("change", update);
1324
- }, []);
1325
- return mobile;
1326
- }
1327
925
  function Carousel({ items, itemsPerRow, ctx }) {
1328
- const isMobile = useIsMobile();
1329
- const perPage = isMobile ? 1 : itemsPerRow;
1330
- const pages = Math.max(1, Math.ceil(items.length / perPage));
1331
926
  const [page, setPage] = import_react.default.useState(0);
927
+ const pages = Math.max(1, Math.ceil(items.length / itemsPerRow));
1332
928
  const current = Math.min(page, pages - 1);
1333
- if (pages <= 1) {
1334
- const cols = Math.max(1, Math.min(items.length, itemsPerRow));
1335
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1336
- "div",
1337
- {
1338
- "data-ai-grid": String(cols),
1339
- style: {
1340
- display: "grid",
1341
- gridTemplateColumns: `repeat(${cols}, 1fr)`,
1342
- gap: AI_TREE_TOKENS.spacing8,
1343
- alignItems: "start"
1344
- },
1345
- children: items
1346
- }
1347
- );
1348
- }
1349
929
  const pageGroups = Array.from(
1350
930
  { length: pages },
1351
- (_, p) => items.slice(p * perPage, (p + 1) * perPage)
931
+ (_, p) => items.slice(p * itemsPerRow, (p + 1) * itemsPerRow)
1352
932
  );
1353
933
  const chrome = (enabled) => ({
1354
934
  border: `1px solid ${ctx.brand.palette.dark}`,
@@ -1373,69 +953,55 @@ function Carousel({ items, itemsPerRow, ctx }) {
1373
953
  cursor: "pointer",
1374
954
  padding: 0
1375
955
  });
1376
- const viewport = /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { flex: isMobile ? "0 0 auto" : 1, minWidth: 0, width: "100%", overflow: "hidden" }, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1377
- "div",
1378
- {
1379
- style: {
1380
- display: "flex",
1381
- transform: `translateX(-${current * 100}%)`,
1382
- transition: "transform 0.4s ease"
1383
- },
1384
- children: pageGroups.map((group, p) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
956
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { "data-ai-carousel": "", style: { display: "flex", flexDirection: "column", gap: AI_TREE_TOKENS.spacing8, alignItems: "center" }, children: [
957
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { display: "flex", alignItems: "center", gap: AI_TREE_TOKENS.spacing6, width: "100%" }, children: [
958
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
959
+ "button",
960
+ {
961
+ type: "button",
962
+ "aria-label": "Previous",
963
+ onClick: () => setPage((p) => Math.max(0, p - 1)),
964
+ style: chrome(current > 0),
965
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_lucide_react.ArrowLeft, { size: 24, strokeWidth: 1.5, "aria-hidden": true })
966
+ }
967
+ ),
968
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { flex: 1, minWidth: 0, overflow: "hidden" }, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1385
969
  "div",
1386
970
  {
1387
- "data-ai-grid": String(perPage),
1388
971
  style: {
1389
- flex: "0 0 100%",
1390
- display: "grid",
1391
- gridTemplateColumns: `repeat(${perPage}, 1fr)`,
1392
- gap: AI_TREE_TOKENS.spacing8,
1393
- alignItems: "start"
972
+ display: "flex",
973
+ transform: `translateX(-${current * 100}%)`,
974
+ transition: "transform 0.4s ease"
1394
975
  },
1395
- children: group
1396
- },
1397
- p
1398
- ))
1399
- }
1400
- ) });
1401
- const prevBtn = /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1402
- "button",
1403
- {
1404
- type: "button",
1405
- "aria-label": "Previous",
1406
- onClick: () => setPage((p) => Math.max(0, p - 1)),
1407
- style: chrome(current > 0),
1408
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_lucide_react.ArrowLeft, { size: 24, strokeWidth: 1.5, "aria-hidden": true })
1409
- }
1410
- );
1411
- const nextBtn = /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1412
- "button",
1413
- {
1414
- type: "button",
1415
- "aria-label": "Next",
1416
- onClick: () => setPage((p) => Math.min(pages - 1, p + 1)),
1417
- style: chrome(current < pages - 1),
1418
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_lucide_react.ArrowRight, { size: 24, strokeWidth: 1.5, "aria-hidden": true })
1419
- }
1420
- );
1421
- const dots = /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { display: "flex", gap: 9, justifyContent: "center" }, children: pageGroups.map((_, p) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", "aria-label": `Page ${p + 1}`, onClick: () => setPage(p), style: dot(p === current) }, p)) });
1422
- if (isMobile) {
1423
- return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { "data-ai-carousel": "", style: { display: "flex", flexDirection: "column", gap: AI_TREE_TOKENS.spacing6, alignItems: "center" }, children: [
1424
- viewport,
1425
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { display: "flex", gap: AI_TREE_TOKENS.spacing6, justifyContent: "center" }, children: [
1426
- prevBtn,
1427
- nextBtn
1428
- ] }),
1429
- dots
1430
- ] });
1431
- }
1432
- return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { "data-ai-carousel": "", style: { display: "flex", flexDirection: "column", gap: AI_TREE_TOKENS.spacing8, alignItems: "center" }, children: [
1433
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { display: "flex", alignItems: "center", gap: AI_TREE_TOKENS.spacing6, width: "100%" }, children: [
1434
- prevBtn,
1435
- viewport,
1436
- nextBtn
976
+ children: pageGroups.map((group, p) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
977
+ "div",
978
+ {
979
+ "data-ai-grid": String(itemsPerRow),
980
+ style: {
981
+ flex: "0 0 100%",
982
+ display: "grid",
983
+ gridTemplateColumns: `repeat(${itemsPerRow}, 1fr)`,
984
+ gap: AI_TREE_TOKENS.spacing8,
985
+ alignItems: "start"
986
+ },
987
+ children: group
988
+ },
989
+ p
990
+ ))
991
+ }
992
+ ) }),
993
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
994
+ "button",
995
+ {
996
+ type: "button",
997
+ "aria-label": "Next",
998
+ onClick: () => setPage((p) => Math.min(pages - 1, p + 1)),
999
+ style: chrome(current < pages - 1),
1000
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_lucide_react.ArrowRight, { size: 24, strokeWidth: 1.5, "aria-hidden": true })
1001
+ }
1002
+ )
1437
1003
  ] }),
1438
- dots
1004
+ pages > 1 && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { display: "flex", gap: 9, justifyContent: "center" }, children: pageGroups.map((_, p) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", "aria-label": `Page ${p + 1}`, onClick: () => setPage(p), style: dot(p === current) }, p)) })
1439
1005
  ] });
1440
1006
  }
1441
1007
  function CollectionBlock({ node, ctx, path }) {
@@ -1529,49 +1095,6 @@ function renderNode(node, ctx, path) {
1529
1095
  switch (node.type) {
1530
1096
  case "text":
1531
1097
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TextBlock, { slots, ctx, path });
1532
- // Layout container: arranges child blocks, contributes no content of its own. `grid` is a
1533
- // nested 12-column grid the children span (a collage is 3–5 media on it, bottom-aligned so
1534
- // mixed aspects read as a composition); `split` is exactly two children at a ratio; `stack`
1535
- // is a column. Children render through this same dispatcher, so edit markers, media
1536
- // resolution, and copy paths all work unchanged inside a group.
1537
- case "group": {
1538
- const layout = str(slots.layout);
1539
- const gap = slots.spacing === "tight" ? AI_TREE_TOKENS.spacing3 : slots.spacing === "airy" ? AI_TREE_TOKENS.spacing8 : AI_TREE_TOKENS.spacing6;
1540
- const kids = (node.children ?? []).map((child, i) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1541
- "div",
1542
- {
1543
- style: layout === "grid" ? {
1544
- gridColumn: `span ${typeof child.span === "number" ? Math.min(12, Math.max(1, child.span)) : 12}`,
1545
- minWidth: 0
1546
- } : { minWidth: 0 },
1547
- children: renderNode(child, ctx, `${path}.c${i}`)
1548
- },
1549
- i
1550
- ));
1551
- if (layout === "grid") {
1552
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1553
- "div",
1554
- {
1555
- "data-ai-group": "grid",
1556
- style: { display: "grid", gridTemplateColumns: "repeat(12, 1fr)", gap, alignItems: "end" },
1557
- children: kids
1558
- }
1559
- );
1560
- }
1561
- if (layout === "split") {
1562
- const ratio = str(slots.ratio);
1563
- const cols = ratio === "3:5" ? "3fr 5fr" : ratio === "5:3" ? "5fr 3fr" : "1fr 1fr";
1564
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1565
- "div",
1566
- {
1567
- "data-ai-group": "split",
1568
- style: { display: "grid", gridTemplateColumns: cols, gap, alignItems: "center" },
1569
- children: kids
1570
- }
1571
- );
1572
- }
1573
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { "data-ai-group": "stack", style: { display: "flex", flexDirection: "column", gap }, children: kids });
1574
- }
1575
1098
  case "button":
1576
1099
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ButtonEl, { slots, ctx, path });
1577
1100
  case "button-row":
@@ -1652,102 +1175,33 @@ function renderNode(node, ctx, path) {
1652
1175
  }
1653
1176
  );
1654
1177
  }
1655
- case "form": {
1656
- const formAttrs = ctx.keyFor ? {
1657
- "data-ohw-editable": "form",
1658
- "data-ohw-key": ctx.keyFor(`${path}.form`),
1659
- "data-ohw-success-text": "Thanks \u2014 we'll be in touch shortly."
1660
- } : {};
1661
- const fieldStyle = {
1662
- width: "100%",
1663
- boxSizing: "border-box",
1664
- border: `1px solid ${ctx.brand.palette.accent}`,
1665
- borderRadius: AI_TREE_TOKENS.radiusButton,
1666
- padding: "12px 14px",
1667
- background: "#fff",
1668
- color: ctx.brand.palette.dark,
1669
- outline: "none",
1670
- ...typeStyle(AI_TREE_TOKENS.type.bodyM, ctx.brand.fonts.body)
1671
- };
1672
- const labelStyle = {
1673
- ...typeStyle(AI_TREE_TOKENS.type.bodyMBold, ctx.brand.fonts.body),
1674
- color: ctx.brand.palette.dark
1675
- };
1676
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1677
- "form",
1678
- {
1679
- ...formAttrs,
1680
- "data-ai-form": "",
1681
- style: {
1682
- display: "grid",
1683
- gridTemplateColumns: "repeat(2, minmax(0, 1fr))",
1684
- columnGap: 64,
1685
- rowGap: AI_TREE_TOKENS.spacing4
1686
- },
1687
- children: (node.children ?? []).map((child, i) => {
1688
- if (child.type === "input") {
1689
- const cs2 = child.slots ?? {};
1690
- const kind = str(cs2.kind);
1691
- const label = str(cs2.label);
1692
- const placeholder = str(cs2.placeholder);
1693
- const name = label.toLowerCase().replace(/[^a-z0-9]+/gu, "-").replace(/^-+|-+$/gu, "") || `field-${i}`;
1694
- const isTextarea = kind === "textarea";
1695
- return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
1696
- "div",
1697
- {
1698
- style: {
1699
- display: "flex",
1700
- flexDirection: "column",
1701
- gap: 8,
1702
- ...isTextarea ? { gridColumn: "1 / -1", maxWidth: 780 } : {}
1703
- },
1704
- children: [
1705
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("label", { ...textAttrs(ctx, `${path}.c${i}.label`), style: labelStyle, children: label }),
1706
- isTextarea ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1707
- "textarea",
1708
- {
1709
- name,
1710
- placeholder,
1711
- style: { ...fieldStyle, height: 140, resize: "vertical" }
1712
- }
1713
- ) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1714
- "input",
1715
- {
1716
- name,
1717
- type: kind === "email" ? "email" : "text",
1718
- placeholder,
1719
- style: { ...fieldStyle, height: 48 }
1720
- }
1721
- )
1722
- ]
1723
- },
1724
- i
1725
- );
1726
- }
1727
- const cs = child.slots ?? {};
1728
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1729
- "button",
1178
+ case "form":
1179
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { display: "flex", flexDirection: "column", gap: AI_TREE_TOKENS.spacing4 }, children: (node.children ?? []).map((child, i) => {
1180
+ if (child.type === "input") {
1181
+ const cs = child.slots ?? {};
1182
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { children: [
1183
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1184
+ "div",
1185
+ {
1186
+ ...textAttrs(ctx, `${path}.c${i}.label`),
1187
+ style: { ...typeStyle(AI_TREE_TOKENS.type.bodyMBold, ctx.brand.fonts.body), color: ctx.brand.palette.dark, marginBottom: 6 },
1188
+ children: str(cs.label)
1189
+ }
1190
+ ),
1191
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1192
+ "div",
1730
1193
  {
1731
- type: "submit",
1732
1194
  style: {
1733
- gridColumn: "1 / -1",
1734
- justifySelf: "start",
1735
- border: "none",
1736
- cursor: "pointer",
1737
- padding: `${AI_TREE_TOKENS.spacing3}px ${AI_TREE_TOKENS.spacing6}px`,
1195
+ border: `1px solid ${ctx.brand.palette.accent}`,
1738
1196
  borderRadius: AI_TREE_TOKENS.radiusButton,
1739
- background: ctx.brand.palette.primary,
1740
- color: AI_TREE_TOKENS.textPrimaryForeground,
1741
- ...typeStyle(AI_TREE_TOKENS.type.button, ctx.brand.fonts.body)
1742
- },
1743
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { ...textAttrs(ctx, `${path}.c${i}.label`), children: str(cs.label) })
1744
- },
1745
- i
1746
- );
1747
- })
1197
+ height: cs.kind === "textarea" ? 96 : 42
1198
+ }
1199
+ }
1200
+ )
1201
+ ] }, i);
1748
1202
  }
1749
- );
1750
- }
1203
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ButtonEl, { slots: child.slots ?? {}, ctx, path: `${path}.c${i}` }) }, i);
1204
+ }) });
1751
1205
  case "schedule-widget":
1752
1206
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1753
1207
  "div",
@@ -1773,14 +1227,11 @@ function AiTreeRenderer({ tree, brand, resolveMedia, editKeyPrefix }) {
1773
1227
  return null;
1774
1228
  }
1775
1229
  const resolvedBrand = brand ?? AI_DEFAULT_BRAND;
1776
- const band = (tree.settings ?? {}).sectionBackground === "accent" ? accentBandContext(resolvedBrand) : null;
1777
- const blockBrand = band?.brand ?? resolvedBrand;
1778
1230
  const ctx = {
1779
- brand: blockBrand,
1231
+ brand: resolvedBrand,
1780
1232
  resolveMedia: resolveMedia ?? (() => null),
1781
- cardSurface: blockBrand.palette.light.toUpperCase() === AI_DEFAULT_BRAND.palette.light.toUpperCase() ? "#ECECEB" : `color-mix(in srgb, ${blockBrand.palette.light} 90%, ${blockBrand.palette.dark})`,
1782
- keyFor: editKeyPrefix ? (path) => `${editKeyPrefix}.${path}` : null,
1783
- ...band ? { buttonLabel: band.buttonLabel } : {}
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
1784
1235
  };
1785
1236
  const settings = tree.settings ?? {};
1786
1237
  const pad = AI_TREE_TOKENS.sectionPadding[settings.spacing ?? "balanced"];
@@ -1788,20 +1239,6 @@ function AiTreeRenderer({ tree, brand, resolveMedia, editKeyPrefix }) {
1788
1239
  const overlayAlpha = { low: 0.25, medium: 0.45, high: 0.65 }[settings.overlayIntensity ?? "medium"];
1789
1240
  const backgroundUrl = isOverlay && settings.backgroundMedia ? ctx.resolveMedia(settings.backgroundMedia) : null;
1790
1241
  const bgAttrs = ctx.keyFor && isOverlay && settings.backgroundMedia ? { "data-ohw-key": ctx.keyFor("settings.backgroundMedia"), "data-ohw-editable": "bg-image" } : {};
1791
- const toneBackground = (() => {
1792
- const { dark, primary, light } = resolvedBrand.palette;
1793
- switch (settings.sectionBackground) {
1794
- case "surface":
1795
- return `color-mix(in srgb, ${light} 94%, ${dark})`;
1796
- case "accent":
1797
- return primary;
1798
- case "accent-soft":
1799
- return `color-mix(in srgb, ${primary} 12%, ${light})`;
1800
- default:
1801
- return void 0;
1802
- }
1803
- })();
1804
- const distributed = !isOverlay && settings.textDistribution;
1805
1242
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
1806
1243
  "section",
1807
1244
  {
@@ -1811,15 +1248,13 @@ function AiTreeRenderer({ tree, brand, resolveMedia, editKeyPrefix }) {
1811
1248
  style: {
1812
1249
  position: "relative",
1813
1250
  padding: `${pad}px 0`,
1814
- background: toneBackground ?? (isOverlay && !backgroundUrl ? "#DBEAFE" : void 0),
1251
+ background: isOverlay && !backgroundUrl ? "#DBEAFE" : void 0,
1815
1252
  backgroundImage: backgroundUrl ? `url(${backgroundUrl})` : void 0,
1816
1253
  backgroundSize: "cover",
1817
- backgroundPosition: "center",
1818
- color: settings.sectionBackground === "accent" ? blockBrand.palette.dark : void 0
1254
+ backgroundPosition: "center"
1819
1255
  },
1820
1256
  children: [
1821
1257
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("style", { children: AI_RESPONSIVE_CSS }),
1822
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("style", { children: AI_MOBILE_CSS }),
1823
1258
  isOverlay && backgroundUrl && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { position: "absolute", inset: 0, background: `rgba(255,255,255,${overlayAlpha})` } }),
1824
1259
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1825
1260
  "div",
@@ -1840,24 +1275,10 @@ function AiTreeRenderer({ tree, brand, resolveMedia, editKeyPrefix }) {
1840
1275
  display: "grid",
1841
1276
  gridTemplateColumns: "repeat(12, 1fr)",
1842
1277
  gap: AI_TREE_TOKENS.spacing6,
1843
- alignItems: distributed === "space-between" ? "stretch" : (distributed ?? settings.verticalPosition) === "top" ? "start" : "center",
1278
+ alignItems: settings.verticalPosition === "top" ? "start" : "center",
1844
1279
  marginTop: r2 > 0 ? AI_TREE_TOKENS.spacing8 : 0
1845
1280
  },
1846
- children: (row.blocks ?? []).map((block, b) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1847
- "div",
1848
- {
1849
- "data-ai-cell": "",
1850
- style: {
1851
- gridColumn: `span ${Math.min(12, Math.max(1, block.span))}`,
1852
- minWidth: 0,
1853
- // space-between: each column becomes a flex column whose content spreads over
1854
- // the full row height instead of clumping at the top.
1855
- ...distributed === "space-between" ? { display: "flex", flexDirection: "column", justifyContent: "space-between" } : {}
1856
- },
1857
- children: renderNode(block, ctx, `r${r2}.b${b}`)
1858
- },
1859
- b
1860
- ))
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))
1861
1282
  },
1862
1283
  r2
1863
1284
  ))
@@ -1873,36 +1294,17 @@ var import_jsx_runtime2 = require("react/jsx-runtime");
1873
1294
  var CONTAINER_ATTR = "data-ohw-ai-generated";
1874
1295
  var REPLACED_ATTR = "data-ohw-ai-replaced-by";
1875
1296
  var REMOVED_ATTR = "data-ohw-ai-removed";
1876
- var TEMPLATE_HIDDEN_ATTR = "data-ohw-ai-template-hidden";
1877
- var CHROME_SECTION_IDS = /* @__PURE__ */ new Set(["navbar", "footer"]);
1878
- function readRootVar(name) {
1879
- if (typeof document === "undefined") return "";
1880
- return getComputedStyle(document.documentElement).getPropertyValue(name).trim();
1881
- }
1882
- function deriveBrandOverride() {
1883
- const dark = readRootVar("--ohw-brand-dark");
1884
- const primary = readRootVar("--ohw-brand-primary");
1885
- const light = readRootVar("--ohw-brand-light");
1886
- if (!dark || !primary || !light) return null;
1887
- const accent = readRootVar("--ohw-brand-accent");
1888
- const heading = readRootVar("--font-heading") || readRootVar("--font-display");
1889
- const body = readRootVar("--font-body");
1890
- return {
1891
- palette: { dark, primary, accent: accent || dark, light },
1892
- fonts: {
1893
- heading: heading || AI_DEFAULT_BRAND.fonts.heading,
1894
- body: body || AI_DEFAULT_BRAND.fonts.body
1895
- }
1896
- };
1897
- }
1898
1297
  function deriveTemplateBrand() {
1899
- const dark = readRootVar("--color-dark");
1900
- const primary = readRootVar("--color-primary");
1901
- const light = readRootVar("--color-light");
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");
1902
1304
  if (!dark || !primary || !light) return null;
1903
- const accent = readRootVar("--color-accent");
1904
- const heading = readRootVar("--font-heading") || readRootVar("--font-display");
1905
- const body = readRootVar("--font-body");
1305
+ const accent = read("--color-accent");
1306
+ const heading = read("--font-heading") || read("--font-display");
1307
+ const body = read("--font-body");
1906
1308
  return {
1907
1309
  palette: { dark, primary, accent: accent || dark, light },
1908
1310
  fonts: {
@@ -1974,24 +1376,6 @@ function syncRemovedSections(state) {
1974
1376
  }
1975
1377
  }
1976
1378
  }
1977
- function syncTemplateHidden(state, pageHasSections) {
1978
- const hide = state.hideTemplate === true && pageHasSections;
1979
- for (const el of Array.from(document.querySelectorAll(`[${TEMPLATE_HIDDEN_ATTR}]`))) {
1980
- if (!hide) {
1981
- el.style.removeProperty("display");
1982
- el.removeAttribute(TEMPLATE_HIDDEN_ATTR);
1983
- }
1984
- }
1985
- if (!hide) return;
1986
- for (const el of Array.from(document.querySelectorAll("[data-ohw-section]"))) {
1987
- if (el.hasAttribute(CONTAINER_ATTR)) continue;
1988
- if (CHROME_SECTION_IDS.has(el.getAttribute("data-ohw-section") ?? "")) continue;
1989
- if (el.parentElement?.closest("[data-ohw-section]")) continue;
1990
- if (el.hasAttribute(REPLACED_ATTR) || el.hasAttribute(REMOVED_ATTR)) continue;
1991
- el.style.display = "none";
1992
- el.setAttribute(TEMPLATE_HIDDEN_ATTR, "");
1993
- }
1994
- }
1995
1379
  function syncReplacedOriginals(state) {
1996
1380
  for (const el of document.querySelectorAll(`[${REPLACED_ATTR}]`)) {
1997
1381
  const byId = el.getAttribute(REPLACED_ATTR) ?? "";
@@ -2010,63 +1394,10 @@ function syncReplacedOriginals(state) {
2010
1394
  }
2011
1395
  }
2012
1396
  }
2013
- var sectionOrderIndex = /* @__PURE__ */ new Map();
2014
- function setAiSectionOrder(raw, currentPath) {
2015
- const next = /* @__PURE__ */ new Map();
2016
- if (raw) {
2017
- try {
2018
- const entries = JSON.parse(raw);
2019
- for (const entry of entries) {
2020
- if (!entry.pagePath || entry.pagePath === currentPath) next.set(entry.instanceId, entry.order);
2021
- }
2022
- } catch {
2023
- }
2024
- }
2025
- sectionOrderIndex = next;
2026
- }
2027
- function applyExplicitOrder(entries) {
2028
- if (sectionOrderIndex.size === 0) return entries;
2029
- return entries.map((entry, index) => ({ entry, index, order: sectionOrderIndex.get(entry.id) })).sort((a, b) => {
2030
- if (a.order === void 0 && b.order === void 0) return a.index - b.index;
2031
- if (a.order === void 0) return 1;
2032
- if (b.order === void 0) return -1;
2033
- return a.order - b.order;
2034
- }).map((item) => item.entry);
2035
- }
2036
- function orderByChain(sections) {
2037
- const ids = new Set(sections.map((entry) => entry.id));
2038
- const after = /* @__PURE__ */ new Map();
2039
- const roots = [];
2040
- for (const entry of sections) {
2041
- const anchor = entry.replaces ?? entry.beforeSection ?? entry.afterSection ?? null;
2042
- if (anchor && ids.has(anchor)) {
2043
- const bucket = after.get(anchor);
2044
- if (bucket) bucket.push(entry);
2045
- else after.set(anchor, [entry]);
2046
- } else {
2047
- roots.push(entry);
2048
- }
2049
- }
2050
- const out = [];
2051
- const seen = /* @__PURE__ */ new Set();
2052
- const visit = (entry) => {
2053
- if (seen.has(entry.id)) return;
2054
- seen.add(entry.id);
2055
- out.push(entry);
2056
- for (const child of after.get(entry.id) ?? []) visit(child);
2057
- };
2058
- for (const root of roots) visit(root);
2059
- return out.length === sections.length ? out : sections;
2060
- }
2061
1397
  function applyAiSectionsToDom(state, options) {
2062
1398
  if (typeof document === "undefined") return;
2063
- const brandOverride = deriveBrandOverride();
2064
1399
  const templateBrand = deriveTemplateBrand();
2065
- const brandKey = brandOverride ? JSON.stringify(brandOverride) : "";
2066
- const pagePath = window.location.pathname;
2067
- const pageSections = state.sections.filter((entry) => (entry.path ?? "/") === pagePath);
2068
- const activeIds = new Set(pageSections.map((entry) => entry.id));
2069
- const ordered = state.hideTemplate === true ? applyExplicitOrder(orderByChain(pageSections)) : pageSections;
1400
+ const activeIds = new Set(state.sections.map((entry) => entry.id));
2070
1401
  for (const [id, section] of mounted) {
2071
1402
  if (!activeIds.has(id)) {
2072
1403
  section.root.unmount();
@@ -2074,8 +1405,8 @@ function applyAiSectionsToDom(state, options) {
2074
1405
  mounted.delete(id);
2075
1406
  }
2076
1407
  }
2077
- for (const entry of ordered) {
2078
- const serialized = JSON.stringify(entry) + brandKey;
1408
+ for (const entry of state.sections) {
1409
+ const serialized = JSON.stringify(entry);
2079
1410
  const existing = mounted.get(entry.id);
2080
1411
  if (existing && existing.serialized === serialized && existing.container.isConnected) {
2081
1412
  continue;
@@ -2100,7 +1431,7 @@ function applyAiSectionsToDom(state, options) {
2100
1431
  AiTreeRenderer,
2101
1432
  {
2102
1433
  tree: entry.tree,
2103
- brand: brandOverride ?? entry.brand ?? options?.brand ?? templateBrand ?? void 0,
1434
+ brand: entry.brand ?? options?.brand ?? templateBrand ?? void 0,
2104
1435
  resolveMedia,
2105
1436
  editKeyPrefix: `ai.${entry.id}`
2106
1437
  }
@@ -2109,20 +1440,8 @@ function applyAiSectionsToDom(state, options) {
2109
1440
  });
2110
1441
  mounted.set(entry.id, { root, container, serialized });
2111
1442
  }
2112
- if (state.hideTemplate === true) {
2113
- let prev = null;
2114
- for (const entry of ordered) {
2115
- const el = mounted.get(entry.id)?.container;
2116
- if (!el) continue;
2117
- if (prev && !(prev.compareDocumentPosition(el) & Node.DOCUMENT_POSITION_FOLLOWING)) {
2118
- prev.insertAdjacentElement("afterend", el);
2119
- }
2120
- prev = el;
2121
- }
2122
- }
2123
1443
  syncReplacedOriginals(state);
2124
1444
  syncRemovedSections(state);
2125
- syncTemplateHidden(state, pageSections.length > 0);
2126
1445
  }
2127
1446
 
2128
1447
  // src/useLinkHrefGuardian.ts
@@ -2729,7 +2048,7 @@ function SchedulingWidget({ notifyOnConnect = false, initialScheduleId, insertAf
2729
2048
  const autoId = (0, import_react5.useId)();
2730
2049
  const insertAfter = insertAfterProp ?? autoId;
2731
2050
  const [schedule, setSchedule] = (0, import_react5.useState)(null);
2732
- const [loading, setLoading] = (0, import_react5.useState)(initialScheduleId !== null);
2051
+ const [loading, setLoading] = (0, import_react5.useState)(true);
2733
2052
  const [inEditor, setInEditor] = (0, import_react5.useState)(false);
2734
2053
  const [isHovered, setIsHovered] = (0, import_react5.useState)(false);
2735
2054
  const [modalState, setModalState] = (0, import_react5.useState)(null);
@@ -2903,10 +2222,8 @@ function SchedulingWidget({ notifyOnConnect = false, initialScheduleId, insertAf
2903
2222
  "*"
2904
2223
  );
2905
2224
  };
2225
+ if (!inEditor && !loading && !schedule) return null;
2906
2226
  const sectionId = `scheduling-${insertAfter}`;
2907
- if (!inEditor && !loading && !schedule) {
2908
- return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("section", { "data-ohw-section": sectionId, "data-ohw-scheduling-anchor": insertAfter, style: { display: "none" } });
2909
- }
2910
2227
  return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
2911
2228
  "section",
2912
2229
  {
@@ -7823,17 +7140,13 @@ function MediaOverlay({
7823
7140
  hover,
7824
7141
  isUploading,
7825
7142
  fadingOut = false,
7826
- selected = false,
7827
- hovered = false,
7828
7143
  onFadeOutComplete,
7829
7144
  onReplace,
7830
- onSelect,
7831
7145
  onVideoSettingsChange
7832
7146
  }) {
7833
7147
  const { rect } = hover;
7834
7148
  const skeletonRef = React8.useRef(null);
7835
7149
  const isVideo = hover.elementType === "video";
7836
- const showChrome = !selected || hovered;
7837
7150
  const autoplay = hover.videoAutoplay ?? true;
7838
7151
  const muted = hover.videoMuted ?? true;
7839
7152
  const probeRef = React8.useRef(null);
@@ -7847,7 +7160,6 @@ function MediaOverlay({
7847
7160
  (prev) => prev && prev.fullWidth === width && prev.height === height ? prev : { fullWidth: width, height }
7848
7161
  );
7849
7162
  }, [isVideo]);
7850
- const replaceLabel = isVideo ? "Replace video" : hover.elementType === "bg-image" ? "Replace background" : "Replace image";
7851
7163
  const replaceMode = buttonSize ? replaceButtonMode({ width: rect.width, height: rect.height }, buttonSize) : "none";
7852
7164
  const box = {
7853
7165
  position: "fixed",
@@ -7881,7 +7193,7 @@ function MediaOverlay({
7881
7193
  }
7882
7194
  );
7883
7195
  }
7884
- const settingsBar = isVideo && !hover.isDragOver && showChrome ? /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(
7196
+ const settingsBar = isVideo && !hover.isDragOver ? /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(
7885
7197
  "div",
7886
7198
  {
7887
7199
  "data-ohw-bridge": "",
@@ -7951,12 +7263,10 @@ function MediaOverlay({
7951
7263
  // in-document, pointer-events does it natively. The button below opts back in, so
7952
7264
  // Replace still works.
7953
7265
  pointerEvents: hover.hasTextOverlap ? "none" : "auto",
7954
- // Selected: a firm component ring with no wash, so the image reads as chosen rather
7955
- // than hovered. Hover keeps the existing tinted preview.
7956
- boxShadow: selected ? "inset 0 0 0 2px var(--color-primary)" : "inset 0 0 0 1.5px var(--color-primary)",
7957
- background: selected ? "transparent" : "color-mix(in srgb, var(--color-primary) 20%, transparent)"
7266
+ boxShadow: "inset 0 0 0 1.5px var(--color-primary)",
7267
+ background: "color-mix(in srgb, var(--color-primary) 20%, transparent)"
7958
7268
  },
7959
- onClick: () => onSelect && !selected ? onSelect(hover.key) : onReplace(hover.key),
7269
+ onClick: () => onReplace(hover.key),
7960
7270
  children: [
7961
7271
  /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(
7962
7272
  Button,
@@ -7977,17 +7287,17 @@ function MediaOverlay({
7977
7287
  },
7978
7288
  children: [
7979
7289
  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 }),
7980
- replaceLabel
7290
+ isVideo ? "Replace video" : "Replace image"
7981
7291
  ]
7982
7292
  }
7983
7293
  ),
7984
- showChrome && replaceMode !== "none" && /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(
7294
+ replaceMode === "none" ? null : /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(
7985
7295
  Button,
7986
7296
  {
7987
7297
  "data-ohw-media-overlay": "",
7988
7298
  variant: "outline",
7989
7299
  size: "sm",
7990
- "aria-label": replaceLabel,
7300
+ "aria-label": isVideo ? "Replace video" : "Replace image",
7991
7301
  className: "gap-1.5 cursor-pointer hover:bg-background",
7992
7302
  style: {
7993
7303
  ...OVERLAY_BUTTON_STYLE,
@@ -8010,7 +7320,7 @@ function MediaOverlay({
8010
7320
  },
8011
7321
  children: [
8012
7322
  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 }),
8013
- replaceMode === "full" ? replaceLabel : null
7323
+ replaceMode === "full" ? isVideo ? "Replace video" : "Replace image" : null
8014
7324
  ]
8015
7325
  }
8016
7326
  )
@@ -8094,8 +7404,6 @@ function parseSectionsFromRoot(root) {
8094
7404
  const id = el.getAttribute("data-ohw-section") ?? "";
8095
7405
  if (!id || seen.has(id) || LINK_PICKER_EXCLUDED_IDS.has(id)) continue;
8096
7406
  if (el.parentElement?.closest("[data-ohw-section]")) continue;
8097
- if (el.hasAttribute("data-ohw-ai-template-hidden") || el.hasAttribute("data-ohw-ai-removed") || el.hasAttribute("data-ohw-ai-replaced-by"))
8098
- continue;
8099
7407
  seen.add(id);
8100
7408
  const label = el.getAttribute("data-ohw-section-label") ?? titleCaseSectionId(id);
8101
7409
  sections.push({ id, label });
@@ -8125,10 +7433,6 @@ function topLevelSections() {
8125
7433
  function instanceIdOf(el) {
8126
7434
  return el.dataset.ohwInstance ?? el.dataset.ohwSection ?? "";
8127
7435
  }
8128
- function findByInstanceId(instanceId) {
8129
- const escapedId = CSS.escape(instanceId);
8130
- return document.querySelector(`[data-ohw-instance="${escapedId}"]`) ?? document.querySelector(`[data-ohw-section="${escapedId}"]:not([data-ohw-instance])`);
8131
- }
8132
7436
  function planSectionMove(instanceId, targetIndex, currentPath) {
8133
7437
  const sections = topLevelSections();
8134
7438
  const index = sections.findIndex((el) => instanceIdOf(el) === instanceId);
@@ -8164,7 +7468,7 @@ function syncRemovedFlags(entries) {
8164
7468
  }
8165
7469
  });
8166
7470
  for (const id of removedIds) {
8167
- const el = findByInstanceId(id);
7471
+ const el = document.querySelector(`[data-ohw-instance="${CSS.escape(id)}"]`);
8168
7472
  if (el) {
8169
7473
  el.style.display = "none";
8170
7474
  el.setAttribute(REMOVED_ATTR2, "");
@@ -8192,7 +7496,9 @@ function applyPersistedOrder(entries) {
8192
7496
  }
8193
7497
  }
8194
7498
  function setSectionRemoved(instanceId, currentPath, existingEntries, removed) {
8195
- if (!findByInstanceId(instanceId)) return null;
7499
+ const escapedId = CSS.escape(instanceId);
7500
+ if (!document.querySelector(`[data-ohw-instance="${escapedId}"]`) && !document.querySelector(`[data-ohw-section="${escapedId}"]:not([data-ohw-instance])`))
7501
+ return null;
8196
7502
  const byId = new Map(existingEntries.map((e) => [e.instanceId, e]));
8197
7503
  const allSections = Array.from(document.querySelectorAll("[data-ohw-section]")).filter(
8198
7504
  (el) => !el.parentElement?.closest("[data-ohw-section]") && !isChromeSection(el)
@@ -8381,7 +7687,6 @@ function AiSectionOverlay({
8381
7687
  }) {
8382
7688
  const [selectedId, setSelectedId] = (0, import_react8.useState)(null);
8383
7689
  const [reviewId, setReviewId] = (0, import_react8.useState)(null);
8384
- const [reviewButtonsHidden, setReviewButtonsHidden] = (0, import_react8.useState)(false);
8385
7690
  const reviewIdRef = (0, import_react8.useRef)(null);
8386
7691
  reviewIdRef.current = reviewId;
8387
7692
  const selectedIdRef = (0, import_react8.useRef)(null);
@@ -8443,7 +7748,6 @@ function AiSectionOverlay({
8443
7748
  }
8444
7749
  const found = readRect(sectionId) != null;
8445
7750
  setReviewId(found ? sectionId : null);
8446
- setReviewButtonsHidden(e.data.hideButtons === true);
8447
7751
  postToParent2({ type: "ow:ai-review-started", sectionId, found });
8448
7752
  if (found) {
8449
7753
  document.querySelector(`[data-ohw-instance="${CSS.escape(sectionId)}"]`)?.scrollIntoView({ behavior: "smooth", block: "nearest" });
@@ -8571,16 +7875,13 @@ function AiSectionOverlay({
8571
7875
  border: `2px solid ${PRIMARY2}`,
8572
7876
  borderRadius: edgeAwareRadius(reviewRect),
8573
7877
  zIndex: 2147483200,
8574
- // The veil itself: swallows clicks so the section stays locked until decided. This
8575
- // stopPropagation only guards the bubble phase; the bridge's capture-phase click
8576
- // handlers additionally bail on `[data-ohw-ai-review]`, without which a click on
8577
- // Accept/Discard resolves to the media beneath and opens the file picker.
7878
+ // The veil itself: swallows clicks so the section stays locked until decided.
8578
7879
  background: "rgba(8, 133, 254, 0.04)",
8579
7880
  pointerEvents: "auto",
8580
7881
  cursor: "default"
8581
7882
  },
8582
7883
  onClick: (e) => e.stopPropagation(),
8583
- children: !reviewButtonsHidden && /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(
7884
+ children: /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(
8584
7885
  "div",
8585
7886
  {
8586
7887
  style: {
@@ -11147,13 +10448,8 @@ function referenceBox(slot) {
11147
10448
  const neighbour = row ? Array.from(row.querySelectorAll('[data-ohw-editable="icon"]')).find(
11148
10449
  (el) => el !== slot && !el.hasAttribute("data-ohw-social-icon-placeholder")
11149
10450
  ) : null;
11150
- if (neighbour) {
11151
- const box2 = neighbour.querySelector(GLYPH_SELECTOR)?.getBoundingClientRect();
11152
- if (box2?.width && box2.height) return box2;
11153
- }
11154
- const own = slot.getBoundingClientRect();
11155
- if (own.width && own.height) return own;
11156
- const box = slot.querySelector(GLYPH_SELECTOR)?.getBoundingClientRect() ?? null;
10451
+ const source = (neighbour ?? slot).querySelector(GLYPH_SELECTOR);
10452
+ const box = source?.getBoundingClientRect() ?? null;
11157
10453
  return box?.width && box.height ? box : null;
11158
10454
  }
11159
10455
  function iconMarkupSizedFor(slot, markup) {
@@ -12954,7 +12250,6 @@ function readLogoSizeState(content, placement) {
12954
12250
  function getLogoElement(el) {
12955
12251
  const marked = el.closest('[data-ohw-role="logo"], [data-ohw-logo]');
12956
12252
  if (marked) return marked;
12957
- if (el.closest('[data-ohw-editable="icon"]')) return null;
12958
12253
  const root = el.closest("nav, [data-ohw-nav-root], footer");
12959
12254
  if (!root) return null;
12960
12255
  const anchor = el.closest("a");
@@ -14023,7 +13318,6 @@ function useSectionDrag({
14023
13318
  }
14024
13319
  const orderJson = JSON.stringify(entries);
14025
13320
  editContentRef.current = { ...editContentRef.current, [SECTION_ORDER_KEY]: orderJson };
14026
- setAiSectionOrder(orderJson, window.location.pathname);
14027
13321
  postToParentRef.current({ type: "ow:change", nodes: [{ key: SECTION_ORDER_KEY, text: orderJson }] });
14028
13322
  applyPersistedOrder(entries);
14029
13323
  clearSectionDragVisuals();
@@ -14883,10 +14177,21 @@ function parseSchedulingInsertAfter(insertAfter) {
14883
14177
  insertBefore: insertAfter.slice(idx + INSERT_BEFORE_MARKER.length)
14884
14178
  };
14885
14179
  }
14886
- function resolveEntryAnchor(entry) {
14887
- if (entry.anchorId !== void 0) return { anchorId: entry.anchorId, beforeId: entry.beforeId ?? null };
14888
- const parsed = parseSchedulingInsertAfter(entry.insertAfter);
14889
- return { anchorId: parsed.anchor, beforeId: parsed.insertBefore };
14180
+ function resolveSchedulingInsert(insertAfter, explicitInsertBefore) {
14181
+ const parsed = parseSchedulingInsertAfter(insertAfter);
14182
+ const insertBefore = explicitInsertBefore ?? parsed.insertBefore;
14183
+ const effectiveInsertAfter = insertBefore && parsed.insertBefore === null ? `${insertAfter}${INSERT_BEFORE_MARKER}${insertBefore}` : insertAfter;
14184
+ return { effectiveInsertAfter, insertBefore };
14185
+ }
14186
+ function getSchedulingMountPoint(insertAfter) {
14187
+ const { anchor } = parseSchedulingInsertAfter(insertAfter);
14188
+ let anchorEl = document.querySelector(`[data-ohw-section="${anchor}"]`);
14189
+ if (!anchorEl && anchor === "scheduling") {
14190
+ const widgets = Array.from(document.querySelectorAll('[data-ohw-section^="scheduling-"]'));
14191
+ anchorEl = widgets.at(-1) ?? null;
14192
+ }
14193
+ if (!anchorEl) return null;
14194
+ return anchorEl.closest('[data-ohw-section-container="scheduling"]') ?? anchorEl;
14890
14195
  }
14891
14196
  function schedulingMountDepth(insertAfter) {
14892
14197
  if (!insertAfter.includes(INSERT_BEFORE_MARKER)) return 0;
@@ -14903,7 +14208,8 @@ function getPageSchedulingEntries(raw) {
14903
14208
  }
14904
14209
  }
14905
14210
  function isSchedulingWidgetMissing(entry) {
14906
- return !document.querySelector(`[data-ohw-section="${schedulingSectionId(entry.insertAfter)}"]`);
14211
+ const { effectiveInsertAfter } = resolveSchedulingInsert(entry.insertAfter);
14212
+ return !document.querySelector(`[data-ohw-section="${schedulingSectionId(effectiveInsertAfter)}"]`);
14907
14213
  }
14908
14214
  function hasMissingSchedulingWidgets(entries) {
14909
14215
  return entries.some(isSchedulingWidgetMissing);
@@ -14933,17 +14239,16 @@ function initSectionsFromContent(content, removeExisting = false) {
14933
14239
  } catch {
14934
14240
  }
14935
14241
  }
14936
- function mountSchedulingWidget(anchorId, notifyOnConnect = false, scheduleId, beforeId, existingWidgetId) {
14937
- const widgetId = existingWidgetId ?? (beforeId ? `${anchorId}${INSERT_BEFORE_MARKER}${beforeId}` : anchorId);
14938
- const sectionId = schedulingSectionId(widgetId);
14242
+ function mountSchedulingWidget(insertAfter, notifyOnConnect = false, scheduleId, explicitInsertBefore) {
14243
+ const { effectiveInsertAfter, insertBefore } = resolveSchedulingInsert(insertAfter, explicitInsertBefore);
14244
+ const sectionId = schedulingSectionId(effectiveInsertAfter);
14939
14245
  if (document.querySelector(`[data-ohw-section="${sectionId}"]`)) return false;
14940
- const anchorEl = document.querySelector(`[data-ohw-section="${anchorId}"]`);
14941
- if (!anchorEl) return false;
14942
- const mountPoint = anchorEl.closest('[data-ohw-section-container="scheduling"]') ?? anchorEl;
14246
+ const mountPoint = getSchedulingMountPoint(effectiveInsertAfter);
14247
+ if (!mountPoint) return false;
14943
14248
  const container = document.createElement("div");
14944
14249
  container.dataset.ohwSectionContainer = "scheduling";
14945
- if (beforeId) {
14946
- const beforeAnchor = document.querySelector(`[data-ohw-section="${beforeId}"]`);
14250
+ if (insertBefore) {
14251
+ const beforeAnchor = document.querySelector(`[data-ohw-section="${insertBefore}"]`);
14947
14252
  const beforePoint = beforeAnchor?.closest('[data-ohw-section-container="scheduling"]') ?? beforeAnchor;
14948
14253
  if (!beforePoint) return false;
14949
14254
  beforePoint.insertAdjacentElement("beforebegin", container);
@@ -14954,25 +14259,19 @@ function mountSchedulingWidget(anchorId, notifyOnConnect = false, scheduleId, be
14954
14259
  }
14955
14260
  tail.insertAdjacentElement("afterend", container);
14956
14261
  }
14957
- try {
14958
- const root = (0, import_client2.createRoot)(container);
14959
- (0, import_react_dom3.flushSync)(() => {
14960
- root.render(
14961
- /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
14962
- SchedulingWidget,
14963
- {
14964
- notifyOnConnect,
14965
- initialScheduleId: scheduleId,
14966
- insertAfter: widgetId
14967
- }
14968
- )
14969
- );
14970
- });
14971
- } catch (err) {
14972
- console.error("[ow:scheduling] render threw", err);
14973
- container.remove();
14974
- return false;
14975
- }
14262
+ const root = (0, import_client2.createRoot)(container);
14263
+ (0, import_react_dom3.flushSync)(() => {
14264
+ root.render(
14265
+ /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
14266
+ SchedulingWidget,
14267
+ {
14268
+ notifyOnConnect,
14269
+ initialScheduleId: scheduleId,
14270
+ insertAfter: effectiveInsertAfter
14271
+ }
14272
+ )
14273
+ );
14274
+ });
14976
14275
  const tracker = getSectionsTracker();
14977
14276
  let sections = [];
14978
14277
  try {
@@ -14980,12 +14279,10 @@ function mountSchedulingWidget(anchorId, notifyOnConnect = false, scheduleId, be
14980
14279
  } catch {
14981
14280
  }
14982
14281
  const inEditor = typeof window !== "undefined" && window.self !== window.top;
14983
- if (inEditor && !sections.find((s) => s.type === "scheduling" && s.insertAfter === widgetId)) {
14282
+ if (inEditor && !sections.find((s) => s.type === "scheduling" && s.insertAfter === effectiveInsertAfter)) {
14984
14283
  sections.push({
14985
14284
  type: "scheduling",
14986
- insertAfter: widgetId,
14987
- anchorId,
14988
- beforeId: beforeId ?? null,
14285
+ insertAfter: effectiveInsertAfter,
14989
14286
  pagePath: window.location.pathname,
14990
14287
  ...scheduleId ? { scheduleId } : {}
14991
14288
  });
@@ -14999,8 +14296,7 @@ function mountSchedulingEntries(entries, notifyOnConnect = false) {
14999
14296
  for (let i = pending.length - 1; i >= 0; i--) {
15000
14297
  const entry = pending[i];
15001
14298
  const shouldNotify = typeof notifyOnConnect === "function" ? notifyOnConnect(entry) : notifyOnConnect;
15002
- const { anchorId, beforeId } = resolveEntryAnchor(entry);
15003
- if (mountSchedulingWidget(anchorId, shouldNotify, entry.scheduleId ?? null, beforeId, entry.insertAfter)) {
14299
+ if (mountSchedulingWidget(entry.insertAfter, shouldNotify, entry.scheduleId)) {
15004
14300
  pending.splice(i, 1);
15005
14301
  }
15006
14302
  }
@@ -15158,11 +14454,6 @@ function applyLinkByKey(key, val) {
15158
14454
  hrefAnchors.forEach((el) => applyLinkHref(el, val));
15159
14455
  }
15160
14456
  }
15161
- function isInsideLinkEditor(target) {
15162
- return Boolean(
15163
- 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"]')
15164
- );
15165
- }
15166
14457
  function isInsideFloatingPanel(target) {
15167
14458
  return Boolean(target.closest("[data-ohw-floating-panel]"));
15168
14459
  }
@@ -15170,6 +14461,11 @@ function isPointOverFloatingPanel(clientX, clientY) {
15170
14461
  const el = document.elementFromPoint(clientX, clientY);
15171
14462
  return Boolean(el instanceof Element && el.closest("[data-ohw-floating-panel]"));
15172
14463
  }
14464
+ function isInsideLinkEditor(target) {
14465
+ return Boolean(
14466
+ 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"]')
14467
+ );
14468
+ }
15173
14469
  function getHrefKeyFromElement(el) {
15174
14470
  if (!el) return null;
15175
14471
  const anchor = el.closest("[data-ohw-href-key]");
@@ -15428,7 +14724,7 @@ function getNavigationSelectionParent(el) {
15428
14724
  if (!isFooterLinksContainer(el) && (el.hasAttribute("data-ohw-footer-col") || el.hasAttribute("data-ohw-footer-column") || isInferredFooterGroup2(el))) {
15429
14725
  return getFooterLinksContainer();
15430
14726
  }
15431
- 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)) {
14727
+ if (el.hasAttribute("data-ohw-nav-container") || el.hasAttribute("data-ohw-nav-drawer") || isFooterLinksContainer(el)) {
15432
14728
  return getNavigationRoot(el);
15433
14729
  }
15434
14730
  return null;
@@ -15643,6 +14939,7 @@ var ICONS = {
15643
14939
  insertUnorderedList: '<line x1="8" y1="6" x2="21" y2="6"/><line x1="8" y1="12" x2="21" y2="12"/><line x1="8" y1="18" x2="21" y2="18"/><line x1="3" y1="6" x2="3.01" y2="6"/><line x1="3" y1="12" x2="3.01" y2="12"/><line x1="3" y1="18" x2="3.01" y2="18"/>',
15644
14940
  insertOrderedList: '<line x1="10" y1="6" x2="21" y2="6"/><line x1="10" y1="12" x2="21" y2="12"/><line x1="10" y1="18" x2="21" y2="18"/><path d="M4 6h1v4"/><path d="M4 10h2"/><path d="M6 18H4c0-1 2-2 2-3s-1-1.5-2-1"/>'
15645
14941
  };
14942
+ var HEIGHT_SETTLE_DELAYS = [150, 400, 800];
15646
14943
  var SELECTION_CHROME_GAP2 = 4;
15647
14944
  var TOOLBAR_STROKE_GAP2 = 4;
15648
14945
  var TOOLBAR_OFFSET_FROM_ELEMENT = SELECTION_CHROME_GAP2 + TOOLBAR_STROKE_GAP2;
@@ -16022,7 +15319,6 @@ function StateToggle({
16022
15319
  );
16023
15320
  }
16024
15321
  var contentCache = /* @__PURE__ */ new Map();
16025
- var fetchedContentPaths = /* @__PURE__ */ new Set();
16026
15322
  var OHW_LOADER_STYLE = {
16027
15323
  position: "fixed",
16028
15324
  inset: 0,
@@ -16141,70 +15437,6 @@ function OhhwellsBridge() {
16141
15437
  const hoveredImageHasTextOverlapRef = (0, import_react17.useRef)(false);
16142
15438
  const dragOverElRef = (0, import_react17.useRef)(null);
16143
15439
  const [mediaHover, setMediaHover] = (0, import_react17.useState)(null);
16144
- const [selectedMedia, setSelectedMedia] = (0, import_react17.useState)(null);
16145
- const selectedMediaElRef = (0, import_react17.useRef)(null);
16146
- const clearMediaSelection = (0, import_react17.useCallback)(() => {
16147
- const prev = selectedMediaElRef.current;
16148
- selectedMediaElRef.current = null;
16149
- setSelectedMedia(null);
16150
- const sectionEl = prev?.closest("[data-ohw-section]") ?? null;
16151
- if (sectionEl) {
16152
- postToParentRef.current({
16153
- type: "ow:section-selected",
16154
- sectionId: sectionEl.dataset.ohwSection ?? null,
16155
- sectionLabel: sectionEl.dataset.ohwSectionLabel ?? titleCaseSectionId(sectionEl.dataset.ohwSection ?? ""),
16156
- key: null
16157
- });
16158
- }
16159
- }, []);
16160
- const clearMediaSelectionRef = (0, import_react17.useRef)(clearMediaSelection);
16161
- clearMediaSelectionRef.current = clearMediaSelection;
16162
- const selectMediaElement = (0, import_react17.useCallback)((el) => {
16163
- const r2 = el.getBoundingClientRect();
16164
- const video = el.dataset.ohwEditable === "video" ? el.querySelector("video") : null;
16165
- selectedMediaElRef.current = el;
16166
- setSelectedMedia({
16167
- key: el.dataset.ohwKey ?? "",
16168
- rect: { top: r2.top, left: r2.left, width: r2.width, height: r2.height },
16169
- elementType: el.dataset.ohwEditable ?? "image",
16170
- hasTextOverlap: false,
16171
- isDragOver: false,
16172
- ...video ? { videoAutoplay: video.autoplay, videoMuted: video.muted } : {}
16173
- });
16174
- const sectionEl = el.closest("[data-ohw-section]");
16175
- aiSectionApiRef.current?.selectFromElement(el, { report: false });
16176
- postToParentRef.current({
16177
- type: "ow:section-selected",
16178
- sectionId: sectionEl?.dataset.ohwSection ?? null,
16179
- sectionLabel: sectionEl ? sectionEl.dataset.ohwSectionLabel ?? titleCaseSectionId(sectionEl.dataset.ohwSection ?? "") : null,
16180
- key: el.dataset.ohwKey ?? null,
16181
- // Display name for the pill — the raw key prettifies into fragments ("Img"); the
16182
- // bridge knows what the node IS, so it names it.
16183
- keyLabel: el.dataset.ohwEditable === "video" ? "Video" : el.dataset.ohwEditable === "bg-image" ? "Background" : "Image"
16184
- });
16185
- }, []);
16186
- const selectMediaElementRef = (0, import_react17.useRef)(selectMediaElement);
16187
- selectMediaElementRef.current = selectMediaElement;
16188
- (0, import_react17.useEffect)(() => {
16189
- if (!selectedMedia) return;
16190
- const update = () => {
16191
- const el = selectedMediaElRef.current;
16192
- if (!el || !el.isConnected) {
16193
- clearMediaSelection();
16194
- return;
16195
- }
16196
- const r2 = el.getBoundingClientRect();
16197
- setSelectedMedia(
16198
- (prev) => prev ? { ...prev, rect: { top: r2.top, left: r2.left, width: r2.width, height: r2.height } } : prev
16199
- );
16200
- };
16201
- window.addEventListener("scroll", update, true);
16202
- window.addEventListener("resize", update);
16203
- return () => {
16204
- window.removeEventListener("scroll", update, true);
16205
- window.removeEventListener("resize", update);
16206
- };
16207
- }, [selectedMedia !== null]);
16208
15440
  const [carouselHover, setCarouselHover] = (0, import_react17.useState)(null);
16209
15441
  const [uploadingRects, setUploadingRects] = (0, import_react17.useState)({});
16210
15442
  const hoveredGapRef = (0, import_react17.useRef)(null);
@@ -16467,6 +15699,13 @@ function OhhwellsBridge() {
16467
15699
  const [isItemDragging, setIsItemDragging] = (0, import_react17.useState)(false);
16468
15700
  const [isFooterFrameSelection, setIsFooterFrameSelection] = (0, import_react17.useState)(false);
16469
15701
  isFooterFrameSelectionRef.current = isFooterFrameSelection;
15702
+ const [floatingPanel, setFloatingPanel] = (0, import_react17.useState)(null);
15703
+ const floatingPanelOpenRef = (0, import_react17.useRef)(false);
15704
+ floatingPanelOpenRef.current = floatingPanel !== null;
15705
+ const [floatingPanelPos, setFloatingPanelPos] = (0, import_react17.useState)(null);
15706
+ const [logoSizeDraft, setLogoSizeDraft] = (0, import_react17.useState)(null);
15707
+ const [editorViewport, setEditorViewport] = (0, import_react17.useState)("desktop");
15708
+ const [parentScrollSnap, setParentScrollSnap] = (0, import_react17.useState)(null);
16470
15709
  const [navDropdownPreviewOpen, setNavDropdownPreviewOpen] = (0, import_react17.useState)(null);
16471
15710
  const [footerHeadingVisible, setFooterHeadingVisible] = (0, import_react17.useState)(null);
16472
15711
  const footerDragRef = (0, import_react17.useRef)(null);
@@ -16481,16 +15720,7 @@ function OhhwellsBridge() {
16481
15720
  const addNavAfterAnchorRef = (0, import_react17.useRef)(null);
16482
15721
  const editContentRef = (0, import_react17.useRef)({});
16483
15722
  const aiSectionsRef = (0, import_react17.useRef)("");
16484
- const brandKitRef = (0, import_react17.useRef)("");
16485
- const stylesRef = (0, import_react17.useRef)("");
16486
15723
  const pendingDeleteUndoRef = (0, import_react17.useRef)(null);
16487
- const [floatingPanel, setFloatingPanel] = (0, import_react17.useState)(null);
16488
- const floatingPanelOpenRef = (0, import_react17.useRef)(false);
16489
- const setFloatingPanelRef = (0, import_react17.useRef)(setFloatingPanel);
16490
- const [floatingPanelPos, setFloatingPanelPos] = (0, import_react17.useState)(null);
16491
- const [logoSizeDraft, setLogoSizeDraft] = (0, import_react17.useState)(null);
16492
- const [editorViewport, setEditorViewport] = (0, import_react17.useState)("desktop");
16493
- const [parentScrollSnap, setParentScrollSnap] = (0, import_react17.useState)(null);
16494
15724
  const [sitePages, setSitePages] = (0, import_react17.useState)([]);
16495
15725
  const [sectionsByPath, setSectionsByPath] = (0, import_react17.useState)({});
16496
15726
  const sectionsPrefetchGenRef = (0, import_react17.useRef)(0);
@@ -16499,18 +15729,7 @@ function OhhwellsBridge() {
16499
15729
  const linkPopoverOpenRef = (0, import_react17.useRef)(false);
16500
15730
  const linkPopoverGraceUntilRef = (0, import_react17.useRef)(0);
16501
15731
  setLinkPopoverRef.current = setLinkPopover;
16502
- setFloatingPanelRef.current = setFloatingPanel;
16503
15732
  linkPopoverSessionRef.current = linkPopover;
16504
- floatingPanelOpenRef.current = Boolean(floatingPanel);
16505
- (0, import_react17.useEffect)(() => {
16506
- const syncViewport = () => {
16507
- const next = window.innerWidth <= 480 ? "mobile" : "desktop";
16508
- setEditorViewport((prev) => prev === next ? prev : next);
16509
- };
16510
- syncViewport();
16511
- window.addEventListener("resize", syncViewport);
16512
- return () => window.removeEventListener("resize", syncViewport);
16513
- }, []);
16514
15733
  const {
16515
15734
  navDragRef,
16516
15735
  navDropSlots,
@@ -17821,31 +17040,15 @@ function OhhwellsBridge() {
17821
17040
  }
17822
17041
  const applyContent = (content) => {
17823
17042
  const imageLoads = [];
17824
- if (typeof content[BRAND_KIT_KEY] === "string") {
17825
- brandKitRef.current = content[BRAND_KIT_KEY];
17826
- applyBrandToDom(parseBrandKit(content[BRAND_KIT_KEY]));
17827
- } else {
17828
- brandKitRef.current = "";
17829
- applyBrandToDom(null);
17830
- }
17831
17043
  if (typeof content[AI_SECTIONS_KEY] === "string") {
17832
17044
  aiSectionsRef.current = content[AI_SECTIONS_KEY];
17833
- setAiSectionOrder(content[SECTION_ORDER_KEY], window.location.pathname);
17834
17045
  applyAiSectionsToDom(parseAiSectionsState(content[AI_SECTIONS_KEY]));
17835
17046
  }
17836
- if (typeof content[STYLE_STORE_KEY] === "string") {
17837
- stylesRef.current = content[STYLE_STORE_KEY];
17838
- applyStylesToDom(parseStyleStore(content[STYLE_STORE_KEY]));
17839
- }
17840
- applyBrandChrome(content);
17841
17047
  for (const [key, val] of Object.entries(content)) {
17842
17048
  if (key === "__ohw_sections") continue;
17843
17049
  if (key === AI_SECTIONS_KEY) continue;
17844
17050
  if (key === LOGO_PLACEHOLDER_KEY) continue;
17845
17051
  if (LOGO_IMAGE_KEYS.includes(key)) continue;
17846
- if (key === BRAND_KIT_KEY) continue;
17847
- if (key === STYLE_STORE_KEY) continue;
17848
- if (BRAND_CHROME_KEYS.has(key)) continue;
17849
17052
  if (applyVideoSettingNode(key, val)) continue;
17850
17053
  if (applyCarouselNode(key, val)) continue;
17851
17054
  document.querySelectorAll(`[data-ohw-key="${key}"]`).forEach((el) => {
@@ -17911,9 +17114,7 @@ function OhhwellsBridge() {
17911
17114
  let cancelled = false;
17912
17115
  setFetchState("loading");
17913
17116
  const apiUrl = process.env.NEXT_PUBLIC_FLOWOPS_API_URL ?? "https://flowops-backend-staging-2yfdh7pwpq-as.a.run.app";
17914
- const initialPath = pathname;
17915
- fetchedContentPaths.add(`${subdomain}::${initialPath}`);
17916
- fetch(`${apiUrl}/api/public/sites/${subdomain}/content?path=${encodeURIComponent(initialPath)}`, { cache: "no-store" }).then((r2) => r2.ok ? r2.json() : null).then((data) => {
17117
+ fetch(`${apiUrl}/api/public/sites/${subdomain}/content`, { cache: "no-store" }).then((r2) => r2.ok ? r2.json() : null).then((data) => {
17917
17118
  if (cancelled) return;
17918
17119
  const content = data?.content ?? {};
17919
17120
  contentCache.set(subdomain, content);
@@ -18033,28 +17234,10 @@ function OhhwellsBridge() {
18033
17234
  initSectionInstancesFromContent(content, window.location.pathname);
18034
17235
  observer?.disconnect();
18035
17236
  try {
18036
- applyBrandChrome(content);
18037
- if (typeof content[BRAND_KIT_KEY] === "string") {
18038
- applyBrandToDom(parseBrandKit(content[BRAND_KIT_KEY]));
18039
- } else {
18040
- applyBrandToDom(null);
18041
- }
18042
- if (typeof content[AI_SECTIONS_KEY] === "string") {
18043
- setAiSectionOrder(content[SECTION_ORDER_KEY], window.location.pathname);
18044
- applyAiSectionsToDom(parseAiSectionsState(content[AI_SECTIONS_KEY]));
18045
- }
18046
- if (typeof content[STYLE_STORE_KEY] === "string") {
18047
- applyStylesToDom(parseStyleStore(content[STYLE_STORE_KEY]));
18048
- }
18049
17237
  for (const [key, val] of Object.entries(content)) {
18050
17238
  if (key === "__ohw_sections") continue;
18051
- if (key === AI_SECTIONS_KEY) continue;
18052
17239
  if (key === LOGO_PLACEHOLDER_KEY) continue;
18053
17240
  if (LOGO_IMAGE_KEYS.includes(key)) continue;
18054
- if (key === BRAND_KIT_KEY) continue;
18055
- if (key === STYLE_STORE_KEY) continue;
18056
- if (key === STYLE_STORE_KEY) continue;
18057
- if (BRAND_CHROME_KEYS.has(key)) continue;
18058
17241
  if (applyVideoSettingNode(key, val)) continue;
18059
17242
  if (applyCarouselNode(key, val)) continue;
18060
17243
  document.querySelectorAll(`[data-ohw-key="${key}"]`).forEach((el) => {
@@ -18100,17 +17283,6 @@ function OhhwellsBridge() {
18100
17283
  debounceTimer = setTimeout(applyFromCache, 150);
18101
17284
  };
18102
17285
  applyFromCache();
18103
- const pathCacheKey = `${subdomain}::${pathname}`;
18104
- if (!fetchedContentPaths.has(pathCacheKey)) {
18105
- fetchedContentPaths.add(pathCacheKey);
18106
- const apiUrl = process.env.NEXT_PUBLIC_FLOWOPS_API_URL ?? "https://flowops-backend-staging-2yfdh7pwpq-as.a.run.app";
18107
- fetch(`${apiUrl}/api/public/sites/${subdomain}/content?path=${encodeURIComponent(pathname)}`, { cache: "no-store" }).then((r2) => r2.ok ? r2.json() : null).then((data) => {
18108
- if (!data?.content) return;
18109
- contentCache.set(subdomain, data.content);
18110
- applyFromCache();
18111
- }).catch(() => {
18112
- });
18113
- }
18114
17286
  observer = new MutationObserver(scheduleApply);
18115
17287
  observer.observe(document.body, { childList: true, subtree: true });
18116
17288
  return () => {
@@ -18206,31 +17378,30 @@ function OhhwellsBridge() {
18206
17378
  }, [isEditMode, pathname, subdomain, fetchState, resyncSelectedNavigationItem]);
18207
17379
  (0, import_react17.useEffect)(() => {
18208
17380
  if (!isEditMode) return;
18209
- let lastPosted = 0;
18210
17381
  const measure = () => {
18211
17382
  const h = document.body.scrollHeight;
18212
- if (h > 50 && Math.abs(h - lastPosted) > 1) {
18213
- lastPosted = h;
18214
- postToParent2({ type: "ow:height", height: h });
18215
- }
18216
- };
18217
- let raf = null;
18218
- const schedule = () => {
18219
- if (raf != null) return;
18220
- raf = requestAnimationFrame(() => {
18221
- raf = null;
18222
- measure();
18223
- });
17383
+ if (h > 50) postToParent2({ type: "ow:height", height: h });
18224
17384
  };
18225
17385
  const t1 = setTimeout(measure, 50);
18226
17386
  const t2 = setTimeout(measure, 500);
18227
- const ro = new ResizeObserver(schedule);
18228
- ro.observe(document.body);
17387
+ let lastWidth = window.innerWidth;
17388
+ let resizeTimers = [];
17389
+ const clearResizeTimers = () => {
17390
+ resizeTimers.forEach(clearTimeout);
17391
+ resizeTimers = [];
17392
+ };
17393
+ const handleResize = () => {
17394
+ if (window.innerWidth === lastWidth) return;
17395
+ lastWidth = window.innerWidth;
17396
+ clearResizeTimers();
17397
+ resizeTimers = HEIGHT_SETTLE_DELAYS.map((delay) => setTimeout(measure, delay));
17398
+ };
17399
+ window.addEventListener("resize", handleResize);
18229
17400
  return () => {
18230
17401
  clearTimeout(t1);
18231
17402
  clearTimeout(t2);
18232
- if (raf != null) cancelAnimationFrame(raf);
18233
- ro.disconnect();
17403
+ clearResizeTimers();
17404
+ window.removeEventListener("resize", handleResize);
18234
17405
  };
18235
17406
  }, [pathname, isEditMode, postToParent2]);
18236
17407
  (0, import_react17.useEffect)(() => {
@@ -18471,7 +17642,6 @@ function OhhwellsBridge() {
18471
17642
  return;
18472
17643
  }
18473
17644
  const target = e.target;
18474
- if (target.closest("[data-ohw-ai-review]")) return;
18475
17645
  if (target.closest("[data-ohw-toolbar]")) return;
18476
17646
  if (target.closest("[data-ohw-state-toggle]")) return;
18477
17647
  if (target.closest("[data-ohw-max-badge]")) return;
@@ -18483,9 +17653,6 @@ function OhhwellsBridge() {
18483
17653
  )) {
18484
17654
  return;
18485
17655
  }
18486
- if (selectedMediaElRef.current && !selectedMediaElRef.current.contains(target) && !target.closest("[data-ohw-media-overlay], [data-ohw-edit-chrome], [data-ohw-item-interaction]")) {
18487
- clearMediaSelectionRef.current();
18488
- }
18489
17656
  {
18490
17657
  const formEl = getFormElement(target);
18491
17658
  const onSuccessText = target.closest(`[${SUCCESS_TEXT_ATTR}]`);
@@ -18637,14 +17804,19 @@ function OhhwellsBridge() {
18637
17804
  }
18638
17805
  const clickedButton = findClosestButtonLike(target);
18639
17806
  const buttonOnMedia = Boolean(clickedButton && isMediaEditable(editable) && editable.contains(clickedButton));
17807
+ console.log("[click-debug]", {
17808
+ editableType: editable.dataset.ohwEditable,
17809
+ editableTag: editable.tagName,
17810
+ targetTag: target.tagName,
17811
+ clickedButtonTag: clickedButton?.tagName ?? null,
17812
+ buttonOnMedia,
17813
+ isMediaEditableEditable: isMediaEditable(editable)
17814
+ });
18640
17815
  if (isMediaEditable(editable) && !buttonOnMedia) {
18641
17816
  e.preventDefault();
18642
17817
  e.stopPropagation();
18643
- if (selectedMediaElRef.current === editable) {
18644
- postToParentRef.current({ type: "ow:image-pick", key: editable.dataset.ohwKey ?? "", elementType: editable.dataset.ohwEditable ?? "image" });
18645
- } else {
18646
- selectMediaElementRef.current(editable);
18647
- }
17818
+ aiSectionApiRef.current?.selectFromElement(editable);
17819
+ postToParentRef.current({ type: "ow:image-pick", key: editable.dataset.ohwKey ?? "", elementType: editable.dataset.ohwEditable ?? "image" });
18648
17820
  return;
18649
17821
  }
18650
17822
  const socialItem = getSocialItem(editable);
@@ -18663,6 +17835,11 @@ function OhhwellsBridge() {
18663
17835
  const hrefLookupTarget = buttonOnMedia ? clickedButton : editable;
18664
17836
  const hrefCtx = getHrefKeyFromElement(hrefLookupTarget);
18665
17837
  const navAnchor = hrefCtx ? getNavigationItemAnchor(hrefCtx.anchor) : null;
17838
+ console.log("[click-debug 2]", {
17839
+ hrefLookupTargetTag: hrefLookupTarget.tagName,
17840
+ hrefCtx: hrefCtx ? { key: hrefCtx.key } : null,
17841
+ navAnchorTag: navAnchor?.tagName ?? null
17842
+ });
18666
17843
  if (navAnchor) {
18667
17844
  e.preventDefault();
18668
17845
  e.stopPropagation();
@@ -18780,7 +17957,6 @@ function OhhwellsBridge() {
18780
17957
  };
18781
17958
  const handleDblClick = (e) => {
18782
17959
  const target = e.target;
18783
- if (target.closest("[data-ohw-ai-review]")) return;
18784
17960
  if (target.closest("[data-ohw-toolbar]")) return;
18785
17961
  if (target.closest("[data-ohw-state-toggle]")) return;
18786
17962
  if (target.closest("[data-ohw-max-badge]")) return;
@@ -18832,9 +18008,6 @@ function OhhwellsBridge() {
18832
18008
  setHoveredItemRect(null);
18833
18009
  hoveredNavContainerRef.current = null;
18834
18010
  setHoveredNavContainerRect(null);
18835
- siblingHintElRef.current = null;
18836
- setSiblingHintRect(null);
18837
- setSiblingHintRects([]);
18838
18011
  return;
18839
18012
  }
18840
18013
  {
@@ -18953,6 +18126,7 @@ function OhhwellsBridge() {
18953
18126
  hoveredNavContainerRef.current = null;
18954
18127
  setHoveredNavContainerRect(null);
18955
18128
  hoveredItemElRef.current = editable;
18129
+ setHoveredItemRect(isSelectedForHoverRef.current(editable) ? null : editable.getBoundingClientRect());
18956
18130
  }
18957
18131
  }
18958
18132
  }
@@ -19249,7 +18423,7 @@ function OhhwellsBridge() {
19249
18423
  }
19250
18424
  };
19251
18425
  const probeImageAt = (clientX, clientY, isDragOver = false, fromParentViewport = false) => {
19252
- if (linkPopoverOpenRef.current || floatingPanelOpenRef.current && isPointOverFloatingPanel(clientX, clientY)) {
18426
+ if (linkPopoverOpenRef.current) {
19253
18427
  if (hoveredImageRef.current) {
19254
18428
  hoveredImageRef.current = null;
19255
18429
  hoveredImageHasTextOverlapRef.current = false;
@@ -19583,9 +18757,7 @@ function OhhwellsBridge() {
19583
18757
  return;
19584
18758
  }
19585
18759
  const { y } = toProbeCoords(clientX, clientY, fromParentViewport);
19586
- const sections = Array.from(document.querySelectorAll("[data-ohw-section]")).filter(
19587
- (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
19588
- ).sort((a, b) => a.getBoundingClientRect().top - b.getBoundingClientRect().top);
18760
+ const sections = Array.from(document.querySelectorAll("[data-ohw-section]")).sort((a, b) => a.getBoundingClientRect().top - b.getBoundingClientRect().top);
19589
18761
  const ZONE = 20;
19590
18762
  for (let i = 0; i < sections.length; i++) {
19591
18763
  const a = sections[i];
@@ -19614,7 +18786,8 @@ function OhhwellsBridge() {
19614
18786
  };
19615
18787
  const handleMouseMove = (e) => {
19616
18788
  const { clientX, clientY } = e;
19617
- if (document.documentElement.hasAttribute("data-ohw-panel-dragging") || floatingPanelOpenRef.current && isPointOverFloatingPanel(clientX, clientY) || isOverEditorChrome(clientX, clientY)) {
18789
+ if (pointOwnedByFloatingPanel(clientX, clientY)) return;
18790
+ if (isOverEditorChrome(clientX, clientY)) {
19618
18791
  document.querySelectorAll("[data-ohw-hovered]").forEach((el) => el.removeAttribute("data-ohw-hovered"));
19619
18792
  formHoverElRef.current = null;
19620
18793
  setFormHoverRect(null);
@@ -19622,12 +18795,6 @@ function OhhwellsBridge() {
19622
18795
  setHoveredItemRect(null);
19623
18796
  hoveredNavContainerRef.current = null;
19624
18797
  setHoveredNavContainerRect(null);
19625
- siblingHintElRef.current = null;
19626
- setSiblingHintRect(null);
19627
- setSiblingHintRects([]);
19628
- dismissImageHover();
19629
- clearImageHover();
19630
- setSectionGap(null);
19631
18798
  return;
19632
18799
  }
19633
18800
  if (probeSocialsRowAt(clientX, clientY)) return;
@@ -19639,11 +18806,7 @@ function OhhwellsBridge() {
19639
18806
  if (e.data?.type !== "ow:pointer-sync") return;
19640
18807
  const { clientX, clientY } = e.data;
19641
18808
  if (typeof clientX !== "number" || typeof clientY !== "number") return;
19642
- if (document.documentElement.hasAttribute("data-ohw-panel-dragging") || floatingPanelOpenRef.current && isPointOverFloatingPanel(clientX, clientY)) {
19643
- dismissImageHover();
19644
- clearImageHover();
19645
- return;
19646
- }
18809
+ if (pointOwnedByFloatingPanel(clientX, clientY)) return;
19647
18810
  if (probeSocialsRowAt(clientX, clientY)) return;
19648
18811
  probeSectionGapAt(clientX, clientY);
19649
18812
  probeImageAt(clientX, clientY);
@@ -19926,23 +19089,10 @@ function OhhwellsBridge() {
19926
19089
  if (e.data?.type !== "ow:hydrate") return;
19927
19090
  const content = e.data.content;
19928
19091
  if (!content) return;
19929
- if (typeof content[BRAND_KIT_KEY] === "string") {
19930
- brandKitRef.current = content[BRAND_KIT_KEY];
19931
- applyBrandToDom(parseBrandKit(content[BRAND_KIT_KEY]));
19932
- } else {
19933
- brandKitRef.current = "";
19934
- applyBrandToDom(null);
19935
- }
19936
19092
  if (typeof content[AI_SECTIONS_KEY] === "string") {
19937
19093
  aiSectionsRef.current = content[AI_SECTIONS_KEY];
19938
- setAiSectionOrder(content[SECTION_ORDER_KEY], window.location.pathname);
19939
19094
  applyAiSectionsToDom(parseAiSectionsState(content[AI_SECTIONS_KEY]));
19940
19095
  }
19941
- if (typeof content[STYLE_STORE_KEY] === "string") {
19942
- stylesRef.current = content[STYLE_STORE_KEY];
19943
- applyStylesToDom(parseStyleStore(content[STYLE_STORE_KEY]));
19944
- }
19945
- applyBrandChrome(content);
19946
19096
  let sectionsJson = null;
19947
19097
  for (const [key, val] of Object.entries(content)) {
19948
19098
  if (key === "__ohw_sections") {
@@ -19952,9 +19102,6 @@ function OhhwellsBridge() {
19952
19102
  if (key === AI_SECTIONS_KEY) continue;
19953
19103
  if (key === LOGO_PLACEHOLDER_KEY) continue;
19954
19104
  if (LOGO_IMAGE_KEYS.includes(key) && !String(val ?? "").trim()) continue;
19955
- if (key === BRAND_KIT_KEY) continue;
19956
- if (key === STYLE_STORE_KEY) continue;
19957
- if (BRAND_CHROME_KEYS.has(key)) continue;
19958
19105
  if (applyVideoSettingNode(key, val)) continue;
19959
19106
  if (applyCarouselNode(key, val)) continue;
19960
19107
  document.querySelectorAll(`[data-ohw-key="${key}"]`).forEach((el) => {
@@ -19968,8 +19115,6 @@ function OhhwellsBridge() {
19968
19115
  if (video && video.src !== val) applyVideoSrc(video, val);
19969
19116
  } else if (el.dataset.ohwEditable === "link") {
19970
19117
  applyLinkHref(el, val);
19971
- } else if (el.dataset.ohwEditable === "icon") {
19972
- applyIconMarkup(el, val);
19973
19118
  } else if (isIconMarkupValue(val)) {
19974
19119
  } else {
19975
19120
  el.innerHTML = val;
@@ -20054,21 +19199,12 @@ function OhhwellsBridge() {
20054
19199
  nodes: collectEditableNodes(editContentRef.current)
20055
19200
  });
20056
19201
  };
20057
- const clearInteractionChrome = () => {
20058
- deactivateRef.current();
20059
- deselectRef.current();
20060
- clearMediaSelectionRef.current();
20061
- };
20062
19202
  const handleAiApplyTree = (e) => {
20063
19203
  if (e.data?.type !== "ow:ai-apply-tree") return;
20064
19204
  const payload = e.data.payload;
20065
19205
  if (!payload || typeof payload.id !== "string" || !isRenderableTree(payload.tree)) return;
20066
- clearInteractionChrome();
20067
19206
  const previous = aiSectionsRef.current;
20068
- const nextState = applyTreeToState(parseAiSectionsState(previous), {
20069
- ...payload,
20070
- path: payload.path ?? window.location.pathname
20071
- });
19207
+ const nextState = applyTreeToState(parseAiSectionsState(previous), payload);
20072
19208
  const nextValue = serializeAiSectionsState(nextState);
20073
19209
  aiSectionsRef.current = nextValue;
20074
19210
  applyAiSectionsToDom(nextState);
@@ -20089,7 +19225,6 @@ function OhhwellsBridge() {
20089
19225
  const exists = document.querySelector(`[data-ohw-section="${CSS.escape(sectionId)}"]`);
20090
19226
  if (!exists) return;
20091
19227
  if (isPageFrameSection(exists)) return;
20092
- clearInteractionChrome();
20093
19228
  const previous = aiSectionsRef.current;
20094
19229
  const nextState = deleteSectionFromState(parseAiSectionsState(previous), sectionId);
20095
19230
  const nextValue = serializeAiSectionsState(nextState);
@@ -20105,10 +19240,8 @@ function OhhwellsBridge() {
20105
19240
  const handleAiSetSections = (e) => {
20106
19241
  if (e.data?.type !== "ow:ai-set-sections") return;
20107
19242
  const value = typeof e.data.value === "string" ? e.data.value : "";
20108
- clearInteractionChrome();
20109
19243
  aiSectionsRef.current = value;
20110
19244
  applyAiSectionsToDom(parseAiSectionsState(value));
20111
- applyStylesToDom(parseStyleStore(stylesRef.current));
20112
19245
  const restoredHeight = document.body.scrollHeight;
20113
19246
  if (restoredHeight > 50) postToParentRef.current({ type: "ow:height", height: restoredHeight });
20114
19247
  postToParentRef.current({ type: "ow:change", nodes: [{ key: AI_SECTIONS_KEY, text: value }] });
@@ -20124,40 +19257,10 @@ function OhhwellsBridge() {
20124
19257
  if (!entries) return;
20125
19258
  const orderJson = JSON.stringify(entries);
20126
19259
  editContentRef.current = { ...editContentRef.current, [SECTION_ORDER_KEY]: orderJson };
20127
- setAiSectionOrder(orderJson, window.location.pathname);
20128
19260
  postToParentRef.current({ type: "ow:change", nodes: [{ key: SECTION_ORDER_KEY, text: orderJson }] });
20129
19261
  window.dispatchEvent(new Event("resize"));
20130
19262
  };
20131
19263
  window.addEventListener("message", handleMoveSection);
20132
- const handleAiSetBrand = (e) => {
20133
- if (e.data?.type !== "ow:ai-set-brand") return;
20134
- const value = typeof e.data.value === "string" ? e.data.value : "";
20135
- const previous = brandKitRef.current;
20136
- brandKitRef.current = value;
20137
- applyBrandToDom(parseBrandKit(value));
20138
- if (aiSectionsRef.current) applyAiSectionsToDom(parseAiSectionsState(aiSectionsRef.current));
20139
- applyStylesToDom(parseStyleStore(stylesRef.current));
20140
- postToParentRef.current({ type: "ow:change", nodes: [{ key: BRAND_KIT_KEY, text: value }] });
20141
- postToParentRef.current({ type: "ow:ai-brand-applied", previous, value });
20142
- };
20143
- window.addEventListener("message", handleAiSetBrand);
20144
- const handleAiSetStyles = (e) => {
20145
- if (e.data?.type !== "ow:ai-set-styles") return;
20146
- const value = typeof e.data.value === "string" ? e.data.value : "";
20147
- const previous = stylesRef.current;
20148
- stylesRef.current = value;
20149
- applyStylesToDom(parseStyleStore(value));
20150
- postToParentRef.current({ type: "ow:change", nodes: [{ key: STYLE_STORE_KEY, text: value }] });
20151
- postToParentRef.current({ type: "ow:ai-styles-applied", previous, value });
20152
- };
20153
- window.addEventListener("message", handleAiSetStyles);
20154
- const handleGetBrand = (e) => {
20155
- if (e.data?.type !== "ow:get-brand") return;
20156
- const template = deriveTemplateBrand();
20157
- const value = brandKitRef.current || (template ? JSON.stringify(template) : "");
20158
- postToParentRef.current({ type: "ow:brand-value", value });
20159
- };
20160
- window.addEventListener("message", handleGetBrand);
20161
19264
  const handlePanelDragging = (e) => {
20162
19265
  if (e.data?.type !== "ow:panel-dragging") return;
20163
19266
  if (e.data.dragging) document.documentElement.setAttribute("data-ohw-panel-dragging", "");
@@ -20215,15 +19318,8 @@ function OhhwellsBridge() {
20215
19318
  closeLinkPopoverRef.current();
20216
19319
  return;
20217
19320
  }
20218
- if (floatingPanelOpenRef.current) {
20219
- setFloatingPanelRef.current(null);
20220
- deselectRef.current();
20221
- deactivateRef.current();
20222
- return;
20223
- }
20224
19321
  deselectRef.current();
20225
19322
  deactivateRef.current();
20226
- clearMediaSelectionRef.current();
20227
19323
  };
20228
19324
  window.addEventListener("message", handleDeactivate);
20229
19325
  const handleToastAction = (e) => {
@@ -20309,10 +19405,6 @@ function OhhwellsBridge() {
20309
19405
  const handleKeyDown = (e) => {
20310
19406
  if (e.key === "Escape" && document.querySelector("[data-ohw-section-picker]")) return;
20311
19407
  if (e.key === "Escape" && document.querySelector("[data-ohw-more-menu]")) return;
20312
- if (e.key === "Escape" && selectedMediaElRef.current) {
20313
- clearMediaSelectionRef.current();
20314
- return;
20315
- }
20316
19408
  if ((e.ctrlKey || e.metaKey) && e.key.toLowerCase() === "a" && activeElRef.current) {
20317
19409
  e.preventDefault();
20318
19410
  selectAllTextInEditable(activeElRef.current);
@@ -20472,12 +19564,6 @@ function OhhwellsBridge() {
20472
19564
  if (aiSectionsRef.current) {
20473
19565
  nodes.push({ key: AI_SECTIONS_KEY, type: "sections", text: aiSectionsRef.current });
20474
19566
  }
20475
- if (stylesRef.current) {
20476
- nodes.push({ key: STYLE_STORE_KEY, type: "styles", text: stylesRef.current });
20477
- }
20478
- if (brandKitRef.current) {
20479
- nodes.push({ key: BRAND_KIT_KEY, type: "brand", text: brandKitRef.current });
20480
- }
20481
19567
  document.querySelectorAll('[data-ohw-editable="form"]').forEach((form) => {
20482
19568
  const formKey = formKeyOf(form);
20483
19569
  if (!formKey) return;
@@ -20495,12 +19581,8 @@ function OhhwellsBridge() {
20495
19581
  if (inserted) {
20496
19582
  const tracker = getSectionsTracker();
20497
19583
  postToParentRef.current({ type: "ow:change", nodes: [{ key: "__ohw_sections", text: tracker.textContent ?? "[]" }] });
20498
- const reportHeight = () => {
20499
- const h = document.body.scrollHeight;
20500
- if (h > 50) postToParentRef.current({ type: "ow:height", height: h });
20501
- };
20502
- reportHeight();
20503
- setTimeout(reportHeight, 500);
19584
+ const h = document.body.scrollHeight;
19585
+ if (h > 50) postToParentRef.current({ type: "ow:height", height: h });
20504
19586
  }
20505
19587
  };
20506
19588
  const handleSwitchSchedule = (e) => {
@@ -20897,16 +19979,13 @@ function OhhwellsBridge() {
20897
19979
  window.removeEventListener("message", handleAiDeleteSection);
20898
19980
  window.removeEventListener("message", handleAiSetSections);
20899
19981
  window.removeEventListener("message", handleMoveSection);
20900
- window.removeEventListener("message", handleAiSetBrand);
20901
- window.removeEventListener("message", handleAiSetStyles);
20902
- window.removeEventListener("message", handleGetBrand);
20903
19982
  window.removeEventListener("message", handlePanelDragging);
20904
19983
  window.removeEventListener("message", handleDeleteSection);
20905
19984
  window.removeEventListener("message", handleDeactivate);
19985
+ document.documentElement.removeAttribute("data-ohw-panel-dragging");
20906
19986
  window.removeEventListener("message", handleToastAction);
20907
19987
  window.removeEventListener("message", handleFormCount);
20908
19988
  window.removeEventListener("message", handleUiEscape);
20909
- document.documentElement.removeAttribute("data-ohw-panel-dragging");
20910
19989
  autoSaveTimers.current.forEach(clearTimeout);
20911
19990
  autoSaveTimers.current.clear();
20912
19991
  if (imageUnhoverTimerRef.current) clearTimeout(imageUnhoverTimerRef.current);
@@ -21516,22 +20595,11 @@ function OhhwellsBridge() {
21516
20595
  const showEditLink = toolbarShowEditLink;
21517
20596
  const currentSections = sectionsByPath[pathname] ?? [];
21518
20597
  linkPopoverOpenRef.current = linkPopover !== null;
21519
- const handleMediaSelect = (0, import_react17.useCallback)((key) => {
21520
- const el = hoveredImageRef.current?.dataset.ohwKey === key ? hoveredImageRef.current : Array.from(document.querySelectorAll(MEDIA_SELECTOR)).find(
21521
- (m) => (m.dataset.ohwKey ?? "") === key
21522
- ) ?? null;
21523
- if (!el) return;
21524
- selectMediaElementRef.current(el);
21525
- }, []);
21526
20598
  const handleMediaReplace = (0, import_react17.useCallback)(
21527
20599
  (key) => {
21528
- postToParent2({
21529
- type: "ow:image-pick",
21530
- key,
21531
- elementType: mediaHover?.elementType ?? selectedMedia?.elementType ?? "image"
21532
- });
20600
+ postToParent2({ type: "ow:image-pick", key, elementType: mediaHover?.elementType ?? "image" });
21533
20601
  },
21534
- [postToParent2, mediaHover?.elementType, selectedMedia?.elementType]
20602
+ [postToParent2, mediaHover?.elementType]
21535
20603
  );
21536
20604
  const handleEditCarousel = (0, import_react17.useCallback)(
21537
20605
  (key) => {
@@ -21603,25 +20671,12 @@ function OhhwellsBridge() {
21603
20671
  },
21604
20672
  `uploading-${key}`
21605
20673
  )),
21606
- mediaHover && !(mediaHover.key in uploadingRects) && mediaHover.key !== selectedMedia?.key && /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
20674
+ mediaHover && !(mediaHover.key in uploadingRects) && /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
21607
20675
  MediaOverlay,
21608
20676
  {
21609
20677
  hover: mediaHover,
21610
20678
  isUploading: false,
21611
20679
  onReplace: handleMediaReplace,
21612
- onSelect: handleMediaSelect,
21613
- onVideoSettingsChange: handleVideoSettingsChange
21614
- }
21615
- ),
21616
- selectedMedia && !(selectedMedia.key in uploadingRects) && !linkPopover && /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
21617
- MediaOverlay,
21618
- {
21619
- hover: selectedMedia,
21620
- selected: true,
21621
- hovered: mediaHover?.key === selectedMedia.key,
21622
- isUploading: false,
21623
- onReplace: handleMediaReplace,
21624
- onSelect: handleMediaSelect,
21625
20680
  onVideoSettingsChange: handleVideoSettingsChange
21626
20681
  }
21627
20682
  ),
@@ -22027,59 +21082,6 @@ function OhhwellsBridge() {
22027
21082
  ) : null
22028
21083
  ] });
22029
21084
  }
22030
-
22031
- // src/ui/EmptySection.tsx
22032
- var import_link = __toESM(require("next/link"), 1);
22033
- var import_jsx_runtime34 = require("react/jsx-runtime");
22034
- function EmptySection({ title, homeHref = "/", eyebrowKey, titleKey, subtitleKey }) {
22035
- return /* @__PURE__ */ (0, import_jsx_runtime34.jsxs)(import_jsx_runtime34.Fragment, { children: [
22036
- /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(
22037
- "p",
22038
- {
22039
- style: {
22040
- fontFamily: "var(--brand-font-body)",
22041
- fontSize: "0.75rem",
22042
- fontWeight: 500,
22043
- letterSpacing: "0.15em",
22044
- textTransform: "uppercase",
22045
- color: "var(--brand-accent)",
22046
- marginBottom: "1.5rem"
22047
- },
22048
- 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" }) })
22049
- }
22050
- ),
22051
- /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(
22052
- "h1",
22053
- {
22054
- style: {
22055
- fontFamily: "var(--brand-font-heading)",
22056
- fontSize: "clamp(2rem, 3.5vw, 2.75rem)",
22057
- lineHeight: 1.1,
22058
- letterSpacing: "-0.025em",
22059
- color: "var(--brand-text)",
22060
- marginBottom: "1rem"
22061
- },
22062
- ...titleKey ? { "data-ohw-editable": "text", "data-ohw-key": titleKey, "data-ohw-max-length": 80 } : {},
22063
- children: title
22064
- }
22065
- ),
22066
- /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(
22067
- "p",
22068
- {
22069
- style: {
22070
- fontFamily: "var(--brand-font-body)",
22071
- fontSize: "1rem",
22072
- lineHeight: 1.7,
22073
- fontWeight: 300,
22074
- color: "var(--brand-text-muted)",
22075
- maxWidth: "340px"
22076
- },
22077
- ...subtitleKey ? { "data-ohw-editable": "text", "data-ohw-key": subtitleKey, "data-ohw-max-length": 160 } : {},
22078
- children: "This page doesn't have any content yet."
22079
- }
22080
- )
22081
- ] });
22082
- }
22083
21085
  // Annotate the CommonJS export names for ESM import in node:
22084
21086
  0 && (module.exports = {
22085
21087
  AI_DEFAULT_BRAND,
@@ -22097,7 +21099,6 @@ function EmptySection({ title, homeHref = "/", eyebrowKey, titleKey, subtitleKey
22097
21099
  DropdownMenuItem,
22098
21100
  DropdownMenuSeparator,
22099
21101
  DropdownMenuTrigger,
22100
- EmptySection,
22101
21102
  ItemActionToolbar,
22102
21103
  ItemInteractionLayer,
22103
21104
  LinkEditorPanel,