/* The Dropdown (components/dropdown.js). A core sheet: every tier.

   Its motion (round 2, 2026-10-08, his item 5: "add subtle motion for
   opening and closing drop down", which supersedes 2026-10-05's "Add
   dropdown but no animations"; the 2026-10-07 reading of that line as
   the dropdown's own motion only is moot): the list arrives from the
   field's edge, its opacity on the curve and a translate of the gap on
   the bounce, over the follow clock (what answers a press answers in
   half the clock, as the bubble does); it leaves the same way back, on
   the curve alone (no bounce on departure), and stays displayed until
   the close has played (a display transition, allow-discrete; the
   script's list.hidden is the synchronous state). Over, there is no
   gap and no travel: the chosen option sits on the field every frame,
   the opacity alone plays. Expand, the list is in the flow and its
   arrival is the window's growth, on the whole clock: the field is a
   grid whose second track goes from 0fr to 1fr (a length first track,
   the control's height, so the trigger's row never moves: an auto
   track moved mid-flight in both engines, measured 2026-10-09), the
   list's gap with it. The chevron folds on the follow and the bounce:
   two arms at 45 degrees, each swinging a right angle about its own
   middle, so the apex crosses the centre and the glyph's centre never
   moves (the Bar Field's, as the owner refined it: 2026-09-06, "This
   rotation is distracting", then "it should stay centered" and "Make
   sure Icons has same timing but animates snappier"; 2026-09-07, "Make
   sure both states are symmetrical and stay centered"; round 6,
   iteration 16 put it back, in a square slot). The field and the options take
   the Button's base list, so a hover, a hold, an open and a release
   answer on the state clock and curve in two or three frames (owner,
   2026-09-07: "top should be like 2-3 frames"; his item 3: "same curve
   same timing"). A page-wide recolour (html[data-recolour]: a Scheme,
   a stroke switch, a colour edited) still rides the clock on the field,
   its value, its chevron and its label with every other part. Every
   duration is a motion token (the follow derives from the clock, which
   components/surface.css zeroes under reduced motion, so everything
   lands at once there); colour and opacity never ride the bounce (law
   5); no new token.

   A row: the label beside, then the field (the label's column is the
   Slider's, so a dropdown and a slider in one window share their edge);
   the label keeps the field's line, so a list opened in the flow leaves
   it where it was. The field is a control in the one ladder
   (components/button.css): rest the inactive ground and the inactive
   stroke, hover tinted by state-hover, held by state-held under a press
   and while open, the focus ring 2 out under html[data-kbd]; its corner
   by the law from the window it stands in. Its list is a panel of the
   system on the level above: opaque (Flat and Full the page ground with
   the text's 4 % wash, Plain the page ground and the stroke role's
   hairline), the inactive stroke as its edge, inset --ds-dropdown-inset,
   at least 192 wide. Its corner is the field's (his item 1, 2026-10-08:
   "make sure corners match in dialogs as well"; iteration 15,
   2026-10-11: "Dropdown dialog must share same radius as dropdown
   button. Further nesting must follow our global rules and have
   according corners"): the control's corner, capped at half the field's
   height, whatever surface the list stands in; its options and its
   search field, nested inside it by the inset, take the one inward law
   (components/surface.css: a part inside takes the corner less its
   inset, at least 0), so at the defaults (corner 4, inset 4) the list
   is 4 and its options square, under corner 8 the options 4, under
   inset 0 the options the field's own, and under Pill the list a pill
   with its options a pill less the inset. The theme's strip inside an
   option takes the option's corner less its own inset. An option at
   rest is clear; active
   (the hand or the keys on it) the inactive ground tinted by
   state-hover; chosen the active ground, the page's words and the
   active stroke, as the ladder's chosen; off the dim words, and a hand
   on it lights nothing. A many-choice option holds the system's
   Checkbox mark before its name (components/check.css, the row owning
   the state); the search field wears the global strokes. Its spacings
   are the system's, not its own (iteration 15, 2026-10-11: "use the
   same spacing as we use between the sliders so we have less values to
   manipulate ... reuse same tokens for various similar places"): the
   gap between the field and its list is the stack's (--stack-gap, what
   stands between two sliders in a stack; space.8 where no surface says
   otherwise), an option's side padding the control's (space.8, the
   Button's), the room either side of a category's rule the list's
   inset, and no space between two options; the list's one token of its
   own is its inset (--ds-dropdown-inset), with the rows it shows. An
   option's height, type and line are the surface's (the control's:
   compact or regular, 48 under a finger).

   The scrollbar stands in the inset (his item 16, 2026-10-08: "i know
   there needs to be space for scroll bar but can we apply correct
   spacing and squeeze in the gap with like 2px spacing between? ... i
   want everything to follow grid and same spacings and margins and
   follow automatically rounded corner rule"): the rows keep the inset
   on both sides whether or not the list scrolls, since the gutter on
   the right IS the inset's room, always reserved (overflow-y: scroll;
   auto reserves it only when the list scrolls, stable differs by
   engine), the list giving its right padding to it; the track is
   invisible (the list's ground and edge show through); the bar is a
   line, not a box: half the inset wide, 2 px clear of the rows, what is
   left to the edge, a pill (the one shape exception), in the dim words'
   ink (the stroke's ink merged with the list's edge into a thickened
   line, measured); its ends clear of the list's corners by the inset.
   At inset 0 there is no gutter and no bar: the dissolving edges alone
   say more lies beyond. The search list's box carries the gutter and
   the search row keeps the inset on its right, so its hairline ends
   with the rows. It is an indicator, not a grab handle: wheel, keys,
   touch and the edges do the scrolling. An engine without
   ::-webkit-scrollbar (Firefox) hides its bar rather than let an
   engine-sized one break the inset; scrollbar-width and scrollbar-color
   never stand beside the pseudo, since the standard property disables
   it in both engines (measured). */
/* the gap between the field and its list, the stack's or a share of it
   (--ds-dropdown-gap; round 6, iteration 21): registered, so the
   script that places the list reads it in pixels */
@property --list-gap {
  syntax: "<length>";
  inherits: true;
  initial-value: 0px;
}
.ds-dropdown {
  /* an option's height: the control's (48 under a finger, below) */
  --option-h: var(--control, calc(var(--ds-grid-cell) * 8));
  /* the list's corner (L3): the field's (components/surface.css writes
     --control-corner on the root: the one corner, capped at half the
     control); its options take it less the inset, below */
  --list-corner: var(--control-corner, var(--ds-radius-corner));
  /* the gap between the field and its list: the stack's (what stands
     between two sliders in a stack, components/surface.css; the
     system's 8 where no surface says) */
  --list-gap: calc(
    var(--stack-gap, var(--ds-space-8)) * var(--ds-dropdown-gap, 1)
  );
  /* where the list starts from and lands at (the motion): a travel of
     the gap from the field's edge below or above, none over or in the
     flow; the follow clock, the whole clock in the flow; the bounce on
     the way in, the curve on the way out (an alias of the curves only,
     as --arrive is) */
  --list-from: 0px;
  --list-clock: var(--ds-motion-follow);
  --list-curve: var(--ds-motion-bounce);
  display: grid;
  grid-template-columns: var(--ds-slider-label) minmax(0, 1fr);
  align-items: start;
  column-gap: var(--ds-slider-gap);
  min-width: 0;
  color: var(--ds-color-text);
  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);
}
.ds-dropdown[data-side="below"] {
  --list-from: calc(var(--list-gap) * -1);
}
.ds-dropdown[data-side="above"] {
  --list-from: var(--list-gap);
}
.ds-dropdown[data-side="expand"] {
  --list-clock: var(--ds-motion-clock);
}
.ds-dropdown__label {
  overflow: hidden;
  color: var(--ds-color-text-dim);
  line-height: var(--control, calc(var(--ds-grid-cell) * 8));
  white-space: nowrap;
}
.ds-dropdown__field {
  position: relative;
  min-width: 0;
}
/* in the flow (Opens: Expand, components/dropdown.js writes data-opens
   as it is set): the field is a grid of two tracks, the trigger's (a
   length, the control's height) and the list's, which grows from
   nothing to the list on the clock and the curve as the list opens and
   back as it closes, the window and what follows with it */
.ds-dropdown[data-opens="expand"] .ds-dropdown__field {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  grid-template-rows: var(--control, calc(var(--ds-grid-cell) * 8)) 0fr;
  transition: grid-template-rows var(--ds-motion-clock) var(--ds-motion-ease);
}
.ds-dropdown[data-opens="expand"][data-open] .ds-dropdown__field {
  grid-template-rows: var(--control, calc(var(--ds-grid-cell) * 8)) 1fr;
}
.ds-dropdown__trigger {
  display: flex;
  align-items: center;
  /* no gap before the chevron: its square slot holds its own room (the
     glyph 8 in from the slot's edge), so the words end 8 from the
     glyph as they start 8 from the field's edge (round 6, iteration
     16's review: with a gap too a narrow field cut its word) */
  gap: 0;
  box-sizing: border-box;
  width: 100%;
  height: var(--control, calc(var(--ds-grid-cell) * 8));
  margin: 0;
  /* the control's side padding on its left (the Button's); on its
     right the chevron's square slot is the room (iteration 16) */
  padding: 0 0 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(--stroke-inactive-w) var(--ds-stroke-inactive);
  color: var(--ds-color-text);
  font: inherit;
  text-align: left;
  cursor: pointer;
  /* a finger's second tap is a tap, not a zoom (iteration 15,
     2026-10-11: "once I toggle dropdown, I should be able to close it
     by tapping again same dropdown"): no double-tap wait on the field */
  touch-action: manipulation;
  /* the Button's base list: a state is an answer on the state clock */
  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);
}
@media (hover: hover) {
  .ds-dropdown__trigger:hover {
    background: color-mix(
      in srgb,
      var(--ds-color-inactive),
      var(--ds-color-text) var(--ds-state-hover)
    );
    box-shadow: inset 0 0 0 var(--hover-edge-w) var(--ds-state-hover-edge);
    color: var(--ds-state-hover-text);
  }
}
.ds-dropdown__trigger:active,
.ds-dropdown[data-open] .ds-dropdown__trigger {
  background: color-mix(
    in srgb,
    var(--ds-color-inactive),
    var(--ds-color-text) var(--ds-state-held)
  );
  box-shadow: inset 0 0 0 var(--held-edge-w) var(--ds-state-held-edge);
  color: var(--ds-state-held-text);
}
/* a ring only where the keys brought the focus (html[data-kbd],
   lib/core.js; owner, 2026-10-08) */
html[data-kbd] .ds-dropdown__trigger:focus-visible {
  outline: var(--focus-ring-w) solid var(--ds-color-focus);
  outline-offset: var(--focus-ring-gap);
}
/* a state forced for a specimen (round 5, the States grid: data-state
   hover, held, active, focus on the root), so a frozen cell paints what
   the hand does; active is the open field (the held tint, the chevron
   through its hinge) */
.ds-dropdown[data-state="hover"] .ds-dropdown__trigger {
  background: color-mix(
    in srgb,
    var(--ds-color-inactive),
    var(--ds-color-text) var(--ds-state-hover)
  );
  box-shadow: inset 0 0 0 var(--hover-edge-w) var(--ds-state-hover-edge);
  color: var(--ds-state-hover-text);
}
.ds-dropdown[data-state="held"] .ds-dropdown__trigger,
.ds-dropdown[data-state="active"] .ds-dropdown__trigger {
  background: color-mix(
    in srgb,
    var(--ds-color-inactive),
    var(--ds-color-text) var(--ds-state-held)
  );
  box-shadow: inset 0 0 0 var(--held-edge-w) var(--ds-state-held-edge);
  color: var(--ds-state-held-text);
}
.ds-dropdown__value {
  display: flex;
  flex: 1 1 auto;
  align-items: center;
  gap: var(--ds-space-8);
  min-width: 0;
}
/* a many-choice field with nothing chosen says so in the dim words */
.ds-dropdown__value .ds-dropdown__name[data-none] {
  color: var(--ds-color-text-dim);
}
/* held, the field's ground is darker: the words take the text colour
   (the dim words fell under 4.5:1 there) */
.ds-dropdown[data-open] .ds-dropdown__value .ds-dropdown__name[data-none] {
  color: var(--ds-color-text);
}
/* the chevron's slot: a square of the control's height at the field's
   right edge, the glyph (16, the kit's) centred in it, so the chevron
   stands where an icon's slot puts every icon (round 6, iteration 16;
   owner: "Icons are always meant to be in square/circular slot, 1:1
   aspect ratio so chevron inside dropdown must be aligned with the
   slot"): its centre the field's height halved from the right edge and
   from the top, as the Button's icon slot is from the left */
.ds-dropdown__chevron {
  display: 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));
  color: var(--ds-color-text-dim);
}
.ds-dropdown__chevron svg {
  flex: none;
  width: var(--ds-space-16);
  height: var(--ds-space-16);
  overflow: visible;
}
/* over its field (data-side over), every option keeps the field's
   chevron slot clear at its right end, so its words and its strip end
   where the field's value does, and the chosen option holds the
   chevron in it, open, where the field's stood (components/dropdown.js
   overChev; owner, 2026-10-12: "keep chevron in option over"); in the
   chosen option's ink. The option's gap before it is given back, as
   the field has none before its slot */
.ds-dropdown[data-side="over"] .ds-dropdown__option {
  padding-right: var(--control, calc(var(--ds-grid-cell) * 8));
}
.ds-dropdown[data-side="over"]
  .ds-dropdown__option:has(> .ds-dropdown__overchev) {
  padding-right: 0;
}
.ds-dropdown__overchev {
  margin-left: calc(var(--ds-space-8) * -1);
  color: inherit;
}
/* the chevron folds (components/dropdown.js draws it as two arms at 45
   degrees): open, each arm swings a right angle about its own middle,
   so the apex crosses the centre and the ends cross back, the glyph's
   centre never moving and the open shape the shut one mirrored (the
   Bar Field's, as the owner refined it, 2026-09-06 and 07: "it should
   stay centered"; "Make sure both states are symmetrical"); on the
   follow and the bounce, as the list arrives (the arms arrive on the
   bounce) */
/* on the list's own clock and its curve: the follow where the list
   floats, the whole clock where it grows in the flow (expand), the
   bounce on the way in and the curve alone on the way out (the motion
   audit, 2026-10-12: in the dock the arms folded in half the time the
   field grew, and bounced as the list left on the curve) */
.ds-dropdown__chevron line {
  transform-box: fill-box;
  transform-origin: center;
  transition: transform var(--list-clock, var(--ds-motion-follow))
    var(--ds-motion-ease);
}
.ds-dropdown[data-open] .ds-dropdown__chevron line {
  transition: transform var(--list-clock, var(--ds-motion-follow))
    var(--ds-motion-bounce);
}
.ds-dropdown[data-open] .ds-dropdown__chevron line[data-arm="left"] {
  transform: rotate(-90deg);
}
.ds-dropdown[data-open] .ds-dropdown__chevron line[data-arm="right"] {
  transform: rotate(90deg);
}
/* a recolour rides the clock and the curve (the list slider.css and
   button.css use), over the state clock's answer */
html[data-recolour] .ds-dropdown__trigger {
  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);
}
html[data-recolour] .ds-dropdown__value,
html[data-recolour] .ds-dropdown__value .ds-dropdown__name,
html[data-recolour] .ds-dropdown__value .ds-dropdown__detail,
html[data-recolour] .ds-dropdown__chevron,
html[data-recolour] .ds-dropdown__label {
  transition: color var(--ds-motion-clock) var(--ds-motion-ease);
}
/* the list: a panel on the level above, below the field by the gap, or
   above it, or over it (the script sets its top; the list steps out by
   its inset, so its options line up with the field), or in the flow; as
   wide as the field and 192 at the least. Its rows stand in a column
   in its box (iteration 21: the list is the frame, its ground, edge,
   corner and height; the box in it scrolls, below) */
.ds-dropdown__list {
  position: absolute;
  z-index: 20;
  left: 0;
  width: max(100%, calc(var(--ds-grid-cell) * 48));
  display: flex;
  flex-direction: column;
  overflow: hidden;
  /* the rows it shows, then it scrolls: the options and their gaps,
     its inset above, and half of the next row (it runs under the edge),
     so a list that scrolls says so beside the bar and a cut never falls
     on a category's edge */
  --list-limit: calc(
    (var(--option-h) + var(--ds-dropdown-inset)) * var(--ds-dropdown-rows) +
      var(--option-h) / 2 + var(--ds-dropdown-inset)
  );
  --box-limit: var(--list-limit);
  max-height: var(--list-limit);
  /* the inset on every side, the box's padding (below); where the
     engine keeps a gutter for the bar, the gutter is the right inset
     (data-gutter, the scrollbar block below). At least a rule, so at
     inset 0 a lit row never covers the list's own hairline (round 6,
     iteration 17's review: its ground and corners broke the edge) */
  --space: max(var(--ds-dropdown-inset), var(--rule));
  border-radius: var(--list-corner);
  box-shadow: inset 0 0 0 var(--stroke-inactive-w) var(--ds-stroke-inactive);
  outline: none;
  /* the motion: in from the field's edge on the follow (the bounce on
     the travel, the curve on the opacity), out the same way back on
     the curve, displayed until the way out has played */
  opacity: 1;
  translate: 0 0;
  transition:
    opacity var(--list-clock) var(--ds-motion-ease),
    translate var(--list-clock) var(--list-curve),
    margin-top var(--list-clock) var(--ds-motion-ease),
    display var(--list-clock) var(--ds-motion-ease) allow-discrete;
}
/* opaque, whatever the tier: a list over other controls must hide them
   (the field's child selector outranks the tiers' panel wash; Plain's
   page ground is below). Its ground is said once, for the edges below */
.ds-dropdown__field > .ds-dropdown__list[data-surface] {
  --list-ground: color-mix(
    in srgb,
    var(--ds-color-text) 4%,
    var(--ds-color-page)
  );
  background: var(--list-ground);
  padding: 0;
}
.ds-dropdown__list > *,
.ds-dropdown__box > * {
  flex: none;
}
.ds-dropdown__list > .ds-dropdown__box {
  /* The list reveals the full-sized scroller; an expanding grid track must
     not shorten it and manufacture a scrollbar for otherwise fitting rows. */
  flex: none;
  height: max-content;
  max-height: var(--box-limit);
}
/* the edges an option runs under dissolve, never cut through its words
   (docs/tone-of-voice/visual.md: the occlusion gradient, 2026-08-27,
   one gradient for every edge content passes under, its direction the
   instance's; "Cropped text dissolves", 2026-09-06): over the edge an
   option runs under (data-before: one scrolled past the top; data-more:
   more below, components/dropdown.js edges, shown only while the edge
   cuts a row), the box fades what passes under it to nothing over the
   room between the edge and the words of a row resting there (its
   inset and the row's own room above its line; owner, 2026-10-11:
   "make sure gradient is used only on available margin above text
   until container edge"). A mask, so whatever passes (the chosen row's
   dark ground, a theme's colours) dissolves into the frame's ground,
   which with the frame's edge stays whole (iteration 21; owner: "we
   still have the same bug, make sure we really define these overflows
   well": a band of the ground painted over the rows drew a stripe over
   the chosen row). The strip at the right where the bar runs is never
   faded */
.ds-dropdown__box {
  --band: calc(
    var(--ds-dropdown-inset) +
      (var(--option-h) - var(--line, calc(var(--ds-grid-cell) * 6))) / 2
  );
  --fade-top: 0px;
  --fade-foot: 0px;
  --fade-mask:
    linear-gradient(
        to bottom,
        transparent,
        #000 var(--fade-top),
        #000 calc(100% - var(--fade-foot)),
        transparent
      )
      left top / calc(100% - var(--space)) 100% no-repeat,
    linear-gradient(#000, #000) right top / var(--space) 100% no-repeat;
  -webkit-mask: var(--fade-mask);
  mask: var(--fade-mask);
}
.ds-dropdown__box[data-before] {
  --fade-top: var(--band);
}
.ds-dropdown__box[data-more] {
  --fade-foot: var(--band);
}
/* ---- the scrollbar in the inset ------------------------------------ */
.ds-dropdown__list,
.ds-dropdown__box {
  /* the bar: half the inset (2 at 4, 4 at 8, none at 0), 2 clear of
     the rows */
  --bar: calc(var(--ds-dropdown-inset) / 2);
  --bar-gap: 2px;
}
/* the gutter is the inset wide; the track lets the list's ground and
   edge through and keeps its ends clear of the corners */
.ds-dropdown__list::-webkit-scrollbar,
.ds-dropdown__box::-webkit-scrollbar {
  width: var(--ds-dropdown-inset);
}
.ds-dropdown__list::-webkit-scrollbar-track,
.ds-dropdown__box::-webkit-scrollbar-track {
  background: transparent;
  /* its ends clear of the list's corners, at any corner (round 6,
     iteration 17's review: at a corner of 16 the thumb's end crossed
     the curve) */
  margin-block: max(var(--ds-dropdown-inset), var(--list-corner));
}
/* the thumb: its ink clipped to the bar's width by transparent borders,
   the gap to the rows on its left, what is left to the edge on its
   right, a pill: the ink is the padding box, whose corners are the
   border box's less the borders, so each side's horizontal radius
   carries its border and the ink's ends round by half its width (a
   radius of half the bar alone left a bar of 4 square at inset 8) */
.ds-dropdown__list::-webkit-scrollbar-thumb,
.ds-dropdown__box::-webkit-scrollbar-thumb {
  --bar-edge: calc(var(--ds-dropdown-inset) - var(--bar) - var(--bar-gap));
  /* the dim words at half: an indicator, never a line that reads as
     the list's edge (round 6, iteration 16's review: in light the full
     dim ink read as a dark line on the edge) */
  background: color-mix(in srgb, var(--ds-color-text-dim) 50%, transparent);
  background-clip: padding-box;
  border: 0 solid transparent;
  border-left-width: var(--bar-gap);
  border-right-width: var(--bar-edge);
  border-radius: calc(var(--bar) / 2 + var(--bar-gap))
    calc(var(--bar) / 2 + var(--bar-edge))
    calc(var(--bar) / 2 + var(--bar-edge))
    calc(var(--bar) / 2 + var(--bar-gap)) / calc(var(--bar) / 2);
}
/* the gutter: where the engine keeps room for the bar (a classic
   scrollbar, the inset wide by the rule above), that room is the right
   inset and the padding gives way to it; where it keeps none (an
   overlay bar: iOS, a Mac with bars that show on scroll; Firefox,
   below), the padding stays, so the rows stand the inset from every
   edge whatever the engine (iteration 15, 2026-10-11, the phone: the
   rows ran to the right edge under an overlay bar). components/
   dropdown.js measures the scroller as the list opens and writes
   data-gutter on the root */
.ds-dropdown[data-gutter] .ds-dropdown__box {
  padding-right: 0;
}
/* no ::-webkit-scrollbar (Firefox): no bar, the rows keep the inset by
   the padding and the edges alone say more lies beyond. Scoped, since
   a scrollbar-width beside the pseudo disables the pseudo in both
   engines (measured 2026-10-08) */
@supports not selector(::-webkit-scrollbar) {
  .ds-dropdown__list,
  .ds-dropdown__box {
    scrollbar-width: none;
  }
}
/* ---- where it stands, and how it arrives --------------------------- */
.ds-dropdown[data-side="below"] .ds-dropdown__list {
  top: calc(100% + var(--list-gap));
}
.ds-dropdown[data-side="above"] .ds-dropdown__list {
  bottom: calc(100% + var(--list-gap));
}
.ds-dropdown[data-side="over"] .ds-dropdown__list {
  left: calc(var(--ds-dropdown-inset) * -1);
  width: calc(
    max(100%, calc(var(--ds-grid-cell) * 48)) + var(--ds-dropdown-inset) * 2
  );
}
/* a list that would pass the window's right edge ends on the field's
   (components/dropdown.js writes data-align), over it stepped out by its
   inset as at its start */
.ds-dropdown[data-align="end"] .ds-dropdown__list {
  left: auto;
  right: 0;
}
.ds-dropdown[data-side="over"][data-align="end"] .ds-dropdown__list {
  right: calc(var(--ds-dropdown-inset) * -1);
}
/* in the flow: under the field by the gap, in the field's second track
   (above), which grows with it; its own least height nothing, so the
   track can start from nothing */
.ds-dropdown[data-side="expand"] .ds-dropdown__list {
  position: static;
  z-index: auto;
  width: auto;
  min-height: 0;
  margin-top: var(--list-gap);
}
/* hidden: the script's state, at once; the box stays displayed until
   the way out has played (the display transition above), takes no
   pointer meanwhile, and lands at its start: faded, moved back to the
   field's edge, its gap closed in the flow. The way out is on the
   curve alone */
.ds-dropdown .ds-dropdown__list[hidden],
.ds-dropdown .ds-dropdown__list[data-search][hidden] {
  --list-curve: var(--ds-motion-ease);
  display: none;
  opacity: 0;
  translate: 0 var(--list-from);
  margin-top: 0;
  pointer-events: none;
}
/* a list posed for a hand busy elsewhere (a pointer on a pane's row,
   components/dropdown.js pose) lets the pointer through: a press or a
   release it hangs over reaches what is under it, never an option */
.ds-dropdown[data-pass] .ds-dropdown__list {
  pointer-events: none;
}
/* the way in starts where the way out ends */
@starting-style {
  .ds-dropdown .ds-dropdown__list:not([hidden]) {
    opacity: 0;
    translate: 0 var(--list-from);
    margin-top: 0;
  }
}
/* the keys on the list: the ladder's ring, 1 px of the focus colour 2
   out, clear of its edge */
html[data-kbd] .ds-dropdown__list:has(.ds-dropdown__box:focus-visible) {
  outline: var(--focus-ring-w) solid var(--ds-color-focus);
  outline-offset: var(--focus-ring-gap);
}
/* Plain draws its surfaces by the hairline on the page's ground, which
   a list keeps too */
html[data-render="plain"] .ds-dropdown__list:not([data-plain] *) {
  box-shadow: inset 0 0 0 var(--rule) var(--ds-color-stroke);
}
html[data-render="plain"]
  .ds-dropdown__field
  > .ds-dropdown__list[data-surface]:not([data-plain] *) {
  --list-ground: var(--ds-color-page);
}
/* with a search: the field on top, the options in a box that scrolls
   under it; the list holds the field and the rows: its inset at either
   end, the search row's padding and margin under the field, and the gap
   between the field's row and the box's, as between options */
.ds-dropdown__list[data-search] {
  display: grid;
  overflow: hidden;
  grid-template-rows: auto auto;
  align-content: start;
  /* the search field's row, then the options' box as a plain list's:
     its rows and half of the next */
  --list-limit: calc(
    (var(--option-h) + var(--ds-dropdown-inset)) *
      (var(--ds-dropdown-rows) + 1) + var(--option-h) / 2 +
      var(--ds-dropdown-inset) * 4
  );
  --box-limit: calc(
    var(--list-limit) - var(--option-h) - var(--space) - var(--ds-dropdown-inset)
  );
}
/* the search row keeps the inset on its right where the box under it
   carries the gutter (data-gutter, above), so its hairline ends with
   the rows */
.ds-dropdown__search {
  display: flex;
  margin: var(--space) var(--space) 0;
  padding-bottom: var(--ds-dropdown-inset);
  box-shadow: inset 0 calc(var(--rule) * -1) 0 var(--ds-color-stroke);
}
.ds-dropdown__find {
  box-sizing: border-box;
  width: 100%;
  height: var(--option-h);
  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(--stroke-inactive-w) var(--ds-stroke-inactive);
  color: var(--ds-color-text);
  font: inherit;
  outline: none;
  -webkit-appearance: none;
  appearance: none;
  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);
}
.ds-dropdown__find::placeholder {
  color: var(--ds-color-text-dim);
  opacity: 1;
}
@media (hover: hover) {
  .ds-dropdown__find:hover {
    background: color-mix(
      in srgb,
      var(--ds-color-inactive),
      var(--ds-color-text) var(--ds-state-hover)
    );
  }
}
html[data-kbd] .ds-dropdown__find:focus-visible {
  outline: var(--focus-ring-w) solid var(--ds-color-focus);
  outline-offset: var(--focus-ring-gap);
}
/* the box: the options the inset apart, as they stand the inset from
   the frame's edge, so two lit rows (the chosen and the one under the
   hand) never touch (round 6, iteration 16; owner: "Add spacing between
   each item inside dropdown, now when I hover it touches directly the
   other bubble"): one value for around and between; under a search
   field, the inset after its rule. The keys keep the active option
   clear of an edge's dissolve by half a row, so it is always whole */
.ds-dropdown__box {
  display: flex;
  flex-direction: column;
  row-gap: var(--ds-dropdown-inset);
  box-sizing: border-box;
  min-height: 0;
  padding: var(--space);
  overflow-y: scroll;
  overscroll-behavior: contain;
  scroll-padding-block: calc(var(--option-h) / 2);
  outline: none;
}
.ds-dropdown__list[data-search] > .ds-dropdown__box {
  padding-top: var(--ds-dropdown-inset);
}
.ds-dropdown__empty {
  padding: 0 var(--ds-space-8);
  color: var(--ds-color-text-dim);
  line-height: var(--option-h);
}
/* what a search finds, for a reader alone (components/dropdown.js) */
.ds-dropdown__told {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}
.ds-dropdown__empty[hidden],
.ds-dropdown__option[hidden],
.ds-dropdown__group[hidden] {
  display: none;
}
/* the list's options and its search field: nested in the list by its
   inset, the list's corner less the inset, at least 0 (the one inward
   law), and never more than half an option's height */
.ds-dropdown__list .ds-dropdown__option,
.ds-dropdown__list .ds-dropdown__find {
  --control-corner: max(
    0px,
    min(var(--list-corner), var(--option-h) / 2) -
      max(var(--ds-dropdown-inset), var(--rule))
  );
  /* the row's height, for a part inside it (the theme's strip) */
  --row-h: var(--option-h);
}
.ds-dropdown__option {
  display: flex;
  align-items: center;
  gap: var(--ds-space-8);
  box-sizing: border-box;
  min-width: 0;
  height: var(--option-h);
  /* the control's side padding (the Button's, the field's) */
  padding: 0 var(--ds-space-8);
  border-radius: var(--control-corner, var(--ds-radius-corner));
  color: var(--ds-color-text);
  cursor: pointer;
  user-select: none;
  -webkit-user-select: none;
  /* a Checkbox mark in a many-choice option stands the option's side
     padding in from the row's edge: its inset for the law */
  --check-space: var(--ds-space-8);
  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);
}
.ds-dropdown__option[data-active] {
  background: color-mix(
    in srgb,
    var(--ds-color-inactive),
    var(--ds-color-text) var(--ds-state-hover)
  );
}
/* a single list's chosen option: the ladder's chosen */
.ds-dropdown:not([data-multiple]) .ds-dropdown__option[aria-selected="true"] {
  background: var(--ds-color-active);
  box-shadow: inset 0 0 0 var(--stroke-active-w) var(--ds-stroke-active);
  color: var(--ds-color-page);
}
.ds-dropdown:not([data-multiple])
  .ds-dropdown__option[aria-selected="true"][data-active] {
  background: color-mix(
    in srgb,
    var(--ds-color-active),
    var(--ds-color-text) var(--ds-state-hover)
  );
}
.ds-dropdown:not([data-multiple])
  .ds-dropdown__option[aria-selected="true"]
  .ds-dropdown__detail {
  color: inherit;
}
.ds-dropdown__option[aria-disabled="true"] {
  color: var(--ds-color-text-dim);
  cursor: default;
}
/* a long name keeps its line and dissolves at its end, never an
   ellipsis (docs/tone-of-voice/visual.md: "Cropped text dissolves",
   2026-09-06, "use gradient fade exactly like in minimize"): a name or
   a label that holds more than it shows (data-cropped, components/
   dropdown.js) fades out over its last 24; the field and the option
   keep their height */
.ds-dropdown__name {
  flex: 1 1 auto;
  min-width: 0;
  overflow: hidden;
  white-space: nowrap;
}
.ds-dropdown__name[data-cropped],
.ds-dropdown__label[data-cropped] {
  mask-image: linear-gradient(
    to left,
    transparent,
    #000 calc(var(--ds-grid-cell) * 6)
  );
}
/* a detail: a dim word at the option's end */
.ds-dropdown__detail {
  flex: none;
  color: var(--ds-color-text-dim);
  white-space: nowrap;
}
/* a category: its heading in the dim words, not an option; a rule
   between two, the list's inset either side of it */
.ds-dropdown__group {
  display: grid;
  row-gap: var(--ds-dropdown-inset);
}
.ds-dropdown__group + .ds-dropdown__group {
  margin-top: var(--ds-dropdown-inset);
  padding-top: var(--ds-dropdown-inset);
  box-shadow: inset 0 var(--rule) 0 var(--ds-color-stroke);
}
.ds-dropdown__head {
  padding: 0 var(--ds-space-8);
  color: var(--ds-color-text-dim);
  line-height: var(--line, calc(var(--ds-grid-cell) * 6));
}
/* a theme: its name in a column of its own, then its five colours as a
   strip across the rest of the width (four of whole cells, the last
   what is left), in the control's inner corner, edged by a hair of the
   text colour at 12 % */
.ds-dropdown[data-kind="swatches"] .ds-dropdown__name {
  flex: none;
  width: calc(var(--ds-grid-cell) * 18);
}
.ds-dropdown__strip {
  position: relative;
  display: grid;
  grid-template-columns:
    repeat(4, round(down, 20%, var(--ds-grid-cell)))
    minmax(0, 1fr);
  flex: 1 1 auto;
  min-width: 0;
  height: var(--ds-space-16);
  overflow: hidden;
  /* a part inside its row (owner, 2026-10-10: "swatches are 2nd level
     of nested and should follow nested corners rule as well"): the
     row's corner (the field's, or the option's) less the strip's inset
     from the row's edge (the row's height less the strip's, halved),
     at least 0 */
  border-radius: max(
    0px,
    var(--control-corner, var(--ds-radius-corner)) -
      (
        var(--row-h, var(--control, calc(var(--ds-grid-cell) * 8))) -
          var(--ds-space-16)
      ) /
      2
  );
}
.ds-dropdown__strip::after {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: inherit;
  box-shadow: inset 0 0 0 var(--rule) var(--colour-hair);
  pointer-events: none;
}
/* a glyph before an option's name (round 6, iteration 6): at the
   slot's preset, a share of the words (iteration 24: --icon, 1em at
   Medium), in their colour; a set's preview after the name: its first
   glyphs in a row, 4 apart, dim, the chosen option's in its words'
   colour */
.ds-dropdown__glyph {
  flex: none;
  width: var(--icon);
  height: var(--icon);
}
/* the glyphs this component draws itself, the option's and the
   chevron's, at the kit's house stroke (iteration 24: icon.weight; the
   attribute is the fallback) */
.ds-dropdown__glyph :is(path, line),
.ds-dropdown__chevron line {
  stroke-width: var(--ds-icon-weight);
}
.ds-dropdown__glyphs {
  display: inline-flex;
  flex: none;
  gap: var(--ds-space-4);
  margin-left: auto;
  color: var(--ds-color-text-dim);
}
.ds-dropdown:not([data-multiple])
  .ds-dropdown__option[aria-selected="true"]
  .ds-dropdown__glyphs {
  color: inherit;
}
/* off (setOff): it keeps its place and its value, takes no hand and
   no key, its words and its chevron dim (the ladder's off: readable,
   never faded) */
.ds-dropdown[data-off] .ds-dropdown__trigger {
  background: var(--ds-color-inactive);
  color: var(--ds-color-text-dim);
  cursor: default;
}
/* a finger: the field's hit band reaches 48 about its centre (the
   Slider's rule), and the list's options are 48 high */
@media (pointer: coarse) {
  .ds-dropdown {
    --option-h: calc(var(--ds-grid-cell) * 12);
  }
  .ds-dropdown__find {
    font-size: var(--ds-text-16);
  }
  .ds-dropdown__trigger {
    position: relative;
  }
  .ds-dropdown__trigger::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
    );
  }
}
@media (forced-colors: active) {
  .ds-dropdown__trigger,
  .ds-dropdown__list,
  .ds-dropdown__find {
    outline: 1px solid CanvasText;
  }
  html[data-kbd] .ds-dropdown__trigger:focus-visible,
  html[data-kbd] .ds-dropdown__list:has(.ds-dropdown__box:focus-visible),
  html[data-kbd] .ds-dropdown__find:focus-visible {
    outline: var(--focus-ring-forced-w) solid Highlight;
  }
  /* as specific as the chosen option's own rules, so the system's
     highlight wins; in a many-choice list the active row is the one
     Highlight row (the marks carry the choices, components/check.css) */
  .ds-dropdown:not([data-multiple]) .ds-dropdown__option[aria-selected="true"],
  .ds-dropdown .ds-dropdown__option[data-active],
  .ds-dropdown:not([data-multiple])
    .ds-dropdown__option[aria-selected="true"][data-active] {
    forced-color-adjust: none;
    background: Highlight;
    box-shadow: none;
    color: HighlightText;
  }
  .ds-dropdown__swatch {
    forced-color-adjust: none;
  }
}
