/*
Button

Builds on @elements/style's .button family: the same class on <button> or
<a>, intent and size as is- modifiers. Adds the variants @elements/style
does not ship (is-secondary, is-outline, is-link), a borderless is-ghost,
a square is-icon in any variant, and a loading state that keeps the
button's width. Reads only tokens. notes/style-deltas.md lists what
differs from @elements/style for the port.

  .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
  [data-loading]              spinner over a hidden label, width kept
*/

@layer components {

.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-label {
  display:     inline-flex;
  align-items: center;
  gap:         var(--control-gap);
  /* A label longer than the button (a block button at phone width) shrinks
     to it and is clipped at its end, so it starts at the button's padding
     instead of spilling past both edges. clip, not hidden: the vertical
     axis stays visible and descenders are not cut. */
  min-width:   0;
  overflow-x:  clip;
}

.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: transparent;
  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 ---------------------------------------------------------------
   shadcn buttonVariants: 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. @elements/style drops every button a pixel on
   :active; that is undone here. */

.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 (shadcn turns its pointer
   events off; here the not-allowed and progress cursors still show). These
   outrank every variant's :hover and :active, @elements/style's included. */
.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: transparent;
  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 (shadcn: Tailwind's hover variant
   is @media (hover: hover)). On a touch screen :hover sticks to the last
   button tapped until the next tap elsewhere, which reads as stuck pressed.
   The variants above paint hover only under (hover: hover); @elements/style
   paints it unconditionally, so here 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: transparent;
    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);
  }
}

.button[data-loading] {
  cursor:  progress;
  opacity: 1;
}

/* opacity, not visibility: the label stays in layout (the width holds) and
   in the accessibility tree, so a busy button keeps its accessible name. */
.button[data-loading] > .button-label {
  opacity: 0;
}

/* The ring is the Spinner template's (.spinner, from ../spinner): one ring,
   one keyframe. Button only centers it over the hidden label. */
.button > .button-spinner {
  position: absolute;
  inset:    0;
  margin:   auto;
}

}
