/* ==========================================================================
   ui.css — buttons, cards, chips, progress rail, sliders, dialogs, toasts
   ========================================================================== */

/* ---------- buttons ----------
   Flat, thin-edged, and dark by default. The old set was soft-UI: a white
   gradient on the primary with an inset highlight and a 20px drop shadow,
   which is a physical button pretending to sit above the page. Nothing else
   here pretends to be an object — the panels are hairlines on near-black and
   the stimuli are the only thing with any weight — so the buttons were the
   one loud surface in a quiet interface.

   What replaces it: one flat fill, one 1px edge, and state carried by the
   edge rather than by shadow. A control announces itself by being crisply
   bounded, not by floating. */
.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: var(--s-2);
  min-height: var(--ctl-lg);
  padding: 0 var(--ctl-pad);
  border-radius: var(--radius-sm);
  font-size: var(--t-body); font-weight: 500; letter-spacing: -.002em;
  color: var(--txt-2);
  background: var(--bg-1);
  border: 1px solid var(--line-2);
  cursor: pointer;
  white-space: nowrap;
  transition: background var(--fast) var(--ease), border-color var(--fast) var(--ease),
              color var(--fast) var(--ease), opacity var(--fast) var(--ease);
  user-select: none;
  -webkit-tap-highlight-color: transparent;
}
.btn:hover  { background: var(--bg-2); border-color: var(--line-3); color: var(--txt); }
/* No translate on press. A control that moves under the cursor is a control
   you can miss the second half of your own click on; the edge lighting up is
   the acknowledgement. */
.btn:active { background: var(--bg-3); border-color: var(--txt-4); }
.btn[disabled], .btn.is-disabled { opacity: .3; pointer-events: none; }

/* The one filled surface in the interface, so it needs no other emphasis:
   flat, unshadowed, and the only place a near-white plane appears. */
.btn-primary {
  background: var(--txt);
  color: var(--bg);
  border-color: var(--txt);
  font-weight: 560;
}
.btn-primary:hover  { background: #ffffff; border-color: #ffffff; color: var(--bg); }
.btn-primary:active { background: var(--txt-2); border-color: var(--txt-2); }

.btn-ghost { background: transparent; border-color: transparent; color: var(--txt-3); }
.btn-ghost:hover { background: var(--bg-2); color: var(--txt); border-color: var(--line); }
.btn-ghost:active { background: var(--bg-3); border-color: var(--line-2); }

.btn-quiet { background: transparent; border-color: var(--line-2); color: var(--txt-2); }
.btn-quiet:hover { color: var(--txt); border-color: var(--line-3); background: var(--bg-2); }

/* Leaves the app. The accent is reserved for focus, links and progress, and
   this is the most literal link in the interface — so it gets the colour, and
   the diagonal arrow that says the destination is not another page of this
   test. */
.btn-link {
  background: var(--accent-dim);
  border-color: color-mix(in srgb, var(--accent) 34%, var(--line));
  color: var(--accent);
}
.btn-link:hover {
  background: color-mix(in srgb, var(--accent) 20%, transparent);
  border-color: var(--accent);
  color: #cfe2f4;
}
.btn-link svg { opacity: .9; }

/* Three sizes, three heights from the control scale. Nothing here sets a
   vertical padding: a control's height is a chosen step, not the residue of
   a font size plus a guess. */
.btn-lg { min-height: 3.25rem; padding: 0 var(--ctl-pad-lg); font-size: var(--t-lede); border-radius: var(--radius-sm); }
.btn-sm { min-height: var(--ctl); padding: 0 var(--ctl-pad-sm); font-size: var(--t-small); border-radius: var(--radius-sm); }
/* Every icon inside a button is sized here, once. An inline <svg> carrying
   only a viewBox has no intrinsic size, so with no rule it collapses — which
   is why buttons asking for an icon have been rendering as bare words.
   Sized in `em` so it tracks whichever of the three button sizes it lands in
   rather than needing a rule per size. */
.btn svg { width: 1.05em; height: 1.05em; flex: 0 0 auto; opacity: .7; }
.btn:hover svg { opacity: .9; }
.btn-primary svg { opacity: .8; }

/* the author link is a real anchor wearing button clothes */
a.btn { text-decoration: none; }

/* `inline-flex`, not the browser default. An icon button is built from a
   bare <button> holding a block-level <svg>, and a block child does not obey
   the button's text-align — so without this the glyph sits against the left
   edge. It was invisible at 2.1rem square and obvious the moment the button
   got wider than its icon. */
.btn-icon {
  display: inline-flex; align-items: center; justify-content: center;
  width: var(--ctl); height: var(--ctl); min-height: 0; padding: 0;
  border-radius: var(--radius-sm);
  background: transparent; border: 1px solid transparent; color: var(--txt-3);
  cursor: pointer;
  transition: background var(--fast) var(--ease), color var(--fast) var(--ease),
              border-color var(--fast) var(--ease);
}
.btn-icon:hover { background: var(--bg-3); color: var(--txt); border-color: var(--line); }

/* Touch minimums in px, not rem.
   The root font-size is a clamp that bottoms out at 14px on a phone, so every
   rem-sized control shrinks exactly where fingers need it to grow — a 2.1rem
   icon button lands at 29px on a 375px screen. These are the controls the test
   is operated with, so they get a floor that does not scale away. */
@media (pointer: coarse) {
  .btn-icon { width: var(--tap); height: var(--tap); }
  .btn { min-height: var(--tap); }
  .btn-sm { min-height: 36px; }
}
.btn-icon svg { width: 1.05rem; height: 1.05rem; }

/* ---------- cards ---------- */
.card {
  background: var(--bg-1);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: 1.25rem var(--s-5);
}
.card-tight { padding: .9rem var(--s-4); }
.card-title {
  font-size: var(--t-tiny); font-weight: 620; letter-spacing: .1em; text-transform: uppercase;
  color: var(--txt-3); margin-bottom: var(--s-3);
}

.panel {
  background: var(--bg-1);
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-1);
}

/* ---------- chips ---------- */
.chip {
  display: inline-flex; align-items: center; gap: var(--s-2);
  padding: var(--s-1) var(--s-3);
  border-radius: 99px;
  font-size: var(--t-tiny); font-weight: 500;
  background: var(--bg-2); border: 1px solid var(--line);
  color: var(--txt-2);
  white-space: nowrap;
}
/* Chips size their own icons. Without this the <svg> collapses to nothing —
   it carries only a viewBox — and the flex gap beside it stays, so every chip
   rendered as a blank column and then its label. Same fault the buttons had. */
.chip svg { width: .9rem; height: .9rem; flex: 0 0 auto; opacity: .85; }
.chip-dot { width: .48rem; height: .48rem; border-radius: 99px; background: currentColor; flex: 0 0 auto; }
.chip-sm { padding: var(--s-1) var(--s-2); font-size: var(--t-tiny); gap: var(--s-1); }
.chip-sm svg { width: .82rem; height: .82rem; }
.chip-sm span { overflow: hidden; text-overflow: ellipsis; max-width: 14rem; }
.chip-accent { color: var(--accent); border-color: color-mix(in srgb, var(--accent) 26%, var(--line)); background: var(--accent-dim); }

/* Tinted chips.
   The palette rule in tokens.css keeps the interface achromatic because a
   coloured element next to a stimulus shifts the adaptation state. These live
   on the welcome page, which shows no stimulus at all, and the tint is doing
   work rather than decorating: it sorts three facts of different kinds — how
   long, how much, and the one people actually want reassurance about. Held to
   a wash and a border so it reads as a category, not as a warning. */
.chip-tint {
  color: color-mix(in srgb, var(--tint) 72%, var(--txt));
  border-color: color-mix(in srgb, var(--tint) 30%, var(--line));
  background: color-mix(in srgb, var(--tint) 9%, transparent);
}
.chip-tint svg { color: var(--tint); opacity: 1; }
.chip-time { --tint: var(--accent); }
.chip-count { --tint: #9b8fd6; }
.chip-safe { --tint: var(--good); }

/* The key-cap style that used to live here is gone. Nothing in the interface
   names a key any more: every test is answered with the pointer, so a <kbd>
   would be describing something that does not exist. */

/* ---------- progress rail (appbar) ---------- */
.rail { display: flex; align-items: center; gap: var(--s-1); min-width: 0; }
.rail-seg {
  position: relative;
  height: .34rem; width: 1.55rem; border-radius: 99px;
  background: var(--line);
  transition: background .3s var(--ease), width .3s var(--ease);
  flex: 0 0 auto;
}
.rail-seg.is-done { background: var(--txt-4); }
.rail-seg.is-now  { background: var(--accent); width: 2.6rem; }
.rail-seg::after {
  content: attr(data-label);
  position: absolute; top: 1.1rem; left: 50%; transform: translateX(-50%);
  font-size: var(--t-micro); color: var(--txt-3); white-space: nowrap;
  opacity: 0; pointer-events: none; transition: opacity .18s;
  background: var(--bg-2); border: 1px solid var(--line); border-radius: var(--radius-sm);
  padding: .12rem var(--s-2);
}
.rail-seg:hover::after { opacity: 1; }
@media (max-width: 860px) { .rail-seg { width: 1rem; } .rail-seg.is-now { width: 1.8rem; } }
@media (max-width: 560px) { .rail { display: none; } }

.rail-count {
  font-family: var(--mono); font-size: var(--t-tiny); color: var(--txt-3);
  margin-left: var(--s-2); letter-spacing: .02em; flex: 0 0 auto;
}

/* ---------- trial progress bar (in stage) ---------- */
/* The trough used to be a black wash, which was legible when this bar sat on
   a light strip. It lives on the dark footer now, so it takes the line colour
   — a trough has to be visible when empty or the bar reads as missing. */
.tprog {
  height: 3px; border-radius: var(--radius-pill); background: var(--line);
  overflow: hidden; width: 100%;
}
.tprog-fill {
  height: 100%; background: var(--txt-3); border-radius: var(--radius-pill);
  transition: width .32s var(--ease-out);
}

/* ---------- sliders ----------
   A fader, not a knob. The thumb was a 1.15rem white disc with a 4px drop
   shadow — the same soft-UI vocabulary the buttons have just lost, and a
   round blob is also the least precise shape to aim a colour setting with.
   A narrow upright cap reads as a position on a scale, and its own edges tell
   you where that position is.

   One thing this deliberately does NOT have: a tick at the centre of the
   track. Both modules that use these sliders — the neutral point and the
   memory colour — measure where YOUR neutral sits, with no reference shown.
   A centre mark would be a reference: it says "zero correction is here", and
   a nudge back to it is the measurement gone. */
.slider {
  -webkit-appearance: none; appearance: none;
  width: 100%; height: 1.5rem; background: transparent; cursor: pointer;
}
.slider::-webkit-slider-runnable-track {
  height: 3px; border-radius: var(--radius-pill);
  background: var(--track, var(--line-2));
}
.slider::-moz-range-track {
  height: 3px; border-radius: var(--radius-pill);
  background: var(--track, var(--line-2));
}
.slider::-webkit-slider-thumb {
  -webkit-appearance: none; appearance: none;
  width: .5rem; height: 1.25rem; border-radius: 2px;
  margin-top: calc((1.25rem - 3px) / -2);
  background: var(--txt); border: 0;
  transition: background var(--fast) var(--ease), box-shadow var(--fast) var(--ease);
}
.slider::-moz-range-thumb {
  width: .5rem; height: 1.25rem; border-radius: 2px;
  background: var(--txt); border: 0;
  transition: background var(--fast) var(--ease), box-shadow var(--fast) var(--ease);
}
.slider:hover::-webkit-slider-thumb  { background: #ffffff; }
.slider:hover::-moz-range-thumb      { background: #ffffff; }
/* The grip widens rather than growing: a thumb that scales moves its own
   edges, and its edges are what you were aiming with. */
.slider:active::-webkit-slider-thumb { background: #ffffff; box-shadow: 0 0 0 4px var(--accent-dim); }
.slider:active::-moz-range-thumb     { background: #ffffff; box-shadow: 0 0 0 4px var(--accent-dim); }
.slider:focus-visible { outline: none; }
.slider:focus-visible::-webkit-slider-thumb { box-shadow: 0 0 0 3px var(--accent); }
.slider:focus-visible::-moz-range-thumb     { box-shadow: 0 0 0 3px var(--accent); }

/* Captions above the track, slider across the full width beneath them.
   Side-by-side in a fixed 4.6rem column, "Yellow · Blue" wrapped onto two
   lines and pushed its own slider out of line with the one above it. A label
   over its control also gives the control the whole width, which is what a
   fine setting wants.

   Three columns so the caption row can be either form: one name on the left
   with a read-out opposite it, or a pair of poles pinned to the two ends of
   the track they describe. */
.slider-row {
  display: grid;
  grid-template-columns: auto 1fr auto;
  align-items: baseline;
  column-gap: var(--s-3); row-gap: var(--s-1);
}
.slider-row label,
.slider-pole {
  grid-row: 1;
  font-size: var(--t-micro); font-weight: 600; letter-spacing: .1em;
  text-transform: uppercase; color: var(--txt-3);
}
.slider-row label { grid-column: 1; }
.slider-row output {
  grid-column: 3; grid-row: 1;
  font-family: var(--mono); font-variant-numeric: tabular-nums;
  font-size: var(--t-tiny); text-align: right; color: var(--txt-3);
}
/* A pole sits over the end of the track it names, so "which way is red" is
   answered by looking rather than by reasoning about a hyphenated pair. */
.slider-pole.is-start { grid-column: 1; text-align: left; }
.slider-pole.is-end   { grid-column: 3; text-align: right; }
.slider-row .slider { grid-column: 1 / -1; grid-row: 2; }

/* Two sliders are two separate settings, and the gap is what says so — at the
   old spacing the second caption sat closer to the first slider than to its
   own. */
.slider-stack { display: flex; flex-direction: column; gap: var(--s-4); }

/* ---------- segmented control ---------- */
.seg {
  display: inline-flex; padding: var(--s-1); gap: var(--s-1);
  background: var(--bg-2); border: 1px solid var(--line); border-radius: var(--radius-sm);
}
.seg button {
  padding: var(--s-1) var(--s-3); border-radius: var(--radius-sm); font-size: var(--t-small);
  color: var(--txt-3); transition: all .14s var(--ease);
}
.seg button:hover { color: var(--txt-2); }
.seg button.is-on { background: var(--bg-3); color: var(--txt); box-shadow: 0 1px 3px rgba(0,0,0,.35); }

/* ---------- notes & callouts ---------- */
.note {
  display: flex; gap: var(--s-3); align-items: flex-start;
  padding: var(--s-3) .9rem;
  border-radius: var(--radius-sm);
  background: var(--bg-2); border: 1px solid var(--line);
  font-size: var(--t-small); color: var(--txt-2); line-height: 1.5;
}
.note svg { width: 1rem; height: 1rem; flex: 0 0 auto; margin-top: var(--s-1); color: var(--txt-3); }
.note-accent { border-color: color-mix(in srgb, var(--accent) 24%, var(--line)); background: var(--accent-dim); }
.note-accent svg { color: var(--accent); }

/* ---------- dialog ---------- */
.scrim {
  position: fixed; inset: 0; z-index: 90;
  background: rgba(4,5,6,.72); backdrop-filter: blur(6px);
  display: grid; place-items: center; padding: var(--s-6);
  animation: fade .2s var(--ease-out);
}
@keyframes fade { from { opacity: 0 } }
.dialog {
  width: min(34rem, 100%);
  background: var(--bg-1); border: 1px solid var(--line-2);
  border-radius: var(--radius-lg); box-shadow: var(--shadow-2);
  padding: var(--s-5);
  animation: pop .26s var(--ease-out);
}
@keyframes pop { from { opacity: 0; transform: translateY(14px) scale(.985) } }
/* ---------- the sheet dialog ----------
   A dialog whose content is long enough to need reading, rather than a
   question with two buttons. Head and foot are pinned and only the middle
   scrolls, so the title and the way out are always on screen — the old
   version scrolled its own heading away and left the only Close button at the
   bottom of everything you had not read yet. */
.dialog-sheet {
  width: min(40rem, 100%);
  max-height: min(86vh, 48rem);
  padding: 0;
  display: flex; flex-direction: column;
  overflow: hidden;                     /* so the head keeps the rounded top */
}
.sheet-head {
  flex: 0 0 auto;
  display: flex; align-items: center; justify-content: space-between; gap: var(--s-4);
  padding: var(--s-4) var(--s-4) var(--s-4) var(--s-5);
  border-bottom: 1px solid var(--line);
}
.sheet-body {
  flex: 1 1 auto; min-height: 0; overflow-y: auto;
  scrollbar-width: thin; scrollbar-color: var(--line-2) transparent;
}
.sheet-body::-webkit-scrollbar { width: 10px; }
.sheet-body::-webkit-scrollbar-track { background: transparent; }
.sheet-body::-webkit-scrollbar-thumb {
  background: var(--line-2); border-radius: var(--radius-pill);
  border: 3px solid transparent; background-clip: content-box;
}
.sheet-foot {
  flex: 0 0 auto;
  display: flex; align-items: center; gap: var(--s-3);
  padding: var(--s-4) var(--s-5);
  border-top: 1px solid var(--line);
}

/* One question per section, each with its own label and its own rule. Four
   paragraphs in a single column all looked equally (un)important; a reader
   who opened this to find out about their data had to read the other three
   to know they were not it. */
.sheet-sec {
  padding: var(--s-5);
  border-bottom: 1px solid var(--line);
}
.sheet-sec:last-child { border-bottom: 0; }
.sheet-sec-label {
  font-size: var(--t-micro); font-weight: 620; letter-spacing: .14em;
  text-transform: uppercase; color: var(--txt-4);
  margin-bottom: var(--s-3);
}
.sheet-sec p {
  font-size: var(--t-small); color: var(--txt-2); line-height: var(--lh-body);
  max-width: 64ch;
}
.sheet-sec p + p { margin-top: var(--s-3); }
/* The one section people open this for. It gets the accent rail rather than
   louder type: the answer is reassuring, and shouting it would not be. */
.sheet-sec.is-key {
  border-left: 2px solid var(--accent);
  background: color-mix(in srgb, var(--accent) 4%, transparent);
}
.sheet-sec.is-key .sheet-sec-label { color: var(--accent); }
.sheet-aside { color: var(--txt-3); }

/* A fact about this run, set as a value rather than buried in a sentence. */
.sheet-meta {
  padding: var(--s-4) var(--s-5);
  border-bottom: 1px solid var(--line);
  background: var(--bg-2);
}
.sheet-meta-row {
  display: flex; align-items: baseline; justify-content: space-between; gap: var(--s-4);
}
.sheet-meta-key { font-size: var(--t-tiny); color: var(--txt-3); }
.sheet-meta-val { font-size: var(--t-body); color: var(--txt); letter-spacing: .04em; }
.sheet-meta-note { font-size: var(--t-micro); color: var(--txt-4); margin-top: var(--s-1); }

.sheet-author .author-head { margin-bottom: var(--s-3); }
.sheet-author .author-links { margin-top: var(--s-4); }

/* wide enough to hold the task recap's animation without squashing it */
.dialog-wide { width: min(42rem, 100%); max-height: min(88vh, 46rem); overflow-y: auto; }
.dialog-wide .demo-stage { aspect-ratio: 16 / 9; }

/* ---------- toast ---------- */
.toast-host {
  position: fixed; bottom: 1.4rem; left: 50%; transform: translateX(-50%);
  z-index: 95; display: flex; flex-direction: column; gap: var(--s-2); align-items: center;
  pointer-events: none;
}
.toast {
  padding: var(--s-2) var(--s-4); border-radius: 99px;
  background: rgba(28,32,36,.96); border: 1px solid var(--line-2);
  box-shadow: var(--shadow-1); font-size: var(--t-small); color: var(--txt);
  animation: toastin .3s var(--ease-out);
}
@keyframes toastin { from { opacity: 0; transform: translateY(10px) } }
.toast.is-out { opacity: 0; transform: translateY(6px); transition: all .25s var(--ease); }

/* ---------- meters ---------- */
.meter { height: .42rem; border-radius: 99px; background: var(--bg-3); overflow: hidden; }
.meter-fill {
  height: 100%; border-radius: 99px;
  background: linear-gradient(90deg, var(--m-from, var(--accent-2)), var(--m-to, var(--accent)));
  transition: width .8s var(--ease-out);
}

/* ---------- table ---------- */
.tbl { width: 100%; border-collapse: collapse; font-size: var(--t-small); }
.tbl th {
  text-align: left; font-size: var(--t-micro); letter-spacing: .1em; text-transform: uppercase;
  color: var(--txt-3); font-weight: 620; padding: var(--s-2) var(--s-3);
  border-bottom: 1px solid var(--line-2);
}
.tbl td { padding: var(--s-2) var(--s-3); border-bottom: 1px solid var(--line); color: var(--txt-2); vertical-align: middle; }
.tbl tr:last-child td { border-bottom: none; }
.tbl td:first-child { color: var(--txt); }
.tbl .num { font-family: var(--mono); text-align: right; font-variant-numeric: tabular-nums; }

/* ---------- fullscreen-able frames ---------- */
.frame {
  background: var(--bg-1); border: 1px solid var(--line);
  border-radius: var(--radius); overflow: hidden;
  display: flex; flex-direction: column;
}
.frame-head {
  display: flex; align-items: center; gap: var(--s-2);
  padding: var(--s-2) var(--s-3); border-bottom: 1px solid var(--line);
  flex: 0 0 auto;
}
.frame-head .card-title { margin: 0; flex: 1 1 auto; min-width: 0; overflow: hidden;
  text-overflow: ellipsis; white-space: nowrap; }
.frame-body { flex: 1 1 auto; min-height: 0; position: relative; padding: var(--s-3); }
.frame-body canvas { width: 100%; height: 100%; display: block; }
.frame:fullscreen { border-radius: 0; padding: 2vh 2vw; background: var(--bg); }
.frame:fullscreen .frame-body { padding: var(--s-5); }

/* ---------- legend ---------- */
.legend { display: flex; flex-wrap: wrap; gap: .35rem var(--s-4); align-items: center; }
.legend-item {
  display: inline-flex; align-items: center; gap: var(--s-2);
  font-size: var(--t-tiny); color: var(--txt-2);
}
.legend-swatch {
  width: .72rem; height: .72rem; border-radius: 3px; flex: 0 0 auto;
  background: var(--legend-color, var(--txt-3));
  box-shadow: inset 0 0 0 1px rgba(255,255,255,.14);
}
.legend-swatch.is-line { height: 0; border-top: 2px solid var(--legend-color); border-radius: 0; box-shadow: none; width: .95rem; }
.legend-value { font-family: var(--mono); color: var(--txt); font-size: var(--t-tiny); }

/* ---------- skip / a11y ---------- */
.visually-hidden {
  position: absolute !important; width: 1px; height: 1px;
  padding: 0; margin: -1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}
