/* orrery.css - the shared design language for every body page.
 *
 * WHERE IT COMES FROM. Macel pointed at worldlens.live and said it looks better. I fetched it and
 * measured what "better" is made of rather than eyeballing it: Inter for prose, ui-monospace for
 * anything that is a number, a near-black base (#0b0f17) with translucent panels (#121823 at
 * 40-80%) over a full-bleed globe, hairline borders (#2a3344), letterspaced caps for micro-labels,
 * and one saturated accent used sparingly. That is a design LANGUAGE, and a language is the part
 * worth taking - their stack is Next.js on Vercel and we self-host, so copying the stack would buy
 * nothing and cost the deploy path we already have.
 *
 * THE ONE RULE THAT MAKES THIS A KIT: every colour that identifies a BODY comes from --accent,
 * which the page sets from bodies.json. Nothing in this file names the Moon. If a rule here only
 * makes sense for one body, it is in the wrong file.
 */

:root {
  --ink:        #0a0e16;   /* the ground behind everything */
  --ink-2:      #0f141f;
  --panel:      #121823;
  /* [2026-08-18, Luna's besluit op OLY-105; meting Atlas, worklog #367] .72/.55 -> .94/.90.
     De contrastmeting op de PIXELS liet zien dat de panelen niet de oorzaak waren van de fails
     (met volledig ondoorzichtige panelen haalde --dimmer nog steeds maar 3.26), maar wel de
     spreiding: onder een half doorzichtig paneel schijnt de bol door en zakt de slechtste pixel
     van een label naar 3.33 op 390px. Kleur EN dekking samen zijn de enige variant met nul fails
     en geen spreiding (mediaan 4.68, slechtste pixel 4.64) op beide breedtes.
     DE RUIL, expliciet: minder doorkijk naar de globe onder de kaarten. Voor een kit die 21
     pagina's draagt is leesbaarheid de eerste eis - dat is Luna's afweging, niet de mijne. */
  /* [2026-08-18, tweede besluit van Luna, na mijn nameting op de uitgerolde pagina's] .94/.90 ->
     VOLLEDIG ONDOORZICHTIG. Haar criterium was "de variant zonder compromis"; .94/.90 bleek dat
     niet te zijn (op 390px: 1 mediaan-fail 4.42 en 2 slechtste-pixel-fails 4.34 en 4.22), volledig
     ondoorzichtig wel (0 fails, laagste mediaan 4.55, slechtste pixel 4.51). Het verlies is echt en
     bewust: je ziet de bol niet meer door de kaarten heen. */
  --panel-a:    rgb(18, 24, 35);
  --panel-b:    rgb(11, 15, 23);
  --line:       #2a3344;
  --line-soft:  rgba(42, 51, 68, 0.55);
  --text:       #e8eef7;
  --dim:        #8b97ab;
  /* [2026-08-18] #5d6879 -> #758195: zelfde tint en verzadiging, alleen lichter, en de laagste
     waarde die 4.5 haalt. Het oude token mat 3.15-3.59 op 26-34 elementen per pagina - micro-
     labels, tellers, voettekst - waar de norm 4.5 is. --dim (#8b97ab, 6.0-6.8) en --text
     (#e8eef7, 15-17) waren nooit het probleem; deze ene kleur droeg vrijwel alle fails.
     [2026-08-26, OLY-396, ergon-slot 31] #758195 -> #7a879b. "De laagste waarde die 4.5 haalt"
     was hierboven een BEWUSTE keuze, en precies daar zat het probleem: hij haalde 4.51 tegen
     --panel-a, dus 0,01 speling. Twee onafhankelijke metingen zetten hem op exact diezelfde vloer
     (08-18 bij het ontstaan, 26-08 bij de terugkerende meting van ERG-12: op alle zes de
     combinaties van 3 lichamen x 2 breedtes was div.micro het krapste element, slechtste pixel
     4.51). Een token zonder speling faalt bij de eerstvolgende achtergrondwijziging, en deze
     meting zou dat dan pas NA de breuk melden.
     #7a879b meet 4.88 tegen --panel-a. Bewust NIET verder (#7c899e gaf 5.02): geen van beide
     redt het active-rail-geval (--dimmer op rgba(255,255,255,0.07) over --panel-a = 3.75, harde
     fail) en dat hoeft ook niet - de rail tekent sinds OLY-333 in --text. Dus alleen zoveel
     speling als het --panel-a-geval vraagt, en geen tint-verschuiving die niemand besteld heeft.
     Review + besluit Luna op OLY-396, met een eigen WCAG-herberekening die deze cijfers
     bevestigde. Dat rail-getal staat hier expres genoteerd terwijl het NIET voorkomt: de enige
     bescherming tegen het opnieuw introduceren ervan is dat iemand deze regel leest. */
  --dimmer:     #7a879b;
  --accent:     #cfe0f5;   /* overridden per body */
  --gold:       #c9a961;   /* the fleet's ring colour, used once per page at most */
  --warn:       #fbbf24;
  --sans: Inter, ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
  --mono: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace;
  --r: 10px;
}

* { box-sizing: border-box; }

html, body {
  margin: 0; height: 100%;
  background: var(--ink);
  color: var(--text);
  font-family: var(--sans);
  font-size: calc(15px * var(--fs, 1));
  -webkit-font-smoothing: antialiased;
  overscroll-behavior: none;
}

/* ---- the stage: globe fills the viewport, everything else floats over it -------------------- */
#stage { position: fixed; inset: 0; background: radial-gradient(120% 90% at 50% 40%, #101725 0%, #05070c 70%); }
#stage .cesium-widget-credits { display: none !important; }
#stage canvas { outline: none; }

/* ---- HOEKRADII: DRIE NIVEAUS, GEEN ZESTIEN -------------------------------------------------
 * [2026-08-17, systeem van Luna op mijn UI-audit OLY-105; zij ontwierp deze CSS, dus dit is haar
 * beslissing en niet mijn opruiming. Zij zei expliciet: doorvoeren, niet wachten op mijn blik.]
 * De audit vond ZESTIEN verschillende radii in dit bestand. Dat is geen muggenzifterij: gemengde
 * radii is precies wat een interface "samengeraapt" laat lezen - een paneel met scherpe hoeken naast
 * een knop met ronde is het duidelijkste signaal dat niemand een keuze heeft gemaakt.
 * HET SYSTEEM:
 *    4px   kleine controls  - knoppen, filterrijen, chips, checkboxjes
 *   10px   kaarten/panelen  - de zijbalken, info-kaarten, de rail
 *   50%    volledig rond    - pills, dots, bolletjes
 * Alles wat ertussenin zat is naar het naaste niveau afgerond. Wie hier een vierde niveau toevoegt,
 * voegt de zestien weer terug - dan liever een van deze drie oprekken.
 */

/* ---- shared bits ---------------------------------------------------------------------------- */
/* [UI-audit OLY-105] 10 -> 11px. NIET naar 12: deze klasse draagt kopjes in KAPITAAL met 0,28em
 * letterspatiering (LAYERS, FEATURES, en de labels in de bovenbalk). Kapitalen lezen bij gelijke
 * puntgrootte groter dan onderkast, en de spatiering helpt mee - de 12px-ondergrens uit die
 * checklist is een heuristiek voor lopende tekst en geen WCAG-eis. Wat WEL fout was: de rail op
 * 9px, en de voettekst op 11px terwijl dat echte leestekst is (staat nu op 12). Dit is dus een
 * afweging en geen negeren van de bevinding. */
.micro {
  font-family: var(--mono);
  font-size: calc(11px * var(--fs, 1));
  letter-spacing: 0.28em;
  text-transform: uppercase;
  color: var(--dimmer);
}
.num { font-family: var(--mono); font-variant-numeric: tabular-nums; }

.panel {
  background: linear-gradient(180deg, var(--panel-a), var(--panel-b));
  border: 1px solid var(--line);
  border-radius: var(--r);
  backdrop-filter: blur(14px) saturate(1.2);
  -webkit-backdrop-filter: blur(14px) saturate(1.2);
  box-shadow: 0 12px 34px rgba(0, 0, 0, 0.45);
}

/* ---- top bar -------------------------------------------------------------------------------- */
#top {
  position: fixed; top: 0; left: 0; right: 0; z-index: 20;
  display: flex; align-items: center; gap: 18px;
  padding: 12px 18px;
  background: linear-gradient(180deg, rgba(5, 7, 12, 0.86), rgba(5, 7, 12, 0));
  pointer-events: none;
}
#top > * { pointer-events: auto; }
#mark { display: flex; align-items: baseline; gap: 10px; }
#mark b { font-size: calc(15px * var(--fs, 1)); font-weight: 600; letter-spacing: 0.02em; }
#mark .sub { color: var(--dim); font-size: calc(12px * var(--fs, 1)); }
#mark .dot {
  width: 7px; height: 7px; border-radius: 50%;
  background: var(--accent); box-shadow: 0 0 12px var(--accent);
  display: inline-block; margin-right: 6px;
  animation: pulse 2.6s ease-in-out infinite;
}
@keyframes pulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.35; } }
#live { margin-left: auto; display: flex; gap: 16px; align-items: center; }
#live .kv { text-align: right; line-height: 1.25; }
#live .kv .v { font-family: var(--mono); font-size: calc(13px * var(--fs, 1)); color: var(--text); }

/* ---- left: layers + filters ------------------------------------------------------------------ */
#left {
  position: fixed; left: 14px; top: 62px; z-index: 15;
  width: 232px; padding: 12px;
  display: flex; flex-direction: column; gap: 14px;
  /* [2026-08-17, gevonden op Titan] HOOGTE ZO DAT HIJ DE VOET NIET OVERLAPT. Titan heeft twaalf
     landvorm-families en dus twaalf rijen; het paneel werd daardoor 710px hoog en ging OVER #foot
     heen. De voet ligt lager in de stapel, dus die verdween erachter - behalve een reepje van 24px
     dat er rechts uitstak, en op het scherm zag je losse letters naast de filterrijen staan alsof er
     een renderfout was. Twee blokken die onder elkaar horen te staan mogen elkaar niet overlappen,
     ook niet als het bovenste toevallig groeit met het lichaam.
     De rekensom: 100vh - top(62) - voet(30vh) - onderrand(96) - tussenruimte(14). Beide blokken
     scrollen zelf, dus er valt niets weg; wat er niet meer gebeurt is dat de voet ONBEREIKBAAR
     achter het paneel ligt.
     [2026-08-19] De onderrand ging van 16 naar 96 omdat de voet zelf omhoog moest (zie #foot):
     dezelfde 80px moeten er hier af, anders schuift het paneel gewoon over de nieuwe voetpositie
     heen en is het probleem verplaatst in plaats van opgelost. */
  max-height: calc(70vh - 172px); overflow: auto;
}
.group > .micro { display: block; margin-bottom: 8px; }
/* [2026-08-17, UI-audit OLY-105 + Luna's prioriteitsadvies] RAAKVLAK NAAR 44px, EN DIT MOET VOOR DE
 * UITROL OVER 50 LICHAMEN. Gemeten stonden deze rijen op 27px hoog waar 44x44 de norm is - en dit is
 * de HELE bedieningsbalk: lagen aan/uit en elk typefilter. Op een telefoon mis je die dus half, en
 * Macel bekijkt deze pagina's op zijn telefoon. Luna's argument waarom het NU moet en niet later:
 * fix je het na de uitrol, dan heb je dezelfde fout vijftig keer uitgerold plus in elke nieuwe
 * frame-variant. min-height in plaats van een vaste hoogte, zodat een lange laagnaam over twee
 * regels nog steeds groeit in plaats van af te kappen. */
.row {
  display: flex; align-items: center; gap: 9px;
  padding: 10px 8px; margin: 1px -8px; min-height: 44px; box-sizing: border-box;
  border-radius: 4px; cursor: pointer;
  color: var(--dim); font-size: calc(13px * var(--fs, 1));
  transition: background 0.14s, color 0.14s;
}
.row:hover { background: rgba(255, 255, 255, 0.04); color: var(--text); }
.row.on { color: var(--text); }
.row .box {
  width: 13px; height: 13px; flex: none;
  border: 1px solid var(--line); border-radius: 4px;
  position: relative; background: rgba(0, 0, 0, 0.35);
}
.row.on .box { border-color: var(--accent); background: var(--accent); }
.row.on .box::after {
  content: ""; position: absolute; inset: 2px 3px 3px 2px;
  border-left: 2px solid #0a0e16; border-bottom: 2px solid #0a0e16;
  transform: rotate(-45deg);
}
.row .swatch { width: 9px; height: 9px; border-radius: 50%; flex: none; }
.row .count { margin-left: auto; font-family: var(--mono); font-size: calc(11px * var(--fs, 1)); color: var(--dimmer); }
/* EEN RIJ ZONDER SCHAKELAAR. [2026-08-19, Atlas] De "unlocated"-rijen zetten niets aan of uit -
   ze openen alleen de kaart - dus krijgen ze geen vakje en geen aan-stand. Een leeg vinkvakje naast
   een naam zou een filter suggereren dat er niet is. */
.row.los { cursor: pointer; padding-left: 2px; }
.row.los .count { text-transform: none; letter-spacing: 0; }

/* ---- right: the detail card ------------------------------------------------------------------ */
#card {
  position: fixed; right: 14px; top: 78px; z-index: 16;
  width: 320px; padding: 16px;
  display: none;
  max-height: calc(100vh - 166px); overflow: auto;
}
#card.open { display: block; animation: rise 0.18s ease-out; }
@keyframes rise { from { opacity: 0; transform: translateY(8px); } to { opacity: 1; transform: none; } }
#card h2 { margin: 6px 0 2px; font-size: calc(19px * var(--fs, 1)); font-weight: 600; letter-spacing: -0.01em; }
/* [2026-08-22, OLY-274] `#card .kind { color: var(--accent) }` STOND HIER EN RAAKTE NIETS.
 * show() bouwt het type-label als `el("div", "micro", ...)` (orrery.js:692), niet als .kind, en
 * nergens in de kit of in de 56 instances krijgt een element ooit class "kind" - gegrepd op de hele
 * repo, 0 treffers in JS en 0 in HTML. Het was een restant van oudere markup.
 * WAAROM WEGGEHAALD EN NIET, ZOALS BIJ #rail a.here, LATEN STAAN ALS TERUGVAL: die regel is een
 * terugval omdat hij WEL zou werken zodra de inline style verdwijnt. Deze had geen element om op te
 * wachten. Een dode selector die eruitziet als een besluit kost de volgende lezer een meting - ik
 * heb er zelf een aan verspild: mijn eerste meting rapporteerde .kind op 1.80, maar dat getal kwam
 * van een element dat mijn eigen sonde had aangemaakt, niet van de pagina.
 * Het echte type-label is .micro en dat draagt --dimmer, gemeten op 4.76 - haalt AA, geen actie. */
#card .facts {
  display: grid; grid-template-columns: auto 1fr; gap: 4px 14px;
  margin: 14px 0; padding: 12px 0;
  border-top: 1px solid var(--line-soft); border-bottom: 1px solid var(--line-soft);
  font-size: calc(13px * var(--fs, 1));
}
#card .facts dt { color: var(--dim); }
#card .facts dd { margin: 0; font-family: var(--mono); font-variant-numeric: tabular-nums; }
#card p { margin: 10px 0; font-size: calc(13.5px * var(--fs, 1)); line-height: 1.55; color: #c6cfdd; }
#card a.src {
  display: inline-block; margin-top: 4px; font-size: calc(12px * var(--fs, 1));
  /* [2026-08-22, OLY-274, Luna's besluit optie (a): --accent draagt geen TEKST meer.]
   * Was `color: var(--accent)`. Gemeten op de live pagina tegen de kaartachtergrond rgb(10,14,22):
   * ryugu 1.80, bennu 2.04 tegen een drempel van 4.5; mars 6.50 als controle. Dit is een LINK, dus
   * onleesbaar betekent hier ook onvindbaar - zwaarder dan lelijke tekst.
   * Het accent houdt zijn niet-tekst-rol: de :hover-rand hieronder blijft var(--accent), net zoals
   * de stip in de rail zijn accent-gloed houdt. Dat is dezelfde afweging als variant B op OLY-333. */
  color: var(--text); text-decoration: none; border-bottom: 1px solid transparent;
}
#card a.src:hover { border-bottom-color: var(--accent); }
#card .x {
  float: right; cursor: pointer; color: var(--dimmer);
  font-size: calc(20px * var(--fs, 1)); line-height: 1; padding: 0 2px;
}
#card .x:hover { color: var(--text); }

/* ---- bottom: the body rail - the whole point of building this as a kit ---------------------- */
#rail {
  position: fixed; left: 50%; transform: translateX(-50%); bottom: 14px; z-index: 18;
  display: flex; align-items: center; gap: 2px;
  padding: 8px 10px; max-width: min(94vw, 980px);
  overflow-x: auto; scrollbar-width: none;
  /* De rail is breder dan hij past zodra alle twintig lichamen erin staan, en een AFGESNEDEN naam
     aan de rand leest als een kapotte regel in plaats van als "hier kun je verder scrollen". Vandaar
     een uitvloeiing aan beide randen: dat is het enige verschil tussen "stuk" en "er is meer". */
  -webkit-mask-image: linear-gradient(90deg, transparent 0, #000 26px, #000 calc(100% - 26px), transparent 100%);
          mask-image: linear-gradient(90deg, transparent 0, #000 26px, #000 calc(100% - 26px), transparent 100%);
}
#rail::-webkit-scrollbar { display: none; }
/* a EN span: een gepland hemellichaam is geen link maar staat er wel, en moet er hetzelfde uitzien.
   De eerste versie stijlde alleen `a`, waardoor de zestien geplande namen als kale tekst aan elkaar
   plakten - "Sun Mercurius Venus Mars Ceres Jupiter Io" - en de rail eruitzag als een kapotte regel
   in plaats van als een routekaart. */
#rail a, #rail span.planned {
  /* Ook 44px: de lichaam-kiezer is de navigatie tussen 50 pagina's, en die moet je op een telefoon
     kunnen raken zonder de verkeerde planeet te openen. */
  flex: none; display: flex; flex-direction: column; align-items: center; gap: 6px;
  padding: 9px 10px; min-height: 44px; min-width: 44px; box-sizing: border-box; justify-content: center;
  border-radius: 10px;
  color: var(--dimmer); text-decoration: none;
  /* [UI-audit OLY-105] 9 -> 11px: dit is de navigatie tussen 50 pagina's en 9px is voor niemand
     comfortabel. Letterspatiering iets terug zodat de balk bij 50 lichamen niet breder wordt. */
  font-family: var(--mono); font-size: calc(11px * var(--fs, 1)); letter-spacing: 0.06em; text-transform: uppercase;
  transition: color 0.14s, background 0.14s;
}
#rail a .orb, #rail span.planned .orb {
  width: 12px; height: 12px; border-radius: 50%;
  background: currentColor; opacity: 0.55;
  transition: opacity 0.14s, box-shadow 0.14s, transform 0.14s;
}
#rail a:hover { color: var(--text); background: rgba(255, 255, 255, 0.05); }
#rail a:hover .orb { opacity: 1; }
/* planned bodies stay visibly UNBUILT rather than silently missing: the rail is a roadmap */
#rail span.planned { cursor: default; opacity: 0.5; }
#rail span.planned .orb { opacity: 0.25; box-shadow: none; }
#rail a.here { color: var(--text); background: rgba(255, 255, 255, 0.07); }
/* [2026-08-22, OLY-333 / OLY-274. Luna's decision, measured by Atlas before and after.]
 * THE `color` ON THE RULE ABOVE HAS NEVER ONCE TAKEN EFFECT, and that is the whole bug.
 * buildRail() in orrery.js does `a.style.color = b.id === here.id ? b.accent : ""` - an INLINE
 * style, which outranks any stylesheet rule. So the current body's own name was painted in its
 * body accent, and the rock-like bodies have dark grey-brown accents: measured LIVE at 390px,
 * ryugu #423d38 came out at 1.47 median / 1.43 worst pixel against a 4.5 threshold, the single
 * contrast failure on the page. 21 of 56 accents fall under 4.5 as text. It stayed hidden until
 * 2026-08-18 because the first 21 pages were nearly all planets with bright accents.
 * WHY THIS RULE AND NOT `!important` ON a.here (the other candidate, also measured):
 * `!important` fixes the text (ryugu 12.87) but .orb carries `box-shadow: 0 0 14px currentColor`,
 * so the dot's glow inherits --text and turns WHITE - the body loses its identity colour. Reading
 * the computed style under both: A gives orb glow rgb(232,238,247), B keeps rgb(66,61,56).
 * Targeting only the NAME span leaves the orb inheriting the accent, so the glow survives, and it
 * measures BETTER besides: ryugu 13.67 median / 13.18 worst, zero fails on the page.
 * The `color` above is deliberately left in place: it is the correct fallback the day the inline
 * style in buildRail goes away. It is dead today, not wrong.
 * DIT WAS DE DERDE VAN DRIE PLEKKEN WAAR --accent ECHTE TEKST SCHILDERDE (OLY-274). De andere twee
 * zijn later diezelfde dag gesloten op Luna's besluit (a): #card a.src ging naar --text, en
 * #card .kind bleek een dode selector en is weg. Sindsdien draagt --accent NERGENS meer tekst -
 * alleen nog vlak, rand, stip en accent-color. Wie hier een nieuwe accent-op-tekst toevoegt, opent
 * die klasse opnieuw; de meetmethode staat in gereedschap/contrast-pixelmeting.py. */
#rail a.here span:not(.orb) { color: var(--text); }
#rail a.here .orb { opacity: 1; transform: scale(1.25); box-shadow: 0 0 14px currentColor; }

/* ---- notes strip ----------------------------------------------------------------------------- */
/* [2026-08-17, gevonden bij Venus door NAAR HET BEELD TE KIJKEN] DE VOETTEKST GROEIDE ONBEGRENSD
 * OMHOOG. Hij stond op `bottom: 16px` zonder hoogtegrens, dus hoe langer de tekst, hoe verder hij
 * omhoog kroop - bij Venus tot achter de linkerbalk EN over de titelregel heen, wat er uitziet als
 * een kapotte pagina. Bij de Maan viel het niet op (korte tekst), bij Mars stond hij net achter het
 * paneel, bij Venus overlapte hij de kop. Een layout die met de tekstlengte meebeweegt is geen
 * layout maar een toeval dat drie keer goed uitpakte.
 * NU: hij begint ONDER de linkerbalk en heeft een eigen hoogte met scroll. De tekst is bewust lang -
 * dat is de plek waar dit project zijn bronnen en zijn benaderingen verantwoordt - dus inkorten is
 * de verkeerde fix; hem laten scrollen is de juiste.
 * De 12px lettergrootte i.p.v. 11px komt uit de UI-audit (OLY-105): 74 elementen stonden onder de
 * 12px-ondergrens, en dit was een van de grootste blokken. */
#foot {
  /* BOVEN DE RAIL, NIET ERACHTER. [2026-08-19, Atlas, gezien op Oberon en Umbriel]
     De voet stond op bottom:16 en de rail op bottom:14 met z-index 18 tegen de 12 van de voet. De
     rail is gecentreerd en max 980px breed, dus op een scherm van 1280 loopt hij van x=150 tot
     x=1130 - en de voet van x=14 tot x=246. Die 96px overlap ligt precies over de laatste regel van
     de voet, en op deze twee lichamen was dat de VORMHERKOMST: "Vorm: 761,4 km straal (JPL
     Horizons/IAU-bundel, i..." hield daar op. Het veld dat vandaag is gebouwd om de herkomst
     zichtbaar te maken, werd onzichtbaar gemaakt door een balk die er overheen lag.
     96px is hetzelfde getal als de tijdbalk (die om precies dezelfde reden omhoog moest): rail
     bottom 14 + hoogte ~60 + lucht. Alleen te zien door ernaar te KIJKEN - een statuscode van 200
     en een geslaagde JS-boot melden hier allebei niets. */
  position: fixed; left: 14px; bottom: 96px; z-index: 12;
  /* ZELFDE BREEDTE ALS #left (232px + 2x12px padding = 256px). Stond op 300px, en dus stak de
   * voettekst rechts NAAST het paneel uit - bij Venus zag je hem tussen de filterrijen door lopen,
   * wat het paneel vies maakt en op een renderfout lijkt. Twee blokken die onder elkaar horen te
   * staan moeten dezelfde breedte hebben; dat is geen opmaakdetail maar het verschil tussen een
   * kolom en twee dingen die toevallig links staan. */
  /* [2026-08-17] 256 -> 232px. De vorige regel hierboven zegt "zelfde breedte als #left (232 + 2x12
     padding)", maar #left rekent met border-box: die 232 IS inclusief padding. De voet stak dus 24px
     verder naar rechts dan het paneel - precies de reepjes tekst die op Titan naast de filterrijen
     opdoken. De redenering klopte, het getal niet, en dat is alleen te zien door te kijken. */
  max-width: 232px; color: var(--dimmer); font-size: calc(12px * var(--fs, 1)); line-height: 1.55;
  max-height: 30vh; overflow-y: auto; padding-right: 6px;
  scrollbar-width: thin; scrollbar-color: var(--line) transparent;
  -webkit-overflow-scrolling: touch;
}
#foot::-webkit-scrollbar { width: 6px; }
#foot::-webkit-scrollbar-thumb { background: var(--line); border-radius: 4px; }
#foot a { color: var(--dim); }

/* ---- narrow screens: the globe stays, the panels become drawers ------------------------------ */
@media (max-width: 820px) {
  #left { top: auto; bottom: 74px; left: 8px; right: 8px; width: auto;
          max-height: 34vh; flex-direction: row; flex-wrap: wrap; gap: 8px 20px; }
  #card { left: 8px; right: 8px; width: auto; top: auto; bottom: 74px; max-height: 52vh; }
  #foot { display: none; }
  #live .kv:not(:last-child) { display: none; }
}


/* [2026-08-17, UI-audit OLY-105] PREFERS-REDUCED-MOTION WERD NERGENS GERESPECTEERD - nul blokken in
 * de hele kit, terwijl er overgangen en een draaiende bol zijn. Wie deze voorkeur aanzet, doet dat
 * meestal omdat beweging hem duizelig of ziek maakt; dat negeren is geen smaakkwestie. */
@media (prefers-reduced-motion: reduce) {
  * { transition-duration: 0.01ms !important; animation-duration: 0.01ms !important;
      animation-iteration-count: 1 !important; }
}

/* ---- tijdbalk (OLY-98, 2026-08-19, Atlas) ---------------------------------------------------
 * ONDERAAN EN GECENTREERD, tussen #foot (links, 256px breed) en #card (rechts, 320px). Op een
 * smal scherm wijkt hij naar de volle breedte minus die twee blokken; een balk die OVER het
 * paneel valt is dezelfde fout als de #left/#foot-overlap die op Titan is weggehaald.
 * De balk bestaat alleen op lichamen met gedateerde formaties - zie bouwTijdbalk(). */
/* BOVEN DE RAIL, EN DAT IS EEN GEMETEN AFSTAND EN GEEN SMAAK. [2026-08-19, Atlas]
 * Eerste versie stond op bottom:16px met z-index 14 - exact waar #rail al ligt (bottom:14px,
 * hoogte 67px, z-index 18). Gevolg: de rail ving elke klik op. Een muisklik op de schuifbalk
 * NAVIGEERDE naar het lichaam eronder, en de ALL-knop was helemaal niet aan te klikken; Playwright
 * meldde letterlijk dat een rail-link de pointer-events onderschepte. Gevonden door de balk in een
 * echte browser te bedienen - HTTP 200 en "de code klopt" zeiden er niets over.
 * 14 (rail-bodem) + 67 (rail-hoogte) + 15 (lucht) = 96. */
#tijd {
  position: fixed; bottom: 96px; left: 50%; transform: translateX(-50%);
  z-index: 14; width: min(520px, calc(100vw - 620px)); padding: 10px 14px;
  display: flex; flex-direction: column; gap: 6px;
}
#tijd .tijdrij { display: flex; align-items: center; gap: 10px; }
#tijd .tijdschuif { flex: 1; accent-color: var(--accent, #d98a3a); height: 4px; cursor: pointer; }
#tijd .tijdjaar {
  font-family: var(--mono); font-size: calc(12px * var(--fs, 1)); color: var(--text);
  min-width: 46px; text-align: right; font-variant-numeric: tabular-nums;
}
/* De knop draagt zijn toestand in de RAND en niet alleen in de kleur: 'aan' moet ook leesbaar zijn
 * voor wie de accentkleur niet van de randkleur onderscheidt. Zelfde reden als de holle kern bij de
 * former-state-pins in Aion. */
#tijd .tijdknop {
  font-family: var(--mono); font-size: calc(10px * var(--fs, 1)); letter-spacing: 0.12em; text-transform: uppercase;
  padding: 4px 9px; border-radius: 3px; cursor: pointer; min-height: 26px;
  background: transparent; border: 1px solid var(--line); color: var(--dim);
}
#tijd .tijdknop.on { border-color: var(--accent, #d98a3a); color: var(--text); }
#tijd .tijdtelling {
  font-family: var(--mono); font-size: calc(10px * var(--fs, 1)); color: var(--dimmer); letter-spacing: 0.04em;
}
@media (max-width: 1100px) {
  #tijd { width: calc(100vw - 40px); left: 20px; right: 20px; transform: none; bottom: 96px; }
}

/* ---- geologische era-band (OLY-98, 2026-09-16) -------------------------------------------
 * Gedeeld visueel taal met de tijdrij hierboven (zelfde mono/dim/accent-tokens), maar een eigen
 * blok: dit vervangt #tijd .tijdrij volledig wanneer een lichaam een epochs.json draagt (zie
 * bouwGeologischeTijdbalk in orrery.js) - nooit allebei tegelijk in dezelfde #tijd. */
#tijd .erabalkcanvas { width: 100%; height: 64px; display: block; }
#tijd .erabalkbron {
  display: flex; flex-wrap: wrap; gap: 2px 6px; font-size: calc(9px * var(--fs, 1));
}
#tijd .eragrens {
  color: var(--dimmer); text-decoration: none; border-bottom: 1px dotted var(--line);
}
#tijd .eragrens:hover, #tijd .eragrens:focus-visible { color: var(--text); border-bottom-color: var(--accent, #d98a3a); }
/* Grade in de RAND van de link en niet alleen in de band zelf - dezelfde reden als .tijdknop.on
 * hierboven: kleur alleen is niet genoeg voor wie geel/blauw niet onderscheidt. */
#tijd .eragrens.grade-disputed { border-bottom-style: dashed; }
#tijd .eragrens.grade-speculative { border-bottom-style: dotted; opacity: .8; }
#tijd .ongedateerd-rail {
  display: flex; align-items: center; justify-content: space-between; gap: 10px;
  padding-top: 4px; border-top: 1px solid var(--line);
}
/* Verplicht geen sr-only-klasse aan te nemen die hier al bestaat, want dat raakt dit hele bestand
 * niet - de losse #tijd-reden-tak in orrery.js maakt zijn EIGEN klasse aan als hij die nog niet
 * vindt (zie het commentaar daar); deze regel is de vangnet-vorm zodat hij nooit zichtbaar breekt
 * als iemand later een globale .sr-only toevoegt met een ander contract. */
.sr-only {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden;
  clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0;
}

/* ---- vormherkomst (radii_bron) -------------------------------------------------------------
 * [2026-08-19, Atlas; order Macel] Een bol zonder meetlat ziet er altijd even stellig uit. Deze
 * regel zegt waar die vorm vandaan komt, en bij een ONBEVESTIGDE vorm staat de reden er voluit -
 * geen tooltip, want die bestaat niet op een aanraakscherm en dit is juist de mededeling die je
 * niet mag missen. */
#mark .vorm { margin-top: 3px; display: flex; flex-wrap: wrap; align-items: center; gap: 7px; }
#mark .vorm-maat {
  font-family: var(--mono); font-size: calc(10px * var(--fs, 1)); letter-spacing: 0.05em; color: var(--dim);
}
#mark .vorm-bron {
  font-family: var(--mono); font-size: calc(10px * var(--fs, 1)); color: var(--dimmer);
  border-left: 1px solid var(--line); padding-left: 7px;
}
/* De vlag draagt zijn betekenis in de RAND en in het WOORD, niet in kleur alleen - dezelfde reden
 * als de holle kern bij de vervallen-staat-pins: kleur alleen is voor een deel van de lezers geen
 * onderscheid. */
#mark .vorm-vlag {
  font-family: var(--mono); font-size: calc(9px * var(--fs, 1)); letter-spacing: 0.1em; text-transform: uppercase;
  color: #e8b84b; border: 1px solid #e8b84b; border-radius: 3px; padding: 1px 6px;
}
#mark .vorm-reden {
  flex-basis: 100%; font-family: var(--mono); font-size: calc(10px * var(--fs, 1)); line-height: 1.5;
  color: var(--dim); max-width: 68ch; margin-top: 2px;
}

/* ---- [OLY-457, 2026-09-20, Luna's measurement + Atlas] TOP-BAR CONTAINMENT ----
 * Measured against the DEPLOYED bytes (not hub/main), 3 bodies x 5 widths, two identical rounds.
 * BASELINE (before this block, same harness):
 *   360: moon 2 / mars 3 / titan 3 button groups off-screen  |  414: 1 / 1 / 2  |  640+: 0
 * AFTER:
 *   360: 1 (sndctl) on all three  |  414: 0 on all three  |  640+: 0
 * WHY THIS MEASURES NAME WIDTH AND NOT JUST "nothing off-screen": a first version gave "zero
 * off-screen at every width" by putting a floor on the #mark BOX - #mark is itself a flex row, so
 * a floor on the box just let .sub/.vorm eat the space. That green was bought by squeezing the
 * page title down to 14px, and since OLY-289 that <b> is also the only level-1 heading. Hence a
 * SHRINK ORDER instead: the name never shrinks, .sub and .vorm shrink first. Name width after the
 * fix: 56/58/60px per body, equal at every viewport, never truncated.
 * Two things measured back OUT because the numbers argued against them: a min-width of 96px on
 * #mark b (also held that 96px at 1400px where the text is 43px - padding, not a floor), and
 * dropping the box floor now that the name is protected (then 360 goes back to 3 off-screen and
 * 414 to 2 - it is load-bearing).
 * DOES NOT FIX: sndctl at 360px. That's the menu class (direction d) - the bar there is ~120px
 * over budget before a button is even added. Containment alone doesn't reach that. */
#mark { min-width: min(100%, 150px); flex: 0 1 auto; }
#live { flex-shrink: 8; }
#mark b           { flex: 0 0 auto; }
#mark .sub, #mark .vorm { flex: 0 1 auto; min-width: 0; flex-shrink: 20; }
#mark b, #mark .sub {
  display: inline-block; max-width: 100%;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; vertical-align: bottom;
}
#live { min-width: 0; flex: 0 1 auto; flex-wrap: nowrap; }
#live .kv { min-width: 0; }
#live .kv .v, #live .kv .k { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
