/* ════════════════════════════════════════════════════════════════════
   sb-buttons.css — SuprBad Button System
   Branch: feature/payment-gateway-failover

   Groups:
     0  Button tokens (:root — owned by THIS file since migration M3)
     1  @keyframes sb-rb-squash
     2  Tier 2 — Utility buttons (site-wide)
     3  Disabled state (Add to Cart, no variation selected)
     4  Tier 1 — Place Order CSS wave variant (Block Checkout + order-pay/form-pay.php)
     5  Tier 1 — Add to Cart JS wave shell (product page)
     6  Processing State — Committed State + Ink Trail (all SB button surfaces)

   See docs/button-system-migration-plan.md for architecture decisions.
   ════════════════════════════════════════════════════════════════════ */

/* ─── Group 0: Button tokens ─────────────────────────────────────── */

/*
  Repatriated from SBNav's nav-tokens.css at migration M3 (ADR-002,
  2026-07-13) — closes the deferred follow-up from Architecture
  Revision 1 (docs/FRONTEND-CHANGELOG.md): these six properties have a
  single real consumer (this file), so they belong here, not in SBNav's
  token file and not in the Shared Design System (they fail the
  Admission Test — see docs/FRONTEND-ARCHITECTURE.md).
  Values identical to the nav-tokens.css originals. Every var() call in
  this file also carries the same value as its fallback, so load order
  is irrelevant.
*/

:root {
  --sb-color-button-bg: #fff;
  --sb-color-button-fg: #000;
  --sb-color-button-border: #000;
  --sb-color-button-bg-active: #000;
  --sb-color-button-fg-active: #fff;
  --sb-color-button-border-active: #fff;
}

/* ─── Group 1: @keyframes ────────────────────────────────────────── */

@keyframes sb-rb-squash {
  0% {
    transform: scaleX(1) scaleY(1);
  }
  18% {
    transform: scaleX(1.024) scaleY(0.963);
  }
  44% {
    transform: scaleX(0.983) scaleY(1.02);
  }
  70% {
    transform: scaleX(1.008) scaleY(0.992);
  }
  100% {
    transform: scaleX(1) scaleY(1);
  }
}

/* ─── Group 2: Tier 2 — Utility buttons ─────────────────────────── */

/*
  Targets all WC buttons except the Tier 1 ATC and Place Order.
  Rubber squash on hover (CSS-only, no JS, no wrapper div).
  Press depress on :active.

  Audit note: .woocommerce a.button and .woocommerce button.button are broad.
  Verify on staging for false positives from plugin-generated editorial buttons.
*/

body .woocommerce a.button,
.woocommerce a.button.alt,
body .woocommerce button.button,
.woocommerce button.button.alt,
.woocommerce input[type='submit'],
.woocommerce input.button.alt,
.wc-block-cart__submit-button,
.wc-block-components-button:not(.wc-block-components-checkout-place-order-button),
.woocommerce-form-login__submit,
.woocommerce-address-fields .button,
.woocommerce-edit-account .button,
.wc-block-components-product-button
  a.wp-block-button__link.wp-element-button.wc-block-components-product-button__button,
.wp-block-button__link:not(.wc-block-components-product-button__button) {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  box-sizing: border-box;
  height: 62px;
  padding: 0 40px;
  border-radius: var(
    --sb-radius-pill,
    100px
  ); /* F2: shared radius scale (fallback keeps this file self-contained) */

  border: 2.5px solid var(--sb-color-button-border, #000);
  background: var(--sb-color-button-bg, #fff);
  font-family: inherit;
  font-size: 15px;
  font-weight: 900;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--sb-color-button-fg, #000);
  text-decoration: none;
  white-space: nowrap;
  overflow: hidden;
  cursor: pointer;
  -webkit-font-smoothing: antialiased;
  -webkit-appearance: none;
  appearance: none;
  user-select: none;
  -webkit-tap-highlight-color: transparent;
  transition:
    box-shadow 0.12s ease,
    background 0.1s ease-out,
    color 0.1s ease-out,
    border-color 0.1s ease-out;
}

/* F-BTN-6 (1.1.0): the Tier 2 utility pill clips its label on narrow phones —
   62px tall, 40px side padding and nowrap leave ~200px of label room at 360px.
   Below 22.5rem (360px) tighten the padding and type for every Tier 2 pill,
   and let only the My Account utility buttons (address / edit-account forms
   and the orders table) wrap onto a second line — the ATC and Place Order
   buttons are untouched. BUILT, not VERIFIED: needs a dated owner observation
   on a ~360px viewport (review §23). */
@media (max-width: 22.5rem) {
  body .woocommerce a.button,
  .woocommerce a.button.alt,
  body .woocommerce button.button,
  .woocommerce button.button.alt,
  .woocommerce input[type='submit'],
  .woocommerce input.button.alt,
  .wc-block-cart__submit-button,
  .wc-block-components-button:not(.wc-block-components-checkout-place-order-button),
  .woocommerce-form-login__submit,
  .woocommerce-address-fields .button,
  .woocommerce-edit-account .button,
  .wc-block-components-product-button
    a.wp-block-button__link.wp-element-button.wc-block-components-product-button__button,
  .wp-block-button__link:not(.wc-block-components-product-button__button) {
    padding: 0 1.25rem;
    font-size: 14px;
  }

  .woocommerce-address-fields .button,
  .woocommerce-edit-account .button,
  body .woocommerce table.my_account_orders a.button {
    height: auto;
    min-height: 62px;
    line-height: 1.2;
    text-align: center;
    white-space: normal;
    overflow-wrap: anywhere;
  }
}

/* Hover: rubber squash — replays on each mouseenter since keyframe ends at 1,1 */
body .woocommerce a.button:not(.disabled, :disabled):hover,
.woocommerce a.button.alt:not(.disabled, :disabled):hover,
body .woocommerce button.button:not(.disabled, :disabled):hover,
.woocommerce button.button.alt:not(.disabled, :disabled):hover,
.woocommerce input[type='submit']:not(.disabled, :disabled):hover,
.woocommerce input.button.alt:not(.disabled, :disabled):hover,
.wc-block-cart__submit-button:not(.disabled, :disabled):hover,
.wc-block-components-button:not(
    .wc-block-components-checkout-place-order-button,
    .disabled,
    :disabled
  ):hover,
.woocommerce-form-login__submit:not(.disabled, :disabled):hover,
.woocommerce-address-fields .button:not(.disabled, :disabled):hover,
.woocommerce-edit-account .button:not(.disabled, :disabled):hover,
.wc-block-components-product-button
  a.wp-block-button__link.wp-element-button.wc-block-components-product-button__button:not(
    .disabled,
    :disabled
  ):hover,
.wp-block-button__link:not(
    .wc-block-components-product-button__button,
    .disabled,
    :disabled
  ):hover {
  animation: sb-rb-squash 0.44s cubic-bezier(0.36, 0.07, 0.19, 0.97) forwards;
}

/* Active: press depress
   :not(.single_add_to_cart_button) on the two button.button selectors excludes
   the Group 5 Add to Cart button — it has classes "button alt" and sits inside
   a .woocommerce ancestor on single product pages, so it would otherwise match
   here and inherit background/color/border-color/box-shadow not designed for it.
   Same exclusion pattern as Group 4's :not(.wc-block-components-checkout-place-order-button). */
body .woocommerce a.button:active,
.woocommerce a.button.alt:active,
body .woocommerce button.button:not(.single_add_to_cart_button):active,
.woocommerce button.button.alt:not(.single_add_to_cart_button):active,
.woocommerce input[type='submit']:active,
.woocommerce input.button.alt:active,
.wc-block-cart__submit-button:active,
.wc-block-components-button:not(.wc-block-components-checkout-place-order-button):active,
.woocommerce-form-login__submit:active,
.woocommerce-address-fields .button:active,
.woocommerce-edit-account .button:active,
.wc-block-components-product-button
  a.wp-block-button__link.wp-element-button.wc-block-components-product-button__button:active,
.wp-block-button__link:not(.wc-block-components-product-button__button):active {
  transform: scale(0.965);
  background: var(--sb-color-button-bg-active, #000);
  color: var(--sb-color-button-fg-active, #fff);
  border-color: var(--sb-color-button-border-active, #fff);
  box-shadow: inset 0 2px 6px rgb(0 0 0 / 55%);
}

/* Orders table action buttons — WC's .woocommerce table.my_account_orders .button (0-3-1)
   and .woocommerce-account ...cell-order-actions a (0-3-1) both beat the shell (0-2-2).
   This rule restores the five overridden geometry properties at (0-3-3). */
body .woocommerce table.my_account_orders a.button {
  display: inline-flex;
  padding: 0 40px;
  font-size: 15px;
  font-weight: 900;
  text-transform: uppercase;
}

/* ─── Group 3: Disabled state — deviations from the active shell only ── */

/*
  The full ATC shell lives in Group 5 (.sb-atc-wrap button.single_add_to_cart_button).
  This group declares ONLY the three properties that differ in disabled state.

  Specificity here is 0-3-1, higher than the Group 5 shell at 0-2-1, so these
  overrides win without !important.

  cursor: pointer — clicking the disabled button triggers variation guidance
  animations (swatch highlighting). See:
  snippets/Products/Color and Size Swatch Tweaks - PHP Helper.php
*/

.sb-atc-wrap button.single_add_to_cart_button.disabled,
.sb-atc-wrap button.single_add_to_cart_button:disabled,
.sb-atc-wrap button.single_add_to_cart_button.wc-variation-selection-needed {
  border-color: rgb(0 0 0 / 20%);
  animation: none;
  cursor: pointer;
}

.sb-atc-wrap button.single_add_to_cart_button.disabled .sb-atc__ink,
.sb-atc-wrap button.single_add_to_cart_button:disabled .sb-atc__ink,
.sb-atc-wrap button.single_add_to_cart_button.wc-variation-selection-needed .sb-atc__ink {
  transform: translateY(102%);
  transition: none;
}

.sb-atc-wrap button.single_add_to_cart_button.disabled .sb-atc__label,
.sb-atc-wrap button.single_add_to_cart_button:disabled .sb-atc__label,
.sb-atc-wrap button.single_add_to_cart_button.wc-variation-selection-needed .sb-atc__label {
  transition: none;
}

/* ─── Group 4: Tier 1 — Place Order CSS wave variant ─────────────── */

/*
  Tier 1 Place Order button — two WooCommerce rendering contexts:

  (A) Block Checkout: .wc-block-components-checkout-place-order-button
      Selector verified on staging 2026-06-12.

  (B) Classic order-pay (form-pay.php): form#order_review #place_order
      Added 2026-06-30. Covers all order-pay pages including retry.
      Requires <span class="sb-place-order-text"> inside the button for z-index
      stacking (same role as .wc-block-components-button__text in context A).
      Injected server-side via the woocommerce_pay_order_button_html filter in
      sb-buttons-enqueue.php. Confirmed filter timing: fires during PHP render
      before DOMContentLoaded, so span is present in initial DOM.
      Cross-project contract agreed 2026-06-30 with suprbad-payment-gateway-failover
      (see docs/BUTTON-SYSTEM-RUNBOOK.md §7.5).

  Width override:
    WC Blocks applies width: 100% and loads after theme CSS, so equal-specificity
    rules lose to load order. !important required to win.

  Alignment (verified on staging 2026-06-13, item 11 resolved):
    .wc-block-checkout__actions_row computed layout:
      display: flex; justify-content: space-between; align-items: center;
    Additional modifier class present: .wc-block-checkout__actions_row--justify-flex-end
    The container's own flex rules handle button positioning — no align-self or
    margin-left needed on the button itself.

  Fill mechanism mirrors .sb-atc__ink dimensions exactly:
    left: -6%, width: 112%, height: 190%, bottom: 0
  Wave: y = 11% − 8%·sin(4π·x/100), 2 cycles, validated in
    _archive/projects/button-lab2/concept-tier1-comparison.html
*/

/* Alignment: WC Blocks adds --justify-flex-end modifier which right-aligns the pill
   within the form column. Override to center within the left checkout column only
   (the form column containing contact, shipping, payment fields). */
.wc-block-checkout__actions_row--justify-flex-end {
  justify-content: center !important;
}

.wc-block-checkout__actions_row .wc-block-components-checkout-place-order-button,
.wp-block-woocommerce-checkout .wc-block-components-checkout-place-order-button,
form#order_review #place_order {
  /* Width override — !important required: WC Blocks loads after theme CSS */
  width: auto !important;
  display: inline-flex;
  align-items: center;
  justify-content: center;

  /* Tier 1 pill shell */
  box-sizing: border-box;
  height: 62px;
  padding: 0 52px;
  border-radius: var(
    --sb-radius-pill,
    100px
  ); /* F2: shared radius scale (fallback keeps this file self-contained) */

  border: 2.5px solid var(--sb-color-button-border, #000);
  background: var(--sb-color-button-bg, #fff);
  font-family: inherit;
  font-size: 15px;
  font-weight: 900;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--sb-color-button-fg, #000);
  overflow: hidden;
  position: relative;
  white-space: nowrap;
  cursor: pointer;
  -webkit-font-smoothing: antialiased;
  transition:
    color 0.38s ease 0.18s,
    transform 0.12s ease,
    box-shadow 0.12s ease;
}

/* Stacking context isolation for context B (form-pay.php).
   The Block Checkout page provides stacking context containment via its React DOM
   environment. The order-pay page does not — without isolation the button's ::before
   and its child span compete in the parent stacking context, and browser compositing
   of the ::before (which gains a stacking context via clip-path) can paint above the
   span even when the span has z-index:1. isolation:isolate guarantees that ::before
   and .sb-place-order-text are compared within the button's own stacking context
   (::before at z-index:auto → step 6; span at z-index:1 → step 7; span wins). */
form#order_review #place_order {
  isolation: isolate;
}

/* Z-index: ::before fill (position:absolute, z-index:auto) paints in CSS stacking
   step 6, after normal-flow inline content (step 5). This means the fill covers
   the text even when color is white. Adding position:relative + z-index:1 to all
   direct children promotes them to stacking step 7 — above the fill.
   Mirrors the .sb-atc__label { z-index:2 } fix used in the ATC button (Group 5).
   Context A (Block Checkout): targets native .wc-block-components-button__text span.
   Context B (form-pay.php): targets .sb-place-order-text span injected by PHP filter.
   > * also added for context B to mirror the Block Checkout pattern exactly. */
.wc-block-components-checkout-place-order-button > *,
.wc-block-components-checkout-place-order-button .wc-block-components-button__text,
form#order_review #place_order > *,
form#order_review #place_order .sb-place-order-text {
  position: relative;
  z-index: 1;
}

/* GPU compositor layer promotion for context B (form-pay.php).
   ::before has transform:translateY(...) → Chrome promotes it to a GPU compositor
   layer. The span is CPU-rendered. Chrome's compositor paints GPU layers above
   CPU content in the same region regardless of CSS z-index, so isolation:isolate
   alone does not stop ::before from covering the span.
   translateZ(0) promotes the span to its own GPU layer; Chrome then orders the two
   GPU layers by z-index within the button's stacking context:
     ::before  → z-index:auto (0) → composited below
     span      → z-index:1       → composited above
   Block Checkout doesn't need this: WC Blocks promotes the button itself to a GPU
   layer via its own CSS, containing ::before as a sub-layer so ordering is
   enforced within that layer already. */
form#order_review #place_order > *,
form#order_review #place_order .sb-place-order-text {
  transform: translateZ(0);
}

/* Hover text color: target button + all descendants.
   Child spans may carry their own color declarations; must target * to override. */
.wc-block-checkout__actions_row .wc-block-components-checkout-place-order-button:hover,
.wc-block-checkout__actions_row .wc-block-components-checkout-place-order-button:hover *,
.wp-block-woocommerce-checkout .wc-block-components-checkout-place-order-button:hover,
.wp-block-woocommerce-checkout .wc-block-components-checkout-place-order-button:hover *,
form#order_review #place_order:hover,
form#order_review #place_order:hover * {
  color: var(--sb-color-button-fg-active, #fff) !important;
}

/* form-pay.php hover suppression (specificity 0,4,2 beats all competing rules):
   animation: none — suppresses Group 2 rubber-squash; Group 2 matches button.button
                     which #place_order has.
   transform: none — suppresses a hover scale from WordPress global-styles targeting
                     .wp-element-button (present on form-pay.php's #place_order, absent
                     on the Block Checkout button — hence no scaling issue there).
   :active's transform: scale(0.965) is a separate selector; unaffected. */
form#order_review #place_order:not(.disabled, :disabled):hover {
  animation: none;
  transform: none;
}

.wc-block-checkout__actions_row .wc-block-components-checkout-place-order-button:active,
.wp-block-woocommerce-checkout .wc-block-components-checkout-place-order-button:active {
  transform: scale(0.965);
  box-shadow: 0 0 0 3px rgb(0 0 0 / 8%);
}

/* form-pay.php active: scale + shadow matching context A.
   Explicit background/color/border-color override Group 2's color-inversion active rule
   (button.button.alt:active → bg:#000, color:#fff, border:#fff) which otherwise matches
   this button. Specificity (2,1,1) beats Group 2 active at (0,3,1). */
form#order_review #place_order:active {
  transform: scale(0.965);
  box-shadow: 0 0 0 3px rgb(0 0 0 / 8%);
  background: var(--sb-color-button-bg, #fff);
  color: var(--sb-color-button-fg, #000);
  border-color: var(--sb-color-button-border, #000);
}

/* Fill mechanism */
.wc-block-components-checkout-place-order-button::before,
form#order_review #place_order::before {
  content: '';
  position: absolute;
  left: -6%;
  width: 112%;
  bottom: -2px;
  height: 190%;
  background: var(--sb-color-button-bg-active, #000);
  transform: translateY(102%);
  transition: transform 0.72s cubic-bezier(0.16, 1, 0.3, 1);
  clip-path: polygon(
    0% 11%,
    5% 6%,
    10% 3%,
    15% 3%,
    20% 6%,
    25% 11%,
    30% 16%,
    35% 19%,
    40% 19%,
    45% 16%,
    50% 11%,
    55% 6%,
    60% 3%,
    65% 3%,
    70% 6%,
    75% 11%,
    80% 16%,
    85% 19%,
    90% 19%,
    95% 16%,
    100% 11%,
    100% 100%,
    0% 100%
  );
}

.wc-block-components-checkout-place-order-button:hover::before,
form#order_review #place_order:hover::before {
  transform: translateY(0%);
}

/* Reset fill when button is in loading/processing state */
.wc-block-components-checkout-place-order-button:disabled::before,
form#order_review #place_order:disabled::before {
  transform: translateY(102%);
  transition: none;
}

/* ─── Group 5: Tier 1 — Add to Cart JS wave shell ────────────────── */

/*
  Applies to the ATC button on single variable product pages.
  The button receives this HTML in the template override:
    <div class="sb-atc-wrap">
      <button class="single_add_to_cart_button ..." data-sb-wave="1">
        <span class="sb-atc__ink" aria-hidden="true">SVG wave + bubbles</span>
        <span class="sb-atc__label">Add to Cart</span>
      </button>
    </div>

  The fill rise (translateY) is CSS-driven via :hover.
  The wave shape and bubble spawning are JS-driven by sb-buttons.js.

  Verification item 9 — wp-element-button source (resolved 2026-06-13):
    Staging confirmed: :root :where(.wp-element-button, .wp-block-button__link)
    originating from global-styles / core-block-supports (WordPress theme.json output).
    The :where() wrapper gives these rules 0-0-0 specificity. Our 0-1-0 selector
    wins automatically. No specificity changes required.
*/

.sb-atc-wrap {
  display: inline-flex;
}

/* Active: button press. Wrapper is display:inline-flex so it scales with the button. */
.sb-atc-wrap:active {
  transform: scale(0.965);
}

/* Tier 1 shell — shared by ALL ATC states (disabled + active).
   Selector specificity: 0-2-1 (.sb-atc-wrap + button + .single_add_to_cart_button).
   This beats WC's button.single_add_to_cart_button:disabled (0-2-1) via load order,
   and beats button.single_add_to_cart_button (0-1-1) outright.
   Group 3 uses 0-3-1 selectors to override only the three disabled differences. */
.sb-atc-wrap button.single_add_to_cart_button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  box-sizing: border-box;
  height: 62px;
  padding: 0 52px;
  border-radius: var(
    --sb-radius-pill,
    100px
  ); /* F2: shared radius scale (fallback keeps this file self-contained) */

  border: 2.5px solid var(--sb-color-button-border, #000);
  background: var(--sb-color-button-bg, #fff);
  font-family: inherit;
  font-size: 15px;
  font-weight: 900;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  overflow: hidden;
  position: relative;
  cursor: pointer;
  -webkit-font-smoothing: antialiased;
  -webkit-appearance: none;
  appearance: none;
  user-select: none;
  -webkit-tap-highlight-color: transparent;
  transition: box-shadow 0.12s ease;
}

/* Ink fill element — positioned below button, rises on hover */
.sb-atc__ink {
  position: absolute;
  left: -6%;
  width: 112%;

  /* bottom: -2px (not 0) — extends ink slightly into the border area to hide a
     browser sub-pixel rasterization seam where the SVG fill's bottom edge meets
     the button's padding-box edge (KI-002 follow-up). Safe because the border
     color is always black, matching the ink — the 2px overlap is invisible.
     Do not modify without re-testing Chrome :active and Firefox/Zen :hover. */
  bottom: -2px;
  height: 190%;
  transform: translateY(102%);
  transition: transform 0.72s cubic-bezier(0.16, 1, 0.3, 1);
  pointer-events: none;
}

.single_add_to_cart_button:not(.disabled, :disabled):hover .sb-atc__ink {
  transform: translateY(0%);
}

.sb-atc__ink svg {
  display: block;
  width: 100%;
  height: 100%;
}

/* Label — sits above ink fill via z-index */
.sb-atc__label {
  position: relative;
  z-index: 2;
  display: flex;
  align-items: center;
  gap: 13px;
  color: var(--sb-color-button-fg, #000);
  transition: color 0.38s ease 0.18s;
  white-space: nowrap;
}

.single_add_to_cart_button:not(.disabled, :disabled):hover .sb-atc__label {
  color: var(--sb-color-button-fg-active, #fff);
}

/* Suppress GreenShift icon spans injected into our ATC button.
   In disabled state woobtconactive renders visible (~56px), active state hidden.
   This width variance on an inline-flex auto-width button causes the size difference.
   Our wave animation replaces GreenShift's loading feedback in this context. */
.sb-atc-wrap button .woobtconactive,
.sb-atc-wrap button .woobtloading {
  display: none;
}

/* Hide the GreenShift cart-icon during morph states.
   color:transparent and font-size:0px do not suppress SVG elements with explicit
   fill attributes; display:none is required.

   Diagnostic confirmed .woobticon is NOT inside .sb-atc-wrap or the button —
   GreenShift places it in an ancestor ABOVE .sb-atc-wrap. The descendant and
   sibling selectors below do not reach it; the :has() rule on body is the
   definitive CSS fix. Descendant/sibling rules kept as belt-and-suspenders.

   JS (_enterActive) mirrors this with an ancestor-walk querySelector (10 levels).
   :has() support: Chrome 105+, Firefox 121+, Safari 15.4+ (all current as of 2026). */
button.single_add_to_cart_button.sb-state-active .woobticon,
button.single_add_to_cart_button.sb-state-busy .woobticon,
button.single_add_to_cart_button.sb-state-active ~ .woobticon,
button.single_add_to_cart_button.sb-state-busy ~ .woobticon {
  display: none !important;
}

/* Catch-all: hides .woobticon anywhere on the page while the ATC button morphs.
   Required because GreenShift renders the icon above .sb-atc-wrap in the DOM tree,
   outside reach of descendant or sibling combinators on the button itself. */
body:has(button.single_add_to_cart_button.sb-state-active) .woobticon,
body:has(button.single_add_to_cart_button.sb-state-busy) .woobticon {
  display: none !important;
}

/* Hide all direct children of the ATC button except the ink SVG during morph.
   GreenShift injects icon spans (cart SVG, woobt_icon, etc.) inside the button
   that survive color:transparent and font-size:0 because SVG fill is not
   suppressed by those properties. display:none on non-ink children is the
   definitive fix regardless of icon class name. */
.single_add_to_cart_button.sb-state-active > :not(.sb-atc__ink),
.single_add_to_cart_button.sb-state-busy > :not(.sb-atc__ink) {
  display: none !important;
}

/* ─── Group 6: Button Morph State Machine ─────────────────────────── */

/*
  Universal 4-state machine applied by sb-morph.js to all primary SB buttons.

  States (CSS classes added by JS to the button element itself):

    idle              Normal. Hover/wave animations run.
    .sb-state-active  Click acknowledged. Width collapses to circle (250ms).
                      Button keeps natural color/border. Text hidden immediately.
    .sb-state-busy    Circle rotates as transparent ring spinner (~1 second).
    .sb-state-processing  Expands back to full width. Black fades in as pill expands.
                          "Processing…" label with animated dots.
                          Stays until page navigates.

  Geometry contract (width / height / border-radius):
    These properties are controlled EXCLUSIVELY by JS inline styles.
    CSS classes must NOT set them. Reason: class rules with !important beat
    inline styles, so the browser never sees the FROM state and jumps to the
    TO state instead of transitioning. The JS inline style IS the FROM state;
    the subsequent inline-style change (after a forced reflow) IS the TO state.

  JS custom properties (set before first class change, for reference only):
    --sb-idle-width     button.offsetWidth (px)
    --sb-idle-height    button.offsetHeight (px)

  Two morph modes (sb-morph.js):
    terminal    stays in processing until navigation — checkout, payment
    round-trip  returns to idle after WC completes — Add to Cart
*/

/* ── Spinner keyframe ────────────────────────────────────────────── */

@keyframes sb-spin {
  to {
    transform: rotate(360deg);
  }
}

/* ── Wrapper layout base (wave fill z-index stacking) ───────────── */

/* .sb-place-order-wrap keeps the button inline-flex for natural sizing.
   The span injected by sb-buttons-enqueue.php provides z-index:1 for the
   button label so it renders above the wave ::before fill. */
.sb-place-order-wrap {
  position: relative;
  display: inline-flex;
}

/* ── active: collapse to circle ──────────────────────────────────── */

/* width / border-radius: controlled by JS inline style — see geometry contract above.
   background-color and border-color are intentionally NOT set here: the button keeps
   its natural appearance (background + border) during the pill-to-circle collapse so
   there is no black flash on click. .sb-state-busy applies the transparent ring style
   after the collapse completes (~300ms). */
.sb-state-active {
  color: transparent !important;
  font-size: 0 !important;
  overflow: hidden !important;
  pointer-events: none;
  cursor: default;

  /* Zero out theme size constraints so JS inline dimensions are the sole authority. */
  min-height: 0 !important;
  min-width: 0 !important;
  max-width: none !important;
  padding: 0 !important;
  line-height: 1 !important;
  box-sizing: border-box !important;

  /* Snap any in-progress :active scale(0.965) transition (Group 4 has
     transition: transform 0.12s ease). CSS !important beats a running
     CSS transition, so the transform clears the moment this class is added.
     Without this, the spin animation composites on a fractional scale()
     and the ring appears as an oval / diagonal line. */
  transform: none !important;
}

/* Snap the ATC wave ink fill back to hidden the instant active/busy begins.
   Without this, a partially-risen ink fill (mid-hover when clicked) remains
   visible against the button's natural white background during the collapse.
   overflow:hidden clips it at the border-box edge, but the fill itself must be
   reset so it doesn't show within the shrinking footprint. transition:none makes
   the snap instantaneous — no partial-fill flash before overflow takes over. */
.sb-state-active .sb-atc__ink,
.sb-state-busy .sb-atc__ink {
  transform: translateY(102%) !important;
  transition: none !important;
}

/* ── busy: ring spinner ──────────────────────────────────────────── */

/* width / height / border-radius: controlled by JS inline style — see geometry contract above. */
.sb-state-busy {
  /* Transparent fill lets page background show through the ring. */
  background-color: transparent !important;

  /* One quarter black, rest faint grey — classic ring spinner. */
  border: 3px solid rgb(0 0 0 / 13%) !important;
  border-left-color: #000 !important;
  color: transparent !important;
  font-size: 0 !important;
  overflow: hidden !important;
  pointer-events: none;
  cursor: default;
  animation: sb-spin 0.75s linear infinite !important;

  /* Zero out theme size constraints so JS inline dimensions are the sole authority.
     Theme min-height with !important would otherwise fight our inline height. */
  min-height: 0 !important;
  max-height: none !important;
  min-width: 0 !important;
  max-width: none !important;
  padding: 0 !important;
  line-height: 1 !important;
  box-sizing: border-box !important;
  box-shadow: none !important;
  outline: none !important;
}

/* ── processing: expand to black pill ───────────────────────────── */

/* width / height / border-radius: controlled by JS inline style — see geometry contract above. */
.sb-state-processing {
  background-color: #000 !important;
  border-color: transparent !important;
  color: #fff !important;
  overflow: hidden !important;
  pointer-events: none;
  cursor: default;
  animation: none !important;
  font-size: 15px !important;
  font-weight: 900 !important;
  letter-spacing: 0.06em !important;
  min-height: 0 !important;
  min-width: 0 !important;
  max-width: none !important;
  box-sizing: border-box !important;

  /* Flex centres the .sb-processing-label regardless of button padding. */
  display: inline-flex !important;
  align-items: center !important;
  justify-content: center !important;
  transform: none !important;
}

/* Processing label */
.sb-processing-label {
  display: inline-flex;
  align-items: center;
  white-space: nowrap;
  overflow: hidden;
  max-width: 100%;
  font-size: 15px;
  font-weight: 900;
  letter-spacing: 0.06em;
  text-transform: uppercase;
}

/* Dots — opacity toggled by JS (0 → 1 sequentially, reset to 0 together) */
.sb-dot {
  display: inline-block;
  opacity: 0;
  transition: opacity 120ms ease;
}

/* ── compact: in-place spinner for short navigation links ──────── */

/* Used for <160px links (e.g. View Cart in WC notice) where the standard
   collapse-to-circle morph is disproportionate. Button keeps its natural
   dimensions; the 22px spinner span is centred by the button's existing
   display:inline-flex layout from Group 2. */
.sb-state-busy-compact {
  pointer-events: none;
  cursor: default;
}

/* Spinner ring — same visual style as .sb-state-busy, sized for small buttons. */
.sb-morph-compact-spinner {
  display: block;
  flex-shrink: 0;
  width: 22px;
  height: 22px;
  border: 3px solid rgb(0 0 0 / 13%);
  border-left-color: #000;
  border-radius: 50%;
  box-sizing: border-box;
  animation: sb-spin 0.75s linear infinite;
}

/* ── Cart page: Proceed to Checkout visual alignment ────────────────────────
   WC Blocks renders the cart sidebar with ~32 CSS px of bottom space below the
   Proceed-to-Checkout button (combination of the block's own margin-bottom and
   the sidebar's padding-bottom). When the button is full-width (255 px) this
   space is hidden behind the button's visual footprint. When it collapses to a
   62 px ring the gap becomes obvious and the ring looks top-biased.
   Zeroing both properties flushes the button/ring to the visual bottom of the
   card so it reads as grounded rather than floating. */
.wc-block-cart__submit.wp-block-woocommerce-proceed-to-checkout-block {
  margin-bottom: 0 !important;
}

.wc-block-cart__sidebar.wc-block-components-sidebar {
  padding-bottom: 16px !important;
}

/* Wave fill — suppress during morph states so the black-fill active state
   does not compete with the background-color driven by .sb-state-processing. */
#place_order.sb-state-active::before,
#place_order.sb-state-busy::before,
#place_order.sb-state-processing::before,
.wc-block-components-checkout-place-order-button.sb-state-active::before,
.wc-block-components-checkout-place-order-button.sb-state-busy::before,
.wc-block-components-checkout-place-order-button.sb-state-processing::before {
  display: none;
}
