/*!
 * ZIC buttons — premium variant, size and state layer
 * ============================================================================
 * A layer over Materio/Bootstrap, not a parallel button system. That choice is
 * deliberate: this app uses Materio's own classes ~800 times (`btn-primary`,
 * `btn-outline-secondary`, `btn-icon`, `btn-text-*`), so a fresh `zic-btn-*`
 * vocabulary would theme nothing until every template was rewritten. The lever
 * is Bootstrap's `--bs-btn-*` variable block: restating the variables re-themes
 * every existing usage, including hover, active and disabled, because
 * Bootstrap's own base rules consume them.
 *
 * WHAT WAS ALREADY CORRECT (checked in the browser, not assumed)
 *
 *   - Variant colours. Materio's final layer maps `.btn-primary` and friends to
 *     `--bs-{primary,secondary,success,danger}`, which materio-overrides.css
 *     points at the AA-safe `-fill` / `-contrast` token pairs, with a
 *     `color-mix(in sRGB, #000 10%, …)` hover. That is exactly the brief's
 *     "darken 10%", and it measures 8.72:1 (light) and 4.74:1 (dark) for the
 *     label. Nothing here restates it.
 *   - The dropdown caret. `.dropdown-toggle::after` is already a CSS chevron
 *     (a rotated square with two borders), so "chevron-down on the right" needs
 *     no work — only the gap handling below.
 *   - The click ripple. `node-waves` is loaded and `main.js` attaches it to
 *     `.btn[class*='btn-']`. Reimplementing a ripple would have fought it.
 *   - `box-sizing`/`inline-flex` centring: `.btn` is already an inline-flex
 *     centred box, which is what makes exact pixel heights possible below.
 *
 * WHAT THIS FILE FIXES (each one measured before the change)
 *
 *   1. Sizes were all off-spec: default 38px (want 40), small 34px (want 32),
 *      large 42px (want 48), icon 38px (want 40). Padding-x 18/14/22 against
 *      the spec's 16/12/24, and font 15/13/17 against 14/13/16.
 *   2. A keyboard user could not see focus on a button at all. `.btn:focus-visible`
 *      in core.css sets `outline: 0` at specificity (0,2,0), which outranks the
 *      token-layer ring (`:where(…)` contributes zero specificity). Measured:
 *      `outline-width: 0px, outline-style: none` on a keyboard-focused button.
 *      WCAG 2.4.7 failure, fixed in section 5.
 *   3. Buttons had no hover transition. Measured on hover:
 *      `transition: none 0s` — the old global theme cross-fade opted `.btn:hover`
 *      out so feedback was never lagged, and that opt-out stripped the button's
 *      own motion too. Section 5 declares a short transition on the state
 *      selectors, which is now the rule for the whole layer: a colour transition
 *      belongs on `:hover`/`:focus-visible`/`:active`, never on the base
 *      selector, or it fires on every theme change. (The opt-out list itself is
 *      gone — there is no global cross-fade any more.)
 *   4. No icon-to-label gap (`gap: normal`), no hover lift, no active press.
 *   5. No loading state, no spaced/vertical button group, and disabled used
 *      Materio's 0.45 rather than the themed token (0.5 light / 0.6 dark).
 */

/* ===========================================================================
 * 1. Geometry — the brief's three sizes.
 *
 * Heights are exact rather than derived from padding + line-height, because
 * that derivation is what put the old default at 38px instead of 40px. The
 * tokens already existed in the form-control contract, so an input and a button
 * placed side by side are the same height by construction, not by coincidence.
 *
 * `border-radius` is set through `--bs-btn-border-radius` and never declared
 * directly, so Bootstrap's own `.rounded-circle` / `.rounded-pill` utilities
 * still win — a direct declaration here would be later in the cascade and would
 * flatten every circle button in the app.
 * ======================================================================== */
.btn {
  --bs-btn-padding-y: 0;
  --bs-btn-padding-x: 16px;
  --bs-btn-font-size: 14px;
  --bs-btn-border-radius: var(--zic-control-radius);
  --bs-btn-disabled-opacity: var(--zic-control-disabled-opacity);
  --bs-btn-focus-box-shadow: none;
  --bs-btn-box-shadow: var(--zic-shadow-sm);
  --bs-btn-active-shadow: var(--zic-shadow-sm);
  block-size: var(--zic-control-height);
  /* 4px, not the 8px a bare icon-and-label pair suggests. The app spaces its
     button icons with Bootstrap's `me-*` utilities — 619 uses of `me-1` — and
     those carry `!important`, so a 8px gap here would add on top and read as
     ~12px across the whole app. `--zic-space-1` is the token the token layer
     reserves for exactly this ("a 4px half-step for optical adjustments inside
     a component: icon/label gaps"), and against the app's own `me-1` it yields
     the brief's 8px total. Markup with no utility still gets a sane 4px. */
  gap: var(--zic-space-1);
}

.btn-sm,
.btn-group-sm > .btn,
.input-group-sm > .btn {
  --bs-btn-padding-x: 12px;
  --bs-btn-font-size: 13px;
  --bs-btn-border-radius: var(--zic-control-radius);
  block-size: var(--zic-control-height-sm);
}

.btn-lg,
.btn-group-lg > .btn,
.input-group-lg > .btn {
  --bs-btn-padding-x: 24px;
  --bs-btn-font-size: 16px;
  --bs-btn-border-radius: var(--zic-control-radius);
  block-size: var(--zic-control-height-lg);
}

/* The shell's caret carries its own `margin-inline: 0.8em 0`. Left alone, the
   new flex gap would add to it and widen every dropdown toggle in the app, so
   the gap is subtracted back out: `0.8em - 4px` plus the 4px gap is the 0.8em
   the shell designed, at every font size. */
.btn.dropdown-toggle::after {
  margin-inline-start: calc(0.8em - var(--zic-space-1));
}

/* Deliberately NOT touched: `.btn .icon-base` sizing. The app pins its button
   icons with `.icon-16px` / `.icon-18px`, and a rule here at (0,2,0) would
   outrank those utilities and resize several hundred icons at once. */

/* ===========================================================================
 * 2. Icon buttons, circles and the FAB.
 *
 * Square by default and sized from the same height tokens as the text buttons,
 * so a row of icon buttons lines up with the text button beside it.
 * ======================================================================== */
.btn-icon {
  inline-size: var(--zic-control-height);
  block-size: var(--zic-control-height);
  padding: 0;
}

.btn-icon.btn-sm,
.btn-group-sm > .btn-icon.btn,
.input-group-sm > .btn-icon.btn {
  inline-size: var(--zic-control-height-sm);
  block-size: var(--zic-control-height-sm);
}

.btn-icon.btn-lg,
.btn-group-lg > .btn-icon.btn,
.input-group-lg > .btn-icon.btn {
  inline-size: var(--zic-control-height-lg);
  block-size: var(--zic-control-height-lg);
}

/* Floating action button. The size and elevation are the component's; where it
   sits is the page's, so positioning is a separate modifier. */
.btn-icon.btn-fab {
  inline-size: 56px;
  block-size: 56px;
  border-radius: 50%;
  box-shadow: var(--zic-shadow-lg);
}

/* Only the FAB gets a glyph size from this file. It is a new component — the
   app has none — so there is no existing `.icon-NNpx` choice to overrule. */
.btn-icon.btn-fab .icon-base {
  inline-size: 24px;
  block-size: 24px;
  font-size: 24px;
}

.btn-icon.btn-fab:hover {
  box-shadow: var(--zic-shadow-xl);
}

.btn-fab--fixed {
  position: fixed;
  inset-block-end: var(--zic-space-4);
  inset-inline-end: var(--zic-space-4);
  z-index: var(--zic-z-sticky);
}

/* ===========================================================================
 * 3. Variants.
 *
 * Only the two that needed work are here; the solid fills are already correct
 * (see the file header).
 * ======================================================================== */

/* Ghost. The shell's `.btn-text-*` already draws the right foreground from
   `--bs-primary`; what it lacks is a hover background, so a ghost button gave
   no feedback at all beyond a shadowless colour shift. */
.btn[class*='btn-text-'] {
  --bs-btn-hover-bg: var(--zic-interactive-hover);
  --bs-btn-active-bg: var(--zic-interactive-active);
}

/* Secondary (outlined) hover fill. Materio mixes the fill toward the paper
   colour, which is nearly invisible on the light canvas; the interactive tint
   is the same wash the nav, menu and table rows use. */
.btn-outline-secondary,
.btn-outline-primary,
.btn-outline-danger,
.btn-outline-success,
.btn-outline-warning,
.btn-outline-info {
  --bs-btn-hover-bg: var(--zic-interactive-hover);
  --bs-btn-active-bg: var(--zic-interactive-active);
}

/* ===========================================================================
 * 4. States — hover lift, active press, shadow.
 *
 * Motion only. Split out so `prefers-reduced-motion` can drop the movement
 * without losing the colour feedback. `scale(1.02)` from the brief is
 * deliberately NOT layered on top of the lift: two transforms on the same
 * element fight, and a 2% scale on a text button resamples the label and
 * jitters a toolbar. Scale is reserved for the label-free icon buttons, below.
 * ======================================================================== */
@media (prefers-reduced-motion: no-preference) {
  .btn:not(:disabled):not(.disabled):hover {
    transform: translateY(-1px);
  }

  .btn:not(:disabled):not(.disabled):active,
  .btn:not(:disabled):not(.disabled).active {
    transform: translateY(0);
  }

  /* Icon buttons have no text to resample, so the pop reads cleanly here. */
  .btn-icon:not(:disabled):not(.disabled):hover {
    transform: translateY(-1px) scale(1.04);
  }

  .btn-icon:not(:disabled):not(.disabled):active {
    transform: translateY(0) scale(1);
  }
}

/* Elevation rises on hover and falls on press. Ghost buttons are excluded —
   a shadow under a borderless button reads as a rendering artefact. */
.btn:not([class*='btn-text-']):not(:disabled):not(.disabled):hover {
  --bs-btn-box-shadow: var(--zic-shadow-md);
}

.btn:not([class*='btn-text-']):not(:disabled):not(.disabled):active {
  --bs-btn-box-shadow: var(--zic-shadow-sm);
}

/* ===========================================================================
 * 5. Focus, transition and disabled.
 * ======================================================================== */

/* Focus visibility. This has to outrank `.btn:focus-visible { outline: 0 }`
   in core.css (0,2,0); matching the specificity and loading later in the
   cascade is what wins, and the ring geometry/colour come from the token layer
   so buttons, links and inputs all show the same ring.
   Measured before: outline-width 0px, outline-style none. */
.btn:focus-visible,
.btn-check:focus-visible + .btn {
  outline: var(--zic-focus-ring-width) solid var(--zic-border-focus);
  outline-offset: var(--zic-focus-ring-offset);
}

/* Interactive states only. A bare `.btn` must NOT carry a colour transition:
   it would fire on a theme change and settle on its own frame, which is the
   stagger this layer exists to prevent. The trade is a soft hover-in and a
   snappy hover-out, which is the right way round for feedback. */
.btn:hover,
.btn:focus-visible,
.btn:active {
  transition:
    color var(--zic-transition-fast),
    background-color var(--zic-transition-fast),
    border-color var(--zic-transition-fast),
    box-shadow var(--zic-transition-fast),
    transform var(--zic-transition-fast);
}

/* Disabled. Opacity comes from the themed token (0.5 on light, 0.6 on dark —
   the dark control sits closer to its surface, so it needs the extra weight).

   Materio sets `pointer-events: none` on both forms. That is redundant for a
   natively `disabled` button, and it makes `cursor: not-allowed` unreachable
   (no pointer events, no cursor), so it is relaxed for the native case only.
   The class-based `.disabled` keeps `pointer-events: none` because it is
   normally an `<a>`, where nothing else would stop the click. */
.btn:disabled {
  pointer-events: auto;
}

.btn:disabled,
.btn.disabled {
  cursor: not-allowed;
}

/* ===========================================================================
 * 6. Loading.
 *
 * Class-driven, no JS: `.is-loading` shows the spinner and blocks input. Pair
 * it with `disabled` (and `aria-busy="true"`) for the full disabled treatment.
 * `currentColor` means the spinner is legible on every variant, including the
 * dark palette's light-on-dark fills, without restating a colour per variant.
 * ======================================================================== */
.btn.is-loading {
  pointer-events: none;
}

.btn.is-loading::before {
  content: '';
  flex: none;
  inline-size: 1em;
  block-size: 1em;
  border: 2px solid currentColor;
  border-block-start-color: transparent;
  border-radius: var(--zic-radius-pill);
  opacity: 0.85;
  animation: zic-btn-spin 600ms linear infinite;
}

/* One spinner at a time: the real icon steps aside while loading. */
.btn.is-loading > .icon-base,
.btn.is-loading > i {
  display: none;
}

/* Spins for as long as it takes. A loading indicator is the one animation that
   still has to move under reduced motion — it is the state, not decoration —
   so it is not neutralised with the rest of the motion. */
@keyframes zic-btn-spin {
  to {
    transform: rotate(360deg);
  }
}

/* ===========================================================================
 * 7. Button groups.
 *
 * The segmented (joined) group is Bootstrap's `.btn-group` and already correct:
 * the inner corners are squared and the outer ones keep the radius. These are
 * the spaced and stacked arrangements the brief names, which had no equivalent.
 * ======================================================================== */
.zic-btn-group {
  display: inline-flex;
  align-items: center;
  gap: var(--zic-space-2);
}

.zic-btn-group--vertical {
  display: inline-flex;
  flex-direction: column;
  align-items: stretch;
  gap: var(--zic-space-2);
}

/* Action row pushed to the trailing edge — the shape every modal footer and
   table header wants. */
.zic-btn-group--end {
  justify-content: flex-end;
  inline-size: 100%;
}

/* The shell draws a divider between joined buttons; with the buttons now on a
   shared height and radius it needs to be the themed border rather than the
   variant colour, which read as a bright seam on dark surfaces. */
.btn-group .btn,
.btn-group-vertical .btn {
  --bs-btn-group-border-color: var(--zic-border-default);
}
