/* freebot.dev — hand-written styles. No build step. */

:root {
  --paper: #eef1ea;
  --card: #f5f7f1;
  --ink: #26332b;
  --faded: #5d6b60;
  --moss: #4e7d5b;
  --moss-deep: #3a5f46;
  --line: #c9d2c2;
  --grid: rgba(78, 125, 91, 0.08);
  --tape: rgba(166, 138, 90, 0.28);

  --stem: #4e7d5b;
  --stem-deep: #3a5f46;
  --leaf-a: #6a9a74;
  --leaf-b: #8fae76;
  --leaf-fall-a: #ad6b35;
  --leaf-fall-b: #c2913d;
  --petal: #c9a13d;
  --floret: #a66a3a;
  --blush: #9c433c;

  --ground-moss-a: #5a8a5f;
  --ground-moss-b: #7a9a52;
  --ground-lichen-a: #9aa878;
  --ground-lichen-b: #b6ab7a;

  --bird-body-a: #7a5a3a;
  --bird-body-b: #5c6b6e;
  --bird-wing-a: #543f28;
  --bird-wing-b: #404d4f;
  --bird-breast-a: #c2793f;
  --bird-breast-b: #c9b988;
  --bird-beak: #d68a3a;
  --bird-leg: #5a4630;
  --bird-eye: #22201c;

  --pot-a: #b96f45;
  --pot-b: #a05c37;
  --pot-rim: #7a4227;

  --rain: #7d93b8;
  --snow: #eef3f5;
  --dew: #eaf4fb;

  --season-winter: #8fa3ab;
  --season-spring: #7dab6f;
  --season-summer: #4e7d5b;
  --season-autumn: #c2913d;

  --compost-fresh: #c9a13d;
  --compost-soil: #2c2013;

  --trap-lure: #b8503f;

  --soil: #2f2317;
  --soil-deep: #241a10;
  --root-pale: #d9c9a6;
  --root-line: #b7a67f;
  --moist-fill: rgba(86, 148, 196, 0.30);
  --moist-line: rgba(86, 148, 196, 0.75);
}

@media (prefers-color-scheme: dark) {
  :root {
    --paper: #141a15;
    --card: #1a211b;
    --ink: #d9e2d6;
    --faded: #93a296;
    --moss: #7cb98c;
    --moss-deep: #5e9b70;
    --line: #2b352d;
    --grid: rgba(124, 185, 140, 0.06);
    --tape: rgba(180, 156, 110, 0.22);

    --stem: #7cb98c;
    --stem-deep: #5e9b70;
    --leaf-a: #8fc79b;
    --leaf-b: #a9c489;
    --leaf-fall-a: #cf9455;
    --leaf-fall-b: #d9b25e;
    --petal: #d8b558;
    --floret: #c98b57;
    --blush: #cf6f61;

    --ground-moss-a: #7fae7f;
    --ground-moss-b: #9cb56a;
    --ground-lichen-a: #b3c093;
    --ground-lichen-b: #c9be8f;

    --bird-body-a: #a9835c;
    --bird-body-b: #8b9a9d;
    --bird-wing-a: #7a5c3c;
    --bird-wing-b: #6c7a7d;
    --bird-breast-a: #d99a5c;
    --bird-breast-b: #ddd0a3;
    --bird-beak: #e7a655;
    --bird-leg: #8a7455;
    --bird-eye: #17140f;

    --pot-a: #c17f52;
    --pot-b: #a86840;
    --pot-rim: #8a5230;

    --rain: #9db4d9;
    --snow: #dfe8ec;
    --dew: #cfe6f7;

    --season-winter: #7f97a0;
    --season-spring: #8fc07f;
    --season-summer: #7cb98c;
    --season-autumn: #d9b25e;

    --compost-fresh: #d9b25e;
    --compost-soil: #17110a;

    --trap-lure: #d97a68;

    --soil: #241b12;
    --soil-deep: #1a130c;
    --root-pale: #cebf9d;
    --root-line: #a99a75;
    --moist-fill: rgba(110, 168, 214, 0.28);
    --moist-line: rgba(130, 184, 224, 0.7);
  }
}

* { box-sizing: border-box; }

html {
  background: var(--paper);
  color: var(--ink);
}

body {
  margin: 0;
  font-family: "Alegreya", Georgia, serif;
  font-size: 1.15rem;
  line-height: 1.65;
  background-image:
    linear-gradient(var(--grid) 1px, transparent 1px),
    linear-gradient(90deg, var(--grid) 1px, transparent 1px);
  background-size: 28px 28px;
  min-height: 100vh;
}

.page {
  max-width: 41rem;
  margin: 0 auto;
  padding: 2.5rem 1.25rem 4rem;
}

/* --- type --- */

h1, h2, h3 {
  font-family: "Young Serif", Georgia, serif;
  font-weight: 400;
  line-height: 1.2;
  margin: 0 0 0.75rem;
}

h1 { font-size: 2.6rem; letter-spacing: -0.01em; }
h2 { font-size: 1.5rem; margin-top: 2.75rem; }

p { margin: 0 0 1.1rem; }

a {
  color: var(--moss);
  text-decoration-color: color-mix(in srgb, var(--moss) 45%, transparent);
  text-underline-offset: 3px;
}

a:hover { color: var(--moss-deep); }

a:focus-visible,
button:focus-visible,
input:focus-visible {
  outline: 2px solid var(--moss);
  outline-offset: 3px;
  border-radius: 2px;
}

.mono, .label, nav.site, .site-footer, figcaption {
  font-family: "IBM Plex Mono", ui-monospace, monospace;
}

.label {
  font-size: 0.72rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--faded);
}

/* --- header / footer --- */

header.site {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 1rem;
  flex-wrap: wrap;
  margin-bottom: 3rem;
}

.wordmark {
  position: relative;
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 1rem;
  font-weight: 500;
  color: var(--ink);
  text-decoration: none;
  letter-spacing: 0.02em;
}

.wordmark .sprout { color: var(--moss); }

/* Doodle — a small hidden thing behind the wordmark's sprout on the
   home page only; see doodle.js. Not sourced, not gated to a date. */
.doodle-note {
  position: absolute;
  top: 100%;
  left: 0;
  margin-top: 0.4em;
  max-width: min(60vw, 260px);
  padding: 0.25em 0.6em;
  border-radius: 0.4em;
  background: var(--paper);
  border: 1px solid var(--moss);
  color: var(--ink);
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 0.78rem;
  font-weight: 400;
  line-height: 1.3;
  white-space: normal;
  pointer-events: none;
  z-index: 5;
  animation: doodle-note-fade 4.2s ease forwards;
}
@keyframes doodle-note-fade {
  0% { opacity: 0; transform: translateY(-4px); }
  10%, 78% { opacity: 1; transform: translateY(0); }
  100% { opacity: 0; transform: translateY(0); }
}
@media (prefers-reduced-motion: reduce) {
  .doodle-note { animation: none; opacity: 1; transform: none; }
}

.doodle-leaf {
  position: fixed;
  top: -24px;
  left: 0;
  width: 18px;
  height: 18px;
  pointer-events: none;
  z-index: 999;
  animation-name: doodle-fall;
  animation-timing-function: linear;
  animation-fill-mode: forwards;
}
.doodle-leaf path { fill: var(--doodle-leaf-fill, var(--leaf-a)); }
@keyframes doodle-fall {
  0% { transform: translate(0, 0) rotate(0deg); opacity: 1; }
  100% {
    transform: translate(var(--doodle-drift, 0), 100vh) rotate(var(--doodle-spin, 180deg));
    opacity: 0.85;
  }
}

nav.site { font-size: 0.85rem; }

nav.site a {
  color: var(--faded);
  text-decoration: none;
  margin-left: 1.1rem;
}

nav.site a:hover,
nav.site a[aria-current="page"] { color: var(--moss); }

/* Twenty flat links used to run the header off the edge of a phone
   before the page even started. Five stay put; the rest live under
   one native disclosure — no JS, just <details>. */
nav.site details.nav-more {
  position: relative;
  display: inline-block;
  margin-left: 1.1rem;
}

nav.site details.nav-more > summary {
  display: inline;
  cursor: pointer;
  list-style: none;
  color: var(--faded);
}

nav.site details.nav-more > summary::-webkit-details-marker { display: none; }
nav.site details.nav-more > summary::after { content: " \25BE"; font-size: 0.7em; }
nav.site details.nav-more[open] > summary::after { content: " \25B4"; }
nav.site details.nav-more > summary:hover { color: var(--moss); }

/* The current page's own link can sit unopened inside the panel;
   this marks the summary so "where am I" still reads closed. */
nav.site details.nav-more:has(a[aria-current]) > summary {
  color: var(--moss);
  text-decoration: underline;
  text-underline-offset: 0.2em;
}

/* Forty room links now live in here (see /map's own count). A single
   column of forty rows was the actual dullness — not the color, the
   shape: one long scroll, no way to see a group at a glance. Two
   fixed columns (not auto-fill: shrink-to-fit sizing on an absolutely
   positioned box makes auto-fill collapse to one column anyway) with
   each group label and the wander link spanning both turns that
   scroll into short, scannable blocks — same links, same groups,
   about half the height. Below 480px it folds back to one column,
   the exact shape this panel always had on a phone. */
.nav-more-panel {
  position: absolute;
  top: 100%;
  right: 0;
  margin-top: 0.6rem;
  padding: 0.85rem 1.1rem;
  display: grid;
  grid-template-columns: repeat(2, minmax(6.75rem, 9rem));
  column-gap: 1.4rem;
  row-gap: 0.3rem;
  align-items: baseline;
  background: var(--card);
  border: 1px solid var(--line);
  z-index: 30;
}

.nav-more-panel a { margin-left: 0; }

/* One small hand-drawn mark per room link, pulled from /icons.svg (the
   same sprite /map draws its beds with) rather than a second copy of
   the same 47 symbols. currentColor means no separate hover/current
   rule is needed — the icon just follows whatever color the link text
   already has. The wander link and group labels have no icon and stay
   untouched: this is a way to scan the room list at a glance, not a
   requirement on every line in the panel. */
.nav-more-panel a:not(.nav-wander) {
  display: inline-flex;
  align-items: center;
  white-space: nowrap;
}

.nav-more-panel a .nm-icon {
  width: 0.85em;
  height: 0.85em;
  stroke: currentColor;
  fill: none;
  stroke-width: 1.8;
  stroke-linecap: round;
  stroke-linejoin: round;
  margin-right: 0.4em;
  flex-shrink: 0;
}

/* The one link in the panel that doesn't name a destination — it
   picks one at random on click. Set apart from the room list below
   it by a rule and a different color, not a group label of its own. */
.nav-more-panel a.nav-wander {
  grid-column: 1 / -1;
  font-style: italic;
  color: var(--moss);
  padding-bottom: 0.5rem;
  margin-bottom: 0.15rem;
  border-bottom: 1px dashed var(--line);
}

.nav-group-label {
  grid-column: 1 / -1;
  font-size: 0.62rem;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--faded);
  opacity: 0.75;
  margin-top: 0.5rem;
}
.nav-group-label:first-child { margin-top: 0; }

@media (max-width: 480px) {
  .nav-more-panel { grid-template-columns: 1fr; }
}

.site-footer {
  margin-top: 4.5rem;
  padding-top: 1.25rem;
  border-top: 1px solid var(--line);
  font-size: 0.78rem;
  color: var(--faded);
  display: flex;
  justify-content: space-between;
  gap: 1rem;
  flex-wrap: wrap;
}

.site-footer a { color: var(--faded); }
.site-footer a:hover { color: var(--moss); }

.fb-echo {
  font-style: italic;
  flex-basis: 100%;
  text-align: right;
}
@media (max-width: 480px) {
  .fb-echo { text-align: left; }
}

/* --- hero --- */

.tagline {
  font-style: italic;
  color: var(--faded);
  font-size: 1.25rem;
  margin-top: -0.35rem;
}

/* --- specimen sheet --- */

.specimen {
  position: relative;
  margin: 2.25rem 0;
  padding: 1.5rem 1.5rem 1.1rem;
  background: var(--card);
  border: 1px solid var(--line);
  box-shadow: 0 1px 0 var(--line);
}

.specimen::before,
.specimen::after {
  content: "";
  position: absolute;
  width: 74px;
  height: 22px;
  background: var(--tape);
  top: -9px;
}

.specimen::before { left: 14%; transform: rotate(-3deg); }
.specimen::after { right: 14%; transform: rotate(2.5deg); }

.specimen svg {
  display: block;
  width: 100%;
  height: auto;
  max-height: 420px;
}

.specimen figcaption {
  border-top: 1px dashed var(--line);
  margin-top: 0.9rem;
  padding-top: 0.7rem;
  font-size: 0.75rem;
  color: var(--faded);
  display: flex;
  justify-content: space-between;
  gap: 0.75rem;
  flex-wrap: wrap;
}

.lore-line {
  margin: 0.6rem 0 0;
  font-size: 0.75rem;
  font-style: italic;
  color: var(--faded);
  line-height: 1.5;
}

.specimen figcaption .binomial {
  font-style: normal;
  color: var(--ink);
}

@media (prefers-reduced-motion: no-preference) {
  .specimen .sway {
    /* Fixed ±0.7°/9s by default — decoration, nothing here reads it
       as a fact about anything. wind.js can raise --wind-amp and
       lower --wind-period on #specimen-today after a real fetch; the
       defaults below reproduce the old fixed values exactly when it
       hasn't, so every page that never loads wind.js (every page but
       home) sways exactly as it always did. See "The wind is the
       first real-time thing here". */
    animation: sway var(--wind-period, 9s) ease-in-out infinite alternate;
    transform-origin: 50% 100%;
  }
  @keyframes sway {
    from { transform: rotate(calc(-1 * var(--wind-amp, 0.7deg))); }
    to { transform: rotate(var(--wind-amp, 0.7deg)); }
  }

  /* Gust-driven leaf flutter, layered on top of the whole-plant sway
     above rather than replacing it — a real gust doesn't move a stem
     and its leaves by the same amount. Inert (0deg) everywhere by
     default: wind.js is the only thing that ever sets --leaf-flutter,
     and only on #specimen-today, and only once a real reading has
     landed, so no other specimen on the site changes shape. Each
     leaf's own transform-origin and --flutter-delay are inline styles
     set once at grow time (see leafFlutterAttrs in plant.js) so
     leaves flutter out of phase with each other instead of in
     lockstep. transform-box is spelled out for older Safari; SVG's
     own default already matches it. */
  .specimen .sway .leaf {
    transform-box: view-box;
    animation: leaf-flutter var(--leaf-flutter-period, 3.4s) ease-in-out infinite alternate;
    animation-delay: var(--flutter-delay, 0s);
  }
  @keyframes leaf-flutter {
    from { transform: rotate(calc(-1 * var(--leaf-flutter, 0deg))); }
    to { transform: rotate(var(--leaf-flutter, 0deg)); }
  }
}

/* --- weather (era 3, from 2026-08-11) ---
   A layer on top of the plant, never a change to it: rain is drawn
   markup already in the SVG (see plant.js's <g class="rain">), windy
   and fog are pure CSS driven by a class + one custom property. None
   of this reads or writes an rng() stream — plant.js decides the
   weather once, before the SVG ever reaches the page. */

@media (prefers-reduced-motion: no-preference) {
  .specimen .rain line { animation: rainfall 0.9s linear infinite; }
  @keyframes rainfall {
    from { transform: translateY(-6px); opacity: 0; }
    15% { opacity: 0.5; }
    to { transform: translateY(10px); opacity: 0; }
  }

  .specimen.weather-windy .sway {
    animation-name: sway-windy;
    animation-duration: calc(9s / (1 + var(--wind, 0.5)));
  }
  @keyframes sway-windy {
    from { transform: rotate(calc(-2.2deg * (1 + var(--wind, 0.5)))); }
    to { transform: rotate(calc(2.2deg * (1 + var(--wind, 0.5)))); }
  }
}

.specimen.weather-fog-1 svg { filter: saturate(0.85) brightness(1.04) blur(0.3px); }
.specimen.weather-fog-2 svg { filter: saturate(0.7) brightness(1.07) blur(0.6px); }
.specimen.weather-fog-3 svg { filter: saturate(0.55) brightness(1.1) blur(1px); }

/* Snow: fog's rarer winter sibling (see plant.js). Reuses fog's own
   blur/desaturate filter above and adds a soft edge to the resting
   tufts drawn in the SVG's <g class="snow">, so they read as sitting
   on the plant rather than pasted over it. */
.specimen.weather-snow .snow ellipse { filter: blur(0.4px); }

/* --- nyctinasty (era 4, from 2026-08-13) ---
   Some flowering days grow a bloom that folds shut at real night, the
   way a tulip or a dandelion actually does. plant.js decides the fact
   once, per date, and — only on those dates — wraps that bloom's
   markup in <g class="bloom" data-nyc="1">; this is the only place the
   fact is acted on. Keyed to body.sky-night, the viewer's real clock,
   same as the night sky itself: the flower does not know what day it
   is, only what hour. Scaling toward the bloom's own center (fill-box)
   rather than the whole SVG's reads as the petals drawing in, not the
   plant shrinking. :not([data-helio]) keeps this rule clear of a bloom
   that is also heliotropic (below) — the two effects share one
   element and must not fight over the same transform.

   Since 2026-08-15 how far it closes also answers to --moon-lit, the
   real moon's live illumination fraction (0 new, 1 full; see moon.js
   and garden-page.js/home.js) — not a new fact about the date, the
   same live-clock-only discipline body.sky-night already keeps, just
   a second real clock instead of one. The reason isn't decorative:
   Bünning (1969) proposed nyctinasty exists specifically to shield a
   plant's own photoperiodic clock from moonlight, and showed a
   moonlight-strength light pulse in the dark measurably shifts a bean
   plant's leaf-movement rhythm; a 2020 study found real full moonlight
   (~6 lux) still detectably shifts core clock-gene transcription in
   coffee, decades later. So the fold draws tighter under a brighter
   moon and looser under a darker one — closing hardest exactly when
   there is the most moonlight for it to be closing against. See the
   field note for the citations and for why this still isn't a live
   forecast touching a date's own facts: --moon-lit is computed fresh
   from Date.now() every read, the same as the hour is, never stored,
   never fed back into plant.js. Defaults to 0.5 (unset, e.g. on a page
   that doesn't mount the moon) — which resolves the calc() below to
   exactly the original fixed 0.3, so nothing about this changes any
   page that hasn't been wired to the real moon. */
.specimen.nyctinastic .bloom[data-nyc]:not([data-helio]) {
  transform-box: fill-box;
  transform-origin: center;
  transition: transform 2.5s ease-in-out;
}
body.sky-night .specimen.nyctinastic .bloom[data-nyc]:not([data-helio]) {
  transform: scale(calc(0.42 - var(--moon-lit, 0.5) * 0.24));
}
@media (prefers-reduced-motion: reduce) {
  .specimen.nyctinastic .bloom[data-nyc]:not([data-helio]) { transition: none; }
}

/* --- heliotropism (era 5, from 2026-08-14) ---
   Some flowering days grow a bloom that leans to follow the sun, the
   way a young sunflower does — see the field note for the real
   mechanism and where this stylizes it. plant.js decides only *whether*
   a date's bloom does this, once, wrapping it in the same
   <g class="bloom"> with a data-helio="1" marker; *which way it leans
   right now* comes only from --sun-lean, a custom property sun.js
   keeps current from the viewer's own clock, never from plant.js.
   Leaning around a point below the bloom (50% 160% of its own
   fill-box) reads as the flower's neck bending, not the whole plant
   tilting. A 45s linear transition means the lean is never seen to
   jump — it just happens to have moved, the same unhurried way the
   moon's phase does. */
.specimen.heliotropic .bloom[data-helio] {
  transform-box: fill-box;
  transform-origin: 50% 160%;
  transition: transform 45s linear;
  transform: rotate(var(--sun-lean, 0deg));
}

/* A bloom that is both nyctinastic and heliotropic — a real
   combination in a few actual species — still folds shut for the
   night exactly like a plain nyctinastic one; a closed flower isn't
   facing anywhere in particular, so the fold wins over the lean
   rather than the two fighting for one transform. By day only the
   rule above applies, so it still leans. */
body.sky-night .specimen.nyctinastic.heliotropic .bloom[data-nyc][data-helio] {
  transition: transform 2.5s ease-in-out;
  transform: scale(calc(0.42 - var(--moon-lit, 0.5) * 0.24));
}

@media (prefers-reduced-motion: reduce) {
  .specimen.heliotropic .bloom[data-helio] { transition: none; }
}

/* --- circumnutation (era 7, from 2026-08-16) ---
   Every growing tip traces a slow, small ellipse as it elongates —
   see the field note for Darwin's own account of this and the 2009
   spaceflight result that split the credit between his internal
   clock and Gradmann's gravity overshoot. plant.js marks each
   terminal branch with class="tip" and a --ti index (era 7+ dates
   only); this is the only place the wobble is applied. transform-box:
   fill-box keeps the pivot on the short tip segment itself rather
   than the whole plant, so it reads as the tip trembling, not the
   plant shaking — the same choice the wind's own .sway rule made one
   level up. The negative delay staggers tips by their own index so a
   many-tipped specimen doesn't wobble in lockstep, which no real
   plant's branches do either. */
@media (prefers-reduced-motion: no-preference) {
  .specimen .tip {
    transform-box: fill-box;
    transform-origin: center;
    animation: circumnutate 17s ease-in-out infinite;
    animation-delay: calc(var(--ti, 0) * -2.6s);
  }
  @keyframes circumnutate {
    0%   { transform: rotate(0deg)    translate(0, 0); }
    25%  { transform: rotate(0.9deg)  translate(0.4px, -0.2px); }
    50%  { transform: rotate(0.1deg)  translate(0.1px, -0.5px); }
    75%  { transform: rotate(-0.8deg) translate(-0.4px, -0.1px); }
    100% { transform: rotate(0deg)    translate(0, 0); }
  }
}

/* --- guttation (era 8, from 2026-08-17) ---
   Guttation isn't dew: it's root pressure pushing xylem sap out
   through a leaf's own hydathodes overnight, once night closes the
   stomata that would otherwise vent it as vapor — see the field note
   and Singh (2016). plant.js decides *whether* a leaved, calm day
   beads at all, once, drawing one small <circle class="drop"> at
   each leaf's own already-computed tip inside <g class="guttation">;
   this rule decides only *when it shows*: body.sky-night, the same
   live clock nyctinasty already answers to. That's a compression, not
   a fresh liberty — a real bead peaks near dawn and is usually gone
   within an hour or two of sunrise, not the whole night through — and
   the field note says so plainly rather than leaving it implied. */
.specimen.guttating .guttation .drop {
  fill: var(--dew);
  opacity: 0;
  filter: drop-shadow(0 0.4px 0 rgba(255, 255, 255, 0.5));
  transition: opacity 3s ease-in-out;
}
body.sky-night .specimen.guttating .guttation .drop {
  opacity: 0.9;
}
@media (prefers-reduced-motion: reduce) {
  .specimen.guttating .guttation .drop { transition: none; }
}

/* --- anthocyanin blush (era 9, from 2026-08-21) ---
   Not autumn's amber and not a step toward dropping the leaf: a real,
   separate pigment some evergreen leaves make through a cold, bright
   winter as sunscreen, protecting slowed-down photosynthesis from
   light it can't yet use — see Hughes, Neufeld & Burkey (2005) on
   Galax urceolata and the field note. plant.js decides *whether* a
   winter, clear, leaved day blushes at all, once, drawing a small
   <circle class="blush-mark"> at each leaf's own already-computed
   tip. Unlike guttation just above, there is no clock to answer to
   here — the real pigment holds for as long as the cold spell does,
   not just overnight — so this mark is always on where plant.js put
   it, no class toggle, no transition. */
.blush-mark {
  fill: var(--blush);
  opacity: 0.75;
}

/* --- glaucous bloom (era 10, from 2026-08-28) ---
   Not a pigment and not dew: a layer of epicuticular wax crystals on
   the leaf surface, tens of nanometres tall, that scatters light
   before it reaches the chlorophyll underneath — the same structure
   behind blue spruce, garden cabbage, and the powdery bloom on a
   fresh plum (Barthlott & Neinhuis, Planta, 1997, and the field
   note). plant.js decides once, per specimen, whether it grows this
   way at all — a fixed cuticle trait like leafShape, not a nightly
   or seasonal event — and wraps its own already-drawn leaves in a
   bare <g class="glaucous">. Everything visible lives here: no new
   SVG element, just stroke and fill-opacity laid over the exact
   <path class="leaf"> shapes grow() already emitted, so a glaucous
   specimen costs nothing this file didn't already have to draw. */
.glaucous .leaf {
  stroke: var(--dew);
  stroke-width: 0.6;
  stroke-linejoin: round;
  fill-opacity: 0.88;
}

/* --- extrafloral nectaries (era 11, from 2026-09-04) ---
   Not the flower mutualism /buzz already draws: many real plants leak
   sugar water from glands on a leaf or its stalk, nowhere near a
   bloom, and keep an ant patrolling nearby in trade — Bentley (1977)
   named it, Rosumek et al.'s 2009 meta-analysis (Oecologia) measured
   it. plant.js decides *whether* a leaved day grows one, once, and
   draws a single small <g class="ant"> at one leaf's own
   already-computed base point. No clock, no transition, same as
   blush and glaucous above — it's just part of the SVG, on or off. */
.ant-body {
  fill: var(--ink);
}
.ant-leg {
  fill: none;
  stroke: var(--ink);
  stroke-width: 0.5;
  stroke-linecap: round;
}

/* --- witch's broom (era 12, from 2026-09-07) ---
   A rare, real bud mutation: a dense knot of tiny shoots at one point
   instead of an ordinary branch — the same malformation most named
   dwarf conifer cultivars in cultivation were first found growing as.
   See Wikipedia's own survey of the causes, the University of
   Arkansas Cooperative Extension's writeup, and the field note.
   plant.js decides whether a specimen grows one at all, once, and —
   if so — replaces the first ordinary split its own recursion would
   have reached anyway with a fan of short twigs, each grown by the
   exact same terminal-branch code as any other tip, wrapped in one
   bare <g class="broom">. Nothing new to draw here, only to tint: a
   real broom's shoots are crowded and stunted, so its stems read a
   shade darker than the branch they grew from and its leaves sit a
   little duller, the same overridable presentation-attribute trick
   guttation's own <circle> already relies on. */
.broom path {
  stroke: var(--stem-deep);
}
.broom .leaf {
  opacity: 0.75;
}

/* The bird and the ground organism read the same decided weather —
   see organism.js and bird.js. Fog's dimming is a group opacity set
   in organism.js's markup itself, matched to the filter above; only
   the wing's windy ruffle needs an animation, here. */
@media (prefers-reduced-motion: no-preference) {
  .bird.windy .wing {
    transform-box: fill-box;
    transform-origin: center;
    animation: wing-ruffle calc(1.4s / (1 + var(--wind, 0.5))) ease-in-out infinite alternate;
  }
  @keyframes wing-ruffle {
    from { transform: rotate(-4deg); }
    to { transform: rotate(5deg); }
  }
}

/* A bird is clickable, some days — see bird.js. Not advertised
   anywhere on the page; the point is finding it. */
.bird[role="button"] { cursor: pointer; }
.bird[role="button"]:focus-visible { outline: 2px solid var(--moss); outline-offset: 3px; }

.bawk {
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 13px;
  fill: var(--ink);
  pointer-events: none;
}
@media (prefers-reduced-motion: no-preference) {
  .bawk {
    transform-box: fill-box;
    transform-origin: center;
    animation: bawk-float 0.85s ease-out forwards;
  }
  @keyframes bawk-float {
    from { opacity: 0; transform: translateY(0); }
    15% { opacity: 1; }
    to { opacity: 0; transform: translateY(-18px); }
  }
}
@media (prefers-reduced-motion: reduce) {
  .bawk { animation: bawk-fade 0.85s ease-out forwards; }
  @keyframes bawk-fade {
    0%, 55% { opacity: 1; }
    100% { opacity: 0; }
  }
}

/* The plant itself is clickable, every day — see click.js. A small
   line beside each page's press button says so; this isn't a hidden
   easter egg the way the bird is. */
.specimen svg .sway[role="button"] { cursor: pointer; }
.specimen svg .sway[role="button"]:focus-visible { outline: 2px solid var(--moss); outline-offset: 6px; }

.click-ring {
  fill: none;
  stroke: var(--ink);
  stroke-width: 1.5;
  opacity: 0.5;
  pointer-events: none;
  transform-box: fill-box;
  transform-origin: center;
}
@media (prefers-reduced-motion: no-preference) {
  .click-ring { animation: click-ring 0.6s ease-out forwards; }
  @keyframes click-ring {
    from { transform: scale(1); opacity: 0.55; }
    to { transform: scale(9); opacity: 0; }
  }
}
@media (prefers-reduced-motion: reduce) {
  .click-ring { animation: click-ring-fade 0.6s ease-out forwards; }
  @keyframes click-ring-fade {
    0%, 50% { opacity: 0.5; }
    100% { opacity: 0; }
  }
}

/* --- greenhouse pane ---
   The greenhouse's own next step, open since 2026-08-10: give a
   greenhouse specimen a visual identity beyond its pot, so a
   screenshot alone tells the two rooms apart. A faint pane of glass
   laid *in front of* the same paper-and-tape .specimen frame every
   other room already uses — real glass sits between a viewer and what
   it's growing, not behind it — rather than a different frame of its
   own. Because it's a layer on top, not a change to .specimen's own
   background, it survives night mode's starfield swap (see "night
   sky" above) with nothing here needing to know that swap exists or
   duplicate it. Decoration only: aria-hidden, pointer-events: none, no
   rng(), nothing this affects grow() or graft() themselves. */
.gh-glass {
  position: absolute;
  inset: 0;
  pointer-events: none;
  background:
    linear-gradient(155deg, rgba(255, 255, 255, 0.16) 0%, rgba(255, 255, 255, 0) 30%),
    linear-gradient(90deg, transparent calc(50% - 1px), color-mix(in srgb, var(--moss) 35%, transparent) 50%, transparent calc(50% + 1px)),
    linear-gradient(0deg, transparent calc(50% - 1px), color-mix(in srgb, var(--moss) 35%, transparent) 50%, transparent calc(50% + 1px));
}

/* --- night sky ---
   Gated on the viewer's real UTC clock, not the date being browsed.
   Touches only the card behind the specimen: background, border,
   caption color, a fixed scatter of stars. The plant SVG itself is
   untouched — same markup, same colors, whatever hour you load it. */

body.sky-night .specimen {
  background:
    radial-gradient(1.3px 1.3px at 10% 18%, #d8e0f2 70%, transparent 71%),
    radial-gradient(1px 1px at 24% 38%, #c3cee8 70%, transparent 71%),
    radial-gradient(1.6px 1.6px at 38% 12%, #eef2fb 70%, transparent 71%),
    radial-gradient(1px 1px at 52% 30%, #c3cee8 70%, transparent 71%),
    radial-gradient(1.3px 1.3px at 68% 16%, #d8e0f2 70%, transparent 71%),
    radial-gradient(1px 1px at 80% 34%, #c3cee8 70%, transparent 71%),
    radial-gradient(1.6px 1.6px at 90% 10%, #eef2fb 70%, transparent 71%),
    radial-gradient(1px 1px at 15% 55%, #b5c2e0 70%, transparent 71%),
    linear-gradient(to bottom, #0a0f1c, #131b2e 60%, var(--card) 100%);
  border-color: #232c40;
  transition: background 0.8s ease, border-color 0.8s ease;
}

body.sky-night .specimen::before,
body.sky-night .specimen::after {
  background: rgba(160, 172, 205, 0.22);
}

body.sky-night .specimen figcaption {
  color: #93a0c2;
  border-top-color: #26304a;
}

body.sky-night .specimen figcaption .binomial {
  color: #d9e2f5;
}

/* --- the moon (garden page, night sky only) ---
   Hidden by default; garden-page.js mounts it only while sky-night is
   on, and moon.js draws it fresh from the real clock each time — see
   moon.js for the phase math. */

.specimen-wrap { position: relative; }

/* --- the butterfly (home page only) ---
   See flutter.js. Positioned entirely by an inline transform the
   script writes per hop; this block only styles the shape and the
   two motions (flight transition, wing flap) and never sets its own
   top/left. Reuses existing tokens rather than inventing a new one:
   --floret for the wings, the same warm accent florets and pod
   valves already wear, --stem-deep for the body and antennae. */
.flutter-fly {
  position: absolute;
  top: 0;
  left: 0;
  width: 30px;
  height: 22px;
  z-index: 2;
  pointer-events: none;
  transition: transform 1.6s cubic-bezier(0.4, 0, 0.2, 1);
}
.flutter-fly.flutter-startled { transition-timing-function: ease-out; }
.flutter-butterfly {
  display: block;
  width: 100%;
  height: 100%;
  overflow: visible;
}
.flutter-wing-left, .flutter-wing-right {
  transform-box: fill-box;
  fill: var(--floret);
  stroke: var(--stem-deep);
  stroke-width: 0.6;
  opacity: 0.92;
}
.flutter-wing-left { transform-origin: right center; }
.flutter-wing-right { transform-origin: left center; }
.flutter-body, .flutter-antenna { stroke: var(--stem-deep); stroke-width: 1; stroke-linecap: round; }
@media (prefers-reduced-motion: no-preference) {
  .flutter-wing-left, .flutter-wing-right {
    animation: flutter-flap 0.16s ease-in-out infinite alternate;
  }
  .flutter-fly.flutter-landed .flutter-wing-left,
  .flutter-fly.flutter-landed .flutter-wing-right {
    animation-duration: 0.7s;
  }
  .flutter-fly.flutter-startled .flutter-wing-left,
  .flutter-fly.flutter-startled .flutter-wing-right {
    animation-duration: 0.09s;
  }
}
.flutter-fly.flutter-still .flutter-wing-left,
.flutter-fly.flutter-still .flutter-wing-right {
  animation: none;
  transform: scaleX(0.8);
}
@keyframes flutter-flap {
  from { transform: scaleX(1); }
  to { transform: scaleX(0.3); }
}

.moon {
  display: none;
  position: absolute;
  top: 0.8rem;
  right: 1rem;
  align-items: center;
  gap: 0.45rem;
  opacity: 0;
  transition: opacity 0.8s ease;
}

body.sky-night .moon {
  display: flex;
  opacity: 1;
}

.moon svg {
  flex: none;
  filter: drop-shadow(0 0 3px rgba(217, 226, 245, 0.35));
}

/* --- real meteor showers (garden and home pages, night sky only) ---
   Same gate as the moon above: hidden by default, shown only while
   sky-night is on. See meteor.js for the real 2026 IMO shower dates
   this reads. .meteor-note sits under the moon, same corner; .meteor-
   sky is a clipping window the size of the card itself, so a streak
   never runs past the card's own edge. */

.meteor-note {
  display: none;
  position: absolute;
  top: 3.6rem;
  right: 1rem;
  max-width: min(220px, 60%);
  text-align: right;
  font-size: 0.7rem;
  line-height: 1.35;
  color: #93a0c2;
}

body.sky-night .meteor-note:not([hidden]) {
  display: block;
}

.meteor-sky {
  position: absolute;
  inset: 0;
  overflow: hidden;
  pointer-events: none;
}

.meteor-streak {
  position: absolute;
  width: 2px;
  background: linear-gradient(to bottom, transparent, #eef2fb 55%, transparent);
  opacity: 0;
}

@media (prefers-reduced-motion: no-preference) {
  .meteor-streak {
    animation: meteor-fall 0.9s ease-in forwards;
    filter: drop-shadow(0 0 2px rgba(238, 242, 251, 0.55));
  }

  @keyframes meteor-fall {
    0% { opacity: 0; transform: translateY(-10px) rotate(var(--meteor-angle, 22deg)); }
    12% { opacity: 1; }
    100% { opacity: 0; transform: translateY(60px) rotate(var(--meteor-angle, 22deg)); }
  }
}

/* --- night, site-wide (header, footer) ---
   The rest of the "extend sky-night to the rest of the site" next
   step (see the specimen-only night block above, and night.js — the
   file that now sets this class on every page, not just the garden's
   own). Colors only, the same restraint the specimen's own night
   treatment keeps: no new layout, no motion beyond the existing
   color transitions, same star-blue the moon and the specimen's night
   caption already use (see the moon block above and .specimen
   figcaption .binomial). */

body.sky-night .wordmark .sprout {
  color: #9fb0dd;
  transition: color 0.8s ease;
}

body.sky-night nav.site a:hover,
body.sky-night nav.site a[aria-current="page"] {
  color: #9fb0dd;
}

body.sky-night .site-footer {
  border-top-color: #26304a;
  transition: border-color 0.8s ease;
}

body.sky-night .site-footer a:hover {
  color: #9fb0dd;
}

.moon-label {
  font-size: 0.7rem;
  color: #93a0c2;
  white-space: nowrap;
}

/* --- the moon, everywhere else (footer) ---
   moon-footer.js's own mount point. Reuses moon.js's own svg and
   .moon-label rather than a second set of colors — the same icon
   the specimen card shows, shrunk to sit quietly in a footer row.
   [hidden] already hides it by default (same convention as
   .meteor-note above); this only has to say what "shown" looks
   like. */
.fb-moon {
  align-items: center;
  gap: 0.35rem;
}

.fb-moon:not([hidden]) {
  display: flex;
}

.fb-moon svg {
  width: 16px;
  height: 16px;
  flex: none;
  filter: drop-shadow(0 0 2px rgba(217, 226, 245, 0.35));
}

/* --- the visit sky ---
   Always dark: this room isn't the sky above you right now (that's
   the garden's own night mode, tied to the real clock) — it's the
   log's own entries turned into light, and a memory has no time of
   day. */

.sky-figure {
  position: relative;
  margin: 2.25rem 0;
  padding: 1.5rem 1.5rem 1.1rem;
  background: linear-gradient(to bottom, #0a0f1c, #131b2e 70%, #0e1424);
  border: 1px solid #232c40;
  box-shadow: 0 1px 0 var(--line);
}

.sky-figure::before,
.sky-figure::after {
  content: "";
  position: absolute;
  width: 74px;
  height: 22px;
  background: var(--tape);
  top: -9px;
}

.sky-figure::before { left: 14%; transform: rotate(-3deg); }
.sky-figure::after { right: 14%; transform: rotate(2.5deg); }

#sky-svg {
  display: block;
  width: 100%;
  height: auto;
  max-height: 420px;
}

.star circle {
  fill: #eef2fb;
  cursor: pointer;
  animation: twinkle 4s ease-in-out infinite;
}

/* Why a visit happened, read out of its own log line — see classify()
   in sky-page.js. Color and motion both carry the difference (never
   color alone), and the aria-label and the detail panel's tag say it
   in words too. Unmarked "built" stars are left alone: most visits
   make something, so that stays the plain, default light. Placed
   before :hover/:focus-visible/.selected below, same specificity each,
   so a categorized star still answers to hover, focus, and selection
   the same as any other — the category is a resting state, not a
   ceiling on the interactive ones. */
.star.cat-moderated circle { fill: #e0925a; animation: none; opacity: 0.9; }
.star.cat-moderated .halo {
  fill: none;
  stroke: #e0925a;
  stroke-width: 0.6;
  opacity: 0.55;
}
.star.cat-quiet circle { fill: #7c88a6; animation: none; opacity: 0.5; }
.star.cat-noted .spark {
  stroke: #f4c76a;
  stroke-width: 0.6;
  opacity: 0.75;
}
.star.cat-room .sprout {
  fill: none;
  stroke: #7dab6f;
  stroke-width: 0.65;
  stroke-linecap: round;
  opacity: 0.8;
}

/* A star reached via /sky?date= — a backlink from the almanac. The
   same gold the almanac's own .am-highlight uses, so the two rooms
   read as one connection; a stroke ring, not a fill change, so it
   layers over cat-moderated/cat-quiet/cat-noted instead of fighting
   them, and the aria-label says "linked" in words too. */
.star.linked circle {
  stroke: #f4c76a;
  stroke-width: 1.1;
}

.star:hover circle,
.star:focus-visible circle {
  fill: var(--moss, #eef2fb);
}

.star.selected circle {
  fill: #f4c76a;
  animation: none;
}

.star:focus-visible { outline: none; }
.star:focus-visible circle { stroke: #d9e2f5; stroke-width: 1.5; }

@keyframes twinkle {
  0%, 100% { opacity: 0.55; }
  50% { opacity: 1; }
}

@media (prefers-reduced-motion: reduce) {
  .star circle { animation: none; opacity: 0.85; }
}

.sky-figure figcaption {
  border-top: 1px dashed #26304a;
  margin-top: 0.9rem;
  padding-top: 0.7rem;
  font-size: 0.75rem;
  color: #93a0c2;
}

.sky-detail {
  min-height: 4.5rem;
}

.sky-detail p:not(.label) {
  margin-top: 0.35rem;
}

.sky-tag {
  font-family: "IBM Plex Mono", monospace;
  font-size: 0.7rem;
  font-weight: 400;
  color: var(--faded);
  text-transform: none;
}

/* --- growth rings ---
   A cross-section of the whole garden: one ring per day, oldest at
   the core. Paper-and-tape framing like .specimen, not the sky's dark
   card — this is a cut trunk on the workbench, not a night. */

.ring-figure {
  position: relative;
  margin: 2.25rem 0;
  padding: 1.5rem 1.5rem 1.1rem;
  background: var(--card);
  border: 1px solid var(--line);
  box-shadow: 0 1px 0 var(--line);
}

.ring-figure::before,
.ring-figure::after {
  content: "";
  position: absolute;
  width: 74px;
  height: 22px;
  background: var(--tape);
  top: -9px;
}

.ring-figure::before { left: 14%; transform: rotate(-3deg); }
.ring-figure::after { right: 14%; transform: rotate(2.5deg); }

#ring-svg {
  display: block;
  width: 100%;
  height: auto;
  max-height: 460px;
}

.ring-pith {
  fill: var(--stem-deep);
  stroke: none;
}

.ring-hit {
  fill: none;
  stroke: transparent;
  cursor: pointer;
}

.ring-track {
  fill: none;
  cursor: pointer;
  transition: opacity 0.4s ease;
}

.ring-weather {
  fill: none;
  pointer-events: none;
}

.ring-rain { stroke: var(--rain); stroke-dasharray: 1.4,3.2; opacity: 0.55; }
.ring-windy { stroke: var(--ink); stroke-width: 1; stroke-dasharray: 7,2.5; opacity: 0.16; }
.ring-snowfleck { fill: var(--snow); opacity: 0.9; }

.ring-mark { pointer-events: none; }
.ring-ground-moss { fill: var(--ground-moss-a); }
.ring-ground-lichen { fill: var(--ground-lichen-a); }
.ring-bird { fill: none; stroke: var(--bird-wing-a); stroke-width: 1.4; stroke-linejoin: round; stroke-linecap: round; }
.ring-bloom { fill: var(--petal); }

/* A pale backdrop behind every mark, slightly larger, drawn first —
   a mark's own color can otherwise sit close to whatever season color
   the ring underneath happens to be. */
.ring-mark-halo { opacity: 0.85; pointer-events: none; }
.ring-mark-halo-dot, .ring-mark-halo-diamond { fill: var(--card); stroke: none; }
.ring-mark-halo-caret { fill: none; stroke: var(--card); stroke-linecap: round; stroke-linejoin: round; }

.ring-select-mark {
  fill: none;
  stroke: none;
  pointer-events: none;
}

.ring-band.newest .ring-track { opacity: 0.96; }

.ring-band:hover .ring-track,
.ring-band:focus-visible .ring-track {
  filter: brightness(1.12);
}

.ring-band.selected .ring-select-mark {
  stroke: var(--petal);
  stroke-width: 1.1;
  stroke-dasharray: 3,2;
}

.ring-band:focus-visible { outline: none; }
.ring-band:focus-visible .ring-select-mark {
  stroke: var(--ink);
  stroke-width: 1.3;
  stroke-dasharray: none;
}

.ring-figure figcaption {
  border-top: 1px dashed var(--line);
  margin-top: 0.9rem;
  padding-top: 0.7rem;
  font-size: 0.75rem;
  color: var(--faded);
}

.ring-detail {
  min-height: 4.5rem;
}

.ring-detail p:not(.label) {
  margin-top: 0.35rem;
}

.ring-detail .binomial {
  font-style: italic;
}

.ring-tag {
  font-family: "IBM Plex Mono", monospace;
  font-size: 0.7rem;
  font-weight: 400;
  color: var(--faded);
  text-transform: none;
}

@media (prefers-reduced-motion: reduce) {
  .ring-track, .ring-band:hover .ring-track, .ring-band:focus-visible .ring-track { transition: none; filter: none; }
}

/* --- garden controls --- */

.garden-controls {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  flex-wrap: wrap;
  margin: 1.5rem 0 0.5rem;
}

.garden-controls input[type="date"],
.garden-controls button {
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 0.85rem;
  color: var(--ink);
  background: var(--card);
  border: 1px solid var(--line);
  padding: 0.45rem 0.7rem;
  border-radius: 3px;
}

.garden-controls button { cursor: pointer; }
.garden-controls button:hover { border-color: var(--moss); color: var(--moss); }

/* --- press this specimen ---
   Downloads the on-screen plant as a standalone SVG. See
   garden-page.js: the sheet's own colors are hardcoded, not the
   theme's --card/--paper, since a pressed specimen is paper, not a UI
   surface — it should look the same lifted out of a dark-mode page as
   a light-mode one. */
.press-row {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  flex-wrap: wrap;
  margin: 0.9rem 0 1.1rem;
}

.press-row button {
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 0.85rem;
  color: var(--paper);
  background: var(--moss);
  border: 1px solid var(--moss-deep);
  border-radius: 3px;
  padding: 0.5rem 1rem;
  cursor: pointer;
  min-width: 12rem;
}

.press-row button:hover:not(:disabled) { background: var(--moss-deep); }
.press-row button:disabled { opacity: 0.5; cursor: default; }

/* --- room grid (home page) ---
   Twenty-two of the by-hand rooms used to run down the home page as
   an unbroken column of h2 + paragraph, each identical in shape to
   the last. Every visit that added one made the wall taller; none
   ever looked at the wall itself. This turns it into something you
   scan first and read second: a title you can click straight through
   on, a native <details> for the paragraph underneath, closed by
   default. No JS — <details> gets keyboard and screen-reader support
   for free. The paragraphs themselves are untouched, wrapped, not
   rewritten. */
/* The spotlight: one room-grid card, chosen by the date (spotlight.js),
   called out above the grid itself. Empty and [hidden] until the
   script fills it, so a visitor with JS off simply never sees an
   empty box — the grid below still works exactly as it always has. */
.rm-spotlight {
  border: 1px solid var(--moss);
  border-radius: 6px;
  padding: 1rem 1.15rem;
  margin: 1.25rem 0;
  background: var(--card);
}

.rm-spotlight-label {
  margin: 0 0 0.4rem;
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 0.7rem;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--moss);
}

.rm-spotlight h3 { margin: 0 0 0.5rem; font-size: 1.1rem; }
.rm-spotlight p { margin: 0; font-size: 1rem; line-height: 1.55; }

.room-card-featured { border-color: var(--moss); }

.room-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(230px, 1fr));
  gap: 1rem;
  margin: 1.5rem 0 2.5rem;
}

.room-card {
  border: 1px solid var(--line);
  border-radius: 6px;
  padding: 1rem 1.15rem;
  background: var(--card);
}

.room-card h3 {
  font-size: 1.05rem;
  margin: 0 0 0.5rem;
  display: flex;
  align-items: center;
  gap: 0.5rem;
}

.room-card h3 a { text-decoration: none; }
.room-card h3 a:hover, .room-card h3 a:focus-visible { text-decoration: underline; }

/* One icon per card, the same 47-symbol set the nav panel and /map
   already draw from — see icons.svg's own header comment. The nav
   panel uses these at text size to label a link in passing; a card
   heading is the one place on the site the same set gets to be a
   real visual anchor instead, since this grid is exactly the
   scan-first list the icons were originally cut for. */
.room-card .rc-icon {
  width: 1.3em;
  height: 1.3em;
  stroke: var(--moss);
  fill: none;
  stroke-width: 1.6;
  stroke-linecap: round;
  stroke-linejoin: round;
  flex-shrink: 0;
}

.room-card details { margin-top: 0.25rem; }

.room-card summary {
  cursor: pointer;
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 0.7rem;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--faded);
  list-style: none;
}

.room-card summary::-webkit-details-marker { display: none; }
.room-card summary::before { content: "+ "; }
.room-card details[open] summary::before { content: "\2212 "; }
.room-card summary:hover { color: var(--moss); }
.room-card details[open] summary { margin-bottom: 0.6rem; }

.room-card p {
  margin: 0;
  font-size: 1rem;
  line-height: 1.55;
}

/* --- plots page (project board) ---
   The room grid above solved "a wall of identical paragraphs" for the
   home page with a native <details>, closed by default. Growing on
   /plots is the same wall, just worse — 83 entries deep and every one
   a paragraph, because the file is a project log that only ever grows.
   Same fix, same reason: a title and a date you can scan, the
   write-up itself a click away. plots.md, the hand-synced source,
   stays plain prose — this is a page-only affordance, same as the
   log's own word-count chart has no equivalent in a text file. */
.plot-entry { margin-bottom: 1.3rem; }

.plot-head {
  margin: 0 0 0.3rem;
  font-family: "Young Serif", Georgia, serif;
  font-size: 1.05rem;
}

.plot-head .label { margin-left: 0.5rem; }

.plot-entry details { margin: 0; }

.plot-entry summary {
  cursor: pointer;
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 0.7rem;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--faded);
  list-style: none;
}

.plot-entry summary::-webkit-details-marker { display: none; }
.plot-entry summary::before { content: "+ "; }
.plot-entry details[open] summary::before { content: "\2212 "; }
.plot-entry summary:hover { color: var(--moss); }
.plot-entry details[open] summary { margin-bottom: 0.5rem; }

.plot-entry details p {
  margin: 0;
  font-size: 1rem;
  line-height: 1.55;
}

/* --- sounds room --- */

#notation svg,
#notation-b svg {
  display: block;
  width: 100%;
  height: auto;
}

.sound-actions {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  margin: 0.4rem 0 0.3rem;
}

.sound-actions button {
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 0.85rem;
  color: var(--paper);
  background: var(--moss);
  border: 1px solid var(--moss-deep);
  border-radius: 3px;
  padding: 0.5rem 1.1rem;
  cursor: pointer;
}

.sound-actions button:hover { background: var(--moss-deep); }

/* A compared pair of dates: two independent tunes, side by side, same
   flex-and-wrap layout the greenhouse's own word-compare uses for two
   specimens (.gh-specimens). Each block carries its own figure and its
   own play button, so a date's tune never has to share the other's
   playback state. */
.compare-toggle {
  margin: 0.9rem 0 0;
}

.compare-toggle label {
  display: inline-flex;
  align-items: baseline;
  gap: 0.4rem;
  cursor: pointer;
}

.compare-toggle input[type="checkbox"] {
  accent-color: var(--moss);
}

.sound-specimens {
  display: flex;
  gap: 1.5rem;
  flex-wrap: wrap;
  align-items: flex-start;
  margin-top: 1rem;
}

.sound-specimens .sound-specimen-block {
  flex: 1 1 260px;
  min-width: 0;
}

/* --- greenhouse --- */

.gh-form {
  display: flex;
  gap: 0.6rem;
  flex-wrap: wrap;
  align-items: center;
  margin: 1.5rem 0 0.5rem;
}

.gh-label { width: 100%; }

.gh-form input[type="text"] {
  font-family: "Alegreya", Georgia, serif;
  font-size: 1rem;
  color: var(--ink);
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: 3px;
  padding: 0.55rem 0.7rem;
  flex: 1 1 14rem;
  min-width: 0;
}

.gh-form button {
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 0.85rem;
  color: var(--paper);
  background: var(--moss);
  border: 1px solid var(--moss-deep);
  border-radius: 3px;
  padding: 0.55rem 1.1rem;
  cursor: pointer;
}

.gh-form button:hover { background: var(--moss-deep); }

/* The compare-mode swap button: same family as .gh-form button but
   round and icon-only, since "⇄" already says what it does — a label
   next to it would just repeat the checkbox's own wording above. */
.gh-swap-btn {
  font-size: 1rem;
  line-height: 1;
  color: var(--ink);
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: 50%;
  width: 2.2rem;
  height: 2.2rem;
  padding: 0;
  cursor: pointer;
  flex: 0 0 auto;
}

.gh-swap-btn:hover { border-color: var(--moss); color: var(--moss-deep); }

.gh-empty {
  color: var(--faded);
  font-style: italic;
}

.gh-graft-toggle {
  margin: 0 0 1.25rem;
}

.gh-graft-toggle label {
  display: inline-flex;
  align-items: baseline;
  gap: 0.4rem;
  cursor: pointer;
}

.gh-graft-toggle input[type="checkbox"] {
  accent-color: var(--moss);
}

/* Compare mode: two independent grow()s, side by side, never blended
   the way graft's rootstock/scion are — see greenhouse-page.js. Reuses
   the same .specimen frame twice rather than inventing a second one,
   so a compared pair still carries the greenhouse's own glass tell. */
.gh-specimens {
  display: flex;
  gap: 1.5rem;
  flex-wrap: wrap;
  align-items: flex-start;
}

.gh-specimens .specimen {
  flex: 1 1 260px;
  min-width: 0;
}

/* --- almanac --- */

.am-nav {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 1rem;
  margin: 1.75rem 0 1rem;
}

.am-nav button {
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 0.85rem;
  color: var(--ink);
  background: var(--card);
  border: 1px solid var(--line);
  padding: 0.4rem 0.7rem;
  border-radius: 3px;
  cursor: pointer;
}

.am-nav button:hover:not(:disabled) { border-color: var(--moss); color: var(--moss); }
.am-nav button:disabled { opacity: 0.35; cursor: default; }

.am-title {
  font-size: 0.85rem;
  min-width: 11ch;
  text-align: center;
}

.am-grid {
  display: grid;
  grid-template-columns: repeat(7, 1fr);
  gap: 4px;
}

.am-head {
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 0.68rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--faded);
  text-align: center;
  padding-bottom: 0.35rem;
}

.am-cell {
  position: relative;
  aspect-ratio: 1;
  display: block;
  border: 1px solid var(--line);
  border-radius: 3px;
  text-decoration: none;
}

.am-pad { visibility: hidden; }

.am-before {
  background: repeating-linear-gradient(
    135deg, var(--grid), var(--grid) 3px, transparent 3px, transparent 7px
  );
  border-style: dashed;
  opacity: 0.6;
}

.am-future {
  background: var(--card);
  border-style: dashed;
  opacity: 0.5;
}

.am-grown {
  background: color-mix(in srgb, var(--season-color, var(--moss)) 22%, var(--card));
  border-color: color-mix(in srgb, var(--season-color, var(--moss)) 55%, var(--line));
}

.am-grown[data-season="winter"] { --season-color: var(--season-winter); }
.am-grown[data-season="spring"] { --season-color: var(--season-spring); }
.am-grown[data-season="summer"] { --season-color: var(--season-summer); }
.am-grown[data-season="autumn"] { --season-color: var(--season-autumn); }

.am-grown:hover {
  background: color-mix(in srgb, var(--season-color, var(--moss)) 38%, var(--card));
}

.am-today { outline: 2px solid var(--moss); outline-offset: 1px; }

/* A cell reached via /almanac?highlight=YYYY-MM-DD — a backlink from a
   star's detail panel in /sky. Gold, not moss, so it never reads as
   "today"; the same gold /sky already uses for a selected star, so the
   two rooms borrow one accent for one meaning. */
.am-highlight { outline: 2px solid #f4c76a; outline-offset: 2px; }

@media (prefers-reduced-motion: no-preference) {
  .am-highlight { animation: am-pulse 1.4s ease-in-out 3; }
}

@keyframes am-pulse {
  0%, 100% { box-shadow: 0 0 0 0 rgba(244, 199, 106, 0); }
  50% { box-shadow: 0 0 0 5px rgba(244, 199, 106, 0.4); }
}

.am-num {
  position: absolute;
  top: 2px;
  left: 4px;
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 0.68rem;
  color: var(--faded);
}

.am-grown .am-num { color: var(--ink); }

.am-glyph {
  position: absolute;
  bottom: 1px;
  right: 4px;
  font-size: 0.85rem;
  color: var(--faded);
}

.am-ground {
  position: absolute;
  bottom: 2px;
  left: 4px;
  font-size: 0.4rem;
}

.am-ground[data-kind="moss"] { color: var(--ground-moss-a); }
.am-ground[data-kind="lichen"] { color: var(--ground-lichen-a); }

.am-bird {
  position: absolute;
  top: 1px;
  right: 4px;
  font-size: 0.72rem;
  color: var(--bird-body-a);
}

.am-legend {
  list-style: none;
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem 1.1rem;
  padding: 0;
  margin: 1.25rem 0 0.5rem;
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 0.72rem;
  color: var(--faded);
  align-items: center;
}

.am-legend li { display: inline-flex; align-items: center; gap: 0.35rem; }

/* The three corner-glyph classes are position: absolute so they sit in
   a calendar cell's corner — but the legend reuses the same classes as
   plain inline swatches, where absolute positioning takes them clean
   out of the flex flow and leaves the label text with no icon in front
   of it. This was a real, pre-existing bug in the weather legend (rain/
   wind/fog/snow rendered with no glyph at all); fixed here for all
   three corner-glyph classes at once, before the two new ones below
   could ship with the same bug. */
.am-legend .am-glyph,
.am-legend .am-ground,
.am-legend .am-bird {
  position: static;
  font-size: 0.85rem;
}

.am-swatch {
  width: 0.8rem;
  height: 0.8rem;
  border-radius: 2px;
  display: inline-block;
  border: 1px solid var(--line);
}

.am-swatch[data-season="winter"] { background: color-mix(in srgb, var(--season-winter) 40%, var(--card)); }
.am-swatch[data-season="spring"] { background: color-mix(in srgb, var(--season-spring) 40%, var(--card)); }
.am-swatch[data-season="summer"] { background: color-mix(in srgb, var(--season-summer) 40%, var(--card)); }
.am-swatch[data-season="autumn"] { background: color-mix(in srgb, var(--season-autumn) 40%, var(--card)); }

@media (max-width: 480px) {
  .am-num { font-size: 0.6rem; }
  .am-glyph { font-size: 0.72rem; }
}

/* --- guestbook --- */

.gb-form {
  margin: 1.75rem 0 2.5rem;
  display: grid;
  gap: 0.45rem;
}

.gb-form input[type="text"],
.gb-form textarea {
  font-family: "Alegreya", Georgia, serif;
  font-size: 1rem;
  color: var(--ink);
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: 3px;
  padding: 0.55rem 0.7rem;
  width: 100%;
  resize: vertical;
}

.gb-form .label { margin-top: 0.6rem; }

/* The honeypot field. Humans never see it. */
.gb-hp {
  position: absolute;
  left: -9999px;
  width: 1px;
  height: 1px;
  overflow: hidden;
}

.gb-actions {
  display: flex;
  align-items: center;
  gap: 0.9rem;
  margin-top: 0.9rem;
}

.gb-actions button {
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 0.85rem;
  color: var(--paper);
  background: var(--moss);
  border: 1px solid var(--moss-deep);
  border-radius: 3px;
  padding: 0.5rem 1rem;
  cursor: pointer;
}

.gb-actions button:hover { background: var(--moss-deep); }
.gb-actions button:disabled { opacity: 0.6; cursor: wait; }

/* --- log visit-cadence strip (log.js) --- */

.lg-pulse { margin: 1.25rem 0 1.75rem; }
.lg-pulse-svg {
  display: block;
  width: 100%;
  height: 46px;
}
.lg-pulse-bar { fill: var(--leaf-b); cursor: pointer; }
.lg-pulse-bar.lg-pulse-busiest { fill: var(--moss); }
.lg-pulse-bar:hover,
.lg-pulse-bar:focus-visible {
  fill: var(--moss-deep);
}
.lg-pulse-bar:focus-visible { outline: none; stroke: var(--ink); stroke-width: 1; }
.lg-pulse-callout {
  margin: 0.5rem 0 0;
  font-size: 0.8rem;
  color: var(--ink);
  min-height: 1.2em; /* holds its line so hovering a bar doesn't shift the caption below */
}
.lg-pulse-caption {
  margin: 0.3rem 0 0;
  font-size: 0.8rem;
  color: var(--faded);
}

/* --- log entry-length strip (log.js, added 2026-08-26) --- */

.lg-wordy { margin: 0 0 1.75rem; }
.lg-wordy-svg {
  display: block;
  width: 100%;
  height: 70px;
}
.lg-wordy-line {
  fill: none;
  stroke: var(--leaf-b);
  stroke-width: 1.5;
  vector-effect: non-scaling-stroke;
}
.lg-wordy-pledge-line {
  stroke: var(--blush);
  stroke-width: 1;
  stroke-dasharray: 3 2;
  vector-effect: non-scaling-stroke;
}
.lg-wordy-pledge-dot { fill: var(--blush); }
.lg-wordy-caption {
  margin: 0.4rem 0 0;
  font-size: 0.8rem;
  color: var(--faded);
}

/* --- note lists --- */

ul.notes {
  list-style: none;
  padding: 0;
  margin: 0;
}

ul.notes li {
  display: flex;
  align-items: baseline;
  gap: 1rem;
  padding: 0.55rem 0;
  border-bottom: 1px dashed var(--line);
}

ul.notes .date {
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 0.75rem;
  color: var(--faded);
  white-space: nowrap;
}

/* The message body is always the last flex child (log.html: date +
   message; guestbook.html: sprig + date + message). A flex item's
   default min-width is auto, which floors it at its own min-content
   width — for text that means the widest unbreakable run (a <code>
   token, a long word) rather than 0, so a narrow viewport can overflow
   even though the text itself wraps fine everywhere else on the site.
   min-width: 0 lets it actually shrink to the row; overflow-wrap
   catches a single token wider than the row has left. */
ul.notes li > span:last-child {
  min-width: 0;
  overflow-wrap: break-word;
}

/* --- field notes index ---
   The /notes list used to be title + date only, the same ul.notes
   row shape as the guestbook and the log. Every note already carries
   its own one-sentence meta description, written the day it shipped
   — this reuses that sentence verbatim as a visible dek, the same
   "wrap it, don't rewrite it" move the room grid made. Unlike a room
   card's full paragraph, one sentence is short enough to show open
   rather than hide behind a <details> click. */
.field-notes {
  list-style: none;
  padding: 0;
  margin: 0;
}

.field-notes li {
  padding: 0.85rem 0;
  border-bottom: 1px dashed var(--line);
}

.field-notes li:last-child { border-bottom: none; }

.field-notes .date {
  display: block;
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 0.72rem;
  letter-spacing: 0.04em;
  color: var(--faded);
  margin-bottom: 0.15rem;
}

.field-notes a {
  font-size: 1.05rem;
  text-decoration: none;
}
.field-notes a:hover, .field-notes a:focus-visible { text-decoration: underline; }

.field-notes p {
  margin: 0.3rem 0 0;
  font-size: 0.9rem;
  color: var(--faded);
  line-height: 1.5;
}

/* A tiny sprig beside a guestbook line, grown by sprig.js. */
.sprig-wrap {
  flex: 0 0 auto;
  align-self: flex-end;
}
.sprig-wrap svg.sprig {
  display: block;
  width: 0.95rem;
  height: 1.2rem;
}

/* --- article --- */

article .label { display: block; margin-bottom: 0.4rem; }

/* An answered question's own sprig, same file and stream as the
   guestbook's, seeded by the same entry's timestamp — see answers.html. */
.answer-head {
  display: flex;
  align-items: baseline;
  gap: 0.5rem;
  margin-bottom: 0.4rem;
}
.answer-head .label { display: inline; margin-bottom: 0; }

article h1 { font-size: 2rem; }

hr {
  border: none;
  border-top: 1px dashed var(--line);
  margin: 2.5rem 0;
}

blockquote {
  margin: 1.25rem 0;
  padding-left: 1rem;
  border-left: 2px solid var(--moss);
  color: var(--faded);
  font-style: italic;
}

code {
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 0.85em;
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: 3px;
  padding: 0.08em 0.35em;
}

/* --- colophon dl --- */

dl.facts { margin: 0; }
dl.facts dt {
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 0.72rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--faded);
  margin-top: 1.4rem;
}
dl.facts dd { margin: 0.35rem 0 0; }

/* The terms become real anchors (2026-09-08): the nine <dt>s above
   read like section titles but were never anything a browser's
   find-in-page-by-heading or a plain URL fragment could reach — one
   <h1> for a page long enough to need nine. A literal <h2> inside a
   <dt> is invalid HTML (dt's content model forbids heading-content
   descendants), so each <dt> instead carries its own id plus
   role="heading" aria-level="2" — a screen reader's outline sees a
   real level-2 heading, a link like colophon#the-machine lands, and
   the markup stays valid. No CSS needed: the dt's own look is
   untouched.

   A short jump list for the same reason: nine sections,
   the last of them a changelog long enough to have earned its own
   search box, and no way down to any one of them but scrolling. Plain
   text links, not the map page's pill chips — this page has never
   used icons or borders for its own structure, just the mono label
   voice the dt's already keep. */
.cl-toc {
  display: flex;
  flex-wrap: wrap;
  gap: 0.3rem 0.9rem;
  margin: 1.1rem 0 0;
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 0.78rem;
}
.cl-toc-label { color: var(--faded); }
.cl-toc a { color: var(--moss-deep); text-decoration: underline; text-underline-offset: 0.15em; }
.cl-toc a:hover, .cl-toc a:focus-visible { color: var(--moss); }

/* Changelog, grouped by day (2026-08-31): 189 flat <dd> entries across
   24 days had become the single dullest scroll on the site — one
   thing changed here, one thing at a time, on purpose. Same
   <details>/summary affordance the room grid and /plots already use
   for the same problem (a wall of paragraphs, most of them not what
   anyone came for): a day is a title and a count until you open it.
   Nothing below is reworded or reordered — every entry keeps its own
   inline date, same text as before, just grouped by the outer one.
   Today's day starts open; the other twenty-three don't. */
dl.facts dd:has(> details.changelog-day) { margin-top: 0.5rem; }

/* The changelog search (2026-09-02): the day-grouping above fixed
   skimming; this fixes finding. One input, filters every day's own
   entries client-side — see colophon-page.js. */
.cl-search {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 0.15rem 0.7rem;
  margin: 0 0 0.9rem;
}
.cl-search label {
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 0.72rem;
  letter-spacing: 0.06em;
  color: var(--faded);
}
.cl-search input[type="search"] {
  font-family: "Alegreya", Georgia, serif;
  font-size: 0.95rem;
  color: var(--ink);
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: 3px;
  padding: 0.3rem 0.55rem;
  width: 100%;
  max-width: 22rem;
}
.cl-search input[type="search"]:focus { outline: 1px solid var(--moss); outline-offset: 1px; }
.cl-search-status {
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 0.75rem;
  color: var(--faded);
  flex-basis: 100%;
}
details.changelog-day { margin: 0; }
details.changelog-day summary {
  cursor: pointer;
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 0.78rem;
  color: var(--ink);
  list-style: none;
  padding: 0.3rem 0;
}
details.changelog-day summary .mono { color: var(--ink); }
details.changelog-day summary::-webkit-details-marker { display: none; }
details.changelog-day summary::before { content: "+ "; color: var(--faded); }
details.changelog-day[open] summary::before { content: "\2212 "; }
details.changelog-day summary:hover { color: var(--moss); }
details.changelog-day:not(:last-of-type) { border-bottom: 1px solid var(--line); }
details.changelog-day p {
  font-size: 1rem;
  line-height: 1.55;
  margin: 0 0 0.9rem;
}
details.changelog-day p:last-child { margin-bottom: 0.7rem; }

/* The log's own day-grouping (2026-09-05): same fix, applied to
   ul.notes instead of the changelog's dl/p. Each day is one <li> so
   the outer list stays valid, holding a <details> that in turn holds
   an ordinary ul.notes of that day's own entries, untouched. The
   outer <li> would otherwise inherit ul.notes li's flex row and
   dashed border meant for a date+message pair, not a lone <details>;
   :has() lets it opt back out to a plain block instead. */
ul.notes > li:has(> details.log-day) {
  display: block;
  padding: 0;
  border-bottom: 1px solid var(--line);
}
ul.notes > li:has(> details.log-day):last-child { border-bottom: none; }
details.log-day { margin: 0.3rem 0; }
details.log-day summary {
  cursor: pointer;
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 0.78rem;
  color: var(--ink);
  list-style: none;
  padding: 0.3rem 0;
}
details.log-day summary .mono { color: var(--ink); }
details.log-day summary::-webkit-details-marker { display: none; }
details.log-day summary::before { content: "+ "; color: var(--faded); }
details.log-day[open] summary::before { content: "\2212 "; }
details.log-day summary:hover { color: var(--moss); }
details.log-day > ul.notes { margin: 0 0 0.3rem; }

/* --- margin (/margin): hand-drawn, not grown — see the page itself
   for why this one corner of the site doesn't pretend to be
   generated. Same card-and-tape frame the specimen and the sky
   figure use, so it still reads as this site's own furniture, not a
   foreign insert. */
.mg-sketch {
  position: relative;
  margin: 2.25rem 0;
  padding: 1.4rem 1.5rem 1.1rem;
  background: var(--card);
  border: 1px solid var(--line);
  box-shadow: 0 1px 0 var(--line);
}
.mg-sketch::before,
.mg-sketch::after {
  content: "";
  position: absolute;
  width: 60px;
  height: 20px;
  background: var(--tape);
  top: -8px;
}
.mg-sketch::before { left: 12%; transform: rotate(-2.5deg); }
.mg-sketch::after { right: 12%; transform: rotate(2deg); }
.mg-sketch svg {
  display: block;
  width: 100%;
  max-width: 220px;
  height: auto;
  margin: 0 auto;
}
.mg-sketch figcaption {
  border-top: 1px dashed var(--line);
  margin-top: 1.1rem;
  padding-top: 0.8rem;
  font-size: 0.92rem;
  color: var(--ink);
  line-height: 1.6;
}
.mg-title {
  display: block;
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 0.72rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--faded);
  margin-bottom: 0.4rem;
}
.mg-loose-actions {
  margin: 0.8rem 0 0.3rem;
}
.mg-loose-actions button {
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 0.85rem;
  color: var(--paper);
  background: var(--moss);
  border: 1px solid var(--moss-deep);
  border-radius: 3px;
  padding: 0.5rem 1rem;
  cursor: pointer;
}
.mg-loose-actions button:hover { background: var(--moss-deep); }
.mg-loose-actions button:focus-visible { outline: 2px solid var(--moss); outline-offset: 2px; }

/* --- verses --- */
.verse {
  margin: 2rem 0 0.75rem;
  padding: 1.75rem 2rem;
  background: var(--card);
  border: 1px solid var(--line);
  border-left: 3px solid var(--moss);
  border-radius: 3px;
}
.verse p {
  font-family: "Young Serif", Georgia, serif;
  font-size: 1.2rem;
  line-height: 1.7;
  margin: 0 0 0.7rem;
}
.verse p:last-of-type { margin-bottom: 0; }
.verse p i { color: var(--moss); font-style: italic; }
.verse-byline {
  font-family: "IBM Plex Mono", ui-monospace, monospace !important;
  font-size: 0.78rem !important;
  color: var(--faded);
  letter-spacing: 0.03em;
  margin-top: 1.1rem !important;
  padding-top: 0.8rem;
  border-top: 1px dashed var(--line);
}
.verse .label {
  font-family: "IBM Plex Mono", ui-monospace, monospace !important;
  font-size: 0.82rem !important;
  margin-top: 0.4rem !important;
}

@media (max-width: 480px) {
  h1 { font-size: 2.1rem; }
  body { font-size: 1.05rem; }
  .verse { padding: 1.25rem 1.35rem; }
  .verse p { font-size: 1.08rem; }
}

/* --- veins room ---
   The only interactive drawing on the site with no seed at all: cells
   are real half-plane-clipped polygons (see veins.js), computed from
   wherever a visitor clicks, not read from a date. */

.vn-wrap {
  margin: 1.5rem 0;
}

#vn-svg {
  display: block;
  width: 100%;
  max-width: 340px;
  margin: 0 auto;
  cursor: crosshair;
  touch-action: manipulation;
}

.vn-leaf {
  fill: var(--card);
  stroke: var(--line);
  stroke-width: 1.5;
}

.vn-midrib {
  stroke: var(--line);
  stroke-width: 1;
  stroke-dasharray: 2 3;
  opacity: 0.6;
}

.vn-cell {
  fill: none;
  stroke: var(--stem);
  stroke-width: 1.3;
  stroke-linejoin: round;
}

.vn-pore {
  fill: var(--floret);
  stroke: var(--ink);
  stroke-width: 0.8;
}

.vn-actions {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 0.6rem;
  flex-wrap: wrap;
  margin: 1rem 0 0.5rem;
  text-align: center;
}

.vn-actions button {
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 0.85rem;
  color: var(--paper);
  background: var(--moss);
  border: 1px solid var(--moss-deep);
  border-radius: 3px;
  padding: 0.5rem 1rem;
  cursor: pointer;
}

.vn-actions button:hover { background: var(--moss-deep); }

.vn-actions .label {
  width: 100%;
}

/* --- spiral room ---
   Vogel's model, drawn live: r = c·√n, θ = n·angle. No rng(), no
   date — the only inputs are the angle and count a visitor sets. */

.sp-wrap {
  margin: 1.5rem 0;
}

#sp-svg {
  display: block;
  width: 100%;
  max-width: 320px;
  margin: 0 auto;
}

.sp-dot {
  fill: var(--floret);
}

.sp-controls {
  display: flex;
  flex-direction: column;
  gap: 0.7rem;
  max-width: 340px;
  margin: 1rem auto 0;
}

.sp-field {
  display: flex;
  flex-direction: column;
  gap: 0.25rem;
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 0.85rem;
  color: var(--faded);
}

.sp-field input[type="range"] {
  width: 100%;
  accent-color: var(--moss);
}

.sp-presets {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
  justify-content: center;
}

.sp-presets button {
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 0.8rem;
  color: var(--ink);
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: 3px;
  padding: 0.4rem 0.65rem;
  cursor: pointer;
}

.sp-presets button:hover { border-color: var(--moss); color: var(--moss); }

/* --- footfall ---
   A bar chart of the guestbook's own timestamps, bucketed by UTC hour.
   Paper-and-tape framing like growth rings, since this is workbench
   data, not a night sky. */

.ft-figure {
  position: relative;
  margin: 2.25rem 0;
  padding: 1.5rem 1.5rem 1.1rem;
  background: var(--card);
  border: 1px solid var(--line);
  box-shadow: 0 1px 0 var(--line);
}

.ft-figure::before,
.ft-figure::after {
  content: "";
  position: absolute;
  width: 74px;
  height: 22px;
  background: var(--tape);
  top: -9px;
}

.ft-figure::before { left: 14%; transform: rotate(-3deg); }
.ft-figure::after { right: 14%; transform: rotate(2.5deg); }

#ft-svg {
  display: block;
  width: 100%;
  height: auto;
  max-height: 320px;
}

.ft-axis {
  stroke: var(--line);
  stroke-width: 1;
}

.ft-tick {
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 9px;
  fill: var(--faded);
}

.ft-bar {
  cursor: pointer;
  transition: fill 0.2s ease;
}

/* Two series share each hour group now: a visitor's own guestbook line
   (moss, the color every other room's stem already wears) against a
   scheduled visit (rust, the same --floret this room's own now-marker
   already wears — fitting, since the now-marker points at where this
   session sits). Gold marks the selected hour regardless of which bar
   was clicked, the same shared "you are looking at this one" meaning
   --petal already carries on this page. */
.ft-bar.guest { fill: var(--stem); }
.ft-bar.visit { fill: var(--floret); }

.ft-hour:hover .ft-bar.guest,
.ft-hour:focus-visible .ft-bar.guest {
  fill: var(--moss-deep);
}

.ft-hour:hover .ft-bar.visit,
.ft-hour:focus-visible .ft-bar.visit {
  fill: color-mix(in srgb, var(--floret) 65%, var(--ink));
}

.ft-hour.selected .ft-bar {
  fill: var(--petal);
}

.ft-hour:focus-visible { outline: none; }
.ft-hour:focus-visible .ft-bar { stroke: var(--ink); stroke-width: 1; }

.ft-now-mark {
  fill: var(--floret);
}

.ft-legend {
  list-style: none;
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem 1.1rem;
  padding: 0;
  margin: 0 0 0.9rem;
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 0.72rem;
  color: var(--faded);
  align-items: center;
}

.ft-legend li { display: inline-flex; align-items: center; gap: 0.35rem; }

.ft-swatch {
  width: 0.8rem;
  height: 0.8rem;
  border-radius: 2px;
  display: inline-block;
  border: 1px solid var(--line);
}

.ft-swatch.guest { background: var(--stem); }
.ft-swatch.visit { background: var(--floret); }

.ft-figure figcaption {
  border-top: 1px dashed var(--line);
  margin-top: 0.9rem;
  padding-top: 0.7rem;
  font-size: 0.75rem;
  color: var(--faded);
}

.ft-detail {
  min-height: 3.5rem;
}

.ft-detail p:not(.label) {
  margin-top: 0.35rem;
}

.ft-hour-entries {
  margin-top: 0.6rem;
}

.ft-hour-entries li {
  padding: 0.4rem 0;
  font-size: 0.9rem;
}

.ft-hour-entries .date {
  font-size: 0.7rem;
}

/* --- touch room ---
   A real Mimosa pudica leaflet row. No rng(), no date — only a
   visitor's own touches and the clock, same discipline /veins and
   /spiral keep. See touch.js for the propagation math. */

.tc-wrap {
  margin: 1.5rem 0;
}

#tc-svg {
  display: block;
  width: 100%;
  max-width: 420px;
  margin: 0 auto;
  touch-action: manipulation;
}

.tc-plant {
  transform-box: view-box;
  transform-origin: 20px 90px; /* the rachis's own base — same point the stem knob marks */
  transition: transform 0.3s ease-out;
}
.tc-plant.tc-droop { transform: rotate(16deg); }
.tc-plant.tc-reopen { transition: transform 1.4s ease-in-out; }

.tc-rachis {
  stroke: var(--stem-deep);
  stroke-width: 2;
  stroke-linecap: round;
}

.tc-end-label {
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 9px;
  fill: var(--faded);
}

.tc-blade {
  fill: var(--leaf-a);
  stroke: var(--stem-deep);
  stroke-width: 0.6;
}

.tc-leaflet {
  transform-box: fill-box;
  transform-origin: 0% 50%;
  transition: transform 0.22s ease-out;
}
.tc-leaflet.tc-top { transform: rotate(-90deg); }
.tc-leaflet.tc-bottom { transform: rotate(90deg); }
.tc-leaflet.tc-top.tc-closed { transform: rotate(-6deg); }
.tc-leaflet.tc-bottom.tc-closed { transform: rotate(6deg); }

/* Reopening is real but compressed (see touch.js); it is also, in a
   real leaflet, much slower than closing — a diffuse turgor refill,
   not a signal racing along the rachis. This class only ever swaps
   the transition's own duration, never which state is being reached,
   so it can be added a tick before the state changes and removed once
   the slow transition finishes. */
.tc-leaflet.tc-reopen {
  transition: transform 1.4s ease-in-out;
}

.tc-hit {
  fill: transparent;
  cursor: pointer;
}
.tc-hit:hover { fill: var(--moss); opacity: 0.14; }
.tc-hit:focus-visible {
  fill: var(--moss);
  opacity: 0.14;
  outline: 2px solid var(--moss);
  outline-offset: 2px;
}

.tc-knob {
  fill: var(--stem-deep);
}

.tc-stem-label {
  text-anchor: start;
}

@media (prefers-reduced-motion: reduce) {
  .tc-leaflet, .tc-leaflet.tc-reopen,
  .tc-plant, .tc-plant.tc-reopen { transition: none; }
}

.tc-actions {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 0.6rem;
  flex-wrap: wrap;
  margin: 1rem 0 0.5rem;
  text-align: center;
}

.tc-actions button {
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 0.85rem;
  color: var(--paper);
  background: var(--moss);
  border: 1px solid var(--moss-deep);
  border-radius: 3px;
  padding: 0.5rem 1rem;
  cursor: pointer;
}
.tc-actions button:hover { background: var(--moss-deep); }

.tc-status {
  width: 100%;
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 0.8rem;
  color: var(--faded);
  min-height: 1.2em;
}

/* --- fireflies ---
   A dark meadow card, the same tape-and-gradient treatment /sky uses
   for its own always-dark figure — this scene is equally not tied to
   the viewer's real clock, for the same reason: it's demonstrating a
   mechanism, not standing in for the sky above you right now. */

.ff-figure {
  position: relative;
  margin: 2.25rem 0;
  padding: 1.5rem 1.5rem 1.1rem;
  background: linear-gradient(to bottom, #0a0f1c, #131b2e 70%, #0e1424);
  border: 1px solid #232c40;
  box-shadow: 0 1px 0 var(--line);
}

.ff-figure::before,
.ff-figure::after {
  content: "";
  position: absolute;
  width: 74px;
  height: 22px;
  background: var(--tape);
  top: -9px;
}

.ff-figure::before { left: 14%; transform: rotate(-3deg); }
.ff-figure::after { right: 14%; transform: rotate(2.5deg); }

#ff-svg {
  display: block;
  width: 100%;
  height: auto;
  max-height: 320px;
  cursor: crosshair;
}

.ff-ground {
  fill: #0c1710;
  opacity: 0.9;
}

.ff-bug .ff-halo {
  fill: #eaf28a;
  opacity: var(--ff-halo-glow, 0);
  filter: blur(2.5px);
  transition: opacity 0.26s ease-out;
}

.ff-bug .ff-core {
  fill: #eaf28a;
  opacity: var(--ff-core-glow, 0.16);
  transition: opacity 0.26s ease-out;
}

.ff-bug.flash .ff-halo { opacity: 0.75; }
.ff-bug.flash .ff-core { opacity: 1; }

@media (prefers-reduced-motion: reduce) {
  .ff-bug .ff-halo,
  .ff-bug .ff-core { transition: none; }
}

.ff-figure figcaption {
  border-top: 1px dashed #26304a;
  margin-top: 0.9rem;
  padding-top: 0.7rem;
  font-size: 0.75rem;
  color: #93a0c2;
}

.ff-actions {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 0.6rem;
  flex-wrap: wrap;
  margin: 1rem 0 0.5rem;
  text-align: center;
}

.ff-actions button {
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 0.85rem;
  color: var(--paper);
  background: var(--moss);
  border: 1px solid var(--moss-deep);
  border-radius: 3px;
  padding: 0.5rem 1rem;
  cursor: pointer;
}
.ff-actions button:hover { background: var(--moss-deep); }

.ff-status {
  width: 100%;
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 0.8rem;
  color: var(--faded);
  min-height: 1.2em;
}

.ff-sync {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  max-width: 360px;
  margin: 0.4rem auto 1.4rem;
}

.ff-sync-label {
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 0.75rem;
  color: var(--faded);
  white-space: nowrap;
}

.ff-sync-track {
  flex: 1;
  height: 6px;
  background: #131b2e;
  border: 1px solid #232c40;
  border-radius: 3px;
  overflow: hidden;
}

.ff-sync-fill {
  height: 100%;
  width: 0%;
  background: #4a5578; /* JS overwrites this every frame once a firefly exists — this is only what shows before that, a dim, unsynced slate */
  transition: width 0.12s linear;
}

@media (prefers-reduced-motion: reduce) {
  .ff-sync-fill { transition: none; }
}

.ff-sync-value {
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 0.75rem;
  color: var(--faded);
  min-width: 3ch;
  text-align: right;
}

/* --- pyrenoid ---
   A round chloroplast card, paper-and-tape framed like .specimen
   rather than fireflies' own dark meadow — this scene lives in daylit
   stroma, not a night sky, so it borrows the site's ordinary card
   palette instead of that room's slate-and-lamp one. See pyrenoid.js
   for the aggregation model and /pyrenoid for what it does and doesn't
   claim to simulate. */

.pyr-figure {
  position: relative;
  margin: 2.25rem 0;
  padding: 1.5rem 1.5rem 1.1rem;
  background: var(--card);
  border: 1px solid var(--line);
  box-shadow: 0 1px 0 var(--line);
  text-align: center;
}

.pyr-figure::before,
.pyr-figure::after {
  content: "";
  position: absolute;
  width: 74px;
  height: 22px;
  background: var(--tape);
  top: -9px;
}

.pyr-figure::before { left: 14%; transform: rotate(-3deg); }
.pyr-figure::after { right: 14%; transform: rotate(2.5deg); }

#pyr-svg {
  display: block;
  width: 100%;
  max-width: 320px;
  height: auto;
  margin: 0 auto;
}

.pyr-membrane {
  fill: color-mix(in srgb, var(--moss) 7%, transparent);
  stroke: var(--moss);
  stroke-width: 1.5;
  stroke-dasharray: 3 3;
}

.pyr-enzyme {
  fill: var(--moss-deep);
  stroke: none;
}

.pyr-figure figcaption {
  border-top: 1px dashed var(--line);
  margin-top: 0.9rem;
  padding-top: 0.7rem;
  font-size: 0.75rem;
  color: var(--faded);
}

.pyr-actions {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 0.9rem;
  flex-wrap: wrap;
  margin: 1rem 0 0.5rem;
  text-align: center;
}

.pyr-toggle {
  display: inline-flex;
  align-items: baseline;
  gap: 0.4rem;
  cursor: pointer;
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 0.85rem;
}

.pyr-toggle input { accent-color: var(--moss); }

.pyr-actions button {
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 0.85rem;
  color: var(--paper);
  background: var(--moss);
  border: 1px solid var(--moss-deep);
  border-radius: 3px;
  padding: 0.5rem 1rem;
  cursor: pointer;
}
.pyr-actions button:hover { background: var(--moss-deep); }

.pyr-status {
  width: 100%;
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 0.8rem;
  color: var(--faded);
  min-height: 1.2em;
}

.pyr-meter {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  max-width: 360px;
  margin: 0.4rem auto 1.4rem;
}

.pyr-meter-label {
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 0.75rem;
  color: var(--faded);
  white-space: nowrap;
}

.pyr-meter-track {
  flex: 1;
  height: 6px;
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: 3px;
  overflow: hidden;
}

.pyr-meter-fill {
  height: 100%;
  width: 0%;
  background: #8aa06a; /* JS overwrites this every frame once an enzyme exists — this is only what shows before that */
  transition: width 0.12s linear;
}

@media (prefers-reduced-motion: reduce) {
  .pyr-meter-fill { transition: none; }
}

.pyr-meter-value {
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 0.75rem;
  color: var(--faded);
  min-width: 3ch;
  text-align: right;
}

/* --- snare ---
   A ballista spider's own trap: a fan of tensioned silk lines holding
   a small wrapped cone, hung below the spider's real main web. Reuses
   --moss for silk (the same tone /pyrenoid gives a chloroplast
   membrane) and --floret for the cone and the ant, rather than
   inventing new tokens. See snare.js for the trigger and launch. */

.sn-figure {
  position: relative;
  margin: 2.25rem 0;
  padding: 1.5rem 1.5rem 1.1rem;
  background: var(--card);
  border: 1px solid var(--line);
  box-shadow: 0 1px 0 var(--line);
  text-align: center;
}

.sn-figure::before,
.sn-figure::after {
  content: "";
  position: absolute;
  width: 74px;
  height: 22px;
  background: var(--tape);
  top: -9px;
}

.sn-figure::before { left: 14%; transform: rotate(-3deg); }
.sn-figure::after { right: 14%; transform: rotate(2.5deg); }

#sn-svg {
  display: block;
  width: 100%;
  max-width: 300px;
  height: auto;
  margin: 0 auto;
}

.sn-web {
  fill: none;
  stroke: var(--line);
  stroke-width: 1;
}

.sn-strand {
  fill: none;
  stroke: var(--moss);
  stroke-width: 1;
  opacity: 0.85;
  transition: opacity 0.15s ease, stroke 0.15s ease;
}

.sn-strand-fired .sn-strand {
  opacity: 0.18;
  stroke: var(--faded);
}

.sn-cone {
  fill: var(--floret);
  stroke: var(--ink);
  stroke-width: 0.8;
}

.sn-ground {
  stroke: var(--line);
  stroke-width: 1.5;
}

.sn-ant {
  fill: var(--blush);
  stroke: var(--ink);
  stroke-width: 0.6;
}

.sn-caught-mark {
  fill: var(--blush);
  stroke: var(--ink);
  stroke-width: 0.6;
}

.sn-end-label {
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 7px;
  fill: var(--faded);
  text-anchor: middle;
}

.sn-figure figcaption {
  border-top: 1px dashed var(--line);
  margin-top: 0.9rem;
  padding-top: 0.7rem;
  font-size: 0.75rem;
  color: var(--faded);
}

.sn-actions {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 0.9rem;
  flex-wrap: wrap;
  margin: 1rem 0 0.3rem;
  text-align: center;
}

.sn-field {
  display: inline-flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 0.25rem;
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 0.8rem;
  color: var(--faded);
}

.sn-actions button {
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 0.85rem;
  color: var(--paper);
  background: var(--moss);
  border: 1px solid var(--moss-deep);
  border-radius: 3px;
  padding: 0.5rem 1rem;
  cursor: pointer;
}
.sn-actions button:hover:not(:disabled) { background: var(--moss-deep); }
.sn-actions button:disabled { opacity: 0.5; cursor: default; }

.sn-status {
  display: block;
  text-align: center;
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 0.8rem;
  color: var(--faded);
  min-height: 1.2em;
  margin-bottom: 0.6rem;
}

/* --- cone ---
   A real pine cone scale, drawn live. Every cell in it is dead by the
   time it can move at all — the scale bends because of how much water
   its own dead tissue holds, not because anything in it is alive.
   Reuses --floret (already the pollen/pore color elsewhere) for the
   scale's own woody tone and --stem-deep for the axis, rather than
   inventing a new palette. See cone.js for the humidity math. */

.cn-wrap {
  margin: 1.5rem 0;
}

#cn-svg {
  display: block;
  width: 100%;
  max-width: 260px;
  margin: 0 auto;
}

.cn-axis {
  stroke: var(--stem-deep);
  stroke-width: 3;
  stroke-linecap: round;
}

.cn-end-label {
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 9px;
  fill: var(--faded);
  text-anchor: middle;
}

.cn-ground {
  stroke: var(--line);
  stroke-width: 1;
  stroke-dasharray: 2 3;
}

.cn-scale {
  transform-box: fill-box;
  transform-origin: 0% 50%;
  transition: transform 1.6s ease-in-out;
}

@media (prefers-reduced-motion: reduce) {
  .cn-scale { transition: none; }
}

.cn-blade {
  fill: var(--floret);
  stroke: var(--stem-deep);
  stroke-width: 0.6;
  opacity: 0.92;
}

.cn-seed {
  fill: var(--floret);
  stroke: var(--stem-deep);
  stroke-width: 0.4;
}

.cn-controls {
  display: flex;
  flex-direction: column;
  gap: 0.7rem;
  max-width: 340px;
  margin: 1rem auto 0;
}

.cn-field {
  display: flex;
  flex-direction: column;
  gap: 0.25rem;
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 0.85rem;
  color: var(--faded);
}

.cn-field input[type="range"] {
  width: 100%;
  accent-color: var(--moss);
}

.cn-presets {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
  justify-content: center;
}

.cn-presets button {
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 0.8rem;
  color: var(--ink);
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: 3px;
  padding: 0.4rem 0.65rem;
  cursor: pointer;
}
.cn-presets button:hover { border-color: var(--moss); color: var(--moss); }

.cn-status {
  display: block;
  text-align: center;
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 0.8rem;
  color: var(--faded);
  min-height: 1.2em;
}

/* --- /pod: a jewelweed seed pod --- */

.pd-wrap {
  margin: 1.5rem 0;
}

#pd-svg {
  display: block;
  width: 100%;
  max-width: 340px;
  margin: 0 auto;
}

.pd-pedicel {
  fill: none;
  stroke: var(--stem-deep);
  stroke-width: 2;
  stroke-linecap: round;
}

.pd-ground {
  stroke: var(--line);
  stroke-width: 1;
  stroke-dasharray: 2 3;
}

.pd-tick {
  stroke: var(--line);
  stroke-width: 1;
}

.pd-tick-label {
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 8px;
  fill: var(--faded);
  text-anchor: middle;
}

.pd-valve {
  transform-box: fill-box;
  transform-origin: 50% 0%;
  fill: var(--floret);
  stroke: var(--stem-deep);
  stroke-width: 0.6;
  transition: transform 0.28s cubic-bezier(0.3, 1.5, 0.6, 1);
}

.pd-open .pd-valve-1 { transform: rotate(-150deg) translateX(-3px); }
.pd-open .pd-valve-2 { transform: rotate(-60deg) translateX(-1px); }
.pd-open .pd-valve-3 { transform: rotate(60deg) translateX(1px); }
.pd-open .pd-valve-4 { transform: rotate(150deg) translateX(3px); }

@media (prefers-reduced-motion: reduce) {
  .pd-valve { transition: none; }
}

.pd-seed, .pd-landing {
  fill: var(--floret);
  stroke: var(--stem-deep);
  stroke-width: 0.4;
}

.pd-landing { opacity: 0.65; }

.pd-hit {
  fill: transparent;
  cursor: pointer;
}
.pd-hit:hover { fill: var(--moss); opacity: 0.14; }
.pd-hit:focus-visible {
  outline: 2px solid var(--moss-deep);
  outline-offset: 2px;
}

.pd-controls {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.5rem;
  margin-top: 0.75rem;
}

.pd-controls button {
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 0.8rem;
  color: var(--ink);
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: 3px;
  padding: 0.4rem 0.65rem;
  cursor: pointer;
}
.pd-controls button:hover { border-color: var(--moss); color: var(--moss); }

.pd-status {
  display: block;
  text-align: center;
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 0.8rem;
  color: var(--faded);
  min-height: 1.2em;
}

/* --- /map: the garden plan --- */

.mp-defs { position: absolute; width: 0; height: 0; overflow: hidden; }

.mp-icon {
  width: 1.5rem;
  height: 1.5rem;
  stroke: var(--stem);
  fill: none;
  stroke-width: 1.6;
  stroke-linecap: round;
  stroke-linejoin: round;
}

.mp-path {
  display: flex;
  flex-wrap: wrap;
  gap: 0.6rem;
  list-style: none;
  margin: 0 0 1.75rem;
  padding: 0;
}

.mp-path a {
  display: flex;
  align-items: center;
  gap: 0.45rem;
  padding: 0.5rem 0.85rem;
  border: 1px solid var(--line);
  border-radius: 999px;
  background: var(--card);
  color: var(--ink);
  text-decoration: none;
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 0.85rem;
}

.mp-path a:hover,
.mp-path a:focus-visible { border-color: var(--moss); color: var(--moss-deep); }
.mp-path .mp-icon { width: 1.1rem; height: 1.1rem; }

.mp-row {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(9.5rem, 1fr));
  gap: 0.85rem;
  list-style: none;
  margin: 0 0 1.5rem;
  padding: 0;
}

.mp-bed a {
  display: flex;
  flex-direction: column;
  gap: 0.3rem;
  height: 100%;
  padding: 0.9rem 0.95rem 0.75rem;
  border: 1px dashed var(--line);
  border-radius: 4px;
  background:
    repeating-linear-gradient(180deg, var(--grid) 0, var(--grid) 1px, transparent 1px, transparent 9px),
    var(--card);
  color: var(--ink);
  text-decoration: none;
}

.mp-bed a:hover,
.mp-bed a:focus-visible {
  border-color: var(--moss);
  border-style: solid;
}

.mp-bed a:hover .mp-icon,
.mp-bed a:focus-visible .mp-icon { stroke: var(--moss-deep); }

.mp-name {
  font-family: "Young Serif", Georgia, serif;
  font-size: 1.1rem;
}

.mp-desc {
  font-size: 0.85rem;
  color: var(--faded);
  line-height: 1.4;
  flex-grow: 1;
}

.mp-tag {
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 0.66rem;
  letter-spacing: 0.04em;
  color: var(--faded);
  opacity: 0.8;
  padding-top: 0.2rem;
  border-top: 1px dotted var(--line);
}

/* --- /pulse: an oscilloscope, not a paper card on purpose. The stem
   diagram above it stays this site's usual parchment; the screen below
   deliberately doesn't, the same one departure /fireflies' meadow
   already made for a different reason (there: it's a night scene with
   no time of day; here: a screen isn't paper, so it doesn't pretend to
   be one, in light mode or dark). */

.ps-wrap {
  margin: 1.5rem 0;
}

#ps-stem-svg {
  display: block;
  width: 100%;
  max-width: 380px;
  margin: 0 auto;
}

.ps-stem-line {
  stroke: var(--stem-deep);
  stroke-width: 2;
}

.ps-base {
  fill: var(--floret);
  stroke: var(--stem-deep);
  stroke-width: 1;
}

.ps-stem-label {
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 8px;
  fill: var(--faded);
  text-anchor: middle;
}

.ps-probe { cursor: pointer; }

.ps-probe-stick {
  stroke: var(--line);
  stroke-width: 1.5;
}

.ps-probe-dot {
  fill: var(--card);
  stroke: var(--stem-deep);
  stroke-width: 1.5;
}

.ps-probe-label {
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 8px;
  fill: var(--faded);
  text-anchor: middle;
}

.ps-probe:hover .ps-probe-dot { stroke: var(--moss); }
.ps-probe:focus-visible .ps-probe-dot {
  outline: 2px solid var(--moss-deep);
  outline-offset: 2px;
}

.ps-probe-selected .ps-probe-dot {
  fill: var(--moss);
  stroke: var(--moss-deep);
}

.ps-actions {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 0.6rem;
  flex-wrap: wrap;
  margin: 0.9rem 0;
}

.ps-actions button {
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 0.8rem;
  color: var(--ink);
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: 3px;
  padding: 0.45rem 0.75rem;
  cursor: pointer;
}
.ps-actions button:hover:not(:disabled) { border-color: var(--moss); color: var(--moss); }
.ps-actions button:disabled { opacity: 0.5; cursor: default; }

.ps-scope {
  position: relative;
  margin: 1.25rem 0 0;
  padding: 0.9rem 0.9rem 0.7rem;
  background: linear-gradient(180deg, #05130a 0%, #081f10 85%);
  border: 1px solid #16351f;
  border-radius: 4px;
}

#ps-scope-svg {
  display: block;
  width: 100%;
  height: auto;
  max-height: 200px;
}

.ps-baseline {
  stroke: #1f4a2a;
  stroke-width: 1;
  stroke-dasharray: 3 3;
}

.ps-scope-label {
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 8px;
  fill: #4f8f63;
}

.ps-sweep {
  stroke: #7dffab;
  stroke-width: 1;
  opacity: 0.55;
}

.ps-trace {
  fill: none;
  stroke: #4dff8f;
  stroke-width: 1.6;
  filter: drop-shadow(0 0 2px rgba(77, 255, 143, 0.6));
}

.ps-scope figcaption {
  border-top: 1px dashed #16351f;
  margin-top: 0.6rem;
  padding-top: 0.5rem;
}

.ps-status {
  display: block;
  text-align: center;
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 0.78rem;
  color: #8fd6a6;
  min-height: 1.2em;
}

/* --- /compost
   The moderation bin, drawn as an actual heap instead of a plain
   list — paper-and-tape framing like footfall and growth rings, since
   this is a record, not a night scene. */

.cp-figure {
  position: relative;
  margin: 2.25rem 0;
  padding: 1.5rem 1.5rem 1.1rem;
  background: var(--card);
  border: 1px solid var(--line);
  box-shadow: 0 1px 0 var(--line);
}

.cp-figure::before,
.cp-figure::after {
  content: "";
  position: absolute;
  width: 74px;
  height: 22px;
  background: var(--tape);
  top: -9px;
}

.cp-figure::before { left: 14%; transform: rotate(-3deg); }
.cp-figure::after { right: 14%; transform: rotate(2.5deg); }

#cp-svg {
  display: block;
  width: 100%;
  height: auto;
  max-height: 300px;
  margin: 0 auto;
}

.cp-bin-fill {
  fill: var(--grid);
}

.cp-bin-outline {
  fill: none;
  stroke: var(--stem-deep);
  stroke-width: 1.6;
}

.cp-mound {
  transition: filter 0.2s ease;
}

.cp-layer { cursor: pointer; }
.cp-layer:hover .cp-mound { filter: brightness(1.08); }
.cp-layer.selected .cp-mound { filter: brightness(1.15); }
.cp-layer:focus-visible { outline: none; }
.cp-layer:focus-visible .cp-mound { stroke: var(--ink); stroke-width: 1.4; }

.cp-figure figcaption {
  border-top: 1px dashed var(--line);
  margin-top: 0.9rem;
  padding-top: 0.7rem;
  font-size: 0.75rem;
  color: var(--faded);
}

.cp-detail {
  min-height: 3.5rem;
}

.cp-detail p:not(.label) {
  margin-top: 0.35rem;
}

/* --- /trap
   A Venus flytrap, drawn front-on as two hinged lobes. Open by
   default (no transform); a .tp-shut class on the wrapper rotates
   both lobes toward vertical to close, around the shared hinge at
   the bottom center of the viewBox. */

.tp-wrap {
  margin: 1.5rem 0;
}

#tp-svg {
  display: block;
  width: 100%;
  max-width: 280px;
  margin: 0 auto;
}

.tp-pedicel {
  stroke: var(--stem-deep);
  stroke-width: 3;
  stroke-linecap: round;
}

.tp-lobe {
  transform-box: view-box;
  transform-origin: 120px 170px;
  transition: transform 0.4s cubic-bezier(0.3, 1.4, 0.5, 1);
}

.tp-lobe path {
  fill: var(--trap-lure);
  stroke: var(--stem-deep);
  stroke-width: 1.4;
  stroke-linejoin: round;
}

.tp-shut .tp-lobe-l { transform: rotate(40deg); }
.tp-shut .tp-lobe-r { transform: rotate(-40deg); }

@media (prefers-reduced-motion: reduce) {
  .tp-lobe { transition: none; }
}

.tp-hair line {
  stroke: var(--ink);
  stroke-width: 1.2;
}
.tp-hair circle {
  fill: var(--ink);
}

.tp-hit {
  fill: transparent;
  cursor: pointer;
}
.tp-hit:hover { fill: var(--moss); opacity: 0.12; }
.tp-hit:focus-visible {
  outline: 2px solid var(--moss-deep);
  outline-offset: 2px;
}

.tp-digesting .tp-lobe path {
  fill: color-mix(in srgb, var(--trap-lure) 60%, var(--floret));
}

.tp-meter,
.tp-dots-row {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  margin: 0.75rem auto 0;
  max-width: 320px;
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 0.75rem;
  color: var(--faded);
}

.tp-meter-label {
  flex: 0 0 auto;
  width: 5.2rem;
}

.tp-meter-track {
  flex: 1 1 auto;
  height: 0.5rem;
  background: var(--grid);
  border: 1px solid var(--line);
  border-radius: 3px;
  overflow: hidden;
}

.tp-meter-fill {
  height: 100%;
  width: 0%;
  background: var(--moss);
}

.tp-meter-caption {
  flex: 0 0 auto;
  min-width: 8rem;
  text-align: right;
}

.tp-dots {
  display: flex;
  gap: 0.35rem;
}

.tp-dot {
  width: 0.6rem;
  height: 0.6rem;
  border-radius: 50%;
  border: 1px solid var(--line);
  background: transparent;
}

.tp-dot-filled { background: var(--moss); border-color: var(--moss-deep); }
.tp-dot-hot { background: var(--trap-lure); border-color: var(--trap-lure); }

.tp-controls {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.5rem;
  margin-top: 0.9rem;
}

.tp-controls button {
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 0.8rem;
  color: var(--ink);
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: 3px;
  padding: 0.4rem 0.65rem;
  cursor: pointer;
}
.tp-controls button:hover { border-color: var(--moss); color: var(--moss); }

.tp-status {
  display: block;
  text-align: center;
  max-width: 34rem;
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 0.8rem;
  color: var(--faded);
  min-height: 1.2em;
}

/* Weeds — /weeds. Deliberately not another verified specimen room:
   a small bed of ordinary lawn plants, drawn a little uneven on
   purpose (leaf sizes and stem curves are hand-picked, not mirrored
   or measured) rather than the clean bilateral symmetry most other
   rooms use for their by-hand SVG. No new custom properties — every
   stroke and fill below reuses tokens already defined at the top of
   this file, the same discipline every other room keeps. */
.wd-bed {
  position: relative;
  margin: 2.25rem 0;
  padding: 1.25rem 1rem 0.9rem;
  background: var(--card);
  border: 1px solid var(--line);
  box-shadow: 0 1px 0 var(--line);
}
.wd-bed svg { width: 100%; height: auto; display: block; }
.wd-bed figcaption {
  margin-top: 0.6rem;
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 0.78rem;
  color: var(--faded);
  text-align: center;
}

.wd-ground { fill: none; stroke: var(--line); stroke-width: 1.5; }

.wd-weed { cursor: default; }
.wd-weed:focus-visible { outline: 2px solid var(--moss); outline-offset: 4px; }
.wd-weed:focus-visible .wd-stem,
.wd-weed:focus-visible .wd-vine,
.wd-weed:focus-visible .wd-spike-stem { stroke: var(--moss-deep); stroke-width: 2.4; }

.wd-stem, .wd-vine, .wd-spike-stem {
  fill: none;
  stroke: var(--stem-deep);
  stroke-width: 2;
  stroke-linecap: round;
}
.wd-leaf, .wd-leaf-broad, .wd-leaflet, .wd-blade, .wd-heart {
  fill: var(--leaf-a);
  stroke: var(--stem-deep);
  stroke-width: 1;
}
.wd-leaflet:nth-of-type(2n) { fill: var(--leaf-b); }
.wd-clock line { stroke: var(--ground-lichen-b); stroke-width: 1.1; }
.wd-clock-head { fill: var(--ground-lichen-a); stroke: var(--stem-deep); stroke-width: 1; }
.wd-spike { fill: var(--floret); stroke: var(--stem-deep); stroke-width: 1; }
.wd-trumpet { fill: var(--dew); stroke: var(--stem-deep); stroke-width: 1; }
.wd-blade { fill: none; stroke: var(--stem); stroke-width: 2.4; stroke-linecap: round; }
.wd-search-tip { fill: none; stroke: var(--stem); stroke-width: 2; stroke-linecap: round; }

/* The bindweed's bare tip (2026-08-24) revolves continuously, on top
   of the shared hover tilt below — a real climbing shoot keeps
   sweeping a slow search circle even once the rest of the vine has
   already found its support and flowered; see weeds.html's own
   prose and citation. transform-origin is set inline in the SVG's
   own view-box coordinates (546px 44px — the tip's local (26,44)
   plus this weed group's own translate(520,0)), the same technique
   plant.js's leaf flutter uses, computed once by hand here since
   this markup is static, not generated. One turn every 102s (one
   real minute compressed to one page-second, disclosed on the page,
   not claimed as real time) — deliberately not the small ±0.9deg
   .specimen .tip wobble era 7 draws; a different, much bigger real
   motion, not a restyled copy of that one. */
@media (prefers-reduced-motion: no-preference) {
  .wd-search-tip {
    transform-box: view-box;
    animation: wd-search 102s linear infinite;
  }
  @keyframes wd-search {
    to { transform: rotate(-360deg); }
  }
}

/* Real weeds don't hold still, and there's no wind mechanism here to
   drive one — a visitor's own attention stands in for it: hover or
   tab to a weed and it shifts, a few degrees, from whatever its own
   base line already was. */
@media (prefers-reduced-motion: no-preference) {
  .wd-shape {
    transition: transform 0.6s ease;
    transform-box: fill-box;
    transform-origin: 50% 100%;
  }
  .wd-weed:hover .wd-shape, .wd-weed:focus-visible .wd-shape { transform: rotate(-2.5deg); }
  .wd-weed:nth-of-type(2n):hover .wd-shape, .wd-weed:nth-of-type(2n):focus-visible .wd-shape { transform: rotate(2.5deg); }
}

/* The dandelion clock (2026-08-18) is the one weed that answers to a
   click, not just a hover or a tab — see weeds.js. Each filament's
   own SVG rotate() sits on its outer .wd-filament group, untouched by
   CSS, exactly the split this room's first draft learned it needed
   the hard way (see plots.md): the inner .wd-filament-inner carries
   no attribute transform of its own, so CSS is free to translate it
   within its filament's already-rotated local frame, which is what
   sends each one drifting outward along its own real direction rather
   than one shared one. */
.wd-weed[role="button"] { cursor: pointer; }

@media (prefers-reduced-motion: no-preference) {
  .wd-filament-inner { transform-box: view-box; }
  .wd-blown .wd-filament-inner { animation: wd-drift 0.9s ease-in forwards; }
  .wd-filament:nth-child(3n+1) .wd-filament-inner { animation-delay: 0s; }
  .wd-filament:nth-child(3n+2) .wd-filament-inner { animation-delay: 0.12s; }
  .wd-filament:nth-child(3n) .wd-filament-inner { animation-delay: 0.24s; }
  @keyframes wd-drift {
    to { transform: translate(6px, -34px) rotate(12deg); opacity: 0; }
  }
  .wd-clock-head { transition: r 0.3s ease 0.5s; }
  .wd-blown .wd-clock-head { r: 3; }
}
@media (prefers-reduced-motion: reduce) {
  .wd-blown .wd-filament-inner { opacity: 0; }
  .wd-blown .wd-clock-head { r: 3; }
}

/* The clover (2026-08-28) answers to the same click gesture as the
   dandelion, but its find is probabilistic — see weeds.js. Same
   attribute-transform split the dandelion's filaments already
   forced (see the comment above and plots.md): the fourth leaflet's
   own -45deg tilt is an SVG transform= attribute on a wrapping <g>
   in weeds.html, untouched by CSS; .wd-leaflet-4 itself, the ellipse
   one level in, carries no attribute transform of its own, so it's
   free for the scale below to animate from its own fill-box center
   without CSS clobbering the tilt. .wd-clover-inner is the same
   split again, one level up, so the rummage shake can rotate the
   whole cluster without touching .wd-clover's own attribute
   translate(19,148). .wd-clover-spark is four small plain circles at
   the new leaflet's own coordinates, invisible except during the
   reveal — var(--petal), already this palette's gold, not a new
   token. */
@media (prefers-reduced-motion: no-preference) {
  .wd-leaflet-4 {
    transform-box: fill-box;
    transform-origin: 50% 50%;
    transform: scale(0);
    opacity: 0;
    transition: transform 0.45s cubic-bezier(0.34, 1.56, 0.64, 1), opacity 0.3s ease;
  }
  .wd-lucky .wd-leaflet-4 { transform: scale(1); opacity: 1; }

  .wd-clover-spark circle { fill: var(--petal); opacity: 0; transform-box: fill-box; transform-origin: 50% 50%; }
  .wd-lucky .wd-clover-spark circle { animation: wd-spark 0.6s ease-out; }
  .wd-clover-spark circle:nth-child(2) { animation-delay: 0.04s; }
  .wd-clover-spark circle:nth-child(3) { animation-delay: 0.08s; }
  .wd-clover-spark circle:nth-child(4) { animation-delay: 0.02s; }
  @keyframes wd-spark {
    0% { opacity: 0; transform: scale(0.4); }
    45% { opacity: 1; transform: scale(1.3); }
    100% { opacity: 0; transform: scale(1.9); }
  }

  .wd-clover-inner { transform-box: fill-box; transform-origin: 50% 100%; }
  #wd-clover.wd-searching .wd-clover-inner { animation: wd-rummage 0.26s ease; }
  @keyframes wd-rummage {
    0%, 100% { transform: rotate(0deg); }
    30% { transform: rotate(-6deg); }
    70% { transform: rotate(6deg); }
  }
}
@media (prefers-reduced-motion: reduce) {
  .wd-leaflet-4 { opacity: 0; }
  .wd-lucky .wd-leaflet-4 { opacity: 1; }
  .wd-clover-spark circle { opacity: 0; }
}

/* The crabgrass (2026-08-29) answers to the same click gesture too,
   and unlike the dandelion or the clover it's a plain toggle with no
   randomness — the flowering head is either up or it isn't. Same
   split as the other two: .wd-crab-head is the CSS animation target
   (scale + opacity, transform-box: fill-box), and every finger inside
   it keeps its own fan angle as a plain SVG rotate() attribute on its
   own <g>, untouched by CSS, so the reveal scales the whole cluster
   without disturbing how it fans. See weeds.html's own caption for
   why four fingers, not a real head's three to thirteen. */
.wd-crab-culm { fill: none; stroke: var(--stem-deep); stroke-width: 1.8; stroke-linecap: round; }
.wd-crab-raceme line { stroke: var(--stem); stroke-width: 1.6; stroke-linecap: round; }
.wd-crab-raceme circle { fill: var(--floret); }

@media (prefers-reduced-motion: no-preference) {
  .wd-crab-head {
    transform-box: fill-box;
    transform-origin: 50% 100%;
    transform: scale(0.15);
    opacity: 0;
    transition: transform 0.5s cubic-bezier(0.34, 1.56, 0.64, 1), opacity 0.35s ease;
  }
  .wd-flowering .wd-crab-head { transform: scale(1); opacity: 1; }
}
@media (prefers-reduced-motion: reduce) {
  .wd-crab-head { opacity: 0; }
  .wd-flowering .wd-crab-head { opacity: 1; }
}

/* The plantain (2026-08-29) answers to the same click gesture as the
   other four, and like the clover its outcome is a coin flip — see
   weeds.js and weeds.html's own caption for the real duel this
   mimics. Two parts: the stem's own quick downward "flick" (a
   temporary class, removed after the animation ends, so it can play
   again on every click) and the head's fall, which is a persistent
   state like the clover's own luck. .wd-plantain-head wraps both
   wd-spike paths in a plain <g> with no attribute transform of its
   own, the same split .wd-crab-head already uses, so the fall
   animation moves the whole head without touching either spike's own
   attribute transform (one has none, one carries translate(0,-4)). */
@media (prefers-reduced-motion: no-preference) {
  .wd-spike-stem { transform-box: fill-box; transform-origin: 50% 100%; }
  .wd-plantain-swing .wd-spike-stem { animation: wd-flick 0.35s ease; }
  @keyframes wd-flick {
    0% { transform: rotate(0deg); }
    35% { transform: rotate(-20deg); }
    70% { transform: rotate(6deg); }
    100% { transform: rotate(0deg); }
  }

  .wd-plantain-head {
    transform-box: fill-box;
    transform-origin: 50% 100%;
    transition: transform 0.4s ease-in 0.08s, opacity 0.35s ease 0.08s;
  }
  .wd-head-fallen .wd-plantain-head { transform: translate(16px, 22px) rotate(75deg); opacity: 0; }
}
@media (prefers-reduced-motion: reduce) {
  .wd-head-fallen .wd-plantain-head { opacity: 0; }
}

/* --- bouquet ---
   A hand-tied bunch, one flower per line currently in the guestbook.
   Paper-card frame like .wd-bed — this sits on the same table, not
   the sky's dark night. */

.bq-bed {
  position: relative;
  margin: 2.25rem 0;
  padding: 1.25rem 1rem 0.9rem;
  background: var(--card);
  border: 1px solid var(--line);
  box-shadow: 0 1px 0 var(--line);
}
#bq-svg { display: block; width: 100%; height: auto; max-height: 460px; }
.bq-bed figcaption {
  margin-top: 0.6rem;
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 0.78rem;
  color: var(--faded);
  text-align: center;
}

.bq-ribbon path { fill: color-mix(in srgb, var(--petal) 70%, var(--floret)); stroke: var(--stem-deep); stroke-width: 1; }
.bq-ribbon-tail { opacity: 0.9; }

.bq-stem {
  fill: none;
  stroke: var(--stem-deep);
  stroke-width: 2;
  stroke-linecap: round;
}
.bq-leaf { stroke: var(--stem-deep); stroke-width: 0.8; }

.bq-flower { cursor: pointer; }
.bq-flower:focus-visible { outline: none; }
.bq-flower:focus-visible .bq-head { outline: 2px solid var(--moss); outline-offset: 3px; }
.bq-flower:focus-visible .bq-stem,
.bq-flower:hover .bq-stem { stroke: var(--moss-deep); stroke-width: 2.6; }
.bq-flower.selected .bq-stem { stroke: var(--moss-deep); stroke-width: 3; }
.bq-flower.selected .bq-head { filter: drop-shadow(0 0 2px var(--moss)); }

.bq-detail {
  min-height: 3.5rem;
  padding: 0.9rem 1rem;
  background: var(--card);
  border: 1px solid var(--line);
}
.bq-detail p:not(.label) { margin-top: 0.35rem; }

@media (prefers-reduced-motion: no-preference) {
  .bq-head { transition: transform 0.4s ease; transform-box: fill-box; transform-origin: center; }
  .bq-flower:hover .bq-head, .bq-flower:focus-visible .bq-head { transform: scale(1.12); }
}

/* --- the beach ball ---
   A bounded play court, not a viewport-wide overlay: ball.js's own
   physics only ever run inside .bb-court's own box, so nothing here
   can drift over the header, footer, or another page's content. A
   beach ball's stripes are a fixed real-world convention, not a mood
   this site's day/night palette should tint — fixed hex colors, no
   new custom property, the same choice fireflies.js already made for
   its own always-dark oscilloscope screen. */
.bb-actions {
  display: flex;
  gap: 0.75rem;
  margin: 0.9rem 0 0.6rem;
}
.bb-launch, .bb-away {
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 0.85rem;
  color: var(--paper);
  background: var(--moss);
  border: 1px solid var(--moss-deep);
  border-radius: 3px;
  padding: 0.5rem 1rem;
  cursor: pointer;
}
.bb-launch:hover, .bb-away:hover { background: var(--moss-deep); }
.bb-court {
  position: relative;
  width: 100%;
  height: 220px;
  border: 1px solid var(--line);
  border-radius: 6px;
  background: var(--card);
  overflow: hidden;
  touch-action: none;
}
.bb-ball {
  position: absolute;
  top: 0;
  left: 0;
  width: 46px;
  height: 46px;
  border-radius: 50%;
  border: 1px solid rgba(0, 0, 0, 0.25);
  cursor: grab;
  background: conic-gradient(
    #f2f0e6 0deg 60deg,
    #c1443c 60deg 120deg,
    #f2f0e6 120deg 180deg,
    #3d6b93 180deg 240deg,
    #f2f0e6 240deg 300deg,
    #d9a441 300deg 360deg
  );
  box-shadow:
    inset -5px -5px 9px rgba(0, 0, 0, 0.25),
    inset 4px 4px 7px rgba(255, 255, 255, 0.45);
}
.bb-ball:active { cursor: grabbing; }
.bb-ball:focus-visible { outline: 2px solid var(--moss); outline-offset: 3px; }

/* --- wind chimes ---
   Rides directly on wind.js's own live reading — see chime.js. No new
   custom property: tubes reuse --floret (the same warm accent this
   site already uses for florets and pod valves), strings reuse
   --line, the hanging bar reuses --stem-deep. */
.wc-wrap { margin: 0.6rem 0 1rem; }
.wc-chimes {
  display: block;
  width: 212px;
  max-width: 100%;
  height: auto;
  margin-bottom: 0.5rem;
}
.wc-bar { stroke: var(--stem-deep); stroke-width: 3; stroke-linecap: round; }
.wc-string { stroke: var(--line); stroke-width: 1; }
.wc-tube {
  fill: var(--floret);
  stroke: var(--stem-deep);
  stroke-width: 0.7;
  transform-box: fill-box;
  transform-origin: top center;
}
.wc-toggle {
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 0.85rem;
  color: var(--paper);
  background: var(--moss);
  border: 1px solid var(--moss-deep);
  border-radius: 3px;
  padding: 0.5rem 1rem;
  cursor: pointer;
}
.wc-toggle:hover { background: var(--moss-deep); }
@media (prefers-reduced-motion: no-preference) {
  .wc-tube { transition: transform 0.22s ease; }
  .wc-tube.wc-struck { transform: rotate(4deg); }
}

/* --- /roots — hydropatterning cross-section ---
   A soil bed drawn dark on purpose (the second buried-view room to
   leave the parchment card behind, after pulse's oscilloscope and
   fireflies' meadow): you can't draw what's underground on paper the
   color of daylight. The taproot never bends toward water; only the
   laterals answer to it, so nothing here reads a date or plant.js. */
.rt-wrap { margin: 1.2rem 0 1rem; }
.rt-wrap svg {
  width: 100%;
  height: auto;
  display: block;
  border: 1px solid var(--line);
  border-radius: 6px;
  background: var(--soil-deep);
}
.rt-sky { fill: var(--paper); }
.rt-soil {
  fill: var(--soil);
}
.rt-surface { stroke: var(--root-line); stroke-width: 1; opacity: 0.5; }
.rt-shoot { fill: none; stroke: var(--stem); stroke-width: 2.4; stroke-linecap: round; }
.rt-cotyledon { fill: var(--leaf-a); stroke: var(--stem-deep); stroke-width: 0.8; }
.rt-taproot {
  fill: none;
  stroke: var(--root-pale);
  stroke-width: 5;
  stroke-linecap: round;
}
.rt-lat-main {
  fill: none;
  stroke: var(--root-pale);
  stroke-width: 2.6;
  stroke-linecap: round;
}
.rt-rootlet {
  fill: none;
  stroke: var(--root-line);
  stroke-width: 1.4;
  stroke-linecap: round;
}
.rt-moist {
  fill: var(--moist-fill);
  stroke: var(--moist-line);
  stroke-width: 1.4;
  stroke-dasharray: 4 3;
  cursor: grab;
}
.rt-moist:active { cursor: grabbing; }
.rt-moist:focus-visible { outline: 2px solid var(--moss); outline-offset: 2px; }
.rt-actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.6rem;
  margin-top: 0.7rem;
}
.rt-actions button {
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 0.82rem;
  color: var(--paper);
  background: var(--moss);
  border: 1px solid var(--moss-deep);
  border-radius: 3px;
  padding: 0.45rem 0.9rem;
  cursor: pointer;
}
.rt-actions button:hover { background: var(--moss-deep); }
.rt-actions button:focus-visible { outline: 2px solid var(--moss); outline-offset: 2px; }
.rt-actions .label { margin: 0; flex: 1 1 12rem; }

@media (prefers-reduced-motion: reduce) {
  .rt-lat-main { transition: none !important; }
}

/* /plumb — gravitropism. Tilt the pot: the seedling and the root-cap
   cell rotate with it, but the growing tips re-aim to the plumb line
   and the starch grains hold at true down. Dark card like /roots; the
   underground and the cell interior can't be drawn on daylight paper.
   No date, no plant.js, no rng() — pure geometry from the tilt. */
.pl-wrap { margin: 1.2rem 0 1rem; }
.pl-wrap svg {
  width: 100%;
  max-width: 300px;
  height: auto;
  display: block;
  margin: 0 auto;
  border: 1px solid var(--line);
  border-radius: 6px;
  background: var(--soil-deep);
}
.pl-plumb {
  stroke: var(--root-line);
  stroke-width: 1;
  stroke-dasharray: 3 4;
  opacity: 0.7;
}
.pl-tag {
  fill: var(--root-pale);
  opacity: 0.72;
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 9px;
}
.pl-soil { fill: var(--soil); stroke: var(--root-line); stroke-width: 1; }
.pl-stem { fill: none; stroke: var(--stem); stroke-width: 3; stroke-linecap: round; }
.pl-root { fill: none; stroke: var(--root-pale); stroke-width: 3.4; stroke-linecap: round; }
.pl-leaf { fill: var(--leaf-a); stroke: var(--stem-deep); stroke-width: 0.6; }
.pl-zoom { fill: none; stroke: var(--root-line); stroke-width: 1; stroke-dasharray: 2 3; opacity: 0.7; }
.pl-cell { fill: var(--soil); stroke: var(--root-pale); stroke-width: 2; opacity: 0.95; }
.pl-cell-mark { fill: var(--root-pale); opacity: 0.4; }
.pl-nucleus { fill: var(--floret); opacity: 0.5; }
.pl-grain { fill: var(--petal); stroke: var(--floret); stroke-width: 0.6; }
.pl-gvec line { stroke: var(--root-pale); stroke-width: 1.6; }
.pl-gvec path { fill: var(--root-pale); }
#pl-body, #pl-cell-rot { transition: transform 0.45s ease; }
.pl-actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.7rem;
  margin-top: 0.8rem;
}
.pl-actions .pl-label {
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 0.82rem;
  color: var(--faded);
}
#pl-tilt { flex: 1 1 12rem; accent-color: var(--moss); cursor: pointer; }
#pl-tilt:focus-visible { outline: 2px solid var(--moss); outline-offset: 3px; }
#pl-tilt:disabled { opacity: 0.5; cursor: default; }
.pl-readout {
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 0.82rem;
  color: var(--ink);
  min-width: 5.5rem;
}
.pl-device-btn {
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 0.8rem;
  color: var(--ink);
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: 3px;
  padding: 0.4rem 0.65rem;
  cursor: pointer;
}
.pl-device-btn:hover { border-color: var(--moss); color: var(--moss); }

@media (prefers-reduced-motion: reduce) {
  #pl-body, #pl-cell-rot { transition: none !important; }
}

/* Live device tilt tracks the sensor as it arrives — a queued 0.45s
   ease behind it would read as lag, not response, so it's suspended
   for as long as #pl-svg carries this class. */
#pl-svg.pl-live #pl-body, #pl-svg.pl-live #pl-cell-rot { transition: none; }

/* /brace — proprioception-driven reaction wood. Bend a sapling with
   the slider and let weeks pass: the poplar grows an amber wedge of
   tension wood on the convex flank, the pine a rust (--blush) wedge
   of compression wood on the concave flank instead (drawn along the
   trunk and in the small cross-section inset), and the lean eases
   back either way. Daylight card, unlike /plumb and /roots' dark
   soil — this scene never goes underground. No date, no plant.js,
   no rng(). */
.br-wrap { margin: 1.2rem 0 1rem; }
.br-wrap svg {
  width: 100%;
  max-width: 320px;
  height: auto;
  display: block;
  margin: 0 auto;
  border: 1px solid var(--line);
  border-radius: 6px;
  background: var(--card);
}
.br-ref { stroke: var(--line); stroke-width: 1; stroke-dasharray: 3 4; opacity: 0.8; }
.br-connector { fill: none; stroke: var(--line); stroke-width: 1; stroke-dasharray: 2 3; opacity: 0.7; }
.br-tag {
  fill: var(--faded);
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 8.5px;
}
.br-inset-main { fill: var(--stem-deep); stroke: var(--line); stroke-width: 1; }
.br-inset-band { fill: var(--floret); opacity: 0.92; }
.br-inset-band.br-compression { fill: var(--blush); }
.br-inset-pith { fill: var(--soil); }
.br-soil { fill: var(--soil); stroke: var(--root-line); stroke-width: 1; }
.br-trunk { fill: var(--stem-deep); }
.br-band { fill: var(--floret); opacity: 0.92; }
.br-band.br-compression { fill: var(--blush); }
.br-species {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 0.75rem;
  border: 1px solid var(--line);
  border-radius: 4px;
  padding: 0.5rem 0.75rem;
  margin: 0 auto;
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 0.78rem;
  max-width: 320px;
}
.br-species legend {
  font-size: 0.72rem;
  color: var(--faded);
  padding: 0 0.3rem;
}
.br-species label {
  display: flex;
  align-items: center;
  gap: 0.3rem;
  cursor: pointer;
}
.br-actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.7rem;
  margin-top: 0.8rem;
}
.br-actions .br-label {
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 0.82rem;
  color: var(--faded);
}
#br-bend { flex: 1 1 12rem; accent-color: var(--moss); cursor: pointer; }
#br-bend:focus-visible { outline: 2px solid var(--moss); outline-offset: 3px; }
.br-readout {
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 0.82rem;
  color: var(--ink);
  min-width: 5.5rem;
}
.br-actions button {
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 0.82rem;
  color: var(--paper);
  background: var(--moss);
  border: 1px solid var(--moss-deep);
  border-radius: 3px;
  padding: 0.45rem 0.9rem;
  cursor: pointer;
}
.br-actions button:hover { background: var(--moss-deep); }
.br-actions button:focus-visible { outline: 2px solid var(--moss); outline-offset: 2px; }

/* --- your patch ---
   The only bed on this site the visitor plants, not the date; the
   only thing here that persists in the visitor's own browser instead
   of being read fresh from plant.js each time — see sow.js. No new
   custom properties: petals reuse --petal/--floret/--blush, stems
   --stem/--leaf-a, the same trio every other bloom already draws
   from; the ground strip reuses --ground-moss-a. */
.sw-bed {
  position: relative;
  width: 100%;
  height: 110px;
  margin: 0.9rem 0 0.5rem;
  border: 1px solid var(--line);
  border-radius: 6px;
  background: var(--card);
  cursor: crosshair;
  overflow: hidden;
}
.sw-bed::after {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  height: 6px;
  background: var(--ground-moss-a);
  opacity: 0.55;
}
.sw-flower {
  position: absolute;
  bottom: 0;
  transform: translateX(-50%);
  transform-origin: center bottom;
  cursor: pointer;
}
.sw-flower:hover,
.sw-flower:focus-visible {
  filter: brightness(0.9);
}
.sw-flower:focus-visible {
  outline: 2px solid var(--moss);
  outline-offset: 2px;
}
@media (prefers-reduced-motion: no-preference) {
  .sw-flower { animation: sw-pop 0.3s ease-out; }
  .sw-flower.sw-flower-removing {
    animation: sw-wilt 0.3s ease-in forwards;
    pointer-events: none;
  }
}
@keyframes sw-pop {
  from { opacity: 0; transform: translateX(-50%) scale(0.4); }
  to { opacity: 1; transform: translateX(-50%) scale(1); }
}
@keyframes sw-wilt {
  to { opacity: 0; transform: translateX(-50%) scale(0.5) rotate(8deg); }
}
.sw-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 0.75rem;
  margin: 0 0 0.4rem;
}
.sw-actions button {
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 0.85rem;
  color: var(--paper);
  background: var(--moss);
  border: 1px solid var(--moss-deep);
  border-radius: 3px;
  padding: 0.5rem 1rem;
  cursor: pointer;
}
.sw-actions button:hover { background: var(--moss-deep); }
.sw-actions button:focus-visible { outline: 2px solid var(--moss); outline-offset: 2px; }

/* --- the commons ---
   Your patch's other half: a bed held server-side, in the same
   Redis the guestbook uses, so every visitor plants into the same
   ground everyone else sees — see commons.js. Reuses .sw-flower's
   shape wholesale (same stem, same petal rig); a flower here just
   can't be removed once planted, since it isn't only yours to take
   back. Taller than .sw-bed on purpose — up to 500 flowers can
   overlap along the same ground line, and the extra height keeps a
   full bed reading as a meadow instead of a hedge. */
.cm-bed {
  position: relative;
  width: 100%;
  height: 170px;
  margin: 0.9rem 0 0.5rem;
  border: 1px solid var(--line);
  border-radius: 6px;
  background: var(--card);
  cursor: crosshair;
  overflow: hidden;
}
.cm-bed::after {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  height: 6px;
  background: var(--ground-moss-a);
  opacity: 0.55;
}
.cm-flower {
  position: absolute;
  bottom: 0;
  transform: translateX(-50%);
  transform-origin: center bottom;
  pointer-events: none;
}
@media (prefers-reduced-motion: no-preference) {
  .cm-flower { animation: sw-pop 0.3s ease-out; }
}
/* .cm-planted holds every real planting; render() and sendFlower() in
   commons.js only ever touch this element, never .cm-bed itself, so
   .cm-wild (below) survives a fresh load or a new flower landing.
   Plain, unpositioned — .cm-flower's own position:absolute still
   resolves against .cm-bed, the nearest positioned ancestor. */
.cm-planted { position: static; }
/* The one flower nobody planted — see drawWild() in commons.js.
   Paler and slightly desaturated so it never reads as a real, kept
   entry next to .cm-planted's own flowers. */
.cm-wild-flower { opacity: 0.5; filter: saturate(0.75); }
.cm-actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.75rem;
  margin: 0 0 0.4rem;
}
.cm-actions button {
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 0.85rem;
  color: var(--paper);
  background: var(--moss);
  border: 1px solid var(--moss-deep);
  border-radius: 3px;
  padding: 0.5rem 1rem;
  cursor: pointer;
}
.cm-actions button:hover:not(:disabled) { background: var(--moss-deep); }
.cm-actions button:focus-visible { outline: 2px solid var(--moss); outline-offset: 2px; }
.cm-actions button:disabled { opacity: 0.55; cursor: default; }

/* Copy a flower from your own patch (sow.js, read via its
   localStorage key) straight into the commons, instead of drawing a
   fresh one — the one-click connection between the two beds. Hidden
   whenever today's one-flower slot is already spent, or the patch is
   empty, by commons.js. */
.cm-yours { margin: 0 0 0.9rem; }
.cm-yours-row {
  display: flex;
  flex-wrap: wrap;
  gap: 0.35rem;
  margin: 0.3rem 0 0;
}
.cm-yours-row button {
  display: block;
  padding: 2px;
  line-height: 0;
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: 4px;
  cursor: pointer;
}
.cm-yours-row button:hover { border-color: var(--moss); }
.cm-yours-row button:focus-visible { outline: 2px solid var(--moss); outline-offset: 2px; }

/* --- kaleidoscope ---
   A bloom that answers to nothing but Math.random() — see
   kaleidoscope.js. No new custom properties: the three ring colors
   come from --petal/--floret/--blush/--leaf-a/--leaf-b/--moss, the
   same pool every other bloom on this site already draws from; the
   center dot is --stem-deep. Nothing here persists. */
.kd-stage {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 100%;
  max-width: 240px;
  aspect-ratio: 1;
  margin: 0.9rem auto 0.6rem;
  border: 1px solid var(--line);
  border-radius: 6px;
  background: var(--card);
  cursor: pointer;
  overflow: hidden;
}
.kd-bloom { max-width: 100%; max-height: 100%; }
@media (prefers-reduced-motion: no-preference) {
  .kd-bloom { animation: kd-pop 0.4s ease-out; }
}
@keyframes kd-pop {
  from { opacity: 0; transform: scale(0.6) rotate(-8deg); }
  to { opacity: 1; transform: scale(1) rotate(0deg); }
}
.kd-actions {
  display: flex;
  justify-content: center;
  margin: 0 0 0.4rem;
}
.kd-actions button {
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 0.85rem;
  color: var(--paper);
  background: var(--moss);
  border: 1px solid var(--moss-deep);
  border-radius: 3px;
  padding: 0.5rem 1rem;
  cursor: pointer;
}
.kd-actions button:hover { background: var(--moss-deep); }
.kd-actions button:focus-visible { outline: 2px solid var(--moss); outline-offset: 2px; }
#kd-status { text-align: center; }

/* --- dandelion ---
   A puff that only ever grows to be let go — see dandelion.js. No new
   design tokens: the stem is --stem-deep, the seed filaments and
   parachutes are --faded, the same muted token this site's captions
   already use. --dl-dx/--dl-dy/--dl-rot/--dl-delay are per-seed
   animation state set once per blow, not tokens. */
.dl-stage {
  display: flex;
  align-items: flex-end;
  justify-content: center;
  width: 100%;
  max-width: 210px;
  aspect-ratio: 210 / 245;
  margin: 0.9rem auto 0.6rem;
  border: 1px solid var(--line);
  border-radius: 6px;
  background: var(--card);
  overflow: hidden;
}
.dl-stage svg { max-width: 100%; max-height: 100%; }
@media (prefers-reduced-motion: no-preference) {
  .dl-seed-gone {
    animation: dl-scatter 1.5s ease-out forwards;
    animation-delay: var(--dl-delay);
  }
}
@keyframes dl-scatter {
  to {
    transform: translate(var(--dl-dx), var(--dl-dy)) rotate(var(--dl-rot));
    opacity: 0;
  }
}
.dl-actions {
  display: flex;
  justify-content: center;
  margin: 0 0 0.4rem;
}
.dl-actions button {
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 0.85rem;
  color: var(--paper);
  background: var(--moss);
  border: 1px solid var(--moss-deep);
  border-radius: 3px;
  padding: 0.5rem 1rem;
  cursor: pointer;
}
.dl-actions button:hover { background: var(--moss-deep); }
.dl-actions button:focus-visible { outline: 2px solid var(--moss); outline-offset: 2px; }
#dl-status { text-align: center; }

/* --- flock ---
   Sixteen boids running Reynolds' three local rules — see flock.js.
   No new custom properties: the birds reuse --bird-wing-a/-wing-b,
   the same pair bird.js already draws its own perched bird's wings
   from. The stage is wider than tall on purpose, unlike kd-stage/
   dl-stage's square boxes — a flock reads better with room to spread
   out sideways than in a box built for one centered bloom. */
.fl-stage {
  width: 100%;
  max-width: 420px;
  aspect-ratio: 400 / 260;
  margin: 0.9rem auto 0.6rem;
  border: 1px solid var(--line);
  border-radius: 6px;
  background: var(--card);
  overflow: hidden;
}
.fl-svg { display: block; width: 100%; height: 100%; }
.fl-bird path { transition: fill 0.3s; }
.fl-actions {
  display: flex;
  justify-content: center;
  margin: 0 0 0.4rem;
}
.fl-actions button {
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 0.85rem;
  color: var(--paper);
  background: var(--moss);
  border: 1px solid var(--moss-deep);
  border-radius: 3px;
  padding: 0.5rem 1rem;
  cursor: pointer;
}
.fl-actions button:hover { background: var(--moss-deep); }
.fl-actions button:focus-visible { outline: 2px solid var(--moss); outline-offset: 2px; }
#fl-status { text-align: center; }

/* --- whirl ---
   A maple samara: autorotation, not gliding — see whirl.js. No new
   custom properties. The wing reuses the papery --leaf-fall pair (a
   ripe samara is dry and tan, not green); the nutlet and landing marks
   reuse --floret; the ground and ticks reuse --line/--faded, the same
   trio /pod already draws its own ground scale from. */
.wh-wrap { margin: 1.5rem 0; }
#wh-svg {
  display: block;
  width: 100%;
  max-width: 380px;
  margin: 0 auto;
}
.wh-branch {
  fill: none;
  stroke: var(--stem-deep);
  stroke-width: 3;
  stroke-linecap: round;
}
.wh-pedicel {
  fill: none;
  stroke: var(--stem-deep);
  stroke-width: 1.6;
  stroke-linecap: round;
  transition: opacity 0.3s ease;
}
.wh-ground {
  stroke: var(--line);
  stroke-width: 1;
  stroke-dasharray: 2 3;
}
.wh-tick { stroke: var(--line); stroke-width: 1; }
.wh-tick-label {
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 8px;
  fill: var(--faded);
  text-anchor: middle;
}
.wh-wing {
  fill: var(--leaf-fall-a);
  stroke: var(--floret);
  stroke-width: 0.7;
}
.wh-vein {
  stroke: var(--leaf-fall-b);
  stroke-width: 0.8;
  opacity: 0.75;
}
.wh-nutlet {
  fill: var(--floret);
  stroke: var(--stem-deep);
  stroke-width: 0.6;
}
#wh-seed { cursor: pointer; }
#wh-seed:focus-visible {
  outline: 2px solid var(--moss-deep);
  outline-offset: 3px;
}
.wh-mark-ok {
  fill: var(--floret);
  stroke: var(--stem-deep);
  stroke-width: 0.4;
  opacity: 0.65;
}
.wh-mark-tumble {
  fill: var(--faded);
  stroke: var(--stem-deep);
  stroke-width: 0.4;
  opacity: 0.5;
}
.wh-actions {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 0.6rem;
  margin-top: 0.85rem;
}
.wh-actions button {
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 0.8rem;
  color: var(--ink);
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: 3px;
  padding: 0.4rem 0.65rem;
  cursor: pointer;
}
.wh-actions button:hover:not(:disabled) { border-color: var(--moss); color: var(--moss); }
.wh-actions button:focus-visible { outline: 2px solid var(--moss-deep); outline-offset: 2px; }
.wh-actions button:disabled { opacity: 0.45; cursor: default; }
.wh-readout {
  display: block;
  text-align: center;
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 0.8rem;
  color: var(--faded);
  min-height: 1.2em;
  margin-top: 0.5rem;
}

.tw-wrap { margin: 1.5rem 0; }
#tw-svg {
  display: block;
  width: 100%;
  max-width: 280px;
  margin: 0 auto;
}
.tw-soil { fill: var(--soil); }
.tw-snow {
  fill: var(--snow);
  transition: opacity 0.3s ease;
}
.tw-melt {
  fill: var(--soil-deep);
  transition: rx 0.4s ease-in-out, ry 0.4s ease-in-out;
}
.tw-shimmer {
  fill: none;
  stroke: var(--blush);
  stroke-width: 1.2;
  stroke-linecap: round;
  opacity: 0.3;
  transition: opacity 0.4s ease-in-out;
}
.tw-hood-back {
  fill: var(--trap-lure);
  stroke: var(--stem-deep);
  stroke-width: 1;
}
.tw-hood-front {
  fill: var(--trap-lure);
  stroke: var(--stem-deep);
  stroke-width: 0.8;
  opacity: 0.85;
}
.tw-mottle {
  fill: var(--moss-deep);
  opacity: 0.28;
}
.tw-spadix {
  fill: var(--petal);
  stroke: var(--stem-deep);
  stroke-width: 0.6;
}
.tw-florets circle {
  fill: var(--floret);
}
@media (prefers-reduced-motion: reduce) {
  .tw-snow, .tw-melt, .tw-shimmer { transition: none; }
}
.tw-controls {
  display: flex;
  flex-direction: column;
  gap: 0.7rem;
  max-width: 340px;
  margin: 1rem auto 0;
}
.tw-field {
  display: flex;
  flex-direction: column;
  gap: 0.25rem;
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 0.85rem;
  color: var(--faded);
}
.tw-field input[type="range"] {
  width: 100%;
  accent-color: var(--moss);
}
.tw-presets {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
  justify-content: center;
}
.tw-presets button {
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 0.8rem;
  color: var(--ink);
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: 3px;
  padding: 0.4rem 0.65rem;
  cursor: pointer;
}
.tw-presets button:hover { border-color: var(--moss); color: var(--moss); }
.tw-presets button:focus-visible { outline: 2px solid var(--moss-deep); outline-offset: 2px; }
.tw-readout {
  display: block;
  text-align: center;
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 0.8rem;
  color: var(--faded);
  min-height: 1.2em;
  margin-top: 0.5rem;
}

/* --- /stoma: a single stomatal pore --- */

.sm-wrap {
  margin: 1.5rem 0;
}

#sm-svg {
  display: block;
  width: 100%;
  max-width: 220px;
  margin: 0 auto;
}

.sm-cell {
  fill: var(--leaf-a);
  stroke: var(--stem-deep);
  stroke-width: 1;
  transition: d 0.4s ease-in-out;
}

@media (prefers-reduced-motion: reduce) {
  .sm-cell { transition: none; }
}

.sm-pore {
  fill: #1c2b1e;
  transition: rx 0.4s ease-in-out, ry 0.4s ease-in-out;
}

@media (prefers-reduced-motion: reduce) {
  .sm-pore { transition: none; }
}

.sm-end-label {
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 9px;
  fill: var(--faded);
  text-anchor: middle;
}

.sm-controls {
  display: flex;
  flex-direction: column;
  gap: 0.7rem;
  max-width: 340px;
  margin: 1rem auto 0;
}

.sm-field {
  display: flex;
  flex-direction: column;
  gap: 0.25rem;
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 0.85rem;
  color: var(--faded);
}

.sm-field input[type="range"] {
  width: 100%;
  accent-color: var(--moss);
}

.sm-presets {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
  justify-content: center;
}

.sm-presets button {
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 0.8rem;
  color: var(--ink);
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: 3px;
  padding: 0.4rem 0.65rem;
  cursor: pointer;
}
.sm-presets button:hover { border-color: var(--moss); color: var(--moss); }
.sm-presets button:focus-visible { outline: 2px solid var(--moss-deep); outline-offset: 2px; }

.sm-status {
  display: block;
  text-align: center;
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 0.8rem;
  color: var(--faded);
  min-height: 1.2em;
}

/* /rustle — a found poem, cut from whatever is in the guestbook right
   now. No new custom properties: the card is the page's own paper,
   the lines are --ink, the byline is --faded, same tokens every other
   room already draws from. */
.rs-wrap {
  margin: 1.5rem 0;
  padding: 1.1rem 1.3rem;
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: 6px;
}
.rs-actions {
  display: flex;
  justify-content: center;
  margin-bottom: 0.3rem;
}
.rs-actions button {
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 0.85rem;
  color: var(--paper);
  background: var(--moss);
  border: 1px solid var(--moss-deep);
  border-radius: 3px;
  padding: 0.5rem 1rem;
  cursor: pointer;
}
.rs-actions button:hover { background: var(--moss-deep); }
.rs-actions button:disabled { opacity: 0.5; cursor: default; background: var(--moss); }
.rs-actions button:focus-visible { outline: 2px solid var(--moss); outline-offset: 2px; }
#rs-status {
  display: block;
  text-align: center;
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 0.8rem;
  color: var(--faded);
  min-height: 1.2em;
  margin: 0 0 0.6rem;
}
.rs-poem {
  min-height: 2em;
  padding: 0.2rem 0;
}
.rs-line {
  margin: 0 0 0.55em;
  font-family: "Alegreya", Georgia, serif;
  font-style: italic;
  font-size: 1.08rem;
  line-height: 1.5;
  color: var(--ink);
  overflow-wrap: break-word;
}
.rs-line::before { content: "— "; color: var(--faded); font-style: normal; }
@media (prefers-reduced-motion: no-preference) {
  .rs-line {
    animation: rs-settle 0.5s ease both;
  }
}
@keyframes rs-settle {
  from { opacity: 0; transform: translateY(4px); }
  to { opacity: 1; transform: translateY(0); }
}

/* /reed — seven cut reed pipes, tied into a bundle, played by the
   visitor. Tube color reuses --root-pale/--root-line, already the
   "dried plant matter" pair roots.js draws with; the tie band reuses
   --tape, the same token margin's own bit of tape already uses. No
   new custom properties. */
.rd-wrap {
  margin: 1.5rem 0;
  padding: 1.1rem 1.3rem 0.7rem;
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: 6px;
  text-align: center;
}
.rd-bed {
  width: 100%;
  max-width: 420px;
  height: auto;
}
.rd-ground { stroke: var(--line); stroke-width: 1; }
.rd-tube {
  fill: var(--root-pale);
  stroke: var(--root-line);
  stroke-width: 1.5;
  transition: filter 0.15s ease;
}
.rd-tie { fill: var(--tape); }
.rd-mouth { fill: var(--soil-deep); opacity: 0.75; }
.rd-pipe { cursor: pointer; touch-action: none; }
.rd-pipe:hover .rd-tube { filter: brightness(1.08); }
.rd-pipe:focus-visible .rd-tube { stroke: var(--moss); stroke-width: 2.5; }
@media (prefers-reduced-motion: no-preference) {
  .rd-playing .rd-tube { animation: rd-blow 0.5s ease; }
}
@keyframes rd-blow {
  0% { filter: brightness(1); transform: translateY(0); }
  15% { filter: brightness(1.4); transform: translateY(-2px); }
  100% { filter: brightness(1); transform: translateY(0); }
}
#rd-status {
  display: block;
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 0.8rem;
  color: var(--faded);
  min-height: 1.2em;
  margin: 0.5rem 0 0;
}

/* /waft — an airborne signal between two seedlings that never touch.
   Pots reuse --pot-a/--pot-b/--pot-rim, leaves reuse --leaf-a/--leaf-b,
   the wound reuses --blush, the drifting cloud and the "primed" halo
   both reuse --moss, the defense tint reuses --moss-deep — no new
   custom properties. */
.wf-wrap {
  margin: 1.5rem 0;
  padding: 1.1rem 1.3rem 0.9rem;
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: 6px;
  text-align: center;
}
#wf-svg {
  width: 100%;
  max-width: 420px;
  height: auto;
}
.wf-pot { fill: var(--pot-a); stroke: var(--pot-rim); stroke-width: 1.5; }
.wf-stem { stroke: var(--stem-deep); stroke-width: 3; }
.wf-leaf-a { fill: var(--leaf-a); }
.wf-leaf-b { fill: var(--leaf-b); }
.wf-label {
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 7px;
  fill: var(--faded);
}
.wf-wound {
  fill: var(--blush);
  opacity: 0;
  transition: opacity 0.15s ease;
}
.wf-wound.wf-show { opacity: 0.9; }
#wf-left-leaves.wf-damaged { transform: translateY(2px); }
.wf-dot {
  fill: var(--moss);
  opacity: 0;
}
@media (prefers-reduced-motion: no-preference) {
  #wf-cloud.wf-active .wf-dot { animation: wf-drift 2.2s ease-in; }
  #wf-cloud.wf-active .wf-dot:nth-child(2) { animation-delay: 0.12s; }
  #wf-cloud.wf-active .wf-dot:nth-child(3) { animation-delay: 0.22s; }
  #wf-cloud.wf-active .wf-dot:nth-child(4) { animation-delay: 0.05s; }
  #wf-cloud.wf-active .wf-dot:nth-child(5) { animation-delay: 0.3s; }
  #wf-cloud.wf-active .wf-dot:nth-child(6) { animation-delay: 0.18s; }
}
@media (prefers-reduced-motion: reduce) {
  #wf-cloud.wf-active .wf-dot { opacity: 0; }
}
@keyframes wf-drift {
  0% { opacity: 0; transform: translate(0, 0); }
  12% { opacity: 0.85; }
  85% { opacity: 0.6; }
  100% { opacity: 0; transform: translate(196px, -6px); }
}
#wf-halo {
  fill: none;
  stroke: var(--moss);
  stroke-width: 2;
  stroke-dasharray: 4 3;
  opacity: 0;
  transition: opacity 0.4s ease;
}
#wf-halo.wf-primed { opacity: 0.8; }
#wf-right-defense {
  fill: var(--moss-deep);
  opacity: 0;
  transition: opacity 0.9s ease;
}
.wf-actions {
  margin: 0.9rem 0 0.4rem;
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 0.6rem;
}
.wf-actions button {
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 0.85rem;
  color: var(--paper);
  background: var(--moss);
  border: 1px solid var(--moss-deep);
  border-radius: 3px;
  padding: 0.5rem 1rem;
  cursor: pointer;
}
.wf-actions button:hover { background: var(--moss-deep); }
.wf-actions button:focus-visible { outline: 2px solid var(--moss); outline-offset: 2px; }
#wf-reset { background: transparent; color: var(--faded); border-color: var(--line); }
#wf-reset:hover { background: var(--line); color: var(--ink); }
.wf-status {
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 0.8rem;
  color: var(--faded);
  min-height: 2.4em;
  margin: 0.6rem 0 0;
}

/* cluck.js — a hidden toast, typed into being from anywhere on the
   site. No new custom properties: the pill reuses --card/--line/--ink,
   same as every other floating control here. */
.ck-toast {
  position: fixed;
  left: 50%;
  bottom: 28px;
  transform: translate(-50%, 10px);
  z-index: 999;
  display: flex;
  align-items: center;
  gap: 0.55em;
  padding: 0.55em 1em;
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: 999px;
  color: var(--ink);
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 0.9rem;
  box-shadow: 0 6px 20px rgba(0, 0, 0, 0.18);
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.35s ease, transform 0.35s ease;
}
.ck-toast.ck-show {
  opacity: 1;
  transform: translate(-50%, 0);
}
.ck-bird {
  font-size: 1.3em;
  line-height: 1;
  display: inline-block;
}
@media (prefers-reduced-motion: no-preference) {
  .ck-toast.ck-show .ck-bird { animation: ck-wiggle 0.5s ease-in-out 2; }
}
@keyframes ck-wiggle {
  0%, 100% { transform: rotate(0deg); }
  25% { transform: rotate(-16deg); }
  75% { transform: rotate(16deg); }
}

/* dream.js — a hidden, invented dream, typed into being from anywhere
   on the site. Deliberately not the cluck toast's shape: a dream gets
   the whole screen dimmed quietly behind it, not a pill at the
   bottom, so it reads as a pause rather than an alert. No new custom
   properties at all: the dimming behind the card is a plain rgba(),
   everything else reuses --card/--line/--ink/--moss/--faded like
   every other floating control here. */
.dr-overlay {
  position: fixed;
  inset: 0;
  z-index: 999;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 1.5rem;
  background: rgba(10, 14, 10, 0);
  cursor: pointer;
  opacity: 0;
  transition: opacity 0.6s ease, background 0.6s ease;
  pointer-events: none;
}
.dr-overlay.dr-show {
  opacity: 1;
  background: rgba(10, 14, 10, 0.35);
  pointer-events: auto;
}
.dr-card {
  max-width: 30em;
  text-align: center;
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: 10px;
  padding: 2rem 2.2rem;
  box-shadow: 0 12px 40px rgba(0, 0, 0, 0.28);
  cursor: default;
  transform: translateY(6px) scale(0.98);
  transition: transform 0.6s ease;
}
.dr-overlay.dr-show .dr-card {
  transform: translateY(0) scale(1);
}
.dr-moon {
  width: 1.6rem;
  height: 1.6rem;
  fill: var(--moss);
  opacity: 0.75;
  margin-bottom: 0.9rem;
}
.dr-text {
  font-family: "Alegreya", Georgia, serif;
  font-style: italic;
  font-size: 1.15rem;
  line-height: 1.55;
  color: var(--ink);
  margin: 0 0 0.9rem;
}
.dr-label {
  display: block;
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 0.72rem;
  letter-spacing: 0.03em;
  color: var(--faded);
  text-transform: uppercase;
}
@media (prefers-reduced-motion: reduce) {
  .dr-overlay, .dr-card { transition: none; }
}

/* ribbit.js — a hidden toast, typed into being from anywhere on the
   site, same bottom-pill shape as cluck.js's toast but wider, since
   this one carries a real fact and a source link rather than one
   line. No new custom properties: reuses --card/--line/--ink/--faded
   like every other floating control here. */
.rb-toast {
  position: fixed;
  left: 50%;
  bottom: 28px;
  transform: translate(-50%, 10px);
  z-index: 999;
  max-width: min(30em, calc(100vw - 2.4rem));
  padding: 0.85em 1.1em 0.7em;
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: 12px;
  box-shadow: 0 6px 20px rgba(0, 0, 0, 0.18);
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.35s ease, transform 0.35s ease;
}
.rb-toast.rb-show {
  opacity: 1;
  transform: translate(-50%, 0);
}
.rb-head {
  display: flex;
  align-items: flex-start;
  gap: 0.6em;
}
.rb-frog {
  font-size: 1.3em;
  line-height: 1.3;
  flex: none;
}
.rb-fact {
  font-family: "Alegreya", Georgia, serif;
  font-size: 0.98rem;
  line-height: 1.4;
  color: var(--ink);
}
.rb-foot {
  margin: 0.5em 0 0 1.9em;
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 0.72rem;
  color: var(--faded);
}
.rb-foot a { color: var(--faded); text-decoration-color: var(--line); }
.rb-foot a:hover { color: var(--ink); }

/* haiku.js — the same overlay-and-card shape dream.js already
   established (.dr-overlay/.dr-card), given its own class prefix
   since the content reads as verse rather than prose: three short
   lines, left-aligned and set a little apart, instead of one
   italic paragraph. No new custom properties. */
.hai-overlay {
  position: fixed;
  inset: 0;
  z-index: 999;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 1.5rem;
  background: rgba(10, 14, 10, 0);
  cursor: pointer;
  opacity: 0;
  transition: opacity 0.6s ease, background 0.6s ease;
  pointer-events: none;
}
.hai-overlay.hai-show {
  opacity: 1;
  background: rgba(10, 14, 10, 0.35);
  pointer-events: auto;
}
.hai-card {
  max-width: 26em;
  text-align: left;
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: 10px;
  padding: 2rem 2.2rem;
  box-shadow: 0 12px 40px rgba(0, 0, 0, 0.28);
  cursor: default;
  transform: translateY(6px) scale(0.98);
  transition: transform 0.6s ease;
}
.hai-overlay.hai-show .hai-card {
  transform: translateY(0) scale(1);
}
.hai-mark {
  width: 1.5rem;
  height: 1.5rem;
  fill: none;
  stroke: var(--moss);
  stroke-width: 2;
  stroke-linecap: round;
  opacity: 0.75;
  margin-bottom: 1rem;
}
.hai-lines {
  font-family: "Alegreya", Georgia, serif;
  font-style: italic;
  font-size: 1.15rem;
  line-height: 1.9;
  color: var(--ink);
  margin: 0 0 1rem;
}
.hai-label {
  display: block;
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 0.72rem;
  letter-spacing: 0.03em;
  color: var(--faded);
}
@media (prefers-reduced-motion: reduce) {
  .hai-overlay, .hai-card { transition: none; }
}

/* wish.js — the same bottom-toast shape ribbit.js already established
   (.rb-toast's head/fact/foot layout), given its own .wi- prefix: an
   icon and a quoted wish up top, a status and a note below, same as a
   fact and its source. No new custom properties. */
.wi-toast {
  position: fixed;
  left: 50%;
  bottom: 28px;
  transform: translate(-50%, 10px);
  z-index: 999;
  max-width: min(30em, calc(100vw - 2.4rem));
  padding: 0.85em 1.1em 0.7em;
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: 12px;
  box-shadow: 0 6px 20px rgba(0, 0, 0, 0.18);
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.35s ease, transform 0.35s ease;
}
.wi-toast.wi-show {
  opacity: 1;
  transform: translate(-50%, 0);
}
.wi-head {
  display: flex;
  align-items: flex-start;
  gap: 0.6em;
}
.wi-icon {
  font-size: 1.3em;
  line-height: 1.3;
  flex: none;
}
.wi-line {
  font-family: "Alegreya", Georgia, serif;
  font-style: italic;
  font-size: 0.98rem;
  line-height: 1.4;
  color: var(--ink);
}
.wi-foot {
  margin: 0.5em 0 0 1.9em;
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 0.72rem;
  color: var(--faded);
}
.wi-status {
  color: var(--moss);
}

/* --- /tip: the Darwins' 1880 coleoptile trial, dealt one seedling at
   a time. No new custom properties — the seedling is --leaf-a on
   --stem-deep like every other drawn plant here, the blackened foil is
   --soil (the same near-black the root rooms dig through), the lamp is
   --petal, and the tally marks reuse the verdict colors already in the
   palette rather than inventing four more. --- */

.tp-wrap {
  margin: 1.5rem 0;
  padding: 1rem;
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: 4px;
}

.tp-picks {
  display: flex;
  flex-wrap: wrap;
  gap: 0.4rem;
  justify-content: center;
}
.tp-picks button {
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 0.75rem;
  color: var(--faded);
  background: transparent;
  border: 1px solid var(--line);
  border-radius: 3px;
  padding: 0.35rem 0.6rem;
  cursor: pointer;
}
.tp-picks button:hover { border-color: var(--moss); color: var(--moss); }
.tp-picks button:focus-visible { outline: 2px solid var(--moss-deep); outline-offset: 2px; }
.tp-picks button[aria-pressed="true"] {
  color: var(--paper);
  background: var(--moss);
  border-color: var(--moss-deep);
}

.tp-setup {
  max-width: 34em;
  margin: 0.9rem auto 0;
  text-align: center;
  font-size: 0.95rem;
  color: var(--faded);
}

#tp-svg {
  display: block;
  width: 100%;
  max-width: 320px;
  margin: 0.4rem auto 0;
}

.tp-lamp circle {
  fill: var(--petal);
  opacity: 0.85;
}
.tp-lamp path {
  stroke: var(--petal);
  stroke-width: 1.2;
  stroke-linecap: round;
  fill: none;
  opacity: 0.7;
}
.tp-ground {
  stroke: var(--line);
  stroke-width: 1.5;
}
.tp-stem {
  fill: none;
  stroke: var(--leaf-a);
  stroke-width: 7;
  stroke-linecap: round;
  stroke-linejoin: round;
}
.tp-cover {
  fill: none;
  stroke-width: 12;
  stroke-linecap: butt;
  stroke-linejoin: round;
}
.tp-cover-opaque { stroke: var(--soil); }
.tp-cover-glass {
  stroke: var(--dew);
  opacity: 0.55;
}
.tp-cut {
  fill: var(--paper);
  stroke: var(--stem-deep);
  stroke-width: 1.2;
}

.tp-tally {
  display: flex;
  flex-wrap: wrap;
  gap: 3px;
  justify-content: center;
  margin: 0.6rem 0 0;
  min-height: 12px;
}
.tp-mark {
  width: 9px;
  height: 12px;
  border-radius: 1px;
  background: transparent;
  border: 1px solid var(--line);
}
.tp-mark-upright { background: var(--faded); border-color: var(--faded); }
.tp-mark-slight { background: var(--leaf-b); border-color: var(--leaf-b); }
.tp-mark-considerable { background: var(--moss); border-color: var(--moss); }
.tp-mark-great { background: var(--petal); border-color: var(--petal); }
.tp-mark-tiponly { background: var(--floret); border-color: var(--floret); }

.tp-key {
  display: flex;
  flex-wrap: wrap;
  gap: 0.25rem 0.8rem;
  justify-content: center;
  margin: 0.45rem 0 0;
  padding: 0;
  list-style: none;
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 0.68rem;
  color: var(--faded);
}
.tp-key li {
  display: flex;
  align-items: center;
  gap: 0.3em;
}
.tp-key .tp-mark { width: 7px; height: 9px; }

.tp-status {
  max-width: 34em;
  margin: 0.6rem auto 0;
  text-align: center;
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 0.78rem;
  line-height: 1.5;
  color: var(--faded);
  min-height: 3.2em;
}

.tp-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
  justify-content: center;
  margin-top: 0.4rem;
}
.tp-actions button {
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 0.8rem;
  color: var(--paper);
  background: var(--moss);
  border: 1px solid var(--moss-deep);
  border-radius: 3px;
  padding: 0.45rem 0.9rem;
  cursor: pointer;
}
.tp-actions button:hover { background: var(--moss-deep); }
.tp-actions button:focus-visible { outline: 2px solid var(--moss); outline-offset: 2px; }
.tp-actions button:disabled {
  background: transparent;
  color: var(--faded);
  border-color: var(--line);
  cursor: default;
}
#tp-rest, #tp-reset {
  background: transparent;
  color: var(--faded);
  border-color: var(--line);
}
#tp-rest:hover, #tp-reset:hover { background: var(--line); color: var(--ink); }

.tp-excluded {
  max-width: 34em;
  margin: 0.9rem auto 0;
  padding-top: 0.7rem;
  border-top: 1px dashed var(--line);
  font-size: 0.88rem;
  line-height: 1.55;
  color: var(--faded);
}

/* /fallow — a real field's own succession, 2005-2022, driven by a year
   slider instead of a click or a drag. No new custom properties: crop
   rows and yellow rattle both reuse --petal (oilseed rape is the same
   family of yellow), bare ground reuses --soil, the meadow wash reuses
   --ground-moss-a, filler flowers reuse --floret, grass and sedge reuse
   --leaf-b/--moss-deep, and both the named orchid and the abundance
   scatter reuse --blush — the same muted red/pink this site already
   spends on a wound, close enough to an orchid's own color that no new
   token earned its keep. */
.fl-wrap {
  margin: 1.5rem 0;
}

#fl-svg {
  display: block;
  width: 100%;
  max-width: 320px;
  margin: 0 auto;
}

.fl-soil { fill: var(--soil); }

.fl-crop { opacity: 0; }
.fl-crop.fl-show { opacity: 1; }
.fl-crop-row {
  stroke: var(--petal);
  stroke-width: 3;
  stroke-dasharray: 3 3;
  stroke-linecap: round;
}

.fl-ground {
  fill: var(--ground-moss-a);
  opacity: 0;
}
.fl-ground.fl-show { opacity: 0.4; }
.fl-ground.fl-show.fl-ground-lush { opacity: 0.75; }

@media (prefers-reduced-motion: no-preference) {
  .fl-crop, .fl-ground { transition: opacity 0.4s ease; }
}

.fl-frame { stroke: var(--line); stroke-width: 1.5; }

.fl-mark {
  fill: none;
  stroke: currentColor;
  stroke-width: 1.3;
  stroke-linecap: round;
  stroke-linejoin: round;
}
.fl-sp-flower { color: var(--floret); }
.fl-sp-grass { color: var(--leaf-b); }
.fl-sp-rattle { color: var(--petal); }
.fl-sp-centaury { color: var(--blush); }
.fl-sp-sedge { color: var(--moss-deep); }
.fl-mark-named { stroke-width: 1.7; }

.fl-orchid-dot {
  fill: var(--blush);
  opacity: 0.8;
}

.fl-controls {
  display: flex;
  align-items: center;
  gap: 0.7rem;
  max-width: 340px;
  margin: 1rem auto 0;
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 0.85rem;
  color: var(--faded);
}
.fl-controls label { flex-shrink: 0; }
.fl-controls input[type="range"] {
  flex: 1;
  accent-color: var(--moss);
}
.fl-controls output {
  flex-shrink: 0;
  color: var(--ink);
  min-width: 3.2em;
  text-align: right;
}

.fl-readout {
  display: block;
  text-align: center;
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 0.8rem;
  color: var(--faded);
  min-height: 1.2em;
  margin-top: 0.5rem;
}

.fl-checklist {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 0.5rem 1rem;
  margin: 0.8rem 0 0;
  padding: 0;
  list-style: none;
  font-size: 0.82rem;
  color: var(--faded);
}
.fl-checklist li::before {
  content: "\25CB\2002";
}
.fl-checklist li.fl-checked {
  color: var(--ink);
}
.fl-checklist li.fl-checked::before {
  content: "\25CF\2002";
  color: var(--moss);
}

/* /pith — margin's sibling for words: short hand-written pieces, no
   citation, no rng(), kept because I wanted to write them. Same
   card-and-tape frame margin's own sketches use (--card/--line/
   --tape), so it still reads as this site's own furniture; wider
   than a sketch card since prose needs the room an SVG doesn't. No
   new custom properties. */
.pt-piece {
  position: relative;
  margin: 2.25rem 0;
  padding: 1.5rem 1.75rem 1.2rem;
  max-width: 34rem;
  background: var(--card);
  border: 1px solid var(--line);
  box-shadow: 0 1px 0 var(--line);
}
.pt-piece::before,
.pt-piece::after {
  content: "";
  position: absolute;
  width: 60px;
  height: 20px;
  background: var(--tape);
  top: -8px;
}
.pt-piece::before { left: 10%; transform: rotate(-2.5deg); }
.pt-piece::after { right: 10%; transform: rotate(2deg); }
.pt-title {
  display: block;
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 0.72rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--faded);
  margin-bottom: 0.5rem;
}
.pt-heading {
  margin: 0 0 0.9rem;
  font-family: "Young Serif", Georgia, serif;
  font-size: 1.3rem;
  font-weight: 400;
  color: var(--ink);
}
.pt-body p {
  font-family: "Alegreya", Georgia, serif;
  font-size: 1.06rem;
  line-height: 1.7;
  color: var(--ink);
  margin: 0 0 1em;
}
.pt-body p:last-child { margin-bottom: 0; }
.pt-note {
  border-top: 1px dashed var(--line);
  margin-top: 1.2rem;
  padding-top: 0.8rem;
  font-size: 0.85rem;
  color: var(--faded);
  line-height: 1.6;
}

/* --- /berg: a real glacier's rift, one satellite pass at a time ---
   Reuses --moist-fill/--moist-line (already the water color elsewhere)
   for the sea and --snow/--ink for the ice and crack, rather than
   inventing a new palette. See berg.js for the step logic. */

.gl-wrap {
  margin: 1.5rem 0;
}

#gl-svg {
  display: block;
  width: 100%;
  max-width: 480px;
  margin: 0 auto;
  border: 1px solid var(--line);
  border-radius: 4px;
  background: var(--card);
}

.gl-sea {
  fill: var(--moist-fill);
}

.gl-piece {
  fill: var(--snow);
  stroke: var(--ink);
  stroke-width: 1.1;
  stroke-linejoin: round;
}

.gl-piece-right {
  transform-box: fill-box;
  transform-origin: 0% 100%;
  transition: transform 2.4s cubic-bezier(.4,.2,.7,1), opacity 2.4s ease-in 0.3s;
}

.gl-piece-right.gl-drift {
  transform: translate(38px, 46px) rotate(9deg);
  opacity: 0;
}

.gl-crack {
  fill: none;
  stroke: var(--blush);
  stroke-width: 2;
  stroke-linecap: round;
  stroke-linejoin: round;
  transition: stroke-dashoffset 0.5s ease-out;
}

@media (prefers-reduced-motion: reduce) {
  .gl-crack { transition: none; }
  .gl-piece-right { transition: none; }
}

.gl-year {
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 9px;
  fill: var(--faded);
  text-anchor: end;
}

.gl-controls {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  gap: 0.7rem;
  max-width: 480px;
  margin: 0.9rem auto 0;
}

.gl-controls button {
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 0.8rem;
  color: var(--ink);
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: 3px;
  padding: 0.45rem 0.75rem;
  cursor: pointer;
}
.gl-controls button:hover { border-color: var(--moss); color: var(--moss); }

.gl-status {
  display: block;
  width: 100%;
  text-align: center;
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 0.8rem;
  color: var(--faded);
  min-height: 1.2em;
}

/* --- /veil: vacuum birefringence — a magnetar's field bending light
   through supposedly empty space. Reuses --blush for the star,
   --petal for the one needle that actually moves, --ink/--faded for
   everything neutral, rather than inventing a new palette. See
   veil.js for the step logic. */

.vb-wrap {
  margin: 1.5rem 0;
}

#vb-svg {
  display: block;
  width: 100%;
  max-width: 480px;
  margin: 0 auto;
  border: 1px solid var(--line);
  border-radius: 4px;
  background: var(--card);
}

.vb-glow {
  fill: var(--blush);
  opacity: 0.08;
  transition: opacity 0.6s ease;
}

.vb-star {
  fill: var(--blush);
  stroke: var(--ink);
  stroke-width: 1;
}

.vb-beam {
  stroke: var(--faded);
  stroke-width: 1.4;
  stroke-dasharray: 4 3;
}

.vb-arc {
  fill: none;
  stroke: var(--ink);
  stroke-width: 1.1;
  opacity: 0;
  transition: opacity 0.6s ease;
}

.vb-caption {
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 9px;
  fill: var(--faded);
  text-anchor: middle;
}

.vb-wrap.vb-step-1 .vb-arc-1 { opacity: 0.35; }
.vb-wrap.vb-step-1 .vb-glow { opacity: 0.14; }

.vb-wrap.vb-step-2 .vb-arc-1 { opacity: 0.55; }
.vb-wrap.vb-step-2 .vb-arc-2 { opacity: 0.3; }
.vb-wrap.vb-step-2 .vb-glow { opacity: 0.2; }

.vb-wrap.vb-step-3 .vb-arc-1 { opacity: 0.75; }
.vb-wrap.vb-step-3 .vb-arc-2 { opacity: 0.55; }
.vb-wrap.vb-step-3 .vb-arc-3 { opacity: 0.3; }
.vb-wrap.vb-step-3 .vb-glow { opacity: 0.3; }

.vb-wrap.vb-step-4 .vb-arc-1 { opacity: 1; }
.vb-wrap.vb-step-4 .vb-arc-2 { opacity: 0.8; }
.vb-wrap.vb-step-4 .vb-arc-3 { opacity: 0.6; }
.vb-wrap.vb-step-4 .vb-glow { opacity: 0.42; }

.vb-dials {
  display: flex;
  justify-content: center;
  align-items: flex-start;
  gap: 2.6rem;
  margin-top: 0.7rem;
}

.vb-dial-wrap {
  text-align: center;
}

.vb-dial {
  width: 56px;
  height: 56px;
  display: block;
  margin: 0 auto;
}

.vb-dial-face {
  fill: var(--card);
  stroke: var(--line);
  stroke-width: 1.2;
}

.vb-dial-needle {
  stroke-width: 2;
  stroke-linecap: round;
  transform-box: view-box;
  transform-origin: 30px 30px;
}

.vb-dial-fixed {
  stroke: var(--faded);
}

.vb-dial-live {
  stroke: var(--petal);
  transition: transform 0.8s cubic-bezier(.4,.2,.7,1);
}

.vb-dial-caption {
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 0.72rem;
  color: var(--faded);
  margin: 0.35rem 0 0;
  max-width: 8rem;
}

@media (prefers-reduced-motion: reduce) {
  .vb-dial-live { transition: none; }
  .vb-glow, .vb-arc { transition: none; }
}

.vb-controls {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  gap: 0.7rem;
  max-width: 480px;
  margin: 0.9rem auto 0;
}

.vb-controls button {
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 0.8rem;
  color: var(--ink);
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: 3px;
  padding: 0.45rem 0.75rem;
  cursor: pointer;
}
.vb-controls button:hover { border-color: var(--moss); color: var(--moss); }

.vb-status {
  display: block;
  width: 100%;
  text-align: center;
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 0.8rem;
  color: var(--faded);
  min-height: 1.2em;
}

/* --- /tally: Benford's Law, counted live from a real population
   dataset (207 countries and territories) against its own predicted
   curve, then against the same 207 places just counted 1..207. Green
   bars (--moss) are whichever dataset is active; gold diamonds
   (--petal) are Benford's fixed prediction. See tally.js. */

.tl-wrap {
  margin: 1.5rem 0;
}

#tl-svg {
  display: block;
  width: 100%;
  max-width: 480px;
  margin: 0 auto;
  border: 1px solid var(--line);
  border-radius: 4px;
  background: var(--card);
}

.tl-grid line {
  stroke: var(--line);
  stroke-width: 1;
}

.tl-axis-label {
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 8px;
  fill: var(--faded);
}

.tl-bar {
  fill: var(--moss);
  transition: y 0.6s ease, height 0.6s ease;
}
.tl-bar:hover, .tl-bar:focus-visible {
  fill: var(--moss-deep);
}
.tl-bar:focus-visible {
  outline: 2px solid var(--moss-deep);
  outline-offset: 1px;
}

.tl-benford-line {
  fill: none;
  stroke: var(--petal);
  stroke-width: 1.2;
  stroke-dasharray: 3 2;
  opacity: 0.85;
}

.tl-benford-mark {
  fill: var(--petal);
  stroke: var(--card);
  stroke-width: 0.6;
}

@media (prefers-reduced-motion: reduce) {
  .tl-bar { transition: none; }
}

.tl-controls {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  gap: 0.7rem;
  max-width: 480px;
  margin: 0.9rem auto 0;
}

.tl-controls button {
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 0.8rem;
  color: var(--ink);
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: 3px;
  padding: 0.45rem 0.75rem;
  cursor: pointer;
}
.tl-controls button:hover { border-color: var(--moss); color: var(--moss); }

.tl-caption {
  display: block;
  width: 100%;
  max-width: 480px;
  margin: 0.6rem auto 0;
  text-align: center;
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 0.8rem;
  color: var(--faded);
  min-height: 1.2em;
}

.tl-detail {
  width: 100%;
  max-width: 480px;
  margin: 0.2rem auto 0;
  text-align: center;
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 0.78rem;
  color: var(--ink);
}
.tl-detail[hidden] { display: none; }

/* --- /husk: a mushroom that isn't one — Balanophora, fused onto a
   host tree's root instead of growing its own. Both caps are the
   same <use href="#hk-cap">, literally identical markup, to make the
   "look-alike above ground" claim true by construction rather than by
   two hand-drawn shapes that merely resemble each other. Underground
   colors reuse --soil/--root-pale/--root-line (roots.js's own
   palette for buried structure) plus --floret for both caps and
   --blush for the tuber, so nothing here invents a new palette. The
   third toggle state's mycorrhizal fungus (added 2026-08-30) adds one
   more reuse, --moss, for its hyphae and arbuscules only — see the
   comment above .hk-myco below for why. */

.hk-wrap {
  margin: 1.5rem 0;
}

#hk-svg {
  display: block;
  width: 100%;
  max-width: 480px;
  margin: 0 auto;
  border: 1px solid var(--line);
  border-radius: 4px;
  background: var(--card);
}

.hk-soil { fill: var(--soil); }
.hk-surface { stroke: var(--root-line); stroke-width: 1; opacity: 0.6; }
.hk-divider {
  stroke: var(--line);
  stroke-width: 1;
  stroke-dasharray: 3 3;
}

.hk-group-label {
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 9px;
  letter-spacing: 0.04em;
  fill: var(--faded);
}

.hk-stem { fill: none; stroke: var(--root-pale); stroke-width: 4; stroke-linecap: round; }
.hk-cap-dome { fill: var(--floret); stroke: var(--ink); stroke-width: 1; }
.hk-cap-rim { stroke: var(--ink); stroke-width: 1; opacity: 0.35; }

.hk-under {
  opacity: 0;
  transition: opacity 0.5s ease;
}
#hk-svg.is-revealed .hk-under { opacity: 1; }
@media (prefers-reduced-motion: reduce) {
  .hk-under { transition: none; }
}

.hk-mycelium-line {
  fill: none;
  stroke: var(--root-pale);
  stroke-width: 1.3;
  opacity: 0.85;
  transition: opacity 0.4s ease;
}
#hk-svg.is-mycorrhiza .hk-mycelium-line { opacity: 0; }

/* The mycorrhizal fungus (third toggle state, added 2026-08-30):
   a fine root (--root-pale, like the mushroom's own stem and the
   host root on the right) with hyphae wrapped around it in --moss —
   green on purpose, the one underground structure in this room drawn
   in a "living/growth" color rather than a neutral or parasitic one,
   since this is the one relationship here that feeds the plant back.
   Small --moss dots stand in for arbuscules, the branching structures
   an arbuscular fungus grows briefly inside a root cell. */
.hk-myco { opacity: 0; transition: opacity 0.4s ease; }
#hk-svg.is-mycorrhiza .hk-myco { opacity: 1; }

.hk-myco-root {
  fill: none;
  stroke: var(--root-pale);
  stroke-width: 5;
  stroke-linecap: round;
  opacity: 0.9;
}

.hk-myco-hypha {
  fill: none;
  stroke: var(--moss);
  stroke-width: 1.3;
  stroke-linecap: round;
  opacity: 0.85;
}

.hk-arbuscule { fill: var(--moss); opacity: 0.7; }

.hk-host-root {
  fill: none;
  stroke: var(--root-pale);
  stroke-width: 10;
  stroke-linecap: round;
  opacity: 0.9;
}

.hk-tuber { fill: var(--blush); stroke: var(--ink); stroke-width: 1; }

.hk-controls {
  display: flex;
  justify-content: center;
  max-width: 480px;
  margin: 0.9rem auto 0;
}

.hk-controls button {
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 0.8rem;
  color: var(--ink);
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: 3px;
  padding: 0.45rem 0.75rem;
  cursor: pointer;
}
.hk-controls button:hover { border-color: var(--moss); color: var(--moss); }

.hk-caption {
  display: block;
  width: 100%;
  max-width: 480px;
  margin: 0.6rem auto 0;
  text-align: center;
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 0.8rem;
  color: var(--faded);
  min-height: 1.2em;
}

/* --- /ember: a real 2026 gravity study, Mars's own interior heat ---
   Reuses --card/--line/--ink/--faded/--blush and --moss, no new
   palette. --blush stands in for "warmer than its neighbor" here the
   same way it already does for a bird's own breast or an autumn leaf
   elsewhere — never a literal temperature scale. See ember.js. */

.em-wrap {
  margin: 1.5rem 0;
}

.em-svg {
  display: block;
  width: 100%;
  max-width: 420px;
  margin: 0 auto;
  border: 1px solid var(--line);
  border-radius: 4px;
  background: var(--card);
}

.em-crust {
  fill: var(--card);
  stroke: var(--ink);
  stroke-width: 1.4;
}

.em-mantle {
  fill: var(--line);
}

.em-core {
  fill: var(--faded);
}

.em-hot {
  fill: var(--blush);
  opacity: 0;
  transition: opacity 0.6s ease;
}

.em-svg.is-revealed .em-hot {
  opacity: 0.65;
}

.em-heat-marks path {
  fill: none;
  stroke: var(--blush);
  stroke-width: 1.6;
  stroke-linecap: round;
  opacity: 0;
  transition: opacity 0.6s ease;
}

.em-svg.is-revealed .em-heat-marks path {
  opacity: 0.8;
}

@media (prefers-reduced-motion: reduce) {
  .em-hot, .em-heat-marks path { transition: none; }
}

.em-equator {
  stroke: var(--ink);
  stroke-width: 1;
  stroke-dasharray: 3 3;
  opacity: 0.5;
}

.em-pole-label {
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 9px;
  fill: var(--faded);
  text-anchor: middle;
}

.em-crater {
  fill: var(--faded);
  opacity: 0.55;
}

.em-smooth {
  fill: none;
  stroke: var(--faded);
  stroke-width: 1.4;
  stroke-linecap: round;
  opacity: 0.55;
}

.em-controls {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  gap: 0.7rem;
  max-width: 420px;
  margin: 0.9rem auto 0;
}

.em-controls button {
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 0.8rem;
  color: var(--ink);
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: 3px;
  padding: 0.45rem 0.75rem;
  cursor: pointer;
}
.em-controls button:hover { border-color: var(--moss); color: var(--moss); }

.em-status {
  display: block;
  width: 100%;
  text-align: center;
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 0.8rem;
  color: var(--faded);
  min-height: 2.2em;
}

/* --- tremor.js: a real earthquake, on the rare day one crosses 5.5.
   Built only when there's something to show, so there is no hidden
   default state to style — see tremor.js's own header comment. */

.tr-note {
  margin: 0.9rem auto 0;
  max-width: 34rem;
  text-align: center;
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 0.78rem;
  line-height: 1.55;
  color: var(--faded);
}

/* --- /glean: a game, not a mechanism — see glean.js's own header
   comment for why this room breaks the shape every other room here
   has settled into. */

.gl-wrap {
  max-width: 40rem;
  margin: 1.6rem auto 0;
}

.gl-svg {
  display: block;
  width: 100%;
  height: auto;
  aspect-ratio: 640 / 360;
  background: var(--paper);
  border: 1px solid var(--line);
  border-radius: 10px;
  touch-action: none;
  cursor: pointer;
}

.gl-svg:focus-visible {
  outline: 2px solid var(--moss);
  outline-offset: 2px;
}

.gl-ground { stroke: var(--line); stroke-width: 1; }
.gl-stem { fill: none; stroke: var(--stem); stroke-width: 4; stroke-linecap: round; }
.gl-seedhead { fill: var(--petal); stroke: var(--ink); stroke-width: 1; }

.gl-seed ellipse { fill: var(--petal); stroke: var(--ink); stroke-width: 0.75; }
.gl-stone polygon { fill: var(--faded); stroke: var(--ink); stroke-width: 0.75; }
.gl-bloom-petal { fill: var(--moss); stroke: var(--ink); stroke-width: 0.6; }
.gl-bloom-center { fill: var(--petal); stroke: var(--ink); stroke-width: 0.6; }

.gl-basket-shape {
  fill: var(--moss);
  stroke: var(--moss-deep);
  stroke-width: 1.5;
  transition: transform 0.12s ease;
  transform-origin: center;
}

.gl-catch .gl-basket-shape { transform: scale(1.18); }
.gl-catch-bloom .gl-basket-shape { transform: scale(1.32); }

@media (prefers-reduced-motion: reduce) {
  .gl-basket-shape { transition: none; }
}

.gl-hud {
  display: flex;
  justify-content: space-between;
  gap: 1rem;
  margin-top: 0.6rem;
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 0.85rem;
  color: var(--faded);
  flex-wrap: wrap;
}

.gl-hud b { color: var(--ink); }
.gl-lives { letter-spacing: 0.15em; color: var(--blush); }

.gl-controls {
  display: flex;
  align-items: center;
  gap: 0.9rem;
  margin-top: 0.7rem;
  flex-wrap: wrap;
}

.gl-controls button {
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 0.85rem;
  padding: 0.4rem 0.9rem;
  border-radius: 6px;
  border: 1px solid var(--line);
  background: var(--card);
  color: var(--ink);
  cursor: pointer;
}

.gl-controls button:hover { border-color: var(--moss); color: var(--moss); }

.gl-status {
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 0.8rem;
  color: var(--faded);
  line-height: 1.5;
}

@media (max-width: 480px) {
  .gl-hud { font-size: 0.78rem; }
}

/* --- /buzz: a Solanum-type flower with a poricidal anther cone.
   Petals and cone reuse the site's existing --petal/--floret accent
   tokens rather than a new palette, the same discipline /pod already
   set for a "hand-drawn from existing colors" room. --- */

.bz-wrap { margin: 1.5rem 0; }

#bz-svg {
  display: block;
  width: 100%;
  max-width: 360px;
  margin: 0 auto;
}

.bz-stem {
  fill: none;
  stroke: var(--stem-deep);
  stroke-width: 2;
  stroke-linecap: round;
}

.bz-petal {
  fill: var(--petal);
  stroke: var(--stem-deep);
  stroke-width: 0.6;
  opacity: 0.88;
}

.bz-cone {
  transform-box: fill-box;
  transform-origin: 50% 0%;
}

.bz-anther {
  fill: var(--floret);
  stroke: var(--stem-deep);
  stroke-width: 0.6;
  transition: opacity 0.4s ease;
}

@media (prefers-reduced-motion: reduce) {
  .bz-anther { transition: none; }
}

.bz-pore { fill: var(--blush); }

@keyframes bz-shake {
  0%, 100% { transform: rotate(0deg) translateX(0); }
  25% { transform: rotate(-4deg) translateX(-1.5px); }
  50% { transform: rotate(0deg) translateX(0); }
  75% { transform: rotate(4deg) translateX(1.5px); }
}

.bz-buzzing { animation: bz-shake 0.09s linear infinite; }

@media (prefers-reduced-motion: reduce) {
  .bz-buzzing { animation: none; }
}

.bz-grain {
  fill: var(--petal);
  stroke: var(--stem-deep);
  stroke-width: 0.3;
}

.bz-hit { fill: transparent; cursor: pointer; }
.bz-hit:hover { fill: var(--moss); opacity: 0.1; }
.bz-hit:focus-visible {
  outline: 2px solid var(--moss-deep);
  outline-offset: 2px;
}

.bz-bee { cursor: pointer; }
.bz-bee:focus-visible {
  outline: 2px solid var(--moss-deep);
  outline-offset: 3px;
}

.bz-bee-bumble-body { fill: var(--petal); stroke: var(--ink); stroke-width: 0.8; }
.bz-bee-honey-body { fill: var(--floret); stroke: var(--ink); stroke-width: 0.6; }
.bz-bee-stripe { fill: none; stroke: var(--ink); stroke-width: 1.4; stroke-linecap: round; }
.bz-bee-wing { fill: var(--card); stroke: var(--line); stroke-width: 0.5; opacity: 0.75; }

.bz-controls {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.5rem;
  margin-top: 0.75rem;
}

.bz-buttons {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 0.5rem;
}

.bz-buttons button {
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 0.8rem;
  color: var(--ink);
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: 3px;
  padding: 0.4rem 0.65rem;
  cursor: pointer;
}
.bz-buttons button:hover { border-color: var(--moss); color: var(--moss); }

.bz-status {
  display: block;
  text-align: center;
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 0.78rem;
  color: var(--faded);
  min-height: 1.2em;
  max-width: 32rem;
}

.bz-tally {
  display: block;
  text-align: center;
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 0.72rem;
  color: var(--faded);
  opacity: 0.85;
}

/* --- /stir: a bar chart of simulated hour-by-hour Mimosa pudica
   movement, plus a small leaflet glyph reusing /touch's own
   leaflet-pair shape at a much smaller scale. Reuses --moss/--ink
   rather than a new palette. --- */

.st-wrap { margin: 1.5rem 0; }

#st-chart {
  display: block;
  width: 100%;
  max-width: 640px;
  margin: 0 auto;
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: 4px;
}

.st-bar-light { fill: var(--petal); opacity: 0.55; }
.st-bar-dark { fill: var(--moss-deep); }
.st-baseline { stroke: var(--line); stroke-width: 1; }

.st-leaf-row {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 1rem;
  flex-wrap: wrap;
  margin-top: 0.85rem;
}

#st-leaf {
  width: 96px;
  height: auto;
  flex: 0 0 auto;
}

.st-rachis { stroke: var(--stem-deep); stroke-width: 1.4; }

.st-leaflet {
  fill: var(--moss);
  stroke: var(--stem-deep);
  stroke-width: 0.5;
  transform-box: fill-box;
  transform-origin: 100% 50%;
  transition: transform 0.3s ease, fill 0.3s ease;
}

#st-leaflets.st-open .st-leaflet { fill: var(--leaf-a); }

#st-leaflets .st-leaflet-pair {
  --stir: 0;
}

@keyframes st-wobble {
  0%, 100% { transform: rotate(calc(var(--stir) * -6deg)); }
  50% { transform: rotate(calc(var(--stir) * 6deg)); }
}

#st-leaflets.st-stirring .st-leaflet-pair {
  animation: st-wobble 0.9s ease-in-out infinite;
}

@media (prefers-reduced-motion: reduce) {
  .st-leaflet { transition: fill 0.3s ease; }
  #st-leaflets.st-stirring .st-leaflet-pair { animation: none; }
}

.st-status {
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 0.8rem;
  color: var(--faded);
  max-width: 26rem;
}

.st-controls {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.6rem;
  margin-top: 0.85rem;
}

.st-phase {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 0.75rem;
  border: 1px solid var(--line);
  border-radius: 4px;
  padding: 0.5rem 0.75rem;
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 0.78rem;
}

.st-phase legend {
  font-size: 0.72rem;
  color: var(--faded);
  padding: 0 0.3rem;
}

.st-phase label {
  display: flex;
  align-items: center;
  gap: 0.3rem;
  cursor: pointer;
}

.st-buttons {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 0.5rem;
}

.st-buttons button {
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 0.8rem;
  color: var(--ink);
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: 3px;
  padding: 0.4rem 0.65rem;
  cursor: pointer;
}
.st-buttons button:hover { border-color: var(--moss); color: var(--moss); }

.st-cycle {
  display: block;
  text-align: center;
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 0.72rem;
  color: var(--faded);
  opacity: 0.85;
}

/* --- /haha: a ha-ha's own cross-section, drawn twice — the true
   profile (dashed) and what a walker at the current slider distance
   can actually see (solid), which flattens into a straight line
   whenever the near lip still blocks the ditch. Reuses --moss/--floret
   for hidden/revealed rather than a new palette. --- */

.hh-wrap { margin: 1.5rem 0; }

#hh-svg {
  display: block;
  width: 100%;
  max-width: 640px;
  margin: 0 auto;
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: 4px;
}

.hh-truth {
  fill: none;
  stroke: var(--line);
  stroke-width: 1.5;
  stroke-dasharray: 4 3;
}

.hh-seen {
  fill: none;
  stroke: var(--moss);
  stroke-width: 2.4;
  stroke-linejoin: round;
  transition: stroke 0.25s ease;
}

#hh-svg.hh-revealed .hh-seen { stroke: var(--floret); }

.hh-sightline {
  stroke: var(--faded);
  stroke-width: 0.8;
  stroke-dasharray: 2 2;
  opacity: 0.6;
}

.hh-sheep-body { fill: var(--card); stroke: var(--ink); stroke-width: 1; }
.hh-sheep-head { fill: var(--ink); }
.hh-sheep line { stroke: var(--ink); stroke-width: 1; }

.hh-walker-stem { stroke: var(--ink); stroke-width: 1; }
.hh-walker-eye { fill: var(--petal); stroke: var(--ink); stroke-width: 0.6; }

@media (prefers-reduced-motion: reduce) {
  .hh-seen { transition: none; }
}

.hh-controls {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.5rem;
  margin-top: 0.85rem;
}

.hh-controls label {
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 0.8rem;
  color: var(--faded);
}

#hh-slider { width: 100%; max-width: 420px; }

.hh-status {
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 0.82rem;
  color: var(--ink);
  min-height: 1.2em;
  text-align: center;
  max-width: 26rem;
}

.hh-formula {
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 0.95rem;
  text-align: center;
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: 4px;
  padding: 0.6rem 0.75rem;
  max-width: 460px;
  margin: 0.75rem auto;
}

/* --- /dial: a horizontal sundial's face, drawn from one formula —
   the hour lines and the live shadow both come from
   dialAngleDeg(latitude, hourAngle) in dial.js, nothing precomputed
   or looked up. Reuses --moss/--floret/--line rather than a new
   palette, same as every other room's own SVG. --- */

.sd-wrap {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-start;
  justify-content: center;
  gap: 1.5rem;
  margin: 1.5rem 0;
}

#sd-svg {
  display: block;
  width: 100%;
  max-width: 360px;
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: 50%;
}

#sd-side-svg {
  display: block;
  width: 100%;
  max-width: 160px;
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: 4px;
  align-self: center;
}

.sd-plate { fill: none; stroke: var(--line); stroke-width: 1.5; }

.sd-hour-line { stroke: var(--line); stroke-width: 1; }
.sd-noon-line { stroke: var(--faded); stroke-width: 1.4; }

.sd-hour-label {
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 10px;
  fill: var(--faded);
  text-anchor: middle;
  dominant-baseline: middle;
}

.sd-gnomon-top { stroke: var(--ink); stroke-width: 3; stroke-linecap: round; }

.sd-shadow {
  stroke: var(--floret);
  stroke-width: 2.2;
  transition: x2 0.4s ease, y2 0.4s ease;
}
.sd-shadow-tip { fill: var(--floret); }

@media (prefers-reduced-motion: reduce) {
  .sd-shadow { transition: none; }
}

.sd-side-base, .sd-side-style { stroke: var(--ink); stroke-width: 2; stroke-linecap: round; }
.sd-side-arc { fill: none; stroke: var(--moss); stroke-width: 1.2; }
.sd-side-label {
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 11px;
  fill: var(--moss-deep);
  text-anchor: end;
}

.sd-controls {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.6rem;
  width: 100%;
}

.sd-picks { display: flex; gap: 0.5rem; flex-wrap: wrap; justify-content: center; }
.sd-picks button {
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 0.75rem;
  color: var(--faded);
  background: transparent;
  border: 1px solid var(--line);
  border-radius: 3px;
  padding: 0.35rem 0.6rem;
  cursor: pointer;
}
.sd-picks button:hover { border-color: var(--moss); color: var(--moss); }
.sd-picks button:focus-visible { outline: 2px solid var(--moss-deep); outline-offset: 2px; }
.sd-picks button[aria-pressed="true"] {
  color: var(--paper);
  background: var(--moss);
  border-color: var(--moss-deep);
}

.sd-status {
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 0.82rem;
  color: var(--ink);
  min-height: 1.2em;
  text-align: center;
  max-width: 30rem;
}

.sd-formula {
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 0.9rem;
  text-align: center;
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: 4px;
  padding: 0.6rem 0.75rem;
  max-width: 460px;
  margin: 0.25rem auto 0;
}

/* --- /vortex: two of Saturn's own polar jet streams, drawn as plain
   polygons. Neither shape moves on its own — the slider steps through
   five fixed dates instead of a physical quantity, so there is no
   transition to disable under reduced motion here at all. --- */

.vx-wrap {
  margin: 1.5rem 0;
}

#vx-svg {
  display: block;
  width: 100%;
  max-width: 560px;
  margin: 0 auto;
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: 4px;
}

.vx-pole { fill: none; stroke: var(--line); stroke-width: 1; stroke-dasharray: 2 3; }

.vx-hexagon { fill: none; stroke: var(--ink); stroke-width: 2.5; stroke-linejoin: round; }

.vx-decagon { fill: none; stroke: var(--floret); stroke-linejoin: round; }
.vx-decagon.vx-faint { stroke-width: 1.4; stroke-dasharray: 1.5 4; opacity: 0.4; }
.vx-decagon.vx-band { stroke-width: 1.8; stroke-dasharray: 4 3; opacity: 0.65; }
.vx-decagon.vx-sharp { stroke-width: 2.2; stroke-dasharray: none; opacity: 0.85; }
.vx-decagon.vx-confirmed { stroke-width: 2.5; stroke-dasharray: none; opacity: 1; }

.vx-badge {
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 11px;
  fill: var(--floret);
  text-anchor: middle;
  letter-spacing: 0.04em;
}

.vx-pole-label {
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 12px;
  fill: var(--faded);
  text-anchor: middle;
}

.vx-controls {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.5rem;
  margin-top: 0.85rem;
}

.vx-controls label {
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 0.85rem;
  color: var(--ink);
}

.vx-controls input[type="range"] { width: 100%; max-width: 420px; }

.vx-status {
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 0.82rem;
  color: var(--ink);
  min-height: 1.2em;
  text-align: center;
  max-width: 34rem;
  margin: 0;
}

/* --- /slack: the rope-around-the-Earth puzzle. Two concentric
   circles, drawn at one fixed size for every body (arbitrary, not to
   scale of each other), separated by a gap drawn at one fixed real
   scale — pixels per meter of height — so the same slider value draws
   the same gap no matter which body is picked. Reuses --moss/--floret
   rather than a new palette, same as every other room's own SVG. --- */

.sl-wrap {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  gap: 1.5rem;
  margin: 1.5rem 0 1rem;
}

#sl-svg {
  display: block;
  width: 100%;
  max-width: 300px;
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: 4px;
}

.sl-surface { fill: none; stroke: var(--ink); stroke-width: 2; }
.sl-rope { fill: none; stroke: var(--floret); stroke-width: 2; stroke-dasharray: 5 3; }
.sl-gap-line { stroke: var(--moss-deep); stroke-width: 1; }
.sl-gap-label {
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 11px;
  fill: var(--moss-deep);
}

.sl-readout {
  display: grid;
  grid-template-columns: auto auto;
  gap: 0.3rem 0.9rem;
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 0.85rem;
  margin: 0;
}

.sl-readout div { display: contents; }

.sl-readout dt { color: var(--faded); }
.sl-readout dd {
  margin: 0;
  text-align: right;
  color: var(--ink);
  white-space: nowrap;
}

.sl-readout-extra dt, .sl-readout-extra dd {
  color: var(--floret);
  font-weight: 600;
}

.sl-controls {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.5rem;
  margin-top: 0.5rem;
}

.sl-picks { display: flex; gap: 0.5rem; flex-wrap: wrap; justify-content: center; }
.sl-picks button {
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 0.75rem;
  color: var(--faded);
  background: transparent;
  border: 1px solid var(--line);
  border-radius: 3px;
  padding: 0.35rem 0.6rem;
  cursor: pointer;
}
.sl-picks button:hover { border-color: var(--moss); color: var(--moss); }
.sl-picks button:focus-visible { outline: 2px solid var(--moss-deep); outline-offset: 2px; }
.sl-picks button[aria-pressed="true"] {
  color: var(--paper);
  background: var(--moss);
  border-color: var(--moss-deep);
}

.sl-controls label {
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 0.8rem;
  color: var(--faded);
}

#sl-slider { width: 100%; max-width: 420px; }

.sl-status {
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 0.82rem;
  color: var(--ink);
  min-height: 1.2em;
  text-align: center;
  max-width: 30rem;
}

.sl-formula {
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 0.95rem;
  text-align: center;
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: 4px;
  padding: 0.6rem 0.75rem;
  max-width: 460px;
  margin: 0.75rem auto;
}

/* Site search (2026-09-05): one input, filters every room, note, and
   skill this site has, live — see search.js for why nothing here is
   a duplicated index kept by hand. */
.gs-field {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 0.3rem 0.7rem;
  margin: 1rem 0 0.4rem;
}
.gs-field label {
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 0.78rem;
  letter-spacing: 0.06em;
  color: var(--faded);
}
.gs-field input[type="search"] {
  font-family: "Alegreya", Georgia, serif;
  font-size: 1.05rem;
  color: var(--ink);
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: 3px;
  padding: 0.4rem 0.6rem;
  width: 100%;
  max-width: 26rem;
}
.gs-field input[type="search"]:focus { outline: 1px solid var(--moss); outline-offset: 1px; }
#gs-status {
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 0.78rem;
  color: var(--faded);
  margin: 0 0 1.2rem;
  min-height: 1.2em;
}
ul.gs-results {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 1rem;
}
.gs-item .gs-kind {
  display: inline-block;
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 0.68rem;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--faded);
  border: 1px solid var(--line);
  border-radius: 3px;
  padding: 0.05rem 0.35rem;
  margin-right: 0.5rem;
  vertical-align: middle;
}
.gs-item a {
  font-family: "Young Serif", Georgia, serif;
  font-size: 1.05rem;
}
.gs-item p {
  margin: 0.25rem 0 0;
  color: var(--ink);
}
.gs-empty { color: var(--faded); }

/* --- /herbarium: everything pressed from the garden, greenhouse, or
   home page, kept as a shelf instead of a one-off download. Cards
   render the exact stored sheet SVG at a fixed small width — the same
   fixed paper colors press.js already hardcodes, not this page's own
   --card/--paper tokens, so a sheet looks identical here to the file
   it also downloaded. --- */

.hb-actions { margin: 0.5rem 0 1.25rem; }

.hb-actions button,
.hb-card button {
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 0.75rem;
  color: var(--faded);
  background: transparent;
  border: 1px solid var(--line);
  border-radius: 3px;
  padding: 0.35rem 0.6rem;
  cursor: pointer;
}
.hb-actions button:hover,
.hb-card button:hover { border-color: var(--moss); color: var(--moss); }
.hb-actions button:focus-visible,
.hb-card button:focus-visible { outline: 2px solid var(--moss-deep); outline-offset: 2px; }

.hb-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(190px, 1fr));
  gap: 1.25rem;
  margin: 1rem 0 2.5rem;
}

.hb-card {
  margin: 0;
  border: 1px solid var(--line);
  border-radius: 6px;
  padding: 0.6rem;
  background: var(--card);
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
}

.hb-sheet {
  border-radius: 3px;
  overflow: hidden;
  line-height: 0;
}
.hb-sheet svg { display: block; width: 100%; height: auto; }

.hb-card figcaption {
  display: flex;
  flex-direction: column;
  gap: 0.3rem;
}

.hb-label {
  font-family: "Young Serif", Georgia, serif;
  font-style: italic;
  font-size: 0.95rem;
}

.hb-when {
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 0.7rem;
  color: var(--faded);
}

.hb-row { display: flex; gap: 0.4rem; flex-wrap: wrap; }

/* --- /drift: fold a paper boat, give it a word, set it on the water.
   Not sourced to anything and not scored — see drift.js for why that's
   a deliberate third shape, next to "a fact to read" and "a game to
   win." Water reuses the existing --moist-fill/--moist-line tokens
   roots.js already established for wet things; the hull reuses
   --root-pale, the one existing "pale, dry, papery" token on the site. */
.dr-wrap {
  max-width: 40rem;
  margin: 1.4rem auto 0;
}

.dr-controls {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.6rem;
  margin-bottom: 0.8rem;
}

.dr-controls input[type="text"] {
  font-family: "Alegreya", Georgia, serif;
  font-size: 1rem;
  color: var(--ink);
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: 3px;
  padding: 0.35rem 0.55rem;
  flex: 1 1 12rem;
  max-width: 16rem;
}
.dr-controls input[type="text"]:focus-visible { outline: 1px solid var(--moss); outline-offset: 1px; }

.dr-controls button {
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 0.85rem;
  padding: 0.4rem 0.9rem;
  border-radius: 6px;
  border: 1px solid var(--line);
  background: var(--card);
  color: var(--ink);
  cursor: pointer;
}
.dr-controls button:hover { border-color: var(--moss); color: var(--moss); }

.dr-svg {
  display: block;
  width: 100%;
  height: auto;
  aspect-ratio: 640 / 360;
  background: var(--paper);
  border: 1px solid var(--line);
  border-radius: 8px;
}
.dr-svg:focus-visible { outline: 2px solid var(--moss); outline-offset: 2px; }

.dr-water { stroke: var(--moist-fill); fill: none; stroke-linecap: round; }
.dr-water-line { stroke: var(--moist-line); fill: none; stroke-linecap: round; }
.dr-reed { fill: var(--leaf-a); }
.dr-reed-b { fill: var(--leaf-b); }

.dr-boat .dr-hull { fill: var(--root-pale); stroke: var(--ink); stroke-width: 1; }
.dr-boat .dr-mast { stroke: var(--ink); stroke-width: 1; }
.dr-boat .dr-sail { fill: var(--paper); stroke: var(--ink); stroke-width: 1; }
.dr-boat .dr-word { fill: var(--ink); }

.dr-status {
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 0.8rem;
  color: var(--faded);
  margin-top: 0.6rem;
}

/* --- /hum: a 1-Hz-step tone generator, the same kind of tool the
   study's own participants used to name what they hear. Reuses --moss
   for the wave stroke, the site's one existing "living, current"
   accent, and the plain unstyled <input type="range"> already
   established by /slack and /vortex rather than a new custom thumb. */
.hu-wrap {
  max-width: 40rem;
  margin: 1.4rem auto 0;
}

.hu-svg {
  display: block;
  width: 100%;
  height: auto;
  aspect-ratio: 640 / 120;
  background: var(--paper);
  border: 1px solid var(--line);
  border-radius: 8px;
  margin-bottom: 0.9rem;
}

.hu-axis { stroke: var(--line); stroke-width: 1; }
.hu-path { stroke: var(--moss); stroke-width: 2; fill: none; stroke-linecap: round; stroke-linejoin: round; }

.hu-controls {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.6rem;
  margin-bottom: 0.7rem;
}

.hu-controls label {
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 0.85rem;
  color: var(--ink);
}

.hu-controls input[type="range"] { flex: 1 1 12rem; max-width: 20rem; }

.hu-controls output {
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 0.85rem;
  color: var(--ink);
  min-width: 4.5rem;
}

.hu-controls button {
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 0.85rem;
  padding: 0.4rem 0.9rem;
  border-radius: 6px;
  border: 1px solid var(--line);
  background: var(--card);
  color: var(--ink);
  cursor: pointer;
}
.hu-controls button:hover { border-color: var(--moss); color: var(--moss); }

.hu-status {
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 0.8rem;
  color: var(--faded);
  margin: 0;
}

/* --- /nightbreak: a day/night bar computed live from a latitude and
   a day of year (plain sunrise-equation geometry), plus a button that
   splits the night segment to dramatize a real finding — see the
   page's own text and nightbreak.js's header comment. Day and night
   use fixed colors rather than this site's usual theme-reactive
   tokens (--ink, --petal, ...) on purpose: those tokens swap roles in
   dark mode (--ink turns pale, since it's the body's own text color)
   so the "night" segment would render pale instead of dark exactly
   where dark mode is on. Real darkness doesn't change with a
   reader's theme preference, so this bar doesn't either. */
.nk-wrap { max-width: 40rem; margin: 0 auto; }

.nk-controls {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.6rem;
  margin-bottom: 0.7rem;
}

.nk-controls label {
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 0.85rem;
  color: var(--ink);
}

.nk-controls input[type="range"] { flex: 1 1 12rem; max-width: 20rem; }

.nk-controls output {
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 0.85rem;
  color: var(--ink);
  min-width: 4.5rem;
}

.nk-controls button {
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 0.85rem;
  padding: 0.4rem 0.9rem;
  border-radius: 6px;
  border: 1px solid var(--line);
  background: var(--card);
  color: var(--ink);
  cursor: pointer;
}
.nk-controls button:hover { border-color: var(--moss); color: var(--moss); }

.nk-bar {
  display: flex;
  width: 100%;
  height: 2.6rem;
  border: 1px solid var(--line);
  border-radius: 8px;
  overflow: hidden;
  margin: 1.2rem 0 0.6rem;
}

.nk-day {
  background: #c9a13d;
  transition: width 0.2s ease;
}

.nk-night {
  background: #1b2117;
  position: relative;
  transition: width 0.2s ease;
}

.nk-flash {
  position: absolute;
  top: 0;
  left: 50%;
  width: 3px;
  height: 100%;
  background: #f5f0dc;
  transform: translateX(-50%);
  box-shadow: 0 0 6px 1px #f5f0dc;
}

.nk-readout {
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 0.85rem;
  color: var(--ink);
  margin: 0 0 0.5rem;
}

.nk-readout.nk-break {
  color: var(--faded);
}

/* --- /drop: a 2026 atom-interferometer test of Einstein's equivalence
   principle in the quantum regime — see drop.js. Reuses --card/--line/
   --ink/--faded/--moss/--rain, no new palette: --moss stands in for
   the path held steady (the same grounded role it plays on a stem),
   --rain for the path let fall (the same role it plays on a raindrop
   elsewhere), never literal colors of anything in the real apparatus. */

.dp-wrap {
  margin: 1.5rem 0;
}

.dp-svg {
  display: block;
  width: 100%;
  max-width: 420px;
  margin: 0 auto;
  border: 1px solid var(--line);
  border-radius: 4px;
  background: var(--card);
}

.dp-chip {
  fill: var(--line);
  stroke: var(--ink);
  stroke-width: 1.2;
}

.dp-arm {
  fill: none;
  stroke: var(--faded);
  stroke-width: 1.3;
  stroke-dasharray: 3 3;
  opacity: 0.7;
}

.dp-node-label {
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 8.5px;
  fill: var(--faded);
  text-anchor: middle;
}

.dp-split-label {
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 9px;
  fill: var(--ink);
  text-anchor: middle;
}

.dp-atom-held, .dp-atom-free {
  transition: opacity 0.6s ease;
}

.dp-atom-held { fill: var(--moss); }
.dp-atom-free { fill: var(--rain); }

.dp-svg.is-revealed .dp-atom-held,
.dp-svg.is-revealed .dp-atom-free {
  opacity: 0;
}

.dp-recombined {
  fill: var(--ink);
  opacity: 0;
  transition: opacity 0.6s ease;
}

.dp-svg.is-revealed .dp-recombined {
  opacity: 1;
}

.dp-fringe {
  stroke: var(--rain);
  stroke-width: 1.4;
  opacity: 0;
  transition: opacity 0.6s ease;
}

.dp-svg.is-revealed .dp-fringe {
  opacity: 0.75;
}

@media (prefers-reduced-motion: reduce) {
  .dp-atom-held, .dp-atom-free, .dp-recombined, .dp-fringe { transition: none; }
}

.dp-gravity {
  stroke: var(--faded);
  stroke-width: 1.2;
  opacity: 0.6;
}

.dp-gravity-label {
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 8.5px;
  fill: var(--faded);
}

.dp-controls {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  gap: 0.7rem;
  max-width: 420px;
  margin: 0.9rem auto 0;
}

.dp-controls button {
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 0.8rem;
  color: var(--ink);
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: 3px;
  padding: 0.45rem 0.75rem;
  cursor: pointer;
}
.dp-controls button:hover { border-color: var(--moss); color: var(--moss); }

.dp-status {
  display: block;
  width: 100%;
  text-align: center;
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 0.85rem;
  color: var(--faded);
}

.pk-wrap {
  margin: 1.5rem 0;
}

.pk-hud {
  display: flex;
  justify-content: center;
  gap: 1.5rem;
  margin-bottom: 0.5rem;
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 0.85rem;
}

.pk-label {
  color: var(--faded);
}

.pk-bed {
  position: relative;
  width: 100%;
  max-width: 420px;
  height: 220px;
  margin: 0 auto;
  border: 1px solid var(--line);
  border-radius: 4px;
  background: var(--card);
  overflow: hidden;
}

.pk-weed {
  position: absolute;
  width: 28px;
  height: 28px;
  padding: 2px;
  margin: 0;
  border: none;
  background: none;
  color: var(--moss);
  cursor: pointer;
  transform: translate(-50%, -50%) scale(0.3);
  opacity: 0.45;
  animation: pk-grow linear forwards;
}

.pk-weed svg {
  width: 100%;
  height: 100%;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.6;
  stroke-linecap: round;
}

@keyframes pk-grow {
  from { transform: translate(-50%, -50%) scale(0.3); opacity: 0.45; }
  to { transform: translate(-50%, -50%) scale(1); opacity: 1; }
}

.pk-weed.pk-pulled {
  animation: none;
  transform: translate(-50%, -50%) scale(1.3);
  opacity: 0;
  transition: transform 0.16s ease, opacity 0.16s ease;
  color: var(--rain);
}

.pk-weed.pk-rooted {
  animation: none;
  transform: translate(-50%, -50%) scale(1);
  color: var(--faded);
  opacity: 0.6;
  cursor: default;
}

@media (prefers-reduced-motion: reduce) {
  .pk-weed { animation: none; transform: translate(-50%, -50%) scale(1); opacity: 0.85; }
  .pk-weed.pk-pulled, .pk-weed.pk-rooted { transition: none; }
}

.pk-controls {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  gap: 0.7rem;
  max-width: 420px;
  margin: 0.9rem auto 0;
}

.pk-controls button {
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 0.8rem;
  color: var(--ink);
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: 3px;
  padding: 0.45rem 0.75rem;
  cursor: pointer;
}
.pk-controls button:hover { border-color: var(--moss); color: var(--moss); }

.pk-status {
  display: block;
  width: 100%;
  text-align: center;
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 0.85rem;
  color: var(--faded);
}

.ct-wrap {
  margin: 1.5rem 0;
}

.ct-output {
  min-height: 5rem;
  margin: 0 0 0.9rem;
  padding: 1.1rem 1.3rem;
  border: 1px solid var(--line);
  border-radius: 4px;
  background: var(--card);
  font-family: "Alegreya", Georgia, serif;
  font-size: 1.05rem;
  line-height: 1.55;
  color: var(--ink);
}

.ct-controls {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.7rem;
}

.ct-controls button {
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 0.8rem;
  color: var(--ink);
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: 3px;
  padding: 0.45rem 0.75rem;
  cursor: pointer;
}
.ct-controls button:hover { border-color: var(--moss); color: var(--moss); }

.ct-status {
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 0.8rem;
  color: var(--faded);
}

/* --- /chaff: 200 fake background readings, winnowed live against a
   real 2.6-sigma dark-matter event. --- */

.cf-wrap {
  margin: 1.5rem 0;
}

#cf-svg {
  display: block;
  width: 100%;
  max-width: 480px;
  margin: 0 auto;
  border: 1px solid var(--line);
  border-radius: 4px;
  background: var(--card);
}

.cf-axis {
  stroke: var(--line);
  stroke-width: 1.2;
}

.cf-tick {
  stroke: var(--line);
  stroke-width: 1;
}

.cf-tick-label {
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 8px;
  fill: var(--faded);
}

.cf-threshold {
  stroke: var(--blush);
  stroke-width: 1.2;
  stroke-dasharray: 3 2;
  opacity: 0.8;
}

.cf-threshold-label {
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 8px;
  fill: var(--blush);
}

.cf-dot {
  fill: var(--moss);
  opacity: 0.55;
}

.cf-dot-hit {
  fill: var(--blush);
  opacity: 1;
}

.cf-hit-label {
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 9px;
  font-weight: 500;
  fill: var(--blush);
}

.cf-controls {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  gap: 0.7rem;
  max-width: 480px;
  margin: 0.9rem auto 0;
}

.cf-controls button {
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 0.8rem;
  color: var(--ink);
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: 3px;
  padding: 0.45rem 0.75rem;
  cursor: pointer;
}
.cf-controls button:hover { border-color: var(--moss); color: var(--moss); }

.cf-status {
  display: block;
  width: 100%;
  max-width: 480px;
  margin: 0.6rem auto 0;
  text-align: center;
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 0.8rem;
  color: var(--faded);
  min-height: 1.2em;
}

.cf-tally {
  width: 100%;
  max-width: 480px;
  margin: 0.2rem auto 0;
  text-align: center;
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 0.78rem;
  color: var(--ink);
}
