@ohhwells/bridge 0.1.120 → 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,31 +650,84 @@ 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
 
697
+ // src/lib/custom-code.ts
698
+ var ALLOWED_HEAD_TAGS = /* @__PURE__ */ new Set(["style", "meta", "link", "script", "noscript", "base"]);
699
+ var CUSTOM_CODE_AFTER_HEAD_KEY = "__ohw_custom_code_after_head";
700
+ var CUSTOM_CODE_BEFORE_HEAD_KEY = "__ohw_custom_code_before_head";
701
+ function clearExisting(slot) {
702
+ document.querySelectorAll(`[data-ohw-custom-code="${slot}"]`).forEach((el) => el.remove());
703
+ }
704
+ function buildNodes(raw, slot) {
705
+ if (!raw || !raw.trim()) return [];
706
+ const parsed = new DOMParser().parseFromString(raw, "text/html");
707
+ const source = [...parsed.head.childNodes, ...parsed.body.childNodes];
708
+ const nodes = [];
709
+ for (const node of source) {
710
+ if (node.nodeType !== Node.ELEMENT_NODE) continue;
711
+ const el = node;
712
+ const tag = el.tagName.toLowerCase();
713
+ if (!ALLOWED_HEAD_TAGS.has(tag)) continue;
714
+ const built = document.createElement(tag);
715
+ for (const attr of Array.from(el.attributes)) built.setAttribute(attr.name, attr.value);
716
+ if (tag === "script" || tag === "style" || tag === "noscript") built.textContent = el.textContent;
717
+ built.dataset.ohwCustomCode = slot;
718
+ nodes.push(built);
719
+ }
720
+ return nodes;
721
+ }
722
+ function applyCustomCodeToHead(content) {
723
+ clearExisting("after");
724
+ clearExisting("before");
725
+ const afterNodes = buildNodes(content[CUSTOM_CODE_AFTER_HEAD_KEY], "after");
726
+ if (afterNodes.length > 0) document.head.prepend(...afterNodes);
727
+ const beforeNodes = buildNodes(content[CUSTOM_CODE_BEFORE_HEAD_KEY], "before");
728
+ if (beforeNodes.length > 0) document.head.append(...beforeNodes);
729
+ }
730
+
504
731
  // src/lib/section-styles.ts
505
732
  var STYLE_STORE_KEY = "__ohw_styles";
506
733
  var STYLE_SHEET_ID = "ohw-section-styles";
@@ -564,9 +791,9 @@ function styleSheetCss() {
564
791
  );
565
792
  }
566
793
  const pad = { tight: 40, balanced: 64, airy: 96 };
567
- for (const [spacing, px] of Object.entries(pad)) {
794
+ for (const [spacing, px2] of Object.entries(pad)) {
568
795
  rules.push(
569
- `[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; }`
570
797
  );
571
798
  }
572
799
  rules.push(
@@ -613,7 +840,6 @@ var NODE_PROPS = [
613
840
  "color",
614
841
  "font-family",
615
842
  "font-size",
616
- "background",
617
843
  "text-align",
618
844
  "justify-content",
619
845
  "align-items"
@@ -663,9 +889,6 @@ function clearNodeProps(root) {
663
889
  h.removeAttribute(NODE_WROTE_ATTR);
664
890
  }
665
891
  }
666
- function buttonSurfaceOf(el) {
667
- return el.closest("a, button") ?? el;
668
- }
669
892
  function alignSubjectOf(el) {
670
893
  const button = el.closest('[data-ohw-role="button"]');
671
894
  if (button?.parentElement) return button.parentElement;
@@ -783,18 +1006,6 @@ function applyStylesToDom(store) {
783
1006
  );
784
1007
  subject.setAttribute(NODE_WROTE_ATTR, "");
785
1008
  }
786
- if (override.buttonBackground !== void 0 || override.buttonText !== void 0) {
787
- const surface = buttonSurfaceOf(el);
788
- if (override.buttonBackground !== void 0) {
789
- saveInline(surface, "background");
790
- surface.style.setProperty("background", override.buttonBackground, "important");
791
- }
792
- if (override.buttonText !== void 0) {
793
- saveInline(surface, "color");
794
- surface.style.setProperty("color", override.buttonText, "important");
795
- }
796
- surface.setAttribute(NODE_WROTE_ATTR, "");
797
- }
798
1009
  }
799
1010
  }
800
1011
  for (const [sectionId, override] of Object.entries(store.sections)) {
@@ -1380,7 +1591,59 @@ var AI_MOBILE_CSS = [
1380
1591
  "[data-ai-grid]{grid-template-columns:repeat(2, 1fr) !important}",
1381
1592
  "}"
1382
1593
  ].join("");
1383
- 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
+ }
1384
1647
  const bs = ctx.buttonStyle;
1385
1648
  if (bs) {
1386
1649
  return {
@@ -1490,6 +1753,22 @@ function pricingCardEmphasis(featured, ctx, hasBg) {
1490
1753
  function textAttrs(ctx, path) {
1491
1754
  return ctx.keyFor ? { "data-ohw-key": ctx.keyFor(path), "data-ohw-editable": "text" } : {};
1492
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");
1493
1772
  var TRANSPARENT_PX = "data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7";
1494
1773
  function MediaBox({
1495
1774
  refValue,
@@ -1507,9 +1786,9 @@ function MediaBox({
1507
1786
  const url = refValue ? ctx.resolveMedia(refValue) : null;
1508
1787
  const isIcon = /^(lucide|simple):/.test(refValue);
1509
1788
  const aspectRatio = aspect && /^\d+:\d+$/.test(aspect) ? aspect.replace(":", " / ") : void 0;
1510
- const editAttrs = ctx.keyFor && editPath && !isIcon ? {
1789
+ const editAttrs = ctx.keyFor && editPath ? {
1511
1790
  "data-ohw-key": ctx.keyFor(editPath),
1512
- ...genericImageEdit ? { "data-ohw-editable": "image" } : {}
1791
+ ...isIcon ? { "data-ohw-editable": "icon" } : genericImageEdit ? { "data-ohw-editable": "image" } : {}
1513
1792
  } : {};
1514
1793
  if (isIcon) {
1515
1794
  const Icon = refValue.startsWith("lucide:") ? lucideByName(refValue.slice(7)) : null;
@@ -1517,6 +1796,7 @@ function MediaBox({
1517
1796
  "span",
1518
1797
  {
1519
1798
  "data-ai-icon": refValue,
1799
+ ...editAttrs,
1520
1800
  style: {
1521
1801
  display: "inline-flex",
1522
1802
  width: 48,
@@ -1576,12 +1856,16 @@ function ButtonEl({
1576
1856
  secondaryColor
1577
1857
  }) {
1578
1858
  const secondary = slots.variant === "secondary";
1859
+ const variant = secondary ? "secondary" : "primary";
1860
+ const tokenColorStyle = buttonColorStyle(ctx, variant);
1579
1861
  const href = str(slots.href);
1580
1862
  const linkAttrs = ctx.keyFor && path ? { "data-ohw-href-key": ctx.keyFor(`${path}.href`), "data-ohw-role": "button" } : {};
1581
1863
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1582
1864
  "a",
1583
1865
  {
1584
1866
  href: href || void 0,
1867
+ className: `btn btn--${variant}`,
1868
+ "data-ohw-btn-variant": variant,
1585
1869
  ...linkAttrs,
1586
1870
  style: {
1587
1871
  display: fullWidth ? "flex" : "inline-flex",
@@ -1591,13 +1875,13 @@ function ButtonEl({
1591
1875
  textDecoration: "none",
1592
1876
  cursor: "pointer",
1593
1877
  whiteSpace: "nowrap",
1594
- ...buttonShellStyle(ctx, fullWidth),
1595
- ...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 } : (
1596
1880
  // Off-band, prefer the template button's measured fill/label pair — a template may
1597
1881
  // fill its CTAs with any token (hvac: accent bg, primary text). On an accent band
1598
1882
  // the flipped palette keeps contrast, so the brand-driven colours stay.
1599
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) }
1600
- )
1884
+ ))
1601
1885
  },
1602
1886
  children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { ...path ? textAttrs(ctx, `${path}.label`) : {}, children: str(slots.label) })
1603
1887
  }
@@ -2571,7 +2855,7 @@ function CollectionBlock({ node, ctx, path }) {
2571
2855
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
2572
2856
  "div",
2573
2857
  {
2574
- "data-ai-grid": "",
2858
+ "data-ai-grid": String(itemsPerRow),
2575
2859
  "data-ohw-pricing-collection": isPricing ? "" : void 0,
2576
2860
  style: {
2577
2861
  display: "grid",
@@ -2819,18 +3103,21 @@ function renderNode(node, ctx, path) {
2819
3103
  "button",
2820
3104
  {
2821
3105
  type: "submit",
3106
+ className: "btn btn--primary",
3107
+ "data-ohw-btn-variant": "primary",
3108
+ "data-ohw-role": "button",
2822
3109
  style: {
2823
3110
  alignSelf: submitAlign,
2824
3111
  border: "none",
2825
3112
  cursor: "pointer",
2826
3113
  // Shape/padding/typography follow the host template's own buttons.
2827
- ...buttonShellStyle(ctx),
3114
+ ...buttonShellStyle(ctx, "primary"),
2828
3115
  // Brand-styled: the template button's measured fill/label pair off-band, else
2829
3116
  // primary fill with a brand-derived label so it reads on custom palettes.
2830
- ...!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 } : {
2831
3118
  background: ctx.brand.palette.primary,
2832
3119
  color: ctx.buttonLabel ?? primaryButtonLabel(ctx.brand)
2833
- }
3120
+ })
2834
3121
  },
2835
3122
  children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { ...textAttrs(ctx, `${path}.c${i}.label`), children: str(cs.label) })
2836
3123
  },
@@ -2927,6 +3214,7 @@ function AiTreeRenderer({
2927
3214
  {
2928
3215
  "data-ai-section": tree.tag ?? "",
2929
3216
  ...bgAttrs,
3217
+ "data-ai-responsive": "",
2930
3218
  style: {
2931
3219
  position: "relative",
2932
3220
  padding: `${pad}px 0`,
@@ -2937,12 +3225,13 @@ function AiTreeRenderer({
2937
3225
  color: settings.sectionBackground === "accent" ? blockBrand.palette.dark : void 0
2938
3226
  },
2939
3227
  children: [
2940
- 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 }),
2941
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})` } }),
2942
3231
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
2943
3232
  "div",
2944
3233
  {
2945
- "data-ai-container": "",
3234
+ "data-ai-section-inner": "",
2946
3235
  style: {
2947
3236
  position: "relative",
2948
3237
  maxWidth: AI_TREE_TOKENS.sectionMaxWidth,
@@ -2953,7 +3242,7 @@ function AiTreeRenderer({
2953
3242
  children: tree.rows.map((row, r2) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
2954
3243
  "div",
2955
3244
  {
2956
- "data-ai-row": "",
3245
+ "data-ai-columns": "",
2957
3246
  style: {
2958
3247
  display: "grid",
2959
3248
  gridTemplateColumns: "repeat(12, 1fr)",
@@ -3133,12 +3422,12 @@ function deriveTemplateButtonStyle() {
3133
3422
  cs.borderBottomLeftRadius
3134
3423
  ].map((v) => v || "0px");
3135
3424
  const radius = corners.every((v) => v === corners[0]) ? corners[0] : corners.join(" ");
3136
- const px = (v) => parseFloat(v) || 0;
3137
- const lineHeight = px(cs.lineHeight) || px(cs.fontSize) * 1.2;
3138
- 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);
3139
3428
  const impliedY = Math.round(Math.max(0, (contentH - lineHeight) / 2));
3140
- const padY = Math.max(px(cs.paddingTop), px(cs.paddingBottom), impliedY);
3141
- 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));
3142
3431
  return {
3143
3432
  radius: radius || "10px",
3144
3433
  padding: `${padY}px ${padX}px`,
@@ -3357,9 +3646,10 @@ function applyAiSectionsToDom(state, options) {
3357
3646
  if (existing && existing.serialized === serialized && existing.container.isConnected) {
3358
3647
  continue;
3359
3648
  }
3360
- const isNewMount = !existing || !existing.container.isConnected;
3361
3649
  let container = existing?.container;
3650
+ let isNewMount = false;
3362
3651
  if (!container || !container.isConnected) {
3652
+ isNewMount = true;
3363
3653
  existing?.root.unmount();
3364
3654
  container?.remove();
3365
3655
  container = document.createElement("div");
@@ -8055,6 +8345,14 @@ function ItemActionToolbar({
8055
8345
  e.preventDefault();
8056
8346
  e.stopPropagation();
8057
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();
8058
8356
  }
8059
8357
  },
8060
8358
  children: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_lucide_react3.Brush, { className: "size-4 shrink-0", "aria-hidden": true })
@@ -8377,20 +8675,32 @@ function ItemInteractionLayer({
8377
8675
  itemDragSurface = true,
8378
8676
  chromeGap,
8379
8677
  toolbarAlign = "center",
8678
+ hidden = false,
8380
8679
  className
8381
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]);
8382
8687
  if (state === "default") return null;
8383
8688
  const gap = chromeGap ?? (state === "hover" ? HOVER_CHROME_GAP : SELECTION_CHROME_GAP);
8384
8689
  const isActive = state === "active-top" || state === "active-bottom";
8385
8690
  const isDragging = state === "dragging";
8386
8691
  const showToolbar = isActive && toolbar;
8387
8692
  const showDragHandle = (isActive || isDragging) && showHandle;
8388
- const itemDragEnabled = itemDragSurface && isActive && showHandle && !isDragging && !dragDisabled;
8693
+ const itemDragEnabled = itemDragSurface && isActive && !isDragging && !dragDisabled && Boolean(showHandle || onItemPointerDown);
8389
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
+ };
8390
8700
  return /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(
8391
8701
  "div",
8392
8702
  {
8393
- ref: elRef,
8703
+ ref: setRootRef,
8394
8704
  "data-ohw-item-interaction": "",
8395
8705
  "data-ohw-item-interaction-state": state,
8396
8706
  className: cn("pointer-events-none", className),
@@ -8400,7 +8710,8 @@ function ItemInteractionLayer({
8400
8710
  left: rect.left - gap,
8401
8711
  width: rect.width + gap * 2,
8402
8712
  height: rect.height + gap * 2,
8403
- zIndex: getChromeZIndex(state)
8713
+ zIndex: getChromeZIndex(state),
8714
+ display: hidden ? "none" : "block"
8404
8715
  },
8405
8716
  children: [
8406
8717
  /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
@@ -9232,6 +9543,7 @@ function MediaOverlay({
9232
9543
  (prev) => prev && prev.fullWidth === width && prev.height === height ? prev : { fullWidth: width, height }
9233
9544
  );
9234
9545
  }, [isVideo]);
9546
+ const replaceLabel = isVideo ? "Replace video" : hover.elementType === "bg-image" ? "Replace background" : "Replace image";
9235
9547
  const replaceMode = buttonSize ? replaceButtonMode({ width: rect.width, height: rect.height }, buttonSize) : "none";
9236
9548
  const box = {
9237
9549
  position: "fixed",
@@ -9330,11 +9642,10 @@ function MediaOverlay({
9330
9642
  className: "flex items-center justify-center cursor-pointer",
9331
9643
  style: {
9332
9644
  ...box,
9333
- // When text overlays this media, let clicks fall THROUGH to the text underneath. In the
9334
- // parent this needed a whole `ow:click-at` round-trip to re-hit-test inside the iframe;
9335
- // in-document, pointer-events does it natively. The button below opts back in, so
9336
- // Replace still works.
9337
- 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",
9338
9649
  // Selected: a firm component ring with no wash, so the image reads as chosen rather
9339
9650
  // than hovered. Hover keeps the existing tinted preview.
9340
9651
  boxShadow: selected ? "inset 0 0 0 2px var(--color-bridge-primary)" : "inset 0 0 0 1.5px var(--color-bridge-primary)",
@@ -9361,17 +9672,17 @@ function MediaOverlay({
9361
9672
  },
9362
9673
  children: [
9363
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 }),
9364
- isVideo ? "Replace video" : "Replace image"
9675
+ replaceLabel
9365
9676
  ]
9366
9677
  }
9367
9678
  ),
9368
- replaceMode === "none" || cueOnly ? null : /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(
9679
+ showChrome && replaceMode !== "none" && /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(
9369
9680
  Button,
9370
9681
  {
9371
9682
  "data-ohw-media-overlay": "",
9372
9683
  variant: "outline",
9373
9684
  size: "sm",
9374
- "aria-label": isVideo ? "Replace video" : "Replace image",
9685
+ "aria-label": replaceLabel,
9375
9686
  className: "gap-1.5 cursor-pointer hover:bg-background",
9376
9687
  style: {
9377
9688
  ...OVERLAY_BUTTON_STYLE,
@@ -9394,7 +9705,7 @@ function MediaOverlay({
9394
9705
  },
9395
9706
  children: [
9396
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 }),
9397
- replaceMode === "full" ? isVideo ? "Replace video" : hover.elementType === "bg-image" ? "Replace background" : "Replace image" : null
9708
+ replaceMode === "full" ? replaceLabel : null
9398
9709
  ]
9399
9710
  }
9400
9711
  )
@@ -11221,6 +11532,13 @@ function findButtonAtPoint(x, y, media) {
11221
11532
  if (hits.length === 0) return null;
11222
11533
  return hits.reduce((best, el) => best.contains(el) ? el : best);
11223
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
+ }
11224
11542
 
11225
11543
  // src/lib/session-search.ts
11226
11544
  var STORAGE_KEY = "ohw-preserved-search";
@@ -11956,6 +12274,26 @@ var import_react12 = require("react");
11956
12274
  var import_react_dom3 = require("react-dom");
11957
12275
  var import_lucide_react15 = require("lucide-react");
11958
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
11959
12297
  var import_jsx_runtime27 = require("react/jsx-runtime");
11960
12298
  var DIM_OVERLAY = "rgba(0, 0, 0, 0.45)";
11961
12299
  function rectsEqual(a, b) {
@@ -12093,20 +12431,22 @@ function SectionPickerOverlay({
12093
12431
  []
12094
12432
  );
12095
12433
  (0, import_react12.useEffect)(() => {
12096
- const applyClip = (iframeOffsetTop, visibleCanvasTop, canvasH) => {
12097
- const top = Math.max(0, visibleCanvasTop - iframeOffsetTop);
12098
- const bottom = Math.min(
12099
- window.innerHeight,
12100
- visibleCanvasTop + canvasH - iframeOffsetTop
12101
- );
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;
12102
12442
  setChromeClip(
12103
- (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
12104
12444
  );
12105
12445
  };
12106
12446
  const onParentScroll = (e) => {
12107
12447
  if (e.data?.type !== "ow:parent-scroll") return;
12108
- const { iframeOffsetTop, headerH, canvasH } = e.data;
12109
- applyClip(iframeOffsetTop, headerH, canvasH);
12448
+ const { iframeOffsetTop, headerH, canvasH, promptBarH = 0 } = e.data;
12449
+ applyClip(iframeOffsetTop, headerH, canvasH, promptBarH);
12110
12450
  iframeOffsetTopRef.current = iframeOffsetTop;
12111
12451
  if (!pointerScreenRef.current && pointerRef.current) {
12112
12452
  pointerScreenRef.current = {
@@ -15699,6 +16039,7 @@ function readLogoSizeState(content, placement) {
15699
16039
  function getLogoElement(el) {
15700
16040
  const marked = el.closest('[data-ohw-role="logo"], [data-ohw-logo]');
15701
16041
  if (marked) return marked;
16042
+ if (el.closest('[data-ohw-editable="icon"]')) return null;
15702
16043
  const root = el.closest("nav, [data-ohw-nav-root], footer");
15703
16044
  if (!root) return null;
15704
16045
  const anchor = el.closest("a");
@@ -15770,36 +16111,50 @@ var import_lucide_react16 = require("lucide-react");
15770
16111
  var import_jsx_runtime29 = require("react/jsx-runtime");
15771
16112
  var PANEL_WIDTH = 256;
15772
16113
  var EDGE_MARGIN = 16;
16114
+ var HEADER_MIN = 56;
15773
16115
  function getVisibleClip(parentScroll) {
15774
16116
  const left = 0;
15775
16117
  const right = window.innerWidth;
15776
- if (!parentScroll) {
16118
+ const band = getIframeChromeClip(parentScroll);
16119
+ if (!band) {
15777
16120
  return { top: 0, bottom: window.innerHeight, left, right };
15778
16121
  }
15779
- const { iframeOffsetTop, headerH: visibleCanvasTop, canvasH } = parentScroll;
15780
- const top = Math.max(0, visibleCanvasTop - iframeOffsetTop);
15781
- const bottom = Math.min(window.innerHeight, visibleCanvasTop + canvasH - iframeOffsetTop);
15782
- return { top, bottom: Math.max(top, bottom), left, right };
16122
+ return { ...band, left, right };
15783
16123
  }
15784
- function defaultFloatingPanelPosition(parentScroll, panelHeight = 280) {
16124
+ function defaultFloatingPanelPosition(parentScroll, _panelHeight = 280) {
15785
16125
  const clip = getVisibleClip(parentScroll);
15786
16126
  return {
15787
16127
  x: Math.max(EDGE_MARGIN, clip.right - PANEL_WIDTH - EDGE_MARGIN),
15788
- y: Math.min(
15789
- Math.max(clip.top + EDGE_MARGIN, EDGE_MARGIN),
15790
- Math.max(clip.top + EDGE_MARGIN, clip.bottom - panelHeight - EDGE_MARGIN)
15791
- )
16128
+ y: clip.top + EDGE_MARGIN
15792
16129
  };
15793
16130
  }
15794
- 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) {
15795
16137
  const clip = getVisibleClip(parentScroll);
15796
- const maxX = Math.max(clip.left + EDGE_MARGIN, clip.right - panelW - EDGE_MARGIN);
15797
- 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);
15798
16140
  return {
15799
- x: Math.min(Math.max(pos.x, clip.left + EDGE_MARGIN), maxX),
15800
- 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)
15801
16143
  };
15802
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
+ }
15803
16158
  function FloatingPanel({
15804
16159
  open,
15805
16160
  title,
@@ -15816,19 +16171,54 @@ function FloatingPanel({
15816
16171
  const panelRef = (0, import_react15.useRef)(null);
15817
16172
  const [measured, setMeasured] = (0, import_react15.useState)({ w: PANEL_WIDTH, h: 280 });
15818
16173
  const dragRef = (0, import_react15.useRef)(null);
15819
- const resolved = position ?? defaultFloatingPanelPosition(parentScroll, measured.h);
15820
- 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]);
15821
16213
  (0, import_react15.useLayoutEffect)(() => {
15822
16214
  if (!open || !panelRef.current) return;
15823
16215
  const el = panelRef.current;
15824
- 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 };
15825
16220
  setMeasured((prev) => prev.w === next.w && prev.h === next.h ? prev : next);
15826
16221
  }, [open, children, title, context]);
15827
- (0, import_react15.useEffect)(() => {
15828
- if (!open || !position || !onPositionChange) return;
15829
- const next = clampPosition(position, parentScroll, measured.w, measured.h);
15830
- if (next.x !== position.x || next.y !== position.y) onPositionChange(next);
15831
- }, [open, parentScroll, measured.w, measured.h, position, onPositionChange]);
15832
16222
  const onHeaderPointerDown = (0, import_react15.useCallback)(
15833
16223
  (e) => {
15834
16224
  if (e.button !== 0) return;
@@ -15862,18 +16252,17 @@ function FloatingPanel({
15862
16252
  return;
15863
16253
  }
15864
16254
  e.preventDefault();
15865
- const next = clampPosition(
16255
+ const next = clampDragPosition(
15866
16256
  {
15867
16257
  x: drag.originX + (e.clientX - drag.startX),
15868
16258
  y: drag.originY + (e.clientY - drag.startY)
15869
16259
  },
15870
- parentScroll,
15871
- measured.w,
15872
- measured.h
16260
+ liveScroll,
16261
+ measured.w
15873
16262
  );
15874
16263
  onPositionChange?.(next);
15875
16264
  },
15876
- [measured.h, measured.w, onPositionChange, parentScroll]
16265
+ [measured.w, onPositionChange, liveScroll]
15877
16266
  );
15878
16267
  const endDrag = (0, import_react15.useCallback)((e) => {
15879
16268
  const drag = dragRef.current;
@@ -15903,7 +16292,7 @@ function FloatingPanel({
15903
16292
  "fixed z-[2147483647] flex w-64 flex-col overflow-hidden rounded-xl border border-border bg-background font-sans shadow-lg outline-none",
15904
16293
  className
15905
16294
  ),
15906
- style: { left: clamped.x, top: clamped.y },
16295
+ style: { left: clamped.x, top: clamped.y, maxHeight },
15907
16296
  onMouseDown: (e) => e.stopPropagation(),
15908
16297
  onPointerDown: (e) => e.stopPropagation(),
15909
16298
  onClick: (e) => e.stopPropagation(),
@@ -15912,7 +16301,7 @@ function FloatingPanel({
15912
16301
  "div",
15913
16302
  {
15914
16303
  "data-ohw-floating-panel-header": "",
15915
- 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",
15916
16305
  onPointerDown: onHeaderPointerDown,
15917
16306
  onPointerMove: onHeaderPointerMove,
15918
16307
  onPointerUp: endDrag,
@@ -15947,7 +16336,11 @@ function FloatingPanel({
15947
16336
  "div",
15948
16337
  {
15949
16338
  "data-ohw-floating-panel-body": "",
15950
- 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" },
15951
16344
  children
15952
16345
  }
15953
16346
  )
@@ -16025,119 +16418,1553 @@ function SocialsDisplayPanel({ display, onChange, className }) {
16025
16418
  ] });
16026
16419
  }
16027
16420
 
16028
- // src/lib/item-drag-interaction.ts
16029
- function disableNativeHrefDrag(el) {
16030
- if (el.draggable) el.draggable = false;
16031
- if (el.getAttribute("draggable") !== "false") {
16032
- 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;
16033
16456
  }
16457
+ return "var(--brand-background, var(--ohw-brand-light, var(--color-light, #ffffff)))";
16034
16458
  }
16035
- function clearTextSelection() {
16036
- const sel = window.getSelection();
16037
- 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;
16038
16464
  }
16039
- function armItemPressDrag() {
16040
- document.documentElement.setAttribute("data-ohw-footer-press-drag", "");
16465
+ function isButtonVariantId(value) {
16466
+ return BUTTON_VARIANT_IDS.includes(value);
16041
16467
  }
16042
- function lockItemDuringDrag() {
16043
- document.documentElement.removeAttribute("data-ohw-footer-press-drag");
16044
- document.documentElement.setAttribute("data-ohw-item-dragging", "");
16045
- clearTextSelection();
16468
+ function isButtonSizeId(value) {
16469
+ return BUTTON_SIZE_IDS.includes(value);
16046
16470
  }
16047
- function unlockItemDragInteraction() {
16048
- const wasDragging = document.documentElement.hasAttribute("data-ohw-item-dragging");
16049
- document.documentElement.removeAttribute("data-ohw-footer-press-drag");
16050
- document.documentElement.removeAttribute("data-ohw-item-dragging");
16051
- if (wasDragging) clearTextSelection();
16471
+ function px(value) {
16472
+ const rounded = Math.round(value * 100) / 100;
16473
+ return `${rounded}px`;
16052
16474
  }
16053
- var armFooterPressDrag = armItemPressDrag;
16054
- var lockFooterDuringDrag = lockItemDuringDrag;
16055
- var unlockFooterDragInteraction = unlockItemDragInteraction;
16056
-
16057
- // src/lib/nav-dnd.ts
16058
- function listReorderableNavItems(draggedEl) {
16059
- return listActiveNavbarItems(draggedEl).filter((el) => {
16060
- const key = el.getAttribute("data-ohw-href-key");
16061
- return isNavbarHrefKey(key) && !isNestedNavChild(el);
16062
- });
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;
16063
16481
  }
16064
- function resolveParentNavHrefKey(child) {
16065
- const childrenRoot = child.closest("[data-ohw-nav-children]");
16066
- const group = childrenRoot?.closest("[data-ohw-nav-group]");
16067
- if (!group) return null;
16068
- const trigger = group.querySelector(":scope > [data-ohw-href-key]") ?? Array.from(group.querySelectorAll("[data-ohw-href-key]")).find(
16069
- (el) => !el.closest("[data-ohw-nav-children]")
16070
- ) ?? null;
16071
- const key = trigger?.getAttribute("data-ohw-href-key");
16072
- 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";
16073
16486
  }
16074
- function listNavChildren(parentHrefKey, draggedEl) {
16075
- const items = listActiveNavbarItems(draggedEl);
16076
- const parent = items.find((el) => el.getAttribute("data-ohw-href-key") === parentHrefKey);
16077
- if (!parent) return [];
16078
- const group = parent.closest("[data-ohw-nav-group]");
16079
- const childrenRoot = group?.querySelector(":scope > [data-ohw-nav-children]");
16080
- if (!childrenRoot) return [];
16081
- return Array.from(childrenRoot.querySelectorAll(":scope > [data-ohw-href-key]")).filter(
16082
- (el) => isNavbarHrefKey(el.getAttribute("data-ohw-href-key"))
16083
- );
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
+ };
16084
16498
  }
16085
- function isVerticalNavLayout(items, draggedEl) {
16086
- const visible = items.filter((el) => el.getClientRects().length > 0);
16087
- if (visible.length >= 2) {
16088
- const a = visible[0].getBoundingClientRect();
16089
- const b = visible[1].getBoundingClientRect();
16090
- const verticalGap = b.top - a.bottom;
16091
- const horizontalGap = b.left - a.right;
16092
- 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
+ }
16093
16554
  }
16094
- const container = getActiveNavbarListContainer(draggedEl);
16095
- if (!container) return false;
16096
- const style = window.getComputedStyle(container);
16097
- 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;
16098
16575
  }
16099
- function buildVerticalRootNavDropSlots(items, draggedEl) {
16100
- const slots = [];
16101
- const barThickness = 3;
16102
- if (items.length === 0) {
16103
- const container = getActiveNavbarListContainer(draggedEl);
16104
- if (!container) return slots;
16105
- const rect = container.getBoundingClientRect();
16106
- slots.push({
16107
- insertIndex: 0,
16108
- parentId: null,
16109
- left: rect.left,
16110
- top: rect.top + rect.height / 2 - barThickness / 2,
16111
- width: Math.max(rect.width, 40),
16112
- height: barThickness,
16113
- direction: "horizontal"
16114
- });
16115
- 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;
16116
16584
  }
16117
- const edgeGap = (() => {
16118
- if (items.length < 2) return 16;
16119
- const a = items[0].getBoundingClientRect();
16120
- const b = items[1].getBoundingClientRect();
16121
- return Math.max(0, b.top - a.bottom);
16122
- })();
16123
- for (let i = 0; i <= items.length; i++) {
16124
- let top;
16125
- let width;
16126
- let left;
16127
- if (i === 0) {
16128
- const first = items[0].getBoundingClientRect();
16129
- top = first.top - edgeGap / 2 - barThickness / 2;
16130
- left = first.left;
16131
- width = first.width;
16132
- } else if (i === items.length) {
16133
- const last = items[items.length - 1].getBoundingClientRect();
16134
- const lastGap = items.length >= 2 ? Math.max(0, last.top - items[items.length - 2].getBoundingClientRect().bottom) : edgeGap;
16135
- top = last.bottom + lastGap / 2 - barThickness / 2;
16136
- left = last.left;
16137
- width = last.width;
16138
- } else {
16139
- const prev = items[i - 1].getBoundingClientRect();
16140
- const next = items[i].getBoundingClientRect();
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();
16141
17968
  top = (prev.bottom + next.top) / 2 - barThickness / 2;
16142
17969
  left = Math.min(prev.left, next.left);
16143
17970
  width = Math.max(prev.right, next.right) - left;
@@ -16454,7 +18281,7 @@ function useNavItemDrag({
16454
18281
  document.querySelectorAll(
16455
18282
  "nav [data-ohw-href-key], [data-ohw-nav-container] [data-ohw-href-key], [data-ohw-nav-drawer] [data-ohw-href-key]"
16456
18283
  ).forEach((el) => {
16457
- if (isNavbarHrefKey(el.getAttribute("data-ohw-href-key"))) {
18284
+ if (isNavbarHrefKey(el.getAttribute("data-ohw-href-key")) || isCtaButton(el)) {
16458
18285
  disableNativeHrefDrag(el);
16459
18286
  }
16460
18287
  });
@@ -16763,12 +18590,6 @@ var SECTION_DRAG_EXCLUDED_SELECTOR = [
16763
18590
  "[data-ohw-media-overlay]",
16764
18591
  "[data-ohw-media-skeleton]"
16765
18592
  ].join(", ");
16766
- function visibleClip(ps) {
16767
- if (!ps) return null;
16768
- const top = Math.max(0, ps.headerH - ps.iframeOffsetTop);
16769
- const bottom = Math.min(window.innerHeight, ps.headerH + ps.canvasH - ps.iframeOffsetTop);
16770
- return { top, bottom: Math.max(top, bottom) };
16771
- }
16772
18593
  function useSectionDrag({
16773
18594
  isEditMode,
16774
18595
  editContentRef,
@@ -16805,7 +18626,7 @@ function useSectionDrag({
16805
18626
  }, [postToParentRef, stopAutoScroll]);
16806
18627
  const updateAutoScroll = (0, import_react17.useCallback)(
16807
18628
  (clientY) => {
16808
- const clip = visibleClip(parentScrollRef.current);
18629
+ const clip = getIframeChromeClip(parentScrollRef.current);
16809
18630
  let delta = 0;
16810
18631
  if (clip) {
16811
18632
  const distTop = clientY - clip.top;
@@ -17011,8 +18832,8 @@ function useSectionDrag({
17011
18832
  }
17012
18833
 
17013
18834
  // src/ui/footer-container-chrome.tsx
17014
- var import_lucide_react17 = require("lucide-react");
17015
- 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");
17016
18837
  function FooterContainerChrome({
17017
18838
  rect,
17018
18839
  onAdd,
@@ -17020,7 +18841,7 @@ function FooterContainerChrome({
17020
18841
  }) {
17021
18842
  const chromeGap = 6;
17022
18843
  const buttonMargin = 7;
17023
- 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)(
17024
18845
  "div",
17025
18846
  {
17026
18847
  "data-ohw-footer-container-chrome": "",
@@ -17032,8 +18853,8 @@ function FooterContainerChrome({
17032
18853
  width: rect.width + chromeGap * 2,
17033
18854
  height: rect.height + chromeGap * 2
17034
18855
  },
17035
- children: /* @__PURE__ */ (0, import_jsx_runtime31.jsxs)(Tooltip, { children: [
17036
- /* @__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)(
17037
18858
  "button",
17038
18859
  {
17039
18860
  type: "button",
@@ -17052,10 +18873,10 @@ function FooterContainerChrome({
17052
18873
  if (addDisabled) return;
17053
18874
  onAdd();
17054
18875
  },
17055
- 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 })
17056
18877
  }
17057
18878
  ) }),
17058
- /* @__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" })
17059
18880
  ] })
17060
18881
  }
17061
18882
  ) });
@@ -17217,7 +19038,14 @@ function collectEditableNodes(extraContent, root = document) {
17217
19038
  // so collecting the DOM alone left them behind: the draft knew the row was showing icons and
17218
19039
  // had gained an item, and the published page went back to the template's own (OHH-736).
17219
19040
  SOCIALS_ORDER_KEY,
17220
- 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
17221
19049
  ]) {
17222
19050
  const text = extraContent[key];
17223
19051
  if (typeof text === "string" && text.length > 0) {
@@ -17537,14 +19365,14 @@ function deleteSelectedNavFooterItem(deps) {
17537
19365
  }
17538
19366
 
17539
19367
  // src/ui/navbar-container-chrome.tsx
17540
- var import_lucide_react18 = require("lucide-react");
17541
- 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");
17542
19370
  function NavbarContainerChrome({
17543
19371
  rect,
17544
19372
  onAdd
17545
19373
  }) {
17546
19374
  const chromeGap = 6;
17547
- return /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
19375
+ return /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
17548
19376
  "div",
17549
19377
  {
17550
19378
  "data-ohw-navbar-container-chrome": "",
@@ -17556,7 +19384,7 @@ function NavbarContainerChrome({
17556
19384
  width: rect.width + chromeGap * 2,
17557
19385
  height: rect.height + chromeGap * 2
17558
19386
  },
17559
- children: /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
19387
+ children: /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
17560
19388
  "button",
17561
19389
  {
17562
19390
  type: "button",
@@ -17573,7 +19401,7 @@ function NavbarContainerChrome({
17573
19401
  e.stopPropagation();
17574
19402
  onAdd();
17575
19403
  },
17576
- 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 })
17577
19405
  }
17578
19406
  )
17579
19407
  }
@@ -17582,7 +19410,7 @@ function NavbarContainerChrome({
17582
19410
 
17583
19411
  // src/ui/drop-indicator.tsx
17584
19412
  var React11 = __toESM(require("react"), 1);
17585
- var import_jsx_runtime33 = require("react/jsx-runtime");
19413
+ var import_jsx_runtime34 = require("react/jsx-runtime");
17586
19414
  var dropIndicatorVariants = cva(
17587
19415
  "ov-gap-line pointer-events-none shrink-0 transition-opacity duration-150",
17588
19416
  {
@@ -17606,7 +19434,7 @@ var dropIndicatorVariants = cva(
17606
19434
  );
17607
19435
  var DropIndicator = React11.forwardRef(
17608
19436
  ({ className, direction, state, ...props }, ref) => {
17609
- return /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
19437
+ return /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(
17610
19438
  "div",
17611
19439
  {
17612
19440
  ref,
@@ -17623,7 +19451,7 @@ var DropIndicator = React11.forwardRef(
17623
19451
  DropIndicator.displayName = "DropIndicator";
17624
19452
 
17625
19453
  // src/ui/badge.tsx
17626
- var import_jsx_runtime34 = require("react/jsx-runtime");
19454
+ var import_jsx_runtime35 = require("react/jsx-runtime");
17627
19455
  var badgeVariants = cva(
17628
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",
17629
19457
  {
@@ -17641,12 +19469,12 @@ var badgeVariants = cva(
17641
19469
  }
17642
19470
  );
17643
19471
  function Badge({ className, variant, ...props }) {
17644
- 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 });
17645
19473
  }
17646
19474
 
17647
19475
  // src/OhhwellsBridge.tsx
17648
- var import_lucide_react19 = require("lucide-react");
17649
- 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");
17650
19478
  var PRIMARY5 = "#0885FE";
17651
19479
  var IMAGE_FADE_MS = 300;
17652
19480
  var MEDIA_SETTLE_TIMEOUT_MS = 2500;
@@ -17768,7 +19596,7 @@ function duplicateSchedulingWidget(instanceId, scheduleId) {
17768
19596
  schedulingRoots.set(container, root);
17769
19597
  (0, import_react_dom4.flushSync)(() => {
17770
19598
  root.render(
17771
- /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(
19599
+ /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(
17772
19600
  SchedulingWidget,
17773
19601
  {
17774
19602
  notifyOnConnect: false,
@@ -17811,21 +19639,10 @@ function parseSchedulingInsertAfter(insertAfter) {
17811
19639
  insertBefore: insertAfter.slice(idx + INSERT_BEFORE_MARKER.length)
17812
19640
  };
17813
19641
  }
17814
- function resolveSchedulingInsert(insertAfter, explicitInsertBefore) {
17815
- const parsed = parseSchedulingInsertAfter(insertAfter);
17816
- const insertBefore = explicitInsertBefore ?? parsed.insertBefore;
17817
- const effectiveInsertAfter = insertBefore && parsed.insertBefore === null ? `${insertAfter}${INSERT_BEFORE_MARKER}${insertBefore}` : insertAfter;
17818
- return { effectiveInsertAfter, insertBefore };
17819
- }
17820
- function getSchedulingMountPoint(insertAfter) {
17821
- const { anchor } = parseSchedulingInsertAfter(insertAfter);
17822
- let anchorEl = document.querySelector(`[data-ohw-section="${anchor}"]`);
17823
- if (!anchorEl && anchor === "scheduling") {
17824
- const widgets = Array.from(document.querySelectorAll('[data-ohw-section^="scheduling-"]'));
17825
- anchorEl = widgets.at(-1) ?? null;
17826
- }
17827
- if (!anchorEl) return null;
17828
- 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 };
17829
19646
  }
17830
19647
  function schedulingMountDepth(insertAfter) {
17831
19648
  if (!insertAfter.includes(INSERT_BEFORE_MARKER)) return 0;
@@ -17842,8 +19659,7 @@ function getPageSchedulingEntries(raw) {
17842
19659
  }
17843
19660
  }
17844
19661
  function isSchedulingWidgetMissing(entry) {
17845
- const { effectiveInsertAfter } = resolveSchedulingInsert(entry.insertAfter);
17846
- return !document.querySelector(`[data-ohw-section="${schedulingSectionId(effectiveInsertAfter)}"]`);
19662
+ return !document.querySelector(`[data-ohw-section="${schedulingSectionId(entry.insertAfter)}"]`);
17847
19663
  }
17848
19664
  function hasMissingSchedulingWidgets(entries) {
17849
19665
  return entries.some(isSchedulingWidgetMissing);
@@ -17881,17 +19697,18 @@ function initSectionsFromContent(content, removeExisting = false, currentPath =
17881
19697
  } catch {
17882
19698
  }
17883
19699
  }
17884
- function mountSchedulingWidget(insertAfter, notifyOnConnect = false, scheduleId, explicitInsertBefore) {
17885
- const { effectiveInsertAfter, insertBefore } = resolveSchedulingInsert(insertAfter, explicitInsertBefore);
17886
- 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);
17887
19703
  if (document.querySelector(`[data-ohw-section="${sectionId}"]`)) return false;
17888
- const mountPoint = getSchedulingMountPoint(effectiveInsertAfter);
17889
- 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;
17890
19707
  const container = document.createElement("div");
17891
19708
  container.dataset.ohwSectionContainer = "scheduling";
17892
19709
  container.dataset.ohwInstance = sectionId;
17893
- if (insertBefore) {
17894
- const beforeAnchor = document.querySelector(`[data-ohw-section="${insertBefore}"]`);
19710
+ if (beforeId) {
19711
+ const beforeAnchor = document.querySelector(`[data-ohw-section="${beforeId}"]`);
17895
19712
  const beforePoint = beforeAnchor?.closest('[data-ohw-section-container="scheduling"]') ?? beforeAnchor;
17896
19713
  if (!beforePoint) return false;
17897
19714
  beforePoint.insertAdjacentElement("beforebegin", container);
@@ -17902,20 +19719,26 @@ function mountSchedulingWidget(insertAfter, notifyOnConnect = false, scheduleId,
17902
19719
  }
17903
19720
  tail.insertAdjacentElement("afterend", container);
17904
19721
  }
17905
- const root = (0, import_client2.createRoot)(container);
17906
- schedulingRoots.set(container, root);
17907
- (0, import_react_dom4.flushSync)(() => {
17908
- root.render(
17909
- /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(
17910
- SchedulingWidget,
17911
- {
17912
- notifyOnConnect,
17913
- initialScheduleId: scheduleId,
17914
- insertAfter: effectiveInsertAfter
17915
- }
17916
- )
17917
- );
17918
- });
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
+ }
17919
19742
  const tracker = getSectionsTracker();
17920
19743
  let sections = [];
17921
19744
  try {
@@ -17923,10 +19746,12 @@ function mountSchedulingWidget(insertAfter, notifyOnConnect = false, scheduleId,
17923
19746
  } catch {
17924
19747
  }
17925
19748
  const inEditor = typeof window !== "undefined" && window.self !== window.top;
17926
- if (inEditor && !sections.find((s) => s.type === "scheduling" && s.insertAfter === effectiveInsertAfter)) {
19749
+ if (inEditor && !sections.find((s) => s.type === "scheduling" && s.insertAfter === widgetId)) {
17927
19750
  sections.push({
17928
19751
  type: "scheduling",
17929
- insertAfter: effectiveInsertAfter,
19752
+ insertAfter: widgetId,
19753
+ anchorId,
19754
+ beforeId: beforeId ?? null,
17930
19755
  pagePath: window.location.pathname,
17931
19756
  ...scheduleId ? { scheduleId } : {}
17932
19757
  });
@@ -17940,7 +19765,8 @@ function mountSchedulingEntries(entries, notifyOnConnect = false) {
17940
19765
  for (let i = pending.length - 1; i >= 0; i--) {
17941
19766
  const entry = pending[i];
17942
19767
  const shouldNotify = typeof notifyOnConnect === "function" ? notifyOnConnect(entry) : notifyOnConnect;
17943
- 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)) {
17944
19770
  pending.splice(i, 1);
17945
19771
  }
17946
19772
  }
@@ -18069,8 +19895,8 @@ function lockVideoBox(video) {
18069
19895
  }
18070
19896
  const fit = video.style.objectFit || getComputedStyle(video).objectFit;
18071
19897
  const bg = getComputedStyle(video).backgroundColor;
18072
- const isTransparent = bg === "rgba(0, 0, 0, 0)" || bg === "transparent" || !bg;
18073
- if (fit === "contain" && isTransparent) {
19898
+ const isTransparent2 = bg === "rgba(0, 0, 0, 0)" || bg === "transparent" || !bg;
19899
+ if (fit === "contain" && isTransparent2) {
18074
19900
  video.style.backgroundColor = "var(--color-dark, #000)";
18075
19901
  }
18076
19902
  }
@@ -18117,6 +19943,11 @@ function applyLinkByKey(key, val) {
18117
19943
  hrefAnchors.forEach((el) => applyLinkHref(el, val));
18118
19944
  }
18119
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
+ }
18120
19951
  function isInsideFloatingPanel(target) {
18121
19952
  return Boolean(target.closest("[data-ohw-floating-panel]"));
18122
19953
  }
@@ -18124,11 +19955,6 @@ function isPointOverFloatingPanel(clientX, clientY) {
18124
19955
  const el = document.elementFromPoint(clientX, clientY);
18125
19956
  return Boolean(el instanceof Element && el.closest("[data-ohw-floating-panel]"));
18126
19957
  }
18127
- function isInsideLinkEditor(target) {
18128
- return Boolean(
18129
- 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"]')
18130
- );
18131
- }
18132
19958
  function getHrefKeyFromElement(el) {
18133
19959
  if (!el) return null;
18134
19960
  const anchor = el.closest("[data-ohw-href-key]");
@@ -18220,7 +20046,10 @@ function listNavigationItems() {
18220
20046
  ).filter((el) => isNavigationItem2(el));
18221
20047
  }
18222
20048
  function getNavigationItemReorderState(anchor) {
18223
- 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
+ }
18224
20053
  return getReorderHandleStateFromAnchor(anchor);
18225
20054
  }
18226
20055
  function getReorderHandleState(el) {
@@ -18319,6 +20148,12 @@ function isPointOverNavigation(x, y) {
18319
20148
  const r2 = panel.getBoundingClientRect();
18320
20149
  if (x >= r2.left && x <= r2.right && y >= r2.top && y <= r2.bottom) return true;
18321
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
+ }
18322
20157
  return false;
18323
20158
  }
18324
20159
  function findHoveredNavOrFooterContainer(x, y) {
@@ -18398,7 +20233,7 @@ function getNavigationSelectionParent(el) {
18398
20233
  if (!isFooterLinksContainer(el) && (el.hasAttribute("data-ohw-footer-col") || el.hasAttribute("data-ohw-footer-column") || isInferredFooterGroup2(el))) {
18399
20234
  return getFooterLinksContainer();
18400
20235
  }
18401
- 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)) {
18402
20237
  return getNavigationRoot(el);
18403
20238
  }
18404
20239
  return null;
@@ -18620,7 +20455,6 @@ var ICONS = {
18620
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"/>',
18621
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"/>'
18622
20457
  };
18623
- var HEIGHT_SETTLE_DELAYS = [150, 400, 800];
18624
20458
  var SELECTION_CHROME_GAP2 = 4;
18625
20459
  var TOOLBAR_STROKE_GAP2 = 4;
18626
20460
  var TOOLBAR_OFFSET_FROM_ELEMENT = SELECTION_CHROME_GAP2 + TOOLBAR_STROKE_GAP2;
@@ -18652,7 +20486,7 @@ function EditGlowChrome({
18652
20486
  hideHandle = false
18653
20487
  }) {
18654
20488
  const GAP = SELECTION_CHROME_GAP2;
18655
- return /* @__PURE__ */ (0, import_jsx_runtime35.jsxs)(
20489
+ return /* @__PURE__ */ (0, import_jsx_runtime36.jsxs)(
18656
20490
  "div",
18657
20491
  {
18658
20492
  ref: elRef,
@@ -18667,7 +20501,7 @@ function EditGlowChrome({
18667
20501
  zIndex: 2147483646
18668
20502
  },
18669
20503
  children: [
18670
- /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(
20504
+ /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(
18671
20505
  "div",
18672
20506
  {
18673
20507
  style: {
@@ -18681,7 +20515,7 @@ function EditGlowChrome({
18681
20515
  }
18682
20516
  }
18683
20517
  ),
18684
- reorderHrefKey && !hideHandle && /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(
20518
+ reorderHrefKey && !hideHandle && /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(
18685
20519
  "div",
18686
20520
  {
18687
20521
  "data-ohw-drag-handle-container": "",
@@ -18693,7 +20527,7 @@ function EditGlowChrome({
18693
20527
  transform: "translate(calc(-100% - 7px), -50%)",
18694
20528
  pointerEvents: dragDisabled ? "none" : "auto"
18695
20529
  },
18696
- children: /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(
20530
+ children: /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(
18697
20531
  DragHandle,
18698
20532
  {
18699
20533
  "aria-label": `Reorder ${reorderHrefKey}`,
@@ -18707,22 +20541,12 @@ function EditGlowChrome({
18707
20541
  }
18708
20542
  );
18709
20543
  }
18710
- function getIframeVisibleClip(parentScroll) {
18711
- if (!parentScroll) return null;
18712
- const { iframeOffsetTop, headerH: visibleCanvasTop, canvasH } = parentScroll;
18713
- const clipTop = Math.max(0, visibleCanvasTop - iframeOffsetTop);
18714
- const clipBottom = Math.min(
18715
- window.innerHeight,
18716
- visibleCanvasTop + canvasH - iframeOffsetTop
18717
- );
18718
- return { top: clipTop, bottom: Math.max(clipTop, clipBottom) };
18719
- }
18720
20544
  function isRectOutsideClip(rect, clip) {
18721
20545
  if (!clip) return false;
18722
20546
  return rect.bottom < clip.top || rect.top > clip.bottom;
18723
20547
  }
18724
20548
  function applyVisibleViewport(el, parentScroll) {
18725
- const clip = getIframeVisibleClip(parentScroll);
20549
+ const clip = getIframeChromeClip(parentScroll);
18726
20550
  const top = clip?.top ?? 0;
18727
20551
  const height = clip ? clip.bottom - clip.top : window.innerHeight;
18728
20552
  el.style.top = `${top}px`;
@@ -18762,7 +20586,7 @@ function clampToolbarLeft(centerX, toolbarWidth) {
18762
20586
  function calcToolbarPos(rect, parentScroll, toolbarW = 306) {
18763
20587
  const GAP = TOOLBAR_OFFSET_FROM_ELEMENT;
18764
20588
  const APPROX_H = 32;
18765
- const clip = getIframeVisibleClip(parentScroll);
20589
+ const clip = getIframeChromeClip(parentScroll);
18766
20590
  const clipTop = clip?.top ?? 0;
18767
20591
  const canvasTopInIframe = parentScroll != null ? parentScroll.headerH - parentScroll.iframeOffsetTop : 0;
18768
20592
  const visibleTop = rect.top - canvasTopInIframe;
@@ -18907,7 +20731,7 @@ function FloatingToolbar({
18907
20731
  return () => ro.disconnect();
18908
20732
  }, [showEditLink, activeCommands]);
18909
20733
  const { top, left, transform } = calcToolbarPos(rect, parentScroll, measuredW);
18910
- return /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(
20734
+ return /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(
18911
20735
  "div",
18912
20736
  {
18913
20737
  ref: setRefs,
@@ -18919,12 +20743,12 @@ function FloatingToolbar({
18919
20743
  zIndex: 2147483647,
18920
20744
  pointerEvents: "auto"
18921
20745
  },
18922
- children: /* @__PURE__ */ (0, import_jsx_runtime35.jsxs)(CustomToolbar, { children: [
18923
- TOOLBAR_GROUPS.map((btns, gi) => /* @__PURE__ */ (0, import_jsx_runtime35.jsxs)(import_react19.default.Fragment, { children: [
18924
- 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, {}),
18925
20749
  btns.map((btn) => {
18926
20750
  const isActive = activeCommands.has(btn.cmd);
18927
- return /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(
20751
+ return /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(
18928
20752
  CustomToolbarButton,
18929
20753
  {
18930
20754
  title: btn.title,
@@ -18933,7 +20757,7 @@ function FloatingToolbar({
18933
20757
  e.preventDefault();
18934
20758
  onCommand(btn.cmd);
18935
20759
  },
18936
- children: /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(
20760
+ children: /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(
18937
20761
  "svg",
18938
20762
  {
18939
20763
  width: "16",
@@ -18954,7 +20778,7 @@ function FloatingToolbar({
18954
20778
  );
18955
20779
  })
18956
20780
  ] }, gi)),
18957
- showEditLink ? /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(
20781
+ showEditLink ? /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(
18958
20782
  CustomToolbarButton,
18959
20783
  {
18960
20784
  type: "button",
@@ -18968,7 +20792,7 @@ function FloatingToolbar({
18968
20792
  e.preventDefault();
18969
20793
  e.stopPropagation();
18970
20794
  },
18971
- 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 })
18972
20796
  }
18973
20797
  ) : null
18974
20798
  ] })
@@ -18985,7 +20809,7 @@ function StateToggle({
18985
20809
  states,
18986
20810
  onStateChange
18987
20811
  }) {
18988
- return /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(
20812
+ return /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(
18989
20813
  ToggleGroup,
18990
20814
  {
18991
20815
  "data-ohw-state-toggle": "",
@@ -18999,11 +20823,12 @@ function StateToggle({
18999
20823
  left: rect.right - 8,
19000
20824
  transform: "translateX(-100%)"
19001
20825
  },
19002
- 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))
19003
20827
  }
19004
20828
  );
19005
20829
  }
19006
20830
  var contentCache = /* @__PURE__ */ new Map();
20831
+ var fetchedContentPaths = /* @__PURE__ */ new Set();
19007
20832
  var brandingCache = /* @__PURE__ */ new Map();
19008
20833
  var OHW_LOADER_STYLE = {
19009
20834
  position: "fixed",
@@ -19015,9 +20840,9 @@ var OHW_LOADER_STYLE = {
19015
20840
  justifyContent: "center"
19016
20841
  };
19017
20842
  function OhwLoaderSpinner() {
19018
- return /* @__PURE__ */ (0, import_jsx_runtime35.jsxs)("svg", { width: "28", height: "28", viewBox: "0 0 28 28", fill: "none", "aria-hidden": true, children: [
19019
- /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("circle", { cx: "14", cy: "14", r: "11", stroke: "#E7E5E4", strokeWidth: "3" }),
19020
- /* @__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)(
19021
20846
  "circle",
19022
20847
  {
19023
20848
  cx: "14",
@@ -19027,7 +20852,7 @@ function OhwLoaderSpinner() {
19027
20852
  strokeWidth: "3",
19028
20853
  strokeDasharray: "17 52",
19029
20854
  strokeLinecap: "round",
19030
- children: /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(
20855
+ children: /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(
19031
20856
  "animateTransform",
19032
20857
  {
19033
20858
  attributeName: "transform",
@@ -19043,7 +20868,7 @@ function OhwLoaderSpinner() {
19043
20868
  ] });
19044
20869
  }
19045
20870
  function OhwBrandMark() {
19046
- return /* @__PURE__ */ (0, import_jsx_runtime35.jsxs)(
20871
+ return /* @__PURE__ */ (0, import_jsx_runtime36.jsxs)(
19047
20872
  "svg",
19048
20873
  {
19049
20874
  width: "16",
@@ -19054,7 +20879,7 @@ function OhwBrandMark() {
19054
20879
  style: { display: "block", flexShrink: 0 },
19055
20880
  xmlns: "http://www.w3.org/2000/svg",
19056
20881
  children: [
19057
- /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(
20882
+ /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(
19058
20883
  "mask",
19059
20884
  {
19060
20885
  id: "ohw-badge-mark",
@@ -19064,15 +20889,15 @@ function OhwBrandMark() {
19064
20889
  y: "0",
19065
20890
  width: "48",
19066
20891
  height: "48",
19067
- 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" })
19068
20893
  }
19069
20894
  ),
19070
- /* @__PURE__ */ (0, import_jsx_runtime35.jsxs)("g", { mask: "url(#ohw-badge-mark)", children: [
19071
- /* @__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" }),
19072
- /* @__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" }),
19073
- /* @__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" }),
19074
- /* @__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" }),
19075
- /* @__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" })
19076
20901
  ] })
19077
20902
  ]
19078
20903
  }
@@ -19109,7 +20934,7 @@ var OHW_BADGE_LABEL_STYLE = {
19109
20934
  whiteSpace: "nowrap"
19110
20935
  };
19111
20936
  function MadeWithOhhWells() {
19112
- return /* @__PURE__ */ (0, import_jsx_runtime35.jsxs)(
20937
+ return /* @__PURE__ */ (0, import_jsx_runtime36.jsxs)(
19113
20938
  "a",
19114
20939
  {
19115
20940
  href: "https://ohhwells.com",
@@ -19119,8 +20944,8 @@ function MadeWithOhhWells() {
19119
20944
  "data-ohw-badge": "",
19120
20945
  style: OHW_BADGE_STYLE,
19121
20946
  children: [
19122
- /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(OhwBrandMark, {}),
19123
- /* @__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" })
19124
20949
  ]
19125
20950
  }
19126
20951
  );
@@ -19607,13 +21432,6 @@ function OhhwellsBridge() {
19607
21432
  const [isItemDragging, setIsItemDragging] = (0, import_react19.useState)(false);
19608
21433
  const [isFooterFrameSelection, setIsFooterFrameSelection] = (0, import_react19.useState)(false);
19609
21434
  isFooterFrameSelectionRef.current = isFooterFrameSelection;
19610
- const [floatingPanel, setFloatingPanel] = (0, import_react19.useState)(null);
19611
- const floatingPanelOpenRef = (0, import_react19.useRef)(false);
19612
- floatingPanelOpenRef.current = floatingPanel !== null;
19613
- const [floatingPanelPos, setFloatingPanelPos] = (0, import_react19.useState)(null);
19614
- const [logoSizeDraft, setLogoSizeDraft] = (0, import_react19.useState)(null);
19615
- const [editorViewport, setEditorViewport] = (0, import_react19.useState)("desktop");
19616
- const [parentScrollSnap, setParentScrollSnap] = (0, import_react19.useState)(null);
19617
21435
  const [navDropdownPreviewOpen, setNavDropdownPreviewOpen] = (0, import_react19.useState)(null);
19618
21436
  const [footerHeadingVisible, setFooterHeadingVisible] = (0, import_react19.useState)(null);
19619
21437
  const footerDragRef = (0, import_react19.useRef)(null);
@@ -19637,6 +21455,21 @@ function OhhwellsBridge() {
19637
21455
  const stylesRef = (0, import_react19.useRef)("");
19638
21456
  const appliedDrawerRef = (0, import_react19.useRef)(null);
19639
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);
19640
21473
  const [sitePages, setSitePages] = (0, import_react19.useState)([]);
19641
21474
  const [sectionsByPath, setSectionsByPath] = (0, import_react19.useState)({});
19642
21475
  const sectionsPrefetchGenRef = (0, import_react19.useRef)(0);
@@ -19645,7 +21478,18 @@ function OhhwellsBridge() {
19645
21478
  const linkPopoverOpenRef = (0, import_react19.useRef)(false);
19646
21479
  const linkPopoverGraceUntilRef = (0, import_react19.useRef)(0);
19647
21480
  setLinkPopoverRef.current = setLinkPopover;
21481
+ setFloatingPanelRef.current = setFloatingPanel;
19648
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
+ }, []);
19649
21493
  (0, import_react19.useEffect)(() => {
19650
21494
  setSectionOrderPersistHandler((entries) => {
19651
21495
  const currentPath = window.location.pathname;
@@ -19911,6 +21755,7 @@ function OhhwellsBridge() {
19911
21755
  hoveredItemElRef.current = null;
19912
21756
  setHoveredItemRect(null);
19913
21757
  setFloatingPanel(null);
21758
+ setFloatingPanelPos(null);
19914
21759
  setLogoSizeDraft(null);
19915
21760
  if (!activeElRef.current) {
19916
21761
  if (nestedGroup?.isConnected) {
@@ -20511,6 +22356,47 @@ function OhhwellsBridge() {
20511
22356
  },
20512
22357
  [beginFooterDrag]
20513
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
+ }, []);
20514
22400
  const handleItemDragStart = (0, import_react19.useCallback)(
20515
22401
  (e) => {
20516
22402
  const selected = selectedElRef.current;
@@ -20520,6 +22406,17 @@ function OhhwellsBridge() {
20520
22406
  }
20521
22407
  const clientX = e?.clientX ?? selected.getBoundingClientRect().left + 8;
20522
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
+ }
20523
22420
  if (startFooterLinkDrag(selected, clientX, clientY, true)) return;
20524
22421
  if (selected.hasAttribute("data-ohw-footer-col") || isInferredFooterGroup2(selected) && selected.closest("footer")) {
20525
22422
  if (startFooterColumnDrag(selected, clientX, clientY, true)) return;
@@ -20529,10 +22426,15 @@ function OhhwellsBridge() {
20529
22426
  setSiblingHintRect(null);
20530
22427
  setIsItemDragging(true);
20531
22428
  },
20532
- [startFooterColumnDrag, startFooterLinkDrag, startNavLinkDrag]
22429
+ [startFooterColumnDrag, startFooterLinkDrag, startNavLinkDrag, refreshButtonDragVisuals]
20533
22430
  );
20534
22431
  const handleItemDragEnd = (0, import_react19.useCallback)(
20535
22432
  (e) => {
22433
+ const collectionButton = buttonCollectionDragRef.current;
22434
+ if (collectionButton) {
22435
+ persistButtonCollectionDrag(collectionButton, e?.clientX);
22436
+ return;
22437
+ }
20536
22438
  if (footerDragRef.current) {
20537
22439
  const x = e?.clientX;
20538
22440
  const y = e?.clientY;
@@ -20555,12 +22457,22 @@ function OhhwellsBridge() {
20555
22457
  }
20556
22458
  setIsItemDragging(false);
20557
22459
  },
20558
- [commitFooterDrag, commitNavDrag, navDragRef]
22460
+ [commitFooterDrag, commitNavDrag, navDragRef, persistButtonCollectionDrag]
20559
22461
  );
20560
22462
  const handleItemChromePointerDown = (0, import_react19.useCallback)((e) => {
20561
22463
  if (e.button !== 0) return;
20562
22464
  const selected = selectedElRef.current;
20563
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
+ }
20564
22476
  const hrefKey = selected.getAttribute("data-ohw-href-key");
20565
22477
  if (hrefKey && isFooterHrefKey(hrefKey)) {
20566
22478
  footerPointerDragRef.current = {
@@ -20639,10 +22551,29 @@ function OhhwellsBridge() {
20639
22551
  setIsFooterFrameSelection(false);
20640
22552
  setToolbarVariant("link-action");
20641
22553
  setToolbarRect(anchor.getBoundingClientRect());
22554
+ if (glowElRef.current) glowElRef.current.style.display = "";
20642
22555
  setToolbarShowEditLink(false);
20643
22556
  setActiveCommands(/* @__PURE__ */ new Set());
20644
- setFloatingPanel(null);
20645
- 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
+ }
20646
22577
  }, [deactivate, markSelected]);
20647
22578
  const selectFrame = (0, import_react19.useCallback)((el) => {
20648
22579
  if (!isNavigationContainer(el)) return;
@@ -20682,6 +22613,7 @@ function OhhwellsBridge() {
20682
22613
  setToolbarShowEditLink(false);
20683
22614
  setActiveCommands(/* @__PURE__ */ new Set());
20684
22615
  setFloatingPanel(null);
22616
+ setFloatingPanelPos(null);
20685
22617
  setLogoSizeDraft(null);
20686
22618
  }, [deactivate, markSelected, postToParent2]);
20687
22619
  const selectLogo = (0, import_react19.useCallback)(
@@ -20727,6 +22659,7 @@ function OhhwellsBridge() {
20727
22659
  });
20728
22660
  }, []);
20729
22661
  const openSocialsDisplayPanel = (0, import_react19.useCallback)((row) => {
22662
+ setFloatingPanelPos(null);
20730
22663
  setParentScrollSnap(parentScrollRef.current);
20731
22664
  setFloatingPanel({
20732
22665
  key: "socials-display",
@@ -20736,6 +22669,87 @@ function OhhwellsBridge() {
20736
22669
  row
20737
22670
  });
20738
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]);
20739
22753
  const isEditModeRef = (0, import_react19.useRef)(false);
20740
22754
  const requestMissingSocialIconsRef = (0, import_react19.useRef)(() => {
20741
22755
  });
@@ -20778,11 +22792,13 @@ function OhhwellsBridge() {
20778
22792
  );
20779
22793
  const closeFloatingPanelOnly = (0, import_react19.useCallback)(() => {
20780
22794
  setFloatingPanel(null);
22795
+ setFloatingPanelPos(null);
20781
22796
  setLogoSizeDraft(null);
20782
22797
  }, []);
20783
22798
  closeFloatingPanelOnlyRef.current = closeFloatingPanelOnly;
20784
22799
  const closeFloatingPanelAndDeselect = (0, import_react19.useCallback)(() => {
20785
22800
  setFloatingPanel(null);
22801
+ setFloatingPanelPos(null);
20786
22802
  setLogoSizeDraft(null);
20787
22803
  deselectRef.current();
20788
22804
  }, []);
@@ -20991,18 +23007,21 @@ function OhhwellsBridge() {
20991
23007
  }
20992
23008
  if (typeof content[STYLE_STORE_KEY] === "string") {
20993
23009
  stylesRef.current = content[STYLE_STORE_KEY];
23010
+ document.querySelectorAll('[data-ohw-editable="form"]').forEach((f) => markFormFields(f));
20994
23011
  applyStylesToDom(parseStyleStore(content[STYLE_STORE_KEY]));
20995
23012
  }
20996
23013
  applyBrandChrome(content);
23014
+ applyCustomCodeToHead(content);
20997
23015
  initSectionInstancesFromContent(content, window.location.pathname);
20998
23016
  for (const [key, val] of Object.entries(content)) {
20999
23017
  if (key === "__ohw_sections") continue;
21000
23018
  if (key === AI_SECTIONS_KEY) continue;
23019
+ if (key === LOGO_PLACEHOLDER_KEY) continue;
23020
+ if (LOGO_IMAGE_KEYS.includes(key)) continue;
21001
23021
  if (key === BRAND_KIT_KEY) continue;
21002
23022
  if (key === STYLE_STORE_KEY) continue;
21003
23023
  if (BRAND_CHROME_KEYS.has(key)) continue;
21004
- if (key === LOGO_PLACEHOLDER_KEY) continue;
21005
- if (LOGO_IMAGE_KEYS.includes(key)) continue;
23024
+ if (key === CUSTOM_CODE_AFTER_HEAD_KEY || key === CUSTOM_CODE_BEFORE_HEAD_KEY) continue;
21006
23025
  if (applyVideoSettingNode(key, val)) continue;
21007
23026
  if (applyCarouselNode(key, val)) continue;
21008
23027
  document.querySelectorAll(`[data-ohw-key="${key}"]`).forEach((el) => {
@@ -21049,6 +23068,7 @@ function OhhwellsBridge() {
21049
23068
  reconcileSocialsFromContent(content);
21050
23069
  applySocialsDisplayFromContent(content);
21051
23070
  applySocialsLabelsFromContent(content, document, { fillEmptyOnly: isEditModeRef.current });
23071
+ syncButtonsFromContent(document, content);
21052
23072
  if (isEditModeRef.current) requestMissingSocialIconsRef.current();
21053
23073
  if (typeof content[STYLE_STORE_KEY] === "string") {
21054
23074
  applyStylesToDom(parseStyleStore(content[STYLE_STORE_KEY]));
@@ -21073,7 +23093,9 @@ function OhhwellsBridge() {
21073
23093
  let cancelled = false;
21074
23094
  setFetchState("loading");
21075
23095
  const apiUrl = process.env.NEXT_PUBLIC_FLOWOPS_API_URL ?? "https://flowops-backend-staging-2yfdh7pwpq-as.a.run.app";
21076
- 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) => {
21077
23099
  if (cancelled) return;
21078
23100
  const content = data?.content ?? {};
21079
23101
  const branding = Boolean(data?.showBranding);
@@ -21209,16 +23231,17 @@ function OhhwellsBridge() {
21209
23231
  applyAiSectionsToDom(parseAiSectionsState(content[AI_SECTIONS_KEY]));
21210
23232
  }
21211
23233
  if (typeof content[STYLE_STORE_KEY] === "string") {
23234
+ document.querySelectorAll('[data-ohw-editable="form"]').forEach((f) => markFormFields(f));
21212
23235
  applyStylesToDom(parseStyleStore(content[STYLE_STORE_KEY]));
21213
23236
  }
21214
23237
  for (const [key, val] of Object.entries(content)) {
21215
23238
  if (key === "__ohw_sections") continue;
21216
23239
  if (key === AI_SECTIONS_KEY) continue;
23240
+ if (key === LOGO_PLACEHOLDER_KEY) continue;
23241
+ if (LOGO_IMAGE_KEYS.includes(key)) continue;
21217
23242
  if (key === BRAND_KIT_KEY) continue;
21218
23243
  if (key === STYLE_STORE_KEY) continue;
21219
23244
  if (BRAND_CHROME_KEYS.has(key)) continue;
21220
- if (key === LOGO_PLACEHOLDER_KEY) continue;
21221
- if (LOGO_IMAGE_KEYS.includes(key)) continue;
21222
23245
  if (applyVideoSettingNode(key, val)) continue;
21223
23246
  if (applyCarouselNode(key, val)) continue;
21224
23247
  document.querySelectorAll(`[data-ohw-key="${key}"]`).forEach((el) => {
@@ -21249,6 +23272,7 @@ function OhhwellsBridge() {
21249
23272
  reconcileSocialsFromContent(content);
21250
23273
  applySocialsDisplayFromContent(content);
21251
23274
  applySocialsLabelsFromContent(content, document, { fillEmptyOnly: isEditModeRef.current });
23275
+ syncButtonsFromContent(document, content);
21252
23276
  if (isEditModeRef.current) requestMissingSocialIconsRef.current();
21253
23277
  if (typeof content[STYLE_STORE_KEY] === "string") {
21254
23278
  applyStylesToDom(parseStyleStore(content[STYLE_STORE_KEY]));
@@ -21269,6 +23293,17 @@ function OhhwellsBridge() {
21269
23293
  debounceTimer = setTimeout(applyFromCache, 150);
21270
23294
  };
21271
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
+ }
21272
23307
  observer = new MutationObserver(scheduleApply);
21273
23308
  observer.observe(document.body, { childList: true, subtree: true });
21274
23309
  return () => {
@@ -21337,7 +23372,10 @@ function OhhwellsBridge() {
21337
23372
  if (roots.length === 0) {
21338
23373
  reconcileNavbarItemsFromContent(contentForNav());
21339
23374
  reconcileFooterOrderFromContent(contentForNav());
21340
- if (isEditMode) syncNavigationDragCursorAttrs();
23375
+ if (isEditMode) {
23376
+ syncButtonsFromContent(document, contentForNav());
23377
+ syncNavigationDragCursorAttrs();
23378
+ }
21341
23379
  return;
21342
23380
  }
21343
23381
  let rafId = null;
@@ -21349,7 +23387,7 @@ function OhhwellsBridge() {
21349
23387
  }
21350
23388
  };
21351
23389
  const run = () => {
21352
- if (footerDragRef.current || navDragRef.current || applying) return;
23390
+ if (footerDragRef.current || navDragRef.current || buttonCollectionDragRef.current || applying) return;
21353
23391
  applying = true;
21354
23392
  observer?.disconnect();
21355
23393
  try {
@@ -21359,6 +23397,7 @@ function OhhwellsBridge() {
21359
23397
  reconcileSocialsFromContent(content);
21360
23398
  applySocialsDisplayFromContent(content);
21361
23399
  applySocialsLabelsFromContent(content, document, { fillEmptyOnly: isEditModeRef.current });
23400
+ syncButtonsFromContent(document, content);
21362
23401
  if (isEditModeRef.current) requestMissingSocialIconsRef.current();
21363
23402
  document.querySelectorAll("footer [data-ohw-href-key]").forEach((el) => {
21364
23403
  if (isFooterHrefKey(el.getAttribute("data-ohw-href-key")) || getSocialItem(el)) {
@@ -21366,9 +23405,9 @@ function OhhwellsBridge() {
21366
23405
  }
21367
23406
  });
21368
23407
  document.querySelectorAll(
21369
- "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"]'
21370
23409
  ).forEach((el) => {
21371
- if (isNavbarHrefKey(el.getAttribute("data-ohw-href-key"))) {
23410
+ if (isNavbarHrefKey(el.getAttribute("data-ohw-href-key")) || isCtaButton(el)) {
21372
23411
  disableNativeHrefDrag(el);
21373
23412
  }
21374
23413
  });
@@ -21393,6 +23432,44 @@ function OhhwellsBridge() {
21393
23432
  observer?.disconnect();
21394
23433
  };
21395
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]);
21396
23473
  (0, import_react19.useEffect)(() => {
21397
23474
  if (!isEditMode) return;
21398
23475
  let lastPosted = 0;
@@ -21415,26 +23492,11 @@ function OhhwellsBridge() {
21415
23492
  const t2 = setTimeout(measure, 500);
21416
23493
  const ro = new ResizeObserver(schedule);
21417
23494
  ro.observe(document.body);
21418
- let lastWidth = window.innerWidth;
21419
- let resizeTimers = [];
21420
- const clearResizeTimers = () => {
21421
- resizeTimers.forEach(clearTimeout);
21422
- resizeTimers = [];
21423
- };
21424
- const handleResize = () => {
21425
- if (window.innerWidth === lastWidth) return;
21426
- lastWidth = window.innerWidth;
21427
- clearResizeTimers();
21428
- resizeTimers = HEIGHT_SETTLE_DELAYS.map((delay) => setTimeout(measure, delay));
21429
- };
21430
- window.addEventListener("resize", handleResize);
21431
23495
  return () => {
21432
23496
  clearTimeout(t1);
21433
23497
  clearTimeout(t2);
21434
23498
  if (raf != null) cancelAnimationFrame(raf);
21435
23499
  ro.disconnect();
21436
- clearResizeTimers();
21437
- window.removeEventListener("resize", handleResize);
21438
23500
  };
21439
23501
  }, [pathname, isEditMode, postToParent2]);
21440
23502
  (0, import_react19.useEffect)(() => {
@@ -21729,6 +23791,13 @@ function OhhwellsBridge() {
21729
23791
  if (target.closest("[data-ohw-state-toggle]")) return;
21730
23792
  if (target.closest("[data-ohw-max-badge]")) return;
21731
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
+ }
21732
23801
  if (selectedMediaElRef.current && !selectedMediaElRef.current.contains(target) && !target.closest("[data-ohw-media-overlay], [data-ohw-edit-chrome], [data-ohw-item-interaction]")) {
21733
23802
  clearMediaSelectionRef.current();
21734
23803
  }
@@ -21749,13 +23818,6 @@ function OhhwellsBridge() {
21749
23818
  return;
21750
23819
  }
21751
23820
  }
21752
- if (isInsideFloatingPanel(target)) return;
21753
- if (target.closest("[data-ohw-form-toolbar]")) return;
21754
- if (target.closest(
21755
- '[data-ohw-field-toolbar], [data-ohw-field-type-picker], [data-radix-popper-content-wrapper], [role="menu"], [data-slot="dropdown-menu-content"]'
21756
- )) {
21757
- return;
21758
- }
21759
23821
  {
21760
23822
  const formEl = getFormElement(target);
21761
23823
  const onSuccessText = target.closest(`[${SUCCESS_TEXT_ATTR}]`);
@@ -21919,14 +23981,6 @@ function OhhwellsBridge() {
21919
23981
  }
21920
23982
  const clickedButton = findClosestButtonLike(target);
21921
23983
  const buttonOnMedia = Boolean(clickedButton && isMediaEditable(editable) && editable.contains(clickedButton));
21922
- console.log("[click-debug]", {
21923
- editableType: editable.dataset.ohwEditable,
21924
- editableTag: editable.tagName,
21925
- targetTag: target.tagName,
21926
- clickedButtonTag: clickedButton?.tagName ?? null,
21927
- buttonOnMedia,
21928
- isMediaEditableEditable: isMediaEditable(editable)
21929
- });
21930
23984
  if (isMediaEditable(editable) && !buttonOnMedia) {
21931
23985
  e.preventDefault();
21932
23986
  e.stopPropagation();
@@ -21961,11 +24015,6 @@ function OhhwellsBridge() {
21961
24015
  const hrefLookupTarget = buttonOnMedia ? clickedButton : editable;
21962
24016
  const hrefCtx = getHrefKeyFromElement(hrefLookupTarget);
21963
24017
  const navAnchor = hrefCtx ? getNavigationItemAnchor(hrefCtx.anchor) : null;
21964
- console.log("[click-debug 2]", {
21965
- hrefLookupTargetTag: hrefLookupTarget.tagName,
21966
- hrefCtx: hrefCtx ? { key: hrefCtx.key } : null,
21967
- navAnchorTag: navAnchor?.tagName ?? null
21968
- });
21969
24018
  if (navAnchor) {
21970
24019
  e.preventDefault();
21971
24020
  e.stopPropagation();
@@ -22148,6 +24197,9 @@ function OhhwellsBridge() {
22148
24197
  setHoveredItemRect(null);
22149
24198
  hoveredNavContainerRef.current = null;
22150
24199
  setHoveredNavContainerRect(null);
24200
+ siblingHintElRef.current = null;
24201
+ setSiblingHintRect(null);
24202
+ setSiblingHintRects([]);
22151
24203
  return;
22152
24204
  }
22153
24205
  {
@@ -22268,7 +24320,6 @@ function OhhwellsBridge() {
22268
24320
  hoveredNavContainerRef.current = null;
22269
24321
  setHoveredNavContainerRect(null);
22270
24322
  hoveredItemElRef.current = editable;
22271
- setHoveredItemRect(isSelectedForHoverRef.current(editable) ? null : editable.getBoundingClientRect());
22272
24323
  }
22273
24324
  }
22274
24325
  }
@@ -22481,6 +24532,20 @@ function OhhwellsBridge() {
22481
24532
  }
22482
24533
  return;
22483
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
+ }
22484
24549
  const navContainers = Array.from(
22485
24550
  document.querySelectorAll("[data-ohw-nav-container]")
22486
24551
  );
@@ -22572,7 +24637,7 @@ function OhhwellsBridge() {
22572
24637
  };
22573
24638
  const probeImageAt = (clientX, clientY, isDragOver = false, fromParentViewport = false) => {
22574
24639
  syncSelectedMediaPointerRelease(true);
22575
- if (linkPopoverOpenRef.current) {
24640
+ if (linkPopoverOpenRef.current || floatingPanelOpenRef.current && isPointOverFloatingPanel(clientX, clientY)) {
22576
24641
  if (hoveredImageRef.current) {
22577
24642
  hoveredImageRef.current = null;
22578
24643
  hoveredImageHasTextOverlapRef.current = false;
@@ -22583,6 +24648,24 @@ function OhhwellsBridge() {
22583
24648
  return;
22584
24649
  }
22585
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
+ }
22586
24669
  if (isPointOverNavigation(x, y)) {
22587
24670
  dismissImageHover();
22588
24671
  probeNavigationHoverAt(x, y);
@@ -22946,7 +25029,11 @@ function OhhwellsBridge() {
22946
25029
  };
22947
25030
  const handleMouseMove = (e) => {
22948
25031
  const { clientX, clientY } = e;
22949
- if (pointOwnedByFloatingPanel(clientX, clientY)) return;
25032
+ if (pointOwnedByFloatingPanel(clientX, clientY)) {
25033
+ dismissImageHover();
25034
+ clearImageHover();
25035
+ return;
25036
+ }
22950
25037
  if (isNavDrawerOpen() && isPointOutsideOpenDrawer(clientX, clientY)) {
22951
25038
  document.querySelectorAll("[data-ohw-hovered]").forEach((el) => el.removeAttribute("data-ohw-hovered"));
22952
25039
  formHoverElRef.current = null;
@@ -22975,7 +25062,12 @@ function OhhwellsBridge() {
22975
25062
  setHoveredItemRect(null);
22976
25063
  hoveredNavContainerRef.current = null;
22977
25064
  setHoveredNavContainerRect(null);
25065
+ siblingHintElRef.current = null;
25066
+ setSiblingHintRect(null);
25067
+ setSiblingHintRects([]);
22978
25068
  dismissImageHover();
25069
+ clearImageHover();
25070
+ setSectionGap(null);
22979
25071
  return;
22980
25072
  }
22981
25073
  if (probeSocialsRowAt(clientX, clientY)) return;
@@ -22987,7 +25079,11 @@ function OhhwellsBridge() {
22987
25079
  if (e.data?.type !== "ow:pointer-sync") return;
22988
25080
  const { clientX, clientY } = e.data;
22989
25081
  if (typeof clientX !== "number" || typeof clientY !== "number") return;
22990
- if (pointOwnedByFloatingPanel(clientX, clientY)) return;
25082
+ if (pointOwnedByFloatingPanel(clientX, clientY)) {
25083
+ dismissImageHover();
25084
+ clearImageHover();
25085
+ return;
25086
+ }
22991
25087
  if (isNavDrawerOpen() && isPointOutsideOpenDrawer(clientX, clientY)) return;
22992
25088
  if (probeSocialsRowAt(clientX, clientY)) return;
22993
25089
  probeSectionGapAt(clientX, clientY);
@@ -23004,6 +25100,11 @@ function OhhwellsBridge() {
23004
25100
  return;
23005
25101
  }
23006
25102
  if (onNavDragOver(e)) return;
25103
+ if (buttonCollectionDragRef.current) {
25104
+ e.preventDefault();
25105
+ if (e.dataTransfer) e.dataTransfer.dropEffect = "move";
25106
+ return;
25107
+ }
23007
25108
  e.preventDefault();
23008
25109
  const el = findImageAtPoint(e.clientX, e.clientY, false);
23009
25110
  if (!el) {
@@ -23028,6 +25129,7 @@ function OhhwellsBridge() {
23028
25129
  };
23029
25130
  const handleDrop = (e) => {
23030
25131
  e.preventDefault();
25132
+ if (buttonCollectionDragRef.current || navDragRef.current || footerDragRef.current) return;
23031
25133
  const el = findImageAtPoint(e.clientX, e.clientY, false);
23032
25134
  if (!el) return;
23033
25135
  const file = e.dataTransfer?.files?.[0];
@@ -23327,9 +25429,11 @@ function OhhwellsBridge() {
23327
25429
  }
23328
25430
  if (typeof content[STYLE_STORE_KEY] === "string") {
23329
25431
  stylesRef.current = content[STYLE_STORE_KEY];
25432
+ document.querySelectorAll('[data-ohw-editable="form"]').forEach((f) => markFormFields(f));
23330
25433
  applyStylesToDom(parseStyleStore(content[STYLE_STORE_KEY]));
23331
25434
  }
23332
25435
  applyBrandChrome(content);
25436
+ applyCustomCodeToHead(content);
23333
25437
  initSectionInstancesFromContent(content, window.location.pathname);
23334
25438
  let sectionsJson = null;
23335
25439
  for (const [key, val] of Object.entries(content)) {
@@ -23338,11 +25442,12 @@ function OhhwellsBridge() {
23338
25442
  continue;
23339
25443
  }
23340
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;
23341
25447
  if (key === BRAND_KIT_KEY) continue;
23342
25448
  if (key === STYLE_STORE_KEY) continue;
23343
25449
  if (BRAND_CHROME_KEYS.has(key)) continue;
23344
- if (key === LOGO_PLACEHOLDER_KEY) continue;
23345
- if (LOGO_IMAGE_KEYS.includes(key) && !String(val ?? "").trim()) continue;
25450
+ if (key === CUSTOM_CODE_AFTER_HEAD_KEY || key === CUSTOM_CODE_BEFORE_HEAD_KEY) continue;
23346
25451
  if (applyVideoSettingNode(key, val)) continue;
23347
25452
  if (applyCarouselNode(key, val)) continue;
23348
25453
  document.querySelectorAll(`[data-ohw-key="${key}"]`).forEach((el) => {
@@ -23381,6 +25486,7 @@ function OhhwellsBridge() {
23381
25486
  editContentRef.current = { ...editContentRef.current, ...content };
23382
25487
  reconcileNavbarItemsFromContent(editContentRef.current);
23383
25488
  reconcileFooterOrderFromContent(editContentRef.current);
25489
+ syncButtonsFromContent(document, editContentRef.current);
23384
25490
  syncNavigationDragCursorAttrs();
23385
25491
  if (typeof content[STYLE_STORE_KEY] === "string") {
23386
25492
  applyStylesToDom(parseStyleStore(content[STYLE_STORE_KEY]));
@@ -23787,6 +25893,12 @@ function OhhwellsBridge() {
23787
25893
  closeLinkPopoverRef.current();
23788
25894
  return;
23789
25895
  }
25896
+ if (floatingPanelOpenRef.current) {
25897
+ setFloatingPanelRef.current(null);
25898
+ deselectRef.current();
25899
+ deactivateRef.current();
25900
+ return;
25901
+ }
23790
25902
  deselectRef.current();
23791
25903
  deactivateRef.current();
23792
25904
  clearMediaSelectionRef.current();
@@ -24053,6 +26165,16 @@ function OhhwellsBridge() {
24053
26165
  if (brandKitRef.current) {
24054
26166
  nodes.push({ key: BRAND_KIT_KEY, type: "brand", text: brandKitRef.current });
24055
26167
  }
26168
+ nodes.push({
26169
+ key: CUSTOM_CODE_BEFORE_HEAD_KEY,
26170
+ type: "text",
26171
+ text: editContentRef.current[CUSTOM_CODE_BEFORE_HEAD_KEY] ?? ""
26172
+ });
26173
+ nodes.push({
26174
+ key: CUSTOM_CODE_AFTER_HEAD_KEY,
26175
+ type: "text",
26176
+ text: editContentRef.current[CUSTOM_CODE_AFTER_HEAD_KEY] ?? ""
26177
+ });
24056
26178
  document.querySelectorAll('[data-ohw-editable="form"]').forEach((form) => {
24057
26179
  const formKey = formKeyOf(form);
24058
26180
  if (!formKey) return;
@@ -24075,8 +26197,12 @@ function OhhwellsBridge() {
24075
26197
  if (inserted) {
24076
26198
  const tracker = getSectionsTracker();
24077
26199
  postToParentRef.current({ type: "ow:change", nodes: [{ key: "__ohw_sections", text: tracker.textContent ?? "[]" }] });
24078
- const h = document.body.scrollHeight;
24079
- 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);
24080
26206
  }
24081
26207
  };
24082
26208
  const handleSwitchSchedule = (e) => {
@@ -24280,11 +26406,8 @@ function OhhwellsBridge() {
24280
26406
  };
24281
26407
  const handleParentScroll = (e) => {
24282
26408
  if (e.data?.type !== "ow:parent-scroll") return;
24283
- const { iframeOffsetTop, headerH, canvasH } = e.data;
24284
- parentScrollRef.current = { iframeOffsetTop, headerH, canvasH };
24285
- if (floatingPanelOpenRef.current) {
24286
- setParentScrollSnap({ iframeOffsetTop, headerH, canvasH });
24287
- }
26409
+ const { iframeOffsetTop, headerH, canvasH, promptBarH = 0 } = e.data;
26410
+ parentScrollRef.current = { iframeOffsetTop, headerH, canvasH, promptBarH };
24288
26411
  if (visibleViewportRef.current) {
24289
26412
  applyVisibleViewport(visibleViewportRef.current, parentScrollRef.current);
24290
26413
  }
@@ -24508,10 +26631,10 @@ function OhhwellsBridge() {
24508
26631
  window.removeEventListener("message", handleDeleteSection);
24509
26632
  window.removeEventListener("message", handleDuplicateSection);
24510
26633
  window.removeEventListener("message", handleDeactivate);
24511
- document.documentElement.removeAttribute("data-ohw-panel-dragging");
24512
26634
  window.removeEventListener("message", handleToastAction);
24513
26635
  window.removeEventListener("message", handleFormCount);
24514
26636
  window.removeEventListener("message", handleUiEscape);
26637
+ document.documentElement.removeAttribute("data-ohw-panel-dragging");
24515
26638
  autoSaveTimers.current.forEach(clearTimeout);
24516
26639
  autoSaveTimers.current.clear();
24517
26640
  if (imageUnhoverTimerRef.current) clearTimeout(imageUnhoverTimerRef.current);
@@ -24674,6 +26797,97 @@ function OhhwellsBridge() {
24674
26797
  unlockFooterDragInteraction();
24675
26798
  };
24676
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]);
24677
26891
  (0, import_react19.useEffect)(() => {
24678
26892
  const handler = (e) => {
24679
26893
  if (e.data?.type !== "ow:request-schedule-config") return;
@@ -24714,7 +26928,7 @@ function OhhwellsBridge() {
24714
26928
  postToParent2({
24715
26929
  type: "ow:ready",
24716
26930
  version: "1",
24717
- bridgeVersion: "0.1.119",
26931
+ bridgeVersion: "0.1.122",
24718
26932
  path: pathname,
24719
26933
  nodes: collectEditableNodes(editContentRef.current),
24720
26934
  sections
@@ -24793,12 +27007,12 @@ function OhhwellsBridge() {
24793
27007
  return;
24794
27008
  }
24795
27009
  if (type !== "ow:logo-size-set") return;
24796
- const px = Number(e.data.px);
24797
- if (!Number.isFinite(px)) return;
24798
- 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 } : {
24799
27013
  ...logoSizeDraft,
24800
- desktopPx: px,
24801
- mobilePx: logoSizeDraft.mobileFollowing ? px : logoSizeDraft.mobilePx
27014
+ desktopPx: px2,
27015
+ mobilePx: logoSizeDraft.mobileFollowing ? px2 : logoSizeDraft.mobilePx
24802
27016
  };
24803
27017
  setLogoSizeDraft(next);
24804
27018
  persistLogoSizeDraft(session.placement, next);
@@ -24896,6 +27110,36 @@ function OhhwellsBridge() {
24896
27110
  const handleDuplicateSelected = (0, import_react19.useCallback)(() => {
24897
27111
  const selected = selectedElRef.current;
24898
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
+ }
24899
27143
  const hrefKey = selected.getAttribute("data-ohw-href-key");
24900
27144
  if (!hrefKey) return;
24901
27145
  const social = getSocialItem(selected);
@@ -25025,7 +27269,7 @@ function OhhwellsBridge() {
25025
27269
  selectRef.current(result.anchor);
25026
27270
  });
25027
27271
  }
25028
- }, [postToParent2]);
27272
+ }, [postToParent2, tokensForButton]);
25029
27273
  const runPendingDeleteUndo = (0, import_react19.useCallback)(() => {
25030
27274
  const pending = pendingDeleteUndoRef.current;
25031
27275
  if (!pending) return false;
@@ -25037,6 +27281,38 @@ function OhhwellsBridge() {
25037
27281
  const handleDeleteSelected = (0, import_react19.useCallback)(() => {
25038
27282
  const selected = selectedElRef.current;
25039
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
+ }
25040
27316
  return deleteSelectedNavFooterItem({
25041
27317
  selected,
25042
27318
  isTextEditing: Boolean(activeElRef.current),
@@ -25174,14 +27450,14 @@ function OhhwellsBridge() {
25174
27450
  },
25175
27451
  [postToParent2]
25176
27452
  );
25177
- return /* @__PURE__ */ (0, import_jsx_runtime35.jsxs)(import_jsx_runtime35.Fragment, { children: [
25178
- /* @__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, {}) }),
25179
- /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("script", { suppressHydrationWarning: true, dangerouslySetInnerHTML: { __html: OHW_LOADER_PREHYDRATE_SCRIPT } }),
25180
- 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, {}),
25181
27457
  bridgeRoot ? (0, import_react_dom5.createPortal)(
25182
- /* @__PURE__ */ (0, import_jsx_runtime35.jsxs)(import_jsx_runtime35.Fragment, { children: [
25183
- /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("div", { ref: attachVisibleViewport, "data-ohw-visible-viewport": "", "aria-hidden": true }),
25184
- 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)(
25185
27461
  AiSectionOverlay,
25186
27462
  {
25187
27463
  postToParent: postToParent2,
@@ -25189,7 +27465,7 @@ function OhhwellsBridge() {
25189
27465
  onSelectionChange: handleAiSectionSelectionChange
25190
27466
  }
25191
27467
  ),
25192
- isEditMode && /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(
27468
+ isEditMode && /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(
25193
27469
  AiMediaStripOverlay,
25194
27470
  {
25195
27471
  postToParent: postToParent2,
@@ -25198,7 +27474,7 @@ function OhhwellsBridge() {
25198
27474
  clearInteractionChromeRef
25199
27475
  }
25200
27476
  ),
25201
- isEditMode && /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(
27477
+ isEditMode && /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(
25202
27478
  AiPricingCardOverlay,
25203
27479
  {
25204
27480
  postToParent: postToParent2,
@@ -25206,12 +27482,12 @@ function OhhwellsBridge() {
25206
27482
  applyTreeLocalRef
25207
27483
  }
25208
27484
  ),
25209
- isSectionDragging && sectionDropSlots.map((slot, i) => /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(
27485
+ isSectionDragging && sectionDropSlots.map((slot, i) => /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(
25210
27486
  "div",
25211
27487
  {
25212
27488
  className: "pointer-events-none fixed z-2147483646",
25213
27489
  style: { left: slot.left, top: slot.y, width: slot.width, height: 3, transform: "translateY(-50%)" },
25214
- children: /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(
27490
+ children: /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(
25215
27491
  DropIndicator,
25216
27492
  {
25217
27493
  direction: "horizontal",
@@ -25222,7 +27498,7 @@ function OhhwellsBridge() {
25222
27498
  },
25223
27499
  `section-drop-${slot.insertIndex}-${i}`
25224
27500
  )),
25225
- 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)(
25226
27502
  MediaOverlay,
25227
27503
  {
25228
27504
  hover: { key, rect, elementType: "image", isDragOver: false, hasTextOverlap: false },
@@ -25233,7 +27509,7 @@ function OhhwellsBridge() {
25233
27509
  },
25234
27510
  `uploading-${key}`
25235
27511
  )),
25236
- 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)(
25237
27513
  MediaOverlay,
25238
27514
  {
25239
27515
  hover: mediaHover,
@@ -25244,7 +27520,7 @@ function OhhwellsBridge() {
25244
27520
  onVideoSettingsChange: handleVideoSettingsChange
25245
27521
  }
25246
27522
  ),
25247
- 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)(
25248
27524
  MediaOverlay,
25249
27525
  {
25250
27526
  hover: selectedMedia,
@@ -25256,7 +27532,7 @@ function OhhwellsBridge() {
25256
27532
  onVideoSettingsChange: handleVideoSettingsChange
25257
27533
  }
25258
27534
  ),
25259
- carouselHover && !linkPopover && !isItemDragging && /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(
27535
+ carouselHover && !linkPopover && !isItemDragging && /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(
25260
27536
  CarouselOverlay,
25261
27537
  {
25262
27538
  hover: carouselHover,
@@ -25264,10 +27540,10 @@ function OhhwellsBridge() {
25264
27540
  cueOnly: !!carouselHover.fullBleedSectionId && carouselHover.fullBleedSectionId !== selectedAiSectionId
25265
27541
  }
25266
27542
  ),
25267
- siblingHintRect && !linkPopover && !isItemDragging && siblingHintRects.length === 0 && /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(ItemInteractionLayer, { rect: siblingHintRect, state: "sibling-hint" }),
25268
- siblingHintRects.length > 0 && !linkPopover && !isItemDragging && siblingHintRects.map((rect, i) => /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(ItemInteractionLayer, { rect, state: "sibling-hint" }, `sibling-hint-${i}`)),
25269
- isItemDragging && draggedItemRect && selectedElRef.current !== footerDragRef.current?.draggedEl && selectedElRef.current !== navDragRef.current?.draggedEl && /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(ItemInteractionLayer, { rect: draggedItemRect, state: "dragging" }),
25270
- 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)(
25271
27547
  "div",
25272
27548
  {
25273
27549
  className: "pointer-events-none fixed z-2147483646",
@@ -25277,7 +27553,7 @@ function OhhwellsBridge() {
25277
27553
  width: slot.width,
25278
27554
  height: slot.height
25279
27555
  },
25280
- children: /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(
27556
+ children: /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(
25281
27557
  DropIndicator,
25282
27558
  {
25283
27559
  direction: slot.direction,
@@ -25288,7 +27564,7 @@ function OhhwellsBridge() {
25288
27564
  },
25289
27565
  `footer-drop-${slot.direction}-${slot.columnIndex}-${slot.insertIndex}-${i}`
25290
27566
  )),
25291
- isItemDragging && navDropSlots.map((slot, i) => /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(
27567
+ isItemDragging && navDropSlots.map((slot, i) => /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(
25292
27568
  "div",
25293
27569
  {
25294
27570
  className: "pointer-events-none fixed z-2147483646",
@@ -25298,7 +27574,7 @@ function OhhwellsBridge() {
25298
27574
  width: slot.width,
25299
27575
  height: slot.height
25300
27576
  },
25301
- children: /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(
27577
+ children: /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(
25302
27578
  DropIndicator,
25303
27579
  {
25304
27580
  direction: slot.direction,
@@ -25309,10 +27585,31 @@ function OhhwellsBridge() {
25309
27585
  },
25310
27586
  `nav-drop-${slot.direction}-${slot.parentId ?? "root"}-${slot.insertIndex}-${i}`
25311
27587
  )),
25312
- hoveredNavContainerRect && (toolbarVariant !== "select-frame" || isFooterFrameSelection) && !linkPopover && !isItemDragging && /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(ItemInteractionLayer, { rect: hoveredNavContainerRect, state: "hover" }),
25313
- hoveredItemRect && !linkPopover && !hoveredNavContainerRect && !isItemDragging && hoveredItemElRef.current !== selectedElRef.current && /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(ItemInteractionLayer, { rect: hoveredItemRect, state: "hover" }),
25314
- hoveredTextRect && !hoveredNavContainerRect && !hoveredItemRect && !isItemDragging && toolbarVariant !== "rich-text" && /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(ItemInteractionLayer, { rect: hoveredTextRect, state: "hover" }),
25315
- 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)(
25316
27613
  ItemInteractionLayer,
25317
27614
  {
25318
27615
  rect: formPickRect,
@@ -25320,13 +27617,13 @@ function OhhwellsBridge() {
25320
27617
  itemDragSurface: false,
25321
27618
  toolbarAlign: "left",
25322
27619
  chromeGap: 24,
25323
- toolbar: fieldPickRect ? void 0 : /* @__PURE__ */ (0, import_jsx_runtime35.jsxs)(
27620
+ toolbar: fieldPickRect ? void 0 : /* @__PURE__ */ (0, import_jsx_runtime36.jsxs)(
25324
27621
  "div",
25325
27622
  {
25326
27623
  "data-ohw-form-toolbar": "",
25327
27624
  className: "pointer-events-auto flex items-center gap-0.5 whitespace-nowrap rounded-lg border border-border bg-background p-1 shadow-md",
25328
27625
  children: [
25329
- /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(
27626
+ /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(
25330
27627
  "button",
25331
27628
  {
25332
27629
  type: "button",
@@ -25334,11 +27631,11 @@ function OhhwellsBridge() {
25334
27631
  className: "flex h-7 w-7 items-center justify-center rounded-md text-foreground transition-colors hover:bg-muted/80",
25335
27632
  onClick: () => setFieldTypePickerOpen((open) => !open),
25336
27633
  "data-ohw-add-field": "",
25337
- 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 })
25338
27635
  }
25339
27636
  ),
25340
- /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("div", { className: "mx-0.5 h-5 w-px bg-border" }),
25341
- /* @__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)(
25342
27639
  "button",
25343
27640
  {
25344
27641
  type: "button",
@@ -25354,11 +27651,11 @@ function OhhwellsBridge() {
25354
27651
  });
25355
27652
  },
25356
27653
  children: [
25357
- /* @__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 }),
25358
27655
  "Form settings",
25359
27656
  formPickCount ? (
25360
27657
  // Counter pill, per the design — not a text suffix.
25361
- /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(
27658
+ /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(
25362
27659
  "span",
25363
27660
  {
25364
27661
  "data-ohw-form-count": "",
@@ -25370,8 +27667,8 @@ function OhhwellsBridge() {
25370
27667
  ]
25371
27668
  }
25372
27669
  ),
25373
- /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("div", { className: "mx-0.5 h-5 w-px bg-border" }),
25374
- /* @__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)(
25375
27672
  "button",
25376
27673
  {
25377
27674
  type: "button",
@@ -25402,7 +27699,7 @@ function OhhwellsBridge() {
25402
27699
  )
25403
27700
  }
25404
27701
  ),
25405
- formHoverRect && !isItemDragging && /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(
27702
+ formHoverRect && !isItemDragging && /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(
25406
27703
  ItemInteractionLayer,
25407
27704
  {
25408
27705
  rect: formHoverRect,
@@ -25410,7 +27707,7 @@ function OhhwellsBridge() {
25410
27707
  chromeGap: formHoverElRef.current && getFieldWrapper(formHoverElRef.current) ? 8 : 24
25411
27708
  }
25412
27709
  ),
25413
- 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)(
25414
27711
  ItemInteractionLayer,
25415
27712
  {
25416
27713
  rect: fieldPickRect,
@@ -25422,7 +27719,7 @@ function OhhwellsBridge() {
25422
27719
  dragHandleLabel: "Reorder field",
25423
27720
  onDragHandleDragStart: handleFieldDragStart,
25424
27721
  onDragHandleDragEnd: handleFieldDragEnd,
25425
- toolbar: /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(
27722
+ toolbar: /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(
25426
27723
  FormFieldToolbar,
25427
27724
  {
25428
27725
  type: fieldPickState.type,
@@ -25435,12 +27732,12 @@ function OhhwellsBridge() {
25435
27732
  )
25436
27733
  }
25437
27734
  ),
25438
- fieldDragging ? fieldDropSlots.map((slot, i) => /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(
27735
+ fieldDragging ? fieldDropSlots.map((slot, i) => /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(
25439
27736
  "div",
25440
27737
  {
25441
27738
  className: "pointer-events-none fixed z-[2147483644]",
25442
27739
  style: { top: slot.top, left: slot.left, width: slot.width },
25443
- children: /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(
27740
+ children: /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(
25444
27741
  DropIndicator,
25445
27742
  {
25446
27743
  direction: "horizontal",
@@ -25453,7 +27750,7 @@ function OhhwellsBridge() {
25453
27750
  )) : null,
25454
27751
  fieldTypePickerOpen && formPickRect ? (() => {
25455
27752
  const toolbar = document.querySelector("[data-ohw-form-toolbar]")?.getBoundingClientRect();
25456
- return /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(
27753
+ return /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(
25457
27754
  "div",
25458
27755
  {
25459
27756
  className: "pointer-events-none fixed z-[2147483645]",
@@ -25461,12 +27758,12 @@ function OhhwellsBridge() {
25461
27758
  top: toolbar ? toolbar.bottom + 6 : formPickRect.top + 16,
25462
27759
  left: toolbar ? toolbar.left : formPickRect.left + 24
25463
27760
  },
25464
- children: /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(FieldTypePicker, { onPick: handleAddField })
27761
+ children: /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(FieldTypePicker, { onPick: handleAddField })
25465
27762
  }
25466
27763
  );
25467
27764
  })() : null,
25468
- toolbarVariant === "select-frame" && toolbarRect && !linkPopover && !isItemDragging && !isFooterFrameSelection && selectedElRef.current && isNavbarLinksContainer2(selectedElRef.current) && /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(NavbarContainerChrome, { rect: toolbarRect, onAdd: handleAddTopLevelNavItem }),
25469
- 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)(
25470
27767
  FooterContainerChrome,
25471
27768
  {
25472
27769
  rect: toolbarRect,
@@ -25474,13 +27771,17 @@ function OhhwellsBridge() {
25474
27771
  addDisabled: !canAddFooterColumn()
25475
27772
  }
25476
27773
  ),
25477
- 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)(
25478
27775
  ItemInteractionLayer,
25479
27776
  {
25480
- 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,
25481
27778
  toolbarBelowRect: selectedElRef.current && isNavigationItem2(selectedElRef.current) ? getOpenNavDropdownPanelRect(selectedElRef.current) : null,
25482
27779
  toolbarBelowEl: selectedElRef.current && isNavigationItem2(selectedElRef.current) ? getOpenNavDropdownPanel(selectedElRef.current) : null,
25483
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
+ ),
25484
27785
  state: isItemDragging ? "dragging" : selectedElRef.current && isNavigationItem2(selectedElRef.current) && getOpenNavDropdownPanel(selectedElRef.current) ? "active-bottom" : resolveItemInteractionState(toolbarRect, parentScrollRef.current),
25485
27786
  showHandle: toolbarVariant === "link-action" && Boolean(reorderHrefKey) || toolbarVariant === "select-frame" && isFooterFrameSelection,
25486
27787
  dragDisabled: reorderDragDisabled,
@@ -25490,18 +27791,25 @@ function OhhwellsBridge() {
25490
27791
  onItemPointerDown: toolbarVariant === "logo" ? void 0 : handleItemChromePointerDown,
25491
27792
  onItemClick: toolbarVariant === "logo" ? void 0 : handleItemChromeClick,
25492
27793
  itemDragSurface: toolbarVariant !== "logo" && !isFooterFrameSelection && !selectedIsSocialsRow,
25493
- 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)(
25494
27795
  ItemActionToolbar,
25495
27796
  {
25496
27797
  onEditLink: openLinkPopoverForSelected,
25497
27798
  onStyle: () => {
25498
27799
  const row = selectedElRef.current;
25499
27800
  if (!row) return;
25500
- 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();
25501
27809
  else openSocialsDisplayPanel(row);
25502
27810
  },
25503
- showStyle: selectedIsSocialsRow,
25504
- styleActive: floatingPanel?.kind === "socials-display",
27811
+ showStyle: selectedIsSocialsRow || selectedIsCta,
27812
+ styleActive: floatingPanel?.kind === "socials-display" || floatingPanel?.kind === "button-style",
25505
27813
  onAddItem: handleAddChildItem,
25506
27814
  onSelectParent: handleSelectParent,
25507
27815
  onDuplicate: handleDuplicateSelected,
@@ -25512,11 +27820,11 @@ function OhhwellsBridge() {
25512
27820
  })(),
25513
27821
  editLinkDisabled: false,
25514
27822
  moreDisabled: false,
25515
- deleteDisabled: selectedElRef.current !== null && (() => {
27823
+ deleteDisabled: selectedElRef.current !== null && isCtaButton(selectedElRef.current) && !canDeleteButton(selectedElRef.current) || selectedElRef.current !== null && (() => {
25516
27824
  const social = getSocialItem(selectedElRef.current);
25517
27825
  return social ? !canRemoveSocialItem(social) : false;
25518
27826
  })(),
25519
- duplicateDisabled: isFooterFrameSelection || selectedIsSocialsRow || selectedElRef.current !== null && (() => {
27827
+ duplicateDisabled: isFooterFrameSelection || selectedIsSocialsRow || selectedElRef.current !== null && isCtaButton(selectedElRef.current) && !canDuplicateButton(selectedElRef.current) || selectedElRef.current !== null && (() => {
25520
27828
  const social = getSocialItem(selectedElRef.current);
25521
27829
  const row = social ? findSocialsRow(social) : null;
25522
27830
  return row ? !canAddSocialItem(row) : false;
@@ -25525,7 +27833,7 @@ function OhhwellsBridge() {
25525
27833
  showAddItem: isFooterFrameSelection || selectedIsSocialsRow || !selectedIsCta && Boolean(
25526
27834
  selectedElRef.current && isNavbarHrefKey(selectedElRef.current.getAttribute("data-ohw-href-key")) && !isNestedNavChild(selectedElRef.current)
25527
27835
  ),
25528
- showMore: (!selectedIsCta || isFooterFrameSelection) && !selectedIsSocialsRow,
27836
+ showMore: !selectedIsSocialsRow,
25529
27837
  dropdownOpen: navDropdownPreviewOpen,
25530
27838
  onDropdownOpenChange: handleNavDropdownOpenChange,
25531
27839
  headingVisible: footerHeadingVisible,
@@ -25534,8 +27842,8 @@ function OhhwellsBridge() {
25534
27842
  ) : void 0
25535
27843
  }
25536
27844
  ),
25537
- toolbarRect && toolbarVariant === "rich-text" && !linkPopover && !isRectOutsideClip(toolbarRect, getIframeVisibleClip(parentScrollRef.current)) && /* @__PURE__ */ (0, import_jsx_runtime35.jsxs)(import_jsx_runtime35.Fragment, { children: [
25538
- /* @__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)(
25539
27847
  EditGlowChrome,
25540
27848
  {
25541
27849
  rect: toolbarRect,
@@ -25545,7 +27853,7 @@ function OhhwellsBridge() {
25545
27853
  hideHandle: isItemDragging
25546
27854
  }
25547
27855
  ),
25548
- /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(
27856
+ /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(
25549
27857
  FloatingToolbar,
25550
27858
  {
25551
27859
  rect: toolbarRect,
@@ -25558,7 +27866,7 @@ function OhhwellsBridge() {
25558
27866
  }
25559
27867
  )
25560
27868
  ] }),
25561
- maxBadge && /* @__PURE__ */ (0, import_jsx_runtime35.jsxs)(
27869
+ maxBadge && /* @__PURE__ */ (0, import_jsx_runtime36.jsxs)(
25562
27870
  "div",
25563
27871
  {
25564
27872
  "data-ohw-max-badge": "",
@@ -25584,7 +27892,7 @@ function OhhwellsBridge() {
25584
27892
  ]
25585
27893
  }
25586
27894
  ),
25587
- toggleState && !linkPopover && /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(
27895
+ toggleState && !linkPopover && /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(
25588
27896
  StateToggle,
25589
27897
  {
25590
27898
  rect: toggleState.rect,
@@ -25593,15 +27901,15 @@ function OhhwellsBridge() {
25593
27901
  onStateChange: handleStateChange
25594
27902
  }
25595
27903
  ),
25596
- sectionGap && !linkPopover && /* @__PURE__ */ (0, import_jsx_runtime35.jsxs)(
27904
+ sectionGap && !linkPopover && /* @__PURE__ */ (0, import_jsx_runtime36.jsxs)(
25597
27905
  "div",
25598
27906
  {
25599
27907
  "data-ohw-section-insert-line": "",
25600
27908
  className: "fixed left-0 w-full z-2147483646 flex items-center pointer-events-none",
25601
27909
  style: { top: sectionGap.y, transform: "translateY(-50%)" },
25602
27910
  children: [
25603
- /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("div", { className: "flex-1 bg-bridge-primary", style: { height: 3 } }),
25604
- /* @__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)(
25605
27913
  Badge,
25606
27914
  {
25607
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",
@@ -25618,11 +27926,11 @@ function OhhwellsBridge() {
25618
27926
  children: "Add Section"
25619
27927
  }
25620
27928
  ),
25621
- /* @__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 } })
25622
27930
  ]
25623
27931
  }
25624
27932
  ),
25625
- linkPopover && dialogPortalContainer ? /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(
27933
+ linkPopover && dialogPortalContainer ? /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(
25626
27934
  LinkPopover,
25627
27935
  {
25628
27936
  panelRef: linkPopoverPanelRef,
@@ -25639,7 +27947,7 @@ function OhhwellsBridge() {
25639
27947
  },
25640
27948
  linkPopover.key
25641
27949
  ) : null,
25642
- floatingPanel && floatingPanel.kind === "socials-display" ? /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(
27950
+ floatingPanel && floatingPanel.kind === "socials-display" ? /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(
25643
27951
  FloatingPanel,
25644
27952
  {
25645
27953
  open: true,
@@ -25649,7 +27957,7 @@ function OhhwellsBridge() {
25649
27957
  onPositionChange: setFloatingPanelPos,
25650
27958
  parentScroll: parentScrollSnap ?? parentScrollRef.current,
25651
27959
  onClose: closeFloatingPanelOnly,
25652
- children: /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(
27960
+ children: /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(
25653
27961
  SocialsDisplayPanel,
25654
27962
  {
25655
27963
  display: socialsDisplayFor(floatingPanel.row, editContentRef.current),
@@ -25660,12 +27968,106 @@ function OhhwellsBridge() {
25660
27968
  }
25661
27969
  )
25662
27970
  }
25663
- ) : 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
25664
28013
  ] }),
25665
28014
  bridgeRoot
25666
28015
  ) : null
25667
28016
  ] });
25668
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
+ }
25669
28071
  // Annotate the CommonJS export names for ESM import in node:
25670
28072
  0 && (module.exports = {
25671
28073
  AI_DEFAULT_BRAND,
@@ -25683,6 +28085,7 @@ function OhhwellsBridge() {
25683
28085
  DropdownMenuItem,
25684
28086
  DropdownMenuSeparator,
25685
28087
  DropdownMenuTrigger,
28088
+ EmptySection,
25686
28089
  ItemActionToolbar,
25687
28090
  ItemInteractionLayer,
25688
28091
  LinkEditorPanel,