@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.cjs CHANGED
@@ -46,6 +46,7 @@ __export(index_exports, {
46
46
  DropdownMenuItem: () => DropdownMenuItem,
47
47
  DropdownMenuSeparator: () => DropdownMenuSeparator,
48
48
  DropdownMenuTrigger: () => DropdownMenuTrigger,
49
+ EmptySection: () => EmptySection,
49
50
  ItemActionToolbar: () => ItemActionToolbar,
50
51
  ItemInteractionLayer: () => ItemInteractionLayer,
51
52
  LinkEditorPanel: () => LinkEditorPanel,
@@ -104,6 +105,10 @@ function enforceLinkHrefs() {
104
105
 
105
106
  // src/lib/ai-tree.ts
106
107
  var AI_TREE_SCHEMA_VERSIONS = [1];
108
+ var BUTTON_FILL_TOKENS = ["dark", "primary", "accent", "light"];
109
+ var BUTTON_STYLES = ["solid", "outline", "ghost"];
110
+ var BUTTON_RADII = ["square", "rounded", "pill"];
111
+ var BUTTON_SIZES = ["s", "m", "l"];
107
112
  var AI_DEFAULT_BRAND = {
108
113
  palette: { dark: "#0C0A09", primary: "#0078E5", accent: "#747271", light: "#ECECEB" },
109
114
  fonts: {
@@ -363,6 +368,118 @@ var FONT_VARS = {
363
368
  var BRAND_FONT_LINK_ID = "ohw-brand-fonts";
364
369
  var CUSTOM_FONT_STYLE_ID = "ohw-brand-custom-fonts";
365
370
  var CUSTOM_FONT_FORMATS = /* @__PURE__ */ new Set(["woff2", "woff", "truetype", "opentype"]);
371
+ var BUTTON_RADIUS_PX = {
372
+ square: 0,
373
+ rounded: AI_TREE_TOKENS.radiusButton,
374
+ pill: 999
375
+ };
376
+ var BUTTON_SIZE_SPEC = {
377
+ s: {
378
+ paddingX: `${AI_TREE_TOKENS.spacing4}px`,
379
+ paddingY: `${AI_TREE_TOKENS.spacing2}px`,
380
+ fontSize: "14px"
381
+ },
382
+ m: {
383
+ paddingX: `${AI_TREE_TOKENS.spacing6}px`,
384
+ paddingY: `${AI_TREE_TOKENS.spacing3}px`,
385
+ fontSize: `${AI_TREE_TOKENS.type.button.size}px`
386
+ },
387
+ l: {
388
+ paddingX: `${AI_TREE_TOKENS.spacing8}px`,
389
+ paddingY: `${AI_TREE_TOKENS.spacing4}px`,
390
+ fontSize: "18px"
391
+ }
392
+ };
393
+ var BUTTON_SIZE_SCALE = { s: 0.85, m: 1, l: 1.25 };
394
+ var roundHalf = (n) => Math.round(n * 2) / 2;
395
+ function measureTemplateButton(variant) {
396
+ const root = document.documentElement;
397
+ const el = document.querySelector(`.btn--${variant}, .btn-${variant}`);
398
+ if (!el) return null;
399
+ const sheet = document.getElementById(BUTTON_RULES_STYLE_ID);
400
+ const rules = sheet?.textContent ?? "";
401
+ const saved = BUTTON_VAR_NAMES.map((name) => [name, root.style.getPropertyValue(name)]);
402
+ if (sheet) sheet.textContent = "";
403
+ for (const [name] of saved) root.style.removeProperty(name);
404
+ const cs = getComputedStyle(el);
405
+ const base = {
406
+ fontSize: parseFloat(cs.fontSize),
407
+ top: parseFloat(cs.paddingTop),
408
+ right: parseFloat(cs.paddingRight),
409
+ bottom: parseFloat(cs.paddingBottom),
410
+ left: parseFloat(cs.paddingLeft)
411
+ };
412
+ if (sheet) sheet.textContent = rules;
413
+ for (const [name, value] of saved) if (value) root.style.setProperty(name, value);
414
+ return Object.values(base).every(Number.isFinite) && base.fontSize > 0 ? base : null;
415
+ }
416
+ function scaledButtonSize(size, base) {
417
+ if (!base) return BUTTON_SIZE_SPEC[size];
418
+ const k = BUTTON_SIZE_SCALE[size];
419
+ const px2 = (n) => `${roundHalf(n * k)}px`;
420
+ return {
421
+ // Each side scaled on its own, so a button with uneven padding (say, room for an icon on one
422
+ // side) keeps its shape; the x/y vars are the averages for templates that read those instead.
423
+ padding: `${px2(base.top)} ${px2(base.right)} ${px2(base.bottom)} ${px2(base.left)}`,
424
+ paddingX: px2((base.left + base.right) / 2),
425
+ paddingY: px2((base.top + base.bottom) / 2),
426
+ fontSize: `${roundHalf(base.fontSize * k)}px`
427
+ };
428
+ }
429
+ var BUTTON_VAR_ROLES = ["bg", "text", "border", "radius", "padding-x", "padding-y", "padding", "font-size"];
430
+ var BUTTON_VAR_NAMES = ["primary", "secondary"].flatMap(
431
+ (variant) => BUTTON_VAR_ROLES.map((role) => `--brand-button-${variant}-${role}`)
432
+ );
433
+ var ROLES_BY_FIELD = {
434
+ fill: ["bg"],
435
+ style: ["bg", "border"],
436
+ textColor: ["text"],
437
+ radius: ["radius"],
438
+ size: ["padding-x", "padding-y", "padding", "font-size"]
439
+ };
440
+ function touchedButtonRoles(variant, kit) {
441
+ if (!kit.touched) return new Set(BUTTON_VAR_ROLES);
442
+ const fields = kit.touched.buttons?.[variant] ?? [];
443
+ const outline = fields.includes("style") && kit.buttons?.[variant]?.style === "outline";
444
+ return new Set(
445
+ fields.flatMap((f) => {
446
+ if (outline && (f === "fill" || f === "style")) return ["border"];
447
+ return ROLES_BY_FIELD[f] ?? [];
448
+ })
449
+ );
450
+ }
451
+ function buttonPalette(kit) {
452
+ return kit.palette ?? {
453
+ dark: "var(--brand-text)",
454
+ primary: "var(--brand-primary)",
455
+ accent: "var(--brand-accent)",
456
+ light: "var(--brand-background)"
457
+ };
458
+ }
459
+ function buttonVariantVars(variant, style, roles, palette) {
460
+ const fill = palette[style.fill];
461
+ const text = palette[style.textColor];
462
+ const size = scaledButtonSize(style.size, roles.has("font-size") ? measureTemplateButton(variant) : null);
463
+ 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" };
464
+ const all = {
465
+ [`--brand-button-${variant}-bg`]: bg,
466
+ [`--brand-button-${variant}-text`]: text,
467
+ [`--brand-button-${variant}-border`]: border,
468
+ [`--brand-button-${variant}-radius`]: `${BUTTON_RADIUS_PX[style.radius]}px`,
469
+ [`--brand-button-${variant}-padding-x`]: size.paddingX,
470
+ [`--brand-button-${variant}-padding-y`]: size.paddingY,
471
+ ...size.padding ? { [`--brand-button-${variant}-padding`]: size.padding } : {},
472
+ [`--brand-button-${variant}-font-size`]: size.fontSize
473
+ };
474
+ const prefix = `--brand-button-${variant}-`;
475
+ return Object.fromEntries(Object.entries(all).filter(([name]) => roles.has(name.slice(prefix.length))));
476
+ }
477
+ function buttonStyleVars(kit) {
478
+ return {
479
+ ...buttonVariantVars("primary", kit.buttons.primary, touchedButtonRoles("primary", kit), buttonPalette(kit)),
480
+ ...buttonVariantVars("secondary", kit.buttons.secondary, touchedButtonRoles("secondary", kit), buttonPalette(kit))
481
+ };
482
+ }
366
483
  function brandColorVars(palette) {
367
484
  const { dark, primary, accent, light } = palette;
368
485
  const mix = (a, aPct, b) => `color-mix(in srgb, ${a} ${aPct}%, ${b})`;
@@ -390,6 +507,11 @@ function brandColorVars(palette) {
390
507
  "--brand-on-primary": light
391
508
  };
392
509
  }
510
+ function isButtonStyle(value) {
511
+ if (!value || typeof value !== "object") return false;
512
+ const v = value;
513
+ 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);
514
+ }
393
515
  function parseCustomFontWeight(raw) {
394
516
  if (typeof raw !== "object" || raw === null) return null;
395
517
  const w = raw;
@@ -409,6 +531,15 @@ function parseCustomFont(raw) {
409
531
  if (weights.length === 0) return null;
410
532
  return { family: f.family, label: f.label, weights };
411
533
  }
534
+ var BUTTON_FIELDS = Object.keys(ROLES_BY_FIELD);
535
+ function parseTouchedButtons(raw) {
536
+ const out = {};
537
+ for (const variant of ["primary", "secondary"]) {
538
+ const list = raw?.[variant];
539
+ if (Array.isArray(list)) out[variant] = BUTTON_FIELDS.filter((f) => list.includes(f));
540
+ }
541
+ return out;
542
+ }
412
543
  function parseBrandKit(raw) {
413
544
  if (!raw) return null;
414
545
  try {
@@ -418,11 +549,21 @@ function parseBrandKit(raw) {
418
549
  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;
419
550
  const fonts = f && typeof f.heading === "string" && typeof f.body === "string" ? { heading: f.heading, body: f.body } : void 0;
420
551
  const customFonts = Array.isArray(parsed?.customFonts) ? parsed.customFonts.map(parseCustomFont).filter((c) => c !== null) : void 0;
421
- if (!palette && !fonts && !customFonts) return null;
552
+ const b = parsed?.buttons;
553
+ const buttons = b && isButtonStyle(b.primary) && isButtonStyle(b.secondary) ? { primary: b.primary, secondary: b.secondary } : void 0;
554
+ if (!palette && !fonts && !customFonts && !buttons) return null;
422
555
  return {
423
556
  ...palette ? { palette } : {},
424
557
  ...fonts ? { fonts } : {},
425
- ...customFonts ? { customFonts } : {}
558
+ ...customFonts ? { customFonts } : {},
559
+ ...buttons ? { buttons } : {},
560
+ ...parsed.touched && typeof parsed.touched === "object" ? {
561
+ touched: {
562
+ palette: parsed.touched.palette === true,
563
+ fonts: parsed.touched.fonts === true,
564
+ buttons: parseTouchedButtons(parsed.touched.buttons)
565
+ }
566
+ } : {}
426
567
  };
427
568
  } catch {
428
569
  return null;
@@ -451,6 +592,39 @@ function loadBrandFonts(families) {
451
592
  }
452
593
  if (link.href !== href) link.href = href;
453
594
  }
595
+ var BUTTON_RULES_STYLE_ID = "ohw-brand-button-rules";
596
+ function buttonRulesCss(kit) {
597
+ const rule = (variant) => {
598
+ const roles = touchedButtonRoles(variant, kit);
599
+ const v = (role) => `var(--brand-button-${variant}-${role})`;
600
+ const decls = [];
601
+ if (roles.has("bg")) decls.push(`background:${v("bg")} !important`);
602
+ if (roles.has("text")) decls.push(`color:${v("text")} !important`);
603
+ if (roles.has("border")) decls.push(`border:1.5px solid ${v("border")} !important`);
604
+ if (roles.has("radius")) decls.push(`border-radius:${v("radius")} !important`);
605
+ if (roles.has("padding-x")) decls.push(`padding:var(--brand-button-${variant}-padding, ${v("padding-y")} ${v("padding-x")}) !important`);
606
+ if (roles.has("font-size")) decls.push(`font-size:${v("font-size")} !important`);
607
+ if (decls.length === 0) return "";
608
+ const selectors = [`html .btn--${variant}`, `html .btn-${variant}`];
609
+ const labelRule = roles.has("text") ? `${selectors.map((x) => `${x} *`).join(",")}{color:inherit !important}` : "";
610
+ return `${selectors.join(",")}{${decls.join(";")}}` + labelRule + (roles.has("bg") ? `${selectors.map((x) => `${x}:hover`).join(",")}{opacity:0.88}` : "");
611
+ };
612
+ return rule("primary") + rule("secondary");
613
+ }
614
+ function syncButtonRules(kit) {
615
+ let el = document.getElementById(BUTTON_RULES_STYLE_ID);
616
+ const css = kit ? buttonRulesCss(kit) : "";
617
+ if (!css) {
618
+ el?.remove();
619
+ return;
620
+ }
621
+ if (!el) {
622
+ el = document.createElement("style");
623
+ el.id = BUTTON_RULES_STYLE_ID;
624
+ document.head.appendChild(el);
625
+ }
626
+ if (el.textContent !== css) el.textContent = css;
627
+ }
454
628
  function escapeCssString(value) {
455
629
  return value.replace(/\\/g, "\\\\").replace(/'/g, "\\'");
456
630
  }
@@ -476,28 +650,47 @@ function loadCustomFonts(fonts) {
476
650
  function applyBrandToDom(kit) {
477
651
  const root = document.documentElement;
478
652
  if (!kit) {
479
- for (const name of [...BRAND_VAR_NAMES, ...LEGACY_BRAND_VAR_NAMES]) root.style.removeProperty(name);
653
+ for (const name of [...BRAND_VAR_NAMES, ...LEGACY_BRAND_VAR_NAMES, ...BUTTON_VAR_NAMES]) {
654
+ root.style.removeProperty(name);
655
+ }
480
656
  for (const name of [...FONT_VARS.heading, ...FONT_VARS.body]) root.style.removeProperty(name);
481
657
  document.getElementById(BRAND_FONT_LINK_ID)?.remove();
482
658
  document.getElementById(CUSTOM_FONT_STYLE_ID)?.remove();
659
+ syncButtonRules(null);
483
660
  return;
484
661
  }
662
+ const applyPalette = kit.touched ? kit.touched.palette === true : true;
663
+ const applyFonts = kit.touched ? kit.touched.fonts === true : true;
485
664
  if (kit.palette) {
486
- for (const [name, value] of Object.entries(brandColorVars(kit.palette))) root.style.setProperty(name, value);
665
+ if (applyPalette) {
666
+ for (const [name, value] of Object.entries(brandColorVars(kit.palette))) root.style.setProperty(name, value);
667
+ } else {
668
+ for (const name of [...BRAND_VAR_NAMES, ...LEGACY_BRAND_VAR_NAMES]) root.style.removeProperty(name);
669
+ }
487
670
  }
671
+ if (kit.buttons) {
672
+ for (const name of BUTTON_VAR_NAMES) root.style.removeProperty(name);
673
+ for (const [name, value] of Object.entries(buttonStyleVars({ ...kit, buttons: kit.buttons }))) root.style.setProperty(name, value);
674
+ }
675
+ syncButtonRules(kit.buttons ? kit : null);
488
676
  if (kit.customFonts) {
489
677
  loadCustomFonts(kit.customFonts);
490
678
  }
491
679
  if (kit.fonts) {
492
- const heading = quoteFamily(kit.fonts.heading);
493
- const body = quoteFamily(kit.fonts.body);
494
- for (const name of FONT_VARS.heading) root.style.setProperty(name, heading);
495
- for (const name of FONT_VARS.body) root.style.setProperty(name, body);
496
- const customFamilies = new Set((kit.customFonts ?? []).map((f) => f.family));
497
- const googleFamilies = [familyOf(kit.fonts.heading), familyOf(kit.fonts.body)].filter(
498
- (f) => !customFamilies.has(f)
499
- );
500
- loadBrandFonts(googleFamilies);
680
+ if (applyFonts) {
681
+ const heading = quoteFamily(kit.fonts.heading);
682
+ const body = quoteFamily(kit.fonts.body);
683
+ for (const name of FONT_VARS.heading) root.style.setProperty(name, heading);
684
+ for (const name of FONT_VARS.body) root.style.setProperty(name, body);
685
+ const customFamilies = new Set((kit.customFonts ?? []).map((f) => f.family));
686
+ const googleFamilies = [familyOf(kit.fonts.heading), familyOf(kit.fonts.body)].filter(
687
+ (f) => !customFamilies.has(f)
688
+ );
689
+ loadBrandFonts(googleFamilies);
690
+ } else {
691
+ for (const name of [...FONT_VARS.heading, ...FONT_VARS.body]) root.style.removeProperty(name);
692
+ document.getElementById(BRAND_FONT_LINK_ID)?.remove();
693
+ }
501
694
  }
502
695
  }
503
696
 
@@ -598,9 +791,9 @@ function styleSheetCss() {
598
791
  );
599
792
  }
600
793
  const pad = { tight: 40, balanced: 64, airy: 96 };
601
- for (const [spacing, px] of Object.entries(pad)) {
794
+ for (const [spacing, px2] of Object.entries(pad)) {
602
795
  rules.push(
603
- `[data-ohw-style-spacing="${spacing}"] { padding-top: ${px}px !important; padding-bottom: ${px}px !important; }`
796
+ `[data-ohw-style-spacing="${spacing}"] { padding-top: ${px2}px !important; padding-bottom: ${px2}px !important; }`
604
797
  );
605
798
  }
606
799
  rules.push(
@@ -647,7 +840,6 @@ var NODE_PROPS = [
647
840
  "color",
648
841
  "font-family",
649
842
  "font-size",
650
- "background",
651
843
  "text-align",
652
844
  "justify-content",
653
845
  "align-items"
@@ -697,9 +889,6 @@ function clearNodeProps(root) {
697
889
  h.removeAttribute(NODE_WROTE_ATTR);
698
890
  }
699
891
  }
700
- function buttonSurfaceOf(el) {
701
- return el.closest("a, button") ?? el;
702
- }
703
892
  function alignSubjectOf(el) {
704
893
  const button = el.closest('[data-ohw-role="button"]');
705
894
  if (button?.parentElement) return button.parentElement;
@@ -817,18 +1006,6 @@ function applyStylesToDom(store) {
817
1006
  );
818
1007
  subject.setAttribute(NODE_WROTE_ATTR, "");
819
1008
  }
820
- if (override.buttonBackground !== void 0 || override.buttonText !== void 0) {
821
- const surface = buttonSurfaceOf(el);
822
- if (override.buttonBackground !== void 0) {
823
- saveInline(surface, "background");
824
- surface.style.setProperty("background", override.buttonBackground, "important");
825
- }
826
- if (override.buttonText !== void 0) {
827
- saveInline(surface, "color");
828
- surface.style.setProperty("color", override.buttonText, "important");
829
- }
830
- surface.setAttribute(NODE_WROTE_ATTR, "");
831
- }
832
1009
  }
833
1010
  }
834
1011
  for (const [sectionId, override] of Object.entries(store.sections)) {
@@ -1414,7 +1591,59 @@ var AI_MOBILE_CSS = [
1414
1591
  "[data-ai-grid]{grid-template-columns:repeat(2, 1fr) !important}",
1415
1592
  "}"
1416
1593
  ].join("");
1417
- function buttonShellStyle(ctx, fullWidth) {
1594
+ function buttonSizeSpec(size) {
1595
+ switch (size) {
1596
+ case "s":
1597
+ return { padding: `${AI_TREE_TOKENS.spacing2}px ${AI_TREE_TOKENS.spacing4}px`, fontSize: 14 };
1598
+ case "l":
1599
+ return { padding: `${AI_TREE_TOKENS.spacing4}px ${AI_TREE_TOKENS.spacing8}px`, fontSize: 18 };
1600
+ case "m":
1601
+ default:
1602
+ return { padding: `${AI_TREE_TOKENS.spacing3}px ${AI_TREE_TOKENS.spacing6}px`, fontSize: AI_TREE_TOKENS.type.button.size };
1603
+ }
1604
+ }
1605
+ function buttonRadiusPx(radius) {
1606
+ switch (radius) {
1607
+ case "square":
1608
+ return 0;
1609
+ case "pill":
1610
+ return 999;
1611
+ case "rounded":
1612
+ default:
1613
+ return AI_TREE_TOKENS.radiusButton;
1614
+ }
1615
+ }
1616
+ function resolveButtonToken(brand, token) {
1617
+ return brand.palette[token];
1618
+ }
1619
+ function buttonColorStyle(ctx, variant) {
1620
+ const token = ctx.brand.buttons?.[variant];
1621
+ if (!token) return null;
1622
+ const fill = resolveButtonToken(ctx.brand, token.fill);
1623
+ const text = resolveButtonToken(ctx.brand, token.textColor);
1624
+ switch (token.style) {
1625
+ case "outline":
1626
+ return { background: "transparent", border: `1px solid ${fill}`, color: text };
1627
+ case "ghost":
1628
+ return { background: "transparent", border: "1px solid transparent", color: text };
1629
+ case "solid":
1630
+ default:
1631
+ return { background: fill, color: text };
1632
+ }
1633
+ }
1634
+ function buttonShellStyle(ctx, variant, fullWidth) {
1635
+ const buttonToken = ctx.brand.buttons?.[variant];
1636
+ if (buttonToken) {
1637
+ const { padding, fontSize } = buttonSizeSpec(buttonToken.size);
1638
+ return {
1639
+ borderRadius: buttonRadiusPx(buttonToken.radius),
1640
+ padding,
1641
+ fontFamily: ctx.brand.fonts.body,
1642
+ fontSize,
1643
+ lineHeight: AI_TREE_TOKENS.type.button.line,
1644
+ fontWeight: AI_TREE_TOKENS.type.button.weight
1645
+ };
1646
+ }
1418
1647
  const bs = ctx.buttonStyle;
1419
1648
  if (bs) {
1420
1649
  return {
@@ -1524,6 +1753,22 @@ function pricingCardEmphasis(featured, ctx, hasBg) {
1524
1753
  function textAttrs(ctx, path) {
1525
1754
  return ctx.keyFor ? { "data-ohw-key": ctx.keyFor(path), "data-ohw-editable": "text" } : {};
1526
1755
  }
1756
+ var AI_RESPONSIVE_CSS = [
1757
+ "@media (max-width: 960px) {",
1758
+ " [data-ai-responsive] [data-ai-grid] { grid-template-columns: repeat(2, 1fr) !important; }",
1759
+ ' [data-ai-responsive] [data-ai-grid="1"] { grid-template-columns: 1fr !important; }',
1760
+ "}",
1761
+ "@media (max-width: 640px) {",
1762
+ " [data-ai-responsive] [data-ai-grid] { grid-template-columns: 1fr !important; }",
1763
+ " [data-ai-responsive] [data-ai-columns] > * { grid-column: 1 / -1 !important; }",
1764
+ // Group containers flatten to a column on phones; span placements come along for free.
1765
+ " [data-ai-responsive] [data-ai-group] { display: flex !important; flex-direction: column !important; }",
1766
+ " [data-ai-responsive] [data-ai-group] > * { grid-column: auto !important; }",
1767
+ " [data-ai-responsive] [data-ai-section-inner] { padding-left: 20px !important; padding-right: 20px !important; }",
1768
+ " [data-ai-responsive] { overflow-x: hidden; }",
1769
+ " [data-ai-responsive] img { max-width: 100%; }",
1770
+ "}"
1771
+ ].join("\n");
1527
1772
  var TRANSPARENT_PX = "data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7";
1528
1773
  function MediaBox({
1529
1774
  refValue,
@@ -1541,9 +1786,9 @@ function MediaBox({
1541
1786
  const url = refValue ? ctx.resolveMedia(refValue) : null;
1542
1787
  const isIcon = /^(lucide|simple):/.test(refValue);
1543
1788
  const aspectRatio = aspect && /^\d+:\d+$/.test(aspect) ? aspect.replace(":", " / ") : void 0;
1544
- const editAttrs = ctx.keyFor && editPath && !isIcon ? {
1789
+ const editAttrs = ctx.keyFor && editPath ? {
1545
1790
  "data-ohw-key": ctx.keyFor(editPath),
1546
- ...genericImageEdit ? { "data-ohw-editable": "image" } : {}
1791
+ ...isIcon ? { "data-ohw-editable": "icon" } : genericImageEdit ? { "data-ohw-editable": "image" } : {}
1547
1792
  } : {};
1548
1793
  if (isIcon) {
1549
1794
  const Icon = refValue.startsWith("lucide:") ? lucideByName(refValue.slice(7)) : null;
@@ -1551,6 +1796,7 @@ function MediaBox({
1551
1796
  "span",
1552
1797
  {
1553
1798
  "data-ai-icon": refValue,
1799
+ ...editAttrs,
1554
1800
  style: {
1555
1801
  display: "inline-flex",
1556
1802
  width: 48,
@@ -1610,12 +1856,16 @@ function ButtonEl({
1610
1856
  secondaryColor
1611
1857
  }) {
1612
1858
  const secondary = slots.variant === "secondary";
1859
+ const variant = secondary ? "secondary" : "primary";
1860
+ const tokenColorStyle = buttonColorStyle(ctx, variant);
1613
1861
  const href = str(slots.href);
1614
1862
  const linkAttrs = ctx.keyFor && path ? { "data-ohw-href-key": ctx.keyFor(`${path}.href`), "data-ohw-role": "button" } : {};
1615
1863
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1616
1864
  "a",
1617
1865
  {
1618
1866
  href: href || void 0,
1867
+ className: `btn btn--${variant}`,
1868
+ "data-ohw-btn-variant": variant,
1619
1869
  ...linkAttrs,
1620
1870
  style: {
1621
1871
  display: fullWidth ? "flex" : "inline-flex",
@@ -1625,13 +1875,13 @@ function ButtonEl({
1625
1875
  textDecoration: "none",
1626
1876
  cursor: "pointer",
1627
1877
  whiteSpace: "nowrap",
1628
- ...buttonShellStyle(ctx, fullWidth),
1629
- ...secondary ? { border: `1px solid ${secondaryColor ?? ctx.brand.palette.dark}`, color: secondaryColor ?? ctx.brand.palette.dark } : (
1878
+ ...buttonShellStyle(ctx, variant, fullWidth),
1879
+ ...tokenColorStyle ?? (secondary ? { border: `1px solid ${secondaryColor ?? ctx.brand.palette.dark}`, color: secondaryColor ?? ctx.brand.palette.dark } : (
1630
1880
  // Off-band, prefer the template button's measured fill/label pair — a template may
1631
1881
  // fill its CTAs with any token (hvac: accent bg, primary text). On an accent band
1632
1882
  // the flipped palette keeps contrast, so the brand-driven colours stay.
1633
1883
  !ctx.buttonLabel && ctx.buttonStyle?.background ? { background: ctx.buttonStyle.background, color: ctx.buttonStyle.color } : { background: ctx.brand.palette.primary, color: ctx.buttonLabel ?? primaryButtonLabel(ctx.brand) }
1634
- )
1884
+ ))
1635
1885
  },
1636
1886
  children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { ...path ? textAttrs(ctx, `${path}.label`) : {}, children: str(slots.label) })
1637
1887
  }
@@ -2605,7 +2855,7 @@ function CollectionBlock({ node, ctx, path }) {
2605
2855
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
2606
2856
  "div",
2607
2857
  {
2608
- "data-ai-grid": "",
2858
+ "data-ai-grid": String(itemsPerRow),
2609
2859
  "data-ohw-pricing-collection": isPricing ? "" : void 0,
2610
2860
  style: {
2611
2861
  display: "grid",
@@ -2853,18 +3103,21 @@ function renderNode(node, ctx, path) {
2853
3103
  "button",
2854
3104
  {
2855
3105
  type: "submit",
3106
+ className: "btn btn--primary",
3107
+ "data-ohw-btn-variant": "primary",
3108
+ "data-ohw-role": "button",
2856
3109
  style: {
2857
3110
  alignSelf: submitAlign,
2858
3111
  border: "none",
2859
3112
  cursor: "pointer",
2860
3113
  // Shape/padding/typography follow the host template's own buttons.
2861
- ...buttonShellStyle(ctx),
3114
+ ...buttonShellStyle(ctx, "primary"),
2862
3115
  // Brand-styled: the template button's measured fill/label pair off-band, else
2863
3116
  // primary fill with a brand-derived label so it reads on custom palettes.
2864
- ...!ctx.buttonLabel && ctx.buttonStyle?.background ? { background: ctx.buttonStyle.background, color: ctx.buttonStyle.color } : {
3117
+ ...buttonColorStyle(ctx, "primary") ?? (!ctx.buttonLabel && ctx.buttonStyle?.background ? { background: ctx.buttonStyle.background, color: ctx.buttonStyle.color } : {
2865
3118
  background: ctx.brand.palette.primary,
2866
3119
  color: ctx.buttonLabel ?? primaryButtonLabel(ctx.brand)
2867
- }
3120
+ })
2868
3121
  },
2869
3122
  children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { ...textAttrs(ctx, `${path}.c${i}.label`), children: str(cs.label) })
2870
3123
  },
@@ -2961,6 +3214,7 @@ function AiTreeRenderer({
2961
3214
  {
2962
3215
  "data-ai-section": tree.tag ?? "",
2963
3216
  ...bgAttrs,
3217
+ "data-ai-responsive": "",
2964
3218
  style: {
2965
3219
  position: "relative",
2966
3220
  padding: `${pad}px 0`,
@@ -2971,12 +3225,13 @@ function AiTreeRenderer({
2971
3225
  color: settings.sectionBackground === "accent" ? blockBrand.palette.dark : void 0
2972
3226
  },
2973
3227
  children: [
2974
- isOverlay && backgroundUrl && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { position: "absolute", inset: 0, background: `rgba(255,255,255,${overlayAlpha})` } }),
3228
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("style", { children: AI_RESPONSIVE_CSS }),
2975
3229
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("style", { children: AI_MOBILE_CSS }),
3230
+ isOverlay && backgroundUrl && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { position: "absolute", inset: 0, background: `rgba(255,255,255,${overlayAlpha})` } }),
2976
3231
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
2977
3232
  "div",
2978
3233
  {
2979
- "data-ai-container": "",
3234
+ "data-ai-section-inner": "",
2980
3235
  style: {
2981
3236
  position: "relative",
2982
3237
  maxWidth: AI_TREE_TOKENS.sectionMaxWidth,
@@ -2987,7 +3242,7 @@ function AiTreeRenderer({
2987
3242
  children: tree.rows.map((row, r2) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
2988
3243
  "div",
2989
3244
  {
2990
- "data-ai-row": "",
3245
+ "data-ai-columns": "",
2991
3246
  style: {
2992
3247
  display: "grid",
2993
3248
  gridTemplateColumns: "repeat(12, 1fr)",
@@ -3167,12 +3422,12 @@ function deriveTemplateButtonStyle() {
3167
3422
  cs.borderBottomLeftRadius
3168
3423
  ].map((v) => v || "0px");
3169
3424
  const radius = corners.every((v) => v === corners[0]) ? corners[0] : corners.join(" ");
3170
- const px = (v) => parseFloat(v) || 0;
3171
- const lineHeight = px(cs.lineHeight) || px(cs.fontSize) * 1.2;
3172
- const contentH = btn.getBoundingClientRect().height - px(cs.borderTopWidth) - px(cs.borderBottomWidth);
3425
+ const px2 = (v) => parseFloat(v) || 0;
3426
+ const lineHeight = px2(cs.lineHeight) || px2(cs.fontSize) * 1.2;
3427
+ const contentH = btn.getBoundingClientRect().height - px2(cs.borderTopWidth) - px2(cs.borderBottomWidth);
3173
3428
  const impliedY = Math.round(Math.max(0, (contentH - lineHeight) / 2));
3174
- const padY = Math.max(px(cs.paddingTop), px(cs.paddingBottom), impliedY);
3175
- const padX = Math.max(px(cs.paddingLeft), px(cs.paddingRight));
3429
+ const padY = Math.max(px2(cs.paddingTop), px2(cs.paddingBottom), impliedY);
3430
+ const padX = Math.max(px2(cs.paddingLeft), px2(cs.paddingRight));
3176
3431
  return {
3177
3432
  radius: radius || "10px",
3178
3433
  padding: `${padY}px ${padX}px`,
@@ -3391,9 +3646,10 @@ function applyAiSectionsToDom(state, options) {
3391
3646
  if (existing && existing.serialized === serialized && existing.container.isConnected) {
3392
3647
  continue;
3393
3648
  }
3394
- const isNewMount = !existing || !existing.container.isConnected;
3395
3649
  let container = existing?.container;
3650
+ let isNewMount = false;
3396
3651
  if (!container || !container.isConnected) {
3652
+ isNewMount = true;
3397
3653
  existing?.root.unmount();
3398
3654
  container?.remove();
3399
3655
  container = document.createElement("div");
@@ -8089,6 +8345,14 @@ function ItemActionToolbar({
8089
8345
  e.preventDefault();
8090
8346
  e.stopPropagation();
8091
8347
  onStyle?.();
8348
+ },
8349
+ // Same as Edit link: mousedown opens the panel and remounts this toolbar
8350
+ // (styleActive flips). Without stopping click, the gesture lands on whatever
8351
+ // is now under the cursor — navbar CTAs park the toolbar below the button, so
8352
+ // that miss deselects and the panel closes as soon as it opens.
8353
+ onClick: (e) => {
8354
+ e.preventDefault();
8355
+ e.stopPropagation();
8092
8356
  }
8093
8357
  },
8094
8358
  children: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_lucide_react3.Brush, { className: "size-4 shrink-0", "aria-hidden": true })
@@ -8411,20 +8675,32 @@ function ItemInteractionLayer({
8411
8675
  itemDragSurface = true,
8412
8676
  chromeGap,
8413
8677
  toolbarAlign = "center",
8678
+ hidden = false,
8414
8679
  className
8415
8680
  }) {
8681
+ const localRef = (0, import_react7.useRef)(null);
8682
+ (0, import_react7.useLayoutEffect)(() => {
8683
+ const node = localRef.current;
8684
+ if (!node) return;
8685
+ node.style.display = hidden ? "none" : "block";
8686
+ }, [hidden, rect.top, rect.left, rect.width, rect.height, state]);
8416
8687
  if (state === "default") return null;
8417
8688
  const gap = chromeGap ?? (state === "hover" ? HOVER_CHROME_GAP : SELECTION_CHROME_GAP);
8418
8689
  const isActive = state === "active-top" || state === "active-bottom";
8419
8690
  const isDragging = state === "dragging";
8420
8691
  const showToolbar = isActive && toolbar;
8421
8692
  const showDragHandle = (isActive || isDragging) && showHandle;
8422
- const itemDragEnabled = itemDragSurface && isActive && showHandle && !isDragging && !dragDisabled;
8693
+ const itemDragEnabled = itemDragSurface && isActive && !isDragging && !dragDisabled && Boolean(showHandle || onItemPointerDown);
8423
8694
  const useDetachedBelowToolbar = Boolean(showToolbar && state === "active-bottom" && toolbarBelowRect);
8695
+ const setRootRef = (node) => {
8696
+ localRef.current = node;
8697
+ if (typeof elRef === "function") elRef(node);
8698
+ else if (elRef) elRef.current = node;
8699
+ };
8424
8700
  return /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(
8425
8701
  "div",
8426
8702
  {
8427
- ref: elRef,
8703
+ ref: setRootRef,
8428
8704
  "data-ohw-item-interaction": "",
8429
8705
  "data-ohw-item-interaction-state": state,
8430
8706
  className: cn("pointer-events-none", className),
@@ -8434,7 +8710,8 @@ function ItemInteractionLayer({
8434
8710
  left: rect.left - gap,
8435
8711
  width: rect.width + gap * 2,
8436
8712
  height: rect.height + gap * 2,
8437
- zIndex: getChromeZIndex(state)
8713
+ zIndex: getChromeZIndex(state),
8714
+ display: hidden ? "none" : "block"
8438
8715
  },
8439
8716
  children: [
8440
8717
  /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
@@ -9266,6 +9543,7 @@ function MediaOverlay({
9266
9543
  (prev) => prev && prev.fullWidth === width && prev.height === height ? prev : { fullWidth: width, height }
9267
9544
  );
9268
9545
  }, [isVideo]);
9546
+ const replaceLabel = isVideo ? "Replace video" : hover.elementType === "bg-image" ? "Replace background" : "Replace image";
9269
9547
  const replaceMode = buttonSize ? replaceButtonMode({ width: rect.width, height: rect.height }, buttonSize) : "none";
9270
9548
  const box = {
9271
9549
  position: "fixed",
@@ -9364,11 +9642,10 @@ function MediaOverlay({
9364
9642
  className: "flex items-center justify-center cursor-pointer",
9365
9643
  style: {
9366
9644
  ...box,
9367
- // When text overlays this media, let clicks fall THROUGH to the text underneath. In the
9368
- // parent this needed a whole `ow:click-at` round-trip to re-hit-test inside the iframe;
9369
- // in-document, pointer-events does it natively. The button below opts back in, so
9370
- // Replace still works.
9371
- pointerEvents: hover.hasTextOverlap || cueOnly ? "none" : "auto",
9645
+ // Visual only. `pointer-events: auto` on this full-bleed rect covered the navbar
9646
+ // (hero bg includes the absolute TopNav) and swallowed CTA press-drag. Text overlay
9647
+ // and cueOnly also need clicks to fall through — Replace / video controls opt back in.
9648
+ pointerEvents: "none",
9372
9649
  // Selected: a firm component ring with no wash, so the image reads as chosen rather
9373
9650
  // than hovered. Hover keeps the existing tinted preview.
9374
9651
  boxShadow: selected ? "inset 0 0 0 2px var(--color-bridge-primary)" : "inset 0 0 0 1.5px var(--color-bridge-primary)",
@@ -9395,17 +9672,17 @@ function MediaOverlay({
9395
9672
  },
9396
9673
  children: [
9397
9674
  isVideo ? /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_lucide_react5.Film, { size: 14 }) : /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_lucide_react5.ImageIcon, { size: 14 }),
9398
- isVideo ? "Replace video" : "Replace image"
9675
+ replaceLabel
9399
9676
  ]
9400
9677
  }
9401
9678
  ),
9402
- replaceMode === "none" || cueOnly ? null : /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(
9679
+ showChrome && replaceMode !== "none" && /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(
9403
9680
  Button,
9404
9681
  {
9405
9682
  "data-ohw-media-overlay": "",
9406
9683
  variant: "outline",
9407
9684
  size: "sm",
9408
- "aria-label": isVideo ? "Replace video" : "Replace image",
9685
+ "aria-label": replaceLabel,
9409
9686
  className: "gap-1.5 cursor-pointer hover:bg-background",
9410
9687
  style: {
9411
9688
  ...OVERLAY_BUTTON_STYLE,
@@ -9428,7 +9705,7 @@ function MediaOverlay({
9428
9705
  },
9429
9706
  children: [
9430
9707
  isVideo ? /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_lucide_react5.Film, { size: 14 }) : /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_lucide_react5.ImageIcon, { size: 14 }),
9431
- replaceMode === "full" ? isVideo ? "Replace video" : hover.elementType === "bg-image" ? "Replace background" : "Replace image" : null
9708
+ replaceMode === "full" ? replaceLabel : null
9432
9709
  ]
9433
9710
  }
9434
9711
  )
@@ -11255,6 +11532,13 @@ function findButtonAtPoint(x, y, media) {
11255
11532
  if (hits.length === 0) return null;
11256
11533
  return hits.reduce((best, el) => best.contains(el) ? el : best);
11257
11534
  }
11535
+ function findNavbarCtaAtPoint(x, y) {
11536
+ const hits = Array.from(
11537
+ document.querySelectorAll('[data-ohw-role="navbar-button"]')
11538
+ ).filter((el) => containsPoint(el, x, y) && !el.closest("[data-ohw-bridge]"));
11539
+ if (hits.length === 0) return null;
11540
+ return hits.reduce((best, el) => best.contains(el) ? el : best);
11541
+ }
11258
11542
 
11259
11543
  // src/lib/session-search.ts
11260
11544
  var STORAGE_KEY = "ohw-preserved-search";
@@ -11990,6 +12274,26 @@ var import_react12 = require("react");
11990
12274
  var import_react_dom3 = require("react-dom");
11991
12275
  var import_lucide_react15 = require("lucide-react");
11992
12276
  var import_navigation2 = require("next/navigation");
12277
+
12278
+ // src/lib/parent-scroll.ts
12279
+ function visibleBand(parentScroll) {
12280
+ const { iframeOffsetTop, headerH: visibleCanvasTop, canvasH } = parentScroll;
12281
+ const top = Math.max(0, visibleCanvasTop - iframeOffsetTop);
12282
+ const bottom = Math.min(window.innerHeight, visibleCanvasTop + canvasH - iframeOffsetTop);
12283
+ return { top, bottom: Math.max(top, bottom) };
12284
+ }
12285
+ function getIframeVisibleClip(parentScroll) {
12286
+ if (!parentScroll) return null;
12287
+ return visibleBand(parentScroll);
12288
+ }
12289
+ function getIframeChromeClip(parentScroll) {
12290
+ if (!parentScroll) return null;
12291
+ const band = visibleBand(parentScroll);
12292
+ const promptBarH = Math.max(0, parentScroll.promptBarH ?? 0);
12293
+ return { top: band.top, bottom: Math.max(band.top, band.bottom - promptBarH) };
12294
+ }
12295
+
12296
+ // src/ui/link-modal/SectionPickerOverlay.tsx
11993
12297
  var import_jsx_runtime27 = require("react/jsx-runtime");
11994
12298
  var DIM_OVERLAY = "rgba(0, 0, 0, 0.45)";
11995
12299
  function rectsEqual(a, b) {
@@ -12127,20 +12431,22 @@ function SectionPickerOverlay({
12127
12431
  []
12128
12432
  );
12129
12433
  (0, import_react12.useEffect)(() => {
12130
- const applyClip = (iframeOffsetTop, visibleCanvasTop, canvasH) => {
12131
- const top = Math.max(0, visibleCanvasTop - iframeOffsetTop);
12132
- const bottom = Math.min(
12133
- window.innerHeight,
12134
- visibleCanvasTop + canvasH - iframeOffsetTop
12135
- );
12434
+ const applyClip = (iframeOffsetTop, visibleCanvasTop, canvasH, promptBarH = 0) => {
12435
+ const next = getIframeVisibleClip({
12436
+ iframeOffsetTop,
12437
+ headerH: visibleCanvasTop,
12438
+ canvasH,
12439
+ promptBarH
12440
+ });
12441
+ if (!next) return;
12136
12442
  setChromeClip(
12137
- (prev) => prev.top === top && prev.bottom === bottom ? prev : { top, bottom: Math.max(top, bottom) }
12443
+ (prev) => prev.top === next.top && prev.bottom === next.bottom ? prev : next
12138
12444
  );
12139
12445
  };
12140
12446
  const onParentScroll = (e) => {
12141
12447
  if (e.data?.type !== "ow:parent-scroll") return;
12142
- const { iframeOffsetTop, headerH, canvasH } = e.data;
12143
- applyClip(iframeOffsetTop, headerH, canvasH);
12448
+ const { iframeOffsetTop, headerH, canvasH, promptBarH = 0 } = e.data;
12449
+ applyClip(iframeOffsetTop, headerH, canvasH, promptBarH);
12144
12450
  iframeOffsetTopRef.current = iframeOffsetTop;
12145
12451
  if (!pointerScreenRef.current && pointerRef.current) {
12146
12452
  pointerScreenRef.current = {
@@ -15733,6 +16039,7 @@ function readLogoSizeState(content, placement) {
15733
16039
  function getLogoElement(el) {
15734
16040
  const marked = el.closest('[data-ohw-role="logo"], [data-ohw-logo]');
15735
16041
  if (marked) return marked;
16042
+ if (el.closest('[data-ohw-editable="icon"]')) return null;
15736
16043
  const root = el.closest("nav, [data-ohw-nav-root], footer");
15737
16044
  if (!root) return null;
15738
16045
  const anchor = el.closest("a");
@@ -15804,36 +16111,50 @@ var import_lucide_react16 = require("lucide-react");
15804
16111
  var import_jsx_runtime29 = require("react/jsx-runtime");
15805
16112
  var PANEL_WIDTH = 256;
15806
16113
  var EDGE_MARGIN = 16;
16114
+ var HEADER_MIN = 56;
15807
16115
  function getVisibleClip(parentScroll) {
15808
16116
  const left = 0;
15809
16117
  const right = window.innerWidth;
15810
- if (!parentScroll) {
16118
+ const band = getIframeChromeClip(parentScroll);
16119
+ if (!band) {
15811
16120
  return { top: 0, bottom: window.innerHeight, left, right };
15812
16121
  }
15813
- const { iframeOffsetTop, headerH: visibleCanvasTop, canvasH } = parentScroll;
15814
- const top = Math.max(0, visibleCanvasTop - iframeOffsetTop);
15815
- const bottom = Math.min(window.innerHeight, visibleCanvasTop + canvasH - iframeOffsetTop);
15816
- return { top, bottom: Math.max(top, bottom), left, right };
16122
+ return { ...band, left, right };
15817
16123
  }
15818
- function defaultFloatingPanelPosition(parentScroll, panelHeight = 280) {
16124
+ function defaultFloatingPanelPosition(parentScroll, _panelHeight = 280) {
15819
16125
  const clip = getVisibleClip(parentScroll);
15820
16126
  return {
15821
16127
  x: Math.max(EDGE_MARGIN, clip.right - PANEL_WIDTH - EDGE_MARGIN),
15822
- y: Math.min(
15823
- Math.max(clip.top + EDGE_MARGIN, EDGE_MARGIN),
15824
- Math.max(clip.top + EDGE_MARGIN, clip.bottom - panelHeight - EDGE_MARGIN)
15825
- )
16128
+ y: clip.top + EDGE_MARGIN
15826
16129
  };
15827
16130
  }
15828
- function clampPosition(pos, parentScroll, panelW, panelH) {
16131
+ function clampX(x, panelW) {
16132
+ const left = EDGE_MARGIN;
16133
+ const maxX = Math.max(left, window.innerWidth - panelW - EDGE_MARGIN);
16134
+ return Math.min(Math.max(x, left), maxX);
16135
+ }
16136
+ function clampDragPosition(pos, parentScroll, panelW) {
15829
16137
  const clip = getVisibleClip(parentScroll);
15830
- const maxX = Math.max(clip.left + EDGE_MARGIN, clip.right - panelW - EDGE_MARGIN);
15831
- const maxY = Math.max(clip.top + EDGE_MARGIN, clip.bottom - panelH - EDGE_MARGIN);
16138
+ const minY = clip.top + EDGE_MARGIN;
16139
+ const maxY = Math.max(minY, clip.bottom - EDGE_MARGIN - HEADER_MIN);
15832
16140
  return {
15833
- x: Math.min(Math.max(pos.x, clip.left + EDGE_MARGIN), maxX),
15834
- y: Math.min(Math.max(pos.y, clip.top + EDGE_MARGIN), maxY)
16141
+ x: clampX(pos.x, panelW),
16142
+ y: Math.min(Math.max(pos.y, minY), maxY)
15835
16143
  };
15836
16144
  }
16145
+ function fitPanelInClip(homeY, parentScroll, preferredHeight) {
16146
+ const clip = getVisibleClip(parentScroll);
16147
+ const minY = clip.top + EDGE_MARGIN;
16148
+ const maxBottom = clip.bottom - EDGE_MARGIN;
16149
+ const band = Math.max(HEADER_MIN, maxBottom - minY);
16150
+ let y = Math.max(homeY, minY);
16151
+ const want = Math.min(Math.max(preferredHeight, HEADER_MIN), band);
16152
+ if (y + want > maxBottom) {
16153
+ y = Math.max(minY, maxBottom - want);
16154
+ }
16155
+ const maxHeight = Math.max(HEADER_MIN, Math.floor(maxBottom - y));
16156
+ return { y, maxHeight };
16157
+ }
15837
16158
  function FloatingPanel({
15838
16159
  open,
15839
16160
  title,
@@ -15850,19 +16171,54 @@ function FloatingPanel({
15850
16171
  const panelRef = (0, import_react15.useRef)(null);
15851
16172
  const [measured, setMeasured] = (0, import_react15.useState)({ w: PANEL_WIDTH, h: 280 });
15852
16173
  const dragRef = (0, import_react15.useRef)(null);
15853
- const resolved = position ?? defaultFloatingPanelPosition(parentScroll, measured.h);
15854
- const clamped = clampPosition(resolved, parentScroll, measured.w, measured.h);
16174
+ const homeRef = (0, import_react15.useRef)(null);
16175
+ if (!open) {
16176
+ homeRef.current = null;
16177
+ } else if (position) {
16178
+ homeRef.current = position;
16179
+ } else if (homeRef.current == null) {
16180
+ homeRef.current = defaultFloatingPanelPosition(parentScroll);
16181
+ }
16182
+ const [liveScroll, setLiveScroll] = (0, import_react15.useState)(parentScroll);
16183
+ const home = position ?? homeRef.current ?? defaultFloatingPanelPosition(parentScroll);
16184
+ const { y: fittedY, maxHeight } = fitPanelInClip(home.y, liveScroll, measured.h);
16185
+ const clamped = {
16186
+ x: clampX(home.x, measured.w),
16187
+ y: fittedY
16188
+ };
16189
+ (0, import_react15.useEffect)(() => {
16190
+ setLiveScroll(parentScroll);
16191
+ }, [parentScroll]);
16192
+ (0, import_react15.useEffect)(() => {
16193
+ if (!open) return;
16194
+ const onParentScroll = (e) => {
16195
+ if (e.data?.type !== "ow:parent-scroll") return;
16196
+ const { iframeOffsetTop, headerH, canvasH, promptBarH = 0 } = e.data;
16197
+ setLiveScroll({ iframeOffsetTop, headerH, canvasH, promptBarH });
16198
+ };
16199
+ window.addEventListener("message", onParentScroll);
16200
+ return () => window.removeEventListener("message", onParentScroll);
16201
+ }, [open]);
16202
+ const positionRef = (0, import_react15.useRef)(position);
16203
+ (0, import_react15.useLayoutEffect)(() => {
16204
+ if (!open) {
16205
+ positionRef.current = position;
16206
+ return;
16207
+ }
16208
+ if (position == null && positionRef.current != null) {
16209
+ homeRef.current = defaultFloatingPanelPosition(liveScroll ?? parentScroll);
16210
+ }
16211
+ positionRef.current = position;
16212
+ }, [open, position, liveScroll, parentScroll]);
15855
16213
  (0, import_react15.useLayoutEffect)(() => {
15856
16214
  if (!open || !panelRef.current) return;
15857
16215
  const el = panelRef.current;
15858
- const next = { w: el.offsetWidth || PANEL_WIDTH, h: el.offsetHeight || 280 };
16216
+ const header = el.querySelector("[data-ohw-floating-panel-header]");
16217
+ const body = el.querySelector("[data-ohw-floating-panel-body]");
16218
+ const contentH = (header?.offsetHeight ?? 0) + (body?.scrollHeight ?? 0) || el.scrollHeight || 280;
16219
+ const next = { w: el.offsetWidth || PANEL_WIDTH, h: contentH };
15859
16220
  setMeasured((prev) => prev.w === next.w && prev.h === next.h ? prev : next);
15860
16221
  }, [open, children, title, context]);
15861
- (0, import_react15.useEffect)(() => {
15862
- if (!open || !position || !onPositionChange) return;
15863
- const next = clampPosition(position, parentScroll, measured.w, measured.h);
15864
- if (next.x !== position.x || next.y !== position.y) onPositionChange(next);
15865
- }, [open, parentScroll, measured.w, measured.h, position, onPositionChange]);
15866
16222
  const onHeaderPointerDown = (0, import_react15.useCallback)(
15867
16223
  (e) => {
15868
16224
  if (e.button !== 0) return;
@@ -15896,18 +16252,17 @@ function FloatingPanel({
15896
16252
  return;
15897
16253
  }
15898
16254
  e.preventDefault();
15899
- const next = clampPosition(
16255
+ const next = clampDragPosition(
15900
16256
  {
15901
16257
  x: drag.originX + (e.clientX - drag.startX),
15902
16258
  y: drag.originY + (e.clientY - drag.startY)
15903
16259
  },
15904
- parentScroll,
15905
- measured.w,
15906
- measured.h
16260
+ liveScroll,
16261
+ measured.w
15907
16262
  );
15908
16263
  onPositionChange?.(next);
15909
16264
  },
15910
- [measured.h, measured.w, onPositionChange, parentScroll]
16265
+ [measured.w, onPositionChange, liveScroll]
15911
16266
  );
15912
16267
  const endDrag = (0, import_react15.useCallback)((e) => {
15913
16268
  const drag = dragRef.current;
@@ -15937,7 +16292,7 @@ function FloatingPanel({
15937
16292
  "fixed z-[2147483647] flex w-64 flex-col overflow-hidden rounded-xl border border-border bg-background font-sans shadow-lg outline-none",
15938
16293
  className
15939
16294
  ),
15940
- style: { left: clamped.x, top: clamped.y },
16295
+ style: { left: clamped.x, top: clamped.y, maxHeight },
15941
16296
  onMouseDown: (e) => e.stopPropagation(),
15942
16297
  onPointerDown: (e) => e.stopPropagation(),
15943
16298
  onClick: (e) => e.stopPropagation(),
@@ -15946,7 +16301,7 @@ function FloatingPanel({
15946
16301
  "div",
15947
16302
  {
15948
16303
  "data-ohw-floating-panel-header": "",
15949
- className: "relative flex cursor-grab items-start gap-2 border-b border-border py-5 pl-5 pr-11 active:cursor-grabbing",
16304
+ className: "relative flex shrink-0 cursor-grab items-start gap-2 border-b border-border py-4! pl-5! pr-11! active:cursor-grabbing",
15950
16305
  onPointerDown: onHeaderPointerDown,
15951
16306
  onPointerMove: onHeaderPointerMove,
15952
16307
  onPointerUp: endDrag,
@@ -15981,7 +16336,11 @@ function FloatingPanel({
15981
16336
  "div",
15982
16337
  {
15983
16338
  "data-ohw-floating-panel-body": "",
15984
- className: cn("flex w-full flex-col gap-4 p-5", bodyClassName),
16339
+ className: cn(
16340
+ "flex min-h-0 w-full flex-1 flex-col gap-4 overflow-y-auto overscroll-contain p-4",
16341
+ bodyClassName
16342
+ ),
16343
+ style: { WebkitOverflowScrolling: "touch" },
15985
16344
  children
15986
16345
  }
15987
16346
  )
@@ -16059,121 +16418,1555 @@ function SocialsDisplayPanel({ display, onChange, className }) {
16059
16418
  ] });
16060
16419
  }
16061
16420
 
16062
- // src/lib/item-drag-interaction.ts
16063
- function disableNativeHrefDrag(el) {
16064
- if (el.draggable) el.draggable = false;
16065
- if (el.getAttribute("draggable") !== "false") {
16066
- el.setAttribute("draggable", "false");
16421
+ // src/ui/button-style-panel.tsx
16422
+ var import_lucide_react17 = require("lucide-react");
16423
+
16424
+ // src/lib/button-styles.ts
16425
+ var BUTTON_VARIANTS_KEY = "__ohw_button_variants";
16426
+ var BUTTON_SIZES_KEY = "__ohw_button_sizes";
16427
+ var BUTTON_ASSIGNMENTS_KEY = "__ohw_button_assignments";
16428
+ var BUTTON_VARIANT_IDS = ["primary", "secondary", "ghost"];
16429
+ var BUTTON_SIZE_IDS = ["s", "m", "l"];
16430
+ var TRANSPARENT = "rgba(0, 0, 0, 0)";
16431
+ var REFERENCE_SIZES = {
16432
+ s: { height: 40, paddingTop: 8, paddingRight: 16, paddingBottom: 8, paddingLeft: 16, fontSize: 15, borderRadius: 8, gap: 8 },
16433
+ m: { height: 48, paddingTop: 12, paddingRight: 24, paddingBottom: 12, paddingLeft: 24, fontSize: 15, borderRadius: 8, gap: 8 },
16434
+ l: { height: 56, paddingTop: 16, paddingRight: 32, paddingBottom: 16, paddingLeft: 32, fontSize: 15, borderRadius: 8, gap: 8 }
16435
+ };
16436
+ function buttonStyleContext(_el) {
16437
+ return "Button";
16438
+ }
16439
+ function previewSlotBackground(el) {
16440
+ let node = el.parentElement;
16441
+ while (node && node !== el.ownerDocument.documentElement) {
16442
+ if (node.closest("[data-ohw-bridge], [data-ohw-bridge-root]")) {
16443
+ node = node.parentElement;
16444
+ continue;
16445
+ }
16446
+ const bg = getComputedStyle(node).backgroundColor;
16447
+ const parsed = parseColor(bg);
16448
+ if (parsed && !isTransparent(parsed)) return bg;
16449
+ node = node.parentElement;
16450
+ }
16451
+ const root = el.ownerDocument.documentElement;
16452
+ const style = getComputedStyle(root);
16453
+ for (const name of ["--brand-surface", "--brand-background", "--ohw-brand-light", "--color-light"]) {
16454
+ const value = style.getPropertyValue(name).trim();
16455
+ if (value) return value;
16067
16456
  }
16457
+ return "var(--brand-background, var(--ohw-brand-light, var(--color-light, #ffffff)))";
16068
16458
  }
16069
- function clearTextSelection() {
16070
- const sel = window.getSelection();
16071
- if (sel && !sel.isCollapsed) sel.removeAllRanges();
16459
+ function buttonId(el) {
16460
+ if (!el) return null;
16461
+ const button = el.matches(CTA_BUTTON_SELECTOR) ? el : el.closest(CTA_BUTTON_SELECTOR);
16462
+ const id = button?.getAttribute("data-ohw-href-key")?.trim();
16463
+ return id || null;
16072
16464
  }
16073
- function armItemPressDrag() {
16074
- document.documentElement.setAttribute("data-ohw-footer-press-drag", "");
16465
+ function isButtonVariantId(value) {
16466
+ return BUTTON_VARIANT_IDS.includes(value);
16075
16467
  }
16076
- function lockItemDuringDrag() {
16077
- document.documentElement.removeAttribute("data-ohw-footer-press-drag");
16078
- document.documentElement.setAttribute("data-ohw-item-dragging", "");
16079
- clearTextSelection();
16468
+ function isButtonSizeId(value) {
16469
+ return BUTTON_SIZE_IDS.includes(value);
16080
16470
  }
16081
- function unlockItemDragInteraction() {
16082
- const wasDragging = document.documentElement.hasAttribute("data-ohw-item-dragging");
16083
- document.documentElement.removeAttribute("data-ohw-footer-press-drag");
16084
- document.documentElement.removeAttribute("data-ohw-item-dragging");
16085
- if (wasDragging) clearTextSelection();
16471
+ function px(value) {
16472
+ const rounded = Math.round(value * 100) / 100;
16473
+ return `${rounded}px`;
16086
16474
  }
16087
- var armFooterPressDrag = armItemPressDrag;
16088
- var lockFooterDuringDrag = lockItemDuringDrag;
16089
- var unlockFooterDragInteraction = unlockItemDragInteraction;
16090
-
16091
- // src/lib/nav-dnd.ts
16092
- function listReorderableNavItems(draggedEl) {
16093
- return listActiveNavbarItems(draggedEl).filter((el) => {
16094
- const key = el.getAttribute("data-ohw-href-key");
16095
- return isNavbarHrefKey(key) && !isNestedNavChild(el);
16096
- });
16475
+ function parsePx(value, rootFont = 16) {
16476
+ if (!value) return 0;
16477
+ const n = parseFloat(value);
16478
+ if (!Number.isFinite(n)) return 0;
16479
+ if (value.trim().endsWith("rem") || value.trim().endsWith("em")) return n * rootFont;
16480
+ return n;
16097
16481
  }
16098
- function resolveParentNavHrefKey(child) {
16099
- const childrenRoot = child.closest("[data-ohw-nav-children]");
16100
- const group = childrenRoot?.closest("[data-ohw-nav-group]");
16101
- if (!group) return null;
16102
- const trigger = group.querySelector(":scope > [data-ohw-href-key]") ?? Array.from(group.querySelectorAll("[data-ohw-href-key]")).find(
16103
- (el) => !el.closest("[data-ohw-nav-children]")
16104
- ) ?? null;
16105
- const key = trigger?.getAttribute("data-ohw-href-key");
16106
- return key && isNavbarHrefKey(key) ? key : null;
16482
+ function classifyHeight(height) {
16483
+ if (height < 44) return "s";
16484
+ if (height < 52) return "m";
16485
+ return "l";
16107
16486
  }
16108
- function listNavChildren(parentHrefKey, draggedEl) {
16109
- const items = listActiveNavbarItems(draggedEl);
16110
- const parent = items.find((el) => el.getAttribute("data-ohw-href-key") === parentHrefKey);
16111
- if (!parent) return [];
16112
- const group = parent.closest("[data-ohw-nav-group]");
16113
- const childrenRoot = group?.querySelector(":scope > [data-ohw-nav-children]");
16114
- if (!childrenRoot) return [];
16115
- return Array.from(childrenRoot.querySelectorAll(":scope > [data-ohw-href-key]")).filter(
16116
- (el) => isNavbarHrefKey(el.getAttribute("data-ohw-href-key"))
16117
- );
16487
+ function scaleGeometry(geometry, scale = 0.75) {
16488
+ return {
16489
+ paddingTop: px(parsePx(geometry.paddingTop) * scale),
16490
+ paddingRight: px(parsePx(geometry.paddingRight) * scale),
16491
+ paddingBottom: px(parsePx(geometry.paddingBottom) * scale),
16492
+ paddingLeft: px(parsePx(geometry.paddingLeft) * scale),
16493
+ fontSize: px(parsePx(geometry.fontSize) * scale),
16494
+ borderRadius: px(parsePx(geometry.borderRadius) * scale),
16495
+ gap: px(parsePx(geometry.gap) * scale),
16496
+ height: Math.round(geometry.height * scale)
16497
+ };
16118
16498
  }
16119
- function isVerticalNavLayout(items, draggedEl) {
16120
- const visible = items.filter((el) => el.getClientRects().length > 0);
16121
- if (visible.length >= 2) {
16122
- const a = visible[0].getBoundingClientRect();
16123
- const b = visible[1].getBoundingClientRect();
16124
- const verticalGap = b.top - a.bottom;
16125
- const horizontalGap = b.left - a.right;
16126
- return verticalGap > horizontalGap;
16499
+ function relativeLuminance(color) {
16500
+ const channel = (value) => {
16501
+ const s = value / 255;
16502
+ return s <= 0.03928 ? s / 12.92 : ((s + 0.055) / 1.055) ** 2.4;
16503
+ };
16504
+ return 0.2126 * channel(color.r) + 0.7152 * channel(color.g) + 0.0722 * channel(color.b);
16505
+ }
16506
+ function contrastRatio(a, b) {
16507
+ const lighter = Math.max(relativeLuminance(a), relativeLuminance(b));
16508
+ const darker = Math.min(relativeLuminance(a), relativeLuminance(b));
16509
+ return (lighter + 0.05) / (darker + 0.05);
16510
+ }
16511
+ function compositeOver(fg, bg) {
16512
+ const a = fg.a + bg.a * (1 - fg.a);
16513
+ if (a < 1e-3) return { ...bg, a: 0 };
16514
+ return {
16515
+ r: (fg.r * fg.a + bg.r * bg.a * (1 - fg.a)) / a,
16516
+ g: (fg.g * fg.a + bg.g * bg.a * (1 - fg.a)) / a,
16517
+ b: (fg.b * fg.a + bg.b * bg.a * (1 - fg.a)) / a,
16518
+ a
16519
+ };
16520
+ }
16521
+ function previewSlotForChrome(chrome, slotBackground) {
16522
+ const text = parseColor(chrome.color);
16523
+ if (!text || text.a < 0.2) return slotBackground;
16524
+ const slot = parseColor(slotBackground) ?? { r: 250, g: 250, b: 249, a: 1 };
16525
+ const fill = parseColor(chrome.background);
16526
+ const effective = fill && !isTransparent(fill) ? compositeOver(fill, slot) : slot;
16527
+ if (contrastRatio(text, effective) >= 3) return slotBackground;
16528
+ return relativeLuminance(text) > 0.55 ? "#2c2c2c" : "#f4f4f5";
16529
+ }
16530
+ function parseColor(input) {
16531
+ if (!input) return null;
16532
+ const value = input.trim().toLowerCase();
16533
+ if (value === "transparent") return { r: 0, g: 0, b: 0, a: 0 };
16534
+ const hex = value.match(/^#([0-9a-f]{3,8})$/);
16535
+ if (hex) {
16536
+ const raw = hex[1];
16537
+ const expand = (pair) => parseInt(pair.length === 1 ? pair + pair : pair, 16);
16538
+ if (raw.length === 3 || raw.length === 4) {
16539
+ return {
16540
+ r: expand(raw[0]),
16541
+ g: expand(raw[1]),
16542
+ b: expand(raw[2]),
16543
+ a: raw.length === 4 ? expand(raw[3]) / 255 : 1
16544
+ };
16545
+ }
16546
+ if (raw.length === 6 || raw.length === 8) {
16547
+ return {
16548
+ r: expand(raw.slice(0, 2)),
16549
+ g: expand(raw.slice(2, 4)),
16550
+ b: expand(raw.slice(4, 6)),
16551
+ a: raw.length === 8 ? expand(raw.slice(6, 8)) / 255 : 1
16552
+ };
16553
+ }
16127
16554
  }
16128
- const container = getActiveNavbarListContainer(draggedEl);
16129
- if (!container) return false;
16130
- const style = window.getComputedStyle(container);
16131
- return style.flexDirection === "column" || style.flexDirection === "column-reverse";
16555
+ const rgb = value.match(
16556
+ /^rgba?\(\s*([\d.]+)[%\s,]+([\d.]+)[%\s,]+([\d.]+)(?:%?\s*[,/]\s*([\d.]+%?))?\s*\)$/
16557
+ );
16558
+ if (rgb) {
16559
+ const alphaRaw = rgb[4];
16560
+ const alpha = alphaRaw == null ? 1 : alphaRaw.endsWith("%") ? parseFloat(alphaRaw) / 100 : parseFloat(alphaRaw);
16561
+ return { r: Number(rgb[1]), g: Number(rgb[2]), b: Number(rgb[3]), a: alpha };
16562
+ }
16563
+ const oklch = value.match(
16564
+ /^oklch\(\s*([\d.]+%?)\s+([\d.]+)\s+([\d.]+)(?:deg)?(?:\s*\/\s*([\d.]+%?))?\s*\)$/
16565
+ );
16566
+ if (oklch) {
16567
+ const Lraw = oklch[1];
16568
+ const L = Lraw.endsWith("%") ? parseFloat(Lraw) / 100 : parseFloat(Lraw);
16569
+ const gray = Math.max(0, Math.min(1, L)) * 255;
16570
+ const alphaRaw = oklch[4];
16571
+ const alpha = alphaRaw == null ? 1 : alphaRaw.endsWith("%") ? parseFloat(alphaRaw) / 100 : parseFloat(alphaRaw);
16572
+ return { r: gray, g: gray, b: gray, a: alpha };
16573
+ }
16574
+ return null;
16132
16575
  }
16133
- function buildVerticalRootNavDropSlots(items, draggedEl) {
16134
- const slots = [];
16135
- const barThickness = 3;
16136
- if (items.length === 0) {
16137
- const container = getActiveNavbarListContainer(draggedEl);
16138
- if (!container) return slots;
16139
- const rect = container.getBoundingClientRect();
16140
- slots.push({
16141
- insertIndex: 0,
16142
- parentId: null,
16143
- left: rect.left,
16144
- top: rect.top + rect.height / 2 - barThickness / 2,
16145
- width: Math.max(rect.width, 40),
16146
- height: barThickness,
16147
- direction: "horizontal"
16148
- });
16149
- return slots;
16576
+ function isTransparent(color) {
16577
+ return !color || color.a < 0.05;
16578
+ }
16579
+ function hasOpaqueBackground(background) {
16580
+ if (!background) return false;
16581
+ const value = background.trim().toLowerCase();
16582
+ if (!value || value === "transparent" || value === "rgba(0, 0, 0, 0)" || value === "rgb(0, 0, 0, 0)") {
16583
+ return false;
16150
16584
  }
16151
- const edgeGap = (() => {
16152
- if (items.length < 2) return 16;
16153
- const a = items[0].getBoundingClientRect();
16154
- const b = items[1].getBoundingClientRect();
16155
- return Math.max(0, b.top - a.bottom);
16156
- })();
16157
- for (let i = 0; i <= items.length; i++) {
16158
- let top;
16159
- let width;
16160
- let left;
16161
- if (i === 0) {
16162
- const first = items[0].getBoundingClientRect();
16163
- top = first.top - edgeGap / 2 - barThickness / 2;
16164
- left = first.left;
16165
- width = first.width;
16166
- } else if (i === items.length) {
16167
- const last = items[items.length - 1].getBoundingClientRect();
16168
- const lastGap = items.length >= 2 ? Math.max(0, last.top - items[items.length - 2].getBoundingClientRect().bottom) : edgeGap;
16169
- top = last.bottom + lastGap / 2 - barThickness / 2;
16170
- left = last.left;
16171
- width = last.width;
16172
- } else {
16173
- const prev = items[i - 1].getBoundingClientRect();
16174
- const next = items[i].getBoundingClientRect();
16175
- top = (prev.bottom + next.top) / 2 - barThickness / 2;
16176
- left = Math.min(prev.left, next.left);
16585
+ const parsed = parseColor(background);
16586
+ if (parsed) return !isTransparent(parsed);
16587
+ return true;
16588
+ }
16589
+ function toCss(color) {
16590
+ const r2 = Math.round(color.r);
16591
+ const g = Math.round(color.g);
16592
+ const b = Math.round(color.b);
16593
+ if (color.a >= 0.995) return `rgb(${r2}, ${g}, ${b})`;
16594
+ return `rgba(${r2}, ${g}, ${b}, ${Math.round(color.a * 100) / 100})`;
16595
+ }
16596
+ function colorKey(input) {
16597
+ const parsed = parseColor(input);
16598
+ if (!parsed || isTransparent(parsed)) return "transparent";
16599
+ return `${Math.round(parsed.r / 8)}-${Math.round(parsed.g / 8)}-${Math.round(parsed.b / 8)}-${Math.round(parsed.a * 4)}`;
16600
+ }
16601
+ function declaredVariant(className) {
16602
+ const match = className.match(/\bbtn--(primary|secondary|ghost)\b/);
16603
+ return match && isButtonVariantId(match[1]) ? match[1] : null;
16604
+ }
16605
+ function fillSlot(sample) {
16606
+ const width = parseFloat(sample.borderWidth);
16607
+ const border = parseColor(sample.borderColor);
16608
+ if (width > 0 && !isTransparent(border)) return "secondary";
16609
+ if (hasOpaqueBackground(sample.background)) return "primary";
16610
+ return "ghost";
16611
+ }
16612
+ function sampleSlot(sample) {
16613
+ return declaredVariant(sample.className) ?? fillSlot(sample);
16614
+ }
16615
+ function fingerprint(sample) {
16616
+ const width = parseFloat(sample.borderWidth);
16617
+ const border = width > 0 ? colorKey(sample.borderColor) : "none";
16618
+ return `${colorKey(sample.background)}|${border}|${colorKey(sample.color)}|${sampleSlot(sample)}`;
16619
+ }
16620
+ function paintColor(chrome) {
16621
+ if (hasOpaqueBackground(chrome.background)) {
16622
+ const parsed = parseColor(chrome.background);
16623
+ return parsed ? toCss({ ...parsed, a: 1 }) : chrome.background;
16624
+ }
16625
+ const width = parseFloat(chrome.borderWidth);
16626
+ if (width > 0 && hasOpaqueBackground(chrome.borderColor)) {
16627
+ const parsed = parseColor(chrome.borderColor);
16628
+ return parsed ? toCss({ ...parsed, a: 1 }) : chrome.borderColor;
16629
+ }
16630
+ const text = parseColor(chrome.color);
16631
+ return text ? toCss({ ...text, a: 1 }) : chrome.color;
16632
+ }
16633
+ function accentColor(chrome) {
16634
+ const bg = parseColor(chrome.background);
16635
+ if (bg && !isTransparent(bg) && relativeLuminance(bg) < 0.55) {
16636
+ return toCss({ ...bg, a: 1 });
16637
+ }
16638
+ const text = parseColor(chrome.color);
16639
+ if (text && !isTransparent(text)) return toCss({ ...text, a: 1 });
16640
+ return paintColor(chrome);
16641
+ }
16642
+ function chromeFrom(sample) {
16643
+ return {
16644
+ background: sample.background || TRANSPARENT,
16645
+ color: sample.color || "currentColor",
16646
+ borderColor: sample.borderColor || TRANSPARENT,
16647
+ borderWidth: sample.borderWidth || "0px"
16648
+ };
16649
+ }
16650
+ function filledPrimary(seed) {
16651
+ const paint = parseColor(paintColor(seed)) ?? { r: 15, g: 15, b: 15, a: 1 };
16652
+ const luminance = relativeLuminance(paint);
16653
+ return {
16654
+ background: toCss({ ...paint, a: 1 }),
16655
+ color: luminance > 0.55 ? "rgb(15, 15, 15)" : "rgb(255, 255, 255)",
16656
+ borderColor: TRANSPARENT,
16657
+ borderWidth: "0px"
16658
+ };
16659
+ }
16660
+ function generatedSecondary(primary) {
16661
+ const paint = accentColor(primary);
16662
+ return {
16663
+ background: TRANSPARENT,
16664
+ color: paint,
16665
+ borderColor: paint,
16666
+ borderWidth: "1px"
16667
+ };
16668
+ }
16669
+ function generatedGhost(primary) {
16670
+ return {
16671
+ background: TRANSPARENT,
16672
+ color: accentColor(primary),
16673
+ borderColor: TRANSPARENT,
16674
+ borderWidth: "0px"
16675
+ };
16676
+ }
16677
+ function normalizeOutlineChrome(outline, primary) {
16678
+ const paint = accentColor(primary);
16679
+ const text = parseColor(outline.color);
16680
+ const paintRgb = parseColor(paint);
16681
+ const primaryLabel = parseColor(primary.color);
16682
+ const matchesOnPrimary = Boolean(text && primaryLabel) && colorKey(outline.color) === colorKey(primary.color);
16683
+ const textTooLight = text ? relativeLuminance(text) > 0.7 : false;
16684
+ const paintIsDarker = paintRgb ? relativeLuminance(paintRgb) < 0.65 : hasOpaqueBackground(primary.background);
16685
+ if (!(matchesOnPrimary || textTooLight) || !paintIsDarker) return outline;
16686
+ const hasBorder = parseFloat(outline.borderWidth) > 0;
16687
+ return {
16688
+ ...outline,
16689
+ color: paint,
16690
+ borderColor: hasBorder ? paint : outline.borderColor
16691
+ };
16692
+ }
16693
+ function generateButtonVariants(samples) {
16694
+ if (samples.length === 0) return { status: "unresolved" };
16695
+ const groups = /* @__PURE__ */ new Map();
16696
+ for (const sample of samples) {
16697
+ const key = fingerprint(sample);
16698
+ const list = groups.get(key) ?? [];
16699
+ list.push(sample);
16700
+ groups.set(key, list);
16701
+ }
16702
+ if (groups.size > 3) return { status: "unresolved" };
16703
+ const bySlot = /* @__PURE__ */ new Map();
16704
+ for (const group of groups.values()) {
16705
+ const sample = group[0];
16706
+ const slot = sampleSlot(sample);
16707
+ if (bySlot.has(slot)) return { status: "unresolved" };
16708
+ bySlot.set(slot, sample);
16709
+ }
16710
+ const primarySample = bySlot.get("primary");
16711
+ const seed = chromeFrom(primarySample ?? samples[0]);
16712
+ const primary = primarySample ? seed : filledPrimary(seed);
16713
+ const secondary = normalizeOutlineChrome(
16714
+ bySlot.has("secondary") ? chromeFrom(bySlot.get("secondary")) : generatedSecondary(primary),
16715
+ primary
16716
+ );
16717
+ const ghost = normalizeOutlineChrome(
16718
+ bySlot.has("ghost") ? chromeFrom(bySlot.get("ghost")) : generatedGhost(primary),
16719
+ primary
16720
+ );
16721
+ return {
16722
+ status: "ready",
16723
+ tokens: { primary, secondary, ghost }
16724
+ };
16725
+ }
16726
+ function geometryFromSample(sample) {
16727
+ return {
16728
+ paddingTop: px(sample.paddingTop),
16729
+ paddingRight: px(sample.paddingRight),
16730
+ paddingBottom: px(sample.paddingBottom),
16731
+ paddingLeft: px(sample.paddingLeft),
16732
+ fontSize: px(sample.fontSize),
16733
+ borderRadius: px(sample.borderRadius),
16734
+ gap: px(sample.gap),
16735
+ height: Math.round(sample.height)
16736
+ };
16737
+ }
16738
+ function shiftSample(base, steps) {
16739
+ return {
16740
+ height: Math.max(24, base.height + steps * 8),
16741
+ paddingTop: Math.max(0, base.paddingTop + steps * 4),
16742
+ paddingBottom: Math.max(0, base.paddingBottom + steps * 4),
16743
+ paddingLeft: Math.max(0, base.paddingLeft + steps * 8),
16744
+ paddingRight: Math.max(0, base.paddingRight + steps * 8),
16745
+ fontSize: base.fontSize,
16746
+ borderRadius: base.borderRadius,
16747
+ gap: base.gap
16748
+ };
16749
+ }
16750
+ function clusterSizes(samples) {
16751
+ const sorted = [...samples].sort((a, b) => a.height - b.height);
16752
+ const clusters = [];
16753
+ for (const sample of sorted) {
16754
+ const current = clusters[clusters.length - 1];
16755
+ const head = current?.[0];
16756
+ if (!current || !head || Math.abs(sample.height - head.height) > 2) clusters.push([sample]);
16757
+ else current.push(sample);
16758
+ }
16759
+ return clusters.map((group) => {
16760
+ const pick = (key) => group.reduce((sum, item) => sum + item[key], 0) / group.length;
16761
+ return {
16762
+ height: pick("height"),
16763
+ paddingTop: pick("paddingTop"),
16764
+ paddingRight: pick("paddingRight"),
16765
+ paddingBottom: pick("paddingBottom"),
16766
+ paddingLeft: pick("paddingLeft"),
16767
+ fontSize: pick("fontSize"),
16768
+ borderRadius: pick("borderRadius"),
16769
+ gap: pick("gap")
16770
+ };
16771
+ });
16772
+ }
16773
+ function tokensFromSamples(bySlot, radius, gap) {
16774
+ const fill = (id) => {
16775
+ const sample = bySlot[id] ?? REFERENCE_SIZES[id];
16776
+ return geometryFromSample({ ...sample, borderRadius: radius, gap });
16777
+ };
16778
+ return { s: fill("s"), m: fill("m"), l: fill("l") };
16779
+ }
16780
+ function interpolateSizes(known) {
16781
+ const order = BUTTON_SIZE_IDS;
16782
+ const indexOf = (id) => order.indexOf(id);
16783
+ const sorted = [...known].sort((a, b) => indexOf(a.id) - indexOf(b.id));
16784
+ const left = sorted[0];
16785
+ const right = sorted[1];
16786
+ const span = indexOf(right.id) - indexOf(left.id);
16787
+ const step = (key) => (right.sample[key] - left.sample[key]) / span;
16788
+ const at = (index) => {
16789
+ const steps = index - indexOf(left.id);
16790
+ return {
16791
+ height: left.sample.height + step("height") * steps,
16792
+ paddingTop: Math.max(0, left.sample.paddingTop + step("paddingTop") * steps),
16793
+ paddingBottom: Math.max(0, left.sample.paddingBottom + step("paddingBottom") * steps),
16794
+ paddingLeft: Math.max(0, left.sample.paddingLeft + step("paddingLeft") * steps),
16795
+ paddingRight: Math.max(0, left.sample.paddingRight + step("paddingRight") * steps),
16796
+ fontSize: Math.max(10, left.sample.fontSize + step("fontSize") * steps),
16797
+ borderRadius: left.sample.borderRadius,
16798
+ gap: left.sample.gap
16799
+ };
16800
+ };
16801
+ const bySlot = {
16802
+ [left.id]: left.sample,
16803
+ [right.id]: { ...right.sample, borderRadius: left.sample.borderRadius, gap: left.sample.gap }
16804
+ };
16805
+ for (const id of order) {
16806
+ if (!bySlot[id]) bySlot[id] = at(indexOf(id));
16807
+ }
16808
+ return tokensFromSamples(bySlot, left.sample.borderRadius, left.sample.gap);
16809
+ }
16810
+ function generateButtonSizes(samples) {
16811
+ const clusters = clusterSizes(samples.filter((sample) => sample.height > 0));
16812
+ if (clusters.length === 0) {
16813
+ return { status: "ready", tokens: tokensFromSamples(REFERENCE_SIZES, 8, 8) };
16814
+ }
16815
+ if (clusters.length > 3) return { status: "unresolved" };
16816
+ if (clusters.length === 3) {
16817
+ const sorted = [...clusters].sort((a, b) => a.height - b.height);
16818
+ const radius = sorted[1].borderRadius;
16819
+ const gap = sorted[1].gap;
16820
+ return {
16821
+ status: "ready",
16822
+ tokens: {
16823
+ s: geometryFromSample({ ...sorted[0], borderRadius: radius, gap }),
16824
+ m: geometryFromSample({ ...sorted[1], borderRadius: radius, gap }),
16825
+ l: geometryFromSample({ ...sorted[2], borderRadius: radius, gap })
16826
+ }
16827
+ };
16828
+ }
16829
+ if (clusters.length === 1) {
16830
+ const known = clusters[0];
16831
+ const slot = classifyHeight(known.height);
16832
+ const offset = BUTTON_SIZE_IDS.indexOf(slot);
16833
+ const bySlot = Object.fromEntries(
16834
+ BUTTON_SIZE_IDS.map((id, index) => [id, shiftSample(known, index - offset)])
16835
+ );
16836
+ bySlot[slot] = known;
16837
+ return { status: "ready", tokens: tokensFromSamples(bySlot, known.borderRadius, known.gap) };
16838
+ }
16839
+ const mapped = clusters.map((sample) => ({ id: classifyHeight(sample.height), sample }));
16840
+ if (mapped[0].id === mapped[1].id) {
16841
+ const larger = clusters.slice().sort((a, b) => b.height - a.height)[0];
16842
+ return generateButtonSizes([larger]);
16843
+ }
16844
+ return { status: "ready", tokens: interpolateSizes(mapped) };
16845
+ }
16846
+ function parseJson(raw) {
16847
+ if (!raw) return null;
16848
+ try {
16849
+ return JSON.parse(raw);
16850
+ } catch {
16851
+ return null;
16852
+ }
16853
+ }
16854
+ function isChrome(value) {
16855
+ if (!value || typeof value !== "object") return false;
16856
+ const chrome = value;
16857
+ return typeof chrome.background === "string" && typeof chrome.color === "string";
16858
+ }
16859
+ function isGeometry(value) {
16860
+ if (!value || typeof value !== "object") return false;
16861
+ const geometry = value;
16862
+ return typeof geometry.paddingTop === "string" && typeof geometry.height === "number";
16863
+ }
16864
+ function readVariantTokens(content) {
16865
+ const parsed = parseJson(content[BUTTON_VARIANTS_KEY]);
16866
+ if (!parsed) return null;
16867
+ if (!BUTTON_VARIANT_IDS.every((id) => isChrome(parsed[id]))) return null;
16868
+ return parsed;
16869
+ }
16870
+ function readSizeTokens(content) {
16871
+ const parsed = parseJson(content[BUTTON_SIZES_KEY]);
16872
+ if (!parsed) return null;
16873
+ if (!BUTTON_SIZE_IDS.every((id) => isGeometry(parsed[id]))) return null;
16874
+ return parsed;
16875
+ }
16876
+ function readAssignments(content) {
16877
+ const parsed = parseJson(content[BUTTON_ASSIGNMENTS_KEY]);
16878
+ if (!parsed || typeof parsed !== "object") return {};
16879
+ const next = {};
16880
+ for (const [id, value] of Object.entries(parsed)) {
16881
+ if (!value || !isButtonVariantId(value.variant) || !isButtonSizeId(value.size)) continue;
16882
+ next[id] = { variant: value.variant, size: value.size };
16883
+ }
16884
+ return next;
16885
+ }
16886
+ function cssLength(style, prop) {
16887
+ return style.getPropertyValue(prop) || "0px";
16888
+ }
16889
+ function readVariantSample(el) {
16890
+ const style = getComputedStyle(el);
16891
+ return {
16892
+ background: style.backgroundColor,
16893
+ color: style.color,
16894
+ borderColor: style.borderTopColor,
16895
+ borderWidth: style.borderTopWidth,
16896
+ className: el.className || ""
16897
+ };
16898
+ }
16899
+ function readSizeSample(el) {
16900
+ const style = getComputedStyle(el);
16901
+ const rootFont = parsePx(getComputedStyle(el.ownerDocument.documentElement).fontSize) || 16;
16902
+ const rect = el.getBoundingClientRect();
16903
+ return {
16904
+ height: rect.height,
16905
+ paddingTop: parsePx(cssLength(style, "padding-top"), rootFont),
16906
+ paddingRight: parsePx(cssLength(style, "padding-right"), rootFont),
16907
+ paddingBottom: parsePx(cssLength(style, "padding-bottom"), rootFont),
16908
+ paddingLeft: parsePx(cssLength(style, "padding-left"), rootFont),
16909
+ fontSize: parsePx(style.fontSize, rootFont),
16910
+ borderRadius: parsePx(style.borderTopLeftRadius, rootFont),
16911
+ gap: parsePx(style.columnGap || style.gap, rootFont)
16912
+ };
16913
+ }
16914
+ function readTypography(el) {
16915
+ const style = getComputedStyle(el);
16916
+ return {
16917
+ fontFamily: style.fontFamily,
16918
+ fontWeight: style.fontWeight,
16919
+ letterSpacing: style.letterSpacing,
16920
+ textTransform: style.textTransform,
16921
+ textDecoration: style.textDecorationLine || style.textDecoration || "none"
16922
+ };
16923
+ }
16924
+ var buttonOriginByEl = /* @__PURE__ */ new WeakMap();
16925
+ var ORIGIN_ATTR = "data-ohw-btn-origin";
16926
+ function parseOriginAttr(raw) {
16927
+ if (!raw) return null;
16928
+ try {
16929
+ const value = JSON.parse(raw);
16930
+ if (!value?.variant || !value.size || !value.typography) return null;
16931
+ return value;
16932
+ } catch {
16933
+ return null;
16934
+ }
16935
+ }
16936
+ function measureLiveOrigin(el) {
16937
+ return {
16938
+ variant: readVariantSample(el),
16939
+ size: readSizeSample(el),
16940
+ typography: readTypography(el)
16941
+ };
16942
+ }
16943
+ function captureButtonOrigin(el) {
16944
+ const cached = buttonOriginByEl.get(el);
16945
+ if (cached) return cached;
16946
+ const fromAttr = parseOriginAttr(el.getAttribute(ORIGIN_ATTR));
16947
+ if (fromAttr) {
16948
+ buttonOriginByEl.set(el, fromAttr);
16949
+ return fromAttr;
16950
+ }
16951
+ const origin = measureLiveOrigin(el);
16952
+ buttonOriginByEl.set(el, origin);
16953
+ try {
16954
+ el.setAttribute(ORIGIN_ATTR, JSON.stringify(origin));
16955
+ } catch {
16956
+ }
16957
+ return origin;
16958
+ }
16959
+ function listStyleButtons(doc) {
16960
+ return Array.from(doc.querySelectorAll(CTA_BUTTON_SELECTOR)).filter((el) => {
16961
+ if (el.closest("[data-ohw-bridge], [data-ohw-bridge-root]")) return false;
16962
+ const rect = el.getBoundingClientRect();
16963
+ return rect.width > 0 && rect.height > 0;
16964
+ });
16965
+ }
16966
+ function generateTokensFromButtons(buttons) {
16967
+ const variants = generateButtonVariants(buttons.map(readVariantSample));
16968
+ const sizes = generateButtonSizes(buttons.map(readSizeSample));
16969
+ if (variants.status !== "ready" || sizes.status !== "ready") return { status: "unresolved" };
16970
+ return { status: "ready", tokens: { variants: variants.tokens, sizes: sizes.tokens } };
16971
+ }
16972
+ function tokensFromButton(el) {
16973
+ const origin = captureButtonOrigin(el);
16974
+ const sample = origin.variant;
16975
+ const variants = generateButtonVariants([sample]);
16976
+ const sizes = generateButtonSizes([origin.size]);
16977
+ const primary = chromeFrom(sample);
16978
+ return {
16979
+ variants: variants.status === "ready" ? variants.tokens : { primary, secondary: generatedSecondary(primary), ghost: generatedGhost(primary) },
16980
+ sizes: sizes.status === "ready" ? sizes.tokens : tokensFromSamples(REFERENCE_SIZES, 8, 8)
16981
+ };
16982
+ }
16983
+ function readStoredStyleTokens(content) {
16984
+ const variants = readVariantTokens(content);
16985
+ const sizes = readSizeTokens(content);
16986
+ if (!variants || !sizes) return null;
16987
+ return { variants, sizes };
16988
+ }
16989
+ function styleTokenNodes(tokens) {
16990
+ return [
16991
+ { key: BUTTON_VARIANTS_KEY, text: JSON.stringify(tokens.variants) },
16992
+ { key: BUTTON_SIZES_KEY, text: JSON.stringify(tokens.sizes) }
16993
+ ];
16994
+ }
16995
+ function ensureButtonStyleTokens(doc, content) {
16996
+ const stored = readStoredStyleTokens(content);
16997
+ if (stored) return { tokens: stored, nodes: [] };
16998
+ const generated = generateTokensFromButtons(listStyleButtons(doc));
16999
+ if (generated.status !== "ready") return { tokens: null, nodes: [] };
17000
+ return { tokens: generated.tokens, nodes: styleTokenNodes(generated.tokens) };
17001
+ }
17002
+ function detectVariant(el, tokens) {
17003
+ const declared = declaredVariant(el.className || "");
17004
+ if (declared) return declared;
17005
+ const stamped = el.getAttribute("data-ohw-btn-variant");
17006
+ if (stamped && isButtonVariantId(stamped)) return stamped;
17007
+ const sample = readVariantSample(el);
17008
+ if (!tokens) return sampleSlot(sample);
17009
+ const key = fingerprint(sample);
17010
+ for (const id of BUTTON_VARIANT_IDS) {
17011
+ const chrome = tokens[id];
17012
+ const tokenKey = fingerprint({
17013
+ background: chrome.background,
17014
+ color: chrome.color,
17015
+ borderColor: chrome.borderColor,
17016
+ borderWidth: chrome.borderWidth,
17017
+ className: `btn btn--${id}`
17018
+ });
17019
+ if (tokenKey === key) return id;
17020
+ }
17021
+ return sampleSlot(sample);
17022
+ }
17023
+ function detectSize(el, tokens) {
17024
+ const stamped = el.getAttribute("data-ohw-btn-size");
17025
+ if (stamped && isButtonSizeId(stamped)) return stamped;
17026
+ const height = el.getBoundingClientRect().height;
17027
+ if (!tokens) return classifyHeight(height);
17028
+ let best = "m";
17029
+ let bestDelta = Number.POSITIVE_INFINITY;
17030
+ for (const id of BUTTON_SIZE_IDS) {
17031
+ const delta = Math.abs(tokens[id].height - height);
17032
+ if (delta < bestDelta) {
17033
+ best = id;
17034
+ bestDelta = delta;
17035
+ }
17036
+ }
17037
+ return best;
17038
+ }
17039
+ function assignmentFor(el, content, tokens) {
17040
+ const id = buttonId(el);
17041
+ const stored = id ? readAssignments(content)[id] : void 0;
17042
+ if (stored) return stored;
17043
+ return {
17044
+ variant: detectVariant(el, tokens?.variants ?? null),
17045
+ size: detectSize(el, tokens?.sizes ?? null)
17046
+ };
17047
+ }
17048
+ function labelElement(el) {
17049
+ return el.querySelector('[data-ohw-editable="text"], [data-ohw-editable="plain"]') ?? (el.matches('[data-ohw-editable="text"], [data-ohw-editable="plain"]') ? el : null);
17050
+ }
17051
+ var paintLockByEl = /* @__PURE__ */ new WeakMap();
17052
+ var paintObserverByEl = /* @__PURE__ */ new WeakMap();
17053
+ var paintingButton = false;
17054
+ function paintButtonChrome(el, assignment, tokens) {
17055
+ const origin = captureButtonOrigin(el);
17056
+ const chrome = tokens.variants[assignment.variant];
17057
+ const originSlot = classifyHeight(origin.size.height);
17058
+ const geometry = assignment.size === originSlot ? geometryFromSample({ ...origin.size, borderRadius: origin.size.borderRadius, gap: origin.size.gap }) : tokens.sizes[assignment.size];
17059
+ const { typography } = origin;
17060
+ el.setAttribute("data-ohw-btn-variant", assignment.variant);
17061
+ el.setAttribute("data-ohw-btn-size", assignment.size);
17062
+ el.style.transition = "none";
17063
+ el.style.boxSizing = "border-box";
17064
+ el.style.display = "inline-flex";
17065
+ el.style.alignItems = "center";
17066
+ el.style.justifyContent = "center";
17067
+ el.style.background = chrome.background;
17068
+ el.style.color = chrome.color;
17069
+ el.style.borderStyle = parseFloat(chrome.borderWidth) > 0 ? "solid" : "none";
17070
+ el.style.borderColor = chrome.borderColor;
17071
+ el.style.borderWidth = chrome.borderWidth;
17072
+ el.style.padding = `${geometry.paddingTop} ${geometry.paddingRight} ${geometry.paddingBottom} ${geometry.paddingLeft}`;
17073
+ el.style.fontSize = geometry.fontSize;
17074
+ el.style.borderRadius = px(origin.size.borderRadius);
17075
+ el.style.gap = geometry.gap;
17076
+ el.style.height = px(geometry.height);
17077
+ el.style.fontFamily = typography.fontFamily;
17078
+ el.style.fontWeight = typography.fontWeight;
17079
+ el.style.letterSpacing = typography.letterSpacing;
17080
+ el.style.textTransform = typography.textTransform;
17081
+ if (typography.textDecoration && typography.textDecoration !== "none") {
17082
+ el.style.textDecoration = typography.textDecoration;
17083
+ } else {
17084
+ el.style.textDecoration = "none";
17085
+ }
17086
+ const label = labelElement(el);
17087
+ if (label && label !== el) label.style.color = chrome.color;
17088
+ }
17089
+ function guardTemplateHover(el) {
17090
+ if (paintObserverByEl.has(el)) return;
17091
+ const observer = new MutationObserver(() => {
17092
+ if (paintingButton) return;
17093
+ const lock = paintLockByEl.get(el);
17094
+ if (!lock) return;
17095
+ paintingButton = true;
17096
+ paintButtonChrome(el, lock.assignment, lock.tokens);
17097
+ paintingButton = false;
17098
+ });
17099
+ observer.observe(el, { attributes: true, attributeFilter: ["style", "class"] });
17100
+ paintObserverByEl.set(el, observer);
17101
+ }
17102
+ function paintedTokensOf(el) {
17103
+ return paintLockByEl.get(el)?.tokens ?? null;
17104
+ }
17105
+ function applyButtonStyle(el, assignment, tokens) {
17106
+ paintLockByEl.set(el, { assignment, tokens });
17107
+ paintingButton = true;
17108
+ paintButtonChrome(el, assignment, tokens);
17109
+ paintingButton = false;
17110
+ guardTemplateHover(el);
17111
+ }
17112
+ function applyButtonStylesFromContent(doc, content) {
17113
+ const assignments = readAssignments(content);
17114
+ for (const [id, assignment] of Object.entries(assignments)) {
17115
+ const el = doc.querySelector(`[data-ohw-href-key="${CSS.escape(id)}"]`);
17116
+ if (!el) continue;
17117
+ applyButtonStyle(el, assignment, tokensFromButton(el));
17118
+ }
17119
+ }
17120
+ function withAssignment(content, id, assignment) {
17121
+ return { ...readAssignments(content), [id]: assignment };
17122
+ }
17123
+
17124
+ // src/ui/button-style-panel.tsx
17125
+ var import_jsx_runtime31 = require("react/jsx-runtime");
17126
+ var SIZE_LABEL = { s: "S", m: "M", l: "L" };
17127
+ function ButtonStylePanel({
17128
+ variants,
17129
+ sizes,
17130
+ variant,
17131
+ size,
17132
+ onVariant,
17133
+ onSize,
17134
+ previewLabel = "Book Now",
17135
+ slotBackground = "var(--brand-background, var(--ohw-brand-light, var(--color-light, #ffffff)))"
17136
+ }) {
17137
+ const preview = scaleGeometry(sizes.m, 0.75);
17138
+ const label = previewLabel.trim() || "Book Now";
17139
+ return /* @__PURE__ */ (0, import_jsx_runtime31.jsxs)("div", { className: "flex w-full flex-col gap-5", children: [
17140
+ /* @__PURE__ */ (0, import_jsx_runtime31.jsxs)("section", { className: "flex w-full flex-col gap-2.5", children: [
17141
+ /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("h3", { className: "text-xs font-semibold uppercase leading-4 tracking-[0.6px] text-stone-500 font-sans", children: "Variants" }),
17142
+ /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("div", { className: "flex w-full flex-col gap-2.5", children: BUTTON_VARIANT_IDS.map((id) => {
17143
+ const chrome = variants[id];
17144
+ const selected = id === variant;
17145
+ const hasBorder = parseFloat(chrome.borderWidth) > 0;
17146
+ const cardSlot = previewSlotForChrome(chrome, slotBackground);
17147
+ return /* @__PURE__ */ (0, import_jsx_runtime31.jsxs)(
17148
+ "button",
17149
+ {
17150
+ type: "button",
17151
+ "aria-pressed": selected,
17152
+ "aria-label": id,
17153
+ onMouseDown: (e) => {
17154
+ e.preventDefault();
17155
+ e.stopPropagation();
17156
+ },
17157
+ onClick: () => onVariant(id),
17158
+ className: cn(
17159
+ "relative w-full overflow-hidden rounded-xl border-solid text-left",
17160
+ selected ? "border-2 border-primary" : "border border-border"
17161
+ ),
17162
+ children: [
17163
+ /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(
17164
+ "div",
17165
+ {
17166
+ className: "flex w-full items-center justify-center py-4",
17167
+ style: { background: cardSlot },
17168
+ children: /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(
17169
+ "span",
17170
+ {
17171
+ className: "inline-flex items-center justify-center whitespace-nowrap font-semibold uppercase leading-none",
17172
+ style: {
17173
+ background: chrome.background,
17174
+ color: chrome.color,
17175
+ borderColor: chrome.borderColor,
17176
+ borderWidth: hasBorder ? chrome.borderWidth : "0px",
17177
+ borderStyle: hasBorder ? "solid" : "none",
17178
+ padding: `${preview.paddingTop} ${preview.paddingRight} ${preview.paddingBottom} ${preview.paddingLeft}`,
17179
+ fontSize: preview.fontSize,
17180
+ borderRadius: preview.borderRadius,
17181
+ height: preview.height,
17182
+ boxSizing: "border-box",
17183
+ letterSpacing: "0.12em"
17184
+ },
17185
+ children: label
17186
+ }
17187
+ )
17188
+ }
17189
+ ),
17190
+ selected ? /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(
17191
+ "span",
17192
+ {
17193
+ className: "absolute right-0.5 top-0.5 flex size-5 items-center justify-center rounded-full bg-primary text-primary-foreground",
17194
+ "aria-hidden": true,
17195
+ children: /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(import_lucide_react17.Check, { className: "size-3", strokeWidth: 3 })
17196
+ }
17197
+ ) : null
17198
+ ]
17199
+ },
17200
+ id
17201
+ );
17202
+ }) })
17203
+ ] }),
17204
+ /* @__PURE__ */ (0, import_jsx_runtime31.jsxs)("section", { className: "flex w-full flex-col gap-2.5", children: [
17205
+ /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("h3", { className: "text-xs font-semibold uppercase leading-4 tracking-[0.6px] text-stone-500 font-sans", children: "Size" }),
17206
+ /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(
17207
+ "div",
17208
+ {
17209
+ className: "flex w-full items-start rounded-lg bg-stone-100 p-1",
17210
+ role: "radiogroup",
17211
+ "aria-label": "Size",
17212
+ children: BUTTON_SIZE_IDS.map((id) => {
17213
+ const selected = id === size;
17214
+ return /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(
17215
+ "button",
17216
+ {
17217
+ type: "button",
17218
+ role: "radio",
17219
+ "aria-checked": selected,
17220
+ onMouseDown: (e) => {
17221
+ e.preventDefault();
17222
+ e.stopPropagation();
17223
+ },
17224
+ onClick: () => onSize(id),
17225
+ className: cn(
17226
+ "flex min-w-14 flex-1 items-center justify-center rounded-md px-3 py-1.5 text-sm font-medium leading-5",
17227
+ selected ? "bg-white text-stone-950 shadow-sm" : "bg-transparent text-stone-500"
17228
+ ),
17229
+ children: SIZE_LABEL[id]
17230
+ },
17231
+ id
17232
+ );
17233
+ })
17234
+ }
17235
+ )
17236
+ ] })
17237
+ ] });
17238
+ }
17239
+
17240
+ // src/lib/button-collections.ts
17241
+ var BUTTON_COLLECTIONS_KEY = "__ohw_button_collections";
17242
+ var BUTTON_SNAPSHOTS_KEY = "__ohw_button_snapshots";
17243
+ var BUTTON_HIDDEN_KEY = "__ohw_button_hidden";
17244
+ var COLLECTION_ATTR = "data-ohw-button-collection";
17245
+ function parseJson2(raw) {
17246
+ if (!raw) return null;
17247
+ try {
17248
+ return JSON.parse(raw);
17249
+ } catch {
17250
+ return null;
17251
+ }
17252
+ }
17253
+ function readCollections(content) {
17254
+ const parsed = parseJson2(content[BUTTON_COLLECTIONS_KEY]);
17255
+ if (!parsed || typeof parsed !== "object") return {};
17256
+ const next = {};
17257
+ for (const [id, value] of Object.entries(parsed)) {
17258
+ if (!value || !Array.isArray(value.members)) continue;
17259
+ next[id] = {
17260
+ id,
17261
+ members: value.members.filter((member) => typeof member === "string" && member),
17262
+ gap: typeof value.gap === "string" ? value.gap : "12px"
17263
+ };
17264
+ }
17265
+ return next;
17266
+ }
17267
+ function readSnapshots(content) {
17268
+ const parsed = parseJson2(content[BUTTON_SNAPSHOTS_KEY]);
17269
+ if (!parsed || typeof parsed !== "object") return {};
17270
+ const next = {};
17271
+ for (const [id, value] of Object.entries(parsed)) {
17272
+ if (!value || typeof value.html !== "string") continue;
17273
+ next[id] = value;
17274
+ }
17275
+ return next;
17276
+ }
17277
+ function readHiddenButtons(content) {
17278
+ const parsed = parseJson2(content[BUTTON_HIDDEN_KEY]);
17279
+ if (!Array.isArray(parsed)) return [];
17280
+ return parsed.filter((id) => typeof id === "string" && id);
17281
+ }
17282
+ function buttonCollectionOf(el) {
17283
+ return el?.closest(`[${COLLECTION_ATTR}]`) ?? null;
17284
+ }
17285
+ function isNavbarCta(el) {
17286
+ return el.getAttribute("data-ohw-role") === "navbar-button" || Boolean(el.closest('[data-ohw-role="navbar-button"]'));
17287
+ }
17288
+ function navbarScope(el) {
17289
+ return el.closest("nav, [data-ohw-nav-container], [data-ohw-nav-drawer]") ?? el.ownerDocument;
17290
+ }
17291
+ function isLastNavbarButton(el) {
17292
+ if (!isNavbarCta(el)) return false;
17293
+ const buttons = Array.from(navbarScope(el).querySelectorAll(CTA_BUTTON_SELECTOR)).filter(
17294
+ (button) => isNavbarCta(button) && buttonId(button)
17295
+ );
17296
+ return buttons.length <= 1;
17297
+ }
17298
+ function canDeleteButton(el) {
17299
+ return !isLastNavbarButton(el);
17300
+ }
17301
+ function canDuplicateButton(el) {
17302
+ return isNavbarCta(el);
17303
+ }
17304
+ function labelElement2(el) {
17305
+ return el.querySelector('[data-ohw-editable="text"], [data-ohw-editable="plain"]') ?? el.querySelector("[data-ohw-key]");
17306
+ }
17307
+ function buttonLabel(el) {
17308
+ return (labelElement2(el)?.textContent ?? el.textContent ?? "").trim();
17309
+ }
17310
+ function buttonHref(el) {
17311
+ return el.getAttribute("href") ?? "";
17312
+ }
17313
+ function hrefKeyBase(hrefKey) {
17314
+ return hrefKey.replace(/-href$/, "").replace(/-copy-\d+$/, "");
17315
+ }
17316
+ function labelKeyForHrefKey(hrefKey) {
17317
+ return `${hrefKey.replace(/-href$/, "")}-label`;
17318
+ }
17319
+ function cleanClone(el) {
17320
+ const copy = el.cloneNode(true);
17321
+ copy.querySelectorAll("[data-ohw-selected], [data-ohw-hovered], [data-ohw-editing]").forEach((node) => {
17322
+ node.removeAttribute("data-ohw-selected");
17323
+ node.removeAttribute("data-ohw-hovered");
17324
+ node.removeAttribute("data-ohw-editing");
17325
+ node.removeAttribute("contenteditable");
17326
+ });
17327
+ copy.removeAttribute("data-ohw-selected");
17328
+ copy.removeAttribute("data-ohw-hovered");
17329
+ copy.removeAttribute("data-ohw-editing");
17330
+ copy.removeAttribute("contenteditable");
17331
+ copy.removeAttribute("data-ohw-drag-disabled");
17332
+ return copy;
17333
+ }
17334
+ function nextCopyId(sourceId, content, doc) {
17335
+ const used = /* @__PURE__ */ new Set([
17336
+ ...Object.keys(readSnapshots(content)),
17337
+ ...Object.keys(readAssignments(content)),
17338
+ ...Array.from(doc.querySelectorAll("[data-ohw-href-key]")).map(
17339
+ (el) => el.getAttribute("data-ohw-href-key") ?? ""
17340
+ )
17341
+ ]);
17342
+ const base = hrefKeyBase(sourceId);
17343
+ let n = 2;
17344
+ while (used.has(`${base}-copy-${n}-href`)) n += 1;
17345
+ return `${base}-copy-${n}-href`;
17346
+ }
17347
+ function nextCollectionId(content, doc) {
17348
+ const used = /* @__PURE__ */ new Set([
17349
+ ...Object.keys(readCollections(content)),
17350
+ ...Array.from(doc.querySelectorAll(`[${COLLECTION_ATTR}]`)).map(
17351
+ (el) => el.getAttribute(COLLECTION_ATTR) ?? ""
17352
+ )
17353
+ ]);
17354
+ let n = 1;
17355
+ while (used.has(`bc-${n}`)) n += 1;
17356
+ return `bc-${n}`;
17357
+ }
17358
+ var COLLECTION_GAP_MAX_PX = 12;
17359
+ var COLLECTION_GAP_DEFAULT = "12px";
17360
+ function usableGap(value) {
17361
+ if (!value || value === "normal") return null;
17362
+ const n = parseFloat(value);
17363
+ if (!Number.isFinite(n) || n <= 0) return null;
17364
+ if (n > COLLECTION_GAP_MAX_PX) return COLLECTION_GAP_DEFAULT;
17365
+ return `${Math.round(n * 100) / 100}px`;
17366
+ }
17367
+ function collectionGap(el, tokens) {
17368
+ const fromCollection = usableGap(buttonCollectionOf(el)?.style.gap);
17369
+ if (fromCollection) return fromCollection;
17370
+ const parent = el.parentElement;
17371
+ if (parent) {
17372
+ const style = getComputedStyle(parent);
17373
+ const fromParent = usableGap(style.columnGap) ?? usableGap(style.gap);
17374
+ if (fromParent) return fromParent;
17375
+ }
17376
+ const fromTokens = usableGap(tokens?.sizes.m.gap);
17377
+ if (fromTokens) return fromTokens;
17378
+ return COLLECTION_GAP_DEFAULT;
17379
+ }
17380
+ function styleCollection(collection, gap) {
17381
+ collection.style.display = "inline-flex";
17382
+ collection.style.alignItems = "center";
17383
+ collection.style.flexWrap = "wrap";
17384
+ collection.style.gap = gap;
17385
+ collection.style.maxWidth = "100%";
17386
+ }
17387
+ function createCollection(doc, id, gap) {
17388
+ const collection = doc.createElement("div");
17389
+ collection.setAttribute(COLLECTION_ATTR, id);
17390
+ styleCollection(collection, gap);
17391
+ return collection;
17392
+ }
17393
+ function setButtonDragAttr(el) {
17394
+ if (isNavbarCta(el) && buttonCollectionOf(el)) el.removeAttribute("data-ohw-drag-disabled");
17395
+ else el.setAttribute("data-ohw-drag-disabled", "true");
17396
+ }
17397
+ function rekeyCopy(copy, id) {
17398
+ copy.setAttribute("data-ohw-href-key", id);
17399
+ const label = labelElement2(copy);
17400
+ const labelKey = labelKeyForHrefKey(id);
17401
+ if (label) label.setAttribute("data-ohw-key", labelKey);
17402
+ setButtonDragAttr(copy);
17403
+ return { labelKey };
17404
+ }
17405
+ function stampVariantClass(el, variant) {
17406
+ const raw = typeof el.className === "string" ? el.className : el.getAttribute("class") || "";
17407
+ const without = raw.replace(/\bbtn--(?:primary|secondary|ghost)\b/g, "").replace(/\s+/g, " ").trim();
17408
+ el.className = without ? `${without} btn--${variant}` : `btn btn--${variant}`;
17409
+ }
17410
+ function snapshotOf(el) {
17411
+ const id = buttonId(el) ?? "";
17412
+ const label = labelElement2(el);
17413
+ return {
17414
+ id,
17415
+ href: buttonHref(el),
17416
+ label: buttonLabel(el),
17417
+ labelKey: label?.getAttribute("data-ohw-key") ?? labelKeyForHrefKey(id),
17418
+ html: cleanClone(el).outerHTML
17419
+ };
17420
+ }
17421
+ function htmlToElement(doc, html) {
17422
+ const wrap = doc.createElement("div");
17423
+ wrap.innerHTML = html;
17424
+ const el = wrap.firstElementChild;
17425
+ return el instanceof HTMLElement ? el : null;
17426
+ }
17427
+ function findButton(doc, id) {
17428
+ return doc.querySelector(`[data-ohw-href-key="${CSS.escape(id)}"]`);
17429
+ }
17430
+ function duplicateButton(source, content) {
17431
+ if (!canDuplicateButton(source)) return null;
17432
+ const sourceId = buttonId(source);
17433
+ if (!sourceId) return null;
17434
+ const doc = source.ownerDocument;
17435
+ const tokens = tokensFromButton(source);
17436
+ const copyId = nextCopyId(sourceId, content, doc);
17437
+ const copy = cleanClone(source);
17438
+ const { labelKey } = rekeyCopy(copy, copyId);
17439
+ const href = buttonHref(source);
17440
+ const label = buttonLabel(source);
17441
+ if (href) copy.setAttribute("href", href);
17442
+ const sourceAssignment = readAssignments(content)[sourceId] ?? assignmentFor(source, content, tokens);
17443
+ const copyAssignment = { ...sourceAssignment };
17444
+ stampVariantClass(copy, copyAssignment.variant);
17445
+ applyButtonStyle(copy, copyAssignment, paintedTokensOf(source) ?? tokens);
17446
+ const copyLabelEl = labelElement2(copy);
17447
+ if (copyLabelEl) copyLabelEl.textContent = label;
17448
+ let collection = buttonCollectionOf(source);
17449
+ const collections = readCollections(content);
17450
+ let collectionId = collection?.getAttribute(COLLECTION_ATTR) ?? "";
17451
+ let members = collectionId && collections[collectionId] ? [...collections[collectionId].members] : [];
17452
+ const gap = collectionGap(source, tokens);
17453
+ if (!collection) {
17454
+ collectionId = nextCollectionId(content, doc);
17455
+ collection = createCollection(doc, collectionId, gap);
17456
+ source.parentElement?.insertBefore(collection, source);
17457
+ collection.append(source, copy);
17458
+ setButtonDragAttr(source);
17459
+ setButtonDragAttr(copy);
17460
+ members = [sourceId, copyId];
17461
+ } else {
17462
+ styleCollection(collection, gap);
17463
+ source.after(copy);
17464
+ setButtonDragAttr(copy);
17465
+ if (!members.includes(sourceId)) members = listCollectionMembers(collection);
17466
+ const at = members.indexOf(sourceId);
17467
+ members.splice(at >= 0 ? at + 1 : members.length, 0, copyId);
17468
+ }
17469
+ const sourceLabelKey = labelElement2(source)?.getAttribute("data-ohw-key");
17470
+ const snapshots = {
17471
+ ...readSnapshots(content),
17472
+ [sourceId]: snapshotOf(source),
17473
+ [copyId]: snapshotOf(copy)
17474
+ };
17475
+ const nextCollections = {
17476
+ ...collections,
17477
+ [collectionId]: { id: collectionId, members, gap }
17478
+ };
17479
+ const assignments = withAssignment(content, copyId, copyAssignment);
17480
+ const nextContent = {
17481
+ ...content,
17482
+ [copyId]: href,
17483
+ [labelKey]: label,
17484
+ [BUTTON_COLLECTIONS_KEY]: JSON.stringify(nextCollections),
17485
+ [BUTTON_SNAPSHOTS_KEY]: JSON.stringify(snapshots),
17486
+ [BUTTON_ASSIGNMENTS_KEY]: JSON.stringify(assignments)
17487
+ };
17488
+ if (sourceLabelKey && nextContent[sourceLabelKey] === void 0) {
17489
+ nextContent[sourceLabelKey] = label;
17490
+ }
17491
+ const nodeKeys = [
17492
+ copyId,
17493
+ labelKey,
17494
+ BUTTON_COLLECTIONS_KEY,
17495
+ BUTTON_SNAPSHOTS_KEY,
17496
+ BUTTON_ASSIGNMENTS_KEY
17497
+ ];
17498
+ if (sourceLabelKey) nodeKeys.push(sourceLabelKey);
17499
+ return {
17500
+ copy,
17501
+ content: nextContent,
17502
+ nodes: contentNodes(nextContent, nodeKeys)
17503
+ };
17504
+ }
17505
+ function listCollectionMembers(collection) {
17506
+ return Array.from(collection.querySelectorAll(CTA_BUTTON_SELECTOR)).map((el) => buttonId(el)).filter((id) => Boolean(id));
17507
+ }
17508
+ function contentNodes(content, keys) {
17509
+ return keys.filter((key) => content[key] !== void 0).map((key) => ({ key, text: content[key] }));
17510
+ }
17511
+ function deleteButton(source, content) {
17512
+ if (!canDeleteButton(source)) return null;
17513
+ const id = buttonId(source);
17514
+ if (!id) return null;
17515
+ const parent = source.parentElement;
17516
+ const nextSibling = source.nextSibling;
17517
+ const removed = source;
17518
+ const collection = buttonCollectionOf(source);
17519
+ const collectionId = collection?.getAttribute(COLLECTION_ATTR) ?? "";
17520
+ const previousCollections = readCollections(content);
17521
+ const previousHidden = readHiddenButtons(content);
17522
+ const snapshots = { ...readSnapshots(content) };
17523
+ const assignments = { ...readAssignments(content) };
17524
+ delete assignments[id];
17525
+ const remainingBeforeUnwrap = collection ? listCollectionMembers(collection).filter((member) => member !== id) : [];
17526
+ removed.remove();
17527
+ let nextCollections = { ...previousCollections };
17528
+ let unwrapped = null;
17529
+ if (collection && collectionId) {
17530
+ const members = listCollectionMembers(collection);
17531
+ if (members.length <= 1) {
17532
+ unwrapped = collection;
17533
+ unwrapCollection(collection);
17534
+ delete nextCollections[collectionId];
17535
+ } else {
17536
+ nextCollections[collectionId] = {
17537
+ id: collectionId,
17538
+ members,
17539
+ gap: collection.style.gap || previousCollections[collectionId]?.gap || "12px"
17540
+ };
17541
+ }
17542
+ }
17543
+ const hidden = previousHidden.includes(id) ? previousHidden : [...previousHidden, id];
17544
+ const nextContent = {
17545
+ ...content,
17546
+ [id]: "",
17547
+ [BUTTON_COLLECTIONS_KEY]: JSON.stringify(nextCollections),
17548
+ [BUTTON_HIDDEN_KEY]: JSON.stringify(hidden),
17549
+ [BUTTON_SNAPSHOTS_KEY]: JSON.stringify(snapshots),
17550
+ [BUTTON_ASSIGNMENTS_KEY]: JSON.stringify(assignments)
17551
+ };
17552
+ return {
17553
+ content: nextContent,
17554
+ nodes: contentNodes(nextContent, [id, BUTTON_COLLECTIONS_KEY, BUTTON_HIDDEN_KEY, BUTTON_SNAPSHOTS_KEY, BUTTON_ASSIGNMENTS_KEY]),
17555
+ undo: () => {
17556
+ if (unwrapped && remainingBeforeUnwrap.length === 1) {
17557
+ const remaining = findButton(removed.ownerDocument, remainingBeforeUnwrap[0]);
17558
+ const host = remaining?.parentElement;
17559
+ if (remaining && host) {
17560
+ const restoredCollection = createCollection(
17561
+ removed.ownerDocument,
17562
+ collectionId,
17563
+ previousCollections[collectionId]?.gap || "12px"
17564
+ );
17565
+ host.insertBefore(restoredCollection, remaining);
17566
+ restoredCollection.append(remaining, removed);
17567
+ return;
17568
+ }
17569
+ }
17570
+ if (nextSibling && nextSibling.parentNode === parent) parent?.insertBefore(removed, nextSibling);
17571
+ else parent?.appendChild(removed);
17572
+ }
17573
+ };
17574
+ }
17575
+ function unwrapCollection(collection) {
17576
+ const parent = collection.parentElement;
17577
+ if (!parent) return;
17578
+ while (collection.firstChild) parent.insertBefore(collection.firstChild, collection);
17579
+ collection.remove();
17580
+ }
17581
+ function reorderButtonInCollection(button, clientX) {
17582
+ const collection = buttonCollectionOf(button);
17583
+ const id = buttonId(button);
17584
+ if (!collection || !id) return null;
17585
+ const siblings = Array.from(collection.querySelectorAll(CTA_BUTTON_SELECTOR)).filter((el) => el !== button);
17586
+ let placed = false;
17587
+ for (const sibling of siblings) {
17588
+ const rect = sibling.getBoundingClientRect();
17589
+ if (clientX < rect.left + rect.width / 2) {
17590
+ sibling.before(button);
17591
+ placed = true;
17592
+ break;
17593
+ }
17594
+ }
17595
+ if (!placed) collection.append(button);
17596
+ return listCollectionMembers(collection);
17597
+ }
17598
+ function listVisibleCollectionButtons(collection) {
17599
+ return Array.from(collection.querySelectorAll(CTA_BUTTON_SELECTOR)).filter((el) => {
17600
+ if (!buttonId(el)) return false;
17601
+ return el.getClientRects().length > 0;
17602
+ });
17603
+ }
17604
+ function isVerticalButtonLayout(items, collection) {
17605
+ if (items.length >= 2) {
17606
+ const a = items[0].getBoundingClientRect();
17607
+ const b = items[1].getBoundingClientRect();
17608
+ return b.top - a.bottom > b.left - a.right;
17609
+ }
17610
+ const style = window.getComputedStyle(collection);
17611
+ return style.flexDirection === "column" || style.flexDirection === "column-reverse";
17612
+ }
17613
+ function buildButtonCollectionDropSlots(collection) {
17614
+ const items = listVisibleCollectionButtons(collection);
17615
+ const barThickness = 3;
17616
+ const slots = [];
17617
+ if (items.length === 0) {
17618
+ const rect = collection.getBoundingClientRect();
17619
+ slots.push({
17620
+ insertIndex: 0,
17621
+ left: rect.left + rect.width / 2 - barThickness / 2,
17622
+ top: rect.top,
17623
+ width: barThickness,
17624
+ height: Math.max(rect.height, 24),
17625
+ direction: "vertical"
17626
+ });
17627
+ return slots;
17628
+ }
17629
+ if (isVerticalButtonLayout(items, collection)) {
17630
+ const edgeGap2 = items.length >= 2 ? Math.max(0, items[1].getBoundingClientRect().top - items[0].getBoundingClientRect().bottom) : 16;
17631
+ for (let i = 0; i <= items.length; i++) {
17632
+ let top;
17633
+ let left;
17634
+ let width;
17635
+ if (i === 0) {
17636
+ const first = items[0].getBoundingClientRect();
17637
+ top = first.top - edgeGap2 / 2 - barThickness / 2;
17638
+ left = first.left;
17639
+ width = first.width;
17640
+ } else if (i === items.length) {
17641
+ const last = items[items.length - 1].getBoundingClientRect();
17642
+ const lastGap = items.length >= 2 ? Math.max(0, last.top - items[items.length - 2].getBoundingClientRect().bottom) : edgeGap2;
17643
+ top = last.bottom + lastGap / 2 - barThickness / 2;
17644
+ left = last.left;
17645
+ width = last.width;
17646
+ } else {
17647
+ const prev = items[i - 1].getBoundingClientRect();
17648
+ const next = items[i].getBoundingClientRect();
17649
+ top = (prev.bottom + next.top) / 2 - barThickness / 2;
17650
+ left = Math.min(prev.left, next.left);
17651
+ width = Math.max(prev.right, next.right) - left;
17652
+ }
17653
+ slots.push({
17654
+ insertIndex: i,
17655
+ left,
17656
+ top,
17657
+ width: Math.max(width, 40),
17658
+ height: barThickness,
17659
+ direction: "horizontal"
17660
+ });
17661
+ }
17662
+ return slots;
17663
+ }
17664
+ const edgeGap = items.length >= 2 ? Math.max(0, items[1].getBoundingClientRect().left - items[0].getBoundingClientRect().right) : 12;
17665
+ for (let i = 0; i <= items.length; i++) {
17666
+ let left;
17667
+ let top;
17668
+ let height;
17669
+ if (i === 0) {
17670
+ const first = items[0].getBoundingClientRect();
17671
+ left = first.left - edgeGap / 2 - barThickness / 2;
17672
+ top = first.top;
17673
+ height = first.height;
17674
+ } else if (i === items.length) {
17675
+ const last = items[items.length - 1].getBoundingClientRect();
17676
+ const lastGap = items.length >= 2 ? Math.max(0, last.left - items[items.length - 2].getBoundingClientRect().right) : edgeGap;
17677
+ left = last.right + lastGap / 2 - barThickness / 2;
17678
+ top = last.top;
17679
+ height = last.height;
17680
+ } else {
17681
+ const prev = items[i - 1].getBoundingClientRect();
17682
+ const next = items[i].getBoundingClientRect();
17683
+ left = (prev.right + next.left) / 2 - barThickness / 2;
17684
+ top = Math.min(prev.top, next.top);
17685
+ height = Math.max(prev.bottom, next.bottom) - top;
17686
+ }
17687
+ slots.push({
17688
+ insertIndex: i,
17689
+ left,
17690
+ top,
17691
+ width: barThickness,
17692
+ height: Math.max(height, 24),
17693
+ direction: "vertical"
17694
+ });
17695
+ }
17696
+ return slots;
17697
+ }
17698
+ function hitTestButtonCollectionDropSlot(collection, clientX, clientY) {
17699
+ const slots = buildButtonCollectionDropSlots(collection);
17700
+ let best = null;
17701
+ for (const slot of slots) {
17702
+ const cx2 = slot.left + slot.width / 2;
17703
+ const cy = slot.top + slot.height / 2;
17704
+ 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;
17705
+ if (!best || dist < best.dist) best = { slot, dist };
17706
+ }
17707
+ return best?.slot ?? null;
17708
+ }
17709
+ function insertButtonAtCollectionIndex(button, insertIndex) {
17710
+ const collection = buttonCollectionOf(button);
17711
+ if (!collection) return null;
17712
+ const items = listVisibleCollectionButtons(collection);
17713
+ const from = items.indexOf(button);
17714
+ if (from < 0) return null;
17715
+ const others = items.filter((el) => el !== button);
17716
+ let index = insertIndex;
17717
+ if (from < insertIndex) index -= 1;
17718
+ if (index <= 0) {
17719
+ const first = others[0];
17720
+ if (first) first.before(button);
17721
+ else collection.append(button);
17722
+ } else if (index >= others.length) {
17723
+ collection.append(button);
17724
+ } else {
17725
+ others[index].before(button);
17726
+ }
17727
+ return listCollectionMembers(collection);
17728
+ }
17729
+ function persistCollectionOrder(collection, content) {
17730
+ const id = collection.getAttribute(COLLECTION_ATTR);
17731
+ if (!id) return null;
17732
+ const collections = readCollections(content);
17733
+ const current = collections[id];
17734
+ const members = listCollectionMembers(collection);
17735
+ const next = {
17736
+ ...collections,
17737
+ [id]: { id, members, gap: collection.style.gap || current?.gap || "12px" }
17738
+ };
17739
+ const nextContent = { ...content, [BUTTON_COLLECTIONS_KEY]: JSON.stringify(next) };
17740
+ return { content: nextContent, nodes: [{ key: BUTTON_COLLECTIONS_KEY, text: nextContent[BUTTON_COLLECTIONS_KEY] }] };
17741
+ }
17742
+ function hideButton(el) {
17743
+ if (el.getAttribute("data-ohw-button-hidden") === "") return;
17744
+ el.setAttribute("data-ohw-button-hidden", "");
17745
+ el.style.display = "none";
17746
+ }
17747
+ function syncButtonsFromContent(doc, content) {
17748
+ reconcileButtonCollections(doc, content);
17749
+ applyButtonContentFromContent(doc, content);
17750
+ applyButtonStylesFromContent(doc, content);
17751
+ }
17752
+ function applyButtonContentFromContent(doc, content) {
17753
+ const snapshots = readSnapshots(content);
17754
+ for (const snapshot of Object.values(snapshots)) {
17755
+ const el = findButton(doc, snapshot.id);
17756
+ if (!el) continue;
17757
+ applySnapshotContent(el, snapshot, content);
17758
+ }
17759
+ }
17760
+ function applySnapshotContent(el, snapshot, content) {
17761
+ const href = content[snapshot.id] ?? snapshot.href;
17762
+ if (href) el.setAttribute("href", href);
17763
+ const labelKey = snapshot.labelKey || labelKeyForHrefKey(snapshot.id);
17764
+ if (labelKey && !el.querySelector(`[data-ohw-key="${CSS.escape(labelKey)}"]`)) {
17765
+ const label2 = labelElement2(el);
17766
+ if (label2) label2.setAttribute("data-ohw-key", labelKey);
17767
+ }
17768
+ const text = content[labelKey];
17769
+ if (text === void 0) return;
17770
+ const label = labelElement2(el);
17771
+ if (label) {
17772
+ if (label.getAttribute("data-ohw-key") !== labelKey) label.setAttribute("data-ohw-key", labelKey);
17773
+ if (label.textContent !== text) label.textContent = text;
17774
+ }
17775
+ }
17776
+ function reconcileButtonCollections(doc, content) {
17777
+ const hidden = new Set(readHiddenButtons(content));
17778
+ for (const id of hidden) {
17779
+ const el = findButton(doc, id);
17780
+ if (el) hideButton(el);
17781
+ }
17782
+ const snapshots = readSnapshots(content);
17783
+ const collections = readCollections(content);
17784
+ for (const collectionState of Object.values(collections)) {
17785
+ const live = collectionState.members.map((id) => findButton(doc, id) ?? restoreSnapshot(doc, snapshots[id], content)).filter((el) => Boolean(el && !hidden.has(buttonId(el) ?? "")));
17786
+ if (live.length === 0) continue;
17787
+ if (live.length === 1) {
17788
+ const existing = buttonCollectionOf(live[0]);
17789
+ if (existing) unwrapCollection(existing);
17790
+ continue;
17791
+ }
17792
+ const anchor = live.find((el) => el.isConnected) ?? live[0];
17793
+ let collection = live.map((el) => buttonCollectionOf(el)).find((el) => el?.getAttribute(COLLECTION_ATTR) === collectionState.id) ?? null;
17794
+ if (!collection) {
17795
+ if (!anchor.parentElement) continue;
17796
+ collection = createCollection(doc, collectionState.id, collectionState.gap);
17797
+ anchor.parentElement.insertBefore(collection, anchor);
17798
+ }
17799
+ styleCollection(collection, collectionState.gap);
17800
+ const current = listCollectionMembers(collection);
17801
+ const sameOrder = current.length === live.length && current.every((id, index) => id === buttonId(live[index]));
17802
+ if (!sameOrder) {
17803
+ for (const member of live) collection.append(member);
17804
+ }
17805
+ for (const member of live) setButtonDragAttr(member);
17806
+ }
17807
+ }
17808
+ function restoreSnapshot(doc, snapshot, content) {
17809
+ if (!snapshot) return null;
17810
+ const existing = findButton(doc, snapshot.id);
17811
+ if (existing) {
17812
+ applySnapshotContent(existing, snapshot, content);
17813
+ return existing;
17814
+ }
17815
+ const el = htmlToElement(doc, snapshot.html);
17816
+ if (!el) return null;
17817
+ el.setAttribute("data-ohw-href-key", snapshot.id);
17818
+ const label = labelElement2(el);
17819
+ const labelKey = snapshot.labelKey || labelKeyForHrefKey(snapshot.id);
17820
+ if (label) label.setAttribute("data-ohw-key", labelKey);
17821
+ applySnapshotContent(el, snapshot, content);
17822
+ setButtonDragAttr(el);
17823
+ return el;
17824
+ }
17825
+ function navbarCtasInScope(el) {
17826
+ const scope = el.closest("nav, [data-ohw-nav-root], [data-ohw-nav-drawer]") ?? el.parentElement;
17827
+ if (!scope) return [];
17828
+ return Array.from(scope.querySelectorAll(CTA_BUTTON_SELECTOR)).filter(
17829
+ (button) => isNavbarCta(button) && buttonId(button)
17830
+ );
17831
+ }
17832
+ function ensureNavbarButtonCollection(button, content) {
17833
+ const existing = buttonCollectionOf(button);
17834
+ if (existing) return existing;
17835
+ if (!isNavbarCta(button) || !buttonId(button)) return null;
17836
+ const parent = button.parentElement;
17837
+ if (!parent) return null;
17838
+ const siblings = navbarCtasInScope(button).filter((el) => el.parentElement === parent);
17839
+ if (siblings.length < 2) return null;
17840
+ const id = nextCollectionId(content, button.ownerDocument);
17841
+ const collection = createCollection(button.ownerDocument, id, collectionGap(button, tokensFromButton(button)));
17842
+ siblings[0].before(collection);
17843
+ for (const member of siblings) {
17844
+ collection.append(member);
17845
+ setButtonDragAttr(member);
17846
+ }
17847
+ return collection;
17848
+ }
17849
+ function collectionDragEnabled(el) {
17850
+ if (!isNavbarCta(el) || !buttonId(el)) return false;
17851
+ if (buttonCollectionOf(el)) return true;
17852
+ return navbarCtasInScope(el).length >= 2;
17853
+ }
17854
+
17855
+ // src/lib/item-drag-interaction.ts
17856
+ function disableNativeHrefDrag(el) {
17857
+ if (el.draggable) el.draggable = false;
17858
+ if (el.getAttribute("draggable") !== "false") {
17859
+ el.setAttribute("draggable", "false");
17860
+ }
17861
+ }
17862
+ function clearTextSelection() {
17863
+ const sel = window.getSelection();
17864
+ if (sel && !sel.isCollapsed) sel.removeAllRanges();
17865
+ }
17866
+ function armItemPressDrag() {
17867
+ document.documentElement.setAttribute("data-ohw-footer-press-drag", "");
17868
+ }
17869
+ function lockItemDuringDrag() {
17870
+ document.documentElement.removeAttribute("data-ohw-footer-press-drag");
17871
+ document.documentElement.setAttribute("data-ohw-item-dragging", "");
17872
+ clearTextSelection();
17873
+ }
17874
+ function unlockItemDragInteraction() {
17875
+ const wasDragging = document.documentElement.hasAttribute("data-ohw-item-dragging");
17876
+ document.documentElement.removeAttribute("data-ohw-footer-press-drag");
17877
+ document.documentElement.removeAttribute("data-ohw-item-dragging");
17878
+ if (wasDragging) clearTextSelection();
17879
+ }
17880
+ var armFooterPressDrag = armItemPressDrag;
17881
+ var lockFooterDuringDrag = lockItemDuringDrag;
17882
+ var unlockFooterDragInteraction = unlockItemDragInteraction;
17883
+
17884
+ // src/lib/nav-dnd.ts
17885
+ function listReorderableNavItems(draggedEl) {
17886
+ return listActiveNavbarItems(draggedEl).filter((el) => {
17887
+ const key = el.getAttribute("data-ohw-href-key");
17888
+ return isNavbarHrefKey(key) && !isNestedNavChild(el);
17889
+ });
17890
+ }
17891
+ function resolveParentNavHrefKey(child) {
17892
+ const childrenRoot = child.closest("[data-ohw-nav-children]");
17893
+ const group = childrenRoot?.closest("[data-ohw-nav-group]");
17894
+ if (!group) return null;
17895
+ const trigger = group.querySelector(":scope > [data-ohw-href-key]") ?? Array.from(group.querySelectorAll("[data-ohw-href-key]")).find(
17896
+ (el) => !el.closest("[data-ohw-nav-children]")
17897
+ ) ?? null;
17898
+ const key = trigger?.getAttribute("data-ohw-href-key");
17899
+ return key && isNavbarHrefKey(key) ? key : null;
17900
+ }
17901
+ function listNavChildren(parentHrefKey, draggedEl) {
17902
+ const items = listActiveNavbarItems(draggedEl);
17903
+ const parent = items.find((el) => el.getAttribute("data-ohw-href-key") === parentHrefKey);
17904
+ if (!parent) return [];
17905
+ const group = parent.closest("[data-ohw-nav-group]");
17906
+ const childrenRoot = group?.querySelector(":scope > [data-ohw-nav-children]");
17907
+ if (!childrenRoot) return [];
17908
+ return Array.from(childrenRoot.querySelectorAll(":scope > [data-ohw-href-key]")).filter(
17909
+ (el) => isNavbarHrefKey(el.getAttribute("data-ohw-href-key"))
17910
+ );
17911
+ }
17912
+ function isVerticalNavLayout(items, draggedEl) {
17913
+ const visible = items.filter((el) => el.getClientRects().length > 0);
17914
+ if (visible.length >= 2) {
17915
+ const a = visible[0].getBoundingClientRect();
17916
+ const b = visible[1].getBoundingClientRect();
17917
+ const verticalGap = b.top - a.bottom;
17918
+ const horizontalGap = b.left - a.right;
17919
+ return verticalGap > horizontalGap;
17920
+ }
17921
+ const container = getActiveNavbarListContainer(draggedEl);
17922
+ if (!container) return false;
17923
+ const style = window.getComputedStyle(container);
17924
+ return style.flexDirection === "column" || style.flexDirection === "column-reverse";
17925
+ }
17926
+ function buildVerticalRootNavDropSlots(items, draggedEl) {
17927
+ const slots = [];
17928
+ const barThickness = 3;
17929
+ if (items.length === 0) {
17930
+ const container = getActiveNavbarListContainer(draggedEl);
17931
+ if (!container) return slots;
17932
+ const rect = container.getBoundingClientRect();
17933
+ slots.push({
17934
+ insertIndex: 0,
17935
+ parentId: null,
17936
+ left: rect.left,
17937
+ top: rect.top + rect.height / 2 - barThickness / 2,
17938
+ width: Math.max(rect.width, 40),
17939
+ height: barThickness,
17940
+ direction: "horizontal"
17941
+ });
17942
+ return slots;
17943
+ }
17944
+ const edgeGap = (() => {
17945
+ if (items.length < 2) return 16;
17946
+ const a = items[0].getBoundingClientRect();
17947
+ const b = items[1].getBoundingClientRect();
17948
+ return Math.max(0, b.top - a.bottom);
17949
+ })();
17950
+ for (let i = 0; i <= items.length; i++) {
17951
+ let top;
17952
+ let width;
17953
+ let left;
17954
+ if (i === 0) {
17955
+ const first = items[0].getBoundingClientRect();
17956
+ top = first.top - edgeGap / 2 - barThickness / 2;
17957
+ left = first.left;
17958
+ width = first.width;
17959
+ } else if (i === items.length) {
17960
+ const last = items[items.length - 1].getBoundingClientRect();
17961
+ const lastGap = items.length >= 2 ? Math.max(0, last.top - items[items.length - 2].getBoundingClientRect().bottom) : edgeGap;
17962
+ top = last.bottom + lastGap / 2 - barThickness / 2;
17963
+ left = last.left;
17964
+ width = last.width;
17965
+ } else {
17966
+ const prev = items[i - 1].getBoundingClientRect();
17967
+ const next = items[i].getBoundingClientRect();
17968
+ top = (prev.bottom + next.top) / 2 - barThickness / 2;
17969
+ left = Math.min(prev.left, next.left);
16177
17970
  width = Math.max(prev.right, next.right) - left;
16178
17971
  }
16179
17972
  slots.push({
@@ -16488,7 +18281,7 @@ function useNavItemDrag({
16488
18281
  document.querySelectorAll(
16489
18282
  "nav [data-ohw-href-key], [data-ohw-nav-container] [data-ohw-href-key], [data-ohw-nav-drawer] [data-ohw-href-key]"
16490
18283
  ).forEach((el) => {
16491
- if (isNavbarHrefKey(el.getAttribute("data-ohw-href-key"))) {
18284
+ if (isNavbarHrefKey(el.getAttribute("data-ohw-href-key")) || isCtaButton(el)) {
16492
18285
  disableNativeHrefDrag(el);
16493
18286
  }
16494
18287
  });
@@ -16797,12 +18590,6 @@ var SECTION_DRAG_EXCLUDED_SELECTOR = [
16797
18590
  "[data-ohw-media-overlay]",
16798
18591
  "[data-ohw-media-skeleton]"
16799
18592
  ].join(", ");
16800
- function visibleClip(ps) {
16801
- if (!ps) return null;
16802
- const top = Math.max(0, ps.headerH - ps.iframeOffsetTop);
16803
- const bottom = Math.min(window.innerHeight, ps.headerH + ps.canvasH - ps.iframeOffsetTop);
16804
- return { top, bottom: Math.max(top, bottom) };
16805
- }
16806
18593
  function useSectionDrag({
16807
18594
  isEditMode,
16808
18595
  editContentRef,
@@ -16839,7 +18626,7 @@ function useSectionDrag({
16839
18626
  }, [postToParentRef, stopAutoScroll]);
16840
18627
  const updateAutoScroll = (0, import_react17.useCallback)(
16841
18628
  (clientY) => {
16842
- const clip = visibleClip(parentScrollRef.current);
18629
+ const clip = getIframeChromeClip(parentScrollRef.current);
16843
18630
  let delta = 0;
16844
18631
  if (clip) {
16845
18632
  const distTop = clientY - clip.top;
@@ -17045,8 +18832,8 @@ function useSectionDrag({
17045
18832
  }
17046
18833
 
17047
18834
  // src/ui/footer-container-chrome.tsx
17048
- var import_lucide_react17 = require("lucide-react");
17049
- var import_jsx_runtime31 = require("react/jsx-runtime");
18835
+ var import_lucide_react18 = require("lucide-react");
18836
+ var import_jsx_runtime32 = require("react/jsx-runtime");
17050
18837
  function FooterContainerChrome({
17051
18838
  rect,
17052
18839
  onAdd,
@@ -17054,7 +18841,7 @@ function FooterContainerChrome({
17054
18841
  }) {
17055
18842
  const chromeGap = 6;
17056
18843
  const buttonMargin = 7;
17057
- return /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(TooltipProvider, { delayDuration: 0, children: /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(
18844
+ return /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(TooltipProvider, { delayDuration: 0, children: /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
17058
18845
  "div",
17059
18846
  {
17060
18847
  "data-ohw-footer-container-chrome": "",
@@ -17066,8 +18853,8 @@ function FooterContainerChrome({
17066
18853
  width: rect.width + chromeGap * 2,
17067
18854
  height: rect.height + chromeGap * 2
17068
18855
  },
17069
- children: /* @__PURE__ */ (0, import_jsx_runtime31.jsxs)(Tooltip, { children: [
17070
- /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(
18856
+ children: /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)(Tooltip, { children: [
18857
+ /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
17071
18858
  "button",
17072
18859
  {
17073
18860
  type: "button",
@@ -17086,10 +18873,10 @@ function FooterContainerChrome({
17086
18873
  if (addDisabled) return;
17087
18874
  onAdd();
17088
18875
  },
17089
- children: /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(import_lucide_react17.Plus, { className: "size-4 shrink-0 text-foreground", "aria-hidden": true })
18876
+ children: /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(import_lucide_react18.Plus, { className: "size-4 shrink-0 text-foreground", "aria-hidden": true })
17090
18877
  }
17091
18878
  ) }),
17092
- /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(TooltipContent, { side: "bottom", sideOffset: 9, children: addDisabled ? "Maximum columns reached" : "Add item" })
18879
+ /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(TooltipContent, { side: "bottom", sideOffset: 9, children: addDisabled ? "Maximum columns reached" : "Add item" })
17093
18880
  ] })
17094
18881
  }
17095
18882
  ) });
@@ -17251,7 +19038,14 @@ function collectEditableNodes(extraContent, root = document) {
17251
19038
  // so collecting the DOM alone left them behind: the draft knew the row was showing icons and
17252
19039
  // had gained an item, and the published page went back to the template's own (OHH-736).
17253
19040
  SOCIALS_ORDER_KEY,
17254
- SOCIALS_DISPLAY_KEY
19041
+ SOCIALS_DISPLAY_KEY,
19042
+ // Button variant/size tokens and per-CTA assignments — JSON in editContent, applied on hydrate.
19043
+ BUTTON_VARIANTS_KEY,
19044
+ BUTTON_SIZES_KEY,
19045
+ BUTTON_ASSIGNMENTS_KEY,
19046
+ BUTTON_COLLECTIONS_KEY,
19047
+ BUTTON_SNAPSHOTS_KEY,
19048
+ BUTTON_HIDDEN_KEY
17255
19049
  ]) {
17256
19050
  const text = extraContent[key];
17257
19051
  if (typeof text === "string" && text.length > 0) {
@@ -17571,14 +19365,14 @@ function deleteSelectedNavFooterItem(deps) {
17571
19365
  }
17572
19366
 
17573
19367
  // src/ui/navbar-container-chrome.tsx
17574
- var import_lucide_react18 = require("lucide-react");
17575
- var import_jsx_runtime32 = require("react/jsx-runtime");
19368
+ var import_lucide_react19 = require("lucide-react");
19369
+ var import_jsx_runtime33 = require("react/jsx-runtime");
17576
19370
  function NavbarContainerChrome({
17577
19371
  rect,
17578
19372
  onAdd
17579
19373
  }) {
17580
19374
  const chromeGap = 6;
17581
- return /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
19375
+ return /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
17582
19376
  "div",
17583
19377
  {
17584
19378
  "data-ohw-navbar-container-chrome": "",
@@ -17590,7 +19384,7 @@ function NavbarContainerChrome({
17590
19384
  width: rect.width + chromeGap * 2,
17591
19385
  height: rect.height + chromeGap * 2
17592
19386
  },
17593
- children: /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
19387
+ children: /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
17594
19388
  "button",
17595
19389
  {
17596
19390
  type: "button",
@@ -17607,7 +19401,7 @@ function NavbarContainerChrome({
17607
19401
  e.stopPropagation();
17608
19402
  onAdd();
17609
19403
  },
17610
- children: /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(import_lucide_react18.Plus, { className: "size-4 shrink-0 text-foreground", "aria-hidden": true })
19404
+ children: /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(import_lucide_react19.Plus, { className: "size-4 shrink-0 text-foreground", "aria-hidden": true })
17611
19405
  }
17612
19406
  )
17613
19407
  }
@@ -17616,7 +19410,7 @@ function NavbarContainerChrome({
17616
19410
 
17617
19411
  // src/ui/drop-indicator.tsx
17618
19412
  var React11 = __toESM(require("react"), 1);
17619
- var import_jsx_runtime33 = require("react/jsx-runtime");
19413
+ var import_jsx_runtime34 = require("react/jsx-runtime");
17620
19414
  var dropIndicatorVariants = cva(
17621
19415
  "ov-gap-line pointer-events-none shrink-0 transition-opacity duration-150",
17622
19416
  {
@@ -17640,7 +19434,7 @@ var dropIndicatorVariants = cva(
17640
19434
  );
17641
19435
  var DropIndicator = React11.forwardRef(
17642
19436
  ({ className, direction, state, ...props }, ref) => {
17643
- return /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
19437
+ return /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(
17644
19438
  "div",
17645
19439
  {
17646
19440
  ref,
@@ -17657,7 +19451,7 @@ var DropIndicator = React11.forwardRef(
17657
19451
  DropIndicator.displayName = "DropIndicator";
17658
19452
 
17659
19453
  // src/ui/badge.tsx
17660
- var import_jsx_runtime34 = require("react/jsx-runtime");
19454
+ var import_jsx_runtime35 = require("react/jsx-runtime");
17661
19455
  var badgeVariants = cva(
17662
19456
  "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",
17663
19457
  {
@@ -17675,12 +19469,12 @@ var badgeVariants = cva(
17675
19469
  }
17676
19470
  );
17677
19471
  function Badge({ className, variant, ...props }) {
17678
- return /* @__PURE__ */ (0, import_jsx_runtime34.jsx)("div", { className: cn(badgeVariants({ variant }), className), ...props });
19472
+ return /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("div", { className: cn(badgeVariants({ variant }), className), ...props });
17679
19473
  }
17680
19474
 
17681
19475
  // src/OhhwellsBridge.tsx
17682
- var import_lucide_react19 = require("lucide-react");
17683
- var import_jsx_runtime35 = require("react/jsx-runtime");
19476
+ var import_lucide_react20 = require("lucide-react");
19477
+ var import_jsx_runtime36 = require("react/jsx-runtime");
17684
19478
  var PRIMARY5 = "#0885FE";
17685
19479
  var IMAGE_FADE_MS = 300;
17686
19480
  var MEDIA_SETTLE_TIMEOUT_MS = 2500;
@@ -17802,7 +19596,7 @@ function duplicateSchedulingWidget(instanceId, scheduleId) {
17802
19596
  schedulingRoots.set(container, root);
17803
19597
  (0, import_react_dom4.flushSync)(() => {
17804
19598
  root.render(
17805
- /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(
19599
+ /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(
17806
19600
  SchedulingWidget,
17807
19601
  {
17808
19602
  notifyOnConnect: false,
@@ -17845,21 +19639,10 @@ function parseSchedulingInsertAfter(insertAfter) {
17845
19639
  insertBefore: insertAfter.slice(idx + INSERT_BEFORE_MARKER.length)
17846
19640
  };
17847
19641
  }
17848
- function resolveSchedulingInsert(insertAfter, explicitInsertBefore) {
17849
- const parsed = parseSchedulingInsertAfter(insertAfter);
17850
- const insertBefore = explicitInsertBefore ?? parsed.insertBefore;
17851
- const effectiveInsertAfter = insertBefore && parsed.insertBefore === null ? `${insertAfter}${INSERT_BEFORE_MARKER}${insertBefore}` : insertAfter;
17852
- return { effectiveInsertAfter, insertBefore };
17853
- }
17854
- function getSchedulingMountPoint(insertAfter) {
17855
- const { anchor } = parseSchedulingInsertAfter(insertAfter);
17856
- let anchorEl = document.querySelector(`[data-ohw-section="${anchor}"]`);
17857
- if (!anchorEl && anchor === "scheduling") {
17858
- const widgets = Array.from(document.querySelectorAll('[data-ohw-section^="scheduling-"]'));
17859
- anchorEl = widgets.at(-1) ?? null;
17860
- }
17861
- if (!anchorEl) return null;
17862
- return anchorEl.closest('[data-ohw-section-container="scheduling"]') ?? anchorEl;
19642
+ function resolveEntryAnchor(entry) {
19643
+ if (entry.anchorId !== void 0) return { anchorId: entry.anchorId, beforeId: entry.beforeId ?? null };
19644
+ const parsed = parseSchedulingInsertAfter(entry.insertAfter);
19645
+ return { anchorId: parsed.anchor, beforeId: parsed.insertBefore };
17863
19646
  }
17864
19647
  function schedulingMountDepth(insertAfter) {
17865
19648
  if (!insertAfter.includes(INSERT_BEFORE_MARKER)) return 0;
@@ -17876,8 +19659,7 @@ function getPageSchedulingEntries(raw) {
17876
19659
  }
17877
19660
  }
17878
19661
  function isSchedulingWidgetMissing(entry) {
17879
- const { effectiveInsertAfter } = resolveSchedulingInsert(entry.insertAfter);
17880
- return !document.querySelector(`[data-ohw-section="${schedulingSectionId(effectiveInsertAfter)}"]`);
19662
+ return !document.querySelector(`[data-ohw-section="${schedulingSectionId(entry.insertAfter)}"]`);
17881
19663
  }
17882
19664
  function hasMissingSchedulingWidgets(entries) {
17883
19665
  return entries.some(isSchedulingWidgetMissing);
@@ -17915,17 +19697,18 @@ function initSectionsFromContent(content, removeExisting = false, currentPath =
17915
19697
  } catch {
17916
19698
  }
17917
19699
  }
17918
- function mountSchedulingWidget(insertAfter, notifyOnConnect = false, scheduleId, explicitInsertBefore) {
17919
- const { effectiveInsertAfter, insertBefore } = resolveSchedulingInsert(insertAfter, explicitInsertBefore);
17920
- const sectionId = schedulingSectionId(effectiveInsertAfter);
19700
+ function mountSchedulingWidget(anchorId, notifyOnConnect = false, scheduleId, beforeId, existingWidgetId) {
19701
+ const widgetId = existingWidgetId ?? (beforeId ? `${anchorId}${INSERT_BEFORE_MARKER}${beforeId}` : anchorId);
19702
+ const sectionId = schedulingSectionId(widgetId);
17921
19703
  if (document.querySelector(`[data-ohw-section="${sectionId}"]`)) return false;
17922
- const mountPoint = getSchedulingMountPoint(effectiveInsertAfter);
17923
- if (!mountPoint) return false;
19704
+ const anchorEl = document.querySelector(`[data-ohw-section="${anchorId}"]`);
19705
+ if (!anchorEl) return false;
19706
+ const mountPoint = anchorEl.closest('[data-ohw-section-container="scheduling"]') ?? anchorEl;
17924
19707
  const container = document.createElement("div");
17925
19708
  container.dataset.ohwSectionContainer = "scheduling";
17926
19709
  container.dataset.ohwInstance = sectionId;
17927
- if (insertBefore) {
17928
- const beforeAnchor = document.querySelector(`[data-ohw-section="${insertBefore}"]`);
19710
+ if (beforeId) {
19711
+ const beforeAnchor = document.querySelector(`[data-ohw-section="${beforeId}"]`);
17929
19712
  const beforePoint = beforeAnchor?.closest('[data-ohw-section-container="scheduling"]') ?? beforeAnchor;
17930
19713
  if (!beforePoint) return false;
17931
19714
  beforePoint.insertAdjacentElement("beforebegin", container);
@@ -17936,20 +19719,26 @@ function mountSchedulingWidget(insertAfter, notifyOnConnect = false, scheduleId,
17936
19719
  }
17937
19720
  tail.insertAdjacentElement("afterend", container);
17938
19721
  }
17939
- const root = (0, import_client2.createRoot)(container);
17940
- schedulingRoots.set(container, root);
17941
- (0, import_react_dom4.flushSync)(() => {
17942
- root.render(
17943
- /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(
17944
- SchedulingWidget,
17945
- {
17946
- notifyOnConnect,
17947
- initialScheduleId: scheduleId,
17948
- insertAfter: effectiveInsertAfter
17949
- }
17950
- )
17951
- );
17952
- });
19722
+ try {
19723
+ const root = (0, import_client2.createRoot)(container);
19724
+ schedulingRoots.set(container, root);
19725
+ (0, import_react_dom4.flushSync)(() => {
19726
+ root.render(
19727
+ /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(
19728
+ SchedulingWidget,
19729
+ {
19730
+ notifyOnConnect,
19731
+ initialScheduleId: scheduleId,
19732
+ insertAfter: widgetId
19733
+ }
19734
+ )
19735
+ );
19736
+ });
19737
+ } catch (err) {
19738
+ console.error("[ow:scheduling] render threw", err);
19739
+ container.remove();
19740
+ return false;
19741
+ }
17953
19742
  const tracker = getSectionsTracker();
17954
19743
  let sections = [];
17955
19744
  try {
@@ -17957,10 +19746,12 @@ function mountSchedulingWidget(insertAfter, notifyOnConnect = false, scheduleId,
17957
19746
  } catch {
17958
19747
  }
17959
19748
  const inEditor = typeof window !== "undefined" && window.self !== window.top;
17960
- if (inEditor && !sections.find((s) => s.type === "scheduling" && s.insertAfter === effectiveInsertAfter)) {
19749
+ if (inEditor && !sections.find((s) => s.type === "scheduling" && s.insertAfter === widgetId)) {
17961
19750
  sections.push({
17962
19751
  type: "scheduling",
17963
- insertAfter: effectiveInsertAfter,
19752
+ insertAfter: widgetId,
19753
+ anchorId,
19754
+ beforeId: beforeId ?? null,
17964
19755
  pagePath: window.location.pathname,
17965
19756
  ...scheduleId ? { scheduleId } : {}
17966
19757
  });
@@ -17974,7 +19765,8 @@ function mountSchedulingEntries(entries, notifyOnConnect = false) {
17974
19765
  for (let i = pending.length - 1; i >= 0; i--) {
17975
19766
  const entry = pending[i];
17976
19767
  const shouldNotify = typeof notifyOnConnect === "function" ? notifyOnConnect(entry) : notifyOnConnect;
17977
- if (mountSchedulingWidget(entry.insertAfter, shouldNotify, entry.scheduleId ?? null)) {
19768
+ const { anchorId, beforeId } = resolveEntryAnchor(entry);
19769
+ if (mountSchedulingWidget(anchorId, shouldNotify, entry.scheduleId ?? null, beforeId, entry.insertAfter)) {
17978
19770
  pending.splice(i, 1);
17979
19771
  }
17980
19772
  }
@@ -18103,8 +19895,8 @@ function lockVideoBox(video) {
18103
19895
  }
18104
19896
  const fit = video.style.objectFit || getComputedStyle(video).objectFit;
18105
19897
  const bg = getComputedStyle(video).backgroundColor;
18106
- const isTransparent = bg === "rgba(0, 0, 0, 0)" || bg === "transparent" || !bg;
18107
- if (fit === "contain" && isTransparent) {
19898
+ const isTransparent2 = bg === "rgba(0, 0, 0, 0)" || bg === "transparent" || !bg;
19899
+ if (fit === "contain" && isTransparent2) {
18108
19900
  video.style.backgroundColor = "var(--color-dark, #000)";
18109
19901
  }
18110
19902
  }
@@ -18151,6 +19943,11 @@ function applyLinkByKey(key, val) {
18151
19943
  hrefAnchors.forEach((el) => applyLinkHref(el, val));
18152
19944
  }
18153
19945
  }
19946
+ function isInsideLinkEditor(target) {
19947
+ return Boolean(
19948
+ 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"]')
19949
+ );
19950
+ }
18154
19951
  function isInsideFloatingPanel(target) {
18155
19952
  return Boolean(target.closest("[data-ohw-floating-panel]"));
18156
19953
  }
@@ -18158,11 +19955,6 @@ function isPointOverFloatingPanel(clientX, clientY) {
18158
19955
  const el = document.elementFromPoint(clientX, clientY);
18159
19956
  return Boolean(el instanceof Element && el.closest("[data-ohw-floating-panel]"));
18160
19957
  }
18161
- function isInsideLinkEditor(target) {
18162
- return Boolean(
18163
- 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"]')
18164
- );
18165
- }
18166
19958
  function getHrefKeyFromElement(el) {
18167
19959
  if (!el) return null;
18168
19960
  const anchor = el.closest("[data-ohw-href-key]");
@@ -18254,7 +20046,10 @@ function listNavigationItems() {
18254
20046
  ).filter((el) => isNavigationItem2(el));
18255
20047
  }
18256
20048
  function getNavigationItemReorderState(anchor) {
18257
- if (isCtaButton(anchor)) return { key: null, disabled: false };
20049
+ if (isCtaButton(anchor)) {
20050
+ if (!collectionDragEnabled(anchor)) return { key: null, disabled: true };
20051
+ return { key: buttonId(anchor), disabled: false };
20052
+ }
18258
20053
  return getReorderHandleStateFromAnchor(anchor);
18259
20054
  }
18260
20055
  function getReorderHandleState(el) {
@@ -18353,6 +20148,12 @@ function isPointOverNavigation(x, y) {
18353
20148
  const r2 = panel.getBoundingClientRect();
18354
20149
  if (x >= r2.left && x <= r2.right && y >= r2.top && y <= r2.bottom) return true;
18355
20150
  }
20151
+ for (const el of document.querySelectorAll('[data-ohw-role="navbar-button"]')) {
20152
+ const r2 = el.getBoundingClientRect();
20153
+ if (r2.width > 0 && r2.height > 0 && x >= r2.left && x <= r2.right && y >= r2.top && y <= r2.bottom) {
20154
+ return true;
20155
+ }
20156
+ }
18356
20157
  return false;
18357
20158
  }
18358
20159
  function findHoveredNavOrFooterContainer(x, y) {
@@ -18432,7 +20233,7 @@ function getNavigationSelectionParent(el) {
18432
20233
  if (!isFooterLinksContainer(el) && (el.hasAttribute("data-ohw-footer-col") || el.hasAttribute("data-ohw-footer-column") || isInferredFooterGroup2(el))) {
18433
20234
  return getFooterLinksContainer();
18434
20235
  }
18435
- if (el.hasAttribute("data-ohw-nav-container") || el.hasAttribute("data-ohw-nav-drawer") || isFooterLinksContainer(el)) {
20236
+ 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)) {
18436
20237
  return getNavigationRoot(el);
18437
20238
  }
18438
20239
  return null;
@@ -18654,7 +20455,6 @@ var ICONS = {
18654
20455
  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"/>',
18655
20456
  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"/>'
18656
20457
  };
18657
- var HEIGHT_SETTLE_DELAYS = [150, 400, 800];
18658
20458
  var SELECTION_CHROME_GAP2 = 4;
18659
20459
  var TOOLBAR_STROKE_GAP2 = 4;
18660
20460
  var TOOLBAR_OFFSET_FROM_ELEMENT = SELECTION_CHROME_GAP2 + TOOLBAR_STROKE_GAP2;
@@ -18686,7 +20486,7 @@ function EditGlowChrome({
18686
20486
  hideHandle = false
18687
20487
  }) {
18688
20488
  const GAP = SELECTION_CHROME_GAP2;
18689
- return /* @__PURE__ */ (0, import_jsx_runtime35.jsxs)(
20489
+ return /* @__PURE__ */ (0, import_jsx_runtime36.jsxs)(
18690
20490
  "div",
18691
20491
  {
18692
20492
  ref: elRef,
@@ -18701,7 +20501,7 @@ function EditGlowChrome({
18701
20501
  zIndex: 2147483646
18702
20502
  },
18703
20503
  children: [
18704
- /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(
20504
+ /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(
18705
20505
  "div",
18706
20506
  {
18707
20507
  style: {
@@ -18715,7 +20515,7 @@ function EditGlowChrome({
18715
20515
  }
18716
20516
  }
18717
20517
  ),
18718
- reorderHrefKey && !hideHandle && /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(
20518
+ reorderHrefKey && !hideHandle && /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(
18719
20519
  "div",
18720
20520
  {
18721
20521
  "data-ohw-drag-handle-container": "",
@@ -18727,7 +20527,7 @@ function EditGlowChrome({
18727
20527
  transform: "translate(calc(-100% - 7px), -50%)",
18728
20528
  pointerEvents: dragDisabled ? "none" : "auto"
18729
20529
  },
18730
- children: /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(
20530
+ children: /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(
18731
20531
  DragHandle,
18732
20532
  {
18733
20533
  "aria-label": `Reorder ${reorderHrefKey}`,
@@ -18741,22 +20541,12 @@ function EditGlowChrome({
18741
20541
  }
18742
20542
  );
18743
20543
  }
18744
- function getIframeVisibleClip(parentScroll) {
18745
- if (!parentScroll) return null;
18746
- const { iframeOffsetTop, headerH: visibleCanvasTop, canvasH } = parentScroll;
18747
- const clipTop = Math.max(0, visibleCanvasTop - iframeOffsetTop);
18748
- const clipBottom = Math.min(
18749
- window.innerHeight,
18750
- visibleCanvasTop + canvasH - iframeOffsetTop
18751
- );
18752
- return { top: clipTop, bottom: Math.max(clipTop, clipBottom) };
18753
- }
18754
20544
  function isRectOutsideClip(rect, clip) {
18755
20545
  if (!clip) return false;
18756
20546
  return rect.bottom < clip.top || rect.top > clip.bottom;
18757
20547
  }
18758
20548
  function applyVisibleViewport(el, parentScroll) {
18759
- const clip = getIframeVisibleClip(parentScroll);
20549
+ const clip = getIframeChromeClip(parentScroll);
18760
20550
  const top = clip?.top ?? 0;
18761
20551
  const height = clip ? clip.bottom - clip.top : window.innerHeight;
18762
20552
  el.style.top = `${top}px`;
@@ -18796,7 +20586,7 @@ function clampToolbarLeft(centerX, toolbarWidth) {
18796
20586
  function calcToolbarPos(rect, parentScroll, toolbarW = 306) {
18797
20587
  const GAP = TOOLBAR_OFFSET_FROM_ELEMENT;
18798
20588
  const APPROX_H = 32;
18799
- const clip = getIframeVisibleClip(parentScroll);
20589
+ const clip = getIframeChromeClip(parentScroll);
18800
20590
  const clipTop = clip?.top ?? 0;
18801
20591
  const canvasTopInIframe = parentScroll != null ? parentScroll.headerH - parentScroll.iframeOffsetTop : 0;
18802
20592
  const visibleTop = rect.top - canvasTopInIframe;
@@ -18941,7 +20731,7 @@ function FloatingToolbar({
18941
20731
  return () => ro.disconnect();
18942
20732
  }, [showEditLink, activeCommands]);
18943
20733
  const { top, left, transform } = calcToolbarPos(rect, parentScroll, measuredW);
18944
- return /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(
20734
+ return /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(
18945
20735
  "div",
18946
20736
  {
18947
20737
  ref: setRefs,
@@ -18953,12 +20743,12 @@ function FloatingToolbar({
18953
20743
  zIndex: 2147483647,
18954
20744
  pointerEvents: "auto"
18955
20745
  },
18956
- children: /* @__PURE__ */ (0, import_jsx_runtime35.jsxs)(CustomToolbar, { children: [
18957
- TOOLBAR_GROUPS.map((btns, gi) => /* @__PURE__ */ (0, import_jsx_runtime35.jsxs)(import_react19.default.Fragment, { children: [
18958
- gi > 0 && /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(CustomToolbarDivider, {}),
20746
+ children: /* @__PURE__ */ (0, import_jsx_runtime36.jsxs)(CustomToolbar, { children: [
20747
+ TOOLBAR_GROUPS.map((btns, gi) => /* @__PURE__ */ (0, import_jsx_runtime36.jsxs)(import_react19.default.Fragment, { children: [
20748
+ gi > 0 && /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(CustomToolbarDivider, {}),
18959
20749
  btns.map((btn) => {
18960
20750
  const isActive = activeCommands.has(btn.cmd);
18961
- return /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(
20751
+ return /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(
18962
20752
  CustomToolbarButton,
18963
20753
  {
18964
20754
  title: btn.title,
@@ -18967,7 +20757,7 @@ function FloatingToolbar({
18967
20757
  e.preventDefault();
18968
20758
  onCommand(btn.cmd);
18969
20759
  },
18970
- children: /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(
20760
+ children: /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(
18971
20761
  "svg",
18972
20762
  {
18973
20763
  width: "16",
@@ -18988,7 +20778,7 @@ function FloatingToolbar({
18988
20778
  );
18989
20779
  })
18990
20780
  ] }, gi)),
18991
- showEditLink ? /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(
20781
+ showEditLink ? /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(
18992
20782
  CustomToolbarButton,
18993
20783
  {
18994
20784
  type: "button",
@@ -19002,7 +20792,7 @@ function FloatingToolbar({
19002
20792
  e.preventDefault();
19003
20793
  e.stopPropagation();
19004
20794
  },
19005
- children: /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(import_lucide_react19.Link, { className: "size-4 shrink-0", "aria-hidden": true })
20795
+ children: /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(import_lucide_react20.Link, { className: "size-4 shrink-0", "aria-hidden": true })
19006
20796
  }
19007
20797
  ) : null
19008
20798
  ] })
@@ -19019,7 +20809,7 @@ function StateToggle({
19019
20809
  states,
19020
20810
  onStateChange
19021
20811
  }) {
19022
- return /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(
20812
+ return /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(
19023
20813
  ToggleGroup,
19024
20814
  {
19025
20815
  "data-ohw-state-toggle": "",
@@ -19033,11 +20823,12 @@ function StateToggle({
19033
20823
  left: rect.right - 8,
19034
20824
  transform: "translateX(-100%)"
19035
20825
  },
19036
- children: states.map((state) => /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(ToggleGroupItem, { value: state, size: "sm", children: state }, state))
20826
+ children: states.map((state) => /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(ToggleGroupItem, { value: state, size: "sm", children: state }, state))
19037
20827
  }
19038
20828
  );
19039
20829
  }
19040
20830
  var contentCache = /* @__PURE__ */ new Map();
20831
+ var fetchedContentPaths = /* @__PURE__ */ new Set();
19041
20832
  var brandingCache = /* @__PURE__ */ new Map();
19042
20833
  var OHW_LOADER_STYLE = {
19043
20834
  position: "fixed",
@@ -19049,9 +20840,9 @@ var OHW_LOADER_STYLE = {
19049
20840
  justifyContent: "center"
19050
20841
  };
19051
20842
  function OhwLoaderSpinner() {
19052
- return /* @__PURE__ */ (0, import_jsx_runtime35.jsxs)("svg", { width: "28", height: "28", viewBox: "0 0 28 28", fill: "none", "aria-hidden": true, children: [
19053
- /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("circle", { cx: "14", cy: "14", r: "11", stroke: "#E7E5E4", strokeWidth: "3" }),
19054
- /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(
20843
+ return /* @__PURE__ */ (0, import_jsx_runtime36.jsxs)("svg", { width: "28", height: "28", viewBox: "0 0 28 28", fill: "none", "aria-hidden": true, children: [
20844
+ /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("circle", { cx: "14", cy: "14", r: "11", stroke: "#E7E5E4", strokeWidth: "3" }),
20845
+ /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(
19055
20846
  "circle",
19056
20847
  {
19057
20848
  cx: "14",
@@ -19061,7 +20852,7 @@ function OhwLoaderSpinner() {
19061
20852
  strokeWidth: "3",
19062
20853
  strokeDasharray: "17 52",
19063
20854
  strokeLinecap: "round",
19064
- children: /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(
20855
+ children: /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(
19065
20856
  "animateTransform",
19066
20857
  {
19067
20858
  attributeName: "transform",
@@ -19077,7 +20868,7 @@ function OhwLoaderSpinner() {
19077
20868
  ] });
19078
20869
  }
19079
20870
  function OhwBrandMark() {
19080
- return /* @__PURE__ */ (0, import_jsx_runtime35.jsxs)(
20871
+ return /* @__PURE__ */ (0, import_jsx_runtime36.jsxs)(
19081
20872
  "svg",
19082
20873
  {
19083
20874
  width: "16",
@@ -19088,7 +20879,7 @@ function OhwBrandMark() {
19088
20879
  style: { display: "block", flexShrink: 0 },
19089
20880
  xmlns: "http://www.w3.org/2000/svg",
19090
20881
  children: [
19091
- /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(
20882
+ /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(
19092
20883
  "mask",
19093
20884
  {
19094
20885
  id: "ohw-badge-mark",
@@ -19098,15 +20889,15 @@ function OhwBrandMark() {
19098
20889
  y: "0",
19099
20890
  width: "48",
19100
20891
  height: "48",
19101
- children: /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("path", { d: "M23.8741 48C37.0594 48 47.7481 37.2548 47.7481 24C47.7481 10.7452 37.0594 0 23.8741 0C10.6888 0 0 10.7452 0 24C0 37.2548 10.6888 48 23.8741 48Z", fill: "white" })
20892
+ children: /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("path", { d: "M23.8741 48C37.0594 48 47.7481 37.2548 47.7481 24C47.7481 10.7452 37.0594 0 23.8741 0C10.6888 0 0 10.7452 0 24C0 37.2548 10.6888 48 23.8741 48Z", fill: "white" })
19102
20893
  }
19103
20894
  ),
19104
- /* @__PURE__ */ (0, import_jsx_runtime35.jsxs)("g", { mask: "url(#ohw-badge-mark)", children: [
19105
- /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("path", { d: "M23.8731 48.0497C37.0584 48.0497 47.7472 37.3046 47.7472 24.0497C47.7472 10.7949 37.0584 0.0497208 23.8731 0.0497208C10.6878 0.0497208 -0.000976562 10.7949 -0.000976562 24.0497C-0.000976562 37.3046 10.6878 48.0497 23.8731 48.0497Z", fill: "#0078E5" }),
19106
- /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("path", { d: "M17.1307 14.7172C13.1687 14.7172 9.38102 18.1154 8.65114 22.34C8.38885 23.8488 8.5598 25.2581 9.06929 26.4451C6.20005 29.1677 1.77216 27.8721 -1.40212 26.1536C-2.73618 25.4317 -3.92695 27.4745 -2.59037 28.1981C1.33389 30.3226 6.86037 31.6621 10.4402 28.4188C11.4718 29.3859 12.867 29.9621 14.4894 29.9621C18.4161 29.9621 22.2038 26.5318 22.9337 22.34C23.6636 18.1162 21.0566 14.7172 17.1298 14.7172H17.1307ZM19.9798 22.34C19.5281 25.0399 17.2689 27.231 14.9754 27.231C12.6466 27.231 11.1877 25.0399 11.6394 22.34C12.1262 19.6401 14.3151 17.4482 16.6438 17.4482C18.9374 17.4482 20.4667 19.6401 19.9798 22.34Z", fill: "white" }),
19107
- /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("path", { d: "M40.0017 27.0262C39.1797 27.081 38.2721 26.995 37.4668 26.7415C37.3344 26.6993 37.28 26.5401 37.3529 26.4205C37.5959 26.0212 37.8255 25.6152 38.009 25.1889C38.1071 24.9918 38.2018 24.793 38.2897 24.5908C38.3274 24.5041 38.4163 24.451 38.5101 24.4619C38.63 24.4754 38.7054 24.4821 38.8881 24.4821L39.1529 24.4796L39.8283 24.4543C45.9229 24.0492 50.4765 20.4319 54.8466 16.9014C56.0172 15.9554 57.6932 17.6208 56.5116 18.5752C51.7687 22.4065 47.1966 26.5081 40.9319 26.9689", fill: "white" }),
19108
- /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("path", { d: "M37.9687 24.27C38.4472 23.1319 38.7656 21.9045 38.9609 20.6991C39.5927 16.76 38.5058 14.2193 36.1553 14.2193C34.1835 14.2193 33.1469 17.2427 32.9199 18.8694C32.743 19.9872 32.5914 22.1219 33.6028 23.9524C33.7553 24.259 34.15 24.7712 34.471 25.1259C34.5447 25.2067 34.6746 25.2 34.7349 25.1082C34.9528 24.7788 35.1615 24.4039 35.3584 24.0257C35.5444 23.6677 35.5888 23.6138 35.8587 23.0207C35.8838 22.966 35.8813 22.9002 35.8478 22.8505C35.5888 22.4597 35.2168 21.9787 35.1204 21.4614C34.9184 20.4455 34.9436 19.2257 35.2218 18.1078C35.4413 17.3118 35.7195 16.7844 35.9039 16.5106C35.9466 16.4466 36.0279 16.4129 36.0975 16.4449C36.369 16.5671 36.5827 16.8838 36.7385 17.396C37.0167 18.2089 37.0167 19.3782 36.814 20.6999C36.6991 21.5271 36.4771 22.3729 36.1746 23.1673C36.1293 23.308 36.0757 23.4461 36.0187 23.5826C36.0187 23.5868 36.0187 23.591 36.0187 23.5961C35.9911 23.6946 35.9207 23.8067 35.8846 23.901C35.5536 24.5497 35.2344 25.1697 34.8439 25.7838C34.8388 25.7863 34.8346 25.7914 34.8296 25.7931C34.6528 26.0525 34.4718 26.2901 34.2866 26.4965C34.2774 26.5099 34.2682 26.5234 34.2589 26.5369C34.2405 26.5638 34.2179 26.5815 34.1944 26.595C33.5064 27.3212 32.7665 27.6893 32.0123 27.6893C31.8606 27.6893 31.6838 27.664 31.507 27.4349C31.3042 27.1299 30.85 26.0879 31.2539 22.9112C31.4785 21.3949 31.8011 20.0268 31.931 19.5408C31.9579 19.4413 31.8908 19.3419 31.7886 19.3293L30.0062 19.1162C29.9241 19.1061 29.8478 19.1566 29.8252 19.2366C29.2704 21.1615 27.0305 27.6885 24.9599 27.6885C24.328 27.6885 24.1512 26.6211 24.1001 26.2909C23.775 23.6264 25.528 18.5492 29.5302 16.2267C29.6048 16.1838 29.635 16.0928 29.5998 16.0136L28.9042 14.467C28.8632 14.3752 28.7501 14.3389 28.6638 14.3886C25.8079 16.0414 24.1847 18.5231 23.2915 20.3436C22.2046 22.5793 21.6993 25.0189 21.9515 26.8738C22.1795 28.7794 23.1398 29.872 24.6054 29.872C26.0543 29.872 27.4369 28.9066 28.7098 27.0187C28.7953 26.8915 28.9889 26.9311 29.0149 27.0819C29.2646 28.5283 29.9811 29.872 31.6579 29.872C33.5282 29.872 35.3232 28.7288 36.7134 26.6447C36.7712 26.5874 36.7972 26.5411 36.8181 26.4906C36.8232 26.4931 36.8282 26.4948 36.8332 26.4973C37.01 26.2025 37.181 25.9051 37.3444 25.6027C37.4508 25.4005 37.5572 25.1992 37.6586 24.9945C37.7181 24.874 37.7743 24.7527 37.8262 24.6289C37.838 24.6002 37.8547 24.5665 37.8706 24.5337", fill: "white" }),
19109
- /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("path", { d: "M30.5839 31.6397C25.7546 34.8577 19.4773 34.7853 14.6907 31.5243C13.5368 30.7384 12.5044 32.6498 13.6474 33.4281C19.034 37.0985 26.3077 37.096 31.7218 33.488C32.8791 32.7172 31.7478 30.8639 30.5839 31.6397Z", fill: "white" })
20895
+ /* @__PURE__ */ (0, import_jsx_runtime36.jsxs)("g", { mask: "url(#ohw-badge-mark)", children: [
20896
+ /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("path", { d: "M23.8731 48.0497C37.0584 48.0497 47.7472 37.3046 47.7472 24.0497C47.7472 10.7949 37.0584 0.0497208 23.8731 0.0497208C10.6878 0.0497208 -0.000976562 10.7949 -0.000976562 24.0497C-0.000976562 37.3046 10.6878 48.0497 23.8731 48.0497Z", fill: "#0078E5" }),
20897
+ /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("path", { d: "M17.1307 14.7172C13.1687 14.7172 9.38102 18.1154 8.65114 22.34C8.38885 23.8488 8.5598 25.2581 9.06929 26.4451C6.20005 29.1677 1.77216 27.8721 -1.40212 26.1536C-2.73618 25.4317 -3.92695 27.4745 -2.59037 28.1981C1.33389 30.3226 6.86037 31.6621 10.4402 28.4188C11.4718 29.3859 12.867 29.9621 14.4894 29.9621C18.4161 29.9621 22.2038 26.5318 22.9337 22.34C23.6636 18.1162 21.0566 14.7172 17.1298 14.7172H17.1307ZM19.9798 22.34C19.5281 25.0399 17.2689 27.231 14.9754 27.231C12.6466 27.231 11.1877 25.0399 11.6394 22.34C12.1262 19.6401 14.3151 17.4482 16.6438 17.4482C18.9374 17.4482 20.4667 19.6401 19.9798 22.34Z", fill: "white" }),
20898
+ /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("path", { d: "M40.0017 27.0262C39.1797 27.081 38.2721 26.995 37.4668 26.7415C37.3344 26.6993 37.28 26.5401 37.3529 26.4205C37.5959 26.0212 37.8255 25.6152 38.009 25.1889C38.1071 24.9918 38.2018 24.793 38.2897 24.5908C38.3274 24.5041 38.4163 24.451 38.5101 24.4619C38.63 24.4754 38.7054 24.4821 38.8881 24.4821L39.1529 24.4796L39.8283 24.4543C45.9229 24.0492 50.4765 20.4319 54.8466 16.9014C56.0172 15.9554 57.6932 17.6208 56.5116 18.5752C51.7687 22.4065 47.1966 26.5081 40.9319 26.9689", fill: "white" }),
20899
+ /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("path", { d: "M37.9687 24.27C38.4472 23.1319 38.7656 21.9045 38.9609 20.6991C39.5927 16.76 38.5058 14.2193 36.1553 14.2193C34.1835 14.2193 33.1469 17.2427 32.9199 18.8694C32.743 19.9872 32.5914 22.1219 33.6028 23.9524C33.7553 24.259 34.15 24.7712 34.471 25.1259C34.5447 25.2067 34.6746 25.2 34.7349 25.1082C34.9528 24.7788 35.1615 24.4039 35.3584 24.0257C35.5444 23.6677 35.5888 23.6138 35.8587 23.0207C35.8838 22.966 35.8813 22.9002 35.8478 22.8505C35.5888 22.4597 35.2168 21.9787 35.1204 21.4614C34.9184 20.4455 34.9436 19.2257 35.2218 18.1078C35.4413 17.3118 35.7195 16.7844 35.9039 16.5106C35.9466 16.4466 36.0279 16.4129 36.0975 16.4449C36.369 16.5671 36.5827 16.8838 36.7385 17.396C37.0167 18.2089 37.0167 19.3782 36.814 20.6999C36.6991 21.5271 36.4771 22.3729 36.1746 23.1673C36.1293 23.308 36.0757 23.4461 36.0187 23.5826C36.0187 23.5868 36.0187 23.591 36.0187 23.5961C35.9911 23.6946 35.9207 23.8067 35.8846 23.901C35.5536 24.5497 35.2344 25.1697 34.8439 25.7838C34.8388 25.7863 34.8346 25.7914 34.8296 25.7931C34.6528 26.0525 34.4718 26.2901 34.2866 26.4965C34.2774 26.5099 34.2682 26.5234 34.2589 26.5369C34.2405 26.5638 34.2179 26.5815 34.1944 26.595C33.5064 27.3212 32.7665 27.6893 32.0123 27.6893C31.8606 27.6893 31.6838 27.664 31.507 27.4349C31.3042 27.1299 30.85 26.0879 31.2539 22.9112C31.4785 21.3949 31.8011 20.0268 31.931 19.5408C31.9579 19.4413 31.8908 19.3419 31.7886 19.3293L30.0062 19.1162C29.9241 19.1061 29.8478 19.1566 29.8252 19.2366C29.2704 21.1615 27.0305 27.6885 24.9599 27.6885C24.328 27.6885 24.1512 26.6211 24.1001 26.2909C23.775 23.6264 25.528 18.5492 29.5302 16.2267C29.6048 16.1838 29.635 16.0928 29.5998 16.0136L28.9042 14.467C28.8632 14.3752 28.7501 14.3389 28.6638 14.3886C25.8079 16.0414 24.1847 18.5231 23.2915 20.3436C22.2046 22.5793 21.6993 25.0189 21.9515 26.8738C22.1795 28.7794 23.1398 29.872 24.6054 29.872C26.0543 29.872 27.4369 28.9066 28.7098 27.0187C28.7953 26.8915 28.9889 26.9311 29.0149 27.0819C29.2646 28.5283 29.9811 29.872 31.6579 29.872C33.5282 29.872 35.3232 28.7288 36.7134 26.6447C36.7712 26.5874 36.7972 26.5411 36.8181 26.4906C36.8232 26.4931 36.8282 26.4948 36.8332 26.4973C37.01 26.2025 37.181 25.9051 37.3444 25.6027C37.4508 25.4005 37.5572 25.1992 37.6586 24.9945C37.7181 24.874 37.7743 24.7527 37.8262 24.6289C37.838 24.6002 37.8547 24.5665 37.8706 24.5337", fill: "white" }),
20900
+ /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("path", { d: "M30.5839 31.6397C25.7546 34.8577 19.4773 34.7853 14.6907 31.5243C13.5368 30.7384 12.5044 32.6498 13.6474 33.4281C19.034 37.0985 26.3077 37.096 31.7218 33.488C32.8791 32.7172 31.7478 30.8639 30.5839 31.6397Z", fill: "white" })
19110
20901
  ] })
19111
20902
  ]
19112
20903
  }
@@ -19143,7 +20934,7 @@ var OHW_BADGE_LABEL_STYLE = {
19143
20934
  whiteSpace: "nowrap"
19144
20935
  };
19145
20936
  function MadeWithOhhWells() {
19146
- return /* @__PURE__ */ (0, import_jsx_runtime35.jsxs)(
20937
+ return /* @__PURE__ */ (0, import_jsx_runtime36.jsxs)(
19147
20938
  "a",
19148
20939
  {
19149
20940
  href: "https://ohhwells.com",
@@ -19153,8 +20944,8 @@ function MadeWithOhhWells() {
19153
20944
  "data-ohw-badge": "",
19154
20945
  style: OHW_BADGE_STYLE,
19155
20946
  children: [
19156
- /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(OhwBrandMark, {}),
19157
- /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("span", { style: OHW_BADGE_LABEL_STYLE, children: "Made with OhhWells" })
20947
+ /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(OhwBrandMark, {}),
20948
+ /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("span", { style: OHW_BADGE_LABEL_STYLE, children: "Made with OhhWells" })
19158
20949
  ]
19159
20950
  }
19160
20951
  );
@@ -19641,13 +21432,6 @@ function OhhwellsBridge() {
19641
21432
  const [isItemDragging, setIsItemDragging] = (0, import_react19.useState)(false);
19642
21433
  const [isFooterFrameSelection, setIsFooterFrameSelection] = (0, import_react19.useState)(false);
19643
21434
  isFooterFrameSelectionRef.current = isFooterFrameSelection;
19644
- const [floatingPanel, setFloatingPanel] = (0, import_react19.useState)(null);
19645
- const floatingPanelOpenRef = (0, import_react19.useRef)(false);
19646
- floatingPanelOpenRef.current = floatingPanel !== null;
19647
- const [floatingPanelPos, setFloatingPanelPos] = (0, import_react19.useState)(null);
19648
- const [logoSizeDraft, setLogoSizeDraft] = (0, import_react19.useState)(null);
19649
- const [editorViewport, setEditorViewport] = (0, import_react19.useState)("desktop");
19650
- const [parentScrollSnap, setParentScrollSnap] = (0, import_react19.useState)(null);
19651
21435
  const [navDropdownPreviewOpen, setNavDropdownPreviewOpen] = (0, import_react19.useState)(null);
19652
21436
  const [footerHeadingVisible, setFooterHeadingVisible] = (0, import_react19.useState)(null);
19653
21437
  const footerDragRef = (0, import_react19.useRef)(null);
@@ -19671,6 +21455,21 @@ function OhhwellsBridge() {
19671
21455
  const stylesRef = (0, import_react19.useRef)("");
19672
21456
  const appliedDrawerRef = (0, import_react19.useRef)(null);
19673
21457
  const pendingDeleteUndoRef = (0, import_react19.useRef)(null);
21458
+ const [floatingPanel, setFloatingPanel] = (0, import_react19.useState)(null);
21459
+ const floatingPanelOpenRef = (0, import_react19.useRef)(false);
21460
+ const floatingPanelRef = (0, import_react19.useRef)(null);
21461
+ floatingPanelRef.current = floatingPanel;
21462
+ const setFloatingPanelRef = (0, import_react19.useRef)(setFloatingPanel);
21463
+ const [floatingPanelPos, setFloatingPanelPos] = (0, import_react19.useState)(null);
21464
+ const buttonCollectionDragRef = (0, import_react19.useRef)(null);
21465
+ const buttonDropInsertIndexRef = (0, import_react19.useRef)(null);
21466
+ const [buttonDropSlots, setButtonDropSlots] = (0, import_react19.useState)([]);
21467
+ const [activeButtonDropIndex, setActiveButtonDropIndex] = (0, import_react19.useState)(null);
21468
+ const buttonPointerDragRef = (0, import_react19.useRef)(null);
21469
+ const [buttonStyleTick, setButtonStyleTick] = (0, import_react19.useState)(0);
21470
+ const [logoSizeDraft, setLogoSizeDraft] = (0, import_react19.useState)(null);
21471
+ const [editorViewport, setEditorViewport] = (0, import_react19.useState)("desktop");
21472
+ const [parentScrollSnap, setParentScrollSnap] = (0, import_react19.useState)(null);
19674
21473
  const [sitePages, setSitePages] = (0, import_react19.useState)([]);
19675
21474
  const [sectionsByPath, setSectionsByPath] = (0, import_react19.useState)({});
19676
21475
  const sectionsPrefetchGenRef = (0, import_react19.useRef)(0);
@@ -19679,7 +21478,18 @@ function OhhwellsBridge() {
19679
21478
  const linkPopoverOpenRef = (0, import_react19.useRef)(false);
19680
21479
  const linkPopoverGraceUntilRef = (0, import_react19.useRef)(0);
19681
21480
  setLinkPopoverRef.current = setLinkPopover;
21481
+ setFloatingPanelRef.current = setFloatingPanel;
19682
21482
  linkPopoverSessionRef.current = linkPopover;
21483
+ floatingPanelOpenRef.current = Boolean(floatingPanel);
21484
+ (0, import_react19.useEffect)(() => {
21485
+ const syncViewport = () => {
21486
+ const next = window.innerWidth <= 480 ? "mobile" : "desktop";
21487
+ setEditorViewport((prev) => prev === next ? prev : next);
21488
+ };
21489
+ syncViewport();
21490
+ window.addEventListener("resize", syncViewport);
21491
+ return () => window.removeEventListener("resize", syncViewport);
21492
+ }, []);
19683
21493
  (0, import_react19.useEffect)(() => {
19684
21494
  setSectionOrderPersistHandler((entries) => {
19685
21495
  const currentPath = window.location.pathname;
@@ -19945,6 +21755,7 @@ function OhhwellsBridge() {
19945
21755
  hoveredItemElRef.current = null;
19946
21756
  setHoveredItemRect(null);
19947
21757
  setFloatingPanel(null);
21758
+ setFloatingPanelPos(null);
19948
21759
  setLogoSizeDraft(null);
19949
21760
  if (!activeElRef.current) {
19950
21761
  if (nestedGroup?.isConnected) {
@@ -20545,6 +22356,47 @@ function OhhwellsBridge() {
20545
22356
  },
20546
22357
  [beginFooterDrag]
20547
22358
  );
22359
+ const persistButtonCollectionDrag = (0, import_react19.useCallback)(
22360
+ (button, clientX) => {
22361
+ const insertIndex = buttonDropInsertIndexRef.current;
22362
+ if (insertIndex != null) insertButtonAtCollectionIndex(button, insertIndex);
22363
+ else if (typeof clientX === "number" && clientX !== 0) reorderButtonInCollection(button, clientX);
22364
+ buttonDropInsertIndexRef.current = null;
22365
+ const collection = buttonCollectionOf(button);
22366
+ if (collection) {
22367
+ const persisted = persistCollectionOrder(collection, editContentRef.current);
22368
+ if (persisted) {
22369
+ editContentRef.current = persisted.content;
22370
+ postToParent2({ type: "ow:change", nodes: persisted.nodes });
22371
+ }
22372
+ }
22373
+ buttonCollectionDragRef.current = null;
22374
+ buttonPointerDragRef.current = null;
22375
+ setButtonDropSlots([]);
22376
+ setActiveButtonDropIndex(null);
22377
+ setIsItemDragging(false);
22378
+ unlockFooterDragInteraction();
22379
+ if (button.isConnected) setToolbarRect(button.getBoundingClientRect());
22380
+ },
22381
+ [postToParent2]
22382
+ );
22383
+ const refreshButtonDragVisuals = (0, import_react19.useCallback)((button, clientX, clientY) => {
22384
+ const collection = buttonCollectionOf(button);
22385
+ if (!collection) {
22386
+ setButtonDropSlots([]);
22387
+ setActiveButtonDropIndex(null);
22388
+ buttonDropInsertIndexRef.current = null;
22389
+ return;
22390
+ }
22391
+ const slots = buildButtonCollectionDropSlots(collection);
22392
+ const active = hitTestButtonCollectionDropSlot(collection, clientX, clientY);
22393
+ setButtonDropSlots(slots);
22394
+ const idx = active ? slots.findIndex((slot) => slot.insertIndex === active.insertIndex) : -1;
22395
+ setActiveButtonDropIndex(idx >= 0 ? idx : null);
22396
+ buttonDropInsertIndexRef.current = active?.insertIndex ?? null;
22397
+ setDraggedItemRect(button.getBoundingClientRect());
22398
+ if (selectedElRef.current === button) setToolbarRect(button.getBoundingClientRect());
22399
+ }, []);
20548
22400
  const handleItemDragStart = (0, import_react19.useCallback)(
20549
22401
  (e) => {
20550
22402
  const selected = selectedElRef.current;
@@ -20554,6 +22406,17 @@ function OhhwellsBridge() {
20554
22406
  }
20555
22407
  const clientX = e?.clientX ?? selected.getBoundingClientRect().left + 8;
20556
22408
  const clientY = e?.clientY ?? selected.getBoundingClientRect().top + 8;
22409
+ if (isCtaButton(selected) && collectionDragEnabled(selected)) {
22410
+ buttonCollectionDragRef.current = selected;
22411
+ setMediaHover(null);
22412
+ if (e?.dataTransfer) {
22413
+ e.dataTransfer.effectAllowed = "move";
22414
+ e.dataTransfer.setData("text/plain", buttonId(selected) ?? "button");
22415
+ }
22416
+ setIsItemDragging(true);
22417
+ refreshButtonDragVisuals(selected, clientX, clientY);
22418
+ return;
22419
+ }
20557
22420
  if (startFooterLinkDrag(selected, clientX, clientY, true)) return;
20558
22421
  if (selected.hasAttribute("data-ohw-footer-col") || isInferredFooterGroup2(selected) && selected.closest("footer")) {
20559
22422
  if (startFooterColumnDrag(selected, clientX, clientY, true)) return;
@@ -20563,10 +22426,15 @@ function OhhwellsBridge() {
20563
22426
  setSiblingHintRect(null);
20564
22427
  setIsItemDragging(true);
20565
22428
  },
20566
- [startFooterColumnDrag, startFooterLinkDrag, startNavLinkDrag]
22429
+ [startFooterColumnDrag, startFooterLinkDrag, startNavLinkDrag, refreshButtonDragVisuals]
20567
22430
  );
20568
22431
  const handleItemDragEnd = (0, import_react19.useCallback)(
20569
22432
  (e) => {
22433
+ const collectionButton = buttonCollectionDragRef.current;
22434
+ if (collectionButton) {
22435
+ persistButtonCollectionDrag(collectionButton, e?.clientX);
22436
+ return;
22437
+ }
20570
22438
  if (footerDragRef.current) {
20571
22439
  const x = e?.clientX;
20572
22440
  const y = e?.clientY;
@@ -20589,12 +22457,22 @@ function OhhwellsBridge() {
20589
22457
  }
20590
22458
  setIsItemDragging(false);
20591
22459
  },
20592
- [commitFooterDrag, commitNavDrag, navDragRef]
22460
+ [commitFooterDrag, commitNavDrag, navDragRef, persistButtonCollectionDrag]
20593
22461
  );
20594
22462
  const handleItemChromePointerDown = (0, import_react19.useCallback)((e) => {
20595
22463
  if (e.button !== 0) return;
20596
22464
  const selected = selectedElRef.current;
20597
22465
  if (!selected) return;
22466
+ if (isCtaButton(selected) && collectionDragEnabled(selected)) {
22467
+ buttonPointerDragRef.current = {
22468
+ el: selected,
22469
+ startX: e.clientX,
22470
+ startY: e.clientY,
22471
+ pointerId: e.pointerId,
22472
+ started: false
22473
+ };
22474
+ return;
22475
+ }
20598
22476
  const hrefKey = selected.getAttribute("data-ohw-href-key");
20599
22477
  if (hrefKey && isFooterHrefKey(hrefKey)) {
20600
22478
  footerPointerDragRef.current = {
@@ -20673,10 +22551,29 @@ function OhhwellsBridge() {
20673
22551
  setIsFooterFrameSelection(false);
20674
22552
  setToolbarVariant("link-action");
20675
22553
  setToolbarRect(anchor.getBoundingClientRect());
22554
+ if (glowElRef.current) glowElRef.current.style.display = "";
20676
22555
  setToolbarShowEditLink(false);
20677
22556
  setActiveCommands(/* @__PURE__ */ new Set());
20678
- setFloatingPanel(null);
20679
- setLogoSizeDraft(null);
22557
+ const panel = floatingPanelRef.current;
22558
+ if (panel?.kind === "button-style" && isCtaButton(anchor)) {
22559
+ const id = buttonId(anchor);
22560
+ if (id && panel.buttonId !== id) {
22561
+ captureButtonOrigin(anchor);
22562
+ setFloatingPanelPos(null);
22563
+ setParentScrollSnap(parentScrollRef.current);
22564
+ setFloatingPanel({
22565
+ ...panel,
22566
+ key: `button-style:${id}`,
22567
+ buttonId: id,
22568
+ context: buttonStyleContext(anchor)
22569
+ });
22570
+ setButtonStyleTick((n) => n + 1);
22571
+ }
22572
+ } else {
22573
+ setFloatingPanel(null);
22574
+ setFloatingPanelPos(null);
22575
+ setLogoSizeDraft(null);
22576
+ }
20680
22577
  }, [deactivate, markSelected]);
20681
22578
  const selectFrame = (0, import_react19.useCallback)((el) => {
20682
22579
  if (!isNavigationContainer(el)) return;
@@ -20716,6 +22613,7 @@ function OhhwellsBridge() {
20716
22613
  setToolbarShowEditLink(false);
20717
22614
  setActiveCommands(/* @__PURE__ */ new Set());
20718
22615
  setFloatingPanel(null);
22616
+ setFloatingPanelPos(null);
20719
22617
  setLogoSizeDraft(null);
20720
22618
  }, [deactivate, markSelected, postToParent2]);
20721
22619
  const selectLogo = (0, import_react19.useCallback)(
@@ -20761,6 +22659,7 @@ function OhhwellsBridge() {
20761
22659
  });
20762
22660
  }, []);
20763
22661
  const openSocialsDisplayPanel = (0, import_react19.useCallback)((row) => {
22662
+ setFloatingPanelPos(null);
20764
22663
  setParentScrollSnap(parentScrollRef.current);
20765
22664
  setFloatingPanel({
20766
22665
  key: "socials-display",
@@ -20770,6 +22669,87 @@ function OhhwellsBridge() {
20770
22669
  row
20771
22670
  });
20772
22671
  }, []);
22672
+ const rememberButtonTokens = (0, import_react19.useCallback)(
22673
+ (content, tokens, nodes) => {
22674
+ if (nodes.length === 0) return content;
22675
+ const next = { ...content };
22676
+ for (const node of nodes) next[node.key] = node.text;
22677
+ editContentRef.current = next;
22678
+ postToParent2({ type: "ow:change", nodes });
22679
+ return next;
22680
+ },
22681
+ [postToParent2]
22682
+ );
22683
+ const tokensForButton = (0, import_react19.useCallback)(
22684
+ (el, persistFallback = false) => {
22685
+ const stored = readStoredStyleTokens(editContentRef.current);
22686
+ if (stored) return stored;
22687
+ const ensured = ensureButtonStyleTokens(document, editContentRef.current);
22688
+ if (ensured.tokens) {
22689
+ rememberButtonTokens(editContentRef.current, ensured.tokens, ensured.nodes);
22690
+ return ensured.tokens;
22691
+ }
22692
+ const fallback = tokensFromButton(el);
22693
+ if (persistFallback) rememberButtonTokens(editContentRef.current, fallback, styleTokenNodes(fallback));
22694
+ return fallback;
22695
+ },
22696
+ [rememberButtonTokens]
22697
+ );
22698
+ const openButtonStylePanel = (0, import_react19.useCallback)(
22699
+ (anchor) => {
22700
+ const id = buttonId(anchor);
22701
+ if (!id) return;
22702
+ captureButtonOrigin(anchor);
22703
+ tokensForButton(anchor);
22704
+ setFloatingPanelPos(null);
22705
+ setParentScrollSnap(parentScrollRef.current);
22706
+ setFloatingPanel({
22707
+ key: `button-style:${id}`,
22708
+ title: "Style",
22709
+ context: buttonStyleContext(anchor),
22710
+ kind: "button-style",
22711
+ buttonId: id
22712
+ });
22713
+ },
22714
+ [tokensForButton]
22715
+ );
22716
+ const applySelectedButtonStyle = (0, import_react19.useCallback)(
22717
+ (patch) => {
22718
+ const panel = floatingPanelRef.current;
22719
+ const id = panel?.kind === "button-style" ? panel.buttonId : null;
22720
+ const el = id ? document.querySelector(`[data-ohw-href-key="${CSS.escape(id)}"]`) : null;
22721
+ if (!el || !id || !isCtaButton(el)) return;
22722
+ const tokens = tokensFromButton(el);
22723
+ const next = { ...assignmentFor(el, editContentRef.current, tokens), ...patch };
22724
+ const assignments = withAssignment(editContentRef.current, id, next);
22725
+ const text = JSON.stringify(assignments);
22726
+ editContentRef.current = { ...editContentRef.current, [BUTTON_ASSIGNMENTS_KEY]: text };
22727
+ applyButtonStyle(el, next, tokens);
22728
+ postToParent2({ type: "ow:change", nodes: [{ key: BUTTON_ASSIGNMENTS_KEY, text }] });
22729
+ setButtonStyleTick((n) => n + 1);
22730
+ if (selectedElRef.current === el) setToolbarRect(el.getBoundingClientRect());
22731
+ },
22732
+ [postToParent2]
22733
+ );
22734
+ (0, import_react19.useEffect)(() => {
22735
+ const onDragOver = (e) => {
22736
+ const dragged = buttonCollectionDragRef.current;
22737
+ if (!dragged) return;
22738
+ e.preventDefault();
22739
+ if (e.dataTransfer) e.dataTransfer.dropEffect = "move";
22740
+ refreshButtonDragVisuals(dragged, e.clientX, e.clientY);
22741
+ };
22742
+ const onDrop = (e) => {
22743
+ if (!buttonCollectionDragRef.current) return;
22744
+ e.preventDefault();
22745
+ };
22746
+ document.addEventListener("dragover", onDragOver);
22747
+ document.addEventListener("drop", onDrop);
22748
+ return () => {
22749
+ document.removeEventListener("dragover", onDragOver);
22750
+ document.removeEventListener("drop", onDrop);
22751
+ };
22752
+ }, [refreshButtonDragVisuals]);
20773
22753
  const isEditModeRef = (0, import_react19.useRef)(false);
20774
22754
  const requestMissingSocialIconsRef = (0, import_react19.useRef)(() => {
20775
22755
  });
@@ -20812,11 +22792,13 @@ function OhhwellsBridge() {
20812
22792
  );
20813
22793
  const closeFloatingPanelOnly = (0, import_react19.useCallback)(() => {
20814
22794
  setFloatingPanel(null);
22795
+ setFloatingPanelPos(null);
20815
22796
  setLogoSizeDraft(null);
20816
22797
  }, []);
20817
22798
  closeFloatingPanelOnlyRef.current = closeFloatingPanelOnly;
20818
22799
  const closeFloatingPanelAndDeselect = (0, import_react19.useCallback)(() => {
20819
22800
  setFloatingPanel(null);
22801
+ setFloatingPanelPos(null);
20820
22802
  setLogoSizeDraft(null);
20821
22803
  deselectRef.current();
20822
22804
  }, []);
@@ -21025,6 +23007,7 @@ function OhhwellsBridge() {
21025
23007
  }
21026
23008
  if (typeof content[STYLE_STORE_KEY] === "string") {
21027
23009
  stylesRef.current = content[STYLE_STORE_KEY];
23010
+ document.querySelectorAll('[data-ohw-editable="form"]').forEach((f) => markFormFields(f));
21028
23011
  applyStylesToDom(parseStyleStore(content[STYLE_STORE_KEY]));
21029
23012
  }
21030
23013
  applyBrandChrome(content);
@@ -21033,12 +23016,12 @@ function OhhwellsBridge() {
21033
23016
  for (const [key, val] of Object.entries(content)) {
21034
23017
  if (key === "__ohw_sections") continue;
21035
23018
  if (key === AI_SECTIONS_KEY) continue;
23019
+ if (key === LOGO_PLACEHOLDER_KEY) continue;
23020
+ if (LOGO_IMAGE_KEYS.includes(key)) continue;
21036
23021
  if (key === BRAND_KIT_KEY) continue;
21037
23022
  if (key === STYLE_STORE_KEY) continue;
21038
23023
  if (BRAND_CHROME_KEYS.has(key)) continue;
21039
- if (key === LOGO_PLACEHOLDER_KEY) continue;
21040
23024
  if (key === CUSTOM_CODE_AFTER_HEAD_KEY || key === CUSTOM_CODE_BEFORE_HEAD_KEY) continue;
21041
- if (LOGO_IMAGE_KEYS.includes(key)) continue;
21042
23025
  if (applyVideoSettingNode(key, val)) continue;
21043
23026
  if (applyCarouselNode(key, val)) continue;
21044
23027
  document.querySelectorAll(`[data-ohw-key="${key}"]`).forEach((el) => {
@@ -21085,6 +23068,7 @@ function OhhwellsBridge() {
21085
23068
  reconcileSocialsFromContent(content);
21086
23069
  applySocialsDisplayFromContent(content);
21087
23070
  applySocialsLabelsFromContent(content, document, { fillEmptyOnly: isEditModeRef.current });
23071
+ syncButtonsFromContent(document, content);
21088
23072
  if (isEditModeRef.current) requestMissingSocialIconsRef.current();
21089
23073
  if (typeof content[STYLE_STORE_KEY] === "string") {
21090
23074
  applyStylesToDom(parseStyleStore(content[STYLE_STORE_KEY]));
@@ -21109,7 +23093,9 @@ function OhhwellsBridge() {
21109
23093
  let cancelled = false;
21110
23094
  setFetchState("loading");
21111
23095
  const apiUrl = process.env.NEXT_PUBLIC_FLOWOPS_API_URL ?? "https://flowops-backend-staging-2yfdh7pwpq-as.a.run.app";
21112
- fetch(`${apiUrl}/api/public/sites/${subdomain}/content`, { cache: "no-store" }).then((r2) => r2.ok ? r2.json() : null).then((data) => {
23096
+ const initialPath = pathname;
23097
+ fetchedContentPaths.add(`${subdomain}::${initialPath}`);
23098
+ fetch(`${apiUrl}/api/public/sites/${subdomain}/content?path=${encodeURIComponent(initialPath)}`, { cache: "no-store" }).then((r2) => r2.ok ? r2.json() : null).then((data) => {
21113
23099
  if (cancelled) return;
21114
23100
  const content = data?.content ?? {};
21115
23101
  const branding = Boolean(data?.showBranding);
@@ -21245,16 +23231,17 @@ function OhhwellsBridge() {
21245
23231
  applyAiSectionsToDom(parseAiSectionsState(content[AI_SECTIONS_KEY]));
21246
23232
  }
21247
23233
  if (typeof content[STYLE_STORE_KEY] === "string") {
23234
+ document.querySelectorAll('[data-ohw-editable="form"]').forEach((f) => markFormFields(f));
21248
23235
  applyStylesToDom(parseStyleStore(content[STYLE_STORE_KEY]));
21249
23236
  }
21250
23237
  for (const [key, val] of Object.entries(content)) {
21251
23238
  if (key === "__ohw_sections") continue;
21252
23239
  if (key === AI_SECTIONS_KEY) continue;
23240
+ if (key === LOGO_PLACEHOLDER_KEY) continue;
23241
+ if (LOGO_IMAGE_KEYS.includes(key)) continue;
21253
23242
  if (key === BRAND_KIT_KEY) continue;
21254
23243
  if (key === STYLE_STORE_KEY) continue;
21255
23244
  if (BRAND_CHROME_KEYS.has(key)) continue;
21256
- if (key === LOGO_PLACEHOLDER_KEY) continue;
21257
- if (LOGO_IMAGE_KEYS.includes(key)) continue;
21258
23245
  if (applyVideoSettingNode(key, val)) continue;
21259
23246
  if (applyCarouselNode(key, val)) continue;
21260
23247
  document.querySelectorAll(`[data-ohw-key="${key}"]`).forEach((el) => {
@@ -21285,6 +23272,7 @@ function OhhwellsBridge() {
21285
23272
  reconcileSocialsFromContent(content);
21286
23273
  applySocialsDisplayFromContent(content);
21287
23274
  applySocialsLabelsFromContent(content, document, { fillEmptyOnly: isEditModeRef.current });
23275
+ syncButtonsFromContent(document, content);
21288
23276
  if (isEditModeRef.current) requestMissingSocialIconsRef.current();
21289
23277
  if (typeof content[STYLE_STORE_KEY] === "string") {
21290
23278
  applyStylesToDom(parseStyleStore(content[STYLE_STORE_KEY]));
@@ -21305,6 +23293,17 @@ function OhhwellsBridge() {
21305
23293
  debounceTimer = setTimeout(applyFromCache, 150);
21306
23294
  };
21307
23295
  applyFromCache();
23296
+ const pathCacheKey = `${subdomain}::${pathname}`;
23297
+ if (!fetchedContentPaths.has(pathCacheKey)) {
23298
+ fetchedContentPaths.add(pathCacheKey);
23299
+ const apiUrl = process.env.NEXT_PUBLIC_FLOWOPS_API_URL ?? "https://flowops-backend-staging-2yfdh7pwpq-as.a.run.app";
23300
+ fetch(`${apiUrl}/api/public/sites/${subdomain}/content?path=${encodeURIComponent(pathname)}`, { cache: "no-store" }).then((r2) => r2.ok ? r2.json() : null).then((data) => {
23301
+ if (!data?.content) return;
23302
+ contentCache.set(subdomain, data.content);
23303
+ applyFromCache();
23304
+ }).catch(() => {
23305
+ });
23306
+ }
21308
23307
  observer = new MutationObserver(scheduleApply);
21309
23308
  observer.observe(document.body, { childList: true, subtree: true });
21310
23309
  return () => {
@@ -21373,7 +23372,10 @@ function OhhwellsBridge() {
21373
23372
  if (roots.length === 0) {
21374
23373
  reconcileNavbarItemsFromContent(contentForNav());
21375
23374
  reconcileFooterOrderFromContent(contentForNav());
21376
- if (isEditMode) syncNavigationDragCursorAttrs();
23375
+ if (isEditMode) {
23376
+ syncButtonsFromContent(document, contentForNav());
23377
+ syncNavigationDragCursorAttrs();
23378
+ }
21377
23379
  return;
21378
23380
  }
21379
23381
  let rafId = null;
@@ -21385,7 +23387,7 @@ function OhhwellsBridge() {
21385
23387
  }
21386
23388
  };
21387
23389
  const run = () => {
21388
- if (footerDragRef.current || navDragRef.current || applying) return;
23390
+ if (footerDragRef.current || navDragRef.current || buttonCollectionDragRef.current || applying) return;
21389
23391
  applying = true;
21390
23392
  observer?.disconnect();
21391
23393
  try {
@@ -21395,6 +23397,7 @@ function OhhwellsBridge() {
21395
23397
  reconcileSocialsFromContent(content);
21396
23398
  applySocialsDisplayFromContent(content);
21397
23399
  applySocialsLabelsFromContent(content, document, { fillEmptyOnly: isEditModeRef.current });
23400
+ syncButtonsFromContent(document, content);
21398
23401
  if (isEditModeRef.current) requestMissingSocialIconsRef.current();
21399
23402
  document.querySelectorAll("footer [data-ohw-href-key]").forEach((el) => {
21400
23403
  if (isFooterHrefKey(el.getAttribute("data-ohw-href-key")) || getSocialItem(el)) {
@@ -21402,9 +23405,9 @@ function OhhwellsBridge() {
21402
23405
  }
21403
23406
  });
21404
23407
  document.querySelectorAll(
21405
- "nav [data-ohw-href-key], [data-ohw-nav-container] [data-ohw-href-key], [data-ohw-nav-drawer] [data-ohw-href-key]"
23408
+ '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"]'
21406
23409
  ).forEach((el) => {
21407
- if (isNavbarHrefKey(el.getAttribute("data-ohw-href-key"))) {
23410
+ if (isNavbarHrefKey(el.getAttribute("data-ohw-href-key")) || isCtaButton(el)) {
21408
23411
  disableNativeHrefDrag(el);
21409
23412
  }
21410
23413
  });
@@ -21429,6 +23432,44 @@ function OhhwellsBridge() {
21429
23432
  observer?.disconnect();
21430
23433
  };
21431
23434
  }, [isEditMode, pathname, subdomain, fetchState, resyncSelectedNavigationItem]);
23435
+ (0, import_react19.useEffect)(() => {
23436
+ if (!isEditMode) return;
23437
+ let applying = false;
23438
+ let rafId = null;
23439
+ const run = () => {
23440
+ if (applying || buttonCollectionDragRef.current) return;
23441
+ const content = editContentRef.current;
23442
+ if (!content[BUTTON_COLLECTIONS_KEY] && !content[BUTTON_SNAPSHOTS_KEY]) return;
23443
+ applying = true;
23444
+ try {
23445
+ syncButtonsFromContent(document, content);
23446
+ } finally {
23447
+ applying = false;
23448
+ }
23449
+ };
23450
+ const schedule = () => {
23451
+ if (rafId != null) return;
23452
+ rafId = requestAnimationFrame(() => {
23453
+ rafId = null;
23454
+ run();
23455
+ });
23456
+ };
23457
+ const observer = new MutationObserver((mutations) => {
23458
+ if (mutations.every((m) => {
23459
+ const t = m.target;
23460
+ return t instanceof Element && Boolean(t.closest("[data-ohw-bridge], [data-ohw-bridge-root], [data-ohw-button-collection]"));
23461
+ })) {
23462
+ return;
23463
+ }
23464
+ schedule();
23465
+ });
23466
+ observer.observe(document.body, { childList: true, subtree: true });
23467
+ run();
23468
+ return () => {
23469
+ if (rafId != null) cancelAnimationFrame(rafId);
23470
+ observer.disconnect();
23471
+ };
23472
+ }, [isEditMode, pathname]);
21432
23473
  (0, import_react19.useEffect)(() => {
21433
23474
  if (!isEditMode) return;
21434
23475
  let lastPosted = 0;
@@ -21451,26 +23492,11 @@ function OhhwellsBridge() {
21451
23492
  const t2 = setTimeout(measure, 500);
21452
23493
  const ro = new ResizeObserver(schedule);
21453
23494
  ro.observe(document.body);
21454
- let lastWidth = window.innerWidth;
21455
- let resizeTimers = [];
21456
- const clearResizeTimers = () => {
21457
- resizeTimers.forEach(clearTimeout);
21458
- resizeTimers = [];
21459
- };
21460
- const handleResize = () => {
21461
- if (window.innerWidth === lastWidth) return;
21462
- lastWidth = window.innerWidth;
21463
- clearResizeTimers();
21464
- resizeTimers = HEIGHT_SETTLE_DELAYS.map((delay) => setTimeout(measure, delay));
21465
- };
21466
- window.addEventListener("resize", handleResize);
21467
23495
  return () => {
21468
23496
  clearTimeout(t1);
21469
23497
  clearTimeout(t2);
21470
23498
  if (raf != null) cancelAnimationFrame(raf);
21471
23499
  ro.disconnect();
21472
- clearResizeTimers();
21473
- window.removeEventListener("resize", handleResize);
21474
23500
  };
21475
23501
  }, [pathname, isEditMode, postToParent2]);
21476
23502
  (0, import_react19.useEffect)(() => {
@@ -21765,6 +23791,13 @@ function OhhwellsBridge() {
21765
23791
  if (target.closest("[data-ohw-state-toggle]")) return;
21766
23792
  if (target.closest("[data-ohw-max-badge]")) return;
21767
23793
  if (isInsideLinkEditor(target)) return;
23794
+ if (isInsideFloatingPanel(target)) return;
23795
+ if (target.closest("[data-ohw-form-toolbar]")) return;
23796
+ if (target.closest(
23797
+ '[data-ohw-field-toolbar], [data-ohw-field-type-picker], [data-radix-popper-content-wrapper], [role="menu"], [data-slot="dropdown-menu-content"]'
23798
+ )) {
23799
+ return;
23800
+ }
21768
23801
  if (selectedMediaElRef.current && !selectedMediaElRef.current.contains(target) && !target.closest("[data-ohw-media-overlay], [data-ohw-edit-chrome], [data-ohw-item-interaction]")) {
21769
23802
  clearMediaSelectionRef.current();
21770
23803
  }
@@ -21785,13 +23818,6 @@ function OhhwellsBridge() {
21785
23818
  return;
21786
23819
  }
21787
23820
  }
21788
- if (isInsideFloatingPanel(target)) return;
21789
- if (target.closest("[data-ohw-form-toolbar]")) return;
21790
- if (target.closest(
21791
- '[data-ohw-field-toolbar], [data-ohw-field-type-picker], [data-radix-popper-content-wrapper], [role="menu"], [data-slot="dropdown-menu-content"]'
21792
- )) {
21793
- return;
21794
- }
21795
23821
  {
21796
23822
  const formEl = getFormElement(target);
21797
23823
  const onSuccessText = target.closest(`[${SUCCESS_TEXT_ATTR}]`);
@@ -21955,14 +23981,6 @@ function OhhwellsBridge() {
21955
23981
  }
21956
23982
  const clickedButton = findClosestButtonLike(target);
21957
23983
  const buttonOnMedia = Boolean(clickedButton && isMediaEditable(editable) && editable.contains(clickedButton));
21958
- console.log("[click-debug]", {
21959
- editableType: editable.dataset.ohwEditable,
21960
- editableTag: editable.tagName,
21961
- targetTag: target.tagName,
21962
- clickedButtonTag: clickedButton?.tagName ?? null,
21963
- buttonOnMedia,
21964
- isMediaEditableEditable: isMediaEditable(editable)
21965
- });
21966
23984
  if (isMediaEditable(editable) && !buttonOnMedia) {
21967
23985
  e.preventDefault();
21968
23986
  e.stopPropagation();
@@ -21997,11 +24015,6 @@ function OhhwellsBridge() {
21997
24015
  const hrefLookupTarget = buttonOnMedia ? clickedButton : editable;
21998
24016
  const hrefCtx = getHrefKeyFromElement(hrefLookupTarget);
21999
24017
  const navAnchor = hrefCtx ? getNavigationItemAnchor(hrefCtx.anchor) : null;
22000
- console.log("[click-debug 2]", {
22001
- hrefLookupTargetTag: hrefLookupTarget.tagName,
22002
- hrefCtx: hrefCtx ? { key: hrefCtx.key } : null,
22003
- navAnchorTag: navAnchor?.tagName ?? null
22004
- });
22005
24018
  if (navAnchor) {
22006
24019
  e.preventDefault();
22007
24020
  e.stopPropagation();
@@ -22184,6 +24197,9 @@ function OhhwellsBridge() {
22184
24197
  setHoveredItemRect(null);
22185
24198
  hoveredNavContainerRef.current = null;
22186
24199
  setHoveredNavContainerRect(null);
24200
+ siblingHintElRef.current = null;
24201
+ setSiblingHintRect(null);
24202
+ setSiblingHintRects([]);
22187
24203
  return;
22188
24204
  }
22189
24205
  {
@@ -22304,7 +24320,6 @@ function OhhwellsBridge() {
22304
24320
  hoveredNavContainerRef.current = null;
22305
24321
  setHoveredNavContainerRect(null);
22306
24322
  hoveredItemElRef.current = editable;
22307
- setHoveredItemRect(isSelectedForHoverRef.current(editable) ? null : editable.getBoundingClientRect());
22308
24323
  }
22309
24324
  }
22310
24325
  }
@@ -22517,6 +24532,20 @@ function OhhwellsBridge() {
22517
24532
  }
22518
24533
  return;
22519
24534
  }
24535
+ const navbarCtaHit = findNavbarCtaAtPoint(x, y);
24536
+ if (navbarCtaHit) {
24537
+ hoveredNavContainerRef.current = null;
24538
+ setHoveredNavContainerRect(null);
24539
+ if (selected !== navbarCtaHit && !selected?.contains(navbarCtaHit)) {
24540
+ clearHrefKeyHover(navbarCtaHit);
24541
+ hoveredItemElRef.current = navbarCtaHit;
24542
+ setHoveredItemRect(navbarCtaHit.getBoundingClientRect());
24543
+ } else if (hoveredItemElRef.current === navbarCtaHit) {
24544
+ hoveredItemElRef.current = null;
24545
+ setHoveredItemRect(null);
24546
+ }
24547
+ return;
24548
+ }
22520
24549
  const navContainers = Array.from(
22521
24550
  document.querySelectorAll("[data-ohw-nav-container]")
22522
24551
  );
@@ -22608,7 +24637,7 @@ function OhhwellsBridge() {
22608
24637
  };
22609
24638
  const probeImageAt = (clientX, clientY, isDragOver = false, fromParentViewport = false) => {
22610
24639
  syncSelectedMediaPointerRelease(true);
22611
- if (linkPopoverOpenRef.current) {
24640
+ if (linkPopoverOpenRef.current || floatingPanelOpenRef.current && isPointOverFloatingPanel(clientX, clientY)) {
22612
24641
  if (hoveredImageRef.current) {
22613
24642
  hoveredImageRef.current = null;
22614
24643
  hoveredImageHasTextOverlapRef.current = false;
@@ -22619,6 +24648,24 @@ function OhhwellsBridge() {
22619
24648
  return;
22620
24649
  }
22621
24650
  const { x, y } = toProbeCoords(clientX, clientY, fromParentViewport);
24651
+ if (!isDragOver) {
24652
+ const buttonHit = findButtonAtPoint(x, y);
24653
+ if (buttonHit) {
24654
+ dismissImageHover();
24655
+ if (findNavbarCtaAtPoint(x, y)) {
24656
+ probeNavigationHoverAt(x, y);
24657
+ return;
24658
+ }
24659
+ document.querySelectorAll("[data-ohw-hovered]").forEach((el) => el.removeAttribute("data-ohw-hovered"));
24660
+ const selected = selectedElRef.current;
24661
+ if (selected !== buttonHit && !selected?.contains(buttonHit)) {
24662
+ clearHrefKeyHover(buttonHit);
24663
+ hoveredItemElRef.current = buttonHit;
24664
+ setHoveredItemRect(buttonHit.getBoundingClientRect());
24665
+ }
24666
+ return;
24667
+ }
24668
+ }
22622
24669
  if (isPointOverNavigation(x, y)) {
22623
24670
  dismissImageHover();
22624
24671
  probeNavigationHoverAt(x, y);
@@ -22982,7 +25029,11 @@ function OhhwellsBridge() {
22982
25029
  };
22983
25030
  const handleMouseMove = (e) => {
22984
25031
  const { clientX, clientY } = e;
22985
- if (pointOwnedByFloatingPanel(clientX, clientY)) return;
25032
+ if (pointOwnedByFloatingPanel(clientX, clientY)) {
25033
+ dismissImageHover();
25034
+ clearImageHover();
25035
+ return;
25036
+ }
22986
25037
  if (isNavDrawerOpen() && isPointOutsideOpenDrawer(clientX, clientY)) {
22987
25038
  document.querySelectorAll("[data-ohw-hovered]").forEach((el) => el.removeAttribute("data-ohw-hovered"));
22988
25039
  formHoverElRef.current = null;
@@ -23011,7 +25062,12 @@ function OhhwellsBridge() {
23011
25062
  setHoveredItemRect(null);
23012
25063
  hoveredNavContainerRef.current = null;
23013
25064
  setHoveredNavContainerRect(null);
25065
+ siblingHintElRef.current = null;
25066
+ setSiblingHintRect(null);
25067
+ setSiblingHintRects([]);
23014
25068
  dismissImageHover();
25069
+ clearImageHover();
25070
+ setSectionGap(null);
23015
25071
  return;
23016
25072
  }
23017
25073
  if (probeSocialsRowAt(clientX, clientY)) return;
@@ -23023,7 +25079,11 @@ function OhhwellsBridge() {
23023
25079
  if (e.data?.type !== "ow:pointer-sync") return;
23024
25080
  const { clientX, clientY } = e.data;
23025
25081
  if (typeof clientX !== "number" || typeof clientY !== "number") return;
23026
- if (pointOwnedByFloatingPanel(clientX, clientY)) return;
25082
+ if (pointOwnedByFloatingPanel(clientX, clientY)) {
25083
+ dismissImageHover();
25084
+ clearImageHover();
25085
+ return;
25086
+ }
23027
25087
  if (isNavDrawerOpen() && isPointOutsideOpenDrawer(clientX, clientY)) return;
23028
25088
  if (probeSocialsRowAt(clientX, clientY)) return;
23029
25089
  probeSectionGapAt(clientX, clientY);
@@ -23040,6 +25100,11 @@ function OhhwellsBridge() {
23040
25100
  return;
23041
25101
  }
23042
25102
  if (onNavDragOver(e)) return;
25103
+ if (buttonCollectionDragRef.current) {
25104
+ e.preventDefault();
25105
+ if (e.dataTransfer) e.dataTransfer.dropEffect = "move";
25106
+ return;
25107
+ }
23043
25108
  e.preventDefault();
23044
25109
  const el = findImageAtPoint(e.clientX, e.clientY, false);
23045
25110
  if (!el) {
@@ -23064,6 +25129,7 @@ function OhhwellsBridge() {
23064
25129
  };
23065
25130
  const handleDrop = (e) => {
23066
25131
  e.preventDefault();
25132
+ if (buttonCollectionDragRef.current || navDragRef.current || footerDragRef.current) return;
23067
25133
  const el = findImageAtPoint(e.clientX, e.clientY, false);
23068
25134
  if (!el) return;
23069
25135
  const file = e.dataTransfer?.files?.[0];
@@ -23363,6 +25429,7 @@ function OhhwellsBridge() {
23363
25429
  }
23364
25430
  if (typeof content[STYLE_STORE_KEY] === "string") {
23365
25431
  stylesRef.current = content[STYLE_STORE_KEY];
25432
+ document.querySelectorAll('[data-ohw-editable="form"]').forEach((f) => markFormFields(f));
23366
25433
  applyStylesToDom(parseStyleStore(content[STYLE_STORE_KEY]));
23367
25434
  }
23368
25435
  applyBrandChrome(content);
@@ -23375,12 +25442,12 @@ function OhhwellsBridge() {
23375
25442
  continue;
23376
25443
  }
23377
25444
  if (key === AI_SECTIONS_KEY) continue;
25445
+ if (key === LOGO_PLACEHOLDER_KEY) continue;
25446
+ if (LOGO_IMAGE_KEYS.includes(key) && !String(val ?? "").trim()) continue;
23378
25447
  if (key === BRAND_KIT_KEY) continue;
23379
25448
  if (key === STYLE_STORE_KEY) continue;
23380
25449
  if (BRAND_CHROME_KEYS.has(key)) continue;
23381
- if (key === LOGO_PLACEHOLDER_KEY) continue;
23382
25450
  if (key === CUSTOM_CODE_AFTER_HEAD_KEY || key === CUSTOM_CODE_BEFORE_HEAD_KEY) continue;
23383
- if (LOGO_IMAGE_KEYS.includes(key) && !String(val ?? "").trim()) continue;
23384
25451
  if (applyVideoSettingNode(key, val)) continue;
23385
25452
  if (applyCarouselNode(key, val)) continue;
23386
25453
  document.querySelectorAll(`[data-ohw-key="${key}"]`).forEach((el) => {
@@ -23419,6 +25486,7 @@ function OhhwellsBridge() {
23419
25486
  editContentRef.current = { ...editContentRef.current, ...content };
23420
25487
  reconcileNavbarItemsFromContent(editContentRef.current);
23421
25488
  reconcileFooterOrderFromContent(editContentRef.current);
25489
+ syncButtonsFromContent(document, editContentRef.current);
23422
25490
  syncNavigationDragCursorAttrs();
23423
25491
  if (typeof content[STYLE_STORE_KEY] === "string") {
23424
25492
  applyStylesToDom(parseStyleStore(content[STYLE_STORE_KEY]));
@@ -23825,6 +25893,12 @@ function OhhwellsBridge() {
23825
25893
  closeLinkPopoverRef.current();
23826
25894
  return;
23827
25895
  }
25896
+ if (floatingPanelOpenRef.current) {
25897
+ setFloatingPanelRef.current(null);
25898
+ deselectRef.current();
25899
+ deactivateRef.current();
25900
+ return;
25901
+ }
23828
25902
  deselectRef.current();
23829
25903
  deactivateRef.current();
23830
25904
  clearMediaSelectionRef.current();
@@ -24123,8 +26197,12 @@ function OhhwellsBridge() {
24123
26197
  if (inserted) {
24124
26198
  const tracker = getSectionsTracker();
24125
26199
  postToParentRef.current({ type: "ow:change", nodes: [{ key: "__ohw_sections", text: tracker.textContent ?? "[]" }] });
24126
- const h = document.body.scrollHeight;
24127
- if (h > 50) postToParentRef.current({ type: "ow:height", height: h });
26200
+ const reportHeight = () => {
26201
+ const h = document.body.scrollHeight;
26202
+ if (h > 50) postToParentRef.current({ type: "ow:height", height: h });
26203
+ };
26204
+ reportHeight();
26205
+ setTimeout(reportHeight, 500);
24128
26206
  }
24129
26207
  };
24130
26208
  const handleSwitchSchedule = (e) => {
@@ -24328,11 +26406,8 @@ function OhhwellsBridge() {
24328
26406
  };
24329
26407
  const handleParentScroll = (e) => {
24330
26408
  if (e.data?.type !== "ow:parent-scroll") return;
24331
- const { iframeOffsetTop, headerH, canvasH } = e.data;
24332
- parentScrollRef.current = { iframeOffsetTop, headerH, canvasH };
24333
- if (floatingPanelOpenRef.current) {
24334
- setParentScrollSnap({ iframeOffsetTop, headerH, canvasH });
24335
- }
26409
+ const { iframeOffsetTop, headerH, canvasH, promptBarH = 0 } = e.data;
26410
+ parentScrollRef.current = { iframeOffsetTop, headerH, canvasH, promptBarH };
24336
26411
  if (visibleViewportRef.current) {
24337
26412
  applyVisibleViewport(visibleViewportRef.current, parentScrollRef.current);
24338
26413
  }
@@ -24556,10 +26631,10 @@ function OhhwellsBridge() {
24556
26631
  window.removeEventListener("message", handleDeleteSection);
24557
26632
  window.removeEventListener("message", handleDuplicateSection);
24558
26633
  window.removeEventListener("message", handleDeactivate);
24559
- document.documentElement.removeAttribute("data-ohw-panel-dragging");
24560
26634
  window.removeEventListener("message", handleToastAction);
24561
26635
  window.removeEventListener("message", handleFormCount);
24562
26636
  window.removeEventListener("message", handleUiEscape);
26637
+ document.documentElement.removeAttribute("data-ohw-panel-dragging");
24563
26638
  autoSaveTimers.current.forEach(clearTimeout);
24564
26639
  autoSaveTimers.current.clear();
24565
26640
  if (imageUnhoverTimerRef.current) clearTimeout(imageUnhoverTimerRef.current);
@@ -24722,6 +26797,97 @@ function OhhwellsBridge() {
24722
26797
  unlockFooterDragInteraction();
24723
26798
  };
24724
26799
  }, [isEditMode]);
26800
+ (0, import_react19.useEffect)(() => {
26801
+ if (!isEditMode) return;
26802
+ const THRESHOLD = 10;
26803
+ const onPointerDown = (e) => {
26804
+ if (e.button !== 0) return;
26805
+ if (buttonCollectionDragRef.current || navDragRef.current || footerDragRef.current) return;
26806
+ if (buttonPointerDragRef.current) return;
26807
+ const target = e.target;
26808
+ if (!target) return;
26809
+ if (target.closest(
26810
+ '[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]'
26811
+ )) {
26812
+ return;
26813
+ }
26814
+ const fromTarget = target.closest('[data-ohw-role="navbar-button"]');
26815
+ const anchor = (fromTarget && !fromTarget.closest("[data-ohw-bridge]") ? fromTarget : null) ?? findNavbarCtaAtPoint(e.clientX, e.clientY);
26816
+ if (!anchor || !collectionDragEnabled(anchor)) return;
26817
+ disableNativeHrefDrag(anchor);
26818
+ setMediaHover(null);
26819
+ buttonPointerDragRef.current = {
26820
+ el: anchor,
26821
+ startX: e.clientX,
26822
+ startY: e.clientY,
26823
+ pointerId: e.pointerId,
26824
+ started: false
26825
+ };
26826
+ };
26827
+ const onPointerMove = (e) => {
26828
+ const pending = buttonPointerDragRef.current;
26829
+ if (!pending) return;
26830
+ if (pending.started) {
26831
+ e.preventDefault();
26832
+ clearTextSelection();
26833
+ const dragged = buttonCollectionDragRef.current;
26834
+ if (!dragged) return;
26835
+ refreshButtonDragVisuals(dragged, e.clientX, e.clientY);
26836
+ return;
26837
+ }
26838
+ const dx = e.clientX - pending.startX;
26839
+ const dy = e.clientY - pending.startY;
26840
+ if (dx * dx + dy * dy < THRESHOLD * THRESHOLD) return;
26841
+ e.preventDefault();
26842
+ pending.started = true;
26843
+ armFooterPressDrag();
26844
+ clearTextSelection();
26845
+ try {
26846
+ document.body.setPointerCapture(pending.pointerId);
26847
+ } catch {
26848
+ }
26849
+ if (linkPopoverOpenRef.current) setLinkPopoverRef.current(null);
26850
+ if (activeElRef.current) deactivateRef.current();
26851
+ ensureNavbarButtonCollection(pending.el, editContentRef.current);
26852
+ buttonCollectionDragRef.current = pending.el;
26853
+ lockFooterDuringDrag();
26854
+ setIsItemDragging(true);
26855
+ refreshButtonDragVisuals(pending.el, e.clientX, e.clientY);
26856
+ };
26857
+ const endPointerDrag = (e) => {
26858
+ const pending = buttonPointerDragRef.current;
26859
+ buttonPointerDragRef.current = null;
26860
+ try {
26861
+ if (document.body.hasPointerCapture(e.pointerId)) {
26862
+ document.body.releasePointerCapture(e.pointerId);
26863
+ }
26864
+ } catch {
26865
+ }
26866
+ if (!pending) return;
26867
+ if (!pending.started) {
26868
+ unlockFooterDragInteraction();
26869
+ return;
26870
+ }
26871
+ suppressNextClickRef.current = true;
26872
+ suppressClickUntilRef.current = Date.now() + 500;
26873
+ persistButtonCollectionDrag(pending.el, e.clientX);
26874
+ };
26875
+ const blockSelectStart = (e) => {
26876
+ if (buttonCollectionDragRef.current || buttonPointerDragRef.current?.started) e.preventDefault();
26877
+ };
26878
+ document.addEventListener("pointerdown", onPointerDown, true);
26879
+ document.addEventListener("pointermove", onPointerMove, true);
26880
+ document.addEventListener("pointerup", endPointerDrag, true);
26881
+ document.addEventListener("pointercancel", endPointerDrag, true);
26882
+ document.addEventListener("selectstart", blockSelectStart, true);
26883
+ return () => {
26884
+ document.removeEventListener("pointerdown", onPointerDown, true);
26885
+ document.removeEventListener("pointermove", onPointerMove, true);
26886
+ document.removeEventListener("pointerup", endPointerDrag, true);
26887
+ document.removeEventListener("pointercancel", endPointerDrag, true);
26888
+ document.removeEventListener("selectstart", blockSelectStart, true);
26889
+ };
26890
+ }, [isEditMode, persistButtonCollectionDrag, refreshButtonDragVisuals]);
24725
26891
  (0, import_react19.useEffect)(() => {
24726
26892
  const handler = (e) => {
24727
26893
  if (e.data?.type !== "ow:request-schedule-config") return;
@@ -24762,7 +26928,7 @@ function OhhwellsBridge() {
24762
26928
  postToParent2({
24763
26929
  type: "ow:ready",
24764
26930
  version: "1",
24765
- bridgeVersion: "0.1.120",
26931
+ bridgeVersion: "0.1.122",
24766
26932
  path: pathname,
24767
26933
  nodes: collectEditableNodes(editContentRef.current),
24768
26934
  sections
@@ -24841,12 +27007,12 @@ function OhhwellsBridge() {
24841
27007
  return;
24842
27008
  }
24843
27009
  if (type !== "ow:logo-size-set") return;
24844
- const px = Number(e.data.px);
24845
- if (!Number.isFinite(px)) return;
24846
- const next = editorViewport === "mobile" ? { ...logoSizeDraft, mobilePx: px, mobileFollowing: false } : {
27010
+ const px2 = Number(e.data.px);
27011
+ if (!Number.isFinite(px2)) return;
27012
+ const next = editorViewport === "mobile" ? { ...logoSizeDraft, mobilePx: px2, mobileFollowing: false } : {
24847
27013
  ...logoSizeDraft,
24848
- desktopPx: px,
24849
- mobilePx: logoSizeDraft.mobileFollowing ? px : logoSizeDraft.mobilePx
27014
+ desktopPx: px2,
27015
+ mobilePx: logoSizeDraft.mobileFollowing ? px2 : logoSizeDraft.mobilePx
24850
27016
  };
24851
27017
  setLogoSizeDraft(next);
24852
27018
  persistLogoSizeDraft(session.placement, next);
@@ -24944,6 +27110,36 @@ function OhhwellsBridge() {
24944
27110
  const handleDuplicateSelected = (0, import_react19.useCallback)(() => {
24945
27111
  const selected = selectedElRef.current;
24946
27112
  if (!selected || !isNavigationItem2(selected)) return;
27113
+ if (isCtaButton(selected)) {
27114
+ const id = buttonId(selected);
27115
+ if (!id) return;
27116
+ const tokens = tokensForButton(selected, true);
27117
+ if (!readAssignments(editContentRef.current)[id]) {
27118
+ const assignment = assignmentFor(selected, editContentRef.current, tokens);
27119
+ const assignments = withAssignment(editContentRef.current, id, assignment);
27120
+ editContentRef.current = {
27121
+ ...editContentRef.current,
27122
+ [BUTTON_ASSIGNMENTS_KEY]: JSON.stringify(assignments)
27123
+ };
27124
+ applyButtonStyle(selected, assignment, tokens);
27125
+ }
27126
+ const result = duplicateButton(selected, editContentRef.current);
27127
+ if (!result) return;
27128
+ editContentRef.current = result.content;
27129
+ const copyHref = result.copy.getAttribute("data-ohw-href-key");
27130
+ const copyHrefVal = copyHref ? result.content[copyHref] : void 0;
27131
+ if (copyHref && copyHrefVal !== void 0) applyLinkByKey(copyHref, copyHrefVal);
27132
+ postToParent2({ type: "ow:change", nodes: result.nodes });
27133
+ enforceLinkHrefs();
27134
+ requestAnimationFrame(() => {
27135
+ selectRef.current(result.copy);
27136
+ requestAnimationFrame(() => {
27137
+ const el = selectedElRef.current;
27138
+ if (el?.isConnected) setToolbarRect(el.getBoundingClientRect());
27139
+ });
27140
+ });
27141
+ return;
27142
+ }
24947
27143
  const hrefKey = selected.getAttribute("data-ohw-href-key");
24948
27144
  if (!hrefKey) return;
24949
27145
  const social = getSocialItem(selected);
@@ -25073,7 +27269,7 @@ function OhhwellsBridge() {
25073
27269
  selectRef.current(result.anchor);
25074
27270
  });
25075
27271
  }
25076
- }, [postToParent2]);
27272
+ }, [postToParent2, tokensForButton]);
25077
27273
  const runPendingDeleteUndo = (0, import_react19.useCallback)(() => {
25078
27274
  const pending = pendingDeleteUndoRef.current;
25079
27275
  if (!pending) return false;
@@ -25085,6 +27281,38 @@ function OhhwellsBridge() {
25085
27281
  const handleDeleteSelected = (0, import_react19.useCallback)(() => {
25086
27282
  const selected = selectedElRef.current;
25087
27283
  if (!selected) return false;
27284
+ if (isCtaButton(selected)) {
27285
+ if (!canDeleteButton(selected)) return false;
27286
+ const previousContent = editContentRef.current;
27287
+ const result = deleteButton(selected, previousContent);
27288
+ if (!result) return false;
27289
+ editContentRef.current = result.content;
27290
+ postToParent2({ type: "ow:change", nodes: result.nodes });
27291
+ const actionId = typeof crypto !== "undefined" && "randomUUID" in crypto ? crypto.randomUUID() : `delete-${Date.now()}`;
27292
+ const restoreNodes = result.nodes.map((node) => ({
27293
+ key: node.key,
27294
+ text: previousContent[node.key] ?? ""
27295
+ }));
27296
+ pendingDeleteUndoRef.current = {
27297
+ actionId,
27298
+ restore: () => {
27299
+ result.undo();
27300
+ editContentRef.current = { ...editContentRef.current, ...Object.fromEntries(restoreNodes.map((node) => [node.key, node.text])) };
27301
+ postToParent2({ type: "ow:change", nodes: restoreNodes });
27302
+ syncButtonsFromContent(document, editContentRef.current);
27303
+ }
27304
+ };
27305
+ deselectRef.current();
27306
+ postToParent2({
27307
+ type: "ow:toast",
27308
+ title: "Button deleted",
27309
+ toastType: "success",
27310
+ actionLabel: "Undo",
27311
+ actionId,
27312
+ duration: 6e3
27313
+ });
27314
+ return true;
27315
+ }
25088
27316
  return deleteSelectedNavFooterItem({
25089
27317
  selected,
25090
27318
  isTextEditing: Boolean(activeElRef.current),
@@ -25222,14 +27450,14 @@ function OhhwellsBridge() {
25222
27450
  },
25223
27451
  [postToParent2]
25224
27452
  );
25225
- return /* @__PURE__ */ (0, import_jsx_runtime35.jsxs)(import_jsx_runtime35.Fragment, { children: [
25226
- /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("div", { id: "ohw-loader", suppressHydrationWarning: true, style: { ...OHW_LOADER_STYLE, display: "none" }, children: /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(OhwLoaderSpinner, {}) }),
25227
- /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("script", { suppressHydrationWarning: true, dangerouslySetInnerHTML: { __html: OHW_LOADER_PREHYDRATE_SCRIPT } }),
25228
- subdomain && !isEditMode && showBranding && /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(MadeWithOhhWells, {}),
27453
+ return /* @__PURE__ */ (0, import_jsx_runtime36.jsxs)(import_jsx_runtime36.Fragment, { children: [
27454
+ /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("div", { id: "ohw-loader", suppressHydrationWarning: true, style: { ...OHW_LOADER_STYLE, display: "none" }, children: /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(OhwLoaderSpinner, {}) }),
27455
+ /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("script", { suppressHydrationWarning: true, dangerouslySetInnerHTML: { __html: OHW_LOADER_PREHYDRATE_SCRIPT } }),
27456
+ subdomain && !isEditMode && showBranding && /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(MadeWithOhhWells, {}),
25229
27457
  bridgeRoot ? (0, import_react_dom5.createPortal)(
25230
- /* @__PURE__ */ (0, import_jsx_runtime35.jsxs)(import_jsx_runtime35.Fragment, { children: [
25231
- /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("div", { ref: attachVisibleViewport, "data-ohw-visible-viewport": "", "aria-hidden": true }),
25232
- isEditMode && /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(
27458
+ /* @__PURE__ */ (0, import_jsx_runtime36.jsxs)(import_jsx_runtime36.Fragment, { children: [
27459
+ /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("div", { ref: attachVisibleViewport, "data-ohw-visible-viewport": "", "aria-hidden": true }),
27460
+ isEditMode && /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(
25233
27461
  AiSectionOverlay,
25234
27462
  {
25235
27463
  postToParent: postToParent2,
@@ -25237,7 +27465,7 @@ function OhhwellsBridge() {
25237
27465
  onSelectionChange: handleAiSectionSelectionChange
25238
27466
  }
25239
27467
  ),
25240
- isEditMode && /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(
27468
+ isEditMode && /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(
25241
27469
  AiMediaStripOverlay,
25242
27470
  {
25243
27471
  postToParent: postToParent2,
@@ -25246,7 +27474,7 @@ function OhhwellsBridge() {
25246
27474
  clearInteractionChromeRef
25247
27475
  }
25248
27476
  ),
25249
- isEditMode && /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(
27477
+ isEditMode && /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(
25250
27478
  AiPricingCardOverlay,
25251
27479
  {
25252
27480
  postToParent: postToParent2,
@@ -25254,12 +27482,12 @@ function OhhwellsBridge() {
25254
27482
  applyTreeLocalRef
25255
27483
  }
25256
27484
  ),
25257
- isSectionDragging && sectionDropSlots.map((slot, i) => /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(
27485
+ isSectionDragging && sectionDropSlots.map((slot, i) => /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(
25258
27486
  "div",
25259
27487
  {
25260
27488
  className: "pointer-events-none fixed z-2147483646",
25261
27489
  style: { left: slot.left, top: slot.y, width: slot.width, height: 3, transform: "translateY(-50%)" },
25262
- children: /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(
27490
+ children: /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(
25263
27491
  DropIndicator,
25264
27492
  {
25265
27493
  direction: "horizontal",
@@ -25270,7 +27498,7 @@ function OhhwellsBridge() {
25270
27498
  },
25271
27499
  `section-drop-${slot.insertIndex}-${i}`
25272
27500
  )),
25273
- Object.entries(uploadingRects).map(([key, { rect, fadingOut }]) => /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(
27501
+ Object.entries(uploadingRects).map(([key, { rect, fadingOut }]) => /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(
25274
27502
  MediaOverlay,
25275
27503
  {
25276
27504
  hover: { key, rect, elementType: "image", isDragOver: false, hasTextOverlap: false },
@@ -25281,7 +27509,7 @@ function OhhwellsBridge() {
25281
27509
  },
25282
27510
  `uploading-${key}`
25283
27511
  )),
25284
- mediaHover && !(mediaHover.key in uploadingRects) && mediaHover.key !== selectedMedia?.key && /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(
27512
+ mediaHover && !(mediaHover.key in uploadingRects) && mediaHover.key !== selectedMedia?.key && /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(
25285
27513
  MediaOverlay,
25286
27514
  {
25287
27515
  hover: mediaHover,
@@ -25292,7 +27520,7 @@ function OhhwellsBridge() {
25292
27520
  onVideoSettingsChange: handleVideoSettingsChange
25293
27521
  }
25294
27522
  ),
25295
- selectedMedia && !(selectedMedia.key in uploadingRects) && !linkPopover && /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(
27523
+ selectedMedia && !(selectedMedia.key in uploadingRects) && !linkPopover && /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(
25296
27524
  MediaOverlay,
25297
27525
  {
25298
27526
  hover: selectedMedia,
@@ -25304,7 +27532,7 @@ function OhhwellsBridge() {
25304
27532
  onVideoSettingsChange: handleVideoSettingsChange
25305
27533
  }
25306
27534
  ),
25307
- carouselHover && !linkPopover && !isItemDragging && /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(
27535
+ carouselHover && !linkPopover && !isItemDragging && /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(
25308
27536
  CarouselOverlay,
25309
27537
  {
25310
27538
  hover: carouselHover,
@@ -25312,10 +27540,10 @@ function OhhwellsBridge() {
25312
27540
  cueOnly: !!carouselHover.fullBleedSectionId && carouselHover.fullBleedSectionId !== selectedAiSectionId
25313
27541
  }
25314
27542
  ),
25315
- siblingHintRect && !linkPopover && !isItemDragging && siblingHintRects.length === 0 && /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(ItemInteractionLayer, { rect: siblingHintRect, state: "sibling-hint" }),
25316
- siblingHintRects.length > 0 && !linkPopover && !isItemDragging && siblingHintRects.map((rect, i) => /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(ItemInteractionLayer, { rect, state: "sibling-hint" }, `sibling-hint-${i}`)),
25317
- isItemDragging && draggedItemRect && selectedElRef.current !== footerDragRef.current?.draggedEl && selectedElRef.current !== navDragRef.current?.draggedEl && /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(ItemInteractionLayer, { rect: draggedItemRect, state: "dragging" }),
25318
- isItemDragging && footerDropSlots.map((slot, i) => /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(
27543
+ siblingHintRect && !linkPopover && !isItemDragging && siblingHintRects.length === 0 && /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(ItemInteractionLayer, { rect: siblingHintRect, state: "sibling-hint" }),
27544
+ siblingHintRects.length > 0 && !linkPopover && !isItemDragging && siblingHintRects.map((rect, i) => /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(ItemInteractionLayer, { rect, state: "sibling-hint" }, `sibling-hint-${i}`)),
27545
+ isItemDragging && draggedItemRect && selectedElRef.current !== footerDragRef.current?.draggedEl && selectedElRef.current !== navDragRef.current?.draggedEl && selectedElRef.current !== buttonCollectionDragRef.current && /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(ItemInteractionLayer, { rect: draggedItemRect, state: "dragging" }),
27546
+ isItemDragging && footerDropSlots.map((slot, i) => /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(
25319
27547
  "div",
25320
27548
  {
25321
27549
  className: "pointer-events-none fixed z-2147483646",
@@ -25325,7 +27553,7 @@ function OhhwellsBridge() {
25325
27553
  width: slot.width,
25326
27554
  height: slot.height
25327
27555
  },
25328
- children: /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(
27556
+ children: /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(
25329
27557
  DropIndicator,
25330
27558
  {
25331
27559
  direction: slot.direction,
@@ -25336,7 +27564,7 @@ function OhhwellsBridge() {
25336
27564
  },
25337
27565
  `footer-drop-${slot.direction}-${slot.columnIndex}-${slot.insertIndex}-${i}`
25338
27566
  )),
25339
- isItemDragging && navDropSlots.map((slot, i) => /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(
27567
+ isItemDragging && navDropSlots.map((slot, i) => /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(
25340
27568
  "div",
25341
27569
  {
25342
27570
  className: "pointer-events-none fixed z-2147483646",
@@ -25346,7 +27574,7 @@ function OhhwellsBridge() {
25346
27574
  width: slot.width,
25347
27575
  height: slot.height
25348
27576
  },
25349
- children: /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(
27577
+ children: /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(
25350
27578
  DropIndicator,
25351
27579
  {
25352
27580
  direction: slot.direction,
@@ -25357,10 +27585,31 @@ function OhhwellsBridge() {
25357
27585
  },
25358
27586
  `nav-drop-${slot.direction}-${slot.parentId ?? "root"}-${slot.insertIndex}-${i}`
25359
27587
  )),
25360
- hoveredNavContainerRect && (toolbarVariant !== "select-frame" || isFooterFrameSelection) && !linkPopover && !isItemDragging && /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(ItemInteractionLayer, { rect: hoveredNavContainerRect, state: "hover" }),
25361
- hoveredItemRect && !linkPopover && !hoveredNavContainerRect && !isItemDragging && hoveredItemElRef.current !== selectedElRef.current && /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(ItemInteractionLayer, { rect: hoveredItemRect, state: "hover" }),
25362
- hoveredTextRect && !hoveredNavContainerRect && !hoveredItemRect && !isItemDragging && toolbarVariant !== "rich-text" && /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(ItemInteractionLayer, { rect: hoveredTextRect, state: "hover" }),
25363
- formPickRect && !isItemDragging && /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(
27588
+ isItemDragging && buttonDropSlots.map((slot, i) => /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(
27589
+ "div",
27590
+ {
27591
+ className: "pointer-events-none fixed z-2147483646",
27592
+ style: {
27593
+ left: slot.left,
27594
+ top: slot.top,
27595
+ width: slot.width,
27596
+ height: slot.height
27597
+ },
27598
+ children: /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(
27599
+ DropIndicator,
27600
+ {
27601
+ direction: slot.direction,
27602
+ state: activeButtonDropIndex === i ? "dragActive" : "dragIdle",
27603
+ className: "!h-full !w-full"
27604
+ }
27605
+ )
27606
+ },
27607
+ `button-drop-${slot.direction}-${slot.insertIndex}-${i}`
27608
+ )),
27609
+ hoveredNavContainerRect && (toolbarVariant !== "select-frame" || isFooterFrameSelection) && !linkPopover && !isItemDragging && /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(ItemInteractionLayer, { rect: hoveredNavContainerRect, state: "hover" }),
27610
+ hoveredItemRect && !linkPopover && !hoveredNavContainerRect && !isItemDragging && hoveredItemElRef.current !== selectedElRef.current && /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(ItemInteractionLayer, { rect: hoveredItemRect, state: "hover" }),
27611
+ hoveredTextRect && !hoveredNavContainerRect && !hoveredItemRect && !isItemDragging && toolbarVariant !== "rich-text" && /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(ItemInteractionLayer, { rect: hoveredTextRect, state: "hover" }),
27612
+ formPickRect && !isItemDragging && /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(
25364
27613
  ItemInteractionLayer,
25365
27614
  {
25366
27615
  rect: formPickRect,
@@ -25368,13 +27617,13 @@ function OhhwellsBridge() {
25368
27617
  itemDragSurface: false,
25369
27618
  toolbarAlign: "left",
25370
27619
  chromeGap: 24,
25371
- toolbar: fieldPickRect ? void 0 : /* @__PURE__ */ (0, import_jsx_runtime35.jsxs)(
27620
+ toolbar: fieldPickRect ? void 0 : /* @__PURE__ */ (0, import_jsx_runtime36.jsxs)(
25372
27621
  "div",
25373
27622
  {
25374
27623
  "data-ohw-form-toolbar": "",
25375
27624
  className: "pointer-events-auto flex items-center gap-0.5 whitespace-nowrap rounded-lg border border-border bg-background p-1 shadow-md",
25376
27625
  children: [
25377
- /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(
27626
+ /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(
25378
27627
  "button",
25379
27628
  {
25380
27629
  type: "button",
@@ -25382,11 +27631,11 @@ function OhhwellsBridge() {
25382
27631
  className: "flex h-7 w-7 items-center justify-center rounded-md text-foreground transition-colors hover:bg-muted/80",
25383
27632
  onClick: () => setFieldTypePickerOpen((open) => !open),
25384
27633
  "data-ohw-add-field": "",
25385
- children: /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(import_lucide_react19.Plus, { size: 15, "aria-hidden": true })
27634
+ children: /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(import_lucide_react20.Plus, { size: 15, "aria-hidden": true })
25386
27635
  }
25387
27636
  ),
25388
- /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("div", { className: "mx-0.5 h-5 w-px bg-border" }),
25389
- /* @__PURE__ */ (0, import_jsx_runtime35.jsxs)(
27637
+ /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("div", { className: "mx-0.5 h-5 w-px bg-border" }),
27638
+ /* @__PURE__ */ (0, import_jsx_runtime36.jsxs)(
25390
27639
  "button",
25391
27640
  {
25392
27641
  type: "button",
@@ -25402,11 +27651,11 @@ function OhhwellsBridge() {
25402
27651
  });
25403
27652
  },
25404
27653
  children: [
25405
- /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(import_lucide_react19.Settings, { size: 14, "aria-hidden": true }),
27654
+ /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(import_lucide_react20.Settings, { size: 14, "aria-hidden": true }),
25406
27655
  "Form settings",
25407
27656
  formPickCount ? (
25408
27657
  // Counter pill, per the design — not a text suffix.
25409
- /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(
27658
+ /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(
25410
27659
  "span",
25411
27660
  {
25412
27661
  "data-ohw-form-count": "",
@@ -25418,8 +27667,8 @@ function OhhwellsBridge() {
25418
27667
  ]
25419
27668
  }
25420
27669
  ),
25421
- /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("div", { className: "mx-0.5 h-5 w-px bg-border" }),
25422
- /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("div", { className: "flex items-center rounded-md bg-muted/70 p-0.5", children: ["default", "success"].map((state) => /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(
27670
+ /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("div", { className: "mx-0.5 h-5 w-px bg-border" }),
27671
+ /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("div", { className: "flex items-center rounded-md bg-muted/70 p-0.5", children: ["default", "success"].map((state) => /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(
25423
27672
  "button",
25424
27673
  {
25425
27674
  type: "button",
@@ -25450,7 +27699,7 @@ function OhhwellsBridge() {
25450
27699
  )
25451
27700
  }
25452
27701
  ),
25453
- formHoverRect && !isItemDragging && /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(
27702
+ formHoverRect && !isItemDragging && /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(
25454
27703
  ItemInteractionLayer,
25455
27704
  {
25456
27705
  rect: formHoverRect,
@@ -25458,7 +27707,7 @@ function OhhwellsBridge() {
25458
27707
  chromeGap: formHoverElRef.current && getFieldWrapper(formHoverElRef.current) ? 8 : 24
25459
27708
  }
25460
27709
  ),
25461
- fieldPickRect && fieldPickState && !isItemDragging && toolbarVariant !== "rich-text" && /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(
27710
+ fieldPickRect && fieldPickState && !isItemDragging && toolbarVariant !== "rich-text" && /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(
25462
27711
  ItemInteractionLayer,
25463
27712
  {
25464
27713
  rect: fieldPickRect,
@@ -25470,7 +27719,7 @@ function OhhwellsBridge() {
25470
27719
  dragHandleLabel: "Reorder field",
25471
27720
  onDragHandleDragStart: handleFieldDragStart,
25472
27721
  onDragHandleDragEnd: handleFieldDragEnd,
25473
- toolbar: /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(
27722
+ toolbar: /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(
25474
27723
  FormFieldToolbar,
25475
27724
  {
25476
27725
  type: fieldPickState.type,
@@ -25483,12 +27732,12 @@ function OhhwellsBridge() {
25483
27732
  )
25484
27733
  }
25485
27734
  ),
25486
- fieldDragging ? fieldDropSlots.map((slot, i) => /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(
27735
+ fieldDragging ? fieldDropSlots.map((slot, i) => /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(
25487
27736
  "div",
25488
27737
  {
25489
27738
  className: "pointer-events-none fixed z-[2147483644]",
25490
27739
  style: { top: slot.top, left: slot.left, width: slot.width },
25491
- children: /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(
27740
+ children: /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(
25492
27741
  DropIndicator,
25493
27742
  {
25494
27743
  direction: "horizontal",
@@ -25501,7 +27750,7 @@ function OhhwellsBridge() {
25501
27750
  )) : null,
25502
27751
  fieldTypePickerOpen && formPickRect ? (() => {
25503
27752
  const toolbar = document.querySelector("[data-ohw-form-toolbar]")?.getBoundingClientRect();
25504
- return /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(
27753
+ return /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(
25505
27754
  "div",
25506
27755
  {
25507
27756
  className: "pointer-events-none fixed z-[2147483645]",
@@ -25509,12 +27758,12 @@ function OhhwellsBridge() {
25509
27758
  top: toolbar ? toolbar.bottom + 6 : formPickRect.top + 16,
25510
27759
  left: toolbar ? toolbar.left : formPickRect.left + 24
25511
27760
  },
25512
- children: /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(FieldTypePicker, { onPick: handleAddField })
27761
+ children: /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(FieldTypePicker, { onPick: handleAddField })
25513
27762
  }
25514
27763
  );
25515
27764
  })() : null,
25516
- toolbarVariant === "select-frame" && toolbarRect && !linkPopover && !isItemDragging && !isFooterFrameSelection && selectedElRef.current && isNavbarLinksContainer2(selectedElRef.current) && /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(NavbarContainerChrome, { rect: toolbarRect, onAdd: handleAddTopLevelNavItem }),
25517
- toolbarVariant === "select-frame" && toolbarRect && !linkPopover && !isItemDragging && !isFooterFrameSelection && selectedElRef.current && isFooterLinksContainer(selectedElRef.current) && /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(
27765
+ toolbarVariant === "select-frame" && toolbarRect && !linkPopover && !isItemDragging && !isFooterFrameSelection && selectedElRef.current && isNavbarLinksContainer2(selectedElRef.current) && /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(NavbarContainerChrome, { rect: toolbarRect, onAdd: handleAddTopLevelNavItem }),
27766
+ toolbarVariant === "select-frame" && toolbarRect && !linkPopover && !isItemDragging && !isFooterFrameSelection && selectedElRef.current && isFooterLinksContainer(selectedElRef.current) && /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(
25518
27767
  FooterContainerChrome,
25519
27768
  {
25520
27769
  rect: toolbarRect,
@@ -25522,13 +27771,17 @@ function OhhwellsBridge() {
25522
27771
  addDisabled: !canAddFooterColumn()
25523
27772
  }
25524
27773
  ),
25525
- toolbarRect && !linkPopover && (toolbarVariant === "link-action" || toolbarVariant === "select-frame" || toolbarVariant === "logo") && /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(
27774
+ toolbarRect && !linkPopover && (toolbarVariant === "link-action" || toolbarVariant === "select-frame" || toolbarVariant === "logo") && /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(
25526
27775
  ItemInteractionLayer,
25527
27776
  {
25528
- rect: isItemDragging && draggedItemRect && (footerDragRef.current?.wasSelected || navDragRef.current?.wasSelected) ? draggedItemRect : toolbarRect,
27777
+ rect: isItemDragging && draggedItemRect && (footerDragRef.current?.wasSelected || navDragRef.current?.wasSelected || selectedElRef.current === buttonCollectionDragRef.current) ? draggedItemRect : toolbarRect,
25529
27778
  toolbarBelowRect: selectedElRef.current && isNavigationItem2(selectedElRef.current) ? getOpenNavDropdownPanelRect(selectedElRef.current) : null,
25530
27779
  toolbarBelowEl: selectedElRef.current && isNavigationItem2(selectedElRef.current) ? getOpenNavDropdownPanel(selectedElRef.current) : null,
25531
27780
  elRef: glowElRef,
27781
+ hidden: isRectOutsideClip(
27782
+ isItemDragging && draggedItemRect && (footerDragRef.current?.wasSelected || navDragRef.current?.wasSelected || selectedElRef.current === buttonCollectionDragRef.current) ? draggedItemRect : toolbarRect,
27783
+ getIframeVisibleClip(parentScrollRef.current)
27784
+ ),
25532
27785
  state: isItemDragging ? "dragging" : selectedElRef.current && isNavigationItem2(selectedElRef.current) && getOpenNavDropdownPanel(selectedElRef.current) ? "active-bottom" : resolveItemInteractionState(toolbarRect, parentScrollRef.current),
25533
27786
  showHandle: toolbarVariant === "link-action" && Boolean(reorderHrefKey) || toolbarVariant === "select-frame" && isFooterFrameSelection,
25534
27787
  dragDisabled: reorderDragDisabled,
@@ -25538,18 +27791,25 @@ function OhhwellsBridge() {
25538
27791
  onItemPointerDown: toolbarVariant === "logo" ? void 0 : handleItemChromePointerDown,
25539
27792
  onItemClick: toolbarVariant === "logo" ? void 0 : handleItemChromeClick,
25540
27793
  itemDragSurface: toolbarVariant !== "logo" && !isFooterFrameSelection && !selectedIsSocialsRow,
25541
- toolbar: toolbarVariant === "link-action" && !isItemDragging || toolbarVariant === "select-frame" && (isFooterFrameSelection || selectedIsSocialsRow) && !isItemDragging ? /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(
27794
+ toolbar: toolbarVariant === "link-action" && !isItemDragging || toolbarVariant === "select-frame" && (isFooterFrameSelection || selectedIsSocialsRow) && !isItemDragging ? /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(
25542
27795
  ItemActionToolbar,
25543
27796
  {
25544
27797
  onEditLink: openLinkPopoverForSelected,
25545
27798
  onStyle: () => {
25546
27799
  const row = selectedElRef.current;
25547
27800
  if (!row) return;
25548
- if (floatingPanel?.kind === "socials-display") closeFloatingPanelOnly();
27801
+ suppressNextClickRef.current = true;
27802
+ suppressClickUntilRef.current = Date.now() + 500;
27803
+ if (isCtaButton(row)) {
27804
+ if (floatingPanelRef.current?.kind === "button-style") closeFloatingPanelOnly();
27805
+ else openButtonStylePanel(row);
27806
+ return;
27807
+ }
27808
+ if (floatingPanelRef.current?.kind === "socials-display") closeFloatingPanelOnly();
25549
27809
  else openSocialsDisplayPanel(row);
25550
27810
  },
25551
- showStyle: selectedIsSocialsRow,
25552
- styleActive: floatingPanel?.kind === "socials-display",
27811
+ showStyle: selectedIsSocialsRow || selectedIsCta,
27812
+ styleActive: floatingPanel?.kind === "socials-display" || floatingPanel?.kind === "button-style",
25553
27813
  onAddItem: handleAddChildItem,
25554
27814
  onSelectParent: handleSelectParent,
25555
27815
  onDuplicate: handleDuplicateSelected,
@@ -25560,11 +27820,11 @@ function OhhwellsBridge() {
25560
27820
  })(),
25561
27821
  editLinkDisabled: false,
25562
27822
  moreDisabled: false,
25563
- deleteDisabled: selectedElRef.current !== null && (() => {
27823
+ deleteDisabled: selectedElRef.current !== null && isCtaButton(selectedElRef.current) && !canDeleteButton(selectedElRef.current) || selectedElRef.current !== null && (() => {
25564
27824
  const social = getSocialItem(selectedElRef.current);
25565
27825
  return social ? !canRemoveSocialItem(social) : false;
25566
27826
  })(),
25567
- duplicateDisabled: isFooterFrameSelection || selectedIsSocialsRow || selectedElRef.current !== null && (() => {
27827
+ duplicateDisabled: isFooterFrameSelection || selectedIsSocialsRow || selectedElRef.current !== null && isCtaButton(selectedElRef.current) && !canDuplicateButton(selectedElRef.current) || selectedElRef.current !== null && (() => {
25568
27828
  const social = getSocialItem(selectedElRef.current);
25569
27829
  const row = social ? findSocialsRow(social) : null;
25570
27830
  return row ? !canAddSocialItem(row) : false;
@@ -25573,7 +27833,7 @@ function OhhwellsBridge() {
25573
27833
  showAddItem: isFooterFrameSelection || selectedIsSocialsRow || !selectedIsCta && Boolean(
25574
27834
  selectedElRef.current && isNavbarHrefKey(selectedElRef.current.getAttribute("data-ohw-href-key")) && !isNestedNavChild(selectedElRef.current)
25575
27835
  ),
25576
- showMore: (!selectedIsCta || isFooterFrameSelection) && !selectedIsSocialsRow,
27836
+ showMore: !selectedIsSocialsRow,
25577
27837
  dropdownOpen: navDropdownPreviewOpen,
25578
27838
  onDropdownOpenChange: handleNavDropdownOpenChange,
25579
27839
  headingVisible: footerHeadingVisible,
@@ -25582,8 +27842,8 @@ function OhhwellsBridge() {
25582
27842
  ) : void 0
25583
27843
  }
25584
27844
  ),
25585
- toolbarRect && toolbarVariant === "rich-text" && !linkPopover && !isRectOutsideClip(toolbarRect, getIframeVisibleClip(parentScrollRef.current)) && /* @__PURE__ */ (0, import_jsx_runtime35.jsxs)(import_jsx_runtime35.Fragment, { children: [
25586
- /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(
27845
+ toolbarRect && toolbarVariant === "rich-text" && !linkPopover && !isRectOutsideClip(toolbarRect, getIframeVisibleClip(parentScrollRef.current)) && /* @__PURE__ */ (0, import_jsx_runtime36.jsxs)(import_jsx_runtime36.Fragment, { children: [
27846
+ /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(
25587
27847
  EditGlowChrome,
25588
27848
  {
25589
27849
  rect: toolbarRect,
@@ -25593,7 +27853,7 @@ function OhhwellsBridge() {
25593
27853
  hideHandle: isItemDragging
25594
27854
  }
25595
27855
  ),
25596
- /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(
27856
+ /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(
25597
27857
  FloatingToolbar,
25598
27858
  {
25599
27859
  rect: toolbarRect,
@@ -25606,7 +27866,7 @@ function OhhwellsBridge() {
25606
27866
  }
25607
27867
  )
25608
27868
  ] }),
25609
- maxBadge && /* @__PURE__ */ (0, import_jsx_runtime35.jsxs)(
27869
+ maxBadge && /* @__PURE__ */ (0, import_jsx_runtime36.jsxs)(
25610
27870
  "div",
25611
27871
  {
25612
27872
  "data-ohw-max-badge": "",
@@ -25632,7 +27892,7 @@ function OhhwellsBridge() {
25632
27892
  ]
25633
27893
  }
25634
27894
  ),
25635
- toggleState && !linkPopover && /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(
27895
+ toggleState && !linkPopover && /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(
25636
27896
  StateToggle,
25637
27897
  {
25638
27898
  rect: toggleState.rect,
@@ -25641,15 +27901,15 @@ function OhhwellsBridge() {
25641
27901
  onStateChange: handleStateChange
25642
27902
  }
25643
27903
  ),
25644
- sectionGap && !linkPopover && /* @__PURE__ */ (0, import_jsx_runtime35.jsxs)(
27904
+ sectionGap && !linkPopover && /* @__PURE__ */ (0, import_jsx_runtime36.jsxs)(
25645
27905
  "div",
25646
27906
  {
25647
27907
  "data-ohw-section-insert-line": "",
25648
27908
  className: "fixed left-0 w-full z-2147483646 flex items-center pointer-events-none",
25649
27909
  style: { top: sectionGap.y, transform: "translateY(-50%)" },
25650
27910
  children: [
25651
- /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("div", { className: "flex-1 bg-bridge-primary", style: { height: 3 } }),
25652
- /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(
27911
+ /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("div", { className: "flex-1 bg-bridge-primary", style: { height: 3 } }),
27912
+ /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(
25653
27913
  Badge,
25654
27914
  {
25655
27915
  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",
@@ -25666,11 +27926,11 @@ function OhhwellsBridge() {
25666
27926
  children: "Add Section"
25667
27927
  }
25668
27928
  ),
25669
- /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("div", { className: "flex-1 bg-bridge-primary", style: { height: 3 } })
27929
+ /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("div", { className: "flex-1 bg-bridge-primary", style: { height: 3 } })
25670
27930
  ]
25671
27931
  }
25672
27932
  ),
25673
- linkPopover && dialogPortalContainer ? /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(
27933
+ linkPopover && dialogPortalContainer ? /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(
25674
27934
  LinkPopover,
25675
27935
  {
25676
27936
  panelRef: linkPopoverPanelRef,
@@ -25687,7 +27947,7 @@ function OhhwellsBridge() {
25687
27947
  },
25688
27948
  linkPopover.key
25689
27949
  ) : null,
25690
- floatingPanel && floatingPanel.kind === "socials-display" ? /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(
27950
+ floatingPanel && floatingPanel.kind === "socials-display" ? /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(
25691
27951
  FloatingPanel,
25692
27952
  {
25693
27953
  open: true,
@@ -25697,7 +27957,7 @@ function OhhwellsBridge() {
25697
27957
  onPositionChange: setFloatingPanelPos,
25698
27958
  parentScroll: parentScrollSnap ?? parentScrollRef.current,
25699
27959
  onClose: closeFloatingPanelOnly,
25700
- children: /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(
27960
+ children: /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(
25701
27961
  SocialsDisplayPanel,
25702
27962
  {
25703
27963
  display: socialsDisplayFor(floatingPanel.row, editContentRef.current),
@@ -25708,12 +27968,106 @@ function OhhwellsBridge() {
25708
27968
  }
25709
27969
  )
25710
27970
  }
25711
- ) : null
27971
+ ) : null,
27972
+ floatingPanel && floatingPanel.kind === "button-style" ? (() => {
27973
+ const button = document.querySelector(
27974
+ `[data-ohw-href-key="${CSS.escape(floatingPanel.buttonId)}"]`
27975
+ );
27976
+ if (!button) return null;
27977
+ captureButtonOrigin(button);
27978
+ const tokens = tokensFromButton(button);
27979
+ const current = assignmentFor(button, editContentRef.current, tokens);
27980
+ void buttonStyleTick;
27981
+ return /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(
27982
+ FloatingPanel,
27983
+ {
27984
+ open: true,
27985
+ title: floatingPanel.title,
27986
+ context: floatingPanel.context,
27987
+ icon: /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(import_lucide_react20.Brush, { className: "size-4 shrink-0", "aria-hidden": true }),
27988
+ position: floatingPanelPos,
27989
+ onPositionChange: setFloatingPanelPos,
27990
+ parentScroll: parentScrollSnap ?? parentScrollRef.current,
27991
+ onClose: closeFloatingPanelOnly,
27992
+ bodyClassName: "gap-5",
27993
+ children: /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(
27994
+ ButtonStylePanel,
27995
+ {
27996
+ variants: tokens.variants,
27997
+ sizes: tokens.sizes,
27998
+ variant: current.variant,
27999
+ size: current.size,
28000
+ slotBackground: previewSlotBackground(button),
28001
+ previewLabel: (button.querySelector(
28002
+ '[data-ohw-editable="text"], [data-ohw-editable="plain"]'
28003
+ )?.textContent ?? button.textContent ?? "Book Now").trim() || "Book Now",
28004
+ onVariant: (variant) => applySelectedButtonStyle({ variant }),
28005
+ onSize: (size) => applySelectedButtonStyle({ size })
28006
+ },
28007
+ floatingPanel.key
28008
+ )
28009
+ },
28010
+ floatingPanel.key
28011
+ );
28012
+ })() : null
25712
28013
  ] }),
25713
28014
  bridgeRoot
25714
28015
  ) : null
25715
28016
  ] });
25716
28017
  }
28018
+
28019
+ // src/ui/EmptySection.tsx
28020
+ var import_link = __toESM(require("next/link"), 1);
28021
+ var import_jsx_runtime37 = require("react/jsx-runtime");
28022
+ function EmptySection({ title, homeHref = "/", eyebrowKey, titleKey, subtitleKey }) {
28023
+ return /* @__PURE__ */ (0, import_jsx_runtime37.jsxs)(import_jsx_runtime37.Fragment, { children: [
28024
+ /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(
28025
+ "p",
28026
+ {
28027
+ style: {
28028
+ fontFamily: "var(--brand-font-body)",
28029
+ fontSize: "0.75rem",
28030
+ fontWeight: 500,
28031
+ letterSpacing: "0.15em",
28032
+ textTransform: "uppercase",
28033
+ color: "var(--brand-accent)",
28034
+ marginBottom: "1.5rem"
28035
+ },
28036
+ children: /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(import_link.default, { href: homeHref, style: { color: "inherit" }, children: /* @__PURE__ */ (0, import_jsx_runtime37.jsx)("span", { ...eyebrowKey ? { "data-ohw-editable": "text", "data-ohw-key": eyebrowKey } : {}, children: "Home" }) })
28037
+ }
28038
+ ),
28039
+ /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(
28040
+ "h1",
28041
+ {
28042
+ style: {
28043
+ fontFamily: "var(--brand-font-heading)",
28044
+ fontSize: "clamp(2rem, 3.5vw, 2.75rem)",
28045
+ lineHeight: 1.1,
28046
+ letterSpacing: "-0.025em",
28047
+ color: "var(--brand-text)",
28048
+ marginBottom: "1rem"
28049
+ },
28050
+ ...titleKey ? { "data-ohw-editable": "text", "data-ohw-key": titleKey, "data-ohw-max-length": 80 } : {},
28051
+ children: title
28052
+ }
28053
+ ),
28054
+ /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(
28055
+ "p",
28056
+ {
28057
+ style: {
28058
+ fontFamily: "var(--brand-font-body)",
28059
+ fontSize: "1rem",
28060
+ lineHeight: 1.7,
28061
+ fontWeight: 300,
28062
+ color: "var(--brand-text-muted)",
28063
+ maxWidth: "340px"
28064
+ },
28065
+ ...subtitleKey ? { "data-ohw-editable": "text", "data-ohw-key": subtitleKey, "data-ohw-max-length": 160 } : {},
28066
+ children: "This page doesn't have any content yet."
28067
+ }
28068
+ )
28069
+ ] });
28070
+ }
25717
28071
  // Annotate the CommonJS export names for ESM import in node:
25718
28072
  0 && (module.exports = {
25719
28073
  AI_DEFAULT_BRAND,
@@ -25731,6 +28085,7 @@ function OhhwellsBridge() {
25731
28085
  DropdownMenuItem,
25732
28086
  DropdownMenuSeparator,
25733
28087
  DropdownMenuTrigger,
28088
+ EmptySection,
25734
28089
  ItemActionToolbar,
25735
28090
  ItemInteractionLayer,
25736
28091
  LinkEditorPanel,