:root {
  --bg: #14171c;
  --card: #1e232b;
  --text: #e8ebf0;
  --muted: #8b95a4;
  --accent: #2dd4bf;
  --line: #2c333e;
  --danger: #e0604f;
  --warn: #fbbf24;
}

* { box-sizing: border-box; }
html { -webkit-text-size-adjust: 100%; }
body {
  margin: 0;
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
  background: var(--bg);
  color: var(--text);
  padding-bottom: calc(40px + env(safe-area-inset-bottom));
}
main { max-width: 760px; margin: 0 auto; padding: 12px 14px; }
a { color: inherit; text-decoration: none; }
.muted { color: var(--muted); font-weight: normal; }
.warn { color: var(--warn); }

/* top bar */
.topbar {
  position: sticky; top: 0; z-index: 10;
  display: flex; align-items: center; justify-content: space-between;
  padding: 10px 16px;
  padding-top: calc(10px + env(safe-area-inset-top));
  background: var(--card);
  border-bottom: 1px solid var(--line);
}
.brand { font-weight: 700; font-size: 17px; color: var(--accent); }
.topbar nav { display: flex; align-items: center; gap: 4px; }
.topbar nav a { padding: 6px 12px; border-radius: 8px; font-size: 15px; }
.topbar nav a.on { background: var(--accent); color: #fff; }
.topbar nav a.quiet { color: var(--muted); }
.whoami {
  display: inline-flex; align-items: center; justify-content: center;
  width: 26px; height: 26px; border-radius: 50%; flex: none;
  background: var(--pc); color: #fff; font-size: 11px; font-weight: 700;
}

/* forms */
.page-title { margin: 10px 4px; font-size: 20px; }
.sectionhead {
  margin: 24px 4px 8px; font-size: 13px; font-weight: 700;
  text-transform: uppercase; letter-spacing: .06em; color: var(--muted);
  display: flex; align-items: baseline; gap: 8px; flex-wrap: wrap;
}
.sectionhead .muted { text-transform: none; letter-spacing: 0; font-size: 12px; }
.crumb { margin: 8px 4px; font-size: 13px; color: var(--muted); }
.crumb a { color: var(--accent); }
.stoform { display: flex; flex-direction: column; gap: 10px; }
.addbox {
  background: var(--card); border: 1px solid var(--line);
  border-radius: 12px; padding: 12px; margin: 10px 0 4px;
}
label { display: flex; flex-direction: column; gap: 5px; font-size: 14px; font-weight: 600; }
input, select, textarea {
  font: inherit; font-weight: normal;
  padding: 10px 12px; border: 1px solid var(--line); border-radius: 10px;
  background: var(--card); color: var(--text); width: 100%;
}
textarea { resize: vertical; }
/* The rule above is written for text inputs. A checkbox inherited its
   width:100% and 12px padding too, which drew the batch tables' ticks as a
   squashed 20x13 rectangle — a small, oddly shaped target that was genuinely
   hard to hit. Give them a proper square instead. */
input[type="checkbox"], input[type="radio"] {
  width: 18px; height: 18px; padding: 0; flex: none;
  accent-color: var(--accent); cursor: pointer;
}
button {
  font: inherit; font-weight: 600;
  background: var(--accent); color: #fff; border: 0;
  padding: 12px; border-radius: 10px; cursor: pointer;
}
button:active { opacity: .8; }
button[disabled] { opacity: .4; cursor: default; }
button.ghost {
  background: transparent; color: var(--muted); border: 1px solid var(--line);
}
.addrow { display: flex; gap: 8px; align-items: center; }
.addrow input, .addrow select { flex: 1; min-width: 0; }
.addrow label.grow { flex: 1; min-width: 0; }
.addrow button { flex: none; padding: 11px 16px; }
.chiprow { display: flex; flex-wrap: wrap; gap: 6px; }
.chip {
  background: var(--card); color: var(--text);
  border: 1px solid var(--line); border-radius: 999px;
  padding: 8px 14px; font-size: 14px;
}
.chip.mini { font-size: 13px; padding: 6px 12px; }
.chip.sel {
  background: var(--pc, var(--accent)); border-color: var(--pc, var(--accent));
  color: #fff; font-weight: 600;
}
.chip.danger-chip { color: var(--danger); border-color: var(--danger); }
.actbtns { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 12px; }
.inlineform { display: inline-flex; flex: none; }
/* The "re-check stored files" row carried a .redetect class that no rule
   ever matched, so its caption sat at body size with no gap and collided
   with the chips above it. */
.redetect {
  align-items: center; gap: 8px; margin: 6px 0 10px; flex-wrap: wrap;
}
.redetect .muted { font-size: 12px; }
.xbtn {
  background: transparent; color: var(--muted); border: 0;
  font-size: 18px; padding: 2px 7px; line-height: 1;
}
.danger-form { margin-top: 10px; }
.error { color: var(--danger); margin: 0; }
.empty { text-align: center; color: var(--muted); margin-top: 60px; line-height: 1.6; }
.noneyet { margin: 4px 2px; font-size: 13px; color: var(--muted); }
.hint { font-size: 13px; margin: 12px 4px; line-height: 1.5; }
.quietlink { color: var(--muted); font-size: 13px; }
.banner {
  background: var(--accent); color: #fff; border-radius: 10px;
  padding: 10px 14px; font-size: 14px; margin: 10px 0;
}
.editbox, .weeknotes, .stepedit { margin: 14px 0; }
.editbox > summary, .weeknotes > summary, .stepedit > summary {
  cursor: pointer; font-size: 13px; color: var(--muted); padding: 6px 4px;
}
.stepedit { margin: 0 0 10px 34px; }
/* The collation member picker. Its summary reads as a section heading
   rather than a quiet disclosure, because it is one. */
.pickbox > summary { display: flex; align-items: baseline; gap: 8px; }
.picktitle {
  font-size: 13px; font-weight: 700; letter-spacing: .06em;
  text-transform: uppercase; color: var(--muted);
}
.pickbox[open] > summary { margin-bottom: 6px; }
/* Cloning page: the primer stock section folds under its own heading. The
   heading is display:flex, which drops the native triangle, so it gets one. */
.stockfold > summary.sectionhead { cursor: pointer; user-select: none; list-style: none; }
.stockfold > summary::-webkit-details-marker { display: none; }
.stockfold > summary::before { content: "▸"; font-size: 10px; }
.stockfold[open] > summary::before { content: "▾"; }
.stockfold > summary:hover { color: var(--accent); }
.datatable td.tubeid { font-variant-numeric: tabular-nums; white-space: nowrap;
  font-weight: 600; color: var(--muted); }

/* --- sequencing checks ---------------------------------------------------- */
.seqpill {
  display: inline-block; font-size: 11px; font-weight: 700; white-space: nowrap;
  padding: 1px 8px; border-radius: 999px; margin-right: 4px;
}
.seqpill.pass { color: #34d399; background: color-mix(in srgb, #34d399 18%, transparent); }
.seqpill.check { color: var(--warn); background: color-mix(in srgb, var(--warn) 18%, transparent); }
.seqpill.fail { color: var(--danger); background: color-mix(in srgb, var(--danger) 18%, transparent); }
.seqpill.nomatch { color: var(--muted); background: var(--line); }
.datatable.seqsummary th, .datatable.seqsummary td { text-align: left; }
.datatable.seqsummary td:last-child { white-space: nowrap; }
.datatable.seqsummary td:nth-child(2) { white-space: normal; min-width: 14em; }
.seqclone {
  border: 1px solid var(--line); border-radius: 10px; background: var(--card);
  padding: 8px 12px; margin: 10px 0;
}
.seqclone > summary { cursor: pointer; font-size: 14px; }
.seqheadline { font-size: 13px; margin: 6px 0; }
.seqplan { font-size: 13px; margin: 4px 0 8px; display: flex; gap: 6px; align-items: baseline; flex-wrap: wrap; }
.seqgroup { margin: 8px 0; }
.seqgroup > summary {
  cursor: pointer; font-size: 12px; font-weight: 700; text-transform: uppercase;
  letter-spacing: .06em; color: var(--muted); padding: 4px 0;
}
.seqgroup > summary .muted { text-transform: none; letter-spacing: 0; }
.seqev { margin: 6px 0 12px; }
.seqevhead { font-size: 13px; margin-bottom: 4px; }
/* The alignment: construct on top, clone below, residues above and below.
   Monospace so every column lines up; scrolls inside its own box. */
.aln {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 12px;
  line-height: 1.4; overflow-x: auto; background: var(--bg);
  border: 1px solid var(--line); border-radius: 8px; padding: 6px 10px;
}
.alnline + .alnline { margin-top: 8px; }
.alnrow { white-space: pre; }
.alnpos {
  display: inline-block; width: 6ch; margin-right: 1ch; text-align: right;
  color: var(--muted); user-select: none;
}
.alnrow.qry { color: var(--muted); }
.alnrow.aa { color: var(--accent); }
.aln b.x { background: color-mix(in srgb, var(--danger) 55%, transparent); color: #fff; }
.aln b.d { background: color-mix(in srgb, var(--accent) 45%, transparent); color: #fff; }
.aln u.ds { text-decoration-color: var(--accent); text-underline-offset: 2px; }
.alnnote { color: var(--muted); font-style: italic; margin-top: 4px; }

/* login */
.login-box { max-width: 340px; margin: 16vh auto 0; text-align: center; }
.login-box h1 { color: var(--accent); }
.login-box form { display: flex; flex-direction: column; gap: 12px; }

/* week navigation and summary */
.weeknav {
  display: flex; align-items: center; justify-content: space-between;
  margin: 8px 0 4px;
}
.weeknav .arrow { font-size: 26px; padding: 0 16px; color: var(--accent); }
.weektitle { text-align: center; }
.weektitle h2 { font-size: 17px; margin: 0; }
.weekbar {
  display: flex; align-items: center; gap: 12px; flex-wrap: wrap;
  margin: 8px 2px 12px;
}
.weekbar .muted { font-size: 13px; }
.meterwrap {
  display: inline-flex; align-items: center; gap: 8px;
  flex: 1; max-width: 220px; min-width: 120px;
}
.meterwrap.wide { max-width: none; margin: 6px 0 2px; }
.daymeter {
  flex: 1; height: 6px; border-radius: 999px; background: var(--line);
  overflow: hidden;
}
.daymeter span {
  display: block; height: 100%; border-radius: 999px; background: var(--accent);
}
.meterwrap .pct { font-size: 12px; color: var(--muted); font-weight: 600; }
.whofilter { margin: 0 0 12px; }

/* the day board */
.daycard {
  background: var(--card); border: 1px solid var(--line);
  border-radius: 14px; padding: 11px 13px; margin-bottom: 10px;
}
.daycard.is-today { border-color: var(--accent); box-shadow: 0 0 0 1px var(--accent); }
.daycard.is-past { opacity: .72; }
.daycard h2 {
  margin: 0 0 8px; font-size: 14px;
  display: flex; align-items: baseline; gap: 8px;
}
.daycard h2 .dayname { font-weight: 700; }
.daycard h2 .daydate { color: var(--muted); font-weight: normal; font-size: 13px; }
.daycard h2 .pill {
  background: var(--accent); color: #fff; font-size: 11px;
  padding: 2px 8px; border-radius: 999px; font-weight: 600;
}
.daycard h2 .daytot { margin-left: auto; color: var(--muted); font-size: 12px; }

.lane { border-left: 3px solid var(--pc); padding-left: 9px; margin-bottom: 12px; }
.lane:last-child { margin-bottom: 0; }
.lanehead { display: flex; align-items: center; gap: 7px; margin-bottom: 5px; }
.avatar {
  display: inline-flex; align-items: center; justify-content: center;
  width: 22px; height: 22px; border-radius: 50%; flex: none;
  background: var(--pc); color: #fff; font-size: 10px; font-weight: 700;
}
.avatar.big { width: 34px; height: 34px; font-size: 13px; }
.lanename { font-size: 13px; font-weight: 600; }
.lanemin { margin-left: auto; font-size: 12px; color: var(--muted); }

/* one step, wherever it appears */
.steprow { display: flex; align-items: flex-start; gap: 9px; padding: 7px 0; }
.steprow + .steprow { border-top: 1px solid var(--line); }
.steprow.full { padding: 9px 0 4px; }
.tick {
  width: 24px; height: 24px; border-radius: 50%; flex: none; padding: 0;
  border: 2px solid var(--line); background: var(--card); color: #fff;
  font-size: 13px; display: flex; align-items: center; justify-content: center;
}
.steprow.done .tick { background: var(--accent); border-color: var(--accent); }
.steprow.done .stepmain .title { text-decoration: line-through; color: var(--muted); }
.stepmain { display: flex; flex-direction: column; gap: 3px; min-width: 0; flex: 1; }
.stepmain .title { font-weight: 600; font-size: 15px; }
.stepmain .sub { font-size: 12px; color: var(--muted); }
.dot {
  display: inline-block; width: 8px; height: 8px; border-radius: 50%;
  margin-right: 7px; vertical-align: middle;
}
.stepmeta { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; }
.stepbtns { display: flex; flex: none; gap: 0; }
.steptools {
  display: flex; gap: 6px; flex-wrap: wrap;
  margin: 0 0 6px 34px;
}
.steptools select, .steptools input { width: auto; flex: 1 1 30%; min-width: 108px;
  font-size: 13px; padding: 6px 8px; }
.daypick, .whopick {
  width: auto; flex: none; font-size: 12px; padding: 6px 8px;
  max-width: 124px; background: var(--bg);
}
.minidate { font-size: 13px; padding: 6px 8px; }

/* badges */
.pri, .est, .due, .status, .who, .donetext, .whenbadge, .trackbadge {
  font-size: 11px; font-weight: 600; padding: 2px 8px; border-radius: 999px;
}
.est { background: var(--bg); color: var(--muted); border: 1px solid var(--line); }
.pri { color: #fff; }
.pri-urgent { background: #ef4444; }
.pri-high { background: var(--warn); }
.pri-normal { background: var(--muted); }
.pri-low { background: var(--muted); opacity: .7; }
.chip.pri-chip { color: var(--text); background: var(--card); }
.chip.pri-chip.sel { color: #fff; }
.chip.pri-urgent.sel { background: #ef4444; border-color: #ef4444; }
.chip.pri-high.sel { background: var(--warn); border-color: var(--warn); }
.chip.pri-normal.sel, .chip.pri-low.sel {
  background: var(--muted); border-color: var(--muted);
}
.due, .whenbadge { background: var(--bg); color: var(--muted); }
.due.soon, .whenbadge.soon { background: color-mix(in srgb, var(--warn) 22%, transparent); color: var(--text); }
.due.today, .whenbadge.today { background: var(--warn); color: #fff; }
.due.overdue, .whenbadge.overdue { background: var(--danger); color: #fff; }
.status { background: var(--bg); color: var(--muted); border: 1px solid var(--line); }
.status.st-active { color: var(--accent); border-color: var(--accent); }
.status.st-blocked { color: var(--danger); border-color: var(--danger); }
.status.st-done { color: var(--muted); }
.who { background: var(--pc); color: #fff; }
.donetext { font-size: 12px; color: var(--muted); padding: 0; }

/* backlog and leftovers */
.backlog, .stranded {
  background: var(--card); border: 1px solid var(--line);
  border-radius: 14px; padding: 11px 13px; margin: 14px 0;
}
.stranded { border-color: var(--danger); }
.backlog h3, .stranded h3 {
  margin: 0 0 8px; font-size: 12px; font-weight: 700;
  text-transform: uppercase; letter-spacing: .06em; color: var(--muted);
  display: flex; align-items: baseline; gap: 8px;
}
.stranded h3 { color: var(--danger); }
.backlog h3 .muted { text-transform: none; letter-spacing: 0; font-size: 12px; }
.weekactions { display: flex; gap: 8px; margin-bottom: 10px; }
.weekactions form { flex: 1; }
.weekactions button { width: 100%; padding: 10px; }

/* projects */
.postit {
  --pc: var(--accent);
  background: color-mix(in srgb, var(--pc) 9%, var(--card));
  border: 1px solid color-mix(in srgb, var(--pc) 32%, var(--line));
  border-top: 4px solid var(--pc);
  border-radius: 14px; padding: 12px 14px; margin: 14px 0;
}
.postit.archived { opacity: .6; }
.postit-head { display: flex; align-items: flex-start; gap: 10px; }
.postit-title { display: flex; flex-direction: column; gap: 4px; min-width: 0; flex: 1; }
.postit-title .title { font-weight: 700; font-size: 16px; }
.postit-title a.title { color: var(--pc); }
.postit-title .aim { font-size: 13px; color: var(--muted); }
/* Click-to-edit collation name: invisible until hovered, so the header
   still reads as a title rather than as a form. */
.postit-title .renameform { display: flex; min-width: 0; }
.collrename {
  flex: 1; min-width: 0; font: inherit; font-weight: 700; font-size: 16px;
  color: inherit; background: none; border: 1px solid transparent;
  border-radius: 6px; padding: 2px 6px; margin: -2px 0 -2px -6px;
  width: 100%;
}
.collrename:hover { border-color: var(--line); }
.collrename:focus {
  outline: none; background: var(--bg); border-color: var(--pc, var(--accent));
  cursor: text;
}
.postit-title .sub { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; }
.trackbadge { background: var(--pc); color: #fff; }
.postit .chiprow { margin-top: 10px; }
.postit .chip.sel { background: var(--pc); border-color: var(--pc); }
.stagerow { margin-top: 10px; }
.stagerow select { background: var(--card); font-size: 13px; padding: 8px 10px; }
.stagedots { display: flex; gap: 3px; flex: none; padding-top: 5px; }
.dotstage {
  width: 7px; height: 7px; border-radius: 50%;
  background: color-mix(in srgb, var(--pc) 22%, var(--line));
}
.dotstage.on { background: var(--pc); }

.expcard {
  background: var(--card); border: 1px solid var(--line);
  border-radius: 12px; padding: 11px 13px; margin-bottom: 8px;
}
.expcard.done { opacity: .62; }
.expmain { display: flex; flex-direction: column; gap: 5px; min-width: 0; }
.expmain .title { font-weight: 600; font-size: 15px; }
.expmain .sub { font-size: 13px; color: var(--muted); }

/* storage */
.cardnote { margin: 8px 2px 0; font-size: 13px; color: var(--muted); white-space: pre-wrap; }

.postit-icon { width: 40px; height: 40px; flex: none; }

/* the Storage tab: sections as equipment-icon tiles instead of post-its */
.stogrid {
  display: grid; grid-template-columns: repeat(auto-fill, minmax(128px, 1fr));
  gap: 12px; margin: 14px 0;
}
.stotile {
  display: flex; flex-direction: column; align-items: center; text-align: center;
  gap: 6px; padding: 16px 10px 12px;
  background: color-mix(in srgb, var(--pc) 8%, var(--card));
  border: 1px solid color-mix(in srgb, var(--pc) 30%, var(--line));
  border-radius: 16px;
}
.stotile:active { opacity: .85; }
.stoimg { width: 68px; height: 88px; }
.stoimg-blank {
  width: 68px; height: 88px; border-radius: 10px;
  border: 2px dashed color-mix(in srgb, var(--pc) 45%, var(--line));
}
.stoname { font-weight: 700; font-size: 14px; color: var(--text); }
.stometer {
  font-size: 11px; font-weight: 600; color: var(--pc);
  background: color-mix(in srgb, var(--pc) 16%, transparent);
  padding: 2px 9px; border-radius: 999px;
}

/* click a section tile -> the unit zooms up and its doors swing open onto
   the racks on that shelf, then the section page loads */
.dooranim {
  position: fixed; inset: 0; z-index: 100;
  display: flex; align-items: center; justify-content: center;
  background: color-mix(in srgb, var(--bg) 92%, #000);
  animation: dooranim-in .16s ease-out;
}
.dooranim[hidden] { display: none; }
@keyframes dooranim-in { from { opacity: 0; } }

.dooranim-close {
  position: absolute; top: 16px; right: 20px; z-index: 2;
  background: transparent; border: 0; color: var(--muted);
  font-size: 30px; line-height: 1; padding: 4px 12px; cursor: pointer;
}
.dooranim-close:hover { color: var(--text); }

.dooranim-stage { perspective: 900px; perspective-origin: 50% 45%; text-align: center; }
.unit {
  position: relative; width: 372px; height: 404px;
  transform-style: preserve-3d;
}
.unit.playing { animation: unit-zoom .36s cubic-bezier(.2,.8,.25,1) both; }
@keyframes unit-zoom {
  from { transform: scale(.3) translateY(22px); opacity: .3; }
  to   { transform: scale(1) translateY(0); opacity: 1; }
}

.unit-interior {
  position: absolute; inset: 0;
  display: flex; align-items: safe center; justify-content: center;
  padding: 18px; border-radius: 14px;
  overflow-x: hidden; overflow-y: auto;   /* a shelf too tall to fit scrolls
                                             rather than losing rows — "safe"
                                             centering keeps the top row
                                             reachable when it does */
  /* .unit is transform-style:preserve-3d for the door-swing 3D effect.
     Flattening isn't enough on its own — Chromium still caches this as a
     static texture from the 3D scene and scrolling stops repainting.
     Promoting it to its own compositing layer (translateZ) is what
     actually forces a real repaint on scroll. */
  transform-style: flat;
  transform: translateZ(0);
  background:
    radial-gradient(ellipse at 50% 40%,
      color-mix(in srgb, var(--pc) 20%, var(--card)),
      color-mix(in srgb, var(--pc) 5%, var(--card)) 70%);
  border: 3px solid color-mix(in srgb, var(--pc) 34%, var(--line));
  box-shadow: inset 0 0 40px rgba(0, 0, 0, .55);
}
.unit-shelfgrid {
  position: relative; width: 100%;
  display: flex; flex-direction: column; gap: 7px;
}
/* one shelf per row, with a metal rail running under it */
.shelfrow {
  display: flex; gap: 5px; padding-bottom: 5px;
  border-bottom: 3px solid;
  border-image: linear-gradient(90deg, #6b727b, #aeb5be 40%, #7f868f) 1;
}
.shelfrow:last-child { border-bottom-color: transparent; }

.unit-cell {
  flex: 1; min-width: 0; position: relative;
  height: var(--cell-h, 54px);   /* sized in JS so every shelf fits */
  border-radius: 3px;
  opacity: 0;
}
.unit.playing .unit-cell { animation: cell-pop .24s cubic-bezier(.2,.9,.3,1) both; }
@keyframes cell-pop {
  from { opacity: 0; transform: translateY(-7px) scale(.9); }
  to   { opacity: 1; transform: translateY(0) scale(1); }
}

/* an empty shelf slot */
.unit-cell.slot {
  display: flex; align-items: center; justify-content: center;
  border: 1px dashed color-mix(in srgb, var(--pc) 30%, var(--line));
  background: color-mix(in srgb, #000 48%, var(--bg));
  color: color-mix(in srgb, var(--pc) 60%, var(--muted));
  font-size: 15px; font-weight: 300;
}
.unit-cell.slot:hover {
  border-style: solid; border-color: var(--pc);
  background: color-mix(in srgb, var(--pc) 14%, var(--bg));
}

/* a rack: a stainless drawer with a pull handle and a label sticker */
.unit-cell.on {
  background: linear-gradient(180deg,
              #e9edf1 0 24%, #ccd2d9 24% 60%, #b0b7bf 100%);
  border: 1px solid #6d747e;
  box-shadow: inset 0 1px 0 rgba(255,255,255,.9),
              inset 0 -3px 5px rgba(0,0,0,.14),
              0 2px 4px rgba(0,0,0,.55);
}
.unit-cell.on::before {   /* lid seam */
  content: ""; position: absolute; left: 0; right: 0; top: 24%;
  border-top: 1px solid rgba(0,0,0,.28);
}
.unit-cell.on:hover {
  border-color: var(--pc);
  box-shadow: inset 0 1px 0 rgba(255,255,255,.9),
              0 0 0 2px var(--pc), 0 4px 8px rgba(0,0,0,.6);
  transform: translateY(-2px);
}
.rack-handle {
  position: absolute; left: 21%; right: 21%; top: 41%;
  height: 15%; min-height: 3px;
  border: 1.5px solid #6f7680; border-top-color: #949ba4;
  border-radius: 1px 1px 6px 6px;
  background: linear-gradient(180deg, #f4f6f8, #c6ccd3);
}
.rack-handle::before, .rack-handle::after {   /* the two mount plates */
  content: ""; position: absolute; top: -45%; height: 190%; width: 15%;
  background: linear-gradient(180deg, #eef1f4, #c3c9d0);
  border: 1px solid #858c95; border-radius: 1px;
}
.rack-handle::before { left: -11%; }
.rack-handle::after { right: -11%; }
.rack-tag {
  position: absolute; left: 7%; right: 7%; bottom: 7%;
  background: rgba(255,255,255,.94); color: #232830;
  font-size: var(--tag-fs, 8px); font-weight: 700; line-height: 1.5;
  border-radius: 2px; padding: 0 2px;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  text-align: center;
}
/* very tall shelves squeeze the drawers: drop the sticker and handle
   rather than render them as unreadable mush */
.unit-shelfgrid.tiny .rack-tag,
.unit-shelfgrid.tiny .rack-handle { display: none; }
.unit-shelfgrid.tiny .unit-cell.on::after {
  content: ""; position: absolute; left: 24%; right: 24%; top: 46%;
  height: 2px; border-radius: 1px; background: #6f7680;
}

.unit-door {
  position: absolute; top: 0; bottom: 0; width: 50%;
  border-radius: 14px; backface-visibility: hidden;
  background: linear-gradient(150deg,
              color-mix(in srgb, var(--pc) 15%, var(--card)),
              var(--card) 55%);
  border: 3px solid color-mix(in srgb, var(--pc) 42%, var(--line));
  display: flex; align-items: center;
  opacity: 0;                 /* only present while the unit is opening */
  pointer-events: none;       /* never swallow clicks meant for a rack */
}
.unit-door-l { left: 0; transform-origin: left center; }
.unit-door-r { right: 0; transform-origin: right center; }
.unit.playing .unit-door-l {
  animation: door-l .58s .3s cubic-bezier(.45,.05,.2,1) both; }
.unit.playing .unit-door-r {
  animation: door-r .58s .3s cubic-bezier(.45,.05,.2,1) both; }
@keyframes door-l {
  from { opacity: 1; transform: rotateY(0); }
  to   { opacity: 1; transform: rotateY(-118deg); }
}
@keyframes door-r {
  from { opacity: 1; transform: rotateY(0); }
  to   { opacity: 1; transform: rotateY(118deg); }
}

.unit-handle {
  position: absolute; width: 5px; height: 54px; border-radius: 3px;
  background: color-mix(in srgb, var(--pc) 60%, var(--muted));
}
.unit-door-l .unit-handle { right: 11px; }
.unit-door-r .unit-handle { left: 11px; }

/* single-door units: the left panel covers the whole front */
.unit[data-doors="1"] .unit-door-l { width: 100%; }
.unit[data-doors="1"] .unit-door-r { display: none; }

/* --- nitrogen tank: looking straight down the bore ------------------- */
.unit[data-doors="lid"] .unit-door,
.unit[data-doors="lid"] .unit-interior { display: none; }
.unit-tank { display: none; }
.unit[data-doors="lid"] .unit-tank {
  display: block; position: absolute; inset: 0; margin: auto;
  width: 372px; height: 372px; border-radius: 50%;
  background: linear-gradient(150deg, #f4f5f7, #d9dde2);
  border: 3px solid #b6bcc4;
  box-shadow: 0 10px 30px rgba(0,0,0,.55);
}
/* the frosted green throat of the tank */
.tank-bore {
  position: absolute; inset: 30px; border-radius: 50%;
  background:
    radial-gradient(circle at 50% 42%, #2f4f3a 0%, #223b2b 55%, #16281d 100%);
  border: 3px solid #cfd5db;
  box-shadow: inset 0 0 26px rgba(0,0,0,.75),
              inset 0 3px 10px rgba(255,255,255,.16);
}
/* the machined center boss the canisters hang off */
.tank-hub {
  position: absolute; inset: 0; margin: auto; width: 86px; height: 86px;
  background: radial-gradient(circle at 42% 36%, #dfe4e9, #9aa2ab 62%, #767e88);
  border: 2px solid #6f7680;
  clip-path: polygon(50% 0,93% 25%,93% 75%,50% 100%,7% 75%,7% 25%);
}
.tank-hub::after {
  content: ""; position: absolute; inset: 0; margin: auto;
  width: 18px; height: 18px; background: #5d646d;
  clip-path: polygon(50% 0,93% 25%,93% 75%,50% 100%,7% 75%,7% 25%);
}
.tank-slots { position: absolute; inset: 0; }
/* each canister sits on a spoke: rotate out, then ride the radius */
.tank-slot {
  position: absolute; left: 50%; top: 50%; width: 0; height: 0;
  transform-origin: 0 0;
  opacity: 0;
}
.unit.playing .tank-slot {
  animation: can-in .3s cubic-bezier(.2,.9,.3,1) both; }
@keyframes can-in {
  from { opacity: 0; }
  to   { opacity: 1; }
}
.can-body {
  position: absolute;
  width: var(--can-w, 38px); height: var(--can-h, 30px);
  left: calc(var(--can-w, 38px) / -2);
  top: calc(var(--can-h, 30px) / -2);
  border-radius: 4px;
  background: linear-gradient(180deg, #eaeef2 0 30%, #c8ced5 30% 68%, #aab1ba);
  border: 1px solid #6f7680;
  box-shadow: inset 0 1px 0 rgba(255,255,255,.9), 0 2px 4px rgba(0,0,0,.5);
}
/* NB: "vacant", not "empty" — there's a generic .empty helper class with a
   60px top margin that would otherwise shove these off their spoke. */
.can-body.vacant {
  background: rgba(0,0,0,.36); border: 1px dashed rgba(255,255,255,.38);
  box-shadow: none; color: rgba(255,255,255,.65);
  display: flex; align-items: center; justify-content: center;
  font-size: 13px; font-weight: 300;
}
/* the pull rod, radiating outward like the real thing */
.can-rod {
  position: absolute; left: -2px; width: 4px; height: 22px;
  top: calc(var(--can-h, 30px) / -2 - 21px);
  border-radius: 2px;
  background: linear-gradient(90deg, #b9c0c8, #eef1f4 45%, #98a0a9);
  box-shadow: 0 1px 2px rgba(0,0,0,.5);
}
.can-rod::before {   /* the ring you hook to lift it */
  content: ""; position: absolute; left: -4px; top: -10px;
  width: 12px; height: 12px; border-radius: 50%;
  border: 2.5px solid #c3cad2; background: transparent;
}
.tank-slot.on:hover .can-body {
  border-color: var(--pc); box-shadow: 0 0 0 2px var(--pc);
}
.tank-slot:hover .can-body.vacant {
  border-style: solid; border-color: var(--pc);
  background: color-mix(in srgb, var(--pc) 22%, transparent);
}
/* rim numbers: the box is centerd on the tank's middle so rotate+translate
   swings it out to the rim; the inner span counter-rotates so the digit
   stays upright wherever it lands. */
.tank-num {
  position: absolute; left: 50%; top: 50%;
  width: 22px; height: 22px; margin: -11px 0 0 -11px;
  transform-origin: 11px 11px;
  display: flex; align-items: center; justify-content: center;
  font-size: 10px; font-weight: 700; color: rgba(226,232,240,.72);
  text-shadow: 0 1px 2px rgba(0,0,0,.8);
  pointer-events: none;
}

/* the plug lifts straight off the mouth */
.unit-lid { display: none; }
.unit[data-doors="lid"] .unit-lid {
  display: block; position: absolute; inset: 0; margin: auto;
  width: 372px; height: 372px; border-radius: 50%;
  background: radial-gradient(circle at 42% 34%, #fdba74, #f97316 58%, #ea580c);
  border: 3px solid #c2410c;
  opacity: 0; pointer-events: none;
}
.unit[data-doors="lid"] .unit-lid::after {   /* the carry handle on top */
  content: ""; position: absolute; left: 50%; top: 50%;
  width: 96px; height: 26px; margin: -13px 0 0 -48px;
  border: 5px solid rgba(255,255,255,.55); border-radius: 8px;
}
.unit.playing .unit-lid {
  animation: lid-off .55s .26s cubic-bezier(.4,.05,.2,1) both; }
@keyframes lid-off {
  from { opacity: 1; transform: scale(1) translateY(0); }
  60%  { opacity: .85; transform: scale(1.1) translateY(-26px); }
  to   { opacity: 0; transform: scale(1.35) translateY(-70px); }
}

.unit-caption {
  margin: 20px 0 2px; font-weight: 700; font-size: 17px; color: var(--text);
}
.unit-sub {
  margin: 0; display: flex; align-items: center; justify-content: center;
  gap: 14px; font-size: 13px;
}
.unit-sub .quietlink { color: var(--pc); }
.unit-sub button.quietlink {
  background: none; border: 0; padding: 0; font: inherit; cursor: pointer;
}

/* --- pulling a rack out of the open unit --- */

/* Freezer → rack → box → tube all stack in the same space, each one
   pushing the last back so the thing you opened visibly came from
   somewhere. What must NOT stack is their furniture: four captions and
   four "← Back to …" buttons landing on top of each other is unreadable.
   So a pushed layer keeps a faint, blurred ghost of its object and drops
   its text entirely — only the layer you're on ever shows controls. */
.dooranim-stage, .rackpull, .boxpop {
  transition: transform .34s cubic-bezier(.2,.8,.25,1),
              opacity .34s ease, filter .34s ease;
}
.dooranim-stage.pushed, .rackpull.pushed, .boxpop.pushed {
  transform: scale(.86) translateY(-6px);
  opacity: .12; filter: blur(3px); pointer-events: none;
}
:is(.dooranim-stage, .rackpull, .boxpop).pushed
  :is(.unit-caption, .unit-sub, .boxactions) {
  visibility: hidden;
}

.rackpull {
  position: absolute; inset: 0; z-index: 3;
  display: flex; flex-direction: column;
  align-items: center; justify-content: safe center;
  overflow: auto; padding: 24px; text-align: center;
  perspective: 1100px; perspective-origin: 50% 30%;
}
.rackpull[hidden] { display: none; }

/* A real -80 rack is a long box whose cells open along its *side*: racked
   up in the freezer, the only thing facing you is the handled end panel.
   So the reveal is two moves, in the order you'd actually do them — pull it
   straight out towards you (still end-on, you're looking at that panel),
   then swing it 90° about its vertical axis so the open side comes round.
   Hence rotateY, not rotateX: it turns like a door, it doesn't tip over.

   The drawer is a genuine 3D object for this — the end panel is a real
   face sitting perpendicular to the grid, so the swing shows one giving way
   to the other instead of a flat card flipping. */
.drawer {
  --depth: 88px;
  position: relative; display: inline-block;
  padding: 9px 11px 12px; border-radius: 5px;
  background: linear-gradient(180deg,
              #e9edf1 0 10%, #ccd2d9 10% 62%, #aab1ba 100%);
  border: 1px solid #6d747e;
  box-shadow: inset 0 1px 0 rgba(255,255,255,.9),
              inset 0 -4px 7px rgba(0,0,0,.16),
              0 22px 42px rgba(0,0,0,.62);
  transform-style: preserve-3d;
  transform-origin: 50% 50%;
}

/* the stainless end panel, hinged perpendicular at the rack's right edge */
.drawer-face {
  position: absolute; top: -1px; right: -1px; bottom: -1px;
  width: var(--depth); border-radius: 4px;
  transform-origin: 100% 50%;
  transform: rotateY(90deg);
  background: linear-gradient(100deg,
              #f4f6f8 0 18%, #dde2e7 44%, #c2c9d1 72%, #a7aeb7 100%);
  border: 1px solid #6d747e;
  box-shadow: inset 0 1px 0 rgba(255,255,255,.95),
              inset -4px 0 8px rgba(0,0,0,.14);
  display: flex; flex-direction: column;
  align-items: center; justify-content: space-evenly;
}
/* the two bar handles you actually grab */
.drawer-grip {
  width: 58%; height: 9px; border-radius: 3px;
  background: linear-gradient(180deg, #fbfcfd, #b9c0c8 60%, #8d949d);
  box-shadow: 0 2px 3px rgba(0,0,0,.45),
              inset 0 1px 0 rgba(255,255,255,.9);
  position: relative;
}
.drawer-grip::before, .drawer-grip::after {   /* mount plates */
  content: ""; position: absolute; top: -3px; width: 7px; height: 15px;
  border-radius: 2px; background: linear-gradient(180deg, #e3e8ec, #979ea7);
}
.drawer-grip::before { left: -6px; }
.drawer-grip::after { right: -6px; }

/* Transforms only — deliberately no opacity here. Animating opacity on the
   same element that carries preserve-3d makes Chromium flatten the 3D
   context, which silently collapses the end panel to nothing. The rack
   starting 340px back is enough to read as emerging anyway. */
.drawer.playing { animation: drawer-out .98s cubic-bezier(.34,.03,.28,1) both; }
@keyframes drawer-out {
  /* racked in the freezer: end-on and set back in the dark */
  0%   { transform: translateZ(-340px) rotateY(-90deg); }
  /* pulled the whole way out, still end-on — the panel is facing you */
  52%  { transform: translateZ(0) rotateY(-90deg); }
  60%  { transform: translateZ(0) rotateY(-90deg); }   /* beat before the turn */
  /* swung round: the open side, and every box in it, comes into view */
  100% { transform: translateZ(0) rotateY(0deg); }
}

/* The pull rail belongs to the canister only — a freezer rack carries its
   handles on the end panel instead, so there's nothing to draw here. */
.drawer-rail { display: none; }
.rackpull.lift .drawer-rail {
  display: block;
  height: 9px; margin: 0 0 9px; border-radius: 3px;
  background: linear-gradient(90deg, #6b727b, #aeb5be 40%, #7f868f);
  position: relative;
}
/* ...and the end panel belongs to the freezer rack only. */
.rackpull.lift .drawer-face { display: none; }
.drawer-handle {
  position: absolute; left: 50%; top: -4px; width: 34%; height: 7px;
  transform: translateX(-50%); border-radius: 4px;
  background: linear-gradient(180deg, #f2f5f8, #9aa1aa);
  box-shadow: 0 1px 2px rgba(0,0,0,.45);
}

/* the well the boxes sit down inside */
.drawer-well {
  padding: 10px; border-radius: 4px;
  background: linear-gradient(180deg,
              color-mix(in srgb, #000 62%, var(--bg)),
              color-mix(in srgb, #000 44%, var(--bg)));
  box-shadow: inset 0 3px 12px rgba(0,0,0,.75);
}
.boxgrid {
  display: grid; gap: 7px;
  grid-template-columns: repeat(var(--bcols, 3), var(--bcell, 60px));
}

/* one opening in the rack — a box in it, or an empty square */
.boxwell {
  position: relative; height: var(--bcell, 60px);
  border-radius: 3px; opacity: 0;
  display: flex; flex-direction: column;
  align-items: center; justify-content: center; gap: 2px;
  overflow: hidden; text-decoration: none;
}
.drawer.playing .boxwell {
  animation: boxwell-in .26s cubic-bezier(.2,.9,.3,1) both;
}
@keyframes boxwell-in {
  from { opacity: 0; transform: translateY(-6px) scale(.86); }
  to   { opacity: 1; transform: none; }
}

.boxwell:not(.on) {           /* an empty opening: you see the well floor */
  border: 1px dashed color-mix(in srgb, var(--pc) 34%, #6d747e);
  background: rgba(0,0,0,.42);
  color: color-mix(in srgb, var(--pc) 62%, var(--muted));
  font-size: 17px; font-weight: 300;
  box-shadow: inset 0 3px 8px rgba(0,0,0,.6);
}
.boxwell:not(.on):hover {
  border-style: solid; border-color: var(--pc);
  background: color-mix(in srgb, var(--pc) 16%, rgba(0,0,0,.42));
}

.boxwell.on {                 /* a box sitting in the opening */
  background: linear-gradient(180deg,
              color-mix(in srgb, var(--pc) 42%, #dfe4ea),
              color-mix(in srgb, var(--pc) 24%, #9fa7b1));
  border: 1px solid color-mix(in srgb, #000 34%, var(--pc));
  box-shadow: inset 0 1px 0 rgba(255,255,255,.75), 0 2px 5px rgba(0,0,0,.55);
}
.boxwell.on:hover {
  filter: brightness(1.14);
  box-shadow: inset 0 1px 0 rgba(255,255,255,.85),
              0 0 0 2px var(--pc), 0 3px 7px rgba(0,0,0,.6);
}
.box-lid {                    /* the cardboard lid seam across the top */
  position: absolute; left: 0; right: 0; top: 22%;
  border-top: 1px solid rgba(0,0,0,.32);
}
.box-name {
  max-width: 92%; padding: 0 2px;
  font-size: 9px; font-weight: 700; line-height: 1.15; color: #10151b;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.box-fill { font-size: 8px; font-weight: 600; color: rgba(16,21,27,.66); }
.boxgrid.tiny .box-name, .boxgrid.tiny .box-fill { display: none; }
.boxgrid.tiny .boxwell:not(.on) { font-size: 12px; }

/* --- taking one box out and getting the lid off --- */

.boxpop {
  position: absolute; inset: 0; z-index: 4;
  display: flex; flex-direction: column;
  align-items: center; justify-content: safe center;
  overflow: auto; padding: 24px; text-align: center;
  /* Deliberately distant — the deck is as deep as the box is wide, and at
     a close perspective its near edge swings towards the camera and blows
     up to several times its real size. Far enough back to be near
     orthographic keeps the grid the size it actually is. */
  perspective: 3000px; perspective-origin: 50% 40%;
}
.boxpop[hidden] { display: none; }

/* The box is a real 3D object: a front label face you see while it's still
   in the rack, and a horizontal deck of tubes under a lid. It comes out
   front-first, turns to put its top towards you, then the lid lifts away.
   As with the rack, no opacity is animated on anything carrying
   preserve-3d — that flattens the scene in Chromium and the faces vanish. */
/* A real 2" cryo box is about 133mm square by 51mm tall, so the front face
   is roughly 0.38 as tall as the box is wide. Deriving it from the width
   keeps that proportion at every box size — a fixed height made a 9×9 look
   like a letterbox. The big top surface is the deck, hinged at its back. */
.samplebox {
  --bw: 300px; --bh: 300px; --side: calc(var(--bw) * .38);
  position: relative; width: var(--bw); height: var(--side);
  transform-style: preserve-3d;
  transform-origin: 50% 0%;
}
.samplebox.playing { animation: box-out 1.18s cubic-bezier(.34,.03,.26,1) both; }
@keyframes box-out {
  /* sitting in the rack, front label towards you, set back */
  0%   { transform: translateZ(-320px) translateY(6px) rotateX(0deg); }
  44%  { transform: translateZ(0) translateY(0) rotateX(0deg); }
  52%  { transform: translateZ(0) translateY(0) rotateX(0deg); }   /* beat */
  /* Turns to lie top-up under your eye. The deck swings up out of the box's
     footprint, so the box settles downwards as it tips — otherwise the top
     surface ends up hanging off the top of the screen. */
  100% { transform: translateZ(0) translateY(calc(var(--bh) * .44))
                    rotateX(-76deg); }
}

/* the cardboard front, with the box's name on it */
.sbox-front {
  position: absolute; inset: 0; border-radius: 4px;
  display: flex; align-items: center; justify-content: center;
  background: linear-gradient(160deg,
              color-mix(in srgb, var(--pc) 34%, #e7ebef),
              color-mix(in srgb, var(--pc) 18%, #aab2bb));
  border: 1px solid color-mix(in srgb, #000 38%, var(--pc));
  box-shadow: inset 0 1px 0 rgba(255,255,255,.7), 0 14px 30px rgba(0,0,0,.6);
  font-weight: 700; font-size: 17px; color: #10151b;
}

/* the deck of tubes: the box's top surface, hinged along its back edge */
.sbox-deck, .sbox-lid {
  position: absolute; left: 0; bottom: 0;
  width: var(--bw); height: var(--bh);
  transform-origin: 50% 100%;
  border-radius: 4px;
}
.sbox-deck {
  transform: rotateX(90deg);
  display: flex; align-items: center; justify-content: center;
  padding: 10px;
  background: linear-gradient(180deg, #20262e, #171c23);
  border: 1px solid #39424e;
  box-shadow: inset 0 3px 14px rgba(0,0,0,.8);
}
/* the lid, floating just above the deck until it comes off */
.sbox-lid {
  transform: rotateX(90deg) translateZ(13px);
  display: flex; align-items: center; justify-content: center;
  background: linear-gradient(150deg,
              color-mix(in srgb, var(--pc) 40%, #eef1f4),
              color-mix(in srgb, var(--pc) 22%, #b3bac2));
  border: 1px solid color-mix(in srgb, #000 34%, var(--pc));
  box-shadow: 0 10px 22px rgba(0,0,0,.55), inset 0 1px 0 rgba(255,255,255,.75);
}
.sbox-lidname {
  font-weight: 700; font-size: 15px; color: #10151b; opacity: .75;
}
/* Lifted off once the box has turned: up off the rim first, then slid back
   and away so the deck is left completely clear. It's decorative from the
   moment it starts moving, so it stops taking clicks. */
.sbox-lid { pointer-events: none; }
.samplebox.playing .sbox-lid {
  animation: lid-off .52s cubic-bezier(.32,.06,.24,1) 820ms both;
}
@keyframes lid-off {
  0%   { transform: rotateX(90deg) translateZ(13px) translateY(0); opacity: 1; }
  34%  { transform: rotateX(90deg) translateZ(64px) translateY(0); opacity: 1; }
  100% { transform: rotateX(90deg) translateZ(64px)
                   translateY(calc(var(--bh) * -1.2)); opacity: 0; }
}

.tubegrid {
  display: grid; gap: 3px;
  grid-template-columns: repeat(var(--tcols, 9), var(--tcell, 30px));
}
/* One position in the box. The compartment itself is square — it's formed
   by the box's cardboard divider — while what sits in it is a round vial.
   So the hole stays square and the cap inside it is the round part. */
.tube {
  width: var(--tcell, 30px); height: var(--tcell, 30px);
  border-radius: 2px; opacity: 0;
  display: flex; align-items: center; justify-content: center;
  text-decoration: none;
  background: rgba(0,0,0,.5); border: 1px solid #3d4653;
  box-shadow: inset 0 2px 5px rgba(0,0,0,.7);
}
.samplebox.playing .tube {
  animation: tube-in .2s ease-out both;
}
@keyframes tube-in {
  from { opacity: 0; transform: scale(.5); }
  to   { opacity: 1; transform: none; }
}

/* The vial cap you're looking down on — white, like the real Eppendorf lid,
   not tinted by the section's color (that read as an odd yellow/orange on
   some sections). */
.tube .cap {
  width: 84%; height: 84%; border-radius: 50%;
  display: flex; align-items: flex-start; justify-content: center;
  padding-top: 16%;
  overflow: hidden;
  font-size: calc(var(--tcell, 30px) * .25); font-weight: 700;
  line-height: 1; color: #0d1219;
  background: radial-gradient(circle at 38% 30%, #ffffff, #d5d9de);
  border: 1px solid #aab0b8;
  box-shadow: inset 0 1px 0 rgba(255,255,255,.85), 0 1px 2px rgba(0,0,0,.55);
}
.tube.gone {
  background: repeating-linear-gradient(45deg,
              rgba(255,255,255,.05) 0 3px, transparent 3px 6px);
  border-style: dashed; border-color: #5b6675;
}
.tube:hover { outline: 2px solid var(--pc); outline-offset: 1px; }
.tubegrid.tiny .cap { font-size: 0; }   /* too small to letter — tooltip carries it */

/* --- lifting one tube out of the box --- */

.tubepop {
  position: absolute; inset: 0; z-index: 5;
  display: flex; flex-direction: column;
  align-items: center; justify-content: safe center;
  overflow: auto; padding: 20px; text-align: center;
}
.tubepop[hidden] { display: none; }

.vialwrap {
  flex: none; position: relative;
  height: min(46vh, 340px); aspect-ratio: 262 / 595;
  filter: drop-shadow(0 16px 26px rgba(0,0,0,.66));
}
.vial-img { display: block; width: 100%; height: 100%; }
/* Sits exactly over the tube's write-on panel — the numbers come from
   measuring the panel in the artwork, so it tracks at any size. */
.vial-label {
  position: absolute;
  left: 27.3%; top: 23.4%; width: 42.7%; height: 36.6%;
  display: flex; flex-direction: column;
  align-items: flex-start; justify-content: flex-start;
  padding: 2% 3%; overflow: hidden;
  opacity: 0;
}
/* Written the way a hand actually fills one in: the ID first, top-left,
   then the sample's name, then what's in it — all three left-aligned,
   wrapping onto as many lines as the panel's width allows rather than
   running off the edge. */
.vial-id, .vial-name-ink, .vial-contents-ink {
  display: block; width: 100%;
  white-space: normal; overflow-wrap: break-word; word-break: break-word;
  color: #10151b;
}
.vial-id { font-weight: 700; line-height: 1.08; }
.vial-name-ink { font-weight: 600; line-height: 1.1; margin-top: .2em; }
.vial-contents-ink { font-weight: 500; line-height: 1.12; margin-top: .15em; }
/* Comes up out of the box the way you'd actually pick one out — lifted
   clear, with the little tilt of being held, settling upright. */
.vialwrap.playing { animation: vial-up .62s cubic-bezier(.2,.78,.28,1) both; }
@keyframes vial-up {
  0%   { opacity: 0; transform: translateY(190px) scale(.52) rotate(-9deg); }
  30%  { opacity: 1; }
  70%  { transform: translateY(-8px) scale(1.02) rotate(2.5deg); }
  100% { opacity: 1; transform: translateY(0) scale(1) rotate(0deg); }
}

.vialwrap.playing .vial-label { animation: vial-ink .3s ease-out 380ms both; }
@keyframes vial-ink { from { opacity: 0; } to { opacity: 1; } }

/* the way back to the bulk tools, which used to be a page load away */
.boxactions { margin-top: 8px; gap: 10px; flex-wrap: wrap; }
/* --pc is set per-section inside the overlay, but this button is also used
   on the project page where nothing defines it. Without a fallback the
   background resolves to nothing and the dark text disappears against the
   dark page — the button is there, just invisible. */
.boxaction {
  background: var(--pc, var(--accent));
  border-color: var(--pc, var(--accent));
  color: #08131a; font-weight: 700;
}
.boxaction:hover { filter: brightness(1.12); }

/* --- hauling a canister up out of the tank --- */

/* A dewar canister doesn't slide sideways — you hook its rod and lift it
   straight up the bore. So it rises instead of pulling out, and it keeps
   the rod and ring it was hanging from. */
.lift-rod { display: none; }
.rackpull.lift .lift-rod {
  display: block; position: relative; flex: none;
  width: 6px; height: 54px; margin: 0 0 -3px; border-radius: 3px;
  background: linear-gradient(90deg, #98a0a9, #eef1f4 45%, #b9c0c8);
  box-shadow: 0 2px 5px rgba(0,0,0,.6);
}
.rackpull.lift .lift-rod::before {   /* the ring you hook it by */
  content: ""; position: absolute; left: 50%; top: -20px;
  width: 26px; height: 26px; margin-left: -13px; border-radius: 50%;
  border: 4px solid #c3cad2; background: transparent;
  box-shadow: 0 2px 4px rgba(0,0,0,.55);
}
.rackpull.lift.playing .lift-rod {
  animation: canister-lift .58s cubic-bezier(.16,.82,.28,1) both;
}

/* the canister body: a cylinder, not a drawer — no tilt, rounded ends */
.rackpull.lift .drawer {
  border-radius: 12px / 9px;
  padding: 13px 12px 15px;
  background: linear-gradient(90deg,
              #8f979f 0 4%, #e6ebef 16%, #f6f8fa 34%,
              #cdd3da 62%, #a8b0b8 86%, #848c95 100%);
  transform-origin: 50% 100%;
}
.rackpull.lift .drawer.playing {
  animation: canister-lift .58s cubic-bezier(.16,.82,.28,1) both;
}
@keyframes canister-lift {
  from { opacity: 0; transform: translateY(96px) scaleY(.72); }
  55%  { opacity: 1; }
  to   { opacity: 1; transform: translateY(0) scaleY(1); }
}
/* the drawer's front rail is a drawer thing; a canister has a lip instead */
.rackpull.lift .drawer-rail {
  height: 5px; border-radius: 999px; margin-bottom: 8px;
  background: linear-gradient(90deg, #7f868f, #dfe4e9 45%, #7f868f);
}
.rackpull.lift .drawer-handle { display: none; }
/* frosted-green throat, matching what you see down the bore */
.rackpull.lift .drawer-well {
  background: linear-gradient(180deg, #1c3327, #142418);
  box-shadow: inset 0 4px 14px rgba(0,0,0,.8);
}

@media (prefers-reduced-motion: reduce) {
  .dooranim { display: none !important; }
}

.iconpick { margin: 4px 0; }
.iconchip { display: inline-flex; align-items: center; gap: 6px; }
.iconchip img { width: 22px; height: 28px; }

/* the shelf/rack grid: a real <table>, high-contrast on purpose so it
   never again reads as a blank/dead page. */
.shelftable-wrap { overflow-x: auto; margin: 14px 0; }
.shelftable { border-collapse: collapse; margin: 0 auto; }
.shelftable th {
  font-size: 12px; font-weight: 600; color: var(--muted);
  padding: 4px 6px; text-align: center;
}
.shelftable th.corner { background: transparent; }
.shelftable td.shelfcell {
  width: 84px; height: 66px; padding: 0;
  border: 1px solid var(--line);
  background: var(--card);
}
.shelftable td.shelfcell a {
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  width: 100%; height: 100%; gap: 3px; text-align: center; padding: 4px;
  box-sizing: border-box;
}
.shelfcell.empty a {
  color: var(--muted); font-size: 26px; font-weight: 300;
}
.shelfcell.empty a:active { background: var(--bg); }
.shelfcell.filled {
  border: 2px solid var(--pc);
  background: color-mix(in srgb, var(--pc) 18%, var(--card));
}
.shelfcell.filled a:active { opacity: .8; }
.shelfcell .scname {
  font-weight: 700; font-size: 12px; color: var(--text);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 100%;
}
.shelfcell .scfill { font-size: 11px; color: var(--muted); font-weight: 600; }

.slotgrid {
  display: grid;
  grid-template-columns: repeat(var(--gcols), 1fr);
  gap: 4px;
  margin: 14px 0;
}
.slotcell {
  aspect-ratio: 1;
  border-radius: 5px;
  border: 1px solid var(--line);
  background: var(--card);
  display: flex; align-items: center; justify-content: center;
  font-size: 9px; font-weight: 600; color: var(--muted);
  overflow: hidden;
}
.slotcell.filled {
  background: color-mix(in srgb, var(--accent) 25%, var(--card));
  border-color: var(--accent); color: var(--text);
}
.slotcell.inactive {
  background: repeating-linear-gradient(45deg, var(--line), var(--line) 3px,
              var(--bg) 3px, var(--bg) 6px);
  border-style: dashed;
}
.warn-banner { background: var(--warn); }

.bulkbar { display: flex; align-items: center; flex-wrap: wrap; gap: 8px 10px; margin: 10px 0; }
.bulkbar .muted { font-size: 13px; }
#bulk-toolbar, #seq-toolbar, #clear-toolbar {
  background: var(--card); border: 1px solid var(--accent);
  border-radius: 12px; padding: 12px; margin: 0 0 12px;
}
#clear-toolbar { border-color: var(--danger); }
#bulk-toolbar[hidden], #seq-toolbar[hidden], #clear-toolbar[hidden] {
  display: none;
}
#bulk-toolbar .addrow input, #seq-toolbar .addrow input { min-width: 0; }
#bulk-toolbar .addrow button[type="submit"],
#seq-toolbar .addrow button[type="submit"] { flex: 2; }
.slotgrid.bulk-mode .slotcell { cursor: pointer; user-select: none; touch-action: none; }
.slotgrid.bulk-mode .slotcell.inactive { cursor: not-allowed; }
.slotcell.bulk-sel {
  outline: 3px solid var(--accent); outline-offset: -2px;
  background: color-mix(in srgb, var(--accent) 45%, var(--card));
}
/* Erasing, not filling — selection reads red, and slots that hold nothing
   stay muted so you can see what the drag would actually empty. */
.slotgrid.clear-mode .slotcell.bulk-sel {
  outline-color: var(--danger);
  background: color-mix(in srgb, var(--danger) 42%, var(--card));
  text-decoration: line-through;
}
.slotgrid.clear-mode .slotcell.bulk-sel:not(.filled) {
  outline-style: dashed; opacity: .55;
  background: color-mix(in srgb, var(--danger) 14%, var(--card));
  text-decoration: none;
}

.rowhead {
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 3px; font-size: 10px; color: var(--muted);
}
.rowhead .rownum { font-weight: 700; }
.rowhead .rownum.copied { color: var(--accent); }
.rowbtn {
  font-size: 9px; padding: 2px 5px; border-radius: 4px; line-height: 1.4;
  background: var(--card); border: 1px solid var(--line); color: var(--muted);
  cursor: pointer;
}
.rowbtn.paste { background: var(--accent); color: #fff; border-color: var(--accent); font-weight: 600; }

.searchform { display: flex; gap: 8px; margin: 4px 0 16px; }
.searchform input { flex: 1; min-width: 0; }
.searchform button { flex: none; padding: 11px 16px; }
.searchlink { margin-left: auto; }
.crumb { display: flex; align-items: center; }

.searchhit {
  display: flex; flex-direction: column; gap: 2px;
  background: color-mix(in srgb, var(--pc) 9%, var(--card));
  border: 1px solid color-mix(in srgb, var(--pc) 32%, var(--line));
  border-left: 4px solid var(--pc);
  border-radius: 10px; padding: 10px 13px; margin-bottom: 8px;
}
.searchhit .title { font-weight: 600; font-size: 15px; color: var(--text); }
.searchhit .sub { font-size: 13px; color: var(--muted); }
.searchhit .crumb-inline { font-size: 12px; color: var(--pc); margin-top: 2px; }

/* people */
.personcard {
  background: var(--card); border: 1px solid var(--line);
  border-left: 4px solid var(--pc);
  border-radius: 12px; padding: 12px 14px; margin-bottom: 10px;
}
.personhead { display: flex; align-items: center; gap: 10px; }
.persontitle { display: flex; flex-direction: column; gap: 2px; flex: 1; min-width: 0; }
.persontitle .title { font-weight: 700; font-size: 16px; }
.persontitle .sub { font-size: 12px; color: var(--muted); }
.feedrow { display: flex; flex-direction: column; gap: 8px; margin: 12px 0; }
.feedurl { font-size: 12px; font-family: ui-monospace, SFMono-Regular, monospace; }
.personcard .addrow { margin-top: 8px; }

@media (max-width: 430px) {
  .topbar { padding-left: 10px; padding-right: 10px; }
  .topbar .brand { display: none; }
  .topbar nav { gap: 2px; width: 100%; }
  .topbar nav a { padding: 6px 8px; font-size: 14px; }
  .steptools select, .steptools input { flex: 1 1 100%; }
}

/* the printable week sheet */
.printsheet { font-size: 14px; }
.printhead h1 { font-size: 20px; margin: 0 0 4px; }
.printhead p { margin: 4px 0 14px; font-size: 13px; }
.printhead .noprint { display: flex; align-items: center; gap: 14px; }
.printday { border-top: 2px solid var(--text); padding: 8px 0 12px; }
.printday h2 { font-size: 15px; margin: 0 0 6px; }
.printlane { margin: 0 0 10px 0; }
.printlane h3 { font-size: 13px; margin: 0 0 4px; }
.printrow { display: flex; align-items: flex-start; gap: 8px; padding: 3px 0; }
.printrow .box {
  width: 15px; height: 15px; border: 1px solid var(--text); border-radius: 3px;
  flex: none; margin-top: 2px; font-size: 11px; line-height: 13px;
  text-align: center;
}
.printmain { display: flex; flex-direction: column; flex: 1; min-width: 0; }
.printmain .title { font-weight: 600; }
.printmain .sub { font-size: 11px; color: var(--muted); }
.printest { flex: none; font-size: 11px; color: var(--muted); }
.printrow.done .title { text-decoration: line-through; color: var(--muted); }
.printnotes { border-top: 2px solid var(--text); padding-top: 8px; }
.printnotes h2 { font-size: 15px; margin: 0 0 4px; }
.printnotes p { white-space: pre-wrap; font-size: 13px; }

@media print {
  :root {
    --bg: #fff; --card: #fff; --text: #000; --muted: #555; --line: #999;
  }
  body { padding: 0; background: #fff; }
  main { max-width: none; padding: 0; }
  .noprint { display: none !important; }
  .printday { break-inside: avoid; }
}

.exportline {
  display: flex; align-items: baseline; flex-wrap: wrap; gap: 4px 10px;
  margin: 8px 4px 14px; font-size: 13px;
}
.exportline .quietlink { color: var(--accent); font-weight: 600; }

/* --- project data pipeline: folders, tables, plots, collations --- */

.datafolder {
  background: var(--card); border: 1px solid var(--line);
  border-radius: 12px; padding: 6px 10px; margin: 0 0 6px;
  margin-left: calc(var(--depth, 0) * 16px);
}
/* A shut folder is one line and nothing else — with everything collapsed by
   default, the gap under each header was most of the page. */
.datafolder > .dfhead { margin-bottom: 0; }
.datafolder[open] > .dfhead { margin-bottom: 6px; }
/* The raw-data tree nests for real, so the indent comes from the DOM and the
   depth variable would double it. The rule is the thread back to the parent
   when a deep folder is open. */
.datafolder.nested { margin-left: 0; }
.foldkids {
  margin: 6px 0 2px 6px; padding-left: 10px;
  border-left: 1px solid var(--line);
}
.foldkids > .datafolder:last-child { margin-bottom: 0; }
.foldcount { white-space: nowrap; font-weight: 600; }
/* Expand/collapse sits in the section heading rather than on a row of its
   own — a full row for an occasional control gives back most of the space
   collapsing the folders had just saved. Quiet on purpose: the teal chips
   beside it are the actions you came to the page for. */
.foldall {
  margin-left: auto; background: none; border: 1px solid var(--line);
  border-radius: 999px; padding: 2px 10px; cursor: pointer;
  font: inherit; font-size: 11px; letter-spacing: .04em;
  text-transform: none; color: var(--muted);
}
.foldall:hover { color: var(--accent); border-color: var(--accent); }
/* A folder header is now a <summary>, so it needs the flex layout to
   survive list-item display and it gets its own caret. */
.dfhead {
  display: flex; align-items: center; gap: 8px;
  font-size: 13px; font-weight: 700; margin-bottom: 6px;
  cursor: pointer; user-select: none; list-style: none;
}
.dfhead::-webkit-details-marker { display: none; }
.dfhead::before {
  content: "▾"; font-size: 10px; color: var(--muted);
  transition: transform .12s ease;
}
.datafolder:not([open]) > .dfhead::before { transform: rotate(-90deg); }
.dfhead:hover .dfname { color: var(--accent); }
.dfhead .dfname { flex: 1; min-width: 0; }
/* One line per file, several across the width. These lists run to dozens of
   rows; a stacked two-line card each turned that into a lot of scrolling for
   very little information. Both halves truncate rather than wrap so a row is
   always exactly one line tall. */
.datasetrow {
  display: flex; flex-direction: row; align-items: baseline; gap: 8px;
  padding: 5px 9px; border-radius: 8px; margin: 0;
  background: var(--bg); border: 1px solid var(--line);
  min-width: 0; overflow: hidden;
}
.datasetrow:hover { border-color: var(--accent); }
.datasetrow .title {
  font-size: 13px; font-weight: 600; flex: 0 1 auto;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.datasetrow .sub {
  font-size: 11px; flex: 1 1 auto; min-width: 0;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  text-align: right;
}

/* Rows sit in their own grid container rather than on the <details> itself:
   a details element wraps everything after its summary in one anonymous box,
   so making the details a grid gives you a single stacked cell, not columns. */
.rowgrid {
  display: grid; gap: 5px;
  grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
  align-items: start;
}

.fieldlabel { font-size: 14px; font-weight: 600; margin: 4px 0 0; }
.checkline {
  flex-direction: row; align-items: center; gap: 8px;
  font-weight: normal; font-size: 14px;
}
.pickchip { display: inline-flex; align-items: center; gap: 6px; cursor: pointer; }
.formula {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 12px;
}

/* a data table that can be wider than the phone it's read on */
.tablewrap {
  overflow-x: auto; border: 1px solid var(--line); border-radius: 10px;
  margin: 8px 0 4px; background: var(--card);
}
.datatable { border-collapse: collapse; width: 100%; font-size: 13px; }
.datatable th, .datatable td {
  padding: 6px 10px; text-align: right; white-space: nowrap;
  border-bottom: 1px solid var(--line);
}
.datatable th {
  position: sticky; top: 0; background: var(--card);
  font-weight: 700; font-size: 12px; color: var(--muted);
}
.datatable tbody tr:last-child td { border-bottom: 0; }
.datatable .rownum, .datatable .rowname {
  text-align: left; color: var(--muted); font-weight: 600;
}
.datatable .rowname { color: var(--text); }

/* the fitted curve */
.plotwrap { margin: 10px 0 4px; }
.plot {
  width: 100%; height: auto; max-height: 52vh;
  background: var(--card); border: 1px solid var(--line);
  border-radius: 12px; padding: 6px 8px 2px;
  overflow: hidden;
}
.plot .gridline { stroke: var(--line); stroke-width: .3; }
.plot .axis { stroke: var(--muted); stroke-width: .6; }
.plot .ticklabel {
  fill: var(--muted); font-size: 4px;
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
}
.plot .ytick { text-anchor: end; dominant-baseline: middle; }
.plot .xtick { text-anchor: middle; }
.plot .axistitle {
  fill: var(--muted); font-size: 4.4px; text-anchor: middle;
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
}
/* Round caps are what turn the zero-length dashes in datasets.DASHES into
   round dots; on a solid curve they're invisible either way. */
.plot .fitline {
  fill: none; stroke-width: 1.1; vector-effect: non-scaling-stroke;
  stroke-linecap: round;
}
.legendchip .fitline { stroke-linecap: round; }
.plot .fitpoint { stroke: var(--card); stroke-width: .5; }

/* One color per curve, shared by the line, its points and the legend key.
   --sc is set inline per element now that a curve's color is editable; the
   s0-s5 classes stay as the fallback palette for anything still using them. */
.s0 { --sc: #2dd4bf; } .s1 { --sc: #a78bfa; } .s2 { --sc: #fb923c; }
.s3 { --sc: #38bdf8; } .s4 { --sc: #f472b6; } .s5 { --sc: #facc15; }
.plot .fitline, .legendchip .fitline { stroke: var(--sc); }
.plot .fitpoint, .legendchip .fitpoint { fill: var(--sc); }
.legendrow { flex-wrap: wrap; gap: 4px 14px; margin-top: 2px; }
.legend { display: inline-flex; align-items: center; gap: 6px; font-size: 13px; }
.legend .swatch {
  width: 12px; height: 3px; border-radius: 2px; background: var(--sc);
}

/* choosing what a collation gathers */
.pickrow {
  display: flex; align-items: center; gap: 10px;
  padding: 6px 0; border-bottom: 1px solid var(--line);
}
.pickrow .checkline { flex: 1; min-width: 0; }
.pickval {
  font-weight: 700; font-variant-numeric: tabular-nums;
  min-width: 5.5em; flex: none;
}
.pickwhat { font-size: 13px; min-width: 0; overflow-wrap: anywhere; }
.groupbox { width: 9em; flex: none; padding: 6px 9px; font-size: 13px; }

@media (max-width: 430px) {
  .pickrow { flex-wrap: wrap; }
  .groupbox { width: 100%; }
}

/* --- 96-well plate map --- */
.platewrap { overflow-x: auto; margin: 8px 0 4px; }
.platemap { border-collapse: separate; border-spacing: 3px; }
.platemap th {
  font-size: 11px; color: var(--muted); font-weight: 700;
  padding: 0 2px; white-space: nowrap;
}
.well {
  width: 54px; height: 40px; padding: 2px; border-radius: 7px;
  display: flex; flex-direction: column; align-items: center;
  justify-content: center; gap: 1px; cursor: pointer;
  background: var(--card); border: 1px solid var(--line); color: var(--text);
  font: inherit; line-height: 1;
}
.well .wname { font-size: 9px; color: var(--muted); font-weight: 700; }
.well .wval { font-size: 11px; font-variant-numeric: tabular-nums; }
/* one tint per condition, matching the curve colors on the fit */
.well.c0 { border-color: #2dd4bf; background: color-mix(in srgb, #2dd4bf 12%, var(--card)); }
.well.c1 { border-color: #a78bfa; background: color-mix(in srgb, #a78bfa 12%, var(--card)); }
.well.c2 { border-color: #fb923c; background: color-mix(in srgb, #fb923c 12%, var(--card)); }
.well.c3 { border-color: #38bdf8; background: color-mix(in srgb, #38bdf8 12%, var(--card)); }
.well.veh { border-style: dashed; }
.well.none { opacity: .35; cursor: default; }
/* an excluded well stays visible and legible — you need to see what you
   dropped, so it's struck through rather than hidden */
.well.off {
  background: repeating-linear-gradient(45deg,
              rgba(224,96,79,.20) 0 4px, transparent 4px 8px);
  border-color: var(--danger); border-style: solid;
}
.well.off .wval { text-decoration: line-through; color: var(--danger); }
.legend .swatch-off { background: var(--danger); }
.legend.c0 { --sc: #2dd4bf; } .legend.c1 { --sc: #a78bfa; }
.legend.c2 { --sc: #fb923c; } .legend.c3 { --sc: #38bdf8; }

@media (max-width: 700px) {
  .well { width: 44px; height: 34px; }
  .well .wval { font-size: 10px; }
}

/* row / column headers on the plate map double as bulk exclude toggles */
.hdrbtn {
  background: transparent; border: 1px solid transparent; color: var(--muted);
  font: inherit; font-size: 11px; font-weight: 700;
  padding: 2px 6px; border-radius: 6px; cursor: pointer; line-height: 1.2;
  min-width: 22px;
}
.hdrbtn:hover { background: var(--card); border-color: var(--line); color: var(--text); }
.hdrbtn:active { opacity: .7; }

/* A header toggle is a fingertip target on touch, not just a label. 22x19px
   is fine for a mouse and far too small for a thumb, and mis-tapping one
   excludes a whole row. */
@media (pointer: coarse), (max-width: 700px) {
  .hdrbtn { min-width: 34px; padding: 9px 8px; font-size: 12px; }
}

/* On the project page this row sits in normal page flow, not centerd in an
   overlay, so it lines up with the headings above it. */
.projectactions { justify-content: flex-start; margin: 12px 4px 4px; }

/* SEM whiskers: same color as their series, thin enough not to compete
   with the fitted curve */
.plot .errbar, .plot .errcap {
  stroke-width: .9; vector-effect: non-scaling-stroke; opacity: .85;
}
.plot .errbar, .plot .errcap { stroke: var(--sc); }

/* The key now lives inside the SVG, right of the axes. Baseline-shifted to
   the middle so each name lines up with its own swatch rather than sitting
   on it. */
.plot .keylabel {
  fill: var(--text, var(--fg)); font-size: 4px;
  dominant-baseline: middle; text-anchor: start;
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
}

/* click a key entry to hide its curve */
.plot .keyel { cursor: pointer; }
.plot .keyhit { fill: transparent; }
.plot .keyel:hover .keylabel { fill: var(--accent); }
.plot .keyel:focus-visible .keyhit {
  fill: none; stroke: var(--accent); stroke-width: .5; rx: 2;
}
/* A hidden curve's key entry stays put, dimmed and struck through — it has
   to remain clickable or there'd be no way to bring the curve back. */
.plot .keyel.is-off { opacity: .38; }
.plot .keyel.is-off .keylabel { text-decoration: line-through; }
.plot .plotel.is-hidden { display: none; }
.datatable tr.is-dim { opacity: .4; }

/* The appearance editor. Its summary is deliberately a chip rather than a
   line of gray text: as plain muted text under the legend it read as a
   caption and got scrolled straight past. */
.styler { margin: 12px 0 4px; }
.styler > summary {
  display: inline-flex; align-items: center; gap: 8px;
  cursor: pointer; user-select: none; font-size: 13px; font-weight: 600;
  padding: 7px 14px; border-radius: 999px;
  border: 1px solid var(--line); background: var(--card);
  color: var(--accent);
}
/* the native disclosure triangle sits outside the pill, so it's replaced */
.styler > summary::-webkit-details-marker { display: none; }
.styler > summary::marker { content: ""; }
.styler > summary::before {
  content: "▸"; font-size: 10px; opacity: .8;
}
.styler[open] > summary::before { content: "▾"; }
.styler > summary:hover { border-color: var(--accent); }
.stylertable td { vertical-align: middle; }
.stylertable input[type="text"], .stylertable select {
  width: 100%; min-width: 84px; padding: 4px 6px; font-size: 13px;
  border-radius: 6px; border: 1px solid var(--line);
  background: var(--bg); color: var(--fg);
}
.stylertable .colorcell { width: 52px; }
/* Native color wells come with a thick chrome border and inner padding;
   stripping both leaves just the swatch, which is all this needs to be. */
.stylertable input[type="color"] {
  width: 38px; height: 26px; padding: 0; border: 1px solid var(--line);
  border-radius: 6px; background: none; cursor: pointer;
}
.stylertable input[type="color"]::-webkit-color-swatch-wrapper { padding: 2px; }
.stylertable input[type="color"]::-webkit-color-swatch {
  border: none; border-radius: 4px;
}
.axisfields { display: flex; flex-wrap: wrap; gap: 10px 16px; margin: 10px 2px; }
/* On the collation page the Apply button sits in the same row as the two
   fields, so it needs to line up with their inputs rather than their tops. */
.axisfields > button { align-self: flex-end; flex: none; }
.axisfields label {
  display: flex; flex-direction: column; gap: 4px;
  font-size: 12px; color: var(--muted); flex: 1 1 220px;
}
.axisfields input {
  padding: 5px 7px; font-size: 13px; border-radius: 6px;
  border: 1px solid var(--line); background: var(--bg); color: var(--fg);
}

/* filing an analysis into a folder */
.movefolder { margin: 6px 0 10px; }
.movefolder select {
  padding: 5px 7px; font-size: 13px; border-radius: 6px;
  border: 1px solid var(--line); background: var(--bg); color: var(--fg);
}
/* the folder heading above a group of analyses */
.foldline {
  display: flex; align-items: baseline; gap: 8px;
  margin: 14px 0 4px; padding-left: calc(var(--depth, 0) * 14px);
  font-size: 13px;
}
.foldline .foldname { font-weight: 600; color: var(--muted); }

/* Renaming a folder in place: the input is invisible until you interact
   with it, so the header still reads as a heading rather than a form. */
.renameform { flex: 1; min-width: 0; display: flex; }
.foldrename {
  flex: 1; min-width: 0; font: inherit; color: inherit;
  background: none; border: 1px solid transparent; border-radius: 6px;
  padding: 2px 6px; margin: -2px 0;
}
.foldrename:hover { border-color: var(--line); }
.foldrename:focus {
  outline: none; background: var(--bg);
  border-color: var(--accent); cursor: text;
}

/* --- schematic beside the curve ------------------------------------------ */
/* Side by side at desk width, stacked on a phone. The schematic is given a
   share of the row rather than a fixed width so it scales with the plot. */
.figrow { display: flex; align-items: center; gap: 14px; flex-wrap: wrap; }
.figrow > .plot { flex: 1 1 340px; min-width: 0; }
.figrow .schematic { flex: 0 1 260px; min-width: 0; margin: 0; }
.figrow .schematic img {
  display: block; width: 100%; height: auto; border-radius: 8px;
}
@media (max-width: 620px) {
  .figrow .schematic { flex-basis: 100%; }
}
.schematicbox { margin-top: 8px; }

/* --- the paper storyboard ------------------------------------------------ */
/* Dashed borders throughout: a panel is a placeholder for a figure that
   usually doesn't exist yet, and it should read as provisional. A panel
   that has its image goes solid, so progress is visible at a glance. */
/* --- storyboard panels: thumbnails, with the real thing on hover --------
   A storyboard is for the shape of the paper. The panel shows a thumbnail;
   pointing at it gives a large, centred preview. That keeps the page
   conceptual and still lets you check a curve without leaving it. */
.storyboard {
  display: grid; gap: 6px; margin: 8px 0 12px;
  grid-template-columns: repeat(auto-fill, minmax(158px, 1fr));
}
.panelart {
  display: flex; align-items: center; justify-content: center;
  min-height: 70px; border-radius: 7px; background: var(--bg);
  border: 1px dashed var(--line); overflow: hidden; padding: 3px;
}
.panelart img { display: block; max-width: 100%; max-height: 104px; height: auto; }
.panelthumb { display: block; max-width: 100%; line-height: 0; }
.panelart .panelplot { display: block; width: 100%; height: auto; max-height: 104px; }
/* Schematic and curve keep their own proportions side by side — a fixed
   split forced the plot into a box wider than its aspect ratio, and an SVG
   letterboxes rather than stretching, which was most of the dead space. */
.panelfig {
  display: flex; align-items: center; justify-content: center; gap: 5px;
}
.panelfig .panelschem { width: auto; height: auto; max-height: 104px; }
.panelart > .panelthumb .panelfig .panelplot { width: auto; height: 92px; }
.panelschem { display: block; max-width: 100%; height: auto; object-fit: contain; }

/* The preview. Fixed, so no ancestor's overflow can clip it, and inert to
   the pointer so moving toward it never counts as leaving the thumbnail. */
.panelzoom {
  display: none; position: fixed; z-index: 200;
  top: 50%; left: 50%; transform: translate(-50%, -50%);
  max-width: 82vw; max-height: 82vh; padding: 16px;
  background: var(--card); border: 1px solid var(--line);
  border-radius: 14px; box-shadow: 0 18px 48px rgba(0, 0, 0, .55);
  pointer-events: none;
}
.panelart.haszoom:hover .panelzoom { display: block; }
/* Qualified with .panelart so these beat the thumbnail caps above rather
   than tying with them — max-height:104px was still winning, and the
   "large" preview came out the size of the thumbnail. */
.panelart .panelzoom .panelfig { gap: 18px; }
.panelart .panelzoom img {
  max-height: 60vh; max-width: 34vw; height: auto; width: auto;
}
.panelart .panelzoom .panelplot {
  width: auto; height: 60vh; max-height: 60vh; max-width: 46vw;
}
.panelart .panelzoom .panelschem { max-height: 60vh; }
/* The panel label on the preview, scaled to match. Sits inside the card's
   padding rather than on the drawing, so it never covers a data point. */
/* Qualified so it outranks .panelletter, which is defined later and would
   otherwise keep the preview's badge at thumbnail size. */
.panelzoom .zoomletter {
  top: 10px; left: 12px; font-size: 22px; padding: 2px 6px;
  border-radius: 6px; background: none;
}
.panelempty { font-size: 12px; color: var(--muted); font-style: italic; }
/* The panel letter, drawn on the artwork's corner the way a printed panel
   label is. Positioned rather than in the flow so it takes no layout room. */
/* The panel card. Tight on purpose: at thumbnail size the padding and gaps
   were a bigger share of each tile than the figure in it. */
.panel {
  position: relative; margin: 0; padding: 5px; border-radius: 9px;
  border: 2px dashed var(--line); background: var(--card);
  display: flex; flex-direction: column; gap: 4px;
}
.panel.filled { border-style: solid; }
.panelletter:empty { display: none; }   /* blank while a figure has one panel */
.panelletter {
  position: absolute; top: 5px; left: 7px; z-index: 1;
  font-weight: 800; font-size: 13px; line-height: 1; color: var(--fg);
  background: var(--card); border-radius: 5px; padding: 2px 5px;
  pointer-events: none;
}
/* Read aloud, never drawn. Without this rule the label rendered as ordinary
   text beside the badge, so every untitled panel read "a Panel a". */
.visually-hidden {
  position: absolute; width: 1px; height: 1px; overflow: hidden;
  clip: rect(0 0 0 0); clip-path: inset(50%); white-space: nowrap;
}
/* Drag affordance. The grip is the only handle: a wholly draggable panel
   fights with the text fields inside it. */
.draggrip { cursor: grab; font-size: 14px; line-height: 1; }
.draggrip:active { cursor: grabbing; }
.dragging { opacity: .45; }
.figbox.dragging { border-style: dashed; }
/* --- storyboard: reading view vs editing ---------------------------------
   Every panel used to carry a title box, a caption box, two selects, Save
   and a row of buttons, whether or not anyone was editing. That is five
   controls per panel on a page whose job is to show you the figures. The
   forms are still there, in the markup and one click away — they are just
   not the thing you look at. */
/* Hide only — never re-show. Each of these elements already has its own
   display (flex for the add-panel row, inline-flex for the inline forms),
   and a rule that switched them all back on would have to guess which.
   Qualifying with body also outranks those component rules, which a bare
   .paneledit did not: it lost on source order and the add-panel row stayed
   visible in reading view. */
/* Editing is per figure and per panel now: one Edit that opened every form
   on the board turned a page for looking at figures into a wall of inputs.
   Hide only — never re-show — because each of these already has its own
   display (flex for the add-panel row, inline-flex for the inline forms)
   and a rule switching them back on would have to guess which. The
   selectors carry two classes so they outrank those component rules. */
.panel:not(.is-editing) .paneledit { display: none; }
.figbox:not(.is-editing) > .figboxhead .paneledit,
.figbox:not(.is-editing) > .figmeta,
.figbox:not(.is-editing) > .addpanel { display: none; }
/* A figure's own Edit opens the figure, not every panel inside it. */
.panel.is-editing .panelview,
.figbox.is-editing > .figmeta .panelview { display: none; }
.panelname {
  margin: 0; font-size: 13px; font-weight: 700; color: var(--fg);
  overflow-wrap: anywhere;
}
.panelcap {
  margin: 2px 0 0; font-size: 12px; color: var(--muted); line-height: 1.35;
  overflow-wrap: anywhere;
}
/* The figure-level controls for a figure that has panels: a plain strip,
   not a card. It must not look like one of the panels below it. */
.figmeta {
  border: 0; background: none; padding: 0; margin: 0 0 10px;
}
.figmeta figcaption { display: flex; flex-direction: column; gap: 6px; }
.panel figcaption { display: flex; flex-direction: column; gap: 4px; }
.panelform { display: flex; flex-direction: column; gap: 6px; }
.paneltitle, .panelcaption {
  width: 100%; font: inherit; font-size: 13px; color: var(--fg);
  background: var(--bg); border: 1px solid var(--line);
  border-radius: 6px; padding: 5px 7px;
}
.paneltitle { font-weight: 600; }
.panelcaption { resize: vertical; font-size: 12px; }
.panelbar { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; }
.panelbar .xbtn[disabled] { opacity: .3; cursor: default; }
/* the native file input is hidden behind its own label, which is the part
   that gets styled — the input still receives the click through the label */
.filepick { position: relative; overflow: hidden; cursor: pointer; }
.filepick input[type="file"] { position: absolute; inset: 0; opacity: 0;
  width: 100%; cursor: pointer; }
.filepick span {
  display: inline-block; font-size: 12px; color: var(--accent);
  border: 1px solid var(--line); border-radius: 999px; padding: 3px 9px;
}
.filepick:hover span { border-color: var(--accent); }
.addpanel { display: flex; gap: 8px; align-items: center; margin-bottom: 18px; }
.addpanel input { flex: 1; min-width: 0; }

/* --- batch analysis ------------------------------------------------------ */
.batchpick { margin-bottom: 10px; }
.batchsummary { margin: 4px 0 8px 18px; font-size: 13px; color: var(--muted); }
.batchsummary li { margin: 3px 0; }
/* The tick column is a label filling the whole cell, so anywhere in the
   column counts as a click on the box — and the rows themselves toggle too
   (see the script on those pages). Ticking down a list of twenty plates
   should not need twenty precise clicks. */
.batchtable .tickcol { width: 46px; text-align: center; padding: 0; }
.tickcell {
  display: flex; align-items: center; justify-content: center;
  padding: 10px 14px; cursor: pointer; margin: 0;
}
.batchtable tbody tr { cursor: pointer; }
.batchtable tbody tr:hover { background: var(--bg); }
.batchtable tr.rowblocked, .batchtable tr.rowblocked .tickcell {
  cursor: default;
}
.batchtable input[type="text"] {
  width: 100%; min-width: 150px; font: inherit; font-size: 13px;
  padding: 4px 6px; border-radius: 6px;
  border: 1px solid var(--line); background: var(--bg); color: var(--fg);
}
/* a plate the template can't be applied to is dimmed rather than hidden —
   seeing that it was left out, and why, is the point */
.batchtable .rowblocked { opacity: .55; }

/* the analysis title, edited alongside the rest of the appearance */
.titlefield {
  display: flex; flex-direction: column; gap: 4px;
  font-size: 12px; color: var(--muted); margin-bottom: 10px;
}
.titlefield input {
  font: inherit; font-size: 14px; font-weight: 600; color: var(--fg);
  padding: 6px 8px; border-radius: 6px;
  border: 1px solid var(--line); background: var(--bg);
}
.titlefield input:focus { outline: none; border-color: var(--accent); }

/* narrowing the collation picker */
.pickfilter {
  display: flex; flex-wrap: wrap; align-items: center; gap: 8px 14px;
  margin: 8px 0 10px; font-size: 12px; color: var(--muted);
}
.pickfilter label { display: flex; align-items: center; gap: 6px; }
.pickfilter select, .pickfilter input[type="search"] {
  font: inherit; font-size: 13px; padding: 4px 7px; border-radius: 6px;
  border: 1px solid var(--line); background: var(--bg); color: var(--fg);
}
.pickfilter input[type="search"] { min-width: 190px; }

/* re-filing a folder, from its own header */
.movein select {
  font: inherit; font-size: 11px; color: var(--muted);
  background: none; border: 1px solid transparent; border-radius: 6px;
  padding: 2px 4px; max-width: 130px;
}
.movein select:hover, .movein select:focus {
  border-color: var(--line); background: var(--bg); color: var(--fg);
}

.normpick { margin: 4px 0 10px; }
.normpick select {
  font: inherit; font-size: 13px; padding: 4px 7px; border-radius: 6px;
  border: 1px solid var(--line); background: var(--bg); color: var(--fg);
}

/* --- storyboard: sections > figures > panels ----------------------------- */
.figsection { margin: 0 0 22px; }
.figsectionhead {
  display: flex; align-items: baseline; gap: 8px;
  font-size: 13px; letter-spacing: .06em; text-transform: uppercase;
  color: var(--muted); margin: 14px 0 8px;
  border-bottom: 1px solid var(--line); padding-bottom: 5px;
}
/* A figure is the container; like its panels it stays dashed until it has
   something in it, so an empty storyboard reads as a plan rather than a
   half-built page. */
.figbox {
  border: 2px dashed var(--line); border-radius: 14px;
  padding: 10px 12px; margin-bottom: 12px; background: var(--card);
}
.figbox.filled { border-style: solid; }
.figboxhead {
  display: flex; align-items: center; gap: 10px; cursor: pointer;
  list-style: none; user-select: none; margin-bottom: 8px;
}
.figboxhead::-webkit-details-marker { display: none; }
.figboxhead::before { content: "▾"; font-size: 10px; color: var(--muted); }
.figbox:not([open]) > .figboxhead::before { transform: rotate(-90deg); }
/* The figure's name is the heading its panels sit under, so it reads as one
   rather than as another line of chrome the same size as everything else. */
.figboxhead .figname {
  flex: 1; min-width: 0; font-weight: 700; font-size: 18px;
  letter-spacing: -.01em;
}
.figboxhead select {
  font: inherit; font-size: 11px; color: var(--muted); background: none;
  border: 1px solid transparent; border-radius: 6px; padding: 2px 4px;
}
.figboxhead select:hover, .figboxhead select:focus {
  border-color: var(--line); background: var(--bg); color: var(--fg);
}
/* the figure's own image sits above its panels, narrower so the panels
   below read as the subdivision rather than siblings of it */
.figself { max-width: 320px; margin-bottom: 10px; }
.subpanels { margin-left: 14px; padding-left: 12px;
  border-left: 2px dotted var(--line); }
.panelmove { margin-top: 4px; }
.panelmove select {
  font: inherit; font-size: 11px; color: var(--muted); background: var(--bg);
  border: 1px solid var(--line); border-radius: 6px; padding: 2px 5px;
  max-width: 100%;
}

/* --- cloning -------------------------------------------------------------- */
.primerseq { word-break: break-all; font-size: 11px; max-width: 34ch; }
.seqblock { word-break: break-all; line-height: 1.5; }

/* a panel that isn't finished says so */
/* Its own green rather than --accent: the app's teal already means "a link
   or a control", and this is a state. */
.figfinal {
  flex: none; font-size: 11px; font-weight: 700; letter-spacing: .04em;
  text-transform: uppercase; color: #34d399;
  border: 1px solid #34d399; border-radius: 999px; padding: 1px 8px;
}
.figstate { margin: 0; font-size: 11px; font-style: italic; }
.figstate.planned { color: var(--muted); }
.figstate.progress { color: var(--accent); }
.figsource {
  font-size: 11px; color: var(--accent); text-decoration: none;
  display: block; margin-bottom: 2px;
}
.figsource:hover { text-decoration: underline; }
.panelform .addrow { gap: 6px; }
.panelform select {
  font: inherit; font-size: 11px; padding: 3px 5px; border-radius: 6px;
  border: 1px solid var(--line); background: var(--bg); color: var(--fg);
  width: 100%;
}

/* the axis break in front of the vehicle panel */
.plot .axisbreak { stroke: var(--muted); stroke-width: .5; vector-effect: non-scaling-stroke; }
.plot .axiscut { stroke: var(--card); stroke-width: 1.6; vector-effect: non-scaling-stroke; }

/* --- circular vector map -------------------------------------------------- */
.vmap { width: 100%; max-width: 620px; height: auto; display: block; margin: 6px auto 10px; }
.vbackbone { fill: none; stroke: var(--line); stroke-width: 2.5; }
.vorigin { stroke: var(--muted); stroke-width: 1.5; }
.varc { fill: none; stroke-linecap: butt; opacity: .9; }
.varc:hover { opacity: 1; stroke-width: 11; }
.vtick { stroke-width: 1.4; }
.vlead { fill: none; stroke-width: .7; opacity: .5; }
.vlabel {
  font-size: 8px; fill: var(--text, var(--fg)); dominant-baseline: middle;
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
}
.vat { fill: var(--muted); font-size: 7px; }
.vname { font-size: 13px; font-weight: 700; fill: var(--text, var(--fg));
         text-anchor: middle; font-family: -apple-system, BlinkMacSystemFont, sans-serif; }
.vsize { font-size: 9px; fill: var(--muted); text-anchor: middle;
         font-family: -apple-system, BlinkMacSystemFont, sans-serif; }

/* the block you paste into a vendor's bulk order box */
.orderblock {
  width: 100%; font-size: 11px; line-height: 1.5; padding: 8px;
  border-radius: 8px; border: 1px solid var(--line);
  background: var(--bg); color: var(--fg); resize: vertical;
  white-space: pre; overflow-x: auto;
}

/* hovering a name lights its mark on the ring, and vice versa */
.vmap [data-key] { transition: opacity .1s ease; }
.vmap .vtick.vlit { stroke-width: 3.2; }
.vmap .varc.vlit { stroke-width: 11; opacity: 1; }
.vmap .vlead.vlit { opacity: 1; stroke-width: 1.4; }
.vmap .vlabel.vlit { font-weight: 700; fill: var(--accent); }
.vmap .vlabel, .vmap .vtick, .vmap .varc { cursor: default; }

/* A label sitting inline among the chip buttons of a .boxactions row — the
   researcher name on the oligo-order download, which Thermo requires. */
.inlinelabel {
  display: inline-flex; align-items: center; gap: 6px;
  font-size: 13px; font-weight: 600;
}
.inlinelabel input {
  width: auto; flex: none; padding: 4px 8px; font-size: 13px;
  font-weight: normal;
}

/* --- goals ---------------------------------------------------------------
   A goal is a claim the paper makes; the experiments that would show it sit
   inside its card, so the card is a container rather than a row. */
.goalcard {
  border: 1px solid var(--line); border-left: 3px solid var(--accent);
  border-radius: 12px; background: var(--card);
  padding: 10px 12px; margin: 10px 0;
}
.goalcard.is-shut { border-left-color: var(--line); opacity: .72; }
.goalhead { display: flex; align-items: baseline; gap: 8px; flex-wrap: wrap; }
.goalhead .title { font-weight: 700; font-size: 15px; }
.goalhead .title:hover { color: var(--accent); }
.goalq { margin: 4px 0 6px; font-size: 13px; color: var(--muted); }
/* The bar counts tasks — steps plus the goal's own todos — because that is
   what the week is actually made of. */
.goalbar {
  height: 5px; border-radius: 999px; background: var(--line);
  overflow: hidden; margin: 8px 0 4px;
}
.goalbar > span { display: block; height: 100%; background: var(--accent); }
.goalpct { margin: 0 0 8px; font-size: 12px; }
.goalcard .expcard { margin: 6px 0 0; }
.todobtns { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; }
.tinydate {
  font-size: 12px; padding: 3px 6px; border-radius: 8px;
  border: 1px solid var(--line); background: var(--bg); color: var(--text);
}

/* A subgoal inside a goal: a link, summarised rather than unpacked, since
   its own page is where the work inside it happens. */
.subgoalrow {
  display: block; margin: 6px 0; padding: 8px 10px;
  border: 1px solid var(--line); border-left: 3px solid var(--accent);
  border-radius: 10px; background: var(--bg);
}
.subgoalrow:hover { border-color: var(--accent); }
.subgoalrow.is-shut { border-left-color: var(--line); opacity: .72; }
.subgoalrow .submain { display: flex; flex-direction: column; gap: 3px; }
.subgoalrow .title { font-weight: 600; font-size: 14px; }
.subgoalrow .sub { font-size: 12px; color: var(--muted); }
.goalbar.mini { height: 3px; margin: 4px 0 0; }

/* --- pilot figures -------------------------------------------------------
   The bench's own board, above the paper's: early figures put up while the
   story is still being worked out. A dashed edge so it reads as scratch
   work rather than part of the paper, and it folds away — shut or open is
   remembered per project — once the paper figures take over. */
.pilotbox {
  border: 1px dashed var(--line); border-radius: 12px;
  margin: 14px 0; padding: 2px 10px;
}
.pilotbox[open] { padding-bottom: 8px; }
.pilothead {
  cursor: pointer; user-select: none; list-style: none;
  display: flex; align-items: baseline; gap: 8px; flex-wrap: wrap;
  padding: 8px 2px; font-size: 13px; font-weight: 700;
  text-transform: uppercase; letter-spacing: .06em; color: var(--muted);
}
.pilothead::-webkit-details-marker { display: none; }
.pilothead::before { content: "▸"; font-size: 10px; }
.pilotbox[open] .pilothead::before { content: "▾"; }
.pilothead:hover { color: var(--accent); }
.pilothead .muted { text-transform: none; letter-spacing: 0; font-size: 12px; }
.pilothead .pilotwhy { font-weight: 400; }
.pilotbox .figsection { margin-top: 0; }

/* Imported artwork is usually black lettering on nothing, which disappears
   against the dark page. The plate is per figure ("Behind it": White or
   Page), so a figure drawn for the dark board keeps it. Only uploaded art
   gets it — a curve the app draws is themed and needs no plate. */
.panelart img.onwhite, .panelzoom img.onwhite {
  background: #fff; border-radius: 6px; padding: 4px;
}

/* The pilot board is for looking at data, not laying out a paper, so its
   art is drawn several times the size of a storyboard thumbnail. Scoped to
   the thumbnail so the hover preview keeps its own (larger) sizes. */
.pilotbox .storyboard {
  grid-template-columns: repeat(auto-fill, minmax(260px, 1fr)); gap: 10px;
}
.pilotbox .panelart { min-height: 150px; }
.pilotbox .panelart > .panelthumb img,
.pilotbox .panelart > img { max-height: 240px; }
.pilotbox .panelart > .panelthumb .panelplot,
.pilotbox .panelart > .panelplot { max-height: 240px; }
.pilotbox .panelart > .panelthumb .panelfig .panelplot { height: 210px; }
.pilotbox .panelart > .panelthumb .panelfig .panelschem { max-height: 210px; }

/* The per-item Edit: a quiet pill on a figure's heading, and the same in
   the corner of a panel, where it sits above the artwork. */
.editbtn {
  background: none; border: 1px solid var(--line); border-radius: 999px;
  padding: 1px 8px; cursor: pointer; font: inherit; font-size: 11px;
  color: var(--muted); line-height: 1.6;
}
.editbtn:hover { color: var(--accent); border-color: var(--accent); }
.figboxhead .editbtn { margin-left: auto; }
.panel > .editbtn {
  position: absolute; top: 4px; right: 4px; z-index: 2;
  background: var(--card); opacity: 0; transition: opacity .12s;
}
/* Out of the way until you go looking for it — but always there for the
   keyboard, and pinned open while that panel is being edited. */
.panel:hover > .editbtn, .panel > .editbtn:focus-visible,
.panel.is-editing > .editbtn { opacity: 1; }

/* The export row: background, whether the fitted table rides along, and the
   three formats, on one line. */
.exportrow {
  display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
  margin: 8px 0;
}
.exportrow select { font-size: 12px; padding: 3px 6px; }
.exportrow .checkline { display: flex; align-items: center; gap: 5px;
  font-size: 12px; color: var(--muted); }
.exportrow button[disabled] { opacity: .5; cursor: default; }
/* The per-panel export row on the storyboard: the same controls, sized to
   sit under a thumbnail. */
.exportrow.mini { gap: 5px; margin: 4px 0 0; font-size: 11px; }
.exportrow.mini select { font-size: 11px; padding: 2px 4px; max-width: 9em; }
.exportrow.mini .chip.mini { padding: 1px 7px; }
/* A day the planner chose rather than a person — shown so that moving a
   step reads as taking it over, which is exactly what it does. */
.autoday {
  font-size: 10px; letter-spacing: .04em; text-transform: uppercase;
  color: var(--muted); border: 1px solid var(--line); border-radius: 4px;
  padding: 0 4px;
}

/* A lane stitched on from another plate: the wells are ordinary wells, but
   the map has to show where the plate stops being one plate. */
.well.brw { box-shadow: inset 0 0 0 1px var(--accent); }
.hdrbtn.brw { color: var(--accent); }
.stitchnote { margin: 8px 0; }
.borrowbox summary { cursor: pointer; }

/* The zero the fit subtracts, stated above the plate map with a switch to
   show the wells that way. */
.zeronote { display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
  color: var(--muted); }
.zeronote strong { color: var(--text); font-variant-numeric: tabular-nums; }

/* ======================================================================
   Cell culture
   ====================================================================== */

/* -- shared bits -------------------------------------------------------- */
.pagehead { display: flex; align-items: center; justify-content: space-between;
  gap: 12px; flex-wrap: wrap; }
.headactions { display: flex; gap: 8px; flex-wrap: wrap; align-items: center; }
.headactions form { display: inline-flex; }
a.chip { display: inline-block; text-decoration: none; }
.chip.go { background: var(--accent); border-color: var(--accent); color: #062b27;
  font-weight: 600; }
.chip.ghost, button.chip.ghost { background: transparent; color: var(--muted); }
button.danger { background: var(--danger); }
.crumbs { font-size: 13px; color: var(--muted); margin: 4px 0 2px; }
.crumbs a { color: var(--muted); }
.small { font-size: 12px; }
.checkline { flex-direction: row; align-items: center; gap: 7px;
  font-weight: normal; }
input.num { width: 5.5em; flex: none; }
.need { color: var(--warn); font-weight: 600; }
.met { color: #4ade80; font-weight: 600; }
.tag { font-size: 10px; text-transform: uppercase; letter-spacing: .05em;
  border: 1px solid var(--line); border-radius: 4px; padding: 0 4px;
  color: var(--muted); font-weight: 600; }
.adot { display: inline-block; width: 10px; height: 10px; border-radius: 50%;
  background: var(--ac); margin-right: 6px; vertical-align: -1px; flex: none; }
.pdot { display: inline-block; width: 10px; height: 10px; border-radius: 3px;
  background: var(--pc, var(--accent)); margin-right: 7px; vertical-align: 0; }
.bar { display: block; height: 6px; border-radius: 999px; background: var(--line);
  overflow: hidden; margin: 5px 0 3px; }
.bar > i { display: block; height: 100%; background: var(--accent);
  border-radius: 999px; }

.onboard { background: var(--card); border: 1px solid var(--line);
  border-radius: 14px; padding: 14px 18px; margin: 12px 0; }
.onboard h3 { margin: 0 0 6px; }
.onboard ol { margin: 6px 0 12px; padding-left: 20px; line-height: 1.6; }

/* -- every well, counted ---------------------------------------------------- */
.countstrip { display: flex; flex-wrap: wrap; gap: 8px 18px; align-items: center;
  background: var(--card); border: 1px solid var(--line); border-radius: 14px;
  padding: 12px 16px; margin: 12px 0; }
.countlead { display: flex; align-items: baseline; gap: 8px; margin-right: 8px; }
.countlead .big { font-size: 26px; font-weight: 700; }
.countcell { display: flex; align-items: center; gap: 6px; font-size: 14px; }
.countcell .n { font-weight: 700; font-variant-numeric: tabular-nums; }

/* Swatches for the legend and the counts: the same colours the drawing uses.
   Prefixed s- because the inherited stylesheet has a bare .empty helper
   (margin-top: 60px) that knocked the "not seeded" swatch off its line. */
i.lg { display: inline-block; width: 14px; height: 14px; border-radius: 50%;
  flex: none; border: 2px solid #5f86a8; }
i.lg.s-growing   { background: #bde4fb; }
i.lg.s-harvested { background: #f7fafc; }
i.lg.s-passaged  { background: #eef2f7; border-color: #94a3b8; }
i.lg.s-discarded { background: #e5e7eb; border-color: #9ca3af; }
i.lg.s-empty     { background: #fff; border: 2px dashed #b4c4d4; }
i.lg.s-pass      { background: #f7fafc; border: 3px solid #16a34a; }
i.lg.s-fail      { background: #f7fafc; border: 3px solid #dc2626; }
i.lg.dot       { width: 10px; height: 10px; border: 2px solid #7c3aed; }
i.lg.dot.run   { background: #7c3aed; }
i.lg.dot.wait  { background: #fff; }
.legend6 { display: flex; flex-wrap: wrap; gap: 6px 14px; font-size: 12px;
  color: var(--muted); margin: 6px 0 12px; }
.legend6 span { display: inline-flex; align-items: center; gap: 6px; }

.attention { border: 1px solid var(--warn); border-radius: 14px;
  padding: 10px 16px; margin: 12px 0; }
.attention h3 { margin: 0 0 4px; color: var(--warn); font-size: 15px; }
.attention ul { margin: 0; padding-left: 18px; line-height: 1.6; }

/* -- lines ------------------------------------------------------------------ */
.linecards { display: grid; gap: 10px;
  grid-template-columns: repeat(auto-fill, minmax(220px, 1fr)); }
.linecard { display: flex; flex-direction: column; gap: 2px; text-decoration: none;
  color: var(--text); background: var(--card); border: 1px solid var(--line);
  border-radius: 14px; padding: 12px 14px; }
.linecard:hover { border-color: var(--accent); }
.linecard .title { font-weight: 700; }
.linecard .sub { font-size: 12px; color: var(--muted); }

/* -- plates ----------------------------------------------------------------- */
.plategroup { margin: 18px 0 8px; }
.plategroup h4 { margin: 0 0 8px; font-size: 15px;
  border-left: 4px solid var(--pc); padding-left: 10px; }
.plategroup h4 a { color: var(--text); }
.plateboard { display: grid; gap: 14px;
  grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); }
.platecard { margin: 0; background: var(--card); border: 1px solid var(--line);
  border-radius: 14px; padding: 10px 10px 12px; }
.platecard:hover { border-color: var(--pc, var(--accent)); }
.platecard.closed { opacity: .55; }
.platecard figcaption { display: flex; flex-direction: column; gap: 2px;
  padding: 6px 4px 0; }
.platecard .title { font-weight: 700; color: var(--text); text-decoration: none; }
.platecard .stepline { font-size: 13px; }
.platecard .sub { font-size: 12px; color: var(--muted); }

svg.plate6 { display: block; width: 100%; height: auto; }
svg.plate6.big { max-width: 560px; }
svg.plate6 a { cursor: pointer; }
.well6 a:hover circle:nth-of-type(1),
.well6.sel circle:nth-of-type(1) { stroke-width: 3; }
.well6 a:focus-visible { outline: none; }
.well6 a:focus-visible circle { stroke: #f59e0b; stroke-width: 4; }

/* -- one plate --------------------------------------------------------------- */
.statusbadge { font-size: 12px; font-weight: 600; vertical-align: middle;
  border-radius: 999px; padding: 3px 10px; margin-left: 8px;
  border: 1px solid var(--line); color: var(--muted); }
.statusbadge.s-active { color: #062b27; background: var(--accent);
  border-color: var(--accent); }
.statusbadge.s-discarded { color: var(--danger); border-color: var(--danger); }
.platefacts { display: flex; flex-wrap: wrap; gap: 6px 22px; margin: 10px 0 14px; }
.platefacts div { display: flex; flex-direction: column; }
.platefacts dt { font-size: 11px; text-transform: uppercase; letter-spacing: .06em;
  color: var(--muted); }
.platefacts dd { margin: 0; font-weight: 600; }
.platelayout { display: grid; grid-template-columns: minmax(0, 1.15fr) minmax(0, 1fr);
  gap: 20px; align-items: start; }
@media (max-width: 820px) { .platelayout { grid-template-columns: 1fr; } }
.wellpanel { background: var(--card); border: 1px solid var(--line);
  border-radius: 14px; padding: 12px 16px; scroll-margin-top: 70px; }
.wellpanel h3 { margin: 0 0 8px; }
.wellpanel .datatable td.st-growing { color: #7cc4f0; }
.harvestform { display: flex; flex-direction: column; gap: 10px; }
.assaypick { border: 1px solid var(--line); border-radius: 10px; padding: 6px 12px 8px;
  margin: 0; }
.assaypick legend { font-size: 12px; text-transform: uppercase; letter-spacing: .06em;
  color: var(--muted); padding: 0 4px; }
.assayline { flex-wrap: wrap; padding: 4px 0; }
.assayline .need, .assayline .met { font-size: 12px; margin-left: auto; }
.samplerow { display: grid; gap: 6px; align-items: center; padding: 8px 0;
  border-top: 1px solid var(--line);
  grid-template-columns: minmax(9em, 1.4fr) auto auto minmax(6em, 1fr) auto auto; }
.samplerow input, .samplerow select { padding: 7px 9px; }
.samplerow .sname { font-weight: 600; font-size: 14px; }
@media (max-width: 620px) { .samplerow { grid-template-columns: 1fr 1fr; } }

.plateactions { display: grid; gap: 12px;
  grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); }
.actioncard { background: var(--card); border: 1px solid var(--line);
  border-radius: 14px; padding: 12px 14px; display: flex; flex-direction: column;
  gap: 8px; }
.actioncard h4 { margin: 0; }
.wellchecks { display: flex; flex-wrap: wrap; gap: 6px 14px; }
.timeline { list-style: none; padding: 0; margin: 0; font-size: 14px; }
.timeline li { padding: 6px 0; border-top: 1px solid var(--line); display: flex;
  gap: 10px; flex-wrap: wrap; }
.timeline .when { color: var(--muted); font-variant-numeric: tabular-nums;
  min-width: 6.5em; }

/* -- progress ----------------------------------------------------------------- */
.progressblock h3 .pdot { margin-right: 8px; }
.progressgrid th, .progressgrid td { vertical-align: top; }
.progressgrid .pcell { min-width: 9em; }
.progressgrid .pcell > span { display: block; }
.progressgrid .pcell .bar { margin: 4px 0; max-width: 9em; }
.frac { font-size: 16px; font-weight: 700; font-variant-numeric: tabular-nums; }
.frac.met { color: #4ade80; }
tr.invalid td { opacity: .55; }

/* -- setup -------------------------------------------------------------------- */
.setuplist { margin: 0 0 10px; padding-left: 20px; line-height: 1.7; }
.setuprow { margin: 6px 0; flex-wrap: wrap; }
.protocolcard { border: 1px solid var(--line); border-left: 4px solid var(--pc);
  border-radius: 14px; padding: 12px 14px; margin: 12px 0;
  display: flex; flex-direction: column; gap: 10px; scroll-margin-top: 70px; }
.steplist { margin: 0; padding-left: 22px; display: flex; flex-direction: column;
  gap: 6px; }
.steplist .addrow input.grow { min-width: 8em; }
.reqgrid input.num { width: 4.5em; padding: 6px 8px; }
.reqform { display: flex; flex-direction: column; gap: 8px; }
.addrow label.small { flex-direction: row; align-items: center; gap: 6px;
  font-weight: normal; }

/* -- new plates ----------------------------------------------------------------- */
.newplate { display: flex; flex-direction: column; gap: 12px; }
.seedgrid { display: grid; grid-template-columns: repeat(3, 64px); gap: 10px;
  border: 1px solid var(--line); border-radius: 12px; padding: 12px 14px;
  width: max-content; margin: 0; }
.seedgrid legend { font-size: 13px; font-weight: 600; padding: 0 4px; }
.seedwell { position: relative; width: 64px; height: 64px; cursor: pointer; }
.seedwell input { position: absolute; opacity: 0; inset: 0; width: 100%;
  height: 100%; margin: 0; cursor: pointer; }
.seedwell span { display: flex; align-items: center; justify-content: center;
  width: 100%; height: 100%; border-radius: 50%; font-weight: 700; font-size: 13px;
  color: #3b5a78; background: #fff; border: 2px dashed #b4c4d4; }
.seedwell input:checked + span { background: #bde4fb; border: 2px solid #5f86a8; }
.seedwell input:focus-visible + span { outline: 3px solid var(--accent); }
/* The inherited table style right-aligns cells; a progress cell reads as one
   stacked block, so its number, bar and notes line up on the left. */
.progressgrid td.pcell { text-align: left; }
