@layer components {
/*
@elements/ui: buttons

Two halves. First, defaults for a bare <button> and submit input with no
class, wrapped in :where() so any single class wins, and their is-
modifiers. Second, at the end of this file, the .button class: the
design system's button, which the Button template renders and any element
can carry (an <a>, a <button>, a <summary>). It reads the theme's control
tokens, takes is-primary, is-secondary, is-outline, is-ghost, is-link,
is-danger, is-xs, is-sm, is-lg, is-icon, is-round and is-block, and owns
every .button element: the bare modifier rules below skip .button.

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 looks and
behaves like one out of the box.

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, so text-align works. An auto-sized grid
   track stretches to fill the content box, the label fills it, and
   `text-align` positions the text inside it. The default stays `center`;
   an author's `text-align: start` (a button stretched by `flex: 1` in a
   row, say) is what decides. Under flex the label would be an item sized
   to its content and text-align would do nothing.

   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);
  text-decoration: none;
  background:      transparent;
  color:           var(--ink);
  border:          var(--control-border-width, 1px) solid var(--rule);
  border-radius:   var(--button-radius);
  text-transform:  var(--case-ui, none);
  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:where(:not(.button)).is-primary,
input[type="submit"]:where(:not(.button)).is-primary,
input[type="reset"]:where(:not(.button)).is-primary,
input[type="button"]:where(:not(.button)).is-primary {
  background:   var(--accent);
  color:        var(--accent-ink);
  border-color: var(--accent);
}

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

button:where(:not(.button)).is-primary:active,
input[type="submit"]:where(:not(.button)).is-primary:active,
input[type="reset"]:where(:not(.button)).is-primary:active,
input[type="button"]:where(:not(.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 (border, no fill) */
/* Every button keeps its border. A borderless button reads as plain text  */
/* and nobody finds it; is-bare is the one variant for clickable text.     */

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

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

button:where(:not(.button)).is-ghost:active,
input[type="submit"]:where(:not(.button)).is-ghost:active,
input[type="reset"]:where(:not(.button)).is-ghost:active,
input[type="button"]:where(:not(.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:where(:not(.button)).is-danger,
input[type="submit"]:where(:not(.button)).is-danger,
input[type="reset"]:where(:not(.button)).is-danger,
input[type="button"]:where(:not(.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:where(:not(.button)).is-danger:hover,
input[type="submit"]:where(:not(.button)).is-danger:hover,
input[type="reset"]:where(:not(.button)).is-danger:hover,
input[type="button"]:where(:not(.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:where(:not(.button)).is-danger:focus-visible,
input[type="submit"]:where(:not(.button)).is-danger:focus-visible,
input[type="reset"]:where(:not(.button)).is-danger:focus-visible,
input[type="button"]:where(:not(.button)).is-danger:focus-visible {
  box-shadow: var(--focus-ring-danger);
}

/* Size: sm */

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

/* Size: lg */

button:where(:not(.button)).is-lg,
input[type="submit"]:where(:not(.button)).is-lg,
input[type="reset"]:where(:not(.button)).is-lg,
input[type="button"]:where(:not(.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:where(:not(.button)).is-block,
input[type="submit"]:where(:not(.button)).is-block,
input[type="reset"]:where(:not(.button)).is-block,
input[type="button"]:where(:not(.button)).is-block {
  /* grid, matching the base shape above, so the label fills the full-width
     button and text-align decides where the text sits. */
  display: grid;
  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;

  /* A bare button is nearly always text that happens to be clickable: a row
     title you click to edit, a "clear done" beside a list. Text starts at the
     start, and a long label wraps instead of running into what sits beside it. */
  justify-content: flex-start;
  text-align:      start;
}

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 below is a second grid item. Left at `normal`, grid would
     split the free space between both tracks and push the ring to one edge
     of a block button. Centre the pair as a group here: a pseudo-element is
     not a child, so the :has(> *) rule at the bottom of this file cannot. */
  justify-content: center;
}

/*
   The spinner is drawn, not marked up. The base is already a column grid with
   `gap: var(--space-2)`, so ::before slots in ahead of the label with no
   markup change: `class="is-loading"` is the whole API.

   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;
  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`. It is not detected from an svg child: a button with
   a text label beside an icon must keep its height and padding, and CSS
   cannot tell an icon alone from an icon beside text.
*/

button:where(:not(.button)).is-icon {
  height:        auto;
  padding:       var(--space-1-5);
  background:    none;
  color:         inherit;
  border:        var(--control-border-width, 1px) solid var(--rule);
  border-radius: var(--radius-sm);
}

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

button:where(:not(.button)).is-icon:active {
  background: var(--bg-canvas);
  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: calc(-1 * var(--control-border-width, 1px));
}

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

/* THE .button CLASS

   The design system's button. Every rule from here to the end of the file
   reads the theme's control tokens, so a <Button> and a bare
   <a class="button is-primary"> render the same under every theme. The
   Button template adds only its label, loading and spinner parts.

     .button.is-primary | is-secondary | is-outline | is-ghost | is-link | is-danger
     .button.is-xs | is-sm | is-lg   size (md has no class)
     .button.is-icon                 square, icon only
     .button.is-round                fully rounded corners (a pill, or a circle with is-icon)
     .button.is-block                full width

   A ghost is a real button: bordered, no fill, quieter text. */

.button {
  position:        relative;
  display:         inline-flex;
  align-items:     center;
  justify-content: center;
  gap:             var(--control-gap);
  height:          var(--control-height);
  padding:         0 var(--control-padding-x);
  border:          var(--control-border-width) solid var(--control-border);
  border-radius:   var(--radius-control);
  background:      var(--control-bg);
  box-shadow:      var(--control-shadow);
  color:           var(--ink);
  font-family:     var(--font-ui);
  font-size:       var(--text-ui);
  font-weight:     var(--weight-ui);
  letter-spacing:  var(--tracking-ui);
  text-transform:  var(--case-ui);
  line-height:     1;
  white-space:     nowrap;
  text-decoration: none;
  cursor:          pointer;
  user-select:     none;
  -webkit-tap-highlight-color: transparent;
}

.button svg {
  flex:   none;
  width:  var(--icon-size);
  height: var(--icon-size);
}

/* The ring is a box-shadow. Forced colors (Windows High Contrast) drops
   box-shadow, so a transparent outline stays underneath: invisible
   normally, painted in the system color there. An outline takes no layout
   space, so focus never moves anything. */
.button:focus-visible,
.button:has(> input:focus-visible) {
  outline:        var(--focus-ring-width) solid transparent;
  outline-offset: var(--focus-ring-offset);
  box-shadow:     var(--focus-ring);
}

/* Sizes */

/* xs steps down from sm by the same amount sm steps down from md, so every
   theme gets a proportionate extra-small size from its own control tokens.
   A theme may set --control-height-xs and --control-padding-x-xs itself. */
.button.is-xs {
  height:    var(--control-height-xs, calc(2 * var(--control-height-sm) - var(--control-height)));
  padding:   0 var(--control-padding-x-xs, calc(2 * var(--control-padding-x-sm) - var(--control-padding-x)));
  gap:       calc(var(--control-gap) / 2);
  font-size: var(--text-ui-sm);
}

.button.is-xs svg {
  width:  var(--icon-size-sm);
  height: var(--icon-size-sm);
}

.button.is-sm {
  height:    var(--control-height-sm);
  padding:   0 var(--control-padding-x-sm);
  font-size: var(--text-ui-sm);
}

.button.is-sm svg {
  width:  var(--icon-size-sm);
  height: var(--icon-size-sm);
}

.button.is-lg {
  height:    var(--control-height-lg);
  padding:   0 var(--control-padding-x-lg);
  font-size: var(--text-ui-lg);
}

.button.is-lg svg {
  width:  var(--icon-size-lg);
  height: var(--icon-size-lg);
}

.button.is-icon {
  width:         var(--control-height);
  height:        var(--control-height);
  padding:       0;
  border-radius: var(--radius-control);
}

.button.is-icon.is-xs {
  width:  var(--control-height-xs, calc(2 * var(--control-height-sm) - var(--control-height)));
  height: var(--control-height-xs, calc(2 * var(--control-height-sm) - var(--control-height)));
}

.button.is-icon.is-sm { width: var(--control-height-sm); height: var(--control-height-sm); }
.button.is-icon.is-lg { width: var(--control-height-lg); height: var(--control-height-lg); }

.button.is-round {
  border-radius: var(--radius-full);
}

.button.is-block {
  display: flex;
  width:   100%;
}

/* Variants */

.button.is-primary {
  background:   var(--accent);
  border-color: var(--accent);
  color:        var(--accent-ink);
}

@media (hover: hover) {
  .button.is-primary:hover {
    background:   var(--accent-deep);
    border-color: var(--accent-deep);
  }
}

.button.is-secondary {
  background:   var(--secondary);
  border-color: transparent;
  color:        var(--secondary-ink);
}

@media (hover: hover) {
  .button.is-secondary:hover {
    background: var(--secondary-hover);
  }
}

.button.is-outline {
  background:   var(--control-bg);
  border-color: var(--control-border);
  color:        var(--ink);
}

@media (hover: hover) {
  .button.is-outline:hover {
    background: var(--bg-muted);
  }
}

.button.is-ghost {
  background:   transparent;
  border-color: var(--rule);
  box-shadow:   none;
  color:        var(--ink-soft);
}

@media (hover: hover) {
  .button.is-ghost:hover {
    background: var(--bg-muted);
    color:      var(--ink);
  }
}

.button.is-link {
  height:                auto;
  padding:               0;
  background:            transparent;
  border-color:          transparent;
  box-shadow:            none;
  color:                 var(--link);
  text-decoration:       underline;
  text-decoration-color: transparent;
  text-underline-offset: calc(var(--link-underline-offset) * 1.25);
}

@media (hover: hover) {
  .button.is-link:hover {
    text-decoration-color: currentColor;
  }
}

.button.is-danger {
  background:   var(--danger-fill);
  border-color: var(--danger-fill);
  color:        var(--white);
}

/* Deepen the fill toward the ink, not toward a literal black: dark in a
   light theme, and the label keeps its contrast in a dark one. */
@media (hover: hover) {
  .button.is-danger:hover {
    background:   color-mix(in oklab, var(--danger-fill) 88%, var(--ink));
    border-color: color-mix(in oklab, var(--danger-fill) 88%, var(--ink));
  }
}

.button.is-danger:focus-visible {
  box-shadow: var(--focus-ring-danger);
}

/* is-ghost and is-link clear the control shadow at (0,2,0), the same
   specificity as .button:focus-visible and later, which cancelled the
   ring. Restore it here, after them. */
.button.is-ghost:focus-visible,
.button.is-link:focus-visible,
.button.is-ghost:has(> input:focus-visible),
.button.is-link:has(> input:focus-visible) {
  box-shadow: var(--focus-ring);
}

/* Invalid: aria-invalid draws a danger border, and a danger
   ring on focus (a button that stands for a field, such as a date picker's
   trigger). The border is always there (control-border-width), so only
   its color changes and nothing moves. After the variants and their
   hover, so it wins in every variant. */

.button[aria-invalid="true"],
.button[aria-invalid="true"]:hover {
  border-color: var(--danger);
}

.button[aria-invalid="true"]:focus-visible,
.button[aria-invalid="true"]:has(> input:focus-visible) {
  box-shadow: var(--focus-ring-danger);
}

/* Pressed */

/* A press changes the paint, never the position: each variant deepens past
   its hover color while the pointer or finger is down, at once (no fade in),
   and eases back on release. The button never shifts on :active. */

.button:active {
  transform: none;
}

.button:active:not([aria-disabled="true"]):not(:disabled) {
  transition-duration: 0s;
}

/* A press on a near-black (light) or near-white (dark) fill cannot go
   deeper, so it moves toward the page color instead: visible on every fill. */
.button.is-primary:active:not([aria-disabled="true"]):not(:disabled) {
  background:   color-mix(in oklab, var(--accent) 78%, var(--bg));
  border-color: color-mix(in oklab, var(--accent) 78%, var(--bg));
}

.button.is-secondary:active:not([aria-disabled="true"]):not(:disabled) {
  background: color-mix(in oklab, var(--secondary-hover) 85%, var(--ink));
}

.button.is-outline:active:not([aria-disabled="true"]):not(:disabled) {
  background:   color-mix(in oklab, var(--bg-muted) 88%, var(--ink));
  border-color: var(--rule-strong);
}

.button.is-ghost:active:not([aria-disabled="true"]):not(:disabled) {
  background: color-mix(in oklab, var(--bg-muted) 88%, var(--ink));
  color:      var(--ink);
}

.button.is-link:active:not([aria-disabled="true"]):not(:disabled) {
  text-decoration-color: currentColor;
  color:                 color-mix(in oklab, var(--link) 75%, var(--ink));
}

.button.is-danger:active:not([aria-disabled="true"]):not(:disabled) {
  background:   color-mix(in oklab, var(--danger-fill) 76%, var(--ink));
  border-color: color-mix(in oklab, var(--danger-fill) 76%, var(--ink));
}

/* States */

.button:disabled,
.button[aria-disabled="true"] {
  cursor:  not-allowed;
  opacity: var(--opacity-disabled);
}

.button:disabled:hover,
.button[aria-disabled="true"]:hover {
  filter: none;
}

/* Hover and press feedback is for buttons that respond: a disabled or busy
   one keeps its resting paint under the pointer, and the not-allowed and
   progress cursors still show. These outrank every variant's :hover and
   :active. */
.button.is-primary:is(:disabled, [aria-disabled="true"]):is(:hover, :active) {
  background:   var(--accent);
  border-color: var(--accent);
}

.button.is-secondary:is(:disabled, [aria-disabled="true"]):is(:hover, :active) {
  background:   var(--secondary);
  border-color: transparent;
}

.button.is-outline:is(:disabled, [aria-disabled="true"]):is(:hover, :active) {
  background:   var(--control-bg);
  border-color: var(--control-border);
}

.button.is-ghost:is(:disabled, [aria-disabled="true"]):is(:hover, :active) {
  background:   transparent;
  border-color: var(--rule);
  color:        var(--ink-soft);
}

.button.is-link:is(:disabled, [aria-disabled="true"]):is(:hover, :active) {
  background:            transparent;
  text-decoration-color: transparent;
}

.button.is-danger:is(:disabled, [aria-disabled="true"]):is(:hover, :active) {
  background:   var(--danger-fill);
  border-color: var(--danger-fill);
}

/* Hover paint is for devices that hover. On a touch screen :hover sticks
   to the last button tapped until the next tap elsewhere, which reads as
   stuck pressed, so there every variant keeps its resting paint under a
   sticky :hover. Press feedback (:active, a deeper paint) stays. */
@media not (hover: hover) {
  .button.is-primary:hover {
    background:   var(--accent);
    border-color: var(--accent);
  }

  .button.is-secondary:hover {
    background:   var(--secondary);
    border-color: transparent;
  }

  .button.is-outline:hover {
    background:   var(--control-bg);
    border-color: var(--control-border);
  }

  .button.is-ghost:hover {
    background:   transparent;
    border-color: var(--rule);
    color:        var(--ink-soft);
  }

  .button.is-link:hover {
    text-decoration-color: transparent;
  }

  .button.is-danger:hover {
    background:   var(--danger-fill);
    border-color: var(--danger-fill);
  }
}

}
