/* ── Theme tokens ── */
html[data-theme="dark"] {
  --bg:                #09090f;
  --surface:           #0f1120;
  --surface-2:         #171929;
  --border:            #1e2244;
  --border-glow:       #3d2d70;
  --text:              #e4e8f5;
  --text-muted:        #8892c0;
  --text-subtle:       #545e8a;
  --accent:            #7c3aed;
  --accent-2:          #3b82f6;
  --accent-light:      #a78bfa;
  --accent-hover:      #6d28d9;
  --gradient:          linear-gradient(135deg, #a78bfa 0%, #60a5fa 100%);
  --gradient-btn:      linear-gradient(135deg, #7c3aed 0%, #3b82f6 100%);
  --gradient-btn-hover:linear-gradient(135deg, #6d28d9 0%, #2563eb 100%);
  --gradient-header:   linear-gradient(135deg, #1a082e 0%, #0f1120 60%, #041630 100%);
  --input-bg:          #0a0b16;
  --placeholder:       #3a4068;
  --placeholder-muted: #161a30;
  --btn-disabled-bg:   #1e2244;
  --btn-disabled-text: #3a4068;
  --scrollbar-inner:   #2a3070;
  --shadow-accent:     0 0 0 1px #3d2d70, 0 4px 20px #7c3aed22;
  /* Plotly gridlines: deliberately fainter than --border, which reads too
     strong behind data in dark mode. Consumed by _plotlyThemeColors(). */
  --plot-grid:         rgba(136, 146, 192, 0.11);
  /* Droplet-quality excluded bands. A tint, not a fade, and its strength is a
     THEME decision rather than one opacity for both: on #09090f a band has to
     lighten to be seen at all, and the same alpha that reads clearly on the
     light ground disappears here. Kept below the ribbon's own fill so the
     distribution still reads through the band it is excluded by. */
  --qc-band-run:       rgba(136, 146, 192, 0.20);
  --qc-band-user:      rgba(136, 146, 192, 0.34);
}
html[data-theme="light"] {
  --bg:                #f8f7ff;
  --surface:           #ffffff;
  --surface-2:         #f0edff;
  --border:            #ddd8ff;
  --border-glow:       #c4b8f8;
  --text:              #1a1545;
  --text-muted:        #5a5490;
  --text-subtle:       #9088c8;
  --accent:            #7c3aed;
  --accent-2:          #3b82f6;
  --accent-light:      #7c3aed;
  --accent-hover:      #6d28d9;
  --gradient:          linear-gradient(135deg, #7c3aed 0%, #3b82f6 100%);
  --gradient-btn:      linear-gradient(135deg, #7c3aed 0%, #3b82f6 100%);
  --gradient-btn-hover:linear-gradient(135deg, #6d28d9 0%, #2563eb 100%);
  --gradient-header:   linear-gradient(135deg, #fdf6ff 0%, #f5f0ff 50%, #eff6ff 100%);
  --input-bg:          #ffffff;
  --placeholder:       #b0a8e0;
  --placeholder-muted: #d5d0f0;
  --btn-disabled-bg:   #ece9ff;
  --btn-disabled-text: #a098d0;
  --scrollbar-inner:   #c4bef0;
  --shadow-accent:     0 0 0 1px #ddd8ff, 0 4px 20px #7c3aed18;
  --plot-grid:         rgba(26, 21, 69, 0.08);
  /* Darkening ground: on a light page the same band works the other way, and
     needs less of it to read. See the dark block for why this is a token. */
  --qc-band-run:       rgba(144, 136, 200, 0.11);
  --qc-band-user:      rgba(144, 136, 200, 0.20);
}

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

body {
  font-family: 'Open Sans', -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
  background: var(--bg);
  color: var(--text);
  min-height: 100vh;
  display: flex;
  flex-direction: column;
  transition: background 0.25s, color 0.25s;
}

/* ── Header ── */
header {
  background: var(--gradient-header);
  border-bottom: 1px solid var(--border-glow);
  padding: 12px 24px;
  display: flex;
  align-items: center;
  gap: 14px;
}

.header-logo {
  width: 38px;
  height: 38px;
  flex-shrink: 0;
  filter: drop-shadow(0 2px 8px #a78bfa55);
}

.header-home-link {
  display: flex;
  align-items: center;
  gap: 14px;
  text-decoration: none;
}

.header-title {
  display: flex;
  flex-direction: column;
  gap: 1px;
}

header h1 {
  font-size: 1.2rem;
  font-weight: 700;
  letter-spacing: -0.02em;
  line-height: 1.2;
}

header .subtitle {
  font-size: 0.72rem;
  font-weight: 500;
  color: var(--text-subtle);
  letter-spacing: 0.06em;
  text-transform: uppercase;
}

header .spacer { flex: 1; }
.sp-header .spacer { flex: 1; }

/* ── Header buttons ──
   Matte: a flat surface with a hairline, no gradient and no glow. One style
   for every control in the bar (+ Import Sample, Notebooks, Sign in/out, All
   analyses) — the accent stays where it carries meaning, on status pills,
   links and the sign-in card, instead of being spent on the chrome. */
.create-run-btn {
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: 8px;
  color: var(--text-muted);
  font-family: 'Open Sans', sans-serif;
  font-size: 0.78rem;
  font-weight: 600;
  padding: 5px 13px;
  cursor: pointer;
  letter-spacing: 0.01em;
  white-space: nowrap;
  transition: background 0.15s, border-color 0.15s, color 0.15s, transform 0.1s;
}
.create-run-btn:hover {
  background: color-mix(in srgb, var(--text) 8%, var(--surface-2));
  border-color: var(--border-glow);
  color: var(--text);
  transform: translateY(-1px);
}
.create-run-btn:active { transform: translateY(0); }
.create-run-btn:focus-visible { outline: 2px solid var(--accent-light); outline-offset: 2px; }

/* ── Create Run modal / Add File modal (shared backdrop + shell) ── */
#addFileBackdrop {
  display: none;
  position: fixed;
  inset: 0;
  background: rgba(0,0,0,0.65);
  z-index: 9999;
  align-items: center;
  justify-content: center;
}
#addFileBackdrop.open { display: flex; }

#addFileModal {
  background: var(--surface);
  border: 1px solid var(--border-glow);
  border-radius: 14px;
  box-shadow: 0 8px 40px rgba(0,0,0,0.5);
  width: min(520px, 95vw);
  max-height: 90vh;
  display: flex;
  flex-direction: column;
  overflow: hidden;
}
#addFileModal .cr-body {
  overflow-y: auto;
  max-height: calc(90vh - 52px);
}

.af-sample-row {
  display: flex;
  align-items: baseline;
  gap: 8px;
  padding: 4px 0 2px;
}
.af-sample-label {
  font-size: 0.68rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--text-subtle);
  flex-shrink: 0;
}
.af-sample-id {
  font-family: "SFMono-Regular", Consolas, monospace;
  font-size: 0.78rem;
  background: var(--gradient);
  -webkit-background-clip: text;
  -webkit-text-fill-color: transparent;
  background-clip: text;
  word-break: break-all;
}

/* ── Create Run modal ── */
#createRunBackdrop {
  display: none;
  position: fixed;
  inset: 0;
  background: rgba(0,0,0,0.65);
  z-index: 9999;
  align-items: center;
  justify-content: center;
}
#createRunBackdrop.open { display: flex; }

#createRunModal {
  background: var(--surface);
  border: 1px solid var(--border-glow);
  border-radius: 14px;
  box-shadow: 0 8px 40px rgba(0,0,0,0.5);
  width: min(520px, 95vw);
  display: flex;
  flex-direction: column;
  overflow: hidden;
}

.cr-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 14px 18px;
  background: var(--surface-2);
  border-bottom: 1px solid var(--border);
}
.cr-title {
  font-size: 0.88rem;
  font-weight: 700;
  background: var(--gradient);
  -webkit-background-clip: text;
  -webkit-text-fill-color: transparent;
  background-clip: text;
}
.cr-close {
  background: none;
  border: none;
  color: var(--text-subtle);
  cursor: pointer;
  font-size: 1rem;
  line-height: 1;
  padding: 2px 4px;
  border-radius: 4px;
  transition: color 0.15s;
}
.cr-close:hover { color: var(--text); }

.cr-body {
  padding: 18px;
  display: flex;
  flex-direction: column;
  gap: 14px;
}

.cr-required { color: #f87171; font-size: 0.75rem; }
.cr-optional { color: var(--text-subtle); font-size: 0.68rem; font-weight: 400; }

.cr-actions {
  display: flex;
  justify-content: flex-end;
  gap: 8px;
  margin-top: 2px;
}
.cr-cancel {
  background: none;
  border: 1px solid var(--border);
  border-radius: 8px;
  color: var(--text-muted);
  font-family: 'Open Sans', sans-serif;
  font-size: 0.82rem;
  font-weight: 500;
  padding: 6px 16px;
  cursor: pointer;
  transition: border-color 0.15s, color 0.15s;
}
.cr-cancel:hover { border-color: var(--accent-light); color: var(--accent-light); }
.cr-submit {
  background: var(--gradient-btn);
  border: none;
  border-radius: 8px;
  color: #fff;
  font-family: 'Open Sans', sans-serif;
  font-size: 0.82rem;
  font-weight: 600;
  padding: 6px 18px;
  cursor: pointer;
  transition: background 0.15s, box-shadow 0.15s;
}
.cr-submit:hover { background: var(--gradient-btn-hover); box-shadow: 0 2px 12px #7c3aed40; }

#crStatus {
  font-size: 0.78rem;
  font-weight: 500;
  min-height: 1em;
}
#crStatus.error   { color: #f87171; }
#crStatus.loading { color: #fbbf24; }
#crStatus.ok      { color: #34d399; }
#afStatus { font-size: 0.78rem; font-weight: 500; min-height: 1em; }
#afStatus.error   { color: #f87171; }
#afStatus.loading { color: #fbbf24; }
#afStatus.ok      { color: #34d399; }

.cr-result-label {
  font-size: 0.68rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: #34d399;
  margin-bottom: 8px;
}
.cr-result-row {
  display: flex;
  align-items: baseline;
  gap: 10px;
  margin-bottom: 5px;
}
.cr-result-key {
  font-size: 0.7rem;
  color: var(--text-subtle);
  width: 72px;
  flex-shrink: 0;
}
#crResult code {
  font-family: "SFMono-Regular", Consolas, monospace;
  font-size: 0.78rem;
  background: var(--gradient);
  -webkit-background-clip: text;
  -webkit-text-fill-color: transparent;
  background-clip: text;
  word-break: break-all;
}

/* ── Theme toggle ── */
#themeToggle {
  background: none;
  border: 1px solid var(--border-glow);
  border-radius: 8px;
  color: var(--text-muted);
  cursor: pointer;
  padding: 6px 8px;
  display: flex;
  align-items: center;
  transition: border-color 0.15s, color 0.15s, background 0.15s;
  line-height: 0;
}
#themeToggle:hover {
  border-color: var(--accent-light);
  color: var(--accent-light);
  background: #7c3aed14;
}
#themeToggle svg { width: 16px; height: 16px; }

/* ── Toolbar ── */
#status {
  font-size: 0.78rem;
  font-weight: 500;
  padding: 2px 0;
  min-width: 160px;
  color: var(--text-subtle);
}
#status.error   { color: #f87171; }
#status.loading { color: #fbbf24; }
#status.ok      { color: #34d399; }

/* ── IGV container ── */
#igv-container {
  margin-top: 4px;
}

#igv-div {
  background: var(--surface);
  border-radius: 12px;
  border: 1px solid var(--border-glow);
  overflow: hidden;
  min-height: 500px;
  box-shadow: var(--shadow-accent);
}

/* ── IGV.js Dark Mode Overrides ── */
html[data-theme="dark"] .igv-navbar {
  background-color: #0f1120 !important;
  border-bottom: 1px solid #1e2244 !important;
}
html[data-theme="dark"] .igv-search-input {
  background-color: #0a0b16 !important;
  color: #e4e8f5 !important;
  border: 1px solid #1e2244 !important;
  outline: none !important;
}
html[data-theme="dark"] .igv-chromosome-select-widget-container select,
html[data-theme="dark"] .igv-navbar select {
  background-color: #0a0b16 !important;
  color: #e4e8f5 !important;
  border: 1px solid #1e2244 !important;
}
html[data-theme="dark"] .igv-current-genome,
html[data-theme="dark"] .igv-windowsize-panel-container { color: #8892c0 !important; }
html[data-theme="dark"] .igv-navbar-button {
  background-color: transparent !important;
  color: #8892c0 !important;
}
html[data-theme="dark"] .igv-navbar-button:hover { background-color: #1e2244 !important; }
html[data-theme="dark"] .igv-navbar-button-clicked {
  background-color: #1e2244 !important;
  color: #e4e8f5 !important;
}
html[data-theme="dark"] .igv-navbar-toggle-button-container {
  background-color: #171929 !important;
  color: #8892c0 !important;
}
html[data-theme="dark"] .igv-zoom-widget svg path,
html[data-theme="dark"] .igv-zoom-widget svg rect { stroke: #8892c0 !important; fill: none !important; }
html[data-theme="dark"] .igv-track-label {
  background-color: #0f1120 !important;
  color: #e4e8f5 !important;
}
html[data-theme="dark"] .igv-track-label:hover { background-color: #1e2244 !important; }
html[data-theme="dark"] .igv-ui-popover {
  background-color: #0f1120 !important;
  border: 1px solid #1e2244 !important;
  color: #e4e8f5 !important;
}
html[data-theme="dark"] .igv-popover-name  { color: #8892c0 !important; }
html[data-theme="dark"] .igv-popover-value { color: #e4e8f5 !important; }
html[data-theme="dark"] .igv-menu-popup {
  background-color: #0f1120 !important;
  border: 1px solid #1e2244 !important;
  color: #e4e8f5 !important;
}
html[data-theme="dark"] .igv-menu-popup-header {
  background-color: #171929 !important;
  border-bottom: 1px solid #1e2244 !important;
  color: #e4e8f5 !important;
}
html[data-theme="dark"] .igv-menu-popup div { color: #e4e8f5 !important; }
html[data-theme="dark"] .igv-menu-popup div:hover { background-color: #1e2244 !important; }
html[data-theme="dark"] .igv-track-label-popover {
  background: #0f1120 !important;
  border: 1px solid #1e2244 !important;
}
html[data-theme="dark"] .igv-track-label-popover__header {
  background: #171929 !important;
  border-bottom: 1px solid #1e2244 !important;
  color: #e4e8f5 !important;
}
html[data-theme="dark"] .igv-track-label-popover__title  { color: #e4e8f5 !important; }
html[data-theme="dark"] .igv-track-label-popover__close  { color: #8892c0 !important; }
html[data-theme="dark"] .igv-track-label-popover__key    { color: #8892c0 !important; }
html[data-theme="dark"] .igv-track-label-popover__value  { color: #e4e8f5 !important; }
html[data-theme="dark"] .igv-ui-dialog,
html[data-theme="dark"] .igv-generic-dialog-container,
html[data-theme="dark"] .igv-generic-container {
  background-color: #0f1120 !important;
  border: 1px solid #1e2244 !important;
  color: #e4e8f5 !important;
}
html[data-theme="dark"] .igv-ui-dialog-header {
  background-color: #171929 !important;
  border-bottom: 1px solid #1e2244 !important;
  color: #e4e8f5 !important;
}
html[data-theme="dark"] .igv-ui-dialog-one-liner {
  background-color: #0f1120 !important;
  color: #e4e8f5 !important;
}
html[data-theme="dark"] .igv-generic-dialog-container > div:first-child,
html[data-theme="dark"] .igv-generic-container > div:first-child {
  background-color: #171929 !important;
  border-bottom: 1px solid #1e2244 !important;
  color: #e4e8f5 !important;
}
html[data-theme="dark"] .igv-ui-dialog input[type="text"],
html[data-theme="dark"] .igv-generic-dialog-container input[type="text"] {
  background-color: #0a0b16 !important;
  color: #e4e8f5 !important;
  border: 1px solid #1e2244 !important;
}
html[data-theme="dark"] .igv-ui-dropdown {
  background-color: #0f1120 !important;
  border: 1px solid #1e2244 !important;
  color: #e4e8f5 !important;
}
html[data-theme="dark"] .igv-ui-dropdown > div { color: #e4e8f5 !important; }
html[data-theme="dark"] .igv-ui-dropdown > div:hover { background-color: #1e2244 !important; }
html[data-theme="dark"] .igv-scrollbar-column { border-color: #1e2244 !important; }
html[data-theme="dark"] .igv-scrollbar-inner  { background-color: #2a3070 !important; }
/* Track column backgrounds */
html[data-theme="dark"] .igv-track-drag-column  { background-color: #0a0b16 !important; }
html[data-theme="dark"] .igv-track-drag-handle  { background-color: #1e2244 !important; }
html[data-theme="dark"] .igv-track-drag-handle-hover { background-color: #2a3070 !important; }
html[data-theme="dark"] .igv-gear-menu-column > div { background-color: #0f1120 !important; }
html[data-theme="dark"] .igv-axis-column { background-color: #0f1120 !important; }
html[data-theme="dark"] .igv-sample-name-column { background-color: #0f1120 !important; color: #e4e8f5 !important; }
/* "Zoom in to see features" notice */
html[data-theme="dark"] .igv-zoom-in-notice-container { background-color: transparent !important; }
html[data-theme="dark"] .igv-zoom-in-notice-container > div,
html[data-theme="dark"] .igv-zoom-in-notice div {
  background-color: rgba(15, 17, 32, 0.8) !important;
  color: #8892c0 !important;
}
/* Ruler tooltip */
html[data-theme="dark"] .igv-ruler-tooltip > div {
  background-color: #0f1120 !important;
  border: 1px solid #1e2244 !important;
  color: #e4e8f5 !important;
}
/* Multi-locus labels */
html[data-theme="dark"] .igv-multi-locus-ruler-label > div {
  background-color: #0f1120 !important;
  color: #e4e8f5 !important;
}
html[data-theme="dark"] .igv-multi-locus-close-button {
  background-color: #1e2244 !important;
  color: #8892c0 !important;
}
/* User feedback / messages */
html[data-theme="dark"] .igv-user-feedback {
  background-color: #0f1120 !important;
  border: 1px solid #1e2244 !important;
  color: #e4e8f5 !important;
}
/* ROI elements */
html[data-theme="dark"] .igv-roi-menu-next-gen {
  background-color: #0f1120 !important;
  border: 1px solid #1e2244 !important;
  color: #e4e8f5 !important;
}
html[data-theme="dark"] .igv-roi-menu-next-gen > div:first-child {
  background-color: #171929 !important;
  border-bottom: 1px solid #1e2244 !important;
  color: #e4e8f5 !important;
}
html[data-theme="dark"] .igv-roi-table {
  background-color: #0f1120 !important;
  border: 1px solid #1e2244 !important;
  color: #e4e8f5 !important;
}
html[data-theme="dark"] .igv-roi-table > div:first-child,
html[data-theme="dark"] .igv-roi-table > div:last-child {
  background-color: #171929 !important;
  color: #e4e8f5 !important;
}
html[data-theme="dark"] .igv-roi-table-button {
  background-color: #1e2244 !important;
  color: #e4e8f5 !important;
}
html[data-theme="dark"] .igv-roi-table-button:hover { background-color: #2a3070 !important; }
/* Generic dialog sub-elements */
html[data-theme="dark"] .igv-generic-dialog-container .igv-generic-dialog-one-liner,
html[data-theme="dark"] .igv-generic-dialog-container .igv-generic-dialog-label-input {
  background-color: #0f1120 !important;
  color: #e4e8f5 !important;
}
html[data-theme="dark"] .igv-generic-dialog-container .igv-generic-dialog-input input {
  background-color: #0a0b16 !important;
  color: #e4e8f5 !important;
  border: 1px solid #1e2244 !important;
}
html[data-theme="dark"] .igv-generic-dialog-container .igv-generic-dialog-ok-cancel div:last-child {
  background-color: #1e2244 !important;
  color: #8892c0 !important;
}
html[data-theme="dark"] .igv-generic-dialog-container .igv-generic-dialog-ok-cancel div:last-child:hover {
  background-color: #2a3070 !important;
}
/* Track viewport canvas backgrounds — clearRect leaves the canvas transparent so the
   CSS background of the parent div shows through as the track background colour. */
html[data-theme="dark"] .igv-track-view-outer,
html[data-theme="dark"] .igv-track-view       { background-color: #0f1120 !important; }
html[data-theme="dark"] .igv-content-div      { background-color: #0f1120 !important; }
html[data-theme="dark"] .igv-viewport         { background-color: #0f1120 !important; }
html[data-theme="dark"] .igv-viewport canvas  { background-color: #0f1120 !important; }

/* ── Placeholder ── */
#placeholder {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  min-height: 420px;
  gap: 12px;
  color: var(--text-muted);
}
#placeholder svg { opacity: 0.25; }
#placeholder p { font-size: 0.88rem; font-weight: 500; }

/* ── Pipeline panel ── */
.pipeline-panel {
  background: var(--surface);
  border-bottom: 1px solid var(--border);
}
.pipeline-panel > summary {
  padding: 10px 24px;
  font-size: 0.82rem;
  font-weight: 600;
  letter-spacing: 0.02em;
  background: var(--gradient);
  -webkit-background-clip: text;
  -webkit-text-fill-color: transparent;
  background-clip: text;
  cursor: pointer;
  user-select: none;
  list-style: none;
}
.pipeline-panel > summary::before {
  content: "▶ ";
  font-size: 0.68rem;
}
.pipeline-panel[open] > summary::before { content: "▼ "; }

.pipeline-body {
  padding: 12px 24px 16px;
  display: flex;
  flex-direction: column;
  gap: 10px;
}

.pipeline-field {
  display: flex;
  flex-direction: column;
  gap: 4px;
}
.pipeline-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 10px;
}
.pipeline-field label {
  font-size: 0.73rem;
  font-weight: 500;
  color: var(--text-muted);
}
.pipeline-field input[type="text"] {
  background: var(--input-bg);
  border: 1px solid var(--border);
  border-radius: 8px;
  color: var(--text);
  font-family: 'Open Sans', "SFMono-Regular", Consolas, monospace;
  font-size: 0.84rem;
  padding: 7px 12px;
  outline: none;
  transition: border-color 0.15s, box-shadow 0.15s;
}
.pipeline-field input[type="text"]:focus,
.pipeline-field select:focus {
  border-color: var(--accent);
  box-shadow: 0 0 0 3px #7c3aed20;
}
.pipeline-field input[type="text"]::placeholder { color: var(--placeholder); }

.pipeline-field select {
  background: var(--input-bg);
  border: 1px solid var(--border);
  border-radius: 8px;
  color: var(--text);
  font-family: 'Open Sans', sans-serif;
  font-size: 0.84rem;
  padding: 7px 12px;
  outline: none;
  cursor: pointer;
  transition: border-color 0.15s, box-shadow 0.15s;
  appearance: none;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 24 24' fill='none' stroke='%238892c0' stroke-width='2'%3E%3Cpath d='M6 9l6 6 6-6'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 10px center;
  padding-right: 30px;
}

.checkbox-field {
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: 0.78rem;
  color: var(--text-muted);
  cursor: pointer;
  user-select: none;
}
.checkbox-field input[type="checkbox"] { cursor: pointer; accent-color: var(--accent); }

.pipeline-actions {
  display: flex;
  align-items: center;
  gap: 10px;
}
.pipeline-actions button {
  background: var(--gradient-btn);
  border: none;
  border-radius: 8px;
  color: #fff;
  font-family: 'Open Sans', sans-serif;
  font-size: 0.84rem;
  font-weight: 600;
  padding: 7px 18px;
  cursor: pointer;
  transition: background 0.15s, box-shadow 0.15s, transform 0.1s;
  white-space: nowrap;
}
.pipeline-actions button:hover {
  background: var(--gradient-btn-hover);
  box-shadow: 0 2px 12px #7c3aed40;
  transform: translateY(-1px);
}
.pipeline-actions button:active { transform: translateY(0); }
.pipeline-actions button:disabled {
  background: var(--btn-disabled-bg);
  color: var(--btn-disabled-text);
  cursor: not-allowed;
  box-shadow: none;
  transform: none;
}

#pipelineStatus {
  font-size: 0.78rem;
  font-weight: 500;
  color: var(--text-subtle);
}
#pipelineStatus.error   { color: #f87171; }
#pipelineStatus.loading { color: #fbbf24; }
#pipelineStatus.ok      { color: #34d399; }

#pipelineResult {
  background: var(--input-bg);
  border: 1px solid var(--border-glow);
  border-radius: 8px;
  padding: 10px 14px;
  font-size: 0.8rem;
}
#pipelineResult .result-label { color: var(--text-muted); margin-bottom: 3px; font-weight: 500; }
#pipelineResult code {
  font-family: 'Open Sans', "SFMono-Regular", Consolas, monospace;
  background: var(--gradient);
  -webkit-background-clip: text;
  -webkit-text-fill-color: transparent;
  background-clip: text;
  word-break: break-all;
}

/* ── Get Insights panel ── */
#insightsStatus {
  font-size: 0.78rem;
  font-weight: 500;
  color: var(--text-subtle);
}
#insightsStatus.error   { color: #f87171; }
#insightsStatus.loading { color: #fbbf24; }
#insightsStatus.ok      { color: #34d399; }


.coverage-plots-section {
  margin-top: 14px;
  padding-top: 14px;
  border-top: 1px solid var(--border);
}

/* Stat cards */
.stat-cards {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin-top: 4px;
}
.stat-card {
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: 10px 12px;
  display: flex;
  flex-direction: column;
  gap: 2px;
}
.stat-card .stat-label {
  font-size: 0.68rem;
  font-weight: 600;
  color: var(--text-muted);
  text-transform: uppercase;
  letter-spacing: 0.05em;
  white-space: nowrap;
}
.stat-card .stat-value {
  font-size: 1.15rem;
  font-weight: 700;
  background: var(--gradient);
  -webkit-background-clip: text;
  -webkit-text-fill-color: transparent;
  background-clip: text;
  line-height: 1.2;
}
.stat-card .stat-sub {
  font-size: 0.68rem;
  color: var(--text-subtle);
}
.stat-card .stat-sub.stat-warn {
  color: #f97316;
}
.stat-card .stat-sub.stat-ok {
  color: #10b981;
}

/* Variant summary layout — align with .vep-section-header 24px side margin */
.vep-section-collapsible > .stat-cards {
  margin-left: 24px;
  margin-right: 24px;
}

/* Genomic Overview metrics table — replaces the old stat-card badge row.
   Reuses .sp-table base styling; adds a Value/Expected/Detail column layout
   with a per-row QC pass/warn flag. */
.gv-overview-wrap {
  margin: 8px 24px 0;
}
.gv-overview-table {
  font-size: 0.86rem;
  /* Column widths must NOT depend on content: sub-rows are toggled in and out,
     and with the default auto layout the browser re-measures only the visible
     rows, so every expand/collapse shifted the columns sideways. Fixed layout
     takes the widths from the <colgroup> instead, so they stay put. */
  table-layout: fixed;
  /* Below this the fixed columns would crush; .sp-table-wrap scrolls instead. */
  min-width: 720px;
}
.gv-ov-col-metric   { width: 240px; }
.gv-ov-col-value    { width: 130px; }
.gv-ov-col-expected { width: 130px; }
/* .gv-ov-col-detail intentionally unsized — it absorbs the remaining width. */
/* 3-column variant (Metric/Value/Detail) — the somatic overview drops the
   Expected column (its metrics have no defensible QC range), so the table needs
   less room before it must scroll. */
.gv-overview-table--no-expected { min-width: 560px; }
.gv-overview-table th.gv-ov-metric {
  font-size: 0.82rem;
  font-weight: 600;
  text-transform: none;
  letter-spacing: 0;
  color: var(--text);
  border-bottom: 1px solid var(--border);
  cursor: help;
}
.gv-overview-table td.gv-ov-value {
  font-weight: 700;
  font-size: 0.95rem;
  background: var(--gradient);
  -webkit-background-clip: text;
  -webkit-text-fill-color: transparent;
  background-clip: text;
}
/* When a metric carries a pass/warn status, the flag colour wins over the
   gradient fill so the assessment reads at a glance. */
.gv-overview-table td.gv-ov-value.gv-ov-ok {
  background: none;
  -webkit-text-fill-color: initial;
  color: #10b981;
}
.gv-overview-table td.gv-ov-value.gv-ov-warn {
  background: none;
  -webkit-text-fill-color: initial;
  color: #f97316;
}
.gv-overview-table .gv-ov-flag {
  display: inline-block;
  margin-left: 6px;
  font-size: 0.78rem;
  font-weight: 700;
}
.gv-overview-table .gv-ov-flag.gv-ov-ok   { color: #10b981; }
.gv-overview-table .gv-ov-flag.gv-ov-warn { color: #f97316; }
.gv-overview-table td.gv-ov-expected {
  color: var(--text-muted);
  font-variant-numeric: tabular-nums;
}
/* A threshold that drives a clinical verdict must be traceable. Cells whose
   expected range carries a source citation get a dotted underline to advertise
   the hover. */
.gv-overview-table td.gv-ov-cited {
  cursor: help;
  text-decoration: underline dotted var(--text-subtle);
  text-underline-offset: 3px;
}
.gv-overview-table td.gv-ov-detail {
  color: var(--text-subtle);
  font-size: 0.8rem;
  white-space: normal;
}

/* Composition sub-rows (e.g. Total Variants → SNVs / Insertions / Deletions).
   A <details> can't wrap <tr>s, so sub-rows are sibling rows toggled via
   [hidden] and indented to read as nested under their parent metric. */
.gv-overview-table th.gv-ov-toggle {
  cursor: pointer;
  user-select: none;
}
.gv-overview-table th.gv-ov-toggle:hover { color: var(--accent, var(--text)); }
.gv-overview-table th.gv-ov-toggle:focus-visible {
  outline: 2px solid var(--accent, #7c5cff);
  outline-offset: -2px;
}
.gv-overview-table .gv-ov-caret {
  display: inline-block;
  width: 12px;
  margin-right: 4px;
  font-size: 0.7rem;
  color: var(--text-subtle);
  /* Centre the arrow glyph in its 12px slot so it lines up with the leaf dot,
     which is also centred (both markers share the same box). */
  text-align: center;
  transition: transform 0.15s ease;
}
.gv-overview-table th.gv-ov-open .gv-ov-caret { transform: rotate(90deg); }
/* Leaf-row marker — occupies the SAME 12px + 4px slot as the caret so a row
   with no expander still aligns its label with the collapsible rows. Draws a
   small filled circle centred in that slot. */
.gv-overview-table .gv-ov-dot {
  display: inline-block;
  width: 12px;
  margin-right: 4px;
  text-align: center;
  line-height: 0;
  vertical-align: middle;
}
.gv-overview-table .gv-ov-dot::before {
  content: "";
  display: inline-block;
  width: 4px;
  height: 4px;
  border-radius: 50%;
  background: var(--text-subtle);
  vertical-align: middle;
}
.gv-overview-table tr.gv-ov-subrow th.gv-ov-sub-metric {
  /* Indentation is applied inline per depth level (see _gvMetricRow) so nesting
     can go arbitrarily deep; only the visual treatment lives here. */
  font-weight: 500;
  color: var(--text-muted);
}
.gv-overview-table tr.gv-ov-subrow td {
  color: var(--text-muted);
}
/* Sub-row values are secondary to the parent metric — drop the gradient fill
   and size down so the parent total stays the visual anchor. */
.gv-overview-table tr.gv-ov-subrow td.gv-ov-value {
  font-size: 0.85rem;
  font-weight: 600;
  background: none;
  -webkit-text-fill-color: initial;
  color: var(--text);
}
.gv-overview-table tr.gv-ov-subrow td.gv-ov-value.gv-ov-ok   { color: #10b981; }
.gv-overview-table tr.gv-ov-subrow td.gv-ov-value.gv-ov-warn { color: #f97316; }
/* Variant summary charts */
.vep-summary-charts {
  display: grid;
  /* minmax(0, 1fr) — NOT plain 1fr. A bare 1fr track is minmax(auto, 1fr),
     whose `auto` floor is the item's min-content width. The embedded Plotly
     SVGs have a large intrinsic min-width, so a plain-1fr track holding the
     narrow (span-1) chart refuses to shrink and steals width from the span-2
     chart — inverting the intended 2:1 ratio and overflowing at narrow
     widths. minmax(0, …) lets tracks shrink below content so the spans hold. */
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 12px;
  margin: 12px 24px 0;
}
/* The somatic stacked VAF histogram spans the full 3-col row; the gnomAD check
   sits below it at 1/3 width. Germline keeps its AF box (span 2) + impact
   histogram (span 1) side-by-side in the same 3-col grid. */
.som-chart-full { grid-column: 1 / -1; }
/* The VAF row is now two plots, not one stacked chart: allele frequency (20 VAF
   bins, needs the width) beside variant counts by classification (~8 bars). The
   2:1 split mirrors the germline AF-box + impact-histogram row in the same grid.
   Both tracks are minmax(0, 1fr) per the note above, so the spans actually hold
   instead of the narrow chart's intrinsic SVG width stealing from the wide one. */
.som-chart-wide   { grid-column: span 2; }
.som-chart-narrow { grid-column: span 1; }
@media (max-width: 800px) {
  .som-chart-wide, .som-chart-narrow { grid-column: 1 / -1; }
}
@media (max-width: 800px) {
  .vep-summary-charts { grid-template-columns: 1fr; }
}
.vep-summary-chart {
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: 14px;
  display: flex;
  flex-direction: column;
}
/* AF-by-impact box plot combines two dimensions — give it 2/3 of the row so
   the four impact tiers have room; the clinical-significance donut keeps 1/3. */
.vep-summary-chart-wide {
  grid-column: span 2;
}
@media (max-width: 800px) {
  .vep-summary-chart-wide { grid-column: span 1; }
}
.vep-af-box-chart {
  flex: 1;
  min-height: 240px;
}
.vep-summary-chart-title {
  font-size: 0.68rem;
  font-weight: 600;
  color: var(--text-muted);
  text-transform: uppercase;
  letter-spacing: 0.05em;
  margin-bottom: 10px;
}

/* Impact stacked bar */
.vep-impact-bar {
  display: flex;
  height: 18px;
  border-radius: 4px;
  overflow: hidden;
  margin-bottom: 6px;
}
.vep-impact-bar > span {
  display: block;
  min-width: 2px;
}
.vep-impact-legend {
  display: flex;
  flex-direction: column;
  gap: 3px;
  font-size: 0.67rem;
  color: var(--text-muted);
}
.vep-impact-legend span::before {
  content: "";
  display: inline-block;
  width: 8px;
  height: 8px;
  border-radius: 2px;
  margin-right: 3px;
  vertical-align: middle;
}

/* AF spectrum bars */
.vep-af-bars {
  display: flex;
  flex-direction: column;
  gap: 5px;
  flex: 1;
  justify-content: center;
}
.vep-af-row {
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: 0.7rem;
}
.vep-af-row .vep-af-label {
  width: 72px;
  text-align: right;
  color: var(--text-muted);
  flex-shrink: 0;
}
.vep-af-row .vep-af-track {
  flex: 1;
  height: 20px;
  background: var(--border);
  border-radius: 3px;
  overflow: hidden;
}
.vep-af-row .vep-af-fill {
  display: block;
  height: 100%;
  border-radius: 3px;
  min-width: 2px;
}
.vep-af-row .vep-af-count {
  width: 52px;
  text-align: right;
  color: var(--text-subtle);
  font-size: 0.65rem;
}

/* ClinSig donut */
.vep-donut-wrap {
  display: flex;
  align-items: center;
  gap: 14px;
}
.vep-donut {
  width: 90px;
  height: 90px;
  border-radius: 50%;
  position: relative;
  flex-shrink: 0;
}
.vep-donut::after {
  content: attr(data-center);
  position: absolute;
  inset: 16px;
  background: var(--surface-2);
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 0.72rem;
  font-weight: 700;
  color: var(--text);
}
.vep-donut-legend {
  display: flex;
  flex-direction: column;
  gap: 3px;
  font-size: 0.65rem;
  color: var(--text-muted);
}
.vep-donut-legend span::before {
  content: "";
  display: inline-block;
  width: 8px;
  height: 8px;
  border-radius: 2px;
  margin-right: 4px;
  vertical-align: middle;
}

/* Germline Variants search */
.gv-search-section {
  margin: 16px 24px 0;
}
.gv-search-bar {
  margin-bottom: 10px;
}
.gv-search-field-row {
  display: flex;
  align-items: center;
  gap: 6px;
}
.gv-search-field-row .filter-field-select {
  flex: 0 0 auto;
  width: auto;
  min-width: 90px;
}
.gv-search-field-row .filter-value-wrap {
  flex: 1 1 200px;
}
.gv-clear-btn {
  background: var(--surface-2);
  color: var(--text-muted);
}
.gv-clear-btn:hover {
  color: var(--text);
}
.gv-default-label {
  font-size: 0.72rem;
  color: var(--text-muted);
  margin: 0 0 6px;
}

/* Phenotype section */
.pheno-section-label {
  font-size: 0.67rem;
  font-weight: 700;
  color: var(--text-subtle);
  text-transform: uppercase;
  letter-spacing: 0.07em;
  margin-bottom: 8px;
}

/* Eye colour bars */
.eye-bars { display: flex; flex-direction: column; gap: 6px; max-width: 420px; }
.eye-bar-row {
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: 0.8rem;
}
.eye-bar-label { width: 90px; color: var(--text-muted); font-weight: 500; }
.eye-bar-track {
  flex: 1;
  height: 10px;
  background: var(--surface-2);
  border-radius: 5px;
  overflow: hidden;
  border: 1px solid var(--border);
}
.eye-bar-fill { height: 100%; border-radius: 5px; transition: width 0.5s ease; }
.eye-bar-pct { width: 36px; text-align: right; color: var(--text-muted); font-size: 0.76rem; }
.eye-prediction {
  font-size: 0.82rem;
  font-weight: 600;
  background: var(--gradient);
  -webkit-background-clip: text;
  -webkit-text-fill-color: transparent;
  background-clip: text;
  margin-bottom: 8px;
}

/* Blood type badge */
.blood-badge {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 52px;
  height: 52px;
  border-radius: 50%;
  background: linear-gradient(135deg, #ed6a3a 0%, #f63b3b 100%);
  color: #fff;
  font-size: 1.1rem;
  font-weight: 700;
  letter-spacing: -0.02em;
  box-shadow: 0 2px 12px #7c3aed40;
}
.blood-row {
  display: flex;
  align-items: center;
  gap: 12px;
}
.blood-note { font-size: 0.72rem; color: var(--text-subtle); max-width: 260px; line-height: 1.4; }

/* Trait chips — flip card */
.trait-chips { display: flex; flex-wrap: wrap; gap: 8px; }
.trait-chip {
  perspective: 500px;
  cursor: pointer;
}
.chip-inner {
  display: grid;
  transform-style: preserve-3d;
  transition: transform 0.35s ease;
}
.trait-chip.flipped .chip-inner { transform: rotateY(180deg); }
.chip-front, .chip-back {
  grid-area: 1 / 1;
  backface-visibility: hidden;
  -webkit-backface-visibility: hidden;
  border-radius: 8px;
  border: 1px solid var(--border);
  background: var(--surface-2);
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 2px;
  padding: 5px 12px;
  white-space: nowrap;
}
.chip-front { position: relative; overflow: visible; }
.chip-back { transform: rotateY(180deg); }
.trait-chip:hover .chip-front,
.trait-chip:hover .chip-back { border-color: var(--accent); }
.chip-label {
  font-size: 0.63rem;
  font-weight: 600;
  color: var(--text-subtle);
  text-transform: uppercase;
  letter-spacing: 0.05em;
  text-align: center;
}
.chip-value {
  font-size: 0.82rem;
  font-weight: 600;
  color: var(--text);
  text-align: center;
}
.chip-back .chip-gene {
  font-size: 0.72rem;
  font-weight: 700;
  color: var(--text);
}
.chip-back .chip-rsid {
  font-size: 0.62rem;
  color: var(--text-subtle);
}
.chip-back .chip-gt {
  font-size: 0.72rem;
  font-family: "SFMono-Regular", Consolas, monospace;
  color: var(--accent);
  margin-top: 1px;
}

/* Chip info button + tooltip */
.chip-info-btn {
  position: absolute;
  top: 3px;
  right: 4px;
  background: none;
  border: 1px solid var(--border);
  border-radius: 50%;
  color: var(--text-subtle);
  width: 13px;
  height: 13px;
  font-size: 8px;
  font-style: italic;
  font-family: Georgia, serif;
  font-weight: 700;
  cursor: default;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 0;
  line-height: 1;
  transition: border-color 0.15s, color 0.15s;
}
.chip-info-btn:hover {
  border-color: var(--accent-light);
  color: var(--accent-light);
}
.chip-tooltip {
  display: none;
  position: absolute;
  bottom: calc(100% + 6px);
  left: 50%;
  transform: translateX(-50%);
  width: 210px;
  background: var(--surface);
  border: 1px solid var(--border-glow);
  border-radius: 8px;
  padding: 8px 10px;
  font-size: 0.71rem;
  color: var(--text-muted);
  line-height: 1.5;
  z-index: 100;
  box-shadow: 0 4px 20px rgba(0,0,0,0.35);
  white-space: normal;
  text-align: left;
  pointer-events: none;
  font-style: normal;
  font-weight: 400;
  font-family: 'Open Sans', sans-serif;
}

/* Advanced collapsible inside insights result */
/* ── Collapsible insight sections (Eye, Sex/Blood, Traits, Health Risks) ── */
.insights-section {
  margin-top: 12px;
  border: 1px solid var(--border);
  border-radius: 10px;
  overflow: hidden;
}
.insights-section > summary {
  padding: 9px 14px;
  font-size: 0.75rem;
  font-weight: 600;
  letter-spacing: 0.03em;
  color: var(--text-muted);
  cursor: pointer;
  user-select: none;
  list-style: none;
  background: var(--surface-2);
  display: flex;
  align-items: center;
  gap: 6px;
}
.insights-section > summary::-webkit-details-marker { display: none; }
.insights-section > summary::before {
  content: "▶";
  font-size: 0.6rem;
  color: var(--text-subtle);
  transition: transform 0.2s;
  flex-shrink: 0;
}
.insights-section[open] > summary::before { transform: rotate(90deg); }
.insights-section-body { padding: 12px 14px 14px; }

.advanced-details {
  margin-top: 14px;
  border: 1px solid var(--border);
  border-radius: 10px;
  overflow: hidden;
}
.advanced-details summary {
  padding: 9px 14px;
  font-size: 0.76rem;
  font-weight: 600;
  letter-spacing: 0.02em;
  color: var(--text-muted);
  cursor: pointer;
  user-select: none;
  list-style: none;
  background: var(--surface-2);
}
.advanced-details summary::before { content: "▶  "; font-size: 0.65rem; }
.advanced-details[open] summary::before { content: "▼  "; }
.advanced-body { padding: 12px 14px 14px; }

.pheno-disclaimer {
  margin-top: 10px;
  font-size: 0.72rem;
  color: var(--text-subtle);
  line-height: 1.5;
}


/* ── Input + Browse button wrapper ── */
.input-with-browse {
  display: flex;
  gap: 6px;
}
.input-with-browse input[type="text"] {
  flex: 1;
  min-width: 0;
}
.browse-btn {
  background: var(--surface-2);
  border: 1px solid var(--border-glow);
  border-radius: 8px;
  color: var(--accent-light);
  padding: 0;
  width: 34px;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  transition: background 0.15s, border-color 0.15s, color 0.15s;
  flex-shrink: 0;
}
.browse-btn:hover {
  background: #7c3aed18;
  border-color: var(--accent-light);
  color: var(--accent-light);
}
.browse-btn svg { display: block; }

/* ── File browser modal ── */
#fileBrowserBackdrop {
  display: none;
  position: fixed;
  inset: 0;
  background: rgba(0,0,0,0.65);
  z-index: 10000;
  align-items: center;
  justify-content: center;
}
#fileBrowserBackdrop.open {
  display: flex;
}
#fileBrowserModal {
  background: var(--surface);
  border: 1px solid var(--border-glow);
  border-radius: 14px;
  box-shadow: 0 8px 40px rgba(0,0,0,0.5);
  width: min(680px, 95vw);
  max-height: 70vh;
  display: flex;
  flex-direction: column;
  overflow: hidden;
}
#fbHeader {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 12px 16px;
  background: var(--surface-2);
  border-bottom: 1px solid var(--border);
  flex-shrink: 0;
}
#fbBack {
  background: none;
  border: 1px solid var(--border);
  border-radius: 6px;
  color: var(--text-muted);
  cursor: pointer;
  font-size: 0.82rem;
  padding: 3px 9px;
  transition: border-color 0.15s, color 0.15s;
}
#fbBack:hover { border-color: var(--accent-light); color: var(--accent-light); }
#fbBack:disabled { opacity: 0.35; cursor: default; }
#fbBreadcrumb {
  flex: 1;
  font-size: 0.8rem;
  color: var(--text-muted);
  word-break: break-all;
  font-family: 'Open Sans', "SFMono-Regular", Consolas, monospace;
}
#fbClose {
  background: none;
  border: none;
  color: var(--text-subtle);
  cursor: pointer;
  font-size: 1.1rem;
  line-height: 1;
  padding: 2px 4px;
  border-radius: 4px;
  transition: color 0.15s;
}
#fbClose:hover { color: var(--text); }
#fbBody {
  overflow-y: auto;
  flex: 1;
  padding: 6px 0;
  scrollbar-width: thin;
  scrollbar-color: var(--scrollbar-inner) transparent;
}
#fbLoading {
  padding: 20px 20px;
  font-size: 0.82rem;
  color: var(--text-subtle);
  text-align: center;
}
.fb-item {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 7px 16px;
  font-size: 0.83rem;
  cursor: pointer;
  transition: background 0.1s;
}
.fb-item:hover { background: var(--surface-2); }
.fb-item .fb-icon { flex-shrink: 0; width: 18px; text-align: center; }
.fb-item .fb-name { flex: 1; color: var(--text); word-break: break-all; }
.fb-item .fb-meta { font-size: 0.72rem; color: var(--text-subtle); white-space: nowrap; }
.fb-item.fb-folder .fb-name { color: var(--accent-light); }

/* ── Health risk dashboard ───────────────────────────────────────────────── */
/* ── Table layout ── */
.hr-table {
  width: 100%;
  border-collapse: collapse;
  margin-top: 8px;
}
.hr-th-cond {
  text-align: left;
  padding: 0 12px 8px 0;
  font-size: 0.62rem;
  font-weight: 700;
  color: var(--text-subtle);
  text-transform: uppercase;
  letter-spacing: 0.06em;
  border-bottom: 2px solid var(--border);
}
.hr-th-level {
  text-align: center;
  padding: 0 8px 8px;
  width: 110px;
  overflow: hidden;
  transition: width 0.2s ease, padding 0.2s ease, opacity 0.2s ease;
  font-size: 0.62rem;
  font-weight: 700;
  color: var(--c, #888);
  letter-spacing: 0.03em;
  border-bottom: 2px solid var(--c, #888);
  width: 100px;
  min-width: 100px;
}
.hr-cat-row td {
  padding: 14px 0 5px;
}
.hr-cat-toggle {
  background: none;
  border: none;
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-size: 0.66rem;
  font-weight: 700;
  color: var(--text-subtle);
  text-transform: uppercase;
  letter-spacing: 0.07em;
  padding: 0;
  font-family: inherit;
}
.hr-cat-toggle:hover { color: var(--text-muted); }
.hr-cat-chevron {
  font-size: 0.75rem;
  transition: transform 0.18s ease;
  display: inline-block;
}
.hr-cat-chevron--collapsed { transform: rotate(-90deg); }
.hr-td-cond {
  padding: 7px 14px 7px 0;
  color: var(--text);
  font-size: 0.73rem;
  line-height: 1.3;
  border-bottom: 1px solid var(--border);
}
.hr-cond-row {
  display: flex;
  align-items: center;
  gap: 6px;
  width: 100%;
}
.hr-cond-content {
  flex: 1;
  min-width: 0;
  overflow: hidden;
  white-space: nowrap;
}
.hr-cond-content--expanded {
  white-space: normal;
  overflow: visible;
}
.hr-cond-expand {
  flex-shrink: 0;
  background: none;
  border: none;
  cursor: pointer;
  color: var(--accent, #a78bfa);
  font-size: 0.68rem;
  font-weight: 600;
  padding: 0;
  white-space: nowrap;
}
.hr-cond-expand:hover { opacity: 0.75; }
.hr-cond-expand--hidden { visibility: hidden; }
.hr-td-cell {
  text-align: center;
  padding: 6px 4px;
  overflow: hidden;
  transition: width 0.2s ease, padding 0.2s ease, opacity 0.2s ease;
  border-bottom: 1px solid var(--border);
}

/* ── Marker dot ── */
.hr-marker {
  display: inline-block;
  width: 14px;
  height: 14px;
  border-radius: 50%;
  border: none;
  cursor: pointer;
  padding: 0;
  transition: transform 0.15s, box-shadow 0.15s;
  vertical-align: middle;
}
.hr-marker:hover { transform: scale(1.45); }

/* ── Import Sample modal ── */
#createRunModal {
  max-height: 90vh;
}
.cr-body {
  overflow-y: auto;
  max-height: calc(90vh - 52px); /* 52px = header height */
}

/* ── Import Sample — file rows ── */
.is-files-section {
  display: flex;
  flex-direction: column;
  gap: 8px;
}
.is-files-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
}
.is-files-label {
  font-size: 0.73rem;
  font-weight: 500;
  color: var(--text-muted);
}
.is-add-file-btn {
  background: none;
  border: 1px solid var(--border-glow);
  border-radius: 6px;
  color: var(--accent-light);
  font-family: 'Open Sans', sans-serif;
  font-size: 0.73rem;
  font-weight: 600;
  padding: 3px 10px;
  cursor: pointer;
  transition: background 0.15s, border-color 0.15s;
}
.is-add-file-btn:hover {
  background: #7c3aed14;
  border-color: var(--accent-light);
}
#isFileRows,
#isFileRowsS3,
#afFileRows,
#afFileRowsS3 {
  display: flex;
  flex-direction: column;
  gap: 8px;
}

/* ── Import Sample — where the bytes come from ── */
.is-source-tabs {
  display: flex;
  gap: 4px;
  border-bottom: 1px solid var(--border);
  margin-bottom: 4px;
}
.is-source-tab {
  background: none;
  border: none;
  border-bottom: 2px solid transparent;
  color: var(--text-muted);
  font-family: 'Open Sans', sans-serif;
  font-size: 0.76rem;
  font-weight: 600;
  padding: 5px 10px;
  cursor: pointer;
  transition: color 0.15s, border-color 0.15s;
}
.is-source-tab:hover { color: var(--text); }
.is-source-tab.active {
  color: var(--accent-light);
  border-bottom-color: var(--accent);
}

/* The file picker and its transfer. The bar is the only moving part in the
   modal, so it stays quiet: a thin accent fill, no animation. */
.is-file-input {
  width: 100%;
  font-family: 'Open Sans', sans-serif;
  font-size: 0.78rem;
  color: var(--text);
}
.is-file-input::file-selector-button {
  background: none;
  border: 1px solid var(--border-glow);
  border-radius: 6px;
  color: var(--accent-light);
  font-family: 'Open Sans', sans-serif;
  font-size: 0.73rem;
  font-weight: 600;
  padding: 4px 10px;
  margin-right: 10px;
  cursor: pointer;
}
.is-file-input::file-selector-button:hover {
  background: #7c3aed14;
  border-color: var(--accent-light);
}
.is-file-upload-state {
  display: flex;
  flex-direction: column;
  gap: 4px;
  margin-top: 6px;
}
.is-file-progress {
  height: 4px;
  border-radius: 2px;
  background: var(--input-bg);
  overflow: hidden;
}
.is-file-progress-fill {
  display: block;
  height: 100%;
  width: 0;
  background: var(--accent);
  transition: width 0.2s linear;
}
.is-file-upload-text {
  font-size: 0.72rem;
  color: var(--text-muted);
  word-break: break-all;
}
.is-file-upload-state.is-done .is-file-progress-fill { background: var(--accent-2); }
.is-file-upload-state.is-error .is-file-progress-fill { background: #f87171; }
.is-file-upload-state.is-error .is-file-upload-text { color: #f87171; }
/* Cancelled is not failed: nothing went wrong, the user changed their mind.
   Muted rather than red, and the bar goes back to empty. */
.is-file-upload-state.is-cancelled .is-file-upload-text { color: var(--text-muted); }
/* Cancel sits under the bar, aligned right, and shows only while a transfer is
   running (file-cards.js hides it otherwise). A text button rather than a
   filled one: it must be reachable without competing with Import. */
.is-file-cancel {
  align-self: flex-start;
  background: none;
  border: none;
  padding: 0;
  color: var(--text-muted);
  font-family: 'Open Sans', sans-serif;
  font-size: 0.72rem;
  font-weight: 600;
  text-decoration: underline;
  cursor: pointer;
}
.is-file-cancel:hover { color: #f87171; }
/* ── Transfers tray (transfers.js) ──
   Bottom-left, because the chat and IGV floating buttons own bottom-right.
   Under every modal backdrop (9999) and under the chat panel (1100): a
   transfer is background work and must never sit on top of what the user is
   doing. Reuses the file cards' progress bar so the two read as one thing. */
.tx-tray {
  position: fixed;
  left: 16px;
  bottom: 16px;
  z-index: 850;
  width: 340px;
  max-width: calc(100vw - 32px);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 10px;
  box-shadow: var(--shadow-accent);
  font-size: 0.78rem;
  color: var(--text);
  overflow: hidden;
}
.tx-tray[hidden] { display: none; }
.tx-head {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 8px 10px;
  background: var(--surface-2);
  border-bottom: 1px solid var(--border);
  cursor: pointer;
  user-select: none;
}
.tx-title { font-weight: 700; }
.tx-summary { color: var(--text-muted); flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.tx-toggle {
  background: none;
  border: 1px solid var(--border);
  border-radius: 6px;
  color: var(--text-muted);
  width: 22px;
  height: 22px;
  line-height: 1;
  cursor: pointer;
}
.tx-body { max-height: 50vh; overflow-y: auto; }
.tx-note {
  padding: 6px 10px;
  color: var(--text-subtle);
  font-size: 0.7rem;
  border-bottom: 1px solid var(--border);
  cursor: help;
}
.tx-group { padding: 6px 10px 8px; border-bottom: 1px solid var(--border); }
.tx-group:last-child { border-bottom: none; }
.tx-group-head { display: flex; align-items: baseline; gap: 8px; margin-bottom: 4px; }
.tx-sample { font-weight: 600; flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.tx-open { color: var(--accent); text-decoration: none; font-size: 0.72rem; white-space: nowrap; }
.tx-open:hover { text-decoration: underline; }
.tx-row { display: flex; flex-direction: column; gap: 3px; padding: 4px 0; }
.tx-row-top, .tx-row-foot { display: flex; align-items: baseline; gap: 8px; }
.tx-name { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.tx-size, .tx-status { color: var(--text-muted); font-size: 0.72rem; white-space: nowrap; }
.tx-status { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; }
.tx-actions { display: flex; gap: 10px; align-items: baseline; }
.tx-act, .tx-repick {
  background: none;
  border: none;
  padding: 0;
  color: var(--text-muted);
  font-family: 'Open Sans', sans-serif;
  font-size: 0.72rem;
  font-weight: 600;
  text-decoration: underline;
  cursor: pointer;
}
.tx-act:hover, .tx-repick:hover { color: var(--accent); }
.tx-repick input { display: none; }
.tx-row.is-done .is-file-progress-fill { background: var(--accent-2); }
.tx-row.is-error .is-file-progress-fill { background: #f87171; }
.tx-row.is-error .tx-status { color: #f87171; }
.tx-row.is-needs-file .is-file-progress-fill { background: #f59e0b; }
.tx-row.is-needs-file .tx-status { color: #f59e0b; }
.tx-row.is-elsewhere .tx-status { font-style: italic; }
.is-file-card {
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: 10px 12px;
  display: flex;
  flex-direction: column;
  gap: 8px;
}
.is-file-card-top {
  display: flex;
  align-items: flex-end;
  gap: 8px;
}
.is-file-card-top .pipeline-field {
  flex: 1;
  min-width: 0;
}
.is-file-remove {
  background: none;
  border: 1px solid var(--border);
  border-radius: 6px;
  color: var(--text-subtle);
  cursor: pointer;
  font-size: 0.82rem;
  line-height: 1;
  padding: 5px 7px;
  flex-shrink: 0;
  align-self: flex-end;
  transition: border-color 0.15s, color 0.15s;
}
.is-file-remove:hover {
  border-color: #f87171;
  color: #f87171;
}

/* ── Search bar ── */
#searchBar {
  background: var(--surface);
  border-bottom: 1px solid var(--border);
  padding: 10px 24px;
  position: relative;
  z-index: 200;
}

.srch-wrap {
  display: flex;
  align-items: center;
  gap: 8px;
  background: var(--input-bg);
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: 0 12px;
  max-width: 680px;
  transition: border-color 0.15s, box-shadow 0.15s;
}
.srch-wrap:focus-within {
  border-color: var(--accent);
  box-shadow: 0 0 0 3px #7c3aed20;
}

.srch-icon {
  flex-shrink: 0;
  color: var(--text-subtle);
}

.srch-input {
  flex: 1;
  background: none;
  border: none;
  outline: none;
  color: var(--text);
  font-family: 'Open Sans', sans-serif;
  font-size: 0.84rem;
  padding: 9px 0;
}
.srch-input::placeholder { color: var(--placeholder); }

.srch-clear {
  background: none;
  border: none;
  color: var(--text-subtle);
  cursor: pointer;
  font-size: 0.82rem;
  padding: 0 2px;
  line-height: 1;
  flex-shrink: 0;
  transition: color 0.15s;
}
.srch-clear:hover { color: var(--text); }

/* ── Search results panel ── */
#searchResults {
  background: var(--surface);
  border-bottom: 1px solid var(--border);
  position: relative;
  z-index: 190;
  max-height: 420px;
  overflow-y: auto;
  scrollbar-width: thin;
  scrollbar-color: var(--scrollbar-inner) transparent;
}

.srch-msg {
  padding: 16px 24px;
  font-size: 0.82rem;
  color: var(--text-subtle);
}
.srch-error { color: #f87171; }

/* Sample result cards */
.srch-card {
  display: block;
  padding: 10px 24px;
  text-decoration: none;
  border-bottom: 1px solid var(--border);
  transition: background 0.1s;
  cursor: pointer;
}
.srch-card:last-of-type { border-bottom: none; }
.srch-card:hover { background: var(--surface-2); }

.srch-card-top {
  display: flex;
  align-items: baseline;
  gap: 8px;
  flex-wrap: wrap;
  margin-bottom: 4px;
}

.srch-card-name {
  font-size: 0.88rem;
  font-weight: 600;
  color: var(--text);
}

.srch-card-id {
  font-family: "SFMono-Regular", Consolas, monospace;
  font-size: 0.72rem;
  color: var(--text-subtle);
}

.srch-card-pills {
  display: flex;
  flex-wrap: wrap;
  gap: 4px;
  margin-left: auto;
}

.srch-pill {
  font-size: 0.63rem;
  font-weight: 600;
  padding: 2px 8px;
  border-radius: 999px;
  border: 1px solid;
  white-space: nowrap;
  letter-spacing: 0.03em;
}
.srch-pill-org { color: #a78bfa; border-color: #a78bfa44; background: #a78bfa0e; }

/* Specimen pills — shared class + per-type colour */
.sp-specimen {
  display: inline-block;
  padding: 1px 7px;
  border-radius: 4px;
  font-size: 0.65rem;
  font-weight: 700;
  letter-spacing: 0.04em;
  border: 1px solid;
  color: #34d399; border-color: #34d39944; background: #34d3990e;
}
.sp-specimen-normal { color: #60a5fa; border-color: #60a5fa44; background: #60a5fa0e; }
.sp-specimen-tumor  { color: #f87171; border-color: #f8717144; background: #f871710e; }

.srch-card-meta {
  display: flex;
  align-items: center;
  gap: 6px;
  font-size: 0.72rem;
  color: var(--text-subtle);
}
.srch-meta-item { white-space: nowrap; }
.srch-meta-sep  { color: var(--border-glow); }

.srch-footer {
  padding: 6px 24px 8px;
  font-size: 0.7rem;
  color: var(--text-subtle);
  border-top: 1px solid var(--border);
  background: var(--surface-2);
}

/* ── Sample page ── */
#samplePage {
  flex: 1;
  display: flex;
  flex-direction: column;
}

.sp-header {
  background: var(--gradient-header);
  border-bottom: 1px solid var(--border-glow);
  padding: 10px 24px;
  display: flex;
  align-items: flex-start;
  gap: 8px;
}
.sp-header-top {
  display: flex;
  align-items: center;
}
.sp-header-actions {
  display: flex;
  align-items: center;
  gap: 10px;
}

.sp-add-file-btn {
  background: var(--gradient-btn);
  border: none;
  border-radius: 8px;
  color: #fff;
  font-family: 'Open Sans', sans-serif;
  font-size: 0.78rem;
  font-weight: 600;
  padding: 6px 14px;
  cursor: pointer;
  white-space: nowrap;
  transition: background 0.15s, box-shadow 0.15s, transform 0.1s;
}
.sp-add-file-btn:hover {
  background: var(--gradient-btn-hover);
  box-shadow: 0 2px 12px #7c3aed40;
  transform: translateY(-1px);
}
.sp-add-file-btn:active { transform: translateY(0); }

/* ── Launch Run group ── */
.sp-launch-group {
  display: flex;
  align-items: center;
  gap: 6px;
}

.sp-launch-select {
  background: var(--input-bg);
  border: 1px solid var(--border-glow);
  border-radius: 8px;
  color: var(--text);
  font-family: 'Open Sans', sans-serif;
  font-size: 0.78rem;
  padding: 0 10px;
  height: 32px;
  min-width: 200px;
  cursor: pointer;
  appearance: auto;
}
.sp-launch-select:focus {
  outline: none;
  border-color: var(--accent);
  box-shadow: 0 0 0 3px #7c3aed20;
}
.sp-advanced-toggle {
  background: none;
  border: none;
  color: var(--text-muted);
  font-size: 0.72rem;
  cursor: pointer;
  padding: 2px 6px;
  white-space: nowrap;
}
.sp-advanced-toggle:hover {
  color: var(--accent);
}

.sp-launch-btn {
  display: inline-flex;
  align-items: center;
  background: none;
  border: 1px solid var(--accent-light);
  border-radius: 8px;
  color: var(--accent-light);
  font-family: 'Open Sans', sans-serif;
  font-size: 0.78rem;
  font-weight: 600;
  padding: 0 14px;
  height: 32px;
  cursor: pointer;
  white-space: nowrap;
  transition: background 0.15s, color 0.15s, box-shadow 0.15s;
}
.sp-launch-btn:hover {
  background: var(--accent-light);
  color: #fff;
  box-shadow: 0 2px 10px #7c3aed30;
}
.sp-launch-btn:disabled,
.sp-launch-btn--disabled {
  opacity: 0.45;
  cursor: not-allowed;
  box-shadow: none;
  pointer-events: none;
}

/* ── Launch Run status bar ── */
.sp-launch-status-bar {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 7px 24px;
  font-size: 0.8rem;
  border-bottom: 1px solid var(--border);
}
.sp-launch-status-loading { color: var(--text-muted); background: var(--surface); }
.sp-launch-status-ok      { color: #34d399; background: #34d39910; }
.sp-launch-status-error   { color: #f87171; background: #f8717110; }

.sp-launch-status-close {
  background: none;
  border: none;
  color: var(--text-muted);
  cursor: pointer;
  margin-left: auto;
  padding: 0 4px;
  font-size: 0.9rem;
  line-height: 1;
}
.sp-launch-status-close:hover { color: var(--text); }

/* ── Launch Run modal ── */
#launchRunBackdrop {
  display: none;
  position: fixed;
  inset: 0;
  background: rgba(0,0,0,0.65);
  z-index: 9999;
  align-items: center;
  justify-content: center;
}
#launchRunBackdrop.open { display: flex; }

#launchRunModal {
  background: var(--surface);
  border: 1px solid var(--border-glow);
  border-radius: 14px;
  box-shadow: 0 8px 40px rgba(0,0,0,0.5);
  width: min(380px, 95vw);
  display: flex;
  flex-direction: column;
  overflow: hidden;
}

.lr-input-row {
  display: flex;
  flex-direction: column;
  gap: 5px;
}

.lr-label {
  font-size: 0.68rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--text-subtle);
}

.lr-select {
  background: var(--input-bg);
  border: 1px solid var(--border-glow);
  border-radius: 8px;
  color: var(--text);
  font-family: 'Open Sans', sans-serif;
  font-size: 0.82rem;
  padding: 0 10px;
  height: 34px;
  width: 100%;
  cursor: pointer;
  appearance: auto;
}
.lr-select:focus {
  outline: none;
  border-color: var(--accent);
  box-shadow: 0 0 0 3px #7c3aed20;
}
.lr-select-sm {
  font-size: 0.72rem;
  height: 28px;
  opacity: 0.85;
  margin-bottom: 4px;
}
/* A typed scalar (an s3:// URI, an obs column) shares the select's chrome so
   the form reads as one control family; the cursor says it is typeable. */
.lr-text {
  cursor: text;
  font-family: 'JetBrains Mono', 'Fira Code', monospace;
  font-size: 0.76rem;
}
/* A DERIVED value sitting in a text field is a suggestion, not an answer, and
   it has to look like one or people leave it alone assuming it is fixed. It
   is rendered in the UI's own face rather than the mono one the field uses
   for typed values, italic and muted — the same visual weight as a
   placeholder, but selectable and editable, which a real placeholder is not
   (we keep the value so a person can tweak one segment instead of retyping
   the whole name). The class is dropped on the first keystroke, so what the
   user typed reads as theirs. */
.lr-text--derived {
  font-family: 'Open Sans', sans-serif;
  font-style: italic;
  color: var(--text-muted);
}
.lr-help {
  font-size: 0.72rem;
  line-height: 1.35;
  color: var(--text-subtle);
}

.lr-advanced {
  border-top: 1px solid var(--border-glow);
  margin-top: 4px;
  padding-top: 4px;
}
.lr-advanced-summary {
  font-size: 0.76rem;
  font-weight: 650;
  letter-spacing: 0;
  color: var(--text-subtle);
  cursor: pointer;
  user-select: none;
  list-style: none;
  display: flex;
  align-items: center;
  gap: 5px;
  padding: 4px 0;
}
.lr-advanced-summary::-webkit-details-marker { display: none; }
.lr-advanced-summary::before {
  content: "▶";
  font-size: 0.55rem;
  transition: transform 0.15s;
}
details[open] .lr-advanced-summary::before { transform: rotate(90deg); }
.lr-advanced-body {
  padding-top: 10px;
  display: flex;
  flex-direction: column;
  gap: 10px;
}
.lr-experiment-toggle {
  display: flex;
  gap: 14px;
  margin-bottom: 4px;
}
.lr-experiment-opt {
  font-size: 0.78rem;
  color: var(--text);
  cursor: pointer;
  display: flex;
  align-items: center;
  gap: 4px;
}
.lr-run-mode {
  display: flex;
  gap: 14px;
  margin-bottom: 4px;
}
.lr-run-mode-opt {
  font-size: 0.78rem;
  color: var(--text);
  cursor: pointer;
  display: flex;
  align-items: center;
  gap: 4px;
}
.lr-new-run-row {
  display: flex;
  align-items: center;
  gap: 8px;
}
.lr-new-run-id {
  font-size: 0.72rem;
  color: var(--text-subtle);
  flex: 1;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.lr-regen-btn {
  background: none;
  border: 1px solid var(--border-glow);
  border-radius: 6px;
  color: var(--text-subtle);
  font-size: 0.75rem;
  padding: 3px 8px;
  cursor: pointer;
  white-space: nowrap;
  flex-shrink: 0;
}
.lr-regen-btn:hover { color: var(--text); border-color: var(--accent); }

#lrStatus {
  font-size: 0.78rem;
  font-weight: 500;
  min-height: 1em;
}
#lrStatus.error   { color: #f87171; }
#lrStatus.loading { color: #fbbf24; }
#lrStatus.ok      { color: #34d399; }

.sp-id-block {
  display: flex;
  flex-direction: column;
  gap: 4px;
}

.sp-meta-row {
  display: flex;
  align-items: baseline;
  gap: 8px;
}

.sp-meta-label {
  font-size: 0.65rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--text-subtle);
  white-space: nowrap;
  min-width: 7rem;
}

.sp-meta-value {
  font-size: 0.9rem;
  font-weight: 600;
  color: var(--text);
}

/* An indication that exists but is not readable by this account. Muted and
   un-bolded so it reads as an absence of ACCESS rather than as a value — the
   one thing it must not look like is a diagnosis. */
.sp-meta-value.sp-indication-withheld {
  font-weight: 400;
  font-style: italic;
  /* --text-muted, not --text-dim: the latter is not a token this stylesheet
     defines, so it would have fallen through to a hardcoded grey in BOTH
     themes and never adapted to either. Caught only because the theme default
     became the operating system's on 2026-09-09, which made light the common
     path rather than the unusual one. */
  color: var(--text-muted);
}

/* Inline editor controls in the sample header's metadata block. Sized off
   .sp-launch-select so the two selects on this page match. */
.sp-meta-select {
  background: var(--input-bg);
  border: 1px solid var(--border-glow);
  border-radius: 6px;
  color: var(--text);
  font-family: 'Open Sans', sans-serif;
  font-size: 0.82rem;
  padding: 0 8px;
  height: 28px;
  max-width: 380px;
  cursor: pointer;
  appearance: auto;
}
.sp-meta-select:focus {
  outline: none;
  border-color: var(--accent);
  box-shadow: 0 0 0 3px #7c3aed20;
}
.sp-meta-select:disabled {
  opacity: 0.6;
  cursor: progress;
}
/* The typed twin of .sp-meta-select — the rename input on the sample and run
   page headers. Wider than the select because a name is prose, not a pick. */
.sp-meta-input {
  cursor: text;
  min-width: 260px;
  max-width: 480px;
}
/* Matches .tag-add-btn — the other "modify this sample's metadata" affordance
   on this page — so the two read as the same class of action. */
.sp-meta-edit-btn {
  background: none;
  border: 1px dashed var(--border);
  border-radius: 999px;
  color: var(--text-muted);
  cursor: pointer;
  font-family: 'Open Sans', sans-serif;
  font-size: 0.72rem;
  padding: 2px 8px;
  white-space: nowrap;
}
.sp-meta-edit-btn:hover { border-color: var(--accent-light); color: var(--accent-light); }
/* The derived-baseline readout and the save status. Deliberately quiet — it is
   a consequence of the selection, not a second thing to read. */
.sp-meta-hint {
  font-size: 0.78rem;
  color: var(--text-muted);
  white-space: nowrap;
}
/* An N/A baseline carries its reason on the title; mark it as hoverable rather
   than leaving the explanation undiscoverable. */
.sp-meta-hint[title]:not([title=""]) {
  cursor: help;
  text-decoration: underline dotted var(--text-subtle);
  text-underline-offset: 3px;
}
/* #f87171 is the error colour every other status line on this page uses
   (.sp-launch-status-error, .tag-modal-status.error, #crStatus.error). */
.sp-meta-hint.is-error { color: #f87171; }

#runPageDuration {
  font-weight: 400;
  color: var(--text-muted);
}

.sp-meta-mono {
  font-family: "SFMono-Regular", Consolas, monospace;
  background: var(--gradient);
  -webkit-background-clip: text;
  -webkit-text-fill-color: transparent;
  background-clip: text;
}

.sp-tab-bar {
  display: flex;
  border-bottom: 1px solid var(--border);
  padding: 0 24px;
  background: var(--surface);
  flex-shrink: 0;
  overflow-x: auto;
  scrollbar-width: none; /* Firefox */
  -ms-overflow-style: none; /* IE/Edge */
}

.sp-tab-bar::-webkit-scrollbar { display: none; } /* Chrome/Safari */

.sp-tab {
  background: none;
  border: none;
  border-bottom: 2px solid transparent;
  color: var(--text-muted);
  cursor: pointer;
  font-family: inherit;
  font-size: 0.875rem;
  font-weight: 500;
  padding: 10px 16px;
  margin-bottom: -1px;
  transition: color 0.15s, border-color 0.15s;
  flex-shrink: 0;
  white-space: nowrap;
  text-decoration: none;
  display: inline-block;
}

.sp-tab:hover { color: var(--text); }

.sp-tab.sp-tab-active {
  color: var(--accent-light);
  border-bottom-color: var(--accent-light);
}

.sp-body {
  padding: 20px 24px;
  flex: 1;
  /* NOT a scroll container. body is min-height:100vh, so this box is never
     shorter than its content and the page scrolls on the window — an
     overflow-y:auto here would scroll nothing, but it would still make this
     box the scrollport for every position:sticky descendant (the Cells tab's
     control dock), which then never sticks because its scrollport never
     scrolls. */
  overflow-y: visible;
}

.sp-tab-panel { /* shown/hidden by JS */ }

/* (genome tab removed — IGV lives in the slide-up panel) */


/* ── IGV slide-up panel ── */
.igv-panel-backdrop {
  position: fixed;
  inset: 0;
  background: rgba(0, 0, 0, 0.45);
  z-index: 499;
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.3s ease;
}
.igv-panel-backdrop--open {
  opacity: 1;
  pointer-events: auto;
}

/* The panel opens at its ceiling: full viewport minus a gap that keeps the
   page's top edge visible so it still reads as a sheet over the sample
   page. The drag handle enforces the same ceiling, read off this token
   (_spIgvPanelTopGap), so a resize can never exceed the default. */
:root { --igv-panel-top-gap: 60px; }
.igv-panel {
  position: fixed;
  bottom: 0;
  left: 0;
  right: 0;
  height: calc(100vh - var(--igv-panel-top-gap));
  min-height: 360px;
  background: var(--surface);
  border-top: 1px solid var(--border-glow);
  border-radius: 14px 14px 0 0;
  box-shadow: 0 -8px 40px rgba(0, 0, 0, 0.4);
  z-index: 500;
  display: flex;
  flex-direction: column;
  transform: translateY(100%);
  transition: transform 0.32s cubic-bezier(0.4, 0, 0.2, 1);
}
.igv-panel--open {
  transform: translateY(0);
}

.igv-panel-resize-handle {
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  height: 6px;
  cursor: ns-resize;
  z-index: 1;
  display: flex;
  align-items: center;
  justify-content: center;
}
.igv-panel-resize-handle::after {
  content: '';
  width: 36px;
  height: 3px;
  border-radius: 2px;
  background: var(--border-glow);
  transition: background 0.15s;
}
.igv-panel-resize-handle:hover::after { background: var(--accent-light); }

.igv-panel-header {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 10px 16px;
  border-bottom: 1px solid var(--border);
  flex-shrink: 0;
  background: var(--surface-2);
  border-radius: 14px 14px 0 0;
}
.igv-panel-title {
  font-size: 0.85rem;
  font-weight: 600;
  color: var(--text);
}
.igv-panel-locus {
  font-family: monospace;
  font-size: 0.78rem;
  color: var(--accent-light);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 4px;
  padding: 1px 7px;
  display: none;
}
.igv-panel-locus:not(:empty) { display: inline; }
.igv-panel-status {
  font-size: 0.78rem;
  font-weight: 500;
}
.igv-panel-status.error   { color: #f87171; }
.igv-panel-status.loading { color: var(--text-muted); }
.igv-panel-close {
  position: absolute;
  top: 8px;
  right: 8px;
  z-index: 10;
  background: none;
  border: none;
  cursor: pointer;
  color: var(--text-muted);
  font-size: 1rem;
  line-height: 1;
  padding: 4px 6px;
  border-radius: 4px;
}
.igv-panel-close:hover { background: var(--border); color: var(--text); }

.igv-panel-body {
  flex: 1;
  /* The track stack is fitted to this box (_spFitTracksToPanel) so the gene
     track is always on screen; auto is the fallback for a panel too short
     even for the BAM minimum, where scrolling beats clipping. */
  overflow-x: hidden;
  overflow-y: auto;
  min-height: 0;
  background: var(--surface);
}

/* Drag grip along the bottom edge of each data track (_spAttachTrackGrips) */
.igv-track-grip {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  height: 7px;
  cursor: ns-resize;
  z-index: 20;
  touch-action: none;
}
/* The mark: the same 36×3 px pill as the panel's own handle, centred on the
   edge and ALWAYS visible — a grip nobody can see is a grip nobody uses. */
.igv-track-grip::before {
  content: "";
  position: absolute;
  left: 50%;
  bottom: 1px;
  width: 36px;
  height: 3px;
  margin-left: -18px;
  border-radius: 2px;
  background: var(--border-glow);
  transition: background 0.12s;
}
/* On hover the whole edge lights up, so the drag target reads as the edge. */
.igv-track-grip::after {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  bottom: 1px;
  height: 2px;
  background: transparent;
  transition: background 0.12s;
}
.igv-track-grip:hover::before,
.igv-track-grip:active::before,
.igv-track-grip:hover::after,
.igv-track-grip:active::after { background: var(--accent-light); }

/* ── Reference sequence bar ── */
/* ── Unified sequence container (nucleotides + amino acids) ── */
.igv-seq-container {
  border-top: 1px solid var(--border);
  background: var(--surface-alt, var(--surface));
}
.igv-seq-layout {
  display: flex;
  align-items: stretch;
  gap: 10px;
  padding: 4px 12px;
}
.igv-seq-labels {
  display: flex;
  flex-direction: column;
  flex-shrink: 0;
  min-width: 160px;
}
.igv-seq-label-cell {
  display: flex;
  align-items: center;
  gap: 10px;
  flex: 1;
  white-space: nowrap;
}
.igv-seq-label {
  font-size: 11px;
  font-weight: 600;
  color: var(--text-muted);
  text-transform: uppercase;
  letter-spacing: 0.04em;
  flex-shrink: 0;
  white-space: nowrap;
  /* Fixed width = widest label ("Amino Acids"), so the control buttons always
     start at the same x after ALL three labels (DNA/mRNA/Amino Acids). */
  width: 84px;
}
.igv-seq-scroll {
  flex: 1;
  overflow-x: auto;
  overflow-y: hidden;
  scrollbar-width: thin;
  position: relative;        /* .igv-seq-guide is placed against it */
}
/* igv's Center Line, continued through the sequence rows (_spPlaceSeqGuide):
   dashed verticals either side of the centre base, igv's own grey and dash. */
.igv-seq-guide {
  position: absolute;
  top: 0;
  bottom: 0;
  pointer-events: none;
  z-index: 2;
  box-sizing: border-box;
  border-left: thin dashed rgba(127, 127, 127, 0.51);
  border-right: thin dashed rgba(127, 127, 127, 0.51);
}
[data-theme="dark"] .igv-seq-guide {
  border-left-color: rgba(200, 200, 200, 0.45);
  border-right-color: rgba(200, 200, 200, 0.45);
}
.igv-seq-scroll::-webkit-scrollbar { height: 4px; }
.igv-seq-scroll::-webkit-scrollbar-thumb { background: var(--border); border-radius: 2px; }
.igv-seq-grid {
  display: grid;
  font-family: 'SF Mono', 'Menlo', 'Consolas', monospace;
  font-size: 13px;           /* _spFitSeqBarFont scales this down with the zoom */
  font-weight: 600;
  line-height: 1.6;
  color: var(--text);
  gap: 0;
  /* Full width of the scroll area, one `1fr` column per base
     (_spSeqGridColumns) — the row stretches like igv's reference track. */
  width: 100%;
}
/* Below ~7 px per base the letters are a smudge: paint each base as a block
   in its own colour instead, as igv's sequence track does. */
.igv-seq-grid--blocks .seq-cell,
.igv-seq-grid--blocks .aa-cell {
  font-size: 0;
  line-height: 0;
  height: 12px;
  border-radius: 1px;
}
.igv-seq-grid--blocks .seq-cell.nt-a { background: #00c000; }
.igv-seq-grid--blocks .seq-cell.nt-t { background: #ff0000; }
.igv-seq-grid--blocks .seq-cell.nt-u { background: #ff0000; }
.igv-seq-grid--blocks .seq-cell.nt-c { background: #0000ff; }
.igv-seq-grid--blocks .seq-cell.nt-g { background: #d29000; }
.igv-seq-grid--blocks .seq-cell.nt-n { background: #808080; }
[data-theme="dark"] .igv-seq-grid--blocks .seq-cell.nt-a { background: #4cd94c; }
[data-theme="dark"] .igv-seq-grid--blocks .seq-cell.nt-t { background: #ff6666; }
[data-theme="dark"] .igv-seq-grid--blocks .seq-cell.nt-u { background: #ff6666; }
[data-theme="dark"] .igv-seq-grid--blocks .seq-cell.nt-c { background: #6b9bff; }
[data-theme="dark"] .igv-seq-grid--blocks .seq-cell.nt-g { background: #f0b840; }
[data-theme="dark"] .igv-seq-grid--blocks .seq-cell.nt-n { background: #999; }
/* Residues keep their zebra background; the glyph just goes. */
.igv-seq-grid--blocks .seq-cell--variant { outline: 1px solid var(--accent-light); }
.igv-mrna-grid {
  margin-top: 2px;
  border-top: 1px dashed var(--border);
  padding-top: 2px;
}
.igv-protein-grid {
  margin-top: 2px;
  border-top: 1px dashed var(--border);
  padding-top: 2px;
}
/* mRNA U base color (same hue as T but distinguishable) */
.nt-u { color: #ff0000; }
[data-theme="dark"] .nt-u { color: #ff6666; }
.igv-gene-badge {
  display: inline-flex;
  align-items: center;
  padding: 1px 8px;
  font-size: 10px;
  font-weight: 600;
  letter-spacing: 0.03em;
  color: var(--accent-light, #a78bfa);
  background: rgba(167, 139, 250, 0.12);
  border: 1px solid rgba(167, 139, 250, 0.25);
  border-radius: 999px;
  white-space: nowrap;
}
[data-theme="dark"] .igv-gene-badge {
  background: rgba(167, 139, 250, 0.15);
  border-color: rgba(167, 139, 250, 0.3);
}
/* Nucleotide cell */
.seq-cell {
  text-align: center;
  user-select: text;
  cursor: text;
  padding: 0;
  line-height: 1.6;
  min-width: 0;              /* a 1fr column may be narrower than a glyph */
  overflow: hidden;
}
/* Amino acid cell — spans 3 columns */
.aa-cell {
  text-align: center;
  user-select: text;
  cursor: text;
  padding: 0;
  line-height: 1.6;
  border-radius: 2px;
  min-width: 0;
  overflow: hidden;
}
.aa-cell--gap {
  color: var(--text-subtle, #545e8a);
  opacity: 0.3;
}
/* Codon zebra striping */
.seq-cell[data-codon-idx="even"],
.aa-cell[data-codon-idx="even"] {
  background: rgba(99, 102, 241, 0.18);
}
[data-theme="dark"] .seq-cell[data-codon-idx="even"],
[data-theme="dark"] .aa-cell[data-codon-idx="even"] {
  background: rgba(99, 102, 241, 0.28);
}
.seq-cell[data-codon-idx="odd"],
.aa-cell[data-codon-idx="odd"] {
  background: rgba(99, 102, 241, 0.06);
}
[data-theme="dark"] .seq-cell[data-codon-idx="odd"],
[data-theme="dark"] .aa-cell[data-codon-idx="odd"] {
  background: rgba(99, 102, 241, 0.12);
}
/* Strand flip button */
.igv-strand-flip {
  display: inline-flex;
  align-items: center;
  gap: 3px;
  background: none;
  border: 1px solid var(--border);
  border-radius: 4px;
  padding: 1px 6px;
  cursor: pointer;
  color: var(--text-muted);
  font-size: 10px;
  transition: background 0.15s, color 0.15s;
  flex-shrink: 0;
}
.igv-strand-flip:hover {
  background: var(--border);
  color: var(--text);
}
.igv-strand-flip--active {
  background: var(--accent-light, #6366f1);
  color: #fff;
  border-color: transparent;
}
.igv-strand-flip--active:hover { opacity: 0.85; }
.igv-strand-flip--active svg { transform: scaleX(-1); }
.igv-strand-flip--active2 {
  background: #d97706;
  color: #fff;
  border-color: transparent;
}
.igv-strand-flip--active2:hover { opacity: 0.85; }
.igv-strand-flip--active2 svg { transform: scaleX(1); }
/* Wildtype ↔ variant-bearing sequence toggle — compact pill next to "DNA Bases",
   mirrors .igv-strand-flip; pink when active (variant shown) to match the
   .seq-cell--variant base highlight. Shown only when a variant was navigated to. */
.igv-variant-toggle {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  background: none;
  border: 1px solid var(--border);
  border-radius: 4px;
  padding: 1px 6px;
  cursor: pointer;
  color: var(--text-muted);
  font-size: 10px;
  font-weight: 600;
  transition: background 0.15s, color 0.15s;
  flex-shrink: 0;
  /* Fixed width fits the wider label ("Wildtype") so toggling Mutant↔Wildtype
     never resizes the label column — which would otherwise shift the start of
     the sequences on the right. */
  width: 62px;
  box-sizing: border-box;
}
.igv-variant-toggle:hover { background: var(--border); color: var(--text); }
.igv-variant-toggle--active {
  background: #db2777;
  color: #fff;
  border-color: transparent;
}
.igv-variant-toggle--active:hover { opacity: 0.85; }
/* RNA-row display toggle — mRNA (U) ↔ cDNA (T). Same compact pill as the variant
   toggle; indigo when active (cDNA) to distinguish from the pink variant toggle.
   Fixed width so mRNA↔cDNA never resizes the label column. */
.igv-rna-toggle {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  background: none;
  border: 1px solid var(--border);
  border-radius: 4px;
  padding: 1px 6px;
  cursor: pointer;
  color: var(--text-muted);
  font-size: 10px;
  font-weight: 600;
  transition: background 0.15s, color 0.15s;
  flex-shrink: 0;
  width: 48px;
  box-sizing: border-box;
}
.igv-rna-toggle:hover { background: var(--border); color: var(--text); }
.igv-rna-toggle--active {
  background: var(--accent-light, #6366f1);
  color: #fff;
  border-color: transparent;
}
.igv-rna-toggle--active:hover { opacity: 0.85; }
.igv-strand-label {
  font-family: 'SF Mono', 'Menlo', monospace;
  font-size: 9px;
  font-weight: 600;
}
/* IGV-style nucleotide colors */
.nt-a { color: #00c000; }
.nt-t { color: #ff0000; }
.nt-c { color: #0000ff; }
.nt-g { color: #d29000; }
.nt-n { color: #808080; }
[data-theme="dark"] .nt-a { color: #4cd94c; }
[data-theme="dark"] .nt-t { color: #ff6666; }
[data-theme="dark"] .nt-c { color: #6b9bff; }
[data-theme="dark"] .nt-g { color: #f0b840; }
[data-theme="dark"] .nt-n { color: #999; }
/* The centre base is marked by the dashed guide (.igv-seq-guide) and the amber
   tint below; it used to be underlined as well, which read as a third marker. */
.nt-center { font-weight: 800; }
/* Center guide — highlight the center column across all tracks */
.seq-center-guide {
  background: rgba(251, 191, 36, 0.2) !important;
  border-radius: 2px;
}
[data-theme="dark"] .seq-center-guide {
  background: rgba(251, 191, 36, 0.25) !important;
}
/* Variant-bearing base/codon highlight — applied to the DNA, mRNA and Amino Acid
   cells that carry the navigated variant's substitution. Pink to match the
   toggle; placed after .seq-center-guide so a variant-at-center reads as variant.
   Works on both .seq-cell and .aa-cell (selected by the standalone class). */
.seq-cell--variant {
  background: rgba(219, 39, 119, 0.22) !important;
  border-radius: 2px;
  font-weight: 800;
  box-shadow: inset 0 -2px 0 #db2777;
}
[data-theme="dark"] .seq-cell--variant {
  background: rgba(219, 39, 119, 0.34) !important;
}
.igv-seq-copy-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 22px;
  height: 22px;
  padding: 0;
  margin-left: auto;
  color: var(--text-muted);
  background: none;
  border: 1px solid transparent;
  border-radius: 4px;
  cursor: pointer;
  transition: background 0.15s, color 0.15s, border-color 0.15s;
  flex-shrink: 0;
}
.igv-seq-copy-icon:hover {
  background: var(--border);
  color: var(--text);
  border-color: var(--border);
}
.igv-header-toggle {
  background: var(--surface);
  color: var(--text-muted);
  border: 1px solid var(--border);
  border-radius: 6px;
  padding: 3px 10px;
  font-size: 0.75rem;
  font-weight: 600;
  cursor: pointer;
  font-family: inherit;
  white-space: nowrap;
  transition: background 0.15s, color 0.15s, border-color 0.15s;
}
.igv-header-toggle:hover { background: var(--border); color: var(--text); }
.igv-header-toggle--active {
  background: var(--accent-light, #6366f1);
  color: #fff;
  border-color: transparent;
}
.igv-header-toggle--active:hover { opacity: 0.85; background: var(--accent-light, #6366f1); color: #fff; }

/* ── Floating Genome button ── */
/* ── Shared FAB label (hidden until hover) ── */
.fab-label {
  max-width: 0;
  overflow: hidden;
  white-space: nowrap;
  opacity: 0;
  transition: max-width 0.25s ease, opacity 0.2s ease, margin-left 0.25s ease;
  margin-left: 0;
}

.draggable-fab { cursor: grab; }
.draggable-fab:active { cursor: grabbing; }

.igv-float-btn {
  position: fixed;
  bottom: 68px;
  right: 24px;
  z-index: 498;
  background: #34d399;
  color: #fff;
  border: none;
  border-radius: 999px;
  padding: 10px 14px;
  font-size: 0.82rem;
  font-weight: 600;
  cursor: pointer;
  box-shadow: 0 4px 14px rgba(52, 211, 153, 0.4);
  display: flex;
  align-items: center;
  gap: 0;
  transition: transform 0.15s, box-shadow 0.15s, background 0.15s, padding 0.25s ease;
  font-family: inherit;
}
.igv-float-btn:hover {
  background: #10b981;
  transform: translateY(-2px);
  box-shadow: 0 6px 18px rgba(52, 211, 153, 0.5);
  padding: 10px 18px 10px 14px;
}
.igv-float-btn:hover .fab-label {
  max-width: 80px;
  opacity: 1;
  margin-left: 7px;
}

/* BAM selector in IGV panel header */
.igv-bam-selector {
  position: relative;
}
.igv-bam-toggle {
  background: var(--accent-light, #6366f1);
  color: #fff;
  border: 1px solid transparent;
  border-radius: 6px;
  padding: 3px 10px;
  font-size: 0.75rem;
  font-weight: 500;
  cursor: pointer;
  font-family: inherit;
  white-space: nowrap;
}
.igv-bam-toggle:hover {
  opacity: 0.85;
}
.igv-bam-dropdown {
  position: absolute;
  top: calc(100% + 6px);
  left: 0;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 8px;
  padding: 6px 0;
  min-width: 180px;
  box-shadow: 0 8px 24px rgba(0,0,0,0.18);
  z-index: 510;
}
.igv-bam-option {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 5px 12px;
  font-size: 0.78rem;
  color: var(--text);
  cursor: pointer;
  white-space: nowrap;
}
.igv-bam-option:hover {
  background: var(--surface-2);
}
.igv-bam-option input[type="checkbox"] {
  accent-color: #34d399;
  margin: 0;
}
.igv-bam-empty {
  padding: 5px 12px;
  font-size: 0.75rem;
  color: var(--text-muted);
}
.igv-bam-option--unavailable {
  color: var(--text-muted);
  cursor: not-allowed;
  opacity: 0.6;
}
.igv-bam-option--unavailable:hover {
  background: none;
}
.igv-bam-unavailable-note {
  font-style: italic;
  font-size: 0.72rem;
}

/* Annotation track selector */
.igv-annotation-selector { position: relative; }
.igv-annotation-toggle {
  background: var(--accent-light, #6366f1);
  color: #fff;
  border: 1px solid transparent;
  border-radius: 6px;
  padding: 3px 10px;
  font-size: 0.75rem;
  font-weight: 500;
  cursor: pointer;
  font-family: inherit;
  white-space: nowrap;
}
.igv-annotation-toggle:hover { opacity: 0.85; }
.igv-annotation-dropdown {
  position: absolute;
  top: calc(100% + 6px);
  left: 0;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 8px;
  padding: 6px 0;
  min-width: 180px;
  box-shadow: 0 8px 24px rgba(0,0,0,0.18);
  z-index: 510;
}
/* BLAT sequence popover — wider, right-aligned (it sits near the header's right
   edge), with a textarea + submit button. */
.igv-blat-dropdown {
  left: auto;
  right: 0;
  padding: 10px;
  min-width: 320px;
  display: flex;
  flex-direction: column;
  gap: 8px;
}
.igv-blat-input {
  width: 100%;
  box-sizing: border-box;
  resize: vertical;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 0.72rem;
  line-height: 1.4;
  padding: 6px 8px;
  color: var(--text);
  background: var(--bg-muted, var(--surface));
  border: 1px solid var(--border);
  border-radius: 6px;
}
.igv-blat-go { align-self: flex-end; }
.igv-annotation-option {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 5px 12px;
  font-size: 0.78rem;
  color: var(--text);
  cursor: pointer;
  white-space: nowrap;
}
.igv-annotation-option:hover { background: var(--surface-2); }
.igv-annotation-option input[type="radio"] {
  accent-color: #a78bfa;
  margin: 0;
}

/* Protein bar amino acid styling */
.aa-residue { color: #fbbf24; font-weight: 600; }
.aa-stop { color: #f87171; font-weight: 800; }
[data-theme="dark"] .aa-residue { color: #fbbf24; }
[data-theme="dark"] .aa-stop { color: #f87171; }



.sp-insights-runner {
  padding-bottom: 16px;
  border-bottom: 1px solid var(--border);
  margin-bottom: 16px;
}

.sp-insights-prompt {
  color: var(--text-muted);
  font-size: 0.875rem;
  padding: 32px 0;
  text-align: center;
}

.sp-loading {
  font-size: 0.84rem;
  color: var(--text-subtle);
  padding: 24px 0;
}

.sp-error {
  font-size: 0.84rem;
  color: #f87171;
  padding: 12px 0;
}

/* Traits / Health Risks indeterminate loading bar */
.sp-insights-bar {
  height: 3px;
  background: var(--border);
  border-radius: 2px;
  overflow: hidden;
  margin-bottom: 24px;
  position: relative;
}
.sp-insights-bar::after {
  content: '';
  position: absolute;
  top: 0; left: 0;
  height: 100%;
  width: 35%;
  background: var(--gradient);
  border-radius: 2px;
  animation: sp-bar-scan 1.5s ease-in-out infinite;
}
@keyframes sp-bar-scan {
  0%   { transform: translateX(-100%); }
  100% { transform: translateX(390%); }
}

/* Shared indeterminate loading bar — used in every per-tab loading
   placeholder across the site. Same scan animation as .sp-insights-bar. */
.momix-loading-bar {
  height: 3px;
  background: var(--border);
  border-radius: 2px;
  overflow: hidden;
  margin: 16px 0;
  position: relative;
}
.momix-loading-bar::after {
  content: '';
  position: absolute;
  top: 0; left: 0;
  height: 100%;
  width: 35%;
  background: var(--gradient);
  border-radius: 2px;
  animation: sp-bar-scan 1.5s ease-in-out infinite;
}
.sp-insights-error {
  font-size: 0.84rem;
  color: #f87171;
  padding: 0 0 16px;
}

/* File table */
.sp-table-wrap {
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
}

/* Somatic-CNV purity/ploidy panel: a compact latest-run scalar table shown
   under the card status row (called value vs reference + abs error). */
.benchmark-scalar-panel {
  margin: 10px 0 4px;
  padding: 10px 12px;
  border: 1px solid var(--border);
  border-radius: 8px;
  background: var(--surface-2, rgba(127, 127, 127, 0.05));
}
.benchmark-scalar-title {
  font-size: 0.72rem;
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--text-muted, #888);
  margin-bottom: 6px;
}
.benchmark-scalar-table { font-variant-numeric: tabular-nums; }
.benchmark-scalar-watch {
  font-size: 0.68rem;
  color: var(--text-muted, #888);
  border: 1px solid var(--border);
  border-radius: 4px;
  padding: 0 5px;
  margin-left: 4px;
  vertical-align: middle;
}

.sp-table {
  width: 100%;
  border-collapse: collapse;
  font-size: 0.82rem;
}

.sp-table th {
  text-align: left;
  padding: 8px 12px 8px 0;
  font-size: 0.65rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--text-subtle);
  border-bottom: 2px solid var(--border);
  white-space: nowrap;
}

.sp-table th:first-child,
.sp-table td:first-child { padding-left: 16px; }

.sp-table th.sp-th-sortable {
  cursor: pointer;
  user-select: none;
}
.sp-table th.sp-th-sortable:hover { color: var(--text); }
.sp-table th.sp-th-sorted { color: var(--text); }

.sp-table td {
  padding: 9px 12px 9px 0;
  border-bottom: 1px solid var(--border);
  color: var(--text);
  vertical-align: middle;
  white-space: nowrap;
}

.sp-table tr:last-child td { border-bottom: none; }
.sp-table tr:hover td { background: var(--surface-2); }

.sp-file-name {
  font-family: "SFMono-Regular", Consolas, monospace;
  font-size: 0.8rem;
  color: var(--text);
}

.sp-type-badge {
  display: inline-block;
  padding: 2px 8px;
  border-radius: 999px;
  font-size: 0.66rem;
  font-weight: 700;
  letter-spacing: 0.04em;
  border: 1px solid;
}
.sp-type-bam      { color: #60a5fa; border-color: #60a5fa44; background: #60a5fa0e; }
.sp-type-bai      { color: #818cf8; border-color: #818cf844; background: #818cf80e; }
.sp-type-fastq    { color: #34d399; border-color: #34d39944; background: #34d3990e; }
.sp-type-vcf      { color: #f472b6; border-color: #f472b644; background: #f472b60e; }
.sp-type-coverage { color: #fbbf24; border-color: #fbbf2444; background: #fbbf240e; }

.sp-uri-cell {
  display: flex;
  align-items: center;
  gap: 6px;
  max-width: 520px;
}

.sp-uri {
  font-family: "SFMono-Regular", Consolas, monospace;
  font-size: 0.75rem;
  color: var(--text-muted);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  flex: 1;
  min-width: 0;
}

.sp-copy-btn {
  background: none;
  border: 1px solid var(--border);
  border-radius: 5px;
  color: var(--text-subtle);
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 3px 5px;
  flex-shrink: 0;
  transition: border-color 0.15s, color 0.15s, background 0.15s;
  line-height: 0;
}
.sp-copy-btn:hover { border-color: var(--accent-light); color: var(--accent-light); background: #7c3aed10; }
.sp-copy-btn.copied { border-color: #34d399; color: #34d399; }

.sp-run-selector-row { display: flex; align-items: center; gap: 10px; padding: 12px 0 8px; }
.sp-run-selector-label { font-size: 0.78rem; color: var(--text-muted); white-space: nowrap; }
.sp-run-select { min-width: 320px; }

/* Files tab: the run picker and the Type / Specimen narrowings are three
   label + select groups in one row. The gap BETWEEN groups is wider than the
   gap inside one, so each label reads with its own control; the groups wrap
   on a narrow window rather than push the table past it. Scoped to this tab
   because the row class is shared by every other run picker on the page. */
#spTabFiles .sp-run-selector-row { gap: 22px; flex-wrap: wrap; }
.sp-files-filter { display: inline-flex; align-items: center; gap: 10px; }
.sp-files-filter-select { min-width: 150px; }
/* One offered value is a fact about the scope, not a choice: the control goes
   quiet (and says why on hover) instead of offering a pick that changes nothing. */
.sp-files-filter-select:disabled { opacity: 0.55; cursor: default; }

/* S3 URI hover tooltip ─────────────────────────────────────────────────────
   Add  data-s3uri="s3://..."  to any element to get a full-URI tooltip on
   hover.  No JS required — works everywhere the attribute is present.       */
[data-s3uri] { position: relative; }
[data-s3uri]:hover::after {
  content: attr(data-s3uri);
  position: absolute;
  bottom: calc(100% + 6px);
  left: 0;
  background: var(--surface);
  border: 1px solid var(--border-glow);
  border-radius: 8px;
  padding: 6px 10px;
  font-size: 0.72rem;
  font-family: "SFMono-Regular", Consolas, monospace;
  color: var(--text-muted);
  white-space: normal;
  word-break: break-all;
  max-width: 480px;
  z-index: 200;
  box-shadow: 0 4px 20px rgba(0, 0, 0, 0.35);
  pointer-events: none;
}

/* ── Runs tab ── */
.sp-runs-toolbar {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 12px 0 10px;
  min-height: 44px;
}

.sp-refresh-btn {
  background: none;
  border: 1px solid var(--border);
  border-radius: 6px;
  color: var(--text-muted);
  cursor: pointer;
  font-size: 0.78rem;
  padding: 4px 10px;
  flex-shrink: 0;
}
.sp-refresh-btn:hover { border-color: var(--accent-light); color: var(--accent-light); }
.sp-refresh-btn:disabled { cursor: default; opacity: 0.7; }
.sp-refresh-btn:disabled:hover { border-color: var(--border); color: var(--text-muted); }

.sp-refresh-icon {
  display: inline-block;
  line-height: 1;
  transform-origin: 50% 50%;
}
.sp-refresh-btn.is-loading .sp-refresh-icon {
  animation: sp-refresh-spin 0.85s linear infinite;
}
@keyframes sp-refresh-spin {
  to { transform: rotate(360deg); }
}

.sp-run-id {
  font-family: "SFMono-Regular", Consolas, monospace;
  font-size: 0.72rem;
  color: var(--text-muted);
  max-width: 240px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  display: inline-block;
  vertical-align: middle;
}

.sp-run-name {
  font-size: 0.78rem;
  color: var(--text-muted);
  max-width: 280px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* The Runs tab's Name cell: the row's identity, so full text colour and the
   width the removed Run ID column freed. Ellipsis past that; the hover title
   carries the whole name and the run_id. */
.sp-run-label {
  font-size: 0.82rem;
  color: var(--text);
  max-width: 520px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  display: inline-block;
  vertical-align: middle;
}

/* Run status badges */
.sp-run-status {
  display: inline-block;
  padding: 2px 8px;
  border-radius: 999px;
  font-size: 0.66rem;
  font-weight: 700;
  letter-spacing: 0.04em;
  border: 1px solid;
  white-space: nowrap;
}
.sp-run-status-started,
.sp-run-status-submitted  { color: #60a5fa; border-color: #60a5fa44; background: #60a5fa0e; }
.sp-run-status-pending,
.sp-run-status-runnable   { color: #94a3b8; border-color: #94a3b844; background: #94a3b80e; }
.sp-run-status-starting,
.sp-run-status-running    { color: #fbbf24; border-color: #fbbf2444; background: #fbbf240e; }
.sp-run-status-succeeded  { color: #34d399; border-color: #34d39944; background: #34d3990e; }
.sp-run-status-failed     { color: #f87171; border-color: #f8717144; background: #f871710e; }

/* Spot-reclaim retry indicator. Rendered inline after the status pill when
   a step has experienced >=1 Spot reclamation. Muted amber — surfaces the
   wear-and-tear without competing with the canonical status colour. */
.sp-retry-badge {
  display: inline-block;
  margin-left: 6px;
  padding: 1px 6px;
  border-radius: 999px;
  font-size: 0.62rem;
  font-weight: 600;
  letter-spacing: 0.02em;
  color: #d97706;
  border: 1px solid #d9770644;
  background: #d9770610;
  white-space: nowrap;
}

/* Run type badges */
.sp-run-type {
  display: inline-block;
  padding: 2px 7px;
  border-radius: 4px;
  font-size: 0.65rem;
  font-weight: 600;
  letter-spacing: 0.03em;
  border: 1px solid;
}
.sp-run-type-pipeline { color: #a78bfa; border-color: #a78bfa44; background: #a78bfa0e; }
.sp-run-type-batch    { color: #67e8f9; border-color: #67e8f944; background: #67e8f90e; }

.sp-run-experiment {
  display: inline-block;
  padding: 1px 7px;
  border-radius: 4px;
  font-size: 0.65rem;
  font-weight: 700;
  letter-spacing: 0.04em;
  border: 1px solid;
}
.sp-run-experiment-wes     { color: #34d399; border-color: #34d39944; background: #34d3990e; }
.sp-run-experiment-wgs     { color: #f59e0b; border-color: #f59e0b44; background: #f59e0b0e; }
.sp-run-experiment-rna-seq { color: #a78bfa; border-color: #a78bfa44; background: #a78bfa0e; }
.sp-run-experiment-scrna   { color: #f472b6; border-color: #f472b644; background: #f472b60e; }
.sp-run-experiment-scrna-seq { color: #f472b6; border-color: #f472b644; background: #f472b60e; }
.sp-run-experiment-iso-seq { color: #22d3ee; border-color: #22d3ee44; background: #22d3ee0e; }
.sp-run-experiment-lp-wgs  { color: #60a5fa; border-color: #60a5fa44; background: #60a5fa0e; }
.sp-run-experiment-hifi-wgs { color: #fb923c; border-color: #fb923c44; background: #fb923c0e; }
.sp-run-experiment-ont-wgs { color: #a3e635; border-color: #a3e63544; background: #a3e6350e; }

.sp-date {
  white-space: nowrap;
  font-size: 0.75rem;
  color: var(--text-subtle);
}

/* View sample link in import modal result */
.cr-view-link {
  display: inline-block;
  margin-top: 6px;
  font-size: 0.78rem;
  font-weight: 600;
  background: var(--gradient);
  -webkit-background-clip: text;
  -webkit-text-fill-color: transparent;
  background-clip: text;
  text-decoration: none;
}
.cr-view-link:hover { opacity: 0.8; }

/* ── FASTQ sub-row (Reads + End selectors) ── */
.is-file-fastq-opts {
  display: flex;
  align-items: flex-end;
  gap: 8px;
}
.is-file-fastq-opts .pipeline-field {
  flex: 1;
  min-width: 0;
}

/* ── Delete Sample button ── */
.sp-delete-btn {
  background: transparent;
  border: 1px solid #ef444455;
  border-radius: 8px;
  color: #f87171;
  font-family: 'Open Sans', sans-serif;
  font-size: 0.78rem;
  font-weight: 600;
  padding: 6px 14px;
  cursor: pointer;
  white-space: nowrap;
  transition: background 0.15s, border-color 0.15s, transform 0.1s;
}
.sp-delete-btn:hover {
  background: #ef44441a;
  border-color: #ef4444aa;
  transform: translateY(-1px);
}
.sp-delete-btn:active { transform: translateY(0); }

/* ── Delete Sample modal ── */
#deleteSampleBackdrop { display:none; position:fixed; inset:0; background:#00000088; z-index:200; align-items:center; justify-content:center; }
#deleteSampleBackdrop.open { display:flex; }
#deleteSampleModal {
  background: var(--surface);
  border: 1px solid var(--border-glow);
  border-radius: 14px;
  width: 440px;
  max-width: calc(100vw - 32px);
  box-shadow: 0 8px 40px #00000060;
}
.ds-warning {
  margin: 4px 0 6px;
  color: var(--text-muted);
  font-size: 0.85rem;
}
.ds-sample-id {
  display: block;
  font-size: 0.82rem;
  color: var(--text);
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: 6px;
  padding: 4px 10px;
  word-break: break-all;
  margin-bottom: 10px;
}
.ds-warning-sub {
  font-size: 0.78rem;
  color: var(--text-subtle);
  margin: 0 0 14px;
  line-height: 1.5;
}
.ds-confirm-btn {
  background: #ef4444 !important;
  background-image: none !important;
}
.ds-confirm-btn:hover {
  background: #dc2626 !important;
  box-shadow: 0 2px 12px #ef444440;
}

/* ── Delete Run modal ── */
#deleteRunBackdrop { display:none; position:fixed; inset:0; background:#00000088; z-index:200; align-items:center; justify-content:center; }
#deleteRunBackdrop.open { display:flex; }
#deleteRunModal {
  background: var(--surface);
  border: 1px solid var(--border-glow);
  border-radius: 14px;
  width: 440px;
  max-width: calc(100vw - 32px);
  box-shadow: 0 8px 40px #00000060;
}

/* ── Delete-result S3 notes (Delete Run / Sample / Samples modals) ──
   Two lists under the tally rows: objects kept ON PURPOSE (shared inputs,
   borrowed artifacts, bytes another run still references — informational)
   and objects that genuinely could not be deleted (error). Rendered by
   renderDeleteS3Notes() in js/shared.js. */
.cr-s3-notes-block { margin-top: 10px; }
.cr-s3-notes-label {
  font-size: 0.68rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--text-subtle);
  margin: 8px 0 4px;
}
.cr-s3-notes-label-error { color: #f87171; }
.cr-s3-notes {
  list-style: none;
  margin: 0;
  padding: 0;
  max-height: 160px;
  overflow-y: auto;
}
.cr-s3-note {
  display: flex;
  align-items: baseline;
  gap: 8px;
  font-size: 0.74rem;
  line-height: 1.45;
  padding: 2px 0;
}
.cr-s3-note code {
  font-family: "SFMono-Regular", Consolas, monospace;
  font-size: 0.72rem;
  color: var(--text);
  word-break: break-all;
  flex-shrink: 0;
  max-width: 55%;
}
.cr-s3-note-reason { color: var(--text-muted); }
.cr-s3-error .cr-s3-note-reason { color: #f87171; }
#drStatus.error, #dsStatus.error { color: #f87171; font-size: 0.78rem; font-weight: 500; }
#drStatus.loading, #dsStatus.loading { color: #fbbf24; font-size: 0.78rem; font-weight: 500; }

/* ── Delete Workflow modal (same shell as Delete Run) ── */
#deleteWorkflowBackdrop { display:none; position:fixed; inset:0; background:#00000088; z-index:200; align-items:center; justify-content:center; }
#deleteWorkflowBackdrop.open { display:flex; }
#deleteWorkflowModal {
  background: var(--surface);
  border: 1px solid var(--border-glow);
  border-radius: 14px;
  width: 440px;
  max-width: calc(100vw - 32px);
  box-shadow: 0 8px 40px #00000060;
}

/* ── VEP Variants tab ── */
.vep-pill-row {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  padding: 12px 24px 16px;
}
.vep-pill {
  display: inline-flex;
  align-items: center;
  padding: 5px 14px;
  border-radius: 999px;
  font-size: 0.82rem;
  font-weight: 600;
  letter-spacing: 0.01em;
  cursor: pointer;
  user-select: none;
  transition: opacity 0.15s, box-shadow 0.15s;
}
.vep-pill:hover { opacity: 0.8; }
.vep-pill--active { box-shadow: 0 0 0 2px currentColor; }
.vep-pill--inactive { opacity: 0.3; }
.vep-pill-actionable {
  background: #dcfce7;
  color: #166534;
  border: 1px solid #bbf7d0;
}
[data-theme="dark"] .vep-pill-actionable {
  background: #14532d;
  color: #86efac;
  border-color: #166534;
}
.vep-pill-path { background: #fce7e7; color: #b91c1c; }
[data-theme="dark"] .vep-pill-path { background: #450a0a; color: #fca5a5; }
.vep-pill-vus  { background: #fef3c7; color: #92400e; }
[data-theme="dark"] .vep-pill-vus  { background: #451a03; color: #fcd34d; }
.vep-pill-high { background: #ffe4e6; color: #9f1239; }
[data-theme="dark"] .vep-pill-high { background: #4c0519; color: #fda4af; }
.vep-pill-miss { background: #e0f2fe; color: #075985; }
[data-theme="dark"] .vep-pill-miss { background: #082f49; color: #7dd3fc; }

.vep-pill-info-wrap {
  position: relative;
  display: inline-flex;
  align-items: center;
  align-self: center;
}
.vep-pill-info-btn {
  background: none;
  border: none;
  cursor: pointer;
  color: var(--text-muted);
  font-size: 0.95rem;
  line-height: 1;
  padding: 2px 4px;
  border-radius: 50%;
  font-family: inherit;
  transition: color 0.15s;
}
.vep-pill-info-btn:hover { color: var(--accent-light); }
.vep-pill-info-popover {
  display: none;
  position: absolute;
  top: calc(100% + 8px);
  left: 50%;
  transform: translateX(-50%);
  width: 280px;
  background: var(--surface-2);
  border: 1px solid var(--border-glow);
  border-radius: 8px;
  padding: 10px 13px;
  font-size: 0.78rem;
  line-height: 1.5;
  color: var(--text-muted);
  box-shadow: 0 4px 16px rgba(0,0,0,0.3);
  z-index: 200;
  pointer-events: none;
}
.vep-pill-info-popover--open { display: block; }

.vep-transcript-note {
  padding: 8px 24px 0;
  font-size: 0.78rem;
  color: var(--text-subtle);
  margin: 0;
}

.vep-section {
  margin: 16px 24px 0;
}
.vep-section-label {
  font-size: 0.78rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--text-subtle);
  margin-bottom: 8px;
}
.vep-clinical-ok {
  padding: 12px 16px;
  background: #f0fdf4;
  border: 1px solid #bbf7d0;
  border-radius: 8px;
  color: #166534;
  font-size: 0.88rem;
  font-weight: 500;
}
[data-theme="dark"] .vep-clinical-ok {
  background: #052e16;
  border-color: #166534;
  color: #4ade80;
}
.vep-clinical-alert {
  padding: 12px 16px;
  background: #fef2f2;
  border: 1px solid #fecaca;
  border-radius: 8px;
  color: #991b1b;
  font-size: 0.88rem;
  font-weight: 600;
  margin-bottom: 12px;
}
[data-theme="dark"] .vep-clinical-alert {
  background: #450a0a;
  border-color: #991b1b;
  color: #fca5a5;
}

.vep-clinical-warning {
  padding: 12px 16px;
  background: #fffbeb;
  border: 1px solid #fde68a;
  border-radius: 8px;
  color: #92400e;
  font-size: 0.88rem;
  font-weight: 600;
  margin: 0 0 12px;
}
[data-theme="dark"] .vep-clinical-warning {
  background: #422006;
  border-color: #b45309;
  color: #fcd34d;
}

.rna-panel-empty {
  padding: 16px 24px;
  font-style: italic;
  color: var(--text-secondary, #6b7280);
  font-size: 0.88rem;
}

/* GTEx overlay banner: names the healthy-tissue distribution each gene's TPM is
   compared against. Sits between the subsection header and the pill row. */
.rna-gtex-banner,
.rna-gtex-hint {
  margin: 2px 12px 8px;
  padding: 6px 10px;
  border-radius: 6px;
  font-size: 0.8rem;
  line-height: 1.3;
}
.rna-gtex-banner {
  color: var(--text-secondary, #4b5563);
  background: color-mix(in srgb, var(--accent, #7c3aed) 8%, transparent);
  border: 1px solid color-mix(in srgb, var(--accent, #7c3aed) 22%, transparent);
  cursor: help;
}
.rna-gtex-hint {
  color: var(--text-secondary, #6b7280);
  background: var(--surface-2, rgba(148, 163, 184, 0.10));
  border: 1px dashed var(--border, rgba(148, 163, 184, 0.35));
  font-style: italic;
}

/* GTEx expression volcano (genome-wide up/down vs healthy GTEx). */
/* Volcano baseline picker. Sits above the banner because it CHANGES what the
   banner says — the reading order is "compare against X" then "here is X". */
.rna-volcano-tissue-row {
  display: flex;
  align-items: center;
  gap: 8px;
  margin: 8px 0 6px;
  flex-wrap: wrap;
}
.rna-volcano-tissue-label {
  font-size: 0.78rem;
  color: var(--text-muted);
}
.rna-volcano-tissue-select {
  background: var(--input-bg);
  border: 1px solid var(--border-glow);
  border-radius: 6px;
  color: var(--text);
  font-family: 'Open Sans', sans-serif;
  font-size: 0.8rem;
  padding: 0 8px;
  height: 28px;
  max-width: 320px;
  cursor: pointer;
  appearance: auto;
}
.rna-volcano-tissue-select:focus {
  outline: none;
  border-color: var(--accent);
  box-shadow: 0 0 0 3px #7c3aed20;
}
/* An exploratory comparison must not look like the sample's own baseline.
   Amber rather than red: it is a caveat about interpretation, not an error. */
.rna-gtex-banner.is-override {
  border-color: #f59e0b;
  color: #f59e0b;
}
/* Strandedness verdict, above the RNA QC metrics table. The 24px horizontal
   inset is .gv-overview-wrap's — the pill has to share the table's left edge,
   so keep the two in step if that margin ever moves. */
.rna-strand-pill-row {
  display: flex;
  align-items: center;
  gap: 10px;
  margin: 0 24px 10px;
  flex-wrap: wrap;
}
.rna-volcano-sub {
  margin: 0 12px 6px;
  font-size: 0.82rem;
  color: var(--text-secondary, #6b7280);
  line-height: 1.35;
}
.rna-volcano-summary {
  margin: 0 12px 8px;
  font-size: 0.82rem;
  font-weight: 600;
  color: var(--text-primary, #374151);
  /* Carries the method tooltip that used to hang off the plain banner. */
  cursor: help;
}
/* Summary + significance-metric toggle share one row. */
.rna-volcano-controls {
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: 8px 16px;
  margin: 0 12px 8px;
}
.rna-volcano-controls .rna-volcano-summary { margin: 0; }
.rna-volcano-note {
  margin: 0 12px 8px;
  font-size: 0.78rem;
  font-style: italic;
  line-height: 1.35;
  color: var(--text-secondary, #6b7280);
}
.rna-volcano-chart { padding: 0 8px 12px; }
.rna-volcano-dist-title {
  margin: 4px 12px 0;
  font-size: 0.82rem;
  font-weight: 600;
  color: var(--text-primary, #374151);
}
.rna-volcano-dist-chart { padding: 0 8px 12px; }
/* Histogram + scatter side by side (each ~half width). Wraps to stacked on
   narrow viewports. */
.rna-volcano-lower {
  display: flex;
  flex-wrap: wrap;
  gap: 4px 12px;
}
.rna-volcano-lower-col { flex: 1 1 340px; min-width: 0; }
.rna-volcano-scatter-chart { padding: 0 8px 12px; }

/* Combined gene-panel chart sits flush in the subsection — extra inset matches
   the pill row above so the chart reads as part of the same control surface. */
.rna-panel-combined-chart { padding: 0 12px 12px; }

/* Pill row reuses .vep-pill styling from Disease Risk; this just nudges the
   layout so the citation icon inside each pill clears the label baseline. */
.rna-panel-pill {
  display: inline-flex;
  align-items: center;
  gap: 4px;
}
.rna-panel-pill-cite {
  font-size: 0.72rem;
  font-weight: 700;
  color: inherit;
  opacity: 0.7;
  text-decoration: none;
  margin-left: 2px;
  line-height: 1;
}
.rna-panel-pill-cite:hover { opacity: 1; }

/* RNA gene-search bar — sits between the curated-panel pill row and the
   combined chart inside the Gene-Panel Expression subsection. */
.rna-gene-search-row {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
  padding: 4px 24px 8px;
}
.rna-gene-search-label {
  font-size: 0.85rem;
  color: var(--text-muted);
  margin-right: 4px;
}
.rna-gene-search-input {
  flex: 0 1 220px;
  padding: 6px 10px;
  font-size: 0.9rem;
  border: 1px solid var(--border);
  border-radius: 6px;
  background: var(--bg);
  color: var(--text);
  font-family: inherit;
}
.rna-gene-search-input:focus {
  outline: none;
  border-color: var(--accent);
  box-shadow: 0 0 0 2px var(--accent-light);
}
.rna-gene-search-button {
  padding: 6px 14px;
  font-size: 0.85rem;
  font-weight: 600;
  border: 1px solid var(--accent);
  border-radius: 6px;
  background: var(--accent);
  color: #fff;
  cursor: pointer;
}
.rna-gene-search-button:hover { opacity: 0.9; }
.rna-gene-search-status {
  font-size: 0.82rem;
  color: var(--text-muted);
  flex-basis: 100%;
  min-height: 1em;
}
.rna-gene-search-status[data-kind="error"] { color: #b91c1c; }
[data-theme="dark"] .rna-gene-search-status[data-kind="error"] { color: #fca5a5; }
.rna-gene-search-status[data-kind="warn"]  { color: #92400e; }
[data-theme="dark"] .rna-gene-search-status[data-kind="warn"]  { color: #fcd34d; }

.rna-gene-search-chips {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  padding: 0 24px 8px;
}
.rna-gene-search-chips:empty { padding: 0; }
.rna-gene-search-chip {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 3px 4px 3px 10px;
  border-radius: 999px;
  font-size: 0.78rem;
  font-weight: 600;
  background: #6b728018;
  color: #374151;
  border: 1px solid #6b728055;
}
[data-theme="dark"] .rna-gene-search-chip {
  background: #6b728022;
  color: #d1d5db;
  border-color: #6b728055;
}
.rna-gene-search-chip-x {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 18px;
  height: 18px;
  border-radius: 50%;
  border: none;
  background: transparent;
  color: inherit;
  font-size: 1rem;
  line-height: 1;
  cursor: pointer;
  opacity: 0.6;
}
.rna-gene-search-chip-x:hover { opacity: 1; background: rgba(0,0,0,0.08); }
[data-theme="dark"] .rna-gene-search-chip-x:hover { background: rgba(255,255,255,0.12); }

.vep-accordion {
  margin: 12px 24px 0;
  border: 1px solid var(--border);
  border-radius: 8px;
  overflow: hidden;
}
.vep-accordion-summary {
  padding: 12px 16px;
  font-size: 0.9rem;
  font-weight: 600;
  cursor: pointer;
  user-select: none;
  background: var(--surface-2);
  list-style: none;
  display: flex;
  align-items: center;
  gap: 8px;
}
.vep-accordion-summary::-webkit-details-marker { display: none; }
.vep-accordion[open] .vep-accordion-summary::before { content: "▾ "; }
.vep-accordion:not([open]) .vep-accordion-summary::before { content: "▸ "; }
.vep-count {
  font-weight: 400;
  color: var(--text-subtle);
  font-size: 0.85rem;
}
.vep-accordion-body {
  padding: 0 0 12px;
}

.vep-table-wrap {
  overflow-x: auto;
}
.vep-table {
  font-size: 0.8rem;
  min-width: 900px;
}
.vep-table th, .vep-table td {
  white-space: nowrap;
}
.vep-csq {
  font-size: 0.75rem;
  color: var(--text-subtle);
}
.disease-blurb {
  margin-top: 3px;
  line-height: 1.4;
  max-width: 520px;
}
.disease-blurb--detail {
  margin: 8px 0 10px;
  max-width: none;
  padding: 8px 12px;
  background: var(--surface-2);
  border-radius: 6px;
}
.vep-pos {
  font-size: 0.78rem;
}
.vep-locus-btn {
  background: none;
  border: none;
  padding: 0;
  font: inherit;
  color: var(--accent);
  cursor: pointer;
  text-decoration: underline dotted;
  text-underline-offset: 2px;
}
.vep-locus-btn:hover { text-decoration: underline; }

#locus-tip {
  position: absolute;
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: 6px;
  padding: 6px 8px;
  box-shadow: 0 4px 16px rgba(0,0,0,.35);
  z-index: 9999;
  display: none;
}
#locus-tip input {
  background: var(--surface);
  border: 1px solid var(--border);
  color: var(--text);
  border-radius: 4px;
  padding: 4px 8px;
  font-family: "SFMono-Regular", Consolas, monospace;
  font-size: 0.82rem;
  min-width: 160px;
  cursor: text;
  outline: none;
}
#inh-tip {
  position: absolute;
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: 6px;
  padding: 6px 10px;
  box-shadow: 0 4px 16px rgba(0,0,0,.35);
  z-index: 9999;
  display: none;
  font-size: 0.82rem;
  color: var(--text);
  max-width: 320px;
  white-space: pre-line;
  pointer-events: none;
}
#col-tip {
  position: absolute;
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: 6px;
  padding: 6px 10px;
  box-shadow: 0 4px 16px rgba(0,0,0,.35);
  z-index: 9999;
  display: none;
  font-size: 0.82rem;
  color: var(--text);
  max-width: 360px;
  white-space: pre-line;
  pointer-events: none;
}
.vep-col-tip { cursor: help; }
.vep-af {
  font-family: monospace;
  font-size: 0.78rem;
  text-align: right;
}
.vep-empty {
  padding: 12px 16px;
  color: var(--text-subtle);
  font-size: 0.85rem;
}

.vep-sig-badge {
  display: inline-block;
  padding: 2px 7px;
  border-radius: 4px;
  font-size: 0.72rem;
  font-weight: 600;
  white-space: nowrap;
}
.vep-sig-path  { background: #fce7e7; color: #b91c1c; }
[data-theme="dark"] .vep-sig-path { background: #450a0a; color: #fca5a5; }
.vep-sig-vus   { background: #fef3c7; color: #92400e; }
[data-theme="dark"] .vep-sig-vus  { background: #451a03; color: #fcd34d; }
.vep-sig-other { background: var(--surface-2); color: var(--text-subtle); }

.vep-acmg-badge {
  display: inline-block;
  margin-left: 4px;
  padding: 1px 5px;
  border-radius: 4px;
  font-size: 0.68rem;
  font-weight: 700;
  background: #dbeafe;
  color: #1d4ed8;
  vertical-align: middle;
}
[data-theme="dark"] .vep-acmg-badge {
  background: #1e3a5f;
  color: #93c5fd;
}

.vep-actionable-badge {
  display: inline-block;
  margin-left: 4px;
  padding: 1px 6px;
  border-radius: 4px;
  font-size: 0.68rem;
  font-weight: 700;
  background: #dcfce7;
  color: #166534;
  white-space: nowrap;
}
[data-theme="dark"] .vep-actionable-badge {
  background: #14532d;
  color: #86efac;
}

.vep-clear-status {
  color: #16a34a;
  font-size: 0.8rem;
  font-weight: 600;
}
[data-theme="dark"] .vep-clear-status {
  color: #4ade80;
}

.vep-footnote {
  padding: 12px 24px;
  font-size: 0.76rem;
  color: var(--text-subtle);
  margin: 0;
}

/* ── VEP category section headers ── */
.vep-section-collapsible {
  margin: 20px 0 0;
}
/* Collapsible section headers read in TITLE CASE, not all-caps.
   The header strings are already authored in title case ("Quality Control",
   "Protein Lollipop (Per-Gene)"); the caps were purely `text-transform`. Two
   things go with dropping it: the letter-spacing, which exists to open up
   all-caps and reads stretched in mixed case, and a small size bump, because
   lowercase has a shorter x-height than caps and looks smaller at the same
   font-size. Applies to every page, not just the somatic dashboard. */
.vep-section-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin: 0 24px 8px;
  padding: 8px 14px;
  background: var(--surface-2);
  border-left: 3px solid var(--accent);
  border-radius: 0 6px 6px 0;
  font-size: 0.88rem;
  font-weight: 650;
  letter-spacing: 0;
  color: var(--text-muted);
  cursor: pointer;
  list-style: none;
  user-select: none;
}
.vep-section-header::-webkit-details-marker { display: none; }
.vep-section-header::after {
  content: "▲";
  font-size: 0.65rem;
  opacity: 0.5;
  transition: transform 0.2s;
}
.vep-section-collapsible:not([open]) .vep-section-header::after {
  transform: rotate(180deg);
}

/* ── Subsection headers (Coverage, Alignment inside Quality Control) ── */
.vep-subsection {
  margin: 12px 0 0;
}
.vep-subsection-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin: 0 24px 6px;
  padding: 6px 12px;
  background: color-mix(in srgb, var(--surface-2) 25%, transparent);
  border-left: 2px solid var(--border);
  border-radius: 0 4px 4px 0;
  font-size: 0.8rem;
  font-weight: 600;
  letter-spacing: 0;
  color: var(--text-subtle);
  cursor: pointer;
  list-style: none;
  user-select: none;
}
.vep-subsection-header::-webkit-details-marker { display: none; }
.vep-subsection-header::after {
  content: "▲";
  font-size: 0.55rem;
  opacity: 0.4;
  transition: transform 0.2s;
}
.vep-subsection:not([open]) .vep-subsection-header::after {
  transform: rotate(180deg);
}
.vep-subsection > .stat-cards {
  margin-left: 24px;
  margin-right: 24px;
}

/* ── VEP profile table (HR-style, gene rows with inline variant dropdowns) ── */
.vep-profile-table {
  margin: 0 24px;
  width: calc(100% - 48px);
  table-layout: fixed;
}
.vep-profile-gene-chip {
  display: inline-block;
  font-family: var(--font-mono, monospace);
  font-size: 0.68rem;
  font-weight: 700;
  padding: 1px 6px;
  border-radius: 4px;
  background: #e0f2fe;
  color: #075985;
  border: 1px solid #bae6fd;
  vertical-align: middle;
  margin-left: 5px;
}
[data-theme="dark"] .vep-profile-gene-chip {
  background: #0c2a3e;
  color: #7dd3fc;
  border-color: #164e63;
}
.vep-th-variants {
  width: 100px;
}
.vep-expand-cell {
  padding: 0 12px;
  white-space: nowrap;
}
.vep-profile-expand-btn {
  display: flex;
  align-items: center;
  justify-content: flex-start;
  gap: 5px;
  width: 100%;
  background: none;
  border: none;
  padding: 0;
  font-size: 0.62rem;
  font-weight: 700;
  letter-spacing: 0.06em;
  color: var(--text-subtle);
  cursor: pointer;
  white-space: nowrap;
  transition: opacity 0.15s;
}
.vep-profile-expand-btn:hover { opacity: 0.6; }
.vep-expand-chevron { flex-shrink: 0; }
.vep-expand-count   { flex-shrink: 0; }
.vep-profile-detail-td {
  padding: 0;
  border-bottom: 2px solid var(--border);
}
.vep-profile-detail-row .vep-table-wrap {
  margin: 0;
  border-radius: 0;
  border: none;
  border-top: 1px solid var(--border);
}

/* ── Phenotype Associations panel ── */
.pheno-cat-details > summary.pheno-cat-header {
  padding: 6px 14px;
  margin: 0 24px;
  font-size: 13px;
  font-weight: 600;
  color: var(--text-muted, #374151);
  letter-spacing: 0.02em;
  cursor: pointer;
  list-style: none;
  display: flex;
  align-items: center;
  gap: 4px;
}
.pheno-cat-header::-webkit-details-marker { display: none; }
.pheno-cat-header::before {
  content: "▼";
  font-size: 0.6rem;
  opacity: 0.45;
  transition: transform 0.2s;
  margin-right: 6px;
  flex-shrink: 0;
}
.pheno-cat-details:not([open]) > .pheno-cat-header::before {
  transform: rotate(-90deg);
}
.pheno-cat-icon { margin-right: 4px; }
.pheno-cat-count {
  margin-left: 8px;
  font-size: 11px;
  font-weight: 400;
  color: var(--text-secondary, #6b7280);
}
.pheno-table .hr-td-cell { white-space: nowrap; }
.pheno-table .hr-th-cond { padding-left: 14px; }
.pheno-table .hr-td-cond { padding-left: 14px; }
.pheno-effect-col { min-width: 80px; }

/* ── VEP category cards ── */
.vep-cat-card {
  margin: 8px 24px 0;
  border: 1px solid var(--border);
  border-left: 3px solid var(--accent-2);
  border-radius: 8px;
  overflow: hidden;
  transition: border-color 0.15s;
}
.vep-cat-card:hover {
  border-color: var(--border-glow);
  border-left-color: var(--accent);
}

.vep-cat-summary {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 10px 16px;
  cursor: pointer;
  user-select: none;
  background: var(--surface-2);
  list-style: none;
  font-size: 0.88rem;
  font-weight: 600;
}
.vep-cat-summary::-webkit-details-marker { display: none; }

.vep-cat-icon {
  font-size: 1rem;
  line-height: 1;
}
.vep-cat-label {
  flex: 1;
  color: var(--text);
}
.vep-cat-dots {
  display: flex;
  align-items: center;
  gap: 5px;
}
.vep-cat-toggle {
  font-size: 0.75rem;
  font-weight: 400;
  color: var(--text-subtle);
  white-space: nowrap;
  transition: color 0.15s;
}
.vep-cat-card[open] .vep-cat-toggle { color: var(--accent-light); }
.vep-cat-card[open] .vep-cat-toggle::after { content: ""; }

.vep-cat-body {
  padding: 0 0 10px;
}

/* ── Risk dot badges (summary line of category cards) ── */
.vep-rdot {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 20px;
  height: 20px;
  padding: 0 6px;
  border-radius: 999px;
  font-size: 0.72rem;
  font-weight: 700;
  color: #fff;
  white-space: nowrap;
}

/* ── Risk level badge (variant table Risk column) ── */
.vep-risk-cell {
  display: inline-flex;
  align-items: center;
  gap: 4px;
}
.vep-risk-badge {
  display: inline-block;
  padding: 2px 7px;
  border-radius: 4px;
  font-size: 0.72rem;
  font-weight: 600;
  white-space: nowrap;
}

.vep-empty-section {
  margin: 4px 24px 0;
  padding: 10px 16px;
}

/* ── Tag badges ── */

.tag-badge {
  display: inline-flex;
  align-items: center;
  gap: 1px;
  padding: 2px 7px;
  border-radius: 999px;
  font-size: 0.72rem;
  font-weight: 500;
  background: var(--gradient-btn);
  color: #fff;
  white-space: nowrap;
}

.tag-badge-sep {
  opacity: 0.65;
  margin: 0 1px;
}

.tag-badge-inherited {
  background: var(--surface);
  color: var(--text-muted);
  border: 1px solid var(--border);
}

.tag-badge-removable {
  padding-right: 3px;
}

.tag-badge-remove {
  background: none;
  border: none;
  color: inherit;
  cursor: pointer;
  font-size: 0.8rem;
  line-height: 1;
  margin-left: 3px;
  opacity: 0.7;
  padding: 0 2px;
}
.tag-badge-remove:hover { opacity: 1; }

.tag-inherited-from {
  font-size: 0.65rem;
  opacity: 0.6;
  margin-left: 2px;
}

/* ── Tag panel (in sample / run header) ── */

.tag-panel {
  display: flex;
  align-items: center;
  gap: 6px;
  flex-wrap: wrap;
  max-width: 480px;
}

.tag-panel-list {
  display: flex;
  flex-wrap: wrap;
  gap: 4px;
  align-items: center;
}

.tag-add-btn {
  background: none;
  border: 1px dashed var(--border);
  border-radius: 999px;
  color: var(--text-muted);
  cursor: pointer;
  font-family: 'Open Sans', sans-serif;
  font-size: 0.72rem;
  padding: 2px 8px;
  white-space: nowrap;
}
.tag-add-btn:hover { border-color: var(--accent-light); color: var(--accent-light); }

.tag-add-btn-inline {
  background: none;
  border: 1px dashed var(--border);
  border-radius: 50%;
  color: var(--text-muted);
  cursor: pointer;
  font-size: 0.7rem;
  line-height: 1;
  padding: 1px 4px;
  vertical-align: middle;
}
.tag-add-btn-inline:hover { border-color: var(--accent-light); color: var(--accent-light); }

/* ── Tag modal backdrop / box ── */

.tag-modal-backdrop {
  position: fixed;
  inset: 0;
  background: rgba(0,0,0,0.55);
  backdrop-filter: blur(3px);
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 2000;
}

.tag-modal {
  background: var(--surface);
  border: 1px solid var(--border-glow);
  border-radius: 12px;
  box-shadow: 0 8px 40px rgba(0,0,0,0.5);
  min-width: 380px;
  max-width: 520px;
  width: 100%;
}

/* ── Tag modal body ── */

.tag-modal .cr-body {
  display: flex;
  flex-direction: column;
  gap: 10px;
  padding: 16px 20px 20px;
}

.tag-add-row {
  display: flex;
  gap: 6px;
  align-items: center;
}

.tag-modal-badges {
  display: flex;
  flex-wrap: wrap;
  gap: 5px;
  min-height: 24px;
}

.tag-modal-status {
  font-size: 0.8rem;
  min-height: 18px;
}
.tag-modal-status.error   { color: #f87171; }
.tag-modal-status.loading { color: var(--text-muted); }
.tag-modal-status.ok      { color: #34d399; }

/* ── Tag cell in file tables ── */

.sp-tag-cell {
  white-space: nowrap;
}

.sp-tag-cell-inner {
  display: flex;
  flex-wrap: nowrap;
  gap: 3px;
  align-items: center;
}

/* ── Filter bar (multi-field search + chips) ── */

.filter-bar {
  background: var(--surface);
  border-bottom: 1px solid var(--border);
  padding: 10px 24px;
  display: flex;
  flex-direction: column;
  gap: 8px;
  position: relative;
  z-index: 200;
}

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

.filter-field-select {
  background: var(--input-bg);
  border: 1px solid var(--border);
  border-radius: 8px;
  color: var(--text);
  font-family: 'Open Sans', sans-serif;
  font-size: 0.82rem;
  padding: 7px 28px 7px 10px;
  height: 36px;
  cursor: pointer;
  outline: none;
  appearance: none;
  -webkit-appearance: none;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 24 24' fill='none' stroke='%238892c0' stroke-width='2'%3E%3Cpath d='M6 9l6 6 6-6'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 8px center;
  flex-shrink: 0;
  transition: border-color 0.15s, box-shadow 0.15s;
}
.filter-field-select:focus {
  border-color: var(--accent);
  box-shadow: 0 0 0 3px #7c3aed20;
}

.filter-value-wrap {
  display: flex;
  align-items: center;
  gap: 8px;
  background: var(--input-bg);
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: 0 12px;
  flex: 1;
  max-width: 460px;
  transition: border-color 0.15s, box-shadow 0.15s;
}
.filter-value-wrap:focus-within {
  border-color: var(--accent);
  box-shadow: 0 0 0 3px #7c3aed20;
}

.filter-tag-wrap {
  display: flex;
  align-items: center;
  gap: 6px;
  flex: 1;
  max-width: 460px;
}

.filter-tag-input {
  background: var(--input-bg);
  border: 1px solid var(--border);
  border-radius: 8px;
  color: var(--text);
  font-family: 'Open Sans', sans-serif;
  font-size: 0.84rem;
  padding: 7px 10px;
  outline: none;
  height: 36px;
  transition: border-color 0.15s, box-shadow 0.15s;
}
.filter-tag-input:focus {
  border-color: var(--accent);
  box-shadow: 0 0 0 3px #7c3aed20;
}
.filter-tag-input.filter-tag-key   { width: 150px; }
.filter-tag-input.filter-tag-val   { flex: 1; min-width: 140px; }
/* The key/value pickers are <select>s; keep the native disclosure arrow and a
   comfortable click target rather than the text-input padding. */
select.filter-tag-input { cursor: pointer; padding-right: 26px; }
select.filter-tag-input:disabled { cursor: default; opacity: 0.6; }

.filter-tag-eq {
  color: var(--text-subtle);
  font-size: 0.9rem;
  flex-shrink: 0;
}

.filter-add-btn {
  background: none;
  border: 1px solid var(--accent-light);
  border-radius: 8px;
  color: var(--accent-light);
  font-family: 'Open Sans', sans-serif;
  font-size: 0.8rem;
  font-weight: 600;
  padding: 6px 14px;
  height: 36px;
  cursor: pointer;
  white-space: nowrap;
  flex-shrink: 0;
  transition: background 0.15s, color 0.15s;
}
.filter-add-btn:hover {
  background: var(--accent-light);
  color: #fff;
}

.filter-chips {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  padding-top: 2px;
}

.filter-chip {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  background: var(--surface-2);
  border: 1px solid var(--border-glow);
  border-radius: 999px;
  font-size: 0.76rem;
  font-weight: 500;
  color: var(--text);
  padding: 3px 6px 3px 10px;
  white-space: nowrap;
}

.filter-chip-remove {
  background: none;
  border: none;
  color: var(--text-subtle);
  cursor: pointer;
  font-size: 0.85rem;
  line-height: 1;
  padding: 0 3px;
  border-radius: 50%;
  transition: color 0.15s, background 0.15s;
}
.filter-chip-remove:hover {
  color: #f87171;
  background: #f8717118;
}

/* Select element used inside .filter-value-wrap for enumerated fields */
.filter-value-select {
  flex: 1;
  background: none;
  border: none;
  outline: none;
  color: var(--text);
  font-family: 'Open Sans', sans-serif;
  font-size: 0.84rem;
  padding: 9px 0;
  cursor: pointer;
  appearance: none;
  -webkit-appearance: none;
}

/* ── Workflow DAG tab ── */
.dag-panel { padding: 0; }

.dag-cy {
  width: 100%;
  height: 520px;
  background: var(--surface);
  border-radius: 0 0 10px 10px;
  border: 1px solid var(--border);
  border-top: none;
  position: relative;
}

.dag-tooltip {
  position: fixed;
  z-index: 900;
  background: var(--surface-2);
  border: 1px solid var(--border-glow);
  border-radius: 8px;
  padding: 10px 13px;
  min-width: 160px;
  /* Sized BY ITS CONTENT, never by a guess. A fixed max-width is what clipped
     "Iso-Seq FASTQ" to "Iso-Seq FA…" and Coverage Analysis's outputs to
     nothing useful — a tooltip whose whole job is to name a step's files must
     not be the thing that hides their names. The cap is a safety rail against a
     pathological label and against narrow viewports, not a layout choice: real
     content tops out around 35 characters and never reaches it, and if it ever
     does the text WRAPS rather than being cut. */
  width: max-content;
  max-width: min(620px, calc(100vw - 24px));
  /* A panel taller than the viewport cannot be positioned into it, and the
     clamp in positionDagTooltip can only pin it to the top while the rest runs
     off the bottom. Scrolling is not truncation — the text is all still there. */
  max-height: calc(100vh - 24px);
  pointer-events: none;
  box-shadow: 0 4px 16px rgba(0,0,0,0.35);
  font-size: 0.78rem;
  line-height: 1.55;
}
/* A tooltip carrying a variant selector has to accept the pointer, and needs
   room for a row of experiment badges. Set per-show by _attachDagInteraction,
   because one tooltip element serves both the interactive pipeline graphs and
   the plain workflow ones. */
/* Only a panel the pointer can reach can be scrolled, so only that one gets a
   scrollbar; the others are short enough that the max-height never bites. */
.dag-tooltip.is-interactive { pointer-events: auto; overflow-y: auto; }

/* Variant selector. `alignment` is three aligners behind one step; the tabs
   name each by the experiments that select it, in the same badges the
   Experiments column uses, and only one variant's detail is shown at a time. */
.dag-tt-tabs {
  display: flex; flex-wrap: wrap; gap: 4px;
  margin: 6px 0 2px;
}
.dag-tt-tab {
  display: flex; flex-wrap: wrap; gap: 3px; align-items: center;
  padding: 3px 6px; cursor: pointer;
  background: none; border: 1px solid var(--border); border-radius: 5px;
  font-family: inherit; color: var(--text-muted);
  transition: border-color .1s, background .1s;
}
.dag-tt-tab[aria-selected="false"] { filter: grayscale(0.55) opacity(0.65); }
.dag-tt-tab[aria-selected="true"] {
  border-color: var(--text-subtle); background: var(--surface);
}
.dag-tt-tab:hover { border-color: var(--text-subtle); }
.dag-tt-tab .sp-run-experiment { pointer-events: none; }

.dag-tt-name {
  font-weight: 700;
  font-size: 0.84rem;
  color: var(--text);
  margin-bottom: 6px;
  border-bottom: 1px solid var(--border);
  padding-bottom: 5px;
}

.dag-tt-row {
  display: flex;
  justify-content: space-between;
  gap: 8px;
  font-size: 0.77rem;
}

.dag-tt-label { color: var(--text-subtle); flex-shrink: 0; }
.dag-tt-val   { color: var(--text); text-align: right; }
/* A value that is a set of experiment badges rather than a string: keep them on
   the value side, gapped, and wrapping instead of overflowing. */
.dag-tt-badges {
  display: flex; flex-wrap: wrap; gap: 3px; justify-content: flex-end;
}
.dag-tt-tool  { color: var(--text-muted); font-family: monospace; font-size: 0.75rem; }

.dag-tt-note  { color: var(--text-subtle); font-style: italic; justify-content: flex-start; }

.dag-tt-status           { font-weight: 700; font-size: 0.72rem; letter-spacing: 0.04em; }
.dag-tt-status-running   { color: #fde68a; }
.dag-tt-status-succeeded { color: #6ee7b7; }
.dag-tt-status-failed    { color: #fca5a5; }
.dag-tt-status-pending   { color: #c7d2fe; }

.dag-tt-divider {
  border-top: 1px solid var(--border);
  margin: 6px 0 5px;
}

.dag-tt-files {
  display: flex;
  gap: 4px 12px;
  /* Wraps rather than squeezing. Once an input lists the aliases it accepts the
     two columns can want more than the panel's cap between them, and shrinking
     them proportionally squeezed the narrow one below its own heading — the
     word "Outputs" was being clipped. Outputs now drops underneath instead. */
  flex-wrap: wrap;
}

.dag-tt-files-col {
  /* NOT `flex: 1` (= `1 1 0%`): that grows both columns equally from a zero
     basis, so a one-item Inputs column and a four-item Outputs column each get
     half the panel and the long one wraps while the short one sits empty.
     Basis `auto` sizes each to its own content. */
  flex: 0 1 auto;
  /* min-content, not 0: a column may shrink to its own content and no further.
     With 0 it could be squeezed to nothing while its text stayed put, which is
     clipping by another name. */
  min-width: min-content;
}

.dag-tt-files-head {
  font-size: 0.68rem;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--text-subtle);
  margin-bottom: 3px;
}

/* The aliases an input accepts, under its label. Monospace because they are
   identifiers the reader will type or grep, wrapping because one slot can
   accept twelve of them. */
.dag-tt-aliases {
  display: flex; flex-wrap: wrap; gap: 2px 4px;
  margin: 1px 0 3px;
}
.dag-tt-alias {
  font-family: "SFMono-Regular", Consolas, monospace;
  font-size: 0.64rem;
  color: var(--text-subtle);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 3px;
  padding: 0 4px;
}

.dag-tt-file-item {
  font-size: 0.75rem;
  color: var(--text-muted);
  /* No ellipsis. The panel grows to fit instead; overflow-wrap only comes into
     play at the max-width cap, where wrapping is the honest failure mode —
     a wrapped name is still readable, a truncated one is not. */
  overflow-wrap: anywhere;
  padding: 1px 0;
}

/* ====================================================================
   Agent Chat Panel
   ==================================================================== */

.chat-float-btn {
  position: fixed;
  bottom: 24px;
  right: 24px;
  z-index: 900;
  display: flex;
  align-items: center;
  gap: 0;
  padding: 10px 14px;
  border: none;
  border-radius: 999px;
  background: #f472b6;
  color: #fff;
  font-size: 0.82rem;
  font-weight: 600;
  cursor: pointer;
  box-shadow: 0 4px 14px rgba(244, 114, 182, 0.4);
  transition: transform 0.15s, box-shadow 0.15s, background 0.15s, padding 0.25s ease;
  font-family: inherit;
}
.chat-float-btn:hover {
  background: #ec4899;
  transform: translateY(-2px);
  box-shadow: 0 6px 18px rgba(244, 114, 182, 0.5);
  padding: 10px 18px 10px 14px;
}
.chat-float-btn:hover .fab-label {
  max-width: 80px;
  opacity: 1;
  margin-left: 7px;
}

.chat-panel {
  position: fixed;
  top: 0;
  right: 0;
  width: 560px;
  min-width: 360px;
  max-width: 80vw;
  height: 100vh;
  z-index: 1100;
  display: flex;
  flex-direction: column;
  background: var(--bg);
  border-left: 1px solid var(--border);
  box-shadow: -4px 0 24px rgba(0,0,0,0.25);
  transform: translateX(100%);
  transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1);
}
.chat-panel--open { transform: translateX(0); }
.chat-panel--resizing { transition: none; }

.chat-resize-handle {
  position: absolute;
  top: 0;
  left: -4px;
  width: 8px;
  height: 100%;
  cursor: col-resize;
  z-index: 1;
}
.chat-resize-handle::after {
  content: '';
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  width: 4px;
  height: 32px;
  border-radius: 2px;
  background: var(--border);
  transition: background 0.15s, height 0.15s;
}
.chat-resize-handle:hover::after,
.chat-resize-handle:active::after {
  background: var(--accent);
  height: 48px;
}

.chat-panel-header {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 12px 16px;
  border-bottom: 1px solid var(--border);
  background: var(--surface);
  flex-shrink: 0;
}
.chat-panel-title {
  font-size: 0.85rem;
  font-weight: 600;
  color: var(--text);
}
.chat-panel-close {
  background: none;
  border: none;
  color: var(--text-muted);
  cursor: pointer;
  font-size: 1.1rem;
  padding: 4px;
}
.chat-panel-close:hover { color: var(--text); }

.chat-limit-toggle {
  background: none;
  border: 1px solid var(--border);
  color: var(--text-muted);
  cursor: pointer;
  font-size: 0.72rem;
  font-family: inherit;
  padding: 3px 10px;
  border-radius: 4px;
  transition: background 0.15s, color 0.15s, border-color 0.15s;
}
.chat-limit-toggle:hover { color: var(--text); border-color: var(--text-muted); }
.chat-limit-toggle--active {
  background: var(--accent);
  color: #fff;
  border-color: var(--accent);
}
.chat-limit-toggle--active:hover {
  opacity: 0.85;
  color: #fff;
}

.chat-new-btn {
  background: none;
  border: 1px solid var(--border);
  color: var(--text-muted);
  cursor: pointer;
  font-size: 0.72rem;
  font-family: inherit;
  padding: 3px 10px;
  border-radius: 4px;
}
.chat-new-btn:hover { color: var(--text); border-color: var(--text-muted); }

.chat-messages {
  flex: 1;
  overflow-y: auto;
  padding: 16px;
  display: flex;
  flex-direction: column;
  gap: 12px;
}

.chat-msg { display: flex; flex-direction: column; gap: 4px; }
.chat-msg--user { align-items: flex-end; }
.chat-msg--assistant { align-items: flex-start; }

.chat-bubble {
  max-width: 90%;
  padding: 10px 14px;
  border-radius: 12px;
  font-size: 0.82rem;
  line-height: 1.5;
  word-break: break-word;
}
.chat-msg--user .chat-bubble {
  background: var(--accent);
  color: #fff;
  border-bottom-right-radius: 4px;
}
.chat-bubble--text {
  background: var(--surface);
  color: var(--text);
  border: 1px solid var(--border);
  border-bottom-left-radius: 4px;
}
.chat-bubble--text .chat-md-h1,
.chat-bubble--text .chat-md-h2,
.chat-bubble--text .chat-md-h3,
.chat-bubble--text .chat-md-h4,
.chat-bubble--text .chat-md-h5,
.chat-bubble--text .chat-md-h6 {
  margin: 10px 0 4px 0;
  font-weight: 600;
  line-height: 1.3;
}
.chat-bubble--text .chat-md-h1 { font-size: 1.05rem; }
.chat-bubble--text .chat-md-h2 { font-size: 0.95rem; }
.chat-bubble--text .chat-md-h3 { font-size: 0.88rem; }
.chat-bubble--text .chat-md-h4 { font-size: 0.84rem; }
.chat-bubble--text .chat-md-h5 { font-size: 0.82rem; text-transform: uppercase; letter-spacing: 0.03em; color: var(--text-muted); }
.chat-bubble--text .chat-md-h6 { font-size: 0.78rem; text-transform: uppercase; letter-spacing: 0.03em; color: var(--text-muted); }
.chat-bubble--text > .chat-md-h1:first-child,
.chat-bubble--text > .chat-md-h2:first-child,
.chat-bubble--text > .chat-md-h3:first-child,
.chat-bubble--text > .chat-md-h4:first-child,
.chat-bubble--text > .chat-md-h5:first-child,
.chat-bubble--text > .chat-md-h6:first-child { margin-top: 0; }

.chat-bubble--text .chat-md-ul,
.chat-bubble--text .chat-md-ol {
  margin: 6px 0;
  padding-left: 20px;
}
.chat-bubble--text .chat-md-ul li,
.chat-bubble--text .chat-md-ol li {
  margin: 2px 0;
}

.chat-bubble--text .chat-md-table {
  border-collapse: collapse;
  margin: 8px 0;
  font-size: 0.78rem;
  width: 100%;
}
.chat-bubble--text .chat-md-table th,
.chat-bubble--text .chat-md-table td {
  border: 1px solid var(--border);
  padding: 4px 8px;
  text-align: left;
  vertical-align: top;
}
.chat-bubble--text .chat-md-table th {
  background: rgba(125, 125, 160, 0.1);
  font-weight: 600;
}

.chat-error {
  background: rgba(239, 68, 68, 0.15);
  border: 1px solid rgba(239, 68, 68, 0.3);
  color: #f87171;
  padding: 8px 12px;
  border-radius: 8px;
  font-size: 0.78rem;
  max-width: 90%;
}

/* Tool call blocks */
.chat-tool-call {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 8px;
  padding: 8px 12px;
  max-width: 90%;
  font-size: 0.78rem;
}
.chat-tool-label {
  display: flex;
  align-items: center;
  gap: 6px;
  font-weight: 500;
  color: var(--accent-light);
}
.chat-tool-spinner {
  width: 12px;
  height: 12px;
  border: 2px solid var(--border);
  border-top-color: var(--accent-light);
  border-radius: 50%;
  animation: chat-spin 0.8s linear infinite;
  flex-shrink: 0;
}
.chat-tool-spinner--done {
  animation: none;
  border-color: #34d399;
  border-top-color: #34d399;
}
@keyframes chat-spin { to { transform: rotate(360deg); } }

/* Status indicator shown while agent is working */
.chat-status-indicator {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 10px 14px;
  border-radius: 12px;
  background: var(--surface, var(--bg));
  border: 1px solid var(--border);
  max-width: 90%;
  animation: chat-status-fade-in 0.3s ease;
}
@keyframes chat-status-fade-in {
  from { opacity: 0; transform: translateY(4px); }
  to   { opacity: 1; transform: translateY(0); }
}
.chat-status-spinner {
  width: 14px;
  height: 14px;
  border: 2px solid var(--border);
  border-top-color: var(--accent-light);
  border-radius: 50%;
  animation: chat-spin 0.8s linear infinite;
  flex-shrink: 0;
}
.chat-status-text {
  font-size: 0.8rem;
  color: var(--text-secondary, var(--text-muted));
}
.chat-status-elapsed {
  font-size: 0.7rem;
  color: var(--text-muted);
  opacity: 0.7;
  margin-left: auto;
}

.chat-tool-explanation {
  color: var(--text-muted);
  font-size: 0.75rem;
  margin-top: 4px;
}
.chat-tool-info {
  color: var(--text-muted);
  font-size: 0.72rem;
  margin-top: 4px;
}
.chat-tool-error {
  color: #f87171;
  font-size: 0.72rem;
  margin-top: 4px;
}

.chat-sql-details {
  margin-top: 6px;
}
.chat-sql-details summary {
  cursor: pointer;
  color: var(--text-muted);
  font-size: 0.72rem;
  user-select: none;
}
.chat-sql {
  margin: 4px 0 0;
  padding: 8px;
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: 6px;
  font-size: 0.72rem;
  line-height: 1.4;
  overflow-x: auto;
  white-space: pre-wrap;
  color: var(--text);
}

/* Thinking blocks */
.chat-thinking {
  max-width: 90%;
  font-size: 0.72rem;
  color: var(--text-muted);
}
.chat-thinking summary {
  cursor: pointer;
  user-select: none;
}
.chat-thinking pre {
  margin: 4px 0 0;
  padding: 6px 8px;
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: 6px;
  font-size: 0.72rem;
  white-space: pre-wrap;
}

/* Charts */
.chat-chart {
  max-width: 95%;
  min-height: 100px;
  margin: 4px 0;
  width: 100%;
}
.chat-chart .js-plotly-plot,
.chat-chart .plot-container {
  width: 100% !important;
}

/* Input row */
.chat-input-row {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 12px 16px;
  border-top: 1px solid var(--border);
  background: var(--surface);
  flex-shrink: 0;
}
.chat-input {
  flex: 1;
  padding: 8px 12px;
  border: 1px solid var(--border);
  border-radius: 8px;
  background: var(--bg);
  color: var(--text);
  font-size: 0.82rem;
  font-family: inherit;
  outline: none;
}
.chat-input:focus { border-color: var(--accent); }
.chat-input::placeholder { color: var(--text-muted); }

.chat-send-btn {
  width: 36px;
  height: 36px;
  border: none;
  border-radius: 8px;
  background: var(--accent);
  color: #fff;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  transition: background 0.15s;
}
.chat-send-btn:hover { background: var(--accent-hover); }
.chat-send-btn:disabled { opacity: 0.5; cursor: not-allowed; }

/* Suggested prompts */
.chat-suggestions {
  padding: 8px 0;
}
.chat-suggestions-title {
  font-size: 0.75rem;
  color: var(--text-muted);
  margin-bottom: 8px;
}
.chat-suggestion-btn {
  display: block;
  width: 100%;
  text-align: left;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 8px;
  padding: 8px 12px;
  margin-bottom: 6px;
  color: var(--text);
  font-size: 0.78rem;
  font-family: inherit;
  cursor: pointer;
  transition: border-color 0.15s, background 0.15s;
}
.chat-suggestion-btn:hover {
  border-color: var(--accent);
  background: var(--bg);
}

/* ── Manual Run ── */

.sp-manual-run-btn {
  border-color: #2dd4bf;
  color: #2dd4bf;
}
.sp-manual-run-btn:hover {
  background: #2dd4bf;
  color: #fff;
  box-shadow: 0 2px 10px #2dd4bf30;
}

#manualRunBackdrop {
  position: fixed;
  inset: 0;
  background: rgba(0, 0, 0, 0.55);
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 200;
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.2s;
}
#manualRunBackdrop.open {
  opacity: 1;
  pointer-events: auto;
}

.mr-modal {
  background: var(--surface);
  border-radius: 12px;
  width: min(580px, 95vw);
  max-height: 85vh;
  overflow-y: auto;
  box-shadow: 0 24px 48px rgba(0, 0, 0, 0.25);
}

.mr-step-label {
  font-weight: 600;
  font-size: 0.82rem;
  color: var(--accent-light);
  margin: 14px 0 10px;
  padding-bottom: 6px;
  border-bottom: 1px solid var(--border);
}

.mr-variant-table {
  border: 1px solid var(--border);
  border-radius: 8px;
  overflow: hidden;
  margin-top: 8px;
}

.mr-variant-header {
  display: grid;
  grid-template-columns: 80px 100px 60px 60px 70px 32px;
  gap: 4px;
  padding: 6px 8px;
  font-size: 0.72rem;
  font-weight: 600;
  color: var(--text-muted);
  background: var(--bg);
  border-bottom: 1px solid var(--border);
}

.mr-variant-row {
  display: grid;
  grid-template-columns: 80px 100px 60px 60px 70px 32px;
  gap: 4px;
  padding: 4px 8px;
  border-bottom: 1px solid var(--border);
}
.mr-variant-row:last-child { border-bottom: none; }

.mr-input {
  width: 100%;
  padding: 4px 6px;
  font-size: 0.78rem;
  font-family: 'Menlo', 'Monaco', 'Courier New', monospace;
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: 4px;
  color: var(--text);
  outline: none;
  transition: border-color 0.15s;
}
.mr-input:focus { border-color: var(--accent); }

/* Hide number input spinners */
.mr-input[type="number"]::-webkit-outer-spin-button,
.mr-input[type="number"]::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; }
.mr-input[type="number"] { -moz-appearance: textfield; }

.mr-remove-btn {
  background: none;
  border: none;
  color: #f87171;
  cursor: pointer;
  font-size: 0.85rem;
  padding: 2px;
  border-radius: 4px;
  transition: background 0.15s;
  line-height: 1;
}
.mr-remove-btn:hover { background: #f8717120; }

.mr-created-files {
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: 0.8rem;
  margin: 8px 0;
}
.mr-created-label { color: var(--text-muted); font-weight: 600; }
#mrCreatedList { color: #34d399; font-family: monospace; }

.mr-ops-preview { margin: 12px 0; }
.mr-ops-title {
  font-size: 0.78rem;
  font-weight: 600;
  color: var(--text-muted);
  margin-bottom: 6px;
}
.mr-op-item {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 4px 0;
  font-size: 0.8rem;
}
.mr-op-badge {
  display: inline-block;
  padding: 1px 8px;
  border-radius: 4px;
  font-size: 0.7rem;
  font-weight: 600;
  min-width: 36px;
  text-align: center;
}
.mr-op-skip .mr-op-badge { background: var(--border); color: var(--text-muted); }
.mr-op-run .mr-op-badge  { background: #34d39920; color: #34d399; }
.mr-op-skip { color: var(--text-muted); }

/* Manual run type badge in runs table */
.sp-run-type-manual {
  background: #2dd4bf18;
  color: #2dd4bf;
  border: 1px solid #2dd4bf40;
  padding: 2px 8px;
  border-radius: 6px;
  font-size: 0.72rem;
  font-weight: 600;
}

/* ── Coverage QC Dashboard ── */
.cov-chart-toolbar {
  margin: 8px 24px 0;
  display: flex;
  justify-content: flex-end;
}
.cov-toggle-label {
  font-size: 0.75rem;
  color: var(--text-muted);
  cursor: pointer;
  display: flex;
  align-items: center;
  gap: 5px;
  user-select: none;
}
.cov-toggle-label input[type="checkbox"] {
  accent-color: var(--accent);
}
.cov-chart-container {
  margin: 4px 24px 16px;
  min-height: 200px;
}
.cnv-plot-container {
  margin: 4px 24px 16px;
  min-height: 340px;
}
.cnv-note {
  margin: 2px 24px 6px;
  font-size: 0.78rem;
  color: var(--text-muted);
}
.cov-table-details {
  margin: 8px 24px 16px;
}
.cov-table-summary {
  cursor: pointer;
  font-size: 0.82rem;
  font-weight: 600;
  color: var(--text-muted);
  padding: 6px 0;
  user-select: none;
}
.cov-table-summary:hover {
  color: var(--text);
}
.sp-table td.num {
  text-align: right;
  font-variant-numeric: tabular-nums;
}

/* ═══════════════════════════════════════════════════════════════════════════
   Somatic Dashboard
   ═══════════════════════════════════════════════════════════════════════════ */

/* ── Mode Switcher ── */
.genomics-mode-switcher {
  display: flex;
  gap: 0;
  margin: 0 24px 12px;
  border: 1px solid var(--border);
  border-radius: 8px;
  overflow: hidden;
  width: fit-content;
}
.genomics-mode-pill {
  padding: 6px 20px;
  font-size: 0.82rem;
  font-weight: 600;
  background: transparent;
  color: var(--text-secondary, #6b7280);
  border: none;
  cursor: pointer;
  transition: all 0.15s ease;
}
.genomics-mode-pill:hover {
  background: var(--surface-2);
}
.genomics-mode-pill--active {
  background: var(--accent);
  color: #fff;
  cursor: default;
}
.genomics-mode-pill--active:hover,
[data-theme="light"] .genomics-mode-pill--active {
  background: var(--accent);
  color: #fff;
}

/* ── TMB Badge ── */
/* ── Gene Role Badges ── */
.somatic-role-badge {
  display: inline-block;
  font-size: 0.65rem;
  font-weight: 700;
  padding: 1px 6px;
  border-radius: 4px;
  white-space: nowrap;
}
.somatic-role-badge-onco {
  background: #fecaca;
  color: #991b1b;
}
[data-theme="dark"] .somatic-role-badge-onco {
  background: #450a0a;
  color: #fca5a5;
}
.somatic-role-badge-tsg {
  background: #dbeafe;
  color: #1e40af;
}
[data-theme="dark"] .somatic-role-badge-tsg {
  background: #1e3a5f;
  color: #93c5fd;
}

/* ── Consequence Spectrum Chart ── */
.vep-chart-card {
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: 14px 16px;
}
.vep-chart-title {
  font-size: 0.78rem;
  font-weight: 600;
  color: var(--text-secondary, #6b7280);
  margin-bottom: 10px;
}
/* ── Somatic VAF Distribution — stacked histogram (Plotly) ── */
.som-vaf-header {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 12px;
  flex-wrap: wrap;
  margin-bottom: 6px;
}
.som-vaf-header .vep-chart-title { margin-bottom: 0; }
.som-vaf-toggles {
  display: flex;
  gap: 14px;
}
.som-toggle {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  font-size: 0.72rem;
  color: var(--text-muted, #8892c0);
  cursor: pointer;
  user-select: none;
}
.som-toggle input {
  margin: 0;
  cursor: pointer;
  accent-color: var(--accent, #7c3aed);
}
.som-vaf-plot {
  width: 100%;
  min-height: 300px;
}

/* ── Hotspot variants scatter + drill-down lollipop ── */
.som-hotspot-plot {
  width: 100%;
  min-height: 300px;
}
.som-hotspot-hint {
  color: var(--accent, #7c3aed);
  font-size: 0.72rem;
  margin: 8px 0 2px;
  opacity: 0.9;
}
.som-hotspot-back {
  background: var(--surface-3, var(--border));
  color: var(--text);
  border: 1px solid var(--border);
  border-radius: 6px;
  padding: 4px 11px;
  font-size: 0.72rem;
  font-weight: 600;
  cursor: pointer;
  margin-right: 10px;
  transition: background 0.15s;
}
.som-hotspot-back:hover { background: var(--border); }
.som-hotspot-gene { color: var(--accent, #f2a33c); font-weight: 700; }
.som-hotspot-sub { color: var(--text-muted, #8892c0); font-size: 0.72rem; margin: 2px 0 8px; }
/* custom two-row legend (aligned title columns; items flow horizontally) */
.som-hotspot-legend { margin: 2px 0 10px; font-size: 0.72rem; }
.som-hs-row { display: flex; flex-wrap: wrap; align-items: center; gap: 5px 15px; margin: 4px 0; }
.som-hs-title { color: var(--text-muted, #8892c0); font-weight: 600; width: 250px; flex: none; }
.som-hs-item { display: inline-flex; align-items: center; gap: 6px; color: var(--text); white-space: nowrap; }
.som-hs-sw { width: 12px; height: 12px; border-radius: 50%; box-sizing: border-box; flex: none; }

/* ── gnomAD Contamination ── */
.som-gnomad-bar,
.som-gnomad-bar-wrap {
  display: flex;
  height: 18px;
  border-radius: 4px;
  overflow: hidden;
  margin-bottom: 10px;
}
.som-gnomad-seg {
  height: 100%;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: .7rem;
  font-weight: 600;
  color: #fff;
  min-width: 2px;
  transition: width 0.3s ease;
}
.som-gnomad-legend {
  display: flex;
  flex-wrap: wrap;
  gap: 8px 16px;
}
.som-gnomad-legend-item {
  display: flex;
  align-items: center;
  gap: 5px;
  font-size: 0.72rem;
  color: var(--text-secondary, #6b7280);
}
.som-gnomad-swatch,
.som-gnomad-dot {
  display: inline-block;
  width: 10px;
  height: 10px;
  border-radius: 2px;
  margin-right: 4px;
  vertical-align: middle;
}

/* ── Somatic QC: one merged Tumor/Normal view ── */
/* 24px side inset matches `.vep-section-collapsible > .vep-chart-card` (the
   Germline Contamination Check bar) so the table and charts line up under it. */
.som-qc-body {
  margin: 12px 24px 0;
  min-width: 0;   /* let Plotly charts shrink instead of overflowing */
}
.som-qc-body .gv-overview-wrap { margin: 0 0 4px; }
/* Five fixed columns (Metric + Tumor + Normal + Expected + Detail) need more
   room than the germline four before .sp-table-wrap should start scrolling. */
.som-qc-body .gv-overview-table { min-width: 850px; }
.som-qc-note { margin: 8px 0 0; }

/* ── Coverage ring + depth curve, side by side (germline AND somatic) ── */
/* minmax(0, …) not bare fr: the embedded SVG/Plotly intrinsic widths would
   otherwise blow the tracks out and overflow at narrow widths. */
.qc-charts { margin: 14px 0 0; }
.qc-charts--pair {
  display: grid;
  grid-template-columns: minmax(0, 1.85fr) minmax(0, 1fr);
  gap: 26px;
  /* stretch, not end: the right-hand column distributes its own contents
     (bar at the top, curve at the bottom) and needs the full row height to do
     it. The curve still lands level with the foot of the ring. */
  align-items: stretch;
}
@media (max-width: 1100px) {
  .qc-charts--pair { grid-template-columns: minmax(0, 1fr); }
}
.cov-depth-curve { min-width: 0; }
/* Right-hand column: both children sit at the FOOT of the column, so the
   contamination bar rides directly on top of the curve instead of being
   stranded at the far end of a column as tall as the ring. The curve still
   lands level with the foot of the ring beside it. */
.qc-side {
  display: flex;
  flex-direction: column;
  justify-content: flex-end;
  gap: 14px;
  min-width: 0;
  height: 100%;
}
/* A column holding only the curve (germline: no contamination bar to stack on
   top of) still bottoms out against the foot of the ring — flex-end above
   already does that, and there is nothing to space away from. */
/* The bar's own card carries a 24px page inset that is wrong inside a column. */
.som-qc-contam .vep-chart-card { margin-left: 0; margin-right: 0; }

/* ── Coverage ring ── */
.cov-ring { margin: 0; min-width: 0; }
.cov-ring-toolbar-row {
  display: flex; align-items: center; gap: 14px; flex-wrap: wrap; margin: 0 0 12px;
}
.cov-ring-toolbar-row .cov-ring-toolbar { margin: 0; }
.cov-ring-toolbar {
  display: flex; width: fit-content; margin: 0 0 12px;
  border: 1px solid var(--border); border-radius: 8px; overflow: hidden;
}
.cov-ring-mode {
  background: transparent; border: 0; border-right: 1px solid var(--border);
  color: var(--text-muted); font: inherit; font-size: 0.78rem;
  padding: 6px 14px; cursor: pointer;
}
.cov-ring-mode:last-child { border-right: 0; }
.cov-ring-mode[aria-pressed="true"] { background: var(--surface-2); color: var(--text); font-weight: 600; }
.cov-ring-mode:hover[aria-pressed="false"] { color: var(--text); }
.cov-ring-mode:focus-visible { outline: 2px solid var(--accent, var(--text)); outline-offset: -2px; }
/* Cap the ring so it does not swallow the panel on a wide screen; the SVG is
   square, so an unbounded width becomes an equally tall block. */
.cov-ring-body { max-width: 620px; margin: 0 auto; }
.cov-ring-svg { width: 100%; height: auto; display: block; overflow: visible; }
.cov-ring-legend {
  display: flex; gap: 20px; align-items: center; flex-wrap: wrap;
  font-size: 0.79rem; color: var(--text-muted); margin: 10px 0 0;
}
.cov-ring-sw {
  width: 18px; height: 3px; border-radius: 2px;
  display: inline-block; margin-right: 7px; vertical-align: middle;
}
.cov-ring-sub { color: var(--text-subtle); }
.cov-ring-note { color: var(--text-subtle); font-size: 0.74rem; margin: 9px 0 0; max-width: 76ch; }

/* The Transcriptomics ring, scoped by its container because .cov-ring-legend and
   .cov-ring-note are shared with the Genomics rings, which want neither of these.
   Its entries are one per BAM rather than a tumour/normal pair, so a wrapped
   two-per-row legend made the third track read as a second group; and its note
   is a single short line since the depth prose came out, so the 76ch cap — which
   exists for the much longer somatic notes — wrapped it for no reason. */
#spRnaCovRing .cov-ring-legend {
  flex-direction: column;
  align-items: flex-start;
  gap: 5px;
}
#spRnaCovRing .cov-ring-note { max-width: none; }
/* The shared .qc-side bottoms its contents out so a contamination bar can ride
   on top of the curve. The RNA panel has no such bar, and its left column is
   taller than the ring itself (selector above, legend and note below), so
   flex-end stranded the curve far below the circle it sits beside. */
.qc-charts--pair:has(#spRnaCovRing) .qc-side { justify-content: center; }

/* ── Transcriptomics ring: per-alignment track selector ──
   Sits above the ring and drops a BAM's track from it. Chips rather than a
   dropdown because the set is small, every option is visible at once, and each
   carries a colour swatch that has to match the track it controls. */
.cov-ring-host { min-width: 0; }
.rna-ring-presets {
  display: flex; align-items: center; gap: 8px; flex-wrap: wrap; margin: 0 0 10px;
}
/* Reuses .cov-ring-mode for the buttons so the preset row and the layout
   toolbar below it read as the same control, not two competing ones. */
.rna-ring-presets .cov-ring-mode {
  border: 1px solid var(--border); border-radius: 999px; padding: 5px 12px;
}
/* One line, always. The picker lives inside .cov-ring-host — the ring's grid
   COLUMN, not the page — so it is ~778px wide on a desktop, and five tracks
   need ~815px: the fifth chip wrapped to a line of its own. Chips are the
   ring's legend as much as its control, so a wrapped one reads as a second,
   lesser group.
   Tightened spacing buys back the ~37px, and nowrap + overflow-x is what makes
   that hold rather than being a coincidence of five short labels: a sixth track
   or a longer name (HiFi-WGS Normal) scrolls instead of silently re-wrapping.
   The hint is a SIBLING, not a member of this row — it used to sit here with
   flex-basis:100% to force its own line, which nowrap would have broken.
   Measured on the live five-track sample at 778px: wrap needed 815px (fifth
   chip orphaned); these values need 755px, leaving 23px of headroom. */
.rna-ring-picker {
  display: flex; flex-wrap: nowrap; align-items: center; gap: 5px;
  margin: 0 0 4px; overflow-x: auto; min-width: 0;
  scrollbar-width: thin;
}
.rna-ring-picker-label {
  font-size: 0.74rem; text-transform: uppercase; letter-spacing: 0.06em;
  color: var(--text-subtle);
}
.rna-ring-chip {
  display: inline-flex; align-items: center; gap: 5px;
  border: 1px solid var(--border); border-radius: 999px;
  padding: 5px 9px 5px 7px; cursor: pointer;
  font-size: 0.78rem; color: var(--text-muted);
  /* Never compress a chip to fit — the row scrolls instead, so every label
     stays readable at its natural width. */
  flex: 0 0 auto; white-space: nowrap;
}
.rna-ring-chip--on { background: var(--surface-2); color: var(--text); }
/* A BAM with no windowed coverage keeps its chip: the note under the ring names
   it, and hiding the control would make an absent track unexplainable. */
.rna-ring-chip--nodata { opacity: 0.55; }
.rna-ring-chip input { margin: 0; cursor: pointer; }
.rna-ring-chip:focus-within { outline: 2px solid var(--accent, var(--text)); outline-offset: 1px; }
.rna-ring-sw {
  width: 14px; height: 3px; border-radius: 2px; display: inline-block;
}
.rna-ring-picker-hint {
  font-size: 0.74rem; color: var(--text-subtle); margin: 0 0 12px;
}
/* Shared hover tooltip: coverage ring AND the contamination bar. */
.qc-tip {
  position: fixed; pointer-events: none; opacity: 0; transition: opacity 0.08s;
  background: var(--surface-2); border: 1px solid var(--border); border-radius: 7px;
  padding: 8px 11px; font-size: 0.78rem; z-index: 60; min-width: 158px;
  box-shadow: 0 6px 20px rgba(0, 0, 0, 0.5);
}
.qc-tip.is-on { opacity: 1; }
.qc-tip-loc { color: var(--text); font-weight: 650; margin-bottom: 5px; }
.qc-tip-row {
  display: flex; justify-content: space-between; gap: 16px; color: var(--text-muted);
}
.qc-tip-row b { color: var(--text); font-variant-numeric: tabular-nums; font-weight: 600; }
.qc-tip-norm { margin-top: 4px; padding-top: 4px; border-top: 1px solid var(--border); }
/* The per-chromosome hover puts three numbers in one row, so it needs more room
   than the single-value window hover the tooltip was sized for. */
.qc-tip:has(.qc-tip-chrom) { min-width: 300px; }
.qc-tip-chrom { gap: 18px; }
.qc-tip-chrom b { white-space: nowrap; }

/* ── Tier section ── */
.som-tier-section {
  margin: 0 24px 16px;
}
.som-tier-header {
  display: flex;
  align-items: center;
  gap: 10px;
  margin-bottom: 8px;
}
.som-tier-heading {
  font-size: 0.85rem;
  font-weight: 600;
  margin: 12px 0 6px;
}
.som-tier-badge {
  display: inline-block;
  padding: 2px 10px;
  border-radius: 5px;
  font-size: .75rem;
  font-weight: 700;
  color: #fff;
}
.som-tier-count {
  font-weight: 400;
  font-size: 0.78rem;
  opacity: 0.7;
}

/* ── Contamination Messages ── */
.som-contamination-ok {
  margin: 0 24px;
  padding: 14px 18px;
  background: #dcfce720;
  border: 1px solid #bbf7d0;
  border-radius: 8px;
  font-size: 0.82rem;
  color: var(--text);
}
[data-theme="dark"] .som-contamination-ok {
  background: #14532d20;
  border-color: #166534;
}
.som-contamination-warn {
  margin: 0 24px;
  padding: 14px 18px;
  background: #fef3c720;
  border: 1px solid #fde68a;
  border-radius: 8px;
  font-size: 0.82rem;
  color: var(--text);
}
[data-theme="dark"] .som-contamination-warn {
  background: #451a0320;
  border-color: #92400e;
}

/* ── QC flag sub-line ── */
.stat-sub-warn { color: #f97316; }
.stat-sub-ok   { color: #22c55e; }

/* ── Chart placeholder ── */
.vep-chart-placeholder {
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: 24px 16px;
  text-align: center;
  font-size: 0.78rem;
  color: var(--text-secondary, #6b7280);
}

/* ── maftools Plot Grid ── */
.mft-grid {
  display: grid;
  gap: 12px;
  margin: 8px 24px 16px;
}
.mft-grid-4 { grid-template-columns: repeat(2, 1fr); }
.mft-grid-2 { grid-template-columns: repeat(2, 1fr); }
@media (max-width: 900px) {
  .mft-grid-4, .mft-grid-2 { grid-template-columns: 1fr; }
}
.vep-section-collapsible > .vep-chart-card {
  margin: 8px 24px 16px;
}
.mft-plot {
  width: 100%;
  min-height: 200px;
}
.mft-plot-wide {
  width: 100%;
  min-height: 320px;
}
#mftWebrSection .vep-chart-card,
#mftWebrSection > #mftWebrStatus.vep-chart-placeholder {
  background: transparent;
  border: none;
}
/* The plotmafSummary + tcgaCompare cards live inside #mftWebrPlots, so
   the generic `.vep-section-collapsible > .vep-chart-card` 24px-inset rule
   doesn't reach them. Apply the same horizontal margin here so their
   left/right edges line up with the section header bar. */
#mftWebrSection > #mftWebrPlots > .vep-chart-card {
  margin: 8px 24px 16px;
}

/* Per-sample maftools plots (signatures, heterogeneity) rendered side-by-side
   inside the maftools Summary section. Two columns on wide viewports;
   collapses to a single column on narrow ones to match the existing
   .mft-grid responsive break. */
.mft-side-by-side {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 12px;
  margin: 8px 24px 16px;
}
@media (max-width: 900px) {
  .mft-side-by-side { grid-template-columns: 1fr; }
}
.mft-side-by-side > .mft-side-panel {
  /* The cards already pick up the standard .vep-chart-card chrome; the
     side-by-side container just lays them out. Remove the outer margin
     vep-chart-card sets so the grid gap controls spacing. */
  margin: 0;
}
.mft-side-panel-title {
  margin: 0 0 4px;
  font-size: 0.95rem;
  font-weight: 600;
}
.mft-side-panel-subtitle {
  margin: 0 0 8px;
  font-size: 0.8rem;
  color: var(--text-secondary, #6b7280);
}
.mft-side-panel .mft-lolli-controls,
.mft-side-panel .mft-lolli-out {
  margin-left: 0;
  margin-right: 0;
}
/* Stretch the panel into a flex column so the figure (and its caption)
   pin to the bottom of the equal-height grid cell. Without this, the
   shorter signatures plot floats up and its caption sits well above
   the heterogeneity caption. */
.mft-side-by-side > .mft-side-panel {
  display: flex;
  flex-direction: column;
}
.mft-side-panel .mft-lolli-out {
  flex: 1 1 auto;
  display: flex;
  flex-direction: column;
  justify-content: flex-end;
}
.mft-side-panel .mft-lolli-figure {
  margin: 0;
}
/* Force a uniform display height on the two PNGs (signatures is wide+short,
   heterogeneity is roughly square) so the figure bottoms — and therefore
   the captions below them — line up across the two columns. */
.mft-side-panel .mft-lolli-img {
  width: 100%;
  height: 320px;
  object-fit: contain;
}

/* ── maftools Per-Gene Lollipop (R-lazy) ── */
.mft-lolli-controls {
  display: flex;
  align-items: center;
  gap: 10px;
  margin: 8px 24px;
  font-size: 0.85rem;
}
.mft-lolli-label {
  color: var(--text-secondary, #6b7280);
  font-weight: 500;
}
.mft-lolli-select {
  min-width: 140px;
  padding: 4px 8px;
  border: 1px solid var(--border-muted, #d1d5db);
  border-radius: 4px;
  background: var(--surface);
  font-size: 0.85rem;
}
.mft-lolli-status {
  color: var(--text-secondary, #6b7280);
  font-style: italic;
  font-size: 0.8rem;
}
.mft-lolli-spinner {
  display: inline-block;
  width: 10px;
  height: 10px;
  border: 2px solid var(--border-muted, #d1d5db);
  border-top-color: var(--text-primary, #374151);
  border-radius: 50%;
  animation: mft-lolli-spin 0.8s linear infinite;
  vertical-align: -1px;
  margin-right: 4px;
}
@keyframes mft-lolli-spin {
  to { transform: rotate(360deg); }
}
.mft-lolli-out {
  margin: 8px 24px 16px;
  min-height: 120px;
}
.mft-lolli-figure {
  margin: 0;
}
.mft-lolli-img {
  max-width: 100%;
  height: auto;
  border: 1px solid var(--border-muted, #e5e7eb);
  border-radius: 4px;
  background: #fff;
}
.mft-lolli-caption {
  margin-top: 6px;
  font-size: 0.8rem;
  color: var(--text-secondary, #6b7280);
  font-style: italic;
}
/* Nested Advanced/Isoform disclosure inside the lollipop panel. Indents
 * to match `.mft-lolli-out` so the "Advanced" summary aligns with the
 * plot's left edge instead of the parent panel's. */
.mft-lolli-advanced {
  margin: 4px 24px 16px;
  font-size: 0.85rem;
}
/* Override the heavy `.vep-section-header` box styling for this nested
 * disclosure — it's a small inline toggle, not a top-level section. */
.vep-section-header.mft-lolli-advanced-summary {
  display: inline-flex;
  gap: 4px;
  margin: 0;
  padding: 4px 0;
  background: none;
  border-left: none;
  border-radius: 0;
  font-size: 0.85rem;
  font-weight: 500;
  text-transform: none;
  letter-spacing: normal;
  color: var(--text-secondary, #6b7280);
}
.vep-section-header.mft-lolli-advanced-summary::after {
  content: "▾";
  font-size: 0.7rem;
  margin-left: 2px;
}
.vep-section-collapsible:not([open]) .vep-section-header.mft-lolli-advanced-summary::after {
  transform: rotate(-90deg);
}
.mft-lolli-advanced-controls {
  margin: 6px 0 0;
}
.mft-lolli-advanced-controls .mft-lolli-select option:disabled {
  color: var(--text-disabled, #9ca3af);
  font-style: italic;
}
.vep-section-subtitle {
  margin: 4px 24px 10px;
  font-size: 0.78rem;
  color: var(--text-secondary, #6b7280);
  font-style: italic;
}

/* ── Proteomics tab ── */
.sp-proteomics-search {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
  align-items: center;
  margin: 0;
  padding: 8px 0;
}
/* cDNA Sequence Search section (Transcriptomics tab) — reuses the .sp-pm-*
   card styling; this only spaces the section header off the QC content above. */
.sp-cdna-section {
  margin-top: 24px;
  padding-top: 16px;
  border-top: 1px solid var(--border-color, #2a2a2a);
}
.sp-cdna-title {
  margin: 0 0 4px;
  font-size: 1.05rem;
}
.sp-cdna-sub {
  margin: 0 0 8px;
  font-size: 0.85rem;
  opacity: 0.75;
}
/* Align row leaders (variant pills + "Search by" label) into a single column
   so the dropdowns/inputs all start at the same x. */
#spTabProteomics .sp-run-selector-row,
#spTabProteomics .sp-proteomics-search {
  margin: 0;
  padding: 8px 0;
}
#spTabProteomics .sp-pm-row-leader {
  min-width: 110px;
  display: inline-flex;
  align-items: center;
  flex-shrink: 0;
}
.sp-pm-class-pill {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 3px 12px;
  border-radius: 999px;
  font-size: 0.72rem;
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
}
.sp-pm-class-pill--germline {
  background: rgba(37, 99, 235, 0.12);
  color: #1d4ed8;
  border: 1px solid rgba(37, 99, 235, 0.35);
}
.sp-pm-class-pill--somatic {
  background: rgba(220, 38, 38, 0.12);
  color: #b91c1c;
  border: 1px solid rgba(220, 38, 38, 0.35);
}
html[data-theme="dark"] .sp-pm-class-pill--germline {
  background: rgba(96, 165, 250, 0.18);
  color: #93c5fd;
  border-color: rgba(96, 165, 250, 0.45);
}
html[data-theme="dark"] .sp-pm-class-pill--somatic {
  background: rgba(248, 113, 113, 0.18);
  color: #fca5a5;
  border-color: rgba(248, 113, 113, 0.45);
}
.sp-search-input {
  flex: 1 1 240px;
  min-width: 200px;
  max-width: 480px;
  padding: 6px 10px;
  font-size: 0.9rem;
  border: 1px solid var(--border-muted, #d1d5db);
  border-radius: 4px;
}
.sp-empty-msg {
  margin: 24px;
  padding: 16px;
  text-align: center;
  color: var(--text-secondary, #6b7280);
  font-style: italic;
  background: var(--bg-muted, #f9fafb);
  border: 1px dashed var(--border-muted, #d1d5db);
  border-radius: 6px;
}
.sp-pm-card {
  margin: 12px 24px;
  padding: 12px 16px;
  border: 1px solid var(--border-muted, #e5e7eb);
  border-radius: 6px;
  background: var(--bg-elevated, #fff);
}
.sp-pm-card-header {
  display: flex;
  flex-wrap: wrap;
  gap: 12px;
  align-items: baseline;
  padding-bottom: 8px;
  margin-bottom: 8px;
  border-bottom: 1px solid var(--border-muted, #e5e7eb);
}
.sp-pm-gene {
  font-weight: 600;
  font-size: 1.05rem;
  color: var(--text-primary, #111827);
}
.sp-pm-meta {
  font-size: 0.85rem;
  color: var(--text-secondary, #6b7280);
}
.sp-pm-meta code {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 0.85em;
}
.sp-pm-flag {
  padding: 2px 8px;
  background: var(--accent-muted, #eef2ff);
  color: var(--accent, #4f46e5);
  border-radius: 10px;
  font-size: 0.75rem;
  font-weight: 500;
}
/* Warning variant of the QC flag — premature stop / ambiguous frame on a
   reads-derived protein translation (sample-protein-search.js). */
.sp-pm-flag--warn {
  background: var(--warn-muted, #fef3c7);
  color: var(--warn, #b45309);
}
/* Highlighted amino-acid motif inside a .sp-pm-seq (aa_motif search). <mark> is
   an element boundary, not an injected character, so find-in-page over the
   residues still works. */
.sp-pm-seq .sp-pm-motif {
  background: var(--accent-muted, #eef2ff);
  color: var(--accent, #4f46e5);
  font-weight: 700;
  border-radius: 2px;
}
.sp-pm-section {
  margin: 6px 0;
  padding: 4px 0;
}
.sp-pm-section > summary {
  cursor: pointer;
  font-weight: 500;
  padding: 4px 0;
  color: var(--text-primary, #111827);
}
/* cDNA-card BLAT button — small pill above the sequence block. */
.sp-pm-seq-tools {
  display: flex;
  justify-content: flex-end;
  margin: 6px 0 0;
}
.sp-pm-blat-btn {
  display: inline-flex;
  align-items: center;
  gap: 3px;
  padding: 2px 8px;
  font-size: 0.68rem;
  font-weight: 600;
  color: var(--accent-light, #6366f1);
  background: none;
  border: 1px solid var(--border);
  border-radius: 4px;
  cursor: pointer;
  transition: background 0.15s, color 0.15s, border-color 0.15s;
}
.sp-pm-blat-btn:hover { background: var(--accent-light, #6366f1); color: #fff; border-color: transparent; }
.sp-pm-seq {
  margin: 6px 0;
  padding: 8px 12px;
  background: var(--bg-muted, #f9fafb);
  border: 1px solid var(--border-muted, #e5e7eb);
  border-radius: 4px;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 0.8rem;
  line-height: 1.5;
  /* Soft-wrap at 60 monospace columns instead of injecting hard "\n" line
     breaks in the markup. Hard newlines are real DOM characters that break
     Ctrl-F / find-in-page and copy-paste across a wrap boundary; a CSS soft
     wrap carries no character, so the sequence stays searchable and copyable as
     one contiguous string. width:60ch keeps the 60-column look (and keeps
     mutant vs wildtype column-aligned); max-width caps it on narrow screens.
     word-break:break-all lets the space-less sequence wrap mid-"word". */
  white-space: pre-wrap;
  word-break: break-all;
  width: 60ch;
  max-width: 100%;
  color: var(--text-primary, #111827);
}
.sp-pm-seq mark.sp-pm-mut-aa {
  background: #fde68a;          /* amber-200 */
  color: #92400e;               /* amber-900 */
  /* No padding/border so glyph advance width matches surrounding monospace
     residues — keeps the per-column alignment between mutant and wildtype. */
  padding: 0;
  border-radius: 0;
}
.sp-pm-mutant {
  margin: 8px 0;
  padding-left: 12px;
  border-left: 3px solid var(--border-muted, #d1d5db);
}
.sp-pm-mutant-head {
  font-size: 0.85rem;
  color: var(--text-secondary, #6b7280);
  margin-bottom: 2px;
}
.sp-pm-mutant-head code {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-weight: 600;
  color: var(--text-primary, #111827);
}
.sp-pm-empty {
  font-size: 0.85rem;
  color: var(--text-secondary, #6b7280);
  font-style: italic;
  padding: 8px 4px;
}

/* ─── Workflow tab ─────────────────────────────────────────────────────── */

/* CANCELED status pill — colored like FAILED but slightly muted. */
.sp-run-status-canceled {
  color: #c084fc;
  border-color: #c084fc44;
  background: #c084fc0e;
}

/* Expand caret cell on the parent workflow row. */
.sp-workflow-caret {
  width: 24px;
  text-align: center;
  color: var(--text-secondary, #6b7280);
  font-size: 0.7rem;
  user-select: none;
}

/* The nested detail row sits directly under its parent workflow row.
   When [hidden] is set, the row is collapsed; we toggle that attribute
   from JS in _spToggleWorkflow(). */
.sp-workflow-detail > td {
  padding: 0 0 12px 0;
  background: var(--bg-subtle, #f8fafc);
  border-top: none;
}

.sp-workflow-step-table {
  width: calc(100% - 36px);
  margin: 8px 0 4px 36px;
  border-collapse: collapse;
  font-size: 0.85rem;
}
.sp-workflow-step-table thead th {
  text-align: left;
  font-weight: 600;
  color: var(--text-secondary, #6b7280);
  border-bottom: 1px solid var(--border, #e5e7eb);
  padding: 4px 8px;
  text-transform: none;
  letter-spacing: 0;
}
.sp-workflow-step-table tbody td {
  padding: 6px 8px;
  border-bottom: 1px solid var(--border-subtle, #f1f5f9);
}
.sp-workflow-step-table tbody tr:last-child td {
  border-bottom: none;
}

/* Run-id link inside the nested step table — keep it visually compact and
   stop the click from bubbling up to the parent expand toggle. The Files
   tab's Run column (under "All runs") links the same way. */
.sp-workflow-runlink,
.sp-run-cell a {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 0.78rem;
  color: var(--accent, #2563eb);
  text-decoration: none;
}
.sp-workflow-runlink:hover,
.sp-run-cell a:hover {
  text-decoration: underline;
}
.sp-workflow-arrow {
  font-size: 0.7rem;
  opacity: 0.7;
}

.sp-workflow-empty {
  padding: 12px 36px;
  color: var(--text-secondary, #6b7280);
  font-style: italic;
}

/* ── Benchmarks tab ── */
.benchmarks-intro {
  color: var(--text-muted);
  font-size: 0.9rem;
  margin: 0 0 0.75rem;
}
.benchmarks-status {
  color: var(--text-subtle);
  font-size: 0.85rem;
  margin: 0 0 0.75rem;
}

/* Roll-up line above the master table: how many benchmarks pass / fail / have
   no data yet, from the SAME per-benchmark roll-ups the rows show. */
.benchmark-rollup {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 0.5rem 0.85rem;
  font-size: 0.78rem;
}
.benchmark-rollup-total {
  color: var(--text-muted);
  font-weight: 600;
}
.benchmark-rollup-item {
  display: inline-flex;
  align-items: center;
  gap: 0.3rem;
  font-weight: 600;
}
.benchmark-rollup-item::before {
  content: "";
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: currentColor;
}
.benchmark-rollup-item--pass   { color: #059f75; }
.benchmark-rollup-item--fail   { color: #dc5b5b; }
.benchmark-rollup-item--ungated { color: #7c3aed; }
.benchmark-rollup-item--nodata { color: var(--text-subtle); }
html[data-theme="dark"] .benchmark-rollup-item--pass { color: #34d399; }
html[data-theme="dark"] .benchmark-rollup-item--fail { color: #f87171; }
html[data-theme="dark"] .benchmark-rollup-item--ungated { color: #a78bfa; }

/* ── Master table: one row per benchmark, expanding into its full section ── */
.benchmark-master-table { table-layout: auto; }
.benchmark-master-table th.benchmark-master-toggle-col,
.benchmark-master-table td.benchmark-master-toggle-col { width: 34px; padding-right: 0; }
/* Per-benchmark <tbody> groups: keep the divider between benchmarks (the shared
   .sp-table rule drops the last row's border inside EVERY tbody). */
.benchmark-master-group + .benchmark-master-group > .benchmark-master-row > td {
  border-top: 1px solid var(--border);
}
.benchmark-master-row { cursor: pointer; }
.benchmark-master-row > td { vertical-align: middle; }
.benchmark-master-row.is-open > td {
  background: var(--surface-2);
  border-bottom-color: transparent;
}
.benchmark-master-toggle {
  background: none;
  border: none;
  padding: 0;
  color: var(--text-muted);
  font-size: 0.7rem;
  line-height: 1;
  cursor: pointer;
}
.benchmark-master-toggle:hover { color: var(--accent); }
.benchmark-master-toggle:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.benchmark-master-name {
  display: block;
  color: var(--text);
  font-weight: 600;
  font-size: 0.86rem;
}
.benchmark-master-scope {
  display: block;
  color: var(--text-subtle);
  font-size: 0.7rem;
  margin-top: 1px;
}
.benchmark-master-cadence { color: var(--text-muted); font-size: 0.78rem; white-space: nowrap; }
.benchmark-master-metrics-cell { white-space: normal; }

/* ── Schedule column: per-term 10-week run strip + relative last-run age ──── */
:root {
  --cad-pass:    #059f75;
  --cad-fail:    #dc5b5b;
  --cad-ungated: #8892c0;
  --cad-off:     #9088c8;
}
html[data-theme="dark"] {
  --cad-pass: #34d399;
  --cad-fail: #f87171;
  --cad-off:  #545e8a;
}

/* The Schedule cell absorbs the old Last run column — hide that column's
   header and cells rather than removing them from the table markup. */
.benchmark-master-table .cad-lastrun-col { display: none; }

/* Cell states. The three EMPTY states are separated by SHAPE, not hue — a full
   bar ran, a short bar was never due, an outline was due and missing. Shape
   keeps them legible for colour-vision-deficient readers and stops "not due"
   from competing with the pass/fail colours that carry the real signal. */
.cad-slot--pass    { background: var(--cad-pass); }
.cad-slot--fail    { background: var(--cad-fail); }
.cad-slot--ungated { background: var(--cad-ungated); }
/* Not due this week — the quiet weeks a slower-than-weekly cadence leaves
   behind. A centred bar at ~a third height: deliberate, clearly not a run. */
.cad-slot--off {
  background: linear-gradient(var(--cad-off) 0 0) center / 100% 34% no-repeat;
}
/* Due, not yet arrived, still inside grace. Same OUTLINE shape as overdue but
   neutral — waiting and late read as one escalating language, and only the
   genuinely late one is coloured. */
.cad-slot--pending { background: transparent; box-shadow: inset 0 0 0 1px var(--cad-ungated); opacity: 0.65; }
/* Due but never ran — outlined, so a gap reads as an omission, not as padding. */
.cad-slot--miss    { background: transparent; box-shadow: inset 0 0 0 1px var(--cad-fail); opacity: 0.85; }
/* Before the benchmark's first run: nothing was owed. The faintest state, so a
   young benchmark never reads as a neglected one. */
.cad-slot--none    { background: var(--border); opacity: 0.3; }

/* Relative age of the last run — the folded-in Last run column. */
.cad-age {
  color: var(--text-muted);
  font-size: 0.68rem;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}
.cad-age.is-behind { color: var(--cad-fail); font-weight: 600; }

/* Terms side by side: a new scope costs width, not row height. */
.cad-il { display: flex; align-items: center; gap: 0.55rem; white-space: nowrap; }
.cad-il-term { display: inline-flex; align-items: center; gap: 0.3rem; }
.cad-il-term + .cad-il-term {
  padding-left: 0.55rem;
  border-left: 1px solid var(--border);
}
.cad-il-key {
  color: var(--text-subtle);
  font-size: 0.6rem;
  font-weight: 700;
  letter-spacing: 0.03em;
}
.cad-il-track {
  display: inline-flex;
  gap: 2px;
  padding: 2px 3px;
  border-radius: 4px;
  background: var(--surface-2);
}
.cad-mini { width: 5px; height: 11px; border-radius: 1.5px; box-sizing: border-box; }

/* ── Canaries tab ──
   The Canaries tab reuses the Benchmarks visual system wholesale; this is the
   only place the two genuinely differ. A benchmark declares at most two cadence
   terms, but a canary declares one strip per scheduled LEG and alignment has
   five (WES, WGS, RNA, HiFi, ONT) — side by side those overflow the column and
   give the whole table a horizontal scrollbar, hiding the last leg off-screen.
   Wrapping costs row height only for the canaries that actually have many legs,
   and keeps every leg visible without shrinking the cells. */
.canary-strip { flex-wrap: wrap; row-gap: 0.3rem; }
/* The left border that separates terms reads as a stray tick on a wrapped
   row's first item, which is no longer beside anything. */
.canary-strip .cad-il-term:first-child { padding-left: 0; border-left: 0; }

/* Section headings. A pipeline canary, a workflow canary and a benchmark
   canary fail in different ways and are read by different people, so they get
   their own tables rather than one table the reader has to sort mentally. The
   tally sits ON the heading line so a section's health is legible without
   scanning its rows. */
.canary-section-head {
  display: grid;
  grid-template-columns: 1.4rem auto 1fr;
  align-items: baseline;
  column-gap: 0.5rem;
  margin: 1.6rem 0 0.5rem;
  cursor: pointer;
}
.canary-section-head:first-child { margin-top: 0.4rem; }
.canary-section-toggle {
  padding: 0;
  border: 0;
  background: none;
  color: var(--text-muted);
  font-size: 0.8rem;
  line-height: 1;
  cursor: pointer;
}
.canary-section-title {
  margin: 0;
  font-size: 0.95rem;
  font-weight: 700;
  letter-spacing: 0.01em;
}
.canary-section-tally { gap: 0.5rem; font-size: 0.7rem; }
/* Starts in the title's column so it lines up under the heading rather than
   under the chevron, and runs to the end so it can wrap. */
.canary-section-note {
  grid-column: 2 / -1;
  margin: 0.15rem 0 0;
  color: var(--text-muted);
  font-size: 0.78rem;
  max-width: 70ch;
}
/* A collapsed section keeps its heading and TALLY — it must still be able to
   say that something inside it is failing — but drops the explanatory note,
   which is only useful next to the rows it describes. */
.canary-section-head.is-closed { margin-bottom: 0; }
.canary-section-head.is-closed .canary-section-note { display: none; }

/* Three sections means three separate tables, and a table sizes its columns to
   its own contents — so Pipelines, Workflows and Benchmarks each landed their
   columns somewhere different and the sections stopped reading as one table
   split up. Fixed widths pin every section to the same grid.

   Experiments is sized in rem rather than as a percentage because it is the
   row's status and must fit its widest case — five legs, on the alignment
   canary — untruncated at any window width. Schedule takes what is left and
   collapses to one line (see _canFitStrips), which is the right way round:
   a clipped leg loses a verdict, a clipped strip loses history the expanded
   row still holds in full. */
.canary-master-table { table-layout: fixed; }
.canary-master-table th:nth-child(1) { width: 2.2rem; }
.canary-master-table th:nth-child(2) { width: 20%; }
/* Sized for the worst case — alignment's five legs, each naming its assay in
   full ("HiFi-WGS", "ONT-WGS", "RNA-seq"). Legs is the row's status, so it is
   the column that must never clip; Schedule gives up the space because it
   already degrades gracefully behind the "+N" marker. */
.canary-master-table th:nth-child(3) { width: 27rem; }
/* Sized for a full "YYYY-MM-DD HH:MM" plus the cell's own padding. Too narrow
   and a fixed-layout cell CLIPS rather than wraps, which lopped the last digit
   off the minute and produced a timestamp that was wrong rather than merely
   cramped. */
.canary-master-table th:nth-child(5) { width: 11rem; }
.canary-master-table td:nth-child(5) { white-space: nowrap; }
/* Fixed layout will not grow a cell to fit, so the long scope line under a
   canary's name has to be told it may wrap. */
.canary-master-table .benchmark-master-scope { white-space: normal; }

/* Experiments never wraps and never clips: one line, always, all of it. */
.canary-master-table .benchmark-master-chips { flex-wrap: nowrap; }
.canary-master-table .benchmark-master-chips > * { flex: none; }

/* Schedule is the column that gives: one line, overflow hidden, and the terms
   that do not fit are replaced by the "+N" marker _canFitStrips appends.

   `flex: none` on the terms is load-bearing, not tidying. A flex item shrinks
   before it overflows, so without it five strips SQUEEZED to fit: the slot
   cells were compressed to slivers, one strip was sliced through the middle,
   and the fitter measured no overflow because there was none — it had been
   absorbed. Terms must keep their natural width so the ones that do not fit
   genuinely overflow and can be counted. */
.canary-master-table .canary-strip--inline {
  flex-wrap: nowrap;
  overflow: hidden;
}
.canary-master-table .canary-strip--inline .cad-il-term { flex: none; }
/* The fitter hides overflowing terms with the `hidden` attribute, whose
   display:none comes from the UA stylesheet and therefore LOSES to
   `.cad-il-term { display: inline-flex }` on specificity. Without this rule
   the terms stay laid out, the "+N" marker is appended after them and lands
   outside the overflow clip, and the cell looks exactly as if the fitter had
   never run — which is how this read for two rounds of debugging. */
.canary-strip--inline .cad-il-term[hidden] { display: none; }
/* Workflow executions behind a workflow canary, shaped like the Sample page's
   Workflows tab: an execution row that expands into its steps. The nested
   table sits inside a detail row that already carries a surface colour, so it
   takes a plain background rather than a second stripe. */
/* Auto layout let Status absorb all the slack and stranded Workflow/Steps out
   near the right edge. Pin the narrow columns so the table reads as a row of
   facts rather than two clusters separated by whitespace. */
.canary-wf-table { table-layout: fixed; }
.canary-wf-table th:nth-child(1) { width: 2.2rem; }
.canary-wf-table th:nth-child(2) { width: 14rem; }
.canary-wf-table th:nth-child(3) { width: 7rem; }
.canary-wf-table th:nth-child(5) { width: 4.5rem; }
.canary-wf-table th:nth-child(6) { width: 11rem; }
.canary-wf-table th:nth-child(7) { width: 6rem; }
.canary-wf-row { cursor: pointer; }
/* Same treatment for the nested step table: Status and Run ID were floating. */
.canary-wf-step-table { table-layout: fixed; }
.canary-wf-step-table th:nth-child(1) { width: 12rem; }
.canary-wf-step-table th:nth-child(3) { width: 7.5rem; }
.canary-wf-step-table th:nth-child(4) { width: 12rem; }
.canary-wf-step-table th:nth-child(5) { width: 6rem; }
.canary-wf-step-table { margin: 0; background: var(--surface); }
.canary-wf-step-table th { font-size: 0.66rem; }
/* Nested two levels deep; without this its cells inherit the outer runs-table
   padding and the rows read as part of the parent table. */
.canary-wf-step-table td,
.canary-wf-step-table th { padding: 3px 8px; }

.canary-strip-more {
  flex: none;
  margin-left: 0.4rem;
  padding: 1px 6px;
  border: 1px dashed var(--border);
  border-radius: 999px;
  color: var(--text-muted);
  font-size: 0.66rem;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

/* A disarmed leg — no schedule set, so nothing is owed and nothing can be
   late. It is not a pass, not a failure and not a gap, so it neither takes a
   status colour nor spends a word saying "disarmed": it just recedes. The
   dashed border is what separates "switched off" from "quietly passing" at a
   glance; the reason stays on hover. */
.canary-chip--off {
  border-style: dashed;
  background: transparent;
  opacity: 0.55;
}
.canary-chip--off .benchmark-master-chip-k { font-style: italic; }
.benchmark-master-chips {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 0.3rem 0.4rem;
}
/* The headline metric's name, shown once per row — grid-mode chips are labelled
   by card (WES / WGS), so the metric they report has to be named somewhere. */
.benchmark-master-metric {
  color: var(--text-subtle);
  font-size: 0.66rem;
  font-weight: 700;
  letter-spacing: 0.05em;
  text-transform: uppercase;
}
.benchmark-master-chip {
  display: inline-flex;
  align-items: baseline;
  gap: 0.28rem;
  padding: 2px 8px;
  border: 1px solid var(--border);
  border-radius: 999px;
  background: var(--surface);
  font-size: 0.74rem;
  white-space: nowrap;
}
.benchmark-master-chip-k { color: var(--text-muted); }
.benchmark-master-chip-v {
  color: var(--text);
  font-weight: 600;
  font-variant-numeric: tabular-nums;
}
.benchmark-master-chip--pass { border-color: #34d39944; background: #34d3990e; }
.benchmark-master-chip--fail { border-color: #f8717144; background: #f871710e; }

/* The expanded sub-section: a full-width row under its summary row.
   SPECIFICITY, not preference: the shared `.sp-table td` rule (0,1,1) sets
   white-space: nowrap, so a bare `.benchmark-master-detail-cell` (0,1,0) never
   won and every paragraph in an expanded body (methodology, limitations) ran
   as ONE unwrapped line, widening the master table past its wrapper and
   forcing a horizontal scroll. Qualify with the element to outrank it. */
.benchmark-master-detail-row > td { background: var(--surface-2); }
.sp-table td.benchmark-master-detail-cell {
  white-space: normal;
  padding: 0 16px 16px !important;
}
.benchmark-master-table .benchmark-master-detail-row:hover > td { background: var(--surface-2); }
.benchmark-section {
  margin: 0;
  padding-top: 0.85rem;
}

/* "Sample Detail" folded into a parent card as another <details> alongside
   Trend/Technical detail. Its body is a single precision/recall table (per
   caller × sample configuration) — no nested cards. It sizes to its content
   (NOT the full, very wide master-table cell it lives in) so every column stays
   on-screen instead of being pushed off the right. */
.benchmark-config-table { table-layout: auto; width: auto; }
/* Full grid: every cell bordered, with UNIFORM left/right padding. The uniform
   padding also overrides .sp-table's `td:first-child { padding-left:16px }`, so
   the Caller entries line up whether or not their row carries the rowspanned
   group cells (otherwise the 2nd+ rows' first cell picks up the extra indent). */
.benchmark-config-table th,
.benchmark-config-table td {
  padding: 8px 18px;
  border: 1px solid var(--border);
}
/* Heavier rule between sample-configuration groups. */
.benchmark-config-table tr.benchmark-config-group > td { border-top: 2px solid var(--text-subtle); }
/* Per-condition cells span their callers; pinned to the top of the span. */
.benchmark-config-table td.benchmark-config-sample,
.benchmark-config-table td.benchmark-config-truth {
  vertical-align: top;
  white-space: nowrap;
}
.benchmark-config-table td.benchmark-config-sample { font-weight: 600; }
.benchmark-config-table td.benchmark-config-truth { color: var(--text-muted); }

/* PASS / FAIL / NO DATA pill — mirrors .sp-run-status colours. */
.benchmark-status {
  display: inline-block;
  padding: 2px 9px;
  border-radius: 999px;
  font-size: 0.64rem;
  font-weight: 700;
  letter-spacing: 0.05em;
  border: 1px solid;
  white-space: nowrap;
}
.benchmark-status-pass   { color: #34d399; border-color: #34d39944; background: #34d3990e; }
.benchmark-status-fail   { color: #f87171; border-color: #f8717144; background: #f871710e; }
.benchmark-status-nodata { color: #94a3b8; border-color: #94a3b844; background: #94a3b80e; }
/* Measured but ungated: distinct from NO DATA (slate) and from a verdict
   (green/red) — the page's own violet, so it reads as a state of the GATE
   rather than of the measurement. */
.benchmark-status-ungated { color: #8b5cf6; border-color: #8b5cf644; background: #8b5cf60e; }
html[data-theme="dark"] .benchmark-status-ungated { color: #a78bfa; border-color: #a78bfa44; background: #a78bfa0e; }

/* Methodology blurb (≤3 sentences) directly under the section header. */
.benchmark-methodology {
  margin: 0 0 0.7rem;
  font-size: 0.82rem;
  line-height: 1.5;
  color: var(--text-muted);
}
/* **bold** words pop against the muted blurb (author-emphasised terms). */
.benchmark-methodology strong { color: var(--text); font-weight: 600; }
/* "…" / "less" expander revealing the rest of the blurb — a light button chip.
   Shared with the QC panel's score-rail notes (.sp-cq-rail-more), which use
   the same first-sentence-then-expand shape for the same reason: the reader
   needs to know what the number IS before deciding whether to read why the
   platform does not cut on it. */
.benchmark-methodology-more,
.sp-cq-rail-more {
  border: 1px solid var(--border);
  background: var(--surface-2);
  border-radius: 5px;
  padding: 0 0.4rem;
  margin-left: 0.3rem;
  cursor: pointer;
  font: inherit;
  font-size: 0.76rem;
  font-weight: 700;
  line-height: 1.5;
  color: var(--accent-light);
  vertical-align: baseline;
  transition: border-color 0.15s, background 0.15s;
}
.benchmark-methodology-more:hover,
.sp-cq-rail-more:hover { border-color: var(--accent); background: var(--surface); }
.sp-cq-rail-more { font-size: 0.7rem; line-height: 1.3; }

/* Reference-standard provenance row: "official" pills under the section header,
   one per truth set/method, linking to the primary source (citation on hover). */
.benchmark-refstd {
  display: flex;
  align-items: center;
  gap: 0.55rem;
  flex-wrap: wrap;
  margin: 0 0 0.95rem;
}
.benchmark-refstd-label {
  font-size: 0.6rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.09em;
  color: var(--text-subtle);
}
.benchmark-refstd-badge {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  padding: 3px 10px 3px 8px;
  border-radius: 999px;
  border: 1px solid var(--border-glow);
  background: var(--surface-2);
  color: var(--text);
  font-size: 0.74rem;
  font-weight: 600;
  text-decoration: none;
  white-space: nowrap;
}
a.benchmark-refstd-badge:hover { border-color: var(--accent); }
.benchmark-refstd-badge svg { width: 13px; height: 13px; color: var(--accent-light); flex: none; }
.benchmark-refstd-ext { color: var(--text-subtle); font-size: 0.72rem; margin-left: 0.1rem; }

/* Per-benchmark card (WES / WGS). */
.benchmark-card {
  border: 1px solid var(--border);
  border-radius: 8px;
  margin-bottom: 0.75rem;
  background: var(--surface);
}
.benchmark-status-row {
  display: flex;
  align-items: center;
  gap: 0.85rem;
  padding: 0.6rem 0.9rem;
  border-radius: 8px 8px 0 0;
}
/* Push the PASS/FAIL/NO-DATA badge to the far right of the line. */
.benchmark-status-row > .benchmark-status { margin-left: auto; }
/* Whole line tints on the gate verdict. */
.benchmark-status-row--pass { background: rgba(52, 211, 153, 0.09); }
.benchmark-status-row--fail { background: rgba(248, 113, 113, 0.08); }
.benchmark-status-row--pass .benchmark-card-label,
.benchmark-status-row--pass .benchmark-metric-k,
.benchmark-status-row--pass .benchmark-metric-v { color: #059f75; }
.benchmark-status-row--fail .benchmark-card-label,
.benchmark-status-row--fail .benchmark-metric-k,
.benchmark-status-row--fail .benchmark-metric-v { color: #dc5b5b; }
html[data-theme="dark"] .benchmark-status-row--pass .benchmark-card-label,
html[data-theme="dark"] .benchmark-status-row--pass .benchmark-metric-k,
html[data-theme="dark"] .benchmark-status-row--pass .benchmark-metric-v { color: #34d399; }
html[data-theme="dark"] .benchmark-status-row--fail .benchmark-card-label,
html[data-theme="dark"] .benchmark-status-row--fail .benchmark-metric-k,
html[data-theme="dark"] .benchmark-status-row--fail .benchmark-metric-v { color: #f87171; }
.benchmark-card-label {
  font-weight: 700;
  font-size: 0.9rem;
  color: var(--text);
  letter-spacing: 0.03em;
  min-width: 3.2rem;
}
.benchmark-metric {
  display: inline-flex;
  align-items: baseline;
  gap: 0.35rem;
  font-size: 0.85rem;
}
.benchmark-metric-k { color: var(--text-muted); }
.benchmark-metric-v {
  color: var(--text);
  font-weight: 600;
  font-variant-numeric: tabular-nums;
}
/* Per-metric ✓ / ✗ next to each top-line value. */
.benchmark-metric-flag { font-size: 0.8rem; font-weight: 700; line-height: 1; }
.benchmark-metric-flag--pass { color: #059f75; }
.benchmark-metric-flag--fail { color: #dc5b5b; }
html[data-theme="dark"] .benchmark-metric-flag--pass { color: #34d399; }
html[data-theme="dark"] .benchmark-metric-flag--fail { color: #f87171; }

/* Technical detail — the runs behind a benchmark's data. Reuses the shared
   .sp-table look from the Sample page Runs tab; this just insets it slightly. */
.benchmark-runs-table { font-size: 0.8rem; }
.benchmark-runs-toolbar {
  display: flex;
  justify-content: flex-start;
  margin: 0 0 0.5rem;
}
.benchmark-view-run { padding: 0 14px; text-decoration: none; }

/* Per-run stratified metrics drill-down (mode=detail). */
.benchmark-drilldown-slot { margin-top: 0.75rem; }
.benchmark-drilldown-caption {
  color: var(--text-muted);
  font-size: 0.75rem;
  margin: 0 0 0.4rem;
  display: flex;
  align-items: center;
  gap: 0.4rem;
}
/* Fixed layout so the columns keep identical geometry when the SNP/INDEL
   selector switches (auto layout would resize columns to each variant's digit
   count — 5-digit SNP counts vs 3-digit INDEL — making the table jump). */
.benchmark-drilldown-table { font-size: 0.8rem; table-layout: fixed; }
.benchmark-drilldown-table th:first-child,
.benchmark-drilldown-table td:first-child { width: 24%; }
/* Right-align numeric columns — applies to both the <td> values and the
   `benchmark-num` <th> headers so each header sits directly over its values. */
.benchmark-drilldown-table .benchmark-num {
  text-align: right;
  font-variant-numeric: tabular-nums;
}
/* The gate subset row (top-line metrics) is bolded to stand out. */
.benchmark-drilldown-table tr.benchmark-drilldown-gate td { font-weight: 700; }

/* Multi-section drill-down (headlines mode): a titled sub-table per section. */
.benchmark-drilldown-section { margin-top: 0.9rem; }
.benchmark-drilldown-section:first-of-type { margin-top: 0.4rem; }
.benchmark-drilldown-section-title {
  font-size: 0.72rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--text-muted);
  margin: 0 0 0.35rem;
}

/* SNP / INDEL segmented selector above the drill-down table. */
.benchmark-variant-toggle {
  display: inline-flex;
  gap: 2px;
  margin: 0 0 0.6rem;
  padding: 2px;
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: 6px;
}
.benchmark-variant-btn {
  border: none;
  background: transparent;
  color: var(--text-muted);
  font-size: 0.72rem;
  font-weight: 600;
  letter-spacing: 0.03em;
  padding: 4px 16px;
  border-radius: 4px;
  cursor: pointer;
}
.benchmark-variant-btn:hover { color: var(--text); }
.benchmark-variant-btn.is-active {
  background: var(--surface);
  color: var(--text);
  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.08);
}

/* Collapsible "Trend detail" — native <details>, chevron in the summary. */
.benchmark-detail { border-top: 1px solid var(--border); }
.benchmark-detail-summary {
  cursor: pointer;
  list-style: none;
  user-select: none;
  padding: 0.45rem 0.9rem;
  font-size: 0.8rem;
  font-weight: 600;
  letter-spacing: 0;
  color: var(--text-muted);
  display: flex;
  align-items: center;
  justify-content: space-between;
}
.benchmark-detail-summary::-webkit-details-marker { display: none; }
.benchmark-detail-summary::after {
  content: "▲";
  font-size: 0.6rem;
  opacity: 0.5;
  transition: transform 0.2s;
}
.benchmark-detail:not([open]) .benchmark-detail-summary::after { transform: rotate(180deg); }
.benchmark-detail-body { padding: 0.25rem 0.75rem 0.75rem; }
/* One row per variant type — all its metric charts (Recall · Precision · F1)
   on one line. grid-template-columns is set inline per row from the group size. */
.benchmark-chart-row {
  display: grid;
  gap: 0.75rem;
  margin-bottom: 0.75rem;
}
.benchmark-chart-cell {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 8px;
  padding: 0.25rem 0.25rem 0.4rem;
  min-width: 0;
}
.benchmark-chart-title {
  color: var(--text);
  font-size: 0.8rem;
  font-weight: 600;
  text-align: center;
  padding: 0.4rem 0.25rem 0.2rem;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.benchmark-chart {
  min-width: 0;
}
.benchmark-empty {
  color: var(--text-subtle);
  font-style: italic;
  font-size: 0.85rem;
}

/* ── Library tab (GTEx visualiser) ───────────────────────────────────────── */
.lib-header { display: flex; align-items: flex-start; justify-content: space-between;
  gap: 16px; flex-wrap: wrap; margin-bottom: 14px; }
.lib-title { font-size: 1.25rem; font-weight: 700; }
.lib-subtitle { color: var(--muted, #64748b); font-size: 0.85rem; margin-top: 2px; }
.lib-view-toggle { display: inline-flex; border: 1px solid var(--border, #cbd5e1);
  border-radius: 8px; overflow: hidden; }
.lib-view-btn { padding: 6px 14px; background: transparent; border: 0; cursor: pointer;
  font-size: 0.85rem; color: var(--muted, #64748b); }
.lib-view-btn.lib-view-active { background: var(--accent, #2563eb); color: #fff; }
.lib-controls-row { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; margin-bottom: 12px; }
/* Standalone (not wrapped in a bordered .srch container like the filter
   inputs), so it must draw its own box — otherwise the inherited
   .srch-input `border:none; background:none` leaves it invisible. */
.lib-gene-input {
  min-width: 260px;
  flex: 0 1 320px;
  background: var(--input-bg);
  border: 1px solid var(--border);
  border-radius: 6px;
  padding: 8px 12px;
}
.lib-gene-input:focus {
  border-color: var(--accent);
  box-shadow: 0 0 0 2px color-mix(in srgb, var(--accent) 30%, transparent);
}
.lib-toggle-group { display: inline-flex; align-items: center; gap: 4px; }
.lib-toggle-label { font-size: 0.78rem; color: var(--muted, #64748b); margin-right: 2px; }
.lib-toggle { padding: 4px 10px; font-size: 0.8rem; cursor: pointer;
  border: 1px solid var(--border, #cbd5e1); background: transparent;
  color: var(--muted, #64748b); border-radius: 6px; }
.lib-toggle.lib-toggle-on { background: var(--accent, #2563eb); color: #fff; border-color: var(--accent, #2563eb); }
.lib-gene-header { margin: 6px 0 10px; }
.lib-gene-symbol { font-size: 1.1rem; font-weight: 700; margin-right: 12px; }
.lib-gene-meta { color: var(--muted, #64748b); font-size: 0.82rem; margin-right: 12px; font-family: monospace; }
.lib-gene-desc { color: var(--muted, #64748b); font-size: 0.85rem; margin-top: 4px; }
.lib-status { color: var(--muted, #64748b); font-size: 0.9rem; min-height: 1.2em; margin: 8px 0; }
.lib-plot { width: 100%; min-height: 560px; }
.lib-attribution { color: var(--muted, #64748b); font-size: 0.75rem; font-style: italic; margin-top: 10px; }
.lib-iso-panel { margin-top: 18px; }
.lib-iso-share { width: 100%; min-height: 420px; }
.lib-iso-heat { width: 100%; min-height: 200px; }
/* Tissue view */
.lib-tissue-select {
  min-width: 300px; max-width: 360px;
  padding: 8px 34px 8px 12px;
  font-family: inherit; font-size: 0.9rem; line-height: 1.2;
  color: var(--text); background-color: var(--bg);
  border: 1px solid var(--border); border-radius: 8px; cursor: pointer;
  -webkit-appearance: none; -moz-appearance: none; appearance: none;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'%3E%3Cpath d='M2 4l4 4 4-4' fill='none' stroke='%238892c0' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
  background-repeat: no-repeat; background-position: right 12px center; background-size: 12px;
  transition: border-color .15s, box-shadow .15s;
}
.lib-tissue-select:hover { border-color: var(--accent); }
.lib-tissue-select:focus { outline: none; border-color: var(--accent); box-shadow: 0 0 0 2px var(--border-glow); }
/* In the sidebar the picker flexes to fill the narrow column instead of its own min-width. */
.lib-tissue-sidebar .lib-controls-row { margin-bottom: 4px; gap: 8px; }
.lib-tissue-sidebar .lib-tissue-select { min-width: 0; max-width: none; flex: 1 1 auto; }
/* The Transcript view's isoform picker sits in the controls row beside the toggles. */
.lib-sibling-select { min-width: 0; max-width: 300px; padding-top: 5px; padding-bottom: 5px; font-size: 0.85rem; }
/* Raise the samples title so it lines up with the top of the picker (no leading margin). */
.lib-tissue-main .lib-panel-title:first-child { margin-top: 0; }
/* Dendrograms on/off switch */
.lib-switch {
  position: relative; width: 40px; height: 22px; padding: 0; flex: none;
  border: 1px solid var(--border); border-radius: 999px; cursor: pointer;
  background: var(--border); transition: background .18s, border-color .18s;
}
.lib-switch[aria-checked="true"] { background: var(--accent); border-color: var(--accent); }
.lib-switch:focus-visible { outline: none; box-shadow: 0 0 0 2px var(--border-glow); }
.lib-switch-knob {
  position: absolute; top: 2px; left: 2px; width: 16px; height: 16px; border-radius: 50%;
  background: #fff; box-shadow: 0 1px 2px rgba(0, 0, 0, 0.35); transition: transform .18s;
}
.lib-switch[aria-checked="true"] .lib-switch-knob { transform: translateX(18px); }
.lib-tissue-grid { display: grid; grid-template-columns: 280px 1fr; gap: 24px; align-items: start; }
@media (max-width: 900px) { .lib-tissue-grid { grid-template-columns: 1fr; } }
.lib-tissue-sidebar { display: flex; flex-direction: column; gap: 14px; }
.lib-anatomogram { display: flex; align-items: center; justify-content: center;
  min-height: 210px; border: 1px solid var(--border, #cbd5e1); border-radius: 10px;
  padding: 10px; background: var(--panel, rgba(148,163,184,0.05)); }
.lib-anatomogram { flex-direction: column; gap: 6px; }
.lib-anat-figure { display: flex; align-items: center; justify-content: center; width: 100%; }
.lib-anatomogram svg { max-width: 100%; max-height: 240px; height: auto; width: auto; }
.lib-anat-credit { color: var(--muted, #64748b); font-size: 0.65rem; text-align: center; }
.lib-tissue-meta { border: 1px solid var(--border, #cbd5e1); border-radius: 10px; overflow: hidden; }
.lib-meta-tname { font-size: 1.05rem; font-weight: 700; padding: 10px 12px 2px; }
.lib-meta-tsite { color: var(--muted, #64748b); font-size: 0.8rem; padding: 0 12px 8px; }
.lib-meta-table { width: 100%; border-collapse: collapse; font-size: 0.82rem; }
.lib-meta-table th { text-align: left; font-weight: 500; color: var(--muted, #64748b);
  padding: 6px 12px; vertical-align: top; width: 42%; }
.lib-meta-table td { padding: 6px 12px; }
.lib-meta-table tr + tr { border-top: 1px solid var(--border, #e2e8f0); }
.lib-meta-mono { font-family: monospace; }
.lib-meta-counts { display: flex; gap: 6px; }
.lib-meta-count { flex: 1; text-align: center; border: 1px solid var(--border, #e2e8f0);
  border-radius: 6px; padding: 6px 2px; }
.lib-meta-count b { display: block; font-size: 1.0rem; }
.lib-meta-count span { color: var(--muted, #64748b); font-size: 0.7rem; text-transform: uppercase; letter-spacing: 0.04em; }
.lib-panel-title { font-weight: 600; font-size: 0.95rem; margin: 22px 0 8px; }
.lib-samples-bar { width: 100%; min-height: 300px; }
.lib-heatmap-section { margin-top: 10px; }
.lib-heatmap-header { display: flex; align-items: center; justify-content: space-between; gap: 12px; flex-wrap: wrap; }
.lib-heatmap-header .lib-panel-title { margin-bottom: 0; }
/* min-height is a placeholder only: the heatmap's real height is set from its
   row count (JS), because the row count is now the reader's choice. */
.lib-heatmap { width: 100%; min-height: 320px; transition: opacity 0.3s ease; }
/* The rounded tiles of both heatmaps: our own SVG laid over Plotly's plot,
   inert to the mouse so hover reaches the heatmap trace beneath. */
.lib-heatmap, .lib-iso-heat { position: relative; }
.lib-heatmap > svg.lib-tiles, .lib-iso-heat > svg.lib-tiles {
  position: absolute; top: 0; left: 0; pointer-events: none; overflow: visible;
}
/* A new window cross-fades into the old one: the rects persist between draws
   (_libDrawTiles reuses them in order), so a cell's fill eases to its new colour
   and, when the grid changes, the tile slides to its new place. */
svg.lib-tiles rect {
  transition: fill 0.45s ease, x 0.35s ease, y 0.35s ease, width 0.35s ease, height 0.35s ease;
}
svg.lib-tiles rect.lib-tile-new { animation: lib-tile-in 0.35s ease both; }
@keyframes lib-tile-in { from { opacity: 0; } to { opacity: 1; } }
/* A window is in flight: the rows on screen are the previous window's. */
.lib-heatmap.lib-pending { opacity: 0.3; pointer-events: none; }
.lib-heatmap-note { color: var(--muted, #64748b); font-size: 0.75rem; font-style: italic; margin-top: 4px; }

/* ── Library → GTEx expression distribution + range slider ─────────────────
   The plot above the tissue heatmap, and the position + size controls that
   choose which window of the tissue's expression RANKING the heatmap draws.
   ────────────────────────────────────────────────────────────────────────── */
/* --lib-dist-ml/-mr mirror the Plotly layout's own left/right margins so the
   controls line up with the plot body. Cosmetic only: the controls run over gene
   RANK and the plot's axis is expression, so nothing here claims a handle sits
   under a bar — the dimmed run of bins is what ties the two together. */
.lib-dist-section {
  margin-top: 18px;
  /* The plot's margins and height, set by app-library.js from the same numbers
     it hands Plotly, so the controls beside and below the plot line up with
     its plot area rather than with a copy of the constants. */
  --lib-dist-ml: 56px; --lib-dist-mr: 16px; --lib-dist-mt: 16px; --lib-dist-mb: 42px; --lib-dist-h: 250px;
  /* The histogram's colour (the tissue's), applied to both sliders. */
  --lib-range-color: var(--accent, #2563eb);
  --lib-range-track: var(--border, #cbd5e1);
  --lib-range-thumb: 15px;
}
.lib-dist-row { display: flex; align-items: flex-start; gap: 0; }
.lib-dist-main { flex: 1 1 auto; min-width: 0; }
.lib-dist-plot { width: 100%; min-height: 260px; }

/* The position control spans exactly the plot area: padded to the plot's own
   margins, with the input pulled out by half a thumb on each side so the thumb
   CENTRES land on the histogram's first and last bin edges (a range input keeps
   its thumb inside its box, so at min/max the centre sits half a thumb in). */
.lib-range-start { padding: 0 var(--lib-dist-mr) 0 var(--lib-dist-ml); margin-top: -6px; }
.lib-range-start .lib-range-solo {
  display: block; width: calc(100% + var(--lib-range-thumb));
  margin: 0 calc(var(--lib-range-thumb) / -2);
}
.lib-range-caption { display: flex; align-items: center; gap: 10px; margin-top: 2px; }
.lib-range-caption .lib-range-value { min-width: 5.5em; }
.lib-range-reset { margin-left: auto; }

/* The size control stands to the right of the plot, as tall as its plot area:
   the column carries the plot's top margin, and the input — a horizontal range
   rotated upright, so every browser draws it the same — is pulled out by half a
   thumb at each end for the same centring reason as above. + is at the top. */
.lib-range-count {
  flex: 0 0 84px; display: flex; flex-direction: column; align-items: center;
  /* The slider occupies the plot area; the label and readout sit below it, in
     the plot's bottom margin, level with the position control's caption. */
  padding-top: var(--lib-dist-mt);
}
.lib-range-count .lib-range-label { margin-top: 10px; line-height: 1.1; }
.lib-range-vertical {
  position: relative; width: 22px; height: var(--lib-range-vlen, 180px); flex: 0 0 auto;
}
.lib-range-vertical .lib-range-solo {
  position: absolute; left: 0; top: 0;
  /* The box is laid out horizontally at the column's height, then rotated
     about its top-left corner and dropped by its own length. */
  width: calc(var(--lib-range-vlen, 180px) + var(--lib-range-thumb));
  height: 22px; margin: 0;
  transform-origin: 0 0;
  /* After the rotation the translate's x runs UPWARD, so the half-thumb in it is
     the whole centring correction — none is needed on `top`. */
  transform: rotate(-90deg) translate(calc(-1 * var(--lib-range-vlen, 180px) - var(--lib-range-thumb) / 2), 0);
}

.lib-range-label { font-size: 0.78rem; color: var(--muted, #64748b); white-space: nowrap; }
.lib-range-value {
  font-size: 0.78rem; font-weight: 600; color: var(--text, #0f172a);
  font-variant-numeric: tabular-nums; white-space: nowrap;
}

/* Single-handle sliders drawn on a visible rail, so a reader can see the travel
   before they grab the thumb; rail and thumb take the histogram's colour. */
.lib-range-solo {
  min-width: 0; height: 22px; margin: 0;
  -webkit-appearance: none; appearance: none; background: transparent;
  cursor: pointer;
}
.lib-range-solo::-webkit-slider-runnable-track {
  height: 4px; border-radius: 2px; background: var(--lib-range-track);
}
.lib-range-solo::-moz-range-track {
  height: 4px; border-radius: 2px; background: var(--lib-range-track);
}
.lib-range-input::-webkit-slider-thumb {
  -webkit-appearance: none; appearance: none;
  margin-top: -5.5px;   /* centre the 15px thumb on the 4px track */
  width: 15px; height: 15px; border-radius: 50%; cursor: grab;
  background: var(--surface, #fff); border: 2px solid var(--lib-range-color);
  box-shadow: 0 1px 3px rgba(15, 23, 42, 0.25);
}
.lib-range-input::-webkit-slider-thumb:active { cursor: grabbing; }
.lib-range-input::-moz-range-thumb {
  width: 15px; height: 15px; border-radius: 50%; cursor: grab;
  background: var(--surface, #fff); border: 2px solid var(--lib-range-color);
  box-shadow: 0 1px 3px rgba(15, 23, 42, 0.25);
}
.lib-range-input:focus-visible::-webkit-slider-thumb { outline: 2px solid var(--lib-range-color); outline-offset: 2px; }

/* The line under the histogram: an (i) that shows the full explanation on
   hover, then one short fact. */
.lib-dist-foot, #libDistNote { display: flex; align-items: center; gap: 7px; font-size: 0.78rem; color: var(--muted, #64748b); margin-top: 8px; font-style: normal; }
.lib-info-wrap { position: relative; display: inline-flex; align-items: center; }
.lib-info-btn {
  width: 14px; height: 14px; padding: 0; line-height: 1; border-radius: 50%;
  background: none; border: 1px solid var(--border, #cbd5e1); color: var(--muted, #64748b);
  font: italic 700 9px Georgia, serif; display: inline-flex; align-items: center; justify-content: center;
  cursor: default; transition: border-color 0.15s, color 0.15s;
}
.lib-info-wrap:hover .lib-info-btn, .lib-info-btn:focus-visible { border-color: var(--lib-range-color, var(--accent)); color: var(--lib-range-color, var(--accent)); }
.lib-info-tip {
  display: none; position: absolute; left: 0; bottom: calc(100% + 8px); width: min(560px, 80vw);
  background: var(--surface, #fff); border: 1px solid var(--border, #cbd5e1); border-radius: 8px;
  padding: 10px 12px; font-size: 0.78rem; line-height: 1.5; color: var(--text, #0f172a);
  box-shadow: 0 6px 24px rgba(15, 23, 42, 0.18); z-index: 100; white-space: normal;
}
.lib-info-wrap:hover .lib-info-tip, .lib-info-btn:focus-visible + .lib-info-tip { display: block; }

.lib-range-note {
  font-size: 0.78rem; color: var(--muted, #64748b);
  margin: 6px 0 2px; min-height: 1.2em;
}
.lib-range-note b { color: var(--text, #0f172a); font-variant-numeric: tabular-nums; }

/* ── Library → ClinVar browser ─────────────────────────────────────────────
   Facet sidebar + variant table, mirroring ClinVar's own results page. All
   filtering is client-side over one fetched gene, so there is no loading state
   between clicks — the styles assume instant re-render. */
.lib-subview-toggle { margin-bottom: 14px; }
.cv-hint { color: var(--muted, #64748b); font-size: 0.78rem; margin: -4px 0 8px; }
.cv-release-badge {
  font-family: monospace; font-size: 0.72rem; color: var(--muted, #64748b);
  border: 1px solid var(--border, #cbd5e1); border-radius: 999px; padding: 3px 10px;
}
.cv-gene-header { display: flex; align-items: baseline; gap: 18px; flex-wrap: wrap; margin: 6px 0 14px; }
.cv-head-stat { color: var(--muted, #64748b); font-size: 0.85rem; }
.cv-head-stat b { color: var(--text, #e2e8f0); font-size: 0.95rem; }

.cv-grid { display: grid; grid-template-columns: 230px 1fr; gap: 22px; align-items: start; }
@media (max-width: 1100px) { .cv-grid { grid-template-columns: 1fr; } }

/* ---- facets ---- */
.cv-facets { display: flex; flex-direction: column; gap: 16px; }
.cv-facet-block { border: 1px solid var(--border, #cbd5e1); border-radius: 10px; padding: 10px 12px; }
.cv-facet-title {
  font-size: 0.72rem; font-weight: 700; text-transform: uppercase; letter-spacing: 0.06em;
  color: var(--muted, #64748b); margin-bottom: 8px;
}
.cv-facet-item {
  display: flex; align-items: center; gap: 7px; padding: 3px 0;
  font-size: 0.82rem; cursor: pointer; line-height: 1.35;
}
.cv-facet-item:hover { color: var(--accent, #7c3aed); }
.cv-facet-item input { cursor: pointer; margin: 0; flex: none; accent-color: var(--accent, #7c3aed); }
.cv-facet-on { font-weight: 600; }
.cv-facet-label { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.cv-facet-sub { color: var(--muted, #64748b); font-size: 0.74rem; }
.cv-facet-count { color: var(--muted, #64748b); font-size: 0.74rem; font-variant-numeric: tabular-nums; flex: none; }
.cv-clear {
  align-self: flex-start; background: transparent; border: 1px solid var(--accent, #7c3aed);
  color: var(--accent, #7c3aed); border-radius: 999px; padding: 3px 12px;
  font-size: 0.74rem; cursor: pointer;
}
.cv-clear:hover { background: var(--accent, #7c3aed); color: #fff; }
.cv-dot { display: inline-block; width: 9px; height: 9px; border-radius: 50%; margin-right: 6px; flex: none; vertical-align: baseline; }

/* ---- stars ---- */
.cv-stars { white-space: nowrap; letter-spacing: -1px; }
.cv-star { color: var(--border, #cbd5e1); font-size: 0.82rem; }
.cv-star-on { color: #d9a441; }
.cv-nostar { color: var(--muted, #64748b); font-size: 0.72rem; font-style: italic; }

/* ---- summary plot ---- */
.cv-summary-plot { width: 100%; min-height: 180px; margin-bottom: 6px; }

/* ---- table ---- */
.cv-table-head { display: flex; align-items: baseline; justify-content: space-between; gap: 12px; margin-top: 10px; }
.cv-result-count { color: var(--muted, #64748b); font-size: 0.78rem; font-variant-numeric: tabular-nums; }
.cv-table-wrap { overflow-x: auto; border: 1px solid var(--border, #cbd5e1); border-radius: 10px; }
.cv-table { width: 100%; border-collapse: collapse; font-size: 0.8rem; }
.cv-table th, .cv-table td { text-align: left; padding: 7px 10px; border-bottom: 1px solid var(--border, #cbd5e1); vertical-align: top; }
.cv-table tbody tr:last-child td { border-bottom: 0; }
.cv-table tbody tr:hover { background: var(--panel-hover, rgba(124,58,237,0.06)); }
.cv-th {
  position: sticky; top: 0; z-index: 1; cursor: pointer; white-space: nowrap;
  font-size: 0.7rem; text-transform: uppercase; letter-spacing: 0.05em;
  color: var(--muted, #64748b); background: var(--panel, #0f1225); font-weight: 600;
}
.cv-th:hover { color: var(--accent, #7c3aed); }
.cv-th-active { color: var(--accent, #7c3aed); }
.cv-mono { font-family: monospace; font-size: 0.74rem; }
.cv-loc { white-space: nowrap; }
.cv-num { text-align: right; font-variant-numeric: tabular-nums; }
.cv-dim { color: var(--muted, #64748b); }
.cv-name { max-width: 320px; word-break: break-word; }
.cv-vlink { color: var(--accent, #7c3aed); text-decoration: none; font-family: monospace; font-size: 0.74rem; }
.cv-vlink:hover { text-decoration: underline; }
.cv-cond { max-width: 260px; }
.cv-more { color: var(--muted, #64748b); font-size: 0.7rem; }
.cv-empty { padding: 26px; text-align: center; color: var(--muted, #64748b); font-size: 0.85rem; }

/* ---- pager ---- */
.cv-pager { display: flex; align-items: center; justify-content: center; gap: 12px; margin-top: 12px; }
.cv-page-btn {
  background: transparent; border: 1px solid var(--border, #cbd5e1); color: inherit;
  border-radius: 7px; padding: 4px 12px; font-size: 0.78rem; cursor: pointer;
}
.cv-page-btn:hover:not(.cv-page-dis) { border-color: var(--accent, #7c3aed); color: var(--accent, #7c3aed); }
.cv-page-dis { opacity: 0.4; cursor: default; }
.cv-page-info { color: var(--muted, #64748b); font-size: 0.78rem; font-variant-numeric: tabular-nums; }
.cv-cap-note { color: var(--muted, #64748b); font-size: 0.75rem; font-style: italic; margin-top: 8px; }

/* ══ Library → gnomAD gene-constraint dashboard ══
   A scorecard rather than a browser: one row per gene, so the layout leads with
   the headline metrics, then gnomAD's own observed-vs-expected table, then the
   genome-wide context, with data-quality caveats pinned alongside rather than
   buried at the bottom. */
.gn-gene-header { margin: 6px 0 14px; }
.gn-gene-line { display: flex; align-items: baseline; gap: 12px; flex-wrap: wrap; }
.gn-ensg { color: var(--muted, #64748b); font-size: 0.8rem; }
.gn-gene-meta { display: flex; align-items: center; gap: 14px; flex-wrap: wrap; margin-top: 6px; }
.gn-meta-item { color: var(--muted, #64748b); font-size: 0.78rem; }
.gn-mono { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; }
.gn-dim { color: var(--muted, #64748b); }
.gn-badge {
  display: inline-block; border: 1px solid var(--border, #cbd5e1); border-radius: 999px;
  padding: 1px 9px; font-size: 0.68rem; font-weight: 600; letter-spacing: 0.03em;
  color: var(--muted, #64748b);
}
.gn-badge-mane { border-color: #2F6E9E; color: #2F6E9E; }

/* ---- scorecard ---- */
.gn-scorecard { display: flex; gap: 14px; flex-wrap: wrap; margin-bottom: 4px; }
.gn-card {
  flex: 1 1 190px; min-width: 170px; border: 1px solid var(--border, #cbd5e1);
  border-radius: 10px; padding: 12px 14px;
}
.gn-card-lead { border-left-width: 4px; border-left-style: solid; }
.gn-card-null { flex: 1 1 100%; }
.gn-card-label {
  font-size: 0.7rem; font-weight: 700; text-transform: uppercase; letter-spacing: 0.06em;
  color: var(--muted, #64748b); cursor: help;
}
.gn-card-value {
  font-size: 1.75rem; font-weight: 700; line-height: 1.15; margin-top: 4px;
  font-variant-numeric: tabular-nums;
}
.gn-card-value-null { font-size: 1.05rem; font-weight: 600; color: var(--muted, #64748b); }
.gn-card-band { font-size: 0.82rem; font-weight: 600; margin-top: 2px; }
.gn-card-note { color: var(--muted, #64748b); font-size: 0.72rem; margin-top: 5px; line-height: 1.4; }

/* ---- layout ---- */
.gn-grid { display: grid; grid-template-columns: 1fr 300px; gap: 22px; align-items: start; }
@media (max-width: 1100px) { .gn-grid { grid-template-columns: 1fr; } }
.gn-side { display: flex; flex-direction: column; gap: 6px; }
.gn-panel { border: 1px solid var(--border, #cbd5e1); border-radius: 10px; padding: 12px 14px; }
.gn-panel .lib-panel-title { margin: 0 0 8px; font-size: 0.78rem; text-transform: uppercase; letter-spacing: 0.05em; color: var(--muted, #64748b); }

/* ---- constraint table ---- */
.gn-table-wrap { overflow-x: auto; border: 1px solid var(--border, #cbd5e1); border-radius: 10px; }
.gn-table { width: 100%; border-collapse: collapse; font-size: 0.82rem; }
.gn-table th, .gn-table td {
  text-align: left; padding: 9px 12px; border-bottom: 1px solid var(--border, #cbd5e1);
  vertical-align: middle;
}
.gn-table th {
  font-size: 0.7rem; font-weight: 700; text-transform: uppercase; letter-spacing: 0.05em;
  color: var(--muted, #64748b);
}
.gn-table tbody tr:last-child td { border-bottom: 0; }
.gn-table .gn-num { text-align: right; font-variant-numeric: tabular-nums; }
.gn-cat { font-weight: 600; }
.gn-cat span { cursor: help; }
.gn-oe { min-width: 230px; }
.gn-oe-val { font-weight: 600; }

/* Drawn 90% confidence interval — the point estimate alone is what makes short
   genes look constrained, so the interval is rendered, not just tabulated. */
.gn-ci { position: relative; display: block; height: 10px; margin-top: 5px; width: 100%; max-width: 200px; }
.gn-ci-track {
  position: absolute; left: 0; right: 0; top: 4px; height: 2px;
  background: var(--border, #cbd5e1); border-radius: 2px;
}
.gn-ci-range {
  position: absolute; top: 3px; height: 4px; border-radius: 2px;
  background: var(--accent, #7c3aed); opacity: 0.35;
}
.gn-ci-point {
  position: absolute; top: 0; width: 2px; height: 10px; margin-left: -1px;
  background: var(--accent, #7c3aed); border-radius: 1px;
}

.gn-dist-plot { width: 100%; min-height: 260px; }

/* ---- caveats ---- */
.gn-caveats { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 9px; }
.gn-caveat { font-size: 0.78rem; line-height: 1.45; color: var(--muted, #64748b); }
.gn-caveat-strong {
  color: inherit; border-left: 3px solid #C9701A; padding-left: 9px; font-weight: 500;
}
.gn-caveat-ok { font-size: 0.78rem; line-height: 1.45; color: var(--muted, #64748b); }
.gn-flag {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 0.72rem;
  background: var(--panel-hover, rgba(124,58,237,0.08)); border-radius: 4px; padding: 1px 5px;
}
.gn-interp { font-size: 0.78rem; line-height: 1.5; color: var(--muted, #64748b); margin: 0; }

/* ══ Library → Cancer Hotspots ══════════════════════════════════════════════
   A lollipop leads, because a hotspot's meaning is positional: which codons
   cluster is the finding, and no table shows that. The alteration table is the
   auditable view beside it, and the catalogue ranking + caveats sit in the
   sidebar — the ranking because a raw tumour count is uncalibrated without its
   peers, the caveats because "hotspot" reads as "actionable" to anyone who has
   not been told otherwise. */
.hs-gene-header { margin: 6px 0 14px; }
.hs-gene-line { display: flex; align-items: baseline; gap: 12px; flex-wrap: wrap; }
.hs-gene-meta { display: flex; align-items: center; gap: 14px; flex-wrap: wrap; margin-top: 6px; }
.hs-meta-item { color: var(--muted, #64748b); font-size: 0.78rem; }
.hs-mono { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; }
.hs-dim { color: var(--muted, #64748b); }
.hs-rank-badge {
  display: inline-block; border: 1px solid #B3261E; border-radius: 999px;
  padding: 1px 9px; font-size: 0.68rem; font-weight: 600; letter-spacing: 0.03em;
  color: #B3261E; cursor: help;
}
.hs-oncokb { font-weight: 600; }

/* ---- scorecard ---- */
.hs-scorecard { display: flex; gap: 14px; flex-wrap: wrap; margin-bottom: 4px; }
.hs-card {
  flex: 1 1 190px; min-width: 170px; border: 1px solid var(--border, #cbd5e1);
  border-radius: 10px; padding: 12px 14px;
}
.hs-card-lead { border-left: 4px solid #B3261E; }
.hs-card-label {
  font-size: 0.7rem; font-weight: 700; text-transform: uppercase; letter-spacing: 0.06em;
  color: var(--muted, #64748b); cursor: help;
}
.hs-card-value {
  font-size: 1.75rem; font-weight: 700; line-height: 1.15; margin-top: 4px;
  font-variant-numeric: tabular-nums; overflow-wrap: anywhere;
}
.hs-card-note { color: var(--muted, #64748b); font-size: 0.72rem; margin-top: 5px; line-height: 1.4; }

/* ---- layout ---- */
.hs-plot-head { display: flex; align-items: baseline; justify-content: space-between; gap: 12px; }
.hs-plot-note {
  color: var(--muted, #64748b); font-size: 0.72rem; line-height: 1.45; margin: 4px 0 2px;
}
.hs-lollipop { width: 100%; min-height: 330px; }
.hs-landscape { width: 100%; }
/* minmax(0, 1fr), NOT bare `1fr` (= minmax(auto, 1fr)): the alteration table's
   intrinsic min-width would otherwise blow out the first track and push the
   sidebar off the viewport. Same trap as `.vep-summary-charts`. */
.hs-grid { display: grid; grid-template-columns: minmax(0, 1fr) 340px; gap: 22px; align-items: start; }
@media (max-width: 1100px) { .hs-grid { grid-template-columns: 1fr; } }
.hs-side { display: flex; flex-direction: column; gap: 6px; }
/* The landscape bars are the navigation surface for a catalogue nobody
   memorises, so they must look clickable. */
.hs-landscape .bars .point { cursor: pointer; }

/* ---- alteration table ---- */
.hs-table-wrap {
  overflow-x: auto; max-height: 560px; overflow-y: auto;
  border: 1px solid var(--border, #cbd5e1); border-radius: 10px;
}
.hs-table { width: 100%; border-collapse: collapse; font-size: 0.82rem; }
.hs-table th, .hs-table td {
  text-align: left; padding: 8px 12px; border-bottom: 1px solid var(--border, #cbd5e1);
  vertical-align: middle; white-space: nowrap;
}
.hs-table th {
  position: sticky; top: 0; z-index: 1; background: var(--panel, #fff);
  font-size: 0.7rem; font-weight: 700; text-transform: uppercase; letter-spacing: 0.05em;
  color: var(--muted, #64748b);
}
.hs-table tbody tr:last-child td { border-bottom: 0; }
.hs-table .hs-num { text-align: right; font-variant-numeric: tabular-nums; }
.hs-sortable { cursor: pointer; user-select: none; }
.hs-sortable:hover { color: var(--fg, #0f172a); }
.hs-hgvsp { font-weight: 600; }
.hs-class-dot {
  display: inline-block; width: 8px; height: 8px; border-radius: 50%;
  margin-right: 7px; vertical-align: middle;
}
.hs-yes { font-weight: 600; color: #1F7A5A; }
/* An unmapped locus is called out, not left blank — a blank cell reads as "no
   data entered" rather than "this coordinate does not exist in GRCh38". */
.hs-warn { color: var(--warn, #8a6d00); font-weight: 600; font-size: 0.76rem; cursor: help; }

/* ---- caveats + empty state ---- */
.hs-caveats { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 9px; }
.hs-caveat { font-size: 0.78rem; line-height: 1.45; color: var(--muted, #64748b); }
.hs-caveat:first-child {
  color: inherit; border-left: 3px solid #C9701A; padding-left: 9px; font-weight: 500;
}
.hs-xref { font-size: 0.78rem; line-height: 1.5; color: var(--muted, #64748b); margin: 10px 0 0; }
.hs-empty { margin: 10px 0; }
.hs-empty-title { font-size: 0.95rem; font-weight: 600; }
.hs-empty-note {
  color: var(--muted, #64748b); font-size: 0.8rem; line-height: 1.5; margin-top: 6px; max-width: 60ch;
}
.hs-suggest-row { display: flex; gap: 8px; flex-wrap: wrap; margin-top: 12px; }
.hs-suggest {
  border: 1px solid var(--border, #cbd5e1); border-radius: 999px; padding: 3px 12px;
  font-size: 0.78rem; font-weight: 600; cursor: pointer;
  background: transparent; color: inherit; font-family: inherit;
}
.hs-suggest:hover { border-color: #B3261E; color: #B3261E; }

/* ---- cancer indications ----
   Per-residue labels carry n/N inline, so a "100%" backed by two tumours is
   self-evidently thin rather than looking like the strongest signal on screen.
   Cohorts under the configured floor are dimmed and carry a hover explanation. */
.hs-ind-head { margin: 4px 0 2px; }
.hs-ind-title { font-size: 1.05rem; font-weight: 700; }
.hs-ind-sub { color: var(--muted, #64748b); font-size: 0.78rem; margin-top: 2px; }
.hs-ind-row { cursor: pointer; }
.hs-ind-row:hover td { background: var(--panel-hover, rgba(124,58,237,0.06)); }
.hs-ind-chips {
  display: flex; flex-wrap: wrap; gap: 4px; margin-top: 5px;
  white-space: normal; max-width: 30ch;
}
.hs-ind-chip {
  display: inline-block; border: 1px solid var(--border, #cbd5e1); border-radius: 999px;
  padding: 0 7px; font-size: 0.68rem; font-weight: 500; line-height: 1.65;
  font-family: system-ui, sans-serif; color: var(--muted, #64748b);
}
/* Below the ranked view's cohort floor — visible, but visibly weaker. */
.hs-ind-chip-small { opacity: 0.6; border-style: dashed; cursor: help; }
.hs-ind-more { font-size: 0.68rem; color: var(--muted, #64748b); align-self: center; }

/* Truncated tool result — a dropped result page otherwise looks like a normal
   row count, which is exactly how a partial aggregate gets read as a finished
   answer. */
.chat-tool-truncated { color: var(--warn, #8a6d00); font-weight: 600; }

/* Figures the answer states but cannot trace to a query result. Rendered under
   the answer rather than inline, so the reader sees the claim and its lack of
   provenance together without the text being rewritten around it. */
.chat-numeracy-warning {
  margin: 10px 0 4px; padding: 9px 12px; font-size: 0.8rem;
  border-left: 3px solid var(--warn, #8a6d00);
  background: color-mix(in srgb, var(--warn, #8a6d00) 7%, transparent);
  border-radius: 0 6px 6px 0;
}
.chat-numeracy-head {
  font-weight: 700; color: var(--warn, #8a6d00);
  font-size: 0.74rem; text-transform: uppercase; letter-spacing: 0.05em;
}
.chat-numeracy-warning ul { margin: 5px 0 0; padding-left: 18px; }
.chat-numeracy-warning li { margin: 3px 0; color: var(--muted, #64748b); }
.chat-numeracy-warning li b { color: var(--text, #0f172a); font-variant-numeric: tabular-nums; }

/* Figures the answer derived in the open. Traceable, so this is a quieter,
   collapsed treatment than the unsourced panel — the distinction between
   "we could not account for this" and "we checked the sum but not the choice
   of sum" is the whole point of separating them. */
.chat-numeracy-derived {
  margin: 8px 0 4px; padding: 7px 12px; font-size: 0.78rem;
  border: 1px solid var(--border, #cbd5e1); border-radius: 6px;
  color: var(--muted, #64748b);
}
.chat-numeracy-derived > summary {
  cursor: pointer; font-weight: 600; font-size: 0.74rem;
  color: var(--muted, #64748b); list-style: revert;
}
.chat-numeracy-derived > summary:hover { color: var(--accent, #7c3aed); }
.chat-numeracy-derived p { margin: 6px 0 0; }
.chat-numeracy-derived ul { margin: 5px 0 0; padding-left: 18px; }
.chat-numeracy-derived li { margin: 3px 0; }
.chat-numeracy-derived li b { color: var(--text, #0f172a); font-variant-numeric: tabular-nums; }

/* ══ Glossary — hoverable jargon definitions ══
   momix readers are expert in one field and lay in the next, so terms are
   annotated automatically from config/glossary.config.json rather than being
   explained inline by the agent. */
.gl-term {
  border-bottom: 1px dotted var(--accent, #7c3aed);
  cursor: help; position: relative; outline: none;
}
.gl-term:focus-visible { outline: 2px solid var(--accent, #7c3aed); outline-offset: 2px; border-radius: 2px; }
.gl-pop {
  position: absolute; bottom: calc(100% + 8px); left: 0; z-index: 60;
  width: max-content; max-width: min(340px, 78vw);
  background: var(--panel, #fff); color: var(--text, #0f172a);
  border: 1px solid var(--border, #cbd5e1); border-radius: 8px;
  padding: 9px 11px; font-size: 0.78rem; line-height: 1.5; font-weight: 400;
  box-shadow: 0 6px 22px rgba(0,0,0,0.16);
  opacity: 0; visibility: hidden; transform: translateY(3px);
  transition: opacity .13s ease, transform .13s ease, visibility .13s;
  white-space: normal; text-align: left; pointer-events: none;
  /* Keep the definition out of copied text: a clinician pasting an answer
     into a report must not get definitions spliced mid-sentence. */
  user-select: none; -webkit-user-select: none;
}
.gl-pop b { display: block; margin-bottom: 3px; font-size: 0.74rem;
  text-transform: uppercase; letter-spacing: 0.05em; color: var(--accent, #7c3aed); }
.gl-term:hover > .gl-pop, .gl-term:focus > .gl-pop, .gl-term:focus-within > .gl-pop {
  opacity: 1; visibility: visible; transform: translateY(0);
}
/* Near the right edge the popover would clip; anchor it to the right instead. */
.gl-term:nth-last-child(-n+3) > .gl-pop { left: auto; right: 0; }
@media (prefers-reduced-motion: reduce) { .gl-pop { transition: none; } }

/* "Terms used" block appended under an answer */
.gl-block {
  margin-top: 14px; border: 1px solid var(--border, #cbd5e1);
  border-radius: 8px; padding: 8px 12px; font-size: 0.8rem;
}
.gl-block > summary {
  cursor: pointer; font-weight: 600; font-size: 0.76rem;
  color: var(--muted, #64748b); list-style: revert;
}
.gl-block > summary:hover { color: var(--accent, #7c3aed); }
.gl-cat {
  font-size: 0.64rem; text-transform: uppercase; letter-spacing: 0.08em;
  color: var(--muted, #64748b); font-weight: 700; margin: 10px 0 4px;
}
dl.gl-list { margin: 6px 0 0; }
dl.gl-list dt { font-weight: 600; margin-top: 7px; }
dl.gl-list dd { margin: 1px 0 0; color: var(--muted, #64748b); line-height: 1.5; }

/* Plan mode — the approval card and the clarifying-question prompt.
   The card is deliberately heavier than a tool result: it is a decision point,
   not an intermediate step, and clicking Approve spends real compute. */
.chat-plan-card {
  margin: 12px 0 6px; padding: 12px 14px;
  border: 1px solid var(--accent, #7c3aed);
  border-radius: 8px;
  background: color-mix(in srgb, var(--accent, #7c3aed) 6%, transparent);
  font-size: 0.82rem;
}
.chat-plan-title {
  font-weight: 700; font-size: 0.74rem; letter-spacing: 0.06em;
  text-transform: uppercase; color: var(--accent-light, #a78bfa);
  margin-bottom: 8px;
}
.chat-plan-row { display: flex; gap: 10px; margin: 4px 0; align-items: baseline; }
.chat-plan-key {
  flex: 0 0 6.5rem; color: var(--text-muted, #8892c0);
  font-size: 0.72rem; text-transform: uppercase; letter-spacing: 0.04em;
}
.chat-plan-val { flex: 1 1 auto; font-variant-numeric: tabular-nums; }
.chat-plan-id { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 0.74rem; }

/* A declared/derived archetype mismatch is the signal the derivation exists to
   produce, so it gets warning treatment rather than being one more grey row. */
.chat-plan-mismatch { color: var(--warn, #8a6d00); }
.chat-plan-mismatch .chat-plan-key { color: var(--warn, #8a6d00); }
.chat-plan-note {
  margin: 4px 0 6px; padding: 6px 9px; font-size: 0.76rem;
  border-left: 3px solid var(--warn, #8a6d00);
  background: color-mix(in srgb, var(--warn, #8a6d00) 8%, transparent);
  border-radius: 0 5px 5px 0; color: var(--text-muted, #8892c0);
}
.chat-plan-actions { display: flex; gap: 10px; align-items: center; margin-top: 10px; }
.chat-plan-approve {
  padding: 6px 14px; border: 0; border-radius: 6px; cursor: pointer;
  background: var(--accent, #7c3aed); color: #fff;
  font-size: 0.78rem; font-weight: 600;
}
.chat-plan-approve:hover:not(:disabled) { background: var(--accent-hover, #6d28d9); }
.chat-plan-approve:disabled { opacity: 0.5; cursor: default; }
.chat-plan-open {
  font-size: 0.76rem; font-weight: 600; text-decoration: none;
  color: var(--accent-light, #a78bfa);
}
.chat-plan-open:hover { text-decoration: underline; }
.chat-plan-status { font-size: 0.76rem; color: var(--text-muted, #8892c0); }
.chat-plan-tables { display: flex; flex-wrap: wrap; gap: 5px; margin-top: 5px; }
.chat-plan-tables code {
  font-size: 0.72rem; padding: 1px 6px; border-radius: 4px;
  background: var(--input-bg, rgba(124,58,237,0.08)); color: var(--text, inherit);
}

.chat-clarify {
  margin: 10px 0 4px; padding: 9px 12px; font-size: 0.82rem;
  border-left: 3px solid var(--accent-2, #3b82f6);
  background: color-mix(in srgb, var(--accent-2, #3b82f6) 7%, transparent);
  border-radius: 0 6px 6px 0;
}
.chat-clarify-q { font-weight: 600; }
.chat-clarify-why { margin-top: 4px; color: var(--text-muted, #8892c0); font-size: 0.78rem; }
.chat-clarify-options { margin: 6px 0 0; padding-left: 18px; }
.chat-clarify-options li { margin: 2px 0; }

/* Plan-mode toggle + banner.
   Plan mode swaps which agent is listening — different tools, different system
   prompt, a different kind of output. A user who thinks they asked a question
   and got a plan has been misled by the UI, so the active state is deliberately
   louder than the row/tool toggles beside it. */
.chat-plan-toggle.chat-limit-toggle--active {
  background: var(--accent, #7c3aed);
  border-color: var(--accent, #7c3aed);
  color: #fff;
}
.chat-plan-banner {
  margin: 0 0 6px; padding: 7px 11px;
  font-size: 0.76rem; line-height: 1.45;
  border-left: 3px solid var(--accent, #7c3aed);
  background: color-mix(in srgb, var(--accent, #7c3aed) 8%, transparent);
  border-radius: 0 5px 5px 0;
  color: var(--text-muted, #8892c0);
}
/* A tint on the whole panel, so the mode is legible without reading anything —
   the banner scrolls, this does not. */
.chat-panel--planning .chat-input-row {
  border-top: 2px solid var(--accent, #7c3aed);
}

/* ══════════════════════════════════════════════════════════════════
   Analyses — the tab, and the single-analysis page
   ══════════════════════════════════════════════════════════════════
   An analysis is read, not operated: the reader wants to know whether
   anything is waiting on them, how far it has got, and what it found.
   So the layout is a stack of labelled sections rather than a dashboard,
   and the only colour that carries meaning is the status pill. */

.an-header {
  display: flex; align-items: flex-start; gap: 16px;
  margin: 0 0 18px;
}
.an-header-actions { display: flex; gap: 8px; align-items: center; margin-left: auto; }
.an-title { margin: 0 0 6px; font-size: 1.25rem; color: var(--text); }
.an-intro {
  margin: 0; max-width: 68ch;
  font-size: 0.85rem; line-height: 1.55; color: var(--text-muted);
}
.an-status { font-size: 0.8rem; color: var(--text-muted); min-height: 1.1em; }
.an-empty {
  padding: 22px; border: 1px dashed var(--border);
  border-radius: 8px; color: var(--text-muted);
  font-size: 0.85rem; line-height: 1.55; max-width: 62ch;
}
.an-select {
  background: var(--input-bg); color: var(--text);
  border: 1px solid var(--border); border-radius: 6px;
  padding: 6px 9px; font-size: 0.8rem; font-family: inherit;
}

.an-list { display: flex; flex-direction: column; gap: 8px; }
.an-card {
  display: flex; align-items: center; gap: 14px;
  padding: 13px 15px; text-decoration: none;
  background: var(--surface); border: 1px solid var(--border);
  border-radius: 8px; transition: border-color .12s, transform .12s;
}
.an-card:hover { border-color: var(--border-glow); transform: translateY(-1px); }
.an-card-main { min-width: 0; flex: 1; }
.an-question {
  color: var(--text); font-size: 0.92rem; font-weight: 600;
  line-height: 1.4; margin-bottom: 5px;
}
.an-meta { display: flex; flex-wrap: wrap; gap: 10px; }
.an-meta-item { font-size: 0.74rem; color: var(--text-subtle); }

/* Status carries the only load-bearing colour on the page. AWAITING_INPUT is
   the one that is about the READER rather than the machine, so it is the one
   that gets the accent. */
.an-status-pill {
  flex-shrink: 0; padding: 3px 10px; border-radius: 999px;
  font-size: 0.7rem; font-weight: 600; letter-spacing: .02em;
  border: 1px solid var(--border); color: var(--text-muted);
  white-space: nowrap;
}
.an-status-awaiting_input {
  border-color: var(--accent); color: #fff;
  background: var(--accent);
}
.an-status-running { border-color: var(--accent-2); color: var(--accent-2); }
.an-status-succeeded { border-color: var(--cad-pass); color: var(--cad-pass); }
.an-status-failed { border-color: var(--cad-fail); color: var(--cad-fail); }

/* ── Single analysis ── */

#analysisPage { padding: 0 24px 60px; max-width: 1080px; margin: 0 auto; }
.andet-question {
  margin: 0 0 10px; font-size: 1.15rem; line-height: 1.4;
  color: var(--text); max-width: 70ch;
}
.andet-section { margin: 26px 0 0; }
.andet-section-head { display: flex; align-items: center; gap: 12px; }
.andet-section-title {
  margin: 0 0 10px; font-size: 0.8rem; font-weight: 700;
  letter-spacing: .06em; text-transform: uppercase; color: var(--text-subtle);
}
.andet-section-head .andet-section-title { margin-bottom: 0; }
.andet-hint {
  font-size: 0.76rem; line-height: 1.5; color: var(--text-muted);
  margin: 4px 0 0; max-width: 70ch;
}

.andet-gate, .andet-escalation {
  padding: 15px; border-radius: 8px; margin-bottom: 10px;
  background: var(--surface);
  border: 1px solid var(--accent);
  box-shadow: var(--shadow-accent);
}
.andet-escalation-done { border-color: var(--border); box-shadow: none; }
.andet-gate-kind, .andet-escalation-q {
  font-size: 0.92rem; font-weight: 600; color: var(--text); line-height: 1.45;
}
.andet-gate-id { font-size: 0.72rem; color: var(--text-subtle); font-family: ui-monospace, monospace; margin-top: 3px; }
.andet-gate-note {
  display: block; width: 100%; margin: 10px 0 0; min-height: 52px;
  background: var(--input-bg); color: var(--text); font-family: inherit;
  border: 1px solid var(--border); border-radius: 6px; padding: 8px 10px;
  font-size: 0.82rem; resize: vertical;
}
.andet-gate-actions, .andet-escalation-actions {
  display: flex; flex-wrap: wrap; gap: 8px; align-items: center; margin-top: 10px;
}
.andet-gate-status { font-size: 0.76rem; color: var(--text-muted); }
.andet-btn-secondary, .andet-btn-danger {
  padding: 7px 13px; border-radius: 6px; font-size: 0.8rem; font-family: inherit;
  cursor: pointer; background: var(--surface-2); color: var(--text);
  border: 1px solid var(--border);
}
.andet-btn-secondary:hover { border-color: var(--border-glow); }
.andet-btn-danger { color: var(--cad-fail); }
.andet-btn-danger:hover { border-color: var(--cad-fail); }
button:disabled { opacity: .55; cursor: default; }

.andet-phases { display: flex; flex-direction: column; gap: 6px; }
.andet-phase {
  padding: 10px 13px; border-radius: 7px;
  background: var(--surface); border: 1px solid var(--border);
  border-left: 3px solid var(--border-glow);
}
.andet-phase-succeeded { border-left-color: var(--cad-pass); }
.andet-phase-failed { border-left-color: var(--cad-fail); }
.andet-phase-running { border-left-color: var(--accent-2); }
.andet-phase-pending { opacity: 0.55; border-left-style: dashed; }
.andet-phase-name { font-size: 0.86rem; font-weight: 600; color: var(--text);
  display: flex; align-items: center; gap: 8px; }
.andet-phase-marker {
  flex: 0 0 auto; width: 18px; height: 18px; border-radius: 50%;
  display: inline-flex; align-items: center; justify-content: center;
  font-size: 0.7rem; font-weight: 700; line-height: 1;
  background: var(--input-bg); color: var(--text-muted); border: 1px solid var(--border);
}
.andet-phase-succeeded .andet-phase-marker { background: var(--cad-pass); color: #fff; border-color: transparent; }
.andet-phase-failed .andet-phase-marker { background: var(--cad-fail); color: #fff; border-color: transparent; }
.andet-phase-running .andet-phase-marker { background: var(--accent-2); color: #fff; border-color: transparent; animation: andet-pulse 1.4s ease-in-out infinite; }
.andet-phase-meta { font-size: 0.74rem; color: var(--text-muted); margin-top: 2px; }

/* Live-run affordances */
@keyframes andet-pulse { 0%,100% { opacity: 1; } 50% { opacity: 0.35; } }
.andet-live {
  font-size: 0.72rem; font-weight: 600; color: var(--accent-2, #06b6d4);
  margin-left: 10px; animation: andet-pulse 1.4s ease-in-out infinite;
}

/* Deliverables — the payoff of the run, surfaced at the top of a finished
   analysis. Two cards: the report to read, the notebook to open. */
.andet-deliverables-section { margin: 4px 0 26px; }
.andet-deliverables {
  display: grid; grid-template-columns: 1fr 1fr; gap: 14px;
}
@media (max-width: 720px) { .andet-deliverables { grid-template-columns: 1fr; } }
.andet-deliverable {
  display: flex; flex-direction: column; justify-content: space-between; gap: 16px;
  padding: 20px 22px; border-radius: 14px;
  background: var(--surface); border: 1px solid var(--border);
  box-shadow: 0 1px 2px rgba(0,0,0,.04), 0 10px 30px rgba(0,0,0,.05);
}
.andet-deliverable-report {
  border-color: transparent;
  background:
    linear-gradient(var(--surface), var(--surface)) padding-box,
    linear-gradient(135deg, var(--accent), var(--accent-2, #06b6d4)) border-box;
  border: 1.5px solid transparent;
}
.andet-deliverable-kind {
  font-size: 0.7rem; font-weight: 700; letter-spacing: 0.12em; text-transform: uppercase;
  color: var(--accent); margin-bottom: 8px;
}
.andet-deliverable-notebook .andet-deliverable-kind { color: var(--accent-2, #06b6d4); }
.andet-deliverable-title { font-size: 1.12rem; font-weight: 700; color: var(--text); line-height: 1.25; }
.andet-deliverable-sub { font-size: 0.82rem; color: var(--text-muted); margin-top: 6px; line-height: 1.5; }
.andet-deliverable-actions { display: flex; flex-wrap: wrap; gap: 10px; }
.andet-deliverable-cta {
  appearance: none; cursor: pointer; font: inherit; font-size: 0.86rem; font-weight: 650;
  padding: 10px 18px; border-radius: 9px; border: 0;
  background: var(--accent); color: #fff;
  transition: filter .12s ease, transform .12s ease;
}
.andet-deliverable-cta:hover { filter: brightness(1.06); }
.andet-deliverable-cta:active { transform: translateY(1px); }
.andet-deliverable-cta:disabled { opacity: 0.6; cursor: default; }
.andet-deliverable-cta:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.andet-deliverable-cta-ghost {
  background: transparent; color: var(--accent);
  border: 1px solid var(--border-strong, var(--border));
}
.andet-deliverable-cta-ghost:hover { filter: none; background: var(--input-bg); }

/* Data-extract review — a collapsible disclosure so the frozen tables are
   available to review at the extract gate without dominating the page for the
   rest of the run (opened/collapsed by _andetLoadExtract). */
.andet-extract-summary {
  cursor: pointer; list-style: none; user-select: none;
  display: flex; align-items: baseline; gap: 10px; flex-wrap: wrap;
  padding: 9px 12px; border: 1px solid var(--border); border-radius: 7px;
  background: var(--surface);
}
.andet-extract-summary::-webkit-details-marker { display: none; }
.andet-extract-summary::before {
  content: '\25B6'; font-size: 0.66rem; color: var(--text-muted);
  transition: transform 0.12s ease;
}
.andet-extract-details[open] > .andet-extract-summary::before { transform: rotate(90deg); }
.andet-extract-summary-label { font-weight: 600; font-size: 0.86rem; color: var(--text); }
.andet-extract-body { padding-top: 10px; }
.andet-xmeta { font-size: 0.8rem; color: var(--text-muted); margin-bottom: 10px; }
.andet-xtable {
  border: 1px solid var(--border); border-radius: 7px;
  padding: 10px 12px; margin-bottom: 8px; background: var(--surface);
}
.andet-xtable-head { display: flex; justify-content: space-between; gap: 10px; flex-wrap: wrap; }
.andet-xtable-name { font-weight: 600; font-family: var(--mono, ui-monospace, monospace); font-size: 0.84rem; }
.andet-xtable-meta { font-size: 0.76rem; color: var(--text-muted); }
.andet-xsql > summary { cursor: pointer; font-size: 0.74rem; color: var(--accent-light, #a78bfa); margin-top: 6px; }
.andet-xpreview { overflow-x: auto; margin: 8px 0; border: 1px solid var(--border); border-radius: 6px; }
.andet-xpreview-cap { font-size: 0.7rem; color: var(--text-muted); padding: 5px 8px; border-bottom: 1px solid var(--border); }
.andet-xpreview-table { border-collapse: collapse; font-size: 0.74rem; width: max-content; min-width: 100%; }
.andet-xpreview-table th, .andet-xpreview-table td {
  padding: 4px 10px; text-align: left; white-space: nowrap;
  border-bottom: 1px solid var(--border); font-family: var(--mono, ui-monospace, monospace);
}
.andet-xpreview-table th { color: var(--text-muted); font-weight: 600; position: sticky; top: 0; background: var(--surface); }
.andet-xpreview-table tbody tr:last-child td { border-bottom: 0; }
.andet-xpreview-empty { font-size: 0.72rem; color: var(--text-muted); margin: 6px 0; font-style: italic; }
.andet-phase-error {
  margin: 8px 0 0; padding: 8px 10px; border-radius: 5px;
  background: var(--input-bg); color: var(--cad-fail);
  font-size: 0.72rem; line-height: 1.5; white-space: pre-wrap; overflow-x: auto;
}

.andet-steer-row { display: flex; gap: 8px; margin: 10px 0 6px; }
.andet-steer-row .chat-input { flex: 1; }
.andet-steers { display: flex; flex-direction: column; gap: 6px; margin-top: 8px; }
.andet-steer {
  padding: 9px 12px; border-radius: 6px;
  background: var(--surface); border: 1px solid var(--border);
}
.andet-steer-msg { font-size: 0.83rem; color: var(--text); line-height: 1.45; }

.andet-plan, .andet-prov {
  background: var(--surface); border: 1px solid var(--border);
  border-radius: 7px; padding: 11px 14px; margin-bottom: 8px;
}
.andet-plan summary, .andet-prov summary {
  cursor: pointer; font-size: 0.83rem; color: var(--text); font-weight: 600;
}
.andet-plan-table { margin-top: 12px; }
.andet-plan-table-name {
  font-size: 0.76rem; font-weight: 600; color: var(--accent-light);
  font-family: ui-monospace, monospace; margin-bottom: 5px;
}
.andet-sql {
  margin: 0; padding: 10px 12px; border-radius: 6px;
  background: var(--input-bg); color: var(--text-muted);
  font-size: 0.72rem; line-height: 1.55; white-space: pre-wrap;
  overflow-x: auto; font-family: ui-monospace, monospace;
}

.andet-artifact {
  padding: 13px 15px; margin-bottom: 9px; border-radius: 8px;
  background: var(--surface); border: 1px solid var(--border);
}
.andet-artifact-head {
  display: flex; align-items: baseline; gap: 10px; flex-wrap: wrap;
  margin-bottom: 8px;
}
.andet-artifact-name { font-size: 0.88rem; font-weight: 600; color: var(--text); }
.andet-artifact-kind {
  font-size: 0.68rem; text-transform: uppercase; letter-spacing: .05em;
  color: var(--text-subtle); border: 1px solid var(--border);
  border-radius: 4px; padding: 1px 6px;
}
.andet-stat-value {
  font-size: 1.6rem; font-weight: 700; color: var(--accent-light);
  font-variant-numeric: tabular-nums;
}
.andet-stat-units { font-size: 0.85rem; font-weight: 400; color: var(--text-muted); }
.andet-table-wrap { overflow-x: auto; }
.andet-table { border-collapse: collapse; font-size: 0.78rem; width: 100%; }
.andet-table th, .andet-table td {
  padding: 5px 10px; text-align: left; border-bottom: 1px solid var(--border);
  white-space: nowrap; font-variant-numeric: tabular-nums;
}
.andet-table th { color: var(--text-subtle); font-weight: 600; }
.andet-table td { color: var(--text); }

/* ── Critic verdict + assembled report ──
   The verdict is rendered both on the report gate (so a reviewer sees what the
   critic flagged before approving) and inside the report. Blocks read as hard
   stops, downgrades as softer. All the text inside is model-supplied and escaped
   in the JS. */
.andet-verdict {
  margin: 12px 0; padding: 12px 14px; border-radius: 8px;
  background: var(--input-bg); border: 1px solid var(--border);
  border-left: 3px solid var(--border-glow);
}
.andet-verdict-block, .andet-verdict.andet-verdict-block { border-left-color: var(--cad-fail); }
.andet-verdict-revise { border-left-color: var(--accent-2); }
.andet-verdict-pass { border-left-color: var(--cad-pass); }
.andet-verdict-head {
  display: flex; align-items: baseline; gap: 10px; flex-wrap: wrap; margin-bottom: 8px;
}
.andet-verdict-label { font-size: 0.86rem; font-weight: 700; color: var(--text); }
.andet-verdict-status {
  font-size: 0.68rem; text-transform: uppercase; letter-spacing: .05em;
  border-radius: 4px; padding: 1px 7px; border: 1px solid var(--border); color: var(--text-muted);
}
.andet-verdict-status-block { color: #fff; background: var(--cad-fail); border-color: transparent; }
.andet-verdict-status-revise { color: #fff; background: var(--accent-2); border-color: transparent; }
.andet-verdict-status-pass { color: #fff; background: var(--cad-pass); border-color: transparent; }
.andet-verdict-group-label {
  font-size: 0.7rem; text-transform: uppercase; letter-spacing: .05em;
  color: var(--text-subtle); font-weight: 700; margin: 8px 0 4px;
}
.andet-verdict-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 6px; }
.andet-verdict-item {
  padding: 8px 10px; border-radius: 6px; background: var(--surface); border: 1px solid var(--border);
  border-left: 3px solid var(--border-glow);
}
.andet-verdict-item.andet-verdict-block { border-left-color: var(--cad-fail); }
.andet-verdict-item.andet-verdict-downgrade { border-left-color: var(--accent-2); }
.andet-verdict-claim { font-size: 0.83rem; color: var(--text); font-weight: 600; line-height: 1.45; }
.andet-verdict-reason { font-size: 0.76rem; color: var(--text-muted); line-height: 1.5; margin: 3px 0; }

.andet-report-meta { margin-bottom: 10px; }
.andet-report-section { margin: 14px 0 0; }
.andet-report-section-title {
  font-size: 0.74rem; text-transform: uppercase; letter-spacing: .05em;
  color: var(--text-subtle); font-weight: 700; margin-bottom: 6px;
}
.andet-report-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 6px; }
.andet-report-item {
  padding: 9px 12px; border-radius: 6px; background: var(--surface); border: 1px solid var(--border);
}
.andet-report-item-head { display: flex; align-items: baseline; gap: 10px; flex-wrap: wrap; }

/* ── Notebook view ──
   Read-only. The markdown cells the analyst writes for a steer, an escalation
   or a truncation notice are the ones a reader most needs to spot, so each
   carries a left rule in the accent rather than blending into the narrative. */
.andet-notebook {
  border: 1px solid var(--border); border-radius: 8px;
  background: var(--surface); overflow: hidden; margin-top: 10px;
}
.andet-cell { padding: 13px 16px; border-bottom: 1px solid var(--border); }
.andet-cell:last-child { border-bottom: none; }
.andet-cell-md { color: var(--text); font-size: 0.86rem; line-height: 1.6; }
.andet-cell-md h3, .andet-cell-md h4, .andet-cell-md h5, .andet-cell-md h6 {
  margin: 0 0 6px; color: var(--text);
}
.andet-cell-md p { margin: 0 0 8px; max-width: 74ch; }
.andet-cell-md p:last-child { margin-bottom: 0; }
.andet-cell-steer, .andet-cell-escalation, .andet-cell-truncated {
  border-left: 3px solid var(--accent);
  background: color-mix(in srgb, var(--accent) 6%, transparent);
}
.andet-quote {
  margin: 0; padding: 2px 0 2px 12px;
  border-left: 2px solid var(--border-glow); color: var(--text-muted);
}
.andet-code, .andet-out {
  margin: 0; padding: 10px 12px; border-radius: 6px;
  background: var(--input-bg); font-family: ui-monospace, monospace;
  font-size: 0.73rem; line-height: 1.55; white-space: pre-wrap; overflow-x: auto;
}
.andet-code { color: var(--text); }
.andet-out { color: var(--text-muted); margin-top: 7px; }
.andet-out-error { color: var(--cad-fail); }
.andet-out-img { display: block; max-width: 100%; margin-top: 9px; border-radius: 5px; }

/* ── Pathway integrity card (Somatic dashboard) ──
   The at-a-glance answer to "can this tumour still present peptide?". Sits at
   the top of the Overview, above the variant summary, because a broken
   antigen-presentation pathway reframes everything below it.

   Tone names (critical/warn/note/ok/muted) come from
   ux.config.json:pathway_integrity, not from the status strings, so a status
   can be re-toned without touching CSS. Every tone pairs a hue with a distinct
   TEXT label — the badge never carries meaning by colour alone. */

.pw-card { margin-top: 0; }

.pw-badge {
  display: inline-block;
  margin-left: 10px;
  padding: 2px 9px;
  border-radius: 4px;
  font-size: 0.72rem;
  font-weight: 700;
  letter-spacing: 0.03em;
  text-transform: uppercase;
  vertical-align: middle;
}
.pw-badge--critical { background: #dc2626; color: #fff; }
.pw-badge--warn     { background: #f59e0b; color: #1a1545; }
.pw-badge--note     { background: var(--surface-2); color: var(--text-muted); border: 1px solid var(--border-glow); }
.pw-badge--ok       { background: #059f75; color: #fff; }
.pw-badge--muted    { background: var(--surface-2); color: var(--text-subtle); border: 1px dashed var(--border-glow); }

.pw-tier {
  margin-left: 8px;
  font-size: 0.72rem;
  font-weight: 600;
  color: var(--text-muted);
  vertical-align: middle;
}

.pw-body { padding: 4px 16px 14px; }

.pw-subtitle {
  margin: 0 0 8px;
  font-size: 0.82rem;
  color: var(--text-subtle);
}
.pw-summary {
  margin: 0 0 12px;
  font-size: 0.92rem;
  font-weight: 600;
  color: var(--text);
}

/* Evidence strip — which axes actually ran. A missing axis renders as an
   explicit "—" chip with the reason on hover, so absence of evidence never
   reads as evidence of absence. */
.pw-evidence {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  margin-bottom: 14px;
}
.pw-chip {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  padding: 3px 9px;
  border-radius: 12px;
  font-size: 0.75rem;
  border: 1px solid var(--border);
  background: var(--surface-2);
  color: var(--text-muted);
}
.pw-chip-mark { font-weight: 700; }
.pw-chip--on  { color: var(--text); border-color: var(--border-glow); }
.pw-chip--on .pw-chip-mark { color: #059f75; }
.pw-chip--off { border-style: dashed; color: var(--text-subtle); }
.pw-chip--info { border-color: var(--border-glow); color: var(--text-muted); }

.pw-gene-table {
  width: 100%;
  border-collapse: collapse;
  font-size: 0.82rem;
  margin-bottom: 12px;
}
.pw-gene-table th {
  text-align: left;
  font-size: 0.7rem;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--text-subtle);
  border-bottom: 1px solid var(--border);
  padding: 5px 8px;
  font-weight: 600;
}
.pw-gene-table td {
  padding: 5px 8px;
  border-bottom: 1px solid var(--border);
  color: var(--text-muted);
  vertical-align: top;
}
/* A left rule keyed to the finding — carries the same information as the
   text label, for scanning down the column. */
.pw-gene-row--critical td:first-child { box-shadow: inset 3px 0 0 #dc2626; }
.pw-gene-row--warn     td:first-child { box-shadow: inset 3px 0 0 #f59e0b; }
.pw-gene-row--note     td:first-child { box-shadow: inset 3px 0 0 var(--border-glow); }

.pw-gene-sym  { font-weight: 700; color: var(--text); font-family: ui-monospace, monospace; }
.pw-gene-role { color: var(--text-subtle); font-size: 0.78rem; }
.pw-gene-nums { color: var(--text-subtle); font-family: ui-monospace, monospace; font-size: 0.76rem; }

.pw-call { font-weight: 600; }
.pw-call--critical { color: #dc2626; }
.pw-call--warn     { color: #b45309; }
.pw-call--note     { color: var(--text-muted); }
.pw-call--ok       { color: var(--text-subtle); font-weight: 400; }
html[data-theme="dark"] .pw-call--warn { color: #f59e0b; }
html[data-theme="dark"] .pw-call--critical { color: #f87171; }

.pw-subclonal {
  font-size: 0.7rem;
  padding: 1px 5px;
  border-radius: 3px;
  background: var(--surface-2);
  border: 1px solid var(--border-glow);
  color: var(--text-subtle);
}

.pw-concordance { margin: 0 0 12px; font-size: 0.82rem; }
.pw-concordance--muted { color: var(--text-subtle); }
.pw-concordance--warn {
  color: #b45309;
  background: #fffbeb;
  border: 1px solid #fde68a;
  border-radius: 6px;
  padding: 8px 11px;
}
html[data-theme="dark"] .pw-concordance--warn {
  color: #fbbf24;
  background: #2a1f08;
  border-color: #78350f;
}

/* Caveats are rendered OPEN and inline, never behind a disclosure: they are the
   reasons a reader should distrust the verdict, and a verdict whose limits are
   one click away is a verdict that gets quoted without them. */
.pw-caveats {
  border-left: 3px solid var(--border-glow);
  padding: 2px 0 2px 11px;
  margin-bottom: 10px;
}
.pw-caveats-title {
  font-size: 0.72rem;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--text-subtle);
  font-weight: 600;
  margin-bottom: 4px;
}
.pw-caveats ul { margin: 0; padding-left: 16px; }
.pw-caveats li {
  font-size: 0.8rem;
  color: var(--text-muted);
  margin-bottom: 4px;
  line-height: 1.45;
}

.pw-notes summary {
  cursor: pointer;
  font-size: 0.78rem;
  color: var(--text-subtle);
}
.pw-notes p {
  font-size: 0.8rem;
  color: var(--text-muted);
  line-height: 1.5;
  margin: 8px 0 0;
}

/* Title + badge + tier as ONE flex child — .vep-section-header is
   space-between, so leaving them as siblings scatters them across the row. */
.pw-head {
  display: inline-flex;
  align-items: center;
  gap: 0;
  min-width: 0;
}

/* The clean components, collapsed to one line. They are still NAMED rather than
   merely counted: "8 others assessed" invites the question "which?", and the
   answer is what tells a reader the panel was complete. */
.pw-quiet {
  margin: 2px 0 12px;
  font-size: 0.78rem;
  color: var(--text-subtle);
  line-height: 1.5;
}
.pw-quiet-count {
  font-weight: 700;
  color: var(--text-muted);
}
.pw-quiet-genes {
  font-family: ui-monospace, monospace;
  font-size: 0.74rem;
}

/* Qualifier beside a status badge — e.g. an INTACT that covers only the genetic
   axis. Deliberately quiet: it narrows the claim, it is not a second verdict. */
.pw-qualifier {
  margin-left: 8px;
  font-size: 0.68rem;
  font-weight: 500;
  text-transform: none;
  letter-spacing: 0;
  color: var(--text-subtle);
  border-bottom: 1px dotted var(--border-glow);
  cursor: help;
}

/* "Not computed yet" / "no tumour-normal run" are PRECONDITIONS, not failures.
   The shared .sp-error paints them red, which reads as something having gone
   wrong with a run that is simply absent. */
.pw-empty {
  color: var(--text-muted);
  font-size: 0.88rem;
  line-height: 1.5;
}

/* Allele-specific expression tag. Carries its own weight next to the bulk
   numbers because it answers a different question — whether the mutant allele
   is transcribed at all — and does so without any baseline behind it. */
.pw-ase {
  margin-left: 6px;
  padding: 1px 6px;
  border-radius: 3px;
  font-size: 0.7rem;
  font-family: ui-monospace, monospace;
  border: 1px solid var(--border-glow);
  color: var(--text-muted);
  cursor: help;
}
.pw-ase--mutant_degraded  { color: #b45309; border-color: #fde68a; }
.pw-ase--mutant_expressed { color: var(--text-muted); }
.pw-ase--not_expressed    { color: #b45309; border-color: #fde68a; }
html[data-theme="dark"] .pw-ase--mutant_degraded,
html[data-theme="dark"] .pw-ase--not_expressed { color: #fbbf24; border-color: #78350f; }

/* Qualifier riding on an evidence chip — e.g. an expression axis that ran but
   could not correct for dilution. Quieter than the chip label: it narrows what
   the tick means, it is not a second axis. */
.pw-chip-note {
  margin-left: 5px;
  padding-left: 5px;
  border-left: 1px solid var(--border-glow);
  font-size: 0.68rem;
  color: var(--text-subtle);
}
/* Overview map — sized by row count in the renderer; the container just needs to
   not collapse before Plotly draws. */
.hs-matrix { width: 100%; min-height: 460px; }

/* Scope chips on the scorecard — which indication a number is measured against.
   The muted variant marks the cards that deliberately do NOT follow the scope. */
.hs-scope-chip {
  display: inline-block; border-radius: 999px; padding: 0 7px; margin-left: 6px;
  font-size: 0.6rem; font-weight: 700; letter-spacing: 0.03em;
  text-transform: none; background: #3987e5; color: #fff; vertical-align: middle;
}
.hs-scope-chip-all { background: transparent; color: var(--muted, #64748b); border: 1px solid var(--border, #cbd5e1); }

/* ══ Tools tab ════════════════════════════════════════════════════════════
   A catalogue of every pipeline and workflow, each expanding into its step
   graph. Row mechanics mirror the Benchmarks master table; the graph canvas
   is shorter than the Run page's 520px because several can be open at once. */

.tools-header { margin-bottom: 14px; }
.tools-title {
  margin: 0 0 4px;
  font-size: 1.05rem;
  font-weight: 700;
  color: var(--text);
}
.tools-intro {
  margin: 0;
  color: var(--text-muted);
  font-size: 0.84rem;
  line-height: 1.6;
}
/* Experiment filter: a row of toggles laid out above the tables, so the reader
   sees every experiment momix runs without opening a control. Each toggle wears
   the same .sp-run-experiment-<slug> colour as the badge in the Experiments
   column — switch one on and its colour is what you scan for in the rows. An
   OFF toggle is the same pill desaturated, which reads as "available, not
   chosen"; hiding the colour entirely would break that link. */
.tools-filter {
  display: flex; align-items: center; gap: 10px 12px; flex-wrap: wrap;
  margin: 0 0 14px; padding: 11px 13px;
  background: var(--surface); border: 1px solid var(--border); border-radius: 8px;
}
.tools-filter[hidden] { display: none; }
.tools-filter-label {
  font-size: 0.72rem; font-weight: 700; letter-spacing: 0.06em;
  text-transform: uppercase; color: var(--text-subtle); white-space: nowrap;
}
.tools-filter-chips { display: flex; flex-wrap: wrap; gap: 7px; }

.tools-chip {
  padding: 4px 11px; font-size: 0.7rem; font-family: inherit;
  cursor: pointer; transition: filter .12s, box-shadow .12s;
}
/* Not chosen: the colour is still there, just drained — so the bar reads as a
   palette of what is available rather than a row of dead grey buttons. */
.tools-chip[aria-pressed="false"] { filter: grayscale(0.45) opacity(0.6); }
.tools-chip[aria-pressed="false"]:hover { filter: none; }
.tools-chip[aria-pressed="true"] { box-shadow: 0 0 0 1px currentColor inset; }
.tools-chip:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

.tools-filter-clear {
  margin-left: auto; padding: 4px 10px;
  background: none; color: var(--text-muted);
  border: 1px solid var(--border); border-radius: 5px;
  font-size: 0.72rem; font-family: inherit; cursor: pointer;
}
.tools-filter-clear:hover { color: var(--text); border-color: var(--text-subtle); }
.tools-filter-clear[hidden] { display: none; }

.tools-status { color: var(--text-muted); font-size: 0.82rem; margin-bottom: 8px; }
.tools-empty  { color: var(--text-subtle); font-size: 0.85rem; padding: 18px 4px; }

/* Workflows / Pipelines sections. The second gets extra top margin so the two
   tables read as distinct groups rather than one continuous list. */
.tools-section { margin-top: 8px; }
.tools-section + .tools-section { margin-top: 30px; }
.tools-section-title {
  margin: 0 0 2px; font-size: 1rem; font-weight: 700; color: var(--text);
}
.tools-section-caption {
  margin: 0 0 10px; color: var(--text-subtle); font-size: 0.78rem; line-height: 1.5;
}

.tools-table { table-layout: auto; }
.tools-table th.tools-toggle-col,
.tools-table td.tools-toggle-col { width: 34px; padding-right: 0; }
/* Keep the divider between tools: the shared .sp-table rule drops the last
   row's border inside EVERY tbody, and each tool is its own tbody. */
.tools-group + .tools-group > .tools-row > td { border-top: 1px solid var(--border); }

.tools-row { cursor: pointer; }
.tools-row > td { vertical-align: middle; }
.tools-row.is-open > td { background: var(--surface-2); border-bottom-color: transparent; }

.tools-toggle {
  background: none; border: none; padding: 0;
  color: var(--text-muted); font-size: 0.7rem; line-height: 1; cursor: pointer;
}
.tools-toggle:hover { color: var(--accent); }
.tools-toggle:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

.tools-name { display: block; color: var(--text); font-weight: 600; font-size: 0.86rem; }
.tools-id   { display: block; color: var(--text-subtle); font-size: 0.7rem; font-family: monospace; margin-top: 1px; }

/* SPECIFICITY, not preference: the shared `.sp-table td` rule (0,1,1) sets
   white-space: nowrap, so a bare `.tools-exp-cell` (0,1,0) LOSES and these
   three rules never applied at all. The visible cost was the inputs table:
   its alias chips could not wrap, so it pinned to its ~1190px min-content
   width, the colspan detail cell propagated that to the outer table, and
   expanding a row with many aliases (single-cell-pipeline) took the whole
   catalogue to 1691px inside a 1224px container — everything from LATEST RUN
   rightwards reachable only by scrolling the table sideways. Qualify with
   .sp-table td to win, the way `.sp-table td.tools-detail-cell` below already
   does. Measured after the fix: 1224px, no horizontal scroll. */
.sp-table td.tools-exp-cell { white-space: normal; }
/* Specimen chips (Tumor / Normal / Mixture), derived from the input taxonomy.
   Muted, distinguishable hues — NOT the status palette; a specimen is not a
   good/bad signal. */
.sp-table td.tools-spec-cell { white-space: normal; }
.tools-spec {
  display: inline-block; margin: 1px 4px 1px 0; padding: 1px 7px;
  border-radius: 4px; background: var(--surface-2); border: 1px solid var(--border);
  color: var(--text-muted); font-size: 0.7rem; white-space: nowrap;
}
.tools-spec--tumor   { border-color: #d98a8a55; color: #e0a3a3; }
.tools-spec--normal  { border-color: #7fa8d955; color: #9cc0e8; }
.tools-spec--mixture { border-color: #b79cf755; color: #c3b0f7; }

.tools-inputs-count { color: var(--text-muted); font-size: 0.82rem; }

.tools-detail-row > td { background: var(--surface-2); }
/* .sp-table td sets white-space:nowrap, which would stretch the detail cell to
   the width of its longest prose line and force the whole table to scroll. It
   is (0,1,1) so the element selector has to be carried here to outrank it. */
.sp-table td.tools-detail-cell { padding: 0 !important; white-space: normal; }
.tools-detail { padding: 14px 16px 18px; }

.tools-desc {
  margin: 0 0 10px; max-width: 82ch;
  color: var(--text); font-size: 0.84rem; line-height: 1.6;
}
.tools-note {
  margin: 0 0 12px; max-width: 82ch; padding: 7px 10px;
  border-left: 2px solid var(--border-glow); border-radius: 0 4px 4px 0;
  background: var(--surface);
  color: var(--text-muted); font-size: 0.78rem; line-height: 1.55;
}
.tools-subhead {
  margin: 14px 0 6px;
  font-size: 0.68rem; font-weight: 700; letter-spacing: 0.06em; text-transform: uppercase;
  color: var(--text-subtle);
}
.tools-subhead:first-child { margin-top: 0; }

/* Collapsible Inputs / Outputs blocks. The `<details>` carries the block's
   vertical rhythm, not the summary: the summary is the first child of the
   details, so `.tools-subhead:first-child` above would zero its top margin and
   run the blocks together. */
.tools-collapsible { margin: 14px 0 6px; }
.tools-collapsible:first-child { margin-top: 0; }
/* Caps, matching the plain `.tools-subhead` blocks it sits beside (RECENT
   ACTIVITY, STEPS) rather than the title case collapsible headers use
   elsewhere: on this page both kinds sit in one column, and alternating cases
   read as two levels of hierarchy where there is only one. Inheriting
   .tools-subhead's size and tracking with them. */
.tools-collapsible-head {
  display: flex;
  align-items: center;
  gap: 6px;
  margin: 0 0 6px;
  cursor: pointer;
  list-style: none;
  user-select: none;
}
.tools-collapsible-head::-webkit-details-marker { display: none; }
.tools-collapsible-head::after {
  content: "\25B2";
  font-size: 0.6rem;
  opacity: 0.5;
  transition: transform 0.2s;
}
.tools-collapsible:not([open]) .tools-collapsible-head::after {
  transform: rotate(180deg);
}
.tools-hint { margin: 0 0 8px; color: var(--text-subtle); font-size: 0.76rem; line-height: 1.55; }

.tools-inputs-table { margin-bottom: 4px; }
.tools-inputs-table td { vertical-align: top; }
.sp-table td .tools-cands, .sp-table td.tools-cands { white-space: normal; }
.tools-alias, .tools-param {
  display: inline-block; margin: 1px 3px 1px 0; padding: 1px 5px;
  border-radius: 3px; background: var(--surface); border: 1px solid var(--border);
  color: var(--text-muted); font-size: 0.7rem; font-family: monospace;
}
.tools-optional {
  margin-left: 6px; padding: 0 5px; border-radius: 3px;
  background: var(--surface); border: 1px solid var(--border);
  color: var(--text-subtle); font-size: 0.65rem; text-transform: uppercase; letter-spacing: 0.04em;
}
.tools-none { color: var(--text-subtle); }

/* Latest run status on the collapsed catalogue row. */
.tools-latest-cell { white-space: nowrap; }
.tools-latest { display: inline-flex; align-items: center; gap: 8px; }
.tools-latest-date { color: var(--text-subtle); font-size: 0.72rem; }
.tools-latest-none { color: var(--text-subtle); font-size: 0.76rem; font-style: italic; }

/* Recent-activity table inside the expanded detail. */
.tools-activity-table { margin-bottom: 4px; }
.tools-activity-table td { vertical-align: middle; }
.tools-activity-more { margin-top: 6px; }

/* Shorter than .dag-cy's 520px: several graphs can be open at once, and no
   pipeline here exceeds seven steps. */
.tools-dag-cy { height: 340px; border-radius: 8px; border-top: 1px solid var(--border); }
/* When the graph cannot be shown, the same box carries the reason rather than
   collapsing — an empty bordered rectangle reads as a rendering failure. */
.tools-dag-msg {
  display: flex; align-items: center; justify-content: center;
  height: auto; min-height: 64px; padding: 18px 20px;
  color: var(--text-subtle); font-size: 0.8rem; text-align: center; line-height: 1.55;
}

/* Top-K slider in the Overview's cell-mode group. Disabled in whole-gene mode
   rather than hidden, so the control row does not reflow when the mode flips. */
.hs-topk-slider { width: 92px; accent-color: #3987e5; vertical-align: middle; cursor: pointer; }
.hs-topk-slider:disabled { opacity: 0.4; cursor: not-allowed; }
.hs-topk-value {
  font-size: 0.72rem; font-weight: 600; color: var(--muted, #64748b);
  min-width: 3.4em; display: inline-block; font-variant-numeric: tabular-nums;
}

/* ── Cells tab (single-cell UMAP) ── */
.sp-cells-stats { display: flex; gap: 14px; flex-wrap: wrap; margin: 10px 0 4px; }
/* The reference caption under the tiles: a full-width line, quieter than the
   numbers, so "which vocabulary" is read before the composition is compared. */
.sp-cells-reference { flex-basis: 100%; font-size: 0.74rem; color: var(--text-subtle); margin-top: -2px; }
/* A caveat inside the caption carries its explanation on hover, not in the
   line: the line is label · value, the paragraph is a tooltip. */
.sp-cells-reference abbr { text-decoration: underline dotted; cursor: help; }
.sp-cells-stat {
  background: var(--surface); border: 1px solid var(--border);
  border-radius: 10px; padding: 7px 13px;
}
.sp-cells-stat b { display: block; font-size: 1.05rem; font-weight: 650; color: var(--text); }
.sp-cells-stat span {
  font-size: 0.68rem; color: var(--text-muted);
  text-transform: uppercase; letter-spacing: 0.04em;
}
/* The control bar's dock: pinned to the top of the viewport while any of
   .sp-cells-figure-scope (controls, composition, embedding) is on screen, and
   released once the scope has scrolled past — sticky stops at its containing
   block's bottom edge, so no scroll listener is needed. The dock, not the bar,
   is sticky so the drawer can hang off it, and it paints the page background
   over the gutter so the figures do not show through above the bar. The
   vertical spacing the bar used to carry as margin lives here as padding for
   the same reason: a margin sits outside the stuck box and the plot would
   scroll through it. */
.sp-cells-controls-dock {
  position: sticky; top: 0; z-index: 2;
  padding: 10px 0 12px; background: var(--bg);
}
.sp-cells-controls {
  display: flex; align-items: center; gap: 18px; flex-wrap: wrap;
  background: var(--surface); border: 1px solid var(--border);
  border-radius: 10px; padding: 9px 14px; margin: 0;
}
.sp-cells-controls label {
  display: flex; align-items: center; gap: 7px;
  font-size: 0.76rem; color: var(--text-muted);
}
.sp-cells-controls input[type="range"] { accent-color: var(--accent); max-width: 130px; }
/* Point size + Opacity wrap as ONE unit. The gap matches the bar's own, so
   the pair is indistinguishable from two loose items until the row runs out —
   at which point both move, rather than Opacity going alone. */
.sp-cells-style-pair {
  display: inline-flex; align-items: center; gap: 18px; flex-wrap: nowrap;
}
/* A control that does not apply on the current axis stays where it is,
   dimmed: removing it re-wraps the bar and moves the plot. */
.sp-cells-controls label.is-disabled { opacity: 0.45; }
/* The Programs button. `is-on` is the drawer being OPEN (inherited from
   .sp-cm-toggle); `is-active` is the axis doing something, which is a
   different fact and must read while the drawer is shut — the button is then
   the only thing on screen saying the figures are cut. */
.sp-cells-program-toggle.is-active {
  border-color: var(--accent-light); color: var(--accent-light); font-variant-numeric: tabular-nums;
}
/* The program drawer. Rows are a two-column grid so every control starts at
   the same x whatever its label's length — the same reason the marker table
   puts its grid on an inner wrapper. */
.sp-cp-row {
  display: grid; grid-template-columns: 5.5em auto auto auto; align-items: center;
  gap: 10px; justify-content: start; font-size: 0.76rem; color: var(--text-muted);
}
.sp-cp-row.is-disabled { opacity: 0.45; }
.sp-cp-label { color: var(--text-subtle); }
.sp-cp-set { color: var(--text); font-weight: 600; font-size: 0.72rem; }
/* The rule under the controls spans the DRAWER, while the prose below it
   keeps a readable measure. Both on one element meant the hairline stopped
   wherever 62ch happened to land and read as a half-drawn border. */
.sp-cp-rows {
  display: grid; gap: 10px;
  padding-bottom: 13px; border-bottom: 1px solid var(--border);
}
/* The calls drawer's provenance line: purity, and the different-count caveat
   with its tooltip. Above the note and in the body colour, because it is a
   fact about THIS run rather than guidance about the control. */
.sp-cp-prov { margin-top: 11px; color: var(--text); font-size: 0.74rem; }
.sp-cp-prov abbr { border-bottom: 1px dotted var(--text-subtle); cursor: help; text-decoration: none; }
.sp-cp-note {
  margin-top: 11px; color: var(--text-muted); font-size: 0.72rem;
  line-height: 1.5; max-width: 62ch;
}
/* The floor's readout holds its width ("any" / "60th" / "12.3%") so a drag
   cannot re-wrap the bar and move the figures under it. */
.sp-cells-floor-val { display: inline-block; min-width: 3.4em; text-align: right; }

/* ── Marker editor ──
 *
 * The sub-menu behind the lineage axis: identity -> genes, editable, with the
 * cells recolouring on every change. OVERLAID on the figures rather than
 * inserted above them, because a panel that pushes the plot down moves the
 * figure for a control that only changes its colours — the same rule the axis
 * caption was moved below the plot for.
 *
 * Deliberately NOT a card per row: this is a dense table a reviewer reads
 * across, and one radius and one shadow per row would flatten the one
 * hierarchy that matters — compartment first, then the lineages it contains. */
.sp-cm-toggle {
  background: var(--surface-2); color: var(--text); border: 1px solid var(--border);
  border-radius: 6px; padding: 3px 10px; font: inherit; font-size: 0.74rem; cursor: pointer;
}
.sp-cm-toggle:hover { border-color: var(--accent-light); }
/* The per-axis tool slot: Markers and Calls stacked in ONE grid cell, so the
   slot is always as wide as the wider of them and the bar cannot re-wrap when
   the axis changes which one is offered. Hidden with `visibility`, which keeps
   the box (and drops the button from the tab order and the a11y tree);
   `display:none` would collapse the slot and bring the 35px jump back. */
.sp-cells-axis-tool { display: inline-grid; justify-items: start; align-items: center; }
.sp-cells-axis-tool > .sp-cm-toggle { grid-area: 1 / 1; }
.sp-cm-toggle.is-hidden { visibility: hidden; }
.sp-cm-toggle.is-on { border-color: var(--accent-light); color: var(--accent-light); }
.sp-cm-panel {
  position: absolute; z-index: 5; left: 0; right: 0;
  background: var(--surface); border: 1px solid var(--border); border-radius: 10px;
  padding: 12px 16px 14px; margin-top: -6px;
  box-shadow: 0 12px 36px rgba(0, 0, 0, 0.32); font-size: 0.78rem;
}
/* The sticky dock is the positioning context (sticky establishes one), so
   the drawer hangs under the control bar wherever the bar is pinned. */
.sp-cm-panel.is-working { opacity: 0.72; }
.sp-cm-head { display: flex; align-items: baseline; gap: 10px; flex-wrap: wrap; margin-bottom: 10px; }
.sp-cm-title { font-size: 0.68rem; letter-spacing: 0.06em; text-transform: uppercase;
               color: var(--text-muted); font-weight: 600; }
.sp-cm-set { color: var(--text); font-weight: 600; }
.sp-cm-badge { font-size: 0.66rem; border: 1px solid var(--border); border-radius: 999px;
               padding: 1px 8px; color: var(--text-muted); }
.sp-cm-badge.is-def { color: var(--accent-light); border-color: var(--accent-light); }
/* The one warning colour on this tab, shared with .sp-cq-banner.is-warn. */
.sp-cm-badge.is-stat { color: #f97316; border-color: #f97316; }
/* NOT `overflow-x: auto`: a scroll container on one axis clips the other (auto
   with visible is not a legal pair, so visible computes to auto), and the last
   row's gene suggestions — which hang below the table — were cut off at its
   bottom edge. `clip` is the one value that leaves overflow-y visible. The
   table is width:100% with wrapping cells, so it never needs to scroll. */
.sp-cm-table-wrap { overflow-x: clip; overflow-y: visible; }
.sp-cm-table { border-collapse: collapse; width: 100%; }
.sp-cm-table th {
  text-align: left; font-size: 0.66rem; letter-spacing: 0.08em; text-transform: uppercase;
  color: var(--text-subtle); font-weight: 600; padding: 4px 12px 6px 0;
  border-bottom: 1px solid var(--border); white-space: nowrap;
}
.sp-cm-table td { padding: 9px 12px 9px 0; border-bottom: 1px solid var(--border); vertical-align: middle; }
.sp-cm-table tr:last-child td { border-bottom: 0; }
/* Swatch column, then text — as a GRID, so the name and its sub-label share
   one text column and every row's name starts at the same x whether or not it
   has a colour. The sub-label used to clear the swatch with a hand-counted
   16px indent, which was right for a coloured row and wrong for the
   compartment, whose swatch does not exist: its name sat left of the others
   and its sub-label hung right of its own name. */
/* ⚠ The grid goes on an INNER wrapper, never on the <td>. `display: grid` on
   a table cell drops its table-cell box, and the whole row collapses: the
   rule select falls to its own line and every column after it loses its
   alignment. */
.sp-cm-identity { white-space: nowrap; }
.sp-cm-identity-grid {
  display: inline-grid; grid-template-columns: 16px auto; align-items: baseline;
}
.sp-cm-identity b { font-weight: 600; grid-column: 2; }
.sp-cm-sw {
  width: 9px; height: 9px; border-radius: 50%; display: inline-block;
  grid-column: 1; grid-row: 1; align-self: center;
}
/* Holds the column open for a row with no colour of its own. */
.sp-cm-sw.is-empty { background: none; }
.sp-cm-sub { grid-column: 2; color: var(--text-subtle); font-size: 0.68rem; }
/* Select and floor on one line, the hint wrapping beneath them rather than
   pushing the floor onto a second row — the rows must stay scannable across,
   and a long hint on one row otherwise shifts that row's controls out of
   line with every other row's. */
.sp-cm-rule-cell { display: flex; align-items: flex-start; gap: 8px; flex-wrap: wrap; }
.sp-cm-rule-cell .sp-cm-rule { margin-top: 1px; }
.sp-cm-floor-wrap { max-width: 260px; }
.sp-cm-hint { white-space: normal; }
.sp-cm-rule { font-size: 0.74rem; padding: 3px 8px; }
.sp-cm-floor { display: inline-flex; align-items: center; gap: 5px; color: var(--text-muted); font-size: 0.72rem; }
/* The unit sits after the number, not in the label — a measurement reads
   "floor 2.3 log1p CP10K", not "floor log1p CP10K 2.3". */
.sp-cm-unit-suffix { color: var(--text-subtle); font-size: 0.7rem; white-space: nowrap; }
/* The floor, its unit and what it means in molecules — stacked, because the
   number alone says nothing about the scale it is on. */
.sp-cm-floor-wrap { display: inline-flex; flex-direction: column; gap: 1px; }
/* An explanation that must be DISCOVERABLE without being read every time —
   the scale the floor is on, what each provenance pill claims. Same treatment
   .sp-meta-hint gives the same problem: the cursor says there is more, and a
   dotted underline advertises it on running text.
   ⚠ NOT on a pill. A pill already carries a border of its own, and a dotted
   rule tucked under that border reads as a rendering fault rather than as an
   affordance — the pill's own outline is the mark that it is a thing you can
   point at, so it needs nothing added. */
.sp-cm-unit-hint[title]:not([title=""]) {
  cursor: help;
  text-decoration: underline dotted var(--text-subtle);
  text-underline-offset: 3px;
}
.sp-cm-badge.sp-cm-unit-hint[title] { text-decoration: none; }
.sp-cm-hint { color: var(--text-muted); font-size: 0.68rem; }
.sp-cm-floor-input {
  width: 58px; background: var(--input-bg); color: var(--text); border: 1px solid var(--border);
  border-radius: 5px; padding: 2px 5px; font: inherit; font-size: 0.74rem;
  font-variant-numeric: tabular-nums;
}
.sp-cm-genes { display: flex; flex-wrap: wrap; gap: 5px; align-items: center; }
.sp-cm-gene {
  display: inline-flex; align-items: center; gap: 4px;
  font-family: "SFMono-Regular", Consolas, monospace; font-size: 0.74rem;
  background: var(--surface-2); border: 1px solid var(--border); border-radius: 5px; padding: 1px 4px 1px 7px;
}
/* Declared and not in this matrix — a fact about the run, not an error, so it
   is drawn as a weaker version of the same chip rather than in a warning hue. */
.sp-cm-gene.is-absent { border-style: dashed; color: var(--text-subtle); }
.sp-cm-x { background: none; border: 0; color: var(--text-subtle); font-size: 0.85rem; line-height: 1;
           cursor: pointer; padding: 0 2px; }
.sp-cm-x:hover { color: var(--text); }
.sp-cm-add { display: inline-flex; align-items: center; gap: 4px; color: var(--text-subtle);
             border: 1px dashed var(--border); border-radius: 5px; padding: 1px 7px; font-size: 0.72rem; }
.sp-cm-add-input {
  background: transparent; border: 0; color: var(--text); width: 62px;
  font-family: "SFMono-Regular", Consolas, monospace; font-size: 0.74rem;
}
.sp-cm-add-input:focus { outline: none; }
/* The completion list. Anchored to the add field and floated, so a list of
   eight does not push the table's rows apart while the reader types. */
.sp-cm-add { position: relative; }
.sp-cm-sugg-list {
  position: absolute; top: calc(100% + 4px); left: 0; z-index: 6; min-width: 180px;
  display: flex; flex-direction: column; background: var(--surface);
  border: 1px solid var(--border); border-radius: 8px; padding: 3px;
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.28); max-height: 232px; overflow-y: auto;
}
/* ⚠ `display: flex` outranks the user agent's `[hidden] { display: none }`,
   so the empty list painted as a thin outlined strip under every add field.
   The attribute has to be honoured explicitly wherever a display is set. */
.sp-cm-sugg-list[hidden] { display: none; }
.sp-cm-sugg {
  display: flex; align-items: baseline; justify-content: space-between; gap: 12px;
  background: none; border: 0; border-radius: 5px; cursor: pointer; text-align: left;
  font-family: "SFMono-Regular", Consolas, monospace; font-size: 0.76rem;
  color: var(--text); padding: 4px 7px;
}
.sp-cm-sugg.is-active, .sp-cm-sugg:hover { background: var(--surface-2); }
/* How many cells carry it — the fact that decides whether a gene is worth
   adding, read off the index's own non-zero count with no block fetch. */
.sp-cm-sugg-pct {
  font-family: 'Open Sans', sans-serif; font-size: 0.68rem; color: var(--text-subtle);
  font-variant-numeric: tabular-nums;
}
.sp-cm-sugg-none {
  padding: 5px 7px; font-size: 0.72rem; color: var(--text-subtle);
  background: var(--surface); border: 1px solid var(--border); border-radius: 8px;
}
.sp-cm-add:focus-within { border-color: var(--accent-light); border-style: solid; }
.sp-cm-detected { white-space: nowrap; color: var(--text-muted); }
.sp-cm-pct { color: var(--text); font-variant-numeric: tabular-nums; }
.sp-cm-of { font-size: 0.7rem; }
.sp-cm-bar { display: block; height: 3px; background: var(--border); border-radius: 2px; margin-top: 4px;
             position: relative; min-width: 96px; }
.sp-cm-bar i { position: absolute; left: 0; top: 0; bottom: 0; background: var(--accent); border-radius: 2px; }
.sp-cm-note { color: var(--text-subtle); font-size: 0.72rem; }
.sp-cm-excl { color: var(--text-muted); }
/* The exclusion half of a conjunctive rule, under the gene controls: the row
   states "genes > floor" AND "in T cell, not CD8" as two lines of one test. */
.sp-cm-excl-clause { display: block; margin-top: 4px; font-size: 0.72rem; }
.sp-cm-foot { display: flex; align-items: center; gap: 10px; margin-top: 12px; flex-wrap: wrap; }
.sp-cm-foot-note { color: var(--text-subtle); font-size: 0.72rem; flex: 1; min-width: 240px; }
.sp-cm-btn {
  background: var(--surface-2); color: var(--text); border: 1px solid var(--border);
  border-radius: 6px; padding: 4px 11px; font: inherit; font-size: 0.74rem; cursor: pointer;
}
.sp-cm-btn.is-primary { background: var(--accent); color: #fff; border-color: transparent; font-weight: 600; }
.sp-cm-btn[disabled] { background: var(--btn-disabled-bg); color: var(--btn-disabled-text);
                       border-color: transparent; cursor: not-allowed; }
.sp-cm-error { color: #f97316; margin-top: 8px; font-size: 0.74rem; }
.sp-cm-empty { color: var(--text-muted); }
.sp-cells-hint { font-size: 0.72rem; color: var(--text-subtle); font-variant-numeric: tabular-nums; }
/* Which instrument is colouring the embedding, and what a program encoding
   means. A figure caption UNDER the plot, not a note under the controls: the
   reader has just chosen "Lineage — marker rule" and the line says what that
   instrument is; chosen a program, and it says the size/opacity is a
   within-sample percentile and which cells carry one. Below rather than
   above because its height changes with the choice, and anything above the
   plot that changes height moves the plot. The caveat variant is for a
   program group whose reading needs guarding (the Doublet-* programs are not
   doublet evidence). */
.sp-cells-axis-caption {
  font-size: 0.74rem; color: var(--text-muted); line-height: 1.45;
  margin: 8px 2px 14px; display: flex; flex-direction: column; gap: 3px;
}
.sp-cells-axis-caption .sp-cells-axis-caveat {
  color: var(--text); border-left: 3px solid #f97316; padding-left: 8px;   /* same warn orange as .sp-cq-banner.is-warn */
}
/* The size/opacity key: three dots at the low, middle and high end of the
   percentile scale, so the encoding has a legend of its own — the Plotly
   legend can only key colour. */
.sp-cells-program-key {
  display: inline-flex; align-items: center; gap: 6px; margin-left: 6px;
  vertical-align: middle;
}
.sp-cells-program-key i {
  display: inline-block; border-radius: 50%; background: currentColor;
}
.sp-cells-plot {
  width: 100%; height: 640px;
  border: 1px solid var(--border); border-radius: 12px;
}

/* Cell-type composition bars.
   Fixed height, so a run switch that empties the chart holds its space instead
   of collapsing the page and dragging the UMAP up under the reader's cursor —
   the same reason the stat tiles keep their labels while the numbers blank. */
.sp-cells-composition-wrap {
  border: 1px solid var(--border); border-radius: 12px;
  padding: 10px 4px 2px; margin-bottom: 12px;
}
.sp-cells-composition-title {
  font-size: 0.68rem; color: var(--text-muted);
  text-transform: uppercase; letter-spacing: 0.04em;
  padding: 0 12px 2px;
}
.sp-cells-composition { width: 100%; height: 240px; }

/* ── Droplet quality ──
 *
 * Deliberately NOT the .metrics-table / .gv-overview-table treatment the genomic tabs
 * use. Those panels grade a scalar against a published threshold, which is a
 * table's job. This one shows three distributions that the repository's own
 * measurement says must not be graded against a constant, so it is built as a
 * figure: hairlines instead of borders, one accent mark per rail, and numerals
 * carrying the emphasis that a table would put in a "status" column.
 *
 * Everything is a token. The SVG uses classes rather than inline fills so the
 * ribbons follow the page between light and dark like the rest of the tab. */
.sp-cq {
  margin: 18px 0 4px;
  border-top: 1px solid var(--border);
}
.sp-cq-toggle {
  display: flex;
  align-items: center;
  gap: 14px;
  width: 100%;
  padding: 14px 2px 12px;
  background: none;
  border: 0;
  cursor: pointer;
  text-align: left;
  color: var(--text-muted);
}
.sp-cq-toggle:hover { color: var(--text); }
.sp-cq-toggle-title {
  font-size: 0.72rem;
  text-transform: uppercase;
  letter-spacing: 0.14em;
  font-weight: 600;
}
/* The teaser ribbon. Sits between the title and the action so the row reads as
 * one object, and shrinks away on narrow screens where it would crowd them. */
.sp-cq-toggle-spark {
  flex: 0 0 auto;
  width: 96px;
  height: 18px;
  opacity: 0.5;
  line-height: 0;
}
.sp-cq-toggle:hover .sp-cq-toggle-spark { opacity: 0.85; }
.sp-cq-toggle-spark svg { width: 100%; height: 100%; display: block; }
.sp-cq-toggle-action {
  margin-left: auto;
  font-size: 0.68rem;
  text-transform: uppercase;
  letter-spacing: 0.1em;
  color: var(--text-subtle);
}
.sp-cq-toggle:hover .sp-cq-toggle-action { color: var(--accent-light); }

/* The generous bottom padding is not whitespace for its own sake: this panel is
 * the last thing on the tab, and the page's floating action buttons are fixed
 * to the bottom-right corner. Without room to scroll past, the final row — the
 * typed count — sits permanently underneath them. */
.sp-cq-body { padding: 2px 2px 72px; }
.sp-cq-sub {
  font-size: 0.78rem;
  color: var(--text-subtle);
  max-width: 62ch;
  margin-bottom: 22px;
  line-height: 1.5;
}

/* One rail per axis. The 4-column grid keeps the three figures on a shared left
 * edge, so the distributions are read against each other rather than each
 * against its own label. */
.sp-cq-rail {
  display: grid;
  grid-template-columns: 148px 1fr 116px;
  /* The note sits under the FIGURE and matches its width exactly — the
   * ribbon is what the sentence describes, so the two share a measure.
   * It is deliberately not run out to the page edge: that would put the
   * prose under the tail's numbers, which belong to the rail above it. */
  grid-template-areas: "head fig tail" ".  note  ." "cl cl cl";
  align-items: end;
  column-gap: 22px;
  padding: 18px 0 14px;
  border-top: 1px solid var(--border);
}
.sp-cq-rail:first-child { border-top: 0; }
.sp-cq-rail-head { grid-area: head; }
.sp-cq-rail-fig  { grid-area: fig; min-width: 0; }
.sp-cq-rail-tail { grid-area: tail; text-align: right; }
.sp-cq-rail-note {
  grid-area: note;
  font-size: 0.72rem;
  color: var(--text-subtle);
  line-height: 1.5;
  margin-top: 10px;
}
.sp-cq-rail-label {
  font-size: 0.66rem;
  text-transform: uppercase;
  letter-spacing: 0.11em;
  color: var(--text-muted);
  margin-bottom: 6px;
}
/* The reading. Light and large because it is the one number on the rail worth
 * carrying away; a table would have made it the same weight as its label. */
.sp-cq-rail-value {
  font-size: 1.65rem;
  font-weight: 200;
  line-height: 1;
  color: var(--text);
  font-variant-numeric: tabular-nums;
}
.sp-cq-rail-unit { font-size: 0.9rem; margin-left: 2px; color: var(--text-muted); }
.sp-cq-rail-range {
  font-size: 0.86rem;
  font-weight: 300;
  color: var(--text-muted);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}
.sp-cq-rail-caption {
  font-size: 0.6rem;
  text-transform: uppercase;
  letter-spacing: 0.09em;
  color: var(--text-subtle);
  margin-top: 5px;
}

/* The positioning context for everything drawn OVER the ribbon — tick labels,
 * derived-threshold marks, the draggable handle and its shading. The whole
 * figure is the drag target, so a click anywhere on a rail moves its handle
 * rather than demanding the reader hit the grip first. */
.sp-cq-rail-fig { position: relative; cursor: ew-resize; touch-action: none; }
.sp-cq-svg { width: 100%; height: 72px; display: block; }
.sp-cq-area { fill: var(--text-muted); opacity: 0.22; }
/* The middle half of the same curve, drawn darker. Clipped to the ribbon in
 * markup, so this is a density and not a box. */
.sp-cq-iqr  { fill: var(--text-muted); opacity: 0.30; }
.sp-cq-base {
  stroke: var(--border);
  stroke-width: 1;
  vector-effect: non-scaling-stroke;
}
.sp-cq-median {
  stroke: var(--accent-light);
  stroke-width: 1.5;
  vector-effect: non-scaling-stroke;
}
/* Positioned HTML, not <text>: the svg's horizontal stretch would scale glyphs
 * with it and render these wider than the page's own type. */
.sp-cq-ticks { position: relative; height: 15px; margin-top: 3px; }
.sp-cq-tick {
  position: absolute;
  top: 0;
  transform: translateX(-50%);
  font-size: 0.6rem;
  color: var(--text-subtle);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

/* Counted against typed. A rule rather than a donut: the quantity is one
 * proportion of one whole, and a second encoding of the same number would
 * suggest there were two. */
.sp-cq-recon {
  margin-top: 26px;
  padding-top: 18px;
  border-top: 1px solid var(--border);
}
/* Two rules sharing a left edge and a scale, so the shortfall is the visible
 * difference between their right ends. Column widths match .sp-cq-rail so the
 * whole panel sits on one grid. */
.sp-cq-recon-grid {
  display: grid;
  grid-template-columns: 148px 1fr 116px;
  align-items: center;
  column-gap: 22px;
  row-gap: 12px;
}
.sp-cq-recon-label {
  font-size: 0.66rem;
  text-transform: uppercase;
  letter-spacing: 0.11em;
  color: var(--text-muted);
}
.sp-cq-recon-track { position: relative; height: 5px; }
.sp-cq-recon-fill { height: 100%; border-radius: 3px; }
/* Counted is the REFERENCE and typed is the measure, so they are separated by
 * weight rather than by hue — two saturated bars of similar length read as two
 * competing quantities instead of one shortfall against a whole. */
.sp-cq-recon-fill.is-counted { background: var(--text-subtle); opacity: 0.4; }
/* The middle rule, drawn only when QC and the typing step disagree. Weighted
 * between the two it sits under, so the eye reads counted -> kept -> typed
 * as one narrowing rather than three unrelated bars. */
.sp-cq-recon-fill.is-kept { background: var(--accent-light); opacity: 0.55; }
.sp-cq-recon-fill.is-typed   { background: var(--accent-light); }
/* The reader's own rule. Accent, like every other thing on this panel that
 * belongs to them rather than to the run — the brackets, the "your cut"
 * readouts — and outlined rather than filled so it reads as a projection over
 * the run's record instead of a fourth measured quantity. */
.sp-cq-recon-fill.is-yourcut {
  background: transparent;
  border: 1px solid var(--accent-light);
  border-radius: 3px;
}
.sp-cq-recon-value {
  text-align: right;
  font-size: 0.95rem;
  font-weight: 300;
  color: var(--text);
  font-variant-numeric: tabular-nums;
}
.sp-cq-recon-note {
  margin-top: 12px;
  font-size: 0.74rem;
  color: var(--text-subtle);
  line-height: 1.5;
  max-width: 74ch;
}

/* ── What the shipped QC decided ──
 *
 * Sits above the rails because it is the fact and they are the evidence: the
 * method chose thresholds, and the ribbons below show the distribution it chose
 * them against. Kept to plain rows of label/value rather than the stat-card
 * treatment used elsewhere — a card implies a reading worth grading, and this
 * block deliberately grades nothing. */
.sp-cq-stats {
  display: flex;
  flex-wrap: wrap;
  gap: 6px 34px;
  margin-bottom: 14px;
}
.sp-cq-stat { display: flex; flex-direction: column; gap: 3px; }
.sp-cq-stat-k {
  font-size: 0.62rem;
  text-transform: uppercase;
  letter-spacing: 0.11em;
  color: var(--text-subtle);
}
.sp-cq-stat-v {
  font-size: 0.92rem;
  color: var(--text);
  font-variant-numeric: tabular-nums;
}
/* The method is the one stat on this panel that is a CHOICE rather than a
 * measurement, so it is the one that carries an affordance. Quiet until
 * hovered — a reader is here to read the run, not to be sold a re-run. */
.sp-cq-method-edit {
  margin-left: 8px;
  padding: 1px 7px;
  border: 1px solid var(--border);
  border-radius: 3px;
  background: none;
  color: var(--text-subtle);
  font: inherit;
  font-size: 0.62rem;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  cursor: pointer;
  vertical-align: middle;
}
.sp-cq-method-edit:hover:not(:disabled) { color: var(--accent-light); border-color: var(--border-glow); }
.sp-cq-method-select {
  font: inherit;
  font-size: 0.8rem;
  padding: 2px 6px;
  background: var(--input-bg);
  color: var(--text);
  border: 1px solid var(--border-glow);
  border-radius: 4px;
  max-width: 100%;
}
.sp-cq-method-note {
  display: block;
  margin-top: 4px;
  font-size: 0.7rem;
  color: var(--text-muted);
  max-width: 46ch;
}
.sp-cq-method-note.is-error { color: #f97316; }
.sp-cq-method-note.is-ok { color: var(--accent-light); }

/* A rule down the left edge rather than a filled panel. The two banners this
 * carries are both things a reader must not skim past, and a tinted box on a
 * page of tinted boxes is exactly what the eye learns to skip. */
.sp-cq-banner {
  border-left: 2px solid var(--accent-light);
  padding: 8px 0 8px 12px;
  margin: 0 0 14px;
  font-size: 0.8rem;
  line-height: 1.55;
  color: var(--text);
  max-width: 74ch;
}
.sp-cq-banner.is-warn { border-left-color: #f97316; }
/* A custom cell set: somebody's decision, not the platform's answer. Accent
 * rather than the warning orange — it is not a fault, it is provenance. */
.sp-cq-banner.is-custom { border-left-color: var(--accent-light); }
.sp-cq-flags {
  list-style: none;
  margin: 0 0 14px;
  padding: 0;
  max-width: 74ch;
}
.sp-cq-flags li {
  position: relative;
  padding-left: 14px;
  margin-bottom: 5px;
  font-size: 0.76rem;
  line-height: 1.5;
  color: var(--text-muted);
}
.sp-cq-flags li::before {
  content: "";
  position: absolute;
  left: 0; top: 0.55em;
  width: 5px; height: 5px;
  border-radius: 50%;
  background: #f97316;
}
.sp-cq-note {
  font-size: 0.76rem;
  color: var(--text-subtle);
  line-height: 1.5;
  max-width: 74ch;
  margin-bottom: 14px;
}

/* ── Derived-threshold marks and the what-if handle ──
 *
 * Both are absolutely positioned over .sp-cq-rail-fig, which is already
 * position:relative for the tick labels, and both are placed by the SAME domain
 * mapping the ribbon was drawn with — so a mark at 500 genes sits exactly where
 * 500 genes sits on the curve.
 *
 * The marks are hairlines and the handle is a filled grip on purpose: what the
 * pipeline derived is a fact about a finished run and must not look adjustable,
 * while the handle is the one thing on this panel the reader can move.
 *
 * ONE kind of mark, always solid. There was a second, faint dashed variant for
 * per-cluster thresholds laid over the pooled ribbon; it is gone, because a
 * threshold measured over one cluster marks nothing on a curve drawn from all
 * of them. Per-cluster thresholds now appear only in the small multiples, each
 * on its own cluster's ribbon — same class, same solid hairline, because there
 * it is the bound that was actually applied to the cells underneath it. */
.sp-cq-dmark {
  position: absolute;
  bottom: 10px;
  width: 0;
  border-left: 1px solid var(--text-muted);
  height: 72%;
  opacity: 0.85;
  pointer-events: none;
  transform: translateX(-0.5px);
}

/* The one exception to "one kind of mark": an INSTRUMENT's own line.
 *
 * scDblFinder attaches a singlet/doublet call to every droplet at a threshold
 * it chose for this library, and the doublet rail marks where that call falls
 * on the pooled score. Unlike the per-cluster dashes this replaces nothing and
 * misstates nothing — the call was made over exactly the distribution drawn
 * here — but it is NOT an applied bound: the pipeline cut no cell on it, so it
 * must not look like the solid "applied by" hairline. Dashed, and a touch
 * fainter, is the difference between "the instrument said" and "the run did". */
.sp-cq-imark {
  position: absolute;
  bottom: 10px;
  width: 0;
  border-left: 1px dashed var(--text-muted);
  height: 72%;
  opacity: 0.7;
  transform: translateX(-0.5px);
  /* Unlike the applied-threshold hairline this one takes the pointer: a
   * dashed line on a slider reads as a threshold, and the hover is where it
   * says it is not one. The line itself is zero-width, so the hit area is a
   * pseudo-element a few pixels either side; a click still falls through to
   * the rail's own drag, which sets the handle at the line — the very gesture
   * the hover text suggests. */
  cursor: help;
}
.sp-cq-imark::before {
  content: "";
  position: absolute;
  top: 0;
  bottom: 0;
  left: -5px;
  right: -5px;
}
.sp-cq-rail-fig:hover .sp-cq-imark { opacity: 1; }

/* An excluded band is a flat ZONE TINT, not a fade.
 *
 * Fading was tried first — wash the region toward --bg so the excluded droplets
 * recede. It does not work on a dark theme: most of an excluded range is empty
 * ribbon, which is already the background colour, so the wash is a no-op
 * exactly where the reader is looking for the boundary. A flat tint delimits
 * the zone whether or not there are droplets in it, which is the reading the
 * band exists to give, and it is the convention an out-of-range region already
 * has in a plot. Kept quiet, because on a ddqc mito rail it covers three
 * quarters of the width. */
.sp-cq-drop {
  position: absolute;
  top: 0;
  bottom: 18px;      /* the plot, not the axis labels beneath it */
  background: var(--qc-band-user, rgba(136, 146, 192, 0.34));
  pointer-events: none;
}
/* The band the RUN excluded, as opposed to the band the reader's cut would.
 * Quieter, and it sits behind: the run's decision is the ground the reader is
 * standing on, theirs is the change they are testing on top of it. Where the
 * two overlap the wash compounds, which is the truthful reading — those
 * droplets are out on both counts. Which band is whose is settled at its edge:
 * a grey hairline is the run's, a bracket is yours. */
.sp-cq-drop.is-run { background: var(--qc-band-run, rgba(136, 146, 192, 0.20)); }
/* ── The range handles: an interval bracket ──
 *
 * Each handle IS a bracket — `[` on the lower bound, `]` on the upper — full
 * height, arms turned inward. The pair encloses the kept droplets the way the
 * notation already encloses a closed interval, [147, 1713], which is a reading
 * this audience has and a slider thumb is not. It also settles which handle a
 * reader is about to grab when the two sit a few pixels apart, on an axis
 * where every other cue is identical.
 *
 * One element, no separate thumb: the vertical stroke of the bracket is the
 * measurement line, so the control adds a single mark to the figure rather
 * than a line plus an ornament sitting on top of it. Everything else on this
 * panel is a hairline; the brackets are the one thing you can move, and the
 * only place this panel spends any weight. */
.sp-cq-handle {
  position: absolute;
  top: -4px;
  /* Stops at the plot's own bottom edge. The figure column is 100px tall and
   * the tick labels own the bottom 15 of it, so a bracket run any lower prints
   * its arm through "0%" and "1.00" at the parked positions. */
  bottom: 18px;
  width: 9px;
  border: 1.5px solid var(--text-subtle);
  background: none;
  cursor: ew-resize;
  touch-action: none;
  /* transform and colour only — NEVER `left`. The bracket's position is driven
   * by the pointer, and a transition on it would make it lag the cursor. */
  transition: transform 0.13s ease, border-color 0.13s ease, box-shadow 0.13s ease;
}
/* The vertical stroke lands ON the threshold, so each edge is offset by its
 * own side: the lower bracket by half its border, the upper by its full width. */
.sp-cq-handle[data-edge="lo"] {
  border-right: 0;
  margin-left: -0.75px;
  transform-origin: left center;
}
.sp-cq-handle[data-edge="hi"] {
  border-left: 0;
  margin-left: -8.25px;
  transform-origin: right center;
}
/* A handle showing a bound it INHERITS — a cluster living with the rail's cut
 * rather than one of its own. Drawn, because that is where its cells are being
 * cut and hiding it would misreport the row; drawn faintly, because nobody
 * decided it here. Moving it is the act of taking ownership, and it picks up
 * .is-set the moment it moves. */
.sp-cq-handle.is-inherit { opacity: 0.4; border-style: dotted; }
.sp-cq-handle.is-set {
  border-color: var(--accent-light);
  box-shadow: 0 0 12px -4px var(--accent-light);
}
/* A handle still sitting where the RUN put it. The handles open on the run's
 * own bounds, so most of them are this most of the time; drawn in the run's
 * grey rather than the reader's accent, because the accent is the signal that
 * the reader changed something, and it has to stay quiet until they do. */
.sp-cq-handle.is-run {
  border-color: var(--text-subtle);
  box-shadow: none;
}
/* The arms open on approach — the jaws-widening gesture the control performs
 * anyway, previewed. Scaled from the vertical stroke so the threshold the
 * bracket marks does not move while it grows. */
.sp-cq-rail-fig:hover .sp-cq-handle { border-color: var(--text-muted); }
.sp-cq-rail-fig:hover /* A handle showing a bound it INHERITS — a cluster living with the rail's cut
 * rather than one of its own. Drawn, because that is where its cells are being
 * cut and hiding it would misreport the row; drawn faintly, because nobody
 * decided it here. Moving it is the act of taking ownership, and it picks up
 * .is-set the moment it moves. */
.sp-cq-handle.is-inherit { opacity: 0.4; border-style: dotted; }
.sp-cq-handle.is-set { border-color: var(--accent-light); }
.sp-cq-rail-fig:hover .sp-cq-handle.is-run { border-color: var(--text-muted); }
.sp-cq-handle:hover,
.sp-cq-handle:active { transform: scaleX(1.5); }
.sp-cq-handle:focus-visible {
  outline: none;
  border-color: var(--accent-light);
  box-shadow: var(--shadow-accent);
}
@media (prefers-reduced-motion: reduce) {
  .sp-cq-handle { transition: none; }
  .sp-cq-handle:hover, .sp-cq-handle:active { transform: none; }
}

/* What the RUN applied, in the tail's leading position — the answer to "what
 * was cut", which is the question the panel exists for. The reader's own
 * what-if sits underneath in the accent, so the two can never be read as one
 * number. */
.sp-cq-rail-applied {
  font-size: 0.86rem;
  color: var(--text);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
  /* The tail is a fixed 116px column at the page's right edge, so anything
   * that will not wrap here does not clip — it runs off the page. nowrap is
   * right for "≥ 297 · ≤ 5,231", which must not break across lines; the
   * overflow guard is what stops a longer string escaping the column. */
  overflow-wrap: anywhere;
}
/* An absent bound is a phrase, not a number, so it is allowed to wrap —
 * nowrap suits "≥ 297 · ≤ 5,231" and overflows "no threshold — by design". */
.sp-cq-rail-applied.is-absent {
  color: var(--text-subtle); font-style: italic; white-space: normal;
}
/* Belt and braces on the whole tail: a fixed-width column at the page edge is
 * exactly where an unexpected string becomes a horizontal scrollbar. */
.sp-cq-rail-tail { overflow-wrap: anywhere; min-width: 0; }
.sp-cq-rail-cut {
  font-size: 0.78rem;
  color: var(--accent-light);
  font-variant-numeric: tabular-nums;
  margin-top: 5px;
  min-height: 1em;          /* hold the row so setting a cut does not reflow the rail */
  white-space: nowrap;
}
.sp-cq-rail-cut.is-idle { visibility: hidden; }

/* ── The what-if readout ──
 *
 * Directly under the rails it is driven by, because dragging a handle IS the
 * interaction and this is its result — put in a sidebar or a header it would
 * read as a separate feature rather than as the rails' own answer. */
.sp-cq-tune {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: 6px 14px;
  padding: 16px 0 4px;
  border-top: 1px solid var(--border);
}
.sp-cq-tune-counts { display: flex; align-items: baseline; gap: 8px; }
.sp-cq-tune-n {
  font-size: 1.65rem;
  font-weight: 200;
  line-height: 1;
  color: var(--text);
  font-variant-numeric: tabular-nums;
}
.sp-cq-tune-of { font-size: 0.78rem; color: var(--text-muted); }
.sp-cq-tune-sub {
  flex: 1 1 22ch;
  font-size: 0.76rem;
  color: var(--text-subtle);
  line-height: 1.5;
}
/* The re-type hint reads as part of the same sentence rather than as a second
 * message, because it IS the other half of the count above it. */
.sp-cq-tune-add { color: var(--text-muted); }
.sp-cq-tune-reset {
  margin-left: auto;
  background: none;
  border: 1px solid var(--border);
  border-radius: 6px;
  padding: 5px 12px;
  font: inherit;
  font-size: 0.7rem;
  text-transform: uppercase;
  letter-spacing: 0.1em;
  color: var(--text-muted);
  cursor: pointer;
}
.sp-cq-tune-reset:hover:not(:disabled) { color: var(--accent-light); border-color: var(--border-glow); }
.sp-cq-tune-actions { display: flex; align-items: center; gap: 10px; }
/* The one control on this panel that spends money and creates a run, so it is
 * the one that is filled rather than outlined. Disabled until a cut exists:
 * saving the run's own thresholds back as a "custom" list would create a
 * second run that decided nothing. */
.sp-cq-tune-save {
  padding: 6px 14px;
  border: 1px solid var(--accent-light);
  border-radius: 4px;
  background: var(--accent);
  color: #fff;
  font: inherit;
  font-size: 0.72rem;
  cursor: pointer;
  white-space: nowrap;
}
.sp-cq-tune-save:disabled { opacity: 0.4; cursor: default; }
.sp-cq-tune-save:hover:not(:disabled) { background: var(--accent-hover); }
.sp-cq-tune-status {
  grid-column: 1 / -1;
  margin-top: 8px;
  font-size: 0.72rem;
  color: var(--text-muted);
  min-height: 1em;
}
.sp-cq-tune-status.is-error { color: #f97316; }
.sp-cq-tune-status.is-ok { color: var(--accent-light); }
.sp-cq-tune-reset:disabled { color: var(--btn-disabled-text); cursor: default; }

/* ── Per-cluster evidence, folded under its own axis ──
 *
 * A <details> in the rail's third grid row, spanning all three columns so each
 * cluster row can repeat the rail's OWN column template: label under the label,
 * ribbon under the ribbon, threshold under the threshold. That alignment is the
 * point. A cluster's gene floor sits directly below the pooled curve it was
 * derived from, and its number lands in the same column as the rail's, so the
 * two can be read against each other without moving your eye off the column.
 *
 * Every ribbon is drawn on the rail's resolved domain, never auto-ranged to its
 * own cells — small multiples each on their own ruler compare nothing, and would
 * make a tight cluster and a broad one look identical. */
.sp-cq-cl { grid-area: cl; margin-top: 12px; }
.sp-cq-cl-summary {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  padding: 4px 0;
  font-size: 0.66rem;
  text-transform: uppercase;
  letter-spacing: 0.11em;
  color: var(--text-subtle);
  cursor: pointer;
  list-style: none;
  user-select: none;
}
.sp-cq-cl-summary::-webkit-details-marker { display: none; }
.sp-cq-cl-summary::before {
  content: "";
  width: 0;
  height: 0;
  border-left: 4px solid currentColor;
  border-top: 3.5px solid transparent;
  border-bottom: 3.5px solid transparent;
  transition: transform 0.14s ease;
}
.sp-cq-cl[open] > .sp-cq-cl-summary::before { transform: rotate(90deg); }
.sp-cq-cl-summary:hover { color: var(--accent-light); }
.sp-cq-cl-body { padding: 6px 0 2px; }
.sp-cq-cl-note {
  font-size: 0.7rem;
  color: var(--text-subtle);
  line-height: 1.5;
  margin: 2px 0 10px;
  max-width: 74ch;
}
/* The rail's own columns, repeated. */
.sp-cq-cl-row {
  display: grid;
  grid-template-columns: 148px 1fr 116px;
  column-gap: 22px;
  align-items: center;
  padding: 5px 0;
}
.sp-cq-cl-name { font-size: 0.78rem; color: var(--text-muted); }
.sp-cq-cl-n {
  display: block;
  font-size: 0.66rem;
  color: var(--text-subtle);
  font-variant-numeric: tabular-nums;
}
.sp-cq-cl-pooled {
  margin-left: 6px;
  padding: 1px 5px;
  border: 1px solid var(--border);
  border-radius: 3px;
  font-size: 0.58rem;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--text-subtle);
}
.sp-cq-cl-fig { position: relative; min-width: 0; cursor: ew-resize; touch-action: none; }
.sp-cq-cl-fig.is-empty { cursor: default; }
.sp-cq-cl-fig .sp-cq-svg { height: 26px; }
/* The row's handles are the rail's, at the row's height and without the tick
 * gutter to clear — there are no tick labels down here. */
.sp-cq-cl-fig .sp-cq-handle { bottom: 0; }
.sp-cq-cl-fig .sp-cq-drop { bottom: 0; }
/* A cluster too small to draw still holds its row, so the grid does not reflow
 * around it and the column stays readable top to bottom. */
.sp-cq-cl-fig.is-empty { height: 26px; }
/* ONE colour scheme across the whole panel, no per-context exception: the
 * median is the accent line, a derived threshold is a muted hairline, and the
 * accent bracket is the reader's own cut. An earlier draft inverted it in here
 * on the argument that the fold's subject is the threshold — but that put an
 * accent hairline beside an accent bracket on the same ribbon, one a fixed
 * record and one the thing you drag, which is exactly the confusion the rail's
 * design set out to avoid: what the pipeline derived is a fact about a finished
 * run and must not look adjustable.
 *
 * Geometry still differs, because the row has no tick gutter to clear. */
.sp-cq-cl-fig .sp-cq-dmark { bottom: 0; height: 100%; }
/* The threshold as a number, in the rail's tail column so it stacks under the
 * rail's own. The hairline says where; this says what — and it is the only
 * reading that survives a run with no cluster map to draw ribbons from, or a
 * screenshot pasted into a slide. */
.sp-cq-cl-val {
  font-size: 0.72rem;
  color: var(--text-muted);
  text-align: right;
  font-variant-numeric: tabular-nums;
  overflow-wrap: anywhere;
}
.sp-cq-cl-val.is-none { color: var(--text-subtle); font-style: italic; }

/* A fold with no cluster map to draw: numbers only, packed.
 *
 * The row grid exists to put a cluster's ribbon under the rail's ribbon and its
 * threshold under the rail's threshold. With no ribbon the middle column is
 * dead space by construction, and reserving it anyway turned an eleven-cluster
 * run into eleven full-width bands of whitespace with one value adrift on the
 * right — which reads as a failed render, not as "the distributions were never
 * recorded". So the column goes, the rows shrink to a name/value pair, and the
 * list flows into columns rather than running a screen and a half down the
 * page. Column count follows the width, so a narrow window gets one. */
.sp-cq-cl-body.is-bare {
  columns: 3 220px;
  column-gap: 34px;
}
.sp-cq-cl-body.is-bare .sp-cq-cl-note { column-span: all; }
.sp-cq-cl-body.is-bare .sp-cq-cl-row {
  grid-template-columns: 1fr auto;
  column-gap: 14px;
  padding: 4px 0;
  /* A row split across a column break would put a cluster's name in one column
   * and its threshold in the next. */
  break-inside: avoid;
}
.sp-cq-cl-body.is-bare .sp-cq-cl-tail { align-self: center; }
.sp-cq-cl-tail { text-align: right; min-width: 0; }
/* The reader's own cut for this cluster, under the run's. Same relationship the
 * rail already draws between its applied bound and its "your cut" line, so the
 * two columns read down consistently. */
.sp-cq-cl-cut {
  margin-top: 2px;
  font-size: 0.66rem;
  color: var(--accent-light);
  font-variant-numeric: tabular-nums;
  overflow-wrap: anywhere;
}
.sp-cq-cl-cut.is-idle { visibility: hidden; }

@media (max-width: 760px) {
  .sp-cq-rail {
    grid-template-columns: 1fr auto;
    grid-template-areas: "head tail" "fig fig" "note note" "cl cl";
    align-items: start;
    row-gap: 12px;
  }
  /* No rail columns left to align to, so the cluster row keeps only the split
   * that still means something: who it is on the left, what it was cut at on
   * the right, with the ribbon on its own line beneath. */
  .sp-cq-cl-row {
    grid-template-columns: 1fr auto;
    column-gap: 12px;
  }
  .sp-cq-cl-fig { grid-column: 1 / -1; order: 3; }
  .sp-cq-rail-tail { align-self: start; }
  .sp-cq-toggle-spark { display: none; }
}

/* Loading state for the UMAP.
   The overlay covers the plot box rather than sitting above it as a line of
   text: switching runs is the slow case, and a caption over the fold from a
   640px blank rectangle reads as "the plot is broken", not "it is coming".
   Opaque, because a half-visible previous run's embedding under a spinner is
   a figure labelled with the wrong run — screenshot-able and wrong. */
/* z-index:0 makes the wrap a stacking context, so Plotly's own high z-index
   internals (the modebar) stay inside it and slide UNDER the pinned dock
   instead of drawing over it. */
.sp-cells-plot-wrap { position: relative; z-index: 0; }
.sp-cells-plot-overlay {
  position: absolute; inset: 0;
  display: flex; justify-content: center;
  /* Held near the top of the 640px box rather than centred in it. Centred puts
     the indicator ~320px down, which on a laptop viewport lands below the fold
     — the reader looks at the plot area, sees blank, and concludes it is
     broken, which is the failure this whole element exists to prevent. */
  align-items: flex-start; padding-top: 160px;
  background: var(--surface);
  border: 1px solid var(--border); border-radius: 12px;
}
/* Faded out rather than switched off. The canvas is composited by the GPU and
   can appear a frame or two after the DOM around it, so a hard cut can flash a
   blank box; across a fade the plot is revealed as the cover dissolves and
   there is no instant with neither plot nor feedback on screen. */
.sp-cells-plot-overlay.is-leaving {
  opacity: 0;
  transition: opacity 220ms ease-out;
  pointer-events: none;
}
@media (prefers-reduced-motion: reduce) {
  .sp-cells-plot-overlay.is-leaving { transition: none; }
}
.sp-cells-plot-overlay-inner { width: min(320px, 60%); text-align: center; }
.sp-cells-plot-overlay-msg {
  margin-top: 12px;
  font-size: 0.84rem;
  color: var(--text-subtle);
}

/* ---------------------------------------------------------------------------
   Sign-in prompt
   ---------------------------------------------------------------------------
   Shown by shared.js when a signed-out visitor hits an endpoint that is not
   part of anonymous access (Benchmarks, the Library, the Tools catalogue). It replaces a bare
   "Error: HTTP 403", which reads as a broken site rather than as a page that
   needs an account.

   Uses the existing tokens rather than new colours so it inherits both themes
   without a second definition. */
/* ── Sign-in gate ─────────────────────────────────────────────────────────
   What a signed-out visitor sees when they open a page that reads private
   sample or run data. Built by momixShowSignInPrompt() in shared.js.

   A CARD THAT REPLACES THE PAGE, not a bar above it. The gate fires on a 403,
   which means no row ever arrived, so the page underneath is an empty table
   and a column of em-dashes. Banner-over-scaffold reads as two unrelated
   failures — "the site is broken" AND "you are locked out" — where the card
   says the one true thing. The site header is deliberately left standing
   (see `momix-signin-gated`) so the reader keeps every way out.

   Every rule here is TOP-LEVEL and token-driven. Top-level because a nesting
   level would put `.momix-signin-prompt` inside a block a browser can skip
   (test_stylesheet_is_parseable asserts depth 0); token-driven because the
   gate must be legible in both themes without a second colour definition. */

.momix-signin-prompt {
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  gap: 0.85rem;
  margin: 8vh auto 3rem;
  padding: 2.4rem 2.2rem 2rem;
  max-width: 460px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 16px;
  box-shadow: var(--shadow-accent), 0 18px 50px rgba(0, 0, 0, 0.16);
  font-size: 0.9rem;
  color: var(--text);
  animation: momix-signin-rise 0.3s ease-out;
}

/* The padlock, in the same gradient disc as every momix call-to-action, so the
   gate reads as part of the product rather than as an error surface. */
.momix-signin-badge {
  flex: 0 0 auto;
  display: grid;
  place-items: center;
  width: 54px;
  height: 54px;
  border-radius: 50%;
  background: var(--gradient-btn);
  color: #fff;
  box-shadow: 0 6px 22px color-mix(in srgb, var(--accent) 42%, transparent);
}
.momix-signin-badge svg { width: 25px; height: 25px; }

.momix-signin-body {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 6px;
  min-width: 0;
}
.momix-signin-title {
  font-weight: 600;
  font-size: 1.22rem;
  letter-spacing: -0.01em;
  color: var(--text);
}
.momix-signin-detail {
  font-size: 0.88rem;
  line-height: 1.5;
  max-width: 34ch;
  color: var(--text-muted);
}

.momix-signin-action {
  width: 100%;
  background: var(--gradient-btn);
  color: #fff;
  border: none;
  border-radius: 8px;
  padding: 0.62rem 1.15rem;
  font-family: inherit;
  font-size: 0.92rem;
  font-weight: 600;
  cursor: pointer;
  box-shadow: 0 2px 10px color-mix(in srgb, var(--accent) 32%, transparent);
  transition: background 0.15s ease, transform 0.12s ease, box-shadow 0.15s ease;
}
.momix-signin-action:hover {
  background: var(--gradient-btn-hover);
  transform: translateY(-1px);
  box-shadow: 0 4px 16px color-mix(in srgb, var(--accent) 40%, transparent);
}
.momix-signin-action:focus-visible { outline: 2px solid var(--accent-light); outline-offset: 2px; }

/* Where a guest CAN go. Links, not prose — the old banner named the three open
   areas in a sentence and left the reader to find them. The hairline is drawn
   on this row so the card needs no extra element to split its two halves. */
.momix-signin-open {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  flex-wrap: wrap;
  width: 100%;
  padding-top: 1rem;
  margin-top: 0.2rem;
  border-top: 1px solid var(--border);
}
.momix-signin-open-label {
  width: 100%;
  margin-bottom: 0.1rem;
  font-size: 0.72rem;
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--text-subtle);
}
.momix-signin-chip {
  display: inline-block;
  padding: 0.22rem 0.62rem;
  border: 1px solid var(--border);
  border-radius: 999px;
  background: var(--surface-2);
  color: var(--text-muted);
  font-size: 0.79rem;
  font-weight: 500;
  text-decoration: none;
  transition: border-color 0.15s ease, color 0.15s ease, background 0.15s ease;
}
.momix-signin-chip:hover {
  border-color: var(--border-glow);
  background: color-mix(in srgb, var(--accent) 9%, var(--surface));
  color: var(--text);
}

/* The card owns the viewport: everything the gated page rendered is removed,
   the site header excepted so the reader keeps their way out. */
body.momix-signin-gated > *:not(header):not(#momixSignInPrompt) { display: none !important; }

@keyframes momix-signin-rise {
  from { opacity: 0; transform: translateY(14px) scale(0.985); }
  to   { opacity: 1; transform: none; }
}

@media (prefers-reduced-motion: reduce) {
  .momix-signin-prompt { animation: none; }
  .momix-signin-action:hover { transform: none; }
}

@media (max-width: 640px) {
  .momix-signin-prompt { margin: 4vh 1rem 2rem; padding: 2rem 1.4rem 1.6rem; }
}

/* ── TCGA TMB evidence-standard switch ────────────────────────────────────
   ONE control with two states (off = strictly-PASS calls, the conservative
   default; on = also count whole-genome-amplified libraries), rather than two
   buttons — the choice is binary and mutually exclusive, so a switch says that
   where a pair of buttons only implies it. role="switch" + aria-checked carry
   the state to assistive tech; the knob position carries it visually. */
.tt-switch { position: relative; width: 38px; height: 21px; flex: 0 0 auto;
  border-radius: 11px; border: 1px solid var(--border, #cbd5e1);
  background: var(--border, #cbd5e1); cursor: pointer; padding: 0;
  transition: background .15s ease, border-color .15s ease; }
.tt-switch[aria-checked="true"] { background: var(--accent, #2563eb);
  border-color: var(--accent, #2563eb); }
.tt-switch-knob { position: absolute; top: 2px; left: 2px; width: 15px; height: 15px;
  border-radius: 50%; background: #fff; transition: transform .15s ease;
  box-shadow: 0 1px 2px rgba(0,0,0,.25); }
.tt-switch[aria-checked="true"] .tt-switch-knob { transform: translateX(17px); }
.tt-switch:focus-visible { outline: 2px solid var(--accent, #2563eb); outline-offset: 2px; }
.tt-switch-text { font-size: 0.85rem; color: var(--text-muted, #64748b); cursor: pointer; }

/* ---------------------------------------------------------------------------
   State-of-the-art benchmark section (.sp-sota*) — the SOTA comparisons get a
   visually DISTINCT treatment from the In-house cards: accent-marked section
   head, tinted table surface, comparator badges, and muted control rows so
   candidates and controls never read as the same thing. Same design system
   (tokens only, light+dark both come from the token definitions); different
   surface. Class family deliberately sp-sota* — nothing here may start with
   "ad" (adblock hides it).

   LAYOUT RULE FOR THIS SECTION — numbers stay next to their labels. Two things
   used to push the results off to the right and force a horizontal scroll:
     1. the master row's Results chips carry each card's FULL truth-set label
        ("Early apoptosis (HEK293, AnnexinV/DAPI)"), which is far longer than
        the In-house WES/WGS chips this table was sized for, and
     2. `.sp-table` is width:100%, so the compare table's few narrow metric
        columns absorbed every spare pixel and floated each number hundreds of
        px away from the option it belongs to.
   Both are fixed by CAPPING, not by hiding: the chips wrap inside a bounded
   measure, and the compare table shrinks to its content (numeric columns are
   `width: 1%`, the auto-layout idiom for "no wider than you need"). Nothing is
   truncated or ellipsed — a clinician still reads every label in full.
--------------------------------------------------------------------------- */
.sp-bench-section-title {
  margin: 26px 0 10px;
  font-size: 1.02rem;
  letter-spacing: 0.02em;
  color: var(--text);
}
.sp-sota-head {
  margin-top: 34px;
  padding: 10px 14px 12px;
  border-left: 3px solid var(--accent);
  background: var(--surface-2);
  border-radius: 0 8px 8px 0;
}
.sp-sota-head .sp-bench-section-title { margin: 0 0 6px; }
.sp-sota-title { color: var(--accent); }
.sp-sota-explainer {
  margin: 0;
  max-width: 72ch;
  font-size: 0.86rem;
  color: var(--text-muted);
}
.sp-sota-tablewrap {
  border: 1px solid var(--accent-light);
  border-radius: 8px;
  background: var(--surface-2);
}
.sp-sota-badge {
  display: inline-block;
  margin-left: 8px;
  padding: 1px 8px;
  border: 1px solid var(--accent);
  border-radius: 999px;
  font-size: 0.72rem;
  letter-spacing: 0.02em;
  color: var(--accent);
  white-space: nowrap;
  vertical-align: middle;
}

/* ── Master row: keep the SOTA table inside the viewport ─────────────────────
   Scoped to .sp-sota-row so the In-house master table, which already fits, is
   not re-flowed. Long benchmark names, truth-set scopes and result chips WRAP
   within a bounded column instead of widening the table past its wrapper. */
.sp-sota-row .benchmark-master-name { white-space: normal; max-width: 44ch; }
.sp-sota-row .benchmark-master-scope { white-space: normal; max-width: 40ch; }
.sp-sota-row .benchmark-master-chips {
  max-width: 26rem;
}

/* ── Per-truth-set scorecard ────────────────────────────────────────────────
   Title + table + run stamp in one panel. The cards score DIFFERENT junk gates
   / eval subsets and their numbers are NOT comparable across the boundary, so
   the panel edge is doing real work: it says where one read stops. */
.sp-sota-body .benchmark-methodology,
.sp-sota-body .benchmark-limitations { max-width: 78ch; }
.sp-sota-card {
  /* Hugs its table rather than bleeding to the full row width — the panel edge
     is meant to say where one read stops, which it cannot do if it always ends
     at the viewport. Stacked, never side by side: two cards score DIFFERENT
     truth and placing them shoulder to shoulder invites the cross-card
     comparison the registry explicitly warns against. */
  width: fit-content;
  max-width: 100%;
  margin: 14px 0 0;
  padding: 12px 14px 10px;
  border: 1px solid var(--border);
  border-radius: 8px;
  background: var(--surface);
}
/* Title left, scope selector right. The selector RE-SCOPES the table under it
   rather than adding a second one, so it belongs in the panel's own header —
   putting it above the panel would read as a filter over both cards. */
.sp-sota-cardhead {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 1.25rem;
  margin: 0 0 8px;
}
.sp-sota-cardtitle {
  margin: 0;
  font-size: 0.72rem;
  font-weight: 700;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  color: var(--text-muted);
}
/* Control label + select in a scorecard header. Used by the per-call block's
   method selector. */
.sp-sota-scopelabel {
  display: inline-flex;
  align-items: center;
  gap: 0.45rem;
  font-size: 0.68rem;
  font-weight: 700;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  color: var(--text-subtle);
  white-space: nowrap;
}
.sp-sota-scope {
  /* No width cap: a native select sizes to its widest option, and these options
     are the registry's full group names ("Regulatory T cell (CD127lo CD25+
     sort)"). Capping it clipped the label mid-word — and the scorecard is
     content-width, so letting the control size itself costs nothing. */
  max-width: 100%;
  padding: 3px 8px;
  border: 1px solid var(--border);
  border-radius: 6px;
  background: var(--surface-2);
  color: var(--text);
  font-family: inherit;
  font-size: 0.78rem;
  font-weight: 400;
  letter-spacing: 0;
  text-transform: none;
  cursor: pointer;
}
.sp-sota-scope:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
/* Counts are the denominator, not a score: same numeric column treatment, muted
   so they never read as one more metric the arms are competing on. */
.sp-sota-countcol { color: var(--text-muted); }
/* The truth group a call implies, trailing its label: "TFH1 → Tconv". Subdued —
   it is the mapping the agreement was scored through, not a second call. */
.sp-sota-implies {
  margin-left: 0.5rem;
  font-size: 0.78em;
  color: var(--text-subtle);
  white-space: nowrap;
}
/* The per-call audit block. Sits under the head-to-head and reads as a
   continuation of it, so it takes the same panel with a slightly heavier top
   rule to say a new question starts here. */
.sp-sota-callcard { margin-top: 18px; }
/* States what the number below the table can and cannot establish. */
.sp-sota-scopenote {
  margin: 10px 0 0;
  max-width: 68ch;
  font-size: 0.76rem;
  line-height: 1.45;
  color: var(--text-muted);
}
.sp-sota-comparewrap { overflow-x: auto; }

/* Content-width comparison table. `width: auto` + `width: 1%` numeric columns
   collapse each metric column to the widest of its header and its values, so
   the numeric block sits immediately right of the option labels. The wrapper
   still scrolls if a viewport cannot hold even that. */
.sp-sota-compare {
  width: auto;
  max-width: 100%;
}
.sp-sota-compare th,
.sp-sota-compare td { padding-top: 7px; padding-bottom: 7px; }
.sp-sota-compare th:first-child,
.sp-sota-compare td:first-child { padding-left: 0; }
/* The option column takes whatever its labels need and wraps only when the
   viewport cannot hold the table; the metric columns are a FIXED narrow width,
   so the numeric block is a constant-width slab pinned to the labels.
   NB the metric width must be an absolute length, not the `width: 1%`
   auto-layout idiom: a percentage cell width inside a `width: auto` table makes
   the browser widen the table until the percentage is satisfiable, which is the
   full-bleed stretch this section is being fixed for. */
.sp-sota-optcol {
  white-space: normal;
  padding-right: 20px !important;
}
.sp-sota-num {
  width: 6.75rem;
  text-align: right;
  white-space: nowrap;
  font-variant-numeric: tabular-nums;
  padding-left: 14px !important;
  padding-right: 0 !important;
}
/* Metric headers wrap ("Margin over decoy" → two lines) inside that width
   rather than setting a column width no value needs. */
.sp-sota-compare th.sp-sota-num {
  white-space: normal;
  vertical-align: bottom;
  line-height: 1.25;
}
/* A single rule separates labels from the numeric block — the eye lands on the
   boundary instead of tracking across empty space. Deliberately NOT per-column
   rules or a winner highlight: highlighting a best cell invites reading an
   exclusion number without its paired retention. */
.sp-sota-optcol + .sp-sota-num {
  border-left: 1px solid var(--border);
  padding-left: 20px !important;
}
/* Controls: the baseline + matched decoys. Muted and dash-separated so the
   eye files them as the floor, never as competitors. */
.sp-sota-ctlrow td {
  color: var(--text-subtle);
  font-style: italic;
  border-top: 1px dashed var(--border);
}
/* Flag rows (annotation-only arms): secondary, upright, not muted-to-nothing —
   they are real results, just not filters. */
.sp-sota-flagrow td { color: var(--text-muted); }
.sp-sota-runstamp {
  margin: 8px 0 0;
  font-size: 0.78rem;
  color: var(--text-subtle);
}
.sp-sota-ctlnote {
  margin: 14px 2px 0;
  max-width: 72ch;
  font-size: 0.8rem;
  color: var(--text-muted);
}
/* ── Reference matrix (cards with `reference_roles`) ──────────────────────
   Rows are ground-truth datasets, columns are the two ROLES a reference plays
   for a tissue. The tissue-matched cell is tinted because that column is the
   claim the card makes ("momix with a reference for your tissue"); the default
   column is what every other tissue gets and is deliberately plain. */
.sp-refmx-body .benchmark-methodology,
.sp-refmx-body .benchmark-limitations { max-width: 78ch; }
.sp-refmx-wrap { overflow-x: auto; margin-top: 14px; }
.sp-refmx {
  width: auto;
  max-width: 100%;
  border-collapse: separate;
  border-spacing: 0;
}
.sp-refmx th,
.sp-refmx td {
  vertical-align: top;
  padding: 10px 14px;
  border-bottom: 1px solid var(--border);
}
/* .sp-table td sets nowrap at higher specificity; the dataset label and the
   "none built" sentence are prose and must wrap inside their column. */
.sp-refmx td.sp-refmx-dscol,
.sp-refmx td.sp-refmx-role { white-space: normal; }
.sp-refmx th {
  font-size: 0.68rem;
  font-weight: 700;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  color: var(--text-subtle);
  white-space: nowrap;
}
.sp-refmx th:first-child,
.sp-refmx td:first-child { padding-left: 0; }
.sp-refmx-dscol {
  min-width: 13rem;
  max-width: 18rem;
  font-weight: 600;
  color: var(--text);
  white-space: normal;
}
/* One rule between the label column and the numeric block, as on the SOTA
   compare table — the eye lands on the boundary instead of crossing space. */
.sp-refmx-dscol + .sp-refmx-role { border-left: 1px solid var(--border); }
.sp-refmx-role { min-width: 17rem; max-width: 24rem; white-space: normal; }
/* The specialist cell is the claim the card makes, so it gets the surface tint.
   A tint, not a hue: the hues inside the cell belong to the metrics. */
.sp-refmx td.sp-refmx-role--matched {
  background: color-mix(in srgb, var(--accent) 9%, transparent);
  box-shadow: inset 2px 0 0 var(--accent);
}
.sp-refmx-role--none { color: var(--text-muted); }
.sp-refmx-ref {
  font-size: 0.86rem;
  font-weight: 600;
  color: var(--text);
  margin-bottom: 7px;
}
/* label · bar · value, one metric per line. Tabular numerals so three values
   stack into a column the eye can subtract. */
.sp-refmx-cell {
  display: grid;
  grid-template-columns: auto minmax(6rem, 1fr) auto;
  gap: 4px 10px;
  align-items: center;
}
.sp-refmx-cell--inline {
  display: flex;
  flex-wrap: wrap;
  gap: 4px 18px;
}
.sp-refmx-cell--inline .sp-refmx-k { margin-right: -12px; }
.sp-refmx-k {
  font-size: 0.74rem;
  color: var(--text-muted);
  white-space: nowrap;
}
.sp-refmx-v {
  font-size: 0.86rem;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  text-align: right;
  white-space: nowrap;
}
.sp-refmx-bar {
  display: block;
  height: 6px;
  border-radius: 3px;
  background: var(--surface-2);
  border: 1px solid var(--border);
  overflow: hidden;
}
.sp-refmx-bar i {
  display: block;
  height: 100%;
  /* --metric-hue is set per bar from _metricColor(), the same function that
     colours the metric's trend line, so one measure is one colour across the
     card. The accent fallback keeps a bar visible if a metric is ever plotted
     without a palette entry. */
  background: var(--metric-hue, var(--accent));
  opacity: 0.9;
}
/* A COUNT metric that falls short is a structural hole — populations the
   reference has no class for. Hatched, not recoloured: another hue here would
   be some other metric's identity, and the hatch reads in greyscale and in
   forced-colours mode where a fill does not. */
.sp-refmx-bar--gap {
  background-image: repeating-linear-gradient(135deg,
    transparent 0 3px, color-mix(in srgb, var(--metric-hue, var(--accent)) 38%, transparent) 3px 4px);
}
.sp-refmx-none {
  font-size: 0.82rem;
  font-style: italic;
  max-width: 20rem;
  line-height: 1.4;
}
/* Cross-tissue controls: the wrong atlas on purpose. Muted, dashed, under the
   row it belongs to, so it reads as the floor and never as a third reference. */
.sp-refmx-ctlrow td {
  padding-top: 6px;
  padding-bottom: 8px;
  border-top: 1px dashed var(--border);
  border-bottom: 1px solid var(--border);
  color: var(--text-subtle);
  font-style: italic;
}
.sp-refmx-ctlrow .sp-refmx-dscol { font-weight: 400; }
.sp-refmx-ctllabel {
  display: block;
  font-size: 0.7rem;
  font-weight: 700;
  font-style: normal;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--text-subtle);
}
.sp-refmx-ctlnote { font-size: 0.76rem; }
.sp-refmx-role--ctl .sp-refmx-v { font-weight: 500; color: var(--text-muted); }

/* The gap: dumbbell small multiples, one per metric, one line per dataset that
   has both a tailored and a default read. */
.sp-refmx-gap { margin-top: 18px; }
.sp-refmx-gapgrid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(19rem, 1fr));
  gap: 12px;
  max-width: 66rem;
  margin-top: 8px;
}
.sp-refmx-gappanel {
  padding: 10px 12px 8px;
  border: 1px solid var(--border);
  border-radius: 8px;
  background: var(--surface);
}
.sp-refmx-gaphead {
  display: flex;
  align-items: center;
  gap: 7px;
  font-size: 0.8rem;
  font-weight: 600;
  margin-bottom: 6px;
}
/* One swatch per panel: the panel's metric, in the hue its bars and its trend
   line already use. Identity is never carried by the text colour. */
.sp-refmx-swatch {
  width: 10px;
  height: 10px;
  border-radius: 3px;
  background: var(--metric-hue, var(--accent));
  flex: none;
}
/* One dataset per row: label above, then the line with its delta at the
   right, so the line gets the panel's full width however long the label. */
.sp-refmx-gaprow {
  display: grid;
  grid-template-columns: 1fr 3.4rem;
  grid-template-areas: "label label" "line delta";
  align-items: center;
  column-gap: 8px;
  margin-top: 4px;
}
.sp-refmx-gapds {
  grid-area: label;
  font-size: 0.72rem;
  color: var(--text-muted);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.sp-refmx-gaprow .sp-refmx-gapsvg { grid-area: line; }
.sp-refmx-gaprow .sp-refmx-gapdelta { grid-area: delta; }
.sp-refmx-gapticks { grid-template-areas: "tick tick"; margin-top: -4px; }
.sp-refmx-gapticks .sp-refmx-gaptick { grid-area: tick; }
.sp-refmx-gapsvg { display: block; width: 100%; height: 22px; }
/* The delta is TEXT and wears text ink, never the series colour; the swatch in
   the panel head carries the identity. Direction is the sign, already there. */
.sp-refmx-gapdelta {
  font-size: 0.78rem;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  color: var(--text);
  text-align: right;
}
.sp-refmx-gapdelta--worse { color: var(--text-muted); }
.sp-refmx-gaptick {
  display: flex;
  justify-content: space-between;
  padding: 0 4px;
  font-size: 0.64rem;
  color: var(--text-subtle);
}
.sp-refmx-gapaxis { stroke: var(--border); stroke-width: 1; }
/* The dumbbell wears the metric's hue; the two ends differ by SHAPE, so the
   role reading survives greyscale and every CVD type. A dashed line is the
   direction cue: the tailored reference lost ground on this row. */
.sp-refmx-gapline {
  stroke: var(--metric-hue, var(--accent));
  stroke-width: 3;
  stroke-linecap: round;
}
.sp-refmx-gapline--worse { stroke-dasharray: 3 2.5; opacity: 0.75; }
.sp-refmx-dot--specialist { fill: var(--metric-hue, var(--accent)); }
/* Open ring on the surface: the default reference, the thing being compared
   against, reads as the reference point rather than a second series. */
.sp-refmx-dot--default {
  fill: var(--surface);
  stroke: var(--metric-hue, var(--accent));
  stroke-width: 2;
}
.sp-refmx-legend {
  display: flex;
  gap: 16px;
  margin-top: 8px;
  font-size: 0.74rem;
  color: var(--text-muted);
}
.sp-refmx-legend span::before {
  content: "";
  display: inline-block;
  width: 9px;
  height: 9px;
  border-radius: 50%;
  margin-right: 6px;
  vertical-align: -1px;
}
/* The legend teaches the SHAPES, in neutral ink — filled end vs open ring —
   because the hue on a dumbbell says which metric, not which reference. */
.sp-refmx-legend--specialist::before { background: var(--text); }
.sp-refmx-legend--default::before {
  background: var(--surface);
  box-shadow: inset 0 0 0 2px var(--text);
}
.sp-refmx-legend--hue::before { display: none; }
.sp-refmx-legend--hue { color: var(--text-subtle); }
/* `cross-tissue`: the specialist atlas on this row was built for another organ.
   A word, not a colour — it qualifies the claim rather than grading it. */
.sp-refmx-mark {
  display: inline-block;
  margin-left: 6px;
  padding: 1px 7px;
  border-radius: 9px;
  border: 1px dashed var(--border-glow);
  font-size: 0.64rem;
  font-weight: 600;
  letter-spacing: 0.02em;
  text-transform: lowercase;
  color: var(--text-muted);
  vertical-align: 1px;
  white-space: nowrap;
  cursor: help;
}
.sp-refmx-tech { margin-top: 10px; }
/* Tier slider: one stop per facet, coarse → fine, the gated tier tagged so a
   reader knows which tier the master row and floors read. The stop labels sit
   under the track at the stop positions and are buttons in their own right. */
.sp-refmx-facetbar {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-start;
  gap: 6px 14px;
  margin: 14px 0 -2px;
}
.sp-refmx-slider {
  width: min(22rem, 100%);
  padding-top: 2px;
}
.sp-refmx-facetrange {
  display: block;
  width: 100%;
  margin: 0;
  accent-color: var(--accent);
  cursor: pointer;
}
.sp-refmx-facetstops {
  display: grid;
  grid-template-columns: repeat(var(--stops, 3), 1fr);
  margin-top: 2px;
}
.sp-refmx-facetstop {
  appearance: none;
  border: 0;
  background: none;
  padding: 2px 0;
  font: inherit;
  font-size: 0.78rem;
  color: var(--text-muted);
  cursor: pointer;
  text-align: center;
  white-space: nowrap;
}
.sp-refmx-facetstop:first-child { text-align: left; }
.sp-refmx-facetstop:last-child { text-align: right; }
.sp-refmx-facetstop:hover { color: var(--text); }
.sp-refmx-facetstop.is-active { color: var(--accent-light); font-weight: 600; }
.sp-refmx-facetgate {
  margin-left: 6px;
  font-size: 0.62rem;
  font-weight: 700;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  opacity: 0.8;
}
.sp-refmx-facetnote {
  flex-basis: 100%;
  font-size: 0.76rem;
  color: var(--text-muted);
  max-width: 78ch;
}

/* ── Somatic tab: "TMB in context" ────────────────────────────────────────
   The sample's TMB placed on the pan-cancer TCGA violins (sample-tmb-context.js).
   Reuses .tt-switch for the evidence-standard toggle; only the controls row and
   the headline readout are new. Colours are tokens, so the card inherits the
   theme rather than pinning a background of its own. */
.stc-card { padding: 14px 16px 8px; }
.stc-controls { display: flex; flex-wrap: wrap; align-items: flex-end;
  gap: 18px 24px; margin-bottom: 12px; }
.stc-control { display: flex; flex-direction: column; gap: 4px; }
/* Indented to the select's own text inset (1px border + 10px padding) so the
   label sits directly above the option text, not above the control's edge. */
.stc-control-label { font-size: 0.8rem; color: var(--text-muted, #64748b);
  padding-left: 11px; }
.stc-mode { margin-bottom: 2px; }
/* The one-line answer, read before the figure: value, rank, cutpoint verdict. */
.stc-headline { display: flex; flex-wrap: wrap; align-items: baseline;
  gap: 6px 12px; margin: 4px 0 10px; }
.stc-value { font-size: 1.55rem; font-weight: 650; color: var(--text, #0f172a);
  line-height: 1.1; }
.stc-unit { font-size: 0.85rem; font-weight: 500; color: var(--text-muted, #64748b); }
.stc-rank { font-size: 0.9rem; color: var(--text-muted, #64748b); }
.stc-rank b { color: var(--text, #0f172a); font-weight: 620; }
/* The TMB-High cutpoint verdict is a chip, but a NEUTRAL one by default: ≥10
   mut/Mb is a treatment-eligibility finding, not a QC pass/fail, so only the
   at-or-above state is tinted and it borrows the existing border/accent tokens
   rather than inventing a colour. */
.stc-flag { font-size: 0.78rem; padding: 2px 9px; border-radius: 10px;
  border: 1px solid var(--border, #cbd5e1); color: var(--text-muted, #64748b); }
.stc-flag-high { border-color: #dc2626; color: #dc2626; }

/* Provenance for the TMB plot's cohort selection: where the cohort came from,
   stated as a fact rather than a hedge (see _stcRenderProvenance). Sits under
   the picker it explains, quiet enough not to compete with the headline. */
.stc-provenance { font-size: 0.78rem; color: var(--text-muted, #64748b);
  margin-top: 4px; max-width: 46ch; line-height: 1.45; }

/* The TMB figure is a plain block, not a collapsible section, so it does not
   inherit `.vep-section-collapsible > .vep-chart-card`'s side inset. Match it
   here so the card still lines up with the sections above and below it. */
#stcSection > .stc-card { margin: 8px 24px 16px; }

/* Disclosure for the figure's how-to-read text. A quiet "…" rather than a
   worded control: the paragraph is read once and then never again, so the
   affordance should not compete with the plot for attention. */
.stc-note-toggle { display: block; margin: 6px 0 0; padding: 0 8px; height: 20px;
  border: 1px solid var(--border, #cbd5e1); border-radius: 10px;
  background: transparent; color: var(--text-muted, #64748b);
  font-size: 0.8rem; line-height: 1; cursor: pointer;
  transition: color .15s ease, border-color .15s ease; }
.stc-note-toggle:hover { color: var(--text, #0f172a); border-color: var(--text-muted, #64748b); }
.stc-note-toggle:focus-visible { outline: 2px solid var(--accent, #2563eb); outline-offset: 2px; }
.stc-note-toggle[aria-expanded="true"] { color: var(--text, #0f172a); }
