/* ── Reset & base ── */
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }

:root {
  --bg: #000000;
  --panel-bg: #0b0b0b;
  --border: #1c1c1c;
  --text: #8a8a8a;
  --text-dim: #444;
  /* Device accent is GOLD. The SedLine draws its EEG and numerics in gold on near-black
     and reserves green/yellow/red for impedance and alarm state — the previous all-green
     theme was a generic "medical monitor" look rather than this device. Kept as a variable
     so the monitor chrome and the canvas constants (COL_TRACE/COL_NUM in app.js) agree. */
  --accent: #e5a83b;
  --accent-dim: #6b5a33;
  --accent-bright: #f0b640;
  --propofol: #c084fc;
  --ketamine: #fb923c;
  --dex: #38bdf8;
  --sevoflurane: #4ade80;
  --frailty: #fbbf24;
  --seizure: #ff5c7a;
  --sleep: #818cf8;
  --danger: #ff4444;
  --section-gap: 20px;
  --panel-width: 286px;
  --font: 'Courier New', Courier, monospace;
  /* Range-thumb geometry. Declared as variables because app.js's enableDragAnywhere needs the
     half-thumb to convert a pointer x into a value, and that number was previously hardcoded
     as `const TH = 7` in two places that had to agree by hand. app.js now reads --thumb-half
     off the element, so CSS is the single source of truth and body.mobile-layout can raise
     both to a touch-sized thumb without app.js knowing anything about it. */
  --thumb: 14px;
  --thumb-half: 7px;
}

html, body {
  height: 100%;
  background: var(--bg);
  color: var(--text);
  font-family: var(--font);
  font-size: 12px;
  overflow: hidden;
}

/* ── App shell ── */
#app {
  display: flex;
  height: 100vh;
  /* svh, NOT dvh. On iOS `100vh` overflows under the URL bar and clips the DSA + footer, but
     `dvh` fixes that by CHANGING as the bar shows/hides — which genuinely resizes the layout
     on every scroll gesture, and each resize resets the sweep and epoch (see applyResize in
     app.js). `svh` is the height with browser chrome EXPANDED, so it never moves: the app is
     a fixed box and the resize churn disappears at source. In an installed PWA svh == dvh,
     so nothing is lost on the channel that matters; the cost is a strip of black at the foot
     of a Safari tab with the bar collapsed. Desktop resolves svh == vh, so this line is inert
     there and browsers without svh ignore it and keep the 100vh above. */
  height: 100svh;
  width: 100vw;
  overflow: hidden;
}

/* ── Monitor panel (left) ── */
#monitor-panel {
  flex: 1 1 auto;
  display: flex;
  flex-direction: column;
  min-width: 0;
  border-right: 1px solid var(--border);
  /* Clip the header's own content so, in a narrow window, the export button can't spill
     rightward over the control panel and cover the top slider (Patient frailty). */
  overflow: hidden;
}

#monitor-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 8px 14px;
  background: #050505;
  border-bottom: 1px solid var(--border);
  flex-shrink: 0;
}

.monitor-title {
  color: var(--accent);
  font-size: 11px;
  font-weight: bold;
  letter-spacing: 0.15em;
}

.monitor-info {
  color: var(--text-dim);
  font-size: 10px;
  letter-spacing: 0.1em;
}

/* Canvas container fills remaining height and centres the waveform window.
   position:relative so the PSi readout can pin to the right gap beside the trace. */
#canvas-container {
  flex: 1 1 auto;
  display: flex;
  align-items: center;
  justify-content: center;
  background: #000;
  overflow: hidden;
  min-height: 0;
  position: relative;
}

/* The waveform wrapper is 60% wide and centred, leaving ~20% clear on each side. The two
   optional readouts take one gap each — Summary on the LEFT, PSi on the RIGHT — so neither
   has to share vertical space with the other and the summary gets the full column height.
   Both widths/offsets are in PERCENT, so they can never grow past their gap and overlap the
   trace as the window changes size. */

/* PSi — large gold readout in the RIGHT gap. */
#psi-display {
  position: absolute;
  right: 4%;
  top: 50%;
  transform: translateY(-50%);
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 2px;
  /* Was pointer-events:none; re-enabled 2026-09-08 so the delayed .param-tip teaching
     summary can hover-trigger. Safe: the readout sits in the clear gap beside the trace,
     so its box covers nothing interactive. */
  pointer-events: auto;
  cursor: help;
}

#psi-display.hidden { display: none; }

/* Summary — bulleted plain-language readout of the current EEG in the LEFT gap, typed in
   over ~2 s once the controls have been static. The Field guide (#guide-panel) SHARES this
   placement — the two occupy the same gap and are mutually exclusive (state.leftPanelMode). */
#summary-panel,
#guide-panel {
  position: absolute;
  left: 0.5%;
  top: 50%;
  transform: translateY(-50%);
  /* 19% + the 0.5% offset keeps the panel inside the ~20% clear gap at every window size;
     the min-width is the point below which the text would stop being legible. */
  width: 19%;
  min-width: 112px;
  max-height: 100%;
  background: #000;
  border: 1px solid var(--border);
  border-left: 2px solid var(--accent-dim);
  border-radius: 2px;
  padding: 6px 7px;
  /* The panel is a fixed FRACTION of the window, so on a small screen the text both wraps
     more and has less height to wrap into. Scaling with vw keeps the longest summary
     (~16 lines) fitting its column; it caps at 10.5px so a large window doesn't inflate it. */
  font-size: clamp(8px, 0.78vw, 10.5px);
  line-height: 1.4;
}
#summary-panel {
  /* Was pointer-events:none + overflow:hidden. Both changed 2026-09-08 for the teaching
     tips: pointer-events per the #psi-display precedent above (the gap covers nothing
     interactive), and overflow VISIBLE because the .sum-tip hover boxes hang below their
     line — any clipping overflow would silently truncate them, and a visible spill of a
     too-long summary is loud where a clipped tip is silent. The vw font clamp above remains
     the actual fit guarantee. */
  overflow: visible;
  pointer-events: auto;
}
#guide-panel {
  /* The catalogue is LONGER than the panel (~26 entries) by design — scroll, never clip. */
  overflow-y: auto;
  pointer-events: auto;
}

#summary-panel.hidden, #guide-panel.hidden { display: none; }

/* Section heading (PATIENT / AGENTS / EEG …). */
.sum-head {
  color: var(--accent-dim);
  font-weight: bold;
  letter-spacing: 0.12em;
  margin-top: 6px;
}
.sum-head:first-child { margin-top: 0; }

/* Bullet line — hanging indent so a wrapped line clears its own bullet. */
.sum-item {
  color: var(--text);
  padding-left: 8px;
  text-indent: -8px;
  /* Smooth fade for the interspersed-wave glow (added/removed by updateSummaryGlow). */
  transition: color 0.25s ease, text-shadow 0.25s ease;
}

/* The headline fact of each section (the frailty profile, the stage, an active agent). */
.sum-item.sum-key { color: var(--accent); }

/* Interspersed-wave highlight: pulses in step with its portion of the waveform glowing.
   Toggled every second run while the Summary panel is shown. */
.sum-item.sum-glow {
  color: var(--accent);
  text-shadow: 0 0 6px var(--accent), 0 0 12px var(--accent);
}

/* Teaching-note hover tips (attached by summaryAttachTips once typing completes). The
   anchor needs position:relative for the absolute .sum-tip; cursor:help is the affordance. */
.sum-item { position: relative; }
.sum-item.has-tip { cursor: help; }

/* ── Field guide catalogue (#guide-panel — see guideBuild/guideRefresh) ─────────────── */
.guide-head {
  color: var(--accent-dim);
  font-weight: bold;
  letter-spacing: 0.12em;
  margin-top: 7px;
}
.guide-head:first-child { margin-top: 0; }
.guide-item {
  position: relative;           /* anchors the .guide-tip */
  color: var(--text);
  margin-top: 3px;
  cursor: help;
}
.guide-marker { color: var(--accent-dim); margin-right: 4px; }
.guide-name   { color: var(--text); }
.guide-desc   { color: #999; font-size: 0.92em; line-height: 1.35; }
/* Live = this pattern is on the trace RIGHT NOW; absent stays listed but greyed — the
   catalogue's whole point is showing what the model CAN demonstrate. */
.guide-item.live   .guide-marker { color: var(--accent); }
.guide-item.live   .guide-name   { color: var(--accent); }
.guide-item.absent .guide-name   { color: #777; }
.guide-item.absent .guide-desc   { color: #666; }

.psi-label {
  font-size: 11px;
  font-weight: bold;
  letter-spacing: 0.18em;
  color: var(--accent-dim);
}

.psi-value {
  font-family: var(--font);
  font-size: 46px;
  font-weight: bold;
  line-height: 1;
  color: var(--accent-bright);
  font-variant-numeric: tabular-nums;
}

/* ── Parameters row (a single line of numerics above the DSA) ──
   Device-style large gold numeral with a small label/unit stacked to its right
   (EMG / SR / ARTF / SEFL / SEFR). No trend strips. Values written by paramsUpdate(). */
#param-row {
  display: flex;
  justify-content: space-around;
  align-items: center;
  gap: 10px;
  padding: 5px 14px;
  background: #050505;
  border-top: 1px solid var(--border);
  flex-shrink: 0;
}

.param {
  display: flex;
  align-items: center;
  gap: 4px;
  line-height: 1;
  /* Anchor + hover trigger for the delayed .param-tip teaching summary. */
  position: relative;
  cursor: help;
}

.param-value {
  font-family: var(--font);
  font-size: 21px;
  font-weight: bold;
  color: var(--accent-bright);
  font-variant-numeric: tabular-nums;   /* equal-width digits */
  /* Reserve width for the widest reading ("100" or "30.0") and right-align, so a value
     changing digit count (SEF 9.5→11.2, EMG 9→10) no longer widens its box and bumps the
     neighbouring parameters via the row's space-around distribution. */
  min-width: 4ch;
  text-align: right;
}

.param-tag {
  display: flex;
  flex-direction: column;
  gap: 1px;
}

.param-label {
  font-size: 8px;
  letter-spacing: 0.12em;
  color: var(--accent-dim);
}

.param-unit {
  font-size: 8px;
  color: var(--text-dim);
}

/* ── DSA panel (below the parameters row) ── */
#dsa-panel {
  flex: 0 0 34%;
  display: flex;
  flex-direction: column;
  min-height: 0;
  border-top: 1px solid var(--border);
  background: #000;
}

#dsa-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 5px 14px;
  background: #050505;
  border-bottom: 1px solid var(--border);
  flex-shrink: 0;
}

.dsa-title {
  color: var(--accent);
  font-size: 10px;
  font-weight: bold;
  letter-spacing: 0.15em;
}

.dsa-info {
  color: var(--text-dim);
  font-size: 9px;
  letter-spacing: 0.1em;
}

#dsa-wrapper {
  flex: 1 1 auto;
  position: relative;
  overflow: hidden;
  min-height: 0;
}

#dsa-canvas {
  display: block;
  width: 100%;
  height: 100%;
}

/* Waveform window: 60% of available width, 80% of available height.
   NOTE: the EEG is now calibrated in µV/mm and mm/sec against real screen millimetres
   (see PX_PER_MM in app.js), so changing these percentages no longer rescales the trace —
   it only changes how much of the sweep is visible. */
#canvas-wrapper {
  width: 60%;
  height: 80%;
  position: relative;
  overflow: hidden;
  background: #000;
  /* Thin gold line border, surrounded by black halo */
  border: 1.5px solid var(--accent);
  box-shadow: 0 0 0 5px #000, 0 0 0 6px #0d0d0d;
}

#eeg-canvas {
  display: block;
  width: 100%;
  height: 100%;
}

/* Highlight layer over the trace — the Summary interspersed-wave flash. Purely visual,
   never intercepts pointer events, and holds no persistent state (cleared each frame). */
#eeg-glow-canvas {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  pointer-events: none;
}

/* ── Control sidebar (right) ── */
#control-panel {
  width: var(--panel-width);
  flex-shrink: 0;
  display: flex;
  flex-direction: column;
  background: var(--panel-bg);
  overflow-y: auto;
  overflow-x: hidden;
}

/* ── Control sections ── */
/* Padding kept tight so all sliders fit without scrolling on a normal window. */
.control-section {
  padding: 8px 14px;
  border-bottom: 1px solid var(--border);
  flex-shrink: 0;
}

.med-section {
  flex: 0 0 auto;
}

.section-title {
  font-size: 10px;
  font-weight: bold;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--accent);
  margin-bottom: 7px;
  padding-bottom: 3px;
  border-bottom: 1px solid var(--accent-dim);
}

/* ── Slider rows ── */
.slider-row {
  margin-bottom: 6px;
}

.slider-row:last-child {
  margin-bottom: 0;
}

.slider-header {
  display: flex;
  align-items: baseline;
  gap: 5px;
  margin-bottom: 4px;
}

.slider-header label {
  color: #ccc;
  font-size: 11px;
  font-weight: bold;
  flex-shrink: 0;
  cursor: default;
}

.band-range {
  color: var(--text-dim);
  font-size: 9px;
  flex: 1;
}

.med-sub {
  color: var(--text-dim);
  font-size: 9px;
  flex: 1;
  letter-spacing: 0.05em;
}

.slider-val {
  color: var(--accent);
  font-size: 11px;
  font-weight: bold;
  min-width: 32px;
  text-align: right;
  flex-shrink: 0;
  /* Push the value to the right edge of the header. Rows with a .band-range spacer already
     fill the gap (this then resolves to 0); rows without one — frailty, epileptiform, the
     med rows since their sub-text became a tooltip — rely on this to keep it right-aligned. */
  margin-left: auto;
}

.suppression-row .slider-header label {
  color: #facc15;
}

.suppression-row input[type="range"]::-webkit-slider-thumb { background: #facc15; }
.suppression-row input[type="range"]::-moz-range-thumb { background: #facc15; }
.suppression-row .slider-val { color: #facc15; }

/* ── Medication row colors ── */
.propofol-row .slider-header label { color: var(--propofol); }
.propofol-row .slider-val { color: var(--propofol); }
.propofol-slider::-webkit-slider-thumb { background: var(--propofol) !important; }
.propofol-slider::-moz-range-thumb { background: var(--propofol) !important; }

.ketamine-row .slider-header label { color: var(--ketamine); }
.ketamine-row .slider-val { color: var(--ketamine); }
.ketamine-slider::-webkit-slider-thumb { background: var(--ketamine) !important; }
.ketamine-slider::-moz-range-thumb { background: var(--ketamine) !important; }

.dex-row .slider-header label { color: var(--dex); }
.dex-row .slider-val { color: var(--dex); }
.dex-slider::-webkit-slider-thumb { background: var(--dex) !important; }
.dex-slider::-moz-range-thumb { background: var(--dex) !important; }

.sevoflurane-row .slider-header label { color: var(--sevoflurane); }
.sevoflurane-row .slider-val { color: var(--sevoflurane); }
.sevoflurane-slider::-webkit-slider-thumb { background: var(--sevoflurane) !important; }
.sevoflurane-slider::-moz-range-thumb { background: var(--sevoflurane) !important; }

.frailty-row .slider-header label { color: var(--frailty); }
.frailty-row .slider-val { color: var(--frailty); }
.frailty-slider::-webkit-slider-thumb { background: var(--frailty) !important; }
.frailty-slider::-moz-range-thumb { background: var(--frailty) !important; }

.seizure-row .slider-header label { color: var(--seizure); }
.seizure-row .slider-val { color: var(--seizure); }
.seizure-slider::-webkit-slider-thumb { background: var(--seizure) !important; }
.seizure-slider::-moz-range-thumb { background: var(--seizure) !important; }

.sleep-row .slider-header label { color: var(--sleep); }
.sleep-row .slider-val { color: var(--sleep); }
.sleep-slider::-webkit-slider-thumb { background: var(--sleep) !important; }
.sleep-slider::-moz-range-thumb { background: var(--sleep) !important; }

/* The patient-state group has no section-title of its own (removed to save vertical space),
   so give the Frailty and Epileptiform control headers the same underline the section titles
   carry — a full-width divider under each heading. Labels are uppercased to read as headings. */
.patient-section .slider-header {
  padding-bottom: 3px;
  border-bottom: 1px solid var(--accent-dim);
}
.patient-section .slider-header label {
  text-transform: uppercase;
}

/* The Frailty row sits flush with the Epileptiform control below it — no divider between
   them (they are one patient-state group), so it just uses the standard slider-row spacing. */

/* ── Range input base styles ── */
input[type="range"] {
  -webkit-appearance: none;
  appearance: none;
  width: 100%;
  height: 4px;
  background: #1e1e1e;
  border-radius: 2px;
  outline: none;
  cursor: pointer;
  border: none;
  touch-action: none;   /* pointer-capture drag drives the value; don't scroll the page */
}

input[type="range"]::-webkit-slider-thumb {
  -webkit-appearance: none;
  appearance: none;
  width: var(--thumb);
  height: var(--thumb);
  border-radius: 50%;
  background: var(--accent);
  cursor: pointer;
  border: 2px solid #000;
  box-shadow: 0 0 4px rgba(0,255,65,0.5);
  transition: box-shadow 0.15s;
  /* Centre the thumb on the 4px track — WebKit otherwise aligns its top to the track top
     (≈5px low). Firefox's -moz-range-thumb self-centres, so this is webkit-only.
     Derived from --thumb rather than hardcoded so the touch-sized mobile thumb centres too;
     at the desktop 14px this resolves to exactly the -5px it has always been. */
  margin-top: calc((4px - var(--thumb)) / 2);
}

input[type="range"]::-webkit-slider-thumb:hover {
  box-shadow: 0 0 8px rgba(0,255,65,0.8);
}

input[type="range"]::-moz-range-thumb {
  width: var(--thumb);
  height: var(--thumb);
  border-radius: 50%;
  background: var(--accent);
  cursor: pointer;
  border: 2px solid #000;
  box-shadow: 0 0 4px rgba(0,255,65,0.5);
}

input[type="range"]::-webkit-slider-runnable-track {
  height: 4px;
  background: #1e1e1e;
  border-radius: 2px;
}

input[type="range"]::-moz-range-track {
  height: 4px;
  background: #1e1e1e;
  border-radius: 2px;
}

/* ── Export area ── */
/* Right-hand group in the top monitor bar (info text + export button) */
.header-right {
  display: flex;
  align-items: center;
  gap: 12px;
  order: 3;              /* far right; see the three-zone header note on .header-left */
  flex: 1 1 0;
  justify-content: flex-end;
}

/* Export button now lives in the top bar — compact, not full-width */
#export-btn {
  padding: 5px 11px;
  background: transparent;
  border: 1px solid var(--accent);
  color: var(--accent);
  font-family: var(--font);
  font-size: 10px;
  font-weight: bold;
  letter-spacing: 0.1em;
  cursor: pointer;
  border-radius: 3px;
  text-transform: uppercase;
  white-space: nowrap;
  transition: background 0.15s, color 0.15s, border-color 0.15s, box-shadow 0.15s;
}

#export-btn:hover:not(:disabled) {
  background: rgba(0,255,65,0.07);
  box-shadow: 0 0 10px rgba(0,255,65,0.3);
}

#export-btn:active:not(:disabled) {
  background: rgba(0,255,65,0.15);
}

#export-btn:disabled {
  cursor: not-allowed;
  opacity: 0.8;
}

#export-btn.recording {
  border-color: var(--danger);
  color: var(--danger);
  animation: pulse-border 1s ease-in-out infinite;
}

/* Quiz Mode link — sits in the header-left beside Export. Filled gold so the "go play"
   entry reads as the one call-to-action among the outline monitor controls. */
#quiz-link {
  padding: 5px 11px;
  background: var(--accent);
  border: 1px solid var(--accent);
  color: #1a1205;
  font-family: var(--font);
  font-size: 10px;
  font-weight: bold;
  letter-spacing: 0.1em;
  cursor: pointer;
  border-radius: 3px;
  text-transform: uppercase;
  white-space: nowrap;
  text-decoration: none;
  transition: background 0.15s, box-shadow 0.15s;
}
#quiz-link:hover {
  background: var(--accent-bright);
  box-shadow: 0 0 10px rgba(229,168,59,0.4);
}
/* "Beta" tag on the Quiz Mode entry — the quiz ships as a preview. A dark pill inset into the
   gold button so it reads as a status tag, not part of the label; sized down and given its own
   letter-spacing so it stays legible at the button's 10px uppercase. */
#quiz-link .quiz-beta {
  display: inline-block;
  margin-left: 6px;
  padding: 1px 5px;
  border-radius: 3px;
  background: #1a1205;
  color: var(--accent);
  font-size: 8px;
  font-weight: bold;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  vertical-align: 1px;
}

/* ── Return to Awake button (in the top bar, next to Export) ── */
.awake-btn {
  padding: 5px 11px;
  background: rgba(0,255,65,0.06);
  border: 1px solid var(--accent);
  color: var(--accent);
  font-family: var(--font);
  font-size: 10px;
  font-weight: bold;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  cursor: pointer;
  border-radius: 3px;
  white-space: nowrap;
  transition: background 0.15s, box-shadow 0.15s;
}

.awake-btn:hover {
  background: rgba(0,255,65,0.14);
  box-shadow: 0 0 10px rgba(0,255,65,0.3);
}

.awake-btn:active {
  background: rgba(0,255,65,0.22);
}

.awake-btn.transitioning {
  animation: pulse-border-green 1s ease-in-out infinite;
  cursor: progress;
}

@keyframes pulse-border-green {
  0%, 100% { box-shadow: 0 0 4px rgba(0,255,65,0.3); }
  50%      { box-shadow: 0 0 14px rgba(0,255,65,0.7); }
}

@keyframes pulse-border {
  0%, 100% { box-shadow: 0 0 4px rgba(255,68,68,0.3); }
  50%       { box-shadow: 0 0 12px rgba(255,68,68,0.7); }
}

/* ── Dose slider labels (epileptiform only — the med sliders show their level in the header) ──
   Each word is absolutely positioned CENTRED under its thumb stop rather than spread with
   space-between, which sat them at 0/33/66/100% of the width while the thumb snaps to the
   7px-inset stops (7 + (i/n)(width-14)) — the "words don't line up" drift. n = 4 here (5 stops).
   The three interior words centre exactly on their stops (and on the track ticks); the two end
   words anchor to the track edges instead of centring, so they don't overflow the row. */
.dose-labels {
  position: relative;
  height: 9px;
  margin-top: 3px;
}

/* WHITE, not --text-dim (#444). At 8px on the #0b0b0b panel these were reported unreadable
   (owner, 2026-08-19) — and they are the labels that say what a discrete slider is actually SET
   to: Off/Sharp/Spike-W/Polyspk/Seizure, and ASA I-IV. A control whose positions cannot be read
   is a control you have to guess at. This rule is shared by the epileptiform, sleep-stage and
   frailty strips, so all three brighten together, which is what keeps them looking like one
   family of controls. */
.dose-labels span {
  position: absolute;
  top: 0;
  white-space: nowrap;
  font-size: 8px;
  color: #ffffff;
  letter-spacing: 0.04em;
  text-transform: uppercase;
}
.dose-labels span:nth-child(1) { left: 0; }                                    /* Off — left end */
.dose-labels span:nth-child(2) { left: calc(25% + 3.5px); transform: translateX(-50%); }
.dose-labels span:nth-child(3) { left: 50%;               transform: translateX(-50%); }
.dose-labels span:nth-child(4) { left: calc(75% - 3.5px); transform: translateX(-50%); }
.dose-labels span:nth-child(5) { right: 0; }                                   /* Seizure — right end */

/* FOUR-stop variant (`.dose-labels.quad`, the discrete frailty slider). The rules above place
   children 2–4 for a FIVE-stop strip, so all three interior positions must be restated — a
   4-child strip would otherwise put its last label at 75% and leave the right end empty.
   Same centring rule as above: a label centres on its thumb position, f*100% + (7 − 14f)px,
   giving ±2.33px at the thirds (7px half-thumb, as in enableDragAnywhere). The tick marks these
   sit under come from the BASE input.dose-slider track rule (33.33%/66.67%), which is already
   the 4-stop pattern — the frailty slider needs no track rule of its own. */
.dose-labels.quad span:nth-child(2) { left: calc(33.333% + 2.33px); transform: translateX(-50%); }
.dose-labels.quad span:nth-child(3) { left: calc(66.667% - 2.33px); transform: translateX(-50%); }
.dose-labels.quad span:nth-child(4) { left: auto; right: 0; transform: none; }

/* 4-position discrete slider — wider thumb, more obvious ticks */
input.dose-slider {
  background: linear-gradient(to right, #1e1e1e 0%, #1e1e1e 100%);
}

/* Discrete-stop tick marks on the med / epileptiform slider tracks. Thin perpendicular
   notches drawn INTO the track background at the interior dose stops so they read as
   targetable positions. Deliberately background-only: the 4px track height, the #1e1e1e
   bar and the thumb's -5px margin are left exactly as-is (changing those broke drag
   before). WebKit and Moz pseudo-tracks MUST be separate rules — a comma list mixing the
   two vendor pseudos makes both engines discard the whole rule.

   Positions align the 2px tick to the THUMB CENTRE, not to a flat fraction of the track:
   the 14px thumb travels only over [7px … width-7px], so value v/max renders at
   7 + (v/max)(width-14). Solving image-left = centre-1 gives, for a 2px tick and 14px
   thumb, the calc offsets below (exact for any track width). Same 7px half-thumb constant
   as enableDragAnywhere in app.js.

   SPECIFICITY: the selector MUST out-weigh the base `input[type="range"]::-…-track` rule
   (elsewhere in this file), whose `background:` shorthand resets background-image to none.
   `input[type="range"]` is (0,1,2); a bare `.dose-slider::…-track` is only (0,1,1) and LOSES,
   so the ticks silently never rendered. Prefixing `input` makes it (0,1,2) and win on source
   order; the `.seizure-slider` variant is (0,2,2) and wins outright. */
input.dose-slider::-webkit-slider-runnable-track {
  background-color: #1e1e1e;
  background-image:
    linear-gradient(#b0b0b0, #b0b0b0),
    linear-gradient(#b0b0b0, #b0b0b0);
  background-repeat: no-repeat;
  background-size: 2px 100%;
  background-position: calc(33.33% + 2px) center, calc(66.67% - 2px) center;
}
input.dose-slider::-moz-range-track {
  background-color: #1e1e1e;
  background-image:
    linear-gradient(#b0b0b0, #b0b0b0),
    linear-gradient(#b0b0b0, #b0b0b0);
  background-repeat: no-repeat;
  background-size: 2px 100%;
  background-position: calc(33.33% + 2px) center, calc(66.67% - 2px) center;
}
/* Epileptiform is a 5-stop slider (Off/Sharp/Spike-wave/Polyspike/Seizure): ticks at the
   three interior stops. Only image + position change; size/repeat/colour cascade from the
   input.dose-slider rules above (per-property cascade — the element matches both selectors). */
input.dose-slider.seizure-slider::-webkit-slider-runnable-track {
  background-image:
    linear-gradient(#b0b0b0, #b0b0b0),
    linear-gradient(#b0b0b0, #b0b0b0),
    linear-gradient(#b0b0b0, #b0b0b0);
  background-position: calc(25% + 3px) center, 50% center, calc(75% - 3px) center;
}
input.dose-slider.seizure-slider::-moz-range-track {
  background-image:
    linear-gradient(#b0b0b0, #b0b0b0),
    linear-gradient(#b0b0b0, #b0b0b0),
    linear-gradient(#b0b0b0, #b0b0b0);
  background-position: calc(25% + 3px) center, 50% center, calc(75% - 3px) center;
}
/* Sleep stage is likewise a 5-stop slider (Awake/N1/N2/N3/REM) — same three interior ticks. */
input.dose-slider.sleep-slider::-webkit-slider-runnable-track {
  background-image:
    linear-gradient(#b0b0b0, #b0b0b0),
    linear-gradient(#b0b0b0, #b0b0b0),
    linear-gradient(#b0b0b0, #b0b0b0);
  background-position: calc(25% + 3px) center, 50% center, calc(75% - 3px) center;
}
input.dose-slider.sleep-slider::-moz-range-track {
  background-image:
    linear-gradient(#b0b0b0, #b0b0b0),
    linear-gradient(#b0b0b0, #b0b0b0),
    linear-gradient(#b0b0b0, #b0b0b0);
  background-position: calc(25% + 3px) center, 50% center, calc(75% - 3px) center;
}

/* Med sliders locked while a sleep stage is active (mutually exclusive axes). The row dims
   and the control shows a not-allowed cursor; the value stays pinned at Off by the JS. */
/* ── Bolus buttons (one per medication row) ──
   A transient effect-site dose on top of the standing dose the slider shows. Styled as a
   compact sibling of .audio-btn (the house button look), sized small enough to sit inline in
   the row header without pushing the gold value off the right edge — .slider-val's
   margin-left:auto keeps that pinned right whatever sits before it.

   Deliberately carries NO running-state animation: the owner chose the DSA injection tick as
   the only new feedback, and the band sliders already travel visibly with the effect because
   processBolus writes them through setSliderValue. Do not add a pulse here. */
.bolus-btn {
  background: transparent;
  border: 1px solid var(--text-dim);
  color: var(--text-dim);
  font-family: var(--font);
  font-size: 8px;
  letter-spacing: 0.08em;
  padding: 0 5px;
  cursor: pointer;
  border-radius: 2px;
  text-transform: uppercase;
  white-space: nowrap;
  flex-shrink: 0;
  transition: border-color 0.15s, color 0.15s, background 0.15s;
}

/* .slider-header is align-items:baseline, so the button's TEXT baseline lined up with the
   label's — and because the button wraps that text in a border and padding, it hung ~1px low and
   sat proud of the label's underside. Centring the MED headers instead puts label, button and
   value on one centre line, which is how the button should read: a control belonging to the
   heading, not a word sharing its baseline.

   Scoped to .med-row deliberately. Every other slider row (bands, frailty, epileptiform, sleep)
   keeps baseline alignment, which is there so the 8px .band-range text sits on the same line as
   the 11px label. Only the med rows contain an element whose box is taller than its text.
   Measured with this pair: button centre exactly on the label centre, and the header back to
   12px — the same as a band row, so the buttons add no height to the panel. */
.med-row .slider-header { align-items: center; }

.bolus-btn:hover:not(:disabled) {
  border-color: var(--accent);
  color: var(--accent);
}

/* Pressed state doubles as the click acknowledgement, since the effect itself takes ~100 case
   seconds to peak and there is nothing to see on the trace for the first moment. */
.bolus-btn:active:not(:disabled) {
  background: rgba(229, 168, 59, 0.22);
  border-color: var(--accent);
  color: var(--accent);
}

.bolus-btn:disabled { cursor: not-allowed; opacity: 0.45; }

.med-row.locked { opacity: 0.4; }
.med-row.locked label,
.med-row.locked .slider-val { color: var(--accent-dim); }
input.dose-slider:disabled { cursor: not-allowed; }

/* ── Monitor controls (PSi + audio + volume, bottom of the sidebar) ──
   PSi stacked above the audio button; both content-width, left-aligned. */
.monitor-controls {
  display: flex;
  flex-direction: column;
  gap: 8px;
  /* Anchor this stack low in the sidebar for a balanced monitor look: as the last flex child
     of the column #control-panel, margin-top:auto collects the spare vertical space above it,
     while margin-bottom leaves a bit of room at the very bottom so it isn't flush to the edge.
     border-bottom:none drops the section divider (it would float above that gap otherwise).
     (Auto margins have no effect once the panel is tall enough to scroll.) */
  margin-top: auto;
  margin-bottom: 16px;
  border-bottom: none;
}

.monitor-controls .audio-btn {
  align-self: flex-start;
}

/* PSi + Summary toggles share one row above the audio button. */
/* Three buttons no longer fit across a --panel-width sidebar, so the row wraps and DISPLAY
   drops onto a second line inside the same block. */
.toggle-row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 6px;
}
.toggle-row .audio-btn {
  align-self: center;   /* the column rule sets flex-start; centre these against each other */
}

/* Volume slider stretches to fill its row. */
.monitor-controls .vol-slider {
  flex: 1;
  width: auto;
}

/* ── Three-zone monitor header ──────────────────────────────────────────────
   Left zone (panel title + export/quiz), a CENTRED brand wordmark, and the
   right zone (Return to Awake). The brand `.header-center` is FIRST in the DOM
   (its markup is the build-extracted BRAND-LOGO block, left in place), so CSS
   `order` puts the panel title at the far left — mirroring the DSA header, where
   "DSA · SPECTROGRAM" sits far left. Equal `flex:1` on the left/right zones keeps
   the wordmark optically centred regardless of their content widths. */
.header-left {
  display: flex;
  align-items: center;
  gap: 12px;
  order: 1;
  flex: 1 1 0;
}

/* Mirror icon — opens the second-screen duplicate window (top-left of the monitor).
   Icon-only, so the gold .audio-on accent carries the open state — the same on-state
   convention as the audio/Index/Summary buttons. Hidden on mobile (below) and hidden
   inside the mirror window itself (mirrorCss in app.js). */
.mirror-btn {
  flex: 0 0 auto;
  width: 22px;
  height: 22px;
  padding: 0;
  background: transparent;
  border: 1px solid var(--text-dim);
  border-radius: 3px;
  color: var(--text-dim);
  font-size: 13px;
  line-height: 1;
  cursor: pointer;
}
.mirror-btn:hover {
  color: var(--accent);
  border-color: var(--accent);
}
.mirror-btn.audio-on {
  color: var(--accent);
  border-color: var(--accent);
  box-shadow: 0 0 6px rgba(229, 168, 59, 0.5);
}

/* Live-stream panel — drops below the header from the stream icon (export-menu pattern):
   the join URL, the session code large in gold, and the live viewer count. Positioned off
   the header-left group; visible whenever a stream is running. */
.stream-panel {
  position: absolute;
  top: 100%;
  left: 0;
  z-index: 30;
  min-width: 240px;
  padding: 10px 14px;
  background: #0b0b0b;
  border: 1px solid var(--accent);
  border-radius: 4px;
  font-family: 'Courier New', Courier, monospace;
  font-size: 11px;
  color: var(--text-dim);
}
.stream-panel.hidden { display: none; }
.stream-url  { color: #bbb; margin-top: 6px; }
.stream-code {
  color: var(--accent);
  font-size: 22px;
  font-weight: bold;
  letter-spacing: 4px;
  margin: 4px 0;
}
.stream-viewers { color: #bbb; }
/* The panel is positioned against the header row. */
.header-left { position: relative; }
.header-center {
  order: 2;
  flex: 0 0 auto;
  display: flex;
  align-items: center;
}

/* Brand wordmark (BRAINWAVE over NEUROSIM, the I and W drawn as EEG signals).
   The markup lives in index.html between the BRAND-LOGO delimiters and carries its own
   fonts/geometry as presentation attributes — all this rule supplies is the size, the gold,
   and the divider, so the same block renders correctly on the standalone's unlock screen
   where styles.css does not exist.

   `color` (not `fill`) is what tints it: the SVG paints with currentColor throughout.

   Height is capped to match #export-btn (5px padding + 10px font ≈ 24px) so the header
   barely grows. #monitor-header is flex-shrink:0 inside the COLUMN flex #monitor-panel, so
   any growth is taken from #canvas-container, never from the control sidebar — the
   "every slider fits without scrolling" invariant is untouched. */
#bw-logo {
  height: 26px;
  width: auto;
  flex-shrink: 0;              /* never let the wordmark squash; see the narrow-window rule */
  color: var(--accent);
}

/* Narrow windows: the wordmark adds ~98px to .header-left, and #monitor-panel clips its own
   overflow (see the note there) specifically so the header can never spill rightward over the
   control panel and cover the Patient-frailty slider. Drop the panel LABEL first — it is the
   most redundant thing in the row, since the waveform it names is directly beneath it — and
   keep the brand and the export button.

   The 1100px breakpoint is measured, not guessed: with the label shown the header content
   needs ~683px, but the panel offers only ~691px at a 1006px window — an 8px margin that any
   font-rendering difference would swallow. Switching at 1100px leaves ~100px of slack. */
@media (max-width: 1100px) {
  .header-left .monitor-title { display: none; }
}

/* Export (with its options menu) keeps its own gold styling. */
.export-wrap {
  position: relative;
}

/* Options menu — opens downward from the export button (it sits in the top header). */
.export-menu {
  position: absolute;
  top: 100%;
  left: 0;
  margin-top: 4px;
  min-width: 132px;
  display: flex;
  flex-direction: column;
  background: #0b0b0b;
  border: 1px solid var(--accent-dim);
  border-radius: 3px;
  overflow: hidden;
  z-index: 20;
  box-shadow: 0 3px 12px rgba(0,0,0,0.7);
}

.export-menu.hidden { display: none; }

.export-opt {
  background: transparent;
  border: none;
  border-bottom: 1px solid var(--border);
  color: var(--accent);
  font-family: var(--font);
  font-size: 10px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  text-align: left;
  white-space: nowrap;
  padding: 7px 11px;
  cursor: pointer;
  transition: background 0.12s;
}

.export-opt:last-child { border-bottom: none; }
.export-opt:hover { background: rgba(229,168,59,0.12); }

/* ── Training display popover (the graduation icon in .header-right) ──
   Export-menu pattern: absolute dropdown off a position:relative wrap, .hidden toggle,
   document-level click-outside close (wireTrainingMenu). Anchored RIGHT because the icon
   sits at the far right of the header. Small text is white/#bbb, never --text-dim — the
   readability rule; these captions are the settings-as-teaching layer, they must be legible. */
.train-wrap { position: relative; }
.train-menu {
  position: absolute;
  top: 100%;
  right: 0;
  margin-top: 4px;
  width: 316px;
  /* Sized to its content, NO max-height and NO overflow scrolling (owner 2026-09-08: the
     popover must be as large as required, never grow scrollbars — the vertical bar also ate
     width and triggered a horizontal one). overflow stays visible so the absolutely-
     positioned .train-tip hover tips on the lower rows are not clipped. With the captions
     out of flow the menu is ~360px tall; on a pathologically short window it may extend
     past the panel, which is acceptable for a transient dropdown. */
  display: flex;
  flex-direction: column;
  gap: 10px;
  padding: 10px 12px;
  background: #0b0b0b;
  border: 1px solid var(--accent-dim);
  border-radius: 3px;
  z-index: 30;
  box-shadow: 0 3px 12px rgba(0,0,0,0.7);
  font-family: var(--font);
  text-align: left;
}
.train-menu.hidden { display: none; }
.train-title {
  color: var(--accent);
  font-size: 10px;
  font-weight: bold;
  letter-spacing: 0.1em;
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 8px;
}
.train-cal { color: #bbb; font-size: 8px; letter-spacing: 0.06em; font-weight: normal; }
.train-cal.train-off { color: var(--accent); font-weight: bold; }
.train-row { display: flex; flex-direction: column; gap: 4px; position: relative; }
.train-row-head { display: flex; justify-content: space-between; align-items: center; gap: 8px; }
.train-label { color: #fff; font-size: 10px; letter-spacing: 0.04em; white-space: nowrap; cursor: help; }
.train-ctl { display: flex; align-items: center; gap: 6px; }
.train-val { color: var(--accent); font-size: 10px; font-weight: bold; min-width: 78px; text-align: center; }
.train-step {
  width: 18px;
  height: 18px;
  padding: 0;
  background: transparent;
  border: 1px solid var(--text-dim);
  border-radius: 2px;
  color: #bbb;
  font-size: 12px;
  line-height: 1;
  cursor: pointer;
}
.train-step:hover { color: var(--accent); border-color: var(--accent); }
.train-menu input[type="range"] { width: 100%; }
/* The teaching captions live in DELAYED HOVER TIPS (owner 2026-09-08 — the inline captions
   made the popover tall). Pure CSS, shared by the training-popover rows (.train-tip) and
   the numeric readouts (.param-tip, in #param-row + the Index): the tip fades in only after
   the pointer has rested on its trigger for 1 s (transition-delay on the :hover state), and
   hides quickly on leave (the base 0-delay transition). pointer-events:none so an open tip
   never traps the pointer. Text stays white/#bbb per the readability rule; the base
   neutralises the big gold numeral styles a .param-tip would otherwise inherit. */
.train-tip,
.param-tip,
.sum-tip,
.guide-tip {
  position: absolute;
  top: 100%;
  z-index: 5;
  margin-top: 2px;
  padding: 6px 8px;
  background: #111;
  border: 1px solid var(--accent-dim);
  border-radius: 3px;
  box-shadow: 0 3px 10px rgba(0,0,0,0.7);
  color: #bbb;
  font-family: var(--font);
  font-size: 8.5px;
  font-weight: normal;
  letter-spacing: normal;
  text-transform: none;
  text-align: left;
  white-space: normal;
  line-height: 1.45;
  opacity: 0;
  visibility: hidden;
  pointer-events: none;
  transition: opacity 0.15s ease 0s, visibility 0s linear 0.15s;
}
.train-tip { left: 0; right: 0; }
/* Readout tips: fixed width, centred under the cell. The first/last cells hug the panel
   edges and #monitor-panel clips its own overflow, so those two align inward instead. */
.param-tip { width: 200px; left: 50%; transform: translateX(-50%); }
.param:first-child .param-tip { left: 0; transform: none; }
.param:last-child  .param-tip { left: auto; right: 0; transform: none; }
#psi-display .param-tip { left: auto; right: 0; transform: none; }
.train-row-head:hover ~ .train-tip,
.param:hover > .param-tip,
#psi-display:hover > .param-tip,
.sum-item.has-tip:hover > .sum-tip,
.guide-item.has-tip:hover > .guide-tip {
  opacity: 1;
  visibility: visible;
  transition-delay: 1s, 1s;
}
/* Summary/guide teaching tips span their panel's width; text-indent 0 undoes the .sum-item
   hanging indent the tip would otherwise inherit. */
.sum-tip, .guide-tip { left: 0; right: 0; text-indent: 0; }
.train-check { color: #fff; font-size: 9px; display: flex; align-items: center; gap: 5px; }
.train-check input { accent-color: var(--accent); }
.train-reset {
  margin-top: 2px;
  padding: 6px 10px;
  background: transparent;
  border: 1px solid var(--accent);
  border-radius: 3px;
  color: var(--accent);
  font-family: var(--font);
  font-size: 9px;
  font-weight: bold;
  letter-spacing: 0.08em;
  cursor: pointer;
}
.train-reset:hover { background: rgba(229,168,59,0.12); }

.audio-btn {
  background: transparent;
  border: 1px solid var(--text-dim);
  color: var(--text-dim);
  font-family: var(--font);
  font-size: 10px;
  letter-spacing: 0.08em;
  padding: 4px 10px;
  cursor: pointer;
  border-radius: 2px;
  text-transform: uppercase;
  white-space: nowrap;
  transition: border-color 0.15s, color 0.15s, box-shadow 0.15s;
}

.audio-btn:hover {
  border-color: var(--accent);
  color: var(--accent);
}

/* The DISPLAY control is an <a>, not a <button>, so it needs the bits a button gets free:
   no underline, and flex centring so its text sits on the same baseline as its neighbours.
   Everything else (border, hover, the gold .audio-on state) comes from .audio-btn above. */
a.audio-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  text-decoration: none;
}

.audio-btn.audio-on {
  border-color: var(--accent);
  color: var(--accent);
  box-shadow: 0 0 6px rgba(0,255,65,0.3);
}

.vol-row {
  display: flex;
  align-items: center;
  gap: 5px;
}

.vol-label {
  font-size: 9px;
  color: var(--text-dim);
  letter-spacing: 0.1em;
  flex-shrink: 0;
}

.vol-slider {
  width: 72px;
  height: 4px;
}

.vol-slider:disabled {
  opacity: 0.25;
  cursor: not-allowed;
  pointer-events: none;
}

/* Audio on/off button with the volume slider BESIDE it (to the right) to save vertical space.
   The PSi button above stays on its own row. */
.audio-row {
  display: flex;
  align-items: center;
  gap: 8px;
}
.audio-row .audio-btn {
  align-self: center;   /* the column rule sets flex-start for the PSi button; centre this one */
  flex-shrink: 0;
}
.audio-row .vol-row {
  flex: 1;              /* VOL label + slider fill the space to the right of the button */
}

/* ── Scrollbar ── */
#control-panel::-webkit-scrollbar { width: 4px; }
#control-panel::-webkit-scrollbar-track { background: transparent; }
#control-panel::-webkit-scrollbar-thumb { background: #222; border-radius: 2px; }

/* ── Educational-use disclaimer ──────────────────────────────────────────────────────────
   Ships in both the standalone and the hosted web app. A startup acknowledgement overlay
   (dismissed via the isolated inline script in index.html) plus a persistent footer reminder
   under the DSA. Gold-on-black, matching the device chrome. */
#disclaimer-overlay {
  position: fixed;
  inset: 0;
  z-index: 1000;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 20px;
  background: rgba(0, 0, 0, 0.88);
}
#disclaimer-overlay.disclaimer-hidden { display: none; }

#disclaimer-card {
  width: 100%;
  max-width: 440px;
  box-sizing: border-box;
  padding: 26px 26px 22px;
  text-align: center;
  color: var(--text);
  background: var(--panel-bg);
  border: 1px solid var(--accent-dim);
  border-radius: 6px;
  box-shadow: 0 0 0 5px #000, 0 0 40px rgba(0, 0, 0, 0.8);
}
#disclaimer-badge {
  display: inline-block;
  margin-bottom: 14px;
  padding: 4px 10px;
  font-size: 10px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--accent);
  border: 1px solid var(--accent-dim);
  border-radius: 3px;
}
#disclaimer-title {
  margin: 0 0 14px;
  font-size: 17px;
  font-weight: bold;
  letter-spacing: 0.06em;
  color: var(--accent-bright);
}
#disclaimer-card p {
  margin: 0 0 12px;
  font-size: 11.5px;
  line-height: 1.6;
}
#disclaimer-card strong { color: var(--accent-bright); font-weight: bold; }

/* Mobile capability disclosure inside the startup card. Hidden for everyone by default; the
   mobile block below reveals it. Desktop and the offline standalone never show it, because
   there is nothing reduced about them. */
#disclaimer-lite { display: none; }
#disclaimer-ack-btn {
  margin-top: 8px;
  padding: 10px 18px;
  font-family: inherit;
  font-size: 12px;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--accent);
  background: transparent;
  border: 1px solid var(--accent);
  border-radius: 3px;
  cursor: pointer;
}
#disclaimer-ack-btn:hover { background: rgba(229, 168, 59, 0.12); }

#disclaimer-footer {
  flex-shrink: 0;
  padding: 4px 8px;
  text-align: center;
  font-size: 9px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  color: var(--accent-dim);
  background: #050505;
  border-top: 1px solid var(--border);
}

/* ── Monitor footer row (index.html + display-compact.html) ──
   One line under the DSA/trend: the compliance strip at the FAR LEFT, the page's monitor
   controls (index: Summary/Index/Audio/VOL/Display, moved out of the sidebar 2026-09-08 so
   the slider deck fits without scrolling; compact: Audio/VOL/Display — its Summary/Index
   toggles never existed) at the right. Pages still carrying the bare #disclaimer-footer
   (quiz, demo) keep the standalone rule above — these are scoped to the wrapper. */
#monitor-footer {
  flex-shrink: 0;
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 3px 8px;
  background: #050505;
  border-top: 1px solid var(--border);
}
/* Inside the row the strip drops its own chrome (the row carries it), hugs the left, and
   keeps its ellipsis — on a narrow window the TEXT truncates, never the controls. */
#monitor-footer #disclaimer-footer {
  flex: 1 1 auto;
  min-width: 0;
  padding: 0;
  text-align: left;
  background: transparent;
  border-top: none;
}
.footer-controls {
  display: flex;
  align-items: center;
  gap: 6px;
  flex-shrink: 0;
}
.footer-controls .vol-slider { width: 72px; }

/* Mobile-only chrome is inert on desktop — the markup ships in both publish targets, but
   nothing reveals it unless body.mobile-layout is set. */
#rotate-prompt,
#mobile-scrim,
#mobile-dock { display: none; }


/* ═══════════════════════════════════════════════════════════════════════════════════════
   MOBILE LAYOUT
   ═══════════════════════════════════════════════════════════════════════════════════════
   Everything below is scoped to `body.mobile-layout`, set before first paint by the boot
   script in index.html (coarse pointer AND screen short-side <= 500px, so tablets keep the
   desktop layout). Desktop matches NONE of it — that is the point. Keep it that way: if a
   rule here needs to change something for everyone, change the base rule instead and prove
   the desktop result is identical.

   Target device is a phone in LANDSCAPE. iPhone 14 is 844x390 CSS px; minus the 47px side
   insets and 21px bottom inset that leaves 750x369 to lay out in. Portrait gets the rotate
   overlay rather than a second layout.

   CONTROLS LIVE ON THE RIGHT, NOT THE BOTTOM. In landscape the trace has width to spare but
   almost no height, so spending 34px of that height on a horizontal dock was the wrong axis —
   and it cost the DSA the height it needs to render at all (see --dsa-h below). A vertical
   rail takes width the trace can afford, and as a bonus the chips get ~39px of height each
   instead of 30px.

   The vertical budget is the whole design constraint (369px, measured):
     monitor header  25   canvas  190 (flex remainder)   param row  20
     DSA            118   footer   16                                = 369
   Tapping the DSA header collapses it (--dsa-h -> 17px) and hands 101px back to the trace.
   Every one of these is a variable below BECAUSE the solo tray anchors itself off them —
   change a number and the tray follows. Re-check the total when you do.
   ═════════════════════════════════════════════════════════════════════════════════════ */

body.mobile-layout {
  /* Fixed + overscroll-none stops iOS rubber-banding the whole app when a drag starts on
     the trace; text-size-adjust stops Safari inflating the numerics in landscape. */
  position: fixed;
  inset: 0;
  overscroll-behavior: none;
  -webkit-text-size-adjust: 100%;
  -webkit-tap-highlight-color: transparent;
  /* Touch-sized range thumb. app.js reads --thumb-half for its drag maths, so raising these
     two is the ONLY change needed to make every slider in the app touch-targetable. */
  --thumb: 28px;
  --thumb-half: 14px;
  /* Width of the vertical chip rail down the right edge. Widened 62 -> 110 -> 130 (2026-08-07,
     owner request) to narrow the elongated landscape trace and give the control stack more
     presence. Everything keys off this one var — #app padding-right, #mobile-dock width, and the
     #control-panel parked/docked positions — so it is the single knob for the split. */
  --rail-w: 130px;
  /* The three fixed-height rows under the trace. They are variables rather than literals
     because the solo tray anchors its bottom edge off their sum, so it lands exactly at the
     foot of the TRACE and never covers the parameters, the DSA or the compliance footer.
     (First version of this layout covered the footer outright — the "always visible"
     educational-use reminder vanished whenever a slider was open.) */
  /* 16 → 24px (2026-09-08): the footer row now also carries the Index/Audio/VOL controls
     (moved out of the sidebar). Everything below the trace anchors off this var, so the
     solo tray and sheets track the taller row automatically; the ~8px comes off the trace. */
  --footer-h: 24px;
  --param-h: 20px;
  /* 118px is not cosmetic. dsaRender subtracts a fixed topPad+botPad+divGap before halving
     what is left into two spectrograms, and bails to a BLACK panel if that remainder is <= 0.
     At the desktop chrome the floor is 58px of wrapper; DSA_COMPACT in app.js drops it to 30.
     118 here leaves ~103px of wrapper -> ~36px per spectrogram. Do not shrink this without
     checking dsaRender's guard — a too-short DSA fails silently, as a black box. */
  --dsa-h: 118px;
}

/* The rail is fixed to the right edge, so the app shell reserves its width. Side insets keep
   the trace out from under the notch in landscape (either edge — iOS reports the inset on
   whichever side the notch lands); the bottom inset clears the home indicator. */
body.mobile-layout #app {
  padding-left: env(safe-area-inset-left);
  padding-right: calc(var(--rail-w) + env(safe-area-inset-right));
  padding-bottom: env(safe-area-inset-bottom);
}

/* The sidebar leaves the flex flow (it becomes the bottom sheet below), so the monitor panel
   is the only child and its divider has nothing to divide. */
body.mobile-layout #monitor-panel { border-right: none; }

/* ── Header: 43px -> 28px ──
   .monitor-title is already hidden here by the existing max-width:1100px rule. The quiz link
   goes because quiz.html has no mobile layout — offering its entry point from a screen that
   cannot render it is a dead end. */
body.mobile-layout #monitor-header { padding: 3px 8px; gap: 6px; }
body.mobile-layout #bw-logo        { height: 18px; }
body.mobile-layout #quiz-link      { display: none; }
/* Export is removed on mobile, not merely shrunk. Two reasons beyond the header space it
   frees: 'full' mode needs getDisplayMedia, which does not exist on iOS Safari, so a third of
   the menu could only ever fail there; and a phone is a viewing device, not where anyone
   assembles a teaching clip. app.js skips wireExportMenu() entirely under this class, so the
   document-level click listener that closes the menu is never attached either. */
body.mobile-layout .export-wrap    { display: none; }
body.mobile-layout .awake-btn      { padding: 3px 7px; font-size: 9px; }
body.mobile-layout .header-left    { gap: 6px; }
body.mobile-layout .header-right   { gap: 6px; }

/* ── Trace: the wrapper takes the whole container ──
   The desktop 60%/80% exists to leave ~20% clear on each side for the Summary and PSi
   readouts. On a phone there is no width to give away, so the wrapper fills and both
   readouts become overlays ON the trace (below). The box-shadow halo goes with the gaps. */
body.mobile-layout #canvas-wrapper {
  width: 100%;
  height: 100%;
  border-width: 1px;
  box-shadow: none;
}

/* PSi -> compact overlay in the trace's top-right corner. Laid out as a row so the label sits
   beside the numeral instead of stacking and stealing height. */
body.mobile-layout #psi-display {
  top: 4px;
  right: 6px;
  transform: none;
  flex-direction: row;
  align-items: baseline;
  gap: 4px;
  padding: 1px 5px;
  background: rgba(0, 0, 0, 0.72);
  border: 1px solid var(--border);
  border-radius: 2px;
}
body.mobile-layout .psi-label { font-size: 8px; letter-spacing: 0.12em; }
body.mobile-layout .psi-value { font-size: 26px; }

/* Summary is REMOVED on mobile, panel and toggle both.
   On desktop it lives in the ~20% dead gap beside the 60%-wide trace and costs nothing. On a
   phone the trace fills the width, so the panel can only sit ON the waveform — and a block of
   text over the trace is exactly the thing a viewer is trying to look past. The toggle goes
   too: leaving a control that reveals a hidden panel is worse than not offering it.
   No JS change is needed — summaryUpdate/guideRefresh only run while state.leftPanelMode
   selects them, and with the toggles gone nothing can set it. The Field guide goes with the
   summary for the same reasons (and its hover tips need a pointer anyway). */
body.mobile-layout #summary-panel,
body.mobile-layout #summary-btn,
body.mobile-layout #guide-panel,
body.mobile-layout #guide-btn { display: none; }
/* DISPLAY is hidden on the phone layout. The alternative presentations are desktop-only for
   now: body.mobile-layout has a hand-tuned vertical budget (--dsa-h and friends) that those
   layouts have not been fitted to, and offering a link into an unfitted page is worse than
   not offering it. Deferred deliberately, not overlooked. */
body.mobile-layout #display-link { display: none; }
/* The mirror window is a desktop teaching feature (a phone doesn't drive a projector). */
body.mobile-layout #mirror-btn   { display: none; }
/* Training display popover is desktop-only (like the export menu): the phone layout runs its
   own display calibration and has no room for the dropdown. app.js also skips
   wireTrainingMenu() under this class, so its document-level click-closer never attaches. */
body.mobile-layout .train-wrap   { display: none; }
/* The live stream is driven from a desktop too; a phone is a VIEWER (via /view), not a sender. */
body.mobile-layout #stream-btn   { display: none; }
body.mobile-layout #stream-panel { display: none; }

/* ── Parameter row ── */
body.mobile-layout #param-row    { height: var(--param-h); padding: 0 8px; gap: 4px; }
body.mobile-layout .param        { gap: 3px; }
body.mobile-layout .param-value  { font-size: 15px; min-width: 3.5ch; }
body.mobile-layout .param-label,
body.mobile-layout .param-unit   { font-size: 7px; }

/* ── DSA ──
   Height comes from --dsa-h, which the solo tray also anchors off. Tapping the header
   collapses it (wired in app.js on the header element that is already there, rather than
   spending a rail chip on it); overriding the VARIABLE rather than the flex-basis means the
   tray's anchor follows the collapse automatically. */
body.mobile-layout #dsa-panel   { flex: 0 0 var(--dsa-h); }
body.mobile-layout #dsa-header  { padding: 2px 8px; cursor: pointer; }
body.mobile-layout .dsa-title   { font-size: 8px; }
body.mobile-layout .dsa-info    { font-size: 7px; }
body.mobile-layout.dsa-collapsed             { --dsa-h: 17px; }
body.mobile-layout.dsa-collapsed #dsa-wrapper { display: none; }

/* ── Footer: 20px -> --footer-h. Shrunk, never removed, and never covered — it is the
   compliance surface. The explicit height is what the control sheet's bottom offset is
   computed from, so the two cannot drift apart. ── */
/* The height lives on the ROW (the wrapper is what the sheet offsets must clear); the
   strip inside just shrinks its type. Note `#monitor-footer #disclaimer-footer` (two ids)
   outranks a `body.mobile-layout #disclaimer-footer` rule, so the mobile overrides for the
   strip are scoped through the wrapper too. */
body.mobile-layout #monitor-footer {
  height: var(--footer-h);
  padding: 1px 6px;
  gap: 6px;
}
body.mobile-layout #monitor-footer #disclaimer-footer {
  font-size: 8px;
  letter-spacing: 0.04em;
}
/* The controls ride the footer on the phone too (they used to live in the All sheet's
   sidebar copy; the row keeps Index/Audio/VOL one tap away — Summary and Display are
   already hidden on mobile by their id rules above). */
body.mobile-layout .footer-controls { gap: 4px; }
body.mobile-layout .footer-controls .audio-btn { padding: 1px 5px; font-size: 8px; }
body.mobile-layout .footer-controls .vol-slider { width: 48px; }
body.mobile-layout .footer-controls .vol-label  { font-size: 7px; }

/* ── Startup disclaimer card ──
   The desktop card is ~330px tall inside a 369px landscape viewport with no scroll, so it
   clips its own acknowledge button. This is the first thing a mobile user ever sees. */
body.mobile-layout #disclaimer-overlay   { padding: 10px; overflow-y: auto; align-items: flex-start; }
body.mobile-layout #disclaimer-card      { padding: 14px 16px; max-width: 480px; }
body.mobile-layout #disclaimer-badge     { margin-bottom: 8px; padding: 3px 8px; font-size: 9px; }
body.mobile-layout #disclaimer-title     { margin: 0 0 8px; font-size: 15px; }
body.mobile-layout #disclaimer-card p    { margin: 0 0 8px; font-size: 10.5px; line-height: 1.45; }
body.mobile-layout #disclaimer-ack-btn   { margin-top: 4px; padding: 8px 16px; font-size: 11px; }

/* Capability disclosure — revealed only on the phone layout. Boxed and gold-tagged rather
   than set as another body paragraph: it is a different KIND of statement from the two above
   it (what the build can do, not what it must not be used for), and a reader skimming to the
   button needs to register it as its own item. */
body.mobile-layout #disclaimer-lite {
  display: block;
  margin: 0 0 10px;
  padding: 7px 9px;
  background: rgba(229, 168, 59, 0.06);
  border: 1px solid var(--accent-dim);
  border-radius: 3px;
  font-size: 10px;
  line-height: 1.45;
  /* Brighter than the --text (#8a8a8a, 6.1:1) used by the paragraphs above it. This box sits
     on a tinted panel rather than pure black and is the smallest text on the card, so it
     needs the extra headroom to read at arm's length. */
  color: #b0b0b0;
}
body.mobile-layout .disclaimer-lite-tag {
  display: block;
  margin-bottom: 3px;
  font-size: 9px;
  font-weight: bold;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--accent);
}

/* ═══ Controls: the dock, and #control-panel re-cast as its sheet ═══════════════════════
   The sliders are NOT moved, cloned or proxied. #control-panel simply becomes a bottom sheet
   and CSS reveals one row or a subset of rows in place. That matters because the engine
   WRITES to these inputs continuously — setSliderValue() runs every frame of a 2-6 minute
   drug glide, and again for all seven bands whenever frailty moves — so any proxy would be a
   second source of truth for a value changing 60x a second, and would have to duplicate the
   -val formatting, the .med-row.locked dim, the per-drug colours and the disabled state.
   Reveal-in-place needs none of that, and verify-clinical.js §15 cannot even see it. */

body.mobile-layout #control-panel {
  position: fixed;
  /* Slides in from the RIGHT, tucked against the inside edge of the chip rail. top/bottom are
     given explicitly so the element has a defined box even while parked — a fixed element
     with `top: auto` falls back to its static-flow position, which is not something to rely
     on once the panel has left the flex row. */
  right: calc(var(--rail-w) + env(safe-area-inset-right));
  left: auto;
  top: 0;
  bottom: calc(var(--footer-h) + env(safe-area-inset-bottom));
  width: min(320px, 42vw);
  z-index: 30;
  border-left: 1px solid var(--accent-dim);
  border-top: none;
  /* Parked off-screen (clearing its own width AND the rail) and inert until a chip opens it. */
  transform: translateX(calc(100% + var(--rail-w)));
  /* 3x slower than the original 0.22s, at the user's request — at phone size the tray covers
     a chunk of the trace, and a slow slide reads as the panel arriving rather than the trace
     being snatched away. */
  transition: transform 0.66s ease;
  pointer-events: none;
  overflow: hidden;
}

/* SOLO — one slider row as a live tray at the foot of the TRACE, so the EEG stays visible
   while it is dragged. The bottom offset is the sum of everything below the trace, so the
   tray lands exactly on the trace's bottom edge and never covers the parameters, the DSA or
   the compliance footer. Every other row, section title and the toggle block are hidden;
   zeroing section padding/border lets the now-empty sections collapse to nothing as flex
   items, leaving only the target row with height. */
body.mobile-layout #control-panel.solo {
  transform: none;
  pointer-events: auto;
  top: auto;
  bottom: calc(var(--footer-h) + var(--dsa-h) + var(--param-h) + env(safe-area-inset-bottom));
  height: auto;
  border: 1px solid var(--accent-dim);
  border-radius: 3px 0 0 3px;
}
body.mobile-layout #control-panel.solo .control-section {
  padding: 0;
  border-bottom: none;
}
body.mobile-layout #control-panel.solo .slider-row:not(.solo-target),
body.mobile-layout #control-panel.solo .section-title,
body.mobile-layout #control-panel.solo .monitor-controls { display: none; }
body.mobile-layout #control-panel.solo .solo-target {
  padding: 7px 14px 9px;
  margin-bottom: 0;
}

/* SHEET — the full panel, or just the band section, as a full-height right drawer. The PSi
   and Audio toggles live in .monitor-controls inside this same panel, so "More" gets them for
   free (Summary's toggle is hidden — see the Summary note above). Stops above the footer so
   the compliance strip stays readable even in this modal mode. */
body.mobile-layout #control-panel.sheet {
  transform: none;
  pointer-events: auto;
  top: 0;
  bottom: calc(var(--footer-h) + env(safe-area-inset-bottom));
  height: auto;
  overflow-y: auto;
}
body.mobile-layout #control-panel.sheet-bands .patient-section,
body.mobile-layout #control-panel.sheet-bands .med-section,
body.mobile-layout #control-panel.sheet-bands .monitor-controls { display: none; }

/* Scrim behind an open sheet (not behind solo — solo is meant to be used while watching the
   trace, so the trace must stay tappable to dismiss it). */
body.mobile-layout #mobile-scrim {
  display: block;
  position: fixed;
  inset: 0;
  background: rgba(0, 0, 0, 0.55);
  z-index: 25;
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.22s ease;
}
body.mobile-layout #mobile-scrim.open { opacity: 1; pointer-events: auto; }

/* ── The chip rail (right edge, vertical) ──
   Nine chips sharing the full 369px of height get ~39px each — a BIGGER tap target than the
   30px they had in the old horizontal dock, taken out of width the trace can spare rather
   than height it cannot. */
body.mobile-layout #mobile-dock {
  display: flex;
  flex-direction: column;
  position: fixed;
  top: 0;
  /* Height, not top+bottom, and in `svh` — the SMALLEST viewport, i.e. with browser chrome
     shown. Matches #app exactly, so the rail is laid out for the height that is actually
     visible in a Safari/Chrome tab rather than the taller chrome-hidden one. */
  bottom: auto;
  height: 100svh;
  right: 0;
  left: auto;
  z-index: 40;
  gap: 2px;
  align-items: stretch;
  /* NOT justify-content:center. Centring a flex column that OVERFLOWS its scroll container
     pushes the overflow out of BOTH ends, and the start of it cannot be scrolled back to —
     with browser chrome present the usable height drops under the ~312px the nine chips need,
     and the first chip (Propofol) ended up at -6px, above the rail and unreachable. The auto
     margins on the first and last chip below centre the stack when there IS room and collapse
     to 0 when there is not, so it scrolls normally instead of clipping. */
  justify-content: flex-start;
  width: calc(var(--rail-w) + env(safe-area-inset-right));
  padding: calc(4px + env(safe-area-inset-top)) calc(4px + env(safe-area-inset-right)) calc(4px + env(safe-area-inset-bottom)) 4px;
  background: #050505;
  border-left: 1px solid var(--border);
  border-top: none;
  /* If a short screen can't fit all nine, scroll them rather than shrinking them below a
     usable tap target. */
  overflow-y: auto;
  overflow-x: hidden;
  scrollbar-width: none;
}
body.mobile-layout #mobile-dock::-webkit-scrollbar { display: none; }

body.mobile-layout .dock-chip {
  /* Fixed height rather than flex:1 — stretching the chips to fill the rail left no breathing
     room at either end and read as a solid slab. At 32px the nine chips plus gaps come to
     ~320px of a 390px rail, so `justify-content: center` on the rail leaves ~35px of black
     above and below. Still well clear of WCAG 2.2 AA's 24px minimum target. */
  flex: 0 0 auto;
  /* 30px, with a 2px gap, puts the nine chips + padding at 294px — so the stack still fits
     WITHOUT scrolling once browser chrome has taken its cut (a Chrome/Safari tab leaves
     roughly 300-330px of a 390px landscape viewport). At the full height it leaves ~48px of
     black at each end. Comfortably above WCAG 2.2 AA's 24px minimum target either way. */
  height: 30px;
  padding: 2px 4px;
  font-family: var(--font);
  font-size: 10px;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--text);
  background: transparent;
  border: 1px solid var(--border);
  border-radius: 3px;
  cursor: pointer;
  touch-action: manipulation;   /* kills the 300ms double-tap-zoom delay */
}
/* The safe centring pair — see the justify-content note on #mobile-dock. Auto margins centre
   the stack while it fits and resolve to 0 once it overflows, which keeps the first chip
   reachable on a short viewport instead of stranding it above the scroll origin. */
body.mobile-layout .dock-chip:first-child { margin-top: auto; }
body.mobile-layout .dock-chip:last-child  { margin-bottom: auto; }

/* Gold while its control is the one on screen — the same on/off language as .audio-on. */
body.mobile-layout .dock-chip.active {
  color: var(--accent);
  border-color: var(--accent);
  background: rgba(229, 168, 59, 0.12);
}
/* A chip whose control is currently locked out (med sliders during a sleep stage) reads as
   unavailable rather than silently doing nothing. Mirrors .med-row.locked. */
body.mobile-layout .dock-chip.chip-locked { opacity: 0.35; }

/* ── Touch-sized tick marks ──
   The base tick offsets are hand-tuned literals for the 7px desktop half-thumb and are left
   untouched (rewriting them in terms of --thumb-half would move desktop by ~0.7px, since the
   literals never matched the exact formula). These recompute them for the 28px thumb.
   Tick centre for value fraction f is  f*100% + TH*(1-2f)px  with TH = 14; a 2px tick's
   background-position is that minus 1px. Selectors are (0,2,3) and beat the (0,1,2) base. */
body.mobile-layout input.dose-slider::-webkit-slider-runnable-track,
body.mobile-layout input.dose-slider::-moz-range-track {
  background-position: calc(33.333% + 3.7px) center, calc(66.667% - 5.7px) center;
}
body.mobile-layout input.dose-slider.seizure-slider::-webkit-slider-runnable-track,
body.mobile-layout input.dose-slider.seizure-slider::-moz-range-track,
body.mobile-layout input.dose-slider.sleep-slider::-webkit-slider-runnable-track,
body.mobile-layout input.dose-slider.sleep-slider::-moz-range-track {
  background-position: calc(25% + 6px) center, calc(50% - 1px) center, calc(75% - 8px) center;
}
/* .dose-labels are centred text, not 2px ticks, so no -1px term: f*100% + 14*(1-2f)px. */
body.mobile-layout .dose-labels span:nth-child(2) { left: calc(25% + 7px); }
body.mobile-layout .dose-labels span:nth-child(4) { left: calc(75% - 7px); }
/* …and the same formula at the thirds for the 4-stop strip: 14*(1−⅔) = ±4.67px. */
body.mobile-layout .dose-labels.quad span:nth-child(2) { left: calc(33.333% + 4.67px); }
body.mobile-layout .dose-labels.quad span:nth-child(3) { left: calc(66.667% - 4.67px); }
body.mobile-layout .dose-labels.quad span:nth-child(4) { left: auto; right: 0; }
/* The taller thumb needs the label strip pushed clear of it. */
body.mobile-layout .dose-labels { margin-top: 8px; }

/* Bolus buttons at touch size. They live inside the med .slider-row, so solo mode reveals each
   one with its own row via the existing data-ctl dock chips — no new chip is needed. The 8px
   desktop size is a mouse target; a finger needs the larger hit area. */
body.mobile-layout .bolus-btn {
  font-size: 10px;
  padding: 5px 10px;
  letter-spacing: 0.06em;
}

/* ── Portrait guard ──
   An overlay, so the simulator underneath stays laid out and running: hiding #app would give
   a 0x0 canvas and make every rotation pay a full resize + phase-cache reset. Above the
   disclaimer overlay (z-index 1000) so a first-run portrait user is told to rotate first. */
@media (orientation: portrait) {
  body.mobile-layout #rotate-prompt {
    display: flex;
    position: fixed;
    inset: 0;
    z-index: 1100;
    align-items: center;
    justify-content: center;
    padding: 24px;
    background: #000;
    text-align: center;
  }
  body.mobile-layout #rotate-card  { max-width: 300px; }
  body.mobile-layout #rotate-glyph {
    font-size: 54px;
    line-height: 1;
    color: var(--accent);
    margin-bottom: 18px;
  }
  body.mobile-layout #rotate-title {
    font-size: 15px;
    font-weight: bold;
    letter-spacing: 0.1em;
    text-transform: uppercase;
    color: var(--accent-bright);
    margin-bottom: 10px;
  }
  body.mobile-layout #rotate-sub {
    font-size: 11px;
    line-height: 1.5;
    color: var(--text);
  }
  /* Capability disclaimer — deliberately quieter than the rotate instruction (which is the
     thing the user has to act on) but set off by a rule and a gold tag so it is not mistaken
     for filler. */
  body.mobile-layout #rotate-lite {
    margin-top: 16px;
    padding-top: 12px;
    border-top: 1px solid var(--border);
    font-size: 10px;
    line-height: 1.5;
    /* NOT --text-dim (#444): that is 2.1:1 on black, well under WCAG AA's 4.5:1 for body
       text this size, and it genuinely disappeared on a phone screen. #b0b0b0 is 9.7:1.
       The monitor chrome can afford dim greys because it is glanceable furniture; a
       disclosure the reader is meant to ACT on cannot. */
    color: #b0b0b0;
  }
  body.mobile-layout .rotate-lite-tag {
    display: block;
    margin-bottom: 3px;
    font-size: 9px;
    font-weight: bold;
    letter-spacing: 0.12em;
    text-transform: uppercase;
    /* Full accent, not --accent-dim (#6b5a33 = 2.5:1) — same reasoning. */
    color: var(--accent);
  }
  /* Nothing under the overlay should be reachable while it is up. */
  body.mobile-layout #mobile-dock { display: none; }
}
