/* RocaViz chrome — light surfaces, recessive hairlines, ink for text. */

:root {
  /* Native controls (checkboxes) follow the app's theme, not the OS's. */
  color-scheme: light;
  --page: #f9f9f7;
  --surface: #fcfcfb;
  --ink: #0b0b0b;
  --ink-2: #52514e;
  --muted: #898781;
  --grid: #e1e0d9;
  --baseline: #c3c2b7;
  --border: rgba(11, 11, 11, 0.10);
  --accent: #2a78d6;
  --link: #2a78d6;  /* = --accent; DataTable shadows --accent, not this */
  --good: #0ca30c;
}

/* Dark theme. The inline <head> script in app.py's index_string always
   stamps data-theme explicitly (localStorage, else prefers-color-scheme)
   before first paint, so this is the one and only dark ruleset needed — no
   separate @media (prefers-color-scheme) copy to keep in sync. */
:root[data-theme="dark"] {
  color-scheme: dark;
  --page: #131315;
  --surface: #1c1c1f;
  --ink: #f3f2ee;
  --ink-2: #bdbbb3;
  --muted: #86847c;
  --grid: #302f31;
  --baseline: #454448;
  --border: rgba(255, 255, 255, 0.12);
  --accent: #56a0f0;
  --link: #56a0f0;
  --good: #29c144;

  /* Dash 4's own components (Dropdown, RangeSlider, ...) read these
     design tokens directly instead of our --ink/--surface variables, so
     they need their own dark remap or they stay hardcoded white-on-white
     with unreadable (inherited var(--ink)) text. */
  --Dash-Fill-Inverse-Strong: #1c1c1f;
  --Dash-Text-Strong: rgba(243, 242, 238, 0.92);
  --Dash-Text-Primary: rgba(243, 242, 238, 0.87);
  --Dash-Text-Weak: rgba(243, 242, 238, 0.65);
  --Dash-Text-Disabled: rgba(243, 242, 238, 0.4);
  --Dash-Stroke-Weak: rgba(255, 255, 255, 0.14);
  --Dash-Stroke-Strong: rgba(255, 255, 255, 0.45);
  --Dash-Fill-Primary-Hover: rgba(255, 255, 255, 0.06);
  --Dash-Fill-Primary-Active: rgba(255, 255, 255, 0.1);
  --Dash-Fill-Interactive-Weak: rgba(255, 255, 255, 0.06);
  --Dash-Fill-Disabled: rgba(255, 255, 255, 0.08);
}

* {
  box-sizing: border-box;
}

body {
  margin: 0;
  background: var(--page);
  color: var(--ink);
  font-family: system-ui, -apple-system, "Segoe UI", sans-serif;
  font-size: 14px;
  line-height: 1.45;
}

.topbar {
  display: flex;
  align-items: baseline;
  gap: 24px;
  padding: 12px 28px;
  background: var(--surface);
  border-bottom: 1px solid var(--grid);
}

.brand {
  font-weight: 650;
  font-size: 16px;
  color: var(--ink);
  text-decoration: none;
  letter-spacing: 0.01em;
}

.nav-links {
  display: flex;
  gap: 18px;
}

.nav-section {
  display: flex;
  align-items: baseline;
  gap: 14px;
}

.nav-section + .nav-section {
  border-left: 1px solid var(--grid);
  padding-left: 18px;
}

.nav-section-label {
  color: var(--muted);
  font-size: 11px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.06em;
}

.nav-link {
  color: var(--ink-2);
  text-decoration: none;
  font-size: 13px;
}

.nav-link:hover {
  color: var(--ink);
}

.nav-dropdown {
  position: relative;
}

.nav-caret {
  margin-left: 3px;
  font-size: 10px;
  color: var(--muted);
}

/* Below the link, with no gap: the pointer must be able to cross into it. */
.nav-menu {
  display: none;
  position: absolute;
  top: 100%;
  left: -10px;
  z-index: 50;
  min-width: 210px;
  padding: 6px 0;
  margin-top: 0;
  background: var(--surface);
  border: 1px solid var(--grid);
  border-radius: 8px;
  box-shadow: 0 6px 18px rgba(0, 0, 0, 0.12);
}

.nav-dropdown::after {  /* hover bridge over the topbar's padding */
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  top: 100%;
  height: 8px;
}

.nav-dropdown:hover .nav-menu,
.nav-dropdown:focus-within .nav-menu {
  display: block;
}

.nav-menu-link {
  display: block;
  padding: 6px 14px;
  color: var(--ink-2);
  text-decoration: none;
  font-size: 13px;
  white-space: nowrap;
}

.nav-menu-link:hover,
.nav-menu-link:focus {
  background: rgba(127, 127, 127, 0.08);
  color: var(--ink);
}

.theme-toggle {
  margin-left: auto; /* pushes it to the topbar's right edge */
  width: 30px;
  height: 30px;
  border-radius: 7px;
  border: 1px solid var(--border);
  background: var(--surface);
  color: var(--ink-2);
  font-size: 15px;
  line-height: 1;
  cursor: pointer;
  opacity: 0.7;
}

.theme-toggle:hover {
  opacity: 1;
  border-color: var(--ink-2);
}

.content {
  max-width: 1280px;
  margin: 0 auto;
  padding: 24px 28px 64px;
}

.page-header {
  margin: 8px 0 20px;
}

.page-header-top {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 12px;
}

.header-actions {
  display: flex;
  gap: 8px;
  flex-shrink: 0;
}

.page-title {
  font-size: 22px;
  font-weight: 650;
  margin: 0 0 4px;
}

.page-subtitle {
  color: var(--ink-2);
  margin: 4px 0 0;
  font-size: 13px;
}

.badge-row {
  display: flex;
  gap: 8px;
  margin: 6px 0;
}

.badge {
  display: inline-block;
  padding: 2px 9px;
  border-radius: 999px;
  border: 1px solid var(--border);
  background: var(--surface);
  color: var(--ink-2);
  font-size: 12px;
}

.badge-running {
  color: var(--good);
  border-color: rgba(12, 163, 12, 0.35);
}

.badge-muted {
  color: var(--muted);
}

/* Experiment-type badges: fixed tint per type, everywhere. */
.badge-type-flowcell {
  background: rgba(74, 58, 167, 0.08);
  border-color: rgba(74, 58, 167, 0.30);
  color: #4a3aa7;
}

.badge-type-galv {
  background: rgba(42, 120, 214, 0.08);
  border-color: rgba(42, 120, 214, 0.30);
  color: #1c5cab;
}

.badge-type-cv {
  background: rgba(0, 131, 0, 0.08);
  border-color: rgba(0, 131, 0, 0.30);
  color: #005e00;
}

.badge-type-select {
  background: rgba(235, 104, 52, 0.10);
  border-color: rgba(235, 104, 52, 0.35);
  color: #b34415;
}

.badge-type-activ {
  background: rgba(237, 161, 0, 0.12);
  border-color: rgba(237, 161, 0, 0.40);
  color: #8a5d00;
}

.badge-type-other {
  background: rgba(11, 11, 11, 0.04);
  border-color: var(--border);
  color: var(--ink-2);
}

.badge-type-none {
  color: var(--muted);
  border-style: dashed;
}

.badge-archived {
  color: var(--muted);
  border-style: dashed;
}

.badge-archived:empty {
  display: none;
}

.link-row {
  margin: -8px 0 16px;
  display: flex;
  gap: 16px;
}

.page-link {
  color: var(--accent);
  text-decoration: none;
  font-size: 13px;
}

.page-link:hover {
  text-decoration: underline;
}

.notice {
  background: var(--surface);
  border: 1px solid var(--grid);
  border-left: 3px solid #eda100;
  border-radius: 6px;
  padding: 10px 14px;
  color: var(--ink-2);
  font-size: 13px;
}

/* ---- stat tiles ---- */
.stat-row {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
  gap: 12px;
  margin: 0 0 18px;
}

.stat-tile {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 8px;
  padding: 12px 14px;
}

.stat-label {
  font-size: 12px;
  color: var(--ink-2);
}

.stat-value {
  font-size: 22px;
  font-weight: 600;
  margin-top: 2px;
}

.stat-sub {
  font-size: 11px;
  color: var(--muted);
  margin-top: 2px;
}

/* ---- multi-metric stat cards ---- */
.stat-cards-row {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
  gap: 18px;
  margin: 0 0 18px;
  align-items: start;
}

.stat-cards-row .card {
  margin-bottom: 0;
}

/* One column of the row holding two stacked cards (electrode + electrolyte),
   which is what keeps three columns of unequal content near equal height. */
.stat-cards-col {
  display: flex;
  flex-direction: column;
  gap: 18px;
}

/* Rows stack vertically inside a card; a row holds one or two metrics. */
.multi-stat-grid {
  display: flex;
  flex-direction: column;
  gap: 11px;
}

.multi-stat-row {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 8px 16px;
}

.multi-stat-cols-3 {
  grid-template-columns: 1fr 1fr 1fr;
}

.multi-stat-row-single {
  grid-template-columns: 1fr;
}

.value-pill {
  display: inline-block;
  padding: 0 8px;
  border-radius: 6px;
}

.value-good {
  background: rgba(12, 163, 12, 0.12);
}

.value-bad {
  background: rgba(208, 59, 59, 0.12);
}

.value-warn {
  background: rgba(237, 161, 0, 0.16);
  color: #8a5d00;
}

.value-orange {
  background: rgba(235, 104, 52, 0.12);
}

.stat-value-sm {
  font-size: 18px;
  font-weight: 600;
  margin-top: 1px;
}

/* ---- cycle range slider (Dash 4 slider, incl. built-in end inputs) ---- */
.cycle-range-row {
  gap: 14px;
  margin-bottom: 20px;
}

.cycle-range-row .cycle-slider {
  flex: 1;
  min-width: 280px;
  max-width: 720px;
}

/* Dash 4 sliders paint from their own theme variable; re-point it. */
.cycle-slider {
  --Dash-Fill-Interactive-Strong: var(--accent);
}

.cycle-slider .dash-slider-root .dash-slider-range {
  background-color: var(--accent);
}

.cycle-slider .dash-slider-root .dash-slider-thumb {
  border-color: var(--accent);
}

/* ---- view tabs (global / per-cycle) ---- */
.view-tabs {
  display: flex !important;
  width: auto !important;
  border-bottom: 1px solid var(--grid);
  margin-bottom: 12px;
}

.view-tabs .view-tab {
  width: auto !important;
  flex: 0 0 auto !important;
  padding: 7px 16px !important;
  border: none !important;
  border-bottom: 2px solid transparent !important;
  background: transparent !important;
  color: var(--ink-2);
  font-size: 13px;
  cursor: pointer;
}

.view-tabs .view-tab:hover {
  color: var(--ink);
}

.view-tabs .view-tab--selected {
  color: var(--ink);
  font-weight: 600;
  border-bottom: 2px solid var(--accent) !important;
}

/* ---- min/median/mean/max summary table ---- */
.stats-table {
  border-collapse: collapse;
  width: 100%;
  font-size: 13px;
  font-variant-numeric: tabular-nums;
}

.stats-table th {
  text-align: right;
  color: var(--ink-2);
  font-weight: 600;
  padding: 4px 14px;
  border-bottom: 1px solid var(--baseline);
}

.stats-table td {
  text-align: right;
  padding: 5px 14px;
  border-bottom: 1px solid var(--grid);
}

.stats-table tr:last-child td {
  border-bottom: none;
}

.stats-table .stats-metric {
  text-align: left;
  color: var(--ink-2);
  padding-left: 0;
}

.stats-table th:first-child {
  text-align: left;
  padding-left: 0;
}

/* ---- cards ---- */
.card {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 8px;
  padding: 16px 18px;
  margin: 0 0 18px;
}

.card-title {
  font-size: 14px;
  font-weight: 650;
  margin: 0 0 10px;
  color: var(--ink);
}

/* Collapsible cards: the title is a <summary> disclosure. */
details.collapsible>summary.card-title {
  cursor: pointer;
  user-select: none;
  list-style: none;
}

details.collapsible>summary.card-title::before {
  content: "▸";
  display: inline-block;
  margin-right: 8px;
  color: var(--muted);
  transition: transform 0.15s;
}

details.collapsible[open]>summary.card-title::before {
  transform: rotate(90deg);
}

details.collapsible:not([open])>summary.card-title {
  margin-bottom: 0;
}

.card-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
  gap: 18px;
  margin-bottom: 18px;
}

.card-grid .card {
  margin-bottom: 0;
}

.card-grid-2 {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 18px;
  margin-bottom: 18px;
}

.card-grid-2 .card {
  margin-bottom: 0;
}

@media (max-width: 980px) {
  .card-grid-2 {
    grid-template-columns: 1fr;
  }
}

.hint {
  color: var(--muted);
  font-size: 12px;
  margin: -4px 0 8px;
}

.subcard-title {
  font-size: 13px;
  font-weight: 600;
  color: var(--ink-2);
  margin: 18px 0 2px;
  padding-top: 14px;
  border-top: 1px solid var(--grid);
}

.subcard-meta {
  margin-left: 8px;
  font-weight: 400;
  color: var(--muted);
  font-variant-numeric: tabular-nums;
}

.subcard-title:first-of-type {
  margin-top: 10px;
  border-top: none;
  padding-top: 0;
}

.empty-note {
  color: var(--muted);
  font-size: 13px;
}

/* subcard title row with a metric badge on the right (e.g. range fade) */
.subcard-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 10px;
}

.fade-badge {
  font-size: 13.5px;
  margin-right: 2em;
}

.fade-badge:empty {
  display: none;
}

.fade-badge-good {
  background: rgba(12, 163, 12, 0.08);
  border-color: rgba(12, 163, 12, 0.30);
  color: #0a6b0a;
}

.fade-badge-warn {
  background: rgba(237, 161, 0, 0.12);
  border-color: rgba(237, 161, 0, 0.40);
  color: #8a5d00;
}

.fade-badge-orange {
  background: rgba(235, 104, 52, 0.10);
  border-color: rgba(235, 104, 52, 0.35);
  color: #b34415;
}

.fade-badge-bad {
  background: rgba(208, 59, 59, 0.08);
  border-color: rgba(208, 59, 59, 0.30);
  color: #a83232;
}

/* axis-fit toggle: small corner button overlaid on a chart's bottom-right */
.graph-wrap {
  position: relative;
}

.fit-toggle {
  position: absolute;
  right: 6px;
  bottom: 2px;
  min-width: 30px;
  height: 30px;
  padding: 0 5px;
  border-radius: 7px;
  border: 1px solid var(--border);
  background: var(--surface);
  color: var(--ink-2);
  font-size: 20px;
  letter-spacing: -1px;
  line-height: 1;
  cursor: pointer;
  opacity: 0.55;
}

.fit-toggle:hover {
  opacity: 1;
  border-color: var(--ink-2);
}

/* non-blocking corner busy indicator (zoom/pan refetches) */
.corner-spinner-wrap {
  position: absolute;
  top: 0;
  right: 0;
  width: 12px;
  height: 12px;
  margin: 6px;
  pointer-events: none;
}

/* full-cover spinner shown only until a graph's first figure ever arrives —
   hides Plotly's default empty-axes canvas. pointer-events:none so it never
   blocks interaction once the (invisible-when-idle) overlay has resolved. */
.initial-spinner-wrap {
  position: absolute;
  inset: 0;
  pointer-events: none;
}

/* scatter-plotter sign filters: pushed to the filter row's right edge */
.sign-toggle-row {
  display: flex;
  gap: 6px;
  margin-left: auto;
}

.sign-toggle {
  padding: 4px 10px;
  border-radius: 999px;
  border: 1px solid var(--border);
  background: var(--surface);
  color: var(--muted);
  font-size: 12px;
  cursor: pointer;
}

.sign-toggle.active {
  color: var(--ink);
  border-color: rgba(42, 120, 214, 0.4);
  background: rgba(42, 120, 214, 0.08);
}

/* ---- working-electrode card ---- */
.electrode-id {
  font-size: 12px;
  color: var(--ink-2);
  word-break: break-all;
}

.electrode-dims {
  font-size: 19px;
  font-weight: 600;
  margin-top: 2px;
}

.mix-note {
  text-align: center;
  color: var(--ink-2);
  font-size: 13px;
  margin: 0;
}

/* condensed synthesis summary below the composition pie */
.synth-line {
  font-size: 13px;
  line-height: 1.6;
  margin-top: 4px;
}

.synth-badges {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 6px;
  font-size: 13px;
  margin-top: 8px;
}

.badge-water {
  background: rgba(42, 120, 214, 0.10);
  border-color: rgba(42, 120, 214, 0.30);
  color: #1c5cab;
}

.badge-agent {
  background: rgba(74, 58, 167, 0.07);
  border-color: rgba(74, 58, 167, 0.25);
  color: #4a3aa7;
}

/* ---- cards holding several metric sections ---- */

/* Test / Capacity / Energy in one card, or a flow cell's feed and recovery
   solutions. The hairline lives on the section rather than on a subcard-title,
   whose :first-of-type reset would key off the section instead of the card. */
.stat-section+.stat-section {
  margin-top: 16px;
  padding-top: 14px;
  border-top: 1px solid var(--grid);
}

.stat-section-title {
  font-size: 13px;
  font-weight: 600;
  color: var(--ink-2);
  margin: 0 0 9px;
}

/* ---- electrolyte card ---- */

/* Identity line: the id with its taxonomy badge riding alongside. */
.solution-head {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: 4px 8px;
}

.solution-id {
  font-size: 12px;
  color: var(--muted);
  word-break: break-all;
}

.solution-name {
  font-size: 15px;
  font-weight: 600;
  line-height: 1.3;
  margin-top: 3px;
}

/* The composition, one badge per ion, between the name and the pH/conductivity
   pair — dense enough that an 11-ion wastewater still reads as one block. */
.ion-badges {
  display: flex;
  flex-wrap: wrap;
  gap: 5px;
  margin: 9px 0 0;
}

.electrolyte-card .multi-stat-grid {
  margin-top: 12px;
}

.badge-ion {
  padding: 1px 8px;
  font-size: 12px;
  white-space: nowrap;
}

/* Tinted by charge sign: the species is spelled out in the badge, so color
   carries the one thing the text can't be scanned for. */
.badge-ion-cation {
  background: rgba(42, 120, 214, 0.10);
  border-color: rgba(42, 120, 214, 0.32);
  color: #1c5cab;
}

.badge-ion-anion {
  background: rgba(235, 104, 52, 0.10);
  border-color: rgba(235, 104, 52, 0.35);
  color: #b34415;
}

/* Trace metals the lab records without a charge (Fe, Mn): present in the
   composition, contributing nothing to conductivity. */
.badge-ion-neutral {
  background: rgba(11, 11, 11, 0.04);
  color: var(--muted);
}

:root[data-theme="dark"] .badge-ion-cation {
  color: #7db4f2;
}

:root[data-theme="dark"] .badge-ion-anion {
  color: #f0906a;
}

:root[data-theme="dark"] .badge-ion-neutral {
  background: rgba(255, 255, 255, 0.06);
}

.ion-conc {
  margin-left: 5px;
  font-variant-numeric: tabular-nums;
  opacity: 0.85;
}

.ion {
  white-space: nowrap;
  font-weight: 600;
}

.ion sup {
  font-size: 0.75em;
  margin-left: 0.5px;
}

.solution-caveat {
  margin: 10px 0 0;
}

/* ---- detail lists ---- */
.dl {
  display: flex;
  flex-direction: column;
  gap: 4px;
}

.dl-row {
  display: flex;
  justify-content: space-between;
  gap: 16px;
  padding: 3px 0;
  border-bottom: 1px solid var(--grid);
  font-size: 13px;
}

.dl-row:last-child {
  border-bottom: none;
}

.dl-label {
  color: var(--ink-2);
  flex-shrink: 0;
}

.dl-value {
  text-align: right;
  word-break: break-word;
}

/* ---- filter row ---- */
.filter-row {
  display: flex;
  align-items: center;
  gap: 10px;
  margin: 0 0 16px;
  flex-wrap: wrap;
}

.filter-label {
  color: var(--ink-2);
  font-size: 13px;
}

.filter-muted {
  color: var(--muted);
}

.filter-input {
  width: 300px;
  padding: 7px 10px;
  border: 1px solid var(--baseline);
  border-radius: 6px;
  background: var(--surface);
  font-size: 13px;
}

.filter-number {
  width: 90px;
  padding: 7px 10px;
  border: 1px solid var(--baseline);
  border-radius: 6px;
  background: var(--surface);
  font-size: 13px;
}

.filter-dropdown {
  width: 220px;
  font-size: 13px;
}

/* Dash 4 ships .dash-datepicker as `flex: 1`, so a DatePickerRange swallows
   whatever's left of its filter row and floats its two bounds a screenful
   apart. Width alone can't win against a flex-basis of 0. */
.filter-row .dash-datepicker {
  flex: 0 0 auto;
  width: 300px;
}

.filter-daterange {
  font-size: 13px;
}

.filter-mode {
  display: flex;
  align-items: center;
  gap: 10px;
  font-size: 13px;
  color: var(--ink-2);
  white-space: nowrap;
}

/* These are native <input type="radio">, so the browser paints them from
   `color-scheme`, not from data-theme — without this the *unselected* option
   is a solid white dot in dark mode, reading as the more-selected of the
   two. Scoped here rather than set on :root so no other page's native
   controls change appearance. */
:root[data-theme="dark"] .filter-mode {
  color-scheme: dark;
}

.scatter-y-dropdown {
  width: 460px;
  max-width: 100%;
}

.filter-check {
  font-size: 13px;
  color: var(--ink-2);
}

/* ---- experiment switcher ---- */
.switcher-row {
  display: flex;
  align-items: center;
  gap: 10px;
  margin: 0 0 14px;
}

.switcher-dropdown {
  width: 440px;
  max-width: 100%;
  font-size: 13px;
}

/* ---- landing hub ---- */
.home-hub {
  max-width: 1280px;
}

.section-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  margin-bottom: 6px;
}

.section-head .card-title {
  margin: 0;
}

.section-all {
  font-size: 12px;
}

.exp-row {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 7px 0;
  border-bottom: 1px solid var(--grid);
  font-size: 13px;
}

.exp-row:last-child {
  border-bottom: none;
}

.exp-date {
  color: var(--muted);
  flex-shrink: 0;
  font-variant-numeric: tabular-nums;
}

.exp-name {
  color: var(--accent);
  text-decoration: none;
  flex-shrink: 0;
  max-width: 60%;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.exp-name:hover {
  text-decoration: underline;
}

/* The muted nickname after the name takes what room is left and is the first
   thing cut short. */
.exp-nickname {
  color: var(--muted);
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.exp-row .badge {
  flex-shrink: 0;
  font-size: 11px;
  padding: 1px 8px;
}

.exp-meta {
  color: var(--muted);
  flex-shrink: 0;
  margin-left: auto;
  font-size: 12px;
}

/* Running now: each row's date, name, type, material, solution, nickname and
   meta sit in shared columns (subgrid), so the badges line up down the card
   and the nickname always starts at the same edge. */
.running-rows {
  display: grid;
  grid-template-columns: auto auto auto auto auto minmax(0, 1fr) auto;
  column-gap: 12px;
}

.running-rows .exp-row {
  display: grid;
  grid-column: 1 / -1;
  grid-template-columns: subgrid;
}

.running-rows .badge {
  justify-self: start;
}

/* The ID column sizes to the longest ID; a percentage cap would resolve
   against that same auto column and clip it. */
.running-rows .exp-name {
  max-width: none;
}

.badge-solution {
  background: transparent;
  border-color: var(--border);
  color: var(--ink-2);
  white-space: nowrap;
}

.exp-columns {
  align-items: start;
}

/* Each landing column stacks several type sections; .card-grid-2 zeroes the
   cards' own margin, so the column owns the spacing between them. */
.exp-column {
  display: flex;
  flex-direction: column;
  gap: 18px;
}

/* ---- compare page ----
   Each summary card is topped by a rule in the color that test carries
   through every chart on the page; that rule is the only thing set inline
   (rocaviz/pages/compare.py), so the width lives here and only the hue
   travels with the data. */
.cmp-add-row {
  align-items: center;
}

/* Cards stretch to a common height and pin their metric grids to the bottom,
   so the same metric sits on the same line across every card — the numbers
   are meant to be read across, not one card at a time. */
.cmp-cards {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
  gap: 18px;
  margin: 0 0 18px;
  align-items: stretch;
}

.cmp-card {
  display: flex;
  flex-direction: column;
  margin-bottom: 0;
  border-top: 3px solid var(--baseline);
  border-radius: 4px 4px 8px 8px;
  padding-top: 13px;
}

.cmp-card .badge-row {
  margin: 6px 0 0;
}

.cmp-card-material {
  color: var(--ink-2);
  font-size: 12.5px;
  margin-top: 7px;
}

.cmp-card .multi-stat-grid {
  margin-top: auto;
  padding-top: 12px;
}

.cmp-card .empty-note {
  margin: 10px 0 2px;
}

.cmp-card-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 8px;
}

.cmp-card-name {
  font-size: 14px;
  font-weight: 650;
  color: var(--ink);
  text-decoration: none;
  overflow-wrap: anywhere;
}

.cmp-card-name:hover {
  color: var(--accent);
}

/* Electrode ids run long enough to wrap a card three lines deep; clip and
   leave the full value on the title attribute. */
.cmp-card-electrode {
  color: var(--muted);
  font-size: 12px;
  margin-top: 2px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.cmp-remove {
  flex-shrink: 0;
  border: none;
  background: none;
  color: var(--muted);
  font-size: 13px;
  line-height: 1;
  padding: 2px 4px;
  cursor: pointer;
  border-radius: 4px;
}

.cmp-remove:hover {
  color: var(--ink);
  background: var(--grid);
}

.cmp-empty {
  margin-bottom: 18px;
}

.cmp-empty-title {
  font-size: 14px;
  font-weight: 600;
  color: var(--ink-2);
  margin: 0 0 4px;
}

/* /explore's selection bar: match count on the left, its actions on the
   right, above the table they act on. */
.cat-compare-bar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  margin: 0 0 10px;
  min-height: 30px;
}

.cat-compare-bar .hint {
  margin: 0;
}

.cat-compare-actions {
  display: flex;
  gap: 8px;
  margin-left: auto;
}

/* ---- table actions ---- */
.table-actions {
  display: flex;
  justify-content: flex-end;
  margin: -6px 0 8px;
}

.btn {
  padding: 6px 14px;
  border: 1px solid var(--baseline);
  border-radius: 6px;
  background: var(--surface);
  color: var(--ink-2);
  font-size: 13px;
  cursor: pointer;
}

.btn:hover:not(:disabled) {
  color: var(--ink);
  border-color: var(--muted);
}

.btn:disabled {
  opacity: 0.45;
  cursor: default;
}

/* ---- tables ---- */
.dash-table-container {
  font-size: 13px;
}

/* !important throughout: dash_table's own bundled CSS loads after ours and
   sets background-color at equal-or-higher specificity, so a plain
   declaration here is silently overridden (only caught testing dark mode —
   the light-mode defaults happened to look close enough to pass unnoticed). */
.dash-table-container .dash-spreadsheet-container .dash-spreadsheet-inner th {
  background: var(--surface) !important;
  color: var(--ink-2) !important;
  font-weight: 600;
  border-bottom: 1px solid var(--baseline) !important;
}

.dash-table-container .dash-spreadsheet-container .dash-spreadsheet-inner td {
  border-bottom: 1px solid var(--grid) !important;
  background: var(--surface) !important;
  color: var(--ink) !important;
}

/* DataTable defines its own --accent (hotpink) on the spreadsheet container,
   shadowing ours; re-point it at the app accent. */
.dash-table-container .dash-spreadsheet-container,
.dash-table-container .dash-spreadsheet-container .dash-spreadsheet-inner,
.dash-table-container .cell-table {
  --accent: inherit;
}

.dash-table-container .dash-cell a,
.dash-table-container .dash-cell a:visited {
  color: var(--link) !important;
  text-decoration: none;
}

.dash-table-container .dash-cell a:hover {
  text-decoration: underline;
}

/* dcc dropdown resets to match */
.Select-control,
.Select-menu-outer {
  font-size: 13px;
}

/* ---- dark-theme overrides for fixed-tint badges ----
   These use literal brand hex colors (not CSS variables) chosen for
   contrast on the light --surface; dark mode needs its own brighter set to
   stay legible on the dark --surface. assets/theme_toggle.js always stamps
   data-theme explicitly (checking localStorage, else prefers-color-scheme)
   before paint, so — unlike the :root variable block above — these don't
   need a prefers-color-scheme fallback copy. */
:root[data-theme="dark"] .badge-running {
  color: var(--good);
  border-color: rgba(41, 193, 68, 0.4);
}

:root[data-theme="dark"] .badge-type-flowcell {
  background: rgba(150, 130, 230, 0.16);
  border-color: rgba(150, 130, 230, 0.42);
  color: #b6a5ef;
}

:root[data-theme="dark"] .badge-type-galv {
  background: rgba(86, 160, 240, 0.14);
  border-color: rgba(86, 160, 240, 0.4);
  color: #8cc0f5;
}

:root[data-theme="dark"] .badge-type-cv {
  background: rgba(41, 193, 68, 0.12);
  border-color: rgba(41, 193, 68, 0.35);
  color: #6bdb85;
}

:root[data-theme="dark"] .badge-type-select {
  background: rgba(240, 150, 100, 0.16);
  border-color: rgba(240, 150, 100, 0.42);
  color: #f2ac80;
}

:root[data-theme="dark"] .badge-type-activ {
  background: rgba(240, 190, 70, 0.16);
  border-color: rgba(240, 190, 70, 0.44);
  color: #f0cb70;
}

:root[data-theme="dark"] .badge-type-other {
  background: rgba(255, 255, 255, 0.06);
}

:root[data-theme="dark"] .value-good {
  background: rgba(41, 193, 68, 0.16);
}

:root[data-theme="dark"] .value-bad {
  background: rgba(230, 90, 90, 0.18);
}

:root[data-theme="dark"] .value-warn {
  background: rgba(240, 190, 70, 0.18);
  color: #f0cb70;
}

:root[data-theme="dark"] .value-orange {
  background: rgba(240, 150, 100, 0.18);
}

:root[data-theme="dark"] .fade-badge-good {
  background: rgba(41, 193, 68, 0.12);
  border-color: rgba(41, 193, 68, 0.35);
  color: #6bdb85;
}

:root[data-theme="dark"] .fade-badge-warn {
  background: rgba(240, 190, 70, 0.16);
  border-color: rgba(240, 190, 70, 0.44);
  color: #f0cb70;
}

:root[data-theme="dark"] .fade-badge-orange {
  background: rgba(240, 150, 100, 0.16);
  border-color: rgba(240, 150, 100, 0.42);
  color: #f2ac80;
}

:root[data-theme="dark"] .fade-badge-bad {
  background: rgba(230, 90, 90, 0.14);
  border-color: rgba(230, 90, 90, 0.4);
  color: #ef8a8a;
}

:root[data-theme="dark"] .badge-water {
  background: rgba(86, 160, 240, 0.14);
  border-color: rgba(86, 160, 240, 0.4);
  color: #8cc0f5;
}

:root[data-theme="dark"] .badge-agent {
  background: rgba(150, 130, 230, 0.14);
  border-color: rgba(150, 130, 230, 0.4);
  color: #b6a5ef;
}

:root[data-theme="dark"] .sign-toggle.active {
  border-color: rgba(86, 160, 240, 0.45);
  background: rgba(86, 160, 240, 0.14);
}

:root[data-theme="dark"] .notice {
  border-left-color: #f0cb70;
}
/* ---- buttons beyond the plain .btn ---- */
.btn-primary {
  background: var(--accent);
  border-color: var(--accent);
  color: #fff;
}

.btn-primary:hover:not(:disabled) {
  color: #fff;
  border-color: var(--accent);
  filter: brightness(1.08);
}

.btn-quiet {
  border-color: transparent;
  background: transparent;
  color: var(--accent);
}

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

.btn-sm {
  padding: 3px 10px;
  font-size: 12px;
}

.btn-danger {
  color: #b42318;
}

.btn-danger:hover:not(:disabled) {
  color: #b42318;
  border-color: #b42318;
}

:root[data-theme="dark"] .btn-danger,
:root[data-theme="dark"] .btn-danger:hover:not(:disabled) {
  color: #f07a6e;
}

/* ---- /solutions catalog ---- */
.sol-count {
  font-size: 13px;
  margin-left: auto;
}

.sol-flash-message {
  margin: 0 0 12px;
  font-size: 13px;
  color: var(--good);
}

.sol-table-card {
  padding: 0;
}

.sol-table-scroll {
  overflow-x: auto;
}

.sol-table,
.data-table {
  width: 100%;
  border-collapse: collapse;
  font-size: 13px;
}

.sol-table th,
.data-table th {
  position: sticky;
  top: 0;
  background: var(--surface);
  text-align: left;
  font-weight: 500;
  color: var(--muted);
  font-size: 12px;
  padding: 10px 12px;
  border-bottom: 1px solid var(--grid);
  white-space: nowrap;
}

.sol-table td,
.data-table td {
  padding: 11px 12px;
  border-bottom: 1px solid var(--grid);
  vertical-align: top;
}

.data-table th,
.data-table td {
  padding-left: 0;
  padding-right: 16px;
}

.data-table td {
  padding-top: 8px;
  padding-bottom: 8px;
}

.sol-table tbody tr:last-child td,
.data-table tbody tr:last-child td {
  border-bottom: none;
}

.sol-table .num,
.data-table .num {
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

.data-table th.num,
.data-table td.num {
  text-align: right;
}

/* Rows that open a page (assets/row_links.js follows data-href). */
tr.row-link {
  cursor: pointer;
}

tr.row-link:hover td {
  background: rgba(127, 127, 127, 0.05);
}

.sol-sort {
  border: none;
  background: none;
  padding: 0;
  font: inherit;
  color: inherit;
  cursor: pointer;
  display: inline-flex;
  gap: 4px;
}

.sol-sort:hover,
.sol-sort-active {
  color: var(--ink);
}

.sol-sort-arrow {
  min-width: 0.8em;
}

/* Name above, id under it in monospace — in the table and on the page. */
.sol-name {
  display: block;
  color: var(--ink);
  font-weight: 600;
  text-decoration: none;
  max-width: 260px;
}

.sol-name:hover {
  color: var(--accent);
}

.sol-id,
.sol-mono {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 12px;
  white-space: nowrap;
}

.sol-id {
  color: var(--ink-2);
  margin-top: 3px;
}

.sol-id-lg {
  font-size: 13px;
  margin: 0 4px 0 0;
  align-self: center;
}

.sol-sub {
  color: var(--muted);
  font-size: 12px;
}

.sol-col-category {
  white-space: nowrap;
}

.sol-col-ions {
  min-width: 300px;
}

.sol-ions {
  margin: 0;
}

.sol-more {
  font-size: 12px;
  color: var(--muted);
  align-self: center;
}

.sol-recent {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 3px;
}

.sol-col-recent {
  max-width: 200px;
}

.sol-recent li {
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.sol-test-link {
  color: var(--accent);
  text-decoration: none;
}

.sol-test-link:hover {
  text-decoration: underline;
}


.sol-empty {
  text-align: center;
  padding: 28px;
}



/* Category toggles: one badge per category, each switching its rows on or
   off. Off is a hollow outline, so on/off reads without relying on hue. */
.cat-toggles {
  display: flex;
  gap: 8px;
  flex-wrap: wrap;
}

.cat-toggle {
  padding: 3px 11px;
  border-radius: 999px;
  border: 1px dashed var(--baseline);
  background: transparent;
  color: var(--muted);
  font: inherit;
  font-size: 12px;
  cursor: pointer;
}

.cat-toggle.on {
  border-style: solid;
  color: var(--ink);
}

.cat-toggle.on.cat-real {
  background: rgba(150, 95, 30, 0.10);
  border-color: rgba(150, 95, 30, 0.40);
}

.cat-toggle.on.cat-synthetic {
  background: rgba(42, 120, 214, 0.10);
  border-color: rgba(42, 120, 214, 0.38);
}

.cat-toggle.on.cat-stock {
  background: rgba(110, 90, 190, 0.10);
  border-color: rgba(110, 90, 190, 0.38);
}

.cat-toggle:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

/* ---- /solutions/<id> ---- */
.back-link {
  display: inline-block;
  margin: 0 0 10px;
  font-size: 13px;
  color: var(--accent);
  text-decoration: none;
}

.back-link:hover {
  text-decoration: underline;
}

.sol-detail-grid {
  margin: 0 0 18px;
}

.sol-fullname {
  margin: 0 0 10px;
  color: var(--ink-2);
}

.sol-notes-text {
  margin: 0;
  white-space: pre-wrap;
  color: var(--ink-2);
  font-size: 13px;
}

.sol-model-note {
  margin: 8px 0 0;
}

.sol-ion-table {
  margin-top: 12px;
  max-width: 640px;
}

/* The shared tests table (inventory/test_groups.py). Fixed layout, so a
   long value can't widen its column: it ends in an ellipsis instead, the
   whole value on hover (.t-ellip). */
.tests-table {
  table-layout: fixed;
  width: 100%;
  min-width: 1080px;
}

.tests-table th,
.tests-table td {
  vertical-align: middle;
}

.t-ellip {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  max-width: 100%;
}

.tests-table .badge {
  max-width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
  vertical-align: middle;
}

.tests-table th.num .sol-sort {
  justify-content: flex-end;
}

.tests-table .t-started { width: 7.5%; }
.tests-table .t-test { width: 15.5%; }
.tests-table .t-type { width: 6%; }
.tests-table .t-cycles { width: 5.5%; }
.tests-table .t-duration { width: 6.5%; }
.tests-table .t-electrode { width: 11%; }
.tests-table .t-material { width: 9%; }
.tests-table .t-electrolyte { width: 13%; }
.tests-table .t-results { width: 22%; }
.tests-table .t-channel { width: 4%; }

/* Arbin channel: pale blue, except the flow-cell rig's (badge-type-flowcell). */
.badge-ch {
  background: rgba(42, 120, 214, 0.07);
  border-color: rgba(42, 120, 214, 0.22);
  color: #3a6ea8;
  font-variant-numeric: tabular-nums;
}

:root[data-theme="dark"] .badge-ch {
  background: rgba(86, 160, 240, 0.12);
  border-color: rgba(86, 160, 240, 0.3);
  color: #9cc4ef;
}

.tests-table th,
.tests-table td {
  padding-left: 6px;
  padding-right: 6px;
}

.tests-table th .sol-sort {
  white-space: nowrap;
}

/* A right-aligned header must not overflow leftward into its neighbour:
   the arrow takes no width until it's shown. */
.tests-table th.num .sol-sort-arrow {
  min-width: 0;
}

.tests-table .badge {
  display: inline-block;
  white-space: nowrap;
}

.t-result-cell {
  font-size: 12px;
  font-variant-numeric: tabular-nums;
  color: var(--ink-2);
}

.tests-pager {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: 12px;
  margin-top: 10px;
}

.all-tests-card {
  padding-top: 8px;
}

.data-table .nowrap {
  white-space: nowrap;
  font-variant-numeric: tabular-nums;
}

.sol-detail-grid {
  align-items: start;
}

.sol-test-group + .sol-test-group {
  margin-top: 6px;
}

.sol-edit-card {
  max-width: 900px;
}

.sol-form-foot {
  margin-top: 18px;
  padding-top: 14px;
  border-top: 1px solid var(--grid);
}

.cat-toggle.on.cat-neutral {
  background: rgba(127, 127, 127, 0.10);
  border-color: rgba(127, 127, 127, 0.40);
}

/* ---- fabrication inventory (inventory/listing.py, fabrication.py, tree.py) ---- */
.inv-table-card {
  padding: 4px 8px 8px;
}

.inv-table-card .dash-cell-value a {
  color: var(--accent);
  text-decoration: none;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 12px;
}

.inv-link {
  color: var(--accent);
  text-decoration: none;
}

.inv-link:hover {
  text-decoration: underline;
}

.inv-title-id {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 20px;
  word-break: break-all;
}

.lineage {
  display: flex;
  flex-direction: column;
  gap: 6px;
  margin: 0 0 12px;
}

.lineage-line {
  display: grid;
  grid-template-columns: 112px minmax(0, 1fr);
  gap: 10px;
  align-items: baseline;
  font-size: 13px;
}

.lineage-value {
  display: flex;
  gap: 4px 10px;
  align-items: baseline;
  flex-wrap: wrap;
  min-width: 0;
  word-break: break-all;
}

.lineage-value .tree-facts {
  word-break: normal;
}

.lineage-stage {
  color: var(--muted);
  font-size: 12px;
}

.tree-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin: 0 0 8px;
}

.tree-controls {
  display: flex;
  gap: 4px;
}

.inv-tree {
  font-size: 13px;
}

.tree-node > summary {
  cursor: pointer;
  padding: 5px 0;
  list-style-position: outside;
}

.tree-node > summary::marker {
  color: var(--muted);
}

/* A branch with nothing under it: a plain line, indented to sit under the
   disclosure arrows of its expandable siblings. */
.tree-bare {
  padding: 5px 0 5px 1.15em;
}

.tree-children {
  margin-left: 18px;
  padding-left: 12px;
  border-left: 1px solid var(--grid);
}

.tree-composite + .tree-composite {
  margin-top: 6px;
  padding-top: 6px;
  border-top: 1px solid var(--grid);
}

.tree-line {
  display: inline-flex;
  gap: 10px;
  align-items: baseline;
  flex-wrap: wrap;
}

.tree-facts {
  color: var(--ink-2);
  font-size: 12px;
}

.tree-counts {
  color: var(--muted);
  font-size: 12px;
}

.tree-electrodes {
  list-style: none;
  margin: 2px 0 4px;
  padding: 0;
}

.tree-leaf {
  padding: 3px 0;
}

.tree-empty {
  margin: 4px 0;
}

.hub-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
  gap: 16px;
}

.hub-grid-4 {
  grid-template-columns: repeat(4, minmax(0, 1fr));
  margin: 0 0 18px;
}

@media (max-width: 900px) {
  .hub-grid-4 {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}

.hub-section-title {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 12px;
}

.hub-all {
  font-size: 13px;
  font-weight: 400;
}

.hub-recent td:first-child {
  max-width: 420px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.hub-link {
  text-decoration: none;
  color: inherit;
}

.hub-card {
  margin: 0;
  height: 100%;
  transition: border-color 0.12s;
}

.hub-link:hover .hub-card {
  border-color: var(--accent);
}

.hub-title {
  font-weight: 650;
}

.hub-count {
  font-size: 26px;
  font-weight: 600;
  margin: 6px 0 4px;
  font-variant-numeric: tabular-nums;
}

/* ---- modal (solution editor) ---- */
.modal-overlay {
  position: fixed;
  inset: 0;
  z-index: 100;
  background: rgba(11, 11, 11, 0.32);
  display: flex;
  align-items: flex-start;
  justify-content: center;
  padding: 5vh 16px;
  overflow-y: auto;
}

.modal-overlay[hidden] {
  display: none;
}

.modal-panel {
  width: min(860px, 100%);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 10px;
  box-shadow: 0 18px 50px rgba(0, 0, 0, 0.18);
}

.modal-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 16px 20px 0;
}

.modal-title {
  margin: 0;
  font-size: 17px;
  font-weight: 650;
}

.modal-x {
  border: none;
  background: none;
  font-size: 22px;
  line-height: 1;
  color: var(--muted);
  cursor: pointer;
}

.modal-x:hover {
  color: var(--ink);
}

.modal-body {
  padding: 8px 20px 4px;
}

.modal-foot {
  padding: 12px 20px 16px;
  border-top: 1px solid var(--grid);
}

.modal-actions {
  display: flex;
  gap: 8px;
  align-items: center;
  margin-top: 12px;
}

.modal-foot-spacer {
  flex: 1;
}

/* ---- solution editor form ---- */
.sol-field {
  margin: 14px 0 0;
}

.sol-label {
  display: block;
  font-size: 12px;
  font-weight: 600;
  color: var(--ink-2);
  margin: 0 0 6px;
}

.sol-hint {
  margin: 4px 0 0;
}

.sol-field-row {
  display: flex;
  flex-wrap: wrap;
  gap: 0 20px;
}

.sol-id-field {
  display: flex;
  align-items: center;
  gap: 4px;
}

.sol-id-prefix {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 13px;
  color: var(--ink-2);
}

.sol-id-code {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  width: 220px;
  text-transform: uppercase;
}

.sol-id-code:disabled {
  color: var(--ink-2);
  background: var(--page);
}

.filter-input.sol-wide {
  width: 100%;
}

.filter-input.sol-notes {
  width: 100%;
  min-height: 64px;
  font-family: inherit;
  resize: vertical;
}

.sol-live {
  margin: 0 0 4px;
}

.sol-props {
  margin: 10px 0 0;
  max-width: 460px;
}

.sol-props .dl-row {
  gap: 8px;
}

.sol-todo {
  margin: 10px 0 0;
  font-size: 13px;
  color: var(--ink-2);
}

.sol-todo-title {
  font-weight: 600;
}

.sol-todo ul {
  margin: 4px 0 0;
  padding-left: 18px;
}

.sol-error {
  margin: 10px 0 0;
  font-size: 13px;
  color: #b42318;
}

:root[data-theme="dark"] .sol-error {
  color: #f07a6e;
}

:root[data-theme="dark"] .modal-overlay {
  background: rgba(0, 0, 0, 0.55);
}

/* ---- ion-composition entry (inventory/ion_entry.py) ---- */
.ion-entry-row {
  display: grid;
  grid-template-columns: minmax(140px, 220px) 90px minmax(0, 1fr);
  gap: 10px;
  align-items: start;
  margin: 0 0 8px;
}

.ion-entry-head {
  color: var(--muted);
  font-size: 12px;
  margin-bottom: 4px;
}

.ion-entry-row .filter-input {
  width: 100%;
}

.ion-entry-reading {
  min-height: 32px;
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: 4px;
  font-size: 13px;
}

.ion-entry-preview {
  display: flex;
  align-items: baseline;
  gap: 8px;
  flex-wrap: wrap;
}

.ion-entry-note {
  color: var(--muted);
  font-size: 12px;
}

.ion-entry-option {
  display: inline-flex;
  align-items: baseline;
  gap: 6px;
}

.ion-entry-choice {
  flex-wrap: wrap;
  gap: 4px 14px;
}

.ion-entry-ask {
  color: #a35c00;
}

.ion-entry-error {
  color: #b42318;
}

:root[data-theme="dark"] .ion-entry-ask {
  color: #f0a64a;
}

:root[data-theme="dark"] .ion-entry-error {
  color: #f07a6e;
}

:root[data-theme="dark"] .ion-entry-choice {
  color-scheme: dark;
}

@media (max-width: 640px) {
  .ion-entry-row {
    grid-template-columns: minmax(0, 1fr) 80px;
  }
  .ion-entry-reading {
    grid-column: 1 / -1;
  }
  .ion-entry-head span:last-child {
    display: none;
  }
}

/* ------------------------------------------------------------ chemicals -- */
.chem-suggest,
.chem-link-row {
  display: flex;
  align-items: center;
  gap: 12px;
  margin: 10px 0;
  flex-wrap: wrap;
}

.chem-link-pick {
  flex: 1 1 260px;
  min-width: 0;
}

.chem-preview {
  margin: -4px 0 8px;
  min-height: 1.2em;
}

.chem-splits label {
  display: block;
  padding: 3px 0;
}

/* ------------------------------------------------- prepared ingredients -- */
.ing-volume {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-bottom: 10px;
}

.ing-volume .sol-label {
  margin: 0 6px 0 0;
}

.ing-row {
  display: grid;
  grid-template-columns: minmax(220px, 2.2fr) minmax(200px, 1.6fr) minmax(170px, 1fr) 28px;
  gap: 8px;
  align-items: start;
  margin-bottom: 6px;
}

.ing-head {
  font-size: 12px;
  color: var(--muted);
}

.ing-unit {
  width: 84px;
  flex: 0 0 auto;
}

.ing-calc-cell,
.ing-amount-cell {
  display: flex;
  align-items: center;
  gap: 6px;
  flex-wrap: wrap;
}

.ing-target {
  width: 84px;
}

.ing-target-unit,
.ing-calc,
.ing-share {
  font-size: 12px;
  color: var(--muted);
  font-variant-numeric: tabular-nums;
}

.ing-calc {
  color: var(--ink-2);
}

.ing-share {
  margin-top: 3px;
}

.ing-amount-cell .filter-number {
  width: 96px;
}

.ing-protocol-card {
  margin-top: 12px;
}

.ing-protocol {
  margin: 4px 0 0;
  padding-left: 20px;
}

.sol-warnings {
  color: var(--warn, #8a5d00);
  margin: 8px 0;
  padding-left: 18px;
}

.sol-name-hint {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
  margin-top: 10px;
}

@media (max-width: 760px) {
  .ing-row {
    grid-template-columns: 1fr;
  }
}

.sol-drift {
  border-color: rgba(237, 161, 0, 0.5);
  background: rgba(237, 161, 0, 0.06);
  margin-bottom: 16px;
}

/* Electrode-material badges: fixed tint per material (components.material_badge). */
.badge-mat { white-space: nowrap; }
.badge-mat-0 { background: rgba(15, 118, 110, 0.08); border-color: rgba(15, 118, 110, 0.32); color: #0f665f; }
:root[data-theme="dark"] .badge-mat-0 { background: rgba(15, 118, 110, 0.18); border-color: rgba(15, 118, 110, 0.5); color: #5fd4c8; }
.badge-mat-1 { background: rgba(67, 56, 202, 0.08); border-color: rgba(67, 56, 202, 0.32); color: #3730a3; }
:root[data-theme="dark"] .badge-mat-1 { background: rgba(67, 56, 202, 0.18); border-color: rgba(67, 56, 202, 0.5); color: #a5a0f5; }
.badge-mat-2 { background: rgba(180, 83, 9, 0.08); border-color: rgba(180, 83, 9, 0.32); color: #8f420a; }
:root[data-theme="dark"] .badge-mat-2 { background: rgba(180, 83, 9, 0.18); border-color: rgba(180, 83, 9, 0.5); color: #f0a868; }
.badge-mat-3 { background: rgba(71, 85, 105, 0.08); border-color: rgba(71, 85, 105, 0.32); color: #3b4656; }
:root[data-theme="dark"] .badge-mat-3 { background: rgba(71, 85, 105, 0.18); border-color: rgba(71, 85, 105, 0.5); color: #b3bfd0; }
.badge-mat-4 { background: rgba(190, 24, 93, 0.08); border-color: rgba(190, 24, 93, 0.32); color: #a3164f; }
:root[data-theme="dark"] .badge-mat-4 { background: rgba(190, 24, 93, 0.18); border-color: rgba(190, 24, 93, 0.5); color: #f38cb8; }
.badge-mat-5 { background: rgba(30, 30, 30, 0.08); border-color: rgba(30, 30, 30, 0.32); color: #262626; }
:root[data-theme="dark"] .badge-mat-5 { background: rgba(30, 30, 30, 0.18); border-color: rgba(30, 30, 30, 0.5); color: #d4d4d4; }
.badge-mat-6 { background: rgba(120, 113, 108, 0.08); border-color: rgba(120, 113, 108, 0.32); color: #5c5652; }
:root[data-theme="dark"] .badge-mat-6 { background: rgba(120, 113, 108, 0.18); border-color: rgba(120, 113, 108, 0.5); color: #c9c3bf; }
.badge-mat-7 { background: rgba(77, 124, 15, 0.08); border-color: rgba(77, 124, 15, 0.32); color: #3f6511; }
:root[data-theme="dark"] .badge-mat-7 { background: rgba(77, 124, 15, 0.18); border-color: rgba(77, 124, 15, 0.5); color: #a9d66e; }

/* One small badge per result in the tests table's Results column. */
.t-results-badges {
  display: flex;
  gap: 4px;
  overflow: hidden;
  white-space: nowrap;
}

.badge-result {
  border-radius: 6px;
  padding: 2px 7px;
  background: rgba(127, 127, 127, 0.06);
  border-color: var(--grid);
  overflow: hidden;
  text-overflow: ellipsis;
  color: var(--ink-2);
  font-size: 11.5px;
  font-variant-numeric: tabular-nums;
  flex: 0 1 auto;
  min-width: 0;
}

/* Test-page link editor (rocaviz/test_links.py). */
.tl-actions {
  margin-top: 10px;
}

.tl-hint {
  margin-top: 10px;
}

.tl-editor .tl-pick {
  width: 100%;
}

/* Row checkboxes on /experiments, and the compare bar they bring up. */
.visually-hidden {
  position: absolute; width: 1px; height: 1px; overflow: hidden;
  clip: rect(0 0 0 0); white-space: nowrap;
}
.tests-table .t-pick { width: 2.5%; }
.tests-table .t-test { width: 14%; }
.tests-table .t-results { width: 21%; }
.tests-table td.t-pick { padding-right: 0; }
.t-pick-box label { display: flex; align-items: center; margin: 0; cursor: pointer; }
.t-pick-input { width: 15px; height: 15px; margin: 0; cursor: pointer; accent-color: var(--link); }
.all-compare-bar {
  display: flex; align-items: center; gap: 10px; margin-left: auto;
}
.all-compare-bar:empty { display: none; }
.tests-card-compare { justify-content: flex-end; margin: 0 0 8px; }
.btn-disabled { opacity: 0.5; cursor: not-allowed; }
.all-compare-bar a.btn { text-decoration: none; }

/* Home: Arbin channel occupancy. */
.home-timeline-card { margin-bottom: 12px; }
.home-usage-head {
  align-items: center; margin-top: 12px; padding-top: 10px;
  border-top: 1px solid var(--border);
}
.home-usage-head .card-subtitle { margin: 0; font-size: 14px; font-weight: 600; }
.home-timeline-card .view-tabs { margin-left: auto; }
.home-timeline .bars .point path { cursor: pointer; }
/* ‹ › at either end of the x axis, paging a window at a time. */
.timeline-wrap { position: relative; }
.timeline-arrow {
  position: absolute; bottom: 4px; width: 24px; height: 24px; padding: 0;
  border: 1px solid var(--border); border-radius: 6px; background: var(--surface, transparent);
  color: var(--ink-2); font-size: 16px; line-height: 20px; cursor: pointer;
}
.timeline-arrow:hover:not(:disabled) { color: var(--link); border-color: var(--link); }
.timeline-arrow:disabled { opacity: 0.3; cursor: default; }
.timeline-prev { left: 12px; }
.timeline-next { right: -4px; }

/* /channels */
.home-usage-all { margin-left: 16px; }
.chu-toolbar {
  display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between;
  gap: 12px 24px; margin: 4px 0 12px;
}
.chu-controls { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; }
/* The timeline's channel labels: HTML, so a click can hide a row. */
.chu-timeline-row { display: flex; align-items: flex-start; }
.chu-sidebar { position: relative; flex: 0 0 64px; }
.chu-graph { flex: 1 1 auto; min-width: 0; }
.chu-ch, .chu-ch-hidden {
  position: absolute; right: 4px; padding: 0 6px; border: 1px solid transparent;
  border-radius: 4px; background: none; color: var(--ink-2); cursor: pointer;
}
.chu-ch {
  display: flex; align-items: center; justify-content: flex-end;
  font-size: 12px; font-variant-numeric: tabular-nums;
}
/* An ellipsis on the line between two rows, where hidden channels were. */
.chu-ch-hidden {
  z-index: 1; font-size: 13px; line-height: 6px; letter-spacing: 1px;
}
.chu-ch:hover, .chu-ch-hidden:hover {
  border-color: var(--border); color: var(--ink); background: var(--surface);
}
.chu-card { padding-top: 8px; margin-bottom: 8px; }
.chu-card .home-timeline .bars .point path { cursor: pointer; }
.chu-card .js-plotly-plot .nsewdrag { cursor: grab; }
.chu-stats td, .chu-stats th { white-space: nowrap; }
.chu-card .chu-toolbar { margin: 4px 0 8px; }
.chu-summary { display: flex; flex-direction: column; gap: 2px; }
.chu-window { font-weight: 600; color: var(--ink); }
.chu-summary .hint { margin: 0; }
/* Right of the % axis: its title, then the averaging window, one per line,
   at the axis' own size. */
.chu-util-panel {
  flex: 0 0 auto; display: flex; flex-direction: column; gap: 4px;
  padding: 2px 0 0 4px; font-size: 11px; color: var(--muted);
}
.chu-util-title { color: var(--ink-2); font-weight: 600; }
/* Dash 4's RadioItems markup: label.dash-options-list-option(.selected). */
.chu-avg { display: flex; flex-direction: column; gap: 2px; }
.chu-avg .dash-options-list-option {
  display: block; margin: 0; padding: 0; cursor: pointer; white-space: nowrap;
  font-size: 11px; font-weight: 400; color: var(--muted); background: none;
}
.chu-avg .dash-options-list-option:hover { color: var(--ink); background: none; }
.chu-avg .dash-options-list-option.selected { color: var(--ink); font-weight: 600; }
.chu-avg .dash-options-list-option-wrapper { display: none; }
.chu-card { margin-bottom: 16px; }

/* Tags (rocaviz/tagging.py): badges on a test page and in the tests table,
   the picker that adds them, and the /tags pages. A tag is a badge like the
   type and channel ones beside it, in plain ink. */
.badge-tag {
  background: rgba(127, 127, 127, 0.07);
  border-color: var(--grid);
  color: var(--ink);
  text-decoration: none;
}
a.badge-tag:hover { border-color: var(--muted); }
.badge-row { flex-wrap: wrap; align-items: center; }
/* A test page's tags join the header's badge row as its own items. */
.tag-badges { display: contents; }
.tag-status { color: #b42318; font-size: 12px; }
.tag-status:empty { display: none; }
/* "+ Tag": a Dash Dropdown dressed as a dashed badge. Its search popover
   opens beneath it, wider than the badge. */
.tag-badges .dash-dropdown-wrapper { display: inline-block; width: auto; position: relative; }
.badge-row button.dash-dropdown.tag-add {
  width: auto; min-width: 0; min-height: 0; height: auto;
  padding: 2px 10px; border: 1px dashed var(--baseline); border-radius: 999px;
  background: transparent; box-shadow: none;
  color: var(--ink-2); font-size: 12px; line-height: 1.5; cursor: pointer;
}
.badge-row button.dash-dropdown.tag-add:hover { border-color: var(--muted); color: var(--ink); }
.badge-row button.dash-dropdown.tag-add:focus,
.badge-row button.dash-dropdown.tag-add[data-state="open"] {
  border-color: var(--accent); color: var(--ink); box-shadow: none; outline: none;
}
.badge-row button.dash-dropdown.tag-add:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
.tag-add .dash-dropdown-trigger { display: inline; }
.tag-add .dash-dropdown-trigger-icon { display: none; }
.tag-add .dash-dropdown-placeholder { color: inherit; }
.tag-badges .dash-dropdown-content { min-width: 260px; font-size: 13px; }
.filter-dropdown.tag-picker { width: 190px; font-size: 12.5px; }

/* /experiments gains the Tags column; give it room from Results and
   Electrolyte so the row still sums to 100% and the channel keeps its 4%. */
.tests-all .t-tags { width: 8%; }
.tests-all .t-results { width: 15%; }
.tests-all .t-electrolyte { width: 11%; }
.tests-table .t-tags { width: 9%; }
.tests-table .t-carbon { width: 9%; }
.tests-table .t-metric { width: 7%; }
.t-tag-badges { display: flex; gap: 4px; overflow: hidden; white-space: nowrap; }
.t-tag-badges .badge-tag { font-size: 11.5px; padding: 2px 7px; flex: 0 1 auto; min-width: 0; }

.tags-index td { vertical-align: middle; }
.tags-index .tag-desc { color: var(--ink-2); max-width: 420px; }
.tag-type-counts { display: flex; flex-wrap: wrap; gap: 4px; }
.tag-editor .filter-label { width: 90px; }
.tag-editor .filter-input { width: 420px; }
.tag-description { min-height: 60px; font-family: inherit; }
.filter-dropdown.tag-add-test { width: 340px; }
.tag-tests-bar { margin-bottom: 10px; }
.tag-tests-bar .tag-tick-hint { margin: 0; }
.tag-tests-bar:has(+ .all-compare-bar:not(:empty)) .tag-tick-hint { display: none; }

/* Filter bar (rocaviz/filter_bar.py): active filters as chips, then
   "+ Filter", whose panel picks a field and then its constraint. */
.fb { position: relative; display: flex; flex-wrap: wrap; align-items: center; gap: 6px; }
.fb-chips { display: contents; }
.fb-chip {
  display: inline-flex; align-items: stretch; border: 1px solid var(--baseline);
  border-radius: 999px; background: var(--surface); font-size: 12.5px; overflow: hidden;
}
.fb-chip button { border: none; background: none; color: var(--ink); font: inherit; cursor: pointer; }
.fb-chip-body { padding: 3px 4px 3px 10px; max-width: 360px; overflow: hidden;
  text-overflow: ellipsis; white-space: nowrap; }
.fb-chip-field { color: var(--ink-2); }
.fb-chip-empty { border-style: dashed; }
.fb-chip-empty .fb-chip-field { color: var(--muted); }
.fb-chip-x { padding: 0 9px 0 4px; color: var(--muted) !important; font-size: 14px !important; }
.fb-chip-x:hover { color: var(--ink) !important; }
.fb-chip:hover { border-color: var(--muted); }
.fb-add {
  padding: 3px 10px; border: 1px dashed var(--baseline); border-radius: 999px;
  background: transparent; color: var(--ink-2); font-size: 12.5px; cursor: pointer;
}
.fb-add:hover, .fb-add-open { border-color: var(--muted); color: var(--ink); }
.fb-pop {
  display: none; position: absolute; top: calc(100% + 6px); left: 0; z-index: 30;
  width: 340px; padding: 8px; background: var(--surface); border: 1px solid var(--grid);
  border-radius: 8px; box-shadow: 0 6px 18px rgba(0, 0, 0, 0.12); font-size: 13px;
}
.fb-pop.open { display: block; }
.fb-stage-pick, .fb-stage-edit, .fb-kind { display: none; }
.fb-pop.stage-pick .fb-stage-pick, .fb-pop.stage-edit .fb-stage-edit { display: block; }
.fb-pop.kind-cat .fb-kind-cat, .fb-pop.kind-num .fb-kind-num,
.fb-pop.kind-date .fb-kind-date, .fb-pop.kind-text .fb-kind-text {
  display: flex; flex-direction: column; gap: 8px;
}
.fb-pop.kind-num .fb-kind-num { flex-direction: row; align-items: center; gap: 6px; }
.fb-search {
  width: 100%; box-sizing: border-box; padding: 6px 9px; margin-bottom: 6px;
  border: 1px solid var(--baseline); border-radius: 6px; background: var(--surface);
  color: var(--ink); font-size: 13px;
}
.fb-fields { max-height: 340px; overflow-y: auto; }
.fb-group-title { padding: 6px 6px 2px; color: var(--muted); font-size: 11.5px; }
.fb-field {
  display: block; width: 100%; padding: 5px 8px; border: none; border-radius: 5px;
  background: none; color: var(--ink); font: inherit; text-align: left; cursor: pointer;
}
.fb-field:hover, .fb-field:focus-visible { background: rgba(127, 127, 127, 0.1); outline: none; }
.fb-edit-head { display: flex; align-items: center; gap: 6px; margin-bottom: 8px; }
.fb-back {
  border: none; background: none; color: var(--muted); font-size: 18px; line-height: 1;
  padding: 0 4px; cursor: pointer;
}
.fb-back:hover { color: var(--ink); }
.fb-title { font-weight: 600; }
.fb-values, .fb-text { width: 100%; box-sizing: border-box; }
.fb-kind-date .dash-datepicker { width: 100%; }
.filter-spacer { flex: 1; }
.all-filter-row { margin-bottom: 10px; }
.all-compare-bar { margin-bottom: 10px; justify-content: flex-end; }

/* The tests table's Columns menu (inventory/test_groups.py). */
.cols-anchor { position: relative; }
.cols-chooser > summary { list-style: none; }
.cols-chooser > summary::-webkit-details-marker { display: none; }
.cols-chooser[open] > summary { border-color: var(--muted); color: var(--ink); }
.cols-pop {
  position: absolute; top: calc(100% + 6px); right: 0; z-index: 30; width: 280px;
  max-height: 480px; overflow-y: auto; padding: 8px; background: var(--surface);
  border: 1px solid var(--grid); border-radius: 8px;
  box-shadow: 0 6px 18px rgba(0, 0, 0, 0.12); font-size: 13px;
}
.cols-heading { padding: 4px 6px; color: var(--muted); font-size: 11.5px; }
.cols-list { margin-bottom: 6px; }
.cols-list .cols-item {
  display: flex; align-items: center; gap: 6px; padding: 3px 6px; border-radius: 5px;
  cursor: grab;
}
.cols-list .cols-item:hover { background: rgba(127, 127, 127, 0.08); }
.cols-grip { color: var(--muted); font-size: 12px; }
.cols-name { flex: 1; }
.cols-x {
  border: none; background: none; color: var(--muted); font-size: 14px; cursor: pointer;
  padding: 0 4px;
}
.cols-x:hover:not(:disabled) { color: var(--ink); }
.cols-x:disabled { visibility: hidden; }
.cols-group { padding: 6px 6px 2px; color: var(--ink-2); font-size: 12px; font-weight: 600; }
.cols-add {
  display: block; width: 100%; padding: 4px 8px; border: none; border-radius: 5px;
  background: none; color: var(--ink); font: inherit; text-align: left; cursor: pointer;
}
.cols-add::before { content: "+ "; color: var(--muted); }
.cols-add:hover { background: rgba(127, 127, 127, 0.1); }
.cols-reset { margin-top: 8px; }
.cols-dragging { opacity: 0.4; }
.cols-list .cols-drop-before { box-shadow: inset 0 2px 0 var(--accent); }
.cols-list .cols-drop-after { box-shadow: inset 0 -2px 0 var(--accent); }
th.cols-drop-before { box-shadow: inset 2px 0 0 var(--accent); }
th.cols-drop-after { box-shadow: inset -2px 0 0 var(--accent); }
th.cols-item { cursor: grab; }
.tests-table .t-notes { width: 14%; }
/* A test's nickname, under its name when it has no column of its own. */
.t-nickname-sub {
  color: var(--muted); font-size: 12px; overflow: hidden; text-overflow: ellipsis;
}

/* A test page's header: title and badges on the left, its notes card on
   the right. The archive and raw-download buttons sit above, at the end of
   the experiment picker's row. */
.switcher-row .header-actions { margin-left: auto; }
.page-header-split { display: flex; align-items: flex-start; gap: 24px; }
.page-header-main { flex: 1; min-width: 0; }
.notes-card {
  flex: 0 1 400px; min-width: 260px; margin: 0; padding: 10px 16px 12px;
  display: flex; flex-direction: column; gap: 6px;
}
.notes-card-head {
  display: flex; align-items: baseline; justify-content: space-between; gap: 12px;
}
/* The nickname is the card's title line; the notes sit beneath. */
.notes-card-title {
  font-size: 14px; font-weight: 600; color: var(--ink); min-width: 0;
  overflow-wrap: anywhere;
}
.notes-card-title .notes-card-empty { font-weight: 400; font-size: 13px; }
.notes-card-edit {
  border: none; background: none; padding: 0; color: var(--link); font-size: 12.5px;
  cursor: pointer;
}
.notes-card-edit:hover { text-decoration: underline; }
.notes-card-text {
  margin: 0; font-size: 13px; line-height: 1.45; color: var(--ink-2);
  white-space: pre-wrap; max-height: 7.5em; overflow-y: auto;
}
.notes-card-empty { color: var(--muted); }
.filter-input.notes-card-input {
  width: 100%; box-sizing: border-box; min-height: 84px; resize: vertical;
  font-family: inherit; line-height: 1.45;
}
.filter-input.notes-card-nickname-input {
  width: 100%; box-sizing: border-box; margin-bottom: 6px; font-weight: 600;
}
.notes-card-actions { display: flex; gap: 8px; margin-top: 6px; }
@media (max-width: 760px) {
  .page-header-split { flex-direction: column; gap: 12px; }
  .notes-card { flex-basis: auto; width: 100%; box-sizing: border-box; }
}
/* The chooser's groups fold away (test_groups.chooser_body). */
.cols-groupblock > summary.cols-group { cursor: pointer; list-style: none; }
.cols-groupblock > summary.cols-group::-webkit-details-marker { display: none; }
.cols-groupblock > summary.cols-group::before {
  content: "▸"; display: inline-block; width: 12px; color: var(--muted);
}
.cols-groupblock[open] > summary.cols-group::before { content: "▾"; }
/* A metric's long unit, on its own line under the header. */
.sol-sort:has(.t-head-unit) { flex-wrap: wrap; }
.t-head-unit { flex-basis: 100%; color: var(--muted); font-size: 11px; font-weight: 400; }
.tests-table th.num .t-head-unit { text-align: right; }
