@ohhwells/bridge 0.1.121 → 0.1.122-next.335

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -31,6 +31,10 @@ function enforceLinkHrefs() {
31
31
 
32
32
  // src/lib/ai-tree.ts
33
33
  var AI_TREE_SCHEMA_VERSIONS = [1];
34
+ var BUTTON_FILL_TOKENS = ["dark", "primary", "accent", "light"];
35
+ var BUTTON_STYLES = ["solid", "outline", "ghost"];
36
+ var BUTTON_RADII = ["square", "rounded", "pill"];
37
+ var BUTTON_SIZES = ["s", "m", "l"];
34
38
  var AI_DEFAULT_BRAND = {
35
39
  palette: { dark: "#0C0A09", primary: "#0078E5", accent: "#747271", light: "#ECECEB" },
36
40
  fonts: {
@@ -290,6 +294,118 @@ var FONT_VARS = {
290
294
  var BRAND_FONT_LINK_ID = "ohw-brand-fonts";
291
295
  var CUSTOM_FONT_STYLE_ID = "ohw-brand-custom-fonts";
292
296
  var CUSTOM_FONT_FORMATS = /* @__PURE__ */ new Set(["woff2", "woff", "truetype", "opentype"]);
297
+ var BUTTON_RADIUS_PX = {
298
+ square: 0,
299
+ rounded: AI_TREE_TOKENS.radiusButton,
300
+ pill: 999
301
+ };
302
+ var BUTTON_SIZE_SPEC = {
303
+ s: {
304
+ paddingX: `${AI_TREE_TOKENS.spacing4}px`,
305
+ paddingY: `${AI_TREE_TOKENS.spacing2}px`,
306
+ fontSize: "14px"
307
+ },
308
+ m: {
309
+ paddingX: `${AI_TREE_TOKENS.spacing6}px`,
310
+ paddingY: `${AI_TREE_TOKENS.spacing3}px`,
311
+ fontSize: `${AI_TREE_TOKENS.type.button.size}px`
312
+ },
313
+ l: {
314
+ paddingX: `${AI_TREE_TOKENS.spacing8}px`,
315
+ paddingY: `${AI_TREE_TOKENS.spacing4}px`,
316
+ fontSize: "18px"
317
+ }
318
+ };
319
+ var BUTTON_SIZE_SCALE = { s: 0.85, m: 1, l: 1.25 };
320
+ var roundHalf = (n) => Math.round(n * 2) / 2;
321
+ function measureTemplateButton(variant) {
322
+ const root = document.documentElement;
323
+ const el = document.querySelector(`.btn--${variant}, .btn-${variant}`);
324
+ if (!el) return null;
325
+ const sheet = document.getElementById(BUTTON_RULES_STYLE_ID);
326
+ const rules = sheet?.textContent ?? "";
327
+ const saved = BUTTON_VAR_NAMES.map((name) => [name, root.style.getPropertyValue(name)]);
328
+ if (sheet) sheet.textContent = "";
329
+ for (const [name] of saved) root.style.removeProperty(name);
330
+ const cs = getComputedStyle(el);
331
+ const base = {
332
+ fontSize: parseFloat(cs.fontSize),
333
+ top: parseFloat(cs.paddingTop),
334
+ right: parseFloat(cs.paddingRight),
335
+ bottom: parseFloat(cs.paddingBottom),
336
+ left: parseFloat(cs.paddingLeft)
337
+ };
338
+ if (sheet) sheet.textContent = rules;
339
+ for (const [name, value] of saved) if (value) root.style.setProperty(name, value);
340
+ return Object.values(base).every(Number.isFinite) && base.fontSize > 0 ? base : null;
341
+ }
342
+ function scaledButtonSize(size, base) {
343
+ if (!base) return BUTTON_SIZE_SPEC[size];
344
+ const k = BUTTON_SIZE_SCALE[size];
345
+ const px2 = (n) => `${roundHalf(n * k)}px`;
346
+ return {
347
+ // Each side scaled on its own, so a button with uneven padding (say, room for an icon on one
348
+ // side) keeps its shape; the x/y vars are the averages for templates that read those instead.
349
+ padding: `${px2(base.top)} ${px2(base.right)} ${px2(base.bottom)} ${px2(base.left)}`,
350
+ paddingX: px2((base.left + base.right) / 2),
351
+ paddingY: px2((base.top + base.bottom) / 2),
352
+ fontSize: `${roundHalf(base.fontSize * k)}px`
353
+ };
354
+ }
355
+ var BUTTON_VAR_ROLES = ["bg", "text", "border", "radius", "padding-x", "padding-y", "padding", "font-size"];
356
+ var BUTTON_VAR_NAMES = ["primary", "secondary"].flatMap(
357
+ (variant) => BUTTON_VAR_ROLES.map((role) => `--brand-button-${variant}-${role}`)
358
+ );
359
+ var ROLES_BY_FIELD = {
360
+ fill: ["bg"],
361
+ style: ["bg", "border"],
362
+ textColor: ["text"],
363
+ radius: ["radius"],
364
+ size: ["padding-x", "padding-y", "padding", "font-size"]
365
+ };
366
+ function touchedButtonRoles(variant, kit) {
367
+ if (!kit.touched) return new Set(BUTTON_VAR_ROLES);
368
+ const fields = kit.touched.buttons?.[variant] ?? [];
369
+ const outline = fields.includes("style") && kit.buttons?.[variant]?.style === "outline";
370
+ return new Set(
371
+ fields.flatMap((f) => {
372
+ if (outline && (f === "fill" || f === "style")) return ["border"];
373
+ return ROLES_BY_FIELD[f] ?? [];
374
+ })
375
+ );
376
+ }
377
+ function buttonPalette(kit) {
378
+ return kit.palette ?? {
379
+ dark: "var(--brand-text)",
380
+ primary: "var(--brand-primary)",
381
+ accent: "var(--brand-accent)",
382
+ light: "var(--brand-background)"
383
+ };
384
+ }
385
+ function buttonVariantVars(variant, style, roles, palette) {
386
+ const fill = palette[style.fill];
387
+ const text = palette[style.textColor];
388
+ const size = scaledButtonSize(style.size, roles.has("font-size") ? measureTemplateButton(variant) : null);
389
+ const { bg, border } = !roles.has("border") ? { bg: fill, border: "transparent" } : style.style === "outline" ? { bg: "transparent", border: fill } : style.style === "ghost" ? { bg: "transparent", border: "transparent" } : { bg: fill, border: "transparent" };
390
+ const all = {
391
+ [`--brand-button-${variant}-bg`]: bg,
392
+ [`--brand-button-${variant}-text`]: text,
393
+ [`--brand-button-${variant}-border`]: border,
394
+ [`--brand-button-${variant}-radius`]: `${BUTTON_RADIUS_PX[style.radius]}px`,
395
+ [`--brand-button-${variant}-padding-x`]: size.paddingX,
396
+ [`--brand-button-${variant}-padding-y`]: size.paddingY,
397
+ ...size.padding ? { [`--brand-button-${variant}-padding`]: size.padding } : {},
398
+ [`--brand-button-${variant}-font-size`]: size.fontSize
399
+ };
400
+ const prefix = `--brand-button-${variant}-`;
401
+ return Object.fromEntries(Object.entries(all).filter(([name]) => roles.has(name.slice(prefix.length))));
402
+ }
403
+ function buttonStyleVars(kit) {
404
+ return {
405
+ ...buttonVariantVars("primary", kit.buttons.primary, touchedButtonRoles("primary", kit), buttonPalette(kit)),
406
+ ...buttonVariantVars("secondary", kit.buttons.secondary, touchedButtonRoles("secondary", kit), buttonPalette(kit))
407
+ };
408
+ }
293
409
  function brandColorVars(palette) {
294
410
  const { dark, primary, accent, light } = palette;
295
411
  const mix = (a, aPct, b) => `color-mix(in srgb, ${a} ${aPct}%, ${b})`;
@@ -317,6 +433,11 @@ function brandColorVars(palette) {
317
433
  "--brand-on-primary": light
318
434
  };
319
435
  }
436
+ function isButtonStyle(value) {
437
+ if (!value || typeof value !== "object") return false;
438
+ const v = value;
439
+ return BUTTON_FILL_TOKENS.includes(v.fill) && BUTTON_FILL_TOKENS.includes(v.textColor) && BUTTON_STYLES.includes(v.style) && BUTTON_RADII.includes(v.radius) && BUTTON_SIZES.includes(v.size);
440
+ }
320
441
  function parseCustomFontWeight(raw) {
321
442
  if (typeof raw !== "object" || raw === null) return null;
322
443
  const w = raw;
@@ -336,6 +457,15 @@ function parseCustomFont(raw) {
336
457
  if (weights.length === 0) return null;
337
458
  return { family: f.family, label: f.label, weights };
338
459
  }
460
+ var BUTTON_FIELDS = Object.keys(ROLES_BY_FIELD);
461
+ function parseTouchedButtons(raw) {
462
+ const out = {};
463
+ for (const variant of ["primary", "secondary"]) {
464
+ const list = raw?.[variant];
465
+ if (Array.isArray(list)) out[variant] = BUTTON_FIELDS.filter((f) => list.includes(f));
466
+ }
467
+ return out;
468
+ }
339
469
  function parseBrandKit(raw) {
340
470
  if (!raw) return null;
341
471
  try {
@@ -345,11 +475,21 @@ function parseBrandKit(raw) {
345
475
  const palette = p && typeof p.dark === "string" && typeof p.primary === "string" && typeof p.accent === "string" && typeof p.light === "string" ? { dark: p.dark, primary: p.primary, accent: p.accent, light: p.light } : void 0;
346
476
  const fonts = f && typeof f.heading === "string" && typeof f.body === "string" ? { heading: f.heading, body: f.body } : void 0;
347
477
  const customFonts = Array.isArray(parsed?.customFonts) ? parsed.customFonts.map(parseCustomFont).filter((c) => c !== null) : void 0;
348
- if (!palette && !fonts && !customFonts) return null;
478
+ const b = parsed?.buttons;
479
+ const buttons = b && isButtonStyle(b.primary) && isButtonStyle(b.secondary) ? { primary: b.primary, secondary: b.secondary } : void 0;
480
+ if (!palette && !fonts && !customFonts && !buttons) return null;
349
481
  return {
350
482
  ...palette ? { palette } : {},
351
483
  ...fonts ? { fonts } : {},
352
- ...customFonts ? { customFonts } : {}
484
+ ...customFonts ? { customFonts } : {},
485
+ ...buttons ? { buttons } : {},
486
+ ...parsed.touched && typeof parsed.touched === "object" ? {
487
+ touched: {
488
+ palette: parsed.touched.palette === true,
489
+ fonts: parsed.touched.fonts === true,
490
+ buttons: parseTouchedButtons(parsed.touched.buttons)
491
+ }
492
+ } : {}
353
493
  };
354
494
  } catch {
355
495
  return null;
@@ -378,6 +518,39 @@ function loadBrandFonts(families) {
378
518
  }
379
519
  if (link.href !== href) link.href = href;
380
520
  }
521
+ var BUTTON_RULES_STYLE_ID = "ohw-brand-button-rules";
522
+ function buttonRulesCss(kit) {
523
+ const rule = (variant) => {
524
+ const roles = touchedButtonRoles(variant, kit);
525
+ const v = (role) => `var(--brand-button-${variant}-${role})`;
526
+ const decls = [];
527
+ if (roles.has("bg")) decls.push(`background:${v("bg")} !important`);
528
+ if (roles.has("text")) decls.push(`color:${v("text")} !important`);
529
+ if (roles.has("border")) decls.push(`border:1.5px solid ${v("border")} !important`);
530
+ if (roles.has("radius")) decls.push(`border-radius:${v("radius")} !important`);
531
+ if (roles.has("padding-x")) decls.push(`padding:var(--brand-button-${variant}-padding, ${v("padding-y")} ${v("padding-x")}) !important`);
532
+ if (roles.has("font-size")) decls.push(`font-size:${v("font-size")} !important`);
533
+ if (decls.length === 0) return "";
534
+ const selectors = [`html .btn--${variant}`, `html .btn-${variant}`];
535
+ const labelRule = roles.has("text") ? `${selectors.map((x) => `${x} *`).join(",")}{color:inherit !important}` : "";
536
+ return `${selectors.join(",")}{${decls.join(";")}}` + labelRule + (roles.has("bg") ? `${selectors.map((x) => `${x}:hover`).join(",")}{opacity:0.88}` : "");
537
+ };
538
+ return rule("primary") + rule("secondary");
539
+ }
540
+ function syncButtonRules(kit) {
541
+ let el = document.getElementById(BUTTON_RULES_STYLE_ID);
542
+ const css = kit ? buttonRulesCss(kit) : "";
543
+ if (!css) {
544
+ el?.remove();
545
+ return;
546
+ }
547
+ if (!el) {
548
+ el = document.createElement("style");
549
+ el.id = BUTTON_RULES_STYLE_ID;
550
+ document.head.appendChild(el);
551
+ }
552
+ if (el.textContent !== css) el.textContent = css;
553
+ }
381
554
  function escapeCssString(value) {
382
555
  return value.replace(/\\/g, "\\\\").replace(/'/g, "\\'");
383
556
  }
@@ -403,28 +576,47 @@ function loadCustomFonts(fonts) {
403
576
  function applyBrandToDom(kit) {
404
577
  const root = document.documentElement;
405
578
  if (!kit) {
406
- for (const name of [...BRAND_VAR_NAMES, ...LEGACY_BRAND_VAR_NAMES]) root.style.removeProperty(name);
579
+ for (const name of [...BRAND_VAR_NAMES, ...LEGACY_BRAND_VAR_NAMES, ...BUTTON_VAR_NAMES]) {
580
+ root.style.removeProperty(name);
581
+ }
407
582
  for (const name of [...FONT_VARS.heading, ...FONT_VARS.body]) root.style.removeProperty(name);
408
583
  document.getElementById(BRAND_FONT_LINK_ID)?.remove();
409
584
  document.getElementById(CUSTOM_FONT_STYLE_ID)?.remove();
585
+ syncButtonRules(null);
410
586
  return;
411
587
  }
588
+ const applyPalette = kit.touched ? kit.touched.palette === true : true;
589
+ const applyFonts = kit.touched ? kit.touched.fonts === true : true;
412
590
  if (kit.palette) {
413
- for (const [name, value] of Object.entries(brandColorVars(kit.palette))) root.style.setProperty(name, value);
591
+ if (applyPalette) {
592
+ for (const [name, value] of Object.entries(brandColorVars(kit.palette))) root.style.setProperty(name, value);
593
+ } else {
594
+ for (const name of [...BRAND_VAR_NAMES, ...LEGACY_BRAND_VAR_NAMES]) root.style.removeProperty(name);
595
+ }
414
596
  }
597
+ if (kit.buttons) {
598
+ for (const name of BUTTON_VAR_NAMES) root.style.removeProperty(name);
599
+ for (const [name, value] of Object.entries(buttonStyleVars({ ...kit, buttons: kit.buttons }))) root.style.setProperty(name, value);
600
+ }
601
+ syncButtonRules(kit.buttons ? kit : null);
415
602
  if (kit.customFonts) {
416
603
  loadCustomFonts(kit.customFonts);
417
604
  }
418
605
  if (kit.fonts) {
419
- const heading = quoteFamily(kit.fonts.heading);
420
- const body = quoteFamily(kit.fonts.body);
421
- for (const name of FONT_VARS.heading) root.style.setProperty(name, heading);
422
- for (const name of FONT_VARS.body) root.style.setProperty(name, body);
423
- const customFamilies = new Set((kit.customFonts ?? []).map((f) => f.family));
424
- const googleFamilies = [familyOf(kit.fonts.heading), familyOf(kit.fonts.body)].filter(
425
- (f) => !customFamilies.has(f)
426
- );
427
- loadBrandFonts(googleFamilies);
606
+ if (applyFonts) {
607
+ const heading = quoteFamily(kit.fonts.heading);
608
+ const body = quoteFamily(kit.fonts.body);
609
+ for (const name of FONT_VARS.heading) root.style.setProperty(name, heading);
610
+ for (const name of FONT_VARS.body) root.style.setProperty(name, body);
611
+ const customFamilies = new Set((kit.customFonts ?? []).map((f) => f.family));
612
+ const googleFamilies = [familyOf(kit.fonts.heading), familyOf(kit.fonts.body)].filter(
613
+ (f) => !customFamilies.has(f)
614
+ );
615
+ loadBrandFonts(googleFamilies);
616
+ } else {
617
+ for (const name of [...FONT_VARS.heading, ...FONT_VARS.body]) root.style.removeProperty(name);
618
+ document.getElementById(BRAND_FONT_LINK_ID)?.remove();
619
+ }
428
620
  }
429
621
  }
430
622
 
@@ -525,9 +717,9 @@ function styleSheetCss() {
525
717
  );
526
718
  }
527
719
  const pad = { tight: 40, balanced: 64, airy: 96 };
528
- for (const [spacing, px] of Object.entries(pad)) {
720
+ for (const [spacing, px2] of Object.entries(pad)) {
529
721
  rules.push(
530
- `[data-ohw-style-spacing="${spacing}"] { padding-top: ${px}px !important; padding-bottom: ${px}px !important; }`
722
+ `[data-ohw-style-spacing="${spacing}"] { padding-top: ${px2}px !important; padding-bottom: ${px2}px !important; }`
531
723
  );
532
724
  }
533
725
  rules.push(
@@ -574,7 +766,6 @@ var NODE_PROPS = [
574
766
  "color",
575
767
  "font-family",
576
768
  "font-size",
577
- "background",
578
769
  "text-align",
579
770
  "justify-content",
580
771
  "align-items"
@@ -624,9 +815,6 @@ function clearNodeProps(root) {
624
815
  h.removeAttribute(NODE_WROTE_ATTR);
625
816
  }
626
817
  }
627
- function buttonSurfaceOf(el) {
628
- return el.closest("a, button") ?? el;
629
- }
630
818
  function alignSubjectOf(el) {
631
819
  const button = el.closest('[data-ohw-role="button"]');
632
820
  if (button?.parentElement) return button.parentElement;
@@ -744,18 +932,6 @@ function applyStylesToDom(store) {
744
932
  );
745
933
  subject.setAttribute(NODE_WROTE_ATTR, "");
746
934
  }
747
- if (override.buttonBackground !== void 0 || override.buttonText !== void 0) {
748
- const surface = buttonSurfaceOf(el);
749
- if (override.buttonBackground !== void 0) {
750
- saveInline(surface, "background");
751
- surface.style.setProperty("background", override.buttonBackground, "important");
752
- }
753
- if (override.buttonText !== void 0) {
754
- saveInline(surface, "color");
755
- surface.style.setProperty("color", override.buttonText, "important");
756
- }
757
- surface.setAttribute(NODE_WROTE_ATTR, "");
758
- }
759
935
  }
760
936
  }
761
937
  for (const [sectionId, override] of Object.entries(store.sections)) {
@@ -1341,7 +1517,59 @@ var AI_MOBILE_CSS = [
1341
1517
  "[data-ai-grid]{grid-template-columns:repeat(2, 1fr) !important}",
1342
1518
  "}"
1343
1519
  ].join("");
1344
- function buttonShellStyle(ctx, fullWidth) {
1520
+ function buttonSizeSpec(size) {
1521
+ switch (size) {
1522
+ case "s":
1523
+ return { padding: `${AI_TREE_TOKENS.spacing2}px ${AI_TREE_TOKENS.spacing4}px`, fontSize: 14 };
1524
+ case "l":
1525
+ return { padding: `${AI_TREE_TOKENS.spacing4}px ${AI_TREE_TOKENS.spacing8}px`, fontSize: 18 };
1526
+ case "m":
1527
+ default:
1528
+ return { padding: `${AI_TREE_TOKENS.spacing3}px ${AI_TREE_TOKENS.spacing6}px`, fontSize: AI_TREE_TOKENS.type.button.size };
1529
+ }
1530
+ }
1531
+ function buttonRadiusPx(radius) {
1532
+ switch (radius) {
1533
+ case "square":
1534
+ return 0;
1535
+ case "pill":
1536
+ return 999;
1537
+ case "rounded":
1538
+ default:
1539
+ return AI_TREE_TOKENS.radiusButton;
1540
+ }
1541
+ }
1542
+ function resolveButtonToken(brand, token) {
1543
+ return brand.palette[token];
1544
+ }
1545
+ function buttonColorStyle(ctx, variant) {
1546
+ const token = ctx.brand.buttons?.[variant];
1547
+ if (!token) return null;
1548
+ const fill = resolveButtonToken(ctx.brand, token.fill);
1549
+ const text = resolveButtonToken(ctx.brand, token.textColor);
1550
+ switch (token.style) {
1551
+ case "outline":
1552
+ return { background: "transparent", border: `1px solid ${fill}`, color: text };
1553
+ case "ghost":
1554
+ return { background: "transparent", border: "1px solid transparent", color: text };
1555
+ case "solid":
1556
+ default:
1557
+ return { background: fill, color: text };
1558
+ }
1559
+ }
1560
+ function buttonShellStyle(ctx, variant, fullWidth) {
1561
+ const buttonToken = ctx.brand.buttons?.[variant];
1562
+ if (buttonToken) {
1563
+ const { padding, fontSize } = buttonSizeSpec(buttonToken.size);
1564
+ return {
1565
+ borderRadius: buttonRadiusPx(buttonToken.radius),
1566
+ padding,
1567
+ fontFamily: ctx.brand.fonts.body,
1568
+ fontSize,
1569
+ lineHeight: AI_TREE_TOKENS.type.button.line,
1570
+ fontWeight: AI_TREE_TOKENS.type.button.weight
1571
+ };
1572
+ }
1345
1573
  const bs = ctx.buttonStyle;
1346
1574
  if (bs) {
1347
1575
  return {
@@ -1451,6 +1679,22 @@ function pricingCardEmphasis(featured, ctx, hasBg) {
1451
1679
  function textAttrs(ctx, path) {
1452
1680
  return ctx.keyFor ? { "data-ohw-key": ctx.keyFor(path), "data-ohw-editable": "text" } : {};
1453
1681
  }
1682
+ var AI_RESPONSIVE_CSS = [
1683
+ "@media (max-width: 960px) {",
1684
+ " [data-ai-responsive] [data-ai-grid] { grid-template-columns: repeat(2, 1fr) !important; }",
1685
+ ' [data-ai-responsive] [data-ai-grid="1"] { grid-template-columns: 1fr !important; }',
1686
+ "}",
1687
+ "@media (max-width: 640px) {",
1688
+ " [data-ai-responsive] [data-ai-grid] { grid-template-columns: 1fr !important; }",
1689
+ " [data-ai-responsive] [data-ai-columns] > * { grid-column: 1 / -1 !important; }",
1690
+ // Group containers flatten to a column on phones; span placements come along for free.
1691
+ " [data-ai-responsive] [data-ai-group] { display: flex !important; flex-direction: column !important; }",
1692
+ " [data-ai-responsive] [data-ai-group] > * { grid-column: auto !important; }",
1693
+ " [data-ai-responsive] [data-ai-section-inner] { padding-left: 20px !important; padding-right: 20px !important; }",
1694
+ " [data-ai-responsive] { overflow-x: hidden; }",
1695
+ " [data-ai-responsive] img { max-width: 100%; }",
1696
+ "}"
1697
+ ].join("\n");
1454
1698
  var TRANSPARENT_PX = "data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7";
1455
1699
  function MediaBox({
1456
1700
  refValue,
@@ -1468,9 +1712,9 @@ function MediaBox({
1468
1712
  const url = refValue ? ctx.resolveMedia(refValue) : null;
1469
1713
  const isIcon = /^(lucide|simple):/.test(refValue);
1470
1714
  const aspectRatio = aspect && /^\d+:\d+$/.test(aspect) ? aspect.replace(":", " / ") : void 0;
1471
- const editAttrs = ctx.keyFor && editPath && !isIcon ? {
1715
+ const editAttrs = ctx.keyFor && editPath ? {
1472
1716
  "data-ohw-key": ctx.keyFor(editPath),
1473
- ...genericImageEdit ? { "data-ohw-editable": "image" } : {}
1717
+ ...isIcon ? { "data-ohw-editable": "icon" } : genericImageEdit ? { "data-ohw-editable": "image" } : {}
1474
1718
  } : {};
1475
1719
  if (isIcon) {
1476
1720
  const Icon = refValue.startsWith("lucide:") ? lucideByName(refValue.slice(7)) : null;
@@ -1478,6 +1722,7 @@ function MediaBox({
1478
1722
  "span",
1479
1723
  {
1480
1724
  "data-ai-icon": refValue,
1725
+ ...editAttrs,
1481
1726
  style: {
1482
1727
  display: "inline-flex",
1483
1728
  width: 48,
@@ -1537,12 +1782,16 @@ function ButtonEl({
1537
1782
  secondaryColor
1538
1783
  }) {
1539
1784
  const secondary = slots.variant === "secondary";
1785
+ const variant = secondary ? "secondary" : "primary";
1786
+ const tokenColorStyle = buttonColorStyle(ctx, variant);
1540
1787
  const href = str(slots.href);
1541
1788
  const linkAttrs = ctx.keyFor && path ? { "data-ohw-href-key": ctx.keyFor(`${path}.href`), "data-ohw-role": "button" } : {};
1542
1789
  return /* @__PURE__ */ jsx(
1543
1790
  "a",
1544
1791
  {
1545
1792
  href: href || void 0,
1793
+ className: `btn btn--${variant}`,
1794
+ "data-ohw-btn-variant": variant,
1546
1795
  ...linkAttrs,
1547
1796
  style: {
1548
1797
  display: fullWidth ? "flex" : "inline-flex",
@@ -1552,13 +1801,13 @@ function ButtonEl({
1552
1801
  textDecoration: "none",
1553
1802
  cursor: "pointer",
1554
1803
  whiteSpace: "nowrap",
1555
- ...buttonShellStyle(ctx, fullWidth),
1556
- ...secondary ? { border: `1px solid ${secondaryColor ?? ctx.brand.palette.dark}`, color: secondaryColor ?? ctx.brand.palette.dark } : (
1804
+ ...buttonShellStyle(ctx, variant, fullWidth),
1805
+ ...tokenColorStyle ?? (secondary ? { border: `1px solid ${secondaryColor ?? ctx.brand.palette.dark}`, color: secondaryColor ?? ctx.brand.palette.dark } : (
1557
1806
  // Off-band, prefer the template button's measured fill/label pair — a template may
1558
1807
  // fill its CTAs with any token (hvac: accent bg, primary text). On an accent band
1559
1808
  // the flipped palette keeps contrast, so the brand-driven colours stay.
1560
1809
  !ctx.buttonLabel && ctx.buttonStyle?.background ? { background: ctx.buttonStyle.background, color: ctx.buttonStyle.color } : { background: ctx.brand.palette.primary, color: ctx.buttonLabel ?? primaryButtonLabel(ctx.brand) }
1561
- )
1810
+ ))
1562
1811
  },
1563
1812
  children: /* @__PURE__ */ jsx("span", { ...path ? textAttrs(ctx, `${path}.label`) : {}, children: str(slots.label) })
1564
1813
  }
@@ -2532,7 +2781,7 @@ function CollectionBlock({ node, ctx, path }) {
2532
2781
  return /* @__PURE__ */ jsx(
2533
2782
  "div",
2534
2783
  {
2535
- "data-ai-grid": "",
2784
+ "data-ai-grid": String(itemsPerRow),
2536
2785
  "data-ohw-pricing-collection": isPricing ? "" : void 0,
2537
2786
  style: {
2538
2787
  display: "grid",
@@ -2780,18 +3029,21 @@ function renderNode(node, ctx, path) {
2780
3029
  "button",
2781
3030
  {
2782
3031
  type: "submit",
3032
+ className: "btn btn--primary",
3033
+ "data-ohw-btn-variant": "primary",
3034
+ "data-ohw-role": "button",
2783
3035
  style: {
2784
3036
  alignSelf: submitAlign,
2785
3037
  border: "none",
2786
3038
  cursor: "pointer",
2787
3039
  // Shape/padding/typography follow the host template's own buttons.
2788
- ...buttonShellStyle(ctx),
3040
+ ...buttonShellStyle(ctx, "primary"),
2789
3041
  // Brand-styled: the template button's measured fill/label pair off-band, else
2790
3042
  // primary fill with a brand-derived label so it reads on custom palettes.
2791
- ...!ctx.buttonLabel && ctx.buttonStyle?.background ? { background: ctx.buttonStyle.background, color: ctx.buttonStyle.color } : {
3043
+ ...buttonColorStyle(ctx, "primary") ?? (!ctx.buttonLabel && ctx.buttonStyle?.background ? { background: ctx.buttonStyle.background, color: ctx.buttonStyle.color } : {
2792
3044
  background: ctx.brand.palette.primary,
2793
3045
  color: ctx.buttonLabel ?? primaryButtonLabel(ctx.brand)
2794
- }
3046
+ })
2795
3047
  },
2796
3048
  children: /* @__PURE__ */ jsx("span", { ...textAttrs(ctx, `${path}.c${i}.label`), children: str(cs.label) })
2797
3049
  },
@@ -2888,6 +3140,7 @@ function AiTreeRenderer({
2888
3140
  {
2889
3141
  "data-ai-section": tree.tag ?? "",
2890
3142
  ...bgAttrs,
3143
+ "data-ai-responsive": "",
2891
3144
  style: {
2892
3145
  position: "relative",
2893
3146
  padding: `${pad}px 0`,
@@ -2898,12 +3151,13 @@ function AiTreeRenderer({
2898
3151
  color: settings.sectionBackground === "accent" ? blockBrand.palette.dark : void 0
2899
3152
  },
2900
3153
  children: [
2901
- isOverlay && backgroundUrl && /* @__PURE__ */ jsx("div", { style: { position: "absolute", inset: 0, background: `rgba(255,255,255,${overlayAlpha})` } }),
3154
+ /* @__PURE__ */ jsx("style", { children: AI_RESPONSIVE_CSS }),
2902
3155
  /* @__PURE__ */ jsx("style", { children: AI_MOBILE_CSS }),
3156
+ isOverlay && backgroundUrl && /* @__PURE__ */ jsx("div", { style: { position: "absolute", inset: 0, background: `rgba(255,255,255,${overlayAlpha})` } }),
2903
3157
  /* @__PURE__ */ jsx(
2904
3158
  "div",
2905
3159
  {
2906
- "data-ai-container": "",
3160
+ "data-ai-section-inner": "",
2907
3161
  style: {
2908
3162
  position: "relative",
2909
3163
  maxWidth: AI_TREE_TOKENS.sectionMaxWidth,
@@ -2914,7 +3168,7 @@ function AiTreeRenderer({
2914
3168
  children: tree.rows.map((row, r2) => /* @__PURE__ */ jsx(
2915
3169
  "div",
2916
3170
  {
2917
- "data-ai-row": "",
3171
+ "data-ai-columns": "",
2918
3172
  style: {
2919
3173
  display: "grid",
2920
3174
  gridTemplateColumns: "repeat(12, 1fr)",
@@ -3094,12 +3348,12 @@ function deriveTemplateButtonStyle() {
3094
3348
  cs.borderBottomLeftRadius
3095
3349
  ].map((v) => v || "0px");
3096
3350
  const radius = corners.every((v) => v === corners[0]) ? corners[0] : corners.join(" ");
3097
- const px = (v) => parseFloat(v) || 0;
3098
- const lineHeight = px(cs.lineHeight) || px(cs.fontSize) * 1.2;
3099
- const contentH = btn.getBoundingClientRect().height - px(cs.borderTopWidth) - px(cs.borderBottomWidth);
3351
+ const px2 = (v) => parseFloat(v) || 0;
3352
+ const lineHeight = px2(cs.lineHeight) || px2(cs.fontSize) * 1.2;
3353
+ const contentH = btn.getBoundingClientRect().height - px2(cs.borderTopWidth) - px2(cs.borderBottomWidth);
3100
3354
  const impliedY = Math.round(Math.max(0, (contentH - lineHeight) / 2));
3101
- const padY = Math.max(px(cs.paddingTop), px(cs.paddingBottom), impliedY);
3102
- const padX = Math.max(px(cs.paddingLeft), px(cs.paddingRight));
3355
+ const padY = Math.max(px2(cs.paddingTop), px2(cs.paddingBottom), impliedY);
3356
+ const padX = Math.max(px2(cs.paddingLeft), px2(cs.paddingRight));
3103
3357
  return {
3104
3358
  radius: radius || "10px",
3105
3359
  padding: `${padY}px ${padX}px`,
@@ -3318,9 +3572,10 @@ function applyAiSectionsToDom(state, options) {
3318
3572
  if (existing && existing.serialized === serialized && existing.container.isConnected) {
3319
3573
  continue;
3320
3574
  }
3321
- const isNewMount = !existing || !existing.container.isConnected;
3322
3575
  let container = existing?.container;
3576
+ let isNewMount = false;
3323
3577
  if (!container || !container.isConnected) {
3578
+ isNewMount = true;
3324
3579
  existing?.root.unmount();
3325
3580
  container?.remove();
3326
3581
  container = document.createElement("div");
@@ -8016,6 +8271,14 @@ function ItemActionToolbar({
8016
8271
  e.preventDefault();
8017
8272
  e.stopPropagation();
8018
8273
  onStyle?.();
8274
+ },
8275
+ // Same as Edit link: mousedown opens the panel and remounts this toolbar
8276
+ // (styleActive flips). Without stopping click, the gesture lands on whatever
8277
+ // is now under the cursor — navbar CTAs park the toolbar below the button, so
8278
+ // that miss deselects and the panel closes as soon as it opens.
8279
+ onClick: (e) => {
8280
+ e.preventDefault();
8281
+ e.stopPropagation();
8019
8282
  }
8020
8283
  },
8021
8284
  children: /* @__PURE__ */ jsx11(Brush, { className: "size-4 shrink-0", "aria-hidden": true })
@@ -8338,20 +8601,32 @@ function ItemInteractionLayer({
8338
8601
  itemDragSurface = true,
8339
8602
  chromeGap,
8340
8603
  toolbarAlign = "center",
8604
+ hidden = false,
8341
8605
  className
8342
8606
  }) {
8607
+ const localRef = useRef2(null);
8608
+ useLayoutEffect2(() => {
8609
+ const node = localRef.current;
8610
+ if (!node) return;
8611
+ node.style.display = hidden ? "none" : "block";
8612
+ }, [hidden, rect.top, rect.left, rect.width, rect.height, state]);
8343
8613
  if (state === "default") return null;
8344
8614
  const gap = chromeGap ?? (state === "hover" ? HOVER_CHROME_GAP : SELECTION_CHROME_GAP);
8345
8615
  const isActive = state === "active-top" || state === "active-bottom";
8346
8616
  const isDragging = state === "dragging";
8347
8617
  const showToolbar = isActive && toolbar;
8348
8618
  const showDragHandle = (isActive || isDragging) && showHandle;
8349
- const itemDragEnabled = itemDragSurface && isActive && showHandle && !isDragging && !dragDisabled;
8619
+ const itemDragEnabled = itemDragSurface && isActive && !isDragging && !dragDisabled && Boolean(showHandle || onItemPointerDown);
8350
8620
  const useDetachedBelowToolbar = Boolean(showToolbar && state === "active-bottom" && toolbarBelowRect);
8621
+ const setRootRef = (node) => {
8622
+ localRef.current = node;
8623
+ if (typeof elRef === "function") elRef(node);
8624
+ else if (elRef) elRef.current = node;
8625
+ };
8351
8626
  return /* @__PURE__ */ jsxs5(
8352
8627
  "div",
8353
8628
  {
8354
- ref: elRef,
8629
+ ref: setRootRef,
8355
8630
  "data-ohw-item-interaction": "",
8356
8631
  "data-ohw-item-interaction-state": state,
8357
8632
  className: cn("pointer-events-none", className),
@@ -8361,7 +8636,8 @@ function ItemInteractionLayer({
8361
8636
  left: rect.left - gap,
8362
8637
  width: rect.width + gap * 2,
8363
8638
  height: rect.height + gap * 2,
8364
- zIndex: getChromeZIndex(state)
8639
+ zIndex: getChromeZIndex(state),
8640
+ display: hidden ? "none" : "block"
8365
8641
  },
8366
8642
  children: [
8367
8643
  /* @__PURE__ */ jsx12(
@@ -9193,6 +9469,7 @@ function MediaOverlay({
9193
9469
  (prev) => prev && prev.fullWidth === width && prev.height === height ? prev : { fullWidth: width, height }
9194
9470
  );
9195
9471
  }, [isVideo]);
9472
+ const replaceLabel = isVideo ? "Replace video" : hover.elementType === "bg-image" ? "Replace background" : "Replace image";
9196
9473
  const replaceMode = buttonSize ? replaceButtonMode({ width: rect.width, height: rect.height }, buttonSize) : "none";
9197
9474
  const box = {
9198
9475
  position: "fixed",
@@ -9291,11 +9568,10 @@ function MediaOverlay({
9291
9568
  className: "flex items-center justify-center cursor-pointer",
9292
9569
  style: {
9293
9570
  ...box,
9294
- // When text overlays this media, let clicks fall THROUGH to the text underneath. In the
9295
- // parent this needed a whole `ow:click-at` round-trip to re-hit-test inside the iframe;
9296
- // in-document, pointer-events does it natively. The button below opts back in, so
9297
- // Replace still works.
9298
- pointerEvents: hover.hasTextOverlap || cueOnly ? "none" : "auto",
9571
+ // Visual only. `pointer-events: auto` on this full-bleed rect covered the navbar
9572
+ // (hero bg includes the absolute TopNav) and swallowed CTA press-drag. Text overlay
9573
+ // and cueOnly also need clicks to fall through — Replace / video controls opt back in.
9574
+ pointerEvents: "none",
9299
9575
  // Selected: a firm component ring with no wash, so the image reads as chosen rather
9300
9576
  // than hovered. Hover keeps the existing tinted preview.
9301
9577
  boxShadow: selected ? "inset 0 0 0 2px var(--color-bridge-primary)" : "inset 0 0 0 1.5px var(--color-bridge-primary)",
@@ -9322,17 +9598,17 @@ function MediaOverlay({
9322
9598
  },
9323
9599
  children: [
9324
9600
  isVideo ? /* @__PURE__ */ jsx15(Film, { size: 14 }) : /* @__PURE__ */ jsx15(ImageIcon, { size: 14 }),
9325
- isVideo ? "Replace video" : "Replace image"
9601
+ replaceLabel
9326
9602
  ]
9327
9603
  }
9328
9604
  ),
9329
- replaceMode === "none" || cueOnly ? null : /* @__PURE__ */ jsxs7(
9605
+ showChrome && replaceMode !== "none" && /* @__PURE__ */ jsxs7(
9330
9606
  Button,
9331
9607
  {
9332
9608
  "data-ohw-media-overlay": "",
9333
9609
  variant: "outline",
9334
9610
  size: "sm",
9335
- "aria-label": isVideo ? "Replace video" : "Replace image",
9611
+ "aria-label": replaceLabel,
9336
9612
  className: "gap-1.5 cursor-pointer hover:bg-background",
9337
9613
  style: {
9338
9614
  ...OVERLAY_BUTTON_STYLE,
@@ -9355,7 +9631,7 @@ function MediaOverlay({
9355
9631
  },
9356
9632
  children: [
9357
9633
  isVideo ? /* @__PURE__ */ jsx15(Film, { size: 14 }) : /* @__PURE__ */ jsx15(ImageIcon, { size: 14 }),
9358
- replaceMode === "full" ? isVideo ? "Replace video" : hover.elementType === "bg-image" ? "Replace background" : "Replace image" : null
9634
+ replaceMode === "full" ? replaceLabel : null
9359
9635
  ]
9360
9636
  }
9361
9637
  )
@@ -11182,6 +11458,13 @@ function findButtonAtPoint(x, y, media) {
11182
11458
  if (hits.length === 0) return null;
11183
11459
  return hits.reduce((best, el) => best.contains(el) ? el : best);
11184
11460
  }
11461
+ function findNavbarCtaAtPoint(x, y) {
11462
+ const hits = Array.from(
11463
+ document.querySelectorAll('[data-ohw-role="navbar-button"]')
11464
+ ).filter((el) => containsPoint(el, x, y) && !el.closest("[data-ohw-bridge]"));
11465
+ if (hits.length === 0) return null;
11466
+ return hits.reduce((best, el) => best.contains(el) ? el : best);
11467
+ }
11185
11468
 
11186
11469
  // src/lib/session-search.ts
11187
11470
  var STORAGE_KEY = "ohw-preserved-search";
@@ -11917,6 +12200,26 @@ import { useCallback as useCallback5, useEffect as useEffect8, useMemo as useMem
11917
12200
  import { createPortal as createPortal2 } from "react-dom";
11918
12201
  import { ArrowLeft as ArrowLeft2, Check as Check2 } from "lucide-react";
11919
12202
  import { usePathname, useRouter as useRouter2 } from "next/navigation";
12203
+
12204
+ // src/lib/parent-scroll.ts
12205
+ function visibleBand(parentScroll) {
12206
+ const { iframeOffsetTop, headerH: visibleCanvasTop, canvasH } = parentScroll;
12207
+ const top = Math.max(0, visibleCanvasTop - iframeOffsetTop);
12208
+ const bottom = Math.min(window.innerHeight, visibleCanvasTop + canvasH - iframeOffsetTop);
12209
+ return { top, bottom: Math.max(top, bottom) };
12210
+ }
12211
+ function getIframeVisibleClip(parentScroll) {
12212
+ if (!parentScroll) return null;
12213
+ return visibleBand(parentScroll);
12214
+ }
12215
+ function getIframeChromeClip(parentScroll) {
12216
+ if (!parentScroll) return null;
12217
+ const band = visibleBand(parentScroll);
12218
+ const promptBarH = Math.max(0, parentScroll.promptBarH ?? 0);
12219
+ return { top: band.top, bottom: Math.max(band.top, band.bottom - promptBarH) };
12220
+ }
12221
+
12222
+ // src/ui/link-modal/SectionPickerOverlay.tsx
11920
12223
  import { jsx as jsx27, jsxs as jsxs17 } from "react/jsx-runtime";
11921
12224
  var DIM_OVERLAY = "rgba(0, 0, 0, 0.45)";
11922
12225
  function rectsEqual(a, b) {
@@ -12054,20 +12357,22 @@ function SectionPickerOverlay({
12054
12357
  []
12055
12358
  );
12056
12359
  useEffect8(() => {
12057
- const applyClip = (iframeOffsetTop, visibleCanvasTop, canvasH) => {
12058
- const top = Math.max(0, visibleCanvasTop - iframeOffsetTop);
12059
- const bottom = Math.min(
12060
- window.innerHeight,
12061
- visibleCanvasTop + canvasH - iframeOffsetTop
12062
- );
12360
+ const applyClip = (iframeOffsetTop, visibleCanvasTop, canvasH, promptBarH = 0) => {
12361
+ const next = getIframeVisibleClip({
12362
+ iframeOffsetTop,
12363
+ headerH: visibleCanvasTop,
12364
+ canvasH,
12365
+ promptBarH
12366
+ });
12367
+ if (!next) return;
12063
12368
  setChromeClip(
12064
- (prev) => prev.top === top && prev.bottom === bottom ? prev : { top, bottom: Math.max(top, bottom) }
12369
+ (prev) => prev.top === next.top && prev.bottom === next.bottom ? prev : next
12065
12370
  );
12066
12371
  };
12067
12372
  const onParentScroll = (e) => {
12068
12373
  if (e.data?.type !== "ow:parent-scroll") return;
12069
- const { iframeOffsetTop, headerH, canvasH } = e.data;
12070
- applyClip(iframeOffsetTop, headerH, canvasH);
12374
+ const { iframeOffsetTop, headerH, canvasH, promptBarH = 0 } = e.data;
12375
+ applyClip(iframeOffsetTop, headerH, canvasH, promptBarH);
12071
12376
  iframeOffsetTopRef.current = iframeOffsetTop;
12072
12377
  if (!pointerScreenRef.current && pointerRef.current) {
12073
12378
  pointerScreenRef.current = {
@@ -15660,6 +15965,7 @@ function readLogoSizeState(content, placement) {
15660
15965
  function getLogoElement(el) {
15661
15966
  const marked = el.closest('[data-ohw-role="logo"], [data-ohw-logo]');
15662
15967
  if (marked) return marked;
15968
+ if (el.closest('[data-ohw-editable="icon"]')) return null;
15663
15969
  const root = el.closest("nav, [data-ohw-nav-root], footer");
15664
15970
  if (!root) return null;
15665
15971
  const anchor = el.closest("a");
@@ -15737,36 +16043,50 @@ import { X as X5 } from "lucide-react";
15737
16043
  import { jsx as jsx29, jsxs as jsxs19 } from "react/jsx-runtime";
15738
16044
  var PANEL_WIDTH = 256;
15739
16045
  var EDGE_MARGIN = 16;
16046
+ var HEADER_MIN = 56;
15740
16047
  function getVisibleClip(parentScroll) {
15741
16048
  const left = 0;
15742
16049
  const right = window.innerWidth;
15743
- if (!parentScroll) {
16050
+ const band = getIframeChromeClip(parentScroll);
16051
+ if (!band) {
15744
16052
  return { top: 0, bottom: window.innerHeight, left, right };
15745
16053
  }
15746
- const { iframeOffsetTop, headerH: visibleCanvasTop, canvasH } = parentScroll;
15747
- const top = Math.max(0, visibleCanvasTop - iframeOffsetTop);
15748
- const bottom = Math.min(window.innerHeight, visibleCanvasTop + canvasH - iframeOffsetTop);
15749
- return { top, bottom: Math.max(top, bottom), left, right };
16054
+ return { ...band, left, right };
15750
16055
  }
15751
- function defaultFloatingPanelPosition(parentScroll, panelHeight = 280) {
16056
+ function defaultFloatingPanelPosition(parentScroll, _panelHeight = 280) {
15752
16057
  const clip = getVisibleClip(parentScroll);
15753
16058
  return {
15754
16059
  x: Math.max(EDGE_MARGIN, clip.right - PANEL_WIDTH - EDGE_MARGIN),
15755
- y: Math.min(
15756
- Math.max(clip.top + EDGE_MARGIN, EDGE_MARGIN),
15757
- Math.max(clip.top + EDGE_MARGIN, clip.bottom - panelHeight - EDGE_MARGIN)
15758
- )
16060
+ y: clip.top + EDGE_MARGIN
15759
16061
  };
15760
16062
  }
15761
- function clampPosition(pos, parentScroll, panelW, panelH) {
16063
+ function clampX(x, panelW) {
16064
+ const left = EDGE_MARGIN;
16065
+ const maxX = Math.max(left, window.innerWidth - panelW - EDGE_MARGIN);
16066
+ return Math.min(Math.max(x, left), maxX);
16067
+ }
16068
+ function clampDragPosition(pos, parentScroll, panelW) {
15762
16069
  const clip = getVisibleClip(parentScroll);
15763
- const maxX = Math.max(clip.left + EDGE_MARGIN, clip.right - panelW - EDGE_MARGIN);
15764
- const maxY = Math.max(clip.top + EDGE_MARGIN, clip.bottom - panelH - EDGE_MARGIN);
16070
+ const minY = clip.top + EDGE_MARGIN;
16071
+ const maxY = Math.max(minY, clip.bottom - EDGE_MARGIN - HEADER_MIN);
15765
16072
  return {
15766
- x: Math.min(Math.max(pos.x, clip.left + EDGE_MARGIN), maxX),
15767
- y: Math.min(Math.max(pos.y, clip.top + EDGE_MARGIN), maxY)
16073
+ x: clampX(pos.x, panelW),
16074
+ y: Math.min(Math.max(pos.y, minY), maxY)
15768
16075
  };
15769
16076
  }
16077
+ function fitPanelInClip(homeY, parentScroll, preferredHeight) {
16078
+ const clip = getVisibleClip(parentScroll);
16079
+ const minY = clip.top + EDGE_MARGIN;
16080
+ const maxBottom = clip.bottom - EDGE_MARGIN;
16081
+ const band = Math.max(HEADER_MIN, maxBottom - minY);
16082
+ let y = Math.max(homeY, minY);
16083
+ const want = Math.min(Math.max(preferredHeight, HEADER_MIN), band);
16084
+ if (y + want > maxBottom) {
16085
+ y = Math.max(minY, maxBottom - want);
16086
+ }
16087
+ const maxHeight = Math.max(HEADER_MIN, Math.floor(maxBottom - y));
16088
+ return { y, maxHeight };
16089
+ }
15770
16090
  function FloatingPanel({
15771
16091
  open,
15772
16092
  title,
@@ -15783,19 +16103,54 @@ function FloatingPanel({
15783
16103
  const panelRef = useRef9(null);
15784
16104
  const [measured, setMeasured] = useState12({ w: PANEL_WIDTH, h: 280 });
15785
16105
  const dragRef = useRef9(null);
15786
- const resolved = position ?? defaultFloatingPanelPosition(parentScroll, measured.h);
15787
- const clamped = clampPosition(resolved, parentScroll, measured.w, measured.h);
16106
+ const homeRef = useRef9(null);
16107
+ if (!open) {
16108
+ homeRef.current = null;
16109
+ } else if (position) {
16110
+ homeRef.current = position;
16111
+ } else if (homeRef.current == null) {
16112
+ homeRef.current = defaultFloatingPanelPosition(parentScroll);
16113
+ }
16114
+ const [liveScroll, setLiveScroll] = useState12(parentScroll);
16115
+ const home = position ?? homeRef.current ?? defaultFloatingPanelPosition(parentScroll);
16116
+ const { y: fittedY, maxHeight } = fitPanelInClip(home.y, liveScroll, measured.h);
16117
+ const clamped = {
16118
+ x: clampX(home.x, measured.w),
16119
+ y: fittedY
16120
+ };
16121
+ useEffect11(() => {
16122
+ setLiveScroll(parentScroll);
16123
+ }, [parentScroll]);
16124
+ useEffect11(() => {
16125
+ if (!open) return;
16126
+ const onParentScroll = (e) => {
16127
+ if (e.data?.type !== "ow:parent-scroll") return;
16128
+ const { iframeOffsetTop, headerH, canvasH, promptBarH = 0 } = e.data;
16129
+ setLiveScroll({ iframeOffsetTop, headerH, canvasH, promptBarH });
16130
+ };
16131
+ window.addEventListener("message", onParentScroll);
16132
+ return () => window.removeEventListener("message", onParentScroll);
16133
+ }, [open]);
16134
+ const positionRef = useRef9(position);
16135
+ useLayoutEffect4(() => {
16136
+ if (!open) {
16137
+ positionRef.current = position;
16138
+ return;
16139
+ }
16140
+ if (position == null && positionRef.current != null) {
16141
+ homeRef.current = defaultFloatingPanelPosition(liveScroll ?? parentScroll);
16142
+ }
16143
+ positionRef.current = position;
16144
+ }, [open, position, liveScroll, parentScroll]);
15788
16145
  useLayoutEffect4(() => {
15789
16146
  if (!open || !panelRef.current) return;
15790
16147
  const el = panelRef.current;
15791
- const next = { w: el.offsetWidth || PANEL_WIDTH, h: el.offsetHeight || 280 };
16148
+ const header = el.querySelector("[data-ohw-floating-panel-header]");
16149
+ const body = el.querySelector("[data-ohw-floating-panel-body]");
16150
+ const contentH = (header?.offsetHeight ?? 0) + (body?.scrollHeight ?? 0) || el.scrollHeight || 280;
16151
+ const next = { w: el.offsetWidth || PANEL_WIDTH, h: contentH };
15792
16152
  setMeasured((prev) => prev.w === next.w && prev.h === next.h ? prev : next);
15793
16153
  }, [open, children, title, context]);
15794
- useEffect11(() => {
15795
- if (!open || !position || !onPositionChange) return;
15796
- const next = clampPosition(position, parentScroll, measured.w, measured.h);
15797
- if (next.x !== position.x || next.y !== position.y) onPositionChange(next);
15798
- }, [open, parentScroll, measured.w, measured.h, position, onPositionChange]);
15799
16154
  const onHeaderPointerDown = useCallback7(
15800
16155
  (e) => {
15801
16156
  if (e.button !== 0) return;
@@ -15829,18 +16184,17 @@ function FloatingPanel({
15829
16184
  return;
15830
16185
  }
15831
16186
  e.preventDefault();
15832
- const next = clampPosition(
16187
+ const next = clampDragPosition(
15833
16188
  {
15834
16189
  x: drag.originX + (e.clientX - drag.startX),
15835
16190
  y: drag.originY + (e.clientY - drag.startY)
15836
16191
  },
15837
- parentScroll,
15838
- measured.w,
15839
- measured.h
16192
+ liveScroll,
16193
+ measured.w
15840
16194
  );
15841
16195
  onPositionChange?.(next);
15842
16196
  },
15843
- [measured.h, measured.w, onPositionChange, parentScroll]
16197
+ [measured.w, onPositionChange, liveScroll]
15844
16198
  );
15845
16199
  const endDrag = useCallback7((e) => {
15846
16200
  const drag = dragRef.current;
@@ -15870,7 +16224,7 @@ function FloatingPanel({
15870
16224
  "fixed z-[2147483647] flex w-64 flex-col overflow-hidden rounded-xl border border-border bg-background font-sans shadow-lg outline-none",
15871
16225
  className
15872
16226
  ),
15873
- style: { left: clamped.x, top: clamped.y },
16227
+ style: { left: clamped.x, top: clamped.y, maxHeight },
15874
16228
  onMouseDown: (e) => e.stopPropagation(),
15875
16229
  onPointerDown: (e) => e.stopPropagation(),
15876
16230
  onClick: (e) => e.stopPropagation(),
@@ -15879,7 +16233,7 @@ function FloatingPanel({
15879
16233
  "div",
15880
16234
  {
15881
16235
  "data-ohw-floating-panel-header": "",
15882
- className: "relative flex cursor-grab items-start gap-2 border-b border-border py-5 pl-5 pr-11 active:cursor-grabbing",
16236
+ className: "relative flex shrink-0 cursor-grab items-start gap-2 border-b border-border py-4! pl-5! pr-11! active:cursor-grabbing",
15883
16237
  onPointerDown: onHeaderPointerDown,
15884
16238
  onPointerMove: onHeaderPointerMove,
15885
16239
  onPointerUp: endDrag,
@@ -15914,7 +16268,11 @@ function FloatingPanel({
15914
16268
  "div",
15915
16269
  {
15916
16270
  "data-ohw-floating-panel-body": "",
15917
- className: cn("flex w-full flex-col gap-4 p-5", bodyClassName),
16271
+ className: cn(
16272
+ "flex min-h-0 w-full flex-1 flex-col gap-4 overflow-y-auto overscroll-contain p-4",
16273
+ bodyClassName
16274
+ ),
16275
+ style: { WebkitOverflowScrolling: "touch" },
15918
16276
  children
15919
16277
  }
15920
16278
  )
@@ -15992,122 +16350,1556 @@ function SocialsDisplayPanel({ display, onChange, className }) {
15992
16350
  ] });
15993
16351
  }
15994
16352
 
15995
- // src/lib/item-drag-interaction.ts
15996
- function disableNativeHrefDrag(el) {
15997
- if (el.draggable) el.draggable = false;
15998
- if (el.getAttribute("draggable") !== "false") {
15999
- el.setAttribute("draggable", "false");
16353
+ // src/ui/button-style-panel.tsx
16354
+ import { Check as Check3 } from "lucide-react";
16355
+
16356
+ // src/lib/button-styles.ts
16357
+ var BUTTON_VARIANTS_KEY = "__ohw_button_variants";
16358
+ var BUTTON_SIZES_KEY = "__ohw_button_sizes";
16359
+ var BUTTON_ASSIGNMENTS_KEY = "__ohw_button_assignments";
16360
+ var BUTTON_VARIANT_IDS = ["primary", "secondary", "ghost"];
16361
+ var BUTTON_SIZE_IDS = ["s", "m", "l"];
16362
+ var TRANSPARENT = "rgba(0, 0, 0, 0)";
16363
+ var REFERENCE_SIZES = {
16364
+ s: { height: 40, paddingTop: 8, paddingRight: 16, paddingBottom: 8, paddingLeft: 16, fontSize: 15, borderRadius: 8, gap: 8 },
16365
+ m: { height: 48, paddingTop: 12, paddingRight: 24, paddingBottom: 12, paddingLeft: 24, fontSize: 15, borderRadius: 8, gap: 8 },
16366
+ l: { height: 56, paddingTop: 16, paddingRight: 32, paddingBottom: 16, paddingLeft: 32, fontSize: 15, borderRadius: 8, gap: 8 }
16367
+ };
16368
+ function buttonStyleContext(_el) {
16369
+ return "Button";
16370
+ }
16371
+ function previewSlotBackground(el) {
16372
+ let node = el.parentElement;
16373
+ while (node && node !== el.ownerDocument.documentElement) {
16374
+ if (node.closest("[data-ohw-bridge], [data-ohw-bridge-root]")) {
16375
+ node = node.parentElement;
16376
+ continue;
16377
+ }
16378
+ const bg = getComputedStyle(node).backgroundColor;
16379
+ const parsed = parseColor(bg);
16380
+ if (parsed && !isTransparent(parsed)) return bg;
16381
+ node = node.parentElement;
16000
16382
  }
16383
+ const root = el.ownerDocument.documentElement;
16384
+ const style = getComputedStyle(root);
16385
+ for (const name of ["--brand-surface", "--brand-background", "--ohw-brand-light", "--color-light"]) {
16386
+ const value = style.getPropertyValue(name).trim();
16387
+ if (value) return value;
16388
+ }
16389
+ return "var(--brand-background, var(--ohw-brand-light, var(--color-light, #ffffff)))";
16001
16390
  }
16002
- function clearTextSelection() {
16003
- const sel = window.getSelection();
16004
- if (sel && !sel.isCollapsed) sel.removeAllRanges();
16391
+ function buttonId(el) {
16392
+ if (!el) return null;
16393
+ const button = el.matches(CTA_BUTTON_SELECTOR) ? el : el.closest(CTA_BUTTON_SELECTOR);
16394
+ const id = button?.getAttribute("data-ohw-href-key")?.trim();
16395
+ return id || null;
16005
16396
  }
16006
- function armItemPressDrag() {
16007
- document.documentElement.setAttribute("data-ohw-footer-press-drag", "");
16397
+ function isButtonVariantId(value) {
16398
+ return BUTTON_VARIANT_IDS.includes(value);
16008
16399
  }
16009
- function lockItemDuringDrag() {
16010
- document.documentElement.removeAttribute("data-ohw-footer-press-drag");
16011
- document.documentElement.setAttribute("data-ohw-item-dragging", "");
16012
- clearTextSelection();
16400
+ function isButtonSizeId(value) {
16401
+ return BUTTON_SIZE_IDS.includes(value);
16013
16402
  }
16014
- function unlockItemDragInteraction() {
16015
- const wasDragging = document.documentElement.hasAttribute("data-ohw-item-dragging");
16016
- document.documentElement.removeAttribute("data-ohw-footer-press-drag");
16017
- document.documentElement.removeAttribute("data-ohw-item-dragging");
16018
- if (wasDragging) clearTextSelection();
16403
+ function px(value) {
16404
+ const rounded = Math.round(value * 100) / 100;
16405
+ return `${rounded}px`;
16019
16406
  }
16020
- var armFooterPressDrag = armItemPressDrag;
16021
- var lockFooterDuringDrag = lockItemDuringDrag;
16022
- var unlockFooterDragInteraction = unlockItemDragInteraction;
16023
-
16024
- // src/lib/nav-dnd.ts
16025
- function listReorderableNavItems(draggedEl) {
16026
- return listActiveNavbarItems(draggedEl).filter((el) => {
16027
- const key = el.getAttribute("data-ohw-href-key");
16028
- return isNavbarHrefKey(key) && !isNestedNavChild(el);
16029
- });
16407
+ function parsePx(value, rootFont = 16) {
16408
+ if (!value) return 0;
16409
+ const n = parseFloat(value);
16410
+ if (!Number.isFinite(n)) return 0;
16411
+ if (value.trim().endsWith("rem") || value.trim().endsWith("em")) return n * rootFont;
16412
+ return n;
16030
16413
  }
16031
- function resolveParentNavHrefKey(child) {
16032
- const childrenRoot = child.closest("[data-ohw-nav-children]");
16033
- const group = childrenRoot?.closest("[data-ohw-nav-group]");
16034
- if (!group) return null;
16035
- const trigger = group.querySelector(":scope > [data-ohw-href-key]") ?? Array.from(group.querySelectorAll("[data-ohw-href-key]")).find(
16036
- (el) => !el.closest("[data-ohw-nav-children]")
16037
- ) ?? null;
16038
- const key = trigger?.getAttribute("data-ohw-href-key");
16039
- return key && isNavbarHrefKey(key) ? key : null;
16414
+ function classifyHeight(height) {
16415
+ if (height < 44) return "s";
16416
+ if (height < 52) return "m";
16417
+ return "l";
16040
16418
  }
16041
- function listNavChildren(parentHrefKey, draggedEl) {
16042
- const items = listActiveNavbarItems(draggedEl);
16043
- const parent = items.find((el) => el.getAttribute("data-ohw-href-key") === parentHrefKey);
16044
- if (!parent) return [];
16045
- const group = parent.closest("[data-ohw-nav-group]");
16046
- const childrenRoot = group?.querySelector(":scope > [data-ohw-nav-children]");
16047
- if (!childrenRoot) return [];
16048
- return Array.from(childrenRoot.querySelectorAll(":scope > [data-ohw-href-key]")).filter(
16049
- (el) => isNavbarHrefKey(el.getAttribute("data-ohw-href-key"))
16050
- );
16419
+ function scaleGeometry(geometry, scale = 0.75) {
16420
+ return {
16421
+ paddingTop: px(parsePx(geometry.paddingTop) * scale),
16422
+ paddingRight: px(parsePx(geometry.paddingRight) * scale),
16423
+ paddingBottom: px(parsePx(geometry.paddingBottom) * scale),
16424
+ paddingLeft: px(parsePx(geometry.paddingLeft) * scale),
16425
+ fontSize: px(parsePx(geometry.fontSize) * scale),
16426
+ borderRadius: px(parsePx(geometry.borderRadius) * scale),
16427
+ gap: px(parsePx(geometry.gap) * scale),
16428
+ height: Math.round(geometry.height * scale)
16429
+ };
16051
16430
  }
16052
- function isVerticalNavLayout(items, draggedEl) {
16053
- const visible = items.filter((el) => el.getClientRects().length > 0);
16054
- if (visible.length >= 2) {
16055
- const a = visible[0].getBoundingClientRect();
16056
- const b = visible[1].getBoundingClientRect();
16057
- const verticalGap = b.top - a.bottom;
16058
- const horizontalGap = b.left - a.right;
16059
- return verticalGap > horizontalGap;
16431
+ function relativeLuminance(color) {
16432
+ const channel = (value) => {
16433
+ const s = value / 255;
16434
+ return s <= 0.03928 ? s / 12.92 : ((s + 0.055) / 1.055) ** 2.4;
16435
+ };
16436
+ return 0.2126 * channel(color.r) + 0.7152 * channel(color.g) + 0.0722 * channel(color.b);
16437
+ }
16438
+ function contrastRatio(a, b) {
16439
+ const lighter = Math.max(relativeLuminance(a), relativeLuminance(b));
16440
+ const darker = Math.min(relativeLuminance(a), relativeLuminance(b));
16441
+ return (lighter + 0.05) / (darker + 0.05);
16442
+ }
16443
+ function compositeOver(fg, bg) {
16444
+ const a = fg.a + bg.a * (1 - fg.a);
16445
+ if (a < 1e-3) return { ...bg, a: 0 };
16446
+ return {
16447
+ r: (fg.r * fg.a + bg.r * bg.a * (1 - fg.a)) / a,
16448
+ g: (fg.g * fg.a + bg.g * bg.a * (1 - fg.a)) / a,
16449
+ b: (fg.b * fg.a + bg.b * bg.a * (1 - fg.a)) / a,
16450
+ a
16451
+ };
16452
+ }
16453
+ function previewSlotForChrome(chrome, slotBackground) {
16454
+ const text = parseColor(chrome.color);
16455
+ if (!text || text.a < 0.2) return slotBackground;
16456
+ const slot = parseColor(slotBackground) ?? { r: 250, g: 250, b: 249, a: 1 };
16457
+ const fill = parseColor(chrome.background);
16458
+ const effective = fill && !isTransparent(fill) ? compositeOver(fill, slot) : slot;
16459
+ if (contrastRatio(text, effective) >= 3) return slotBackground;
16460
+ return relativeLuminance(text) > 0.55 ? "#2c2c2c" : "#f4f4f5";
16461
+ }
16462
+ function parseColor(input) {
16463
+ if (!input) return null;
16464
+ const value = input.trim().toLowerCase();
16465
+ if (value === "transparent") return { r: 0, g: 0, b: 0, a: 0 };
16466
+ const hex = value.match(/^#([0-9a-f]{3,8})$/);
16467
+ if (hex) {
16468
+ const raw = hex[1];
16469
+ const expand = (pair) => parseInt(pair.length === 1 ? pair + pair : pair, 16);
16470
+ if (raw.length === 3 || raw.length === 4) {
16471
+ return {
16472
+ r: expand(raw[0]),
16473
+ g: expand(raw[1]),
16474
+ b: expand(raw[2]),
16475
+ a: raw.length === 4 ? expand(raw[3]) / 255 : 1
16476
+ };
16477
+ }
16478
+ if (raw.length === 6 || raw.length === 8) {
16479
+ return {
16480
+ r: expand(raw.slice(0, 2)),
16481
+ g: expand(raw.slice(2, 4)),
16482
+ b: expand(raw.slice(4, 6)),
16483
+ a: raw.length === 8 ? expand(raw.slice(6, 8)) / 255 : 1
16484
+ };
16485
+ }
16060
16486
  }
16061
- const container = getActiveNavbarListContainer(draggedEl);
16062
- if (!container) return false;
16063
- const style = window.getComputedStyle(container);
16064
- return style.flexDirection === "column" || style.flexDirection === "column-reverse";
16487
+ const rgb = value.match(
16488
+ /^rgba?\(\s*([\d.]+)[%\s,]+([\d.]+)[%\s,]+([\d.]+)(?:%?\s*[,/]\s*([\d.]+%?))?\s*\)$/
16489
+ );
16490
+ if (rgb) {
16491
+ const alphaRaw = rgb[4];
16492
+ const alpha = alphaRaw == null ? 1 : alphaRaw.endsWith("%") ? parseFloat(alphaRaw) / 100 : parseFloat(alphaRaw);
16493
+ return { r: Number(rgb[1]), g: Number(rgb[2]), b: Number(rgb[3]), a: alpha };
16494
+ }
16495
+ const oklch = value.match(
16496
+ /^oklch\(\s*([\d.]+%?)\s+([\d.]+)\s+([\d.]+)(?:deg)?(?:\s*\/\s*([\d.]+%?))?\s*\)$/
16497
+ );
16498
+ if (oklch) {
16499
+ const Lraw = oklch[1];
16500
+ const L = Lraw.endsWith("%") ? parseFloat(Lraw) / 100 : parseFloat(Lraw);
16501
+ const gray = Math.max(0, Math.min(1, L)) * 255;
16502
+ const alphaRaw = oklch[4];
16503
+ const alpha = alphaRaw == null ? 1 : alphaRaw.endsWith("%") ? parseFloat(alphaRaw) / 100 : parseFloat(alphaRaw);
16504
+ return { r: gray, g: gray, b: gray, a: alpha };
16505
+ }
16506
+ return null;
16065
16507
  }
16066
- function buildVerticalRootNavDropSlots(items, draggedEl) {
16067
- const slots = [];
16068
- const barThickness = 3;
16069
- if (items.length === 0) {
16070
- const container = getActiveNavbarListContainer(draggedEl);
16071
- if (!container) return slots;
16072
- const rect = container.getBoundingClientRect();
16073
- slots.push({
16074
- insertIndex: 0,
16075
- parentId: null,
16076
- left: rect.left,
16077
- top: rect.top + rect.height / 2 - barThickness / 2,
16078
- width: Math.max(rect.width, 40),
16079
- height: barThickness,
16080
- direction: "horizontal"
16081
- });
16082
- return slots;
16508
+ function isTransparent(color) {
16509
+ return !color || color.a < 0.05;
16510
+ }
16511
+ function hasOpaqueBackground(background) {
16512
+ if (!background) return false;
16513
+ const value = background.trim().toLowerCase();
16514
+ if (!value || value === "transparent" || value === "rgba(0, 0, 0, 0)" || value === "rgb(0, 0, 0, 0)") {
16515
+ return false;
16083
16516
  }
16084
- const edgeGap = (() => {
16085
- if (items.length < 2) return 16;
16086
- const a = items[0].getBoundingClientRect();
16087
- const b = items[1].getBoundingClientRect();
16088
- return Math.max(0, b.top - a.bottom);
16089
- })();
16090
- for (let i = 0; i <= items.length; i++) {
16091
- let top;
16092
- let width;
16093
- let left;
16094
- if (i === 0) {
16095
- const first = items[0].getBoundingClientRect();
16096
- top = first.top - edgeGap / 2 - barThickness / 2;
16097
- left = first.left;
16098
- width = first.width;
16099
- } else if (i === items.length) {
16100
- const last = items[items.length - 1].getBoundingClientRect();
16101
- const lastGap = items.length >= 2 ? Math.max(0, last.top - items[items.length - 2].getBoundingClientRect().bottom) : edgeGap;
16102
- top = last.bottom + lastGap / 2 - barThickness / 2;
16103
- left = last.left;
16104
- width = last.width;
16105
- } else {
16106
- const prev = items[i - 1].getBoundingClientRect();
16107
- const next = items[i].getBoundingClientRect();
16108
- top = (prev.bottom + next.top) / 2 - barThickness / 2;
16109
- left = Math.min(prev.left, next.left);
16110
- width = Math.max(prev.right, next.right) - left;
16517
+ const parsed = parseColor(background);
16518
+ if (parsed) return !isTransparent(parsed);
16519
+ return true;
16520
+ }
16521
+ function toCss(color) {
16522
+ const r2 = Math.round(color.r);
16523
+ const g = Math.round(color.g);
16524
+ const b = Math.round(color.b);
16525
+ if (color.a >= 0.995) return `rgb(${r2}, ${g}, ${b})`;
16526
+ return `rgba(${r2}, ${g}, ${b}, ${Math.round(color.a * 100) / 100})`;
16527
+ }
16528
+ function colorKey(input) {
16529
+ const parsed = parseColor(input);
16530
+ if (!parsed || isTransparent(parsed)) return "transparent";
16531
+ return `${Math.round(parsed.r / 8)}-${Math.round(parsed.g / 8)}-${Math.round(parsed.b / 8)}-${Math.round(parsed.a * 4)}`;
16532
+ }
16533
+ function declaredVariant(className) {
16534
+ const match = className.match(/\bbtn--(primary|secondary|ghost)\b/);
16535
+ return match && isButtonVariantId(match[1]) ? match[1] : null;
16536
+ }
16537
+ function fillSlot(sample) {
16538
+ const width = parseFloat(sample.borderWidth);
16539
+ const border = parseColor(sample.borderColor);
16540
+ if (width > 0 && !isTransparent(border)) return "secondary";
16541
+ if (hasOpaqueBackground(sample.background)) return "primary";
16542
+ return "ghost";
16543
+ }
16544
+ function sampleSlot(sample) {
16545
+ return declaredVariant(sample.className) ?? fillSlot(sample);
16546
+ }
16547
+ function fingerprint(sample) {
16548
+ const width = parseFloat(sample.borderWidth);
16549
+ const border = width > 0 ? colorKey(sample.borderColor) : "none";
16550
+ return `${colorKey(sample.background)}|${border}|${colorKey(sample.color)}|${sampleSlot(sample)}`;
16551
+ }
16552
+ function paintColor(chrome) {
16553
+ if (hasOpaqueBackground(chrome.background)) {
16554
+ const parsed = parseColor(chrome.background);
16555
+ return parsed ? toCss({ ...parsed, a: 1 }) : chrome.background;
16556
+ }
16557
+ const width = parseFloat(chrome.borderWidth);
16558
+ if (width > 0 && hasOpaqueBackground(chrome.borderColor)) {
16559
+ const parsed = parseColor(chrome.borderColor);
16560
+ return parsed ? toCss({ ...parsed, a: 1 }) : chrome.borderColor;
16561
+ }
16562
+ const text = parseColor(chrome.color);
16563
+ return text ? toCss({ ...text, a: 1 }) : chrome.color;
16564
+ }
16565
+ function accentColor(chrome) {
16566
+ const bg = parseColor(chrome.background);
16567
+ if (bg && !isTransparent(bg) && relativeLuminance(bg) < 0.55) {
16568
+ return toCss({ ...bg, a: 1 });
16569
+ }
16570
+ const text = parseColor(chrome.color);
16571
+ if (text && !isTransparent(text)) return toCss({ ...text, a: 1 });
16572
+ return paintColor(chrome);
16573
+ }
16574
+ function chromeFrom(sample) {
16575
+ return {
16576
+ background: sample.background || TRANSPARENT,
16577
+ color: sample.color || "currentColor",
16578
+ borderColor: sample.borderColor || TRANSPARENT,
16579
+ borderWidth: sample.borderWidth || "0px"
16580
+ };
16581
+ }
16582
+ function filledPrimary(seed) {
16583
+ const paint = parseColor(paintColor(seed)) ?? { r: 15, g: 15, b: 15, a: 1 };
16584
+ const luminance = relativeLuminance(paint);
16585
+ return {
16586
+ background: toCss({ ...paint, a: 1 }),
16587
+ color: luminance > 0.55 ? "rgb(15, 15, 15)" : "rgb(255, 255, 255)",
16588
+ borderColor: TRANSPARENT,
16589
+ borderWidth: "0px"
16590
+ };
16591
+ }
16592
+ function generatedSecondary(primary) {
16593
+ const paint = accentColor(primary);
16594
+ return {
16595
+ background: TRANSPARENT,
16596
+ color: paint,
16597
+ borderColor: paint,
16598
+ borderWidth: "1px"
16599
+ };
16600
+ }
16601
+ function generatedGhost(primary) {
16602
+ return {
16603
+ background: TRANSPARENT,
16604
+ color: accentColor(primary),
16605
+ borderColor: TRANSPARENT,
16606
+ borderWidth: "0px"
16607
+ };
16608
+ }
16609
+ function normalizeOutlineChrome(outline, primary) {
16610
+ const paint = accentColor(primary);
16611
+ const text = parseColor(outline.color);
16612
+ const paintRgb = parseColor(paint);
16613
+ const primaryLabel = parseColor(primary.color);
16614
+ const matchesOnPrimary = Boolean(text && primaryLabel) && colorKey(outline.color) === colorKey(primary.color);
16615
+ const textTooLight = text ? relativeLuminance(text) > 0.7 : false;
16616
+ const paintIsDarker = paintRgb ? relativeLuminance(paintRgb) < 0.65 : hasOpaqueBackground(primary.background);
16617
+ if (!(matchesOnPrimary || textTooLight) || !paintIsDarker) return outline;
16618
+ const hasBorder = parseFloat(outline.borderWidth) > 0;
16619
+ return {
16620
+ ...outline,
16621
+ color: paint,
16622
+ borderColor: hasBorder ? paint : outline.borderColor
16623
+ };
16624
+ }
16625
+ function generateButtonVariants(samples) {
16626
+ if (samples.length === 0) return { status: "unresolved" };
16627
+ const groups = /* @__PURE__ */ new Map();
16628
+ for (const sample of samples) {
16629
+ const key = fingerprint(sample);
16630
+ const list = groups.get(key) ?? [];
16631
+ list.push(sample);
16632
+ groups.set(key, list);
16633
+ }
16634
+ if (groups.size > 3) return { status: "unresolved" };
16635
+ const bySlot = /* @__PURE__ */ new Map();
16636
+ for (const group of groups.values()) {
16637
+ const sample = group[0];
16638
+ const slot = sampleSlot(sample);
16639
+ if (bySlot.has(slot)) return { status: "unresolved" };
16640
+ bySlot.set(slot, sample);
16641
+ }
16642
+ const primarySample = bySlot.get("primary");
16643
+ const seed = chromeFrom(primarySample ?? samples[0]);
16644
+ const primary = primarySample ? seed : filledPrimary(seed);
16645
+ const secondary = normalizeOutlineChrome(
16646
+ bySlot.has("secondary") ? chromeFrom(bySlot.get("secondary")) : generatedSecondary(primary),
16647
+ primary
16648
+ );
16649
+ const ghost = normalizeOutlineChrome(
16650
+ bySlot.has("ghost") ? chromeFrom(bySlot.get("ghost")) : generatedGhost(primary),
16651
+ primary
16652
+ );
16653
+ return {
16654
+ status: "ready",
16655
+ tokens: { primary, secondary, ghost }
16656
+ };
16657
+ }
16658
+ function geometryFromSample(sample) {
16659
+ return {
16660
+ paddingTop: px(sample.paddingTop),
16661
+ paddingRight: px(sample.paddingRight),
16662
+ paddingBottom: px(sample.paddingBottom),
16663
+ paddingLeft: px(sample.paddingLeft),
16664
+ fontSize: px(sample.fontSize),
16665
+ borderRadius: px(sample.borderRadius),
16666
+ gap: px(sample.gap),
16667
+ height: Math.round(sample.height)
16668
+ };
16669
+ }
16670
+ function shiftSample(base, steps) {
16671
+ return {
16672
+ height: Math.max(24, base.height + steps * 8),
16673
+ paddingTop: Math.max(0, base.paddingTop + steps * 4),
16674
+ paddingBottom: Math.max(0, base.paddingBottom + steps * 4),
16675
+ paddingLeft: Math.max(0, base.paddingLeft + steps * 8),
16676
+ paddingRight: Math.max(0, base.paddingRight + steps * 8),
16677
+ fontSize: base.fontSize,
16678
+ borderRadius: base.borderRadius,
16679
+ gap: base.gap
16680
+ };
16681
+ }
16682
+ function clusterSizes(samples) {
16683
+ const sorted = [...samples].sort((a, b) => a.height - b.height);
16684
+ const clusters = [];
16685
+ for (const sample of sorted) {
16686
+ const current = clusters[clusters.length - 1];
16687
+ const head = current?.[0];
16688
+ if (!current || !head || Math.abs(sample.height - head.height) > 2) clusters.push([sample]);
16689
+ else current.push(sample);
16690
+ }
16691
+ return clusters.map((group) => {
16692
+ const pick = (key) => group.reduce((sum, item) => sum + item[key], 0) / group.length;
16693
+ return {
16694
+ height: pick("height"),
16695
+ paddingTop: pick("paddingTop"),
16696
+ paddingRight: pick("paddingRight"),
16697
+ paddingBottom: pick("paddingBottom"),
16698
+ paddingLeft: pick("paddingLeft"),
16699
+ fontSize: pick("fontSize"),
16700
+ borderRadius: pick("borderRadius"),
16701
+ gap: pick("gap")
16702
+ };
16703
+ });
16704
+ }
16705
+ function tokensFromSamples(bySlot, radius, gap) {
16706
+ const fill = (id) => {
16707
+ const sample = bySlot[id] ?? REFERENCE_SIZES[id];
16708
+ return geometryFromSample({ ...sample, borderRadius: radius, gap });
16709
+ };
16710
+ return { s: fill("s"), m: fill("m"), l: fill("l") };
16711
+ }
16712
+ function interpolateSizes(known) {
16713
+ const order = BUTTON_SIZE_IDS;
16714
+ const indexOf = (id) => order.indexOf(id);
16715
+ const sorted = [...known].sort((a, b) => indexOf(a.id) - indexOf(b.id));
16716
+ const left = sorted[0];
16717
+ const right = sorted[1];
16718
+ const span = indexOf(right.id) - indexOf(left.id);
16719
+ const step = (key) => (right.sample[key] - left.sample[key]) / span;
16720
+ const at = (index) => {
16721
+ const steps = index - indexOf(left.id);
16722
+ return {
16723
+ height: left.sample.height + step("height") * steps,
16724
+ paddingTop: Math.max(0, left.sample.paddingTop + step("paddingTop") * steps),
16725
+ paddingBottom: Math.max(0, left.sample.paddingBottom + step("paddingBottom") * steps),
16726
+ paddingLeft: Math.max(0, left.sample.paddingLeft + step("paddingLeft") * steps),
16727
+ paddingRight: Math.max(0, left.sample.paddingRight + step("paddingRight") * steps),
16728
+ fontSize: Math.max(10, left.sample.fontSize + step("fontSize") * steps),
16729
+ borderRadius: left.sample.borderRadius,
16730
+ gap: left.sample.gap
16731
+ };
16732
+ };
16733
+ const bySlot = {
16734
+ [left.id]: left.sample,
16735
+ [right.id]: { ...right.sample, borderRadius: left.sample.borderRadius, gap: left.sample.gap }
16736
+ };
16737
+ for (const id of order) {
16738
+ if (!bySlot[id]) bySlot[id] = at(indexOf(id));
16739
+ }
16740
+ return tokensFromSamples(bySlot, left.sample.borderRadius, left.sample.gap);
16741
+ }
16742
+ function generateButtonSizes(samples) {
16743
+ const clusters = clusterSizes(samples.filter((sample) => sample.height > 0));
16744
+ if (clusters.length === 0) {
16745
+ return { status: "ready", tokens: tokensFromSamples(REFERENCE_SIZES, 8, 8) };
16746
+ }
16747
+ if (clusters.length > 3) return { status: "unresolved" };
16748
+ if (clusters.length === 3) {
16749
+ const sorted = [...clusters].sort((a, b) => a.height - b.height);
16750
+ const radius = sorted[1].borderRadius;
16751
+ const gap = sorted[1].gap;
16752
+ return {
16753
+ status: "ready",
16754
+ tokens: {
16755
+ s: geometryFromSample({ ...sorted[0], borderRadius: radius, gap }),
16756
+ m: geometryFromSample({ ...sorted[1], borderRadius: radius, gap }),
16757
+ l: geometryFromSample({ ...sorted[2], borderRadius: radius, gap })
16758
+ }
16759
+ };
16760
+ }
16761
+ if (clusters.length === 1) {
16762
+ const known = clusters[0];
16763
+ const slot = classifyHeight(known.height);
16764
+ const offset = BUTTON_SIZE_IDS.indexOf(slot);
16765
+ const bySlot = Object.fromEntries(
16766
+ BUTTON_SIZE_IDS.map((id, index) => [id, shiftSample(known, index - offset)])
16767
+ );
16768
+ bySlot[slot] = known;
16769
+ return { status: "ready", tokens: tokensFromSamples(bySlot, known.borderRadius, known.gap) };
16770
+ }
16771
+ const mapped = clusters.map((sample) => ({ id: classifyHeight(sample.height), sample }));
16772
+ if (mapped[0].id === mapped[1].id) {
16773
+ const larger = clusters.slice().sort((a, b) => b.height - a.height)[0];
16774
+ return generateButtonSizes([larger]);
16775
+ }
16776
+ return { status: "ready", tokens: interpolateSizes(mapped) };
16777
+ }
16778
+ function parseJson(raw) {
16779
+ if (!raw) return null;
16780
+ try {
16781
+ return JSON.parse(raw);
16782
+ } catch {
16783
+ return null;
16784
+ }
16785
+ }
16786
+ function isChrome(value) {
16787
+ if (!value || typeof value !== "object") return false;
16788
+ const chrome = value;
16789
+ return typeof chrome.background === "string" && typeof chrome.color === "string";
16790
+ }
16791
+ function isGeometry(value) {
16792
+ if (!value || typeof value !== "object") return false;
16793
+ const geometry = value;
16794
+ return typeof geometry.paddingTop === "string" && typeof geometry.height === "number";
16795
+ }
16796
+ function readVariantTokens(content) {
16797
+ const parsed = parseJson(content[BUTTON_VARIANTS_KEY]);
16798
+ if (!parsed) return null;
16799
+ if (!BUTTON_VARIANT_IDS.every((id) => isChrome(parsed[id]))) return null;
16800
+ return parsed;
16801
+ }
16802
+ function readSizeTokens(content) {
16803
+ const parsed = parseJson(content[BUTTON_SIZES_KEY]);
16804
+ if (!parsed) return null;
16805
+ if (!BUTTON_SIZE_IDS.every((id) => isGeometry(parsed[id]))) return null;
16806
+ return parsed;
16807
+ }
16808
+ function readAssignments(content) {
16809
+ const parsed = parseJson(content[BUTTON_ASSIGNMENTS_KEY]);
16810
+ if (!parsed || typeof parsed !== "object") return {};
16811
+ const next = {};
16812
+ for (const [id, value] of Object.entries(parsed)) {
16813
+ if (!value || !isButtonVariantId(value.variant) || !isButtonSizeId(value.size)) continue;
16814
+ next[id] = { variant: value.variant, size: value.size };
16815
+ }
16816
+ return next;
16817
+ }
16818
+ function cssLength(style, prop) {
16819
+ return style.getPropertyValue(prop) || "0px";
16820
+ }
16821
+ function readVariantSample(el) {
16822
+ const style = getComputedStyle(el);
16823
+ return {
16824
+ background: style.backgroundColor,
16825
+ color: style.color,
16826
+ borderColor: style.borderTopColor,
16827
+ borderWidth: style.borderTopWidth,
16828
+ className: el.className || ""
16829
+ };
16830
+ }
16831
+ function readSizeSample(el) {
16832
+ const style = getComputedStyle(el);
16833
+ const rootFont = parsePx(getComputedStyle(el.ownerDocument.documentElement).fontSize) || 16;
16834
+ const rect = el.getBoundingClientRect();
16835
+ return {
16836
+ height: rect.height,
16837
+ paddingTop: parsePx(cssLength(style, "padding-top"), rootFont),
16838
+ paddingRight: parsePx(cssLength(style, "padding-right"), rootFont),
16839
+ paddingBottom: parsePx(cssLength(style, "padding-bottom"), rootFont),
16840
+ paddingLeft: parsePx(cssLength(style, "padding-left"), rootFont),
16841
+ fontSize: parsePx(style.fontSize, rootFont),
16842
+ borderRadius: parsePx(style.borderTopLeftRadius, rootFont),
16843
+ gap: parsePx(style.columnGap || style.gap, rootFont)
16844
+ };
16845
+ }
16846
+ function readTypography(el) {
16847
+ const style = getComputedStyle(el);
16848
+ return {
16849
+ fontFamily: style.fontFamily,
16850
+ fontWeight: style.fontWeight,
16851
+ letterSpacing: style.letterSpacing,
16852
+ textTransform: style.textTransform,
16853
+ textDecoration: style.textDecorationLine || style.textDecoration || "none"
16854
+ };
16855
+ }
16856
+ var buttonOriginByEl = /* @__PURE__ */ new WeakMap();
16857
+ var ORIGIN_ATTR = "data-ohw-btn-origin";
16858
+ function parseOriginAttr(raw) {
16859
+ if (!raw) return null;
16860
+ try {
16861
+ const value = JSON.parse(raw);
16862
+ if (!value?.variant || !value.size || !value.typography) return null;
16863
+ return value;
16864
+ } catch {
16865
+ return null;
16866
+ }
16867
+ }
16868
+ function measureLiveOrigin(el) {
16869
+ return {
16870
+ variant: readVariantSample(el),
16871
+ size: readSizeSample(el),
16872
+ typography: readTypography(el)
16873
+ };
16874
+ }
16875
+ function captureButtonOrigin(el) {
16876
+ const cached = buttonOriginByEl.get(el);
16877
+ if (cached) return cached;
16878
+ const fromAttr = parseOriginAttr(el.getAttribute(ORIGIN_ATTR));
16879
+ if (fromAttr) {
16880
+ buttonOriginByEl.set(el, fromAttr);
16881
+ return fromAttr;
16882
+ }
16883
+ const origin = measureLiveOrigin(el);
16884
+ buttonOriginByEl.set(el, origin);
16885
+ try {
16886
+ el.setAttribute(ORIGIN_ATTR, JSON.stringify(origin));
16887
+ } catch {
16888
+ }
16889
+ return origin;
16890
+ }
16891
+ function listStyleButtons(doc) {
16892
+ return Array.from(doc.querySelectorAll(CTA_BUTTON_SELECTOR)).filter((el) => {
16893
+ if (el.closest("[data-ohw-bridge], [data-ohw-bridge-root]")) return false;
16894
+ const rect = el.getBoundingClientRect();
16895
+ return rect.width > 0 && rect.height > 0;
16896
+ });
16897
+ }
16898
+ function generateTokensFromButtons(buttons) {
16899
+ const variants = generateButtonVariants(buttons.map(readVariantSample));
16900
+ const sizes = generateButtonSizes(buttons.map(readSizeSample));
16901
+ if (variants.status !== "ready" || sizes.status !== "ready") return { status: "unresolved" };
16902
+ return { status: "ready", tokens: { variants: variants.tokens, sizes: sizes.tokens } };
16903
+ }
16904
+ function tokensFromButton(el) {
16905
+ const origin = captureButtonOrigin(el);
16906
+ const sample = origin.variant;
16907
+ const variants = generateButtonVariants([sample]);
16908
+ const sizes = generateButtonSizes([origin.size]);
16909
+ const primary = chromeFrom(sample);
16910
+ return {
16911
+ variants: variants.status === "ready" ? variants.tokens : { primary, secondary: generatedSecondary(primary), ghost: generatedGhost(primary) },
16912
+ sizes: sizes.status === "ready" ? sizes.tokens : tokensFromSamples(REFERENCE_SIZES, 8, 8)
16913
+ };
16914
+ }
16915
+ function readStoredStyleTokens(content) {
16916
+ const variants = readVariantTokens(content);
16917
+ const sizes = readSizeTokens(content);
16918
+ if (!variants || !sizes) return null;
16919
+ return { variants, sizes };
16920
+ }
16921
+ function styleTokenNodes(tokens) {
16922
+ return [
16923
+ { key: BUTTON_VARIANTS_KEY, text: JSON.stringify(tokens.variants) },
16924
+ { key: BUTTON_SIZES_KEY, text: JSON.stringify(tokens.sizes) }
16925
+ ];
16926
+ }
16927
+ function ensureButtonStyleTokens(doc, content) {
16928
+ const stored = readStoredStyleTokens(content);
16929
+ if (stored) return { tokens: stored, nodes: [] };
16930
+ const generated = generateTokensFromButtons(listStyleButtons(doc));
16931
+ if (generated.status !== "ready") return { tokens: null, nodes: [] };
16932
+ return { tokens: generated.tokens, nodes: styleTokenNodes(generated.tokens) };
16933
+ }
16934
+ function detectVariant(el, tokens) {
16935
+ const declared = declaredVariant(el.className || "");
16936
+ if (declared) return declared;
16937
+ const stamped = el.getAttribute("data-ohw-btn-variant");
16938
+ if (stamped && isButtonVariantId(stamped)) return stamped;
16939
+ const sample = readVariantSample(el);
16940
+ if (!tokens) return sampleSlot(sample);
16941
+ const key = fingerprint(sample);
16942
+ for (const id of BUTTON_VARIANT_IDS) {
16943
+ const chrome = tokens[id];
16944
+ const tokenKey = fingerprint({
16945
+ background: chrome.background,
16946
+ color: chrome.color,
16947
+ borderColor: chrome.borderColor,
16948
+ borderWidth: chrome.borderWidth,
16949
+ className: `btn btn--${id}`
16950
+ });
16951
+ if (tokenKey === key) return id;
16952
+ }
16953
+ return sampleSlot(sample);
16954
+ }
16955
+ function detectSize(el, tokens) {
16956
+ const stamped = el.getAttribute("data-ohw-btn-size");
16957
+ if (stamped && isButtonSizeId(stamped)) return stamped;
16958
+ const height = el.getBoundingClientRect().height;
16959
+ if (!tokens) return classifyHeight(height);
16960
+ let best = "m";
16961
+ let bestDelta = Number.POSITIVE_INFINITY;
16962
+ for (const id of BUTTON_SIZE_IDS) {
16963
+ const delta = Math.abs(tokens[id].height - height);
16964
+ if (delta < bestDelta) {
16965
+ best = id;
16966
+ bestDelta = delta;
16967
+ }
16968
+ }
16969
+ return best;
16970
+ }
16971
+ function assignmentFor(el, content, tokens) {
16972
+ const id = buttonId(el);
16973
+ const stored = id ? readAssignments(content)[id] : void 0;
16974
+ if (stored) return stored;
16975
+ return {
16976
+ variant: detectVariant(el, tokens?.variants ?? null),
16977
+ size: detectSize(el, tokens?.sizes ?? null)
16978
+ };
16979
+ }
16980
+ function labelElement(el) {
16981
+ return el.querySelector('[data-ohw-editable="text"], [data-ohw-editable="plain"]') ?? (el.matches('[data-ohw-editable="text"], [data-ohw-editable="plain"]') ? el : null);
16982
+ }
16983
+ var paintLockByEl = /* @__PURE__ */ new WeakMap();
16984
+ var paintObserverByEl = /* @__PURE__ */ new WeakMap();
16985
+ var paintingButton = false;
16986
+ function paintButtonChrome(el, assignment, tokens) {
16987
+ const origin = captureButtonOrigin(el);
16988
+ const chrome = tokens.variants[assignment.variant];
16989
+ const originSlot = classifyHeight(origin.size.height);
16990
+ const geometry = assignment.size === originSlot ? geometryFromSample({ ...origin.size, borderRadius: origin.size.borderRadius, gap: origin.size.gap }) : tokens.sizes[assignment.size];
16991
+ const { typography } = origin;
16992
+ el.setAttribute("data-ohw-btn-variant", assignment.variant);
16993
+ el.setAttribute("data-ohw-btn-size", assignment.size);
16994
+ el.style.transition = "none";
16995
+ el.style.boxSizing = "border-box";
16996
+ el.style.display = "inline-flex";
16997
+ el.style.alignItems = "center";
16998
+ el.style.justifyContent = "center";
16999
+ el.style.background = chrome.background;
17000
+ el.style.color = chrome.color;
17001
+ el.style.borderStyle = parseFloat(chrome.borderWidth) > 0 ? "solid" : "none";
17002
+ el.style.borderColor = chrome.borderColor;
17003
+ el.style.borderWidth = chrome.borderWidth;
17004
+ el.style.padding = `${geometry.paddingTop} ${geometry.paddingRight} ${geometry.paddingBottom} ${geometry.paddingLeft}`;
17005
+ el.style.fontSize = geometry.fontSize;
17006
+ el.style.borderRadius = px(origin.size.borderRadius);
17007
+ el.style.gap = geometry.gap;
17008
+ el.style.height = px(geometry.height);
17009
+ el.style.fontFamily = typography.fontFamily;
17010
+ el.style.fontWeight = typography.fontWeight;
17011
+ el.style.letterSpacing = typography.letterSpacing;
17012
+ el.style.textTransform = typography.textTransform;
17013
+ if (typography.textDecoration && typography.textDecoration !== "none") {
17014
+ el.style.textDecoration = typography.textDecoration;
17015
+ } else {
17016
+ el.style.textDecoration = "none";
17017
+ }
17018
+ const label = labelElement(el);
17019
+ if (label && label !== el) label.style.color = chrome.color;
17020
+ }
17021
+ function guardTemplateHover(el) {
17022
+ if (paintObserverByEl.has(el)) return;
17023
+ const observer = new MutationObserver(() => {
17024
+ if (paintingButton) return;
17025
+ const lock = paintLockByEl.get(el);
17026
+ if (!lock) return;
17027
+ paintingButton = true;
17028
+ paintButtonChrome(el, lock.assignment, lock.tokens);
17029
+ paintingButton = false;
17030
+ });
17031
+ observer.observe(el, { attributes: true, attributeFilter: ["style", "class"] });
17032
+ paintObserverByEl.set(el, observer);
17033
+ }
17034
+ function paintedTokensOf(el) {
17035
+ return paintLockByEl.get(el)?.tokens ?? null;
17036
+ }
17037
+ function applyButtonStyle(el, assignment, tokens) {
17038
+ paintLockByEl.set(el, { assignment, tokens });
17039
+ paintingButton = true;
17040
+ paintButtonChrome(el, assignment, tokens);
17041
+ paintingButton = false;
17042
+ guardTemplateHover(el);
17043
+ }
17044
+ function applyButtonStylesFromContent(doc, content) {
17045
+ const assignments = readAssignments(content);
17046
+ for (const [id, assignment] of Object.entries(assignments)) {
17047
+ const el = doc.querySelector(`[data-ohw-href-key="${CSS.escape(id)}"]`);
17048
+ if (!el) continue;
17049
+ applyButtonStyle(el, assignment, tokensFromButton(el));
17050
+ }
17051
+ }
17052
+ function withAssignment(content, id, assignment) {
17053
+ return { ...readAssignments(content), [id]: assignment };
17054
+ }
17055
+
17056
+ // src/ui/button-style-panel.tsx
17057
+ import { jsx as jsx31, jsxs as jsxs21 } from "react/jsx-runtime";
17058
+ var SIZE_LABEL = { s: "S", m: "M", l: "L" };
17059
+ function ButtonStylePanel({
17060
+ variants,
17061
+ sizes,
17062
+ variant,
17063
+ size,
17064
+ onVariant,
17065
+ onSize,
17066
+ previewLabel = "Book Now",
17067
+ slotBackground = "var(--brand-background, var(--ohw-brand-light, var(--color-light, #ffffff)))"
17068
+ }) {
17069
+ const preview = scaleGeometry(sizes.m, 0.75);
17070
+ const label = previewLabel.trim() || "Book Now";
17071
+ return /* @__PURE__ */ jsxs21("div", { className: "flex w-full flex-col gap-5", children: [
17072
+ /* @__PURE__ */ jsxs21("section", { className: "flex w-full flex-col gap-2.5", children: [
17073
+ /* @__PURE__ */ jsx31("h3", { className: "text-xs font-semibold uppercase leading-4 tracking-[0.6px] text-stone-500 font-sans", children: "Variants" }),
17074
+ /* @__PURE__ */ jsx31("div", { className: "flex w-full flex-col gap-2.5", children: BUTTON_VARIANT_IDS.map((id) => {
17075
+ const chrome = variants[id];
17076
+ const selected = id === variant;
17077
+ const hasBorder = parseFloat(chrome.borderWidth) > 0;
17078
+ const cardSlot = previewSlotForChrome(chrome, slotBackground);
17079
+ return /* @__PURE__ */ jsxs21(
17080
+ "button",
17081
+ {
17082
+ type: "button",
17083
+ "aria-pressed": selected,
17084
+ "aria-label": id,
17085
+ onMouseDown: (e) => {
17086
+ e.preventDefault();
17087
+ e.stopPropagation();
17088
+ },
17089
+ onClick: () => onVariant(id),
17090
+ className: cn(
17091
+ "relative w-full overflow-hidden rounded-xl border-solid text-left",
17092
+ selected ? "border-2 border-primary" : "border border-border"
17093
+ ),
17094
+ children: [
17095
+ /* @__PURE__ */ jsx31(
17096
+ "div",
17097
+ {
17098
+ className: "flex w-full items-center justify-center py-4",
17099
+ style: { background: cardSlot },
17100
+ children: /* @__PURE__ */ jsx31(
17101
+ "span",
17102
+ {
17103
+ className: "inline-flex items-center justify-center whitespace-nowrap font-semibold uppercase leading-none",
17104
+ style: {
17105
+ background: chrome.background,
17106
+ color: chrome.color,
17107
+ borderColor: chrome.borderColor,
17108
+ borderWidth: hasBorder ? chrome.borderWidth : "0px",
17109
+ borderStyle: hasBorder ? "solid" : "none",
17110
+ padding: `${preview.paddingTop} ${preview.paddingRight} ${preview.paddingBottom} ${preview.paddingLeft}`,
17111
+ fontSize: preview.fontSize,
17112
+ borderRadius: preview.borderRadius,
17113
+ height: preview.height,
17114
+ boxSizing: "border-box",
17115
+ letterSpacing: "0.12em"
17116
+ },
17117
+ children: label
17118
+ }
17119
+ )
17120
+ }
17121
+ ),
17122
+ selected ? /* @__PURE__ */ jsx31(
17123
+ "span",
17124
+ {
17125
+ className: "absolute right-0.5 top-0.5 flex size-5 items-center justify-center rounded-full bg-primary text-primary-foreground",
17126
+ "aria-hidden": true,
17127
+ children: /* @__PURE__ */ jsx31(Check3, { className: "size-3", strokeWidth: 3 })
17128
+ }
17129
+ ) : null
17130
+ ]
17131
+ },
17132
+ id
17133
+ );
17134
+ }) })
17135
+ ] }),
17136
+ /* @__PURE__ */ jsxs21("section", { className: "flex w-full flex-col gap-2.5", children: [
17137
+ /* @__PURE__ */ jsx31("h3", { className: "text-xs font-semibold uppercase leading-4 tracking-[0.6px] text-stone-500 font-sans", children: "Size" }),
17138
+ /* @__PURE__ */ jsx31(
17139
+ "div",
17140
+ {
17141
+ className: "flex w-full items-start rounded-lg bg-stone-100 p-1",
17142
+ role: "radiogroup",
17143
+ "aria-label": "Size",
17144
+ children: BUTTON_SIZE_IDS.map((id) => {
17145
+ const selected = id === size;
17146
+ return /* @__PURE__ */ jsx31(
17147
+ "button",
17148
+ {
17149
+ type: "button",
17150
+ role: "radio",
17151
+ "aria-checked": selected,
17152
+ onMouseDown: (e) => {
17153
+ e.preventDefault();
17154
+ e.stopPropagation();
17155
+ },
17156
+ onClick: () => onSize(id),
17157
+ className: cn(
17158
+ "flex min-w-14 flex-1 items-center justify-center rounded-md px-3 py-1.5 text-sm font-medium leading-5",
17159
+ selected ? "bg-white text-stone-950 shadow-sm" : "bg-transparent text-stone-500"
17160
+ ),
17161
+ children: SIZE_LABEL[id]
17162
+ },
17163
+ id
17164
+ );
17165
+ })
17166
+ }
17167
+ )
17168
+ ] })
17169
+ ] });
17170
+ }
17171
+
17172
+ // src/lib/button-collections.ts
17173
+ var BUTTON_COLLECTIONS_KEY = "__ohw_button_collections";
17174
+ var BUTTON_SNAPSHOTS_KEY = "__ohw_button_snapshots";
17175
+ var BUTTON_HIDDEN_KEY = "__ohw_button_hidden";
17176
+ var COLLECTION_ATTR = "data-ohw-button-collection";
17177
+ function parseJson2(raw) {
17178
+ if (!raw) return null;
17179
+ try {
17180
+ return JSON.parse(raw);
17181
+ } catch {
17182
+ return null;
17183
+ }
17184
+ }
17185
+ function readCollections(content) {
17186
+ const parsed = parseJson2(content[BUTTON_COLLECTIONS_KEY]);
17187
+ if (!parsed || typeof parsed !== "object") return {};
17188
+ const next = {};
17189
+ for (const [id, value] of Object.entries(parsed)) {
17190
+ if (!value || !Array.isArray(value.members)) continue;
17191
+ next[id] = {
17192
+ id,
17193
+ members: value.members.filter((member) => typeof member === "string" && member),
17194
+ gap: typeof value.gap === "string" ? value.gap : "12px"
17195
+ };
17196
+ }
17197
+ return next;
17198
+ }
17199
+ function readSnapshots(content) {
17200
+ const parsed = parseJson2(content[BUTTON_SNAPSHOTS_KEY]);
17201
+ if (!parsed || typeof parsed !== "object") return {};
17202
+ const next = {};
17203
+ for (const [id, value] of Object.entries(parsed)) {
17204
+ if (!value || typeof value.html !== "string") continue;
17205
+ next[id] = value;
17206
+ }
17207
+ return next;
17208
+ }
17209
+ function readHiddenButtons(content) {
17210
+ const parsed = parseJson2(content[BUTTON_HIDDEN_KEY]);
17211
+ if (!Array.isArray(parsed)) return [];
17212
+ return parsed.filter((id) => typeof id === "string" && id);
17213
+ }
17214
+ function buttonCollectionOf(el) {
17215
+ return el?.closest(`[${COLLECTION_ATTR}]`) ?? null;
17216
+ }
17217
+ function isNavbarCta(el) {
17218
+ return el.getAttribute("data-ohw-role") === "navbar-button" || Boolean(el.closest('[data-ohw-role="navbar-button"]'));
17219
+ }
17220
+ function navbarScope(el) {
17221
+ return el.closest("nav, [data-ohw-nav-container], [data-ohw-nav-drawer]") ?? el.ownerDocument;
17222
+ }
17223
+ function isLastNavbarButton(el) {
17224
+ if (!isNavbarCta(el)) return false;
17225
+ const buttons = Array.from(navbarScope(el).querySelectorAll(CTA_BUTTON_SELECTOR)).filter(
17226
+ (button) => isNavbarCta(button) && buttonId(button)
17227
+ );
17228
+ return buttons.length <= 1;
17229
+ }
17230
+ function canDeleteButton(el) {
17231
+ return !isLastNavbarButton(el);
17232
+ }
17233
+ function canDuplicateButton(el) {
17234
+ return isNavbarCta(el);
17235
+ }
17236
+ function labelElement2(el) {
17237
+ return el.querySelector('[data-ohw-editable="text"], [data-ohw-editable="plain"]') ?? el.querySelector("[data-ohw-key]");
17238
+ }
17239
+ function buttonLabel(el) {
17240
+ return (labelElement2(el)?.textContent ?? el.textContent ?? "").trim();
17241
+ }
17242
+ function buttonHref(el) {
17243
+ return el.getAttribute("href") ?? "";
17244
+ }
17245
+ function hrefKeyBase(hrefKey) {
17246
+ return hrefKey.replace(/-href$/, "").replace(/-copy-\d+$/, "");
17247
+ }
17248
+ function labelKeyForHrefKey(hrefKey) {
17249
+ return `${hrefKey.replace(/-href$/, "")}-label`;
17250
+ }
17251
+ function cleanClone(el) {
17252
+ const copy = el.cloneNode(true);
17253
+ copy.querySelectorAll("[data-ohw-selected], [data-ohw-hovered], [data-ohw-editing]").forEach((node) => {
17254
+ node.removeAttribute("data-ohw-selected");
17255
+ node.removeAttribute("data-ohw-hovered");
17256
+ node.removeAttribute("data-ohw-editing");
17257
+ node.removeAttribute("contenteditable");
17258
+ });
17259
+ copy.removeAttribute("data-ohw-selected");
17260
+ copy.removeAttribute("data-ohw-hovered");
17261
+ copy.removeAttribute("data-ohw-editing");
17262
+ copy.removeAttribute("contenteditable");
17263
+ copy.removeAttribute("data-ohw-drag-disabled");
17264
+ return copy;
17265
+ }
17266
+ function nextCopyId(sourceId, content, doc) {
17267
+ const used = /* @__PURE__ */ new Set([
17268
+ ...Object.keys(readSnapshots(content)),
17269
+ ...Object.keys(readAssignments(content)),
17270
+ ...Array.from(doc.querySelectorAll("[data-ohw-href-key]")).map(
17271
+ (el) => el.getAttribute("data-ohw-href-key") ?? ""
17272
+ )
17273
+ ]);
17274
+ const base = hrefKeyBase(sourceId);
17275
+ let n = 2;
17276
+ while (used.has(`${base}-copy-${n}-href`)) n += 1;
17277
+ return `${base}-copy-${n}-href`;
17278
+ }
17279
+ function nextCollectionId(content, doc) {
17280
+ const used = /* @__PURE__ */ new Set([
17281
+ ...Object.keys(readCollections(content)),
17282
+ ...Array.from(doc.querySelectorAll(`[${COLLECTION_ATTR}]`)).map(
17283
+ (el) => el.getAttribute(COLLECTION_ATTR) ?? ""
17284
+ )
17285
+ ]);
17286
+ let n = 1;
17287
+ while (used.has(`bc-${n}`)) n += 1;
17288
+ return `bc-${n}`;
17289
+ }
17290
+ var COLLECTION_GAP_MAX_PX = 12;
17291
+ var COLLECTION_GAP_DEFAULT = "12px";
17292
+ function usableGap(value) {
17293
+ if (!value || value === "normal") return null;
17294
+ const n = parseFloat(value);
17295
+ if (!Number.isFinite(n) || n <= 0) return null;
17296
+ if (n > COLLECTION_GAP_MAX_PX) return COLLECTION_GAP_DEFAULT;
17297
+ return `${Math.round(n * 100) / 100}px`;
17298
+ }
17299
+ function collectionGap(el, tokens) {
17300
+ const fromCollection = usableGap(buttonCollectionOf(el)?.style.gap);
17301
+ if (fromCollection) return fromCollection;
17302
+ const parent = el.parentElement;
17303
+ if (parent) {
17304
+ const style = getComputedStyle(parent);
17305
+ const fromParent = usableGap(style.columnGap) ?? usableGap(style.gap);
17306
+ if (fromParent) return fromParent;
17307
+ }
17308
+ const fromTokens = usableGap(tokens?.sizes.m.gap);
17309
+ if (fromTokens) return fromTokens;
17310
+ return COLLECTION_GAP_DEFAULT;
17311
+ }
17312
+ function styleCollection(collection, gap) {
17313
+ collection.style.display = "inline-flex";
17314
+ collection.style.alignItems = "center";
17315
+ collection.style.flexWrap = "wrap";
17316
+ collection.style.gap = gap;
17317
+ collection.style.maxWidth = "100%";
17318
+ }
17319
+ function createCollection(doc, id, gap) {
17320
+ const collection = doc.createElement("div");
17321
+ collection.setAttribute(COLLECTION_ATTR, id);
17322
+ styleCollection(collection, gap);
17323
+ return collection;
17324
+ }
17325
+ function setButtonDragAttr(el) {
17326
+ if (isNavbarCta(el) && buttonCollectionOf(el)) el.removeAttribute("data-ohw-drag-disabled");
17327
+ else el.setAttribute("data-ohw-drag-disabled", "true");
17328
+ }
17329
+ function rekeyCopy(copy, id) {
17330
+ copy.setAttribute("data-ohw-href-key", id);
17331
+ const label = labelElement2(copy);
17332
+ const labelKey = labelKeyForHrefKey(id);
17333
+ if (label) label.setAttribute("data-ohw-key", labelKey);
17334
+ setButtonDragAttr(copy);
17335
+ return { labelKey };
17336
+ }
17337
+ function stampVariantClass(el, variant) {
17338
+ const raw = typeof el.className === "string" ? el.className : el.getAttribute("class") || "";
17339
+ const without = raw.replace(/\bbtn--(?:primary|secondary|ghost)\b/g, "").replace(/\s+/g, " ").trim();
17340
+ el.className = without ? `${without} btn--${variant}` : `btn btn--${variant}`;
17341
+ }
17342
+ function snapshotOf(el) {
17343
+ const id = buttonId(el) ?? "";
17344
+ const label = labelElement2(el);
17345
+ return {
17346
+ id,
17347
+ href: buttonHref(el),
17348
+ label: buttonLabel(el),
17349
+ labelKey: label?.getAttribute("data-ohw-key") ?? labelKeyForHrefKey(id),
17350
+ html: cleanClone(el).outerHTML
17351
+ };
17352
+ }
17353
+ function htmlToElement(doc, html) {
17354
+ const wrap = doc.createElement("div");
17355
+ wrap.innerHTML = html;
17356
+ const el = wrap.firstElementChild;
17357
+ return el instanceof HTMLElement ? el : null;
17358
+ }
17359
+ function findButton(doc, id) {
17360
+ return doc.querySelector(`[data-ohw-href-key="${CSS.escape(id)}"]`);
17361
+ }
17362
+ function duplicateButton(source, content) {
17363
+ if (!canDuplicateButton(source)) return null;
17364
+ const sourceId = buttonId(source);
17365
+ if (!sourceId) return null;
17366
+ const doc = source.ownerDocument;
17367
+ const tokens = tokensFromButton(source);
17368
+ const copyId = nextCopyId(sourceId, content, doc);
17369
+ const copy = cleanClone(source);
17370
+ const { labelKey } = rekeyCopy(copy, copyId);
17371
+ const href = buttonHref(source);
17372
+ const label = buttonLabel(source);
17373
+ if (href) copy.setAttribute("href", href);
17374
+ const sourceAssignment = readAssignments(content)[sourceId] ?? assignmentFor(source, content, tokens);
17375
+ const copyAssignment = { ...sourceAssignment };
17376
+ stampVariantClass(copy, copyAssignment.variant);
17377
+ applyButtonStyle(copy, copyAssignment, paintedTokensOf(source) ?? tokens);
17378
+ const copyLabelEl = labelElement2(copy);
17379
+ if (copyLabelEl) copyLabelEl.textContent = label;
17380
+ let collection = buttonCollectionOf(source);
17381
+ const collections = readCollections(content);
17382
+ let collectionId = collection?.getAttribute(COLLECTION_ATTR) ?? "";
17383
+ let members = collectionId && collections[collectionId] ? [...collections[collectionId].members] : [];
17384
+ const gap = collectionGap(source, tokens);
17385
+ if (!collection) {
17386
+ collectionId = nextCollectionId(content, doc);
17387
+ collection = createCollection(doc, collectionId, gap);
17388
+ source.parentElement?.insertBefore(collection, source);
17389
+ collection.append(source, copy);
17390
+ setButtonDragAttr(source);
17391
+ setButtonDragAttr(copy);
17392
+ members = [sourceId, copyId];
17393
+ } else {
17394
+ styleCollection(collection, gap);
17395
+ source.after(copy);
17396
+ setButtonDragAttr(copy);
17397
+ if (!members.includes(sourceId)) members = listCollectionMembers(collection);
17398
+ const at = members.indexOf(sourceId);
17399
+ members.splice(at >= 0 ? at + 1 : members.length, 0, copyId);
17400
+ }
17401
+ const sourceLabelKey = labelElement2(source)?.getAttribute("data-ohw-key");
17402
+ const snapshots = {
17403
+ ...readSnapshots(content),
17404
+ [sourceId]: snapshotOf(source),
17405
+ [copyId]: snapshotOf(copy)
17406
+ };
17407
+ const nextCollections = {
17408
+ ...collections,
17409
+ [collectionId]: { id: collectionId, members, gap }
17410
+ };
17411
+ const assignments = withAssignment(content, copyId, copyAssignment);
17412
+ const nextContent = {
17413
+ ...content,
17414
+ [copyId]: href,
17415
+ [labelKey]: label,
17416
+ [BUTTON_COLLECTIONS_KEY]: JSON.stringify(nextCollections),
17417
+ [BUTTON_SNAPSHOTS_KEY]: JSON.stringify(snapshots),
17418
+ [BUTTON_ASSIGNMENTS_KEY]: JSON.stringify(assignments)
17419
+ };
17420
+ if (sourceLabelKey && nextContent[sourceLabelKey] === void 0) {
17421
+ nextContent[sourceLabelKey] = label;
17422
+ }
17423
+ const nodeKeys = [
17424
+ copyId,
17425
+ labelKey,
17426
+ BUTTON_COLLECTIONS_KEY,
17427
+ BUTTON_SNAPSHOTS_KEY,
17428
+ BUTTON_ASSIGNMENTS_KEY
17429
+ ];
17430
+ if (sourceLabelKey) nodeKeys.push(sourceLabelKey);
17431
+ return {
17432
+ copy,
17433
+ content: nextContent,
17434
+ nodes: contentNodes(nextContent, nodeKeys)
17435
+ };
17436
+ }
17437
+ function listCollectionMembers(collection) {
17438
+ return Array.from(collection.querySelectorAll(CTA_BUTTON_SELECTOR)).map((el) => buttonId(el)).filter((id) => Boolean(id));
17439
+ }
17440
+ function contentNodes(content, keys) {
17441
+ return keys.filter((key) => content[key] !== void 0).map((key) => ({ key, text: content[key] }));
17442
+ }
17443
+ function deleteButton(source, content) {
17444
+ if (!canDeleteButton(source)) return null;
17445
+ const id = buttonId(source);
17446
+ if (!id) return null;
17447
+ const parent = source.parentElement;
17448
+ const nextSibling = source.nextSibling;
17449
+ const removed = source;
17450
+ const collection = buttonCollectionOf(source);
17451
+ const collectionId = collection?.getAttribute(COLLECTION_ATTR) ?? "";
17452
+ const previousCollections = readCollections(content);
17453
+ const previousHidden = readHiddenButtons(content);
17454
+ const snapshots = { ...readSnapshots(content) };
17455
+ const assignments = { ...readAssignments(content) };
17456
+ delete assignments[id];
17457
+ const remainingBeforeUnwrap = collection ? listCollectionMembers(collection).filter((member) => member !== id) : [];
17458
+ removed.remove();
17459
+ let nextCollections = { ...previousCollections };
17460
+ let unwrapped = null;
17461
+ if (collection && collectionId) {
17462
+ const members = listCollectionMembers(collection);
17463
+ if (members.length <= 1) {
17464
+ unwrapped = collection;
17465
+ unwrapCollection(collection);
17466
+ delete nextCollections[collectionId];
17467
+ } else {
17468
+ nextCollections[collectionId] = {
17469
+ id: collectionId,
17470
+ members,
17471
+ gap: collection.style.gap || previousCollections[collectionId]?.gap || "12px"
17472
+ };
17473
+ }
17474
+ }
17475
+ const hidden = previousHidden.includes(id) ? previousHidden : [...previousHidden, id];
17476
+ const nextContent = {
17477
+ ...content,
17478
+ [id]: "",
17479
+ [BUTTON_COLLECTIONS_KEY]: JSON.stringify(nextCollections),
17480
+ [BUTTON_HIDDEN_KEY]: JSON.stringify(hidden),
17481
+ [BUTTON_SNAPSHOTS_KEY]: JSON.stringify(snapshots),
17482
+ [BUTTON_ASSIGNMENTS_KEY]: JSON.stringify(assignments)
17483
+ };
17484
+ return {
17485
+ content: nextContent,
17486
+ nodes: contentNodes(nextContent, [id, BUTTON_COLLECTIONS_KEY, BUTTON_HIDDEN_KEY, BUTTON_SNAPSHOTS_KEY, BUTTON_ASSIGNMENTS_KEY]),
17487
+ undo: () => {
17488
+ if (unwrapped && remainingBeforeUnwrap.length === 1) {
17489
+ const remaining = findButton(removed.ownerDocument, remainingBeforeUnwrap[0]);
17490
+ const host = remaining?.parentElement;
17491
+ if (remaining && host) {
17492
+ const restoredCollection = createCollection(
17493
+ removed.ownerDocument,
17494
+ collectionId,
17495
+ previousCollections[collectionId]?.gap || "12px"
17496
+ );
17497
+ host.insertBefore(restoredCollection, remaining);
17498
+ restoredCollection.append(remaining, removed);
17499
+ return;
17500
+ }
17501
+ }
17502
+ if (nextSibling && nextSibling.parentNode === parent) parent?.insertBefore(removed, nextSibling);
17503
+ else parent?.appendChild(removed);
17504
+ }
17505
+ };
17506
+ }
17507
+ function unwrapCollection(collection) {
17508
+ const parent = collection.parentElement;
17509
+ if (!parent) return;
17510
+ while (collection.firstChild) parent.insertBefore(collection.firstChild, collection);
17511
+ collection.remove();
17512
+ }
17513
+ function reorderButtonInCollection(button, clientX) {
17514
+ const collection = buttonCollectionOf(button);
17515
+ const id = buttonId(button);
17516
+ if (!collection || !id) return null;
17517
+ const siblings = Array.from(collection.querySelectorAll(CTA_BUTTON_SELECTOR)).filter((el) => el !== button);
17518
+ let placed = false;
17519
+ for (const sibling of siblings) {
17520
+ const rect = sibling.getBoundingClientRect();
17521
+ if (clientX < rect.left + rect.width / 2) {
17522
+ sibling.before(button);
17523
+ placed = true;
17524
+ break;
17525
+ }
17526
+ }
17527
+ if (!placed) collection.append(button);
17528
+ return listCollectionMembers(collection);
17529
+ }
17530
+ function listVisibleCollectionButtons(collection) {
17531
+ return Array.from(collection.querySelectorAll(CTA_BUTTON_SELECTOR)).filter((el) => {
17532
+ if (!buttonId(el)) return false;
17533
+ return el.getClientRects().length > 0;
17534
+ });
17535
+ }
17536
+ function isVerticalButtonLayout(items, collection) {
17537
+ if (items.length >= 2) {
17538
+ const a = items[0].getBoundingClientRect();
17539
+ const b = items[1].getBoundingClientRect();
17540
+ return b.top - a.bottom > b.left - a.right;
17541
+ }
17542
+ const style = window.getComputedStyle(collection);
17543
+ return style.flexDirection === "column" || style.flexDirection === "column-reverse";
17544
+ }
17545
+ function buildButtonCollectionDropSlots(collection) {
17546
+ const items = listVisibleCollectionButtons(collection);
17547
+ const barThickness = 3;
17548
+ const slots = [];
17549
+ if (items.length === 0) {
17550
+ const rect = collection.getBoundingClientRect();
17551
+ slots.push({
17552
+ insertIndex: 0,
17553
+ left: rect.left + rect.width / 2 - barThickness / 2,
17554
+ top: rect.top,
17555
+ width: barThickness,
17556
+ height: Math.max(rect.height, 24),
17557
+ direction: "vertical"
17558
+ });
17559
+ return slots;
17560
+ }
17561
+ if (isVerticalButtonLayout(items, collection)) {
17562
+ const edgeGap2 = items.length >= 2 ? Math.max(0, items[1].getBoundingClientRect().top - items[0].getBoundingClientRect().bottom) : 16;
17563
+ for (let i = 0; i <= items.length; i++) {
17564
+ let top;
17565
+ let left;
17566
+ let width;
17567
+ if (i === 0) {
17568
+ const first = items[0].getBoundingClientRect();
17569
+ top = first.top - edgeGap2 / 2 - barThickness / 2;
17570
+ left = first.left;
17571
+ width = first.width;
17572
+ } else if (i === items.length) {
17573
+ const last = items[items.length - 1].getBoundingClientRect();
17574
+ const lastGap = items.length >= 2 ? Math.max(0, last.top - items[items.length - 2].getBoundingClientRect().bottom) : edgeGap2;
17575
+ top = last.bottom + lastGap / 2 - barThickness / 2;
17576
+ left = last.left;
17577
+ width = last.width;
17578
+ } else {
17579
+ const prev = items[i - 1].getBoundingClientRect();
17580
+ const next = items[i].getBoundingClientRect();
17581
+ top = (prev.bottom + next.top) / 2 - barThickness / 2;
17582
+ left = Math.min(prev.left, next.left);
17583
+ width = Math.max(prev.right, next.right) - left;
17584
+ }
17585
+ slots.push({
17586
+ insertIndex: i,
17587
+ left,
17588
+ top,
17589
+ width: Math.max(width, 40),
17590
+ height: barThickness,
17591
+ direction: "horizontal"
17592
+ });
17593
+ }
17594
+ return slots;
17595
+ }
17596
+ const edgeGap = items.length >= 2 ? Math.max(0, items[1].getBoundingClientRect().left - items[0].getBoundingClientRect().right) : 12;
17597
+ for (let i = 0; i <= items.length; i++) {
17598
+ let left;
17599
+ let top;
17600
+ let height;
17601
+ if (i === 0) {
17602
+ const first = items[0].getBoundingClientRect();
17603
+ left = first.left - edgeGap / 2 - barThickness / 2;
17604
+ top = first.top;
17605
+ height = first.height;
17606
+ } else if (i === items.length) {
17607
+ const last = items[items.length - 1].getBoundingClientRect();
17608
+ const lastGap = items.length >= 2 ? Math.max(0, last.left - items[items.length - 2].getBoundingClientRect().right) : edgeGap;
17609
+ left = last.right + lastGap / 2 - barThickness / 2;
17610
+ top = last.top;
17611
+ height = last.height;
17612
+ } else {
17613
+ const prev = items[i - 1].getBoundingClientRect();
17614
+ const next = items[i].getBoundingClientRect();
17615
+ left = (prev.right + next.left) / 2 - barThickness / 2;
17616
+ top = Math.min(prev.top, next.top);
17617
+ height = Math.max(prev.bottom, next.bottom) - top;
17618
+ }
17619
+ slots.push({
17620
+ insertIndex: i,
17621
+ left,
17622
+ top,
17623
+ width: barThickness,
17624
+ height: Math.max(height, 24),
17625
+ direction: "vertical"
17626
+ });
17627
+ }
17628
+ return slots;
17629
+ }
17630
+ function hitTestButtonCollectionDropSlot(collection, clientX, clientY) {
17631
+ const slots = buildButtonCollectionDropSlots(collection);
17632
+ let best = null;
17633
+ for (const slot of slots) {
17634
+ const cx2 = slot.left + slot.width / 2;
17635
+ const cy = slot.top + slot.height / 2;
17636
+ const dist = slot.direction === "vertical" ? Math.abs(clientX - cx2) + Math.abs(clientY - cy) * 0.25 : Math.abs(clientY - cy) + Math.abs(clientX - cx2) * 0.25;
17637
+ if (!best || dist < best.dist) best = { slot, dist };
17638
+ }
17639
+ return best?.slot ?? null;
17640
+ }
17641
+ function insertButtonAtCollectionIndex(button, insertIndex) {
17642
+ const collection = buttonCollectionOf(button);
17643
+ if (!collection) return null;
17644
+ const items = listVisibleCollectionButtons(collection);
17645
+ const from = items.indexOf(button);
17646
+ if (from < 0) return null;
17647
+ const others = items.filter((el) => el !== button);
17648
+ let index = insertIndex;
17649
+ if (from < insertIndex) index -= 1;
17650
+ if (index <= 0) {
17651
+ const first = others[0];
17652
+ if (first) first.before(button);
17653
+ else collection.append(button);
17654
+ } else if (index >= others.length) {
17655
+ collection.append(button);
17656
+ } else {
17657
+ others[index].before(button);
17658
+ }
17659
+ return listCollectionMembers(collection);
17660
+ }
17661
+ function persistCollectionOrder(collection, content) {
17662
+ const id = collection.getAttribute(COLLECTION_ATTR);
17663
+ if (!id) return null;
17664
+ const collections = readCollections(content);
17665
+ const current = collections[id];
17666
+ const members = listCollectionMembers(collection);
17667
+ const next = {
17668
+ ...collections,
17669
+ [id]: { id, members, gap: collection.style.gap || current?.gap || "12px" }
17670
+ };
17671
+ const nextContent = { ...content, [BUTTON_COLLECTIONS_KEY]: JSON.stringify(next) };
17672
+ return { content: nextContent, nodes: [{ key: BUTTON_COLLECTIONS_KEY, text: nextContent[BUTTON_COLLECTIONS_KEY] }] };
17673
+ }
17674
+ function hideButton(el) {
17675
+ if (el.getAttribute("data-ohw-button-hidden") === "") return;
17676
+ el.setAttribute("data-ohw-button-hidden", "");
17677
+ el.style.display = "none";
17678
+ }
17679
+ function syncButtonsFromContent(doc, content) {
17680
+ reconcileButtonCollections(doc, content);
17681
+ applyButtonContentFromContent(doc, content);
17682
+ applyButtonStylesFromContent(doc, content);
17683
+ }
17684
+ function applyButtonContentFromContent(doc, content) {
17685
+ const snapshots = readSnapshots(content);
17686
+ for (const snapshot of Object.values(snapshots)) {
17687
+ const el = findButton(doc, snapshot.id);
17688
+ if (!el) continue;
17689
+ applySnapshotContent(el, snapshot, content);
17690
+ }
17691
+ }
17692
+ function applySnapshotContent(el, snapshot, content) {
17693
+ const href = content[snapshot.id] ?? snapshot.href;
17694
+ if (href) el.setAttribute("href", href);
17695
+ const labelKey = snapshot.labelKey || labelKeyForHrefKey(snapshot.id);
17696
+ if (labelKey && !el.querySelector(`[data-ohw-key="${CSS.escape(labelKey)}"]`)) {
17697
+ const label2 = labelElement2(el);
17698
+ if (label2) label2.setAttribute("data-ohw-key", labelKey);
17699
+ }
17700
+ const text = content[labelKey];
17701
+ if (text === void 0) return;
17702
+ const label = labelElement2(el);
17703
+ if (label) {
17704
+ if (label.getAttribute("data-ohw-key") !== labelKey) label.setAttribute("data-ohw-key", labelKey);
17705
+ if (label.textContent !== text) label.textContent = text;
17706
+ }
17707
+ }
17708
+ function reconcileButtonCollections(doc, content) {
17709
+ const hidden = new Set(readHiddenButtons(content));
17710
+ for (const id of hidden) {
17711
+ const el = findButton(doc, id);
17712
+ if (el) hideButton(el);
17713
+ }
17714
+ const snapshots = readSnapshots(content);
17715
+ const collections = readCollections(content);
17716
+ for (const collectionState of Object.values(collections)) {
17717
+ const live = collectionState.members.map((id) => findButton(doc, id) ?? restoreSnapshot(doc, snapshots[id], content)).filter((el) => Boolean(el && !hidden.has(buttonId(el) ?? "")));
17718
+ if (live.length === 0) continue;
17719
+ if (live.length === 1) {
17720
+ const existing = buttonCollectionOf(live[0]);
17721
+ if (existing) unwrapCollection(existing);
17722
+ continue;
17723
+ }
17724
+ const anchor = live.find((el) => el.isConnected) ?? live[0];
17725
+ let collection = live.map((el) => buttonCollectionOf(el)).find((el) => el?.getAttribute(COLLECTION_ATTR) === collectionState.id) ?? null;
17726
+ if (!collection) {
17727
+ if (!anchor.parentElement) continue;
17728
+ collection = createCollection(doc, collectionState.id, collectionState.gap);
17729
+ anchor.parentElement.insertBefore(collection, anchor);
17730
+ }
17731
+ styleCollection(collection, collectionState.gap);
17732
+ const current = listCollectionMembers(collection);
17733
+ const sameOrder = current.length === live.length && current.every((id, index) => id === buttonId(live[index]));
17734
+ if (!sameOrder) {
17735
+ for (const member of live) collection.append(member);
17736
+ }
17737
+ for (const member of live) setButtonDragAttr(member);
17738
+ }
17739
+ }
17740
+ function restoreSnapshot(doc, snapshot, content) {
17741
+ if (!snapshot) return null;
17742
+ const existing = findButton(doc, snapshot.id);
17743
+ if (existing) {
17744
+ applySnapshotContent(existing, snapshot, content);
17745
+ return existing;
17746
+ }
17747
+ const el = htmlToElement(doc, snapshot.html);
17748
+ if (!el) return null;
17749
+ el.setAttribute("data-ohw-href-key", snapshot.id);
17750
+ const label = labelElement2(el);
17751
+ const labelKey = snapshot.labelKey || labelKeyForHrefKey(snapshot.id);
17752
+ if (label) label.setAttribute("data-ohw-key", labelKey);
17753
+ applySnapshotContent(el, snapshot, content);
17754
+ setButtonDragAttr(el);
17755
+ return el;
17756
+ }
17757
+ function navbarCtasInScope(el) {
17758
+ const scope = el.closest("nav, [data-ohw-nav-root], [data-ohw-nav-drawer]") ?? el.parentElement;
17759
+ if (!scope) return [];
17760
+ return Array.from(scope.querySelectorAll(CTA_BUTTON_SELECTOR)).filter(
17761
+ (button) => isNavbarCta(button) && buttonId(button)
17762
+ );
17763
+ }
17764
+ function ensureNavbarButtonCollection(button, content) {
17765
+ const existing = buttonCollectionOf(button);
17766
+ if (existing) return existing;
17767
+ if (!isNavbarCta(button) || !buttonId(button)) return null;
17768
+ const parent = button.parentElement;
17769
+ if (!parent) return null;
17770
+ const siblings = navbarCtasInScope(button).filter((el) => el.parentElement === parent);
17771
+ if (siblings.length < 2) return null;
17772
+ const id = nextCollectionId(content, button.ownerDocument);
17773
+ const collection = createCollection(button.ownerDocument, id, collectionGap(button, tokensFromButton(button)));
17774
+ siblings[0].before(collection);
17775
+ for (const member of siblings) {
17776
+ collection.append(member);
17777
+ setButtonDragAttr(member);
17778
+ }
17779
+ return collection;
17780
+ }
17781
+ function collectionDragEnabled(el) {
17782
+ if (!isNavbarCta(el) || !buttonId(el)) return false;
17783
+ if (buttonCollectionOf(el)) return true;
17784
+ return navbarCtasInScope(el).length >= 2;
17785
+ }
17786
+
17787
+ // src/lib/item-drag-interaction.ts
17788
+ function disableNativeHrefDrag(el) {
17789
+ if (el.draggable) el.draggable = false;
17790
+ if (el.getAttribute("draggable") !== "false") {
17791
+ el.setAttribute("draggable", "false");
17792
+ }
17793
+ }
17794
+ function clearTextSelection() {
17795
+ const sel = window.getSelection();
17796
+ if (sel && !sel.isCollapsed) sel.removeAllRanges();
17797
+ }
17798
+ function armItemPressDrag() {
17799
+ document.documentElement.setAttribute("data-ohw-footer-press-drag", "");
17800
+ }
17801
+ function lockItemDuringDrag() {
17802
+ document.documentElement.removeAttribute("data-ohw-footer-press-drag");
17803
+ document.documentElement.setAttribute("data-ohw-item-dragging", "");
17804
+ clearTextSelection();
17805
+ }
17806
+ function unlockItemDragInteraction() {
17807
+ const wasDragging = document.documentElement.hasAttribute("data-ohw-item-dragging");
17808
+ document.documentElement.removeAttribute("data-ohw-footer-press-drag");
17809
+ document.documentElement.removeAttribute("data-ohw-item-dragging");
17810
+ if (wasDragging) clearTextSelection();
17811
+ }
17812
+ var armFooterPressDrag = armItemPressDrag;
17813
+ var lockFooterDuringDrag = lockItemDuringDrag;
17814
+ var unlockFooterDragInteraction = unlockItemDragInteraction;
17815
+
17816
+ // src/lib/nav-dnd.ts
17817
+ function listReorderableNavItems(draggedEl) {
17818
+ return listActiveNavbarItems(draggedEl).filter((el) => {
17819
+ const key = el.getAttribute("data-ohw-href-key");
17820
+ return isNavbarHrefKey(key) && !isNestedNavChild(el);
17821
+ });
17822
+ }
17823
+ function resolveParentNavHrefKey(child) {
17824
+ const childrenRoot = child.closest("[data-ohw-nav-children]");
17825
+ const group = childrenRoot?.closest("[data-ohw-nav-group]");
17826
+ if (!group) return null;
17827
+ const trigger = group.querySelector(":scope > [data-ohw-href-key]") ?? Array.from(group.querySelectorAll("[data-ohw-href-key]")).find(
17828
+ (el) => !el.closest("[data-ohw-nav-children]")
17829
+ ) ?? null;
17830
+ const key = trigger?.getAttribute("data-ohw-href-key");
17831
+ return key && isNavbarHrefKey(key) ? key : null;
17832
+ }
17833
+ function listNavChildren(parentHrefKey, draggedEl) {
17834
+ const items = listActiveNavbarItems(draggedEl);
17835
+ const parent = items.find((el) => el.getAttribute("data-ohw-href-key") === parentHrefKey);
17836
+ if (!parent) return [];
17837
+ const group = parent.closest("[data-ohw-nav-group]");
17838
+ const childrenRoot = group?.querySelector(":scope > [data-ohw-nav-children]");
17839
+ if (!childrenRoot) return [];
17840
+ return Array.from(childrenRoot.querySelectorAll(":scope > [data-ohw-href-key]")).filter(
17841
+ (el) => isNavbarHrefKey(el.getAttribute("data-ohw-href-key"))
17842
+ );
17843
+ }
17844
+ function isVerticalNavLayout(items, draggedEl) {
17845
+ const visible = items.filter((el) => el.getClientRects().length > 0);
17846
+ if (visible.length >= 2) {
17847
+ const a = visible[0].getBoundingClientRect();
17848
+ const b = visible[1].getBoundingClientRect();
17849
+ const verticalGap = b.top - a.bottom;
17850
+ const horizontalGap = b.left - a.right;
17851
+ return verticalGap > horizontalGap;
17852
+ }
17853
+ const container = getActiveNavbarListContainer(draggedEl);
17854
+ if (!container) return false;
17855
+ const style = window.getComputedStyle(container);
17856
+ return style.flexDirection === "column" || style.flexDirection === "column-reverse";
17857
+ }
17858
+ function buildVerticalRootNavDropSlots(items, draggedEl) {
17859
+ const slots = [];
17860
+ const barThickness = 3;
17861
+ if (items.length === 0) {
17862
+ const container = getActiveNavbarListContainer(draggedEl);
17863
+ if (!container) return slots;
17864
+ const rect = container.getBoundingClientRect();
17865
+ slots.push({
17866
+ insertIndex: 0,
17867
+ parentId: null,
17868
+ left: rect.left,
17869
+ top: rect.top + rect.height / 2 - barThickness / 2,
17870
+ width: Math.max(rect.width, 40),
17871
+ height: barThickness,
17872
+ direction: "horizontal"
17873
+ });
17874
+ return slots;
17875
+ }
17876
+ const edgeGap = (() => {
17877
+ if (items.length < 2) return 16;
17878
+ const a = items[0].getBoundingClientRect();
17879
+ const b = items[1].getBoundingClientRect();
17880
+ return Math.max(0, b.top - a.bottom);
17881
+ })();
17882
+ for (let i = 0; i <= items.length; i++) {
17883
+ let top;
17884
+ let width;
17885
+ let left;
17886
+ if (i === 0) {
17887
+ const first = items[0].getBoundingClientRect();
17888
+ top = first.top - edgeGap / 2 - barThickness / 2;
17889
+ left = first.left;
17890
+ width = first.width;
17891
+ } else if (i === items.length) {
17892
+ const last = items[items.length - 1].getBoundingClientRect();
17893
+ const lastGap = items.length >= 2 ? Math.max(0, last.top - items[items.length - 2].getBoundingClientRect().bottom) : edgeGap;
17894
+ top = last.bottom + lastGap / 2 - barThickness / 2;
17895
+ left = last.left;
17896
+ width = last.width;
17897
+ } else {
17898
+ const prev = items[i - 1].getBoundingClientRect();
17899
+ const next = items[i].getBoundingClientRect();
17900
+ top = (prev.bottom + next.top) / 2 - barThickness / 2;
17901
+ left = Math.min(prev.left, next.left);
17902
+ width = Math.max(prev.right, next.right) - left;
16111
17903
  }
16112
17904
  slots.push({
16113
17905
  insertIndex: i,
@@ -16421,7 +18213,7 @@ function useNavItemDrag({
16421
18213
  document.querySelectorAll(
16422
18214
  "nav [data-ohw-href-key], [data-ohw-nav-container] [data-ohw-href-key], [data-ohw-nav-drawer] [data-ohw-href-key]"
16423
18215
  ).forEach((el) => {
16424
- if (isNavbarHrefKey(el.getAttribute("data-ohw-href-key"))) {
18216
+ if (isNavbarHrefKey(el.getAttribute("data-ohw-href-key")) || isCtaButton(el)) {
16425
18217
  disableNativeHrefDrag(el);
16426
18218
  }
16427
18219
  });
@@ -16730,12 +18522,6 @@ var SECTION_DRAG_EXCLUDED_SELECTOR = [
16730
18522
  "[data-ohw-media-overlay]",
16731
18523
  "[data-ohw-media-skeleton]"
16732
18524
  ].join(", ");
16733
- function visibleClip(ps) {
16734
- if (!ps) return null;
16735
- const top = Math.max(0, ps.headerH - ps.iframeOffsetTop);
16736
- const bottom = Math.min(window.innerHeight, ps.headerH + ps.canvasH - ps.iframeOffsetTop);
16737
- return { top, bottom: Math.max(top, bottom) };
16738
- }
16739
18525
  function useSectionDrag({
16740
18526
  isEditMode,
16741
18527
  editContentRef,
@@ -16772,7 +18558,7 @@ function useSectionDrag({
16772
18558
  }, [postToParentRef, stopAutoScroll]);
16773
18559
  const updateAutoScroll = useCallback9(
16774
18560
  (clientY) => {
16775
- const clip = visibleClip(parentScrollRef.current);
18561
+ const clip = getIframeChromeClip(parentScrollRef.current);
16776
18562
  let delta = 0;
16777
18563
  if (clip) {
16778
18564
  const distTop = clientY - clip.top;
@@ -16979,7 +18765,7 @@ function useSectionDrag({
16979
18765
 
16980
18766
  // src/ui/footer-container-chrome.tsx
16981
18767
  import { Plus as Plus2 } from "lucide-react";
16982
- import { jsx as jsx31, jsxs as jsxs21 } from "react/jsx-runtime";
18768
+ import { jsx as jsx32, jsxs as jsxs22 } from "react/jsx-runtime";
16983
18769
  function FooterContainerChrome({
16984
18770
  rect,
16985
18771
  onAdd,
@@ -16987,7 +18773,7 @@ function FooterContainerChrome({
16987
18773
  }) {
16988
18774
  const chromeGap = 6;
16989
18775
  const buttonMargin = 7;
16990
- return /* @__PURE__ */ jsx31(TooltipProvider, { delayDuration: 0, children: /* @__PURE__ */ jsx31(
18776
+ return /* @__PURE__ */ jsx32(TooltipProvider, { delayDuration: 0, children: /* @__PURE__ */ jsx32(
16991
18777
  "div",
16992
18778
  {
16993
18779
  "data-ohw-footer-container-chrome": "",
@@ -16999,8 +18785,8 @@ function FooterContainerChrome({
16999
18785
  width: rect.width + chromeGap * 2,
17000
18786
  height: rect.height + chromeGap * 2
17001
18787
  },
17002
- children: /* @__PURE__ */ jsxs21(Tooltip, { children: [
17003
- /* @__PURE__ */ jsx31(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsx31(
18788
+ children: /* @__PURE__ */ jsxs22(Tooltip, { children: [
18789
+ /* @__PURE__ */ jsx32(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsx32(
17004
18790
  "button",
17005
18791
  {
17006
18792
  type: "button",
@@ -17019,10 +18805,10 @@ function FooterContainerChrome({
17019
18805
  if (addDisabled) return;
17020
18806
  onAdd();
17021
18807
  },
17022
- children: /* @__PURE__ */ jsx31(Plus2, { className: "size-4 shrink-0 text-foreground", "aria-hidden": true })
18808
+ children: /* @__PURE__ */ jsx32(Plus2, { className: "size-4 shrink-0 text-foreground", "aria-hidden": true })
17023
18809
  }
17024
18810
  ) }),
17025
- /* @__PURE__ */ jsx31(TooltipContent, { side: "bottom", sideOffset: 9, children: addDisabled ? "Maximum columns reached" : "Add item" })
18811
+ /* @__PURE__ */ jsx32(TooltipContent, { side: "bottom", sideOffset: 9, children: addDisabled ? "Maximum columns reached" : "Add item" })
17026
18812
  ] })
17027
18813
  }
17028
18814
  ) });
@@ -17184,7 +18970,14 @@ function collectEditableNodes(extraContent, root = document) {
17184
18970
  // so collecting the DOM alone left them behind: the draft knew the row was showing icons and
17185
18971
  // had gained an item, and the published page went back to the template's own (OHH-736).
17186
18972
  SOCIALS_ORDER_KEY,
17187
- SOCIALS_DISPLAY_KEY
18973
+ SOCIALS_DISPLAY_KEY,
18974
+ // Button variant/size tokens and per-CTA assignments — JSON in editContent, applied on hydrate.
18975
+ BUTTON_VARIANTS_KEY,
18976
+ BUTTON_SIZES_KEY,
18977
+ BUTTON_ASSIGNMENTS_KEY,
18978
+ BUTTON_COLLECTIONS_KEY,
18979
+ BUTTON_SNAPSHOTS_KEY,
18980
+ BUTTON_HIDDEN_KEY
17188
18981
  ]) {
17189
18982
  const text = extraContent[key];
17190
18983
  if (typeof text === "string" && text.length > 0) {
@@ -17505,13 +19298,13 @@ function deleteSelectedNavFooterItem(deps) {
17505
19298
 
17506
19299
  // src/ui/navbar-container-chrome.tsx
17507
19300
  import { Plus as Plus3 } from "lucide-react";
17508
- import { jsx as jsx32 } from "react/jsx-runtime";
19301
+ import { jsx as jsx33 } from "react/jsx-runtime";
17509
19302
  function NavbarContainerChrome({
17510
19303
  rect,
17511
19304
  onAdd
17512
19305
  }) {
17513
19306
  const chromeGap = 6;
17514
- return /* @__PURE__ */ jsx32(
19307
+ return /* @__PURE__ */ jsx33(
17515
19308
  "div",
17516
19309
  {
17517
19310
  "data-ohw-navbar-container-chrome": "",
@@ -17523,7 +19316,7 @@ function NavbarContainerChrome({
17523
19316
  width: rect.width + chromeGap * 2,
17524
19317
  height: rect.height + chromeGap * 2
17525
19318
  },
17526
- children: /* @__PURE__ */ jsx32(
19319
+ children: /* @__PURE__ */ jsx33(
17527
19320
  "button",
17528
19321
  {
17529
19322
  type: "button",
@@ -17540,7 +19333,7 @@ function NavbarContainerChrome({
17540
19333
  e.stopPropagation();
17541
19334
  onAdd();
17542
19335
  },
17543
- children: /* @__PURE__ */ jsx32(Plus3, { className: "size-4 shrink-0 text-foreground", "aria-hidden": true })
19336
+ children: /* @__PURE__ */ jsx33(Plus3, { className: "size-4 shrink-0 text-foreground", "aria-hidden": true })
17544
19337
  }
17545
19338
  )
17546
19339
  }
@@ -17549,7 +19342,7 @@ function NavbarContainerChrome({
17549
19342
 
17550
19343
  // src/ui/drop-indicator.tsx
17551
19344
  import * as React11 from "react";
17552
- import { jsx as jsx33 } from "react/jsx-runtime";
19345
+ import { jsx as jsx34 } from "react/jsx-runtime";
17553
19346
  var dropIndicatorVariants = cva(
17554
19347
  "ov-gap-line pointer-events-none shrink-0 transition-opacity duration-150",
17555
19348
  {
@@ -17573,7 +19366,7 @@ var dropIndicatorVariants = cva(
17573
19366
  );
17574
19367
  var DropIndicator = React11.forwardRef(
17575
19368
  ({ className, direction, state, ...props }, ref) => {
17576
- return /* @__PURE__ */ jsx33(
19369
+ return /* @__PURE__ */ jsx34(
17577
19370
  "div",
17578
19371
  {
17579
19372
  ref,
@@ -17590,7 +19383,7 @@ var DropIndicator = React11.forwardRef(
17590
19383
  DropIndicator.displayName = "DropIndicator";
17591
19384
 
17592
19385
  // src/ui/badge.tsx
17593
- import { jsx as jsx34 } from "react/jsx-runtime";
19386
+ import { jsx as jsx35 } from "react/jsx-runtime";
17594
19387
  var badgeVariants = cva(
17595
19388
  "inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2",
17596
19389
  {
@@ -17608,12 +19401,12 @@ var badgeVariants = cva(
17608
19401
  }
17609
19402
  );
17610
19403
  function Badge({ className, variant, ...props }) {
17611
- return /* @__PURE__ */ jsx34("div", { className: cn(badgeVariants({ variant }), className), ...props });
19404
+ return /* @__PURE__ */ jsx35("div", { className: cn(badgeVariants({ variant }), className), ...props });
17612
19405
  }
17613
19406
 
17614
19407
  // src/OhhwellsBridge.tsx
17615
- import { Link as Link2, Plus as Plus4, Settings } from "lucide-react";
17616
- import { Fragment as Fragment10, jsx as jsx35, jsxs as jsxs22 } from "react/jsx-runtime";
19408
+ import { Brush as Brush2, Link as Link2, Plus as Plus4, Settings } from "lucide-react";
19409
+ import { Fragment as Fragment10, jsx as jsx36, jsxs as jsxs23 } from "react/jsx-runtime";
17617
19410
  var PRIMARY5 = "#0885FE";
17618
19411
  var IMAGE_FADE_MS = 300;
17619
19412
  var MEDIA_SETTLE_TIMEOUT_MS = 2500;
@@ -17735,7 +19528,7 @@ function duplicateSchedulingWidget(instanceId, scheduleId) {
17735
19528
  schedulingRoots.set(container, root);
17736
19529
  flushSync2(() => {
17737
19530
  root.render(
17738
- /* @__PURE__ */ jsx35(
19531
+ /* @__PURE__ */ jsx36(
17739
19532
  SchedulingWidget,
17740
19533
  {
17741
19534
  notifyOnConnect: false,
@@ -17778,21 +19571,10 @@ function parseSchedulingInsertAfter(insertAfter) {
17778
19571
  insertBefore: insertAfter.slice(idx + INSERT_BEFORE_MARKER.length)
17779
19572
  };
17780
19573
  }
17781
- function resolveSchedulingInsert(insertAfter, explicitInsertBefore) {
17782
- const parsed = parseSchedulingInsertAfter(insertAfter);
17783
- const insertBefore = explicitInsertBefore ?? parsed.insertBefore;
17784
- const effectiveInsertAfter = insertBefore && parsed.insertBefore === null ? `${insertAfter}${INSERT_BEFORE_MARKER}${insertBefore}` : insertAfter;
17785
- return { effectiveInsertAfter, insertBefore };
17786
- }
17787
- function getSchedulingMountPoint(insertAfter) {
17788
- const { anchor } = parseSchedulingInsertAfter(insertAfter);
17789
- let anchorEl = document.querySelector(`[data-ohw-section="${anchor}"]`);
17790
- if (!anchorEl && anchor === "scheduling") {
17791
- const widgets = Array.from(document.querySelectorAll('[data-ohw-section^="scheduling-"]'));
17792
- anchorEl = widgets.at(-1) ?? null;
17793
- }
17794
- if (!anchorEl) return null;
17795
- return anchorEl.closest('[data-ohw-section-container="scheduling"]') ?? anchorEl;
19574
+ function resolveEntryAnchor(entry) {
19575
+ if (entry.anchorId !== void 0) return { anchorId: entry.anchorId, beforeId: entry.beforeId ?? null };
19576
+ const parsed = parseSchedulingInsertAfter(entry.insertAfter);
19577
+ return { anchorId: parsed.anchor, beforeId: parsed.insertBefore };
17796
19578
  }
17797
19579
  function schedulingMountDepth(insertAfter) {
17798
19580
  if (!insertAfter.includes(INSERT_BEFORE_MARKER)) return 0;
@@ -17809,8 +19591,7 @@ function getPageSchedulingEntries(raw) {
17809
19591
  }
17810
19592
  }
17811
19593
  function isSchedulingWidgetMissing(entry) {
17812
- const { effectiveInsertAfter } = resolveSchedulingInsert(entry.insertAfter);
17813
- return !document.querySelector(`[data-ohw-section="${schedulingSectionId(effectiveInsertAfter)}"]`);
19594
+ return !document.querySelector(`[data-ohw-section="${schedulingSectionId(entry.insertAfter)}"]`);
17814
19595
  }
17815
19596
  function hasMissingSchedulingWidgets(entries) {
17816
19597
  return entries.some(isSchedulingWidgetMissing);
@@ -17848,17 +19629,18 @@ function initSectionsFromContent(content, removeExisting = false, currentPath =
17848
19629
  } catch {
17849
19630
  }
17850
19631
  }
17851
- function mountSchedulingWidget(insertAfter, notifyOnConnect = false, scheduleId, explicitInsertBefore) {
17852
- const { effectiveInsertAfter, insertBefore } = resolveSchedulingInsert(insertAfter, explicitInsertBefore);
17853
- const sectionId = schedulingSectionId(effectiveInsertAfter);
19632
+ function mountSchedulingWidget(anchorId, notifyOnConnect = false, scheduleId, beforeId, existingWidgetId) {
19633
+ const widgetId = existingWidgetId ?? (beforeId ? `${anchorId}${INSERT_BEFORE_MARKER}${beforeId}` : anchorId);
19634
+ const sectionId = schedulingSectionId(widgetId);
17854
19635
  if (document.querySelector(`[data-ohw-section="${sectionId}"]`)) return false;
17855
- const mountPoint = getSchedulingMountPoint(effectiveInsertAfter);
17856
- if (!mountPoint) return false;
19636
+ const anchorEl = document.querySelector(`[data-ohw-section="${anchorId}"]`);
19637
+ if (!anchorEl) return false;
19638
+ const mountPoint = anchorEl.closest('[data-ohw-section-container="scheduling"]') ?? anchorEl;
17857
19639
  const container = document.createElement("div");
17858
19640
  container.dataset.ohwSectionContainer = "scheduling";
17859
19641
  container.dataset.ohwInstance = sectionId;
17860
- if (insertBefore) {
17861
- const beforeAnchor = document.querySelector(`[data-ohw-section="${insertBefore}"]`);
19642
+ if (beforeId) {
19643
+ const beforeAnchor = document.querySelector(`[data-ohw-section="${beforeId}"]`);
17862
19644
  const beforePoint = beforeAnchor?.closest('[data-ohw-section-container="scheduling"]') ?? beforeAnchor;
17863
19645
  if (!beforePoint) return false;
17864
19646
  beforePoint.insertAdjacentElement("beforebegin", container);
@@ -17869,20 +19651,26 @@ function mountSchedulingWidget(insertAfter, notifyOnConnect = false, scheduleId,
17869
19651
  }
17870
19652
  tail.insertAdjacentElement("afterend", container);
17871
19653
  }
17872
- const root = createRoot2(container);
17873
- schedulingRoots.set(container, root);
17874
- flushSync2(() => {
17875
- root.render(
17876
- /* @__PURE__ */ jsx35(
17877
- SchedulingWidget,
17878
- {
17879
- notifyOnConnect,
17880
- initialScheduleId: scheduleId,
17881
- insertAfter: effectiveInsertAfter
17882
- }
17883
- )
17884
- );
17885
- });
19654
+ try {
19655
+ const root = createRoot2(container);
19656
+ schedulingRoots.set(container, root);
19657
+ flushSync2(() => {
19658
+ root.render(
19659
+ /* @__PURE__ */ jsx36(
19660
+ SchedulingWidget,
19661
+ {
19662
+ notifyOnConnect,
19663
+ initialScheduleId: scheduleId,
19664
+ insertAfter: widgetId
19665
+ }
19666
+ )
19667
+ );
19668
+ });
19669
+ } catch (err) {
19670
+ console.error("[ow:scheduling] render threw", err);
19671
+ container.remove();
19672
+ return false;
19673
+ }
17886
19674
  const tracker = getSectionsTracker();
17887
19675
  let sections = [];
17888
19676
  try {
@@ -17890,10 +19678,12 @@ function mountSchedulingWidget(insertAfter, notifyOnConnect = false, scheduleId,
17890
19678
  } catch {
17891
19679
  }
17892
19680
  const inEditor = typeof window !== "undefined" && window.self !== window.top;
17893
- if (inEditor && !sections.find((s) => s.type === "scheduling" && s.insertAfter === effectiveInsertAfter)) {
19681
+ if (inEditor && !sections.find((s) => s.type === "scheduling" && s.insertAfter === widgetId)) {
17894
19682
  sections.push({
17895
19683
  type: "scheduling",
17896
- insertAfter: effectiveInsertAfter,
19684
+ insertAfter: widgetId,
19685
+ anchorId,
19686
+ beforeId: beforeId ?? null,
17897
19687
  pagePath: window.location.pathname,
17898
19688
  ...scheduleId ? { scheduleId } : {}
17899
19689
  });
@@ -17907,7 +19697,8 @@ function mountSchedulingEntries(entries, notifyOnConnect = false) {
17907
19697
  for (let i = pending.length - 1; i >= 0; i--) {
17908
19698
  const entry = pending[i];
17909
19699
  const shouldNotify = typeof notifyOnConnect === "function" ? notifyOnConnect(entry) : notifyOnConnect;
17910
- if (mountSchedulingWidget(entry.insertAfter, shouldNotify, entry.scheduleId ?? null)) {
19700
+ const { anchorId, beforeId } = resolveEntryAnchor(entry);
19701
+ if (mountSchedulingWidget(anchorId, shouldNotify, entry.scheduleId ?? null, beforeId, entry.insertAfter)) {
17911
19702
  pending.splice(i, 1);
17912
19703
  }
17913
19704
  }
@@ -18036,8 +19827,8 @@ function lockVideoBox(video) {
18036
19827
  }
18037
19828
  const fit = video.style.objectFit || getComputedStyle(video).objectFit;
18038
19829
  const bg = getComputedStyle(video).backgroundColor;
18039
- const isTransparent = bg === "rgba(0, 0, 0, 0)" || bg === "transparent" || !bg;
18040
- if (fit === "contain" && isTransparent) {
19830
+ const isTransparent2 = bg === "rgba(0, 0, 0, 0)" || bg === "transparent" || !bg;
19831
+ if (fit === "contain" && isTransparent2) {
18041
19832
  video.style.backgroundColor = "var(--color-dark, #000)";
18042
19833
  }
18043
19834
  }
@@ -18084,6 +19875,11 @@ function applyLinkByKey(key, val) {
18084
19875
  hrefAnchors.forEach((el) => applyLinkHref(el, val));
18085
19876
  }
18086
19877
  }
19878
+ function isInsideLinkEditor(target) {
19879
+ return Boolean(
19880
+ 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"]')
19881
+ );
19882
+ }
18087
19883
  function isInsideFloatingPanel(target) {
18088
19884
  return Boolean(target.closest("[data-ohw-floating-panel]"));
18089
19885
  }
@@ -18091,11 +19887,6 @@ function isPointOverFloatingPanel(clientX, clientY) {
18091
19887
  const el = document.elementFromPoint(clientX, clientY);
18092
19888
  return Boolean(el instanceof Element && el.closest("[data-ohw-floating-panel]"));
18093
19889
  }
18094
- function isInsideLinkEditor(target) {
18095
- return Boolean(
18096
- 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"]')
18097
- );
18098
- }
18099
19890
  function getHrefKeyFromElement(el) {
18100
19891
  if (!el) return null;
18101
19892
  const anchor = el.closest("[data-ohw-href-key]");
@@ -18187,7 +19978,10 @@ function listNavigationItems() {
18187
19978
  ).filter((el) => isNavigationItem2(el));
18188
19979
  }
18189
19980
  function getNavigationItemReorderState(anchor) {
18190
- if (isCtaButton(anchor)) return { key: null, disabled: false };
19981
+ if (isCtaButton(anchor)) {
19982
+ if (!collectionDragEnabled(anchor)) return { key: null, disabled: true };
19983
+ return { key: buttonId(anchor), disabled: false };
19984
+ }
18191
19985
  return getReorderHandleStateFromAnchor(anchor);
18192
19986
  }
18193
19987
  function getReorderHandleState(el) {
@@ -18286,6 +20080,12 @@ function isPointOverNavigation(x, y) {
18286
20080
  const r2 = panel.getBoundingClientRect();
18287
20081
  if (x >= r2.left && x <= r2.right && y >= r2.top && y <= r2.bottom) return true;
18288
20082
  }
20083
+ for (const el of document.querySelectorAll('[data-ohw-role="navbar-button"]')) {
20084
+ const r2 = el.getBoundingClientRect();
20085
+ if (r2.width > 0 && r2.height > 0 && x >= r2.left && x <= r2.right && y >= r2.top && y <= r2.bottom) {
20086
+ return true;
20087
+ }
20088
+ }
18289
20089
  return false;
18290
20090
  }
18291
20091
  function findHoveredNavOrFooterContainer(x, y) {
@@ -18365,7 +20165,7 @@ function getNavigationSelectionParent(el) {
18365
20165
  if (!isFooterLinksContainer(el) && (el.hasAttribute("data-ohw-footer-col") || el.hasAttribute("data-ohw-footer-column") || isInferredFooterGroup2(el))) {
18366
20166
  return getFooterLinksContainer();
18367
20167
  }
18368
- if (el.hasAttribute("data-ohw-nav-container") || el.hasAttribute("data-ohw-nav-drawer") || isFooterLinksContainer(el)) {
20168
+ 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)) {
18369
20169
  return getNavigationRoot(el);
18370
20170
  }
18371
20171
  return null;
@@ -18587,7 +20387,6 @@ var ICONS = {
18587
20387
  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"/>',
18588
20388
  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"/>'
18589
20389
  };
18590
- var HEIGHT_SETTLE_DELAYS = [150, 400, 800];
18591
20390
  var SELECTION_CHROME_GAP2 = 4;
18592
20391
  var TOOLBAR_STROKE_GAP2 = 4;
18593
20392
  var TOOLBAR_OFFSET_FROM_ELEMENT = SELECTION_CHROME_GAP2 + TOOLBAR_STROKE_GAP2;
@@ -18619,7 +20418,7 @@ function EditGlowChrome({
18619
20418
  hideHandle = false
18620
20419
  }) {
18621
20420
  const GAP = SELECTION_CHROME_GAP2;
18622
- return /* @__PURE__ */ jsxs22(
20421
+ return /* @__PURE__ */ jsxs23(
18623
20422
  "div",
18624
20423
  {
18625
20424
  ref: elRef,
@@ -18634,7 +20433,7 @@ function EditGlowChrome({
18634
20433
  zIndex: 2147483646
18635
20434
  },
18636
20435
  children: [
18637
- /* @__PURE__ */ jsx35(
20436
+ /* @__PURE__ */ jsx36(
18638
20437
  "div",
18639
20438
  {
18640
20439
  style: {
@@ -18648,7 +20447,7 @@ function EditGlowChrome({
18648
20447
  }
18649
20448
  }
18650
20449
  ),
18651
- reorderHrefKey && !hideHandle && /* @__PURE__ */ jsx35(
20450
+ reorderHrefKey && !hideHandle && /* @__PURE__ */ jsx36(
18652
20451
  "div",
18653
20452
  {
18654
20453
  "data-ohw-drag-handle-container": "",
@@ -18660,7 +20459,7 @@ function EditGlowChrome({
18660
20459
  transform: "translate(calc(-100% - 7px), -50%)",
18661
20460
  pointerEvents: dragDisabled ? "none" : "auto"
18662
20461
  },
18663
- children: /* @__PURE__ */ jsx35(
20462
+ children: /* @__PURE__ */ jsx36(
18664
20463
  DragHandle,
18665
20464
  {
18666
20465
  "aria-label": `Reorder ${reorderHrefKey}`,
@@ -18674,22 +20473,12 @@ function EditGlowChrome({
18674
20473
  }
18675
20474
  );
18676
20475
  }
18677
- function getIframeVisibleClip(parentScroll) {
18678
- if (!parentScroll) return null;
18679
- const { iframeOffsetTop, headerH: visibleCanvasTop, canvasH } = parentScroll;
18680
- const clipTop = Math.max(0, visibleCanvasTop - iframeOffsetTop);
18681
- const clipBottom = Math.min(
18682
- window.innerHeight,
18683
- visibleCanvasTop + canvasH - iframeOffsetTop
18684
- );
18685
- return { top: clipTop, bottom: Math.max(clipTop, clipBottom) };
18686
- }
18687
20476
  function isRectOutsideClip(rect, clip) {
18688
20477
  if (!clip) return false;
18689
20478
  return rect.bottom < clip.top || rect.top > clip.bottom;
18690
20479
  }
18691
20480
  function applyVisibleViewport(el, parentScroll) {
18692
- const clip = getIframeVisibleClip(parentScroll);
20481
+ const clip = getIframeChromeClip(parentScroll);
18693
20482
  const top = clip?.top ?? 0;
18694
20483
  const height = clip ? clip.bottom - clip.top : window.innerHeight;
18695
20484
  el.style.top = `${top}px`;
@@ -18729,7 +20518,7 @@ function clampToolbarLeft(centerX, toolbarWidth) {
18729
20518
  function calcToolbarPos(rect, parentScroll, toolbarW = 306) {
18730
20519
  const GAP = TOOLBAR_OFFSET_FROM_ELEMENT;
18731
20520
  const APPROX_H = 32;
18732
- const clip = getIframeVisibleClip(parentScroll);
20521
+ const clip = getIframeChromeClip(parentScroll);
18733
20522
  const clipTop = clip?.top ?? 0;
18734
20523
  const canvasTopInIframe = parentScroll != null ? parentScroll.headerH - parentScroll.iframeOffsetTop : 0;
18735
20524
  const visibleTop = rect.top - canvasTopInIframe;
@@ -18874,7 +20663,7 @@ function FloatingToolbar({
18874
20663
  return () => ro.disconnect();
18875
20664
  }, [showEditLink, activeCommands]);
18876
20665
  const { top, left, transform } = calcToolbarPos(rect, parentScroll, measuredW);
18877
- return /* @__PURE__ */ jsx35(
20666
+ return /* @__PURE__ */ jsx36(
18878
20667
  "div",
18879
20668
  {
18880
20669
  ref: setRefs,
@@ -18886,12 +20675,12 @@ function FloatingToolbar({
18886
20675
  zIndex: 2147483647,
18887
20676
  pointerEvents: "auto"
18888
20677
  },
18889
- children: /* @__PURE__ */ jsxs22(CustomToolbar, { children: [
18890
- TOOLBAR_GROUPS.map((btns, gi) => /* @__PURE__ */ jsxs22(React12.Fragment, { children: [
18891
- gi > 0 && /* @__PURE__ */ jsx35(CustomToolbarDivider, {}),
20678
+ children: /* @__PURE__ */ jsxs23(CustomToolbar, { children: [
20679
+ TOOLBAR_GROUPS.map((btns, gi) => /* @__PURE__ */ jsxs23(React12.Fragment, { children: [
20680
+ gi > 0 && /* @__PURE__ */ jsx36(CustomToolbarDivider, {}),
18892
20681
  btns.map((btn) => {
18893
20682
  const isActive = activeCommands.has(btn.cmd);
18894
- return /* @__PURE__ */ jsx35(
20683
+ return /* @__PURE__ */ jsx36(
18895
20684
  CustomToolbarButton,
18896
20685
  {
18897
20686
  title: btn.title,
@@ -18900,7 +20689,7 @@ function FloatingToolbar({
18900
20689
  e.preventDefault();
18901
20690
  onCommand(btn.cmd);
18902
20691
  },
18903
- children: /* @__PURE__ */ jsx35(
20692
+ children: /* @__PURE__ */ jsx36(
18904
20693
  "svg",
18905
20694
  {
18906
20695
  width: "16",
@@ -18921,7 +20710,7 @@ function FloatingToolbar({
18921
20710
  );
18922
20711
  })
18923
20712
  ] }, gi)),
18924
- showEditLink ? /* @__PURE__ */ jsx35(
20713
+ showEditLink ? /* @__PURE__ */ jsx36(
18925
20714
  CustomToolbarButton,
18926
20715
  {
18927
20716
  type: "button",
@@ -18935,7 +20724,7 @@ function FloatingToolbar({
18935
20724
  e.preventDefault();
18936
20725
  e.stopPropagation();
18937
20726
  },
18938
- children: /* @__PURE__ */ jsx35(Link2, { className: "size-4 shrink-0", "aria-hidden": true })
20727
+ children: /* @__PURE__ */ jsx36(Link2, { className: "size-4 shrink-0", "aria-hidden": true })
18939
20728
  }
18940
20729
  ) : null
18941
20730
  ] })
@@ -18952,7 +20741,7 @@ function StateToggle({
18952
20741
  states,
18953
20742
  onStateChange
18954
20743
  }) {
18955
- return /* @__PURE__ */ jsx35(
20744
+ return /* @__PURE__ */ jsx36(
18956
20745
  ToggleGroup,
18957
20746
  {
18958
20747
  "data-ohw-state-toggle": "",
@@ -18966,11 +20755,12 @@ function StateToggle({
18966
20755
  left: rect.right - 8,
18967
20756
  transform: "translateX(-100%)"
18968
20757
  },
18969
- children: states.map((state) => /* @__PURE__ */ jsx35(ToggleGroupItem, { value: state, size: "sm", children: state }, state))
20758
+ children: states.map((state) => /* @__PURE__ */ jsx36(ToggleGroupItem, { value: state, size: "sm", children: state }, state))
18970
20759
  }
18971
20760
  );
18972
20761
  }
18973
20762
  var contentCache = /* @__PURE__ */ new Map();
20763
+ var fetchedContentPaths = /* @__PURE__ */ new Set();
18974
20764
  var brandingCache = /* @__PURE__ */ new Map();
18975
20765
  var OHW_LOADER_STYLE = {
18976
20766
  position: "fixed",
@@ -18982,9 +20772,9 @@ var OHW_LOADER_STYLE = {
18982
20772
  justifyContent: "center"
18983
20773
  };
18984
20774
  function OhwLoaderSpinner() {
18985
- return /* @__PURE__ */ jsxs22("svg", { width: "28", height: "28", viewBox: "0 0 28 28", fill: "none", "aria-hidden": true, children: [
18986
- /* @__PURE__ */ jsx35("circle", { cx: "14", cy: "14", r: "11", stroke: "#E7E5E4", strokeWidth: "3" }),
18987
- /* @__PURE__ */ jsx35(
20775
+ return /* @__PURE__ */ jsxs23("svg", { width: "28", height: "28", viewBox: "0 0 28 28", fill: "none", "aria-hidden": true, children: [
20776
+ /* @__PURE__ */ jsx36("circle", { cx: "14", cy: "14", r: "11", stroke: "#E7E5E4", strokeWidth: "3" }),
20777
+ /* @__PURE__ */ jsx36(
18988
20778
  "circle",
18989
20779
  {
18990
20780
  cx: "14",
@@ -18994,7 +20784,7 @@ function OhwLoaderSpinner() {
18994
20784
  strokeWidth: "3",
18995
20785
  strokeDasharray: "17 52",
18996
20786
  strokeLinecap: "round",
18997
- children: /* @__PURE__ */ jsx35(
20787
+ children: /* @__PURE__ */ jsx36(
18998
20788
  "animateTransform",
18999
20789
  {
19000
20790
  attributeName: "transform",
@@ -19010,7 +20800,7 @@ function OhwLoaderSpinner() {
19010
20800
  ] });
19011
20801
  }
19012
20802
  function OhwBrandMark() {
19013
- return /* @__PURE__ */ jsxs22(
20803
+ return /* @__PURE__ */ jsxs23(
19014
20804
  "svg",
19015
20805
  {
19016
20806
  width: "16",
@@ -19021,7 +20811,7 @@ function OhwBrandMark() {
19021
20811
  style: { display: "block", flexShrink: 0 },
19022
20812
  xmlns: "http://www.w3.org/2000/svg",
19023
20813
  children: [
19024
- /* @__PURE__ */ jsx35(
20814
+ /* @__PURE__ */ jsx36(
19025
20815
  "mask",
19026
20816
  {
19027
20817
  id: "ohw-badge-mark",
@@ -19031,15 +20821,15 @@ function OhwBrandMark() {
19031
20821
  y: "0",
19032
20822
  width: "48",
19033
20823
  height: "48",
19034
- children: /* @__PURE__ */ jsx35("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" })
20824
+ children: /* @__PURE__ */ jsx36("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" })
19035
20825
  }
19036
20826
  ),
19037
- /* @__PURE__ */ jsxs22("g", { mask: "url(#ohw-badge-mark)", children: [
19038
- /* @__PURE__ */ jsx35("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" }),
19039
- /* @__PURE__ */ jsx35("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" }),
19040
- /* @__PURE__ */ jsx35("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" }),
19041
- /* @__PURE__ */ jsx35("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" }),
19042
- /* @__PURE__ */ jsx35("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" })
20827
+ /* @__PURE__ */ jsxs23("g", { mask: "url(#ohw-badge-mark)", children: [
20828
+ /* @__PURE__ */ jsx36("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" }),
20829
+ /* @__PURE__ */ jsx36("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" }),
20830
+ /* @__PURE__ */ jsx36("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" }),
20831
+ /* @__PURE__ */ jsx36("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" }),
20832
+ /* @__PURE__ */ jsx36("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" })
19043
20833
  ] })
19044
20834
  ]
19045
20835
  }
@@ -19076,7 +20866,7 @@ var OHW_BADGE_LABEL_STYLE = {
19076
20866
  whiteSpace: "nowrap"
19077
20867
  };
19078
20868
  function MadeWithOhhWells() {
19079
- return /* @__PURE__ */ jsxs22(
20869
+ return /* @__PURE__ */ jsxs23(
19080
20870
  "a",
19081
20871
  {
19082
20872
  href: "https://ohhwells.com",
@@ -19086,8 +20876,8 @@ function MadeWithOhhWells() {
19086
20876
  "data-ohw-badge": "",
19087
20877
  style: OHW_BADGE_STYLE,
19088
20878
  children: [
19089
- /* @__PURE__ */ jsx35(OhwBrandMark, {}),
19090
- /* @__PURE__ */ jsx35("span", { style: OHW_BADGE_LABEL_STYLE, children: "Made with OhhWells" })
20879
+ /* @__PURE__ */ jsx36(OhwBrandMark, {}),
20880
+ /* @__PURE__ */ jsx36("span", { style: OHW_BADGE_LABEL_STYLE, children: "Made with OhhWells" })
19091
20881
  ]
19092
20882
  }
19093
20883
  );
@@ -19574,13 +21364,6 @@ function OhhwellsBridge() {
19574
21364
  const [isItemDragging, setIsItemDragging] = useState16(false);
19575
21365
  const [isFooterFrameSelection, setIsFooterFrameSelection] = useState16(false);
19576
21366
  isFooterFrameSelectionRef.current = isFooterFrameSelection;
19577
- const [floatingPanel, setFloatingPanel] = useState16(null);
19578
- const floatingPanelOpenRef = useRef12(false);
19579
- floatingPanelOpenRef.current = floatingPanel !== null;
19580
- const [floatingPanelPos, setFloatingPanelPos] = useState16(null);
19581
- const [logoSizeDraft, setLogoSizeDraft] = useState16(null);
19582
- const [editorViewport, setEditorViewport] = useState16("desktop");
19583
- const [parentScrollSnap, setParentScrollSnap] = useState16(null);
19584
21367
  const [navDropdownPreviewOpen, setNavDropdownPreviewOpen] = useState16(null);
19585
21368
  const [footerHeadingVisible, setFooterHeadingVisible] = useState16(null);
19586
21369
  const footerDragRef = useRef12(null);
@@ -19604,6 +21387,21 @@ function OhhwellsBridge() {
19604
21387
  const stylesRef = useRef12("");
19605
21388
  const appliedDrawerRef = useRef12(null);
19606
21389
  const pendingDeleteUndoRef = useRef12(null);
21390
+ const [floatingPanel, setFloatingPanel] = useState16(null);
21391
+ const floatingPanelOpenRef = useRef12(false);
21392
+ const floatingPanelRef = useRef12(null);
21393
+ floatingPanelRef.current = floatingPanel;
21394
+ const setFloatingPanelRef = useRef12(setFloatingPanel);
21395
+ const [floatingPanelPos, setFloatingPanelPos] = useState16(null);
21396
+ const buttonCollectionDragRef = useRef12(null);
21397
+ const buttonDropInsertIndexRef = useRef12(null);
21398
+ const [buttonDropSlots, setButtonDropSlots] = useState16([]);
21399
+ const [activeButtonDropIndex, setActiveButtonDropIndex] = useState16(null);
21400
+ const buttonPointerDragRef = useRef12(null);
21401
+ const [buttonStyleTick, setButtonStyleTick] = useState16(0);
21402
+ const [logoSizeDraft, setLogoSizeDraft] = useState16(null);
21403
+ const [editorViewport, setEditorViewport] = useState16("desktop");
21404
+ const [parentScrollSnap, setParentScrollSnap] = useState16(null);
19607
21405
  const [sitePages, setSitePages] = useState16([]);
19608
21406
  const [sectionsByPath, setSectionsByPath] = useState16({});
19609
21407
  const sectionsPrefetchGenRef = useRef12(0);
@@ -19612,7 +21410,18 @@ function OhhwellsBridge() {
19612
21410
  const linkPopoverOpenRef = useRef12(false);
19613
21411
  const linkPopoverGraceUntilRef = useRef12(0);
19614
21412
  setLinkPopoverRef.current = setLinkPopover;
21413
+ setFloatingPanelRef.current = setFloatingPanel;
19615
21414
  linkPopoverSessionRef.current = linkPopover;
21415
+ floatingPanelOpenRef.current = Boolean(floatingPanel);
21416
+ useEffect15(() => {
21417
+ const syncViewport = () => {
21418
+ const next = window.innerWidth <= 480 ? "mobile" : "desktop";
21419
+ setEditorViewport((prev) => prev === next ? prev : next);
21420
+ };
21421
+ syncViewport();
21422
+ window.addEventListener("resize", syncViewport);
21423
+ return () => window.removeEventListener("resize", syncViewport);
21424
+ }, []);
19616
21425
  useEffect15(() => {
19617
21426
  setSectionOrderPersistHandler((entries) => {
19618
21427
  const currentPath = window.location.pathname;
@@ -19878,6 +21687,7 @@ function OhhwellsBridge() {
19878
21687
  hoveredItemElRef.current = null;
19879
21688
  setHoveredItemRect(null);
19880
21689
  setFloatingPanel(null);
21690
+ setFloatingPanelPos(null);
19881
21691
  setLogoSizeDraft(null);
19882
21692
  if (!activeElRef.current) {
19883
21693
  if (nestedGroup?.isConnected) {
@@ -20478,6 +22288,47 @@ function OhhwellsBridge() {
20478
22288
  },
20479
22289
  [beginFooterDrag]
20480
22290
  );
22291
+ const persistButtonCollectionDrag = useCallback10(
22292
+ (button, clientX) => {
22293
+ const insertIndex = buttonDropInsertIndexRef.current;
22294
+ if (insertIndex != null) insertButtonAtCollectionIndex(button, insertIndex);
22295
+ else if (typeof clientX === "number" && clientX !== 0) reorderButtonInCollection(button, clientX);
22296
+ buttonDropInsertIndexRef.current = null;
22297
+ const collection = buttonCollectionOf(button);
22298
+ if (collection) {
22299
+ const persisted = persistCollectionOrder(collection, editContentRef.current);
22300
+ if (persisted) {
22301
+ editContentRef.current = persisted.content;
22302
+ postToParent2({ type: "ow:change", nodes: persisted.nodes });
22303
+ }
22304
+ }
22305
+ buttonCollectionDragRef.current = null;
22306
+ buttonPointerDragRef.current = null;
22307
+ setButtonDropSlots([]);
22308
+ setActiveButtonDropIndex(null);
22309
+ setIsItemDragging(false);
22310
+ unlockFooterDragInteraction();
22311
+ if (button.isConnected) setToolbarRect(button.getBoundingClientRect());
22312
+ },
22313
+ [postToParent2]
22314
+ );
22315
+ const refreshButtonDragVisuals = useCallback10((button, clientX, clientY) => {
22316
+ const collection = buttonCollectionOf(button);
22317
+ if (!collection) {
22318
+ setButtonDropSlots([]);
22319
+ setActiveButtonDropIndex(null);
22320
+ buttonDropInsertIndexRef.current = null;
22321
+ return;
22322
+ }
22323
+ const slots = buildButtonCollectionDropSlots(collection);
22324
+ const active = hitTestButtonCollectionDropSlot(collection, clientX, clientY);
22325
+ setButtonDropSlots(slots);
22326
+ const idx = active ? slots.findIndex((slot) => slot.insertIndex === active.insertIndex) : -1;
22327
+ setActiveButtonDropIndex(idx >= 0 ? idx : null);
22328
+ buttonDropInsertIndexRef.current = active?.insertIndex ?? null;
22329
+ setDraggedItemRect(button.getBoundingClientRect());
22330
+ if (selectedElRef.current === button) setToolbarRect(button.getBoundingClientRect());
22331
+ }, []);
20481
22332
  const handleItemDragStart = useCallback10(
20482
22333
  (e) => {
20483
22334
  const selected = selectedElRef.current;
@@ -20487,6 +22338,17 @@ function OhhwellsBridge() {
20487
22338
  }
20488
22339
  const clientX = e?.clientX ?? selected.getBoundingClientRect().left + 8;
20489
22340
  const clientY = e?.clientY ?? selected.getBoundingClientRect().top + 8;
22341
+ if (isCtaButton(selected) && collectionDragEnabled(selected)) {
22342
+ buttonCollectionDragRef.current = selected;
22343
+ setMediaHover(null);
22344
+ if (e?.dataTransfer) {
22345
+ e.dataTransfer.effectAllowed = "move";
22346
+ e.dataTransfer.setData("text/plain", buttonId(selected) ?? "button");
22347
+ }
22348
+ setIsItemDragging(true);
22349
+ refreshButtonDragVisuals(selected, clientX, clientY);
22350
+ return;
22351
+ }
20490
22352
  if (startFooterLinkDrag(selected, clientX, clientY, true)) return;
20491
22353
  if (selected.hasAttribute("data-ohw-footer-col") || isInferredFooterGroup2(selected) && selected.closest("footer")) {
20492
22354
  if (startFooterColumnDrag(selected, clientX, clientY, true)) return;
@@ -20496,10 +22358,15 @@ function OhhwellsBridge() {
20496
22358
  setSiblingHintRect(null);
20497
22359
  setIsItemDragging(true);
20498
22360
  },
20499
- [startFooterColumnDrag, startFooterLinkDrag, startNavLinkDrag]
22361
+ [startFooterColumnDrag, startFooterLinkDrag, startNavLinkDrag, refreshButtonDragVisuals]
20500
22362
  );
20501
22363
  const handleItemDragEnd = useCallback10(
20502
22364
  (e) => {
22365
+ const collectionButton = buttonCollectionDragRef.current;
22366
+ if (collectionButton) {
22367
+ persistButtonCollectionDrag(collectionButton, e?.clientX);
22368
+ return;
22369
+ }
20503
22370
  if (footerDragRef.current) {
20504
22371
  const x = e?.clientX;
20505
22372
  const y = e?.clientY;
@@ -20522,12 +22389,22 @@ function OhhwellsBridge() {
20522
22389
  }
20523
22390
  setIsItemDragging(false);
20524
22391
  },
20525
- [commitFooterDrag, commitNavDrag, navDragRef]
22392
+ [commitFooterDrag, commitNavDrag, navDragRef, persistButtonCollectionDrag]
20526
22393
  );
20527
22394
  const handleItemChromePointerDown = useCallback10((e) => {
20528
22395
  if (e.button !== 0) return;
20529
22396
  const selected = selectedElRef.current;
20530
22397
  if (!selected) return;
22398
+ if (isCtaButton(selected) && collectionDragEnabled(selected)) {
22399
+ buttonPointerDragRef.current = {
22400
+ el: selected,
22401
+ startX: e.clientX,
22402
+ startY: e.clientY,
22403
+ pointerId: e.pointerId,
22404
+ started: false
22405
+ };
22406
+ return;
22407
+ }
20531
22408
  const hrefKey = selected.getAttribute("data-ohw-href-key");
20532
22409
  if (hrefKey && isFooterHrefKey(hrefKey)) {
20533
22410
  footerPointerDragRef.current = {
@@ -20606,10 +22483,29 @@ function OhhwellsBridge() {
20606
22483
  setIsFooterFrameSelection(false);
20607
22484
  setToolbarVariant("link-action");
20608
22485
  setToolbarRect(anchor.getBoundingClientRect());
22486
+ if (glowElRef.current) glowElRef.current.style.display = "";
20609
22487
  setToolbarShowEditLink(false);
20610
22488
  setActiveCommands(/* @__PURE__ */ new Set());
20611
- setFloatingPanel(null);
20612
- setLogoSizeDraft(null);
22489
+ const panel = floatingPanelRef.current;
22490
+ if (panel?.kind === "button-style" && isCtaButton(anchor)) {
22491
+ const id = buttonId(anchor);
22492
+ if (id && panel.buttonId !== id) {
22493
+ captureButtonOrigin(anchor);
22494
+ setFloatingPanelPos(null);
22495
+ setParentScrollSnap(parentScrollRef.current);
22496
+ setFloatingPanel({
22497
+ ...panel,
22498
+ key: `button-style:${id}`,
22499
+ buttonId: id,
22500
+ context: buttonStyleContext(anchor)
22501
+ });
22502
+ setButtonStyleTick((n) => n + 1);
22503
+ }
22504
+ } else {
22505
+ setFloatingPanel(null);
22506
+ setFloatingPanelPos(null);
22507
+ setLogoSizeDraft(null);
22508
+ }
20613
22509
  }, [deactivate, markSelected]);
20614
22510
  const selectFrame = useCallback10((el) => {
20615
22511
  if (!isNavigationContainer(el)) return;
@@ -20649,6 +22545,7 @@ function OhhwellsBridge() {
20649
22545
  setToolbarShowEditLink(false);
20650
22546
  setActiveCommands(/* @__PURE__ */ new Set());
20651
22547
  setFloatingPanel(null);
22548
+ setFloatingPanelPos(null);
20652
22549
  setLogoSizeDraft(null);
20653
22550
  }, [deactivate, markSelected, postToParent2]);
20654
22551
  const selectLogo = useCallback10(
@@ -20694,6 +22591,7 @@ function OhhwellsBridge() {
20694
22591
  });
20695
22592
  }, []);
20696
22593
  const openSocialsDisplayPanel = useCallback10((row) => {
22594
+ setFloatingPanelPos(null);
20697
22595
  setParentScrollSnap(parentScrollRef.current);
20698
22596
  setFloatingPanel({
20699
22597
  key: "socials-display",
@@ -20703,6 +22601,87 @@ function OhhwellsBridge() {
20703
22601
  row
20704
22602
  });
20705
22603
  }, []);
22604
+ const rememberButtonTokens = useCallback10(
22605
+ (content, tokens, nodes) => {
22606
+ if (nodes.length === 0) return content;
22607
+ const next = { ...content };
22608
+ for (const node of nodes) next[node.key] = node.text;
22609
+ editContentRef.current = next;
22610
+ postToParent2({ type: "ow:change", nodes });
22611
+ return next;
22612
+ },
22613
+ [postToParent2]
22614
+ );
22615
+ const tokensForButton = useCallback10(
22616
+ (el, persistFallback = false) => {
22617
+ const stored = readStoredStyleTokens(editContentRef.current);
22618
+ if (stored) return stored;
22619
+ const ensured = ensureButtonStyleTokens(document, editContentRef.current);
22620
+ if (ensured.tokens) {
22621
+ rememberButtonTokens(editContentRef.current, ensured.tokens, ensured.nodes);
22622
+ return ensured.tokens;
22623
+ }
22624
+ const fallback = tokensFromButton(el);
22625
+ if (persistFallback) rememberButtonTokens(editContentRef.current, fallback, styleTokenNodes(fallback));
22626
+ return fallback;
22627
+ },
22628
+ [rememberButtonTokens]
22629
+ );
22630
+ const openButtonStylePanel = useCallback10(
22631
+ (anchor) => {
22632
+ const id = buttonId(anchor);
22633
+ if (!id) return;
22634
+ captureButtonOrigin(anchor);
22635
+ tokensForButton(anchor);
22636
+ setFloatingPanelPos(null);
22637
+ setParentScrollSnap(parentScrollRef.current);
22638
+ setFloatingPanel({
22639
+ key: `button-style:${id}`,
22640
+ title: "Style",
22641
+ context: buttonStyleContext(anchor),
22642
+ kind: "button-style",
22643
+ buttonId: id
22644
+ });
22645
+ },
22646
+ [tokensForButton]
22647
+ );
22648
+ const applySelectedButtonStyle = useCallback10(
22649
+ (patch) => {
22650
+ const panel = floatingPanelRef.current;
22651
+ const id = panel?.kind === "button-style" ? panel.buttonId : null;
22652
+ const el = id ? document.querySelector(`[data-ohw-href-key="${CSS.escape(id)}"]`) : null;
22653
+ if (!el || !id || !isCtaButton(el)) return;
22654
+ const tokens = tokensFromButton(el);
22655
+ const next = { ...assignmentFor(el, editContentRef.current, tokens), ...patch };
22656
+ const assignments = withAssignment(editContentRef.current, id, next);
22657
+ const text = JSON.stringify(assignments);
22658
+ editContentRef.current = { ...editContentRef.current, [BUTTON_ASSIGNMENTS_KEY]: text };
22659
+ applyButtonStyle(el, next, tokens);
22660
+ postToParent2({ type: "ow:change", nodes: [{ key: BUTTON_ASSIGNMENTS_KEY, text }] });
22661
+ setButtonStyleTick((n) => n + 1);
22662
+ if (selectedElRef.current === el) setToolbarRect(el.getBoundingClientRect());
22663
+ },
22664
+ [postToParent2]
22665
+ );
22666
+ useEffect15(() => {
22667
+ const onDragOver = (e) => {
22668
+ const dragged = buttonCollectionDragRef.current;
22669
+ if (!dragged) return;
22670
+ e.preventDefault();
22671
+ if (e.dataTransfer) e.dataTransfer.dropEffect = "move";
22672
+ refreshButtonDragVisuals(dragged, e.clientX, e.clientY);
22673
+ };
22674
+ const onDrop = (e) => {
22675
+ if (!buttonCollectionDragRef.current) return;
22676
+ e.preventDefault();
22677
+ };
22678
+ document.addEventListener("dragover", onDragOver);
22679
+ document.addEventListener("drop", onDrop);
22680
+ return () => {
22681
+ document.removeEventListener("dragover", onDragOver);
22682
+ document.removeEventListener("drop", onDrop);
22683
+ };
22684
+ }, [refreshButtonDragVisuals]);
20706
22685
  const isEditModeRef = useRef12(false);
20707
22686
  const requestMissingSocialIconsRef = useRef12(() => {
20708
22687
  });
@@ -20745,11 +22724,13 @@ function OhhwellsBridge() {
20745
22724
  );
20746
22725
  const closeFloatingPanelOnly = useCallback10(() => {
20747
22726
  setFloatingPanel(null);
22727
+ setFloatingPanelPos(null);
20748
22728
  setLogoSizeDraft(null);
20749
22729
  }, []);
20750
22730
  closeFloatingPanelOnlyRef.current = closeFloatingPanelOnly;
20751
22731
  const closeFloatingPanelAndDeselect = useCallback10(() => {
20752
22732
  setFloatingPanel(null);
22733
+ setFloatingPanelPos(null);
20753
22734
  setLogoSizeDraft(null);
20754
22735
  deselectRef.current();
20755
22736
  }, []);
@@ -20958,6 +22939,7 @@ function OhhwellsBridge() {
20958
22939
  }
20959
22940
  if (typeof content[STYLE_STORE_KEY] === "string") {
20960
22941
  stylesRef.current = content[STYLE_STORE_KEY];
22942
+ document.querySelectorAll('[data-ohw-editable="form"]').forEach((f) => markFormFields(f));
20961
22943
  applyStylesToDom(parseStyleStore(content[STYLE_STORE_KEY]));
20962
22944
  }
20963
22945
  applyBrandChrome(content);
@@ -20966,12 +22948,12 @@ function OhhwellsBridge() {
20966
22948
  for (const [key, val] of Object.entries(content)) {
20967
22949
  if (key === "__ohw_sections") continue;
20968
22950
  if (key === AI_SECTIONS_KEY) continue;
22951
+ if (key === LOGO_PLACEHOLDER_KEY) continue;
22952
+ if (LOGO_IMAGE_KEYS.includes(key)) continue;
20969
22953
  if (key === BRAND_KIT_KEY) continue;
20970
22954
  if (key === STYLE_STORE_KEY) continue;
20971
22955
  if (BRAND_CHROME_KEYS.has(key)) continue;
20972
- if (key === LOGO_PLACEHOLDER_KEY) continue;
20973
22956
  if (key === CUSTOM_CODE_AFTER_HEAD_KEY || key === CUSTOM_CODE_BEFORE_HEAD_KEY) continue;
20974
- if (LOGO_IMAGE_KEYS.includes(key)) continue;
20975
22957
  if (applyVideoSettingNode(key, val)) continue;
20976
22958
  if (applyCarouselNode(key, val)) continue;
20977
22959
  document.querySelectorAll(`[data-ohw-key="${key}"]`).forEach((el) => {
@@ -21018,6 +23000,7 @@ function OhhwellsBridge() {
21018
23000
  reconcileSocialsFromContent(content);
21019
23001
  applySocialsDisplayFromContent(content);
21020
23002
  applySocialsLabelsFromContent(content, document, { fillEmptyOnly: isEditModeRef.current });
23003
+ syncButtonsFromContent(document, content);
21021
23004
  if (isEditModeRef.current) requestMissingSocialIconsRef.current();
21022
23005
  if (typeof content[STYLE_STORE_KEY] === "string") {
21023
23006
  applyStylesToDom(parseStyleStore(content[STYLE_STORE_KEY]));
@@ -21042,7 +23025,9 @@ function OhhwellsBridge() {
21042
23025
  let cancelled = false;
21043
23026
  setFetchState("loading");
21044
23027
  const apiUrl = process.env.NEXT_PUBLIC_FLOWOPS_API_URL ?? "https://flowops-backend-staging-2yfdh7pwpq-as.a.run.app";
21045
- fetch(`${apiUrl}/api/public/sites/${subdomain}/content`, { cache: "no-store" }).then((r2) => r2.ok ? r2.json() : null).then((data) => {
23028
+ const initialPath = pathname;
23029
+ fetchedContentPaths.add(`${subdomain}::${initialPath}`);
23030
+ fetch(`${apiUrl}/api/public/sites/${subdomain}/content?path=${encodeURIComponent(initialPath)}`, { cache: "no-store" }).then((r2) => r2.ok ? r2.json() : null).then((data) => {
21046
23031
  if (cancelled) return;
21047
23032
  const content = data?.content ?? {};
21048
23033
  const branding = Boolean(data?.showBranding);
@@ -21178,16 +23163,17 @@ function OhhwellsBridge() {
21178
23163
  applyAiSectionsToDom(parseAiSectionsState(content[AI_SECTIONS_KEY]));
21179
23164
  }
21180
23165
  if (typeof content[STYLE_STORE_KEY] === "string") {
23166
+ document.querySelectorAll('[data-ohw-editable="form"]').forEach((f) => markFormFields(f));
21181
23167
  applyStylesToDom(parseStyleStore(content[STYLE_STORE_KEY]));
21182
23168
  }
21183
23169
  for (const [key, val] of Object.entries(content)) {
21184
23170
  if (key === "__ohw_sections") continue;
21185
23171
  if (key === AI_SECTIONS_KEY) continue;
23172
+ if (key === LOGO_PLACEHOLDER_KEY) continue;
23173
+ if (LOGO_IMAGE_KEYS.includes(key)) continue;
21186
23174
  if (key === BRAND_KIT_KEY) continue;
21187
23175
  if (key === STYLE_STORE_KEY) continue;
21188
23176
  if (BRAND_CHROME_KEYS.has(key)) continue;
21189
- if (key === LOGO_PLACEHOLDER_KEY) continue;
21190
- if (LOGO_IMAGE_KEYS.includes(key)) continue;
21191
23177
  if (applyVideoSettingNode(key, val)) continue;
21192
23178
  if (applyCarouselNode(key, val)) continue;
21193
23179
  document.querySelectorAll(`[data-ohw-key="${key}"]`).forEach((el) => {
@@ -21218,6 +23204,7 @@ function OhhwellsBridge() {
21218
23204
  reconcileSocialsFromContent(content);
21219
23205
  applySocialsDisplayFromContent(content);
21220
23206
  applySocialsLabelsFromContent(content, document, { fillEmptyOnly: isEditModeRef.current });
23207
+ syncButtonsFromContent(document, content);
21221
23208
  if (isEditModeRef.current) requestMissingSocialIconsRef.current();
21222
23209
  if (typeof content[STYLE_STORE_KEY] === "string") {
21223
23210
  applyStylesToDom(parseStyleStore(content[STYLE_STORE_KEY]));
@@ -21238,6 +23225,17 @@ function OhhwellsBridge() {
21238
23225
  debounceTimer = setTimeout(applyFromCache, 150);
21239
23226
  };
21240
23227
  applyFromCache();
23228
+ const pathCacheKey = `${subdomain}::${pathname}`;
23229
+ if (!fetchedContentPaths.has(pathCacheKey)) {
23230
+ fetchedContentPaths.add(pathCacheKey);
23231
+ const apiUrl = process.env.NEXT_PUBLIC_FLOWOPS_API_URL ?? "https://flowops-backend-staging-2yfdh7pwpq-as.a.run.app";
23232
+ fetch(`${apiUrl}/api/public/sites/${subdomain}/content?path=${encodeURIComponent(pathname)}`, { cache: "no-store" }).then((r2) => r2.ok ? r2.json() : null).then((data) => {
23233
+ if (!data?.content) return;
23234
+ contentCache.set(subdomain, data.content);
23235
+ applyFromCache();
23236
+ }).catch(() => {
23237
+ });
23238
+ }
21241
23239
  observer = new MutationObserver(scheduleApply);
21242
23240
  observer.observe(document.body, { childList: true, subtree: true });
21243
23241
  return () => {
@@ -21306,7 +23304,10 @@ function OhhwellsBridge() {
21306
23304
  if (roots.length === 0) {
21307
23305
  reconcileNavbarItemsFromContent(contentForNav());
21308
23306
  reconcileFooterOrderFromContent(contentForNav());
21309
- if (isEditMode) syncNavigationDragCursorAttrs();
23307
+ if (isEditMode) {
23308
+ syncButtonsFromContent(document, contentForNav());
23309
+ syncNavigationDragCursorAttrs();
23310
+ }
21310
23311
  return;
21311
23312
  }
21312
23313
  let rafId = null;
@@ -21318,7 +23319,7 @@ function OhhwellsBridge() {
21318
23319
  }
21319
23320
  };
21320
23321
  const run = () => {
21321
- if (footerDragRef.current || navDragRef.current || applying) return;
23322
+ if (footerDragRef.current || navDragRef.current || buttonCollectionDragRef.current || applying) return;
21322
23323
  applying = true;
21323
23324
  observer?.disconnect();
21324
23325
  try {
@@ -21328,6 +23329,7 @@ function OhhwellsBridge() {
21328
23329
  reconcileSocialsFromContent(content);
21329
23330
  applySocialsDisplayFromContent(content);
21330
23331
  applySocialsLabelsFromContent(content, document, { fillEmptyOnly: isEditModeRef.current });
23332
+ syncButtonsFromContent(document, content);
21331
23333
  if (isEditModeRef.current) requestMissingSocialIconsRef.current();
21332
23334
  document.querySelectorAll("footer [data-ohw-href-key]").forEach((el) => {
21333
23335
  if (isFooterHrefKey(el.getAttribute("data-ohw-href-key")) || getSocialItem(el)) {
@@ -21335,9 +23337,9 @@ function OhhwellsBridge() {
21335
23337
  }
21336
23338
  });
21337
23339
  document.querySelectorAll(
21338
- "nav [data-ohw-href-key], [data-ohw-nav-container] [data-ohw-href-key], [data-ohw-nav-drawer] [data-ohw-href-key]"
23340
+ 'nav [data-ohw-href-key], [data-ohw-nav-container] [data-ohw-href-key], [data-ohw-nav-drawer] [data-ohw-href-key], [data-ohw-role="navbar-button"]'
21339
23341
  ).forEach((el) => {
21340
- if (isNavbarHrefKey(el.getAttribute("data-ohw-href-key"))) {
23342
+ if (isNavbarHrefKey(el.getAttribute("data-ohw-href-key")) || isCtaButton(el)) {
21341
23343
  disableNativeHrefDrag(el);
21342
23344
  }
21343
23345
  });
@@ -21362,6 +23364,44 @@ function OhhwellsBridge() {
21362
23364
  observer?.disconnect();
21363
23365
  };
21364
23366
  }, [isEditMode, pathname, subdomain, fetchState, resyncSelectedNavigationItem]);
23367
+ useEffect15(() => {
23368
+ if (!isEditMode) return;
23369
+ let applying = false;
23370
+ let rafId = null;
23371
+ const run = () => {
23372
+ if (applying || buttonCollectionDragRef.current) return;
23373
+ const content = editContentRef.current;
23374
+ if (!content[BUTTON_COLLECTIONS_KEY] && !content[BUTTON_SNAPSHOTS_KEY]) return;
23375
+ applying = true;
23376
+ try {
23377
+ syncButtonsFromContent(document, content);
23378
+ } finally {
23379
+ applying = false;
23380
+ }
23381
+ };
23382
+ const schedule = () => {
23383
+ if (rafId != null) return;
23384
+ rafId = requestAnimationFrame(() => {
23385
+ rafId = null;
23386
+ run();
23387
+ });
23388
+ };
23389
+ const observer = new MutationObserver((mutations) => {
23390
+ if (mutations.every((m) => {
23391
+ const t = m.target;
23392
+ return t instanceof Element && Boolean(t.closest("[data-ohw-bridge], [data-ohw-bridge-root], [data-ohw-button-collection]"));
23393
+ })) {
23394
+ return;
23395
+ }
23396
+ schedule();
23397
+ });
23398
+ observer.observe(document.body, { childList: true, subtree: true });
23399
+ run();
23400
+ return () => {
23401
+ if (rafId != null) cancelAnimationFrame(rafId);
23402
+ observer.disconnect();
23403
+ };
23404
+ }, [isEditMode, pathname]);
21365
23405
  useEffect15(() => {
21366
23406
  if (!isEditMode) return;
21367
23407
  let lastPosted = 0;
@@ -21384,26 +23424,11 @@ function OhhwellsBridge() {
21384
23424
  const t2 = setTimeout(measure, 500);
21385
23425
  const ro = new ResizeObserver(schedule);
21386
23426
  ro.observe(document.body);
21387
- let lastWidth = window.innerWidth;
21388
- let resizeTimers = [];
21389
- const clearResizeTimers = () => {
21390
- resizeTimers.forEach(clearTimeout);
21391
- resizeTimers = [];
21392
- };
21393
- const handleResize = () => {
21394
- if (window.innerWidth === lastWidth) return;
21395
- lastWidth = window.innerWidth;
21396
- clearResizeTimers();
21397
- resizeTimers = HEIGHT_SETTLE_DELAYS.map((delay) => setTimeout(measure, delay));
21398
- };
21399
- window.addEventListener("resize", handleResize);
21400
23427
  return () => {
21401
23428
  clearTimeout(t1);
21402
23429
  clearTimeout(t2);
21403
23430
  if (raf != null) cancelAnimationFrame(raf);
21404
23431
  ro.disconnect();
21405
- clearResizeTimers();
21406
- window.removeEventListener("resize", handleResize);
21407
23432
  };
21408
23433
  }, [pathname, isEditMode, postToParent2]);
21409
23434
  useEffect15(() => {
@@ -21698,6 +23723,13 @@ function OhhwellsBridge() {
21698
23723
  if (target.closest("[data-ohw-state-toggle]")) return;
21699
23724
  if (target.closest("[data-ohw-max-badge]")) return;
21700
23725
  if (isInsideLinkEditor(target)) return;
23726
+ if (isInsideFloatingPanel(target)) return;
23727
+ if (target.closest("[data-ohw-form-toolbar]")) return;
23728
+ if (target.closest(
23729
+ '[data-ohw-field-toolbar], [data-ohw-field-type-picker], [data-radix-popper-content-wrapper], [role="menu"], [data-slot="dropdown-menu-content"]'
23730
+ )) {
23731
+ return;
23732
+ }
21701
23733
  if (selectedMediaElRef.current && !selectedMediaElRef.current.contains(target) && !target.closest("[data-ohw-media-overlay], [data-ohw-edit-chrome], [data-ohw-item-interaction]")) {
21702
23734
  clearMediaSelectionRef.current();
21703
23735
  }
@@ -21718,13 +23750,6 @@ function OhhwellsBridge() {
21718
23750
  return;
21719
23751
  }
21720
23752
  }
21721
- if (isInsideFloatingPanel(target)) return;
21722
- if (target.closest("[data-ohw-form-toolbar]")) return;
21723
- if (target.closest(
21724
- '[data-ohw-field-toolbar], [data-ohw-field-type-picker], [data-radix-popper-content-wrapper], [role="menu"], [data-slot="dropdown-menu-content"]'
21725
- )) {
21726
- return;
21727
- }
21728
23753
  {
21729
23754
  const formEl = getFormElement(target);
21730
23755
  const onSuccessText = target.closest(`[${SUCCESS_TEXT_ATTR}]`);
@@ -21888,14 +23913,6 @@ function OhhwellsBridge() {
21888
23913
  }
21889
23914
  const clickedButton = findClosestButtonLike(target);
21890
23915
  const buttonOnMedia = Boolean(clickedButton && isMediaEditable(editable) && editable.contains(clickedButton));
21891
- console.log("[click-debug]", {
21892
- editableType: editable.dataset.ohwEditable,
21893
- editableTag: editable.tagName,
21894
- targetTag: target.tagName,
21895
- clickedButtonTag: clickedButton?.tagName ?? null,
21896
- buttonOnMedia,
21897
- isMediaEditableEditable: isMediaEditable(editable)
21898
- });
21899
23916
  if (isMediaEditable(editable) && !buttonOnMedia) {
21900
23917
  e.preventDefault();
21901
23918
  e.stopPropagation();
@@ -21930,11 +23947,6 @@ function OhhwellsBridge() {
21930
23947
  const hrefLookupTarget = buttonOnMedia ? clickedButton : editable;
21931
23948
  const hrefCtx = getHrefKeyFromElement(hrefLookupTarget);
21932
23949
  const navAnchor = hrefCtx ? getNavigationItemAnchor(hrefCtx.anchor) : null;
21933
- console.log("[click-debug 2]", {
21934
- hrefLookupTargetTag: hrefLookupTarget.tagName,
21935
- hrefCtx: hrefCtx ? { key: hrefCtx.key } : null,
21936
- navAnchorTag: navAnchor?.tagName ?? null
21937
- });
21938
23950
  if (navAnchor) {
21939
23951
  e.preventDefault();
21940
23952
  e.stopPropagation();
@@ -22117,6 +24129,9 @@ function OhhwellsBridge() {
22117
24129
  setHoveredItemRect(null);
22118
24130
  hoveredNavContainerRef.current = null;
22119
24131
  setHoveredNavContainerRect(null);
24132
+ siblingHintElRef.current = null;
24133
+ setSiblingHintRect(null);
24134
+ setSiblingHintRects([]);
22120
24135
  return;
22121
24136
  }
22122
24137
  {
@@ -22237,7 +24252,6 @@ function OhhwellsBridge() {
22237
24252
  hoveredNavContainerRef.current = null;
22238
24253
  setHoveredNavContainerRect(null);
22239
24254
  hoveredItemElRef.current = editable;
22240
- setHoveredItemRect(isSelectedForHoverRef.current(editable) ? null : editable.getBoundingClientRect());
22241
24255
  }
22242
24256
  }
22243
24257
  }
@@ -22450,6 +24464,20 @@ function OhhwellsBridge() {
22450
24464
  }
22451
24465
  return;
22452
24466
  }
24467
+ const navbarCtaHit = findNavbarCtaAtPoint(x, y);
24468
+ if (navbarCtaHit) {
24469
+ hoveredNavContainerRef.current = null;
24470
+ setHoveredNavContainerRect(null);
24471
+ if (selected !== navbarCtaHit && !selected?.contains(navbarCtaHit)) {
24472
+ clearHrefKeyHover(navbarCtaHit);
24473
+ hoveredItemElRef.current = navbarCtaHit;
24474
+ setHoveredItemRect(navbarCtaHit.getBoundingClientRect());
24475
+ } else if (hoveredItemElRef.current === navbarCtaHit) {
24476
+ hoveredItemElRef.current = null;
24477
+ setHoveredItemRect(null);
24478
+ }
24479
+ return;
24480
+ }
22453
24481
  const navContainers = Array.from(
22454
24482
  document.querySelectorAll("[data-ohw-nav-container]")
22455
24483
  );
@@ -22541,7 +24569,7 @@ function OhhwellsBridge() {
22541
24569
  };
22542
24570
  const probeImageAt = (clientX, clientY, isDragOver = false, fromParentViewport = false) => {
22543
24571
  syncSelectedMediaPointerRelease(true);
22544
- if (linkPopoverOpenRef.current) {
24572
+ if (linkPopoverOpenRef.current || floatingPanelOpenRef.current && isPointOverFloatingPanel(clientX, clientY)) {
22545
24573
  if (hoveredImageRef.current) {
22546
24574
  hoveredImageRef.current = null;
22547
24575
  hoveredImageHasTextOverlapRef.current = false;
@@ -22552,6 +24580,24 @@ function OhhwellsBridge() {
22552
24580
  return;
22553
24581
  }
22554
24582
  const { x, y } = toProbeCoords(clientX, clientY, fromParentViewport);
24583
+ if (!isDragOver) {
24584
+ const buttonHit = findButtonAtPoint(x, y);
24585
+ if (buttonHit) {
24586
+ dismissImageHover();
24587
+ if (findNavbarCtaAtPoint(x, y)) {
24588
+ probeNavigationHoverAt(x, y);
24589
+ return;
24590
+ }
24591
+ document.querySelectorAll("[data-ohw-hovered]").forEach((el) => el.removeAttribute("data-ohw-hovered"));
24592
+ const selected = selectedElRef.current;
24593
+ if (selected !== buttonHit && !selected?.contains(buttonHit)) {
24594
+ clearHrefKeyHover(buttonHit);
24595
+ hoveredItemElRef.current = buttonHit;
24596
+ setHoveredItemRect(buttonHit.getBoundingClientRect());
24597
+ }
24598
+ return;
24599
+ }
24600
+ }
22555
24601
  if (isPointOverNavigation(x, y)) {
22556
24602
  dismissImageHover();
22557
24603
  probeNavigationHoverAt(x, y);
@@ -22915,7 +24961,11 @@ function OhhwellsBridge() {
22915
24961
  };
22916
24962
  const handleMouseMove = (e) => {
22917
24963
  const { clientX, clientY } = e;
22918
- if (pointOwnedByFloatingPanel(clientX, clientY)) return;
24964
+ if (pointOwnedByFloatingPanel(clientX, clientY)) {
24965
+ dismissImageHover();
24966
+ clearImageHover();
24967
+ return;
24968
+ }
22919
24969
  if (isNavDrawerOpen() && isPointOutsideOpenDrawer(clientX, clientY)) {
22920
24970
  document.querySelectorAll("[data-ohw-hovered]").forEach((el) => el.removeAttribute("data-ohw-hovered"));
22921
24971
  formHoverElRef.current = null;
@@ -22944,7 +24994,12 @@ function OhhwellsBridge() {
22944
24994
  setHoveredItemRect(null);
22945
24995
  hoveredNavContainerRef.current = null;
22946
24996
  setHoveredNavContainerRect(null);
24997
+ siblingHintElRef.current = null;
24998
+ setSiblingHintRect(null);
24999
+ setSiblingHintRects([]);
22947
25000
  dismissImageHover();
25001
+ clearImageHover();
25002
+ setSectionGap(null);
22948
25003
  return;
22949
25004
  }
22950
25005
  if (probeSocialsRowAt(clientX, clientY)) return;
@@ -22956,7 +25011,11 @@ function OhhwellsBridge() {
22956
25011
  if (e.data?.type !== "ow:pointer-sync") return;
22957
25012
  const { clientX, clientY } = e.data;
22958
25013
  if (typeof clientX !== "number" || typeof clientY !== "number") return;
22959
- if (pointOwnedByFloatingPanel(clientX, clientY)) return;
25014
+ if (pointOwnedByFloatingPanel(clientX, clientY)) {
25015
+ dismissImageHover();
25016
+ clearImageHover();
25017
+ return;
25018
+ }
22960
25019
  if (isNavDrawerOpen() && isPointOutsideOpenDrawer(clientX, clientY)) return;
22961
25020
  if (probeSocialsRowAt(clientX, clientY)) return;
22962
25021
  probeSectionGapAt(clientX, clientY);
@@ -22973,6 +25032,11 @@ function OhhwellsBridge() {
22973
25032
  return;
22974
25033
  }
22975
25034
  if (onNavDragOver(e)) return;
25035
+ if (buttonCollectionDragRef.current) {
25036
+ e.preventDefault();
25037
+ if (e.dataTransfer) e.dataTransfer.dropEffect = "move";
25038
+ return;
25039
+ }
22976
25040
  e.preventDefault();
22977
25041
  const el = findImageAtPoint(e.clientX, e.clientY, false);
22978
25042
  if (!el) {
@@ -22997,6 +25061,7 @@ function OhhwellsBridge() {
22997
25061
  };
22998
25062
  const handleDrop = (e) => {
22999
25063
  e.preventDefault();
25064
+ if (buttonCollectionDragRef.current || navDragRef.current || footerDragRef.current) return;
23000
25065
  const el = findImageAtPoint(e.clientX, e.clientY, false);
23001
25066
  if (!el) return;
23002
25067
  const file = e.dataTransfer?.files?.[0];
@@ -23296,6 +25361,7 @@ function OhhwellsBridge() {
23296
25361
  }
23297
25362
  if (typeof content[STYLE_STORE_KEY] === "string") {
23298
25363
  stylesRef.current = content[STYLE_STORE_KEY];
25364
+ document.querySelectorAll('[data-ohw-editable="form"]').forEach((f) => markFormFields(f));
23299
25365
  applyStylesToDom(parseStyleStore(content[STYLE_STORE_KEY]));
23300
25366
  }
23301
25367
  applyBrandChrome(content);
@@ -23308,12 +25374,12 @@ function OhhwellsBridge() {
23308
25374
  continue;
23309
25375
  }
23310
25376
  if (key === AI_SECTIONS_KEY) continue;
25377
+ if (key === LOGO_PLACEHOLDER_KEY) continue;
25378
+ if (LOGO_IMAGE_KEYS.includes(key) && !String(val ?? "").trim()) continue;
23311
25379
  if (key === BRAND_KIT_KEY) continue;
23312
25380
  if (key === STYLE_STORE_KEY) continue;
23313
25381
  if (BRAND_CHROME_KEYS.has(key)) continue;
23314
- if (key === LOGO_PLACEHOLDER_KEY) continue;
23315
25382
  if (key === CUSTOM_CODE_AFTER_HEAD_KEY || key === CUSTOM_CODE_BEFORE_HEAD_KEY) continue;
23316
- if (LOGO_IMAGE_KEYS.includes(key) && !String(val ?? "").trim()) continue;
23317
25383
  if (applyVideoSettingNode(key, val)) continue;
23318
25384
  if (applyCarouselNode(key, val)) continue;
23319
25385
  document.querySelectorAll(`[data-ohw-key="${key}"]`).forEach((el) => {
@@ -23352,6 +25418,7 @@ function OhhwellsBridge() {
23352
25418
  editContentRef.current = { ...editContentRef.current, ...content };
23353
25419
  reconcileNavbarItemsFromContent(editContentRef.current);
23354
25420
  reconcileFooterOrderFromContent(editContentRef.current);
25421
+ syncButtonsFromContent(document, editContentRef.current);
23355
25422
  syncNavigationDragCursorAttrs();
23356
25423
  if (typeof content[STYLE_STORE_KEY] === "string") {
23357
25424
  applyStylesToDom(parseStyleStore(content[STYLE_STORE_KEY]));
@@ -23758,6 +25825,12 @@ function OhhwellsBridge() {
23758
25825
  closeLinkPopoverRef.current();
23759
25826
  return;
23760
25827
  }
25828
+ if (floatingPanelOpenRef.current) {
25829
+ setFloatingPanelRef.current(null);
25830
+ deselectRef.current();
25831
+ deactivateRef.current();
25832
+ return;
25833
+ }
23761
25834
  deselectRef.current();
23762
25835
  deactivateRef.current();
23763
25836
  clearMediaSelectionRef.current();
@@ -24056,8 +26129,12 @@ function OhhwellsBridge() {
24056
26129
  if (inserted) {
24057
26130
  const tracker = getSectionsTracker();
24058
26131
  postToParentRef.current({ type: "ow:change", nodes: [{ key: "__ohw_sections", text: tracker.textContent ?? "[]" }] });
24059
- const h = document.body.scrollHeight;
24060
- if (h > 50) postToParentRef.current({ type: "ow:height", height: h });
26132
+ const reportHeight = () => {
26133
+ const h = document.body.scrollHeight;
26134
+ if (h > 50) postToParentRef.current({ type: "ow:height", height: h });
26135
+ };
26136
+ reportHeight();
26137
+ setTimeout(reportHeight, 500);
24061
26138
  }
24062
26139
  };
24063
26140
  const handleSwitchSchedule = (e) => {
@@ -24261,11 +26338,8 @@ function OhhwellsBridge() {
24261
26338
  };
24262
26339
  const handleParentScroll = (e) => {
24263
26340
  if (e.data?.type !== "ow:parent-scroll") return;
24264
- const { iframeOffsetTop, headerH, canvasH } = e.data;
24265
- parentScrollRef.current = { iframeOffsetTop, headerH, canvasH };
24266
- if (floatingPanelOpenRef.current) {
24267
- setParentScrollSnap({ iframeOffsetTop, headerH, canvasH });
24268
- }
26341
+ const { iframeOffsetTop, headerH, canvasH, promptBarH = 0 } = e.data;
26342
+ parentScrollRef.current = { iframeOffsetTop, headerH, canvasH, promptBarH };
24269
26343
  if (visibleViewportRef.current) {
24270
26344
  applyVisibleViewport(visibleViewportRef.current, parentScrollRef.current);
24271
26345
  }
@@ -24489,10 +26563,10 @@ function OhhwellsBridge() {
24489
26563
  window.removeEventListener("message", handleDeleteSection);
24490
26564
  window.removeEventListener("message", handleDuplicateSection);
24491
26565
  window.removeEventListener("message", handleDeactivate);
24492
- document.documentElement.removeAttribute("data-ohw-panel-dragging");
24493
26566
  window.removeEventListener("message", handleToastAction);
24494
26567
  window.removeEventListener("message", handleFormCount);
24495
26568
  window.removeEventListener("message", handleUiEscape);
26569
+ document.documentElement.removeAttribute("data-ohw-panel-dragging");
24496
26570
  autoSaveTimers.current.forEach(clearTimeout);
24497
26571
  autoSaveTimers.current.clear();
24498
26572
  if (imageUnhoverTimerRef.current) clearTimeout(imageUnhoverTimerRef.current);
@@ -24655,6 +26729,97 @@ function OhhwellsBridge() {
24655
26729
  unlockFooterDragInteraction();
24656
26730
  };
24657
26731
  }, [isEditMode]);
26732
+ useEffect15(() => {
26733
+ if (!isEditMode) return;
26734
+ const THRESHOLD = 10;
26735
+ const onPointerDown = (e) => {
26736
+ if (e.button !== 0) return;
26737
+ if (buttonCollectionDragRef.current || navDragRef.current || footerDragRef.current) return;
26738
+ if (buttonPointerDragRef.current) return;
26739
+ const target = e.target;
26740
+ if (!target) return;
26741
+ if (target.closest(
26742
+ '[data-ohw-drag-handle-container], [data-slot="drag-handle"], [data-ohw-toolbar], [data-ohw-item-toolbar-anchor], [data-ohw-link-popover-root], [data-ohw-floating-panel]'
26743
+ )) {
26744
+ return;
26745
+ }
26746
+ const fromTarget = target.closest('[data-ohw-role="navbar-button"]');
26747
+ const anchor = (fromTarget && !fromTarget.closest("[data-ohw-bridge]") ? fromTarget : null) ?? findNavbarCtaAtPoint(e.clientX, e.clientY);
26748
+ if (!anchor || !collectionDragEnabled(anchor)) return;
26749
+ disableNativeHrefDrag(anchor);
26750
+ setMediaHover(null);
26751
+ buttonPointerDragRef.current = {
26752
+ el: anchor,
26753
+ startX: e.clientX,
26754
+ startY: e.clientY,
26755
+ pointerId: e.pointerId,
26756
+ started: false
26757
+ };
26758
+ };
26759
+ const onPointerMove = (e) => {
26760
+ const pending = buttonPointerDragRef.current;
26761
+ if (!pending) return;
26762
+ if (pending.started) {
26763
+ e.preventDefault();
26764
+ clearTextSelection();
26765
+ const dragged = buttonCollectionDragRef.current;
26766
+ if (!dragged) return;
26767
+ refreshButtonDragVisuals(dragged, e.clientX, e.clientY);
26768
+ return;
26769
+ }
26770
+ const dx = e.clientX - pending.startX;
26771
+ const dy = e.clientY - pending.startY;
26772
+ if (dx * dx + dy * dy < THRESHOLD * THRESHOLD) return;
26773
+ e.preventDefault();
26774
+ pending.started = true;
26775
+ armFooterPressDrag();
26776
+ clearTextSelection();
26777
+ try {
26778
+ document.body.setPointerCapture(pending.pointerId);
26779
+ } catch {
26780
+ }
26781
+ if (linkPopoverOpenRef.current) setLinkPopoverRef.current(null);
26782
+ if (activeElRef.current) deactivateRef.current();
26783
+ ensureNavbarButtonCollection(pending.el, editContentRef.current);
26784
+ buttonCollectionDragRef.current = pending.el;
26785
+ lockFooterDuringDrag();
26786
+ setIsItemDragging(true);
26787
+ refreshButtonDragVisuals(pending.el, e.clientX, e.clientY);
26788
+ };
26789
+ const endPointerDrag = (e) => {
26790
+ const pending = buttonPointerDragRef.current;
26791
+ buttonPointerDragRef.current = null;
26792
+ try {
26793
+ if (document.body.hasPointerCapture(e.pointerId)) {
26794
+ document.body.releasePointerCapture(e.pointerId);
26795
+ }
26796
+ } catch {
26797
+ }
26798
+ if (!pending) return;
26799
+ if (!pending.started) {
26800
+ unlockFooterDragInteraction();
26801
+ return;
26802
+ }
26803
+ suppressNextClickRef.current = true;
26804
+ suppressClickUntilRef.current = Date.now() + 500;
26805
+ persistButtonCollectionDrag(pending.el, e.clientX);
26806
+ };
26807
+ const blockSelectStart = (e) => {
26808
+ if (buttonCollectionDragRef.current || buttonPointerDragRef.current?.started) e.preventDefault();
26809
+ };
26810
+ document.addEventListener("pointerdown", onPointerDown, true);
26811
+ document.addEventListener("pointermove", onPointerMove, true);
26812
+ document.addEventListener("pointerup", endPointerDrag, true);
26813
+ document.addEventListener("pointercancel", endPointerDrag, true);
26814
+ document.addEventListener("selectstart", blockSelectStart, true);
26815
+ return () => {
26816
+ document.removeEventListener("pointerdown", onPointerDown, true);
26817
+ document.removeEventListener("pointermove", onPointerMove, true);
26818
+ document.removeEventListener("pointerup", endPointerDrag, true);
26819
+ document.removeEventListener("pointercancel", endPointerDrag, true);
26820
+ document.removeEventListener("selectstart", blockSelectStart, true);
26821
+ };
26822
+ }, [isEditMode, persistButtonCollectionDrag, refreshButtonDragVisuals]);
24658
26823
  useEffect15(() => {
24659
26824
  const handler = (e) => {
24660
26825
  if (e.data?.type !== "ow:request-schedule-config") return;
@@ -24695,7 +26860,7 @@ function OhhwellsBridge() {
24695
26860
  postToParent2({
24696
26861
  type: "ow:ready",
24697
26862
  version: "1",
24698
- bridgeVersion: "0.1.120",
26863
+ bridgeVersion: "0.1.122",
24699
26864
  path: pathname,
24700
26865
  nodes: collectEditableNodes(editContentRef.current),
24701
26866
  sections
@@ -24774,12 +26939,12 @@ function OhhwellsBridge() {
24774
26939
  return;
24775
26940
  }
24776
26941
  if (type !== "ow:logo-size-set") return;
24777
- const px = Number(e.data.px);
24778
- if (!Number.isFinite(px)) return;
24779
- const next = editorViewport === "mobile" ? { ...logoSizeDraft, mobilePx: px, mobileFollowing: false } : {
26942
+ const px2 = Number(e.data.px);
26943
+ if (!Number.isFinite(px2)) return;
26944
+ const next = editorViewport === "mobile" ? { ...logoSizeDraft, mobilePx: px2, mobileFollowing: false } : {
24780
26945
  ...logoSizeDraft,
24781
- desktopPx: px,
24782
- mobilePx: logoSizeDraft.mobileFollowing ? px : logoSizeDraft.mobilePx
26946
+ desktopPx: px2,
26947
+ mobilePx: logoSizeDraft.mobileFollowing ? px2 : logoSizeDraft.mobilePx
24783
26948
  };
24784
26949
  setLogoSizeDraft(next);
24785
26950
  persistLogoSizeDraft(session.placement, next);
@@ -24877,6 +27042,36 @@ function OhhwellsBridge() {
24877
27042
  const handleDuplicateSelected = useCallback10(() => {
24878
27043
  const selected = selectedElRef.current;
24879
27044
  if (!selected || !isNavigationItem2(selected)) return;
27045
+ if (isCtaButton(selected)) {
27046
+ const id = buttonId(selected);
27047
+ if (!id) return;
27048
+ const tokens = tokensForButton(selected, true);
27049
+ if (!readAssignments(editContentRef.current)[id]) {
27050
+ const assignment = assignmentFor(selected, editContentRef.current, tokens);
27051
+ const assignments = withAssignment(editContentRef.current, id, assignment);
27052
+ editContentRef.current = {
27053
+ ...editContentRef.current,
27054
+ [BUTTON_ASSIGNMENTS_KEY]: JSON.stringify(assignments)
27055
+ };
27056
+ applyButtonStyle(selected, assignment, tokens);
27057
+ }
27058
+ const result = duplicateButton(selected, editContentRef.current);
27059
+ if (!result) return;
27060
+ editContentRef.current = result.content;
27061
+ const copyHref = result.copy.getAttribute("data-ohw-href-key");
27062
+ const copyHrefVal = copyHref ? result.content[copyHref] : void 0;
27063
+ if (copyHref && copyHrefVal !== void 0) applyLinkByKey(copyHref, copyHrefVal);
27064
+ postToParent2({ type: "ow:change", nodes: result.nodes });
27065
+ enforceLinkHrefs();
27066
+ requestAnimationFrame(() => {
27067
+ selectRef.current(result.copy);
27068
+ requestAnimationFrame(() => {
27069
+ const el = selectedElRef.current;
27070
+ if (el?.isConnected) setToolbarRect(el.getBoundingClientRect());
27071
+ });
27072
+ });
27073
+ return;
27074
+ }
24880
27075
  const hrefKey = selected.getAttribute("data-ohw-href-key");
24881
27076
  if (!hrefKey) return;
24882
27077
  const social = getSocialItem(selected);
@@ -25006,7 +27201,7 @@ function OhhwellsBridge() {
25006
27201
  selectRef.current(result.anchor);
25007
27202
  });
25008
27203
  }
25009
- }, [postToParent2]);
27204
+ }, [postToParent2, tokensForButton]);
25010
27205
  const runPendingDeleteUndo = useCallback10(() => {
25011
27206
  const pending = pendingDeleteUndoRef.current;
25012
27207
  if (!pending) return false;
@@ -25018,6 +27213,38 @@ function OhhwellsBridge() {
25018
27213
  const handleDeleteSelected = useCallback10(() => {
25019
27214
  const selected = selectedElRef.current;
25020
27215
  if (!selected) return false;
27216
+ if (isCtaButton(selected)) {
27217
+ if (!canDeleteButton(selected)) return false;
27218
+ const previousContent = editContentRef.current;
27219
+ const result = deleteButton(selected, previousContent);
27220
+ if (!result) return false;
27221
+ editContentRef.current = result.content;
27222
+ postToParent2({ type: "ow:change", nodes: result.nodes });
27223
+ const actionId = typeof crypto !== "undefined" && "randomUUID" in crypto ? crypto.randomUUID() : `delete-${Date.now()}`;
27224
+ const restoreNodes = result.nodes.map((node) => ({
27225
+ key: node.key,
27226
+ text: previousContent[node.key] ?? ""
27227
+ }));
27228
+ pendingDeleteUndoRef.current = {
27229
+ actionId,
27230
+ restore: () => {
27231
+ result.undo();
27232
+ editContentRef.current = { ...editContentRef.current, ...Object.fromEntries(restoreNodes.map((node) => [node.key, node.text])) };
27233
+ postToParent2({ type: "ow:change", nodes: restoreNodes });
27234
+ syncButtonsFromContent(document, editContentRef.current);
27235
+ }
27236
+ };
27237
+ deselectRef.current();
27238
+ postToParent2({
27239
+ type: "ow:toast",
27240
+ title: "Button deleted",
27241
+ toastType: "success",
27242
+ actionLabel: "Undo",
27243
+ actionId,
27244
+ duration: 6e3
27245
+ });
27246
+ return true;
27247
+ }
25021
27248
  return deleteSelectedNavFooterItem({
25022
27249
  selected,
25023
27250
  isTextEditing: Boolean(activeElRef.current),
@@ -25155,14 +27382,14 @@ function OhhwellsBridge() {
25155
27382
  },
25156
27383
  [postToParent2]
25157
27384
  );
25158
- return /* @__PURE__ */ jsxs22(Fragment10, { children: [
25159
- /* @__PURE__ */ jsx35("div", { id: "ohw-loader", suppressHydrationWarning: true, style: { ...OHW_LOADER_STYLE, display: "none" }, children: /* @__PURE__ */ jsx35(OhwLoaderSpinner, {}) }),
25160
- /* @__PURE__ */ jsx35("script", { suppressHydrationWarning: true, dangerouslySetInnerHTML: { __html: OHW_LOADER_PREHYDRATE_SCRIPT } }),
25161
- subdomain && !isEditMode && showBranding && /* @__PURE__ */ jsx35(MadeWithOhhWells, {}),
27385
+ return /* @__PURE__ */ jsxs23(Fragment10, { children: [
27386
+ /* @__PURE__ */ jsx36("div", { id: "ohw-loader", suppressHydrationWarning: true, style: { ...OHW_LOADER_STYLE, display: "none" }, children: /* @__PURE__ */ jsx36(OhwLoaderSpinner, {}) }),
27387
+ /* @__PURE__ */ jsx36("script", { suppressHydrationWarning: true, dangerouslySetInnerHTML: { __html: OHW_LOADER_PREHYDRATE_SCRIPT } }),
27388
+ subdomain && !isEditMode && showBranding && /* @__PURE__ */ jsx36(MadeWithOhhWells, {}),
25162
27389
  bridgeRoot ? createPortal3(
25163
- /* @__PURE__ */ jsxs22(Fragment10, { children: [
25164
- /* @__PURE__ */ jsx35("div", { ref: attachVisibleViewport, "data-ohw-visible-viewport": "", "aria-hidden": true }),
25165
- isEditMode && /* @__PURE__ */ jsx35(
27390
+ /* @__PURE__ */ jsxs23(Fragment10, { children: [
27391
+ /* @__PURE__ */ jsx36("div", { ref: attachVisibleViewport, "data-ohw-visible-viewport": "", "aria-hidden": true }),
27392
+ isEditMode && /* @__PURE__ */ jsx36(
25166
27393
  AiSectionOverlay,
25167
27394
  {
25168
27395
  postToParent: postToParent2,
@@ -25170,7 +27397,7 @@ function OhhwellsBridge() {
25170
27397
  onSelectionChange: handleAiSectionSelectionChange
25171
27398
  }
25172
27399
  ),
25173
- isEditMode && /* @__PURE__ */ jsx35(
27400
+ isEditMode && /* @__PURE__ */ jsx36(
25174
27401
  AiMediaStripOverlay,
25175
27402
  {
25176
27403
  postToParent: postToParent2,
@@ -25179,7 +27406,7 @@ function OhhwellsBridge() {
25179
27406
  clearInteractionChromeRef
25180
27407
  }
25181
27408
  ),
25182
- isEditMode && /* @__PURE__ */ jsx35(
27409
+ isEditMode && /* @__PURE__ */ jsx36(
25183
27410
  AiPricingCardOverlay,
25184
27411
  {
25185
27412
  postToParent: postToParent2,
@@ -25187,12 +27414,12 @@ function OhhwellsBridge() {
25187
27414
  applyTreeLocalRef
25188
27415
  }
25189
27416
  ),
25190
- isSectionDragging && sectionDropSlots.map((slot, i) => /* @__PURE__ */ jsx35(
27417
+ isSectionDragging && sectionDropSlots.map((slot, i) => /* @__PURE__ */ jsx36(
25191
27418
  "div",
25192
27419
  {
25193
27420
  className: "pointer-events-none fixed z-2147483646",
25194
27421
  style: { left: slot.left, top: slot.y, width: slot.width, height: 3, transform: "translateY(-50%)" },
25195
- children: /* @__PURE__ */ jsx35(
27422
+ children: /* @__PURE__ */ jsx36(
25196
27423
  DropIndicator,
25197
27424
  {
25198
27425
  direction: "horizontal",
@@ -25203,7 +27430,7 @@ function OhhwellsBridge() {
25203
27430
  },
25204
27431
  `section-drop-${slot.insertIndex}-${i}`
25205
27432
  )),
25206
- Object.entries(uploadingRects).map(([key, { rect, fadingOut }]) => /* @__PURE__ */ jsx35(
27433
+ Object.entries(uploadingRects).map(([key, { rect, fadingOut }]) => /* @__PURE__ */ jsx36(
25207
27434
  MediaOverlay,
25208
27435
  {
25209
27436
  hover: { key, rect, elementType: "image", isDragOver: false, hasTextOverlap: false },
@@ -25214,7 +27441,7 @@ function OhhwellsBridge() {
25214
27441
  },
25215
27442
  `uploading-${key}`
25216
27443
  )),
25217
- mediaHover && !(mediaHover.key in uploadingRects) && mediaHover.key !== selectedMedia?.key && /* @__PURE__ */ jsx35(
27444
+ mediaHover && !(mediaHover.key in uploadingRects) && mediaHover.key !== selectedMedia?.key && /* @__PURE__ */ jsx36(
25218
27445
  MediaOverlay,
25219
27446
  {
25220
27447
  hover: mediaHover,
@@ -25225,7 +27452,7 @@ function OhhwellsBridge() {
25225
27452
  onVideoSettingsChange: handleVideoSettingsChange
25226
27453
  }
25227
27454
  ),
25228
- selectedMedia && !(selectedMedia.key in uploadingRects) && !linkPopover && /* @__PURE__ */ jsx35(
27455
+ selectedMedia && !(selectedMedia.key in uploadingRects) && !linkPopover && /* @__PURE__ */ jsx36(
25229
27456
  MediaOverlay,
25230
27457
  {
25231
27458
  hover: selectedMedia,
@@ -25237,7 +27464,7 @@ function OhhwellsBridge() {
25237
27464
  onVideoSettingsChange: handleVideoSettingsChange
25238
27465
  }
25239
27466
  ),
25240
- carouselHover && !linkPopover && !isItemDragging && /* @__PURE__ */ jsx35(
27467
+ carouselHover && !linkPopover && !isItemDragging && /* @__PURE__ */ jsx36(
25241
27468
  CarouselOverlay,
25242
27469
  {
25243
27470
  hover: carouselHover,
@@ -25245,10 +27472,10 @@ function OhhwellsBridge() {
25245
27472
  cueOnly: !!carouselHover.fullBleedSectionId && carouselHover.fullBleedSectionId !== selectedAiSectionId
25246
27473
  }
25247
27474
  ),
25248
- siblingHintRect && !linkPopover && !isItemDragging && siblingHintRects.length === 0 && /* @__PURE__ */ jsx35(ItemInteractionLayer, { rect: siblingHintRect, state: "sibling-hint" }),
25249
- siblingHintRects.length > 0 && !linkPopover && !isItemDragging && siblingHintRects.map((rect, i) => /* @__PURE__ */ jsx35(ItemInteractionLayer, { rect, state: "sibling-hint" }, `sibling-hint-${i}`)),
25250
- isItemDragging && draggedItemRect && selectedElRef.current !== footerDragRef.current?.draggedEl && selectedElRef.current !== navDragRef.current?.draggedEl && /* @__PURE__ */ jsx35(ItemInteractionLayer, { rect: draggedItemRect, state: "dragging" }),
25251
- isItemDragging && footerDropSlots.map((slot, i) => /* @__PURE__ */ jsx35(
27475
+ siblingHintRect && !linkPopover && !isItemDragging && siblingHintRects.length === 0 && /* @__PURE__ */ jsx36(ItemInteractionLayer, { rect: siblingHintRect, state: "sibling-hint" }),
27476
+ siblingHintRects.length > 0 && !linkPopover && !isItemDragging && siblingHintRects.map((rect, i) => /* @__PURE__ */ jsx36(ItemInteractionLayer, { rect, state: "sibling-hint" }, `sibling-hint-${i}`)),
27477
+ isItemDragging && draggedItemRect && selectedElRef.current !== footerDragRef.current?.draggedEl && selectedElRef.current !== navDragRef.current?.draggedEl && selectedElRef.current !== buttonCollectionDragRef.current && /* @__PURE__ */ jsx36(ItemInteractionLayer, { rect: draggedItemRect, state: "dragging" }),
27478
+ isItemDragging && footerDropSlots.map((slot, i) => /* @__PURE__ */ jsx36(
25252
27479
  "div",
25253
27480
  {
25254
27481
  className: "pointer-events-none fixed z-2147483646",
@@ -25258,7 +27485,7 @@ function OhhwellsBridge() {
25258
27485
  width: slot.width,
25259
27486
  height: slot.height
25260
27487
  },
25261
- children: /* @__PURE__ */ jsx35(
27488
+ children: /* @__PURE__ */ jsx36(
25262
27489
  DropIndicator,
25263
27490
  {
25264
27491
  direction: slot.direction,
@@ -25269,7 +27496,7 @@ function OhhwellsBridge() {
25269
27496
  },
25270
27497
  `footer-drop-${slot.direction}-${slot.columnIndex}-${slot.insertIndex}-${i}`
25271
27498
  )),
25272
- isItemDragging && navDropSlots.map((slot, i) => /* @__PURE__ */ jsx35(
27499
+ isItemDragging && navDropSlots.map((slot, i) => /* @__PURE__ */ jsx36(
25273
27500
  "div",
25274
27501
  {
25275
27502
  className: "pointer-events-none fixed z-2147483646",
@@ -25279,7 +27506,7 @@ function OhhwellsBridge() {
25279
27506
  width: slot.width,
25280
27507
  height: slot.height
25281
27508
  },
25282
- children: /* @__PURE__ */ jsx35(
27509
+ children: /* @__PURE__ */ jsx36(
25283
27510
  DropIndicator,
25284
27511
  {
25285
27512
  direction: slot.direction,
@@ -25290,10 +27517,31 @@ function OhhwellsBridge() {
25290
27517
  },
25291
27518
  `nav-drop-${slot.direction}-${slot.parentId ?? "root"}-${slot.insertIndex}-${i}`
25292
27519
  )),
25293
- hoveredNavContainerRect && (toolbarVariant !== "select-frame" || isFooterFrameSelection) && !linkPopover && !isItemDragging && /* @__PURE__ */ jsx35(ItemInteractionLayer, { rect: hoveredNavContainerRect, state: "hover" }),
25294
- hoveredItemRect && !linkPopover && !hoveredNavContainerRect && !isItemDragging && hoveredItemElRef.current !== selectedElRef.current && /* @__PURE__ */ jsx35(ItemInteractionLayer, { rect: hoveredItemRect, state: "hover" }),
25295
- hoveredTextRect && !hoveredNavContainerRect && !hoveredItemRect && !isItemDragging && toolbarVariant !== "rich-text" && /* @__PURE__ */ jsx35(ItemInteractionLayer, { rect: hoveredTextRect, state: "hover" }),
25296
- formPickRect && !isItemDragging && /* @__PURE__ */ jsx35(
27520
+ isItemDragging && buttonDropSlots.map((slot, i) => /* @__PURE__ */ jsx36(
27521
+ "div",
27522
+ {
27523
+ className: "pointer-events-none fixed z-2147483646",
27524
+ style: {
27525
+ left: slot.left,
27526
+ top: slot.top,
27527
+ width: slot.width,
27528
+ height: slot.height
27529
+ },
27530
+ children: /* @__PURE__ */ jsx36(
27531
+ DropIndicator,
27532
+ {
27533
+ direction: slot.direction,
27534
+ state: activeButtonDropIndex === i ? "dragActive" : "dragIdle",
27535
+ className: "!h-full !w-full"
27536
+ }
27537
+ )
27538
+ },
27539
+ `button-drop-${slot.direction}-${slot.insertIndex}-${i}`
27540
+ )),
27541
+ hoveredNavContainerRect && (toolbarVariant !== "select-frame" || isFooterFrameSelection) && !linkPopover && !isItemDragging && /* @__PURE__ */ jsx36(ItemInteractionLayer, { rect: hoveredNavContainerRect, state: "hover" }),
27542
+ hoveredItemRect && !linkPopover && !hoveredNavContainerRect && !isItemDragging && hoveredItemElRef.current !== selectedElRef.current && /* @__PURE__ */ jsx36(ItemInteractionLayer, { rect: hoveredItemRect, state: "hover" }),
27543
+ hoveredTextRect && !hoveredNavContainerRect && !hoveredItemRect && !isItemDragging && toolbarVariant !== "rich-text" && /* @__PURE__ */ jsx36(ItemInteractionLayer, { rect: hoveredTextRect, state: "hover" }),
27544
+ formPickRect && !isItemDragging && /* @__PURE__ */ jsx36(
25297
27545
  ItemInteractionLayer,
25298
27546
  {
25299
27547
  rect: formPickRect,
@@ -25301,13 +27549,13 @@ function OhhwellsBridge() {
25301
27549
  itemDragSurface: false,
25302
27550
  toolbarAlign: "left",
25303
27551
  chromeGap: 24,
25304
- toolbar: fieldPickRect ? void 0 : /* @__PURE__ */ jsxs22(
27552
+ toolbar: fieldPickRect ? void 0 : /* @__PURE__ */ jsxs23(
25305
27553
  "div",
25306
27554
  {
25307
27555
  "data-ohw-form-toolbar": "",
25308
27556
  className: "pointer-events-auto flex items-center gap-0.5 whitespace-nowrap rounded-lg border border-border bg-background p-1 shadow-md",
25309
27557
  children: [
25310
- /* @__PURE__ */ jsx35(
27558
+ /* @__PURE__ */ jsx36(
25311
27559
  "button",
25312
27560
  {
25313
27561
  type: "button",
@@ -25315,11 +27563,11 @@ function OhhwellsBridge() {
25315
27563
  className: "flex h-7 w-7 items-center justify-center rounded-md text-foreground transition-colors hover:bg-muted/80",
25316
27564
  onClick: () => setFieldTypePickerOpen((open) => !open),
25317
27565
  "data-ohw-add-field": "",
25318
- children: /* @__PURE__ */ jsx35(Plus4, { size: 15, "aria-hidden": true })
27566
+ children: /* @__PURE__ */ jsx36(Plus4, { size: 15, "aria-hidden": true })
25319
27567
  }
25320
27568
  ),
25321
- /* @__PURE__ */ jsx35("div", { className: "mx-0.5 h-5 w-px bg-border" }),
25322
- /* @__PURE__ */ jsxs22(
27569
+ /* @__PURE__ */ jsx36("div", { className: "mx-0.5 h-5 w-px bg-border" }),
27570
+ /* @__PURE__ */ jsxs23(
25323
27571
  "button",
25324
27572
  {
25325
27573
  type: "button",
@@ -25335,11 +27583,11 @@ function OhhwellsBridge() {
25335
27583
  });
25336
27584
  },
25337
27585
  children: [
25338
- /* @__PURE__ */ jsx35(Settings, { size: 14, "aria-hidden": true }),
27586
+ /* @__PURE__ */ jsx36(Settings, { size: 14, "aria-hidden": true }),
25339
27587
  "Form settings",
25340
27588
  formPickCount ? (
25341
27589
  // Counter pill, per the design — not a text suffix.
25342
- /* @__PURE__ */ jsx35(
27590
+ /* @__PURE__ */ jsx36(
25343
27591
  "span",
25344
27592
  {
25345
27593
  "data-ohw-form-count": "",
@@ -25351,8 +27599,8 @@ function OhhwellsBridge() {
25351
27599
  ]
25352
27600
  }
25353
27601
  ),
25354
- /* @__PURE__ */ jsx35("div", { className: "mx-0.5 h-5 w-px bg-border" }),
25355
- /* @__PURE__ */ jsx35("div", { className: "flex items-center rounded-md bg-muted/70 p-0.5", children: ["default", "success"].map((state) => /* @__PURE__ */ jsx35(
27602
+ /* @__PURE__ */ jsx36("div", { className: "mx-0.5 h-5 w-px bg-border" }),
27603
+ /* @__PURE__ */ jsx36("div", { className: "flex items-center rounded-md bg-muted/70 p-0.5", children: ["default", "success"].map((state) => /* @__PURE__ */ jsx36(
25356
27604
  "button",
25357
27605
  {
25358
27606
  type: "button",
@@ -25383,7 +27631,7 @@ function OhhwellsBridge() {
25383
27631
  )
25384
27632
  }
25385
27633
  ),
25386
- formHoverRect && !isItemDragging && /* @__PURE__ */ jsx35(
27634
+ formHoverRect && !isItemDragging && /* @__PURE__ */ jsx36(
25387
27635
  ItemInteractionLayer,
25388
27636
  {
25389
27637
  rect: formHoverRect,
@@ -25391,7 +27639,7 @@ function OhhwellsBridge() {
25391
27639
  chromeGap: formHoverElRef.current && getFieldWrapper(formHoverElRef.current) ? 8 : 24
25392
27640
  }
25393
27641
  ),
25394
- fieldPickRect && fieldPickState && !isItemDragging && toolbarVariant !== "rich-text" && /* @__PURE__ */ jsx35(
27642
+ fieldPickRect && fieldPickState && !isItemDragging && toolbarVariant !== "rich-text" && /* @__PURE__ */ jsx36(
25395
27643
  ItemInteractionLayer,
25396
27644
  {
25397
27645
  rect: fieldPickRect,
@@ -25403,7 +27651,7 @@ function OhhwellsBridge() {
25403
27651
  dragHandleLabel: "Reorder field",
25404
27652
  onDragHandleDragStart: handleFieldDragStart,
25405
27653
  onDragHandleDragEnd: handleFieldDragEnd,
25406
- toolbar: /* @__PURE__ */ jsx35(
27654
+ toolbar: /* @__PURE__ */ jsx36(
25407
27655
  FormFieldToolbar,
25408
27656
  {
25409
27657
  type: fieldPickState.type,
@@ -25416,12 +27664,12 @@ function OhhwellsBridge() {
25416
27664
  )
25417
27665
  }
25418
27666
  ),
25419
- fieldDragging ? fieldDropSlots.map((slot, i) => /* @__PURE__ */ jsx35(
27667
+ fieldDragging ? fieldDropSlots.map((slot, i) => /* @__PURE__ */ jsx36(
25420
27668
  "div",
25421
27669
  {
25422
27670
  className: "pointer-events-none fixed z-[2147483644]",
25423
27671
  style: { top: slot.top, left: slot.left, width: slot.width },
25424
- children: /* @__PURE__ */ jsx35(
27672
+ children: /* @__PURE__ */ jsx36(
25425
27673
  DropIndicator,
25426
27674
  {
25427
27675
  direction: "horizontal",
@@ -25434,7 +27682,7 @@ function OhhwellsBridge() {
25434
27682
  )) : null,
25435
27683
  fieldTypePickerOpen && formPickRect ? (() => {
25436
27684
  const toolbar = document.querySelector("[data-ohw-form-toolbar]")?.getBoundingClientRect();
25437
- return /* @__PURE__ */ jsx35(
27685
+ return /* @__PURE__ */ jsx36(
25438
27686
  "div",
25439
27687
  {
25440
27688
  className: "pointer-events-none fixed z-[2147483645]",
@@ -25442,12 +27690,12 @@ function OhhwellsBridge() {
25442
27690
  top: toolbar ? toolbar.bottom + 6 : formPickRect.top + 16,
25443
27691
  left: toolbar ? toolbar.left : formPickRect.left + 24
25444
27692
  },
25445
- children: /* @__PURE__ */ jsx35(FieldTypePicker, { onPick: handleAddField })
27693
+ children: /* @__PURE__ */ jsx36(FieldTypePicker, { onPick: handleAddField })
25446
27694
  }
25447
27695
  );
25448
27696
  })() : null,
25449
- toolbarVariant === "select-frame" && toolbarRect && !linkPopover && !isItemDragging && !isFooterFrameSelection && selectedElRef.current && isNavbarLinksContainer2(selectedElRef.current) && /* @__PURE__ */ jsx35(NavbarContainerChrome, { rect: toolbarRect, onAdd: handleAddTopLevelNavItem }),
25450
- toolbarVariant === "select-frame" && toolbarRect && !linkPopover && !isItemDragging && !isFooterFrameSelection && selectedElRef.current && isFooterLinksContainer(selectedElRef.current) && /* @__PURE__ */ jsx35(
27697
+ toolbarVariant === "select-frame" && toolbarRect && !linkPopover && !isItemDragging && !isFooterFrameSelection && selectedElRef.current && isNavbarLinksContainer2(selectedElRef.current) && /* @__PURE__ */ jsx36(NavbarContainerChrome, { rect: toolbarRect, onAdd: handleAddTopLevelNavItem }),
27698
+ toolbarVariant === "select-frame" && toolbarRect && !linkPopover && !isItemDragging && !isFooterFrameSelection && selectedElRef.current && isFooterLinksContainer(selectedElRef.current) && /* @__PURE__ */ jsx36(
25451
27699
  FooterContainerChrome,
25452
27700
  {
25453
27701
  rect: toolbarRect,
@@ -25455,13 +27703,17 @@ function OhhwellsBridge() {
25455
27703
  addDisabled: !canAddFooterColumn()
25456
27704
  }
25457
27705
  ),
25458
- toolbarRect && !linkPopover && (toolbarVariant === "link-action" || toolbarVariant === "select-frame" || toolbarVariant === "logo") && /* @__PURE__ */ jsx35(
27706
+ toolbarRect && !linkPopover && (toolbarVariant === "link-action" || toolbarVariant === "select-frame" || toolbarVariant === "logo") && /* @__PURE__ */ jsx36(
25459
27707
  ItemInteractionLayer,
25460
27708
  {
25461
- rect: isItemDragging && draggedItemRect && (footerDragRef.current?.wasSelected || navDragRef.current?.wasSelected) ? draggedItemRect : toolbarRect,
27709
+ rect: isItemDragging && draggedItemRect && (footerDragRef.current?.wasSelected || navDragRef.current?.wasSelected || selectedElRef.current === buttonCollectionDragRef.current) ? draggedItemRect : toolbarRect,
25462
27710
  toolbarBelowRect: selectedElRef.current && isNavigationItem2(selectedElRef.current) ? getOpenNavDropdownPanelRect(selectedElRef.current) : null,
25463
27711
  toolbarBelowEl: selectedElRef.current && isNavigationItem2(selectedElRef.current) ? getOpenNavDropdownPanel(selectedElRef.current) : null,
25464
27712
  elRef: glowElRef,
27713
+ hidden: isRectOutsideClip(
27714
+ isItemDragging && draggedItemRect && (footerDragRef.current?.wasSelected || navDragRef.current?.wasSelected || selectedElRef.current === buttonCollectionDragRef.current) ? draggedItemRect : toolbarRect,
27715
+ getIframeVisibleClip(parentScrollRef.current)
27716
+ ),
25465
27717
  state: isItemDragging ? "dragging" : selectedElRef.current && isNavigationItem2(selectedElRef.current) && getOpenNavDropdownPanel(selectedElRef.current) ? "active-bottom" : resolveItemInteractionState(toolbarRect, parentScrollRef.current),
25466
27718
  showHandle: toolbarVariant === "link-action" && Boolean(reorderHrefKey) || toolbarVariant === "select-frame" && isFooterFrameSelection,
25467
27719
  dragDisabled: reorderDragDisabled,
@@ -25471,18 +27723,25 @@ function OhhwellsBridge() {
25471
27723
  onItemPointerDown: toolbarVariant === "logo" ? void 0 : handleItemChromePointerDown,
25472
27724
  onItemClick: toolbarVariant === "logo" ? void 0 : handleItemChromeClick,
25473
27725
  itemDragSurface: toolbarVariant !== "logo" && !isFooterFrameSelection && !selectedIsSocialsRow,
25474
- toolbar: toolbarVariant === "link-action" && !isItemDragging || toolbarVariant === "select-frame" && (isFooterFrameSelection || selectedIsSocialsRow) && !isItemDragging ? /* @__PURE__ */ jsx35(
27726
+ toolbar: toolbarVariant === "link-action" && !isItemDragging || toolbarVariant === "select-frame" && (isFooterFrameSelection || selectedIsSocialsRow) && !isItemDragging ? /* @__PURE__ */ jsx36(
25475
27727
  ItemActionToolbar,
25476
27728
  {
25477
27729
  onEditLink: openLinkPopoverForSelected,
25478
27730
  onStyle: () => {
25479
27731
  const row = selectedElRef.current;
25480
27732
  if (!row) return;
25481
- if (floatingPanel?.kind === "socials-display") closeFloatingPanelOnly();
27733
+ suppressNextClickRef.current = true;
27734
+ suppressClickUntilRef.current = Date.now() + 500;
27735
+ if (isCtaButton(row)) {
27736
+ if (floatingPanelRef.current?.kind === "button-style") closeFloatingPanelOnly();
27737
+ else openButtonStylePanel(row);
27738
+ return;
27739
+ }
27740
+ if (floatingPanelRef.current?.kind === "socials-display") closeFloatingPanelOnly();
25482
27741
  else openSocialsDisplayPanel(row);
25483
27742
  },
25484
- showStyle: selectedIsSocialsRow,
25485
- styleActive: floatingPanel?.kind === "socials-display",
27743
+ showStyle: selectedIsSocialsRow || selectedIsCta,
27744
+ styleActive: floatingPanel?.kind === "socials-display" || floatingPanel?.kind === "button-style",
25486
27745
  onAddItem: handleAddChildItem,
25487
27746
  onSelectParent: handleSelectParent,
25488
27747
  onDuplicate: handleDuplicateSelected,
@@ -25493,11 +27752,11 @@ function OhhwellsBridge() {
25493
27752
  })(),
25494
27753
  editLinkDisabled: false,
25495
27754
  moreDisabled: false,
25496
- deleteDisabled: selectedElRef.current !== null && (() => {
27755
+ deleteDisabled: selectedElRef.current !== null && isCtaButton(selectedElRef.current) && !canDeleteButton(selectedElRef.current) || selectedElRef.current !== null && (() => {
25497
27756
  const social = getSocialItem(selectedElRef.current);
25498
27757
  return social ? !canRemoveSocialItem(social) : false;
25499
27758
  })(),
25500
- duplicateDisabled: isFooterFrameSelection || selectedIsSocialsRow || selectedElRef.current !== null && (() => {
27759
+ duplicateDisabled: isFooterFrameSelection || selectedIsSocialsRow || selectedElRef.current !== null && isCtaButton(selectedElRef.current) && !canDuplicateButton(selectedElRef.current) || selectedElRef.current !== null && (() => {
25501
27760
  const social = getSocialItem(selectedElRef.current);
25502
27761
  const row = social ? findSocialsRow(social) : null;
25503
27762
  return row ? !canAddSocialItem(row) : false;
@@ -25506,7 +27765,7 @@ function OhhwellsBridge() {
25506
27765
  showAddItem: isFooterFrameSelection || selectedIsSocialsRow || !selectedIsCta && Boolean(
25507
27766
  selectedElRef.current && isNavbarHrefKey(selectedElRef.current.getAttribute("data-ohw-href-key")) && !isNestedNavChild(selectedElRef.current)
25508
27767
  ),
25509
- showMore: (!selectedIsCta || isFooterFrameSelection) && !selectedIsSocialsRow,
27768
+ showMore: !selectedIsSocialsRow,
25510
27769
  dropdownOpen: navDropdownPreviewOpen,
25511
27770
  onDropdownOpenChange: handleNavDropdownOpenChange,
25512
27771
  headingVisible: footerHeadingVisible,
@@ -25515,8 +27774,8 @@ function OhhwellsBridge() {
25515
27774
  ) : void 0
25516
27775
  }
25517
27776
  ),
25518
- toolbarRect && toolbarVariant === "rich-text" && !linkPopover && !isRectOutsideClip(toolbarRect, getIframeVisibleClip(parentScrollRef.current)) && /* @__PURE__ */ jsxs22(Fragment10, { children: [
25519
- /* @__PURE__ */ jsx35(
27777
+ toolbarRect && toolbarVariant === "rich-text" && !linkPopover && !isRectOutsideClip(toolbarRect, getIframeVisibleClip(parentScrollRef.current)) && /* @__PURE__ */ jsxs23(Fragment10, { children: [
27778
+ /* @__PURE__ */ jsx36(
25520
27779
  EditGlowChrome,
25521
27780
  {
25522
27781
  rect: toolbarRect,
@@ -25526,7 +27785,7 @@ function OhhwellsBridge() {
25526
27785
  hideHandle: isItemDragging
25527
27786
  }
25528
27787
  ),
25529
- /* @__PURE__ */ jsx35(
27788
+ /* @__PURE__ */ jsx36(
25530
27789
  FloatingToolbar,
25531
27790
  {
25532
27791
  rect: toolbarRect,
@@ -25539,7 +27798,7 @@ function OhhwellsBridge() {
25539
27798
  }
25540
27799
  )
25541
27800
  ] }),
25542
- maxBadge && /* @__PURE__ */ jsxs22(
27801
+ maxBadge && /* @__PURE__ */ jsxs23(
25543
27802
  "div",
25544
27803
  {
25545
27804
  "data-ohw-max-badge": "",
@@ -25565,7 +27824,7 @@ function OhhwellsBridge() {
25565
27824
  ]
25566
27825
  }
25567
27826
  ),
25568
- toggleState && !linkPopover && /* @__PURE__ */ jsx35(
27827
+ toggleState && !linkPopover && /* @__PURE__ */ jsx36(
25569
27828
  StateToggle,
25570
27829
  {
25571
27830
  rect: toggleState.rect,
@@ -25574,15 +27833,15 @@ function OhhwellsBridge() {
25574
27833
  onStateChange: handleStateChange
25575
27834
  }
25576
27835
  ),
25577
- sectionGap && !linkPopover && /* @__PURE__ */ jsxs22(
27836
+ sectionGap && !linkPopover && /* @__PURE__ */ jsxs23(
25578
27837
  "div",
25579
27838
  {
25580
27839
  "data-ohw-section-insert-line": "",
25581
27840
  className: "fixed left-0 w-full z-2147483646 flex items-center pointer-events-none",
25582
27841
  style: { top: sectionGap.y, transform: "translateY(-50%)" },
25583
27842
  children: [
25584
- /* @__PURE__ */ jsx35("div", { className: "flex-1 bg-bridge-primary", style: { height: 3 } }),
25585
- /* @__PURE__ */ jsx35(
27843
+ /* @__PURE__ */ jsx36("div", { className: "flex-1 bg-bridge-primary", style: { height: 3 } }),
27844
+ /* @__PURE__ */ jsx36(
25586
27845
  Badge,
25587
27846
  {
25588
27847
  className: "px-8 py-1 bg-bridge-primary hover:bg-bridge-primary text-primary-foreground text-xs font-medium shrink-0 rounded-full cursor-pointer pointer-events-auto",
@@ -25599,11 +27858,11 @@ function OhhwellsBridge() {
25599
27858
  children: "Add Section"
25600
27859
  }
25601
27860
  ),
25602
- /* @__PURE__ */ jsx35("div", { className: "flex-1 bg-bridge-primary", style: { height: 3 } })
27861
+ /* @__PURE__ */ jsx36("div", { className: "flex-1 bg-bridge-primary", style: { height: 3 } })
25603
27862
  ]
25604
27863
  }
25605
27864
  ),
25606
- linkPopover && dialogPortalContainer ? /* @__PURE__ */ jsx35(
27865
+ linkPopover && dialogPortalContainer ? /* @__PURE__ */ jsx36(
25607
27866
  LinkPopover,
25608
27867
  {
25609
27868
  panelRef: linkPopoverPanelRef,
@@ -25620,7 +27879,7 @@ function OhhwellsBridge() {
25620
27879
  },
25621
27880
  linkPopover.key
25622
27881
  ) : null,
25623
- floatingPanel && floatingPanel.kind === "socials-display" ? /* @__PURE__ */ jsx35(
27882
+ floatingPanel && floatingPanel.kind === "socials-display" ? /* @__PURE__ */ jsx36(
25624
27883
  FloatingPanel,
25625
27884
  {
25626
27885
  open: true,
@@ -25630,7 +27889,7 @@ function OhhwellsBridge() {
25630
27889
  onPositionChange: setFloatingPanelPos,
25631
27890
  parentScroll: parentScrollSnap ?? parentScrollRef.current,
25632
27891
  onClose: closeFloatingPanelOnly,
25633
- children: /* @__PURE__ */ jsx35(
27892
+ children: /* @__PURE__ */ jsx36(
25634
27893
  SocialsDisplayPanel,
25635
27894
  {
25636
27895
  display: socialsDisplayFor(floatingPanel.row, editContentRef.current),
@@ -25641,12 +27900,106 @@ function OhhwellsBridge() {
25641
27900
  }
25642
27901
  )
25643
27902
  }
25644
- ) : null
27903
+ ) : null,
27904
+ floatingPanel && floatingPanel.kind === "button-style" ? (() => {
27905
+ const button = document.querySelector(
27906
+ `[data-ohw-href-key="${CSS.escape(floatingPanel.buttonId)}"]`
27907
+ );
27908
+ if (!button) return null;
27909
+ captureButtonOrigin(button);
27910
+ const tokens = tokensFromButton(button);
27911
+ const current = assignmentFor(button, editContentRef.current, tokens);
27912
+ void buttonStyleTick;
27913
+ return /* @__PURE__ */ jsx36(
27914
+ FloatingPanel,
27915
+ {
27916
+ open: true,
27917
+ title: floatingPanel.title,
27918
+ context: floatingPanel.context,
27919
+ icon: /* @__PURE__ */ jsx36(Brush2, { className: "size-4 shrink-0", "aria-hidden": true }),
27920
+ position: floatingPanelPos,
27921
+ onPositionChange: setFloatingPanelPos,
27922
+ parentScroll: parentScrollSnap ?? parentScrollRef.current,
27923
+ onClose: closeFloatingPanelOnly,
27924
+ bodyClassName: "gap-5",
27925
+ children: /* @__PURE__ */ jsx36(
27926
+ ButtonStylePanel,
27927
+ {
27928
+ variants: tokens.variants,
27929
+ sizes: tokens.sizes,
27930
+ variant: current.variant,
27931
+ size: current.size,
27932
+ slotBackground: previewSlotBackground(button),
27933
+ previewLabel: (button.querySelector(
27934
+ '[data-ohw-editable="text"], [data-ohw-editable="plain"]'
27935
+ )?.textContent ?? button.textContent ?? "Book Now").trim() || "Book Now",
27936
+ onVariant: (variant) => applySelectedButtonStyle({ variant }),
27937
+ onSize: (size) => applySelectedButtonStyle({ size })
27938
+ },
27939
+ floatingPanel.key
27940
+ )
27941
+ },
27942
+ floatingPanel.key
27943
+ );
27944
+ })() : null
25645
27945
  ] }),
25646
27946
  bridgeRoot
25647
27947
  ) : null
25648
27948
  ] });
25649
27949
  }
27950
+
27951
+ // src/ui/EmptySection.tsx
27952
+ import Link3 from "next/link";
27953
+ import { Fragment as Fragment11, jsx as jsx37, jsxs as jsxs24 } from "react/jsx-runtime";
27954
+ function EmptySection({ title, homeHref = "/", eyebrowKey, titleKey, subtitleKey }) {
27955
+ return /* @__PURE__ */ jsxs24(Fragment11, { children: [
27956
+ /* @__PURE__ */ jsx37(
27957
+ "p",
27958
+ {
27959
+ style: {
27960
+ fontFamily: "var(--brand-font-body)",
27961
+ fontSize: "0.75rem",
27962
+ fontWeight: 500,
27963
+ letterSpacing: "0.15em",
27964
+ textTransform: "uppercase",
27965
+ color: "var(--brand-accent)",
27966
+ marginBottom: "1.5rem"
27967
+ },
27968
+ children: /* @__PURE__ */ jsx37(Link3, { href: homeHref, style: { color: "inherit" }, children: /* @__PURE__ */ jsx37("span", { ...eyebrowKey ? { "data-ohw-editable": "text", "data-ohw-key": eyebrowKey } : {}, children: "Home" }) })
27969
+ }
27970
+ ),
27971
+ /* @__PURE__ */ jsx37(
27972
+ "h1",
27973
+ {
27974
+ style: {
27975
+ fontFamily: "var(--brand-font-heading)",
27976
+ fontSize: "clamp(2rem, 3.5vw, 2.75rem)",
27977
+ lineHeight: 1.1,
27978
+ letterSpacing: "-0.025em",
27979
+ color: "var(--brand-text)",
27980
+ marginBottom: "1rem"
27981
+ },
27982
+ ...titleKey ? { "data-ohw-editable": "text", "data-ohw-key": titleKey, "data-ohw-max-length": 80 } : {},
27983
+ children: title
27984
+ }
27985
+ ),
27986
+ /* @__PURE__ */ jsx37(
27987
+ "p",
27988
+ {
27989
+ style: {
27990
+ fontFamily: "var(--brand-font-body)",
27991
+ fontSize: "1rem",
27992
+ lineHeight: 1.7,
27993
+ fontWeight: 300,
27994
+ color: "var(--brand-text-muted)",
27995
+ maxWidth: "340px"
27996
+ },
27997
+ ...subtitleKey ? { "data-ohw-editable": "text", "data-ohw-key": subtitleKey, "data-ohw-max-length": 160 } : {},
27998
+ children: "This page doesn't have any content yet."
27999
+ }
28000
+ )
28001
+ ] });
28002
+ }
25650
28003
  export {
25651
28004
  AI_DEFAULT_BRAND,
25652
28005
  AI_TREE_SCHEMA_VERSIONS,
@@ -25663,6 +28016,7 @@ export {
25663
28016
  DropdownMenuItem,
25664
28017
  DropdownMenuSeparator,
25665
28018
  DropdownMenuTrigger,
28019
+ EmptySection,
25666
28020
  ItemActionToolbar,
25667
28021
  ItemInteractionLayer,
25668
28022
  LinkEditorPanel,