@layer components {
/* =============================================================================
   @elements/style — buttons

   Bare <button> defaults + the .button coercion class for anchors and
   other elements. Wrapped in :where() so any single class wins.

   input[type=submit|reset|button] styles as a button with no class at all.
   A submit input IS a button to everyone who meets it, so it has to look and
   behave like one out of the box — the reset strips its native chrome, and
   without these selectors it landed as flat unstyled text with a default
   cursor, reading as a broken control rather than a button.

   The two variants a submit input cannot take are is-loading and is-icon:
   both need content inside the element, and an input is void — no children,
   no ::before. Use a <button> when you need either.

   Intent variants:   is-primary / is-ghost / is-danger

   Size variants:     is-sm / is-lg / is-hero
   Width variant:     is-block
   Chrome variant:    is-bare (strip all chrome) / is-icon (icon-only label)
   States:            is-loading / is-disabled (in addition to :disabled)

   Each variant owns ONE axis. Order doesn't matter, no hidden requirements:
     <button class="is-primary is-lg is-block">Sign up</button>
============================================================================= */

/* BASE SHAPE ---------------------------------------------------------- */

/*
   inline-GRID, not inline-flex, and that is load-bearing.

   A button is centered text. Under inline-flex a bare label is an anonymous
   flex item sized to its content, so `justify-content: center` is the only
   thing positioning it — and `text-align` does nothing at all. That is a
   silent failure: someone styling a button back down to plain text writes
   `text-align: start`, sees no change, and goes looking in the wrong place.
   It bites hardest on the shape that most wants left-aligned text, a button
   stretched by `flex: 1` or `width: 100%` in a row.

   Grid does not have that problem. Auto-sized tracks stretch to fill the
   container (justify-content's initial `normal` behaves as `stretch` for grid,
   and never for flex), so a text-only button gets one track the full width of
   its content box, the label fills it, and `text-align` positions the text
   inside it. The default stays `center`, so nothing looks different — but now
   the author's `text-align: start` is what decides, which is what everybody
   expected all along.

   A button with an element child (an icon beside a label) opts back into
   centering the group below, because there `text-align` is not what you mean.
*/

:where(button, .button,
       input[type="submit"], input[type="reset"], input[type="button"]) {
  display:         inline-grid;
  grid-auto-flow:  column;
  align-items:     center;
  text-align:      center;
  gap:             var(--space-2);
  height:          var(--button-height);
  padding:         0 var(--button-padding-x);
  font-family:     var(--font-sans);
  font-size:       var(--button-font-size);
  font-weight:     var(--button-font-weight);
  line-height:     1;
  letter-spacing:  var(--tracking-normal);
  white-space:     nowrap;
  text-decoration: none;
  background:      transparent;
  color:           var(--ink);
  border:          1px solid var(--rule);
  border-radius:   var(--button-radius);
  cursor:          pointer;
  user-select:     none;
  transition:
    var(--transition-colors),
    transform var(--duration-instant) var(--ease-out),
    box-shadow var(--duration-fast) var(--ease-out);
}

/* Inputs carry native chrome that survives a background change in some
   engines and clips the border-radius. Drop it so they take the shape above. */
:where(input[type="submit"], input[type="reset"], input[type="button"]) {
  appearance:         none;
  -webkit-appearance: none;
  text-align:         center;
}

:where(button:hover, .button:hover,
       input[type="submit"]:hover, input[type="reset"]:hover, input[type="button"]:hover) {
  background:   var(--bg-muted);
}

:where(button:active, .button:active,
       input[type="submit"]:active, input[type="reset"]:active, input[type="button"]:active) {
  background: var(--bg-panel);
  transform:  translateY(1px);
}

:where(button:focus-visible, .button:focus-visible,
       input[type="submit"]:focus-visible, input[type="reset"]:focus-visible,
       input[type="button"]:focus-visible) {
  outline:    none;
  box-shadow: var(--focus-ring);
}

:where(button:disabled, button[aria-disabled="true"],
       .button:disabled, .button.is-disabled, .button[aria-disabled="true"],
       input[type="submit"]:disabled, input[type="reset"]:disabled,
       input[type="button"]:disabled) {
  cursor:  not-allowed;
  opacity: var(--opacity-disabled);
}

/* INTENT — PRIMARY (accent-filled) ----------------------------------- */
/* Consumes --accent. Default theme: monochrome (neutral-900). Branded   */
/* theme (e.g. themes/elements.css): brand color via --accent override.  */

button.is-primary, .button.is-primary,
input[type="submit"].is-primary,
input[type="reset"].is-primary,
input[type="button"].is-primary {
  background:   var(--accent);
  color:        var(--accent-ink);
  border-color: var(--accent);
}

button.is-primary:hover, .button.is-primary:hover,
input[type="submit"].is-primary:hover,
input[type="reset"].is-primary:hover,
input[type="button"].is-primary:hover {
  background:   var(--accent-deep);
  border-color: var(--accent-deep);
}

button.is-primary:active, .button.is-primary:active,
input[type="submit"].is-primary:active,
input[type="reset"].is-primary:active,
input[type="button"].is-primary:active {
  background:   color-mix(in srgb, var(--accent) 92%, black);
  border-color: color-mix(in srgb, var(--accent) 92%, black);
}

/* INTENT — GHOST (no border, no fill) -------------------------------- */

button.is-ghost, .button.is-ghost,
input[type="submit"].is-ghost,
input[type="reset"].is-ghost,
input[type="button"].is-ghost {
  background:   transparent;
  color:        var(--ink-soft);
  border-color: transparent;
}

button.is-ghost:hover, .button.is-ghost:hover,
input[type="submit"].is-ghost:hover,
input[type="reset"].is-ghost:hover,
input[type="button"].is-ghost:hover {
  background:   var(--bg-panel);
  color:        var(--ink);
  border-color: transparent;
}

button.is-ghost:active, .button.is-ghost:active,
input[type="submit"].is-ghost:active,
input[type="reset"].is-ghost:active,
input[type="button"].is-ghost:active {
  background: var(--bg-canvas);
}

/* INTENT — DANGER (red-filled) -------------------------------------- */
/* Hover derives from --danger with color-mix rather than --danger-ink:  */
/* in dark mode --danger-ink is a light text tint, not a fill color.     */

button.is-danger, .button.is-danger,
input[type="submit"].is-danger,
input[type="reset"].is-danger,
input[type="button"].is-danger {
  /* --danger-fill, not --danger. --danger is the on-page danger color, and in
     dark mode it is danger-400, a light red that white text measures 2.90:1
     against. --danger-fill is the step meant to sit behind a label: 4.82:1 in
     light and 6.66:1 in dark, both clear of AA. */
  background:   var(--danger-fill);
  color:        var(--white);
  border-color: var(--danger-fill);
}

button.is-danger:hover, .button.is-danger:hover,
input[type="submit"].is-danger:hover,
input[type="reset"].is-danger:hover,
input[type="button"].is-danger:hover {
  background:   color-mix(in srgb, var(--danger-fill) 88%, black);
  border-color: color-mix(in srgb, var(--danger-fill) 88%, black);
}

button.is-danger:focus-visible, .button.is-danger:focus-visible,
input[type="submit"].is-danger:focus-visible,
input[type="reset"].is-danger:focus-visible,
input[type="button"].is-danger:focus-visible {
  box-shadow: var(--focus-ring-danger);
}

/* SIZE — SM ---------------------------------------------------------- */

button.is-sm, .button.is-sm,
input[type="submit"].is-sm,
input[type="reset"].is-sm,
input[type="button"].is-sm {
  height:    var(--button-height-sm);
  padding:   0 var(--button-padding-x-sm);
  font-size: var(--text-xs);
}

/* SIZE — LG ---------------------------------------------------------- */

button.is-lg, .button.is-lg,
input[type="submit"].is-lg,
input[type="reset"].is-lg,
input[type="button"].is-lg {
  height:    var(--button-height-lg);
  padding:   0 var(--button-padding-x-lg);
  font-size: var(--button-font-size-lg);
}

/* SIZE — HERO -------------------------------------------------------- */

button.is-hero, .button.is-hero,
input[type="submit"].is-hero,
input[type="reset"].is-hero,
input[type="button"].is-hero {
  height:      var(--button-height-hero);
  padding:     0 var(--space-6);
  font-size:   var(--text-md);
  font-weight: var(--weight-semibold);
}

/* WIDTH — BLOCK ------------------------------------------------------ */

button.is-block, .button.is-block,
input[type="submit"].is-block,
input[type="reset"].is-block,
input[type="button"].is-block {
  display: flex;
  width:   100%;
}

/* CHROME — BARE (strip chrome entirely) ----------------------------- */

button.is-bare, .button.is-bare,
input[type="submit"].is-bare,
input[type="reset"].is-bare,
input[type="button"].is-bare {
  height:        auto;
  padding:       0;
  background:    none;
  color:         inherit;
  border:        none;
  border-radius: 0;
}

button.is-bare:hover, .button.is-bare:hover,
button.is-bare:active, .button.is-bare:active,
input[type="submit"].is-bare:active,
input[type="reset"].is-bare:active,
input[type="button"].is-bare:active {
  background: none;
  border:     none;
  transform:  none;
}

/* STATE — LOADING ---------------------------------------------------- */

button.is-loading, .button.is-loading {
  cursor:         progress;
  pointer-events: none;
  opacity:        var(--opacity-muted);
}

/*
   The spinner is drawn, not marked up. The base is already inline-flex with
   `gap: var(--space-2)`, so ::before slots in ahead of the label with no
   markup change — `class="is-loading"` is the whole API. Every app was
   hand-rolling the same rotating svg and keyframes.

   currentColor means it inherits the intent: white on is-primary, ink on a
   ghost. The ring is a transparent-topped border, which is why it reads as a
   spinner rather than a solid disc.
*/
button.is-loading::before, .button.is-loading::before {
  content:       "";
  width:         1em;
  height:        1em;
  flex:          none;
  border:        2px solid currentColor;
  border-top-color: transparent;
  border-radius: var(--radius-full);
  /* Literal, not --duration-slow: that token is 280ms, tuned for transitions.
     A full revolution at that speed reads as a blur. */
  animation:     elements-button-spin 700ms linear infinite;
}

/* Respect a reduced-motion preference: keep the ring, drop the rotation. */
@media (prefers-reduced-motion: reduce) {
  button.is-loading::before, .button.is-loading::before {
    animation: none;
  }
}

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

/* ICON — a button whose whole label is an icon ----------------------- */
/*
   Opt in with `is-icon`. This used to auto-detect via
   `:has(> svg:only-child)`, which is wrong: `:only-child` counts ELEMENTS, so
   a button with a text label beside an icon — <button>Saving <svg/></button> —
   matched too, lost its height and padding, and silently shrank next to its
   siblings. CSS cannot tell "no other elements" from "no text either", so
   there is no guard to add and the detection had to go.

   Wrapping the label in a <span> was the workaround. It is no longer needed:
   a button styles as an icon button only when you say so.
*/

button.is-icon, .button.is-icon {
  height:        auto;
  padding:       var(--space-1-5);
  background:    none;
  color:         inherit;
  border:        none;
  border-radius: var(--radius-sm);
}

button.is-icon:hover, .button.is-icon:hover {
  background: var(--bg-panel);
  border:     none;
  transform:  none;
}

button.is-icon:active, .button.is-icon:active {
  background: var(--bg-canvas);
  border:     none;
  transform:  none;
}

/* A button holding elements (an icon beside a label) centers its parts as a
   group. Only the text-only case leaves the track stretched for text-align. */
:where(button:has(> *), .button:has(> *),
       .button-group > button, .button-group > .button) {
  justify-content: center;
}

/* BUTTON GROUP — adjacent buttons share borders ---------------------- */

.button-group {
  display: inline-flex;
}

.button-group > button,
.button-group > .button {
  border-radius: 0;
}

.button-group > button:first-child,
.button-group > .button:first-child {
  border-top-left-radius:    var(--button-radius);
  border-bottom-left-radius: var(--button-radius);
}

.button-group > button:last-child,
.button-group > .button:last-child {
  border-top-right-radius:    var(--button-radius);
  border-bottom-right-radius: var(--button-radius);
}

.button-group > button + button,
.button-group > .button + .button {
  margin-left: -1px;
}

.button-group > button:hover,
.button-group > .button:hover {
  z-index: var(--z-raised);
}

}
