/*
 * DARK MODE, on Ben's preference for the frog sim's chrome.
 *
 * Three of these scenes are pale — savanna dust, a green pasture, a milky
 * shallow sea — and cream cards on top of them competed with the picture for the
 * same brightness. Dark panels sit behind the scene instead of on top of it, and
 * the key-moment overlay was already dark, so the two now belong together.
 *
 * Two accents rather than one. Tuva green is #004445, which is nearly black: it
 * is a fill colour, and on a dark card it disappears. Anything that has to READ
 * on dark — a heading, the timeline's own marks — uses the lit variant.
 */
:root {
  --ink: #ece5d6;                     /* pale warm text, on dark */
  --paper: #1b1815;                   /* the dark ground */
  --line: rgba(236, 229, 214, 0.22);
  --tuva: #004445;                    /* the brand green, for fills */
  --tuva-lit: #4fb3a6;                /* and for anything that must read on dark */
  --tuva-fill: #1c6f66;               /* a mid teal a white label can sit on */
  --gold: #ffd166;
  --warn: #e08a5a;
  --card: rgba(26, 24, 20, 0.94);
  --font: "Roboto", ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif;
}

* { box-sizing: border-box; }
html, body { height: 100%; margin: 0; }
body {
  font-family: var(--font);
  color: var(--ink);
  background: #241d13;
  overflow: hidden;
}

/*
 * The shared toolbar is position:fixed and reserves its height with a padding-top
 * on body. So the app is a flex child inside that padding rather than pinned to
 * the viewport: at inset:0 it ignores the padding and its own top row — world
 * switcher, season chip, readout — slides under the toolbar.
 */
body { display: flex; flex-direction: column; }
#sim-app { flex: 1; min-height: 0; position: relative; }
#stage-panel { position: absolute; inset: 0; }
#canvas-frame { position: absolute; inset: 0; }
#sim-canvas { display: block; width: 100%; height: 100%; outline: none; }
/*
 * THE FOCUS RING HAS TO WORK IN THREE DIFFERENT WORLDS.
 *
 * Controls here sit over the live scene, and the scene's background is a different
 * colour in each world: savanna #d8c79b, soil #d7d8bd, reef #25708e. Measured
 * against those, the two colours this sim was using both fail 1.4.11's 3:1 on at
 * least two of the three — teal #4fb3a6 gives 1.51 / 1.73 / 2.20, and gold #ffd166
 * gives 1.16 / 1.01 / 3.85. Gold on the soil block is 1.01, which is invisible.
 *
 * A single hue cannot pass everywhere, so the ring is two-tone: a near-black core
 * that measures 10.06 / 11.57 / 3.03 against the three backgrounds, and a white
 * halo outside it that separates the core from a dark scene. One of the two always
 * reads, whatever is behind it.
 */
:root { --focus-core: #10201f; --focus-halo: rgba(255, 255, 255, .96); }
.km-chip:focus-visible,
.world-btn:focus-visible,
.control-card.action:focus-visible,
.info-dot:focus-visible,
#scale-chip:focus-visible,
#moment-btn:focus-visible,
#run-done-worlds button:focus-visible,
#run-done-close:focus-visible {
  outline: 2px solid var(--focus-core); outline-offset: 2px;
  box-shadow: 0 0 0 5px var(--focus-halo);
}
#sim-canvas:focus-visible {
  outline: 3px solid var(--focus-core); outline-offset: -3px;
  box-shadow: inset 0 0 0 6px var(--focus-halo);
}

.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;
}

/* ── world switcher ─────────────────────────────────────────────────────── */
#worlds {
  position: absolute; left: 14px; top: 12px; display: flex; gap: 6px;
}
.world-btn {
  font: inherit; font-size: 13px; cursor: pointer; padding: 7px 13px;
  border: 1px solid var(--line); border-radius: 6px;
  background: var(--card); color: var(--ink);
}
.world-btn:hover:not(:disabled) { border-color: var(--tuva-lit); }
.world-btn.is-active { background: var(--tuva-fill); border-color: var(--tuva-lit); color: #f2fbf9; }
.world-btn:disabled { opacity: .45; cursor: default; }
.world-btn:focus-visible { outline: 2px solid var(--tuva-lit); outline-offset: 2px; }

/* ── season chip ────────────────────────────────────────────────────────── */
/*
 * The timeline: the whole run laid along the bottom, with a diamond for each planned
 * key moment. Ben's reasoning, kept here because it is the design: "the student has
 * no idea how many key moments are coming... a human that knows what to expect has an
 * easier time sitting patiently". The playhead stopping on a diamond IS the freeze
 * signal — colour is deliberately not used for it, colour is this sim's readout.
 */
#timeline {
  position: absolute; left: 16px; right: 16px; bottom: 10px;
  display: flex; align-items: center; gap: 12px;
  background: var(--card); border: 1px solid var(--line); border-radius: 8px;
  padding: 8px 16px;
}
.hud-label { font-size: 12px; letter-spacing: .08em; text-transform: uppercase; opacity: .65; }
.hud-value { font-size: 15px; font-variant-numeric: tabular-nums; white-space: nowrap; }
#timeline.is-done { border-color: var(--warn); }
#timeline.is-done .hud-value { color: var(--warn); }
#tl-track {
  position: relative; flex: 1; height: 14px; align-self: center;
}
#tl-track::before {
  content: ''; position: absolute; left: 0; right: 0; top: 6px; height: 3px;
  border-radius: 2px; background: rgba(236, 229, 214, .16);
}
#tl-fill {
  position: absolute; left: 0; top: 6px; height: 3px; border-radius: 2px;
  background: var(--tuva-lit); width: 0;
}
/*
 * The playhead spans EXACTLY what a diamond spans.
 *
 * A 10 px square rotated 45 degrees is 14.142 px from point to point, so sitting
 * at top 2.5 px it runs 0.429 to 14.571. The playhead was 1 to 14, so wherever it
 * came to rest on a diamond — which is every key moment, by design — the
 * diamond's points showed past both ends of it. Half a pixel each end, and enough
 * to read as not lining up (Ben).
 */
#tl-head {
  position: absolute; top: 0.429px; width: 3px; height: 14.142px; border-radius: 2px;
  background: var(--tuva-lit); transform: translateX(-50%);
}
#timeline.is-held #tl-head { background: var(--gold); }
#timeline.is-held { border-color: var(--gold); }
/* one diamond per planned key moment */
.tl-mark {
  position: absolute; top: 2.5px; width: 10px; height: 10px;
  transform: translateX(-50%) rotate(45deg);
  background: var(--paper); border: 2px solid var(--tuva-lit);
  border-radius: 2px;
}
.tl-mark.done { background: var(--tuva-lit); }
.tl-mark[hidden] { display: none; }
/* An INVITED moment: it fires when the student performs the experiment its tooltip
   names, so its diamond is dashed until they do. */
.tl-mark.tl-invite { border-style: dashed; }
.tl-mark.active {
  background: var(--gold); border-color: var(--gold);
  animation: tlPulse 1.6s ease-in-out infinite;
}
@keyframes tlPulse {
  /* 1.35 took an active diamond to 19.1 px inside a 14 px track, so it burst out
     of the bar on every beat. 1.18 still reads as a pulse and stays in its lane. */
  0%, 100% { transform: translateX(-50%) rotate(45deg) scale(1); }
  50%      { transform: translateX(-50%) rotate(45deg) scale(1.18); }
}
@media (prefers-reduced-motion: reduce) {
  .tl-mark.active { animation: none; }
}

/* ── tree tags ──────────────────────────────────────────────────────────── */
.tree-tag {
  position: absolute; transform: translate(-50%, -100%);
  background: var(--tuva); color: #fff; border-radius: 4px;
  padding: 3px 9px; font-size: 12px; white-space: nowrap;
  pointer-events: none; opacity: 0; transition: opacity .3s ease;
  /* The label grows out of the trunk it names, so the corner it swings from is
     the point it is pinned to. */
  transform-origin: 50% 100%;
}
/* White on --warn measured 2.64:1, under the 4.5 floor for 12px text. Dark ink on
   the same orange is 6.54:1, and the orange still reads as the "no partner" side. */
.tree-tag.is-none { background: var(--warn); color: #2a1608; }
.tree-tag.is-on { opacity: 1; }
/*
 * ARRIVING, not fading in.
 *
 * A tenth of a second of opacity is a change a student who is watching the
 * elephants will simply not see (Ben, twice). The label now swings up out of the
 * trunk, overshoots, and settles: half a second of motion with a real overshoot
 * in it, which is what the eye is built to catch. It plays ONCE, on the frame the
 * label is first earned — `is-new` is added then and taken off when the animation
 * ends, so a label that scrolls off the edge of the screen and back does not pop
 * again.
 */
.tree-tag.is-new { animation: tag-pop .52s cubic-bezier(.2, 1.5, .4, 1) both; }
@keyframes tag-pop {
  0%   { opacity: 0; transform: translate(-50%, -100%) translateY(10px) scale(.35) rotate(-7deg); }
  55%  { opacity: 1; transform: translate(-50%, -100%) translateY(-5px) scale(1.22) rotate(2deg); }
  75%  { transform: translate(-50%, -100%) translateY(1px) scale(.94) rotate(-1deg); }
  100% { opacity: 1; transform: translate(-50%, -100%) translateY(0) scale(1) rotate(0); }
}

/* ── readout cards ──────────────────────────────────────────────────────── */
/*
 * A card belongs to a subject that has been named, and nothing else is on screen
 * until then. See subjectsNamed in worlds.js: the landing screen is the scene,
 * the dial and the world switcher, and that is all the student has to read.
 */
.tree-card.is-unnamed { display: none; }
.tree-card.is-new { animation: cardArrive .5s cubic-bezier(.2, .7, .3, 1) both; }
@keyframes cardArrive {
  from { opacity: 0; transform: translateY(-8px) scale(.97); }
  to   { opacity: 1; transform: none; }
}
@media (prefers-reduced-motion: reduce) {
  .tree-card.is-new { animation: none; }
}

#readout {
  position: absolute; right: 14px; top: 12px; display: flex; flex-direction: column; gap: 10px;
  width: 232px;
}
.tree-card {
  background: var(--card); border: 1px solid var(--line); border-radius: 8px;
  padding: 11px 13px 12px;
}
.tree-card h3 {
  margin: 0 0 8px; font-size: 13px; letter-spacing: .04em; text-transform: uppercase;
  color: var(--tuva-lit);
}
#card-none h3 { color: var(--warn); }
.stat {
  display: flex; justify-content: space-between; gap: 10px;
  font-size: 13px; padding: 2px 0;
}
.stat b { font-weight: 500; font-variant-numeric: tabular-nums; }
/* Swatches in the ants' own colours: two kinds of ant are on screen and the
 * readout has to say which row is which. See MAT.antRed / MAT.gall in acacia.js. */
.stat .key { display: flex; align-items: center; gap: 6px; }
.dot { width: 8px; height: 8px; border-radius: 50%; flex: none; }
.dot--ant { background: #a4552a; }
.dot--rival { background: #3b3128; }
.dot--nod { background: #d4697a; }
.dot--zoox { background: #a8763f; }
.stat--out { border-top: 1px solid var(--line); margin-top: 6px; padding-top: 6px; font-size: 14px; }
.pship {
  margin-top: 8px; font-size: 12px; padding: 4px 8px; border-radius: 4px;
  background: rgba(79, 179, 166, .14); color: var(--tuva-lit);
}
.pship.is-breaking { background: rgba(224, 138, 90, .16); color: var(--warn); }
.pship.is-lost { background: rgba(224, 138, 90, .26); color: var(--warn); }

/* ── controls ───────────────────────────────────────────────────────────── */
#controls {
  position: absolute; left: 14px; bottom: 62px; width: 268px;
  display: flex; flex-direction: column; gap: 9px;
}
.control-card {
  background: var(--card); border: 1px solid var(--line); border-radius: 8px;
  padding: 11px 13px 12px; display: block;
}
.slider-head { display: flex; justify-content: space-between; align-items: baseline; gap: 10px; }
.slider-head-l { display: inline-flex; align-items: center; gap: 6px; }
.slider-label { font-size: 13px; }
.slider-head { flex-wrap: wrap; }
.slider-inline-value { margin-left: auto; text-align: right; }
/* heat patterns: one toggle button each, two rows of chips under the slider */
.preset-row { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 8px; }
.preset-row[hidden] { display: none; }
.preset-label { flex: 0 0 100%; font-size: 12px; opacity: .75; }
.preset-btn {
  flex: 1 1 auto; min-height: 36px; padding: 3px 9px; border-radius: 6px;
  border: 1px solid var(--line); background: transparent; color: var(--ink);
  font: inherit; font-size: 13px; cursor: pointer;
}
.preset-btn:hover { background: rgba(236, 229, 214, .12); }
.preset-btn:focus-visible { outline: 2px solid var(--gold); outline-offset: 2px; }
.preset-btn[aria-pressed="true"] { background: var(--tuva-lit); border-color: var(--tuva-lit); color: #10201f; font-weight: 700; }
/*
 * The dial's note used to sit open under the slider, which put a sentence about
 * what MIGHT happen on screen before the student had done anything (Ben). It is a
 * footnote about the control, so it hides behind the control's own info dot.
 */
.info-dot {
  flex: none; width: 17px; height: 17px; border-radius: 50%; padding: 0;
  border: 1px solid rgba(236, 229, 214, .55); background: transparent;
  color: var(--ink); font: inherit; font-size: 12px; font-style: italic;
  font-weight: 700; line-height: 1; cursor: pointer;
  display: inline-flex; align-items: center; justify-content: center;
}
.info-dot::before { content: 'i'; }
.info-dot:hover { border-color: var(--tuva-lit); color: var(--tuva-lit); }
.info-dot:focus-visible { outline: 2px solid var(--gold); outline-offset: 2px; }
.info-dot[aria-expanded="true"] { background: var(--tuva-lit); border-color: var(--tuva-lit); color: #10201f; }
.slider-inline-value { font-size: 13px; color: var(--tuva-lit); font-weight: 500; }
.slider-track-row { display: block; margin-top: 7px; }
#browse-slider { width: 100%; accent-color: var(--gold); }
.slider-scale {
  display: flex; justify-content: space-between; font-size: 12px; opacity: .6; margin-top: 2px;
}
.threshold-note {
  display: block; margin-top: 7px; font-size: 12px; line-height: 1.35; color: var(--warn);
}
.threshold-note[hidden] { display: none; }
.control-card.action {
  font: inherit; font-size: 13px; cursor: pointer; text-align: left; color: var(--ink);
}
.control-card.action:hover { border-color: var(--tuva-lit); }
.control-card.action:focus-visible { outline: 2px solid var(--tuva-lit); outline-offset: 2px; }

/* ── the end of a run ───────────────────────────────────────────────────── */
#run-done {
  position: absolute; left: 50%; top: 46%; transform: translate(-50%, -50%);
  z-index: 12; width: min(430px, 78%); text-align: center;
  padding: 20px 22px 18px; border-radius: 12px;
  background: var(--card); border: 1px solid var(--tuva-lit);
  box-shadow: 0 10px 34px rgba(0, 0, 0, .5);
  animation: runDoneIn .4s cubic-bezier(.2, .7, .3, 1) both;
}
#run-done[hidden] { display: none; }
#run-done h2 {
  margin: 0 0 8px; font-size: 17px; font-weight: 600; color: var(--gold);
}
#run-done p { margin: 0 0 10px; font-size: 13px; line-height: 1.45; }
#run-done-again { color: var(--ink); opacity: .78; }
#run-done-worlds { display: flex; flex-wrap: wrap; gap: 8px; justify-content: center; margin: 12px 0; }
#run-done-worlds:empty { display: none; }
#run-done-keep { display: flex; gap: 10px; justify-content: center; margin: 12px 0 4px; }
#run-done-keep[hidden] { display: none; }
#run-done-keep button {
  font: inherit; font-size: 13px; cursor: pointer; padding: 8px 16px; border-radius: 7px;
  border: 1px solid rgba(236, 229, 214, .5); background: transparent; color: var(--ink);
}
#run-keep { background: var(--tuva) !important; color: #fff !important; border-color: var(--tuva-lit) !important; }
#run-done-keep button:hover { background: var(--tuva-lit); color: #10201f; }
#run-done-keep button:focus-visible { outline: 2px solid var(--gold); outline-offset: 2px; }
#run-done.is-asking #run-done-worlds,
#run-done.is-asking #run-done-again,
#run-done.is-asking #run-done-close { display: none; }
#run-done-worlds button {
  font: inherit; font-size: 13px; cursor: pointer; padding: 8px 14px;
  border-radius: 7px; border: 1px solid var(--tuva-lit);
  background: var(--tuva); color: #fff;
}
#run-done-worlds button:hover { background: var(--tuva-lit); color: #10201f; }
#run-done-worlds button:focus-visible,
#run-done-close:focus-visible { outline: 2px solid var(--gold); outline-offset: 2px; }
#run-done-close {
  font: inherit; font-size: 12px; cursor: pointer; padding: 5px 12px;
  border-radius: 999px; border: 1px solid rgba(236, 229, 214, .4);
  background: transparent; color: var(--ink);
}
@keyframes runDoneIn {
  from { opacity: 0; transform: translate(-50%, -46%) scale(.96); }
  to   { opacity: 1; transform: translate(-50%, -50%) scale(1); }
}
@media (prefers-reduced-motion: reduce) { #run-done { animation: none; } }

/* ── performance readout ────────────────────────────────────────────────── */
/* Same look and wording as the art bench's badge, so the two read alike.
   12px is this sim's minimum font size everywhere (Ben's WCAG 2.1 floor). */
#perf {
  /* Stacked above the scale chip, which now owns this corner. The chip is a thing
     the student reads; this is a thing the builder reads. */
  position: absolute; right: 14px; bottom: 96px;
  font-size: 12px; font-variant-numeric: tabular-nums;
  color: #fff; background: rgba(0, 68, 69, .72);
  padding: 4px 9px; border-radius: 4px; pointer-events: none;
}
#perf.warn { background: rgba(150, 60, 0, .82); }
#perf[hidden] { display: none; }

/* ── the moment bar ─────────────────────────────────────────────────────── */
/*
 * TOP CENTRE, which is where the frog sim puts its moment card and what Ben
 * prefers. It also separates the three things a key moment shows into three
 * bands that never fight: the card at the top, the framed subject centred, and
 * the note about a tapped badge along the bottom. At the foot of the frame the
 * card and the note were stacked in the same corner of the eye.
 */
#moment-bar {
  position: absolute; left: 50%; transform: translateX(-50%); top: 12px;
  /* Text across the full width, Continue under it at the bottom right (Ben,
     2026-10-26): as a side column the button took a strip the caption could
     have used, on every screen and not only a narrow one. */
  display: flex; flex-direction: column; gap: 8px; max-width: min(620px, 74%);
  background: var(--card); border: 1px solid var(--line); border-radius: 8px;
  padding: 10px 12px 10px 15px;
  /* Never taller than the stage it sits on, whatever the caption says. */
  max-height: calc(100% - 24px); overflow-y: auto;
}
#moment-bar[hidden] { display: none; }
/* Grows to fill the bar, and may shrink past its content: without min-width 0 a
   long unbroken caption would push the button off instead of wrapping it. */
#moment-text { display: flex; flex-direction: column; gap: 3px; min-width: 0; }
/* Names the thing the student is looking at, so "Continue" has an obvious subject. */
#moment-label {
  font-size: 12px; font-weight: 500; letter-spacing: .09em; text-transform: uppercase;
  color: var(--gold);
}
#moment-caption { font-size: 14px; line-height: 1.4; text-wrap: pretty; }
/* The one place the sim exaggerates scale, disclosed at the moment it is on screen. */
/* ── the scale footnote chip ───────────────────────────────────────────────
   Anything drawn larger than life says so, on screen, at the moment it is being
   looked at. It used to be a third line inside the key-moment card, where it ate
   the card's reading budget and only ever appeared during a beat. As a chip it can
   stand wherever an exaggerated thing is drawn, including the close-up.
   It sits above the dial, out of the way of both the framed subject and the bar. */
#scale-chip {
  /* The bottom right corner, just above the timeline (Ben). It has been up in the
     top left, where it sat among the world buttons and read as another control,
     and above the dial, where it read as part of the dial. Down here it is a
     footnote in the place a footnote goes, and out of the picture either way. */
  position: absolute; right: 16px; bottom: 62px; z-index: 6;
  font: inherit; font-size: 12px; line-height: 1.2; cursor: pointer;
  padding: 5px 10px; border-radius: 999px;
  border: 1px solid rgba(246, 242, 233, .5);
  background: rgba(35, 32, 26, .88); color: #f6f2e9;
  display: inline-flex; align-items: center; gap: 6px;
}
#scale-chip::after {
  content: 'i'; font-size: 10px; font-weight: 700; font-style: italic;
  width: 14px; height: 14px; border-radius: 50%; flex: none;
  display: inline-flex; align-items: center; justify-content: center;
  border: 1px solid rgba(246, 242, 233, .7);
}
#scale-chip[hidden] { display: none; }
#scale-chip:hover { border-color: rgba(246, 242, 233, .85); }
#scale-chip:focus-visible { outline: 2px solid var(--gold); outline-offset: 2px; }
#scale-tip {
  /* Above the chip it belongs to, in the same corner. It was left behind at the
     chip's old top-left home when the chip moved to the bottom right, so it opened
     on the far side of the screen from the thing that opened it (Ben). */
  position: absolute; right: 16px; bottom: 94px; z-index: 7; max-width: 260px;
  padding: 8px 12px; border-radius: 8px;
  background: rgba(35, 32, 26, .95); border: 1px solid rgba(246, 242, 233, .55);
  color: #f6f2e9; font-size: 12px; line-height: 1.45;
}
#scale-tip[hidden] { display: none; }
/* The chip is a footnote about what is on screen, so it stays legible while the
   rest of the chrome steps back for a key moment. */
body.km-quiet #scale-chip, body.km-quiet #scale-tip { opacity: 1; }

#moment-label { font-weight: 600; letter-spacing: .01em; }
#moment-btn {
  /* Gold on dark, as the frog's Continue is: the one warm thing on the card, so
     the eye finds the way onward without hunting for it. */
  font: inherit; font-size: 13px; font-weight: 600; cursor: pointer; white-space: nowrap;
  padding: 6px 14px; border-radius: 6px; border: none;
  background: var(--gold); color: #20180c;
  align-self: flex-end;
}
#moment-btn:focus-visible { outline: 2px solid var(--gold); outline-offset: 2px; }

/* ── quiet chrome during a key moment ──────────────────────────────────── */
/* Everything that is not the moment itself steps back, so the framed view and its
   bar are what the eye lands on. The season chip stays: its stopped sweep is how
   the freeze is signalled. Nothing is removed, only dimmed, and the readout keeps
   a little more presence because a student may want to glance at the numbers the
   caption is talking about. */
#worlds, #controls, #readout, #tree-tags, #perf {
  transition: opacity .5s ease;
}
/*
 * A key moment CLEARS THE SCREEN, it does not dim it.
 *
 * These were faded to 12% and the readout to 30%, on the reasoning that nothing
 * should jump when it comes back. What that actually produced was a beat playing
 * on top of a ghost of the whole interface: the cards, the dial, the world buttons
 * and both tree tags all still occupying their space, still competing for the eye,
 * still overlapping the badges and the card (Ben: cluttered and overwhelming, hard
 * to know what to look at). A faint control is still something to read.
 *
 * So anything that cannot act on the moment goes, properly — invisible, unclickable
 * and out of the way. What stays is what the moment is made of: the framed subject
 * and its badges, the card, the scale footnote, and the timeline, whose playhead
 * stopped ON a diamond is the whole signal that the calendar is being held. The
 * shared toolbar stays too; it is the way out of anything.
 *
 * It is a fade rather than a cut only so the screen clears smoothly. These are all
 * absolutely positioned, so nothing reflows when they go.
 */
.km-quiet #worlds, .km-quiet #controls, .km-quiet #zoom-btn,
.km-quiet #tree-tags, .km-quiet #perf, .km-quiet #readout,
.km-quiet #zoom-hint, .km-quiet #hotspot-tip {
  opacity: 0;
  visibility: hidden;
  pointer-events: none;
  transition: opacity .28s ease, visibility 0s linear .28s;
}

/* ── the readout cards at rest ──────────────────────────────────────────── */
/* On the shared quiet timer the cards fold to their two load-bearing lines:
   canopy left and the partnership state. Hover (or touch) unfolds a card. */
#readout .tree-card .stat:not(.stat--out) {
  overflow: hidden;
  max-height: 40px;
  transition: max-height .45s ease, opacity .45s ease, padding .45s ease;
}
/* "Algae in tissue" is the investment the whole comparison is about: it stays up on the reef. */
#readout.mini .tree-card .stat:not(.stat--out):not(.stat--keep) {
  max-height: 0;
  opacity: 0;
  padding-top: 0;
  padding-bottom: 0;
}
#readout.mini .tree-card:hover .stat:not(.stat--out),
#readout.mini .tree-card:focus-within .stat:not(.stat--out) {
  max-height: 40px;
  opacity: 1;
  padding-top: 2px;
  padding-bottom: 2px;
}
#readout.mini .tree-card .stat--out { border-top: none; margin-top: 0; padding-top: 0; }
#readout.mini .tree-card:hover .stat--out,
#readout.mini .tree-card:focus-within .stat--out {
  border-top: 1px solid var(--line); margin-top: 6px; padding-top: 6px;
}

/* ── the browsing-pressure card at rest ─────────────────────────────────── */
/* Once the run is going and the dial has not been touched for a while, the card
   folds down to its one-line summary: "Browsing pressure  High". Touching it, or
   focusing it from the keyboard, unfolds it again. The full card is the tool; the
   folded card is the record of what the tool was set to. */
#controls .slider-row .slider-track-row,
#controls .slider-row .slider-scale,
#controls .slider-row .preset-row,
#controls .slider-row .threshold-note {
  overflow: hidden;
  max-height: 60px;
  transition: max-height .45s ease, opacity .45s ease, margin .45s ease;
}
#controls .slider-row.mini .slider-track-row,
#controls .slider-row.mini .slider-scale,
#controls .slider-row.mini .preset-row,
#controls .slider-row.mini .threshold-note {
  max-height: 0;
  opacity: 0;
  margin: 0;
  pointer-events: none;
}
#controls .slider-row .preset-row { max-height: 140px; }
#controls .slider-row.mini { cursor: pointer; }

/* ── key-moment frame ────────────────────────────────────────────── */
/* Brackets settle on the subject, a chip names the view, callouts point things out.
   Everything is positioned from JS each frame; this file only says what it looks like. */
#km-frame {
  position: absolute; inset: 0;
  z-index: 6;                 /* over the canvas and vignette, under the cards and bar */
  pointer-events: none;
}
#km-frame[hidden] { display: none; }
#km-svg { position: absolute; inset: 0; width: 100%; height: 100%; }
#km-svg line { stroke: #f6f2e9; stroke-width: 1.4; opacity: .65; }
#km-box { position: absolute; transition: opacity .35s ease; }
.km-c {
  position: absolute; width: 26px; height: 26px;
  border: 0 solid rgba(246, 242, 233, .95);
  filter: drop-shadow(0 0 4px rgba(20, 16, 10, .55));
}
.km-tl { top: 0;    left: 0;   border-top-width: 2px;    border-left-width: 2px;  border-top-left-radius: 6px; }
.km-tr { top: 0;    right: 0;  border-top-width: 2px;    border-right-width: 2px; border-top-right-radius: 6px; }
.km-bl { bottom: 0; left: 0;   border-bottom-width: 2px; border-left-width: 2px;  border-bottom-left-radius: 6px; }
.km-br { bottom: 0; right: 0;  border-bottom-width: 2px; border-right-width: 2px; border-bottom-right-radius: 6px; }
.km-chip {
  /* Bigger than it was: at 12px in the scene's own colours a badge read as part of
     the picture and went unnoticed (Ben). It is a control, so it is sized like one. */
  /*
   * The whole border is the hue, which is how the frog sim does it: a ring of
   * colour reads as one object belonging to one thing, where a bar on the leading
   * edge read as a piece of trim. No underline — the badge is plainly a control
   * without one, and dotted text under coloured text was busy (Ben).
   */
  position: absolute; width: max-content; padding: 5px 12px; border-radius: 999px;
  /* max-content alone made "Cups with the animals drawn in" 206px wide, over half a
     phone screen. It may still size to its text; it may not outgrow the stage. */
  max-width: min(46vw, 280px);
  background: rgba(16, 22, 21, .88);
  border: 1px solid var(--chip-hue, rgba(232, 226, 208, .62));
  font-size: 13px; line-height: 1.25; color: var(--ink); white-space: nowrap;
  /* a badge is a button: tap it and a short note opens */
  font-family: inherit; cursor: pointer; pointer-events: auto;
  text-decoration: none;
  box-shadow: 0 1px 6px rgba(0, 0, 0, .35);
  transition: opacity .2s ease, box-shadow .2s ease, background .2s ease;
}
.km-chip:hover { box-shadow: 0 0 6px var(--chip-hue, rgba(232, 226, 208, .6)); }
.km-chip:focus-visible { outline: 2px solid var(--gold); outline-offset: 2px; }
/* the tapped badge holds the eye: it glows in its own hue, as the frog's does */
.km-chip.focused {
  background: rgba(30, 40, 38, .95);
  box-shadow: 0 0 9px var(--chip-hue, var(--gold));
}
.km-chip.dim { opacity: .55; }
/*
 * The note about the highlighted thing. Bottom centre, above the beat's card, with
 * the callout's own hue down its left edge — the same colour its badge, its leader
 * line, its ring and the meshes it lit are all wearing. Cornered like the frame, so
 * it reads as part of the same instrument rather than a browser tooltip.
 */
#km-tip {
  /* Under the framed subject. Left and top are set by kmTipPlace: parked at the
     foot of the picture it sat a long way from the four corners it belongs to
     (Ben), so it follows the frame and only falls back to the bottom band. */
  position: absolute; left: 50%; transform: translateX(-50%); bottom: 62px;
  /* max-width, not width: a fixed 560 px left a short note sitting in a box with
     a wide empty strip to the right of it (Ben). */
  max-width: min(560px, 76%); padding: 11px 15px 11px 16px; border-radius: 8px;
  background: rgba(28, 25, 20, .96);
  /* The corners carry the hue and the border stays neutral: a coloured left bar
     said the same thing twice, and the corners are what rhyme with the camera
     frame's own four corners (Ben). */
  border: 1px solid rgba(246, 242, 233, .3);
  color: #f6f2e9; font-size: 13px; line-height: 1.5; pointer-events: auto;
  white-space: normal; text-align: left;
  /* NOT text-wrap: balance. Tried, measured, reverted: balance shortens the lines
     without shrinking the box, so the longest note went from 44 px of ragged
     space to 267 px. max-width above is the fix — a short note now hugs its text
     to the padding, and a long one wraps the way any paragraph does. */
  box-shadow: 0 3px 14px rgba(0, 0, 0, .45);
}
#km-tip[hidden] { display: none; }
/* the note's own corners, in its hue */
.km-tc {
  position: absolute; width: 12px; height: 12px; pointer-events: none;
  border: 0 solid var(--note-hue, #e8e2d0);
}
.km-tc-tl { top: -1px; left: -1px;  border-top-width: 2px; border-left-width: 2px; }
.km-tc-tr { top: -1px; right: -1px; border-top-width: 2px; border-right-width: 2px; }
.km-tc-bl { bottom: -1px; left: -1px;  border-bottom-width: 2px; border-left-width: 2px; }
.km-tc-br { bottom: -1px; right: -1px; border-bottom-width: 2px; border-right-width: 2px; }
/* the brackets settle on rather than pop */
#km-frame.km-in #km-box { animation: kmSettle .45s ease-out; }
@keyframes kmSettle {
  from { transform: scale(1.15); opacity: 0; }
  to   { transform: scale(1);    opacity: 1; }
}
@media (prefers-reduced-motion: reduce) {
  #km-frame.km-in #km-box { animation: none; }
}

/* the hotspot hover tip */
#hotspot-tip {
  position: absolute; transform: translate(-50%, -100%);
  padding: 4px 12px; border-radius: 999px; white-space: nowrap;
  background: rgba(35, 32, 26, .9); border: 1px solid rgba(246, 242, 233, .55);
  color: #f6f2e9; font-size: 12px; pointer-events: none;
}
#hotspot-tip[hidden] { display: none; }

/* the fly-in button only exists on screen when it is the needed control */
#zoom-btn:not(.shown):not(:focus-visible) {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}
/*
 * THE WAY BACK OUT SITS UNDER THE SUBJECT, not in the corner.
 *
 * In the close-up the camera has taken the eye to one small thing in the middle of
 * the frame, and a button that appears in the control dock at the bottom left
 * appears outside of where anyone is looking — it was going unnoticed (Ben). The
 * close-up centres its subject, so the button goes directly beneath it: centred,
 * about three quarters of the way down, clear of the subject and on the path the
 * eye already travels. It leaves the dock only while it is the way out.
 */
/*
 * And it ARRIVES rather than being there. Same entrance as the tree labels, for the
 * same reason: after a second and a half of camera travel the eye is still moving,
 * and a control that is simply present is a control nobody sees.
 */
#zoom-btn.is-new { animation: zoombtn-pop .42s cubic-bezier(.2, 1.5, .4, 1) both; }
@keyframes zoombtn-pop {
  0%   { opacity: 0; transform: translateX(-50%) translateY(10px) scale(.55); }
  60%  { opacity: 1; transform: translateX(-50%) translateY(-3px) scale(1.12); }
  100% { opacity: 1; transform: translateX(-50%) translateY(0) scale(1); }
}
@media (prefers-reduced-motion: reduce) {
  #zoom-btn.is-new { animation: none; }
}

#zoom-btn.shown {
  position: absolute; left: 50%; top: 74%; transform: translateX(-50%);
  z-index: 7; white-space: nowrap;
  box-shadow: 0 2px 10px rgba(0, 0, 0, .38);
}

/* ── hint ───────────────────────────────────────────────────────────────── */
.interaction-hint {
  position: absolute; left: 50%; transform: translateX(-50%); bottom: 62px;
  /* Prominent on purpose: at 12px and 74% it was missed ("mid-run hint too faint", Rajat). */
  background: rgba(20, 18, 14, .96); color: #fff; border-radius: 12px;
  border: 2px solid var(--gold, #f2c14e);
  padding: 9px 18px; font-size: 16px; font-weight: 600; line-height: 1.3;
  max-width: calc(100% - 28px); text-align: center; pointer-events: none;
  box-shadow: 0 4px 16px rgba(0, 0, 0, .5);
  transition: opacity .35s ease;
}
.interaction-hint[hidden] { display: none; }

/* ── info dialog ────────────────────────────────────────────────────────── */
/* ── the info dialog ───────────────────────────────────────────────────────
   HOUSE CHROME, NOT THIS SIM'S CHROME.
   This sim had dressed the dialog in its own dark palette, so the About window
   looked like a different product from every other sim's (Ben). The dialog is
   shared furniture: white body, dark ink, a tinted tab strip, and a solid header
   the shared toolbar colours. Values taken from orbital_motion, which is the
   reference, so the three tabs read the same here as anywhere else.
   The body scrolls rather than growing: this sim's About panel is six paragraphs
   and it used to run off the bottom of the screen. */
:root {
  --dialog-bg: #ffffff;
  --dialog-text: #2c3e3e;
  --dialog-border: #e5eae9;
  --dialog-tabs-bg: #f8fbfb;
  --tab-text: #5a6e6e;
}
#info-dialog {
  border: none; border-radius: 12px; padding: 0; margin: auto;
  width: min(560px, calc(100vw - 32px));
  max-height: calc(100vh - 80px); overflow: hidden;
  background: var(--dialog-bg); color: var(--dialog-text);
  box-shadow: 0 8px 32px rgba(0, 0, 0, .22);
}
#info-dialog::backdrop { background: rgba(0, 0, 0, .45); }
.dialog-header {
  display: flex; justify-content: space-between; align-items: center;
  padding: 14px 16px;
  background: var(--tuva); color: #fff;
}
.dialog-header h2 { margin: 0; font-size: 16px; font-weight: 600; }
#dialog-close-btn {
  background: transparent; border: none; color: #fff;
  font-size: 18px; line-height: 1; cursor: pointer;
  width: 32px; height: 32px; border-radius: 6px;
  display: flex; align-items: center; justify-content: center;
}
#dialog-close-btn:hover { background: rgba(255, 255, 255, .15); }
#dialog-close-btn:focus-visible { outline: 3px solid var(--gold); outline-offset: 2px; }
.dialog-tabs {
  display: flex; border-bottom: 2px solid var(--dialog-border);
  background: var(--dialog-tabs-bg);
}
.tab-btn {
  flex: 1; background: transparent; border: none;
  border-bottom: 3px solid transparent; margin-bottom: -2px;
  padding: 10px 0; font-family: inherit; font-size: 14px; font-weight: 600;
  color: var(--tab-text); cursor: pointer;
  transition: color .15s, border-color .15s;
}
.tab-btn:hover { color: var(--tuva); }
.tab-btn.active,
.tab-btn[aria-selected="true"] { color: var(--tuva); border-bottom-color: var(--tuva); }
.tab-btn:focus-visible { outline: 3px solid var(--gold); outline-offset: -3px; }
.dialog-body {
  padding: 16px 20px; overflow-y: auto; max-height: calc(100vh - 240px);
  color: var(--dialog-text); font-size: 14px; line-height: 1.55;
}
.dialog-body p { margin: 0 0 12px; }
/* The canonical section headings and the collapsible Model details (INFO-1). */
.dialog-body h3 {
  font-size: 14px; font-weight: 600; color: var(--tuva);
  margin: 14px 0 6px;
}
.dialog-body h3:first-child { margin-top: 0; }
.dialog-body details {
  margin: 8px 0 10px; border: 1px solid var(--dialog-border);
  border-radius: 6px; padding: 6px 12px;
}
.dialog-body summary { cursor: pointer; font-weight: 600; color: var(--tuva); }
.dialog-body details[open] summary { margin-bottom: 6px; }
.dialog-body summary:focus-visible { outline: 3px solid var(--gold); outline-offset: 2px; }
/* The standard's code as a badge ahead of its text, the way every other sim's
   teacher tab carries it. */
.standard-tag {
  display: inline-block; padding: 1px 7px; margin-right: 5px;
  border-radius: 5px; background: #e7f1f0; color: var(--tuva);
  font-weight: 600; font-size: 13px;
}

@media (max-width: 720px) {
  #readout { width: 170px; }
  #controls { width: 214px; }
  .tree-card { padding: 8px 10px 9px; }
  .stat { font-size: 12px; }
  /* A phone has no width to spare: the card takes the stage less a small margin.
     WIDTH, not max-width — an absolutely positioned box shrink-wraps its content,
     so a max-width alone capped it at 195px of the 370 available. */
  #moment-bar { width: calc(100% - 20px); max-width: none; top: 8px; }
  /* A badge is a pill, and a pill is one line — until one line will not fit the
     screen. Then the label wraps rather than being cut off mid-word. */
  .km-chip { white-space: normal; }
}
@media (prefers-reduced-motion: reduce) {
  .interaction-hint, .tree-tag, #vignette { transition: none; }
  .tree-tag.is-new { animation: none; }
}

/* ── "N rows sent" confirmation: seen, not only announced ───────────────── */
.sent-toast {
  position: fixed; left: 50%; bottom: 112px; transform: translateX(-50%);
  z-index: 60; max-width: calc(100vw - 24px);
  padding: 10px 16px; border-radius: 8px;
  background: var(--card); color: var(--ink); border: 1px solid var(--line);
  font-size: 14px; font-weight: 600; text-align: center;
  box-shadow: 0 4px 18px rgba(0, 0, 0, .45);
  pointer-events: none;
}

/* the word that says what a fly-in marker is */
.marker-label {
  position: absolute; transform: translateX(-50%);
  padding: 2px 10px; border-radius: 999px; white-space: nowrap;
  background: rgba(35, 32, 26, .92); border: 1px solid rgba(246, 242, 233, .55);
  color: #f6f2e9; font-size: 12px; font-weight: 600; pointer-events: none;
}
.marker-label[hidden] { display: none; }
.km-quiet .marker-label { opacity: 0; visibility: hidden; }
/* on a phone the dial card fills the bottom: lift the hint clear of it */
@media (max-width: 600px) {
  .interaction-hint { bottom: 168px; font-size: 15px; }
}

/* run speed: 1x / 2x / 4x, at the end of the timeline */
.speed-btn {
  flex: none; min-width: 44px; min-height: 32px; padding: 2px 10px;
  border-radius: 6px; border: 1px solid var(--line); background: transparent;
  color: var(--ink); font: inherit; font-size: 14px; font-weight: 700; cursor: pointer;
}
.speed-btn:hover { background: rgba(236, 229, 214, .12); }
.speed-btn[aria-disabled="true"] { opacity: .5; cursor: default; }
/* Shown only when WebGL cannot start: the scene is empty, and this says why. */
#no-gl { position: absolute; z-index: 5; top: 50%; left: 50%; transform: translate(-50%, -50%); max-width: min(380px, calc(100% - 24px)); padding: 12px 16px; line-height: 1.45; background: var(--card); color: var(--ink); border: 1px solid var(--warn); border-radius: 10px; text-align: center; pointer-events: none; }
