/* The button, and the one ladder of states for everything ours. A core
   sheet (every tier; it names no effect).

   The ladder (owner, 2026-09-12: unchosen at rest is the track and the
   ink; chosen the fill; the rungs tunable globally; 2026-09-06: an off
   state must still be readable):
     rest          the inactive ground, the text, the inactive stroke;
     hover         the ground tinted toward the text by --ds-state-hover;
     held          the same by --ds-state-held;
     chosen        the active ground, the page's words, the active
                   stroke (the fill's pair): a pressed toggle, a
                   checked cell, and a form's default action (the
                   button Enter in its form presses, :default);
     chosen hover  the active ground tinted by --ds-state-hover;
     chosen held   the same by --ds-state-held;
     off           the inactive ground and the dim words, no tint, the
                   stroke kept: readable, never faded.
   Focus is a layer, not a rung (2026-10-06): a 1 px ring in the focus
   colour, 2 out (inset in a group), over whatever rung the button
   stands on. The Slider reads the same rungs: its fill is the chosen
   part, its track the rest, its field a rest part (each tinted under
   the hand; the field held while it scrubs); the Dropdown's field,
   options and search, and the picker's hex and ×, read them too.

   A state can be forced for a specimen (data-state: rest, hover, held,
   chosen, chosen-hover, chosen-held, off; then the real ones are not
   read, so the forced state is exactly that state; data-focus forces the
   ring) or be the real one (:hover, :active, aria-pressed or
   aria-checked, :disabled, :focus-visible). A group (.ds-group) joins
   buttons at no gap: they share one hairline and only the group's outer
   corners round (the radio grid's idiom). In a group a cell's lines are
   its slot's (the place the cell takes at rest), drawn under its words
   by the slot itself, so they stay where they are while the box is
   pressed; the group's own ground is the inactive one, the track, which
   only a pressed cell shows. The corner is the system's hand-down where
   the button stands on a surface, its own token corner elsewhere.

   The motion of a state (the tool plan, §2.2): a state is an answer,
   not a move (owner, 2026-09-07: "make sure we don't use slow fade on
   hovers. top should be like 2-3 frames"), so the ground, the words and
   the stroke ride the state clock (--ds-motion-state, 48 ms) on the
   state curve (the curve or linear, never the bounce: a colour never
   bounces), the same both ways (a slow release on a hover fill reads as
   lag). A change of chosen-ness (a toggle pressed on, a cell checked)
   rides the chosen change (--chosen-clock: the state clock); off lands at
   once, both ways; the focus ring and the hair it moves in a group land
   in one frame; a recolour (html[data-recolour]: a scheme, a stroke
   switch, a colour edited) rides the clock and the curve. The marks
   for the chosen change and for off (data-rung-change) are
   components/button.js's. Under reduced motion the state clock is 0
   (components/surface.css).

   The press (owner, 2026-09-07: "Make it so when I press, container
   shrinks to 96% and icon to 90%. Don't use scale for container - use
   height and width so we can stay on grid even with downstates";
   decisions.md, 2026-09-07 (3)): under :active or [data-press] the
   ground is the held rung; under [data-press] the box takes its pressed
   size, --press-w by --press-h, each side moved in by a whole device
   pixel at the least and the box budget at the most (--press-x,
   --press-y), the margins taking the difference back, so nothing
   around it moves. A box's rest size is measured, never guessed, so
   the size is components/button.js's to write: it marks every press it
   sees (a pointer's, Space, Enter) with data-press and the sizes, and
   keeps the mark for the press floor (--ds-press-hold), so a quick tap
   still shows the press; a specimen forced held takes the same mark
   from its figure. Its words or its icon in a .ds-button__label shrink
   to --press-label (components/button.js: --ds-press-label of its
   width, or less far where --ds-press-label-budget binds, so an icon
   takes the whole share and a long label moves 3 px an edge; the
   sheet's own fallback is the plain share), a transform on the label
   only, never on the box. One move (owner, 2026-10-08: "for button
   states it feels like sometimes there's small delay between text and
   container. make sure its rock solid same curve same timing"): the
   box cannot ride a curve (a side moves a whole device pixel: a curve
   would draw an anti-aliased edge for two frames, twenty under Slow
   ×8), so the label lands with the box, in one frame both ways, and
   has no transition; the ground, the words' colour and the stroke keep
   the state clock, the ladder's answer, which is what reduced motion
   and Still already played. The size rule (owner, 2026-10-08: "bigger
   elements have a bit less delta in scale then small elements. small
   things always need to overshoot and overdo things more to be
   visible"): a move is the fraction's share of the object, rounded to
   whole device pixels, one device pixel at the least, the budget at
   the most; components/button.js holds the arithmetic. A cell
   of a group presses inside its slot: the slot's lines and the group's
   outline stay, the hair between it and its neighbour stays, the box
   and its ground go in by the press and leave the group's track around
   them, and the box's outer corners at an end of the group are the
   group's corner less the press (the corner law of
   components/surface.css: max(0, corner − inset), each axis by its
   own inset), so no square corner and no gap of the page opens in a
   group. */
.ds-button {
  /* the clock a change of chosen-ness rides: the state clock, as the
     decision of 2026-09-07 (2) has it (a state change is never a fade) */
  --chosen-clock: var(--ds-motion-state);
  /* the stroke of the rung it stands on (a chosen rung the active one)
     and its weight (components/surface.css), drawn by the box alone,
     by its slot in a group */
  --button-stroke: var(--ds-stroke-inactive);
  --button-stroke-w: var(--stroke-inactive-w);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  box-sizing: border-box;
  /* the control row, 32 on 16 / 24 type; 24 on 14 / 20 in a compact
     surface (components/surface.css) */
  height: var(--control, calc(var(--ds-grid-cell) * 8));
  /* the least it takes: 32, or the width a figure snapped to the
     lattice from its words (--snap-w, whole cells: playground/scene.js
     snapWidths, figures/states.js), which the press below reads too, so
     a snapped box still narrows */
  min-width: var(--snap-w, calc(var(--ds-grid-cell) * 8));
  margin: 0;
  padding: 0 var(--ds-space-8);
  border: 0;
  border-radius: var(--control-corner, var(--ds-radius-corner));
  background: var(--ds-color-inactive);
  box-shadow: inset 0 0 0 var(--button-stroke-w) var(--button-stroke);
  color: var(--ds-color-text);
  /* the system's type (round 5): its family, the surface's size and
     line, its weight */
  font-family: var(--ds-type-family);
  font-size: var(--type, var(--ds-text-16));
  line-height: var(--line, calc(var(--ds-grid-cell) * 6));
  font-weight: var(--ds-type-weight);
  white-space: nowrap;
  cursor: pointer;
  user-select: none;
  -webkit-user-select: none;
  -webkit-tap-highlight-color: transparent;
  transition:
    background-color var(--ds-motion-state) var(--ds-motion-state-ease),
    color var(--ds-motion-state) var(--ds-motion-state-ease),
    box-shadow var(--ds-motion-state) var(--ds-motion-state-ease);
}
/* a button's words or icon, the part the press shrinks: no transition
   (it lands with the box, one move), about its centre; in a row whose
   words start at its left (a layer row, a list's row: the box's
   justify-content start) about its left edge, left center, and in one
   whose words end at its right about its right edge, so the word's
   edge stays on its text column while the box's moves in by the press
   (--press-origin: components/button.js reads the alignment the box
   has when it measures the press, so no row has to say it) */
.ds-button__label {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--ds-space-8);
  transform-origin: var(--press-origin, center);
}
/* the icon slot (round 6, iteration 6, 2026-10-11; owner: "Make
   invisible icon slot and place icon in the middle of it. Place this
   slot on the left edge ... This invisible slot should hug walls on the
   left top bottom of the containers ... Make it scale with font
   accordingly to its size"): a square of the control's height, the
   first thing in the label, flush with the button's left, top and
   bottom edges (the button's left padding gives way to it), nothing of
   it drawn; the icon centred in it at the slot's preset, a share of the
   button's type (iteration 24: components/surface.css --icon; Medium,
   the default, is 1em, so a regular button's is 16 and a compact one's
   14), and the Type size moves it. The label's gap stands between the
   slot and the words; a button of the slot alone is a square of the
   control */
.ds-button__icon {
  display: inline-flex;
  flex: none;
  align-items: center;
  justify-content: center;
  width: var(--control, calc(var(--ds-grid-cell) * 8));
  height: var(--control, calc(var(--ds-grid-cell) * 8));
  margin: 0;
}
.ds-button__icon > svg {
  width: var(--icon);
  height: var(--icon);
}
/* with the slot (owner, 2026-10-11: "[(icon slot)(spacing from 0 to
   fill space to make button flood entire row)(text)(half of icon
   slot)]"; "It's either a button that has limited width and then it's
   0. Or it's a button that floods entire row and then we use space
   between icon and text to fill it"): no padding at the left (the slot
   is the edge); a button of its own width has no room between the slot
   and the words (the slot's own room around the icon is the optical
   gap); a button stretched across a row puts every pixel it was given
   between the slot and the words (the words' auto margin), so the
   words end at the right; after the words the slot's own room (round 6,
   iteration 10; owner, with a drawing of the five spaces: "All of these
   spacing must be identical"): half of what the control's height leaves
   round the icon, 8 regular ((32 - 16) / 2) and 5 compact, the one
   space at the icon's left, above and below it, between it and the
   words, and after the words */
.ds-button:has(> .ds-button__label > .ds-button__icon:first-child) {
  padding-left: 0;
  /* the room after the words: the slot's room round the icon, and a
     quarter of the icon's size more, since a glyph's ink stands inset
     in its box (a plus of 16 draws 9 wide) while the words' ink meets
     their box's edge; measured to the ink, 11.3 before the icon and
     11.3 after the words at regular, 7.9 and 8.5 compact (owner,
     2026-10-11: "Make sure button has correct margin on the right side.
     Now it's a bit too small") */
  --room: calc(
    (var(--control, calc(var(--ds-grid-cell) * 8)) - var(--icon)) / 2 +
      var(--icon) / 4
  );
  padding-right: var(--room);
}
.ds-button:has(> .ds-button__label > .ds-button__icon:first-child)
  > .ds-button__label {
  width: 100%;
  justify-content: flex-start;
  gap: 0;
}
.ds-button__icon + * {
  margin-left: auto;
}
/* the slot alone ("Empty icon should result in square aspect ratio and
   icon slot entirely filling button"): a square of the control, at
   every density */
.ds-button:has(> .ds-button__label > .ds-button__icon:only-child) {
  min-width: var(--control, calc(var(--ds-grid-cell) * 8));
  padding-right: 0;
}
/* pressed, the square narrows as any box does (its least width would
   hold it at the control while its margins moved it: the last pass,
   2026-10-11) */
.ds-button[data-press]:has(> .ds-button__label > .ds-button__icon:only-child) {
  min-width: 0;
}
@media (hover: hover) {
  .ds-button:not([data-state]):hover {
    background: color-mix(
      in srgb,
      var(--ds-color-inactive),
      var(--ds-color-text) var(--ds-state-hover)
    );
  }
}
.ds-button[data-state="hover"] {
  background: color-mix(
    in srgb,
    var(--ds-color-inactive),
    var(--ds-color-text) var(--ds-state-hover)
  );
}
:where(html:not([data-presses])) .ds-button:not([data-state]):active,
.ds-button:not([data-state])[data-press],
.ds-button[data-state="held"] {
  background: color-mix(
    in srgb,
    var(--ds-color-inactive),
    var(--ds-color-text) var(--ds-state-held)
  );
}
/* its edge and its words under the hand and held (iteration 21, the
   ladder's More): the rungs' tokens, the rest's by default; a chosen
   button keeps the active rung's (below; iteration 22's audit: these
   outranked it, so a chosen toggle under the hand took the rest's
   words on the active ground, 2:1), a disabled one its rest */
@media (hover: hover) {
  .ds-button:not([data-state]):not(
      :disabled,
      [aria-pressed="true"],
      [aria-checked="true"],
      :default
    ):hover {
    --button-stroke: var(--ds-state-hover-edge);
    --button-stroke-w: var(--hover-edge-w);
    color: var(--ds-state-hover-text);
  }
}
.ds-button[data-state="hover"] {
  --button-stroke: var(--ds-state-hover-edge);
  --button-stroke-w: var(--hover-edge-w);
  color: var(--ds-state-hover-text);
}
:where(html:not([data-presses]))
  .ds-button:not([data-state]):not(
    :disabled,
    [aria-pressed="true"],
    [aria-checked="true"],
    :default
  ):active,
.ds-button:not([data-state]):not(
    :disabled,
    [aria-pressed="true"],
    [aria-checked="true"],
    :default
  )[data-press],
.ds-button[data-state="held"] {
  --button-stroke: var(--ds-state-held-edge);
  --button-stroke-w: var(--held-edge-w);
  color: var(--ds-state-held-text);
}
.ds-button:not([data-state])[aria-pressed="true"],
.ds-button:not([data-state])[aria-checked="true"],
.ds-button:not([data-state]):default:not(:disabled),
.ds-button[data-state="chosen"],
.ds-button[data-state="chosen-hover"],
.ds-button[data-state="chosen-held"] {
  --button-stroke: var(--ds-stroke-active);
  --button-stroke-w: var(--stroke-active-w);
  background: var(--ds-color-active);
  color: var(--ds-color-page);
}
@media (hover: hover) {
  .ds-button:not([data-state]):not(:disabled)[aria-pressed="true"]:hover,
  .ds-button:not([data-state]):not(:disabled)[aria-checked="true"]:hover,
  .ds-button:not([data-state]):not(:disabled):default:hover {
    background: color-mix(
      in srgb,
      var(--ds-color-active),
      var(--ds-color-text) var(--ds-state-hover)
    );
  }
}
.ds-button[data-state="chosen-hover"] {
  background: color-mix(
    in srgb,
    var(--ds-color-active),
    var(--ds-color-text) var(--ds-state-hover)
  );
}
.ds-button:not([data-state]):not(:disabled):is(
    [aria-pressed="true"],
    [aria-checked="true"],
    :default
  )[data-press],
:where(html:not([data-presses]))
  .ds-button:not([data-state]):not(:disabled):is(
    [aria-pressed="true"],
    [aria-checked="true"],
    :default
  ):active,
.ds-button[data-state="chosen-held"] {
  background: color-mix(
    in srgb,
    var(--ds-color-active),
    var(--ds-color-text) var(--ds-state-held)
  );
}
/* focus is a layer, not a rung (2026-10-06): the ring stands for a real
   keyboard focus, or for data-focus over any rung, forced or real, so a
   specimen can show a focused chosen, a focused off. A real focus rings
   only where the keys brought it, html[data-kbd] (lib/core.js; owner,
   2026-10-08: "eliminate all ways for system to display it"): a click
   on a button draws none, a Tab or an arrow draws one */
html[data-kbd] .ds-button:not([data-state]):focus-visible,
.ds-button[data-focus] {
  outline: var(--focus-ring-w) solid var(--ds-color-focus);
  outline-offset: var(--focus-ring-gap);
}
.ds-button:not([data-state]):disabled,
.ds-button[data-state="off"] {
  background: var(--ds-color-inactive);
  color: var(--ds-color-text-dim);
  cursor: default;
}
/* a group: joined cells, one hairline between, the outer corners only.
   Its own ground is the track, the inactive ground: under its cells at
   rest, it is what a pressed cell leaves around itself (the press,
   below), so no gap of the page opens inside the group. A join's cells
   are as wide as their words or their glyph, never less, and grow alike
   where the join is stretched: a cell grown from nothing took an equal
   share of the group's words ("Glyphs" stood 3.4 from its edges in a
   cell of 58 beside "Text" at 13.1; the critique of iteration 24), and
   a join of one width per cell stretched a square Play to its frame
   field's 47, its glyph 7.5 off its centre (the review of its
   refinement) */
.ds-group {
  display: inline-flex;
  border-radius: var(--control-corner, var(--ds-radius-corner));
  overflow: hidden;
  background: var(--ds-color-inactive);
}
/* a choice of one (components/choice.js, a radio group): columns of
   one width, the widest cell's words at the least, so a choice is its
   widest cell as many times as it has cells and a stretched one shares
   its room equally; where the room is short a cell keeps its words and
   the others share what is left. A pressed cell narrows inside its
   column (components/button.js takes its pressed width outright), so no
   column moves. (No weight of its own beyond a group's, so a page's own
   display, a pick a phone alone shows, still holds) */
.ds-group:where(.ds-choice, [role="radiogroup"]) {
  display: inline-grid;
  grid-auto-columns: minmax(max-content, 1fr);
  grid-auto-flow: column;
}
/* a cell: its lines are its slot's, a layer of its own (::before) at
   the box's rest place, under its words (the cell its own stacking
   context, the layer at the bottom of it), so they hold still while
   the box is pressed (the slot's place is the pressed box and the
   margins the press gives it, the box's rest place) */
.ds-group > .ds-button {
  /* the group's corner, which its end cells' slots wear */
  --slot-corner: var(--control-corner, var(--ds-radius-corner));
  position: relative;
  isolation: isolate;
  /* in a join, and in a choice a page lays out as a row of its own
     (display: flex), its own width at the least, the room left shared
     alike (a press narrows it through its padding: components/button.js) */
  flex: 1 0 auto;
  border-radius: 0;
  box-shadow: none;
}
.ds-group > .ds-button::before {
  content: "";
  position: absolute;
  z-index: -1;
  inset: calc(var(--press-y, 0px) * -1) calc(var(--press-x, 0px) * -1);
  box-shadow: inset 0 0 0 var(--button-stroke-w) var(--button-stroke);
  pointer-events: none;
  transition: box-shadow var(--ds-motion-state) var(--ds-motion-state-ease);
}
/* each cell after the first draws its slot but its left side: the cell
   before draws the hair between, so every line is one hair (two rings
   met made two); a chosen cell's lines are the active stroke (the hair
   on its left still the cell before's) */
.ds-group > .ds-button + .ds-button::before {
  box-shadow:
    inset 0 var(--button-stroke-w) 0 var(--button-stroke),
    inset calc(var(--button-stroke-w) * -1) 0 0 var(--button-stroke),
    inset 0 calc(var(--button-stroke-w) * -1) 0 var(--button-stroke);
}
/* the slots at the group's ends wear its corner, so its outline is one
   line round the curve (a square ring clipped by it has none there) */
.ds-group > .ds-button:first-child::before {
  border-top-left-radius: var(--slot-corner);
  border-bottom-left-radius: var(--slot-corner);
}
.ds-group > .ds-button:last-child::before {
  border-top-right-radius: var(--slot-corner);
  border-bottom-right-radius: var(--slot-corner);
}
/* the slot's lines change with the cell's ground, as the cell's own
   list below times it (a layer has a list of its own): on the state
   clock, a change of chosen-ness on the chosen clock, at once into and
   out of off and while the ring is up (the ring and the hair land in
   one frame), a recolour on the clock */
.ds-group > .ds-button[data-rung-change="chosen"]::before {
  transition-duration: var(--chosen-clock);
}
.ds-group
  > .ds-button:is(
    :disabled,
    [data-state="off"],
    [data-rung-change="off"],
    [data-focus]
  )::before,
html[data-kbd] .ds-group > .ds-button:focus-visible::before {
  transition: none;
}
html[data-recolour] .ds-group > .ds-button::before {
  transition: box-shadow var(--ds-motion-clock) var(--ds-motion-ease);
}
/* in a group the ring lies inside the cell (a neighbour would cover it
   2 out), with a hair of the page's ground inside it, so it reads on
   the chosen ground too; it is the cell's own (the slot's lines lie
   under it) */
html[data-kbd] .ds-group > .ds-button:not([data-state]):focus-visible,
.ds-group > .ds-button[data-focus] {
  outline-offset: -1px;
  box-shadow: inset 0 0 0 2px var(--ds-color-page);
}

/* ---- the press ------------------------------------------------------
   The pressed size, written by components/button.js from the measured
   rest size: the box narrows and lowers by a whole device pixel a side
   at the least, its margins the same, so its margin box, and every
   neighbour, stays where it was. A cell of a join keeps its flex
   width and narrows through its padding (--press-pad), so its share of
   the row and every other cell's stay as they were; a choice's cell
   narrows inside its column (above). The box and the
   label land in one frame (a size between two whole pixels is the half
   pixel the owner's rule is for; the label has no transition, so it
   lands with the box) */
.ds-button[data-press] {
  /* unset in a group's cell (its width the flex algorithm's, auto) */
  width: var(--press-w);
  min-width: 0;
  height: var(--press-h);
  padding-inline: var(--press-pad);
  margin: var(--press-y) var(--press-x);
}
/* the label's scale as measured (--press-label: the share, or less far
   where the label budget binds); a page without components/button.js
   keeps the plain share */
:where(html:not([data-presses]))
  .ds-button:not([data-state]):active
  .ds-button__label,
.ds-button[data-press] .ds-button__label {
  scale: var(--press-label, var(--ds-press-label));
}
/* a label that spans the box (the icon slot's) keeps its rest layout
   under the press: its rest width (components/button.js --press-label-w)
   and its rest place (the box's left moved in by the press, the label
   back out by it), so the slot, the words and the room between them
   stand as at rest and only the scale moves them, all about the box's
   centre (owner, 2026-10-11: "button scales asymmetrically. make sure
   content scales from one anchor point in the middle (same as
   rectangle)") */
.ds-button[data-press]:has(> .ds-button__label > .ds-button__icon:first-child)
  > .ds-button__label {
  width: var(--press-label-w, 100%);
  max-width: none;
  /* both margins, so the label fills the narrowed content box exactly
     and the box's centring has no free space to move it by */
  margin-inline: calc(var(--press-x, 0px) * -1);
  flex: none;
}
/* with the icon slot (round 6, iteration 13; owner, with a cross over
   the button and an arrow at its middle: "Make sure all objects are
   scaled from the center, from the same anchor point"): the box
   narrows about its centre (the margins), and the label, which spans
   the box, scales about that same centre: its own box is the content
   box, short of the room after the words, so its origin stands half
   that room past its middle (an icon alone has no room after it and
   keeps the middle); the icon and the words ride the label, one anchor
   for the three. (Iteration 11 shrank the icon and the words each about
   its own centre; two anchors read as a wobble.) A row that aligns its
   words to an edge keeps its own origin (--press-origin,
   components/button.js) */
.ds-button:has(> .ds-button__label > .ds-button__icon:first-child)
  > .ds-button__label {
  transform-origin: var(--press-origin, calc(50% + var(--room) / 2) center);
}
.ds-button:has(> .ds-button__label > .ds-button__icon:only-child)
  > .ds-button__label {
  /* An icon alone follows the button's centre even when its width stretches.
     Keep the square glyph slot; icon-and-text buttons retain their edge layout. */
  justify-content: center;
  transform-origin: var(--press-origin, center);
}
/* a group's cell keeps its share of the row pressed: its least width
   (the 32, or the lattice's snap, --snap-w) less the press either side,
   which its margins take back (a cell of a join grows from its own
   width, and a one-letter cell held at 32 by its least width narrowed
   to its words under the press, its neighbours 4 to the left; the
   review of the refinement of iteration 24) */
.ds-group > .ds-button[data-press] {
  min-width: max(
    0px,
    var(--snap-w, calc(var(--ds-grid-cell) * 8)) - var(--press-x, 0px) * 2
  );
}
/* a cell of a group, pressed: its slot keeps its lines and its place
   (above), and the box's corners at an end of the group are the
   group's less the press, each axis by its own inset (the corner law:
   max(0, corner − inset)), so the box and the track it leaves around it
   share their arcs; between two cells the corners stay square */
.ds-group > .ds-button[data-press]:first-child {
  border-top-left-radius: max(0px, var(--slot-corner) - var(--press-x, 0px))
    max(0px, var(--slot-corner) - var(--press-y, 0px));
  border-bottom-left-radius: max(0px, var(--slot-corner) - var(--press-x, 0px))
    max(0px, var(--slot-corner) - var(--press-y, 0px));
}
.ds-group > .ds-button[data-press]:last-child {
  border-top-right-radius: max(0px, var(--slot-corner) - var(--press-x, 0px))
    max(0px, var(--slot-corner) - var(--press-y, 0px));
  border-bottom-right-radius: max(0px, var(--slot-corner) - var(--press-x, 0px))
    max(0px, var(--slot-corner) - var(--press-y, 0px));
}

/* ---- the motion of a state -------------------------------------------
   The base list (on .ds-button, above) rides the state clock. These
   change it for one kind of change at a time */
/* a change of chosen-ness, on the state clock: components/button.js
   marks it (data-rung-change) for this clock. The lab's Chosen change,
   html[data-chosen], left with its row in round 5, and its rules at
   iteration 24's audit, since nothing wrote it */
.ds-button[data-rung-change="chosen"] {
  transition-duration: var(--chosen-clock);
}
/* the focus ring, and the hair it moves inside a group, land in one
   frame: the stroke leaves the list while the ring is up */
html[data-kbd] .ds-button:focus-visible,
.ds-button[data-focus] {
  transition-property: background-color, color;
}
/* a group's cell's own box-shadow is only that hair (its lines are its
   slot's): it lands with the ring both ways, the focus leaving too (an
   arrow moves the check and the focus in one change, which a chosen
   clock would otherwise fade the hair out on) */
.ds-group > .ds-button {
  transition-property: background-color, color;
}
/* off has no transition (the tool plan, §2.2): into off, and (marked by
   components/button.js) out of it */
.ds-button:is(:disabled, [data-state="off"], [data-rung-change="off"]) {
  transition: none;
}
/* a recolour (lib/core.js recolour: a scheme, a stroke switch, a colour
   edited) rides the clock and the curve, every rung, off too (it beats
   the rules above by its html[…], all but Instant's) */
html[data-recolour] .ds-button {
  transition:
    background-color var(--ds-motion-clock) var(--ds-motion-ease),
    color var(--ds-motion-clock) var(--ds-motion-ease),
    box-shadow var(--ds-motion-clock) var(--ds-motion-ease);
}
/* the group's track, which only a pressed cell shows, too */
html[data-recolour] .ds-group {
  transition: background-color var(--ds-motion-clock) var(--ds-motion-ease);
}

/* a finger: a band of 48 about the button's centre (the touch unit),
   the button its window's height (a group lets its bands out where
   its cells end) */
@media (pointer: coarse) {
  .ds-button {
    position: relative;
  }
  .ds-button::after {
    content: "";
    position: absolute;
    left: 0;
    right: 0;
    top: calc(
      (
          var(--control, calc(var(--ds-grid-cell) * 8)) - var(--ds-grid-cell) *
            12
        ) /
        2
    );
    bottom: calc(
      (
          var(--control, calc(var(--ds-grid-cell) * 8)) - var(--ds-grid-cell) *
            12
        ) /
        2
    );
  }
  /* a joined group would clip its cells' bands (overflow: hidden rounds
     its outer corners): under a finger it rounds its end cells instead,
     the same corners, and lets the bands out (the playground's composer
     found it first) */
  .ds-group {
    overflow: visible;
  }
  .ds-group > .ds-button:first-child {
    border-top-left-radius: var(--slot-corner);
    border-bottom-left-radius: var(--slot-corner);
  }
  .ds-group > .ds-button:last-child {
    border-top-right-radius: var(--slot-corner);
    border-bottom-right-radius: var(--slot-corner);
  }
  /* pressed, the band keeps its 48 about the smaller box */
  .ds-button[data-press]::after {
    top: calc((var(--press-h) - var(--ds-grid-cell) * 12) / 2);
    bottom: calc((var(--press-h) - var(--ds-grid-cell) * 12) / 2);
  }
}
@media (forced-colors: active) {
  .ds-button {
    outline: 1px solid CanvasText;
    outline-offset: -1px;
  }
  .ds-button:not([data-state])[aria-pressed="true"],
  .ds-button:not([data-state])[aria-checked="true"],
  .ds-button:not([data-state]):default:not(:disabled),
  .ds-button[data-state="chosen"],
  .ds-button[data-state="chosen-hover"],
  .ds-button[data-state="chosen-held"] {
    forced-color-adjust: none;
    background: Highlight;
    color: HighlightText;
  }
  html[data-kbd] .ds-button:not([data-state]):focus-visible,
  .ds-button[data-focus] {
    outline: var(--focus-ring-forced-w) solid Highlight;
  }
  /* off, real or forced (the States stage and matrix): the words in
     GrayText, as the engine draws a real off one's, so the forced rung
     paints what real input does (said for both, so an engine that
     leaves an author's colours, WebKit's emulation, draws them alike) */
  .ds-button:not([data-state]):disabled,
  .ds-button[data-state="off"] {
    color: GrayText;
  }
}
