/* <!-- TODO: Sim Display Name --> */

:root {
  --accent:       #004445;
  --accent-light: #006668;
  --focus-glow:   #FFD166;
  /* Pale enough to be seen against the drawer's dark table, which every drag
     and mark indicator sits on. Measurements in the drop-ready rules below. */
  --indicator:    #F5F1E5;
  --ink:          #2C3E3E;
  --muted:        #5A6E6E;
}

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

html, body {
  height: 100%;
  overflow: hidden;
  background: #F5F1E5;
  font-family: Roboto, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
}

#sim-app {
  display: flex;
  flex-direction: column;
  height: 100%;
  position: relative; /* anchors #reveal-panel + its tab to the whole app's edges */
}

#stage-panel {
  flex: 1;
  position: relative;
  overflow: hidden;
}

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

#sim-canvas:focus-visible {
  outline: 2px solid #60a5fa;
  outline-offset: -2px;
}

/* ── Accessibility ── */
.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

/* ── Info dialog ──
   The dialog is Tuva Sims "chrome", so its accent is espresso (#5c4738),
   NOT the sim's green var(--accent). Header = purple with white text; active
   tab / h3 / footer button = purple; tabs/footer surface = #F8FBFB; body text =
   var(--ink). The shared toolbar stylesheet also injects these purple rules
   (scoped to #info-dialog) so existing green sims reskin automatically — keep
   this block in sync with it; do not redefine per sim. */
#info-dialog {
  border: none;
  border-radius: 12px;
  padding: 0;
  width: min(560px, calc(100vw - 32px));
  max-height: calc(100vh - 80px);
  overflow: hidden;
  margin: auto;
  box-shadow: 0 8px 32px rgba(0, 0, 0, 0.22);
  background: #ffffff;
}

#info-dialog::backdrop {
  background: rgba(30, 16, 48, 0.55);
}

.dialog-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 14px 16px;
  background: #5c4738;
  color: #ffffff;
}

.dialog-header h2 {
  font-size: 16px;
  font-weight: 600;
}

#dialog-close-btn {
  background: transparent;
  border: none;
  color: #ffffff;
  font-size: 18px;
  cursor: pointer;
  width: 32px;
  height: 32px;
  border-radius: 6px;
  display: flex;
  align-items: center;
  justify-content: center;
  line-height: 1;
}

#dialog-close-btn:hover {
  background: rgba(255, 255, 255, 0.15);
}

#dialog-close-btn:focus-visible {
  outline: 3px solid var(--focus-glow);
  outline-offset: 2px;
}

.dialog-tabs {
  display: flex;
  border-bottom: 2px solid #E5EAE9;
  background: #F8FBFB;
}

.tab-btn {
  flex: 1;
  background: none;
  border: none;
  border-bottom: 3px solid transparent;
  margin-bottom: -2px;
  padding: 10px 0;
  font-family: Roboto, Arial, sans-serif;
  font-size: 14px;
  font-weight: 600;
  color: var(--muted);
  cursor: pointer;
  transition: color 0.15s, border-color 0.15s;
}

.tab-btn:hover {
  color: #5c4738;
}

.tab-btn.active,
.tab-btn[aria-selected="true"] {
  color: #5c4738;
  border-bottom-color: #5c4738;
}

.tab-btn:focus-visible {
  outline: 3px solid var(--focus-glow);
  outline-offset: -3px;
  box-shadow: 0 0 0 2px var(--ink);
}

.dialog-body {
  padding: 16px 20px;
  overflow-y: auto;
  max-height: calc(100vh - 240px);
  color: var(--ink);
  font-size: 14px;
  line-height: 1.55;
}

.dialog-body h3 {
  font-size: 14px;
  font-weight: 600;
  color: #5c4738;
  margin-top: 14px;
  margin-bottom: 6px;
}

.dialog-body h3:first-child {
  margin-top: 0;
}

.dialog-body p {
  margin-bottom: 8px;
}

.dialog-body ul,
.dialog-body ol {
  padding-left: 20px;
  margin-bottom: 8px;
}

.dialog-body li {
  margin-bottom: 4px;
}

/* ── The answer key, at the foot of the teacher tab ──
   Folded shut, and it stays shut until a teacher opens it: this is the one
   thing in the dialog a student is not meant to read. Built by
   renderTeacherKey(). */
.teacher-key {
  margin-top: 16px;
  border-top: 1px solid rgba(0, 68, 69, 0.18);
  padding-top: 10px;
}

.teacher-key > summary {
  font-size: 14px;
  font-weight: 600;
  color: #5c4738;
  cursor: pointer;
}

.teacher-key > summary:focus-visible {
  outline: 2px solid var(--focus-glow);
  outline-offset: 2px;
  border-radius: 4px;
}

#teacher-key-body { margin-top: 8px; }

/* A real tree, not a ruled list. Each group is its own <ul> inside its
   parent's <li>, so the connectors hang off the child list: ::before draws
   the elbow (down the trunk, then right to the child), ::after carries the
   trunk on past it, and the LAST child has no ::after, so the trunk stops
   where its elbow turns. Nested groups fork again further right. Drawn with
   borders, not glyphs, so it scales with the text. The line is the muted
   ink, 4.8:1 against the cream panel (SC 1.4.11 asks for 3:1). Drawn by
   renderOneDrawerKey(). */
#teacher-key-body ul {
  --tk-line: var(--muted);
  --tk-elbow: 0.72em;       /* how far down the group's name the elbow meets it */
  list-style: none;
  margin: 0;
  padding: 0;
}

#teacher-key-body li ul {
  position: relative;
  margin-left: 5px;
  padding-left: 17px;
}

#teacher-key-body li ul::before {
  content: '';
  position: absolute;
  left: 0;
  top: 0;
  width: 13px;
  height: var(--tk-elbow);
  border-left: 2px solid var(--tk-line);
  border-bottom: 2px solid var(--tk-line);
  box-sizing: border-box;
}

#teacher-key-body li ul:not(:last-of-type)::after {
  content: '';
  position: absolute;
  left: 0;
  top: var(--tk-elbow);
  bottom: 0;
  border-left: 2px solid var(--tk-line);
}

/* One block per drawer. Both keys are open at once, so each needs its own
   name over it and enough air that the two trees cannot be read as one. */
.tk-drawer + .tk-drawer {
  margin-top: 16px;
  border-top: 1px solid rgba(0, 68, 69, 0.14);
  padding-top: 12px;
}

/* Tuva green and a size up, because a bold dark heading sat level with the
   bold dark clade right under it ("Animals past and present" over
   "Mammals") and the two read as one list. */
.tk-drawer-name {
  font-size: 15px;
  font-weight: 700;
  color: var(--accent);
  margin: 0 0 8px;
}

/* Which drawer is on the table, said in words rather than by showing only
   that one, so it survives being read in greyscale (SC 1.4.1). */
.tk-open {
  font-weight: 400;
  font-style: italic;
  color: var(--muted);
}

/* Padding, not margin: a margin on the last <li> inside a child list collapses
   out of it and breaks the trunk running down that list. */
#teacher-key-body li { padding-bottom: 2px; }

.tk-group {
  display: block;
  font-weight: 600;
  color: var(--ink);
}

/* The animals themselves, one step quieter than the group that holds them,
   so the shape of the tree reads before the contents do. */
.tk-members {
  display: block;
  padding-left: 14px;
  color: var(--muted);
}

.tk-note {
  font-size: 14px;
  color: var(--muted);
  margin-top: 8px;
}

.tk-source {
  font-size: 14px;
  color: var(--muted);
  margin-top: 6px;
}

/* ── Skull Drawer: stage + bench chrome ── */

#sim-app {
  background: #232323;
}

#stage-panel {
  background: #232323;
}

#sim-canvas {
  touch-action: none;
}

/* ── Skull Drawer: top strip + labels + pickup HUD ── */

.top-strip {
  position: absolute;
  top: 10px;
  left: 10px;
  /* Exactly as wide as its two tabs (Ben, 2026-09-22). It used to stretch the
     full width of the stage to carry a step line and the trait row; the step
     line is gone and the trait row is its own strip below, so there is nothing
     left for the extra width to hold. */
  width: max-content;
  max-width: calc(100% - 20px);
  display: flex;
  align-items: center;
  z-index: 5;
  /* So a wrapped row starts at the left edge rather than centring itself
     under the tabs. */
  justify-content: flex-start;
  /* No panel on the strip itself any more: it holds three controls side by
     side and each wears its own, so they read as three things rather than one
     long bar (Ben, 2026-10-05). */
  background: none;
  padding: 0;
}

/* The tab group keeps the cream panel the strip used to wear. */
.top-strip .drawer-tabs {
  margin-bottom: 0;
  background: rgba(245, 241, 229, 0.94);
  border-radius: 8px;
  padding: 0 6px;
}

/* A row of its own under the tabs and the step line (flex-basis: 100% in a
   wrapping strip), so it never squeezes either of them. Hidden until the
   student has tagged something, since before that it has nothing to show. */
.trait-filters {
  width: max-content;
  max-width: calc(100% - 20px);
  z-index: 5;
  background: rgba(245, 241, 229, 0.94);
  border-radius: 8px;
  padding: 4px 8px;
  display: flex;
  /* One line, always. Wrapping to a second line doubled the strip's height,
     and the drawer is fitted below the strip (topReservePx()), so on a
     narrow stage the skulls were squeezed to nothing to make room for a row
     of chips. The row scrolls sideways instead. */
  flex-wrap: nowrap;
  overflow-x: auto;
  scrollbar-width: thin;
  align-items: center;
  gap: 6px;
  /* This used to be `2px 0`, which silently cancelled the `4px 8px` above it
     and left the first and last chip touching the strip's own edge (Ben,
     2026-10-05). */
  padding: 3px 8px;
}
.trait-filters > * { flex-shrink: 0; }
.trait-filters.hidden { display: none; }

.trait-filters-label {
  white-space: nowrap;
  font-family: Roboto, Arial, sans-serif;
  font-size: 14px;
  color: var(--muted);
  margin-right: 2px;
}

.trait-chip {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  border: 1px solid rgba(0, 68, 69, 0.25);
  background: #ffffff;
  color: var(--ink);
  border-radius: 999px;
  padding: 3px 10px;
  font-family: Roboto, Arial, sans-serif;
  font-size: 14px;
  cursor: pointer;
}
.trait-chip:hover { background: #F5F1E5; }
.trait-chip:focus-visible { outline: 3px solid var(--focus-glow); outline-offset: 2px; }

/* The pressed chip carries its own trait's colour as a filled background, so
   which trait the drawer is showing is legible without reading the row. */
.trait-chip[aria-pressed="true"] {
  background: var(--chip-color, var(--accent));
  border-color: var(--chip-color, var(--accent));
  color: #ffffff;
  font-weight: 600;
}

/* While a trait is showing, a skull's pill says what the student answered
   for it, in that trait's colour; a skull they have not answered it for
   keeps its name and goes quiet, alongside its bone dimming in the 3D
   scene (see updateHoverAndShadow()). */
.label-pill.pill-answer {
  /* The SAME dark as a resting pill and as the photo card under a skull. It
     used to take the shown trait's colour, which was right while one trait
     filled the whole pill; now each row carries its own icon disc in that
     colour, so tinting the box as well left the drawer switching between a
     black label and a dark green one for no reason a student could read
     (Ben, 2026-10-05). */
  background: rgba(20, 20, 20, 0.86);
  color: #f5f1e5;
  font-weight: 600;
}
.label-pill.pill-untagged { opacity: 0.45; }

.top-strip.hidden { display: none; }

.drawer-prompt {
  flex: 1;
  min-width: 160px;
  text-align: center;
  font-family: Roboto, Arial, sans-serif;
  font-size: 14px;
  color: var(--ink);
  transition: opacity 160ms ease;
}

/* setPromptStep() (sim JS) toggles this while it swaps the line's text
   underneath, so a step change reads as a short cross-fade instead of a
   jump-cut. */

/* The family tree's drawer tab (Ben's third walkthrough, finding #3;
   coordinator review: a bordered, drop-shadowed "pill" doesn't read as a
   tab). Pinned to #stage-panel's own bottom edge, centred, flush against
   it — no gap, no shadow — with only the top two corners rounded and a
   FLAT bottom edge, so it reads as a physical tab sticking up out of the
   stage. Solid background matching #reveal-panel's own exactly (not a
   translucent chip colour), and no side/bottom border at all — only a
   hairline top border in the SAME colour #reveal-panel's own border-top
   uses, so the tab and the panel read as one piece, not a button sitting
   on top of a panel. `bottom` (not transform — .reveal-panel's own slide
   already uses transform) is what openReveal()/closeReveal() animate to
   ride the tab up onto #reveal-panel's top edge and back down. */

/* No family tree in the close-up (segment 7, item 4) — pickUp()/putBack()
   toggle this. */

@media (prefers-reduced-motion: reduce) {
  
}

/* Specimen labels: one per skull, always present, repositioned each frame
   from the projected 3D anchor (see updateLabelPositions() in the sim JS).
   A label is also the keyboard proxy for picking that skull up: it is
   focusable, and Enter / Space / click all pick up the specimen it names. */
.specimen-labels {
  position: absolute;
  inset: 0;
  pointer-events: none;
  z-index: 3;
}

.specimen-labels.hidden { display: none; }

/* Held off the screen for the length of a drawer switch. The names go before
   the skulls slide out, and come back only once the arriving cast has settled,
   so a name is never seen travelling or sitting over the wrong skull (Ben,
   2026-09-28). visibility rides the same transition as the fade, so a faded
   label is also un-clickable and out of the tab order, not merely invisible. */
.specimen-labels { transition: opacity 140ms linear, visibility 0s linear 0s; }
.specimen-labels.slide-hidden {
  opacity: 0;
  visibility: hidden;
  transition: opacity 140ms linear, visibility 0s linear 140ms;
}
@media (prefers-reduced-motion: reduce) {
  .specimen-labels,
  .specimen-labels.slide-hidden { transition: none; }
}

/* A label has two alternate faces, .label-full (photo card) and .label-pill
   (one-line text pill); only one shows at a time, per the mode-* class
   updateLabelPositions() sets each frame from measured screen geometry.
   Every resting label is a pill, centered below the skull, or beside it
   (mode-pill-right) if even the pill would reach the next skull. mode-full
   is the hover card: the one skull under the pointer swaps to its
   photograph, drawn over everything else, since only one is ever up. */
.specimen-label {
  position: absolute;
  pointer-events: auto;
  cursor: pointer;
  /* A drag across the drawer would otherwise sweep a text selection through
     every name it passes, leaving them highlighted in the browser's own blue.
     These are handles, not prose. */
  -webkit-user-select: none;
  user-select: none;
}

/* Over every other label, and over the pill it replaced. */
.specimen-label.mode-full { z-index: 3; }

.specimen-label.mode-full,
.specimen-label.mode-pill { transform: translate(-50%, 0); }

/* A card being dragged shows the grabbing cursor and NOTHING else. It must not
   take pointer-events: none, however tempting -- it is the element holding
   pointer capture, and taking pointer-events away from a capturing element
   makes the browser release the capture mid-gesture, so the pointerup never
   reaches the handler and the whole drop is silently dropped. Its covering its
   own target is handled where it belongs, in the hit test: pickLabelAtPointer()
   walks the full elementsFromPoint stack rather than reading the top of it. */
.specimen-label.dragging { cursor: grabbing; }

/* Marked by the first press of G, waiting for the second. The keyboard path
   lights the same targets a drag does, so this only has to say which node is
   the one being moved. */
.specimen-label.marked .label-full,
.specimen-label.marked .label-pill {
  outline: 3px solid var(--indicator);
  outline-offset: 3px;
  border-radius: 8px;
}

.specimen-label:focus-visible {
  outline: 3px solid var(--focus-glow);
  outline-offset: 2px;
}

.specimen-label .label-full,
.specimen-label .label-pill { display: none; }

.specimen-label.mode-full .label-full {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 3px;
  padding: 4px 8px 6px;
  border-radius: 8px;
  background: rgba(20, 20, 20, 0.55);
  max-width: 150px;
  text-align: center;
}

.specimen-label.mode-pill .label-pill,
.specimen-label.mode-pill-right .label-pill {
  display: block;
}

.label-full img {
  width: 56px;
  height: 56px;
  object-fit: cover;
  border-radius: 6px;
  border: 1px solid rgba(255, 255, 255, 0.25);
}

.specimen-name {
  color: #f5f1e5;
  font-family: Roboto, Arial, sans-serif;
  font-size: 14px;
  font-weight: 600;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  max-width: 140px;
}

.specimen-length {
  color: #e8e0cf;
  font-family: Roboto, Arial, sans-serif;
  font-size: 12px;
}

.specimen-badges { display: flex; gap: 4px; }

.trait-dots { display: flex; gap: 3px; }
.trait-dots.hidden { display: none; }

.trait-dot {
  width: 8px;
  height: 8px;
  border-radius: 50%;
  border: 1.5px solid currentColor;
  background: transparent;
  display: inline-block;
}

.trait-dot.filled { background: currentColor; }

/* The one-line fallback: name plus any badge, no photo, no dots — small
   enough to fit where a photo card would bury a neighboring skull. */
.label-pill {
  background: rgba(20, 20, 20, 0.78);
  color: #f5f1e5;
  font-family: Roboto, Arial, sans-serif;
  font-size: 14px;
  font-weight: 600;
  line-height: 1.3;
  padding: 3px 8px;
  border-radius: 999px;
  white-space: nowrap;
  max-width: 220px;
  overflow: hidden;
}

/* The pill's two faces: its name and badges at rest, or what the student
   answered while a trait is being shown (refreshLabel()). */
.label-pill .pill-resting { display: inline-flex; align-items: center; gap: 5px; max-width: 100%; }
.label-pill .pill-answer-text { display: none; }
.label-pill.pill-answer .pill-resting { display: none; }
.label-pill.pill-answer .pill-answer-text { display: inline; }

/* An answer is a phrase, not a name: "a ring of bone, open at the back" is
   33 characters and was being cut off mid-word inside a pill that never
   wrapped (Ben, 2026-10-05). While a trait is being shown the pill becomes a
   two-line block, broken at the comma by refreshLabel() so the break lands
   where the sense does rather than wherever the box runs out. */
.label-pill.pill-answer {
  white-space: normal;
  text-align: left;
  max-width: 150px;
  line-height: 1.25;
}

/* Several traits at once: a stack of rows, each a dot in the trait's colour
   and the short form of the answer. The dot is not the only carrier of which
   trait a row belongs to -- the rows are in the drawer's trait order, the
   same order as the chips above -- so this does not fail WCAG 1.4.1 on its
   own, and the title attribute spells every row out in words. */
.label-pill.pill-rows .pill-answer-text {
  display: flex;
  flex-direction: column;
  gap: 1px;
  align-items: flex-start;
}
/* Any label carrying answers is a block, not a pill: the name sits on top of
   them now, so even one trait shown is two rows deep. At that height the
   999px radius is a half-circle on each end and it ate the corners of the
   top and bottom rows (Ben, 2026-10-06). .pill-rows-multi is kept for the
   extra padding two or more rows want. */
.label-pill.pill-rows {
  border-radius: 9px;
}
.label-pill.pill-rows-multi {
  padding: 5px 9px;
}

.pill-row { display: inline-flex; align-items: center; gap: 5px; white-space: nowrap; font-size: 14px; }

/* The row for a trait the student has not answered yet: a ruled line where
   the answer will go, in place of the em dash this used to draw
   (sim-writing-style rule 5). 34px is the width of the shortest answers in
   the set ("cup", "two"), so an unanswered row is the same shape as an
   answered one and the column still reads as a pattern. Decorative only --
   the span is aria-hidden and the pill's title says "not tagged" in words. */
.pill-row-untagged {
  display: inline-block;
  width: 34px;
  border-bottom: 2px solid rgba(245, 241, 229, 0.45);
  margin-bottom: 4px;
}

/* The specimen's name, sitting above its own answers inside the same block.
   Set apart from them by weight and a hairline, so the block reads as one
   label with a heading rather than as four equal rows. */
.pill-row-name {
  display: block;
  font-weight: 700;
  padding-bottom: 3px;
  margin-bottom: 2px;
  border-bottom: 1px solid rgba(245, 241, 229, 0.22);
  align-self: stretch;
  /* A name is one word of a caption and survives being cut; an answer is the
     thing being compared and does not, which is why only this row ellipsizes
     (same rule as .pill-name). */
  max-width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* Only the NAME ellipsizes when a pill is squeezed; a badge is two words
   and loses its meaning the moment it is cut. */
.pill-name { overflow: hidden; text-overflow: ellipsis; min-width: 0; }

/* A card raises slightly on hover/focus, matching the 3D skull's own float
   (see updateHoverAndShadow() in the sim JS) — the two together read as
   "this whole thing is being picked up". */
.specimen-label:hover .label-full,
.specimen-label:hover .label-pill,
.specimen-label:focus-visible .label-full,
.specimen-label:focus-visible .label-pill {
  transform: translateY(-2px);
  transition: transform 0.15s ease;
}

/* Hover / pick-up frame: four corner brackets around a skull's projected
   screen box. Shown on drawer-view hover (pointer over the mesh or its
   card) or keyboard focus on a card, and kept tracking the picked-up skull
   through the fly-in — see updateHoverFrame() in the sim JS — so the
   brackets read as growing into the close-up frame (#pickup-hud's own
   .pu-frame) rather than disappearing and a new one popping in. */
.hover-frame {
  position: absolute;
  z-index: 5;
  pointer-events: none;
  transition: opacity 0.15s ease;
}
.hover-frame.hidden { opacity: 0; }
.hf-c {
  position: absolute;
  width: 20px;
  height: 20px;
  border: 0 solid rgba(245, 241, 229, 0.92);
  filter: drop-shadow(0 1px 3px rgba(0, 0, 0, 0.6));
}
.hf-tl { top: 0;    left: 0;   border-top-width: 2px;    border-left-width: 2px;  border-top-left-radius: 6px; }
.hf-tr { top: 0;    right: 0;  border-top-width: 2px;    border-right-width: 2px; border-top-right-radius: 6px; }
.hf-bl { bottom: 0; left: 0;   border-bottom-width: 2px; border-left-width: 2px;  border-bottom-left-radius: 6px; }
.hf-br { bottom: 0; right: 0;  border-bottom-width: 2px; border-right-width: 2px; border-bottom-right-radius: 6px; }

/* Pick-up close-up (segment 4): a corner-bracket frame around the held
   skull, trait badges + leader lines along its left edge, the choices panel
   along its right edge (or below, narrow layout), the animal card at its
   top-left, Previous/Next arrows outside those two columns, Put back below
   the frame, and zoom buttons at its bottom-right corner. Every piece is
   positioned every frame from the skull's own projected screen box (see
   updatePickupHud() in the sim JS), so it all follows rotation/zoom/resize.
   #pickup-hud is the outer container (ids kept: the harness and focus
   management read them) — a full-stage overlay, transparent except for its
   children, each positioned with its own absolute left/top. */
.pickup-hud {
  position: absolute;
  inset: 0;
  z-index: 6;
  pointer-events: none;
}

.pickup-hud.hidden { display: none; }

/* The close-up chrome fades in only once the fly-in settles
   (data-state="picked"); #hover-frame carries the brackets during the fly
   itself, so nothing pops in — see updateHoverFrame()/updatePickupHud().
   .pu-lines rides in the SAME group on purpose: a badge is technically
   still clickable the instant it exists in the DOM, even at opacity 0 (an
   invisible element isn't unclickable), so a leader line kept on its own
   timer could still reach the screen before the badge naming it was
   visible — exactly the "floating line" bug this group already exists to
   prevent for the frame/card/badges. Riding the same CSS transition closes
   that gap for free, with no JS timing of its own to keep in sync. */
.pu-frame, .pu-card, .pu-badges-col, .pu-bottombar, .pu-zoom, .pu-lines {
  opacity: 0;
  transition: opacity 0.2s ease;
}
.pickup-hud[data-state="picked"] .pu-frame,
.pickup-hud[data-state="picked"] .pu-card,
.pickup-hud[data-state="picked"] .pu-badges-col,
.pickup-hud[data-state="picked"] .pu-bottombar,
.pickup-hud[data-state="picked"] .pu-zoom,
.pickup-hud[data-state="picked"] .pu-lines {
  opacity: 1;
}

.pu-frame { position: absolute; pointer-events: none; }
.pu-c {
  position: absolute;
  width: 26px;
  height: 26px;
  /* Same near-white as .hf-c's hover brackets (Ben's third walkthrough,
     finding #5: this used to be a dark green, rgba(44, 62, 62, 0.85), and
     the crossfade from the hover frame into this one read as the frame
     itself changing color when a skull was picked up). */
  border: 0 solid rgba(245, 241, 229, 0.92);
  filter: drop-shadow(0 1px 2px rgba(0, 0, 0, 0.35));
}
.pu-tl { top: 0;    left: 0;   border-top-width: 2px;    border-left-width: 2px;  border-top-left-radius: 8px; }
.pu-tr { top: 0;    right: 0;  border-top-width: 2px;    border-right-width: 2px; border-top-right-radius: 8px; }
.pu-bl { bottom: 0; left: 0;   border-bottom-width: 2px; border-left-width: 2px;  border-bottom-left-radius: 8px; }
.pu-br { bottom: 0; right: 0;  border-bottom-width: 2px; border-right-width: 2px; border-bottom-right-radius: 8px; }

.pu-lines { position: absolute; inset: 0; pointer-events: none; overflow: visible; }
.pu-lead { stroke-width: 1.5; fill: none; opacity: 0.7; }
.pu-lead-occluded { stroke-dasharray: 3 4; opacity: 0.35; }
.pu-lead-active { stroke-width: 2.5; opacity: 1; }

/* Item 6: pinned to the stage's own top-left corner with a fixed inset —
   no longer positioned from the frame (updatePickupHud() sets none of this
   any more), so it never moves as the skull is zoomed or turned. */
.pu-card {
  position: absolute;
  left: 16px;
  top: 16px;
  pointer-events: none;
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 6px 12px 6px 6px;
  border-radius: 8px;
  background: rgba(245, 241, 229, 0.94);
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.25);
  max-width: 400px;
}
.pu-card img {
  /* Landscape, not square (Ben, 2026-10-03: "i'm fine with making the images
     go larger when we're viewing the larger image alongside the skull").
     Every source photograph is landscape and several are near 3:2, so the
     old 150x150 square threw away a third of the width and took half the
     cave bear's face with it. At 220x150 a 3:2 picture loses 2%. The HEIGHT
     is unchanged on purpose: chromeReserves() reserves the card's height
     above the frame, so growing sideways costs the skull nothing. */
  width: 220px;
  height: 150px;
  object-fit: cover;
  border-radius: 6px;
  flex-shrink: 0;
}
/* Explicit, not left to the UA default: `.pu-card img` sets a width and a
   height, and a specimen with no image at all must take no room rather than
   leave a 150px gap where a picture would be. */
.pu-card img[hidden] { display: none; }

/* A narrow stage has no room to spare beside the skull, so the card goes
   back to the smaller square there. */
.pickup-hud.narrow .pu-card { max-width: 320px; }
.pickup-hud.narrow .pu-card img { width: 150px; height: 150px; }

.pu-card-text {
  display: flex;
  flex-direction: column;
  font-family: Roboto, Arial, sans-serif;
  font-size: 14px;
  color: var(--ink);
  line-height: 1.35;
}
.pu-card-text .hud-name { font-weight: 700; font-size: 15px; }
.pu-card-text .hud-latin { font-style: italic; }

/* A note about this particular specimen. Quieter than the identification
   above it and separated by a hairline, so it reads as a remark about the
   bone rather than another field of the label. */
.pu-card-text .hud-note {
  margin-top: 6px;
  padding-top: 6px;
  border-top: 1px solid rgba(0, 68, 69, 0.18);
  font-size: 12px;
  line-height: 1.4;
  color: var(--muted);
  max-width: 190px;
}


/* Item 5: pinned to the stage's fixed left edge, vertically centred —
   updatePickupHud() only ever sets this column's WIDTH now (narrow-stage
   shrink), never its position. Being centred, it claims equal room above
   and below its own middle, so the max-height has to clear the LARGER of
   the fixed card (top) and bottom-bar/zoom (bottom) reserves on BOTH
   sides — 2x the card's own reserve, the bigger of the two. overflow-y is a
   safety floor on top of that, for a specimen with an unusually long trait
   list on a short stage. */
/* Round "x" beside the selected region badge: a visible way to let the
   region go, and a hint that pressing the badge again does the same thing
   (Ben, 2026-09-21). Positioned from JS against the selected badge's own
   rect so it matches its height exactly whatever the badge wraps to. */
.pu-deselect {
  position: absolute;
  pointer-events: auto;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 0;
  background: rgba(245, 241, 229, 0.94);
  color: var(--ink);
  border: 1px solid rgba(0, 68, 69, 0.25);
  border-radius: 999px;
  font-family: Roboto, Arial, sans-serif;
  font-size: 15px;
  line-height: 1;
  cursor: pointer;
  transition: background-color 0.15s ease, box-shadow 0.15s ease;
}
.pu-deselect:hover { background: #fff; }
.pu-deselect:focus-visible { outline: 3px solid var(--focus-glow); outline-offset: 2px; }
.pu-deselect[hidden] { display: none; }

.pu-badges-col {
  position: absolute;
  left: 16px;
  top: 50%;
  transform: translateY(-50%);
  max-height: calc(100% - 380px);
  overflow-y: auto;
  /* The column scrolls VERTICALLY when there are more badges than room, and
     never sideways. `overflow-y: auto` alone sets overflow-x to auto as well,
     and a hovered badge gains a box-shadow that overflows by about 6px, which
     was enough to flash a horizontal scrollbar in and out as the pointer moved
     along the column (Ben, 2026-10-05). */
  overflow-x: hidden;
  /* ...and the shadow then needs somewhere to live, or clipping it is just a
     different wrong. */
  padding-right: 8px;
  pointer-events: none;
  display: flex;
  flex-direction: column;
  gap: 6px;
}
.pu-badges {
  flex: 1;
  min-height: 0;
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: 8px;
  list-style: none;
}
/* Left-aligned (item 5 follow-up): the column sits at the stage's own left
   edge now, not hugging the frame's edge the way it used to, so right-
   aligning each pill left a ragged LEFT edge instead of a tidy one — a
   list reads left-aligned. */
.pu-badges li { display: flex; justify-content: flex-start; }

/* Item 2: the cascade arrival for the render that follows the chrome fade-in
   — see refreshBadges()'s `cascade` argument. Each <li> gets its own
   animation-delay inline (40ms apart). */
@keyframes pu-badge-cascade-in {
  from { opacity: 0; transform: translateX(-10px); }
  to   { opacity: 1; transform: none; }
}
.pu-badges li.pu-cascade { animation: pu-badge-cascade-in 0.28s ease both; }
/* Built, but not yet shown: see refreshBadges() in the sim JS. */
.pu-badges li.pu-badge-pending { opacity: 0; }

.pu-badge {
  pointer-events: auto;
  display: flex;
  align-items: center;
  gap: 6px;
  background: rgba(245, 241, 229, 0.94);
  color: var(--ink);
  border: 1px solid rgba(0, 68, 69, 0.25);
  border-radius: 999px;
  padding: 5px 10px 5px 8px;
  font-family: Roboto, Arial, sans-serif;
  font-size: 14px;
  font-weight: 600;
  cursor: pointer;
  /* The badge column's own width less its right padding (PU_BADGE_COL_W in
     the sim JS): wide enough for the longest label this drawer set has plus
     a condition tag, "Upper canine" with BROKEN beside it. */
  max-width: 222px;
  text-align: left;
  transition: background-color 0.15s ease, box-shadow 0.15s ease, transform 0.1s ease;
}
.pu-badge:hover { background: #ffffff; box-shadow: 0 2px 6px rgba(0, 0, 0, 0.18); }
.pu-badge:active { transform: scale(0.96); }
.pu-badge:focus-visible { outline: 3px solid var(--focus-glow); outline-offset: 2px; }
.pu-badge.active { background: var(--accent); color: #fff; border-color: var(--accent); }
.pu-badge.dim { opacity: 0.55; }

.pu-badge-label { font-weight: 700; white-space: nowrap; }
.pu-badge-choice {
  color: var(--muted);
  font-weight: 400;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.pu-badge.active .pu-badge-choice { color: rgba(255, 255, 255, 0.85); }

/* Item 2: a small trailing chevron says "this opens something" without
   words — the affordance that replaces the removed hint line. */
.pu-badge-chevron {
  width: 6px;
  height: 6px;
  flex-shrink: 0;
  border-top: 2px solid currentColor;
  border-right: 2px solid currentColor;
  transform: rotate(45deg);
  opacity: 0.7;
}

/* Item 2: if nothing's been picked ~6s after the badges appear, they pulse
   once each, staggered — see the setTimeout in refreshBadges(). */
@keyframes pu-badge-pulse {
  0%, 100% { transform: scale(1); box-shadow: none; }
  40% { transform: scale(1.06); box-shadow: 0 0 0 5px rgba(0, 68, 69, 0.2); }
}
.pu-badge.pu-badge-pulse { animation: pu-badge-pulse 0.6s ease; }
@media (prefers-reduced-motion: reduce) {
  .pu-badges li.pu-cascade { animation: none; opacity: 1; transform: none; }
  .pu-badge.pu-badge-pulse { animation: none; }
}

.hud-complete-msg {
  pointer-events: none;
  align-self: flex-end;
  font-family: Roboto, Arial, sans-serif;
  font-size: 14px;
  font-weight: 600;
  color: var(--accent);
  background: rgba(245, 241, 229, 0.94);
  border-radius: 8px;
  padding: 4px 10px;
}
.hud-complete-msg.hidden { display: none; }

/* Item 5: Previous / Put back / Next as one row, fixed to the stage's own
   bottom edge, centred — none of the three is positioned individually any
   more (updatePickupHud() never touches them), so nothing here can drift
   with zoom the way the old frame-relative placement did. */
/* The account the drawer held back until the groups were checked: a band
   across the bottom of the stage, above the Previous / Put back / Next row.
   Centred on the stage rather than the skull, because it does not move while
   the specimen is turned or zoomed, and a caption that slides cannot be read
   along with. chromeReserves() measures its height so the specimen is fitted
   above it. */
.pu-reveal {
  position: absolute;
  left: 50%;
  transform: translateX(-50%);
  bottom: 84px;
  z-index: 4;
  pointer-events: none;
  width: min(640px, calc(100% - 320px));
  box-sizing: border-box;
  padding: 10px 14px;
  border-radius: 8px;
  border-left: 4px solid var(--accent);
  background: rgba(245, 241, 229, 0.96);
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.25);
  font-family: Roboto, Arial, sans-serif;
  font-size: 14px;
  line-height: 1.5;
  color: var(--ink);
}
.pu-reveal[hidden] { display: none; }
/* On a narrow stage the gutters are narrower too, so the band can use more
   of the width -- and it needs to, since the text is the same length. */
.pickup-hud.narrow .pu-reveal { width: calc(100% - 32px); }

.pu-bottombar {
  position: absolute;
  left: 50%;
  bottom: 16px;
  transform: translateX(-50%);
  display: flex;
  align-items: center;
  gap: 10px;
  pointer-events: none; /* children opt back in below */
}
.pu-arrow {
  pointer-events: auto;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 40px;
  height: 40px;
  flex-shrink: 0;
  border-radius: 50%;
  border: 1px solid rgba(0, 68, 69, 0.25);
  background: rgba(245, 241, 229, 0.94);
  color: var(--ink);
  cursor: pointer;
}
.pu-arrow:hover { background: #ffffff; }
.pu-arrow:active { transform: scale(0.94); }
.pu-arrow:focus-visible { outline: 3px solid var(--focus-glow); outline-offset: 2px; }
/* One chevron rule for all three buttons. The path is drawn symmetric about
   the centre of its 24x24 viewBox, so the flex centring above puts the ink
   itself in the middle of the circle — no per-direction nudge, and nothing
   that drifts if the stroke width changes. */
.pu-chevron {
  display: block;
  width: 17px;
  height: 17px;
  flex-shrink: 0;
  fill: none;
  stroke: currentColor;
  stroke-width: 2.4;
  stroke-linecap: round;
  stroke-linejoin: round;
}
.pu-chevron-down { width: 13px; height: 13px; }

.pu-putback {
  pointer-events: auto;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  flex-shrink: 0;
  /* Exactly the .pu-arrow circles' height, so the three buttons read as one
     row of controls rather than a short pill between two tall discs (Ben's
     seventh walkthrough). */
  height: 40px;
  background: rgba(245, 241, 229, 0.94);
  color: var(--ink);
  border: 1px solid rgba(0, 68, 69, 0.25);
  border-radius: 999px;
  padding: 0 18px;
  font-family: Roboto, Arial, sans-serif;
  font-size: 14px;
  font-weight: 600;
  cursor: pointer;
  white-space: nowrap;
}
.pu-putback:hover { background: #ffffff; }
.pu-putback:active { transform: scale(0.97); }
.pu-putback:focus-visible { outline: 3px solid var(--focus-glow); outline-offset: 2px; }

/* Item 5: a single stacked control, + above -, joined with a shared border
   like a map's zoom control — fixed to the stage's bottom-right corner, so
   it never moves when clicked (the bug: the old frame-relative zoom grew/
   shrank the frame it was anchored to on every click). */
.pu-zoom {
  position: absolute;
  right: 16px;
  bottom: 16px;
  pointer-events: auto;
  display: flex;
  flex-direction: column;
  border-radius: 8px;
  overflow: hidden;
  border: 1px solid rgba(0, 68, 69, 0.25);
  background: rgba(245, 241, 229, 0.94);
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2);
}
.pu-zoom-btn {
  width: 36px;
  height: 36px;
  border: none;
  background: transparent;
  color: var(--ink);
  font-size: 16px;
  font-weight: 700;
  line-height: 1;
  cursor: pointer;
}
.pu-zoom-btn + .pu-zoom-btn { border-top: 1px solid rgba(0, 68, 69, 0.25); }
.pu-zoom-btn:hover { background: #ffffff; }
.pu-zoom-btn:active { background: #eae5d6; }
.pu-zoom-btn:focus-visible { outline: 3px solid var(--focus-glow); outline-offset: 2px; }

/* Narrow stage (checks N1): badges drop the current-choice text and the
   badge column/panel both shrink (widths set in JS, PU_*_W_NARROW) so the
   frame still gets real room between two fixed columns on a tight stage. */
.pickup-hud.narrow .pu-badge-choice { display: none; }
.pickup-hud.narrow .pu-badge { max-width: 136px; }
/* Long labels wrap rather than being cut off mid-word, which is what used to
   happen here -- "Front teet", "Eye socke". The column is wide enough for
   every label in both drawer sets except "Upper canine" with its condition
   tag, and that one grows downward instead of off the edge. */
.pickup-hud.narrow .pu-badge-label { white-space: normal; }
.pickup-hud.narrow .pu-panel-arrow { display: none; }

.drawer-tabs {
  display: flex;
  gap: 2px;
  margin-bottom: 6px;
  /* The underline is a travelling bar, not each tab's own border: a border
     cannot slide from one tab to the next, it can only blink off here and on
     there. positionTabUnderline() measures the active tab and moves this. */
  position: relative;
}

.drawer-tab-underline {
  position: absolute;
  bottom: 0;
  height: 3px;
  border-radius: 2px;
  background: var(--accent);
  transition: left 260ms cubic-bezier(0.4, 0, 0.2, 1), width 260ms cubic-bezier(0.4, 0, 0.2, 1);
  pointer-events: none;
}

.drawer-tab {
  background: transparent;
  border: none;
  border-bottom: 3px solid transparent;
  padding: 6px 9px;
  font-family: Roboto, Arial, sans-serif;
  font-size: 14px;
  font-weight: 600;
  color: var(--muted);
  cursor: pointer;
  display: flex;
  align-items: center;
  gap: 5px;
  white-space: nowrap;
}

/* The "Coming later" badge is the widest thing in the whole toggle -- at the
   shared .badge size it was 102px of a 254px strip, so the tab that is not
   ready yet took more room than the one in use. Smaller only here; the same
   badge on a specimen ("Not a primate", "Jaw only") is doing real work and
   keeps its size. */

.drawer-tab.active {
  color: var(--accent);
  /* No border-bottom-color here any more -- .drawer-tab-underline carries it,
     so it can travel between the two tabs instead of jumping. */
}

.drawer-tab:disabled {
  color: #a9b3b3;
  cursor: not-allowed;
}

.drawer-tab:focus-visible {
  outline: 3px solid var(--focus-glow);
  outline-offset: -2px;
  box-shadow: 0 0 0 2px var(--ink);
}

.badge {
  display: inline-block;
  font-size: 12px;
  font-weight: 700;
  border-radius: 10px;
  padding: 1px 7px;
  text-transform: uppercase;
  letter-spacing: 0.02em;
}

/* Both of these used to carry no colour of their own, so they inherited the
   page's dark ink onto a near-black label and were all but invisible (Ben's
   ninth walkthrough). Each now brings its own light ground: amber for a
   caveat about the specimen itself, cool grey-green for the taxonomic note.
   Both clear 9:1 against their own background. */
.badge-jaw {
  background: #F2DFAE;
  color: #3D2F00;
}

.badge-outgroup {
  background: #DCE6E4;
  color: #1F3332;
}

/* Inside a pill the badge sits on an 11px line, so it comes down a size. */
.label-pill .badge {
  font-size: 12px;
  padding: 1px 5px;
  flex-shrink: 0;
}

/* Ben's third walkthrough, finding #3: the family tree used to be a plain
   top-strip button that squeezed the stage's own height open; it is now a
   drawer tab (#btn-reveal, styled by .reveal-tab below) pinned to the
   stage's bottom edge, and this panel slides up from underneath the stage
   as an overlay instead — position:absolute against #sim-app (see its own
   position:relative) so opening it never resizes/refits the 3D view.
   Always rendered (never display:none) so the closed state can still show
   the tab sitting flush with the stage's bottom edge; .open swaps the
   transform to slide the panel (and the tab riding on its top edge, kept
   in sync from JS — see openReveal()/closeReveal()) into view. */

@media (prefers-reduced-motion: reduce) {
  
}

/* One column now, and the diagram is the whole of it: the student's own tree
   is the drawer above this panel, not a list beside it. */

/* The left edge carries the group's own colour from the table and from its
   bracket on the tree; the border says matched (solid) or not (dashed), so
   the verdict never rests on colour alone. */


/* A clade's name, riding just above its own branching point — small and
   quiet, so it reads as a label on the fork rather than as another animal. */

/* The humans: on the tree, because where we sit next to the chimpanzee is
   the point, but hollow and italic because no such skull is in the drawer.
   The sentence that says so is .reveal-tree-note, under the diagram. */

/* The rule the outgroup block sits below: it is not a primate, so it hangs
   off nothing. */

/* One of the student's own groups, drawn against the tips it contains, in
   that group's own colour from the table. A bracket that lines up with one
   fork of the tree matched; a dashed one did not, and a dashed bracket
   spanning two clades is what a wrong group looks like. Dash, not colour
   alone, carries the distinction (WCAG 2.1 AA). */

@media (max-width: 560px) {
  .top-strip { font-size: 14px; }
  
  
  
}

/* ── Trait tagging on the model (segments 2 + 4 + 7 polish pass) ──
   The painted region lives on the bone itself (see buildRegionPatches() in
   the sim JS); the badge column + leader line that point at it live in
   #pickup-hud (above); this block styles the choices panel a badge opens.
   --panel-accent (set per-open in openTraitPopover()) carries that badge's
   own trait colour through the accent bar, the arrow and the heading dot,
   so the panel visibly belongs to the one painted patch on the skull. */
.pu-panel {
  --panel-accent: var(--accent); /* JS overrides this per trait; the var() fallback covers the instant before the first open */
  position: absolute;
  z-index: 7;
  pointer-events: auto;
  background: #ffffff;
  border-radius: 12px;
  border-top: 4px solid var(--panel-accent);
  padding: 14px 16px 12px;
  width: 240px;
  max-width: calc(100vw - 16px);
  box-shadow: 0 6px 24px rgba(0, 0, 0, 0.3);
}

.pu-panel.hidden { display: none; }
.pu-panel.narrow { width: 180px; } /* must match PU_PANEL_W_NARROW */

/* A small notch on the panel's left edge, top-aligned with the badge row
   that opened it (positionPanel() in the sim JS) — a visual "this is the
   one that opened me" pointer back toward the badge column, in the same
   trait colour as the accent bar above. Hidden on the narrow layout, where
   the panel is too close to the badge column for the notch to read as
   pointing at any one badge in particular. */
.pu-panel-arrow {
  position: absolute;
  top: 22px;
  left: -6px;
  width: 12px;
  height: 12px;
  background: #ffffff;
  border-left: 3px solid var(--panel-accent);
  border-bottom: 3px solid var(--panel-accent);
  transform: rotate(45deg);
}

.pu-panel-head {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-bottom: 2px;
}

.pu-panel-dot {
  width: 12px;
  height: 12px;
  border-radius: 50%;
  flex-shrink: 0;
  background: var(--panel-accent);
}

.pu-panel h3 {
  font-family: Roboto, Arial, sans-serif;
  font-size: 16px;
  font-weight: 700;
  color: var(--ink);
  margin: 0;
  overflow-wrap: normal;
  word-break: normal;
}

.pu-panel-hint {
  font-family: Roboto, Arial, sans-serif;
  font-size: 14px;
  color: var(--muted);
  line-height: 1.4;
  /* Lines up under the trait name, past the dot, rather than the panel's
     own left edge — reads as "about the heading above" (checklist item:
     hint in smaller muted type beneath the heading). */
  margin: 2px 0 10px 20px;
  overflow-wrap: normal;
  word-break: normal;
}

.pu-panel-hint.hidden { display: none; }

/* One line under the choices: some of them may fit no skull in the drawer. */
.pu-panel-nomatch {
  font-family: Roboto, Arial, sans-serif;
  font-size: 13px;
  font-style: italic;
  color: var(--muted);
  line-height: 1.4;
  margin: 8px 0 0;
  overflow-wrap: normal;
  word-break: normal;
}

/* What this specimen cannot be asked. Set apart from the hint -- which is
   about the TRAIT, the same on every skull -- because this is about THIS
   bone on THIS fossil, and a student who reads it as general guidance will
   answer anyway. A rule rather than a tint, so it survives a greyscale
   print and does not depend on colour alone (WCAG 1.4.1). */
.pu-panel-condition {
  font-family: Roboto, Arial, sans-serif;
  font-size: 14px;
  line-height: 1.4;
  color: var(--ink);
  background: #F6EFE2;
  border-left: 3px solid #8A6D3B;
  border-radius: 0 4px 4px 0;
  margin: 0 0 10px 20px;
  padding: 6px 8px;
  overflow-wrap: normal;
  word-break: normal;
}

.pu-panel-condition.hidden { display: none; }

/* The same fact on the badge, so it is known before the badge is pressed. */
.pu-badge-condition {
  font-family: Roboto, Arial, sans-serif;
  font-size: 12px;
  font-weight: 700;
  letter-spacing: 0.02em;
  text-transform: uppercase;
  color: #6B5325;
  background: #F2E7D3;
  border-radius: 3px;
  padding: 1px 5px;
  margin-left: 6px;
  white-space: nowrap;
}

@media (prefers-color-scheme: dark) {
  .pu-panel-condition { background: #3A3224; border-left-color: #C9A227; color: #F5F1E5; }
  .pu-badge-condition { background: #3A3224; color: #E8D9A8; }
}

.pu-panel-options {
  display: flex;
  flex-direction: column;
  gap: 8px;
  margin-bottom: 4px;
}

/* Item 4: a choice CARD, not a radio row — a full-width tap target the
   student presses to name what they see, not a form control they "select".
   44px is the standard minimum touch target, and the label/marker pair is
   spaced with justify-content so the row reads as one whole unit, not text
   crammed against an edge. border-left is transparent until chosen (below),
   so every row already reserves the same 4px so nothing shifts when it
   fills in. */
.pu-panel-option {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  min-height: 44px;
  text-align: left;
  background: #F5F1E5;
  color: var(--ink);
  border: 1px solid rgba(0, 68, 69, 0.2);
  border-left-width: 4px;
  border-left-color: transparent;
  border-radius: 8px;
  padding: 10px 14px;
  font-family: Roboto, Arial, sans-serif;
  font-size: 14px;
  line-height: 1.3;
  cursor: pointer;
  transition: background-color 0.15s ease, border-color 0.15s ease, transform 0.1s ease;
}

.pu-panel-option-label { overflow-wrap: normal; word-break: normal; }

.pu-panel-option:hover { background: #ffffff; border-color: var(--panel-accent); }
.pu-panel-option:active { transform: scale(0.98); }

/* The chosen answer gets a check mark at the trailing edge (checklist item:
   "not merely a colour change" — legible without relying on colour alone),
   invisible — not a circle outline — until chosen. No radio-style ring: the
   filled left edge below plus this mark together ARE the chosen state. */
.pu-panel-option-check {
  flex-shrink: 0;
  width: 18px;
  height: 18px;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 15px;
  line-height: 1;
  color: transparent;
}

.pu-panel-option-check::before { content: '\2713'; }

.pu-panel-option[aria-current="true"] {
  border-color: var(--panel-accent);
  border-left-color: var(--panel-accent);
  font-weight: 600;
}

.pu-panel-option[aria-current="true"] .pu-panel-option-check {
  color: var(--panel-accent);
}

/* Focus rings visible on every row, including the chosen one — outline
   draws outside the row's own border/box-shadow, so aria-current's inset
   shadow can never hide it. */
.pu-panel-option:focus-visible,
.pu-panel-clear:focus-visible {
  outline: 3px solid var(--focus-glow);
  outline-offset: 2px;
}

/* "Not sure yet" set apart behind its own divider so it never reads as a
   third answer alongside the real options (checklist item) — quieter type,
   centred, no row chrome of its own. */
.pu-panel-clear-row {
  margin-top: 8px;
  padding-top: 8px;
  border-top: 1px solid rgba(0, 68, 69, 0.12);
  text-align: center;
}

.pu-panel-clear {
  background: transparent;
  color: var(--muted);
  border: none;
  font-family: Roboto, Arial, sans-serif;
  font-size: 14px;
  text-decoration: underline;
  cursor: pointer;
  padding: 4px 8px;
}

.pu-panel-clear:hover { color: var(--ink); }

/* Reset confirmation dialog (pre-flight §A: never discard collected rows silently). */

/* ── Groups on the table (segment 3) ──
   zone-svg draws every group's rounded outline (recomputed each frame from
   its members' screen positions, see updateZoneLayout()); zone-chips holds
   one .zone-chip per group, positioned at its outline's top-left corner the
   same way. Both sit above the specimen labels (z-index 3) and below the
   pick-up HUD (6), and both hide together with the labels while a skull is
   picked up. */
.zone-svg {
  position: absolute;
  inset: 0;
  z-index: 4;
  pointer-events: none;
  width: 100%;
  height: 100%;
}

.zone-svg.hidden { display: none; }

/* The tree drawn over the row of skulls (spec 10.20). Stubs and the root are
   quiet; a fork carries its own group's colour so a branch and its chip read
   as the same object. */
/* The unresolved part of the tree: a stub off every tip and the root bar that
   joins whatever is still ungrouped. Quiet, but not decorative -- the shape of
   the tree is content, so SC 1.4.11 asks 3:1 of it. Over the table's
   (78, 67, 57) the old 0.34 and 0.28 came to 2.03:1 and 1.79:1; 0.62 and 0.55
   clear it, and the root stays a touch quieter than the stubs. */
.tree-stub { stroke: rgba(210, 204, 190, 0.62); stroke-width: 2; }
.tree-root { stroke: rgba(210, 204, 190, 0.55); stroke-width: 2; }
.tree-fork { stroke-width: 3.5; stroke-linecap: round; }

/* A fork is the group itself: you can grab it and you can drop on it (spec
   10.20b). The drawn bracket is 3.5px, which is far too thin to aim at, so an
   invisible 18px line rides on top of it and carries the pointer. It is the
   only thing inside .zone-svg that takes pointer events. */
.tree-fork-hit {
  stroke-width: 18;
  stroke-opacity: 0;
  stroke-linecap: round;
  fill: none;
  pointer-events: stroke;
  cursor: grab;
}

/* The drop targets a drag could land on, outlined where they actually are: a
   ring around each skull (drawn in the SVG, since a mesh on the canvas cannot
   take a CSS border) and the same dashes around each group's name badge. The
   name cards keep their own ring because a card is a target too. */
.drop-target-ring {
  fill: none;
  stroke: var(--indicator);
  stroke-width: 2;
  stroke-dasharray: 6 4;
  stroke-opacity: 0.85;
}

/* The one target the pointer is actually over. Every legal target looked
   equally ready before, so nothing said where a release would land: this one
   goes solid, thickens, and fills faintly, which is the difference between
   "you could drop here" and "let go and it lands here". */
.drop-target-ring.armed {
  stroke-dasharray: none;
  stroke-width: 3;
  stroke-opacity: 1;
  fill: var(--indicator);
  fill-opacity: 0.14;
}

.zone-chip.drop-armed {
  outline: 3px solid var(--indicator);
  outline-offset: 3px;
  background: rgba(245, 241, 229, 0.22);
}

.zone-chip.drop-ready {
  outline: 2px dashed var(--indicator);
  outline-offset: 3px;
}

/* Drag start lights every node the dragged thing can legally join, so the
   student can see where a drop would land before letting go: a pale band along
   a ready fork, a pale dashed ring around a ready card.

   Pale, not --accent, and not the group's own colour. Both were tried and both
   are invisible here: the drawer's table measures (78, 67, 57), against which
   --accent #004445 is 1.14:1 and a group colour at 0.24 opacity is 1.13:1,
   where WCAG 2.1 SC 1.4.11 asks 3:1 of a state indicator. --indicator against
   the table is 8.5:1 and the band composites to 4.7:1. Dashed rather than
   solid so it never reads as the amber focus ring. */
.tree-fork-hit.drop-ready { stroke: var(--indicator); stroke-opacity: 0.65; }

/* A card carries NO ring of its own. It is a drop target, but it sits directly
   under the skull that is already ringed, so marking both drew two outlines
   around one thing. The ring goes on the bone, which is what a student aims
   at. (The .marked ring below is different: that marks the node being moved,
   not a place to drop it.) */

.zone-chips {
  position: absolute;
  inset: 0;
  z-index: 4;
  pointer-events: none;
}

.zone-chips.hidden { display: none; }

.zone-chip {
  position: absolute;
  pointer-events: auto;
  cursor: grab;
  -webkit-user-select: none;
  user-select: none;
  max-width: 260px;
  /* The same badge every specimen wears (.label-pill), so a group reads as one
     more named thing on the table rather than as chrome. Opaque, because it
     sits ON its own bracket and under whatever stem comes down from the fork
     above -- it has to cover both. Its own colour is the border, which is what
     still tells two groups apart. */
  font-family: Roboto, Arial, sans-serif;
  font-size: 14px;
  font-weight: 600;
  line-height: 1.3;
  /* Even on both sides. It was 4px right against 11px left, left over from
     when a round button always sat at that end; a chip carrying only a name
     looked pushed against its own edge (Ben, 2026-10-05). */
  padding: 3px 11px;
  border-radius: 999px;
  background: rgba(20, 20, 20, 0.92);
  color: var(--indicator);
  border: 1.5px solid var(--zone-color, var(--indicator));
  display: inline-flex;
  align-items: center;
  gap: 4px;
}

/* The student's own row: their name for the group, and the pencil and bin
   that act on it. It stays a ROW even when the badge below it stacks. */
.zone-chip-head {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  min-width: 0;
  max-width: 100%;
}

.zone-chip-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* A checked chip stacks: the group's own name on one line, the name
   scientists give it underneath. Side by side, "Group 3 | Lemurs, lorises
   and their relatives" was a 390px bar lying across the drawer, and several
   of them crossed each other (Ben, 2026-10-05). */
.zone-chip.has-verdict {
  flex-direction: column;
  align-items: flex-start;
  gap: 2px;
  max-width: 190px;
  border-radius: 10px;
  padding: 5px 11px;
}
.zone-chip.has-verdict .zone-chip-head { align-self: stretch; }
.zone-chip.has-verdict .zone-chip-name { white-space: normal; overflow: visible; }
.zone-chip.has-verdict .zone-chip-verdict {
  border-left: none;
  padding-left: 0;
  margin-left: 0;
  white-space: normal;
  line-height: 1.25;
}

/* Why, from the student's own tags. Under the verdict, on the selected chip
   only. */
.zone-chip-why {
  display: none;
  align-self: stretch;
  font-size: 12px;
  font-weight: 400;
  line-height: 1.3;
  white-space: normal;
  padding-top: 3px;
  margin-top: 2px;
  border-top: 1px solid rgba(245, 241, 229, 0.22);
  color: rgba(245, 241, 229, 0.86);
}
.zone-chip.selected .zone-chip-why { display: block; }
.zone-chip-why[hidden] { display: none !important; }
.zone-chip.verdict-match .zone-chip-why {
  color: #2C3E3E;
  border-top-color: rgba(0, 68, 69, 0.28);
}

/* The verdict. A match carries the name scientists use for that group, which
   is the thing the student actually gains; a miss says in words what it
   means. Neither carries an icon -- see renderZoneChips() for why. */
.zone-chip-verdict {
  flex: 0 0 auto;
  font-weight: 700;
  padding-left: 6px;
  margin-left: 2px;
  border-left: 1px solid rgba(245, 241, 229, 0.3);
  /* No cap of its own: the badge's own max-width governs, and a clade name is
     the point of the whole annotation. "Old World monkeys (Africa and Asia)"
     is the longest in this drawer and was being cut to "(Afri...". */
  white-space: nowrap;
}
.zone-chip-verdict[hidden] { display: none; }
/* The 460px was for a matched chip laid out on ONE line, where the clade
   name had to fit beside the group's own name. A checked chip stacks now, so
   the width belongs to .has-verdict below and this must not outrank it. */
/* A determined group becomes the printed slip a determined specimen gets in a
   real collection: cream card, dark ink, a hairline between the name the
   student gave it and the name it turns out to have. The group's own colour
   stays as the border, so which fork it belongs to is still readable. */
.zone-chip.verdict-match {
  background: #F5F1E5;
  color: #2C3E3E;
  border-width: 2px;
}
.zone-chip.verdict-match .zone-chip-name {
  color: #5A6E6E;
  font-weight: 600;
  letter-spacing: 0.01em;
}
.zone-chip.verdict-match .zone-chip-verdict {
  color: #1E2D2D;
  font-weight: 700;
  padding-top: 3px;
  margin-top: 1px;
  border-top: 1px solid rgba(0, 68, 69, 0.28);
  align-self: stretch;
}
/* Undetermined: still the handwritten tag it was, with the reason in muted
   ink. No cross: the grouping is not a wrong answer, it is one the accepted
   tree has no single name for. */
/* The pencil and the bin on a determined slip. Every other badge on the
   table is near-black, so the buttons are drawn in --indicator (#F5F1E5) --
   which is the slip's own background colour, exactly. On the one badge that
   turns cream the two controls were cream on cream and simply vanished
   (Ben, 2026-10-06). On the slip they are the slip's ink instead: the glyph
   measures 9.9:1 against the cream, well past the 3:1 SC 1.4.11 asks of a
   control, and the hover fills with the Tuva green at 9.7:1 the other way. */
.zone-chip.verdict-match .zone-chip-btn { background: rgba(0, 68, 69, 0.12); }
.zone-chip.verdict-match .zone-chip-btn svg { fill: var(--ink); }
.zone-chip.verdict-match .zone-chip-btn:hover { background: var(--accent); }
.zone-chip.verdict-match .zone-chip-btn:hover svg { fill: var(--indicator); }
/* Same trap, one step along: the rename field is a cream pill, which on the
   cream slip has no edge at all. It keeps its dark text and gains one. */
.zone-chip.verdict-match .zone-chip-input {
  background: #FFFFFF;
  box-shadow: inset 0 0 0 1px rgba(0, 68, 69, 0.4);
}

/* On the way there. Everything on this fork belongs together; it is simply
   not the whole of the group scientists name, which in the fossil drawer is
   the ONLY way to reach a group of five, since a join builds a fork of two.
   So it keeps the tag it already wore -- its own colour, a solid edge -- and
   gains a line saying what is still missing. Not the cream determination
   slip, because it has not earned a name; not the dashed tag, because
   nothing about it is wrong. */
.zone-chip.verdict-partial .zone-chip-verdict {
  color: rgba(245, 241, 229, 0.86);
  font-weight: 500;
  padding-top: 3px;
  margin-top: 1px;
  border-top: 1px solid rgba(245, 241, 229, 0.26);
  align-self: stretch;
}

/* Undetermined: still the handwritten tag it was, with the reason in muted
   ink. No cross: the grouping is not a wrong answer, it is one the accepted
   tree has no single name for. This is the MIXED case only now -- a fork
   holding animals from two different groups, or one holding an animal that
   belongs elsewhere. A fork that is merely part-way wears .verdict-partial
   above. */
.zone-chip.verdict-miss {
  border-style: dashed;
}
.zone-chip.verdict-miss .zone-chip-verdict {
  color: rgba(245, 241, 229, 0.72);
  font-weight: 500;
  font-style: italic;
  padding-top: 3px;
  margin-top: 1px;
  border-top: 1px solid rgba(245, 241, 229, 0.22);
  align-self: stretch;
}

/* Editing in place. The field is sized to the name so the badge does not jump
   width the moment it is clicked into. */
.zone-chip-input {
  font: inherit;
  color: #141414;
  background: var(--indicator);
  border: 0;
  border-radius: 999px;
  padding: 0 8px;
  width: 11ch;
  min-width: 0;
}
.zone-chip-input:focus-visible { outline: 2px solid var(--focus-glow); outline-offset: 1px; }

/* Rename and remove, in line with the name and the same height as it, shown
   only once the group is selected. Round, because they sit inside a pill. */
/* 26px, not the 20px they started at: WCAG 2.5.8 asks 24x24 of a target, and
   at 20 with a 13px glyph they read as decoration rather than as buttons
   (Ben, 2026-09-22). The badge grew its padding to sit around them. */
.zone-chip-btn {
  flex: 0 0 auto;
  display: none;
  place-items: center;
  width: 26px;
  height: 26px;
  padding: 0;
  border: 0;
  border-radius: 50%;
  background: rgba(245, 241, 229, 0.18);
  cursor: pointer;
}
.zone-chip.selected .zone-chip-btn,
.zone-chip.editing .zone-chip-btn { display: grid; }
/* "Check this group" is the one chip button that is always there: a student
   who has to select a group first to find it never learns they may check
   early. It goes once the group wears a verdict. */
.zone-chip .zone-chip-check { display: grid; }
.zone-chip .zone-chip-check[hidden] { display: none; }
.zone-chip-btn svg { width: 16px; height: 16px; fill: var(--indicator); }
.zone-chip-btn:hover { background: var(--indicator); }
.zone-chip-btn:hover svg { fill: #141414; }
.zone-chip-btn:focus-visible { outline: 2px solid var(--focus-glow); outline-offset: 2px; }

.zone-chip.zone-chip-hidden { display: none; }

/* Same as a dragged card: cursor only. The chip holds pointer capture while its
   group is dragged, and pickForkAtPointer() excludes the dragged group by id
   rather than relying on the chip being untouchable. */
.zone-chip.dragging { cursor: grabbing; }

.zone-chip.selected { border-width: 2.5px; padding-right: 4px; }

/* A marked chip's ring has the dark table on one side and the chip's own cream
   face on the other, so it needs both: ink reads against the chip (10:1), the
   pale outer ring reads against the table (8.5:1). It has to come AFTER
   .selected, which sets the same property at the same specificity: a chip can
   be both, and the mark is the state waiting on the student's next press. */
.zone-chip.marked {
  outline: 2px solid var(--indicator);
  outline-offset: 2px;
}

.zone-chip:focus-visible {
  outline: 3px solid var(--focus-glow);
  outline-offset: 2px;
}

/* A tab switch is a journey, not a cut (Ben, 2026-09-28): the underline slides
   to the tab you picked, the drawer you were in leaves the way you came from,
   and the new one arrives from the side you are heading towards. Honoured by
   prefers-reduced-motion, where the whole thing becomes an instant swap. */
@media (prefers-reduced-motion: reduce) {
  .drawer-tab-underline { transition: none; }
}

/* The image credit under each specimen's scan credit in the About tab. A
   second work with its own author, so it is set apart from the line above
   rather than running on from it. */
.credit-image {
  font-size: 14px;
  color: var(--muted);
}

/* ── Trait glyphs ──────────────────────────────────────────────────────────
   One drawing per trait, on a disc in the trait's colour, repeated wherever
   that trait appears: the compare chips, the rows beside each skull, the
   badges in close-up and the question panel's heading. It replaces a plain
   coloured dot, which carried the trait only by a colour the student had to
   memorise (Ben, 2026-10-05). */
.trait-icon { display: block; flex-shrink: 0; }
.pill-row .trait-icon { margin-right: 1px; }
.pu-panel-dot .trait-icon,
.pu-badge .trait-icon,
.trait-chip .trait-icon { display: block; }
/* The dot rules these replace sized a 7-9px circle; the glyph needs the room
   its own drawing takes, so the old fixed sizes are cleared. */
.pu-panel-dot { width: auto; height: auto; background: none !important; border-radius: 0; }

/* The tree is the second look now, not the first. */


/* Check my groups, under the compare strip rather than floating at the foot
   of the stage. */
.check-btn {
  font-family: Roboto, Arial, sans-serif;
  font-size: 14px;
  font-weight: 700;
  color: var(--ink);
  background: rgba(245, 241, 229, 0.94);
  border: 1px solid rgba(0, 68, 69, 0.25);
  border-radius: 999px;
  padding: 6px 14px;
  cursor: pointer;
}
.check-btn:hover { background: #ffffff; }
.check-btn:focus-visible { outline: 3px solid var(--focus-glow); outline-offset: 2px; }
.check-btn.hidden { display: none; }

/* Undo and redo. The same cream panel the other two controls on the strip
   wear, so the row reads as controls of one kind. */
.history-controls {
  display: flex;
  align-items: center;
  gap: 2px;
  background: rgba(245, 241, 229, 0.94);
  border-radius: 8px;
  padding: 3px 5px;
}

.history-controls.hidden { display: none; }

.history-btn {
  display: grid;
  place-items: center;
  width: 30px;
  height: 30px;
  padding: 0;
  border: 0;
  border-radius: 6px;
  background: none;
  cursor: pointer;
  color: var(--ink);
}

.history-btn svg {
  width: 20px;
  height: 20px;
  fill: none;
  stroke: currentColor;
  stroke-width: 2;
  stroke-linecap: round;
  stroke-linejoin: round;
}

.history-btn:hover:not(:disabled) { background: rgba(0, 68, 69, 0.1); }
.history-btn:focus-visible { outline: 3px solid var(--focus-glow); outline-offset: 1px; }

/* Nothing left to undo. Kept in place rather than hidden, so the pair does
   not shuffle sideways every time one end of the history runs out. SC 1.4.11
   exempts a disabled control, but a student still has to see that the button
   is there, so this clears the 3:1 a live one would owe: measured 3.09:1
   against the strip's cream. */
.history-btn:disabled {
  color: #7E8C8C;
  cursor: default;
}

/* The answer key, folded at the foot of the teacher tab. */

/* The thumbnail a confirmed group earns, on the label itself. */
.pill-thumb {
  display: block;
  width: 100%;
  max-width: 120px;
  height: 46px;
  object-fit: cover;
  border-radius: 5px;
  margin-bottom: 5px;
}

/* ── One row across the top ────────────────────────────────────────────────
   Which drawer, then what to compare, then check the groups. They were
   stacked three deep down the left, which cost the skulls height they
   needed and read as three unrelated controls (Ben, 2026-10-05). */
.top-strip {
  display: flex;
  align-items: stretch;   /* every control the same height, whatever its text */
  gap: 8px;
  /* One row wherever one row fits, which is Ben's ask (2026-10-05) and is
     every width from about 1000px up. Below that the three controls are
     wider than the stage, and `nowrap` put Premolars, Face and Check my
     groups off the right edge of a 600px screen with no way to reach them.
     Wrapping is the only degradation that keeps every control on screen. */
  flex-wrap: wrap;
  row-gap: 8px;
}
.top-strip > * { align-self: stretch; }
.top-strip .trait-filters,
.top-strip .check-btn,
.top-strip .drawer-tabs {
  margin: 0;
  min-height: 36px;
  display: flex;
  align-items: center;
}
/* The tab group keeps its own positioning context. This block used to set
   `position: static` on all three, which took it away: the travelling
   underline is absolute at `bottom: 0` of its parent, so with the tabs
   static it fell through to #top-strip instead. That was invisible while
   the strip was exactly as tall as the tabs, and showed the moment the
   strip grew a second row -- the underline dropped to the foot of the whole
   strip and sat under the note (Ben, 2026-10-06). */
.top-strip .drawer-tabs { position: relative; }
/* ...but hidden still means hidden. The rules above set `display: flex` at
   the same specificity as `.trait-filters.hidden` and `.check-btn.hidden`
   and come later in the file, so they were winning: on a fresh drawer the
   compare strip showed as an empty cream box and Check my groups was
   offering to check nothing (Ben, 2026-10-05). */
.top-strip .trait-filters.hidden,
.top-strip .check-btn.hidden { display: none; }

.top-strip .check-btn { white-space: nowrap; }
/* The tab group keeps its own cream panel; the other two get their own, so
   the three read as three controls rather than one long bar. */
.top-strip .check-btn { border-radius: 8px; }
.top-strip .trait-filters { border-radius: 8px; }

/* The group chips leave and arrive with the specimen names: both belong to
   the cast being swapped, and sharing the transition keeps them in step.
   They used to stay up through the whole switch, so the old drawer's groups
   hung over the new drawer's skulls (Ben, 2026-10-05). */
.zone-chips { transition: opacity 140ms linear, visibility 0s linear 0s; }
.zone-chips.slide-hidden {
  opacity: 0;
  visibility: hidden;
  transition: opacity 140ms linear, visibility 0s linear 140ms;
}
@media (prefers-reduced-motion: reduce) {
  .zone-chips,
  .zone-chips.slide-hidden { transition: none; }
}

/* A pill carrying a photograph is a card, not a capsule: the 999px radius
   cut the corners off the picture and the name under it (Ben, 2026-10-05). */
.label-pill:has(.pill-thumb) { border-radius: 9px; }
.label-pill.pill-has-thumb { border-radius: 9px; }

/* ── Reset confirmation dialog (names what Reset clears) ── */
.reset-confirm-dialog {
  border: none;
  border-radius: 12px;
  padding: 24px 28px;
  width: min(380px, calc(100vw - 32px));
  margin: auto;
  background: #ffffff;
  box-shadow: 0 8px 32px rgba(0, 0, 0, 0.25);
  font-family: Roboto, Arial, sans-serif;
}
.reset-confirm-dialog::backdrop { background: rgba(0, 0, 0, 0.45); }
.reset-confirm-dialog h3 { margin: 0 0 8px; font-size: 1.1rem; font-weight: 600; color: var(--accent); }
.reset-confirm-dialog p { margin: 0 0 20px; font-size: 0.9rem; color: var(--ink); line-height: 1.45; }
.reset-confirm-actions { display: flex; gap: 10px; justify-content: flex-end; flex-wrap: wrap; }
.reset-confirm-btn {
  padding: 8px 16px;
  border-radius: 6px;
  font-family: Roboto, Arial, sans-serif;
  font-size: 0.85rem;
  font-weight: 500;
  cursor: pointer;
}
.reset-confirm-btn--keep { background: var(--accent); color: #fff; border: 1px solid var(--accent); }
.reset-confirm-btn--keep:hover { background: var(--accent-light); }
.reset-confirm-btn--discard { background: #fff; color: var(--ink); border: 1px solid #ccc; }
.reset-confirm-btn--discard:hover { background: #f5f5f5; border-color: #aaa; }
.reset-confirm-btn:focus-visible { outline: 3px solid var(--focus-glow); outline-offset: 2px; }

/* The line beside Check my groups, until a check has been used once. */
.check-hint {
  font-family: Roboto, Arial, sans-serif;
  font-size: 13px;
  color: #fff;
  line-height: 1.3;
  align-self: center;
  text-shadow: 0 1px 2px rgba(0, 0, 0, 0.6);
}
.check-hint.hidden { display: none; }

/* ── The scientists' groups on the drawer, after the first check ── */
/* Back to my groups wears Tuva green, so a glance tells the student the
   table is showing the scientists' groups, not their own. */
.check-btn.sci-on { background: var(--accent); color: #fff; border-color: var(--accent); }
.check-btn.sci-on:hover { background: var(--accent-light); border-color: var(--accent-light); }
/* The scientists' groups are on the table: the table is looked at, not
   worked in, so the group badges lose their rename, remove and check
   buttons and the grip cursor. */
.sci-view .zone-chip { cursor: default; }
.sci-view .zone-chip .zone-chip-btn { display: none; }
/* Shown only when WebGL cannot start: the scene is empty, and this says why. */
#no-gl { position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); z-index: 5; max-width: min(380px, calc(100% - 24px)); padding: 12px 16px; line-height: 1.45; font-size: 14px; color: var(--ink); background: rgba(255, 255, 255, 0.94); border: 1px solid #B26464; border-radius: 10px; text-align: center; pointer-events: none; }
