@ohhwells/bridge 0.1.78 → 0.1.79-next.241

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -46,6 +46,7 @@ __export(index_exports, {
46
46
  DropdownMenuItem: () => DropdownMenuItem,
47
47
  DropdownMenuSeparator: () => DropdownMenuSeparator,
48
48
  DropdownMenuTrigger: () => DropdownMenuTrigger,
49
+ EmptySection: () => EmptySection,
49
50
  ItemActionToolbar: () => ItemActionToolbar,
50
51
  ItemInteractionLayer: () => ItemInteractionLayer,
51
52
  LinkEditorPanel: () => LinkEditorPanel,
@@ -156,7 +157,12 @@ function parseAiSectionsState(raw) {
156
157
  media: entry.media && typeof entry.media === "object" ? entry.media : {}
157
158
  }));
158
159
  const removed = Array.isArray(parsed.removed) ? parsed.removed.filter((id) => typeof id === "string" && id.length > 0) : [];
159
- return { v: 1, sections, ...removed.length ? { removed } : {} };
160
+ return {
161
+ v: 1,
162
+ sections,
163
+ ...removed.length ? { removed } : {},
164
+ ...parsed.hideTemplate === true ? { hideTemplate: true } : {}
165
+ };
160
166
  } catch {
161
167
  return EMPTY_AI_SECTIONS;
162
168
  }
@@ -169,6 +175,7 @@ function applyTreeToState(state, payload) {
169
175
  const entry = {
170
176
  id: payload.id,
171
177
  label: payload.label ?? "Generated section",
178
+ ...typeof payload.path === "string" && payload.path ? { path: payload.path } : {},
172
179
  afterSection: payload.mode === "insert" && !insertBefore ? payload.targetSectionId ?? null : null,
173
180
  ...insertBefore && payload.targetSectionId ? { beforeSection: payload.targetSectionId } : {},
174
181
  ...payload.mode === "replace" && payload.targetSectionId ? { replaces: payload.targetSectionId } : {},
@@ -191,6 +198,320 @@ function deleteSectionFromState(state, sectionId) {
191
198
  return { ...state, v: 1, sections: state.sections, removed: [...removed, sectionId] };
192
199
  }
193
200
 
201
+ // src/lib/brand-chrome.ts
202
+ var BRAND_NAME_KEY = "__ohw_brand_name";
203
+ var BRAND_TITLE_KEY = "__ohw_site_title";
204
+ var BRAND_FAVICON_LETTER_KEY = "__ohw_favicon_letter";
205
+ var BRAND_CHROME_KEYS = /* @__PURE__ */ new Set([
206
+ BRAND_NAME_KEY,
207
+ BRAND_TITLE_KEY,
208
+ BRAND_FAVICON_LETTER_KEY
209
+ ]);
210
+ function upsertMeta(selector, attr, token, value) {
211
+ let el = document.head.querySelector(selector);
212
+ if (!el) {
213
+ el = document.createElement("meta");
214
+ el.setAttribute(attr, token);
215
+ document.head.appendChild(el);
216
+ }
217
+ if (el.getAttribute("content") !== value) el.setAttribute("content", value);
218
+ }
219
+ function escapeXml(value) {
220
+ return value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;");
221
+ }
222
+ function applyLetterFavicon(letter) {
223
+ const svg = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64"><rect width="64" height="64" rx="12" fill="#111827"/><text x="32" y="46" font-family="system-ui,-apple-system,Segoe UI,sans-serif" font-size="40" font-weight="700" text-anchor="middle" fill="#ffffff">${escapeXml(letter)}</text></svg>`;
224
+ const href = `data:image/svg+xml,${encodeURIComponent(svg)}`;
225
+ let link = document.head.querySelector('link[rel="icon"]');
226
+ if (!link) {
227
+ link = document.createElement("link");
228
+ link.rel = "icon";
229
+ document.head.appendChild(link);
230
+ }
231
+ link.type = "image/svg+xml";
232
+ if (link.href !== href) link.href = href;
233
+ }
234
+ function applyBrandChrome(content) {
235
+ const name = content[BRAND_NAME_KEY];
236
+ if (typeof name === "string" && name.length > 0) {
237
+ document.querySelectorAll("[data-ohw-wordmark]").forEach((el) => {
238
+ if (el.textContent !== name) el.textContent = name;
239
+ if (el.getAttribute("title") !== name) el.setAttribute("title", name);
240
+ });
241
+ }
242
+ const title = content[BRAND_TITLE_KEY];
243
+ if (typeof title === "string" && title.length > 0) {
244
+ if (document.title !== title) document.title = title;
245
+ upsertMeta('meta[property="og:title"]', "property", "og:title", title);
246
+ upsertMeta('meta[name="twitter:title"]', "name", "twitter:title", title);
247
+ }
248
+ const letter = content[BRAND_FAVICON_LETTER_KEY];
249
+ if (typeof letter === "string" && letter.length > 0) {
250
+ applyLetterFavicon(letter);
251
+ }
252
+ }
253
+
254
+ // src/lib/brand-kit.ts
255
+ var BRAND_KIT_KEY = "__ohw_brand";
256
+ var BRAND_VAR_PREFIX = "--ohw-brand-";
257
+ var BRAND_VAR_NAMES = ["primary", "accent", "light", "dark", "surface", "border", "muted"].map(
258
+ (role) => `${BRAND_VAR_PREFIX}${role}`
259
+ );
260
+ var FONT_VARS = {
261
+ heading: ["--font-heading", "--font-display", "--brand-font-heading"],
262
+ body: ["--font-body", "--brand-font-body"]
263
+ };
264
+ var BRAND_FONT_LINK_ID = "ohw-brand-fonts";
265
+ function brandColorVars(kit) {
266
+ const { dark, primary, accent, light } = kit.palette;
267
+ const mix = (a, aPct, b) => `color-mix(in srgb, ${a} ${aPct}%, ${b})`;
268
+ return {
269
+ [`${BRAND_VAR_PREFIX}primary`]: primary,
270
+ [`${BRAND_VAR_PREFIX}accent`]: accent,
271
+ [`${BRAND_VAR_PREFIX}light`]: light,
272
+ [`${BRAND_VAR_PREFIX}dark`]: dark,
273
+ [`${BRAND_VAR_PREFIX}surface`]: mix(light, 95, dark),
274
+ [`${BRAND_VAR_PREFIX}border`]: mix(light, 85, dark),
275
+ [`${BRAND_VAR_PREFIX}muted`]: mix(dark, 62, light)
276
+ };
277
+ }
278
+ function parseBrandKit(raw) {
279
+ if (!raw) return null;
280
+ try {
281
+ const parsed = JSON.parse(raw);
282
+ const p = parsed?.palette;
283
+ const f = parsed?.fonts;
284
+ if (!p || !f || typeof p.dark !== "string" || typeof p.primary !== "string" || typeof p.accent !== "string" || typeof p.light !== "string" || typeof f.heading !== "string" || typeof f.body !== "string") {
285
+ return null;
286
+ }
287
+ return {
288
+ palette: { dark: p.dark, primary: p.primary, accent: p.accent, light: p.light },
289
+ fonts: { heading: f.heading, body: f.body }
290
+ };
291
+ } catch {
292
+ return null;
293
+ }
294
+ }
295
+ function familyOf(stack) {
296
+ const first = stack.split(",")[0]?.trim() ?? "";
297
+ return first.replace(/^['"]|['"]$/g, "");
298
+ }
299
+ function loadBrandFonts(families) {
300
+ const unique = [...new Set(families.map((f) => f.trim()).filter(Boolean))];
301
+ if (unique.length === 0) return;
302
+ const spec = unique.map((f) => `${f.replace(/ /g, "+")}:400,500,600,700`).join("|");
303
+ const href = `https://fonts.googleapis.com/css?family=${spec}&display=swap`;
304
+ let link = document.getElementById(BRAND_FONT_LINK_ID);
305
+ if (!link) {
306
+ link = document.createElement("link");
307
+ link.id = BRAND_FONT_LINK_ID;
308
+ link.rel = "stylesheet";
309
+ document.head.appendChild(link);
310
+ }
311
+ if (link.href !== href) link.href = href;
312
+ }
313
+ function applyBrandToDom(kit) {
314
+ const root = document.documentElement;
315
+ if (!kit) {
316
+ for (const name of BRAND_VAR_NAMES) root.style.removeProperty(name);
317
+ for (const name of [...FONT_VARS.heading, ...FONT_VARS.body]) root.style.removeProperty(name);
318
+ document.getElementById(BRAND_FONT_LINK_ID)?.remove();
319
+ return;
320
+ }
321
+ for (const [name, value] of Object.entries(brandColorVars(kit))) root.style.setProperty(name, value);
322
+ for (const name of FONT_VARS.heading) root.style.setProperty(name, kit.fonts.heading);
323
+ for (const name of FONT_VARS.body) root.style.setProperty(name, kit.fonts.body);
324
+ loadBrandFonts([familyOf(kit.fonts.heading), familyOf(kit.fonts.body)]);
325
+ }
326
+
327
+ // src/lib/section-styles.ts
328
+ var STYLE_STORE_KEY = "__ohw_styles";
329
+ var STYLE_SHEET_ID = "ohw-section-styles";
330
+ function parseStyleStore(raw) {
331
+ if (!raw) return null;
332
+ try {
333
+ const parsed = JSON.parse(raw);
334
+ if (parsed?.v !== 1) return null;
335
+ return {
336
+ v: 1,
337
+ sections: typeof parsed.sections === "object" && parsed.sections ? parsed.sections : {},
338
+ nodes: typeof parsed.nodes === "object" && parsed.nodes ? parsed.nodes : {}
339
+ };
340
+ } catch {
341
+ return null;
342
+ }
343
+ }
344
+ var BG_VALUES = {
345
+ surface: "color-mix(in srgb, var(--ohw-brand-light, var(--color-light, #ECECEB)) 94%, var(--ohw-brand-dark, var(--color-dark, #0C0A09)))",
346
+ accent: "var(--ohw-brand-primary, var(--color-primary, #0078E5))",
347
+ "accent-soft": "color-mix(in srgb, var(--ohw-brand-primary, var(--color-primary, #0078E5)) 12%, var(--ohw-brand-light, var(--color-light, #FFFFFF)))"
348
+ };
349
+ var HEADLINE_SIZES = { md: 24, lg: 32, xl: 40, display: 56 };
350
+ function styleSheetCss() {
351
+ const rules = [];
352
+ for (const [tone, value] of Object.entries(BG_VALUES)) {
353
+ rules.push(`[data-ohw-style-bg="${tone}"] { background: ${value} !important; }`);
354
+ }
355
+ rules.push(
356
+ `[data-ohw-style-bg="accent"] { color: var(--ohw-brand-light, var(--color-light, #FFFFFF)) !important; }`
357
+ );
358
+ rules.push(
359
+ `[data-ohw-style-distribution="space-between"] { display: flex !important; flex-direction: column; justify-content: space-between; }`,
360
+ `[data-ohw-style-distribution="center"] { display: flex !important; flex-direction: column; justify-content: center; }`
361
+ );
362
+ for (const [scale, size] of Object.entries(HEADLINE_SIZES)) {
363
+ rules.push(
364
+ `[data-ohw-style-headline="${scale}"] :is(h1, h2, h3) { font-size: ${size}px !important; line-height: 1.15 !important; }`
365
+ );
366
+ }
367
+ rules.push(
368
+ `[data-ohw-style-aspect="fill-height"] img { height: 100% !important; aspect-ratio: auto !important; object-fit: cover; }`
369
+ );
370
+ for (const aspect of ["1:1", "4:5", "3:4", "3:2", "16:9", "2:1", "3:1"]) {
371
+ rules.push(
372
+ `[data-ohw-style-aspect="${aspect.replace(":", "-")}"] img { aspect-ratio: ${aspect.replace(":", " / ")} !important; height: auto !important; object-fit: cover; }`
373
+ );
374
+ }
375
+ const pad = { tight: 40, balanced: 64, airy: 96 };
376
+ for (const [spacing, px] of Object.entries(pad)) {
377
+ rules.push(
378
+ `[data-ohw-style-spacing="${spacing}"] { padding-top: ${px}px !important; padding-bottom: ${px}px !important; }`
379
+ );
380
+ }
381
+ return rules.join("\n");
382
+ }
383
+ var STYLE_FONT_LINK_ID = "ohw-style-fonts";
384
+ function loadStyleFonts(families) {
385
+ const unique = [...new Set(families.map((f) => f.trim()).filter(Boolean))];
386
+ const existing = document.getElementById(STYLE_FONT_LINK_ID);
387
+ if (unique.length === 0) {
388
+ existing?.remove();
389
+ return;
390
+ }
391
+ const spec = unique.map((f) => `${f.replace(/ /g, "+")}:400,500,600,700`).join("|");
392
+ const href = `https://fonts.googleapis.com/css?family=${spec}&display=swap`;
393
+ let link = existing;
394
+ if (!link) {
395
+ link = document.createElement("link");
396
+ link.id = STYLE_FONT_LINK_ID;
397
+ link.rel = "stylesheet";
398
+ document.head.appendChild(link);
399
+ }
400
+ if (link.href !== href) link.href = href;
401
+ }
402
+ var SECTION_ATTRS = {
403
+ sectionBackground: "data-ohw-style-bg",
404
+ textDistribution: "data-ohw-style-distribution",
405
+ headlineScale: "data-ohw-style-headline",
406
+ imageAspect: "data-ohw-style-aspect",
407
+ spacing: "data-ohw-style-spacing"
408
+ };
409
+ var NODE_WROTE_ATTR = "data-ohw-style-node";
410
+ var NODE_PROPS = ["color", "font-family", "font-size", "background"];
411
+ function saveInline(el, prop) {
412
+ const attr = `data-ohw-style-prev-${prop}`;
413
+ if (el.hasAttribute(attr)) return;
414
+ const value = el.style.getPropertyValue(prop) || (prop === "background" ? el.style.getPropertyValue("background-color") : "");
415
+ el.setAttribute(attr, value);
416
+ }
417
+ function restoreInline(el, prop) {
418
+ const attr = `data-ohw-style-prev-${prop}`;
419
+ if (!el.hasAttribute(attr)) return;
420
+ const prev = el.getAttribute(attr) ?? "";
421
+ if (prev) el.style.setProperty(prop, prev);
422
+ else el.style.removeProperty(prop);
423
+ el.removeAttribute(attr);
424
+ }
425
+ function ensureStyleSheet() {
426
+ let el = document.getElementById(STYLE_SHEET_ID);
427
+ if (!el) {
428
+ el = document.createElement("style");
429
+ el.id = STYLE_SHEET_ID;
430
+ document.head.appendChild(el);
431
+ }
432
+ const css = styleSheetCss();
433
+ if (el.textContent !== css) el.textContent = css;
434
+ }
435
+ function clearSectionAttrs(root) {
436
+ for (const attr of Object.values(SECTION_ATTRS)) {
437
+ for (const el of Array.from(root.querySelectorAll(`[${attr}]`))) el.removeAttribute(attr);
438
+ }
439
+ for (const el of Array.from(root.querySelectorAll("[data-ohw-style-bgcolor]"))) {
440
+ restoreInline(el, "background");
441
+ el.removeAttribute("data-ohw-style-bgcolor");
442
+ }
443
+ }
444
+ function clearNodeProps(root) {
445
+ for (const el of Array.from(root.querySelectorAll(`[${NODE_WROTE_ATTR}]`))) {
446
+ const h = el;
447
+ for (const prop of NODE_PROPS) restoreInline(h, prop);
448
+ h.removeAttribute(NODE_WROTE_ATTR);
449
+ }
450
+ }
451
+ function buttonSurfaceOf(el) {
452
+ return el.closest("a, button") ?? el;
453
+ }
454
+ function applyStylesToDom(store) {
455
+ ensureStyleSheet();
456
+ clearSectionAttrs(document);
457
+ clearNodeProps(document);
458
+ loadStyleFonts(
459
+ store ? Object.values(store.nodes).flatMap((n) => n.fontFamily ? [n.fontFamily] : []) : []
460
+ );
461
+ if (!store) return;
462
+ for (const [sectionId, override] of Object.entries(store.sections)) {
463
+ const sections = document.querySelectorAll(
464
+ `[data-ohw-section="${CSS.escape(sectionId)}"]`
465
+ );
466
+ for (const marker of Array.from(sections)) {
467
+ const section = marker.querySelector(":scope > [data-ai-section]") ?? marker;
468
+ for (const [prop, attr] of Object.entries(SECTION_ATTRS)) {
469
+ const value = override[prop];
470
+ if (value === void 0) continue;
471
+ if (prop === "sectionBackground" && override.sectionBackgroundColor !== void 0) continue;
472
+ section.setAttribute(attr, String(value).replace(":", "-"));
473
+ }
474
+ if (override.sectionBackgroundColor !== void 0) {
475
+ saveInline(section, "background");
476
+ section.style.setProperty("background", override.sectionBackgroundColor, "important");
477
+ section.setAttribute("data-ohw-style-bgcolor", "");
478
+ }
479
+ }
480
+ }
481
+ for (const [key, override] of Object.entries(store.nodes)) {
482
+ const nodes = document.querySelectorAll(`[data-ohw-key="${CSS.escape(key)}"]`);
483
+ for (const el of Array.from(nodes)) {
484
+ if (override.color !== void 0) {
485
+ saveInline(el, "color");
486
+ el.style.setProperty("color", override.color, "important");
487
+ el.setAttribute(NODE_WROTE_ATTR, "");
488
+ }
489
+ if (override.fontFamily !== void 0) {
490
+ saveInline(el, "font-family");
491
+ el.style.setProperty("font-family", `'${override.fontFamily}'`, "important");
492
+ el.setAttribute(NODE_WROTE_ATTR, "");
493
+ }
494
+ if (override.fontSize !== void 0) {
495
+ saveInline(el, "font-size");
496
+ el.style.setProperty("font-size", `${override.fontSize}px`, "important");
497
+ el.setAttribute(NODE_WROTE_ATTR, "");
498
+ }
499
+ if (override.buttonBackground !== void 0 || override.buttonText !== void 0) {
500
+ const surface = buttonSurfaceOf(el);
501
+ if (override.buttonBackground !== void 0) {
502
+ saveInline(surface, "background");
503
+ surface.style.setProperty("background", override.buttonBackground, "important");
504
+ }
505
+ if (override.buttonText !== void 0) {
506
+ saveInline(surface, "color");
507
+ surface.style.setProperty("color", override.buttonText, "important");
508
+ }
509
+ surface.setAttribute(NODE_WROTE_ATTR, "");
510
+ }
511
+ }
512
+ }
513
+ }
514
+
194
515
  // src/ui/ai-tree/aiSectionsManager.tsx
195
516
  var import_react_dom = require("react-dom");
196
517
  var import_client = require("react-dom/client");
@@ -205,7 +526,8 @@ function lucideByName(name) {
205
526
  }
206
527
  var typeStyle = (spec, font) => ({
207
528
  fontFamily: font,
208
- fontSize: spec.size,
529
+ // Headings shrink with the viewport (reaching full size around ~900px wide); body copy stays put.
530
+ fontSize: spec.size >= 24 ? `clamp(${Math.max(18, Math.round(spec.size * 0.6))}px, ${(spec.size / 9).toFixed(2)}vw, ${spec.size}px)` : spec.size,
209
531
  lineHeight: spec.line,
210
532
  fontWeight: spec.weight
211
533
  });
@@ -214,12 +536,58 @@ var featureLines = (value) => value.split(/\n|<br\s*\/?>/i).map((line) => line.t
214
536
  var CHECK_MASK = `data:image/svg+xml,${encodeURIComponent(
215
537
  '<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>'
216
538
  )}`;
539
+ var AI_MOBILE_CSS = [
540
+ "@media (max-width: 768px){",
541
+ "[data-ai-section]{overflow-x:hidden}",
542
+ "[data-ai-container]{padding:0 20px !important}",
543
+ "[data-ai-row]{display:flex !important;flex-direction:column !important;align-items:stretch !important}",
544
+ "[data-ai-cell]{width:100%;min-width:0}",
545
+ "[data-ai-grid]{grid-template-columns:1fr !important}",
546
+ // Group containers flatten to a column on phones; span placements come along for free.
547
+ "[data-ai-group]{display:flex !important;flex-direction:column !important}",
548
+ "[data-ai-group] > *{grid-column:auto !important}",
549
+ "[data-ai-section] img{max-width:100%}",
550
+ "}",
551
+ "@media (min-width: 769px) and (max-width: 1024px){",
552
+ "[data-ai-grid]{grid-template-columns:repeat(2, 1fr) !important}",
553
+ "}"
554
+ ].join("");
217
555
  var FEATURE_LINE_CSS = [
218
556
  "[data-ai-features]>div{position:relative;padding-left:40px;min-height:24px}",
219
557
  '[data-ai-features]>div::before{content:"";position:absolute;left:0;top:1px;width:24px;height:24px;',
220
558
  `background-color:currentColor;-webkit-mask:url("${CHECK_MASK}") no-repeat 0 0/24px 24px;`,
221
559
  `mask:url("${CHECK_MASK}") no-repeat 0 0/24px 24px}`
222
560
  ].join("");
561
+ function hexLuminance(color) {
562
+ const m = /^#([0-9a-f]{6})$/i.exec(color.trim());
563
+ if (!m) return null;
564
+ const [r2, g, b] = [0, 2, 4].map((i) => {
565
+ const c = parseInt(m[1].slice(i, i + 2), 16) / 255;
566
+ return c <= 0.03928 ? c / 12.92 : Math.pow((c + 0.055) / 1.055, 2.4);
567
+ });
568
+ return 0.2126 * r2 + 0.7152 * g + 0.0722 * b;
569
+ }
570
+ function hexContrast(a, b) {
571
+ const la = hexLuminance(a);
572
+ const lb = hexLuminance(b);
573
+ if (la === null || lb === null) return null;
574
+ const [hi, lo] = la > lb ? [la, lb] : [lb, la];
575
+ return (hi + 0.05) / (lo + 0.05);
576
+ }
577
+ function accentBandContext(brand) {
578
+ const p = brand.palette;
579
+ const lightWins = (hexContrast(p.light, p.primary) ?? 99) >= (hexContrast(p.dark, p.primary) ?? 0);
580
+ if (lightWins) {
581
+ return {
582
+ brand: { ...brand, palette: { dark: p.light, primary: p.light, accent: p.light, light: p.primary } },
583
+ buttonLabel: p.primary
584
+ };
585
+ }
586
+ return {
587
+ brand: { ...brand, palette: { dark: p.dark, primary: p.dark, accent: p.dark, light: p.light } },
588
+ buttonLabel: p.light
589
+ };
590
+ }
223
591
  function textAttrs(ctx, path) {
224
592
  return ctx.keyFor ? { "data-ohw-key": ctx.keyFor(path), "data-ohw-editable": "text" } : {};
225
593
  }
@@ -231,7 +599,12 @@ var AI_RESPONSIVE_CSS = [
231
599
  "@media (max-width: 640px) {",
232
600
  " [data-ai-responsive] [data-ai-grid] { grid-template-columns: 1fr !important; }",
233
601
  " [data-ai-responsive] [data-ai-columns] > * { grid-column: 1 / -1 !important; }",
602
+ // Group containers flatten to a column on phones; span placements come along for free.
603
+ " [data-ai-responsive] [data-ai-group] { display: flex !important; flex-direction: column !important; }",
604
+ " [data-ai-responsive] [data-ai-group] > * { grid-column: auto !important; }",
234
605
  " [data-ai-responsive] [data-ai-section-inner] { padding-left: 20px !important; padding-right: 20px !important; }",
606
+ " [data-ai-responsive] { overflow-x: hidden; }",
607
+ " [data-ai-responsive] img { max-width: 100%; }",
235
608
  "}"
236
609
  ].join("\n");
237
610
  var TRANSPARENT_PX = "data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7";
@@ -309,7 +682,7 @@ function ButtonEl({
309
682
  }) {
310
683
  const secondary = slots.variant === "secondary";
311
684
  const href = str(slots.href);
312
- const linkAttrs = ctx.keyFor && path ? { "data-ohw-href-key": ctx.keyFor(`${path}.href`) } : {};
685
+ const linkAttrs = ctx.keyFor && path ? { "data-ohw-href-key": ctx.keyFor(`${path}.href`), "data-ohw-role": "button" } : {};
313
686
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
314
687
  "a",
315
688
  {
@@ -325,7 +698,7 @@ function ButtonEl({
325
698
  textDecoration: "none",
326
699
  cursor: "pointer",
327
700
  ...typeStyle(AI_TREE_TOKENS.type.button, ctx.brand.fonts.body),
328
- ...secondary ? { border: `1px solid ${ctx.brand.palette.dark}`, color: ctx.brand.palette.dark } : { background: ctx.brand.palette.primary, color: AI_TREE_TOKENS.textPrimaryForeground }
701
+ ...secondary ? { border: `1px solid ${ctx.brand.palette.dark}`, color: ctx.brand.palette.dark } : { background: ctx.brand.palette.primary, color: ctx.buttonLabel ?? AI_TREE_TOKENS.textPrimaryForeground }
329
702
  },
330
703
  children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { ...path ? textAttrs(ctx, `${path}.label`) : {}, children: str(slots.label) })
331
704
  }
@@ -831,7 +1204,24 @@ function CardBlock({ node, ctx, path }) {
831
1204
  minWidth: 0
832
1205
  },
833
1206
  children: [
834
- media && (horizontal ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { flex: 1, minWidth: 0, display: "flex", alignItems: "center", padding: mediaInset }, children: media }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1207
+ media && (horizontal ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1208
+ "div",
1209
+ {
1210
+ style: (
1211
+ // An icon hugs its glyph — flex:1 gave a 48px icon half the card and pushed the
1212
+ // text to the far side. Photos keep the half-and-half split. The inset has no
1213
+ // inner padding (the photo split absorbed that), so the icon carries its own gap.
1214
+ /^(lucide|simple):/.test(mediaRef) ? {
1215
+ flexShrink: 0,
1216
+ display: "flex",
1217
+ alignItems: "center",
1218
+ padding: mediaInset,
1219
+ [mediaPosition === "right" ? "marginLeft" : "marginRight"]: 20
1220
+ } : { flex: 1, minWidth: 0, display: "flex", alignItems: "center", padding: mediaInset }
1221
+ ),
1222
+ children: media
1223
+ }
1224
+ ) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
835
1225
  "div",
836
1226
  {
837
1227
  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" },
@@ -922,13 +1312,44 @@ function AccordionBlock({ node, ctx, path }) {
922
1312
  ) })
923
1313
  ] }, i)) });
924
1314
  }
1315
+ function useIsMobile() {
1316
+ const [mobile, setMobile] = import_react.default.useState(
1317
+ () => typeof window !== "undefined" && window.matchMedia("(max-width: 768px)").matches
1318
+ );
1319
+ import_react.default.useEffect(() => {
1320
+ const mq = window.matchMedia("(max-width: 768px)");
1321
+ const update = () => setMobile(mq.matches);
1322
+ update();
1323
+ mq.addEventListener("change", update);
1324
+ return () => mq.removeEventListener("change", update);
1325
+ }, []);
1326
+ return mobile;
1327
+ }
925
1328
  function Carousel({ items, itemsPerRow, ctx }) {
1329
+ const isMobile = useIsMobile();
1330
+ const perPage = isMobile ? 1 : itemsPerRow;
1331
+ const pages = Math.max(1, Math.ceil(items.length / perPage));
926
1332
  const [page, setPage] = import_react.default.useState(0);
927
- const pages = Math.max(1, Math.ceil(items.length / itemsPerRow));
928
1333
  const current = Math.min(page, pages - 1);
1334
+ if (pages <= 1) {
1335
+ const cols = Math.max(1, Math.min(items.length, itemsPerRow));
1336
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1337
+ "div",
1338
+ {
1339
+ "data-ai-grid": String(cols),
1340
+ style: {
1341
+ display: "grid",
1342
+ gridTemplateColumns: `repeat(${cols}, 1fr)`,
1343
+ gap: AI_TREE_TOKENS.spacing8,
1344
+ alignItems: "start"
1345
+ },
1346
+ children: items
1347
+ }
1348
+ );
1349
+ }
929
1350
  const pageGroups = Array.from(
930
1351
  { length: pages },
931
- (_, p) => items.slice(p * itemsPerRow, (p + 1) * itemsPerRow)
1352
+ (_, p) => items.slice(p * perPage, (p + 1) * perPage)
932
1353
  );
933
1354
  const chrome = (enabled) => ({
934
1355
  border: `1px solid ${ctx.brand.palette.dark}`,
@@ -953,55 +1374,69 @@ function Carousel({ items, itemsPerRow, ctx }) {
953
1374
  cursor: "pointer",
954
1375
  padding: 0
955
1376
  });
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)(
1377
+ 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)(
1378
+ "div",
1379
+ {
1380
+ style: {
1381
+ display: "flex",
1382
+ transform: `translateX(-${current * 100}%)`,
1383
+ transition: "transform 0.4s ease"
1384
+ },
1385
+ children: pageGroups.map((group, p) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
969
1386
  "div",
970
1387
  {
1388
+ "data-ai-grid": String(perPage),
971
1389
  style: {
972
- display: "flex",
973
- transform: `translateX(-${current * 100}%)`,
974
- transition: "transform 0.4s ease"
1390
+ flex: "0 0 100%",
1391
+ display: "grid",
1392
+ gridTemplateColumns: `repeat(${perPage}, 1fr)`,
1393
+ gap: AI_TREE_TOKENS.spacing8,
1394
+ alignItems: "start"
975
1395
  },
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
- )
1396
+ children: group
1397
+ },
1398
+ p
1399
+ ))
1400
+ }
1401
+ ) });
1402
+ const prevBtn = /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1403
+ "button",
1404
+ {
1405
+ type: "button",
1406
+ "aria-label": "Previous",
1407
+ onClick: () => setPage((p) => Math.max(0, p - 1)),
1408
+ style: chrome(current > 0),
1409
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_lucide_react.ArrowLeft, { size: 24, strokeWidth: 1.5, "aria-hidden": true })
1410
+ }
1411
+ );
1412
+ const nextBtn = /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1413
+ "button",
1414
+ {
1415
+ type: "button",
1416
+ "aria-label": "Next",
1417
+ onClick: () => setPage((p) => Math.min(pages - 1, p + 1)),
1418
+ style: chrome(current < pages - 1),
1419
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_lucide_react.ArrowRight, { size: 24, strokeWidth: 1.5, "aria-hidden": true })
1420
+ }
1421
+ );
1422
+ 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)) });
1423
+ if (isMobile) {
1424
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { "data-ai-carousel": "", style: { display: "flex", flexDirection: "column", gap: AI_TREE_TOKENS.spacing6, alignItems: "center" }, children: [
1425
+ viewport,
1426
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { display: "flex", gap: AI_TREE_TOKENS.spacing6, justifyContent: "center" }, children: [
1427
+ prevBtn,
1428
+ nextBtn
1429
+ ] }),
1430
+ dots
1431
+ ] });
1432
+ }
1433
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { "data-ai-carousel": "", style: { display: "flex", flexDirection: "column", gap: AI_TREE_TOKENS.spacing8, alignItems: "center" }, children: [
1434
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { display: "flex", alignItems: "center", gap: AI_TREE_TOKENS.spacing6, width: "100%" }, children: [
1435
+ prevBtn,
1436
+ viewport,
1437
+ nextBtn
1003
1438
  ] }),
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
+ dots
1005
1440
  ] });
1006
1441
  }
1007
1442
  function CollectionBlock({ node, ctx, path }) {
@@ -1095,6 +1530,49 @@ function renderNode(node, ctx, path) {
1095
1530
  switch (node.type) {
1096
1531
  case "text":
1097
1532
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TextBlock, { slots, ctx, path });
1533
+ // Layout container: arranges child blocks, contributes no content of its own. `grid` is a
1534
+ // nested 12-column grid the children span (a collage is 3–5 media on it, bottom-aligned so
1535
+ // mixed aspects read as a composition); `split` is exactly two children at a ratio; `stack`
1536
+ // is a column. Children render through this same dispatcher, so edit markers, media
1537
+ // resolution, and copy paths all work unchanged inside a group.
1538
+ case "group": {
1539
+ const layout = str(slots.layout);
1540
+ const gap = slots.spacing === "tight" ? AI_TREE_TOKENS.spacing3 : slots.spacing === "airy" ? AI_TREE_TOKENS.spacing8 : AI_TREE_TOKENS.spacing6;
1541
+ const kids = (node.children ?? []).map((child, i) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1542
+ "div",
1543
+ {
1544
+ style: layout === "grid" ? {
1545
+ gridColumn: `span ${typeof child.span === "number" ? Math.min(12, Math.max(1, child.span)) : 12}`,
1546
+ minWidth: 0
1547
+ } : { minWidth: 0 },
1548
+ children: renderNode(child, ctx, `${path}.c${i}`)
1549
+ },
1550
+ i
1551
+ ));
1552
+ if (layout === "grid") {
1553
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1554
+ "div",
1555
+ {
1556
+ "data-ai-group": "grid",
1557
+ style: { display: "grid", gridTemplateColumns: "repeat(12, 1fr)", gap, alignItems: "end" },
1558
+ children: kids
1559
+ }
1560
+ );
1561
+ }
1562
+ if (layout === "split") {
1563
+ const ratio = str(slots.ratio);
1564
+ const cols = ratio === "3:5" ? "3fr 5fr" : ratio === "5:3" ? "5fr 3fr" : "1fr 1fr";
1565
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1566
+ "div",
1567
+ {
1568
+ "data-ai-group": "split",
1569
+ style: { display: "grid", gridTemplateColumns: cols, gap, alignItems: "center" },
1570
+ children: kids
1571
+ }
1572
+ );
1573
+ }
1574
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { "data-ai-group": "stack", style: { display: "flex", flexDirection: "column", gap }, children: kids });
1575
+ }
1098
1576
  case "button":
1099
1577
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ButtonEl, { slots, ctx, path });
1100
1578
  case "button-row":
@@ -1175,33 +1653,111 @@ function renderNode(node, ctx, path) {
1175
1653
  }
1176
1654
  );
1177
1655
  }
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",
1193
- {
1194
- style: {
1195
- border: `1px solid ${ctx.brand.palette.accent}`,
1196
- borderRadius: AI_TREE_TOKENS.radiusButton,
1197
- height: cs.kind === "textarea" ? 96 : 42
1198
- }
1199
- }
1200
- )
1201
- ] }, i);
1202
- }
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
- }) });
1656
+ case "form": {
1657
+ const formAttrs = ctx.keyFor ? {
1658
+ "data-ohw-editable": "form",
1659
+ "data-ohw-key": ctx.keyFor(`${path}.form`),
1660
+ "data-ohw-success-text": "Thanks \u2014 we'll be in touch shortly."
1661
+ } : {};
1662
+ const fieldStyle = {
1663
+ width: "100%",
1664
+ boxSizing: "border-box",
1665
+ border: `1px solid color-mix(in srgb, ${ctx.brand.palette.dark} 45%, #ffffff)`,
1666
+ borderRadius: 0,
1667
+ padding: 12,
1668
+ background: "#fff",
1669
+ color: ctx.brand.palette.dark,
1670
+ outline: "none",
1671
+ ...typeStyle(AI_TREE_TOKENS.type.bodyM, ctx.brand.fonts.body)
1672
+ };
1673
+ const labelStyle = {
1674
+ ...typeStyle(AI_TREE_TOKENS.type.bodyMBold, ctx.brand.fonts.body),
1675
+ color: ctx.brand.palette.dark,
1676
+ textAlign: "left",
1677
+ width: "100%"
1678
+ };
1679
+ const centered = ctx.sectionAlignment === "center";
1680
+ const submitAlign = centered ? "center" : "flex-start";
1681
+ const children = node.children ?? [];
1682
+ return (
1683
+ // 32px between the field group and the submit. In a stacked (centered) section the form is
1684
+ // capped at 780px and centered — the section's 12-col grid would otherwise leave it hugging
1685
+ // the left edge; a split section lets it fill its own column.
1686
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
1687
+ "form",
1688
+ {
1689
+ ...formAttrs,
1690
+ "data-ai-form": "",
1691
+ style: {
1692
+ display: "flex",
1693
+ flexDirection: "column",
1694
+ gap: 32,
1695
+ width: "100%",
1696
+ ...centered ? { maxWidth: 780, marginLeft: "auto", marginRight: "auto" } : {}
1697
+ },
1698
+ children: [
1699
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { display: "flex", flexDirection: "column", gap: 24, width: "100%", alignItems: "flex-start" }, children: children.map((child, i) => {
1700
+ if (child.type !== "input") return null;
1701
+ const cs = child.slots ?? {};
1702
+ const kind = str(cs.kind);
1703
+ const label = str(cs.label);
1704
+ const placeholder = str(cs.placeholder);
1705
+ const required = cs.required === true;
1706
+ const name = label.toLowerCase().replace(/[^a-z0-9]+/gu, "-").replace(/^-+|-+$/gu, "") || `field-${i}`;
1707
+ const isTextarea = kind === "textarea";
1708
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { display: "flex", flexDirection: "column", gap: 8, width: "100%" }, children: [
1709
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("label", { ...textAttrs(ctx, `${path}.c${i}.label`), style: labelStyle, children: label }),
1710
+ isTextarea ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1711
+ "textarea",
1712
+ {
1713
+ name,
1714
+ placeholder,
1715
+ required,
1716
+ style: { ...fieldStyle, height: 180, resize: "vertical" }
1717
+ }
1718
+ ) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1719
+ "input",
1720
+ {
1721
+ name,
1722
+ type: kind === "email" ? "email" : "text",
1723
+ placeholder,
1724
+ required,
1725
+ style: { ...fieldStyle, height: 48 }
1726
+ }
1727
+ )
1728
+ ] }, i);
1729
+ }) }),
1730
+ children.map((child, i) => {
1731
+ if (child.type === "input") return null;
1732
+ const cs = child.slots ?? {};
1733
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1734
+ "button",
1735
+ {
1736
+ type: "submit",
1737
+ style: {
1738
+ alignSelf: submitAlign,
1739
+ border: "none",
1740
+ cursor: "pointer",
1741
+ padding: "12px 24px",
1742
+ // Corner radius follows the host template's own buttons (measured from a template
1743
+ // CTA); 8px only when the page has no template button to match.
1744
+ borderRadius: ctx.buttonRadius ?? 8,
1745
+ // Brand-styled: primary fill, brand-derived label colour (not a fixed token) so it
1746
+ // reads correctly on custom palettes.
1747
+ background: ctx.brand.palette.primary,
1748
+ color: ctx.buttonLabel ?? ctx.brand.palette.light,
1749
+ ...typeStyle(AI_TREE_TOKENS.type.button, ctx.brand.fonts.body)
1750
+ },
1751
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { ...textAttrs(ctx, `${path}.c${i}.label`), children: str(cs.label) })
1752
+ },
1753
+ i
1754
+ );
1755
+ })
1756
+ ]
1757
+ }
1758
+ )
1759
+ );
1760
+ }
1205
1761
  case "schedule-widget":
1206
1762
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1207
1763
  "div",
@@ -1222,16 +1778,27 @@ function renderNode(node, ctx, path) {
1222
1778
  return null;
1223
1779
  }
1224
1780
  }
1225
- function AiTreeRenderer({ tree, brand, resolveMedia, editKeyPrefix }) {
1781
+ function AiTreeRenderer({
1782
+ tree,
1783
+ brand,
1784
+ buttonRadius,
1785
+ resolveMedia,
1786
+ editKeyPrefix
1787
+ }) {
1226
1788
  if (!isRenderableTree(tree)) {
1227
1789
  return null;
1228
1790
  }
1229
1791
  const resolvedBrand = brand ?? AI_DEFAULT_BRAND;
1792
+ const band = (tree.settings ?? {}).sectionBackground === "accent" ? accentBandContext(resolvedBrand) : null;
1793
+ const blockBrand = band?.brand ?? resolvedBrand;
1230
1794
  const ctx = {
1231
- brand: resolvedBrand,
1795
+ brand: blockBrand,
1232
1796
  resolveMedia: resolveMedia ?? (() => null),
1233
- cardSurface: resolvedBrand.palette.light.toUpperCase() === AI_DEFAULT_BRAND.palette.light.toUpperCase() ? "#ECECEB" : `color-mix(in srgb, ${resolvedBrand.palette.light} 90%, ${resolvedBrand.palette.dark})`,
1234
- keyFor: editKeyPrefix ? (path) => `${editKeyPrefix}.${path}` : null
1797
+ cardSurface: blockBrand.palette.light.toUpperCase() === AI_DEFAULT_BRAND.palette.light.toUpperCase() ? "#ECECEB" : `color-mix(in srgb, ${blockBrand.palette.light} 90%, ${blockBrand.palette.dark})`,
1798
+ keyFor: editKeyPrefix ? (path) => `${editKeyPrefix}.${path}` : null,
1799
+ sectionAlignment: (tree.settings ?? {}).alignment === "center" ? "center" : "left",
1800
+ buttonRadius,
1801
+ ...band ? { buttonLabel: band.buttonLabel } : {}
1235
1802
  };
1236
1803
  const settings = tree.settings ?? {};
1237
1804
  const pad = AI_TREE_TOKENS.sectionPadding[settings.spacing ?? "balanced"];
@@ -1239,6 +1806,20 @@ function AiTreeRenderer({ tree, brand, resolveMedia, editKeyPrefix }) {
1239
1806
  const overlayAlpha = { low: 0.25, medium: 0.45, high: 0.65 }[settings.overlayIntensity ?? "medium"];
1240
1807
  const backgroundUrl = isOverlay && settings.backgroundMedia ? ctx.resolveMedia(settings.backgroundMedia) : null;
1241
1808
  const bgAttrs = ctx.keyFor && isOverlay && settings.backgroundMedia ? { "data-ohw-key": ctx.keyFor("settings.backgroundMedia"), "data-ohw-editable": "bg-image" } : {};
1809
+ const toneBackground = (() => {
1810
+ const { dark, primary, light } = resolvedBrand.palette;
1811
+ switch (settings.sectionBackground) {
1812
+ case "surface":
1813
+ return `color-mix(in srgb, ${light} 94%, ${dark})`;
1814
+ case "accent":
1815
+ return primary;
1816
+ case "accent-soft":
1817
+ return `color-mix(in srgb, ${primary} 12%, ${light})`;
1818
+ default:
1819
+ return void 0;
1820
+ }
1821
+ })();
1822
+ const distributed = !isOverlay && settings.textDistribution;
1242
1823
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
1243
1824
  "section",
1244
1825
  {
@@ -1248,13 +1829,15 @@ function AiTreeRenderer({ tree, brand, resolveMedia, editKeyPrefix }) {
1248
1829
  style: {
1249
1830
  position: "relative",
1250
1831
  padding: `${pad}px 0`,
1251
- background: isOverlay && !backgroundUrl ? "#DBEAFE" : void 0,
1832
+ background: toneBackground ?? (isOverlay && !backgroundUrl ? "#DBEAFE" : void 0),
1252
1833
  backgroundImage: backgroundUrl ? `url(${backgroundUrl})` : void 0,
1253
1834
  backgroundSize: "cover",
1254
- backgroundPosition: "center"
1835
+ backgroundPosition: "center",
1836
+ color: settings.sectionBackground === "accent" ? blockBrand.palette.dark : void 0
1255
1837
  },
1256
1838
  children: [
1257
1839
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("style", { children: AI_RESPONSIVE_CSS }),
1840
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("style", { children: AI_MOBILE_CSS }),
1258
1841
  isOverlay && backgroundUrl && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { position: "absolute", inset: 0, background: `rgba(255,255,255,${overlayAlpha})` } }),
1259
1842
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1260
1843
  "div",
@@ -1275,10 +1858,24 @@ function AiTreeRenderer({ tree, brand, resolveMedia, editKeyPrefix }) {
1275
1858
  display: "grid",
1276
1859
  gridTemplateColumns: "repeat(12, 1fr)",
1277
1860
  gap: AI_TREE_TOKENS.spacing6,
1278
- alignItems: settings.verticalPosition === "top" ? "start" : "center",
1861
+ alignItems: distributed === "space-between" ? "stretch" : (distributed ?? settings.verticalPosition) === "top" ? "start" : "center",
1279
1862
  marginTop: r2 > 0 ? AI_TREE_TOKENS.spacing8 : 0
1280
1863
  },
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))
1864
+ children: (row.blocks ?? []).map((block, b) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1865
+ "div",
1866
+ {
1867
+ "data-ai-cell": "",
1868
+ style: {
1869
+ gridColumn: `span ${Math.min(12, Math.max(1, block.span))}`,
1870
+ minWidth: 0,
1871
+ // space-between: each column becomes a flex column whose content spreads over
1872
+ // the full row height instead of clumping at the top.
1873
+ ...distributed === "space-between" ? { display: "flex", flexDirection: "column", justifyContent: "space-between" } : {}
1874
+ },
1875
+ children: renderNode(block, ctx, `r${r2}.b${b}`)
1876
+ },
1877
+ b
1878
+ ))
1282
1879
  },
1283
1880
  r2
1284
1881
  ))
@@ -1294,17 +1891,36 @@ var import_jsx_runtime2 = require("react/jsx-runtime");
1294
1891
  var CONTAINER_ATTR = "data-ohw-ai-generated";
1295
1892
  var REPLACED_ATTR = "data-ohw-ai-replaced-by";
1296
1893
  var REMOVED_ATTR = "data-ohw-ai-removed";
1894
+ var TEMPLATE_HIDDEN_ATTR = "data-ohw-ai-template-hidden";
1895
+ var CHROME_SECTION_IDS = /* @__PURE__ */ new Set(["navbar", "footer"]);
1896
+ function readRootVar(name) {
1897
+ if (typeof document === "undefined") return "";
1898
+ return getComputedStyle(document.documentElement).getPropertyValue(name).trim();
1899
+ }
1900
+ function deriveBrandOverride() {
1901
+ const dark = readRootVar("--ohw-brand-dark");
1902
+ const primary = readRootVar("--ohw-brand-primary");
1903
+ const light = readRootVar("--ohw-brand-light");
1904
+ if (!dark || !primary || !light) return null;
1905
+ const accent = readRootVar("--ohw-brand-accent");
1906
+ const heading = readRootVar("--font-heading") || readRootVar("--font-display");
1907
+ const body = readRootVar("--font-body");
1908
+ return {
1909
+ palette: { dark, primary, accent: accent || dark, light },
1910
+ fonts: {
1911
+ heading: heading || AI_DEFAULT_BRAND.fonts.heading,
1912
+ body: body || AI_DEFAULT_BRAND.fonts.body
1913
+ }
1914
+ };
1915
+ }
1297
1916
  function deriveTemplateBrand() {
1298
- if (typeof document === "undefined") return null;
1299
- const cs = getComputedStyle(document.documentElement);
1300
- const read = (name) => cs.getPropertyValue(name).trim();
1301
- const dark = read("--color-dark");
1302
- const primary = read("--color-primary");
1303
- const light = read("--color-light");
1917
+ const dark = readRootVar("--color-dark");
1918
+ const primary = readRootVar("--color-primary");
1919
+ const light = readRootVar("--color-light");
1304
1920
  if (!dark || !primary || !light) return null;
1305
- const accent = read("--color-accent");
1306
- const heading = read("--font-heading") || read("--font-display");
1307
- const body = read("--font-body");
1921
+ const accent = readRootVar("--color-accent");
1922
+ const heading = readRootVar("--font-heading") || readRootVar("--font-display");
1923
+ const body = readRootVar("--font-body");
1308
1924
  return {
1309
1925
  palette: { dark, primary, accent: accent || dark, light },
1310
1926
  fonts: {
@@ -1313,6 +1929,13 @@ function deriveTemplateBrand() {
1313
1929
  }
1314
1930
  };
1315
1931
  }
1932
+ function deriveTemplateButtonRadius() {
1933
+ if (typeof document === "undefined") return null;
1934
+ const btn = document.querySelector('[data-ohw-role="button"]');
1935
+ if (!btn) return null;
1936
+ const radius = getComputedStyle(btn).borderTopLeftRadius;
1937
+ return radius || null;
1938
+ }
1316
1939
  var mounted = /* @__PURE__ */ new Map();
1317
1940
  function findTemplateSection(id) {
1318
1941
  for (const el of document.querySelectorAll(`[data-ohw-section="${CSS.escape(id)}"]`)) {
@@ -1376,6 +1999,24 @@ function syncRemovedSections(state) {
1376
1999
  }
1377
2000
  }
1378
2001
  }
2002
+ function syncTemplateHidden(state, pageHasSections) {
2003
+ const hide = state.hideTemplate === true && pageHasSections;
2004
+ for (const el of Array.from(document.querySelectorAll(`[${TEMPLATE_HIDDEN_ATTR}]`))) {
2005
+ if (!hide) {
2006
+ el.style.removeProperty("display");
2007
+ el.removeAttribute(TEMPLATE_HIDDEN_ATTR);
2008
+ }
2009
+ }
2010
+ if (!hide) return;
2011
+ for (const el of Array.from(document.querySelectorAll("[data-ohw-section]"))) {
2012
+ if (el.hasAttribute(CONTAINER_ATTR)) continue;
2013
+ if (CHROME_SECTION_IDS.has(el.getAttribute("data-ohw-section") ?? "")) continue;
2014
+ if (el.parentElement?.closest("[data-ohw-section]")) continue;
2015
+ if (el.hasAttribute(REPLACED_ATTR) || el.hasAttribute(REMOVED_ATTR)) continue;
2016
+ el.style.display = "none";
2017
+ el.setAttribute(TEMPLATE_HIDDEN_ATTR, "");
2018
+ }
2019
+ }
1379
2020
  function syncReplacedOriginals(state) {
1380
2021
  for (const el of document.querySelectorAll(`[${REPLACED_ATTR}]`)) {
1381
2022
  const byId = el.getAttribute(REPLACED_ATTR) ?? "";
@@ -1394,10 +2035,64 @@ function syncReplacedOriginals(state) {
1394
2035
  }
1395
2036
  }
1396
2037
  }
2038
+ var sectionOrderIndex = /* @__PURE__ */ new Map();
2039
+ function setAiSectionOrder(raw, currentPath) {
2040
+ const next = /* @__PURE__ */ new Map();
2041
+ if (raw) {
2042
+ try {
2043
+ const entries = JSON.parse(raw);
2044
+ for (const entry of entries) {
2045
+ if (!entry.pagePath || entry.pagePath === currentPath) next.set(entry.instanceId, entry.order);
2046
+ }
2047
+ } catch {
2048
+ }
2049
+ }
2050
+ sectionOrderIndex = next;
2051
+ }
2052
+ function applyExplicitOrder(entries) {
2053
+ if (sectionOrderIndex.size === 0) return entries;
2054
+ return entries.map((entry, index) => ({ entry, index, order: sectionOrderIndex.get(entry.id) })).sort((a, b) => {
2055
+ if (a.order === void 0 && b.order === void 0) return a.index - b.index;
2056
+ if (a.order === void 0) return 1;
2057
+ if (b.order === void 0) return -1;
2058
+ return a.order - b.order;
2059
+ }).map((item) => item.entry);
2060
+ }
2061
+ function orderByChain(sections) {
2062
+ const ids = new Set(sections.map((entry) => entry.id));
2063
+ const after = /* @__PURE__ */ new Map();
2064
+ const roots = [];
2065
+ for (const entry of sections) {
2066
+ const anchor = entry.replaces ?? entry.beforeSection ?? entry.afterSection ?? null;
2067
+ if (anchor && ids.has(anchor)) {
2068
+ const bucket = after.get(anchor);
2069
+ if (bucket) bucket.push(entry);
2070
+ else after.set(anchor, [entry]);
2071
+ } else {
2072
+ roots.push(entry);
2073
+ }
2074
+ }
2075
+ const out = [];
2076
+ const seen = /* @__PURE__ */ new Set();
2077
+ const visit = (entry) => {
2078
+ if (seen.has(entry.id)) return;
2079
+ seen.add(entry.id);
2080
+ out.push(entry);
2081
+ for (const child of after.get(entry.id) ?? []) visit(child);
2082
+ };
2083
+ for (const root of roots) visit(root);
2084
+ return out.length === sections.length ? out : sections;
2085
+ }
1397
2086
  function applyAiSectionsToDom(state, options) {
1398
2087
  if (typeof document === "undefined") return;
2088
+ const brandOverride = deriveBrandOverride();
1399
2089
  const templateBrand = deriveTemplateBrand();
1400
- const activeIds = new Set(state.sections.map((entry) => entry.id));
2090
+ const templateButtonRadius = deriveTemplateButtonRadius();
2091
+ const brandKey = brandOverride ? JSON.stringify(brandOverride) : "";
2092
+ const pagePath = window.location.pathname;
2093
+ const pageSections = state.sections.filter((entry) => (entry.path ?? "/") === pagePath);
2094
+ const activeIds = new Set(pageSections.map((entry) => entry.id));
2095
+ const ordered = state.hideTemplate === true ? applyExplicitOrder(orderByChain(pageSections)) : pageSections;
1401
2096
  for (const [id, section] of mounted) {
1402
2097
  if (!activeIds.has(id)) {
1403
2098
  section.root.unmount();
@@ -1405,8 +2100,8 @@ function applyAiSectionsToDom(state, options) {
1405
2100
  mounted.delete(id);
1406
2101
  }
1407
2102
  }
1408
- for (const entry of state.sections) {
1409
- const serialized = JSON.stringify(entry);
2103
+ for (const entry of ordered) {
2104
+ const serialized = JSON.stringify(entry) + brandKey;
1410
2105
  const existing = mounted.get(entry.id);
1411
2106
  if (existing && existing.serialized === serialized && existing.container.isConnected) {
1412
2107
  continue;
@@ -1431,7 +2126,8 @@ function applyAiSectionsToDom(state, options) {
1431
2126
  AiTreeRenderer,
1432
2127
  {
1433
2128
  tree: entry.tree,
1434
- brand: entry.brand ?? options?.brand ?? templateBrand ?? void 0,
2129
+ brand: brandOverride ?? entry.brand ?? options?.brand ?? templateBrand ?? void 0,
2130
+ buttonRadius: templateButtonRadius,
1435
2131
  resolveMedia,
1436
2132
  editKeyPrefix: `ai.${entry.id}`
1437
2133
  }
@@ -1440,8 +2136,20 @@ function applyAiSectionsToDom(state, options) {
1440
2136
  });
1441
2137
  mounted.set(entry.id, { root, container, serialized });
1442
2138
  }
2139
+ if (state.hideTemplate === true) {
2140
+ let prev = null;
2141
+ for (const entry of ordered) {
2142
+ const el = mounted.get(entry.id)?.container;
2143
+ if (!el) continue;
2144
+ if (prev && !(prev.compareDocumentPosition(el) & Node.DOCUMENT_POSITION_FOLLOWING)) {
2145
+ prev.insertAdjacentElement("afterend", el);
2146
+ }
2147
+ prev = el;
2148
+ }
2149
+ }
1443
2150
  syncReplacedOriginals(state);
1444
2151
  syncRemovedSections(state);
2152
+ syncTemplateHidden(state, pageSections.length > 0);
1445
2153
  }
1446
2154
 
1447
2155
  // src/useLinkHrefGuardian.ts
@@ -1568,6 +2276,7 @@ function EmailCaptureModal({ title, subtitle, onSubmit, onClose }) {
1568
2276
  /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1569
2277
  import_radix_ui.Dialog.Overlay,
1570
2278
  {
2279
+ "data-ohw-scheduling-modal": "",
1571
2280
  className: "fixed inset-0 z-50",
1572
2281
  style: { background: "rgba(0,0,0,0.45)" }
1573
2282
  }
@@ -1575,6 +2284,7 @@ function EmailCaptureModal({ title, subtitle, onSubmit, onClose }) {
1575
2284
  /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
1576
2285
  import_radix_ui.Dialog.Content,
1577
2286
  {
2287
+ "data-ohw-scheduling-modal": "",
1578
2288
  className: "fixed left-1/2 top-1/2 z-50 w-full -translate-x-1/2 -translate-y-1/2 bg-white rounded-xl shadow-xl outline-none font-body box-border overflow-hidden",
1579
2289
  style: { maxWidth: 400 },
1580
2290
  children: [
@@ -2048,7 +2758,7 @@ function SchedulingWidget({ notifyOnConnect = false, initialScheduleId, insertAf
2048
2758
  const autoId = (0, import_react5.useId)();
2049
2759
  const insertAfter = insertAfterProp ?? autoId;
2050
2760
  const [schedule, setSchedule] = (0, import_react5.useState)(null);
2051
- const [loading, setLoading] = (0, import_react5.useState)(true);
2761
+ const [loading, setLoading] = (0, import_react5.useState)(initialScheduleId !== null);
2052
2762
  const [inEditor, setInEditor] = (0, import_react5.useState)(false);
2053
2763
  const [isHovered, setIsHovered] = (0, import_react5.useState)(false);
2054
2764
  const [modalState, setModalState] = (0, import_react5.useState)(null);
@@ -2222,8 +2932,10 @@ function SchedulingWidget({ notifyOnConnect = false, initialScheduleId, insertAf
2222
2932
  "*"
2223
2933
  );
2224
2934
  };
2225
- if (!inEditor && !loading && !schedule) return null;
2226
2935
  const sectionId = `scheduling-${insertAfter}`;
2936
+ if (!inEditor && !loading && !schedule) {
2937
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("section", { "data-ohw-section": sectionId, "data-ohw-scheduling-anchor": insertAfter, style: { display: "none" } });
2938
+ }
2227
2939
  return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
2228
2940
  "section",
2229
2941
  {
@@ -7140,13 +7852,17 @@ function MediaOverlay({
7140
7852
  hover,
7141
7853
  isUploading,
7142
7854
  fadingOut = false,
7855
+ selected = false,
7856
+ hovered = false,
7143
7857
  onFadeOutComplete,
7144
7858
  onReplace,
7859
+ onSelect,
7145
7860
  onVideoSettingsChange
7146
7861
  }) {
7147
7862
  const { rect } = hover;
7148
7863
  const skeletonRef = React8.useRef(null);
7149
7864
  const isVideo = hover.elementType === "video";
7865
+ const showChrome = !selected || hovered;
7150
7866
  const autoplay = hover.videoAutoplay ?? true;
7151
7867
  const muted = hover.videoMuted ?? true;
7152
7868
  const probeRef = React8.useRef(null);
@@ -7160,6 +7876,7 @@ function MediaOverlay({
7160
7876
  (prev) => prev && prev.fullWidth === width && prev.height === height ? prev : { fullWidth: width, height }
7161
7877
  );
7162
7878
  }, [isVideo]);
7879
+ const replaceLabel = isVideo ? "Replace video" : hover.elementType === "bg-image" ? "Replace background" : "Replace image";
7163
7880
  const replaceMode = buttonSize ? replaceButtonMode({ width: rect.width, height: rect.height }, buttonSize) : "none";
7164
7881
  const box = {
7165
7882
  position: "fixed",
@@ -7193,7 +7910,7 @@ function MediaOverlay({
7193
7910
  }
7194
7911
  );
7195
7912
  }
7196
- const settingsBar = isVideo && !hover.isDragOver ? /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(
7913
+ const settingsBar = isVideo && !hover.isDragOver && showChrome ? /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(
7197
7914
  "div",
7198
7915
  {
7199
7916
  "data-ohw-bridge": "",
@@ -7263,10 +7980,12 @@ function MediaOverlay({
7263
7980
  // in-document, pointer-events does it natively. The button below opts back in, so
7264
7981
  // Replace still works.
7265
7982
  pointerEvents: hover.hasTextOverlap ? "none" : "auto",
7266
- boxShadow: "inset 0 0 0 1.5px var(--color-primary)",
7267
- background: "color-mix(in srgb, var(--color-primary) 20%, transparent)"
7983
+ // Selected: a firm component ring with no wash, so the image reads as chosen rather
7984
+ // than hovered. Hover keeps the existing tinted preview.
7985
+ boxShadow: selected ? "inset 0 0 0 2px var(--color-primary)" : "inset 0 0 0 1.5px var(--color-primary)",
7986
+ background: selected ? "transparent" : "color-mix(in srgb, var(--color-primary) 20%, transparent)"
7268
7987
  },
7269
- onClick: () => onReplace(hover.key),
7988
+ onClick: () => onSelect && !selected ? onSelect(hover.key) : onReplace(hover.key),
7270
7989
  children: [
7271
7990
  /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(
7272
7991
  Button,
@@ -7287,17 +8006,17 @@ function MediaOverlay({
7287
8006
  },
7288
8007
  children: [
7289
8008
  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 }),
7290
- isVideo ? "Replace video" : "Replace image"
8009
+ replaceLabel
7291
8010
  ]
7292
8011
  }
7293
8012
  ),
7294
- replaceMode === "none" ? null : /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(
8013
+ showChrome && replaceMode !== "none" && /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(
7295
8014
  Button,
7296
8015
  {
7297
8016
  "data-ohw-media-overlay": "",
7298
8017
  variant: "outline",
7299
8018
  size: "sm",
7300
- "aria-label": isVideo ? "Replace video" : "Replace image",
8019
+ "aria-label": replaceLabel,
7301
8020
  className: "gap-1.5 cursor-pointer hover:bg-background",
7302
8021
  style: {
7303
8022
  ...OVERLAY_BUTTON_STYLE,
@@ -7320,7 +8039,7 @@ function MediaOverlay({
7320
8039
  },
7321
8040
  children: [
7322
8041
  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 }),
7323
- replaceMode === "full" ? isVideo ? "Replace video" : "Replace image" : null
8042
+ replaceMode === "full" ? replaceLabel : null
7324
8043
  ]
7325
8044
  }
7326
8045
  )
@@ -7404,6 +8123,8 @@ function parseSectionsFromRoot(root) {
7404
8123
  const id = el.getAttribute("data-ohw-section") ?? "";
7405
8124
  if (!id || seen.has(id) || LINK_PICKER_EXCLUDED_IDS.has(id)) continue;
7406
8125
  if (el.parentElement?.closest("[data-ohw-section]")) continue;
8126
+ if (el.hasAttribute("data-ohw-ai-template-hidden") || el.hasAttribute("data-ohw-ai-removed") || el.hasAttribute("data-ohw-ai-replaced-by"))
8127
+ continue;
7407
8128
  seen.add(id);
7408
8129
  const label = el.getAttribute("data-ohw-section-label") ?? titleCaseSectionId(id);
7409
8130
  sections.push({ id, label });
@@ -7689,6 +8410,7 @@ function AiSectionOverlay({
7689
8410
  }) {
7690
8411
  const [selectedId, setSelectedId] = (0, import_react8.useState)(null);
7691
8412
  const [reviewId, setReviewId] = (0, import_react8.useState)(null);
8413
+ const [reviewButtonsHidden, setReviewButtonsHidden] = (0, import_react8.useState)(false);
7692
8414
  const reviewIdRef = (0, import_react8.useRef)(null);
7693
8415
  reviewIdRef.current = reviewId;
7694
8416
  const selectedIdRef = (0, import_react8.useRef)(null);
@@ -7750,6 +8472,7 @@ function AiSectionOverlay({
7750
8472
  }
7751
8473
  const found = readRect(sectionId) != null;
7752
8474
  setReviewId(found ? sectionId : null);
8475
+ setReviewButtonsHidden(e.data.hideButtons === true);
7753
8476
  postToParent2({ type: "ow:ai-review-started", sectionId, found });
7754
8477
  if (found) {
7755
8478
  document.querySelector(`[data-ohw-instance="${CSS.escape(sectionId)}"]`)?.scrollIntoView({ behavior: "smooth", block: "nearest" });
@@ -7877,13 +8600,16 @@ function AiSectionOverlay({
7877
8600
  border: `2px solid ${PRIMARY2}`,
7878
8601
  borderRadius: edgeAwareRadius(reviewRect),
7879
8602
  zIndex: 2147483200,
7880
- // The veil itself: swallows clicks so the section stays locked until decided.
8603
+ // The veil itself: swallows clicks so the section stays locked until decided. This
8604
+ // stopPropagation only guards the bubble phase; the bridge's capture-phase click
8605
+ // handlers additionally bail on `[data-ohw-ai-review]`, without which a click on
8606
+ // Accept/Discard resolves to the media beneath and opens the file picker.
7881
8607
  background: "rgba(8, 133, 254, 0.04)",
7882
8608
  pointerEvents: "auto",
7883
8609
  cursor: "default"
7884
8610
  },
7885
8611
  onClick: (e) => e.stopPropagation(),
7886
- children: /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(
8612
+ children: !reviewButtonsHidden && /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(
7887
8613
  "div",
7888
8614
  {
7889
8615
  style: {
@@ -10450,8 +11176,13 @@ function referenceBox(slot) {
10450
11176
  const neighbour = row ? Array.from(row.querySelectorAll('[data-ohw-editable="icon"]')).find(
10451
11177
  (el) => el !== slot && !el.hasAttribute("data-ohw-social-icon-placeholder")
10452
11178
  ) : null;
10453
- const source = (neighbour ?? slot).querySelector(GLYPH_SELECTOR);
10454
- const box = source?.getBoundingClientRect() ?? null;
11179
+ if (neighbour) {
11180
+ const box2 = neighbour.querySelector(GLYPH_SELECTOR)?.getBoundingClientRect();
11181
+ if (box2?.width && box2.height) return box2;
11182
+ }
11183
+ const own = slot.getBoundingClientRect();
11184
+ if (own.width && own.height) return own;
11185
+ const box = slot.querySelector(GLYPH_SELECTOR)?.getBoundingClientRect() ?? null;
10455
11186
  return box?.width && box.height ? box : null;
10456
11187
  }
10457
11188
  function iconMarkupSizedFor(slot, markup) {
@@ -12252,6 +12983,7 @@ function readLogoSizeState(content, placement) {
12252
12983
  function getLogoElement(el) {
12253
12984
  const marked = el.closest('[data-ohw-role="logo"], [data-ohw-logo]');
12254
12985
  if (marked) return marked;
12986
+ if (el.closest('[data-ohw-editable="icon"]')) return null;
12255
12987
  const root = el.closest("nav, [data-ohw-nav-root], footer");
12256
12988
  if (!root) return null;
12257
12989
  const anchor = el.closest("a");
@@ -13320,6 +14052,7 @@ function useSectionDrag({
13320
14052
  }
13321
14053
  const orderJson = JSON.stringify(entries);
13322
14054
  editContentRef.current = { ...editContentRef.current, [SECTION_ORDER_KEY]: orderJson };
14055
+ setAiSectionOrder(orderJson, window.location.pathname);
13323
14056
  postToParentRef.current({ type: "ow:change", nodes: [{ key: SECTION_ORDER_KEY, text: orderJson }] });
13324
14057
  applyPersistedOrder(entries);
13325
14058
  clearSectionDragVisuals();
@@ -14179,21 +14912,10 @@ function parseSchedulingInsertAfter(insertAfter) {
14179
14912
  insertBefore: insertAfter.slice(idx + INSERT_BEFORE_MARKER.length)
14180
14913
  };
14181
14914
  }
14182
- function resolveSchedulingInsert(insertAfter, explicitInsertBefore) {
14183
- const parsed = parseSchedulingInsertAfter(insertAfter);
14184
- const insertBefore = explicitInsertBefore ?? parsed.insertBefore;
14185
- const effectiveInsertAfter = insertBefore && parsed.insertBefore === null ? `${insertAfter}${INSERT_BEFORE_MARKER}${insertBefore}` : insertAfter;
14186
- return { effectiveInsertAfter, insertBefore };
14187
- }
14188
- function getSchedulingMountPoint(insertAfter) {
14189
- const { anchor } = parseSchedulingInsertAfter(insertAfter);
14190
- let anchorEl = document.querySelector(`[data-ohw-section="${anchor}"]`);
14191
- if (!anchorEl && anchor === "scheduling") {
14192
- const widgets = Array.from(document.querySelectorAll('[data-ohw-section^="scheduling-"]'));
14193
- anchorEl = widgets.at(-1) ?? null;
14194
- }
14195
- if (!anchorEl) return null;
14196
- return anchorEl.closest('[data-ohw-section-container="scheduling"]') ?? anchorEl;
14915
+ function resolveEntryAnchor(entry) {
14916
+ if (entry.anchorId !== void 0) return { anchorId: entry.anchorId, beforeId: entry.beforeId ?? null };
14917
+ const parsed = parseSchedulingInsertAfter(entry.insertAfter);
14918
+ return { anchorId: parsed.anchor, beforeId: parsed.insertBefore };
14197
14919
  }
14198
14920
  function schedulingMountDepth(insertAfter) {
14199
14921
  if (!insertAfter.includes(INSERT_BEFORE_MARKER)) return 0;
@@ -14210,8 +14932,7 @@ function getPageSchedulingEntries(raw) {
14210
14932
  }
14211
14933
  }
14212
14934
  function isSchedulingWidgetMissing(entry) {
14213
- const { effectiveInsertAfter } = resolveSchedulingInsert(entry.insertAfter);
14214
- return !document.querySelector(`[data-ohw-section="${schedulingSectionId(effectiveInsertAfter)}"]`);
14935
+ return !document.querySelector(`[data-ohw-section="${schedulingSectionId(entry.insertAfter)}"]`);
14215
14936
  }
14216
14937
  function hasMissingSchedulingWidgets(entries) {
14217
14938
  return entries.some(isSchedulingWidgetMissing);
@@ -14241,16 +14962,17 @@ function initSectionsFromContent(content, removeExisting = false) {
14241
14962
  } catch {
14242
14963
  }
14243
14964
  }
14244
- function mountSchedulingWidget(insertAfter, notifyOnConnect = false, scheduleId, explicitInsertBefore) {
14245
- const { effectiveInsertAfter, insertBefore } = resolveSchedulingInsert(insertAfter, explicitInsertBefore);
14246
- const sectionId = schedulingSectionId(effectiveInsertAfter);
14965
+ function mountSchedulingWidget(anchorId, notifyOnConnect = false, scheduleId, beforeId, existingWidgetId) {
14966
+ const widgetId = existingWidgetId ?? (beforeId ? `${anchorId}${INSERT_BEFORE_MARKER}${beforeId}` : anchorId);
14967
+ const sectionId = schedulingSectionId(widgetId);
14247
14968
  if (document.querySelector(`[data-ohw-section="${sectionId}"]`)) return false;
14248
- const mountPoint = getSchedulingMountPoint(effectiveInsertAfter);
14249
- if (!mountPoint) return false;
14969
+ const anchorEl = document.querySelector(`[data-ohw-section="${anchorId}"]`);
14970
+ if (!anchorEl) return false;
14971
+ const mountPoint = anchorEl.closest('[data-ohw-section-container="scheduling"]') ?? anchorEl;
14250
14972
  const container = document.createElement("div");
14251
14973
  container.dataset.ohwSectionContainer = "scheduling";
14252
- if (insertBefore) {
14253
- const beforeAnchor = document.querySelector(`[data-ohw-section="${insertBefore}"]`);
14974
+ if (beforeId) {
14975
+ const beforeAnchor = document.querySelector(`[data-ohw-section="${beforeId}"]`);
14254
14976
  const beforePoint = beforeAnchor?.closest('[data-ohw-section-container="scheduling"]') ?? beforeAnchor;
14255
14977
  if (!beforePoint) return false;
14256
14978
  beforePoint.insertAdjacentElement("beforebegin", container);
@@ -14261,19 +14983,25 @@ function mountSchedulingWidget(insertAfter, notifyOnConnect = false, scheduleId,
14261
14983
  }
14262
14984
  tail.insertAdjacentElement("afterend", container);
14263
14985
  }
14264
- const root = (0, import_client2.createRoot)(container);
14265
- (0, import_react_dom3.flushSync)(() => {
14266
- root.render(
14267
- /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
14268
- SchedulingWidget,
14269
- {
14270
- notifyOnConnect,
14271
- initialScheduleId: scheduleId,
14272
- insertAfter: effectiveInsertAfter
14273
- }
14274
- )
14275
- );
14276
- });
14986
+ try {
14987
+ const root = (0, import_client2.createRoot)(container);
14988
+ (0, import_react_dom3.flushSync)(() => {
14989
+ root.render(
14990
+ /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
14991
+ SchedulingWidget,
14992
+ {
14993
+ notifyOnConnect,
14994
+ initialScheduleId: scheduleId,
14995
+ insertAfter: widgetId
14996
+ }
14997
+ )
14998
+ );
14999
+ });
15000
+ } catch (err) {
15001
+ console.error("[ow:scheduling] render threw", err);
15002
+ container.remove();
15003
+ return false;
15004
+ }
14277
15005
  const tracker = getSectionsTracker();
14278
15006
  let sections = [];
14279
15007
  try {
@@ -14281,10 +15009,12 @@ function mountSchedulingWidget(insertAfter, notifyOnConnect = false, scheduleId,
14281
15009
  } catch {
14282
15010
  }
14283
15011
  const inEditor = typeof window !== "undefined" && window.self !== window.top;
14284
- if (inEditor && !sections.find((s) => s.type === "scheduling" && s.insertAfter === effectiveInsertAfter)) {
15012
+ if (inEditor && !sections.find((s) => s.type === "scheduling" && s.insertAfter === widgetId)) {
14285
15013
  sections.push({
14286
15014
  type: "scheduling",
14287
- insertAfter: effectiveInsertAfter,
15015
+ insertAfter: widgetId,
15016
+ anchorId,
15017
+ beforeId: beforeId ?? null,
14288
15018
  pagePath: window.location.pathname,
14289
15019
  ...scheduleId ? { scheduleId } : {}
14290
15020
  });
@@ -14298,7 +15028,8 @@ function mountSchedulingEntries(entries, notifyOnConnect = false) {
14298
15028
  for (let i = pending.length - 1; i >= 0; i--) {
14299
15029
  const entry = pending[i];
14300
15030
  const shouldNotify = typeof notifyOnConnect === "function" ? notifyOnConnect(entry) : notifyOnConnect;
14301
- if (mountSchedulingWidget(entry.insertAfter, shouldNotify, entry.scheduleId)) {
15031
+ const { anchorId, beforeId } = resolveEntryAnchor(entry);
15032
+ if (mountSchedulingWidget(anchorId, shouldNotify, entry.scheduleId ?? null, beforeId, entry.insertAfter)) {
14302
15033
  pending.splice(i, 1);
14303
15034
  }
14304
15035
  }
@@ -14456,6 +15187,11 @@ function applyLinkByKey(key, val) {
14456
15187
  hrefAnchors.forEach((el) => applyLinkHref(el, val));
14457
15188
  }
14458
15189
  }
15190
+ function isInsideLinkEditor(target) {
15191
+ return Boolean(
15192
+ 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"]')
15193
+ );
15194
+ }
14459
15195
  function isInsideFloatingPanel(target) {
14460
15196
  return Boolean(target.closest("[data-ohw-floating-panel]"));
14461
15197
  }
@@ -14463,11 +15199,6 @@ function isPointOverFloatingPanel(clientX, clientY) {
14463
15199
  const el = document.elementFromPoint(clientX, clientY);
14464
15200
  return Boolean(el instanceof Element && el.closest("[data-ohw-floating-panel]"));
14465
15201
  }
14466
- function isInsideLinkEditor(target) {
14467
- return Boolean(
14468
- 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"]')
14469
- );
14470
- }
14471
15202
  function getHrefKeyFromElement(el) {
14472
15203
  if (!el) return null;
14473
15204
  const anchor = el.closest("[data-ohw-href-key]");
@@ -14726,7 +15457,7 @@ function getNavigationSelectionParent(el) {
14726
15457
  if (!isFooterLinksContainer(el) && (el.hasAttribute("data-ohw-footer-col") || el.hasAttribute("data-ohw-footer-column") || isInferredFooterGroup2(el))) {
14727
15458
  return getFooterLinksContainer();
14728
15459
  }
14729
- if (el.hasAttribute("data-ohw-nav-container") || el.hasAttribute("data-ohw-nav-drawer") || isFooterLinksContainer(el)) {
15460
+ 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)) {
14730
15461
  return getNavigationRoot(el);
14731
15462
  }
14732
15463
  return null;
@@ -14941,7 +15672,6 @@ var ICONS = {
14941
15672
  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"/>',
14942
15673
  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"/>'
14943
15674
  };
14944
- var HEIGHT_SETTLE_DELAYS = [150, 400, 800];
14945
15675
  var SELECTION_CHROME_GAP2 = 4;
14946
15676
  var TOOLBAR_STROKE_GAP2 = 4;
14947
15677
  var TOOLBAR_OFFSET_FROM_ELEMENT = SELECTION_CHROME_GAP2 + TOOLBAR_STROKE_GAP2;
@@ -15321,6 +16051,8 @@ function StateToggle({
15321
16051
  );
15322
16052
  }
15323
16053
  var contentCache = /* @__PURE__ */ new Map();
16054
+ var fetchedContentPaths = /* @__PURE__ */ new Set();
16055
+ var brandingCache = /* @__PURE__ */ new Map();
15324
16056
  var OHW_LOADER_STYLE = {
15325
16057
  position: "fixed",
15326
16058
  inset: 0,
@@ -15358,6 +16090,89 @@ function OhwLoaderSpinner() {
15358
16090
  )
15359
16091
  ] });
15360
16092
  }
16093
+ function OhwBrandMark() {
16094
+ return /* @__PURE__ */ (0, import_jsx_runtime33.jsxs)(
16095
+ "svg",
16096
+ {
16097
+ width: "16",
16098
+ height: "16",
16099
+ viewBox: "0 0 48 48",
16100
+ fill: "none",
16101
+ "aria-hidden": true,
16102
+ style: { display: "block", flexShrink: 0 },
16103
+ xmlns: "http://www.w3.org/2000/svg",
16104
+ children: [
16105
+ /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
16106
+ "mask",
16107
+ {
16108
+ id: "ohw-badge-mark",
16109
+ style: { maskType: "luminance" },
16110
+ maskUnits: "userSpaceOnUse",
16111
+ x: "0",
16112
+ y: "0",
16113
+ width: "48",
16114
+ height: "48",
16115
+ children: /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("path", { d: "M23.8741 48C37.0594 48 47.7481 37.2548 47.7481 24C47.7481 10.7452 37.0594 0 23.8741 0C10.6888 0 0 10.7452 0 24C0 37.2548 10.6888 48 23.8741 48Z", fill: "white" })
16116
+ }
16117
+ ),
16118
+ /* @__PURE__ */ (0, import_jsx_runtime33.jsxs)("g", { mask: "url(#ohw-badge-mark)", children: [
16119
+ /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("path", { d: "M23.8731 48.0497C37.0584 48.0497 47.7472 37.3046 47.7472 24.0497C47.7472 10.7949 37.0584 0.0497208 23.8731 0.0497208C10.6878 0.0497208 -0.000976562 10.7949 -0.000976562 24.0497C-0.000976562 37.3046 10.6878 48.0497 23.8731 48.0497Z", fill: "#0078E5" }),
16120
+ /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("path", { d: "M17.1307 14.7172C13.1687 14.7172 9.38102 18.1154 8.65114 22.34C8.38885 23.8488 8.5598 25.2581 9.06929 26.4451C6.20005 29.1677 1.77216 27.8721 -1.40212 26.1536C-2.73618 25.4317 -3.92695 27.4745 -2.59037 28.1981C1.33389 30.3226 6.86037 31.6621 10.4402 28.4188C11.4718 29.3859 12.867 29.9621 14.4894 29.9621C18.4161 29.9621 22.2038 26.5318 22.9337 22.34C23.6636 18.1162 21.0566 14.7172 17.1298 14.7172H17.1307ZM19.9798 22.34C19.5281 25.0399 17.2689 27.231 14.9754 27.231C12.6466 27.231 11.1877 25.0399 11.6394 22.34C12.1262 19.6401 14.3151 17.4482 16.6438 17.4482C18.9374 17.4482 20.4667 19.6401 19.9798 22.34Z", fill: "white" }),
16121
+ /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("path", { d: "M40.0017 27.0262C39.1797 27.081 38.2721 26.995 37.4668 26.7415C37.3344 26.6993 37.28 26.5401 37.3529 26.4205C37.5959 26.0212 37.8255 25.6152 38.009 25.1889C38.1071 24.9918 38.2018 24.793 38.2897 24.5908C38.3274 24.5041 38.4163 24.451 38.5101 24.4619C38.63 24.4754 38.7054 24.4821 38.8881 24.4821L39.1529 24.4796L39.8283 24.4543C45.9229 24.0492 50.4765 20.4319 54.8466 16.9014C56.0172 15.9554 57.6932 17.6208 56.5116 18.5752C51.7687 22.4065 47.1966 26.5081 40.9319 26.9689", fill: "white" }),
16122
+ /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("path", { d: "M37.9687 24.27C38.4472 23.1319 38.7656 21.9045 38.9609 20.6991C39.5927 16.76 38.5058 14.2193 36.1553 14.2193C34.1835 14.2193 33.1469 17.2427 32.9199 18.8694C32.743 19.9872 32.5914 22.1219 33.6028 23.9524C33.7553 24.259 34.15 24.7712 34.471 25.1259C34.5447 25.2067 34.6746 25.2 34.7349 25.1082C34.9528 24.7788 35.1615 24.4039 35.3584 24.0257C35.5444 23.6677 35.5888 23.6138 35.8587 23.0207C35.8838 22.966 35.8813 22.9002 35.8478 22.8505C35.5888 22.4597 35.2168 21.9787 35.1204 21.4614C34.9184 20.4455 34.9436 19.2257 35.2218 18.1078C35.4413 17.3118 35.7195 16.7844 35.9039 16.5106C35.9466 16.4466 36.0279 16.4129 36.0975 16.4449C36.369 16.5671 36.5827 16.8838 36.7385 17.396C37.0167 18.2089 37.0167 19.3782 36.814 20.6999C36.6991 21.5271 36.4771 22.3729 36.1746 23.1673C36.1293 23.308 36.0757 23.4461 36.0187 23.5826C36.0187 23.5868 36.0187 23.591 36.0187 23.5961C35.9911 23.6946 35.9207 23.8067 35.8846 23.901C35.5536 24.5497 35.2344 25.1697 34.8439 25.7838C34.8388 25.7863 34.8346 25.7914 34.8296 25.7931C34.6528 26.0525 34.4718 26.2901 34.2866 26.4965C34.2774 26.5099 34.2682 26.5234 34.2589 26.5369C34.2405 26.5638 34.2179 26.5815 34.1944 26.595C33.5064 27.3212 32.7665 27.6893 32.0123 27.6893C31.8606 27.6893 31.6838 27.664 31.507 27.4349C31.3042 27.1299 30.85 26.0879 31.2539 22.9112C31.4785 21.3949 31.8011 20.0268 31.931 19.5408C31.9579 19.4413 31.8908 19.3419 31.7886 19.3293L30.0062 19.1162C29.9241 19.1061 29.8478 19.1566 29.8252 19.2366C29.2704 21.1615 27.0305 27.6885 24.9599 27.6885C24.328 27.6885 24.1512 26.6211 24.1001 26.2909C23.775 23.6264 25.528 18.5492 29.5302 16.2267C29.6048 16.1838 29.635 16.0928 29.5998 16.0136L28.9042 14.467C28.8632 14.3752 28.7501 14.3389 28.6638 14.3886C25.8079 16.0414 24.1847 18.5231 23.2915 20.3436C22.2046 22.5793 21.6993 25.0189 21.9515 26.8738C22.1795 28.7794 23.1398 29.872 24.6054 29.872C26.0543 29.872 27.4369 28.9066 28.7098 27.0187C28.7953 26.8915 28.9889 26.9311 29.0149 27.0819C29.2646 28.5283 29.9811 29.872 31.6579 29.872C33.5282 29.872 35.3232 28.7288 36.7134 26.6447C36.7712 26.5874 36.7972 26.5411 36.8181 26.4906C36.8232 26.4931 36.8282 26.4948 36.8332 26.4973C37.01 26.2025 37.181 25.9051 37.3444 25.6027C37.4508 25.4005 37.5572 25.1992 37.6586 24.9945C37.7181 24.874 37.7743 24.7527 37.8262 24.6289C37.838 24.6002 37.8547 24.5665 37.8706 24.5337", fill: "white" }),
16123
+ /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("path", { d: "M30.5839 31.6397C25.7546 34.8577 19.4773 34.7853 14.6907 31.5243C13.5368 30.7384 12.5044 32.6498 13.6474 33.4281C19.034 37.0985 26.3077 37.096 31.7218 33.488C32.8791 32.7172 31.7478 30.8639 30.5839 31.6397Z", fill: "white" })
16124
+ ] })
16125
+ ]
16126
+ }
16127
+ );
16128
+ }
16129
+ var OHW_BADGE_STYLE = {
16130
+ position: "fixed",
16131
+ left: 20,
16132
+ bottom: 20,
16133
+ zIndex: 2147483e3,
16134
+ boxSizing: "border-box",
16135
+ display: "inline-flex",
16136
+ alignItems: "center",
16137
+ gap: 0,
16138
+ padding: "6px 8px",
16139
+ margin: 0,
16140
+ background: "#ffffff",
16141
+ border: "1px solid #e7e5e4",
16142
+ borderRadius: 9999,
16143
+ boxShadow: "0 1px 3px rgba(0, 0, 0, 0.1)",
16144
+ color: "#0c0a09",
16145
+ textDecoration: "none",
16146
+ fontFamily: "-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif"
16147
+ };
16148
+ var OHW_BADGE_LABEL_STYLE = {
16149
+ padding: "0 4px",
16150
+ fontSize: 14,
16151
+ lineHeight: "24px",
16152
+ fontWeight: 500,
16153
+ fontStyle: "normal",
16154
+ letterSpacing: "normal",
16155
+ textTransform: "none",
16156
+ color: "#0c0a09",
16157
+ whiteSpace: "nowrap"
16158
+ };
16159
+ function MadeWithOhhWells() {
16160
+ return /* @__PURE__ */ (0, import_jsx_runtime33.jsxs)(
16161
+ "a",
16162
+ {
16163
+ href: "https://ohhwells.com",
16164
+ target: "_blank",
16165
+ rel: "noopener noreferrer",
16166
+ "aria-label": "Made with OhhWells",
16167
+ "data-ohw-badge": "",
16168
+ style: OHW_BADGE_STYLE,
16169
+ children: [
16170
+ /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(OhwBrandMark, {}),
16171
+ /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("span", { style: OHW_BADGE_LABEL_STYLE, children: "Made with OhhWells" })
16172
+ ]
16173
+ }
16174
+ );
16175
+ }
15361
16176
  var OHW_LOADER_PREHYDRATE_SCRIPT = `(function(){try{var p=location.hostname.split(".");var fromHost=p.length>=3&&p[0]!=="www"?p[0]:"";var fromQuery=new URLSearchParams(location.search).get("subdomain")||"";if(!fromHost&&!fromQuery)return;var e=document.getElementById("ohw-loader");if(e)e.style.display="flex"}catch(e){}})();`;
15362
16177
  function resolveSubdomain(subdomainFromQuery) {
15363
16178
  if (subdomainFromQuery) return subdomainFromQuery;
@@ -15417,6 +16232,7 @@ function OhhwellsBridge() {
15417
16232
  }
15418
16233
  }, []);
15419
16234
  const [fetchState, setFetchState] = (0, import_react17.useState)("idle");
16235
+ const [showBranding, setShowBranding] = (0, import_react17.useState)(false);
15420
16236
  const autoSaveTimers = (0, import_react17.useRef)(/* @__PURE__ */ new Map());
15421
16237
  const activeElRef = (0, import_react17.useRef)(null);
15422
16238
  const pointerHeldRef = (0, import_react17.useRef)(false);
@@ -15439,6 +16255,70 @@ function OhhwellsBridge() {
15439
16255
  const hoveredImageHasTextOverlapRef = (0, import_react17.useRef)(false);
15440
16256
  const dragOverElRef = (0, import_react17.useRef)(null);
15441
16257
  const [mediaHover, setMediaHover] = (0, import_react17.useState)(null);
16258
+ const [selectedMedia, setSelectedMedia] = (0, import_react17.useState)(null);
16259
+ const selectedMediaElRef = (0, import_react17.useRef)(null);
16260
+ const clearMediaSelection = (0, import_react17.useCallback)(() => {
16261
+ const prev = selectedMediaElRef.current;
16262
+ selectedMediaElRef.current = null;
16263
+ setSelectedMedia(null);
16264
+ const sectionEl = prev?.closest("[data-ohw-section]") ?? null;
16265
+ if (sectionEl) {
16266
+ postToParentRef.current({
16267
+ type: "ow:section-selected",
16268
+ sectionId: sectionEl.dataset.ohwSection ?? null,
16269
+ sectionLabel: sectionEl.dataset.ohwSectionLabel ?? titleCaseSectionId(sectionEl.dataset.ohwSection ?? ""),
16270
+ key: null
16271
+ });
16272
+ }
16273
+ }, []);
16274
+ const clearMediaSelectionRef = (0, import_react17.useRef)(clearMediaSelection);
16275
+ clearMediaSelectionRef.current = clearMediaSelection;
16276
+ const selectMediaElement = (0, import_react17.useCallback)((el) => {
16277
+ const r2 = el.getBoundingClientRect();
16278
+ const video = el.dataset.ohwEditable === "video" ? el.querySelector("video") : null;
16279
+ selectedMediaElRef.current = el;
16280
+ setSelectedMedia({
16281
+ key: el.dataset.ohwKey ?? "",
16282
+ rect: { top: r2.top, left: r2.left, width: r2.width, height: r2.height },
16283
+ elementType: el.dataset.ohwEditable ?? "image",
16284
+ hasTextOverlap: false,
16285
+ isDragOver: false,
16286
+ ...video ? { videoAutoplay: video.autoplay, videoMuted: video.muted } : {}
16287
+ });
16288
+ const sectionEl = el.closest("[data-ohw-section]");
16289
+ aiSectionApiRef.current?.selectFromElement(el, { report: false });
16290
+ postToParentRef.current({
16291
+ type: "ow:section-selected",
16292
+ sectionId: sectionEl?.dataset.ohwSection ?? null,
16293
+ sectionLabel: sectionEl ? sectionEl.dataset.ohwSectionLabel ?? titleCaseSectionId(sectionEl.dataset.ohwSection ?? "") : null,
16294
+ key: el.dataset.ohwKey ?? null,
16295
+ // Display name for the pill — the raw key prettifies into fragments ("Img"); the
16296
+ // bridge knows what the node IS, so it names it.
16297
+ keyLabel: el.dataset.ohwEditable === "video" ? "Video" : el.dataset.ohwEditable === "bg-image" ? "Background" : "Image"
16298
+ });
16299
+ }, []);
16300
+ const selectMediaElementRef = (0, import_react17.useRef)(selectMediaElement);
16301
+ selectMediaElementRef.current = selectMediaElement;
16302
+ (0, import_react17.useEffect)(() => {
16303
+ if (!selectedMedia) return;
16304
+ const update = () => {
16305
+ const el = selectedMediaElRef.current;
16306
+ if (!el || !el.isConnected) {
16307
+ clearMediaSelection();
16308
+ return;
16309
+ }
16310
+ const r2 = el.getBoundingClientRect();
16311
+ setSelectedMedia(
16312
+ (prev) => prev ? { ...prev, rect: { top: r2.top, left: r2.left, width: r2.width, height: r2.height } } : prev
16313
+ );
16314
+ };
16315
+ window.addEventListener("scroll", update, true);
16316
+ window.addEventListener("resize", update);
16317
+ return () => {
16318
+ window.removeEventListener("scroll", update, true);
16319
+ window.removeEventListener("resize", update);
16320
+ };
16321
+ }, [selectedMedia !== null]);
15442
16322
  const [carouselHover, setCarouselHover] = (0, import_react17.useState)(null);
15443
16323
  const [uploadingRects, setUploadingRects] = (0, import_react17.useState)({});
15444
16324
  const hoveredGapRef = (0, import_react17.useRef)(null);
@@ -15701,13 +16581,6 @@ function OhhwellsBridge() {
15701
16581
  const [isItemDragging, setIsItemDragging] = (0, import_react17.useState)(false);
15702
16582
  const [isFooterFrameSelection, setIsFooterFrameSelection] = (0, import_react17.useState)(false);
15703
16583
  isFooterFrameSelectionRef.current = isFooterFrameSelection;
15704
- const [floatingPanel, setFloatingPanel] = (0, import_react17.useState)(null);
15705
- const floatingPanelOpenRef = (0, import_react17.useRef)(false);
15706
- floatingPanelOpenRef.current = floatingPanel !== null;
15707
- const [floatingPanelPos, setFloatingPanelPos] = (0, import_react17.useState)(null);
15708
- const [logoSizeDraft, setLogoSizeDraft] = (0, import_react17.useState)(null);
15709
- const [editorViewport, setEditorViewport] = (0, import_react17.useState)("desktop");
15710
- const [parentScrollSnap, setParentScrollSnap] = (0, import_react17.useState)(null);
15711
16584
  const [navDropdownPreviewOpen, setNavDropdownPreviewOpen] = (0, import_react17.useState)(null);
15712
16585
  const [footerHeadingVisible, setFooterHeadingVisible] = (0, import_react17.useState)(null);
15713
16586
  const footerDragRef = (0, import_react17.useRef)(null);
@@ -15722,7 +16595,16 @@ function OhhwellsBridge() {
15722
16595
  const addNavAfterAnchorRef = (0, import_react17.useRef)(null);
15723
16596
  const editContentRef = (0, import_react17.useRef)({});
15724
16597
  const aiSectionsRef = (0, import_react17.useRef)("");
16598
+ const brandKitRef = (0, import_react17.useRef)("");
16599
+ const stylesRef = (0, import_react17.useRef)("");
15725
16600
  const pendingDeleteUndoRef = (0, import_react17.useRef)(null);
16601
+ const [floatingPanel, setFloatingPanel] = (0, import_react17.useState)(null);
16602
+ const floatingPanelOpenRef = (0, import_react17.useRef)(false);
16603
+ const setFloatingPanelRef = (0, import_react17.useRef)(setFloatingPanel);
16604
+ const [floatingPanelPos, setFloatingPanelPos] = (0, import_react17.useState)(null);
16605
+ const [logoSizeDraft, setLogoSizeDraft] = (0, import_react17.useState)(null);
16606
+ const [editorViewport, setEditorViewport] = (0, import_react17.useState)("desktop");
16607
+ const [parentScrollSnap, setParentScrollSnap] = (0, import_react17.useState)(null);
15726
16608
  const [sitePages, setSitePages] = (0, import_react17.useState)([]);
15727
16609
  const [sectionsByPath, setSectionsByPath] = (0, import_react17.useState)({});
15728
16610
  const sectionsPrefetchGenRef = (0, import_react17.useRef)(0);
@@ -15731,7 +16613,18 @@ function OhhwellsBridge() {
15731
16613
  const linkPopoverOpenRef = (0, import_react17.useRef)(false);
15732
16614
  const linkPopoverGraceUntilRef = (0, import_react17.useRef)(0);
15733
16615
  setLinkPopoverRef.current = setLinkPopover;
16616
+ setFloatingPanelRef.current = setFloatingPanel;
15734
16617
  linkPopoverSessionRef.current = linkPopover;
16618
+ floatingPanelOpenRef.current = Boolean(floatingPanel);
16619
+ (0, import_react17.useEffect)(() => {
16620
+ const syncViewport = () => {
16621
+ const next = window.innerWidth <= 480 ? "mobile" : "desktop";
16622
+ setEditorViewport((prev) => prev === next ? prev : next);
16623
+ };
16624
+ syncViewport();
16625
+ window.addEventListener("resize", syncViewport);
16626
+ return () => window.removeEventListener("resize", syncViewport);
16627
+ }, []);
15735
16628
  const {
15736
16629
  navDragRef,
15737
16630
  navDropSlots,
@@ -17042,15 +17935,31 @@ function OhhwellsBridge() {
17042
17935
  }
17043
17936
  const applyContent = (content) => {
17044
17937
  const imageLoads = [];
17938
+ if (typeof content[BRAND_KIT_KEY] === "string") {
17939
+ brandKitRef.current = content[BRAND_KIT_KEY];
17940
+ applyBrandToDom(parseBrandKit(content[BRAND_KIT_KEY]));
17941
+ } else {
17942
+ brandKitRef.current = "";
17943
+ applyBrandToDom(null);
17944
+ }
17045
17945
  if (typeof content[AI_SECTIONS_KEY] === "string") {
17046
17946
  aiSectionsRef.current = content[AI_SECTIONS_KEY];
17947
+ setAiSectionOrder(content[SECTION_ORDER_KEY], window.location.pathname);
17047
17948
  applyAiSectionsToDom(parseAiSectionsState(content[AI_SECTIONS_KEY]));
17048
17949
  }
17950
+ if (typeof content[STYLE_STORE_KEY] === "string") {
17951
+ stylesRef.current = content[STYLE_STORE_KEY];
17952
+ applyStylesToDom(parseStyleStore(content[STYLE_STORE_KEY]));
17953
+ }
17954
+ applyBrandChrome(content);
17049
17955
  for (const [key, val] of Object.entries(content)) {
17050
17956
  if (key === "__ohw_sections") continue;
17051
17957
  if (key === AI_SECTIONS_KEY) continue;
17052
17958
  if (key === LOGO_PLACEHOLDER_KEY) continue;
17053
17959
  if (LOGO_IMAGE_KEYS.includes(key)) continue;
17960
+ if (key === BRAND_KIT_KEY) continue;
17961
+ if (key === STYLE_STORE_KEY) continue;
17962
+ if (BRAND_CHROME_KEYS.has(key)) continue;
17054
17963
  if (applyVideoSettingNode(key, val)) continue;
17055
17964
  if (applyCarouselNode(key, val)) continue;
17056
17965
  document.querySelectorAll(`[data-ohw-key="${key}"]`).forEach((el) => {
@@ -17110,16 +18019,22 @@ function OhhwellsBridge() {
17110
18019
  };
17111
18020
  const cached = contentCache.get(subdomain);
17112
18021
  if (cached) {
18022
+ setShowBranding(brandingCache.get(subdomain) ?? false);
17113
18023
  applyContent(cached).finally(() => setFetchState("done"));
17114
18024
  return;
17115
18025
  }
17116
18026
  let cancelled = false;
17117
18027
  setFetchState("loading");
17118
18028
  const apiUrl = process.env.NEXT_PUBLIC_FLOWOPS_API_URL ?? "https://flowops-backend-staging-2yfdh7pwpq-as.a.run.app";
17119
- fetch(`${apiUrl}/api/public/sites/${subdomain}/content`, { cache: "no-store" }).then((r2) => r2.ok ? r2.json() : null).then((data) => {
18029
+ const initialPath = pathname;
18030
+ fetchedContentPaths.add(`${subdomain}::${initialPath}`);
18031
+ fetch(`${apiUrl}/api/public/sites/${subdomain}/content?path=${encodeURIComponent(initialPath)}`, { cache: "no-store" }).then((r2) => r2.ok ? r2.json() : null).then((data) => {
17120
18032
  if (cancelled) return;
17121
18033
  const content = data?.content ?? {};
18034
+ const branding = Boolean(data?.showBranding);
17122
18035
  contentCache.set(subdomain, content);
18036
+ brandingCache.set(subdomain, branding);
18037
+ setShowBranding(branding);
17123
18038
  return applyContent(content);
17124
18039
  }).catch(() => {
17125
18040
  }).finally(() => {
@@ -17236,10 +18151,28 @@ function OhhwellsBridge() {
17236
18151
  initSectionInstancesFromContent(content, window.location.pathname);
17237
18152
  observer?.disconnect();
17238
18153
  try {
18154
+ applyBrandChrome(content);
18155
+ if (typeof content[BRAND_KIT_KEY] === "string") {
18156
+ applyBrandToDom(parseBrandKit(content[BRAND_KIT_KEY]));
18157
+ } else {
18158
+ applyBrandToDom(null);
18159
+ }
18160
+ if (typeof content[AI_SECTIONS_KEY] === "string") {
18161
+ setAiSectionOrder(content[SECTION_ORDER_KEY], window.location.pathname);
18162
+ applyAiSectionsToDom(parseAiSectionsState(content[AI_SECTIONS_KEY]));
18163
+ }
18164
+ if (typeof content[STYLE_STORE_KEY] === "string") {
18165
+ applyStylesToDom(parseStyleStore(content[STYLE_STORE_KEY]));
18166
+ }
17239
18167
  for (const [key, val] of Object.entries(content)) {
17240
18168
  if (key === "__ohw_sections") continue;
18169
+ if (key === AI_SECTIONS_KEY) continue;
17241
18170
  if (key === LOGO_PLACEHOLDER_KEY) continue;
17242
18171
  if (LOGO_IMAGE_KEYS.includes(key)) continue;
18172
+ if (key === BRAND_KIT_KEY) continue;
18173
+ if (key === STYLE_STORE_KEY) continue;
18174
+ if (key === STYLE_STORE_KEY) continue;
18175
+ if (BRAND_CHROME_KEYS.has(key)) continue;
17243
18176
  if (applyVideoSettingNode(key, val)) continue;
17244
18177
  if (applyCarouselNode(key, val)) continue;
17245
18178
  document.querySelectorAll(`[data-ohw-key="${key}"]`).forEach((el) => {
@@ -17285,6 +18218,17 @@ function OhhwellsBridge() {
17285
18218
  debounceTimer = setTimeout(applyFromCache, 150);
17286
18219
  };
17287
18220
  applyFromCache();
18221
+ const pathCacheKey = `${subdomain}::${pathname}`;
18222
+ if (!fetchedContentPaths.has(pathCacheKey)) {
18223
+ fetchedContentPaths.add(pathCacheKey);
18224
+ const apiUrl = process.env.NEXT_PUBLIC_FLOWOPS_API_URL ?? "https://flowops-backend-staging-2yfdh7pwpq-as.a.run.app";
18225
+ fetch(`${apiUrl}/api/public/sites/${subdomain}/content?path=${encodeURIComponent(pathname)}`, { cache: "no-store" }).then((r2) => r2.ok ? r2.json() : null).then((data) => {
18226
+ if (!data?.content) return;
18227
+ contentCache.set(subdomain, data.content);
18228
+ applyFromCache();
18229
+ }).catch(() => {
18230
+ });
18231
+ }
17288
18232
  observer = new MutationObserver(scheduleApply);
17289
18233
  observer.observe(document.body, { childList: true, subtree: true });
17290
18234
  return () => {
@@ -17380,30 +18324,31 @@ function OhhwellsBridge() {
17380
18324
  }, [isEditMode, pathname, subdomain, fetchState, resyncSelectedNavigationItem]);
17381
18325
  (0, import_react17.useEffect)(() => {
17382
18326
  if (!isEditMode) return;
18327
+ let lastPosted = 0;
17383
18328
  const measure = () => {
17384
18329
  const h = document.body.scrollHeight;
17385
- if (h > 50) postToParent2({ type: "ow:height", height: h });
18330
+ if (h > 50 && Math.abs(h - lastPosted) > 1) {
18331
+ lastPosted = h;
18332
+ postToParent2({ type: "ow:height", height: h });
18333
+ }
18334
+ };
18335
+ let raf = null;
18336
+ const schedule = () => {
18337
+ if (raf != null) return;
18338
+ raf = requestAnimationFrame(() => {
18339
+ raf = null;
18340
+ measure();
18341
+ });
17386
18342
  };
17387
18343
  const t1 = setTimeout(measure, 50);
17388
18344
  const t2 = setTimeout(measure, 500);
17389
- let lastWidth = window.innerWidth;
17390
- let resizeTimers = [];
17391
- const clearResizeTimers = () => {
17392
- resizeTimers.forEach(clearTimeout);
17393
- resizeTimers = [];
17394
- };
17395
- const handleResize = () => {
17396
- if (window.innerWidth === lastWidth) return;
17397
- lastWidth = window.innerWidth;
17398
- clearResizeTimers();
17399
- resizeTimers = HEIGHT_SETTLE_DELAYS.map((delay) => setTimeout(measure, delay));
17400
- };
17401
- window.addEventListener("resize", handleResize);
18345
+ const ro = new ResizeObserver(schedule);
18346
+ ro.observe(document.body);
17402
18347
  return () => {
17403
18348
  clearTimeout(t1);
17404
18349
  clearTimeout(t2);
17405
- clearResizeTimers();
17406
- window.removeEventListener("resize", handleResize);
18350
+ if (raf != null) cancelAnimationFrame(raf);
18351
+ ro.disconnect();
17407
18352
  };
17408
18353
  }, [pathname, isEditMode, postToParent2]);
17409
18354
  (0, import_react17.useEffect)(() => {
@@ -17644,6 +18589,7 @@ function OhhwellsBridge() {
17644
18589
  return;
17645
18590
  }
17646
18591
  const target = e.target;
18592
+ if (target.closest("[data-ohw-ai-review]")) return;
17647
18593
  if (target.closest("[data-ohw-toolbar]")) return;
17648
18594
  if (target.closest("[data-ohw-state-toggle]")) return;
17649
18595
  if (target.closest("[data-ohw-max-badge]")) return;
@@ -17655,6 +18601,9 @@ function OhhwellsBridge() {
17655
18601
  )) {
17656
18602
  return;
17657
18603
  }
18604
+ if (selectedMediaElRef.current && !selectedMediaElRef.current.contains(target) && !target.closest("[data-ohw-media-overlay], [data-ohw-edit-chrome], [data-ohw-item-interaction]")) {
18605
+ clearMediaSelectionRef.current();
18606
+ }
17658
18607
  {
17659
18608
  const formEl = getFormElement(target);
17660
18609
  const onSuccessText = target.closest(`[${SUCCESS_TEXT_ATTR}]`);
@@ -17806,19 +18755,14 @@ function OhhwellsBridge() {
17806
18755
  }
17807
18756
  const clickedButton = findClosestButtonLike(target);
17808
18757
  const buttonOnMedia = Boolean(clickedButton && isMediaEditable(editable) && editable.contains(clickedButton));
17809
- console.log("[click-debug]", {
17810
- editableType: editable.dataset.ohwEditable,
17811
- editableTag: editable.tagName,
17812
- targetTag: target.tagName,
17813
- clickedButtonTag: clickedButton?.tagName ?? null,
17814
- buttonOnMedia,
17815
- isMediaEditableEditable: isMediaEditable(editable)
17816
- });
17817
18758
  if (isMediaEditable(editable) && !buttonOnMedia) {
17818
18759
  e.preventDefault();
17819
18760
  e.stopPropagation();
17820
- aiSectionApiRef.current?.selectFromElement(editable);
17821
- postToParentRef.current({ type: "ow:image-pick", key: editable.dataset.ohwKey ?? "", elementType: editable.dataset.ohwEditable ?? "image" });
18761
+ if (selectedMediaElRef.current === editable) {
18762
+ postToParentRef.current({ type: "ow:image-pick", key: editable.dataset.ohwKey ?? "", elementType: editable.dataset.ohwEditable ?? "image" });
18763
+ } else {
18764
+ selectMediaElementRef.current(editable);
18765
+ }
17822
18766
  return;
17823
18767
  }
17824
18768
  const socialItem = getSocialItem(editable);
@@ -17837,11 +18781,6 @@ function OhhwellsBridge() {
17837
18781
  const hrefLookupTarget = buttonOnMedia ? clickedButton : editable;
17838
18782
  const hrefCtx = getHrefKeyFromElement(hrefLookupTarget);
17839
18783
  const navAnchor = hrefCtx ? getNavigationItemAnchor(hrefCtx.anchor) : null;
17840
- console.log("[click-debug 2]", {
17841
- hrefLookupTargetTag: hrefLookupTarget.tagName,
17842
- hrefCtx: hrefCtx ? { key: hrefCtx.key } : null,
17843
- navAnchorTag: navAnchor?.tagName ?? null
17844
- });
17845
18784
  if (navAnchor) {
17846
18785
  e.preventDefault();
17847
18786
  e.stopPropagation();
@@ -17959,6 +18898,7 @@ function OhhwellsBridge() {
17959
18898
  };
17960
18899
  const handleDblClick = (e) => {
17961
18900
  const target = e.target;
18901
+ if (target.closest("[data-ohw-ai-review]")) return;
17962
18902
  if (target.closest("[data-ohw-toolbar]")) return;
17963
18903
  if (target.closest("[data-ohw-state-toggle]")) return;
17964
18904
  if (target.closest("[data-ohw-max-badge]")) return;
@@ -18010,6 +18950,9 @@ function OhhwellsBridge() {
18010
18950
  setHoveredItemRect(null);
18011
18951
  hoveredNavContainerRef.current = null;
18012
18952
  setHoveredNavContainerRect(null);
18953
+ siblingHintElRef.current = null;
18954
+ setSiblingHintRect(null);
18955
+ setSiblingHintRects([]);
18013
18956
  return;
18014
18957
  }
18015
18958
  {
@@ -18128,7 +19071,6 @@ function OhhwellsBridge() {
18128
19071
  hoveredNavContainerRef.current = null;
18129
19072
  setHoveredNavContainerRect(null);
18130
19073
  hoveredItemElRef.current = editable;
18131
- setHoveredItemRect(isSelectedForHoverRef.current(editable) ? null : editable.getBoundingClientRect());
18132
19074
  }
18133
19075
  }
18134
19076
  }
@@ -18425,7 +19367,7 @@ function OhhwellsBridge() {
18425
19367
  }
18426
19368
  };
18427
19369
  const probeImageAt = (clientX, clientY, isDragOver = false, fromParentViewport = false) => {
18428
- if (linkPopoverOpenRef.current) {
19370
+ if (linkPopoverOpenRef.current || floatingPanelOpenRef.current && isPointOverFloatingPanel(clientX, clientY)) {
18429
19371
  if (hoveredImageRef.current) {
18430
19372
  hoveredImageRef.current = null;
18431
19373
  hoveredImageHasTextOverlapRef.current = false;
@@ -18759,7 +19701,9 @@ function OhhwellsBridge() {
18759
19701
  return;
18760
19702
  }
18761
19703
  const { y } = toProbeCoords(clientX, clientY, fromParentViewport);
18762
- const sections = Array.from(document.querySelectorAll("[data-ohw-section]")).sort((a, b) => a.getBoundingClientRect().top - b.getBoundingClientRect().top);
19704
+ const sections = Array.from(document.querySelectorAll("[data-ohw-section]")).filter(
19705
+ (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
19706
+ ).sort((a, b) => a.getBoundingClientRect().top - b.getBoundingClientRect().top);
18763
19707
  const ZONE = 20;
18764
19708
  for (let i = 0; i < sections.length; i++) {
18765
19709
  const a = sections[i];
@@ -18788,8 +19732,7 @@ function OhhwellsBridge() {
18788
19732
  };
18789
19733
  const handleMouseMove = (e) => {
18790
19734
  const { clientX, clientY } = e;
18791
- if (pointOwnedByFloatingPanel(clientX, clientY)) return;
18792
- if (isOverEditorChrome(clientX, clientY)) {
19735
+ if (document.documentElement.hasAttribute("data-ohw-panel-dragging") || floatingPanelOpenRef.current && isPointOverFloatingPanel(clientX, clientY) || isOverEditorChrome(clientX, clientY)) {
18793
19736
  document.querySelectorAll("[data-ohw-hovered]").forEach((el) => el.removeAttribute("data-ohw-hovered"));
18794
19737
  formHoverElRef.current = null;
18795
19738
  setFormHoverRect(null);
@@ -18797,6 +19740,12 @@ function OhhwellsBridge() {
18797
19740
  setHoveredItemRect(null);
18798
19741
  hoveredNavContainerRef.current = null;
18799
19742
  setHoveredNavContainerRect(null);
19743
+ siblingHintElRef.current = null;
19744
+ setSiblingHintRect(null);
19745
+ setSiblingHintRects([]);
19746
+ dismissImageHover();
19747
+ clearImageHover();
19748
+ setSectionGap(null);
18800
19749
  return;
18801
19750
  }
18802
19751
  if (probeSocialsRowAt(clientX, clientY)) return;
@@ -18808,7 +19757,11 @@ function OhhwellsBridge() {
18808
19757
  if (e.data?.type !== "ow:pointer-sync") return;
18809
19758
  const { clientX, clientY } = e.data;
18810
19759
  if (typeof clientX !== "number" || typeof clientY !== "number") return;
18811
- if (pointOwnedByFloatingPanel(clientX, clientY)) return;
19760
+ if (document.documentElement.hasAttribute("data-ohw-panel-dragging") || floatingPanelOpenRef.current && isPointOverFloatingPanel(clientX, clientY)) {
19761
+ dismissImageHover();
19762
+ clearImageHover();
19763
+ return;
19764
+ }
18812
19765
  if (probeSocialsRowAt(clientX, clientY)) return;
18813
19766
  probeSectionGapAt(clientX, clientY);
18814
19767
  probeImageAt(clientX, clientY);
@@ -19091,10 +20044,23 @@ function OhhwellsBridge() {
19091
20044
  if (e.data?.type !== "ow:hydrate") return;
19092
20045
  const content = e.data.content;
19093
20046
  if (!content) return;
20047
+ if (typeof content[BRAND_KIT_KEY] === "string") {
20048
+ brandKitRef.current = content[BRAND_KIT_KEY];
20049
+ applyBrandToDom(parseBrandKit(content[BRAND_KIT_KEY]));
20050
+ } else {
20051
+ brandKitRef.current = "";
20052
+ applyBrandToDom(null);
20053
+ }
19094
20054
  if (typeof content[AI_SECTIONS_KEY] === "string") {
19095
20055
  aiSectionsRef.current = content[AI_SECTIONS_KEY];
20056
+ setAiSectionOrder(content[SECTION_ORDER_KEY], window.location.pathname);
19096
20057
  applyAiSectionsToDom(parseAiSectionsState(content[AI_SECTIONS_KEY]));
19097
20058
  }
20059
+ if (typeof content[STYLE_STORE_KEY] === "string") {
20060
+ stylesRef.current = content[STYLE_STORE_KEY];
20061
+ applyStylesToDom(parseStyleStore(content[STYLE_STORE_KEY]));
20062
+ }
20063
+ applyBrandChrome(content);
19098
20064
  let sectionsJson = null;
19099
20065
  for (const [key, val] of Object.entries(content)) {
19100
20066
  if (key === "__ohw_sections") {
@@ -19104,6 +20070,9 @@ function OhhwellsBridge() {
19104
20070
  if (key === AI_SECTIONS_KEY) continue;
19105
20071
  if (key === LOGO_PLACEHOLDER_KEY) continue;
19106
20072
  if (LOGO_IMAGE_KEYS.includes(key) && !String(val ?? "").trim()) continue;
20073
+ if (key === BRAND_KIT_KEY) continue;
20074
+ if (key === STYLE_STORE_KEY) continue;
20075
+ if (BRAND_CHROME_KEYS.has(key)) continue;
19107
20076
  if (applyVideoSettingNode(key, val)) continue;
19108
20077
  if (applyCarouselNode(key, val)) continue;
19109
20078
  document.querySelectorAll(`[data-ohw-key="${key}"]`).forEach((el) => {
@@ -19117,6 +20086,8 @@ function OhhwellsBridge() {
19117
20086
  if (video && video.src !== val) applyVideoSrc(video, val);
19118
20087
  } else if (el.dataset.ohwEditable === "link") {
19119
20088
  applyLinkHref(el, val);
20089
+ } else if (el.dataset.ohwEditable === "icon") {
20090
+ applyIconMarkup(el, val);
19120
20091
  } else if (isIconMarkupValue(val)) {
19121
20092
  } else {
19122
20093
  el.innerHTML = val;
@@ -19201,12 +20172,21 @@ function OhhwellsBridge() {
19201
20172
  nodes: collectEditableNodes(editContentRef.current)
19202
20173
  });
19203
20174
  };
20175
+ const clearInteractionChrome = () => {
20176
+ deactivateRef.current();
20177
+ deselectRef.current();
20178
+ clearMediaSelectionRef.current();
20179
+ };
19204
20180
  const handleAiApplyTree = (e) => {
19205
20181
  if (e.data?.type !== "ow:ai-apply-tree") return;
19206
20182
  const payload = e.data.payload;
19207
20183
  if (!payload || typeof payload.id !== "string" || !isRenderableTree(payload.tree)) return;
20184
+ clearInteractionChrome();
19208
20185
  const previous = aiSectionsRef.current;
19209
- const nextState = applyTreeToState(parseAiSectionsState(previous), payload);
20186
+ const nextState = applyTreeToState(parseAiSectionsState(previous), {
20187
+ ...payload,
20188
+ path: payload.path ?? window.location.pathname
20189
+ });
19210
20190
  const nextValue = serializeAiSectionsState(nextState);
19211
20191
  aiSectionsRef.current = nextValue;
19212
20192
  applyAiSectionsToDom(nextState);
@@ -19227,6 +20207,7 @@ function OhhwellsBridge() {
19227
20207
  const exists = document.querySelector(`[data-ohw-section="${CSS.escape(sectionId)}"]`);
19228
20208
  if (!exists) return;
19229
20209
  if (isPageFrameSection(exists)) return;
20210
+ clearInteractionChrome();
19230
20211
  const previous = aiSectionsRef.current;
19231
20212
  const nextState = deleteSectionFromState(parseAiSectionsState(previous), sectionId);
19232
20213
  const nextValue = serializeAiSectionsState(nextState);
@@ -19242,8 +20223,10 @@ function OhhwellsBridge() {
19242
20223
  const handleAiSetSections = (e) => {
19243
20224
  if (e.data?.type !== "ow:ai-set-sections") return;
19244
20225
  const value = typeof e.data.value === "string" ? e.data.value : "";
20226
+ clearInteractionChrome();
19245
20227
  aiSectionsRef.current = value;
19246
20228
  applyAiSectionsToDom(parseAiSectionsState(value));
20229
+ applyStylesToDom(parseStyleStore(stylesRef.current));
19247
20230
  const restoredHeight = document.body.scrollHeight;
19248
20231
  if (restoredHeight > 50) postToParentRef.current({ type: "ow:height", height: restoredHeight });
19249
20232
  postToParentRef.current({ type: "ow:change", nodes: [{ key: AI_SECTIONS_KEY, text: value }] });
@@ -19259,10 +20242,40 @@ function OhhwellsBridge() {
19259
20242
  if (!entries) return;
19260
20243
  const orderJson = JSON.stringify(entries);
19261
20244
  editContentRef.current = { ...editContentRef.current, [SECTION_ORDER_KEY]: orderJson };
20245
+ setAiSectionOrder(orderJson, window.location.pathname);
19262
20246
  postToParentRef.current({ type: "ow:change", nodes: [{ key: SECTION_ORDER_KEY, text: orderJson }] });
19263
20247
  window.dispatchEvent(new Event("resize"));
19264
20248
  };
19265
20249
  window.addEventListener("message", handleMoveSection);
20250
+ const handleAiSetBrand = (e) => {
20251
+ if (e.data?.type !== "ow:ai-set-brand") return;
20252
+ const value = typeof e.data.value === "string" ? e.data.value : "";
20253
+ const previous = brandKitRef.current;
20254
+ brandKitRef.current = value;
20255
+ applyBrandToDom(parseBrandKit(value));
20256
+ if (aiSectionsRef.current) applyAiSectionsToDom(parseAiSectionsState(aiSectionsRef.current));
20257
+ applyStylesToDom(parseStyleStore(stylesRef.current));
20258
+ postToParentRef.current({ type: "ow:change", nodes: [{ key: BRAND_KIT_KEY, text: value }] });
20259
+ postToParentRef.current({ type: "ow:ai-brand-applied", previous, value });
20260
+ };
20261
+ window.addEventListener("message", handleAiSetBrand);
20262
+ const handleAiSetStyles = (e) => {
20263
+ if (e.data?.type !== "ow:ai-set-styles") return;
20264
+ const value = typeof e.data.value === "string" ? e.data.value : "";
20265
+ const previous = stylesRef.current;
20266
+ stylesRef.current = value;
20267
+ applyStylesToDom(parseStyleStore(value));
20268
+ postToParentRef.current({ type: "ow:change", nodes: [{ key: STYLE_STORE_KEY, text: value }] });
20269
+ postToParentRef.current({ type: "ow:ai-styles-applied", previous, value });
20270
+ };
20271
+ window.addEventListener("message", handleAiSetStyles);
20272
+ const handleGetBrand = (e) => {
20273
+ if (e.data?.type !== "ow:get-brand") return;
20274
+ const template = deriveTemplateBrand();
20275
+ const value = brandKitRef.current || (template ? JSON.stringify(template) : "");
20276
+ postToParentRef.current({ type: "ow:brand-value", value });
20277
+ };
20278
+ window.addEventListener("message", handleGetBrand);
19266
20279
  const handlePanelDragging = (e) => {
19267
20280
  if (e.data?.type !== "ow:panel-dragging") return;
19268
20281
  if (e.data.dragging) document.documentElement.setAttribute("data-ohw-panel-dragging", "");
@@ -19320,8 +20333,15 @@ function OhhwellsBridge() {
19320
20333
  closeLinkPopoverRef.current();
19321
20334
  return;
19322
20335
  }
20336
+ if (floatingPanelOpenRef.current) {
20337
+ setFloatingPanelRef.current(null);
20338
+ deselectRef.current();
20339
+ deactivateRef.current();
20340
+ return;
20341
+ }
19323
20342
  deselectRef.current();
19324
20343
  deactivateRef.current();
20344
+ clearMediaSelectionRef.current();
19325
20345
  };
19326
20346
  window.addEventListener("message", handleDeactivate);
19327
20347
  const handleToastAction = (e) => {
@@ -19407,6 +20427,10 @@ function OhhwellsBridge() {
19407
20427
  const handleKeyDown = (e) => {
19408
20428
  if (e.key === "Escape" && document.querySelector("[data-ohw-section-picker]")) return;
19409
20429
  if (e.key === "Escape" && document.querySelector("[data-ohw-more-menu]")) return;
20430
+ if (e.key === "Escape" && selectedMediaElRef.current) {
20431
+ clearMediaSelectionRef.current();
20432
+ return;
20433
+ }
19410
20434
  if ((e.ctrlKey || e.metaKey) && e.key.toLowerCase() === "a" && activeElRef.current) {
19411
20435
  e.preventDefault();
19412
20436
  selectAllTextInEditable(activeElRef.current);
@@ -19566,6 +20590,12 @@ function OhhwellsBridge() {
19566
20590
  if (aiSectionsRef.current) {
19567
20591
  nodes.push({ key: AI_SECTIONS_KEY, type: "sections", text: aiSectionsRef.current });
19568
20592
  }
20593
+ if (stylesRef.current) {
20594
+ nodes.push({ key: STYLE_STORE_KEY, type: "styles", text: stylesRef.current });
20595
+ }
20596
+ if (brandKitRef.current) {
20597
+ nodes.push({ key: BRAND_KIT_KEY, type: "brand", text: brandKitRef.current });
20598
+ }
19569
20599
  document.querySelectorAll('[data-ohw-editable="form"]').forEach((form) => {
19570
20600
  const formKey = formKeyOf(form);
19571
20601
  if (!formKey) return;
@@ -19583,8 +20613,12 @@ function OhhwellsBridge() {
19583
20613
  if (inserted) {
19584
20614
  const tracker = getSectionsTracker();
19585
20615
  postToParentRef.current({ type: "ow:change", nodes: [{ key: "__ohw_sections", text: tracker.textContent ?? "[]" }] });
19586
- const h = document.body.scrollHeight;
19587
- if (h > 50) postToParentRef.current({ type: "ow:height", height: h });
20616
+ const reportHeight = () => {
20617
+ const h = document.body.scrollHeight;
20618
+ if (h > 50) postToParentRef.current({ type: "ow:height", height: h });
20619
+ };
20620
+ reportHeight();
20621
+ setTimeout(reportHeight, 500);
19588
20622
  }
19589
20623
  };
19590
20624
  const handleSwitchSchedule = (e) => {
@@ -19981,13 +21015,16 @@ function OhhwellsBridge() {
19981
21015
  window.removeEventListener("message", handleAiDeleteSection);
19982
21016
  window.removeEventListener("message", handleAiSetSections);
19983
21017
  window.removeEventListener("message", handleMoveSection);
21018
+ window.removeEventListener("message", handleAiSetBrand);
21019
+ window.removeEventListener("message", handleAiSetStyles);
21020
+ window.removeEventListener("message", handleGetBrand);
19984
21021
  window.removeEventListener("message", handlePanelDragging);
19985
21022
  window.removeEventListener("message", handleDeleteSection);
19986
21023
  window.removeEventListener("message", handleDeactivate);
19987
- document.documentElement.removeAttribute("data-ohw-panel-dragging");
19988
21024
  window.removeEventListener("message", handleToastAction);
19989
21025
  window.removeEventListener("message", handleFormCount);
19990
21026
  window.removeEventListener("message", handleUiEscape);
21027
+ document.documentElement.removeAttribute("data-ohw-panel-dragging");
19991
21028
  autoSaveTimers.current.forEach(clearTimeout);
19992
21029
  autoSaveTimers.current.clear();
19993
21030
  if (imageUnhoverTimerRef.current) clearTimeout(imageUnhoverTimerRef.current);
@@ -20190,7 +21227,7 @@ function OhhwellsBridge() {
20190
21227
  postToParent2({
20191
21228
  type: "ow:ready",
20192
21229
  version: "1",
20193
- bridgeVersion: "0.1.77",
21230
+ bridgeVersion: "0.1.79",
20194
21231
  path: pathname,
20195
21232
  nodes: collectEditableNodes(editContentRef.current),
20196
21233
  sections
@@ -20597,11 +21634,22 @@ function OhhwellsBridge() {
20597
21634
  const showEditLink = toolbarShowEditLink;
20598
21635
  const currentSections = sectionsByPath[pathname] ?? [];
20599
21636
  linkPopoverOpenRef.current = linkPopover !== null;
21637
+ const handleMediaSelect = (0, import_react17.useCallback)((key) => {
21638
+ const el = hoveredImageRef.current?.dataset.ohwKey === key ? hoveredImageRef.current : Array.from(document.querySelectorAll(MEDIA_SELECTOR)).find(
21639
+ (m) => (m.dataset.ohwKey ?? "") === key
21640
+ ) ?? null;
21641
+ if (!el) return;
21642
+ selectMediaElementRef.current(el);
21643
+ }, []);
20600
21644
  const handleMediaReplace = (0, import_react17.useCallback)(
20601
21645
  (key) => {
20602
- postToParent2({ type: "ow:image-pick", key, elementType: mediaHover?.elementType ?? "image" });
21646
+ postToParent2({
21647
+ type: "ow:image-pick",
21648
+ key,
21649
+ elementType: mediaHover?.elementType ?? selectedMedia?.elementType ?? "image"
21650
+ });
20603
21651
  },
20604
- [postToParent2, mediaHover?.elementType]
21652
+ [postToParent2, mediaHover?.elementType, selectedMedia?.elementType]
20605
21653
  );
20606
21654
  const handleEditCarousel = (0, import_react17.useCallback)(
20607
21655
  (key) => {
@@ -20642,6 +21690,7 @@ function OhhwellsBridge() {
20642
21690
  return /* @__PURE__ */ (0, import_jsx_runtime33.jsxs)(import_jsx_runtime33.Fragment, { children: [
20643
21691
  /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("div", { id: "ohw-loader", suppressHydrationWarning: true, style: { ...OHW_LOADER_STYLE, display: "none" }, children: /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(OhwLoaderSpinner, {}) }),
20644
21692
  /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("script", { suppressHydrationWarning: true, dangerouslySetInnerHTML: { __html: OHW_LOADER_PREHYDRATE_SCRIPT } }),
21693
+ subdomain && !isEditMode && showBranding && /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(MadeWithOhhWells, {}),
20645
21694
  bridgeRoot ? (0, import_react_dom4.createPortal)(
20646
21695
  /* @__PURE__ */ (0, import_jsx_runtime33.jsxs)(import_jsx_runtime33.Fragment, { children: [
20647
21696
  /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("div", { ref: attachVisibleViewport, "data-ohw-visible-viewport": "", "aria-hidden": true }),
@@ -20673,12 +21722,25 @@ function OhhwellsBridge() {
20673
21722
  },
20674
21723
  `uploading-${key}`
20675
21724
  )),
20676
- mediaHover && !(mediaHover.key in uploadingRects) && /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
21725
+ mediaHover && !(mediaHover.key in uploadingRects) && mediaHover.key !== selectedMedia?.key && /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
20677
21726
  MediaOverlay,
20678
21727
  {
20679
21728
  hover: mediaHover,
20680
21729
  isUploading: false,
20681
21730
  onReplace: handleMediaReplace,
21731
+ onSelect: handleMediaSelect,
21732
+ onVideoSettingsChange: handleVideoSettingsChange
21733
+ }
21734
+ ),
21735
+ selectedMedia && !(selectedMedia.key in uploadingRects) && !linkPopover && /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
21736
+ MediaOverlay,
21737
+ {
21738
+ hover: selectedMedia,
21739
+ selected: true,
21740
+ hovered: mediaHover?.key === selectedMedia.key,
21741
+ isUploading: false,
21742
+ onReplace: handleMediaReplace,
21743
+ onSelect: handleMediaSelect,
20682
21744
  onVideoSettingsChange: handleVideoSettingsChange
20683
21745
  }
20684
21746
  ),
@@ -21084,6 +22146,59 @@ function OhhwellsBridge() {
21084
22146
  ) : null
21085
22147
  ] });
21086
22148
  }
22149
+
22150
+ // src/ui/EmptySection.tsx
22151
+ var import_link = __toESM(require("next/link"), 1);
22152
+ var import_jsx_runtime34 = require("react/jsx-runtime");
22153
+ function EmptySection({ title, homeHref = "/", eyebrowKey, titleKey, subtitleKey }) {
22154
+ return /* @__PURE__ */ (0, import_jsx_runtime34.jsxs)(import_jsx_runtime34.Fragment, { children: [
22155
+ /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(
22156
+ "p",
22157
+ {
22158
+ style: {
22159
+ fontFamily: "var(--brand-font-body)",
22160
+ fontSize: "0.75rem",
22161
+ fontWeight: 500,
22162
+ letterSpacing: "0.15em",
22163
+ textTransform: "uppercase",
22164
+ color: "var(--brand-accent)",
22165
+ marginBottom: "1.5rem"
22166
+ },
22167
+ 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" }) })
22168
+ }
22169
+ ),
22170
+ /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(
22171
+ "h1",
22172
+ {
22173
+ style: {
22174
+ fontFamily: "var(--brand-font-heading)",
22175
+ fontSize: "clamp(2rem, 3.5vw, 2.75rem)",
22176
+ lineHeight: 1.1,
22177
+ letterSpacing: "-0.025em",
22178
+ color: "var(--brand-text)",
22179
+ marginBottom: "1rem"
22180
+ },
22181
+ ...titleKey ? { "data-ohw-editable": "text", "data-ohw-key": titleKey, "data-ohw-max-length": 80 } : {},
22182
+ children: title
22183
+ }
22184
+ ),
22185
+ /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(
22186
+ "p",
22187
+ {
22188
+ style: {
22189
+ fontFamily: "var(--brand-font-body)",
22190
+ fontSize: "1rem",
22191
+ lineHeight: 1.7,
22192
+ fontWeight: 300,
22193
+ color: "var(--brand-text-muted)",
22194
+ maxWidth: "340px"
22195
+ },
22196
+ ...subtitleKey ? { "data-ohw-editable": "text", "data-ohw-key": subtitleKey, "data-ohw-max-length": 160 } : {},
22197
+ children: "This page doesn't have any content yet."
22198
+ }
22199
+ )
22200
+ ] });
22201
+ }
21087
22202
  // Annotate the CommonJS export names for ESM import in node:
21088
22203
  0 && (module.exports = {
21089
22204
  AI_DEFAULT_BRAND,
@@ -21101,6 +22216,7 @@ function OhhwellsBridge() {
21101
22216
  DropdownMenuItem,
21102
22217
  DropdownMenuSeparator,
21103
22218
  DropdownMenuTrigger,
22219
+ EmptySection,
21104
22220
  ItemActionToolbar,
21105
22221
  ItemInteractionLayer,
21106
22222
  LinkEditorPanel,