/* ==========================================================================
   Virtual Characterization Lab — design system

   Two themes, and the data panels follow the theme with the chrome.

   The plots used to stay on white in both, on the argument that a spectrum
   is the thing that gets printed and should not change color when the room
   lights do. In practice a white panel in a dark interface is a lamp: the eye
   adapts to the chrome, the panel glares, and the faint features a student is
   looking for are the first thing lost. So `--paper*`, the plot furniture and
   the trace palette are stepped for each theme. The argument about printing
   is kept where it applies: a plot exported as a picture is drawn with the
   light palette whatever the page shows (`.plot-print`, below).

   Every color is a token on :root, and the light palette redefines the same
   tokens under [data-theme="light"]. Nothing may hard-code a chrome color, or
   it will be unreadable in one theme or the other.
   ========================================================================== */

:root {
  /* chrome */
  --bg:            #17191b;
  --bg-raised:     #212325;
  --bg-inset:      #121315;
  --border:        #3f4443;
  --border-strong: #646a6e;

  /* data surfaces. A shade below the card they sit on, so a plot reads as a
     screen set into the panel. The ink clears 13:1 on it and the dim ink
     7:1. The light values are in the `.plot-print` rule after the light
     palette. */
  --paper:         #131416;
  --paper-dim:     #1c1e20;
  --paper-border:  #3f4443;
  --paper-ink:     #eff1f2;
  --paper-ink-dim: #a7b1b7;

  /* text on chrome.
     `--ink-faint` is set by the contrast requirement and not by taste: it is
     the palest ink allowed to carry words, and it has to clear 4.5:1 against
     the LIGHTEST surface it is ever drawn on, which is `--bg-raised` and not
     the page. At #6b7887 it was 3.8:1 there. */
  --ink:           #eff1f2;
  --ink-dim:       #bfc6cb;
  --ink-faint:     #8f979c;

  /* accents. Ohio State scarlet, #ba0c2f, is 2.4:1 on these grays: enough
     to fill a button under white type (6.6:1), which is what `--accent-dim`
     does, and too dark to draw a line or a word in. The accent for text and
     strokes on the dark theme is the same hue lifted to 5:1 on `--bg-raised`.
     The neutrals are the Buckeye UX gray shades. */
  --accent:        #ee6377;
  --accent-dim:    #ba0c2f;
  --accent-glow:   rgba(238, 99, 119, 0.14);
  --warn:          #e8a33d;
  --danger:        #ff8a50;
  --ok:            #5cc98a;
  --info:          #6aa9e0;

  /* Okabe–Ito, color-blind safe — the trace palette, stepped for the dark
     plot surface. Same eight hues in the same order as the light steps; the
     blue, the pink and the purple are lifted, because Okabe and Ito chose
     their values for white paper and the blue is 3.5:1 on this one. Every
     step clears 4.5:1 on `--paper`; adjacent slots, and any two of the first
     four, are ΔE ≥ 9 (OKLab ×100) apart under simulated protanopia and
     deuteranopia (Machado, Oliveira & Fernandes 2009). The simulators name
     their colors by the canonical Okabe–Ito hex, and LinePlot maps each name
     onto the step here (`inkFor`). */
  --t1: #419cdf;  --t2: #d55e00;  --t3: #099069;  --t4: #c976a4;
  --t5: #e69f00;  --t6: #56b4e9;  --t7: #9271b7;  --t8: #a3acb6;
  /* The two neutrals a figure draws data in: "observed" ink, and the grey of
     a background curve or a first cycle. */
  --plot-trace-ink:   #eff1f2;
  --plot-trace-muted: #868e92;

  /* Canvas plot furniture. Read by LinePlot through getComputedStyle, so the
     spectra and the panel they sit in cannot drift apart. */
  --plot-grid:          #26292c;
  --plot-axis:          #646a6e;
  --plot-tick:          rgba(239, 241, 242, 0.4);
  --plot-label-bg:      rgba(19, 20, 22, 0.88);
  --plot-cursor:        rgba(239, 241, 242, 0.34);
  --plot-marker:        rgba(167, 177, 183, 0.55);
  --plot-select-fill:   rgba(106, 169, 224, 0.18);
  /* A region a FIGURE is pointing at, as opposed to one the reader has just
     dragged out. Warm and very faint, so it cannot be mistaken for a live
     selection and cannot compete with the trace that crosses it. */
  --plot-band:          rgba(222, 190, 120, 0.12);
  --plot-select-stroke: rgba(122, 184, 236, 0.85);
  /* The plot toolbar's buttons: hover, pressed, and a copy that worked. */
  --plot-btn-hover:     #2a2d30;
  --plot-btn-on-bg:     #1c3550;
  --plot-btn-on-border: #3d6b96;
  --plot-btn-on-ink:    #cfe3f7;
  --plot-btn-ok-bg:     #173a2a;
  --plot-btn-ok-border: #2f7a55;
  --plot-btn-ok-ink:    #bfe8d1;
  /* Marks laid over a micrograph: the analysis spot or box, and the halo
     that keeps it visible on bright and dark regions of the image alike. */
  --probe-ink:  #ee6377;
  --probe-fill: rgba(238, 99, 119, 0.16);
  --probe-halo: rgba(0, 0, 0, 0.7);

  /* Alert text. Separate from the accent it is derived from, because a tint
     that reads on a dark card is washed out on a light one. */
  --warn-ink:   #f1c07a;
  --danger-ink: #ffb08a;
  --info-ink:   #a3cbea;
  --ok-ink:     #96dcb3;
  --on-accent:  #ffffff;
  --brief-grad: #1c1e20;
  --scrim:      rgba(8, 9, 10, 0.72);

  /* The focus ring, and the pale companion drawn under it so that the ring is
     visible against a dark control as well as against the page behind it.
     Both clear 3:1 on every surface in this palette. */
  --focus:      #ee6377;
  --focus-halo: rgba(18, 19, 21, 0.9);

  /* THE HOVER FILL OF A PRIMARY BUTTON IS BOUNDED BY ITS OWN LABEL. On the
     dark theme the hover used to jump to a bright accent, which put `--on-accent`
     at 2:1 on top of it — the button became less readable the moment the
     pointer was on it, which is the opposite of what the state is for. The
     fill may only move as far as the label still allows, so the visible part
     of the hover is carried by the border instead. */
  --accent-hover: #8c0a24;

  /* The InfoTip: its ring (3:1 on the card, § 1.4.11), the faint tint inside
     it, and the bubble, a step lighter than the card so that it reads as
     lying on top of it. */
  --tip-ring:   #7c8388;
  --tip-bg:     #2a2d30;

  --radius:    7px;
  --radius-sm: 4px;
  --shadow:    0 4px 16px rgba(0, 0, 0, 0.35);
  --sans: ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
  --mono: ui-monospace, "SF Mono", Menlo, Consolas, "Liberation Mono", monospace;

  --sidebar-w: 286px;
  --header-h:  56px;

  color-scheme: dark;
}

/* --------------------------------------------------------- light palette -- */

:root[data-theme="light"] {
  /* chrome */
  --bg:            #eff1f2;
  --bg-raised:     #ffffff;
  --bg-inset:      #f6f7f8;
  --border:        #dfe3e5;
  --border-strong: #a7b1b7;

  /* data surfaces: in the rule below, which the exported figure shares */

  /* Marks over a micrograph: the light theme's scarlet, on a white halo. */
  --probe-ink:  #ba0c2f;
  --probe-fill: rgba(186, 12, 47, 0.14);
  --probe-halo: rgba(255, 255, 255, 0.85);

  /* text on chrome. See the note in the dark palette: the binding surface
     here is the DARKEST one, `--bg-inset`, where #79848f was 3.1:1. */
  --ink:           #212325;
  --ink-dim:       #3f4443;
  --ink-faint:     #5d6367;

  /* accents. The Ohio State palette (Buckeye UX): Scarlet #ba0c2f is the
     accent, 6.6:1 on white and 6.2:1 on the darkest light surface,
     `--bg-inset`; the neutrals are its gray tints and shades. Each accent
     passes contrast on that DARKEST light surface — a white card was the old
     test and it let a four-to-one color through onto every inset panel and
     chip. Errors are vermilion rather than red, so that nothing reads as an
     error for being in the university's color.

     `--accent-dim` is the exception: it is a BACKGROUND, under `--on-accent`,
     and white on scarlet is 6.6:1. */
  --accent:        #ba0c2f;
  --accent-dim:    #ba0c2f;
  --accent-glow:   rgba(186, 12, 47, 0.08);
  --warn:          #8e5c0f;
  --danger:        #b33c00;
  --ok:            #187542;
  --info:          #1a6ba6;

  --warn-ink:      #7a4e08;
  --danger-ink:    #8f3000;
  --info-ink:      #14568a;
  --ok-ink:        #14663a;
  --on-accent:     #ffffff;
  --brief-grad:    #f6f7f8;
  --scrim:         rgba(33, 35, 37, 0.45);

  --focus:         #ba0c2f;
  --focus-halo:    rgba(255, 255, 255, 0.95);

  --accent-hover:  #8c0a24;

  --tip-ring:      #858e94;
  --tip-bg:        #ffffff;

  --shadow:        0 4px 16px rgba(33, 35, 37, 0.14);

  color-scheme: light;
}

/* The light data surfaces, plot furniture and trace palette.

   Also carried by `.plot-print`, a class LinePlot reads its palette from when
   it exports a PNG, so an exported figure is on white from either theme.

   The trace steps are Okabe–Ito's own except where one fails 3:1 on white:
   the orange (2.2:1) and the sky blue (2.3:1) are darkened in the same hue,
   and the green and the purple a little, which also opens the green–pink pair
   from ΔE 7.6 to 9.3 under simulated deuteranopia. */
:root[data-theme="light"], .plot-print {
  --paper:         #ffffff;
  --paper-dim:     #f6f7f8;
  --paper-border:  #dfe3e5;
  --paper-ink:     #212325;
  --paper-ink-dim: #5d6367;

  --t1: #0072b2;  --t2: #d55e00;  --t3: #099069;  --t4: #cc79a7;
  --t5: #c48702;  --t6: #3c9cd0;  --t7: #8362a7;  --t8: #444444;
  --plot-trace-ink:   #212325;
  --plot-trace-muted: #868e92;

  --plot-grid:          #eff1f2;
  --plot-axis:          #a7b1b7;
  --plot-tick:          rgba(33, 35, 37, 0.35);
  --plot-label-bg:      rgba(255, 255, 255, 0.88);
  --plot-cursor:        rgba(33, 35, 37, 0.28);
  --plot-marker:        rgba(100, 106, 110, 0.5);
  --plot-select-fill:   rgba(69, 160, 200, 0.14);
  --plot-band:          rgba(190, 160, 90, 0.13);
  --plot-select-stroke: rgba(40, 120, 170, 0.6);
  --plot-btn-hover:     #eff1f2;
  --plot-btn-on-bg:     #dbe8f5;
  --plot-btn-on-border: #8fb4d6;
  --plot-btn-on-ink:    #12456e;
  --plot-btn-ok-bg:     #d9efe4;
  --plot-btn-ok-border: #79b79a;
  --plot-btn-ok-ink:    #1c5c3f;
}

* { box-sizing: border-box; }

html, body { height: 100%; }

body {
  margin: 0;
  background: var(--bg);
  color: var(--ink);
  font: 14px/1.5 var(--sans);
  -webkit-font-smoothing: antialiased;
  overflow: hidden;
}

button, input, select, textarea { font: inherit; color: inherit; }

/* ------------------------------------------------------- focus and reading --

   ONE FOCUS RING, ON EVERYTHING, AND IT IS NOT NEGOTIABLE.

   The form controls used to set `outline: none` and say "focused" with a
   border tint and a three-pixel glow at twelve percent alpha. On the light
   theme that is a ring of about 1.1:1 against the field beside it — a change
   you can see only if you already know where to look, which is precisely the
   person it is not for. Buttons had nothing of their own at all and fell back
   on whatever the browser drew, which differs between browsers and disappears
   entirely against a dark chrome in some of them.

   `:focus-visible` rather than `:focus`, so a mouse click on a button does not
   leave a ring behind it; the ring appears for the keyboard and for anything
   else the browser decides is a navigation. The outer halo is what makes one
   colour work on every surface: the ring is drawn against the halo rather than
   against whatever the control happens to be sitting on.

   WCAG 2.2 § 2.4.7 and § 1.4.11. */

:where(a[href], button, input, select, textarea, summary, [tabindex]):focus-visible {
  outline: 2px solid var(--focus);
  outline-offset: 2px;
  box-shadow: 0 0 0 4px var(--focus-halo);
  border-radius: var(--radius-sm);
}

/* FOCUS MUST NOT END UP UNDER THE HEADER. The page scrolls inside its own
   region with a sticky header over the top of it, so tabbing to a control just
   below the fold scrolls it to the very top of the scroll port — which is
   behind the header. WCAG 2.2 § 2.4.11 asks that a focused control not be
   entirely hidden, and this asks the browser for a gap on each side when it
   scrolls something into view. */
:where(a[href], button, input, select, textarea, [tabindex]) {
  scroll-margin-top: calc(var(--header-h) + 12px);
  scroll-margin-bottom: 16px;
}
/* And not under the measurement page's quote bar, which stays at the foot of
   the panel: a control tabbed to near the bottom is brought up above it. */
.main:has(.measure-bar) { scroll-padding-bottom: 84px; }
@media (max-width: 820px) {
  .main:has(.measure-bar) { scroll-padding-bottom: 116px; }
}

/* Announced, never drawn. Used by the live regions and by the skip link. */
.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip: rect(0 0 0 0);
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

/* THE WAY PAST THE RACK. The instrument rack is twenty-odd buttons and it
   comes before the panel they act on in the source, so reaching the results
   from the keyboard meant tabbing through the whole shelf every time the page
   was rebuilt. WCAG 2.2 § 2.4.1. */
.skip-link {
  position: absolute;
  left: 8px;
  top: 8px;
  z-index: 100;
  padding: 9px 14px;
  background: var(--bg-raised);
  color: var(--ink);
  border: 1px solid var(--accent);
  border-radius: var(--radius-sm);
  font-size: 13px;
  font-weight: 600;
  text-decoration: none;
  transform: translateY(-160%);
  transition: transform 0.12s;
}
.skip-link:focus { transform: translateY(0); }

/* ------------------------------------------------------- the InfoTip -- */

/* ONE "?" FOR THE WHOLE APP, built by `infoTip` in src/ui/dom.js.

   The mark is 16 pixels, a thin muted ring on nothing — no fill, so it does
   not sit on the page as a grey disc — so that a form with a dozen of them
   still reads as a form. It sits right after the words
   it explains and is centred on their capitals: the wrapper has nothing in
   its own flow, so its baseline is its bottom edge, and raising that edge by
   0.36 em less half its height puts its middle at the middle of a capital
   letter whatever the size of the label. The press target is the 24-pixel
   button laid over it, which is what WCAG 2.2 § 2.5.8 asks for; only the
   drawing is small. */
.tip {
  position: relative;
  display: inline-block;
  flex: 0 0 auto;
  width: 16px;
  height: 16px;
  margin-left: 5px;
  vertical-align: calc(0.36em - 8px);
}
/* Inside a flex row (a card title) vertical-align does nothing, and centring
   on the line is the same thing to within a pixel. */
.card-title > .tip, .fold-heading > .tip { align-self: center; vertical-align: 0; }

.tip-btn {
  position: absolute;
  top: -4px;
  left: -4px;
  z-index: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 24px;
  height: 24px;
  padding: 0;
  margin: 0;
  background: transparent;
  border: 0;
  border-radius: 50%;
  color: var(--ink-faint);
  font: 700 10px/1 var(--sans);
  cursor: pointer;
}
/* The drawn mark, under the glyph. */
.tip-btn::before {
  content: '';
  position: absolute;
  inset: 4px;
  z-index: -1;
  border: 1px solid var(--tip-ring);
  border-radius: 50%;
  background: transparent;
  transition: background 0.12s, border-color 0.12s, box-shadow 0.12s;
}
.tip-btn:hover,
.tip.open .tip-btn { color: var(--accent); }
.tip-btn:hover::before,
.tip.open .tip-btn::before {
  border-color: var(--accent);
  background: var(--accent-glow);
}
/* The focus ring goes round the mark rather than round the invisible target,
   so that it is a circle on the thing a sighted keyboard user is looking at. */
.tip-btn:focus-visible { outline: none; box-shadow: none; }
.tip-btn:focus-visible::before {
  border-color: var(--accent);
  box-shadow: 0 0 0 2px var(--bg-raised), 0 0 0 4px var(--focus), 0 0 0 6px var(--focus-halo);
}
@media (prefers-reduced-motion: reduce) {
  .tip-btn::before { transition: none; }
}

/* FIXED, AND PLACED FROM SCRIPT. Every card sets `overflow: hidden`, so an
   absolutely positioned bubble inside one was cut off at the card's edge —
   and it is the tip on the LAST control of a panel, the one nearest that
   edge, that lost the most of itself. Fixed positioning escapes the clip; the
   popover attribute, where the browser has it, escapes everything else too.
   The `inset: auto` and `margin: 0` are there to undo the popover UA sheet,
   which would otherwise centre it in the window.

   About 32 characters to the line, which is a sentence or two a line at a
   time without the eye having to travel; in the ink colour rather than the
   dimmed one, because the bubble is the thing being read. The small arrow
   points at the mark it came from (`--tip-arrow-x`, set by the script). */
.tip-body {
  position: fixed;
  inset: auto;
  margin: 0;
  top: 0;
  left: 0;
  z-index: 80;
  width: max-content;
  max-width: min(32ch, calc(100vw - 16px));
  padding: 8px 11px;
  background: var(--tip-bg);
  color: var(--ink);
  border: 1px solid var(--border-strong);
  border-radius: 6px;
  box-shadow: var(--shadow);
  font: 400 12.5px/1.5 var(--sans);
  text-align: left;
  text-transform: none;
  letter-spacing: normal;
  white-space: normal;
  overflow: visible;
  /* Selectable, because the whole point of a disclosure over a `title` is
     that the text is real text somebody can copy into their notes. */
  user-select: text;
}
.tip-body::before {
  content: '';
  position: absolute;
  top: -5px;
  left: calc(var(--tip-arrow-x, 14px) - 4.5px);
  width: 8px;
  height: 8px;
  background: var(--tip-bg);
  border-left: 1px solid var(--border-strong);
  border-top: 1px solid var(--border-strong);
  transform: rotate(45deg);
}
.tip-body.above::before {
  top: auto;
  bottom: -5px;
  transform: rotate(225deg);
}
/* A popover is `display: none` until it is shown, which is what hides it
   where the attribute is supported; `[hidden]` does the same where it is
   not. Neither needs a rule — this only stops the UA sheet's border. */
.tip-body:popover-open { display: block; }
.tip-body strong { color: var(--ink); font-weight: 600; }
.tip-body p { margin: 0 0 7px; }
.tip-body p:last-child { margin-bottom: 0; }

/* The name half of a label, so the bubble hangs off the words rather than
   off the far end of a space-between row. */
.field-label-name { min-width: 0; }


::-webkit-scrollbar { width: 10px; height: 10px; }
::-webkit-scrollbar-track { background: transparent; }
::-webkit-scrollbar-thumb { background: var(--border); border-radius: 5px; }
::-webkit-scrollbar-thumb:hover { background: var(--border-strong); }

/* ------------------------------------------------------------- app frame -- */

#app {
  display: grid;
  grid-template-columns: var(--sidebar-w) minmax(0, 1fr);
  /* The header sizes itself. Under the narrow layout it is two rows deep, and
     a fixed track would either crop the second one or leave a gap above the
     first. `--header-h` survives as its minimum. */
  grid-template-rows: auto minmax(0, 1fr);
  grid-template-areas: "header header" "sidebar main";
  height: 100vh;
  /* Phone browsers put their address bar inside 100vh, which cuts the footer
     of the rack off the bottom of the screen. */
  height: 100dvh;
  /* Nothing in the chrome may push the page sideways: a header that outgrows
     the window scrolls inside its own rail instead, and the closed drawer sits
     off to the left where it belongs. */
  overflow: hidden;
}

.app-header {
  grid-area: header;
  display: flex;
  align-items: center;
  gap: 18px;
  padding: 0 18px;
  min-height: var(--header-h);
  min-width: 0;
  background: var(--bg-raised);
  border-bottom: 1px solid var(--border);
  z-index: 20;
}

.brand {
  display: flex;
  align-items: baseline;
  gap: 9px;
  flex: 0 0 auto;
  min-width: 0;
}
.brand-mark {
  font-weight: 650;
  letter-spacing: -0.01em;
  font-size: 15px;
  white-space: nowrap;
}
.brand-mark span,
.brand-abbr span { color: var(--accent); }
/* The wordmark for a header that has three characters to spare rather than
   twenty-seven. Both are in the DOM; the media queries swap the pair. */
.brand-abbr {
  display: none;
  font-weight: 650;
  font-size: 15px;
  letter-spacing: 0.03em;
}
.brand-sub {
  font-size: 11px;
  color: var(--ink-faint);
  text-transform: uppercase;
  letter-spacing: 0.09em;
  white-space: nowrap;
}

/* Virtual lab / Instrument explorer. Two activities, visibly separate. */
.section-tabs {
  display: flex;
  gap: 2px;
  padding: 2px;
  background: var(--bg-inset);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  flex: 0 0 auto;
  min-width: 0;
}
/* Long label and short. See `bothLengths` in ui/app.js. */
.short-form { display: none; }
.section-tab {
  padding: 5px 12px;
  background: transparent;
  border: none;
  border-radius: 3px;
  color: var(--ink-dim);
  font-size: 12.5px;
  font-weight: 500;
  cursor: pointer;
  white-space: nowrap;
  transition: background 0.13s, color 0.13s;
}
.section-tab:hover { color: var(--ink); }
.section-tab.active {
  background: var(--accent-dim);
  color: var(--on-accent);
  font-weight: 600;
}

.explorer-note {
  font-size: 11.5px;
  color: var(--ink-faint);
  padding: 6px 11px;
  background: var(--bg-inset);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  white-space: nowrap;
}

/* Not a label: this is the way back to the brief from anywhere inside a
   problem, and it is the only control in the header that names a place. A
   button gets no pointer cursor and no hover state for free, and without them
   it read as a status badge and went unclicked. */
.case-chip {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 5px 11px;
  background: var(--bg-inset);
  border: 1px solid var(--border);
  border-radius: 100px;
  font-size: 12.5px;
  color: inherit;
  cursor: pointer;
  flex: 0 1 auto;
  /* A floor as well as a ceiling. Left free to shrink it was the first thing
     the header squeezed, and a sixty-pixel chip with the title ellipsised away
     to nothing is a control that no longer says where you are. The floor is the
     label itself: a fixed one left a short label like "EL-4" stranded at the
     left of an empty pill. Past this the rail scrolls instead. */
  min-width: max-content;
  max-width: 22rem;
  transition: background 0.12s, border-color 0.12s;
}
.case-chip:hover {
  background: var(--bg);
  border-color: var(--accent-dim);
}
.case-chip b {
  font-weight: 600;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.case-chip .dot {
  flex: 0 0 auto;
  width: 7px; height: 7px; border-radius: 50%;
  background: var(--accent);
  box-shadow: 0 0 0 3px var(--accent-glow);
}

/* Everything the header says about the problem rather than about the app.
   It absorbs the slack, so the account and theme controls sit against the
   right edge whether or not there are gauges to show — and it is where the
   overflow goes, because those two must never be the things that get cut off.
   Under the narrow layout it becomes a row of its own. */
.header-rail {
  flex: 1 1 0;
  min-width: 0;
  display: flex;
  align-items: center;
  gap: 12px;
  /* Room for a focus ring on the chip, which a clipped scroll box would eat. */
  padding: 3px 0;
  overflow-x: auto;
  overflow-y: hidden;
  /* A scrollbar inside a 56 px header is worse than the scroll it reports.
     What says there is more is the gauge half off the edge. */
  scrollbar-width: none;
}
.header-rail::-webkit-scrollbar { display: none; }
.header-rail > .hud { margin-left: auto; }
/* The rail is a row of its own only when it has something in it, so an empty
   one must not reserve height. */
.header-rail:empty { display: none; }

.theme-toggle {
  flex: 0 0 auto;
  font-size: 15px;
  font-weight: 600;
  line-height: 1;
  padding: 5px 9px;
  color: var(--ink-dim);
}
.theme-toggle:hover { color: var(--ink); }

/* ------------------------------------------------------------ budget HUD -- */

.hud {
  display: flex;
  gap: 8px;
  align-items: center;
  flex: 0 0 auto;
}

.gauge {
  min-width: 116px;
  padding: 5px 10px 6px;
  background: var(--bg-inset);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  flex: 0 0 auto;
}

/* Four budgets, a wordmark, three tabs, a case chip and two controls come to
   about 1540 px laid out at full size, so below that the header sheds
   decoration, one step at a time, in order of how little each thing matters.
   Every step is sized to buy back what the next hundred pixels of narrowing
   costs, so that the four gauges and a legible problem name both survive all
   the way down to the narrow layout. Measured against the longest problem
   title in the set, which is what sets the chip's appetite. */
@media (max-width: 1540px) {
  .app-header { gap: 12px; padding: 0 12px; }
  .header-rail { gap: 10px; }
  .gauge { min-width: 100px; padding: 5px 8px 6px; }
}
@media (max-width: 1420px) {
  /* "inorganic methods" is the one thing in the header that is purely a
     flourish, so it is the first to go. */
  .brand-sub { display: none; }
  .gauge { min-width: 92px; }
  .gauge-label { letter-spacing: 0.05em; }
}
@media (max-width: 1300px) {
  /* "Instrument explorer" becomes "Explorer" and "Instrument time" becomes
     "Inst. time". The signed-in address gives up its place to the dot that
     says whether the work is safe, which is the part that is glanced at. */
  .long-form { display: none; }
  .short-form { display: inline; }
  .account-name { display: none; }
  .account-chip { max-width: none; }
  .gauge { min-width: 84px; }
}
/* The abbreviated wordmark buys back about 170 px, which is most of what the
   two secondary gauges need — so it goes before them, and they survive
   another two hundred pixels of narrowing. */
@media (max-width: 1160px) {
  .brand-mark { display: none; }
  .brand-abbr { display: block; }
  .case-chip .chip-word { display: none; }
  .case-chip { min-width: max-content; max-width: 15rem; }
  .gauge { min-width: 78px; }
}
/* Sample and consultations are the two budgets a student can carry in their
   head, so they are the two a single-row header gives up last of all. The
   narrow layout below keeps all four, because there the rail is a row of its
   own and scrolls sideways instead. */
@media (min-width: 821px) and (max-width: 980px) {
  .gauge.secondary { display: none; }
}
.gauge-top {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 8px;
  margin-bottom: 4px;
}
.gauge-label {
  font-size: 9.5px;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--ink-faint);
}
.gauge-value {
  font: 600 12.5px/1 var(--mono);
  font-variant-numeric: tabular-nums;
}
.gauge-bar {
  height: 3px;
  background: var(--border);
  border-radius: 2px;
  overflow: hidden;
}
.gauge-fill {
  height: 100%;
  background: var(--accent);
  border-radius: 2px;
  transition: width 0.35s cubic-bezier(0.2, 0.7, 0.3, 1), background 0.3s;
}
.gauge.low  .gauge-fill { background: var(--warn); }
.gauge.crit .gauge-fill { background: var(--danger); }
.gauge.low  .gauge-value { color: var(--warn); }
.gauge.crit .gauge-value { color: var(--danger); }

/* --------------------------------------------------------------- sidebar -- */

.sidebar {
  grid-area: sidebar;
  display: flex;
  flex-direction: column;
  background: var(--bg-raised);
  border-right: 1px solid var(--border);
  min-height: 0;
}

/* The sidebar is a fixed-height flex column that has to hold three lists of
   unpredictable length plus a footer. Every section may therefore shrink and
   scroll internally, and `min-height: 0` is what makes that possible — without
   it a flex child refuses to go below its content height, which pushes the
   footer off the bottom of the column and clips whatever is above it. */
.sidebar-section {
  display: flex;
  flex-direction: column;
  min-height: 0;
  flex: 1 1 auto;
}
/* The bench is usually short, so it takes only what it needs — but it is
   capped, and it keeps enough room to show a couple of specimens even when the
   window is small. */
.sidebar-section.bench {
  flex: 0 1 auto;
  max-height: 34%;
  min-height: 96px;
}
/* The bench list sizes to its contents rather than to the space available.
   An inner `flex: 1 1 0` makes a scroll region contribute nothing to its
   parent's intrinsic height, so the section collapsed to its min-height and
   showed one clipped specimen no matter how much room there was. Growing to
   content and letting `max-height` do the capping is the behavior that was
   wanted: short lists show in full, long ones scroll. */
.sidebar-section.bench .sidebar-scroll { flex: 0 1 auto; }
/* The instrument rack is the longest list and gets the largest share. */
.sidebar-section.instruments { flex: 2 1 0; min-height: 110px; }
.sidebar-section.grow { flex: 1 1 0; min-height: 84px; }
/* A folded section is a header and nothing else, and it must not keep
   reserving the min-height its expanded form asked for. `!important` earns its
   place here: it has to beat all three of the specific rules above. */
.sidebar-section.collapsed {
  flex: 0 0 auto !important;
  min-height: 0 !important;
  max-height: none !important;
}
.sidebar-section + .sidebar-section { border-top: 1px solid var(--border); }

.sidebar-title {
  display: flex;
  align-items: center;
  gap: 7px;
  width: 100%;
  padding: 10px 13px 7px;
  font-size: 10px;
  font-family: inherit;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.11em;
  color: var(--ink-faint);
  background: transparent;
  border: 0;
  text-align: left;
  cursor: pointer;
  flex-shrink: 0;
  transition: color 0.12s;
}
.sidebar-title:hover { color: var(--ink-dim); }
.sidebar-title .fold-label { flex: 1; min-width: 0; }
.sidebar-title .count {
  font: 500 10px var(--mono);
  color: var(--ink-faint);
  background: var(--bg-inset);
  padding: 1px 6px;
  border-radius: 100px;
  letter-spacing: 0;
}

/* One caret, rotated. Two glyphs would drift apart in some fonts. */
.fold-caret {
  display: inline-block;
  width: 9px;
  font-size: 9px;
  line-height: 1;
  color: var(--ink-faint);
  transition: transform 0.14s ease;
  transform-origin: 50% 45%;
}
.fold-caret.folded { transform: rotate(-90deg); }
@media (prefers-reduced-motion: reduce) {
  .fold-caret { transition: none; }
}

.sidebar-scroll {
  overflow-y: auto;
  padding: 0 8px 10px;
  /* flex: 1 1 0 plus min-height: 0 is what lets this scroll instead of forcing
     its parent taller than the column. */
  flex: 1 1 0;
  min-height: 0;
}

.instrument-group { margin-bottom: 3px; }
.instrument-group.collapsed { margin-bottom: 0; }
.instrument-group-label {
  display: flex;
  align-items: center;
  gap: 6px;
  width: 100%;
  padding: 6px 6px 4px;
  font-size: 10.5px;
  font-family: inherit;
  font-weight: 600;
  color: var(--ink-faint);
  letter-spacing: 0.04em;
  background: transparent;
  border: 0;
  text-align: left;
  cursor: pointer;
  border-radius: var(--radius-sm);
  transition: color 0.12s, background 0.12s;
}
.instrument-group-label:hover { color: var(--ink); background: var(--bg-inset); }
.instrument-group-label .group-count {
  margin-left: auto;
  font: 500 9.5px var(--mono);
  color: var(--ink-faint);
  opacity: 0.7;
}

.inst {
  display: block;
  width: 100%;
  text-align: left;
  padding: 7px 9px;
  margin-bottom: 2px;
  background: transparent;
  border: 1px solid transparent;
  border-radius: var(--radius-sm);
  cursor: pointer;
  transition: background 0.12s, border-color 0.12s;
}
.inst:hover { background: var(--bg-inset); }
.inst.active {
  background: var(--accent-glow);
  border-color: var(--accent-dim);
}
.inst-name {
  font-size: 13px;
  font-weight: 500;
  display: flex;
  align-items: center;
  gap: 6px;
}
.inst-short { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* How many times this instrument has already been used. The number is the
   useful part; a word for it would not fit and does not add anything. */
.inst-runs {
  flex-shrink: 0;
  font: 600 9.5px var(--mono);
  color: var(--accent);
  background: var(--accent-glow);
  padding: 1px 5px;
  border-radius: 100px;
}
/* Consumes sample. A dot, because the row already carries a price and a time
   and a third piece of prose was the reason the panel felt crowded. */
.inst-dot {
  flex-shrink: 0;
  margin-left: auto;
  width: 5px;
  height: 5px;
  border-radius: 50%;
  background: var(--danger);
  opacity: 0.65;
}
.inst-meta {
  font-size: 10.5px;
  color: var(--ink-faint);
  margin-top: 1px;
  font-variant-numeric: tabular-nums;
}
.inst.unavailable { opacity: 0.42; }
.inst-tag {
  font-size: 9px;
  padding: 1px 5px;
  border-radius: 3px;
  background: var(--border);
  color: var(--ink-dim);
  text-transform: uppercase;
  letter-spacing: 0.05em;
}
.inst-tag.destructive { background: rgba(255, 138, 80, 0.16); color: var(--danger); }

/* notebook entries */
.run {
  display: flex;
  align-items: center;
  gap: 8px;
  width: 100%;
  text-align: left;
  padding: 6px 9px;
  margin-bottom: 2px;
  background: transparent;
  border: 1px solid transparent;
  border-radius: var(--radius-sm);
  cursor: pointer;
}
.run:hover { background: var(--bg-inset); }
.run.active { background: var(--accent-glow); border-color: var(--accent-dim); }
.run-idx {
  font: 600 10px var(--mono);
  color: var(--ink-faint);
  min-width: 20px;
}
.run-body { min-width: 0; flex: 1; }
/* Both of these are spans, and both need to be blocks: inline they run
   together, so a bench entry read "as received178 mg" with no break between
   the label and the amount. */
.run-name {
  display: block;
  font-size: 12.5px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.run-sub {
  display: block;
  font-size: 10px;
  color: var(--ink-faint);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.run.exhausted { opacity: 0.42; }
.run.exhausted .run-name { text-decoration: line-through; }
/* A preparation that destroyed sample and returned nothing. Marked, not
   hidden: it is a result, and it is often the most informative one on the
   problem. */
.run-failed .run-idx { color: var(--warn); }
.run-failed .run-sub { color: var(--warn); }

.sidebar-empty {
  padding: 14px;
  font-size: 12px;
  color: var(--ink-faint);
  line-height: 1.5;
}

.sidebar-foot {
  /* Sticks to the bottom even when every section above is folded away. */
  margin-top: auto;
  padding: 9px;
  border-top: 1px solid var(--border);
  display: flex;
  gap: 6px;
  /* Never shrinks and never scrolls away: this is how a student submits. */
  flex: 0 0 auto;
}

/* ------------------------------------------------------------------ main -- */

.main {
  grid-area: main;
  overflow-y: auto;
  min-height: 0;
  --main-pad-bottom: 40px;
  padding: 18px 22px var(--main-pad-bottom);
  /* So that panels inside can size themselves against the space they actually
     have. The window is a poor proxy for it: the rack takes 286 px of it on a
     problem and none of it on the chooser, and a viewport query cannot tell
     those two apart. */
  container: panel / inline-size;
  /* SCROLL ANCHORING OFF, and this is the fix for a real bug rather than a
     preference. The browser watches for content changing size ABOVE the
     viewport and scrolls by the same amount to keep what you are looking at
     still. Which is exactly right for an image loading into an article, and
     exactly wrong here: this app re-renders a whole panel and then restores
     the offset itself, and the panels build their canvases a tick later. So
     the sequence was — panel rebuilt short, offset restored correctly, six
     hundred pixels of micrograph inserted above the fold, and the browser
     helpfully added six hundred pixels to the scroll to compensate for a
     shift that the restore had already accounted for. Adjusting the
     magnification dropped the reader half a page down the result every time.
     There is one authority on where this panel is scrolled to and it is
     `ui/scroll.js`. */
  overflow-anchor: none;
}
.main-inner { max-width: 1180px; margin: 0 auto; }

.page-head { margin-bottom: 16px; }
.page-title {
  font-size: 20px;
  font-weight: 620;
  letter-spacing: -0.015em;
  margin: 0 0 3px;
}
/* A lead paragraph runs the width of its card, like every other block of text
   in the panel. Capped at 74ch it stopped at about 45 % of a wide card and left
   a column of white beside the first thing on the page. */
.page-sub {
  color: var(--ink-dim);
  font-size: 13px;
  margin: 0;
}

/* ----------------------------------------------------------------- cards -- */

.card {
  background: var(--bg-raised);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  margin-bottom: 14px;
  overflow: hidden;
}
.card-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: 11px 14px;
  border-bottom: 1px solid var(--border);
}
.card-title {
  font-size: 13px;
  font-weight: 600;
  margin: 0;
  display: flex;
  align-items: center;
  gap: 8px;
}
.card-body { padding: 14px; }
.card-body.tight { padding: 10px; }
.card-body.flush { padding: 0; }

.brief {
  background: linear-gradient(180deg, var(--bg-raised), var(--brief-grad));
  border-left: 3px solid var(--accent);
}
.brief p { margin: 0 0 9px; }
.brief p:last-child { margin-bottom: 0; }
.brief strong { color: var(--accent); font-weight: 600; }

/* ----------------------------------------------------------------- forms -- */

.field-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(196px, 100%), 1fr));
  gap: 12px;
}

.field { display: flex; flex-direction: column; gap: 5px; }
.field-label {
  font-size: 11.5px;
  font-weight: 550;
  color: var(--ink-dim);
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 8px;
}
.field-label .hint {
  font-weight: 400;
  font-size: 10.5px;
  color: var(--ink-faint);
  font-variant-numeric: tabular-nums;
}
.field-help {
  font-size: 11px;
  color: var(--ink-faint);
  line-height: 1.45;
}

select, input[type="text"], input[type="number"],
input[type="email"], input[type="password"], textarea {
  width: 100%;
  /* Tall enough to be a 24-pixel target at every font size the app uses.
     WCAG 2.2 § 2.5.8. */
  min-height: 30px;
  padding: 7px 9px;
  background: var(--bg-inset);
  /* `--border-strong`, not `--border`: the edge of a field is what tells you
     there is a field, and at 1.3:1 against the fill there was nothing to see.
     WCAG 2.2 § 1.4.11 asks 3:1 of the boundary of a control. */
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-sm);
  transition: border-color 0.14s, box-shadow 0.14s;
}
/* NO `outline: none` HERE ANY MORE. It used to sit in the rule above, with a
   tinted border and a 12 %-alpha glow put up in its place — about 1.1:1
   against the field next to it, which is not an indicator, it is a hint. The
   ring now comes from the one `:focus-visible` rule near the top of the file;
   this only deepens the border underneath it. */
select:focus-visible, input:focus-visible, textarea:focus-visible {
  border-color: var(--accent);
}
select { cursor: pointer; }
textarea { resize: vertical; min-height: 72px; line-height: 1.5; }

input[type="range"] {
  width: 100%;
  height: 22px;
  background: transparent;
  -webkit-appearance: none;
  appearance: none;
  cursor: pointer;
}
input[type="range"]::-webkit-slider-runnable-track {
  height: 3px;
  background: var(--border);
  border-radius: 2px;
}
input[type="range"]::-webkit-slider-thumb {
  -webkit-appearance: none;
  width: 14px; height: 14px;
  margin-top: -5.5px;
  background: var(--accent);
  border-radius: 50%;
  border: 2px solid var(--bg-raised);
}
input[type="range"]::-moz-range-track {
  height: 3px; background: var(--border); border-radius: 2px;
}
input[type="range"]::-moz-range-thumb {
  width: 12px; height: 12px; background: var(--accent);
  border: 2px solid var(--bg-raised); border-radius: 50%;
}

.checkrow {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 6px 0;
  cursor: pointer;
  user-select: none;
}
.checkrow input { width: 15px; height: 15px; accent-color: var(--accent); cursor: pointer; }
.checkrow.compact { padding: 3px 0; font-size: 12.5px; }
.checkrow.compact input { width: 14px; height: 14px; }

/* A set of choices — solvents for a screen — rather than one of them. Spans
   the whole field grid, because five items in a 196px column is a column of
   ellipses. */
.field.field-wide { grid-column: 1 / -1; }
.checkgrid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(168px, 100%), 1fr));
  gap: 0 14px;
  padding: 2px 0;
}

/* --------------------------------------------------------------- buttons -- */

/* ONE LANGUAGE FOR BUTTONS, the instruments' own. The main action of a page or
   a tool is filled in the accent (Run measurement, Refine, Fit); every other
   action is the same button outlined in the accent on the card's own surface
   (white in the light theme); a minor one is a ghost. Choices between values
   are not buttons at all but segmented controls and sliders, as on an
   instrument's settings. */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  padding: 7px 13px;
  background: var(--bg-raised);
  border: 1px solid var(--accent);
  border-radius: var(--radius-sm);
  color: var(--accent);
  cursor: pointer;
  font-size: 13px;
  font-weight: 600;
  white-space: nowrap;
  transition: background 0.13s, border-color 0.13s, color 0.13s, opacity 0.13s;
}
.btn:hover:not(:disabled) { background: var(--accent-glow); }
.btn:disabled { opacity: 0.4; cursor: not-allowed; }
.btn.primary {
  background: var(--accent-dim);
  border-color: var(--accent);
  color: var(--on-accent);
  font-weight: 600;
}
.btn.primary:hover:not(:disabled) {
  background: var(--accent-hover);
  border-color: var(--ink);
}
.btn.ghost { background: transparent; border-color: transparent; color: var(--ink-dim); }
/* A ghost button has no fill and no border, so nothing marks where it can be
   pressed; the box still has to be big enough to press. § 2.5.8. */
.btn.ghost, .btn.small.ghost { min-height: 26px; min-width: 26px; }
.btn.ghost:hover:not(:disabled) { background: var(--bg-inset); color: var(--ink); }
.btn.small { padding: 4px 9px; font-size: 11.5px; min-height: 24px; min-width: 24px; }
.btn.danger { border-color: rgba(255,138,80,0.4); color: var(--danger); }
.btn.grow { flex: 1; }

.btn-row { display: flex; gap: 8px; flex-wrap: wrap; align-items: center; }

/* ------------------------------------------------------------ cost quote -- */

.quote {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(104px, 100%), 1fr));
  gap: 1px;
  background: var(--border);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  overflow: hidden;
}
.quote-cell { padding: 8px 11px; background: var(--bg-inset); }
.quote-label {
  font-size: 9.5px;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--ink-faint);
  margin-bottom: 2px;
}
.quote-val {
  font: 600 14px/1.2 var(--mono);
  font-variant-numeric: tabular-nums;
}
.quote-cell.over .quote-val { color: var(--danger); }

.alert {
  display: flex;
  gap: 9px;
  padding: 9px 12px;
  border-radius: var(--radius-sm);
  font-size: 12.5px;
  line-height: 1.5;
  margin-top: 10px;
}
.alert-icon { flex-shrink: 0; font-weight: 700; }
.alert.warn   { background: rgba(232,163,61,0.1);  border: 1px solid rgba(232,163,61,0.28);  color: var(--warn-ink); }
.alert.danger { background: rgba(255,138,80,0.1);  border: 1px solid rgba(255,138,80,0.3);   color: var(--danger-ink); }
.alert.info   { background: rgba(106,169,224,0.09); border: 1px solid rgba(106,169,224,0.26); color: var(--info-ink); }
.alert.ok     { background: rgba(92,201,138,0.09);  border: 1px solid rgba(92,201,138,0.26);  color: var(--ok-ink); }
.alert ul { margin: 4px 0 0; padding-left: 18px; }

/* ------------------------------------------------------------------ plot -- */

.plot-panel {
  background: var(--paper);
  border-radius: var(--radius);
  overflow: hidden;
  color: var(--paper-ink);
}
/* The plot's own buttons sit on the plot's paper, without a strip of their own:
   quiet pills that darken under the pointer and take the accent when on. */
.plot-toolbar {
  display: flex;
  align-items: center;
  gap: 2px;
  padding: 6px 8px 2px;
  flex-wrap: wrap;
}
.pbtn-sep { width: 1px; height: 16px; margin: 0 6px; background: var(--paper-border); }
.plot-toolbar .spacer { flex: 1; }
.pbtn {
  /* 24 CSS pixels tall whatever is written on it, which for the single-glyph
     ones is the difference between a target and a speck. WCAG 2.2 § 2.5.8. */
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: 24px;
  min-width: 24px;
  padding: 3px 10px;
  background: transparent;
  border: 1px solid transparent;
  border-radius: 999px;
  color: var(--paper-ink-dim);
  font-size: 11.5px;
  font-weight: 500;
  cursor: pointer;
  white-space: nowrap;
  transition: background 0.12s, color 0.12s;
}
.pbtn:hover { background: var(--plot-btn-hover); color: var(--paper-ink); }
.pbtn:focus-visible { outline: 2px solid var(--focus); outline-offset: 1px; }
.pbtn.on {
  background: var(--accent-glow);
  border-color: transparent;
  color: var(--accent);
  font-weight: 600;
}
/* A copy that worked. Nothing else on the page moves when it does, so the
   button has to say so itself. */
.pbtn.copied {
  background: var(--plot-btn-ok-bg);
  border-color: var(--plot-btn-ok-border);
  color: var(--plot-btn-ok-ink);
}
.plot-readout {
  font: 500 11.5px var(--mono);
  font-variant-numeric: tabular-nums;
  color: var(--paper-ink-dim);
  min-width: 190px;
  text-align: right;
}
.plot-canvas-wrap { position: relative; }
.plot-canvas-wrap canvas { display: block; width: 100%; touch-action: none; cursor: crosshair; }

.plot-legend {
  display: flex;
  flex-wrap: wrap;
  gap: 4px 14px;
  padding: 2px 12px 8px;
  font-size: 11.5px;
}
/* One entry per trace: a swatch that shows or hides it, a name that makes it
   the trace the readout reads, and its value at the cursor. Both controls are
   24 px targets (WCAG 2.2 § 2.5.8); the active name is outlined and bold,
   so which trace is being read never depends on color. */
.legend-item {
  display: inline-flex;
  align-items: center;
  gap: 1px;
  color: var(--paper-ink);
  user-select: none;
}
.legend-vis, .legend-name {
  display: inline-flex;
  align-items: center;
  min-height: 24px;
  min-width: 24px;
  padding: 0 5px;
  background: none;
  border: 1px solid transparent;
  border-radius: var(--radius-sm);
  color: inherit;
  font: inherit;
  cursor: pointer;
}
.legend-vis { justify-content: center; }
.legend-vis:hover, .legend-name:hover { background: var(--plot-btn-hover); }
.legend-item.active .legend-name {
  background: var(--paper);
  border-color: var(--paper-border);
  font-weight: 600;
}
.legend-item.off .legend-name { opacity: 0.45; text-decoration: line-through; }
.legend-item.off .legend-swatch { opacity: 0.35; }
.legend-swatch { width: 13px; height: 3px; border-radius: 2px; }
.legend-value {
  font: 500 11px var(--mono);
  font-variant-numeric: tabular-nums;
  color: var(--paper-ink-dim);
  padding-right: 4px;
}
.legend-value:empty { display: none; }

/* --------------------------------------------------------------- metadata - */

.meta-table {
  width: 100%;
  border-collapse: collapse;
  font-size: 12.5px;
}
.meta-table th, .meta-table td {
  text-align: left;
  padding: 5px 10px 5px 0;
  vertical-align: top;
  border-bottom: 1px solid var(--border);
}
.meta-table th {
  font-weight: 500;
  color: var(--ink-faint);
  white-space: nowrap;
  width: 1%;
  padding-right: 18px;
}
.meta-table td { font-variant-numeric: tabular-nums; }
.meta-table tr:last-child th, .meta-table tr:last-child td { border-bottom: none; }

.tag-list { display: flex; flex-wrap: wrap; gap: 5px; }
.tag {
  font-size: 10.5px;
  padding: 2px 7px;
  border-radius: 100px;
  background: var(--bg-inset);
  border: 1px solid var(--border);
  color: var(--ink-dim);
}

/* ----------------------------------------------------- the reference shelf -- */

/* Fifty-four bottles, in boxes. A list of lists, so that a screen reader
   hears the grouping the sighted reader sees, and so that the markup says
   what the layout is claiming. */
.shelf { list-style: none; margin: 0; padding: 0; }
.shelf-group + .shelf-group { margin-top: 12px; }
.shelf-title {
  margin: 0 0 4px;
  padding: 0 10px;
  font-size: 10.5px;
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--ink-faint);
}
.shelf-note {
  margin: 0 0 6px;
  padding: 0 10px;
  font-size: 11.5px;
  color: var(--ink-faint);
}
.shelf-items { list-style: none; margin: 0; padding: 0; }

/* THE SHELF AS PICTURES (src/ui/standard-shelf.js), on the Explorer's home
   page and on each instrument's: a row of chips, one per shelf with its count,
   and a search; then the standards on the open shelf as tiles, smaller than
   the lab's specimens because there are more of them. While a search is
   typed it looks along every shelf, and the chip that was open is outlined
   rather than filled, because it is not what is shown. */
.shelf-picker { margin: 0; padding: 0; border: 0; min-width: 0; }
.shelf-picker > legend { padding: 0; margin-bottom: 8px; }
.shelf-bar { display: flex; flex-wrap: wrap; align-items: center; gap: 8px 12px; margin-bottom: 6px; }
.shelf-groups { display: flex; flex-wrap: wrap; gap: 6px; }
.shelf-chip {
  position: relative;
  display: inline-flex;
  align-items: center;
  gap: 6px;
  min-height: 30px;
  padding: 3px 11px;
  border: 1px solid var(--border-strong);
  border-radius: 100px;
  background: var(--bg-raised);
  color: var(--ink-dim);
  font-size: 12.5px;
  cursor: pointer;
}
.shelf-chip input { position: absolute; inset: 0; width: 100%; height: 100%; margin: 0; opacity: 0; cursor: pointer; }
.shelf-chip:hover { border-color: var(--accent); color: var(--ink); }
.shelf-chip.suggested { border-color: var(--accent); color: var(--accent); }
.shelf-chip:has(input:checked) { background: var(--accent-dim); border-color: var(--accent-dim); color: var(--on-accent); font-weight: 600; }
.shelf-chip:has(input:focus-visible) { outline: 2px solid var(--focus); outline-offset: 2px; }
.shelf-chip-count { font: 11px var(--mono); opacity: 0.8; }
.shelf-groups.searching .shelf-chip:has(input:checked) {
  background: var(--bg-raised); border: 1px dashed var(--accent); color: var(--ink-dim); font-weight: 400;
}
.shelf-search { flex: 1 1 180px; max-width: 260px; min-height: 30px; }
.shelf-status { margin: 0 0 8px; min-height: 1.4em; font-size: 12px; line-height: 1.4; color: var(--ink-faint); }
.shelf-tiles { display: grid; grid-template-columns: repeat(auto-fill, minmax(96px, 1fr)); gap: 8px; }
/* The name under the picture, not over it: at this size a band across the
   foot of the picture covered the heap, the foil or the vial's liquid, which
   is the part of the picture that says anything. */
.specimen-tile.shelf-tile { display: flex; flex-direction: column; aspect-ratio: auto; }
.specimen-tile.shelf-tile .tile-art { position: relative; inset: auto; display: block; width: 100%; height: auto; aspect-ratio: 3 / 2; }
.specimen-tile.shelf-tile .tile-art > svg { position: absolute; inset: 0; width: 100%; height: 100%; }
.specimen-tile.shelf-tile .tile-overlay {
  position: static;
  padding: 3px 7px 4px;
  border-top: 1px solid var(--border);
  background: var(--bg-raised);
  color: var(--ink);
  font-size: 11.5px;
}
.specimen-tile.shelf-tile .tile-meta { color: var(--ink-faint); font-size: 11px; }
.specimen-tile.shelf-tile .tile-check { top: 4px; right: 4px; padding: 0 6px; }
.shelf-foot { margin: 8px 0 0; }

/* The standard chosen, under the shelf: its full name and formula, how it
   comes, and what it is on the shelf to show. */
.standard-card { margin-top: 12px; }
.standard-card-head { display: flex; flex-wrap: wrap; align-items: baseline; gap: 4px 12px; font-size: 13.5px; }
.standard-card-formula { color: var(--ink-dim); }
.standard-card-form { margin: 3px 0 0; font-size: 12px; color: var(--ink-faint); }
.standard-card-teaches { margin: 6px 0 0; font-size: 12.5px; line-height: 1.55; color: var(--ink-dim); }

/* --------------------------------------------------------------- results -- */


.result-grid {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 300px;
  gap: 14px;
  align-items: start;
}
/* A spectrum and a 300 px column of numbers beside it need about 720 px of
   panel between them. Under that the numbers go below the plot, which is the
   order they are read in anyway. */
@container panel (max-width: 720px) {
  .result-grid { grid-template-columns: minmax(0, 1fr); }
}

.numtable {
  width: 100%;
  border-collapse: collapse;
  font: 12px var(--mono);
  font-variant-numeric: tabular-nums;
}
.numtable th {
  text-align: right;
  padding: 5px 9px;
  background: var(--bg-inset);
  color: var(--ink-faint);
  font-weight: 600;
  font-size: 10.5px;
  /* Not uppercased: a heading is a symbol and a unit, and capitals change
     both — δ becomes Δ, μ an M, mm/s megametres per second. */
  letter-spacing: 0.02em;
  position: sticky;
  top: 0;
}
.numtable th:first-child, .numtable td:first-child { text-align: left; }
.numtable td { text-align: right; padding: 4px 9px; border-bottom: 1px solid var(--border); }
.numtable tbody tr:hover { background: var(--bg-inset); }
/* Both axes: a run of twelve reflections is wider than a phone, and the page
   itself must not be the thing that scrolls to show it. */
/* A SCROLL BOX HAS TO BE REACHABLE FROM THE KEYBOARD. Several of these hold
   three hundred structures or a forty-line peak list inside a 320 px window,
   and a region that scrolls but cannot be focused can be scrolled by a mouse
   wheel and by nothing else. `tabindex` is put on by `h`, which also gives it
   a role and a name; this is the styling half. */
.table-scroll { max-height: 320px; overflow: auto; }
.table-scroll:focus-visible { outline: 2px solid var(--focus); outline-offset: -2px; }

/* A table whose rows are a choice rather than a readout: the indexing
   candidates, the phases in a Rietveld model, the components of a fitted
   photoelectron line. The selected row is what the plot above is drawing. */
.numtable.selectable tbody tr { cursor: pointer; }
.numtable.selectable tbody tr.row-selected,
.numtable.selectable tbody tr.row-selected:hover {
  background: var(--accent-glow);
  box-shadow: inset 2px 0 0 var(--accent);
}

/* Legend for the colored highlights drawn on a plot by the panel below it. */
.marker-key {
  display: flex;
  align-items: center;
  gap: 6px;
  flex-wrap: wrap;
  margin: 6px 0 8px;
  font-size: 11.5px;
  color: var(--ink-faint);
}
.marker-key .marker-swatch {
  width: 10px;
  height: 10px;
  border-radius: 2px;
  display: inline-block;
}
.marker-key .marker-swatch + span { margin-right: 8px; }


/* ---------------------------------------------------------------- images -- */

/* The image and the instrument's data bar under it are the microscope's own
   screen and stay black in both themes; the frame around them is the page's,
   so it follows the theme. */
.micrograph {
  position: relative;
  background: #000;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  overflow: hidden;
  line-height: 0;
  /* Rendered at 560 px square; letting it stretch wider only interpolates. */
  max-width: 600px;
  margin: 0 auto;
}
.micrograph canvas { display: block; width: 100%; }
/* The beam's line, at the boundary between the frame leaving and the frame
   arriving. Two pixels of glow: enough to read as a raster and not enough to
   be a graphic. */
.scan-line {
  position: absolute;
  left: 0;
  right: 0;
  height: 2px;
  z-index: 2;
  pointer-events: none;
  background: linear-gradient(
    to bottom,
    rgba(255, 255, 255, 0) 0%,
    rgba(255, 255, 255, 0.75) 50%,
    rgba(255, 255, 255, 0) 100%
  );
  box-shadow: 0 0 10px 2px rgba(255, 255, 255, 0.28);
}
/* The console under the screen. A host of its own so the panel can rebuild the
   controls when a knob moves without rebuilding anything above them. */
.scope-strip-host { display: contents; }
/* Click/drag target for microanalysis, laid over the micrograph. */
.probe-overlay {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  touch-action: none;
  /* A long press is the start of a box, not a selection or a callout. */
  -webkit-user-select: none;
  user-select: none;
  -webkit-touch-callout: none;
}
/* With an analysis or the ruler armed, a finger on the frame draws; Safari on
   an iPad reads this from the frame, not from the SVG laid over it. */
.micrograph.probe-armed { touch-action: none; }
/* The marks follow the theme's accent, and each carries a halo in the
   opposite sense, because what they are drawn against is the specimen and
   not the page: a micrograph has bright edges and black holes in the same
   frame, and a mark that is only light or only dark vanishes on one of them. */
.probe-marker, .probe-dot, .probe-drag, .probe-keyboard line {
  filter: drop-shadow(0 0 1.2px var(--probe-halo));
}
.probe-marker {
  fill: var(--probe-fill);
  stroke: var(--probe-ink);
  stroke-width: 1.5;
}
.probe-marker.saed {
  fill: rgba(232, 163, 61, 0.12);
  stroke: var(--warn);
}
.probe-dot { fill: var(--probe-ink); stroke: none; }
.probe-drag {
  fill: var(--probe-fill);
  stroke: var(--probe-ink);
  stroke-width: 1.5;
  stroke-dasharray: 4 3;
}
/* Where the probe sits when the overlay is being driven from the keyboard.
   Drawn only while the overlay has focus and an analysis is armed. */
.probe-keyboard line {
  stroke: var(--probe-ink);
  stroke-width: 1.5;
  paint-order: stroke;
}
.probe-overlay:focus-visible {
  outline: 2px solid var(--focus);
  outline-offset: -2px;
}
/* The free ruler. Drawn in the warning colour rather than the probe colour so
   that a measurement is visibly not an analysis somebody was charged for. */
.ruler-line {
  stroke: var(--warn, #e8a33d);
  stroke-width: 1.6;
  stroke-linecap: round;
}
.ruler-text {
  fill: #fff6e6;
  font: 600 12px var(--mono);
  paint-order: stroke;
  stroke: rgba(0, 0, 0, 0.8);
  stroke-width: 3px;
}
/* A TICK BOX IS A TARGET, AND THE DEFAULT ONE IS THIRTEEN PIXELS ACROSS.
   A `.checkline` wraps its box in a label, so the words are part of the
   target and the pair is comfortably over the minimum; a radio alone in a
   table cell has nothing to borrow from and has to be big enough by itself.
   WCAG 2.2 § 2.5.8. `accent-color` is what keeps the mark visible on the dark
   theme, where a default tick is drawn in the system palette and not in
   ours. */
input[type="radio"], input[type="checkbox"] {
  width: 24px;
  height: 24px;
  accent-color: var(--accent);
  cursor: pointer;
}

/* A checkbox with its label on one line, for an analysis panel's options. */
.checkline {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  min-height: 24px;
  font-size: 12.5px;
  cursor: pointer;
}
/* The label is the target here, so the box itself can stay the size it
   looks right at. */
.checkline input[type="checkbox"], .checkline input[type="radio"] {
  margin: 0;
  width: 16px;
  height: 16px;
}
.probe-label {
  fill: #eafffb;
  font: 600 11px var(--mono);
  paint-order: stroke;
  stroke: rgba(0, 0, 0, 0.75);
  stroke-width: 3px;
}

.micrograph-bar {
  display: flex;
  justify-content: space-between;
  gap: 12px;
  padding: 6px 10px;
  background: #0a0a0a;
  color: #d8d8d8;
  font: 11px var(--mono);
  line-height: 1.4;
}

/* The microscope console: one strip of small controls directly under the
   frame they act on, rather than three cards stacked above it. Bounded by the
   micrograph's own width so the controls line up with the image edges. */
.scope-strip {
  max-width: 600px;
  margin: 8px auto 0;
  line-height: 1.4;
}
.scope-controls {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  gap: 8px 10px;
}
.scope-control {
  display: flex;
  flex-direction: column;
  gap: 3px;
  flex: 1 1 118px;
  min-width: 0;
}
/* Wrapped rather than clipped: "Accelerating voltage" truncated to
   "Accelerating volta..." is not a label, and the controls bottom-align
   anyway so a second line costs nothing but height. */
.scope-control > span {
  font-size: 10.5px;
  font-weight: 600;
  line-height: 1.25;
  letter-spacing: 0.02em;
  text-transform: uppercase;
  color: var(--ink-faint);
}
.scope-control select,
.scope-control input {
  width: 100%;
  padding: 4px 6px;
  font-size: 12px;
}
/* The stage is a pad, not a field, so it sizes to its buttons. */
.scope-control.stage { flex: 0 0 auto; }
/* Fixed width and tabular figures: the readout changes on every press and
   nothing under the pointer may move when it does. */
.stage-readout {
  width: 74px;
  font-size: 9.5px !important;
  font-weight: 500 !important;
  text-transform: none !important;
  letter-spacing: 0 !important;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  color: var(--ink-dim) !important;
  margin-top: -2px;
}
.stage-pad {
  display: grid;
  grid-template-columns: repeat(3, 22px);
  grid-auto-rows: 22px;
  gap: 2px;
}
.btn.tiny {
  padding: 0;
  min-width: 0;
  width: 22px;
  height: 22px;
  font-size: 11px;
  line-height: 1;
  border-radius: var(--radius-sm);
}
.scope-foot {
  margin-top: 7px;
  font-size: 11px;
  color: var(--ink-faint);
}

/* Element maps: one tile per element, all the same size, so that two of them
   are compared by looking rather than by remembering. */
.map-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(168px, 1fr));
  gap: 10px;
}
.map-tile {
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  overflow: hidden;
  background: #000;
}
.map-tile canvas { display: block; width: 100%; }
.map-tile .map-caption {
  display: flex;
  justify-content: space-between;
  gap: 8px;
  padding: 5px 8px;
  background: #0a0a0a;
  color: #d8d8d8;
  font: 11px var(--mono);
}
.map-tile .map-caption .faint { color: #8d8d8d; }
.map-composite {
  max-width: 420px;
  margin: 0 auto;
  border-radius: var(--radius-sm);
  overflow: hidden;
  background: #000;
  line-height: 0;
}
.map-composite canvas { display: block; width: 100%; }
.map-key {
  display: flex;
  flex-wrap: wrap;
  gap: 4px 14px;
  margin-top: 8px;
  font-size: 11.5px;
}
.map-key .map-swatch {
  display: inline-block;
  width: 10px;
  height: 10px;
  border-radius: 2px;
  margin-right: 5px;
  vertical-align: -1px;
}

/* A card head that folds its body away. A real button, so it works from the
   keyboard and announces its state, and styled to look like the head it
   replaces rather than like a control. */
button.card-head.foldable {
  width: 100%;
  background: none;
  border: 0;
  border-bottom: 1px solid var(--border);
  font: inherit;
  color: inherit;
  text-align: left;
  cursor: pointer;
}
button.card-head.foldable:hover { background: var(--surface-2, rgba(127, 127, 127, 0.06)); }
button.card-head.foldable .card-title { flex: 1 1 auto; }
button.card-head.foldable .fold-caret {
  flex: 0 0 auto;
  transition: transform 0.12s ease;
}
button.card-head.foldable .fold-caret.folded { transform: rotate(-90deg); }
.card.collapsed > button.card-head.foldable { border-bottom: 0; }

/* The problem's number, on its card. Small, monospaced and ahead of the
   title, because it is a handle rather than a name. */
.problem-number {
  font: 600 10.5px var(--mono);
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--ink-faint);
  margin-right: 8px;
  white-space: nowrap;
}

/* A reading with no ceiling behind it: the number, and no bar. */
.gauge.meter .gauge-value { font-variant-numeric: tabular-nums; }

/* The answer block, now that it lives at the foot of the brief rather than on
   a page of its own. It needs the weight of a page head without being one. */
.section-head { margin: 4px 0 14px; }
.section-title {
  font-size: 17px;
  font-weight: 620;
  letter-spacing: -0.01em;
  margin: 0 0 3px;
}
.section-sub {
  color: var(--ink-dim);
  font-size: 13px;
  margin: 0;
}

/* ----------------------------------------------------------------- misc --- */

.empty-state {
  text-align: center;
  padding: 54px 20px;
  color: var(--ink-faint);
}
.empty-state h3 { color: var(--ink-dim); font-weight: 550; margin: 0 0 6px; font-size: 15px; }
.empty-state p { margin: 0 auto; max-width: 46ch; font-size: 13px; line-height: 1.6; }

.spinner {
  width: 15px; height: 15px;
  border: 2px solid var(--border);
  border-top-color: var(--accent);
  border-radius: 50%;
  animation: spin 0.7s linear infinite;
  display: inline-block;
}
@keyframes spin { to { transform: rotate(360deg); } }

.progress-wrap { padding: 26px; text-align: center; }
.progress-track {
  height: 4px; background: var(--border); border-radius: 2px;
  overflow: hidden; margin: 14px auto 0; max-width: 340px;
}
.progress-fill {
  height: 100%; background: var(--accent); border-radius: 2px;
  transition: width 0.14s linear;
}
.progress-note { font-size: 12px; color: var(--ink-dim); margin-top: 10px; }

.divider { height: 1px; background: var(--border); margin: 14px 0; }

.mono { font-family: var(--mono); font-variant-numeric: tabular-nums; }
.dim { color: var(--ink-dim); }
.faint { color: var(--ink-faint); }
.nowrap { white-space: nowrap; }
/* A SUBSCRIPT THAT DOES NOT PUSH THE LINE APART. The browser default for
   <sub> is `vertical-align: sub`, which grows the line box, so a paragraph
   with one symbol in it is set at a different leading from the paragraph
   above it. Shifting it by hand keeps the line box the size the text set. */
sub {
  font-size: 0.72em;
  line-height: 0;
  position: relative;
  bottom: -0.25em;
  vertical-align: baseline;
}

.notes-list { margin: 0; padding-left: 20px; font-size: 12.5px; line-height: 1.6; }
.notes-list li { margin-bottom: 5px; color: var(--ink-dim); }
.notes-list li:last-child { margin-bottom: 0; }

/* modal */
.modal-backdrop {
  position: fixed; inset: 0;
  background: var(--scrim);
  display: flex; align-items: center; justify-content: center;
  padding: 24px; z-index: 100;
  backdrop-filter: blur(2px);
}
.modal {
  background: var(--bg-raised);
  border: 1px solid var(--border-strong);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
  max-width: 620px; width: 100%;
  max-height: 86vh; display: flex; flex-direction: column;
}
.modal-head {
  padding: 14px 16px; border-bottom: 1px solid var(--border);
  display: flex; justify-content: space-between; align-items: center;
}
.modal-body { padding: 16px; overflow-y: auto; }
.modal-foot {
  padding: 12px 16px; border-top: 1px solid var(--border);
  display: flex; justify-content: flex-end; gap: 8px;
}

/* The rack is always on screen at desktop width, so the toggle is hidden. */
#menu-toggle { display: none; font-size: 16px; padding: 4px 9px; }

/* Dims the panel behind the drawer, and dismisses it when tapped. Never seen
   on a wide screen, where the rack is simply part of the page. */
.sidebar-scrim {
  display: none;
  grid-area: main;
  background: var(--scrim);
  border: 0;
  z-index: 29;
}

/* ---------------------------------------------------------- narrow layout - */

/* Below this the rack cannot have a column of its own: 286 px out of 375 px
   leaves nothing for a spectrum. It becomes a drawer over the panel, and the
   header becomes two rows. */
@media (max-width: 820px) {
  #app {
    grid-template-columns: minmax(0, 1fr);
    grid-template-areas: "header" "main";
  }

  /* Named rows rather than a wrapping flex line, because which of six items
     lands on which row should be readable without simulating the wrap. Row
     one is navigation and never moves; row two is the problem it applies to
     and what is left of its allowance. */
  .app-header {
    display: grid;
    grid-template-columns: auto auto minmax(0, 1fr) auto auto;
    grid-template-areas:
      "menu brand tabs account theme"
      "rail rail  rail rail    rail";
    align-items: center;
    gap: 0 10px;
    padding: 7px 10px;
  }
  #menu-toggle { grid-area: menu; display: inline-flex; }
  /* Nothing to toggle on the chooser, where there is no rack. */
  #app:has(.sidebar.hidden) #menu-toggle { visibility: hidden; }

  .brand { grid-area: brand; }
  .brand-mark, .brand-sub { display: none; }
  .brand-abbr { display: block; }

  /* The tabs are the whole of the navigation here, so they take the width
     that is going. Shared out in proportion to the labels rather than evenly:
     an even split gave "Lab" sixty pixels for three characters while
     "Problems" went three pixels short and lost its last letter. */
  .section-tabs { grid-area: tabs; }
  .section-tab {
    flex: 1 1 auto;
    min-width: 0;
    padding: 6px 8px;
    overflow: hidden;
    text-overflow: ellipsis;
  }

  .account-chip { grid-area: account; }
  .theme-toggle { grid-area: theme; }

  /* The padding stands in for a row gap, so that a header with no second row
     costs nothing rather than a stripe of empty chrome. */
  .header-rail { grid-area: rail; padding-top: 7px; }
  /* The orientation notes are the first thing a phone gives up. What is left
     is then only a hidden note, and that must not hold the row open. */
  .hud.note { display: none; }
  .header-rail:not(:has(.gauge, .case-chip)) { display: none; }
  /* Left-aligned rather than pushed right: the first gauge has to begin on
     screen for the strip to look like it continues. */
  .header-rail > .hud { margin-left: 0; }
  /* WRAPPED, NOT SCROLLED, once the rail is a row of its own.
     At 320 px four gauges are 360 px wide, so the last one and a half sat
     outside the rail and could be reached only by dragging the strip — and
     the strip hides its own scrollbar, so nothing said it could be dragged
     and a keyboard could not drag it. They are four short readings; two rows
     of two shows all of them. WCAG 2.2 § 1.4.10. */
  .header-rail { overflow-x: visible; flex-wrap: wrap; }
  /* `flex: 0 0 auto` is what kept it one long row: a strip that may not shrink
     below its content never reaches the width at which it would wrap. */
  .header-rail > .hud { flex: 1 1 auto; min-width: 0; flex-wrap: wrap; }
  /* Its own row, so rather than defending a floor against the gauges it
     simply keeps the width its title wants and lets the row scroll. Left free
     to shrink here it collapsed to the dot alone. */
  .case-chip { flex: 0 0 auto; min-width: 0; max-width: min(62vw, 20rem); }
  .gauge { min-width: 82px; }

  /* ONE ROW ON A PHONE: the problem's chip and its four readings side by
     side, because every row the header takes is a row the page loses. The
     readings stand their name over their value, so each is as wide as the
     longer of the two rather than both, and the chip and the four fit in 360
     pixels. On a narrower phone the readings wrap beside the chip, two by
     two, rather than under it. */
  .header-rail { flex-wrap: nowrap; gap: 8px; }
  .header-rail > .hud { flex: 1 1 0; gap: 5px; }
  .case-chip { padding: 4px 9px; gap: 6px; max-width: 38vw; }
  .gauge { min-width: 0; padding: 3px 7px 4px; }
  .gauge-top { flex-direction: column; align-items: flex-start; gap: 2px; margin-bottom: 3px; }
  .gauge.meter .gauge-top { margin-bottom: 0; }
  .gauge-label { font-size: 8.5px; letter-spacing: 0.04em; }
  .gauge-value { font-size: 12px; }

  /* The rack, over the panel rather than beside it. Both sit in the same grid
     area, so the drawer has no header height to keep in step with. */
  .sidebar {
    grid-area: main;
    width: min(var(--sidebar-w), 84vw);
    z-index: 30;
    transform: translateX(-100%);
    transition: transform 0.22s ease;
    box-shadow: var(--shadow);
  }
  .sidebar.open { transform: none; }
  #app:has(.sidebar.open) .sidebar-scrim { display: block; }

  /* A 34 px row is a comfortable click and an uncomfortable tap. */
  .sidebar-title { padding: 12px 13px 9px; }
  .instrument-group-label { padding: 9px 6px 7px; }
  .inst { padding: 10px 9px; }
  .run { padding: 9px 9px; }
  .checkrow { padding: 9px 0; }

  .main { --main-pad-bottom: 32px; padding: 14px 14px var(--main-pad-bottom); }
  .page-title { font-size: 18px; }
  /* Its own line under the plot buttons rather than a 190 px column beside
     them, which pushed the buttons into three rows of two. */
  .plot-readout { flex: 1 1 100%; min-width: 0; text-align: left; }
  .modal-backdrop { padding: 12px; }
  .modal { max-height: 88dvh; }
}

/* A phone in portrait. The wordmark is the last decoration left, and what it
   buys is the three tab labels spelled out rather than clipped: at 375 px
   the navigation row is about twenty pixels short of holding them, and this
   block finds thirty. */
@media (max-width: 480px) {
  .brand { display: none; }
  .app-header { gap: 0 6px; padding: 7px 8px; }
  .section-tab { padding: 6px 5px; }
  #menu-toggle { padding: 4px 7px; }
  .main { --main-pad-bottom: 28px; padding: 12px 11px var(--main-pad-bottom); }
  .card-body { padding: 11px; }
  .card-head { padding: 10px 11px; }
  .meta-table th { white-space: normal; }
  .micrograph-bar { flex-wrap: wrap; }
}

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after { animation-duration: 0.01ms !important; transition-duration: 0.01ms !important; }
}

/* ------------------------------------------------------- problem chooser -- */

/* The chooser has no rack, no bench and no notebook, so it gets the width.
   Scoped to the wide layout: below 820 px the grid has one column already, and
   a two-track override there put the header and the panel into a track of zero
   width and left the app looking like a blank page. */
.sidebar.hidden { display: none; }
@media (min-width: 821px) {
  #app:has(.sidebar.hidden) { grid-template-columns: 0 minmax(0, 1fr); }
}

.problem-grid {
  display: grid;
  /* `min()` so that a card narrower than its own minimum shrinks rather than
     spilling out of a 320 px phone. */
  grid-template-columns: repeat(auto-fit, minmax(min(310px, 100%), 1fr));
  gap: 10px;
}

.problem-card {
  display: flex;
  flex-direction: column;
  gap: 6px;
  padding: 12px 13px;
  text-align: left;
  background: var(--bg-inset);
  border: 1px solid var(--border);
  border-left: 3px solid var(--border-strong);
  border-radius: var(--radius);
  cursor: pointer;
  transition: border-color 0.12s, background 0.12s;
}
.problem-card:hover {
  background: var(--bg-raised);
  border-color: var(--accent-dim);
}
.problem-card.started { border-left-color: var(--accent); }

.problem-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 10px;
}
.problem-title { font-size: 14px; font-weight: 620; letter-spacing: -0.01em; flex: 1 1 auto; }
.problem-tagline { font-size: 12.5px; color: var(--ink-dim); line-height: 1.45; }

.problem-meta {
  display: flex;
  flex-wrap: wrap;
  gap: 4px 12px;
  font: 500 10.5px var(--mono);
  color: var(--ink-faint);
  font-variant-numeric: tabular-nums;
}

.problem-flag {
  display: flex;
  align-items: center;
  gap: 6px;
  font-size: 11px;
  color: var(--warn-ink);
}
.problem-flag .flag-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 14px; height: 14px;
  border-radius: 50%;
  background: rgba(232, 163, 61, 0.18);
  color: var(--warn);
  font: 700 9px var(--sans);
  flex-shrink: 0;
}

.problem-progress {
  display: flex;
  flex-wrap: wrap;
  gap: 4px 10px;
  padding-top: 2px;
  border-top: 1px dashed var(--border);
  /* To the foot of the card, so the dashed line sits at one height across a
     row of cards and the slack goes above it, under the description. */
  margin-top: auto;
  font-size: 11px;
  color: var(--ink-dim);
}
.problem-progress .submitted { color: var(--ok); }

/* difficulty */
.pill {
  flex-shrink: 0;
  padding: 1px 7px;
  border-radius: 100px;
  font: 600 9.5px var(--sans);
  text-transform: uppercase;
  letter-spacing: 0.07em;
  border: 1px solid var(--border-strong);
  color: var(--ink-faint);
}
.pill.intro    { color: var(--ok);     border-color: rgba(92, 201, 138, 0.45); }
.pill.standard { color: var(--info);   border-color: rgba(106, 169, 224, 0.45); }
.pill.hard     { color: var(--warn);   border-color: rgba(232, 163, 61, 0.45); }

/* Graded or Practice, on a problem card and at the top of the answer card.
   The word carries the meaning; the border is the second cue (solid and
   filled for graded, dashed and empty for practice), so it does not rest on
   colour. */
.mode-pill {
  flex-shrink: 0;
  display: inline-block;
  padding: 1px 7px;
  border-radius: 4px;
  font: 600 10px var(--sans);
  letter-spacing: 0.06em;
  text-transform: uppercase;
  white-space: nowrap;
}
.mode-pill.graded {
  color: var(--accent);
  border: 1px solid var(--accent);
  background: color-mix(in srgb, var(--accent) 12%, transparent);
}
.mode-pill.practice {
  color: var(--ink-dim);
  border: 1px dashed var(--border-strong);
}
.problem-head .mode-pill { align-self: center; }

/* One line at the top of the answer card saying which kind of problem this is. */
.answer-mode-note {
  margin: 0 0 12px;
  font-size: 12.5px;
  color: var(--ink-dim);
}

/* The hand-in card's list of graded problems. */
.graded-list summary { cursor: pointer; font-size: 12.5px; color: var(--ink-dim); }
.graded-list ul {
  margin: 8px 0 0;
  padding: 0;
  list-style: none;
  columns: 2 280px;
  font-size: 12.5px;
}
.graded-list li { break-inside: avoid; padding: 2px 0; }

/* An instrument that is in the rack and cannot be booked. */
.inst.unavailable { opacity: 0.55; }
.inst.unavailable .inst-short { text-decoration: line-through; }
.inst-meta.blocked { color: var(--warn); }

.sidebar-foot.thin { padding-top: 0; border-top: 0; }

/* --------------------------------------------------------------- account -- */

/* The chip in the header, and the dot that says where the work is being kept.
   It carries a color rather than a word because it is glanced at, not read;
   the wording lives in its tooltip and in the dialog behind it. */
.account-chip {
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  gap: 6px;
  max-width: 12rem;
  color: var(--ink-dim);
}
.account-chip:hover { color: var(--ink); }
.account-name {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.account-chip.signed-out { color: var(--warn-ink); }

.sync-dot {
  flex: 0 0 auto;
  width: 7px; height: 7px;
  border-radius: 100px;
  background: var(--ink-faint);
}
.sync-dot.ok   { background: var(--ok); }
.sync-dot.warn { background: var(--warn); }
.sync-dot.bad  { background: var(--danger); }
/* Saving is the only transient state, so it is the only one that moves. A
   pulsing dot for a resting state would read as a fault. */
.sync-dot.busy {
  background: var(--accent);
  animation: sync-pulse 1.1s ease-in-out infinite;
}
@keyframes sync-pulse {
  0%, 100% { opacity: 1; }
  50%      { opacity: 0.25; }
}
@media (prefers-reduced-motion: reduce) {
  .sync-dot.busy { animation: none; }
}

.account-modal { max-width: 27rem; }
.account-modal .card-title { margin-bottom: 4px; }
.account-modal .field { margin-bottom: 12px; }
.account-modal .modal-foot {
  padding: 12px 0 0;
  margin-top: 4px;
}

.account-email {
  font-weight: 600;
  color: var(--ink);
  margin: 0 0 10px;
}

.hint {
  font-size: 11.5px;
  color: var(--ink-dim);
  line-height: 1.5;
  margin: 0 0 10px;
}
.hint.quiet { color: var(--ink-faint); font-size: 11px; margin-bottom: 0; }

.account-error,
.account-notice {
  font-size: 11.5px;
  line-height: 1.45;
  margin: 0 0 10px;
  padding: 8px 10px;
  border-radius: var(--radius-sm);
  border: 1px solid var(--border-strong);
}
.account-error  { color: var(--danger-ink); border-color: rgba(255, 138, 80, 0.4); }
.account-notice { color: var(--info-ink);   border-color: rgba(106, 169, 224, 0.4); }

/* A button that reads as prose, for "create one instead" and the like. */
.linkish {
  background: none;
  border: 0;
  padding: 0;
  font: inherit;
  color: var(--accent);
  cursor: pointer;
  text-decoration: underline;
  text-underline-offset: 2px;
}
.linkish:hover { color: var(--ink); }

/* Newer work arrived from another machine. Deliberately not a modal: it is
   news, not a question, and it must not block a student reading a spectrum. */
.stale-banner {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
  margin-bottom: 14px;
  padding: 9px 12px;
  font-size: 12px;
  color: var(--info-ink);
  background: var(--bg-raised);
  border: 1px solid rgba(106, 169, 224, 0.4);
  border-left: 3px solid var(--info);
  border-radius: var(--radius-sm);
}
.stale-banner span { flex: 1 1 16rem; line-height: 1.45; }

@media (max-width: 480px) {
  .stale-banner { font-size: 11.5px; padding: 9px 10px; }
  .stale-banner .btn { flex: 1 1 auto; }
}

/* A color key beside a phase name in a refinement table. */
.phase-swatch {
  display: inline-block;
  width: 9px;
  height: 9px;
  border-radius: 2px;
  margin-right: 6px;
  vertical-align: baseline;
}

/* The set of phases a refinement is actually going to fit. Boxed off from the
   catalog below it, because the distinction between "available" and "chosen"
   is the one the panel exists to make. */
.phase-model {
  border: 1px solid var(--border);
  border-left: 3px solid var(--accent);
  border-radius: var(--radius-sm);
  padding: 10px 12px 12px;
  margin-bottom: 4px;
  background: var(--bg-inset);
}
.phase-model .empty-model {
  font-size: 12px;
  color: var(--ink-faint);
  padding: 10px 2px;
}

/* The folded escape hatch for a structure the library does not carry. */
details.structure-paste > summary {
  cursor: pointer;
  font-size: 11.5px;
  color: var(--ink-faint);
  padding: 4px 0;
}
details.structure-paste > summary:hover { color: var(--ink); }

/* A mass GAIN in a table of losses. Worth coloring, because the sign is the
   whole result and a minus sign in a column of minus signs is not read. */
.numtable td.gain { color: var(--accent); font-weight: 600; }

/* ------------------------------------------------------- methods reference -- */

.method-group { margin-top: 22px; }
.method-group .group-title {
  font-size: 12px;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--ink-faint);
  margin: 0 0 10px;
}
.method-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
  gap: 10px;
}
.method-card {
  display: block;
  text-align: left;
  width: 100%;
  padding: 12px 14px;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--bg-raised);
  color: inherit;
  font: inherit;
  cursor: pointer;
  transition: border-color 0.12s, transform 0.12s;
}
.method-card:hover { border-color: var(--accent); transform: translateY(-1px); }
.method-card-head { display: flex; align-items: baseline; gap: 8px; margin-bottom: 5px; }
.method-short {
  font: 600 11px var(--mono);
  color: var(--accent);
  letter-spacing: 0.03em;
}
.method-name { font-size: 12.5px; font-weight: 600; }
.method-line { margin: 0 0 8px; font-size: 12px; color: var(--ink-dim); line-height: 1.45; }
.method-card-foot { font-size: 11px; color: var(--ink-faint); display: flex; gap: 5px; }
.method-card-foot .dotsep { opacity: 0.5; }
.method-card-foot .warn-word { color: var(--warn, #d55e00); }

/* A plain disclosure that folds a long list or a card's body: a native
   <details>, so it opens from the keyboard (Enter or Space on the summary). */
details.fold > summary {
  cursor: pointer;
  font-size: 12.5px;
  color: var(--ink-dim);
  min-height: 24px;
  padding: 2px 0;
}
details.fold > summary:hover { color: var(--ink); }
details.fold[open] > summary { margin-bottom: 8px; }

/* A whole card that folds: the card head is the summary, the body the rest. */
details.card-fold > summary.card-head {
  justify-content: flex-start;
  cursor: pointer;
  list-style: none;
  border-bottom: none;
}
details.card-fold > summary.card-head::-webkit-details-marker { display: none; }
details.card-fold > summary.card-head::before {
  content: "\25b8";
  color: var(--ink-faint);
  font-size: 11px;
}
details.card-fold[open] > summary.card-head { border-bottom: 1px solid var(--border); }
details.card-fold[open] > summary.card-head::before { content: "\25be"; }

/* The one-sentence summary under a method's title. */
.method-lede {
  font-size: 15px;
  line-height: 1.5;
  color: var(--ink);
  border-left: 3px solid var(--accent);
  padding: 2px 0 2px 12px;
  margin: 0 0 14px;
}

/* ------------------------------------------------------------------ diagrams
   A figure is HTML around an SVG: the title and the caption are markup, so
   they wrap, and only geometry is inside the viewBox. See the header comment
   of src/methods/schematics.js for why that split is load-bearing.
   Nothing here may set `text-anchor`: a stylesheet beats a presentation
   attribute, so a rule here would silently override every label that asked to
   be anchored at its start or end. Each text node states its own.
   ------------------------------------------------------------------------ */
.schematic-figure {
  margin: 0 0 18px;
  padding: 12px 12px 10px;
  background: var(--bg-inset);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
}
.schematic-figure + .schematic-figure { margin-top: -6px; }
.schematic-title {
  display: flex;
  align-items: baseline;
  gap: 8px;
  font: 600 11px var(--sans);
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--ink-dim);
  margin-bottom: 8px;
}
/* The figure number, in the accent, so that a reader scanning for "Figure 4"
   finds it without reading the titles. Numbering runs across diagrams and
   demonstrations together because that is one series to whoever is reading. */
.fig-number {
  flex: 0 0 auto;
  font: 600 10px var(--sans);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--accent);
}
.fig-name { flex: 1 1 auto; }
.schematic { display: block; width: 100%; max-width: 620px; margin: 0 auto; height: auto; }

/* THE SAME DRAWING, RE-CUT FOR A PHONE. Below a breakpoint the figure shows
   its content in one to four regions stacked top to bottom instead of the
   whole 480-unit drawing, so the type stays near the size it was drawn at.
   The planning is src/methods/narrow-layout.js; which form shows is a class
   the figure's own width sets, not the viewport's, because a figure inside a
   two-column layout is narrow on a wide screen too. */
.schematic-narrow { display: none; }
.schematic-figure.is-narrow > .schematic { display: none; }
.schematic-figure.is-narrow > .schematic-narrow {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 12px;
}
.schematic-region { width: 100%; }
/* A drawing kept whole on a phone although it is wider than a phone shows at
   full size gets its type back up to the size it was drawn at, by at most
   15 %, which the layout test checks still collides with nothing. */
.schematic-region .sch-label { font-size: calc(9px * var(--sch-boost, 1)); }
.schematic-region .sch-sub,
.schematic-region .sch-tag,
.schematic-region .sch-legend text { font-size: calc(8.5px * var(--sch-boost, 1)); }
.schematic-region .sch-equation { font-size: calc(10.5px * var(--sch-boost, 1)); }
/* A band of a diagram that was only words, set as text so it wraps. Sized to
   match the drawing's own type at the narrow scale. */
.schematic-words { width: 100%; padding: 0 8px; text-align: center; }
.schematic-words .words { margin: 0; font-family: var(--sans); line-height: 1.4; }
.schematic-words .words + .words { margin-top: 4px; }
.schematic-words .sch-label { font-size: 12.5px; font-weight: 600; color: var(--ink); }
.schematic-words .sch-sub { font-size: 12px; color: var(--ink-dim); }
.schematic-words .sch-tag { font-size: 12px; color: var(--ink-faint); }
.schematic-words .sch-equation { font-size: 13px; font-family: var(--mono); color: var(--ink); }
/* On a phone the figure runs to the edges of its card: the 24 px of inset and
   padding it gives up are 7 % more type. */
@media (max-width: 620px) {
  .card-body > .schematic-figure {
    margin-left: -14px;
    margin-right: -14px;
    padding: 10px 6px 8px;
    border-left: 0;
    border-right: 0;
    border-radius: 0;
  }
  .card-body > .schematic-figure .schematic-title,
  .card-body > .schematic-figure .schematic-caption { padding-left: 6px; padding-right: 6px; }
}
@media (max-width: 480px) {
  .card-body > .schematic-figure { margin-left: -11px; margin-right: -11px; }
}
/* Paper is wide enough for the drawing as drawn. */
@media print {
  .schematic-figure.is-narrow > .schematic { display: block; }
  .schematic-figure.is-narrow > .schematic-narrow { display: none; }
}
.schematic-caption {
  margin: 10px auto 0;
  max-width: 620px;
  padding-top: 8px;
  border-top: 1px solid var(--border);
  font-size: 12px;
  line-height: 1.55;
  color: var(--ink-dim);
}

/* Type. No halo behind it: an outlined glyph reads as a printing fault at this
   size, so instead no label is allowed to sit on a line in the first place.
   `src/tests/methods.test.js` samples every stroke in every diagram against
   every text box and fails if one crosses another — which is the constraint a
   halo was papering over. */
.sch-label { font: 600 9px var(--sans); fill: var(--ink); }
.sch-sub { font: 8.5px var(--sans); fill: var(--ink-dim); }
/* A subscript inside a diagram label. SVG has no <sub>, so this is a tspan
   with a `dy`, and the only thing left to say is how much smaller it is. */
.sch-subscript { font-size: 0.72em; }
.sch-tag { font: 8.5px var(--sans); fill: var(--ink-faint); }
.sch-equation { font: 10.5px var(--mono); fill: var(--ink); }

/* Apparatus. */
.sch-box rect { fill: var(--bg-raised); stroke: var(--ink-faint); stroke-width: 1; }
.sch-box.source rect, .sch-box.detector rect { fill: var(--accent-glow); stroke: var(--accent); }
.sch-box-plain { fill: var(--bg-raised); stroke: var(--ink-faint); stroke-width: 1; }
.sch-specimen rect { fill: var(--bg-raised); stroke: none; }
.sch-specimen rect.sch-outline { fill: none; stroke: var(--ink-dim); stroke-width: 1.1; }
.sch-hatch { stroke: var(--ink-faint); stroke-width: 0.6; opacity: 0.55; }
.sch-solid { fill: var(--bg-raised); stroke: var(--ink-faint); stroke-width: 1; }
.sch-vacuum { fill: none; stroke: var(--ink-faint); stroke-width: 1; stroke-dasharray: 5 4; }
.sch-cavity { fill: var(--bg-raised); stroke: var(--ink-dim); stroke-width: 1.2; }
.sch-chamber { fill: var(--bg-raised); stroke: var(--ink-dim); stroke-width: 1.2; }
.sch-cone { fill: none; stroke: var(--ink-dim); stroke-width: 1.6; }
.sch-crystal, .sch-mirror { stroke: var(--ink); stroke-width: 2.6; stroke-linecap: round; }
.sch-crystal-face { fill: var(--bg-raised); stroke: var(--ink-dim); stroke-width: 1.2; }
.sch-mirror-arc { fill: none; stroke: var(--ink); stroke-width: 2.2; }
.sch-analyzer { fill: none; stroke: var(--accent); stroke-width: 1.6; }
.sch-analyzer-line { stroke: var(--accent); stroke-width: 1.6; }
.sch-rotor { fill: var(--bg-raised); stroke: var(--ink-dim); stroke-width: 1.2; }
.sch-rotor-cap { fill: var(--accent-glow); stroke: var(--accent); stroke-width: 1; }
.sch-magnet { fill: var(--accent-glow); stroke: var(--accent); stroke-width: 1; }
.sch-magnet-bore { fill: var(--bg-inset); stroke: var(--accent); stroke-width: 0.8; }
.sch-coil ellipse { fill: none; stroke: var(--ink-dim); stroke-width: 1.3; }
.sch-plasma { fill: var(--accent-glow); stroke: var(--accent); stroke-width: 1.3; }
.sch-sphere { fill: none; stroke: var(--ink-dim); stroke-width: 1.4; }
.sch-sphere-inner { fill: none; stroke: var(--ink-faint); stroke-width: 0.8; stroke-dasharray: 3 3; }
.sch-baffle { fill: var(--ink-faint); }
.sch-furnace { fill: none; stroke: var(--ink-dim); stroke-width: 2.6; }
.sch-cell { fill: none; stroke: var(--ink-dim); stroke-width: 1.6; }
.sch-electrolyte { stroke: var(--accent); stroke-width: 1; stroke-dasharray: 4 3; }
.sch-electrolyte-fill { fill: var(--accent-glow); }
.sch-electrode { fill: var(--bg-raised); stroke: var(--ink-dim); stroke-width: 1; }
.sch-wire { stroke: var(--ink-dim); stroke-width: 1.4; fill: none; }
.sch-wire-thin { stroke: var(--ink-faint); stroke-width: 0.9; fill: none; }
.sch-plane { stroke: var(--ink-dim); stroke-width: 1.2; }

/* Beams, and the arrowheads they end in. */
.sch-beam line, .sch-beam path { stroke: var(--accent); stroke-width: 1.5; fill: none; }
.sch-beam.faint line, .sch-beam.faint path { stroke: var(--ink-faint); stroke-width: 1.1; }
.sch-beam.alt line, .sch-beam.alt path { stroke: var(--t2); stroke-width: 1.4; }
.sch-head { fill: var(--accent); }
.sch-head.faint { fill: var(--ink-faint); }
.sch-head.alt { fill: var(--t2); }
.sch-wave { stroke: var(--accent); stroke-width: 1.2; }

/* Construction: guides, leaders, angle arcs, dimension brackets. */
.sch-guide { stroke: var(--ink-faint); stroke-width: 0.9; stroke-dasharray: 3 3; fill: none; }
.sch-leader { stroke: var(--ink-faint); stroke-width: 0.7; }
.sch-angle path { stroke: var(--ink-dim); stroke-width: 1; }
.sch-bracket line { stroke: var(--ink-faint); stroke-width: 0.9; }
.sch-tick { stroke: var(--ink-faint); stroke-width: 1.2; }
.sch-extra { stroke: var(--t2); stroke-width: 2.4; fill: none; }

/* Atoms, and the volumes a beam reaches. */
.sch-dot { fill: var(--ink-dim); }
.sch-dot.mark { fill: var(--t2); }
.sch-dot.hole { fill: none; stroke: var(--t2); stroke-width: 1.4; }
.sch-dot.bright { fill: var(--accent); }
.sch-dot.absorber { fill: var(--accent); }
.sch-region { fill: var(--accent-glow); stroke: var(--accent); stroke-width: 1; }
.sch-region.deep { fill: var(--accent-glow); stroke: var(--accent); stroke-dasharray: 3 2; }
.sch-region.shallow { fill: var(--t2); opacity: 0.45; stroke: none; }
.sch-ring { fill: none; stroke: var(--accent); stroke-width: 0.9; opacity: 0.65; }
.sch-ring.alt { stroke: var(--t2); }
.sch-envelope { stroke: var(--ink-faint); stroke-width: 0.9; stroke-dasharray: 6 4; }
.sch-band { fill: var(--ink-faint); opacity: 0.14; }

/* Energy levels and the transitions between them. */
.sch-level { stroke: var(--ink); stroke-width: 1.6; }
.sch-level-line { stroke: var(--ink); stroke-width: 1.4; fill: none; }
.sch-transition { stroke: var(--accent); stroke-width: 1.3; fill: none; }
.sch-transition.alt { stroke: var(--t2); }

/* Inset plots, for the shape of a result beside the thing that produces it. */
.sch-panel-face { fill: var(--bg-raised); stroke: var(--ink-faint); stroke-width: 0.8; }
.sch-axis { stroke: var(--ink-faint); stroke-width: 1; }
/* THREE CURVES IN ONE PANEL HAVE TO BE THREE DIFFERENT CURVES.
   `.alt` and `.faint` were being asked for in the diagrams and had no rules
   here, so the correlogram figure drew a small particle, a large one and a
   broad distribution as three lines of identical teal crossing each other —
   the one figure on the page whose entire subject is telling them apart.
   The variants follow `.sch-beam`, which got this right: a second hue from
   the colour-blind-safe trace palette, and a dash for the third so the set
   survives being printed in grey. */
.sch-trace { stroke: var(--accent); stroke-width: 1.5; fill: none; }
.sch-trace.alt { stroke: var(--t2); stroke-width: 1.5; }
.sch-trace.faint { stroke: var(--ink-dim); stroke-width: 1.4; stroke-dasharray: 5 3.5; }

/* A key beside a panel that holds more than one trace. The swatch inherits
   the trace classes, so a legend cannot disagree with the curve it names. */
.sch-legend text { font: 8.5px var(--sans); fill: var(--ink-dim); }
.sch-legend line { stroke-linecap: round; }
/* A line fitted to part of a trace and extrapolated past it — a Tauc
   extrapolation, a Curie–Weiss intercept. Distinct from `.sch-guide`, which is
   a construction line somebody drew, because this one came out of a fit. */
.sch-fit { stroke: var(--warn, #d55e00); stroke-width: 1.2; stroke-dasharray: 6 3; fill: none; }

/* What it is blind to reads as a caution, not as a feature list. */
.notes-list.blind li::marker { color: var(--warn, #d55e00); }

/* A claim with its reason: the claim in the ink, the reason in the dim ink,
   one run of text so it wraps as a sentence rather than as two columns. */
.claim-list li { margin-bottom: 8px; }
.claim-lead { font-weight: 600; color: var(--ink); }
.claim-text { color: var(--ink-dim); }

/* The subsections of "How it works". Quiet, because they divide a section a
   reader is already inside rather than starting a new one. */
.method-subhead {
  font-size: 13px;
  font-weight: 600;
  color: var(--ink);
  margin: 20px 0 6px;
  padding-top: 12px;
  border-top: 1px solid var(--border);
}
.card-body > .method-subhead:first-child { margin-top: 0; padding-top: 0; border-top: none; }



/* Citations. An italic bracketed number per source, [1], numbered by first
   appearance on the page, and the list at its foot (src/methods/references.js).
   The number is a link with a target big enough to press on a phone. */
em.cite { font-style: italic; white-space: nowrap; }
em.cite .cite-link {
  color: var(--accent);
  text-decoration: none;
  font-weight: 600;
  /* A bigger target than the digit itself, for a finger. */
  padding: 2px 1px;
}
em.cite .cite-link:hover { text-decoration: underline; }
.reference-list { margin: 0; padding-left: 26px; font-size: 13px; line-height: 1.55; color: var(--ink-dim); }
.reference-list li { margin-bottom: 7px; padding-left: 2px; }
.reference-list li:focus { outline: 2px solid var(--focus); outline-offset: 2px; }
.reference-list a { color: var(--accent); word-break: break-word; }

.challenge { margin-bottom: 14px; }
.challenge:last-child { margin-bottom: 0; }
.challenge-title {
  font-size: 12.5px;
  font-weight: 600;
  margin-bottom: 3px;
}
.challenge-text { margin: 0; font-size: 12.5px; line-height: 1.55; color: var(--ink-dim); }

.setting-note { margin-bottom: 12px; }
.setting-note:last-child { margin-bottom: 0; }
.setting-head { display: flex; align-items: baseline; gap: 8px; flex-wrap: wrap; }
.setting-label { font-size: 12.5px; font-weight: 600; }
.setting-default { font: 11px var(--mono); color: var(--ink-faint); }
.setting-help { margin: 2px 0 0; font-size: 12.5px; line-height: 1.5; color: var(--ink-dim); }

/* What to run with it: the other half of every measurement. */
.pairs-list { list-style: none; margin: 0; padding: 0; }
.pairs-list li {
  display: grid;
  grid-template-columns: minmax(120px, 190px) 1fr;
  gap: 4px 14px;
  padding: 9px 0;
  border-top: 1px solid var(--border);
  font-size: 12.5px;
  line-height: 1.55;
}
.pairs-list li:first-child { border-top: none; padding-top: 2px; }
.pair-with { font-weight: 600; color: var(--ink); }
.pair-why { color: var(--ink-dim); }
@media (max-width: 620px) {
  .pairs-list li { grid-template-columns: 1fr; }
}

/* Where an analysis came from: the frame beside a note about it. */
.provenance { display: flex; gap: 16px; flex-wrap: wrap; align-items: flex-start; }
.provenance-frame {
  width: 260px;
  height: 260px;
  flex: 0 0 auto;
  border-radius: var(--radius-sm);
  /* The frame is the page's, as on `.micrograph`; the image inside is not. */
  border: 1px solid var(--border);
  background: #000;
}
.provenance-note { flex: 1 1 220px; min-width: 200px; font-size: 12.5px; line-height: 1.55; }
.provenance-note p { color: var(--ink-dim); }
@media (max-width: 560px) {
  .provenance-frame { width: 100%; height: auto; aspect-ratio: 1 / 1; }
}

/* A DEMONSTRATION FOLDED INTO A CLAIM. The three cards that say what an
   instrument answers, what it cannot see and where it lies are lists somebody
   is reading; a plot that appeared unasked in the middle of one would push
   the rest of the list off the screen. So it is a disclosure, and the summary
   says what would be measured — opening it is a decision. */
.section-figure {
  margin-top: 12px;
  border-top: 1px solid var(--border);
  padding-top: 10px;
}
.section-figure + .section-figure { margin-top: 8px; }
.section-figure > summary {
  display: flex;
  align-items: baseline;
  gap: 8px;
  /* 24 px of target, like every other control. */
  min-height: 24px;
  padding: 2px 0;
  cursor: pointer;
  list-style: none;
  font-size: 12.5px;
  color: var(--ink-dim);
}
.section-figure > summary::-webkit-details-marker { display: none; }
.section-figure > summary:focus-visible {
  outline: 2px solid var(--focus);
  outline-offset: 2px;
  border-radius: var(--radius-sm);
}
.section-figure-cue {
  flex: 0 0 auto;
  padding: 2px 8px;
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-sm);
  background: var(--bg-inset);
  color: var(--ink);
  font-size: 11.5px;
  font-weight: 600;
  white-space: nowrap;
}
.section-figure[open] > summary .section-figure-cue {
  background: var(--accent-glow);
  border-color: var(--accent);
  color: var(--accent);
}
.section-figure-ask { min-width: 0; }
.section-figure-body { padding-top: 10px; }

.demo-card { margin: 0 0 14px; }
.demo-card .demo-ask {
  margin: 0 0 12px;
  padding-left: 9px;
  border-left: 2px solid var(--border);
  font-size: 12.5px;
  color: var(--ink-dim);
}
/* The caption sits UNDER the figure, hanging off a numbered label, the way a
   caption does in a book. It used to be a tinted box titled "What to notice",
   which reads as an aside beside the figure rather than as part of it — and a
   reader who has decided the boxes are optional has decided to skip the half
   of the figure that says what it means. */
.demo-caption {
  display: block;
  margin: 12px auto 0;
  padding-top: 9px;
  border-top: 1px solid var(--border);
  max-width: 760px;
}
.demo-caption .fig-number { display: inline; margin-right: 7px; }
.demo-caption p {
  margin: 0;
  font-size: 12.5px;
  line-height: 1.6;
  color: var(--ink-dim);
}
/* Only the first paragraph runs on from the figure number; a second one is a
   second paragraph, which is the whole reason an author writes it. */
.demo-caption p:first-of-type { display: inline; }
.demo-caption p + p { margin-top: 9px; }
/* The plot's own furniture, quieter inside a figure than inside a result
   panel: the toolbar is still there for a reader who wants to zoom or export,
   but it is not the first thing the eye lands on. */
.demo-plot .plot-toolbar { opacity: 0.55; transition: opacity 120ms ease; }
.demo-card:hover .plot-toolbar, .demo-plot .plot-toolbar:focus-within { opacity: 1; }
.demo-images { display: flex; gap: 12px; flex-wrap: wrap; }
.demo-image { margin: 0; flex: 1 1 240px; min-width: 200px; }
.demo-image canvas { width: 100%; height: auto; display: block; border-radius: var(--radius-sm); }
.demo-image figcaption {
  font-size: 11.5px;
  color: var(--ink-faint);
  text-align: center;
  margin-top: 5px;
}
.demo-tables { display: flex; gap: 14px; flex-wrap: wrap; }
.demo-table { flex: 1 1 280px; min-width: 240px; }
.numtable td.empty-result { text-align: center; color: var(--ink-faint); font-style: italic; }
.sch-jaws line { stroke: var(--ink); stroke-width: 2.6; stroke-linecap: round; }
.sch-span { stroke: var(--ink-faint); stroke-width: 0.9; fill: none; }

/* --------------------------------------------------------- the answer form
   A problem declares the shape of its own answer, so the submission page is
   fields rather than one text box. The row grid is the important part: an
   element and an amount side by side, with the unit stated once, is what
   makes "quantify the three metals" a question a student can answer without
   first inventing a format. */
.number-field { display: flex; align-items: center; gap: 8px; }
.number-field input { flex: 0 1 160px; }
.number-field .unit,
.answer-row .unit { color: var(--ink-faint); font-size: 12px; white-space: nowrap; }

.answer-rows { display: flex; flex-direction: column; gap: 6px; }
.answer-row {
  display: grid;
  grid-template-columns: minmax(0, 1.4fr) minmax(0, 1fr) 68px;
  gap: 8px;
  align-items: center;
}
.answer-row.head {
  font-size: 11px;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--ink-faint);
}
.answer-row.head span { padding-left: 2px; }
.answer-row input, .answer-row select { width: 100%; min-width: 0; }

.check-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
  gap: 6px 14px;
}
.check-item { display: flex; align-items: center; gap: 7px; font-size: 13px; cursor: pointer; }
.check-item input { width: auto; margin: 0; }

@media (max-width: 560px) {
  .answer-row { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr) 52px; }
}

/* A caution inside an analysis panel: the measurement ran, and the number it
   produced is not worth having. Coloured rather than boxed, because it sits
   between two tables and a box would read as a separate section. */
.warn-line {
  color: var(--warn-ink);
  font-size: 12.5px;
  line-height: 1.5;
  margin: 0;
  padding-left: 10px;
  border-left: 2px solid rgba(232, 163, 61, 0.5);
}

/* ---- Course mode: the evidence box under an answer -------------------------
   A disclosure rather than a popover: `<details>` opens from the keyboard with
   no script, and what was chosen is printed in the closed state as labelled
   chips, so nothing rests on colour. Raised one step off the card so it reads
   as belonging to the question above it and not as another question. */
.evidence-box { margin-top: 10px; }
.evidence-picker {
  border: 1px solid var(--border);
  border-left: 3px solid var(--border-strong);
  border-radius: var(--radius-sm);
  background: var(--bg-inset);
}
.evidence-summary {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 4px 10px;
  min-height: 30px;
  padding: 6px 10px;
  cursor: pointer;
  list-style: none;
  font-size: 12px;
}
.evidence-summary::-webkit-details-marker { display: none; }
.evidence-summary::before {
  content: "\25b8";
  color: var(--ink-faint);
  font-size: 11px;
}
.evidence-picker[open] > .evidence-summary::before { content: "\25be"; }
.evidence-title { font-weight: 550; color: var(--ink-dim); }
.evidence-chips { display: inline-flex; flex-wrap: wrap; gap: 4px; min-width: 0; }
.evidence-chip {
  padding: 1px 8px;
  border: 1px solid var(--border-strong);
  border-radius: 100px;
  font-size: 11px;
  line-height: 1.5;
  color: var(--ink);
  background: var(--bg-raised);
}
.evidence-chip::before { content: "\2713\00a0"; color: var(--ok-ink); }
.evidence-none { font-size: 11px; color: var(--ink-faint); }
.evidence-covers { flex-basis: 100%; font-size: 11px; line-height: 1.4; color: var(--ink-faint); }
.evidence-picker[open] .evidence-covers { display: none; }
.evidence-body { padding: 4px 10px 10px; display: flex; flex-direction: column; gap: 8px; }
.evidence-body .check-grid { grid-template-columns: repeat(auto-fill, minmax(min(240px, 100%), 1fr)); }
.evidence-body .check-item { min-height: 24px; align-items: flex-start; }

/* The line beside Export: quiet, and it must not push the button around when
   the time changes. */
.answer-actions .saved-status { font-variant-numeric: tabular-nums; }
.field-error { color: var(--danger-ink); font-size: 12.5px; margin: 6px 0 0; }

@media (max-width: 560px) {
  .evidence-body .check-grid { grid-template-columns: minmax(0, 1fr); }
}

/* ------------------------------------------------------------ fold cards -- */

/* A titled section that folds to its head (src/ui/panels.js): the "At a
   glance" summary, and every analysis tool on a result page. The head is the
   title (a real button, so it works from the keyboard and announces its
   state) with its "?" and a note at the right, then the one line that says
   what the section is for, which stays visible when folded because it is
   what somebody reads to decide whether to open it.

   IT WRAPS RATHER THAN OVERLAPS. It was a grid of the title and the note, and
   on a phone the note kept its whole width while the title, held to one line,
   ran on under its "?" and under the note ("Found versus calculated" over
   "agreement threshold ±1.0 % absolute"). Now the note goes to the line below
   when the two do not fit side by side, and the title itself wraps only when
   it alone is wider than the card. */
.fold-head {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  column-gap: 12px;
  row-gap: 0;
  padding: 7px 14px 7px 8px;
}
.fold-heading { flex: 1 1 auto; min-width: 0; gap: 2px; }
.fold-toggle {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  min-height: 28px;
  padding: 3px 6px;
  margin: 0;
  background: none;
  border: 0;
  border-radius: var(--radius-sm);
  color: inherit;
  font: inherit;
  text-align: left;
  cursor: pointer;
}
.fold-toggle:hover { background: var(--bg-inset); }
.fold-toggle .fold-caret { flex: 0 0 auto; }
.fold-meta { flex: 0 1 auto; margin-left: auto; font-size: 11.5px; color: var(--ink-faint); text-align: right; }
.fold-purpose {
  flex: 1 1 100%;
  margin: 0 0 3px;
  padding-left: 22px;
  font-size: 12px;
  line-height: 1.45;
  color: var(--ink-dim);
}
.fold-card.is-folded > .fold-head { border-bottom: 0; }

/* -------------------------------------------------------- at a glance -- */

/* The figures of merit as a two-column list: names down the left in the
   dimmed ink, values at full width in the reading ink, a hairline between
   rows. On a narrow panel each name sits over its value. */
.glance {
  display: grid;
  grid-template-columns: minmax(7.5rem, 10.5rem) minmax(0, 1fr);
  margin: 0;
}
.glance-row { display: contents; }
.glance dt,
.glance dd {
  padding: 7px 0;
  border-top: 1px solid var(--border);
}
.glance dt {
  padding-right: 16px;
  font-size: 12px;
  font-weight: 600;
  line-height: 1.55;
  color: var(--ink-dim);
}
.glance dd {
  margin: 0;
  font-size: 13px;
  line-height: 1.55;
  color: var(--ink);
}
.glance-row:first-child > dt,
.glance-row:first-child > dd { border-top: 0; padding-top: 0; }
.glance-row:last-child > dt,
.glance-row:last-child > dd { padding-bottom: 0; }
.glance-list { margin: 0; padding-left: 18px; }
.glance-list li { margin-bottom: 3px; }
.glance-card .fold-meta a { color: var(--accent); }
@container panel (max-width: 560px) {
  .glance { grid-template-columns: minmax(0, 1fr); }
  .glance dd { border-top: 0; padding-top: 1px; }
  .glance dt { padding-bottom: 0; }
}

/* ------------------------------------------------ the measurement page -- */

/* ONE ALIGNED GRID OF SETTINGS. Names in one column, controls in the next,
   each value shown once — in its control, with the unit beside a number —
   and the explanation behind the "?" after the name. The controls stop at a
   comfortable width rather than running across a wide card. */
.settings-grid {
  display: grid;
  grid-template-columns: minmax(9rem, max-content) minmax(0, 26rem);
  align-items: center;
  column-gap: 20px;
  row-gap: 10px;
}
.setting-name {
  font-size: 12.5px;
  font-weight: 550;
  line-height: 1.35;
  color: var(--ink-dim);
}
.setting-name.top { align-self: start; padding-top: 4px; }
.setting-control {
  display: flex;
  align-items: center;
  gap: 8px;
  min-width: 0;
}
.setting-control > select,
.setting-control > input[type="number"] { flex: 1 1 auto; min-width: 0; }
.setting-control > input[type="number"] { max-width: 9rem; }
.setting-control > input[type="range"] { flex: 1 1 auto; }
.setting-unit,
.setting-readout {
  flex: 0 0 auto;
  font-size: 12px;
  color: var(--ink-faint);
  font-variant-numeric: tabular-nums;
}
.setting-readout { min-width: 4.5em; color: var(--ink-dim); }
.setting-control .checkgrid { flex: 1 1 auto; padding: 0; }
.setting-control.check { min-height: 30px; }
.setting-control.check input { width: 15px; height: 15px; accent-color: var(--accent); margin: 0; }
.settings-foot {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 6px 16px;
  margin-top: 12px;
  padding-top: 10px;
  border-top: 1px solid var(--border);
  font-size: 12px;
  color: var(--ink-faint);
}
.settings-foot .checkrow { padding: 0; font-size: 12.5px; color: var(--ink-dim); }
.link-btn {
  padding: 0;
  background: none;
  border: 0;
  color: var(--accent);
  font: inherit;
  text-decoration: underline;
  text-underline-offset: 2px;
  cursor: pointer;
  min-height: 24px;
}
.link-btn:hover { text-decoration-thickness: 2px; }
@container panel (max-width: 560px) {
  .settings-grid { grid-template-columns: minmax(0, 1fr); row-gap: 4px; }
  .settings-grid .setting-control { margin-bottom: 8px; }
  .setting-name.top { padding-top: 0; }
}

/* The help for every setting at once, in the dialog "About these settings". */
.settings-help { margin: 0; }
.settings-help dt { font-weight: 600; color: var(--ink); margin-top: 12px; }
.settings-help dt:first-child { margin-top: 0; }
.settings-help dd { margin: 3px 0 0; color: var(--ink-dim); line-height: 1.55; }
.settings-help .setting-default { font-weight: 400; color: var(--ink-faint); margin-left: 6px; }

/* SPECIMENS AS TILES. A picture of the portion (src/ui/sample-art.js), with
   its name, its form and how much is left on a translucent band across the
   foot. The band is dark in both themes, because it lies over a picture and
   not over the page; the white type clears 4.5:1 on it over any picture. The
   radio button is the real control, laid over the whole tile and invisible,
   so the tiles are one radio group to the keyboard and to a screen reader;
   which tile is chosen is said by a ring, a tick and the word, not by colour
   alone. */
.specimen-tiles {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(140px, 160px));
  gap: 10px;
  margin: 0;
  padding: 0;
  border: 0;
  min-width: 0;
}
.specimen-tiles > legend { padding: 0; margin-bottom: 8px; }
/* The tabs on a specimen form that also offers reference standards
   (src/ui/lab-standards.js), in the style of the header's section tabs. On a
   narrow panel they share the width, so each is a target a thumb can hit. */
.specimen-tabs {
  display: inline-flex;
  gap: 2px;
  max-width: 100%;
  margin-bottom: 12px;
  padding: 2px;
  background: var(--bg-inset);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
}
.specimen-tab {
  min-height: 28px;
  padding: 4px 14px;
  background: transparent;
  border: none;
  border-radius: 3px;
  color: var(--ink-dim);
  font-size: 12.5px;
  font-weight: 500;
  cursor: pointer;
  white-space: nowrap;
  transition: background 0.13s, color 0.13s;
}
.specimen-tab:hover { color: var(--ink); }
.specimen-tab.active { background: var(--accent-dim); color: var(--on-accent); font-weight: 600; }
@container panel (max-width: 560px) {
  .specimen-tabs { display: flex; }
  .specimen-tab { flex: 1 1 0; min-height: 32px; padding: 6px 8px; }
}
.specimen-tile {
  position: relative;
  display: block;
  aspect-ratio: 4 / 3;
  overflow: hidden;
  border: 1px solid var(--border-strong);
  border-radius: var(--radius);
  background: var(--bg-inset);
  cursor: pointer;
  transition: border-color 0.12s, box-shadow 0.12s;
}
.specimen-tile:hover { border-color: var(--ink-faint); }
.specimen-tile input {
  position: absolute;
  inset: 0;
  z-index: 2;
  width: 100%;
  height: 100%;
  margin: 0;
  opacity: 0;
  cursor: pointer;
}
.specimen-tile .tile-art,
.specimen-tile .tile-art > svg {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
}
.tile-overlay {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  padding: 5px 8px 6px;
  background: rgba(14, 15, 16, 0.7);
  color: #ffffff;
  font-size: 11.5px;
  line-height: 1.3;
}
.tile-name { display: block; font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.tile-meta { display: block; color: rgba(255, 255, 255, 0.86); }
.tile-check {
  position: absolute;
  top: 6px;
  right: 6px;
  display: none;
  padding: 1px 7px 1px 5px;
  border-radius: 100px;
  background: var(--accent-dim);
  color: var(--on-accent);
  font-size: 10.5px;
  font-weight: 600;
}
.specimen-tile:has(input:checked) {
  border-color: var(--accent);
  box-shadow: 0 0 0 1px var(--accent);
}
.specimen-tile:has(input:checked) .tile-check { display: block; }
.specimen-tile:has(input:focus-visible) {
  outline: 2px solid var(--focus);
  outline-offset: 2px;
  box-shadow: 0 0 0 1px var(--accent), 0 0 0 6px var(--focus-halo);
}
.specimen-history { margin-top: 8px; font-size: 12px; color: var(--ink-faint); line-height: 1.45; }
/* The placeholder picture: a neutral ground and the outline of the form. */
.tile-placeholder .ground { fill: var(--bg-inset); }
.tile-placeholder .shape { fill: var(--border); stroke: var(--border-strong); stroke-width: 1.2; }

/* THE QUOTE AND THE BUTTON, ALWAYS IN REACH. A bar that stays at the foot of
   the panel while the settings above it scroll, so changing a setting and
   seeing what it costs never needs a scroll, and neither does running it. */
.measure-bar {
  position: sticky;
  /* A sticky box keeps clear of its scroller's padding, and the panel has a
     generous bottom padding so the last card does not sit on the window's
     edge. The bar is let into it, to float 8 px above the edge. */
  bottom: calc(8px - var(--main-pad-bottom, 0px));
  z-index: 5;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 10px 18px;
  margin: 0 0 14px;
  padding: 10px 14px;
  background: var(--bg-raised);
  border: 1px solid var(--border-strong);
  border-radius: var(--radius);
  box-shadow: 0 -6px 18px rgba(0, 0, 0, 0.22);
}
.measure-quote {
  display: flex;
  flex-wrap: wrap;
  gap: 6px 20px;
  flex: 1 1 auto;
  min-width: 0;
  margin: 0;
}
.mq-cell { display: flex; flex-direction: column; min-width: 0; }
.mq-cell dt {
  font-size: 9.5px;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--ink-faint);
}
.mq-cell dd {
  margin: 0;
  font: 600 14px/1.3 var(--mono);
  font-variant-numeric: tabular-nums;
  color: var(--ink);
}
.mq-cell.over dd { color: var(--danger); }
.mq-cell .mq-note { font: 400 11px var(--sans); color: var(--ink-faint); margin-left: 4px; }
.mq-cell.over .mq-note { color: var(--danger-ink); }
.measure-actions { display: flex; align-items: center; gap: 8px; margin-left: auto; }
.measure-actions .btn.primary { min-height: 34px; padding: 7px 16px; }
/* On a phone the four figures share one row, with the notes under them, and
   the button takes the width below: the bar is the height of two lines of
   type and a button rather than of three rows. */
@container panel (max-width: 560px) {
  .measure-bar { padding: 8px 10px; gap: 8px; }
  .measure-quote { display: grid; grid-template-columns: repeat(4, auto); justify-content: space-between; gap: 0 10px; }
  .mq-cell dt { font-size: 8.5px; letter-spacing: 0.04em; }
  .mq-cell dd { font-size: 12.5px; }
  .mq-cell .mq-note { display: block; margin-left: 0; font-size: 10px; }
  .measure-actions { flex: 1 1 100%; }
  .measure-actions .btn.primary { flex: 1 1 auto; min-height: 32px; }
}
.settings-foot > span { display: inline-flex; align-items: center; gap: 0; }

/* The Interpretation section: how to read this kind of measurement (free),
   then the staff's notes on this one (a consultation). */
.notes-subhead {
  display: flex;
  align-items: center;
  margin: 0 0 6px;
  font-size: 12.5px;
  font-weight: 600;
  color: var(--ink);
}
.notes-subhead:not(:first-child) { margin-top: 14px; padding-top: 12px; border-top: 1px solid var(--border); }
.reading-list li { color: var(--ink-dim); }
.reading-list li strong { color: var(--ink); font-weight: 600; }

/* The settings drawn (src/ui/setting-diagrams.js): the apparatus first, at the
   centre and as wide as the card allows up to 640 px, and the settings that
   are not on the drawing in their aligned grid under it. The drawing is the
   main control of the card, so it is not pushed into a column beside a list. */
.settings-stack {
  display: flex;
  flex-direction: column;
  gap: 16px;
}
.settings-stack > * { min-width: 0; }
.settings-figure { margin: 0 auto; width: 100%; max-width: 640px; }
.settings-figure .settings-svg { display: block; width: 100%; max-width: 640px; height: auto; margin: 0 auto; }
.settings-figure figcaption { margin-top: 6px; font-size: 12px; line-height: 1.5; color: var(--ink-dim); }
.set-dial-face { fill: var(--bg-inset); stroke: var(--ink-faint); stroke-width: 1; }
.set-dial-wedge { fill: var(--t2); opacity: 0.6; }
.set-dial-tick { stroke: var(--ink-dim); stroke-width: 1.2; }
.set-crater { fill: var(--bg-inset); stroke: var(--ink-dim); stroke-width: 1; }

/* SPACING INSIDE A TOOL. A row of buttons after a field, a help line, a table
   or a paragraph keeps a clear gap above it; the intro sentence that used to
   provide one is now the head's purpose line. A call site that sets its own
   margin inline still wins. */
:is(.field, .field-grid, .field-help, .table-scroll, .settings-grid, .settings-stack, p, ul, .alert, .checkrow) + .btn-row { margin-top: 12px; }
.btn-row + :is(.field, .field-grid, .table-scroll, .settings-grid) { margin-top: 12px; }
.fold-body .field-help { margin-top: 2px; }
/* A lone control in a tool does not run the width of the card: a select of
   a dozen words stretched to a thousand pixels is hard to read across. The
   help under it still runs the full width. */
.fold-body .field > :is(select, input[type="text"], input[type="number"]) { max-width: 26rem; }
.field-label + .btn-row { margin-top: 6px; }
.set-range-axis { stroke: var(--ink-faint); stroke-width: 1.2; }
.set-range-band { fill: var(--accent-glow); stroke: var(--accent); stroke-width: 1; }
.set-line { stroke: var(--ink-dim); stroke-width: 1.2; fill: none; }
.set-scan path { stroke: var(--accent); stroke-width: 3; stroke-linecap: round; }
.set-mark { stroke: var(--t2); stroke-width: 2; }
.set-visible { fill: var(--t2); opacity: 0.55; }

/* KNOBS: parts of a drawing that set a setting (src/ui/knobs.js). The handle is
   drawn in the accent so it reads as something to take hold of; the hit area
   around it is invisible and larger than the handle, a 24-pixel target at
   least (WCAG 2.2 § 2.5.8). A knob for a setting that is set elsewhere is drawn
   at its value without the affordance. */
.knob { cursor: grab; outline: none; }
.knob:active { cursor: grabbing; }
.knob-hit { fill: transparent; stroke: none; }
.knob-handle { fill: var(--accent); stroke: var(--bg-raised); stroke-width: 1.5; }
.knob-face { fill: var(--bg-inset); stroke: var(--accent); stroke-width: 1.2; }
.knob-hand { stroke: var(--accent); stroke-width: 2; stroke-linecap: round; }
.knob-hub { fill: var(--accent); }
.knob-detent { fill: var(--ink-faint); }
.knob-detent.on { fill: var(--accent); }
.knob:hover .knob-handle, .knob:hover .knob-face { stroke-width: 2.4; }
.knob:focus-visible .knob-handle, .knob:focus-visible .knob-face {
  stroke: var(--focus); stroke-width: 3; paint-order: stroke;
}
.knob:focus-visible .knob-hit { fill: var(--accent-glow); }
.knob-static .knob-handle { fill: var(--ink-faint); }
.knob-static .knob-face { stroke: var(--ink-faint); }
.knob-static .knob-hand, .knob-static .knob-hub { stroke: var(--ink-faint); fill: var(--ink-faint); }
.knob { touch-action: none; }
/* On a methods page the drawing is above the notes on each setting. */
.method-settings > .settings-figure { margin-bottom: 4px; }
/* THE SETTINGS, MEASURING (src/ui/method-live.js): the settings not on the
   drawing as rows, then the reference standard's measurement at the settings
   chosen, with the one before the last change dashed behind it. Faded while a
   new one is being taken. */
.live-settings { padding-bottom: 12px; border-bottom: 1px solid var(--border); }
.live-controls:empty { display: none; }
.live-controls .live-rows { display: flex; flex-direction: column; gap: 14px; margin: 12px 0 0; }
.live-modes { display: flex; flex-wrap: wrap; align-items: center; gap: 6px 12px; margin-top: 12px; }
/* Which standard is on the instrument (the shelf's chips), and which of a
   measurement's graphs is shown (a segmented control over the plot). */
.live-standards { display: flex; flex-wrap: wrap; gap: 6px; margin: 8px 0 2px; }
.live-tabs { width: fit-content; max-width: 100%; margin: 0 auto 8px; }
.live-head { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 6px 12px; }
.live-title { display: flex; flex-wrap: wrap; align-items: center; gap: 4px 6px; font-size: 12.5px; color: var(--ink-dim); }
.live-title strong { color: var(--ink); font-weight: 600; }
.live-dot { width: 8px; height: 8px; border-radius: 50%; background: var(--accent); box-shadow: 0 0 0 3px var(--accent-glow); }
.live-status { margin: 4px 0 8px; min-height: 1.4em; font-size: 12px; color: var(--ink-faint); }
.live-out { transition: opacity 0.15s; }
.live-out.busy { opacity: 0.55; }
.live-plot + .live-plot { margin-top: 8px; }
/* THE MEASUREMENT IS CENTRED IN ITS CARD, as the drawing of the settings
   above it is: an image narrower than the card (an AFM height map, a
   micrograph) sits in the middle and not against the left edge. */
.live-images { display: flex; flex-wrap: wrap; align-items: flex-start; justify-content: center; gap: 10px; }
.live-image { margin: 0; flex: 1 1 220px; max-width: 360px; }
.live-image canvas { display: block; width: 100%; height: auto; border-radius: var(--radius-sm); }
.live-image.before { flex-basis: 160px; max-width: 240px; opacity: 0.75; }
.live-image figcaption { margin-top: 3px; font-size: 11.5px; color: var(--ink-faint); text-align: center; }
.live-tables { display: flex; flex-wrap: wrap; justify-content: center; gap: 12px; }
.live-table { flex: 0 1 auto; min-width: min(260px, 100%); max-width: 100%; }
.live-table-caption { margin-bottom: 4px; font-size: 11.5px; color: var(--ink-faint); }
.live-empty { margin: 0; font-size: 12.5px; color: var(--ink-dim); text-align: center; }
.method-settings > .setting-notes { border-top: 1px solid var(--border); padding-top: 10px; }
.knob-track { stroke: var(--accent); stroke-width: 2; stroke-linecap: round; opacity: 0.55; }
.knob-hit-line { stroke: transparent; stroke-width: 24; stroke-linecap: round; }
.knob-static .knob-track { stroke: var(--ink-faint); }

/* ------------------------------------------------------- visible choices -- */

/* A SHORT LIST SHOWN, NOT HIDDEN (src/ui/settings.js). Every one of these is a
   real radio button, checkbox or switch under its styling: the input is made
   invisible and laid over its label, so the arrow keys, the space bar and a
   screen reader work as they do on any form, and the focus ring is drawn on
   the label it sits in. What is chosen is said by a fill, a weight and, for a
   list, a filled dot — never by colour alone. */
.choice, .pill-toggle, .switch { position: relative; cursor: pointer; }
.choice input, .pill-toggle input, .switch input {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  margin: 0;
  opacity: 0;
  cursor: pointer;
}
.choice:has(input:focus-visible),
.pill-toggle:has(input:focus-visible),
.switch:has(input:focus-visible) .switch-track {
  outline: 2px solid var(--focus);
  outline-offset: 2px;
  box-shadow: 0 0 0 4px var(--focus-halo);
}
.choice-control { flex-direction: column; align-items: stretch; gap: 4px; }

/* Side by side: the segments of one control. */
.segmented {
  display: inline-flex;
  flex-wrap: wrap;
  align-self: flex-start;
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-sm);
  background: var(--bg-inset);
  overflow: hidden;
}
.segmented .choice {
  display: inline-flex;
  align-items: center;
  min-height: 30px;
  padding: 4px 12px;
  font-size: 12.5px;
  color: var(--ink-dim);
  border-left: 1px solid var(--border);
  white-space: nowrap;
  transition: background 0.12s, color 0.12s;
}
.segmented .choice:first-child { border-left: 0; }
.segmented .choice:hover { background: var(--bg-raised); color: var(--ink); }
.segmented .choice:has(input:checked) {
  background: var(--accent-dim);
  color: var(--on-accent);
  font-weight: 600;
}
.choice-note { font-size: 11.5px; color: var(--ink-faint); line-height: 1.4; }

/* One above another: a choice with its explanation beside it. */
.choice-list { display: flex; flex-direction: column; gap: 4px; }
.choice-list .choice {
  display: grid;
  grid-template-columns: 14px minmax(0, 1fr);
  column-gap: 8px;
  align-items: start;
  padding: 6px 10px;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--bg-inset);
  font-size: 12.5px;
  transition: border-color 0.12s, background 0.12s;
}
.choice-list .choice:hover { border-color: var(--border-strong); }
.choice-list .choice:has(input:checked) { border-color: var(--accent); background: var(--accent-glow); }
.choice-dot {
  width: 12px;
  height: 12px;
  margin-top: 2px;
  border: 1.5px solid var(--ink-faint);
  border-radius: 50%;
}
.choice:has(input:checked) .choice-dot { border-color: var(--accent); background: radial-gradient(var(--accent) 45%, transparent 50%); }
.choice-name { color: var(--ink); }
.choice:has(input:checked) .choice-name { font-weight: 600; }
.choice-gloss { grid-column: 2; color: var(--ink-faint); font-size: 11.5px; line-height: 1.4; }

/* On and off. */
.switch { display: inline-flex; align-items: center; gap: 8px; min-height: 30px; }
.switch-track {
  position: relative;
  width: 34px;
  height: 18px;
  border-radius: 100px;
  background: var(--bg-inset);
  border: 1px solid var(--border-strong);
  transition: background 0.14s, border-color 0.14s;
}
.switch-thumb {
  position: absolute;
  top: 2px;
  left: 2px;
  width: 12px;
  height: 12px;
  border-radius: 50%;
  background: var(--ink-faint);
  transition: transform 0.14s, background 0.14s;
}
.switch:has(input:checked) .switch-track { background: var(--accent-dim); border-color: var(--accent); }
.switch:has(input:checked) .switch-thumb { transform: translateX(16px); background: var(--on-accent); }
.switch-text { font-size: 12.5px; color: var(--ink-dim); min-width: 2em; }
@media (prefers-reduced-motion: reduce) {
  .switch-track, .switch-thumb, .segmented .choice, .choice-list .choice { transition: none; }
}

/* A number with a step either side. */
.stepper { gap: 0; }
.stepper > input[type="number"] {
  max-width: 6.5rem;
  border-radius: 0;
  text-align: center;
  -moz-appearance: textfield;
}
.stepper > input[type="number"]::-webkit-inner-spin-button,
.stepper > input[type="number"]::-webkit-outer-spin-button { -webkit-appearance: none; margin: 0; }
.step-btn {
  min-width: 30px;
  min-height: 30px;
  padding: 0 8px;
  border: 1px solid var(--border-strong);
  background: var(--bg-raised);
  color: var(--ink);
  font: 600 14px/1 var(--sans);
  cursor: pointer;
}
.step-btn:first-child { border-radius: var(--radius-sm) 0 0 var(--radius-sm); border-right: 0; }
.stepper > input + .step-btn { border-radius: 0 var(--radius-sm) var(--radius-sm) 0; border-left: 0; }
.step-btn:hover:not(:disabled) { background: var(--bg-inset); color: var(--accent); }
.step-btn:disabled { opacity: 0.4; cursor: not-allowed; }
.stepper > .setting-unit { margin-left: 8px; }

/* Several at once: pills that light up when chosen. */
.pill-toggles { display: flex; flex-wrap: wrap; gap: 6px; }
.pill-toggle {
  display: inline-flex;
  align-items: center;
  min-height: 28px;
  padding: 3px 11px;
  border: 1px solid var(--border-strong);
  border-radius: 100px;
  background: var(--bg-inset);
  font-size: 12.5px;
  color: var(--ink-dim);
}
.pill-toggle:has(input:checked) {
  border-color: var(--accent);
  background: var(--accent-glow);
  color: var(--ink);
  font-weight: 600;
}
.pill-toggle:has(input:checked)::before { content: '\2713'; margin-right: 5px; color: var(--accent); }
/* On a narrow panel a segmented control that will not fit on one line becomes
   an even grid of buttons rather than a row that breaks in the middle. */
@container panel (max-width: 560px) {
  .segmented {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(84px, 1fr));
    gap: 1px;
    align-self: stretch;
    background: var(--border);
  }
  .segmented .choice { justify-content: center; border-left: 0; background: var(--bg-inset); white-space: normal; text-align: center; }
}

/* A choice drawn as two parts of the apparatus (the SEM's two detectors): the
   one in use is solid and the other is faded, and either can be clicked. */
.knob-off { opacity: 0.5; }
.knob:hover .knob-off { opacity: 0.8; }
/* White on red: the chosen segment's words take the on-accent ink, not the
   ordinary ink, which is dark on the light theme. */
.segmented .choice:has(input:checked) .choice-name { color: var(--on-accent); }

/* KNOBS ARE SCARLET, AND NOTHING ELSE IN THEIR DRAWING IS. In a drawing that
   has knobs in it, everything that cannot be moved — the source, the analyzer,
   the beams, the shaded regions, the scale a handle sits on — is drawn in
   neutral ink, and every knob, its track, its detents and the words that name
   its value are drawn in the app's accent, with a dashed ring that says it can
   be taken hold of. Scarlet therefore means "you can move this". A filled
   accent that carries text uses --accent-dim, the red that holds white
   (--on-accent) at 4.5:1 or better in both themes; --accent itself is the
   lighter red of the dark theme and is only used for strokes, handles and
   words on the card. */
.knob-figure .sch-box.source rect,
.knob-figure .sch-box.detector rect { fill: var(--bg-raised); stroke: var(--ink-faint); }
.knob-figure .sch-rotor-cap, .knob-figure .sch-magnet, .knob-figure .sch-plasma,
.knob-figure .sch-region, .knob-figure .sch-region.deep, .knob-figure .set-range-band,
.knob-figure .sch-electrolyte-fill { fill: var(--ink-faint); fill-opacity: 0.16; stroke: var(--ink-faint); }
.knob-figure .sch-electrolyte-fill { stroke: none; }
.knob-figure .sch-beam line, .knob-figure .sch-beam path,
.knob-figure .sch-beam.alt line, .knob-figure .sch-beam.alt path,
.knob-figure .sch-analyzer, .knob-figure .sch-analyzer-line, .knob-figure .sch-electrolyte,
.knob-figure .sch-wave, .knob-figure .sch-ring, .knob-figure .sch-transition,
.knob-figure .sch-trace, .knob-figure .set-scan path { stroke: var(--ink-dim); }
.knob-figure .sch-head, .knob-figure .sch-head.alt,
.knob-figure .sch-dot.bright, .knob-figure .sch-dot.absorber { fill: var(--ink-dim); }
.knob-figure .sch-magnet-bore { stroke: var(--ink-faint); }

/* THE TYPE OF A DRAWING YOU OPERATE. The drawing is shown up to 640 px wide
   from a 330-unit frame, about 1.9 px to the unit, so the 8.5-unit type of the
   method pages would be 16 px here. Words on the controls and on the
   apparatus are set so that they come out at 12 to 13 px at that size.
   Where the card is narrower (a phone, a split screen) the drawing is shown
   smaller, and the type goes up by a quarter so that it stays near the size it
   had before; src/tests/setting-diagrams.test.js checks every drawing at both. */
.knob-figure { --kt: 1; }
@container panel (max-width: 560px) {
  .knob-figure { --kt: 1.25; }
}
.knob-figure .sch-label { font-size: calc(6.8px * var(--kt)); }
.knob-figure .sch-sub { font-size: calc(6.4px * var(--kt)); }
.knob-figure .sch-tag, .knob-figure .knob-seg-text { font-size: calc(6.6px * var(--kt)); }

.knob .knob-handle { fill: var(--accent); stroke: var(--bg-raised); stroke-width: 2; }
.knob .knob-face { fill: var(--accent-glow); stroke: var(--accent); stroke-width: 1.6; }
.knob .knob-hand { stroke: var(--accent); stroke-width: 2.6; }
.knob .knob-hub { fill: var(--accent); }
.knob .knob-detent { fill: var(--ink-faint); }
.knob .knob-detent.on { fill: var(--accent); }
.knob .knob-track { stroke: var(--accent); opacity: 0.6; }
.knob .sch-tag, .knob .knob-label { fill: var(--accent); }
.knob .knob-hit { fill: transparent; stroke: var(--accent); stroke-width: 1; stroke-dasharray: 3 3; opacity: 0.75; }
.knob:hover .knob-hit { fill: var(--accent-glow); opacity: 1; }
.knob:hover .knob-handle { stroke-width: 3; }
.knob:focus-visible .knob-hit { fill: var(--accent-glow); stroke: var(--focus); stroke-width: 2; stroke-dasharray: none; opacity: 1; }
.knob-hit-line { stroke: transparent; }
.knob-static .knob-hit { stroke: none; }

/* A SELECTOR ON A PART (partSelector in src/ui/setting-diagram-kit.js): a strip
   of segments attached to a box, one for each value. The chosen one is filled
   with the accent and its words are white; the others are outlined. */
.knob-seg { fill: var(--bg-raised); stroke: var(--accent); stroke-width: 1; }
.knob-seg.on { fill: var(--accent-dim); }
.knob-seg-text { fill: var(--accent); font-family: var(--sans); dominant-baseline: central; pointer-events: none; }
.knob-seg-text.on { fill: var(--on-accent); font-weight: 600; }
.knob:hover .knob-seg:not(.on) { fill: var(--accent-glow); }
.knob-static .knob-seg { stroke: var(--ink-faint); }
.knob-static .knob-seg.on { fill: var(--bg-inset); stroke: var(--ink-dim); }
.knob-static .knob-seg-text { fill: var(--ink-faint); }
.knob-static .knob-seg-text.on { fill: var(--ink); }

/* EVERY KNOB SAYS WHAT IT SETS AND AT WHAT (src/ui/setting-diagram-kit.js): its
   name in the accent, and under or beside it the value in the same accent, a
   little heavier, so that the value is the word that moves. */
.knob .knob-title, .knob .knob-value { fill: var(--accent); }
.knob .knob-value { font-weight: 600; }
/* The name of a box that carries its own selector is the apparatus's, not the knob's. */
.knob .knob-name { fill: var(--ink); }

/* A PART THAT IS ITSELF THE TOGGLE (partToggle): the box of the apparatus, lit
   with the accent when on, outlined when off; its dashed ring is its own edge. */
.knob-figure .sch-box.knob-toggle rect { fill: var(--bg-raised); stroke: var(--accent); stroke-width: 1; }
.knob-figure .sch-box.knob-toggle.on rect { fill: var(--accent-dim); }
.knob-toggle text { fill: var(--accent); font-family: var(--sans); pointer-events: none; }
.knob-toggle.on text { fill: var(--on-accent); }
.knob-toggle .knob-value { font-weight: 600; }
.knob:hover .knob-toggle:not(.on) rect { fill: var(--accent-glow); }
.knob-static .knob-toggle rect { stroke: var(--ink-faint); }
.knob-static .knob-toggle.on rect { fill: var(--bg-inset); stroke: var(--ink-dim); }
.knob-static .knob-toggle text { fill: var(--ink-faint); }
.knob-static .knob-toggle.on text { fill: var(--ink); }

/* The line that says the drawing can be handled, above it. Its mark is a
   small dashed ring, the same as the rings in the drawing. */
.knob-hint {
  display: flex;
  align-items: center;
  gap: 8px;
  margin: 0 0 6px;
  font-size: 12px;
  color: var(--ink-dim);
}
.knob-hint-dot {
  flex: 0 0 auto;
  box-sizing: border-box;
  width: 14px;
  height: 14px;
  border: 1.5px dashed var(--accent);
  border-radius: 50%;
  background: radial-gradient(var(--accent) 28%, transparent 32%);
}

/* THE PERIODIC TABLE AS A PICKER (src/ui/settings.js, src/ui/periodic.js). It
   takes the full width of the settings, eighteen columns at least 24 pixels
   each, and scrolls sideways on a phone rather than shrinking its cells below
   a target a finger can hit. An element that can be chosen is a filled cell;
   one that cannot is only its symbol, faint; the chosen one is white on the
   accent and bold. */
.settings-grid > .span-all { grid-column: 1 / -1; }
.element-picker { flex-direction: column; align-items: stretch; gap: 10px; }
.pt-scroll { overflow-x: auto; padding: 2px; }
.periodic {
  display: grid;
  grid-template-columns: repeat(18, minmax(24px, 1fr));
  grid-template-rows: repeat(7, 26px) 8px repeat(2, 26px);
  gap: 2px;
  max-width: 36rem;
  min-width: calc(18 * 26px);
}
.pt-cell {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  /* Square, as a printed periodic table is: with 2 px between them the cells
     read as one grid rather than as separate buttons. */
  border-radius: 0;
  font: 600 11px/1 var(--sans);
  color: var(--ink);
  background: var(--bg-inset);
  border: 1px solid var(--border-strong);
  cursor: pointer;
}
.pt-cell input { position: absolute; inset: 0; width: 100%; height: 100%; margin: 0; opacity: 0; cursor: pointer; }
.pt-cell:hover { border-color: var(--accent); }
.pt-cell:has(input:checked) { background: var(--accent-dim); border-color: var(--accent); color: var(--on-accent); }
.pt-cell:has(input:focus-visible) { outline: 2px solid var(--focus); outline-offset: 1px; z-index: 1; }
.pt-cell.off { background: none; border-color: transparent; color: var(--ink-faint); font-weight: 400; opacity: 0.55; cursor: default; }
.pt-levels { display: flex; flex-wrap: wrap; align-items: center; gap: 6px 12px; }
.pt-levels-name { font-size: 12.5px; font-weight: 550; color: var(--ink-dim); }

/* The microscope console under the image: the drawing of the column with its
   knobs, then any remaining choices, with the stage pad beside them. */
.scope-strip { max-width: 680px; }
.scope-controls { align-items: flex-start; gap: 12px 18px; }
.scope-controls > .settings-figure { flex: 1 1 360px; min-width: 0; }
.scope-controls > .settings-grid { flex: 1 1 100%; }

/* The knobs, small: after a measurement each setting is one labelled control
   in a row that wraps (src/ui/results.js, compactSettings). A list too long to
   lie side by side wraps as chips; the explanations behind each choice are in
   its "?" rather than under every option. */
.compact-controls { display: flex; flex-wrap: wrap; align-items: flex-start; gap: 10px 20px; }
.compact-setting { display: flex; flex-direction: column; gap: 4px; min-width: 0; max-width: 100%; }
.compact-setting .setting-name { font-size: 11.5px; padding-top: 0; }
.compact-setting .setting-control { max-width: 100%; }
.compact-setting .segmented .choice { min-height: 26px; padding: 3px 9px; font-size: 12px; }
.compact-setting .choice-note { display: none; }
.compact-setting .choice-list { flex-direction: row; flex-wrap: wrap; gap: 4px; }
.compact-setting .choice-list .choice { padding: 3px 9px; grid-template-columns: 12px auto; }
.compact-setting .choice-gloss { display: none; }
.compact-setting .stepper > input[type="number"] { max-width: 5rem; }
.compact-setting select { max-width: 14rem; }
.scope-controls > .compact-controls { flex: 1 1 360px; }

/* A choice between numbers, as a slider with a stop for each (settings.js). */
.stepped-slider { flex-direction: column; align-items: stretch; gap: 2px; }
.slider-row { display: flex; align-items: center; gap: 10px; }
.slider-row > input[type="range"] { flex: 1 1 auto; min-width: 8rem; }
.slider-value { flex: 0 0 auto; min-width: 4.5em; font-size: 12.5px; font-weight: 600; color: var(--ink); font-variant-numeric: tabular-nums; }
.slider-ticks { display: flex; justify-content: space-between; margin-right: calc(4.5em + 10px); font-size: 10.5px; color: var(--ink-faint); }
.slider-ticks .on { color: var(--accent); font-weight: 600; }
.compact-setting .stepped-slider { min-width: 14rem; }

/* ------------------------------------------------- the knobs, refined -- */

/* THE STOPS OF A KNOB ARE WRITTEN ONCE (src/ui/setting-diagram-kit.js): grey,
   and the stop it is on in the accent and bold. A knob that cannot be set here
   names its stop in the ordinary ink. */
.knob .knob-tick { fill: var(--ink-faint); }
.knob .knob-tick.on { fill: var(--accent); font-weight: 700; }
.knob-static .knob-tick { fill: var(--ink-faint); }
.knob-static .knob-tick.on { fill: var(--ink); font-weight: 700; }
/* What a value comes to (the field a magnification scans) is read, not set. */
.knob-figure .knob-note { fill: var(--ink-dim); }
/* EVERY SETTING'S NAME IS IN THE ACCENT, the name of a box that carries its own
   selector ("X-ray source", "Detector") as much as a dial's title. */
.knob .knob-name { fill: var(--accent); font-weight: 600; }
.knob-figure .knob-range-name { fill: var(--accent); font-weight: 600; }
/* A part of a knob that waits on another (the ion gun's timer with the gun
   switched off): grey, standing at the time it will come back on at, and still
   there to be turned. */
.knob .knob-idle .knob-face { fill: none; stroke: var(--ink-faint); }
.knob .knob-idle .knob-hand { stroke: var(--ink-faint); }
.knob .knob-idle .knob-hub, .knob .knob-idle .knob-detent.on { fill: var(--ink-faint); }
.knob .knob-idle .knob-tick, .knob .knob-idle .knob-title { fill: var(--ink-faint); }
.knob .knob-idle .knob-tick.on { fill: var(--ink-dim); }
.knob .knob-idle .knob-hit { stroke: var(--ink-faint); }
/* A part that is the switch, greyed where its setting does nothing (the ¹H
   decoupler on a ¹H experiment). */
.knob .knob-idle .sch-box.knob-toggle rect, .knob .knob-idle .sch-box.knob-toggle.on rect { fill: none; stroke: var(--ink-faint); stroke-dasharray: 3 2; }
.knob .knob-idle .sch-box.knob-toggle text { fill: var(--ink-faint); }
/* The volume an electron beam spreads into under the surface, in orange. */
.knob-figure .sch-region.set-volume { fill: var(--t5); fill-opacity: 0.5; stroke: var(--t5); }

/* The "?" after a knob's title (src/ui/knobs.js): the app's own button, laid
   over the room the drawing keeps for it at the end of the title. */
.knob-canvas { position: relative; max-width: 640px; margin: 0 auto; }
.knob-tips { position: absolute; inset: 0; pointer-events: none; }
.knob-tip.tip { position: absolute; width: var(--d, 14px); height: var(--d, 14px); margin: 0; transform: translate(-50%, -50%); pointer-events: auto; }
.knob-tip .tip-btn { top: -4px; left: -4px; width: calc(var(--d, 14px) + 8px); height: calc(var(--d, 14px) + 8px); font-size: calc(var(--d, 14px) * 0.68); }
/* On a lit part: a white ring and a white "?" on the accent. */
.knob-tip.on-accent .tip-btn { color: var(--on-accent); }
.knob-tip.on-accent .tip-btn::before { background: transparent; border-color: var(--on-accent); }
.knob-tip.on-accent .tip-btn:hover::before, .knob-tip.on-accent.open .tip-btn::before { background: rgba(255, 255, 255, 0.18); border-color: var(--on-accent); }
.knob-tip.on-accent .tip-btn:hover, .knob-tip.on-accent.open .tip-btn { color: var(--on-accent); }
.knob-help-slot { visibility: hidden; }

/* ------------------------------------- the same knobs, as form controls -- */

/* A CHOICE BETWEEN NAMES IS A STRIP OF SEGMENTS, drawn as the selectors on the
   drawings are: outlined in the accent, the one chosen filled with white words
   on it. */
.segmented { border-color: var(--accent); background: transparent; }
.segmented .choice { color: var(--accent); border-left-color: var(--accent); }
.segmented .choice:hover { background: var(--accent-glow); color: var(--accent); }
.segmented .choice:has(input:checked) { background: var(--accent-dim); color: var(--on-accent); }
/* ON A NARROW PANEL THE SEGMENTS COME APART into separate buttons with a gap
   between them, each outlined on its own. Kept as one box they wrapped into a
   grid whose last row was short, and the strip became a block with a ragged
   corner and segments of different shapes. */
@container panel (max-width: 560px) {
  .segmented {
    display: flex; flex-wrap: wrap; gap: 6px;
    border: 0; border-radius: 0; background: transparent; overflow: visible;
  }
  .segmented .choice {
    flex: 1 1 auto; justify-content: center;
    border: 1px solid var(--accent); border-radius: var(--radius-sm);
    background: var(--bg-raised);
  }
  .segmented .choice:first-child { border-left: 1px solid var(--accent); }
  .segmented .choice:has(input:checked) { background: var(--accent-dim); }
}
/* A list with explanations: no radio dot; the one chosen is outlined and
   marked in the accent. */
.choice-list .choice { grid-template-columns: minmax(0, 1fr); }
.choice-gloss { grid-column: 1; }
.choice-list .choice:has(input:checked) { box-shadow: inset 3px 0 0 var(--accent); }
.choice-list .choice:has(input:checked) .choice-name { color: var(--accent); }

/* A CHOICE BETWEEN NUMBERS IS A SLIDER (src/ui/settings.js): the native range
   in the accent, its stops named under it at their own places, grey, and the
   one it is on in the accent and bold. */
.stepped-slider { display: flex; flex-direction: column; align-items: stretch; }
.slider-row { display: flex; align-items: flex-start; gap: 12px; }
.slider-track { position: relative; flex: 1 1 auto; min-width: 10rem; padding-bottom: 15px; }
.slider-track > input[type="range"] { display: block; width: 100%; margin: 2px 0 0; accent-color: var(--accent-dim); }
.slider-ticks {
  position: absolute; left: 8px; right: 8px; bottom: 0; height: 14px; margin: 0;
  display: block; font-size: 10.5px; line-height: 14px; color: var(--ink-faint);
}
.slider-tick { position: absolute; top: 0; transform: translateX(-50%); white-space: nowrap; font-variant-numeric: tabular-nums; }
.slider-tick.first { transform: translateX(-8px); }
.slider-tick.last { transform: translateX(calc(-100% + 8px)); }
.slider-tick.on { color: var(--accent); font-weight: 700; }
.slider-tick.hidden { visibility: hidden; }
.slider-note { align-self: flex-end; margin-top: 3px; font-size: 11.5px; color: var(--ink-dim); white-space: nowrap; }
.stage-icon { width: 11px; height: 11px; fill: currentColor; }
.stage-btn.home .stage-icon { fill-rule: evenodd; }
.setting-fixed { font-size: 12.5px; font-weight: 600; color: var(--ink); font-variant-numeric: tabular-nums; }

/* THE SETTINGS ON A RESULT, small and aligned (src/ui/results.js,
   compactSettings): the names in a column, in the accent as on the drawings,
   each with its "?", and each control on its own line. */
.compact-controls { display: grid; grid-template-columns: max-content minmax(0, 1fr); gap: 9px 14px; align-items: center; }
.compact-controls > .setting-name { display: flex; align-items: center; padding-top: 0; font-size: 12px; font-weight: 600; color: var(--accent); }
.compact-controls > .setting-name.top { align-self: center; }
.compact-controls > .setting-control { min-width: 0; max-width: 100%; }
.compact-controls .choice-note, .compact-controls .choice-gloss { display: none; }
.compact-controls .segmented .choice { min-height: 26px; padding: 3px 10px; font-size: 12px; }
.compact-controls .choice-list {
  display: inline-flex; flex-direction: row; flex-wrap: wrap; gap: 0; align-self: flex-start;
  border: 1px solid var(--accent); border-radius: var(--radius-sm); overflow: hidden;
}
.compact-controls .choice-list .choice {
  display: inline-flex; align-items: center; min-height: 26px; padding: 3px 10px; font-size: 12px;
  border: 0; border-left: 1px solid var(--accent); border-radius: 0; background: transparent; box-shadow: none;
}
.compact-controls .choice-list .choice:first-child { border-left: 0; }
.compact-controls .choice-list .choice .choice-name { color: var(--accent); }
.compact-controls .choice-list .choice:hover { background: var(--accent-glow); }
.compact-controls .choice-list .choice:has(input:checked) { background: var(--accent-dim); }
.compact-controls .choice-list .choice:has(input:checked) .choice-name { color: var(--on-accent); }
/* On a narrow panel the strip comes apart into separate buttons, as the
   segmented controls do: wrapped as one outlined box, its parts took
   different shapes. */
@container panel (max-width: 560px) {
  /* Each name over its control, so a slider has the panel's width for its
     stops rather than what the column of names leaves it. */
  .compact-controls { grid-template-columns: minmax(0, 1fr); gap: 4px 0; }
  .compact-controls > .setting-name { margin-top: 8px; }
  .compact-controls .slider-track { min-width: 0; }
  .compact-controls .choice-list { display: flex; gap: 6px; border: 0; border-radius: 0; overflow: visible; }
  .compact-controls .choice-list .choice {
    flex: 1 1 auto; justify-content: center;
    border: 1px solid var(--accent); border-radius: var(--radius-sm);
  }
  .compact-controls .choice-list .choice:first-child { border-left: 1px solid var(--accent); }
}
.compact-controls .stepper > input[type="number"] { max-width: 5rem; }
.compact-controls select { max-width: 16rem; }
.compact-controls .slider-track { min-width: 12rem; }
/* The settings of the measurement page's form, named as the drawing's knobs are. */
.settings-grid > .setting-name { color: var(--accent); font-weight: 600; }

/* THE CONSOLE UNDER AN IMAGE: the image's settings and the stage, each a
   named group, side by side, one above the other on a narrow panel. */
.scope-strip { max-width: 640px; margin: 12px auto 0; }
.scope-controls { display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: 14px 26px; align-items: start; }
.scope-group { min-width: 0; }
.scope-head {
  display: flex; align-items: center; margin-bottom: 8px; padding-bottom: 4px;
  border-bottom: 1px solid var(--border);
  font-size: 10.5px; font-weight: 600; letter-spacing: 0.06em; text-transform: uppercase; color: var(--ink-faint);
}
@container panel (max-width: 560px) {
  .scope-controls { grid-template-columns: minmax(0, 1fr); }
  /* One above the other, the stage comes first, right under the image it
     moves; the image's other settings follow. */
  .scope-controls > .scope-group.stage { order: -1; }
}
/* The stage: arrows drawn as the knobs are, outlined in the accent, the
   position in tabular figures under them so that nothing moves as it changes. */
.stage-control { display: flex; flex-direction: column; align-items: center; gap: 6px; }
.stage-pad { grid-template-columns: repeat(3, 28px); grid-auto-rows: 28px; gap: 3px; }
.stage-btn {
  display: flex; align-items: center; justify-content: center; padding: 0;
  border: 1px solid var(--accent); border-radius: var(--radius-sm);
  background: transparent; color: var(--accent); font: 600 13px/1 var(--sans); cursor: pointer;
}
.stage-btn:hover:not(:disabled) { background: var(--accent-glow); }
.stage-btn:active:not(:disabled) { background: var(--accent-dim); color: var(--on-accent); }
.stage-btn:disabled { border-color: var(--border); color: var(--ink-faint); cursor: default; }
.stage-btn:focus-visible { outline: 2px solid var(--focus); outline-offset: 2px; box-shadow: 0 0 0 4px var(--focus-halo); }
.stage-where { min-width: 9.5em; text-align: center; font-size: 11px; color: var(--ink-dim); font-variant-numeric: tabular-nums; white-space: nowrap; }
.probe-settings { margin-top: 10px; }

/* A picker under the drawing (the periodic table of an XPS region): its name
   over it, both centred, since it is too wide for the grid's second column. */
.settings-below { display: flex; flex-direction: column; gap: 14px; }
.settings-picker { display: flex; flex-direction: column; align-items: center; gap: 8px; min-width: 0; }
.settings-picker > .setting-name { color: var(--accent); font-weight: 600; padding-top: 0; }
.settings-picker > .setting-control { align-items: center; max-width: 100%; }

/* The whole path a moving part could follow, of which only some is reachable
   (the DLS detector's arc): a neutral guide, with the knob's accent track over
   the part it can actually be set to. */
.knob-figure .set-arc-guide { fill: none; stroke: var(--ink-faint); stroke-width: 1; stroke-dasharray: 2 3; }

/* The switch for the advanced settings, in the head of the settings card. */
.advanced-toggle { display: inline-flex; align-items: center; margin-left: auto; padding-left: 12px; }
.advanced-toggle .switch { min-height: 24px; }
.advanced-toggle .switch-text { font-size: 12px; font-weight: 600; color: var(--ink-dim); }
.advanced-toggle .switch:has(input:checked) .switch-text { color: var(--accent); }

/* A KNOB THAT CANNOT BE SET HERE IS GREY IN EVERY PART: its chosen pip, its
   handle, its dial, and a part that is its own switch (lit or not). Written
   with the drawing's class so that they outrank the accent rules above, which
   left a red pip on a timer and a red flood gun among the grey knobs. */
.knob-figure .knob-static .knob-detent.on { fill: var(--ink-dim); }
.knob-figure .knob-static .knob-handle { fill: var(--ink-faint); stroke: var(--bg-raised); }
.knob-figure .knob-static .knob-face { fill: none; stroke: var(--ink-faint); }
.knob-figure .knob-static .knob-hand { stroke: var(--ink-faint); }
.knob-figure .knob-static .knob-hub { fill: var(--ink-faint); }
.knob-figure .knob-static .knob-track { stroke: var(--ink-faint); }
.knob-figure .knob-static .sch-box.knob-toggle rect { fill: var(--bg-raised); stroke: var(--ink-faint); }
.knob-figure .knob-static .sch-box.knob-toggle.on rect { fill: var(--bg-inset); stroke: var(--ink-dim); }
.knob-figure .knob-static .knob-toggle text { fill: var(--ink-dim); }
.knob-figure .knob-static .knob-toggle.on text { fill: var(--ink); }
.knob-figure .knob-static .knob-title, .knob-figure .knob-static .knob-name { fill: var(--ink-dim); }
/* A card's head breaks between its parts before it breaks inside its title:
   the title stays on one line while it and what is beside it (a note, a
   switch) fit, the part beside it goes to the line below when they do not,
   and the title wraps only when it alone is wider than the card. A title held
   to one line ran past the card's edge and was cut off on a phone. */
.card-head { flex-wrap: wrap; row-gap: 4px; }
.card-head > .card-title { min-width: 0; }
.card-head > :last-child:not(:first-child) { margin-left: auto; }
.advanced-toggle .switch-text { white-space: nowrap; }

/* The name of a range neither of whose ends can be set here, and the arc a
   diffractometer's detector sweeps, which is shown, not set: quiet. */
.knob-figure .knob-range-name.knob-static-name { fill: var(--ink-dim); }
.knob-figure .set-scan path { stroke: var(--ink-faint); stroke-width: 1.6; }

/* THE CRYSTAL SYSTEMS TO SEARCH (results.js, unitCellPanel): five tiles, each
   with its cell drawn and the rule it keeps, face up (outlined and drawn in the
   accent) to be searched, turned over (grey, "not searched") to be left out. */
.cell-systems { margin-bottom: 12px; }
.cell-systems-head { display: flex; align-items: center; margin-bottom: 8px; }
.cell-tiles { display: grid; grid-template-columns: repeat(auto-fill, minmax(112px, 1fr)); gap: 8px; }
.cell-tile { position: relative; height: 124px; padding: 0; border: 0; background: none; cursor: pointer; perspective: 600px; font: inherit; }
.cell-flip { position: absolute; inset: 0; transition: transform 0.35s ease; transform-style: preserve-3d; }
.cell-tile:not(.on) .cell-flip { transform: rotateY(180deg); }
.cell-face {
  position: absolute; inset: 0; display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 2px; padding: 6px; border-radius: var(--radius-sm); backface-visibility: hidden; text-align: center;
}
.cell-face.front { border: 1.5px solid var(--accent); background: var(--accent-glow); color: var(--accent); }
.cell-face.back { transform: rotateY(180deg); border: 1px dashed var(--border-strong); background: var(--bg-inset); color: var(--ink-faint); }
.cell-name { font-size: 12px; font-weight: 600; line-height: 1.2; }
.cell-rule { font-size: 10.5px; line-height: 1.25; }
.cell-face.front .cell-rule { color: var(--ink-dim); }
.cell-sketch { width: 84px; height: 64px; }
.cell-sketch .edge { fill: none; stroke: currentColor; stroke-width: 1.4; stroke-linejoin: round; }
.cell-sketch .hidden-edge { fill: none; stroke: currentColor; stroke-width: 1; stroke-dasharray: 2 2; opacity: 0.7; }
.cell-tile:hover .cell-face.front { background: color-mix(in srgb, var(--accent) 22%, transparent); }
.cell-tile:hover .cell-face.back { border-color: var(--accent); color: var(--ink-dim); }
.cell-tile:focus-visible { outline: none; }
.cell-tile:focus-visible .cell-face { outline: 2px solid var(--focus); outline-offset: 2px; }
@media (prefers-reduced-motion: reduce) { .cell-flip { transition: none; } }

/* THE ANALYSES OF AN IMAGE (results.js, probeToolbar): buttons with a small
   drawing of what each does over its name, outlined in the accent as the
   knobs are, and filled when armed. Particle analysis opens under them. */
.analysis-btns { display: flex; flex-wrap: wrap; gap: 8px; }
.analysis-btn {
  display: inline-flex; flex-direction: column; align-items: center; justify-content: center; gap: 4px;
  min-width: 104px; padding: 8px 10px 7px; border: 1px solid var(--accent); border-radius: var(--radius-sm);
  background: transparent; color: var(--accent); font: 600 12px/1.2 var(--sans); cursor: pointer;
}
.analysis-btn:hover { background: var(--accent-glow); }
.analysis-btn.primary { background: var(--accent-dim); color: var(--on-accent); border-color: var(--accent-dim); }
.analysis-btn:focus-visible { outline: 2px solid var(--focus); outline-offset: 2px; }
.analysis-icon { width: 44px; height: 30px; }
.analysis-icon .ln { fill: none; stroke: currentColor; stroke-width: 1.4; stroke-linecap: round; stroke-linejoin: round; }
.analysis-icon .faint { opacity: 0.5; }
.analysis-icon .dash { stroke-dasharray: 2.5 2; }
.analysis-icon .fill { fill: currentColor; stroke: none; }
.analysis-icon .soft { fill: currentColor; opacity: 0.3; stroke: none; }
.particle-section { margin-top: 14px; padding-top: 12px; border-top: 1px solid var(--border); }
.particle-section > .subhead { display: flex; align-items: center; margin: 0 0 8px; font-size: 13px; font-weight: 600; color: var(--ink); }

/* A knob waiting on another, greyed in every part: its track, handle, stops and
   segments as well as its dial (a standard's mass with no standard). */
.knob .knob-idle .knob-track { stroke: var(--ink-faint); opacity: 0.6; }
.knob .knob-idle .knob-handle { fill: var(--ink-faint); }
.knob .knob-idle .knob-detent, .knob .knob-idle .knob-detent.on { fill: var(--ink-faint); }
.knob .knob-idle .knob-seg { stroke: var(--ink-faint); fill: var(--bg-raised); }
.knob .knob-idle .knob-seg.on { fill: var(--bg-inset); stroke: var(--ink-dim); }
.knob .knob-idle .knob-seg-text, .knob .knob-idle .knob-value { fill: var(--ink-faint); }
.knob .knob-idle .knob-seg-text.on { fill: var(--ink-dim); }
/* The NMR pulse sequence under its drawing (setting-diagrams-spectra.js). */
.knob-figure .set-chan { stroke: var(--ink-faint); stroke-width: 1; }
.knob-figure .set-pulse { fill: var(--ink-dim); }
.knob-figure .set-pulse-lock { fill: var(--ink-faint); fill-opacity: 0.45; stroke: var(--ink-dim); stroke-width: 0.8; }
.knob-figure .set-pulse-dec { fill: var(--ink-faint); fill-opacity: 0.25; stroke: var(--ink-faint); stroke-width: 0.8; stroke-dasharray: 2 2; }
.knob-figure .set-fid { fill: none; stroke: var(--ink-dim); stroke-width: 1.1; }
.knob-figure .set-dim { stroke: var(--ink-faint); stroke-width: 0.8; }

/* The EPR sweep: the window chosen as a box on the field axis, the others outlined. */
.knob-figure .set-sweep-box { fill: var(--accent-glow); stroke: var(--accent); stroke-width: 1.2; }
.knob-figure .knob-static .set-sweep-box { fill: var(--bg-inset); stroke: var(--ink-faint); }
.knob-figure .set-sweep-ghost { fill: none; stroke: var(--ink-faint); stroke-width: 0.8; }

/* THE SETTINGS CARD'S HEAD ON A PHONE. Three things share it — the title, the
   instrument's model with "About these settings", and the Advanced switch —
   and in one row the middle one was squeezed to a word a line and the switch
   ran off the card. On a narrow screen the title and the switch share the
   first line and the model takes the second, the full width. */
.settings-head { flex-wrap: wrap; row-gap: 2px; }
@media (max-width: 640px) {
  .settings-head > .faint { order: 3; flex: 1 0 100%; }
  .settings-head .advanced-toggle { padding-left: 0; }
}

/* The parts of a knob that are drawn but not taken hold of: an EPR sweep's box
   and its words, between the two handles that set it. */
.knob-figure .knob-inert { pointer-events: none; }

/* The air in an infrared spectrometer's sample compartment, as molecules:
   oxygen dark, hydrogen light, carbon darkest. */
.set-atom { stroke: var(--ink-dim); stroke-width: 0.8; }
.set-atom.o { fill: var(--ink-faint); }
.set-atom.h { fill: var(--bg-raised); }
.set-atom.c { fill: var(--ink-dim); }

/* The analysis armed in the Microanalysis card: a line saying what to do on
   the image, and the analysis's settings under it, with no box round them. */
.probe-armed { margin-top: 12px; }
.probe-armed-hint { margin: 0 0 6px; font-size: 13px; font-weight: 600; color: var(--ink); }

/* The periodic-table picker on a narrow card (src/ui/settings.js): below the
   width its eighteen columns need, the table and its lines give way to the
   plain drop-down it carries. The picker's own width decides, not the
   window's, so a narrow card on a wide screen counts as narrow. */
.settings-picker { container-type: inline-size; }
.element-picker .pt-select { display: none; }
@container (max-width: 480px) {
  .element-picker .pt-select { display: block; width: 100%; max-width: 22rem; }
  .element-picker .pt-scroll, .element-picker .pt-levels { display: none; }
}

/* A stepped slider under a finger (src/ui/settings.js, fingerSteps): the track
   takes the touch, a swipe up or down scrolls the page, and the browser's own
   input is left to the keyboard. */
@media (hover: none) and (pointer: coarse) {
  .slider-track { touch-action: pan-y; }
  .slider-track > input[type="range"] { pointer-events: none; }
}

/* The dashed rings fade when the drawing has been seen or used, and come back
   when the page has been idle (src/ui/knobs.js, RING_SHOW_MS); a knob in use
   does not bring its ring back. The keyboard's focus keeps its solid outline. */
.knob-figure .knob .knob-hit { transition: opacity 0.8s ease, fill 0.15s ease; }
.knob-figure.rings-quiet .knob .knob-hit { opacity: 0; }
.knob-figure.rings-quiet .knob:focus-visible .knob-hit { opacity: 1; transition-duration: 0.15s; }
/* POINTING AT THE KEY SHOWS WHAT IT IS A KEY TO: with the pointer on (or a
   finger pressing) "Settings controls" over a drawing, every dashed ring in it
   comes back at once, faded or not. */
.knob-hint { width: fit-content; cursor: help; }
.knob-figure:has(.knob-hint:hover, .knob-hint:active) .knob .knob-hit { opacity: 1; transition-duration: 0.2s; }

/* A knob set sideways shares a finger with the page: a swipe up or down over
   it scrolls (src/ui/knobs.js). A dial keeps `touch-action: none`. */
.knob.knob-pans { touch-action: pan-y; }

/* A selector's value that cannot be chosen now (setting-diagram-kit.js,
   partSelector `unavailable`): dashed, its words struck through. */
.knob-figure .knob-seg.off { fill: none; stroke-dasharray: 2 2; }
.knob-figure .knob-seg-text.off { fill: var(--ink-faint); text-decoration: line-through; }

/* AT A GLANCE, ILLUSTRATED (src/ui/methods.js, glanceSummary; the pictures in
   src/ui/glance-kit.js). Three tiles side by side, each a small picture over
   its label and a line or two; on a narrow card they stack, the picture
   beside the words. Then what it tells you against what it cannot see, then
   the figures as chips. */
.glance-sum { display: flex; flex-direction: column; gap: 14px; container-type: inline-size; }
.glance-tiles { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 10px; }
.glance-tile {
  display: flex; flex-direction: column; align-items: center; gap: 6px; text-align: center;
  padding: 10px 10px 12px; border: 1px solid var(--border); border-radius: 8px; background: var(--bg-inset);
}
.glance-art-box { flex: 0 0 auto; }
.glance-art { display: block; width: 96px; height: 56px; overflow: visible; }
.glance-tile-label { font-size: 10.5px; font-weight: 700; letter-spacing: 0.06em; text-transform: uppercase; color: var(--ink-dim); }
.glance-tile-text { margin-top: 2px; font-size: 12.5px; line-height: 1.4; color: var(--ink); }
.glance-lists { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 8px 18px; }
.glance-list-head { font-size: 10.5px; font-weight: 700; letter-spacing: 0.06em; text-transform: uppercase; color: var(--ink-dim); }
.glance-lists ul { list-style: none; margin: 5px 0 0; padding: 0; }
.glance-lists li { position: relative; padding-left: 18px; margin-bottom: 4px; font-size: 12.5px; line-height: 1.45; color: var(--ink); }
.glance-yes li::before { content: "\2713"; position: absolute; left: 1px; font-weight: 700; color: var(--ok); }
.glance-no li::before { content: "\2715"; position: absolute; left: 1px; font-weight: 700; color: var(--ink-faint); }
.glance-figures { display: flex; flex-wrap: wrap; gap: 6px; }
.glance-figure { padding: 3px 10px; border: 1px solid var(--border); border-radius: 999px; background: var(--bg-inset); font-size: 12px; }
.glance-figure-name { color: var(--ink-dim); }
.glance-figure-value { font-weight: 600; color: var(--ink); font-variant-numeric: tabular-nums; }
.glance-detail { margin-top: 14px; }
.glance-detail > summary { cursor: pointer; font-size: 12.5px; font-weight: 600; color: var(--accent); margin-bottom: 10px; }
@container (max-width: 520px) {
  .glance-tiles { grid-template-columns: 1fr; }
  .glance-tile { flex-direction: row; text-align: left; align-items: center; gap: 12px; padding: 8px 10px; }
  .glance-lists { grid-template-columns: 1fr; }
}

/* The pictures' parts. A micrograph is dark in either theme, as one is. */
.gl-axis { fill: none; stroke: var(--ink-faint); stroke-width: 1; }
.gl-trace { fill: none; stroke: var(--accent); stroke-width: 1.5; stroke-linejoin: round; stroke-linecap: round; }
.gl-tag { font-size: 7px; fill: var(--ink-dim); }
.gl-part { fill: var(--bg-raised); stroke: var(--ink-dim); stroke-width: 1.2; }
.gl-well { fill: var(--bg); stroke: var(--ink-dim); stroke-width: 1; }
.gl-powder { fill: var(--ink-dim); }
.gl-tape { fill: var(--ink); }
.gl-hand { stroke: var(--accent); stroke-width: 1.8; stroke-linecap: round; }
.gl-hub { fill: var(--accent); }
.gl-field { fill: #26292d; }
.gl-grain { fill: #c3c8ce; }
.gl-scalebar { stroke: #ffffff; stroke-width: 1.4; }

/* More of the glance pictures' parts (src/ui/glance-kit.js). A TEM image is
   bright with dark particles, in either theme, as one is. */
.gl-guide { stroke: var(--ink-faint); stroke-width: 0.8; stroke-dasharray: 2 2; }
.gl-glass { fill: var(--bg); stroke: var(--ink-dim); stroke-width: 1; }
.gl-liquid { fill: var(--info); opacity: 0.3; }
.gl-meniscus { stroke: var(--info); stroke-width: 1; }
.gl-cap { fill: var(--accent-glow); stroke: var(--accent); stroke-width: 1; }
.gl-cup { fill: var(--bg-raised); stroke: var(--ink-dim); stroke-width: 1.2; }
.gl-laser { fill: none; stroke: var(--accent); stroke-width: 1.2; }
.gl-spot { fill: var(--accent); }
.gl-bright { fill: #e4e7ea; }
.gl-dark-grain { fill: #3a3e44; }
.gl-fringe { stroke: #9aa1a9; stroke-width: 0.8; }
.gl-ring { fill: none; stroke: #c3c8ce; stroke-width: 0.9; }
.gl-copper { fill: var(--ink-faint); stroke: var(--ink-dim); stroke-width: 1; }
.gl-opening { fill: var(--bg); }

/* THE PART A KNOB SETS, LIT WHILE IT IS SET (src/ui/knobs.js, `partOf` in the
   kit). The knobs are in the accent and the instrument in its greys, as drawn,
   and a penetration depth (an interaction volume, a signal depth) in its own
   amber. A part a live knob changes takes the accent only while that knob is
   under the pointer, has the focus or is being turned (`part-active`). */
.knob-figure [data-part-of],
.knob-figure [data-part-of] > * { transition: fill 0.15s ease, stroke 0.15s ease, opacity 0.15s ease; }
.knob-figure .part-active { filter: drop-shadow(0 0 2.5px var(--accent)); }
.knob-figure .part-active:is(rect, polygon, ellipse, circle),
.knob-figure .part-active > :is(rect, polygon, ellipse, circle):not(.sch-outline):not(.knob-hit),
.knob-figure .part-active > .sch-box > rect {
  fill: color-mix(in srgb, var(--accent) 22%, var(--bg-raised));
  stroke: var(--accent);
  stroke-width: 2;
}
.knob-figure .part-active > rect.sch-outline { stroke: var(--accent); }
.knob-figure .part-active:is(line, path),
.knob-figure .part-active > :is(line, path) { stroke: var(--accent); }
/* A beam's own colour rules name its kind (`.sch-beam.alt path`, one class
   more than the rule above), so a lit second-hue beam kept its grey shaft and
   only its head, which takes the lit marker, turned red. */
.knob-figure .sch-beam.part-active :is(line, path) { stroke: var(--accent); }
.knob-figure .part-active .sch-hatch { stroke: var(--accent); opacity: 0.6; }
.knob-figure .part-active:is(.set-volume, .sch-region) { fill: var(--accent); stroke: none; opacity: 0.75; }
.knob-figure marker.marker-lit .sch-head { fill: var(--accent); }
/* A reference mark on a scale that no knob sets (EPR's free-electron g) is in
   the ink, so that it is not read as a control's colour. */
.set-mark.neutral { stroke: var(--ink-dim); }

/* ------------------------------------------------------- a tool's table --
   The pieces every tool on a result page builds its table from (results.js,
   `toolTable`): a round + to the left that adds a row, a round × that takes
   one out, a share with a bar under it, limits typed in a row as a range. */
.tool-table td.tool-row-name { font-family: var(--sans, system-ui, sans-serif); font-weight: 600; white-space: nowrap; }
.tool-table td { white-space: nowrap; }
input.tool-limit[type] { width: 66px; padding: 3px 6px; font: inherit; text-align: right; }
.tool-range-dash { margin: 0 4px; color: var(--ink-faint); }
.kind-chip { display: inline-block; padding: 1px 8px; border-radius: 999px; font: 600 11px var(--sans, system-ui, sans-serif); white-space: nowrap; }
.kind-chip.endo { background: var(--accent-glow); color: var(--accent); }
.kind-chip.exo { background: var(--bg-inset); color: var(--ink-dim); border: 1px solid var(--border); }
.tool-plot { margin-bottom: 10px; }
.tool-hypothesis { margin-top: 14px; padding-top: 12px; border-top: 1px solid var(--border); }
.tool-fields { display: flex; flex-wrap: wrap; gap: 10px; margin-top: 6px; }
.tool-field { display: flex; flex-direction: column; gap: 4px; font-size: 11.5px; color: var(--ink-dim); }
.tool-field > input { width: 100%; }
.tool-controls { margin: 10px 0; }
input.tool-name[type] { width: 96px; padding: 3px 6px; font: inherit; }
.tool-table td.tool-row-name input.tool-name[type] { margin-left: 2px; }
.tool-table td.tool-flags { white-space: nowrap; }
.tool-table .checkrow.compact { justify-content: flex-end; }
.kind-chip.flag { background: var(--accent-glow); color: var(--accent); }
.kind-chip.quiet { background: var(--bg-inset); color: var(--ink-dim); border: 1px solid var(--border); }
.tool-verdict { display: flex; align-items: baseline; gap: 8px; margin-top: 10px; font-size: 12px; color: var(--ink-dim); }
.tool-verdict > .kind-chip { flex: 0 0 auto; }
/* A difference against its tolerance: a dot on a scale centred on zero, the
   accepted band shaded; grey inside it, the accent outside. */
.tool-table td.dev-cell { min-width: 130px; }
.dev-track { position: relative; display: block; height: 10px; margin-top: 3px; border-radius: 3px; background: var(--bg-inset); }
.dev-band { position: absolute; top: 0; bottom: 0; background: var(--border); border-radius: 2px; }
.dev-zero { position: absolute; left: 50%; top: -2px; bottom: -2px; border-left: 1px solid var(--ink-faint); }
.dev-dot { position: absolute; top: 50%; width: 8px; height: 8px; margin: -4px 0 0 -4px; border-radius: 50%; background: var(--ink-dim); }
.dev-cell.out { color: var(--accent); font-weight: 600; }
.dev-cell.out .dev-dot { background: var(--accent); }
/* A composition drawn above its table (results.js, compositionBody). */
.composition-pad { padding: 12px 14px 4px; }
.composition .sample-bar { margin-top: 0; }
.numtable th.words, .numtable td.words { text-align: left; }
.tool-plot-head { display: flex; align-items: center; flex-wrap: wrap; gap: 6px 10px; margin-bottom: 6px; }
.tool-plot-label { font-size: 11.5px; color: var(--ink-dim); }
.tool-table td.tool-ref { text-align: center; }
.tool-table td.tool-ref-value input.tool-limit[type] { width: 56px; border-color: var(--accent); }

/* ------------------------------------------------- the Rietveld refinement --
   One table is the model and the result (src/ui/results.js, rietveldPanel):
   the "+" to its left opens the ways to add a phase, and the refined numbers
   fill the same rows. Under it the background slider and Refine, and under
   those the refined pattern and the figures of the fit, drawn. */
.tool-model { display: flex; align-items: flex-start; gap: 8px; }
.tool-model > .table-scroll { flex: 1 1 auto; min-width: 0; }
.tool-add {
  flex: 0 0 auto;
  width: 30px;
  height: 30px;
  margin-top: 1px;
  border-radius: 50%;
  border: 1.5px solid var(--accent);
  background: var(--accent-glow);
  color: var(--accent);
  font: 600 20px/1 var(--sans, system-ui, sans-serif);
  cursor: pointer;
  display: grid;
  place-items: center;
  padding: 0;
}
.tool-add > span { display: block; margin-top: -2px; }
.tool-add.open { visibility: hidden; }
.tool-add:hover { background: var(--accent-dim); color: #fff; }
/* Take a phase out: a round button that can be seen, scarlet under the pointer. */
.tool-x {
  width: 24px;
  height: 24px;
  padding: 0;
  border-radius: 50%;
  border: 1.5px solid var(--border-strong);
  background: transparent;
  color: var(--ink-dim);
  font: 600 16px/1 var(--sans, system-ui, sans-serif);
  cursor: pointer;
  display: inline-grid;
  place-items: center;
}
.tool-x > span { margin-top: -2px; }
.tool-x:hover { border-color: var(--accent); color: var(--accent); background: var(--accent-glow); }
.tool-x:focus-visible, .tool-plus:focus-visible { outline: 2px solid var(--focus); outline-offset: 2px; }
/* Put a library phase in: the same + as beside the table, smaller. */
.riet-pick { width: 1%; text-align: center !important; padding-left: 6px !important; padding-right: 2px !important; }
.tool-plus {
  width: 24px;
  height: 24px;
  padding: 0;
  border-radius: 50%;
  border: 1.5px solid var(--accent);
  background: var(--accent-glow);
  color: var(--accent);
  font: 600 16px/1 var(--sans, system-ui, sans-serif);
  cursor: pointer;
  display: inline-grid;
  place-items: center;
}
.tool-plus > span { margin-top: -2px; }
.tool-plus:hover { background: var(--accent-dim); color: #fff; }
.riet-in { display: inline-block; width: 24px; color: var(--ink-faint); font-weight: 700; }
.numtable.riet-lib th, .numtable.riet-lib td { text-align: left; white-space: nowrap; vertical-align: middle; }
.numtable.riet-lib td { padding-top: 3px; padding-bottom: 3px; }
/* The primary button's twin in white: same size and weight, scarlet on white. */
.btn.btn-white { /* the ordinary .btn; the name is kept where a tool asks for it by name */ }
.tool-add:focus-visible { outline: 2px solid var(--focus); outline-offset: 2px; }
.riet-table td.riet-phase { font-family: var(--sans, system-ui, sans-serif); white-space: nowrap; }
.riet-table .riet-name { font-weight: 600; color: var(--ink); }
.riet-table .riet-sub { display: block; margin-left: 15px; font-size: 10.5px; color: var(--ink-faint); }
.riet-table td.riet-start { color: var(--ink-faint); font-style: italic; }
.tool-table td.tool-remove { width: 1%; padding-left: 2px; padding-right: 2px; }
.riet-table tr.riet-nd td { color: var(--ink-faint); }
.tool-table tr.tool-empty td {
  font-family: var(--sans, system-ui, sans-serif);
  color: var(--ink-faint);
  text-align: left;
  white-space: normal;
  padding: 12px 9px;
}
.tool-table td.share-cell { min-width: 64px; }
.share-bar { display: block; height: 4px; margin-top: 3px; border-radius: 2px; background: var(--bg-inset); overflow: hidden; }
.share-bar > span { display: block; height: 100%; border-radius: 2px; }
.riet-drawer {
  margin: 8px 0 0 38px;
  padding: 10px 12px 12px;
  border: 1px solid var(--border);
  border-left: 3px solid var(--accent);
  border-radius: var(--radius-sm);
  background: var(--bg-inset);
}
.riet-drawer-head { display: flex; align-items: center; justify-content: space-between; gap: 8px; flex-wrap: wrap; margin-bottom: 10px; }
.riet-search { display: flex; align-items: center; gap: 8px; margin-bottom: 8px; }
.riet-search > input { flex: 1 1 auto; min-width: 0; }
.riet-search > .hint { flex: 0 0 auto; }
.riet-controls { margin-top: 14px; }
.riet-controls .slider-note { white-space: normal; text-align: right; }
.riet-result, .tool-result { margin-top: 14px; padding-top: 12px; border-top: 1px solid var(--border); }

.fit-figures {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(230px, 1fr));
  gap: 14px 22px;
  margin-top: 12px;
}
.fit-figure-name { font-size: 12px; font-weight: 600; color: var(--ink-dim); margin-bottom: 8px; }
.fit-figure-name .hint { font-weight: 400; color: var(--ink-faint); }
.rbars { display: grid; gap: 5px; }
.rbar-row { display: grid; grid-template-columns: 40px 1fr 54px; align-items: center; gap: 6px; }
.rbar-name { font: 12px var(--mono); color: var(--ink-dim); }
.rbar-track { position: relative; height: 9px; border-radius: 3px; background: var(--bg-inset); }
.rbar-fill { position: absolute; left: 0; top: 0; bottom: 0; border-radius: 3px; }
.rbar-fill.rbar-wp { background: var(--t1); }
.rbar-fill.rbar-p { background: var(--t6); opacity: 0.75; }
.rbar-fill.rbar-exp { background: var(--plot-trace-muted); }
.rbar-floor { position: absolute; top: -3px; bottom: -3px; width: 0; border-left: 1.5px dashed var(--ink-faint); }
.rbar-value { font: 12px var(--mono); text-align: right; color: var(--ink); font-variant-numeric: tabular-nums; }
.rbar-axis { display: flex; justify-content: space-between; margin: 3px 60px 0 46px; font-size: 10px; color: var(--ink-faint); }
/* Three bands in the app's greys, one light grey each; the band the value is in
   is the dark one. */
.gof-gauge { position: relative; display: flex; gap: 2px; height: 10px; margin-top: 22px; }
.gof-band { height: 100%; background: var(--border); }
.gof-band.on { background: var(--ink-faint); }
.gof-band.low { flex: 0 0 calc(25% - 1px); border-radius: 3px 0 0 3px; }
.gof-band.mid { flex: 0 0 calc(35% - 1px); }
.gof-band.high { flex: 1 1 auto; border-radius: 0 3px 3px 0; }
.gof-pointer { position: absolute; top: -5px; bottom: -5px; width: 3px; margin-left: -1.5px; border-radius: 2px; background: var(--ink); }
.gof-value {
  position: absolute;
  bottom: calc(100% + 2px);
  left: 50%;
  transform: translateX(-50%);
  font: 700 12px var(--mono);
  color: var(--ink);
  white-space: nowrap;
}
.gof-axis { position: relative; height: 14px; margin-top: 3px; font-size: 10px; color: var(--ink-faint); }
.gof-axis > span { position: absolute; transform: translateX(-50%); }
.gof-axis > span:first-child { transform: none; }
.gof-verdict { margin-top: 4px; font-size: 12px; color: var(--ink-dim); }
.fit-chips { grid-column: 1 / -1; display: flex; flex-wrap: wrap; gap: 6px; }
.fit-chip { border: 1px solid var(--border); border-radius: 999px; padding: 2px 10px; font: 12px var(--mono); color: var(--ink); }
.fit-chip-name { font: 11px var(--sans, system-ui, sans-serif); color: var(--ink-faint); margin-right: 6px; }
.riet-standard { margin-top: 14px; }
.sample-bar { display: flex; height: 24px; margin-top: 4px; border: 1px solid var(--border); border-radius: var(--radius-sm); overflow: hidden; }
.sample-seg {
  flex: 1 1 0;
  min-width: 3px;
  display: flex;
  align-items: center;
  justify-content: center;
  overflow: hidden;
  font-size: 10.5px;
  font-weight: 600;
  color: var(--paper);
  white-space: nowrap;
}
.sample-seg.rest {
  color: var(--ink-dim);
  background: repeating-linear-gradient(45deg, var(--bg-inset) 0 4px, var(--border) 4px 7px);
}
.sample-bar-caption { margin: 3px 0 8px; font-size: 10.5px; color: var(--ink-faint); }
.reading-list > li.riet-note[hidden] { display: none; }
@media (max-width: 480px) {
  .riet-drawer { margin-left: 0; }
}

/* A section folded in place (app.js, problemsPanel) hides its body. */
.card-body[hidden] { display: none !important; }

/* The head of a plot that carries a choice about it (results.js, xyPanel): the
   title on the left, the segmented controls beside it, wrapping on a phone. */
.card-head.plot-head { flex-wrap: wrap; gap: 8px 14px; }
.plot-choice { display: flex; align-items: center; flex-wrap: wrap; gap: 6px 8px; margin-left: auto; }

/* THE NAME OF A CONTROL IS IN THE ACCENT on a result as on an instrument's
   settings (`.settings-grid > .setting-name`): what a drag on a plot does, a
   window's limits, a hypothesis typed in, a choice in a plot's head. Headings
   of sections and figures stay in the ink. */
.tool-plot-label,
.tool-field > span,
.tool-card label.field-label > span:first-child,
.field-label.control-name > span:first-child { color: var(--accent); font-weight: 600; }
.tool-card label.field-label > .hint, .field-label.control-name > .hint { color: var(--ink-faint); font-weight: 400; }
.field-label.control-name { display: flex; gap: 8px; align-items: baseline; margin-bottom: 3px; font-size: 12px; line-height: 1.25; }

/* The instruments' stepper, compact for a row of a table (results.js, stepField). */
.stepper.compact { display: inline-flex; align-items: stretch; vertical-align: middle; }
.stepper.compact > input[type="number"] { width: 64px; max-width: none; padding: 3px 4px; font: inherit; }
.stepper.compact > .step-btn { min-width: 24px; min-height: 26px; padding: 0 6px; font-size: 13px; }
.tool-range-pair { display: inline-flex; align-items: center; white-space: nowrap; }
.tool-range-pair > .tool-range-dash { margin: 0 6px; }

/* A photoelectron component's parameters hold a binding energy to two decimals. */
.xps-table input.tool-limit[type] { width: 76px; }

/* ------------------------------------------- a tool's controls and figures --
   (results.js, controlField, figureTiles): a control's name in the accent with
   its unit in brackets, close above its field; a fit's numbers as tiles. */
.tool-controls-row { display: flex; flex-wrap: wrap; align-items: flex-end; gap: 10px 18px; margin: 10px 0 4px; }
.control-field { display: flex; flex-direction: column; gap: 3px; min-width: 0; }
.control-field.grow { flex: 1 1 200px; }
.control-field > input { width: 100%; }
.control-name { display: inline-flex; align-items: center; gap: 4px; font-size: 12px; font-weight: 600; line-height: 1.25; color: var(--accent); }
.tool-field { gap: 3px; }
.tool-field > span { line-height: 1.25; }
.tool-empty-line { margin: 10px 0; padding: 10px 12px; border: 1px dashed var(--border); border-radius: var(--radius-sm); color: var(--ink-dim); font-size: 12.5px; }
.figure-tiles { display: grid; grid-template-columns: repeat(auto-fill, minmax(140px, 1fr)); gap: 8px; }
.figure-tile { display: flex; flex-direction: column; gap: 2px; padding: 8px 10px; border: 1px solid var(--border); border-radius: var(--radius-sm); background: var(--bg-raised); }
.figure-tile.lead { border-color: var(--ink-faint); }
.figure-name { font-size: 11px; color: var(--ink-faint); }
.figure-value { font: 600 17px var(--mono); color: var(--ink); font-variant-numeric: tabular-nums; }
.figure-tile.lead .figure-value { font-size: 20px; }
.figure-unit { font: 400 11.5px var(--sans, system-ui, sans-serif); color: var(--ink-dim); }
.figure-note { font-size: 10.5px; color: var(--ink-faint); }
/* A measured moment against the spin-only values. */
.spin-scale { margin-top: 14px; }
.spin-scale-name { display: block; font-size: 11.5px; color: var(--ink-dim); margin-bottom: 18px; }
.spin-track { position: relative; height: 8px; border-radius: 3px; background: var(--border); }
.spin-mark { position: absolute; top: -4px; bottom: -4px; width: 0; border-left: 1.5px solid var(--ink-faint); }
.spin-mark > span { position: absolute; bottom: calc(100% + 2px); left: 0; transform: translateX(-50%); font-size: 10px; color: var(--ink-faint); white-space: nowrap; }
.spin-track .gof-pointer { top: -6px; bottom: -6px; }
/* The measured value under the axis numbers, so it never sits on one. */
.spin-track .gof-value { bottom: auto; top: calc(100% + 22px); }
.spin-axis { position: relative; height: 44px; font-size: 10px; color: var(--ink-faint); }
.spin-axis > span { position: absolute; top: 14px; transform: translateX(-50%); }

/* --------------------------------------- the Mössbauer and EPR fit tables --
   (results.js, subspectrumFitPanel and centreFitPanel): one table each, in the
   photoelectron fit's pattern. The shape of an environment is a segmented
   control at the size of the row's fields; the nucleus stays a menu, having
   too many choices to show at once. */
.tool-table .segmented.in-table { display: flex; flex-wrap: nowrap; width: max-content; margin: 5px 0 0 14px; }
.tool-table .segmented.in-table .choice { min-height: 22px; padding: 1px 8px; font-size: 11.5px; }
.tool-table select.tool-select { padding: 3px 4px; min-width: 92px; max-width: 150px; font: inherit; }
.moss-table input.tool-limit[type] { width: 70px; }
.epr-table input.tool-limit[type] { width: 70px; }
.moss-table td, .epr-table td { vertical-align: top; }

/* The linear-combination fit (results.js, lcfPanel): its options under the
   window, and E0 read off the spectrum beside it, as a value and not a field. */
.tool-flags-row { display: flex; flex-wrap: wrap; gap: 6px 18px; margin: 2px 0 10px; }
.control-value { display: inline-flex; align-items: center; min-height: 30px; font-size: 12px; color: var(--ink-dim); }

/* The ligand-field panel (results.js, ligandFieldPanel): its eighteen
   configurations as two segmented rows, one per geometry. */
.lf-configs { display: grid; gap: 5px; }
.lf-config-row { display: flex; flex-wrap: wrap; align-items: center; gap: 4px 10px; }
.lf-config-geo { min-width: 170px; font-size: 11.5px; color: var(--ink-dim); }
.lf-ion { display: flex; flex-wrap: wrap; align-items: center; gap: 4px 12px; }

/* --------------------------------------------- the automatic integration --
   (results.js, automaticIntegrationCard): the integral curve drawn over the
   spectrum's silhouette, the numbers on brackets under the axis, and each
   multiplet in the table as its stick pattern. */
.autoint-figure { margin: 0; }
.autoint-figure svg { display: block; width: 100%; height: auto; max-height: 220px; overflow: visible; }
.autoint-trace { fill: none; stroke: var(--ink); stroke-width: 1.2; stroke-linejoin: round; }
.autoint-steps { fill: none; stroke: var(--ink-faint); stroke-width: 1.2; stroke-linejoin: round; opacity: 0.45; }
.autoint-axis, .autoint-tick line { stroke: var(--ink-faint); stroke-width: 1; }
.autoint-tick text, .autoint-axis-name { font: 10px var(--sans, system-ui, sans-serif); fill: var(--ink-faint); }
.autoint-solvent line { stroke: var(--ink-faint); stroke-width: 1; stroke-dasharray: 3 3; }
.autoint-solvent text { font: 9.5px var(--sans, system-ui, sans-serif); fill: var(--ink-faint); }
.autoint-label path, .autoint-label line { stroke-width: 1.2; }
.autoint-label text { font: 600 10.5px var(--mono); }
.multiplet-cell { display: inline-flex; align-items: center; gap: 6px; vertical-align: middle; }
.multiplet-icon { flex: 0 0 auto; display: block; }

/* A peak on the solvent, its water or the reference (results.js, peaksPanel):
   set back and named. */
.numtable tr.solvent-row td { color: var(--ink-faint); }
.kind-chip.solvent-chip { margin-left: 6px; padding: 0 6px; font-size: 10.5px; }

/* ----------------------------------------------- the assignment helper --
   (results.js, assignmentHelperPanel): the kinds of group, each folded to its
   name; in each, a tile per subtype that turns over when picked, as the
   crystal systems of the unit-cell search do — grey face up, the group drawn
   with the atoms or bond in question picked out; turned, its color, which is
   the color its range takes on the spectrum. */
.assign-head { display: flex; align-items: center; flex-wrap: wrap; gap: 6px 12px; margin-bottom: 6px; }
.assign-clear { margin-left: auto; }
.assign-group { border-top: 1px solid var(--border); }
.assign-group:last-child { border-bottom: 1px solid var(--border); }
.assign-group-head {
  display: flex; align-items: center; gap: 8px; width: 100%; padding: 7px 2px;
  border: 0; background: none; color: var(--ink); font: inherit; text-align: left; cursor: pointer;
}
.assign-group-head:hover .assign-group-title { color: var(--accent); }
.assign-group-head:focus-visible { outline: 2px solid var(--focus, var(--accent)); outline-offset: 1px; }
.assign-chevron { display: inline-block; width: 10px; font-size: 10px; color: var(--ink-faint); transition: transform 0.15s ease; }
.assign-group-head[aria-expanded="true"] .assign-chevron { transform: rotate(90deg); }
.assign-group-title { font-size: 12.5px; font-weight: 600; }
.assign-group-n { font: 10.5px var(--mono); color: var(--ink-faint); }
.assign-count { margin-left: auto; font-size: 11px; color: var(--accent); }
.assign-tiles { display: grid; grid-template-columns: repeat(auto-fill, minmax(136px, 1fr)); gap: 8px; padding: 2px 0 12px; }
.assign-tiles[hidden] { display: none; }
.assign-tile { position: relative; height: 112px; padding: 0; border: 0; background: none; cursor: pointer; perspective: 600px; font: inherit; color: var(--ink); }
/* At rest the tile is flat and one face is shown; a pick turns it edge-on and
   back (results.js, paintTile), the face changing at the edge. */
.assign-flip { position: absolute; inset: 0; }
.assign-tile.turning .assign-flip { animation: assign-turn 0.36s ease-in-out; }
@keyframes assign-turn { 0% { transform: rotateY(0deg); } 50% { transform: rotateY(90deg); } 100% { transform: rotateY(0deg); } }
.assign-face {
  position: absolute; inset: 0; display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 3px; padding: 6px 8px; border-radius: var(--radius-sm); text-align: center; overflow: hidden;
}
.assign-face.off { border: 1px solid var(--border); background: var(--bg-raised); color: var(--ink); }
.assign-face.on {
  visibility: hidden;
  border: 1.5px solid var(--pick, var(--accent));
  background: color-mix(in srgb, var(--pick, var(--accent)) 12%, var(--bg-raised));
  color: var(--pick, var(--accent));
}
.assign-tile.face-on .assign-face.on { visibility: visible; }
.assign-tile.face-on .assign-face.off { visibility: hidden; }
.assign-tile:hover .assign-face.off { border-color: var(--ink-faint); }
.assign-tile:focus-visible { outline: none; }
.assign-tile:focus-visible .assign-face { outline: 2px solid var(--focus, var(--accent)); outline-offset: 2px; }
.assign-tile.outside .assign-face.off { opacity: 0.55; }
.assign-picture { display: flex; align-items: center; justify-content: center; min-height: 48px; }
.assign-name { font-size: 12px; font-weight: 600; line-height: 1.25; }
.assign-range { font: 11px var(--mono); color: var(--ink-dim); }
.assign-face.on .assign-range { color: inherit; font-weight: 600; }
.assign-note {
  font-size: 10px; line-height: 1.25; color: var(--ink-faint);
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
}
.assign-face.on .assign-note { color: var(--ink-dim); }
.assign-strip { position: relative; display: block; align-self: stretch; height: 5px; margin-top: 2px; border-radius: 2px; background: var(--bg-inset); }
.assign-strip > span { position: absolute; top: 0; bottom: 0; border-radius: 2px; background: var(--ink-faint); }
.assign-strip > span.line { width: 2px; margin-left: -1px; }
@media (prefers-reduced-motion: reduce) { .assign-tile.turning .assign-flip { animation: none; } }
/* A structure fragment (fragment-draw.js): the group in a quiet ink, the atom
   or the bond in question in the face's color and heavier. */
.frag-svg { display: block; overflow: visible; }
.frag-bond { stroke: var(--ink-faint); stroke-width: 1.3; stroke-linecap: round; }
.frag-bond.mark { stroke: currentColor; stroke-width: 2.2; }
.frag-label { font: 500 12px var(--sans, system-ui, sans-serif); fill: var(--ink-dim); }
.frag-label .mark, .frag-label.mark { fill: currentColor; font-weight: 700; }
.frag-dot { fill: currentColor; }
.assign-face.off .frag-bond.mark, .assign-face.off .frag-dot { stroke: var(--ink); }
.assign-face.off .frag-label .mark, .assign-face.off .frag-label.mark, .assign-face.off .frag-dot { fill: var(--ink); }

/* -------------------------------------------------- combustion analysis --
   (results.js, combustionCard): the detector trace drawn from the percentages
   found, an element per tile with each determination against ±0.4 %, and the
   composition bar. */
.combustion-trace { margin: 0 0 10px; }
.combustion-trace svg { display: block; width: 100%; height: auto; max-height: 200px; overflow: visible; }
.combustion-line { fill: none; stroke: var(--ink); stroke-width: 1.4; stroke-linejoin: round; }
.combustion-label text { font: 600 10.5px var(--mono); }
.combustion-label text.combustion-gas { font: 600 11px var(--sans, system-ui, sans-serif); fill: var(--ink); }
.element-tiles { display: grid; grid-template-columns: repeat(auto-fit, minmax(132px, 1fr)); gap: 8px; }
.element-tile {
  display: flex;
  flex-direction: column;
  gap: 2px;
  padding: 7px 10px 8px;
  border: 1px solid var(--border);
  border-top-width: 3px;
  border-radius: var(--radius-sm);
  background: var(--bg-raised);
}
.element-tile-head { display: flex; align-items: baseline; justify-content: space-between; gap: 6px; }
.element-symbol { font: 700 20px var(--sans, system-ui, sans-serif); line-height: 1.1; }
.element-gas { font-size: 10.5px; color: var(--ink-faint); }
.rep-track { position: relative; display: block; height: 10px; margin-top: 6px; border-radius: 3px; background: var(--bg-inset); }
.rep-band { position: absolute; top: 0; bottom: 0; background: var(--border); border-radius: 2px; }
.rep-mean { position: absolute; top: -2px; bottom: -2px; border-left: 1px solid var(--ink-faint); }
.rep-dot { position: absolute; top: 50%; width: 7px; height: 7px; margin: -3.5px 0 0 -3.5px; border-radius: 50%; border: 1px solid var(--bg-raised); }
.rep-caption { font-size: 10px; color: var(--ink-faint); }
.combustion-bar { margin-top: 12px; }

/* Found versus calculated (results.js, formulaCheckPanel): each formula's
   windows against the found values, and a row per formula. */
.window-figure { display: grid; gap: 4px; margin-bottom: 12px; }
.window-row { display: grid; grid-template-columns: 26px 1fr 84px; align-items: center; gap: 8px; }
.window-el { font: 700 13px var(--sans, system-ui, sans-serif); color: var(--ink); }
.window-track { position: relative; min-height: 30px; border-bottom: 1px solid var(--border); }
.window-lane { position: absolute; left: 0; right: 0; height: 7px; }
.window-band { position: absolute; top: 0; bottom: 0; border-radius: 2px; opacity: 0.28; }
.window-band.in { opacity: 0.55; }
.window-dot { position: absolute; top: 50%; width: 7px; height: 7px; margin: -3.5px 0 0 -3.5px; border-radius: 50%; }
.window-found { position: absolute; top: 0; bottom: -3px; border-left: 2px solid var(--ink); }
.window-found-value { position: absolute; bottom: -15px; transform: translateX(-50%); font: 600 10px var(--mono); color: var(--ink); white-space: nowrap; }
.window-scale { font: 10px var(--mono); color: var(--ink-faint); text-align: right; }
.window-row + .window-row { margin-top: 12px; }
.window-key { display: flex; flex-wrap: wrap; gap: 4px 16px; margin-top: 14px; font-size: 10.5px; color: var(--ink-faint); }
.window-key > span { display: inline-flex; align-items: center; gap: 6px; }
.window-found-key { display: inline-block; width: 0; height: 12px; border-left: 2px solid var(--ink); }
.window-band-key { display: inline-block; width: 22px; height: 7px; border-radius: 2px; background: var(--ink-faint); opacity: 0.45; }
.formula-table tr.found-row td { font-weight: 600; background: var(--bg-inset); }
.formula-table input.formula-input[type] { width: 120px; }
.formula-table td.dev-cell { min-width: 84px; }
.dev-calc { display: block; font-size: 10px; color: var(--ink-faint); font-weight: 400; }

/* Which sample a result came from (results.js, renderResult), under the title. */
.page-sample { display: flex; flex-wrap: wrap; align-items: baseline; gap: 4px 8px; margin: 2px 0 0; font-size: 13.5px; color: var(--ink); }
.page-sample-name { font-size: 10.5px; font-weight: 600; letter-spacing: 0.06em; text-transform: uppercase; color: var(--ink-faint); }

/* ------------------------------------------- found versus calculated, a mixture --
   One row per component (a formula and its moles, with what it alone would give
   faint under each element), then the mixture's composition, the found values
   and the difference as a dot on its scale (results.js, `formulaCheckPanel`). */
.formula-table td.part-cell { color: var(--ink-faint); }
.formula-table td.amount-cell { white-space: nowrap; }
.formula-table tr.calc-row td { font-weight: 600; border-top: 2px solid var(--border); }
.formula-table tr.delta-row td { vertical-align: top; }
.formula-table tr.delta-row td.tool-row-name { white-space: normal; }
.formula-table input.formula-input[type] { width: 104px; }
.formula-table td.amount-cell .stepper.compact > input[type="number"] { width: 44px; }

/* A composition drawn in a methods page's live panel is the result page's own: a
   combustion analysis takes the width of the panel, with the measurement before
   the change under it; its card is the panel's, not a card within a card. */
.live-table.wide { flex: 1 1 100%; }
.live-table .card { margin-bottom: 0; box-shadow: none; }
.live-table .composition-pad { padding-left: 0; padding-right: 0; }

/* A composition in a methods page's live panel (a bar over a table) fills the
   panel's width, as wide as the settings above it. With the measurement before
   the change beside it the pair fills the width, each at least 340px; where
   they do not both fit they stack, each the full width. A table that is not a
   composition (a peak list) keeps its own width, centred. */
.live-table.fills { flex: 1 1 340px; }
