/*!
 * SELLC Physics Lab — lab.css
 * Santini Engineering LLC · santinieng.com
 *
 * The ONLY stylesheet for /lab/. Standalone by design (www/styles.css is not
 * loaded here) but it reuses that file's custom-property names, header /
 * container / footer structure and accessibility posture, so a lab page reads
 * as the same site.
 *
 * Sim pages write markup against these class names and ship NO CSS of their
 * own. Where a name could reasonably be guessed two ways, both are accepted —
 * the aliases are listed beside each canonical name below.
 *
 * ─────────────────────────────────────────────────────────────────────────
 *  PAGE SKELETON
 * ─────────────────────────────────────────────────────────────────────────
 *   <a class="skip-link" href="#main">Skip to content</a>
 *   <header class="lab-header">
 *     <p class="lab-eyebrow">SELLC Physics Lab</p>
 *     <h1>Ideal Gas</h1>
 *     <p class="lab-subtitle">…</p>
 *     <a class="lab-back" href="/lab/">All simulations</a>
 *   </header>
 *   <main class="lab-main" id="main">
 *     <section class="sim-stage">   canvas + .transport + .readouts
 *     <aside   class="sim-controls"> .control-group > .control rows
 *     <section class="sim-plots">    .plot-card > canvas.plot-canvas
 *     <section class="sim-theory">   .eq blocks, .try-list, .assumptions
 *   </main>
 *   <footer class="lab-footer">
 *
 * ─────────────────────────────────────────────────────────────────────────
 *  CLASS INDEX  (canonical — aliases)
 * ─────────────────────────────────────────────────────────────────────────
 *   .lab-header      .lab-eyebrow .lab-subtitle .lab-back .lab-nav .lab-brand
 *   .page-jumps      .footer-nav
 *   .lab-main        grid areas: stage / controls / plots / theory
 *   .sim-stage       .stage-head .stage-figure  canvas
 *   .transport       — .sim-transport .transport-bar .stage-transport
 *                    .btn .btn-primary .btn-ghost .transport-speed
 *   .readouts        — .readout-grid .sim-readouts .stage-readouts (also <dl>)
 *                    .readout > .readout-label + .readout-value (also dt/dd)
 *   .sim-controls    .control-group (— .controls-group, <fieldset>)
 *                    .control (— .control-row .slider-row .field)
 *                    .control-value (— output, .value)
 *                    .control-hint (— .hint .help)
 *   .sim-plots       .plot-card (— .plot) canvas.plot-canvas .plot-caption
 *   .sim-theory      .theory-grid .theory-col .eq (— .equation .equation-block)
 *                    .eq-where .eq-label .try-list .assumptions .callout
 *   .sim-cards       — .sim-card-grid .lab-cards  (landing page)
 *                    .sim-card > .sim-card-tag .sim-card-title .sim-card-body
 *                                .sim-card-action .sim-card-figure
 *   utilities        .visually-hidden .note .warn .tag .kbd .num .muted
 *                    .table-scroll .stack .row
 *
 * Canvas heights come from two inherited custom properties, so a page that
 * needs a taller stage sets one on any ancestor and touches no CSS file:
 *   <section class="sim-stage" style="--stage-h: 520px">
 *   <section class="sim-plots" style="--plot-h: 320px">
 */

/* ═══════════════════════════════════════════════════════════════════════
   1. Design tokens — same names as www/styles.css, plus lab additions
   ═══════════════════════════════════════════════════════════════════════ */

:root {
  /* Brand (identical values to www/styles.css) */
  --primary: #003366;
  --primary-light: #004e8c;
  --secondary: #ff8800;        /* decorative ONLY — fails AA as text */
  --secondary-text: #a85800;   /* accessible orange for text and links */
  --secondary-deep: #8f4a00;   /* darker orange for small text on a tinted
                                  ground: 5.76:1 on the .tag--accent fill,
                                  where --secondary-text measures 4.46:1 */
  --light-gray: #f5f5f5;
  --dark-gray: #333;
  --white: #fff;
  --shadow: 0 4px 12px rgba(0, 0, 0, 0.1);

  /* Lab additions */
  --muted: #5a6470;            /* 5.9:1 on white, 5.5:1 on --light-gray */
  --line: #e3e7ec;
  --line-strong: #c8d2dd;
  --surface: #fff;
  --surface-sunken: #f7f9fb;
  --ok: #2e7d32;
  --warn-bg: #fff8e6;
  --warn-line: #f0c674;
  --warn-text: #7a5200;

  --radius: 12px;
  --radius-sm: 8px;
  --gap: 20px;

  --font-body: 'Roboto', Arial, Helvetica, sans-serif;
  --font-math: 'Cambria Math', Cambria, 'Palatino Linotype', Palatino, Georgia,
    'Times New Roman', serif;
  --font-mono: 'SFMono-Regular', Menlo, Consolas, 'Liberation Mono', monospace;

  /* Canvas heights — a sim may override per page with an inline style. */
  --stage-h: clamp(260px, 46vh, 460px);
  --plot-h: 260px;

  /* Plot palette — read by js/core/plot.js off the canvas element. MUST stay
     in step with DEFAULT_COLORS there.
     Every entry clears 4.5:1 on the white plot ground (min 5.18:1), and the
     luminances are spread as far as that allows: consecutive entries differ by
     >= 1.33:1 in greyscale, worst pair anywhere 1.10:1. That is still not a
     readable difference, and no six-colour AA-compliant set does much better
     (~1.13:1 is the ceiling) — so plot.js also cycles dash patterns for line
     series and marker shapes for scatter series. Hue is never the only carrier
     of meaning. */
  --plot-canvas-bg: transparent;
  --plot-bg: #fff;
  --plot-grid: #e6eaf0;
  --plot-grid-strong: #aab6c4;
  /* Log-axis minors only, and it MUST stay paler than --plot-grid. A minor
     gridline is the unlabelled 2..9 between two decades; if it reads as
     strongly as the labelled decade beside it, eight subordinate lines become
     eight apparent decades and the axis is misread by a factor of ten. On
     white: minor 1.10:1, grid 1.21:1, strong 2.06:1 — each step visible, none
     of them competing with the data. tools/lab-css-check.mjs asserts both the
     ordering and the fact that plot.js can find this token at all. */
  --plot-grid-minor: #f1f4f8;
  --plot-axis: #6b7480;
  --plot-text: #454d57;        /* 8.6:1 on white */
  --plot-label: #003366;
  --plot-series-1: #003366;   /* navy    Y 0.033 */
  --plot-series-2: #c2410c;   /* orange  Y 0.153 */
  --plot-series-3: #1b5e20;   /* green   Y 0.083 */
  --plot-series-4: #0369a1;   /* blue    Y 0.127 */
  --plot-series-5: #6a1b9a;   /* purple  Y 0.062 */
  --plot-series-6: #b02a37;   /* crimson Y 0.112 */
}

/* ═══════════════════════════════════════════════════════════════════════
   2. Base
   ═══════════════════════════════════════════════════════════════════════ */

*,
*::before,
*::after {
  box-sizing: border-box;
}

html {
  /* Belt and braces: a lab page must never scroll sideways. Individual wide
     children (equations, tables, plots) scroll inside themselves instead. */
  overflow-x: hidden;
  scroll-behavior: smooth;
}

body {
  margin: 0;
  padding: 0;
  overflow-x: hidden;
  font-family: var(--font-body);
  font-size: 16px;
  line-height: 1.6;
  color: var(--dark-gray);
  background:
    radial-gradient(1200px 600px at 0% -10%, rgba(0, 51, 102, 0.06), transparent 60%),
    radial-gradient(900px 500px at 100% -5%, rgba(255, 136, 0, 0.05), transparent 55%),
    var(--light-gray);
  -webkit-text-size-adjust: 100%;
}

img,
svg,
canvas,
video {
  max-width: 100%;
}

h1,
h2,
h3,
h4 {
  color: var(--primary);
  line-height: 1.25;
  margin: 0 0 0.5em;
  overflow-wrap: break-word;
}

h1 { font-size: clamp(1.6rem, 1.1rem + 2.2vw, 2.4rem); font-weight: 700; letter-spacing: 0.3px; }
h2 { font-size: clamp(1.25rem, 1.05rem + 0.9vw, 1.6rem); font-weight: 700; }
h3 { font-size: 1.06rem; font-weight: 700; }
h4 { font-size: 0.95rem; font-weight: 700; }

p { margin: 0 0 1em; }
p:last-child { margin-bottom: 0; }

a {
  color: var(--primary);
  text-decoration: none;
  border-bottom: 1px dotted var(--primary);
  transition: color 0.2s ease, border-color 0.2s ease;
}

a:hover {
  color: var(--secondary-text);
  border-bottom: 1px solid var(--secondary-text);
}

/* Visible keyboard focus everywhere (WCAG 2.4.7). The UA default is nearly
   invisible on the rounded buttons over dark backgrounds. */
a:focus-visible,
button:focus-visible,
input:focus-visible,
select:focus-visible,
summary:focus-visible,
[tabindex]:focus-visible,
canvas:focus-visible {
  outline: 3px solid var(--secondary-text);
  outline-offset: 2px;
  border-radius: 3px;
}

/* Fallback for engines without :focus-visible. */
input[type="range"]:focus,
input[type="checkbox"]:focus,
select:focus {
  outline: 3px solid var(--secondary-text);
  outline-offset: 2px;
}

input[type="range"]:focus:not(:focus-visible),
input[type="checkbox"]:focus:not(:focus-visible),
select:focus:not(:focus-visible) {
  outline: none;
}

hr {
  border: 0;
  border-top: 1px solid var(--line);
  margin: 22px 0;
}

code,
.mono {
  font-family: var(--font-mono);
  font-size: 0.88em;
  background: var(--surface-sunken);
  border: 1px solid var(--line);
  border-radius: 4px;
  padding: 1px 5px;
}

sup,
sub {
  /* Keep superscripts from opening up the line box. */
  font-size: 0.72em;
  line-height: 0;
  position: relative;
  vertical-align: baseline;
}

sup { top: -0.5em; }
sub { bottom: -0.25em; }

/* ── Skip link — same behaviour as www/intranet ────────────────────────── */

.skip-link {
  position: absolute;
  left: 8px;
  top: -48px;
  z-index: 1000;
  padding: 10px 18px;
  background: var(--primary);
  color: #fff;
  border: 0;
  border-radius: 0 0 8px 8px;
  text-decoration: none;
  font-weight: 600;
  transition: top 0.2s ease;
}

.skip-link:focus,
.skip-link:focus-visible {
  top: 0;
  color: #fff;
  outline: 2px solid var(--secondary);
  outline-offset: 2px;
}

.visually-hidden,
.sr-only {
  position: absolute !important;
  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;
}

/* ═══════════════════════════════════════════════════════════════════════
   3. Shell — container, header, footer
   ═══════════════════════════════════════════════════════════════════════ */

.container,
.lab-container {
  max-width: 1240px;
  margin: 26px auto;
  padding: 26px;
  background: var(--white);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
}

/* ── Header ───────────────────────────────────────────────────────────── */

/* Shown only when scripting is off. Every sim computes its results in the
   browser, so a scripted page and an unscripted one are not the same page with
   less polish — the unscripted one has no numbers at all, and should say so
   rather than present empty canvases and em-dashes as if they were results. */
.lab-noscript {
  margin: 0 0 24px;
  padding: 14px 16px;
  border: 1px solid var(--warn-line);
  border-left-width: 4px;
  border-radius: var(--radius-sm);
  background: var(--warn-bg);
  color: var(--warn-text);
  font-size: 0.92rem;
  line-height: 1.6;
}

.lab-header {
  position: relative;
  overflow: hidden;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px 22px;
  padding: 28px 30px;
  margin-bottom: 24px;
  color: #fff;
  background: linear-gradient(135deg, var(--primary) 0%, var(--primary-light) 60%, #00599e 100%);
  border-radius: var(--radius) var(--radius) 0 0;
}

/* Faint engineering-paper texture, matching the intranet header. */
.lab-header::after {
  content: "";
  position: absolute;
  inset: 0;
  pointer-events: none;
  background-image:
    radial-gradient(circle at 12% 120%, rgba(255, 136, 0, 0.18), transparent 45%),
    repeating-linear-gradient(45deg,
      rgba(255, 255, 255, 0.045) 0,
      rgba(255, 255, 255, 0.045) 1px,
      transparent 1px,
      transparent 14px);
}

.lab-header > * {
  position: relative;
  z-index: 1;
}

.lab-header h1,
.lab-header h2 {
  color: #fff;
  margin: 0;
  /* Shares its row with the back link; the eyebrow and subtitle each take a
     full row of their own (see below). */
  flex: 1 1 auto;
  min-width: 0;
}

/* `order` pins the three header rows no matter what sequence the sim author
   writes the children in: eyebrow, then title + back link, then subtitle.
   Only one header child is ever focusable, so reordering cannot desynchronise
   the visual and focus orders (WCAG 2.4.3). */
.lab-eyebrow {
  order: -1;
  flex: 0 0 100%;
  margin: 0;
  font-size: 0.72rem;
  font-weight: 700;
  letter-spacing: 1.4px;
  text-transform: uppercase;
  color: rgba(255, 255, 255, 0.85);
}

/* flex-basis 100% with NO max-width. A max-width would clamp the hypothetical
   main size, so whether the subtitle wrapped onto its own line would depend on
   how long the sim author's sentence happens to be. This guarantees the row. */
.lab-subtitle {
  order: 1;
  flex: 0 0 100%;
  margin: 0;
  font-size: 0.98rem;
  font-weight: 300;
  line-height: 1.5;
  color: rgba(255, 255, 255, 0.92);
}

.lab-brand {
  display: inline-flex;
  align-items: center;
  gap: 12px;
  color: #fff;
  border: 0;
  font-weight: 700;
  letter-spacing: 0.4px;
}

.lab-brand:hover {
  color: #fff;
  border: 0;
}

.lab-logo {
  width: 44px;
  height: 44px;
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: 12px;
  font-size: 1rem;
  font-weight: 700;
  letter-spacing: 1.2px;
  background: linear-gradient(135deg, rgba(255, 255, 255, 0.22), rgba(255, 255, 255, 0.06));
  border: 1.5px solid rgba(255, 255, 255, 0.35);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.25);
}

/* Back link / header nav — pushed to the trailing edge on wide screens. */
.lab-back,
.lab-nav {
  margin-left: auto;
  flex: 0 0 auto;
}

.lab-back,
.lab-nav a {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  min-height: 44px;
  max-width: 100%;
  padding: 9px 16px;
  border-radius: 999px;
  border: 1px solid rgba(255, 255, 255, 0.32);
  background: rgba(255, 255, 255, 0.1);
  color: #fff;
  font-size: 0.78rem;
  font-weight: 600;
  letter-spacing: 0.6px;
  text-transform: uppercase;
  white-space: normal;
  text-align: center;
}

.lab-nav {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
}

/* Hover DARKENS the pill rather than lightening it. A 22 % white wash over the
   header gradient's light end (#00599e, plus the ::after texture) leaves the
   white label at 4.06:1 — 3.74:1 where the orange radial reaches — under the
   4.5:1 floor for 0.78 rem text. The dark wash measures 8.94:1 at that same
   worst point, and the brighter border still signals the hover. */
.lab-back:hover,
.lab-nav a:hover {
  background: rgba(0, 26, 51, 0.34);
  border-color: rgba(255, 255, 255, 0.7);
  color: #fff;
}

.lab-back:focus-visible,
.lab-nav a:focus-visible,
.footer-nav a:focus-visible {
  outline: 3px solid #fff;
  outline-offset: 2px;
}

/* Section links stay outside the main grid and work without JavaScript. */
.page-jumps {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px 12px;
  margin: 0 0 24px;
  padding: 12px 16px;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
}

.page-jumps > span {
  color: var(--muted);
  font-size: 0.85rem;
  margin-right: 4px;
}

.page-jumps a {
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  padding: 8px 12px;
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  font-weight: 600;
}

.page-jumps a:hover {
  background: var(--surface-sunken);
  border-color: var(--primary);
}

.lab-main > [id] {
  scroll-margin-top: 20px;
}

.footer-nav {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 8px 20px;
  margin-bottom: 12px;
}

.footer-nav a {
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  padding: 8px 0;
}

/* ── Footer ───────────────────────────────────────────────────────────── */

footer,
.lab-footer {
  margin-top: 28px;
  padding: 22px 26px;
  text-align: center;
  background: var(--primary);
  color: #fff;
  border-radius: 0 0 var(--radius) var(--radius);
  font-size: 0.88rem;
}

footer p,
.lab-footer p {
  margin: 0;
}

footer a,
.lab-footer a {
  color: #fff;
  border-bottom-color: rgba(255, 255, 255, 0.5);
}

footer a:hover,
.lab-footer a:hover {
  color: #fff;
  border-bottom-color: #fff;
}

/* ═══════════════════════════════════════════════════════════════════════
   4. Main grid — stage + controls side by side at >= 1000px
   ═══════════════════════════════════════════════════════════════════════ */

.lab-main {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: var(--gap);
  align-items: start;
}

/* min-width:0 is what actually stops a canvas from forcing the page wide. */
.lab-main > * {
  min-width: 0;
  grid-column: 1 / -1;
}

@media (min-width: 1000px) {
  .lab-main {
    grid-template-columns: minmax(0, 1.7fr) minmax(290px, 0.85fr);
    grid-template-areas:
      "stage controls"
      "plots plots"
      "theory theory";
  }

  .lab-main > .sim-stage { grid-area: stage; }
  .lab-main > .sim-controls { grid-area: controls; }
  .lab-main > .sim-plots { grid-area: plots; }
  .lab-main > .sim-theory { grid-area: theory; }
}

/* Shared panel look for the four regions. */
.sim-stage,
.sim-controls,
.sim-plots,
.sim-theory,
.panel {
  min-width: 0;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: 18px;
}

.sim-stage > h2,
.sim-controls > h2,
.sim-plots > h2,
.sim-theory > h2,
.panel > h2 {
  margin: 0 0 14px;
  padding-bottom: 9px;
  font-size: 1.1rem;
  letter-spacing: 0.2px;
  border-bottom: 2px solid var(--line);
  position: relative;
}

.sim-stage > h2::after,
.sim-controls > h2::after,
.sim-plots > h2::after,
.sim-theory > h2::after,
.panel > h2::after {
  content: "";
  position: absolute;
  left: 0;
  bottom: -2px;
  width: 56px;
  height: 2px;
  background: var(--secondary);
}

/* ═══════════════════════════════════════════════════════════════════════
   5. Stage — canvas, transport bar, live readouts
   ═══════════════════════════════════════════════════════════════════════ */

.sim-stage {
  display: flex;
  flex-direction: column;
  gap: 14px;
}

.stage-head {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  justify-content: space-between;
  gap: 6px 14px;
}

.stage-figure {
  position: relative;
  margin: 0;
  border-radius: var(--radius-sm);
  overflow: hidden;
  background: linear-gradient(180deg, #fbfcfe, #f2f5f8);
  border: 1px solid var(--line);
}

.sim-stage canvas,
.stage-canvas {
  display: block;
  width: 100%;
  height: var(--stage-h);
  /* A canvas is replaced content: without an explicit CSS height it keeps its
     intrinsic attribute height and the drawing ends up stretched. */
  touch-action: none;
}

.stage-caption {
  margin: 8px 0 0;
  font-size: 0.82rem;
  color: var(--muted);
}

/* ── Transport bar ─────────────────────────────────────────────────────── */

.transport {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 10px;
  padding: 10px 12px;
  border-radius: var(--radius-sm);
  background: var(--surface-sunken);
  border: 1px solid var(--line);
}

button,
.btn {
  font: inherit;
  font-size: 0.88rem;
  font-weight: 600;
  line-height: 1.2;
  padding: 9px 18px;
  border-radius: 999px;
  border: 1px solid var(--line-strong);
  background: var(--white);
  color: var(--primary);
  cursor: pointer;
  transition: background 0.18s ease, color 0.18s ease, border-color 0.18s ease;
  white-space: nowrap;
  min-height: 38px;
}

button:hover,
.btn:hover {
  background: #eef3f8;
  border-color: var(--primary-light);
}

button:disabled,
.btn:disabled,
button[disabled] {
  opacity: 0.45;
  cursor: not-allowed;
  background: var(--surface-sunken);
  border-color: var(--line);
}

/* The play/pause control is the primary action in the bar. */
.transport button:first-of-type,
.btn-primary,
button.btn-primary {
  background: var(--primary);
  border-color: var(--primary);
  color: #fff;
}

.transport button:first-of-type:hover,
.btn-primary:hover {
  background: var(--primary-light);
  border-color: var(--primary-light);
  color: #fff;
}

.transport button:first-of-type:disabled,
.btn-primary:disabled {
  background: var(--muted);
  border-color: var(--muted);
  color: #fff;
}

/* Running state is carried by the label AND aria-pressed, never by colour
   alone. The bare `button[aria-pressed="true"]` selector (0,1,1) loses to
   `.transport button:first-of-type` (0,2,1) above, and the play button IS the
   first button in the bar — so the running tint never appeared. Repeating the
   selector inside each transport alias ties the specificity, and a tie goes to
   the later rule. White on #a85800 is 5.17:1, on #8f4a00 6.67:1. */
.transport button[aria-pressed="true"],
.transport button.is-running,
button[aria-pressed="true"],
.btn.is-running {
  background: var(--secondary-text);
  border-color: var(--secondary-text);
  color: #fff;
}

.transport button[aria-pressed="true"]:hover,
.transport button.is-running:hover,
button[aria-pressed="true"]:hover,
.btn.is-running:hover {
  background: var(--secondary-deep);
  border-color: var(--secondary-deep);
  color: #fff;
}

.btn-ghost,
button.btn-ghost {
  background: transparent;
  border-color: var(--line-strong);
  color: var(--primary);
}

.transport label,
.transport-speed label {
  font-size: 0.82rem;
  font-weight: 600;
  color: var(--muted);
  white-space: nowrap;
}

.transport-speed,
.transport .speed {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-left: auto;
}

.transport select,
.transport input[type="range"] {
  margin: 0;
  width: auto;
  min-width: 92px;
}

.transport output {
  font-family: var(--font-mono);
  font-size: 0.8rem;
  color: var(--muted);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

/* ── Readout grid ─────────────────────────────────────────────────────── */

.readouts {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(146px, 100%), 1fr));
  gap: 10px;
  margin: 0;
  padding: 0;
}

.readout,
.readouts > div,
.metric {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
  padding: 9px 12px;
  border-radius: var(--radius-sm);
  background: var(--surface-sunken);
  border: 1px solid var(--line);
  border-left: 3px solid var(--primary);
}

.readout--accent { border-left-color: var(--secondary-text); }
.readout--ok { border-left-color: var(--ok); }

/* The other half of a conservation check. A card that is only ever green is not
   a check, it is decoration — and projectile shipped nine of them, four on
   quantities that are not checks at all and five that the page's own text says
   can blow up. Paired with .readout--ok and repainted from the residual, so the
   colour carries the verdict rather than the intent. The --warn-* tokens
   already existed and nothing used them. */
.readout--warn {
  border-left-color: var(--warn-line);
  background: var(--warn-bg);
}
.readout--warn .readout-value { color: var(--warn-text); }

.readout-label,
.readouts dt {
  font-size: 0.7rem;
  font-weight: 700;
  letter-spacing: 0.7px;
  text-transform: uppercase;
  color: var(--muted);
  margin: 0;
}

/* Case matters for some symbols and the uppercase transform destroys them:
   gamma becomes Gamma, n (moles) becomes N (particles) — a distinction this lab
   draws explicitly — rho becomes a capital Rho that reads as P, q" (heat flux)
   becomes Q" (heat), c_p (molar) becomes C_P (total), and the d of a
   differential becomes D. Wrap such a fragment in .sym to opt out; the
   transform still applies to the words around it, so labels stay uniform.

   The guard is UNCONDITIONAL, and must stay that way. It used to enumerate the
   uppercasing containers — `.readout-label .sym`, `.readouts dt .sym` and so on
   — which meant every new uppercased container silently reopened the bug: two
   of the four listed selectors matched nothing in any page, while the materials
   and theory table headers, which do uppercase, were missing. That is how
   conduction's materials table came to render rho and alpha as Greek capitals
   glyph-identical to Latin P and A, in a thermal table where P and A mean other
   things.

   Enumeration is not needed. text-transform is INHERITED, so the uppercase
   lives on the ancestor and reaches .sym only by inheritance; a declaration on
   the element itself always wins over an inherited value, whatever the
   ancestor's specificity. One selector therefore covers every container that
   exists now and every one added later. Do not re-scope it. */
.sym {
  text-transform: none;
}

.readout-value,
.readouts dd {
  margin: 0;
  font-size: 1.02rem;
  font-weight: 500;
  color: var(--dark-gray);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* <dl> markup: pair each dt with the dd that follows it. */
.readouts dt {
  padding: 9px 12px 0;
  border-radius: var(--radius-sm) var(--radius-sm) 0 0;
  background: var(--surface-sunken);
  border: 1px solid var(--line);
  border-bottom: 0;
  border-left: 3px solid var(--primary);
}

.readouts dd {
  padding: 0 12px 9px;
  border-radius: 0 0 var(--radius-sm) var(--radius-sm);
  background: var(--surface-sunken);
  border: 1px solid var(--line);
  border-top: 0;
  border-left: 3px solid var(--primary);
}

/* ═══════════════════════════════════════════════════════════════════════
   6. Controls — grouped, labelled inputs
   ═══════════════════════════════════════════════════════════════════════ */

.sim-controls {
  display: flex;
  flex-direction: column;
  gap: 16px;
}

.control-group,
.sim-controls fieldset {
  margin: 0;
  padding: 14px;
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  background: var(--surface-sunken);
  display: flex;
  flex-direction: column;
  gap: 14px;
  min-width: 0;
}

.control-group > h3,
.sim-controls legend {
  margin: 0;
  padding: 0 6px 0 0;
  font-size: 0.74rem;
  font-weight: 700;
  letter-spacing: 1px;
  text-transform: uppercase;
  color: var(--primary);
}

.sim-controls legend {
  margin-left: -2px;
}

/* One control = label (left) + value (right) + full-width input beneath. */
.control,
.field {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  align-items: baseline;
  gap: 4px 10px;
  min-width: 0;
}

.control > label,
.field > label {
  grid-column: 1;
  font-size: 0.86rem;
  font-weight: 600;
  color: var(--dark-gray);
  min-width: 0;
  overflow-wrap: break-word;
}

.control > output,
.control .control-value,
.control .value,
.field > output,
.control-value {
  grid-column: 2;
  justify-self: end;
  font-family: var(--font-mono);
  font-size: 0.82rem;
  font-weight: 500;
  color: var(--primary);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

.control > input,
.control > select,
.field > input,
.field > select {
  grid-column: 1 / -1;
  width: 100%;
  min-width: 0;
}

/* A checkbox belongs beside its label, not under it. */
.control > input[type="checkbox"],
.field > input[type="checkbox"] {
  grid-column: 2;
  grid-row: 1;
  justify-self: end;
  width: 18px;
  height: 18px;
  min-width: 0;
  accent-color: var(--primary);
  cursor: pointer;
}

.control-hint,
.control .hint,
.hint,
.help {
  grid-column: 1 / -1;
  margin: 0;
  font-size: 0.76rem;
  line-height: 1.45;
  color: var(--muted);
}

/* ── Range input ──────────────────────────────────────────────────────── */

input[type="range"] {
  -webkit-appearance: none;
  appearance: none;
  width: 100%;
  height: 22px;
  margin: 2px 0;
  background: transparent;
  cursor: pointer;
}

input[type="range"]::-webkit-slider-runnable-track {
  height: 6px;
  border-radius: 3px;
  background: linear-gradient(90deg, var(--primary-light), #9fb4c8);
}

input[type="range"]::-moz-range-track {
  height: 6px;
  border-radius: 3px;
  background: linear-gradient(90deg, var(--primary-light), #9fb4c8);
}

input[type="range"]::-webkit-slider-thumb {
  -webkit-appearance: none;
  appearance: none;
  width: 18px;
  height: 18px;
  margin-top: -6px;
  border-radius: 50%;
  background: var(--primary);
  border: 2px solid #fff;
  box-shadow: 0 1px 4px rgba(0, 0, 0, 0.3);
}

input[type="range"]::-moz-range-thumb {
  width: 16px;
  height: 16px;
  border-radius: 50%;
  background: var(--primary);
  border: 2px solid #fff;
  box-shadow: 0 1px 4px rgba(0, 0, 0, 0.3);
}

input[type="range"]:disabled {
  opacity: 0.5;
  cursor: not-allowed;
}

/* ── Select / text / number ───────────────────────────────────────────── */

select,
input[type="text"],
input[type="number"] {
  font: inherit;
  font-size: 0.88rem;
  padding: 7px 10px;
  color: var(--dark-gray);
  background: var(--white);
  border: 1px solid var(--line-strong);
  border-radius: 6px;
  min-height: 38px;
  max-width: 100%;
}

select:hover,
input[type="text"]:hover,
input[type="number"]:hover {
  border-color: var(--primary-light);
}

/* ── Checkbox row variant ─────────────────────────────────────────────── */

.checkbox-row {
  display: flex;
  align-items: center;
  gap: 10px;
  min-width: 0;
}

.checkbox-row input[type="checkbox"] {
  width: 18px;
  height: 18px;
  flex: 0 0 auto;
  accent-color: var(--primary);
  cursor: pointer;
}

.checkbox-row label {
  font-size: 0.86rem;
  font-weight: 600;
  cursor: pointer;
  min-width: 0;
}

/* ═══════════════════════════════════════════════════════════════════════
   7. Plots
   ═══════════════════════════════════════════════════════════════════════ */

.sim-plots {
  display: grid;
  gap: 16px;
  /* min() keeps a single column from overflowing a 360px viewport. */
  grid-template-columns: repeat(auto-fit, minmax(min(330px, 100%), 1fr));
}

/* A heading inside the plots panel spans the whole grid. */
.sim-plots > h2,
.sim-plots > h3,
.sim-plots > p {
  grid-column: 1 / -1;
}

.plot-card,
.plot {
  min-width: 0;
  margin: 0;
  padding: 12px;
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  background: var(--surface);
}

.plot-card h3,
.plot h3 {
  margin: 0 0 8px;
  font-size: 0.9rem;
  font-weight: 700;
  color: var(--primary);
}

.sim-plots canvas,
.plot-canvas,
.sim-stage canvas.plot-canvas,
.plot-card canvas,
.plot canvas {
  display: block;
  width: 100%;
  height: var(--plot-h);
  background: var(--plot-bg);
  border-radius: 6px;
}

.plot-caption,
.plot-card figcaption,
.plot figcaption {
  margin: 8px 0 0;
  font-size: 0.78rem;
  line-height: 1.5;
  color: var(--muted);
}

/* Legend — series colour AND dash/shape, so it survives greyscale. */
.plot-legend,
.legend {
  display: flex;
  flex-wrap: wrap;
  gap: 6px 16px;
  margin: 8px 0 0;
  padding: 0;
  list-style: none;
  font-size: 0.78rem;
  color: var(--muted);
}

.plot-legend li,
.legend li {
  display: flex;
  align-items: center;
  gap: 7px;
  margin: 0;
  padding: 0;
}

.legend-swatch {
  width: 20px;
  height: 0;
  flex: 0 0 auto;
  border-top: 3px solid var(--primary);
}

.legend-swatch--dashed { border-top-style: dashed; }
.legend-swatch--dotted { border-top-style: dotted; }

/* A marker series is a dot, not a line — a rule of the same colour reads as a
   fourth curve on a plot that has three. */
.legend-swatch--dot,
.legend-swatch--square {
  border-top: none;
  height: 8px;
  width: 8px;
  background: currentColor;
  align-self: center;
}
.legend-swatch--dot { border-radius: 50%; }

/* ═══════════════════════════════════════════════════════════════════════
   8. Theory — equations, what to try, assumptions
   ═══════════════════════════════════════════════════════════════════════ */

.sim-theory {
  font-size: 0.94rem;
}

.theory-grid {
  display: grid;
  gap: 18px 26px;
  grid-template-columns: repeat(auto-fit, minmax(min(300px, 100%), 1fr));
  align-items: start;
}

.theory-col {
  min-width: 0;
}

.sim-theory h3 {
  margin: 0 0 8px;
  padding-bottom: 5px;
  font-size: 0.98rem;
  border-bottom: 1px solid var(--line);
}

.sim-theory ul,
.try-list,
.assumptions {
  margin: 0 0 14px;
  padding-left: 4px;
  list-style: none;
}

.sim-theory li,
.try-list li,
.assumptions li {
  position: relative;
  margin-bottom: 9px;
  padding-left: 18px;
  line-height: 1.55;
}

.sim-theory li::before,
.try-list li::before,
.assumptions li::before {
  content: "▸";
  position: absolute;
  left: 0;
  top: -1px;
  color: var(--secondary-text);
  font-weight: 700;
}

.assumptions li::before {
  content: "•";
  font-size: 1.3em;
  line-height: 1;
  top: 1px;
}

.sim-theory ol {
  margin: 0 0 14px;
  padding-left: 22px;
}

.sim-theory ol li {
  padding-left: 0;
}

.sim-theory ol li::before {
  content: none;
}

/* ── Equation blocks ──────────────────────────────────────────────────── */

.eq,
.equation {
  display: block;
  margin: 0 0 12px;
  padding: 12px 16px;
  font-family: var(--font-math);
  font-size: 1.1rem;
  line-height: 1.75;
  color: var(--primary);
  background: var(--surface-sunken);
  border: 1px solid var(--line);
  border-left: 3px solid var(--secondary);
  border-radius: 0 var(--radius-sm) var(--radius-sm) 0;
  /* A long equation scrolls inside its own box, never the page. */
  overflow-x: auto;
  overflow-y: hidden;
  white-space: nowrap;
  -webkit-overflow-scrolling: touch;
}

/* Multi-line derivations opt out of the nowrap. */
.eq--wrap,
.eq.wrap {
  white-space: normal;
}

.eq var,
.equation var,
.eq i {
  font-style: italic;
  font-family: var(--font-math);
}

.eq-label {
  display: block;
  margin: 0 0 4px;
  font-family: var(--font-body);
  font-size: 0.7rem;
  font-weight: 700;
  letter-spacing: 0.9px;
  text-transform: uppercase;
  color: var(--muted);
  white-space: normal;
}

.eq-where,
.eq-note {
  margin: -6px 0 14px;
  padding-left: 16px;
  font-size: 0.82rem;
  line-height: 1.55;
  color: var(--muted);
}

.eq-where li {
  padding-left: 0;
  margin-bottom: 4px;
}

.eq-where li::before {
  content: none;
}

/* ── Callouts ─────────────────────────────────────────────────────────── */

.callout,
.note,
.warn {
  margin: 0 0 14px;
  padding: 11px 14px;
  border-radius: var(--radius-sm);
  border: 1px solid var(--line);
  background: var(--surface-sunken);
  font-size: 0.86rem;
  line-height: 1.55;
}

.callout strong,
.note strong,
.warn strong {
  color: var(--primary);
}

.warn {
  background: var(--warn-bg);
  border-color: var(--warn-line);
  color: var(--warn-text);
}

.warn strong {
  color: var(--warn-text);
}


.num,
.numeric {
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
}

/* `[hidden]` beats any author-origin `display`.
   The user agent's `[hidden] { display: none }` is user-agent origin, so ANY
   author rule that sets `display` on the same element wins the cascade and the
   attribute silently does nothing. That has now bitten twice here — once on
   `.tag` (display:inline-block) and once on `.control-group` (display:flex),
   both times shipping an element that JavaScript believed it had hidden — and
   `.plot-legend li` (display:flex) would have been the third.
   `!important` on this one declaration is the standard fix (it is what
   normalize.css does) and it cannot hide anything that was not explicitly
   marked hidden by its author. */
[hidden] {
  display: none !important;
}

.tag,
.badge {
  display: inline-block;
  padding: 3px 10px;
  border-radius: 999px;
  font-size: 0.66rem;
  font-weight: 700;
  letter-spacing: 0.7px;
  text-transform: uppercase;
  background: #e6eef5;
  color: var(--primary);
  white-space: nowrap;
}

/* --secondary-text on this fill measures 4.46:1 — just under the floor for
   0.66 rem bold text. --secondary-deep is 5.76:1 on the same fill. */
.tag--accent {
  background: #ffeccc;
  color: var(--secondary-deep);
}

/* ── Tables ───────────────────────────────────────────────────────────── */

.table-scroll {
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
  margin-bottom: 14px;
  /* The cue that there is more to the right. At 360 px the ampacity ladder
     clips its last column and a phone hides the scrollbar, so nothing said
     so (found 2026-09-04). Edge shadows that exist only while there is
     content past the edge: the two covers scroll WITH the content
     (attachment: local) and slide off the shadows, which stay put. No
     JavaScript, no extra element, nothing when the table fits. The cells
     are transparent, so the shadow shows through them. */
  background:
    linear-gradient(to right, var(--surface) 30%, rgba(255, 255, 255, 0)),
    linear-gradient(to left, var(--surface) 30%, rgba(255, 255, 255, 0)) 100% 0,
    radial-gradient(farthest-side at 0 50%, rgba(0, 0, 0, 0.22), rgba(0, 0, 0, 0)),
    radial-gradient(farthest-side at 100% 50%, rgba(0, 0, 0, 0.22), rgba(0, 0, 0, 0)) 100% 0;
  background-repeat: no-repeat;
  background-size: 40px 100%, 40px 100%, 14px 100%, 14px 100%;
  background-attachment: local, local, scroll, scroll;
}

.sim-theory table,
.lab-table {
  border-collapse: collapse;
  width: 100%;
  font-size: 0.85rem;
}

.sim-theory th,
.sim-theory td,
.lab-table th,
.lab-table td {
  padding: 7px 10px;
  text-align: left;
  border-bottom: 1px solid var(--line);
  vertical-align: top;
}

.sim-theory th,
.lab-table th {
  color: var(--primary);
  font-weight: 700;
  font-size: 0.76rem;
  letter-spacing: 0.5px;
  text-transform: uppercase;
  border-bottom: 2px solid var(--line-strong);
}

.sim-theory td.num,
.lab-table td.num {
  text-align: right;
}

/* ═══════════════════════════════════════════════════════════════════════
   9. Landing page — simulation cards
   ═══════════════════════════════════════════════════════════════════════ */

.lab-intro {
  margin: 0 0 26px;
  font-size: 1rem;
  color: var(--dark-gray);
  max-width: 76ch;
}

.sim-card-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(290px, 100%), 1fr));
  gap: 18px;
  margin: 0;
  padding: 0;
  list-style: none;
}



.sim-card {
  position: relative;
  display: flex;
  flex-direction: column;
  width: 100%;
  min-width: 0;
  padding: 20px 22px;
  border-radius: var(--radius);
  background: var(--white);
  border: 1px solid var(--line);
  box-shadow: 0 1px 4px rgba(0, 0, 0, 0.04);
  color: inherit;
  text-decoration: none;
  overflow: hidden;
  transition: transform 0.22s ease, box-shadow 0.22s ease, border-color 0.22s ease;
}

a.sim-card {
  border-bottom: 1px solid var(--line);
}

.sim-card::before {
  content: "";
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  height: 4px;
  background: var(--accent, var(--primary));
  transition: height 0.22s ease;
}

.sim-card:hover {
  transform: translateY(-3px);
  box-shadow: 0 12px 28px rgba(0, 0, 0, 0.09);
  border-color: var(--line-strong);
  color: inherit;
}

.sim-card:hover::before {
  height: 6px;
}

.sim-card:focus-visible {
  outline: 3px solid var(--secondary-text);
  outline-offset: 3px;
}

/* Per-discipline accents. */
.sim-card--thermo { --accent: #c2410c; }
.sim-card--heat { --accent: #b02a37; }
.sim-card--mechanics { --accent: #2e7d32; }
.sim-card--cycles { --accent: #00796b; }
.sim-card--state { --accent: #0369a1; }
.sim-card--fields { --accent: #6a1b9a; }

.sim-card-top {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  margin-bottom: 12px;
}

.sim-card-tag {
  display: inline-block;
  padding: 4px 10px;
  border-radius: 999px;
  font-size: 0.64rem;
  font-weight: 700;
  letter-spacing: 0.7px;
  text-transform: uppercase;
  background: var(--surface-sunken);
  border: 1px solid var(--line);
  color: var(--muted);
  white-space: nowrap;
}

.sim-card-figure {
  display: block;
  width: 100%;
  height: 92px;
  margin: 0 0 14px;
  border-radius: var(--radius-sm);
  background: var(--surface-sunken);
  border: 1px solid var(--line);
  overflow: hidden;
}

.sim-card-figure svg,
.sim-card-figure img {
  display: block;
  width: 100%;
  height: 100%;
}

.sim-card-title,
.sim-card h2,
.sim-card h3 {
  margin: 0 0 8px;
  font-size: 1.14rem;
  font-weight: 700;
  color: var(--primary);
  line-height: 1.3;
}

.sim-card-body,
.sim-card p {
  flex: 1 1 auto;
  margin: 0 0 14px;
  font-size: 0.9rem;
  line-height: 1.55;
  color: var(--muted);
}

.sim-card-meta {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  margin: 0 0 14px;
  padding: 0;
  list-style: none;
  font-size: 0.72rem;
  color: var(--muted);
}

.sim-card-meta li {
  margin: 0;
  padding: 3px 9px;
  border-radius: 6px;
  background: var(--surface-sunken);
  border: 1px solid var(--line);
}

.sim-card-meta li::before {
  content: none;
}

.sim-card-action {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  align-self: flex-start;
  font-size: 0.86rem;
  font-weight: 600;
  color: var(--accent, var(--primary));
  border: 0;
}

.sim-card-action::after {
  content: "→";
  transition: transform 0.22s ease;
}

.sim-card:hover .sim-card-action::after {
  transform: translateX(4px);
}

/* ═══════════════════════════════════════════════════════════════════════
   10. Utilities
   ═══════════════════════════════════════════════════════════════════════ */

.stack {
  display: flex;
  flex-direction: column;
  gap: 12px;
  min-width: 0;
}

.row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 10px;
  min-width: 0;
}


/* ═══════════════════════════════════════════════════════════════════════
   11. Responsive
   ═══════════════════════════════════════════════════════════════════════ */

@media (max-width: 900px) {
  .container,
  .lab-container {
    margin: 12px;
    padding: 16px;
  }

  .lab-header {
    padding: 22px 18px;
    gap: 8px 14px;
  }

  :root {
    --gap: 16px;
    --stage-h: clamp(240px, 42vh, 380px);
    --plot-h: 230px;
  }
}

@media (max-width: 560px) {
  body {
    font-size: 15px;
  }

  .container,
  .lab-container {
    margin: 8px;
    padding: 12px;
    border-radius: var(--radius-sm);
  }

  .lab-header {
    padding: 18px 14px;
    border-radius: var(--radius-sm) var(--radius-sm) 0 0;
  }

  /* Back link drops to its own line rather than squeezing the title. */
  .lab-back,
  .lab-nav {
    margin-left: 0;
    flex-basis: 100%;
  }

  .sim-stage,
  .sim-controls,
  .sim-plots,
  .sim-theory,
  .panel {
    padding: 13px;
  }

  .transport {
    padding: 9px;
    gap: 8px;
  }

  .transport button {
    flex: 1 1 auto;
    padding: 9px 12px;
  }

  .transport-speed,
  .transport .speed {
    margin-left: 0;
    flex: 1 1 100%;
  }

  .readouts {
    grid-template-columns: repeat(auto-fit, minmax(min(128px, 100%), 1fr));
    gap: 8px;
  }

  .readout,
  .readouts > div,
  .metric {
    padding: 7px 10px;
  }

  .readout-value {
    font-size: 0.95rem;
  }

  .eq,
  .equation {
    padding: 10px 12px;
    font-size: 1rem;
  }

  footer,
  .lab-footer {
    padding: 16px 12px;
    border-radius: 0 0 var(--radius-sm) var(--radius-sm);
  }
}

/* ═══════════════════════════════════════════════════════════════════════
   12. Reduced motion — honour the preference, keep everything legible
   ═══════════════════════════════════════════════════════════════════════ */

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

  html {
    scroll-behavior: auto;
  }

  .sim-card:hover {
    transform: none;
  }

  .sim-card:hover .sim-card-action::after {
    transform: none;
  }

  .skip-link {
    transition: none;
  }
}

/* ═══════════════════════════════════════════════════════════════════════
   13. Forced colours / high contrast
   ═══════════════════════════════════════════════════════════════════════ */

@media (forced-colors: active) {
  .sim-card,
  .readout,
  .control-group,
  .plot-card,
  .transport {
    border: 1px solid CanvasText;
  }

  button,
  .btn {
    border: 1px solid ButtonText;
  }
}

/* ═══════════════════════════════════════════════════════════════════════
   14. Print — the canvases carry the result, so keep them
   ═══════════════════════════════════════════════════════════════════════ */

@media print {
  body {
    background: #fff;
  }

  .container,
  .lab-container {
    margin: 0;
    padding: 0;
    box-shadow: none;
  }

  .transport,
  .page-jumps,
  .footer-nav,
  .skip-link,
  .lab-back,
  .lab-nav {
    display: none !important;
  }

  .lab-main {
    display: block;
  }

  .sim-stage,
  .sim-controls,
  .sim-plots,
  .sim-theory {
    break-inside: avoid;
    margin-bottom: 14px;
  }
}
