@import url('../src/psi-ui.tokens.css');
@import url('../src/psi-ui.css');
:root{
  /* Safe-area insets lifted into custom properties.

     Two reasons this indirection exists rather than using env() at each site:
     1. Headless Chromium reports every env(safe-area-inset-*) as 0, so safe-area handling is
        otherwise UNTESTABLE — tools/verify_mobile.mjs substitutes real device values here.
     2. One place to reason about, instead of a dozen calc()s that must each remember it. */
  --sat:env(safe-area-inset-top,0px);
  --sab:env(safe-area-inset-bottom,0px);
  --sal:env(safe-area-inset-left,0px);
  --sar:env(safe-area-inset-right,0px);

  /* THE VIEWPORT HEIGHT THAT IS ACTUALLY VISIBLE.

     `100vh` on iOS is the LARGE viewport — the height with browser chrome hidden. This app
     never scrolls, so the chrome never retracts, so `100vh` is ~108px taller than anything the
     user can see and everything anchored to the bottom is pushed off the screen. `svh` is the
     small viewport: the height with chrome SHOWN, which is the honest one here.

     The plain `100vh` line stays as a fallback for browsers without svh (pre-2022); they get
     the old behaviour rather than a broken `height:`. */
  --appvh:100vh;
}
@supports (height:100svh){ :root{ --appvh:100svh; } }

:root{
  --deep:#070F07; --forest:#0D1F0D; --green:#2D5A27; --accent:#00BE5A; --alive:#00FF64;
  --text:#F0F0EA; --dim:rgba(240,240,234,.72); --muted:rgba(240,240,234,.55);   /* WCAG AA: muted/dim lifted so the faint card labels clear 4.5:1 over the glass cards */
  --aqua:#32C8FF;
  --sans:"Hanken Grotesk",-apple-system,BlinkMacSystemFont,system-ui,sans-serif;
}
*{box-sizing:border-box;margin:0;padding:0}
body{background:var(--deep);color:var(--text);font-family:var(--sans);-webkit-font-smoothing:antialiased;overflow:hidden;height:var(--appvh)}
#scene{position:fixed;inset:0;display:block;cursor:grab}
#scene:active{cursor:grabbing}

/* loader */
#loader{position:fixed;inset:0;z-index:50;background:var(--deep);display:flex;flex-direction:column;align-items:center;justify-content:center;gap:22px}
#loader span{font-size:12px;letter-spacing:.14em;text-transform:uppercase;color:var(--dim)}
.emblem-spin{width:50px;height:50px;border-radius:50%;background:conic-gradient(#E682E6,#32C8FF,#00BE5A,#FFC800,#1482FF,#FF8C42,#E682E6);-webkit-mask:radial-gradient(circle 16px,transparent 98%,#000);mask:radial-gradient(circle 16px,transparent 98%,#000);animation:spin 2.4s linear infinite}
@keyframes spin{to{transform:rotate(360deg)}}

/* boot choreography — chrome fades in after the opening flight */
.hud,#chapters,#cartos,#globe,#locator,#mapinset,#labels,#leaders,#llegend{transition:opacity 1s ease}
body.booting .hud,body.booting #chapters,body.booting #cartos,body.booting #globe,body.booting #locator,body.booting #mapinset,body.booting #labels,body.booting #leaders,body.booting #wlegend,body.booting #hlegend,body.booting #plegend,body.booting #llegend,body.booting #live-pts{opacity:0!important;pointer-events:none}

/* HUD */
.hud{position:fixed;top:0;left:0;right:0;z-index:30;display:flex;justify-content:space-between;align-items:center;padding:18px 24px;pointer-events:none}
/* Top scrim. The HUD used to rely on the framing happening to put dark sky behind it, which
   holds only while the camera looks DOWN at a plate. Frame a park from its valley toward
   rising ground — often the better shot — and lit terrain reaches the top of frame and the
   wordmark and tag stop being readable. Guarantee the contrast instead of inheriting it.
   position:absolute (not a flex child) or it becomes a third item in the space-between row. */
.hud::before{content:'';position:absolute;inset:0 0 auto 0;height:132px;background:linear-gradient(180deg,rgba(4,10,6,.62) 0%,rgba(4,10,6,.34) 46%,rgba(4,10,6,0) 100%);pointer-events:none;z-index:-1}
.hud>*{pointer-events:auto}
.brand img{height:26px;width:auto;display:block;opacity:.95}
.hud-right{display:flex;align-items:center;gap:14px}
.hud-tag{font-size:11px;letter-spacing:.14em;text-transform:uppercase;color:var(--muted)}
/* FR / EN segmented toggle */
#lang{display:inline-flex;align-items:center;padding:2px;border:1px solid rgba(240,240,234,.16);border-radius:999px;background:linear-gradient(180deg,rgba(7,15,7,.42),rgba(7,15,7,.62));backdrop-filter:blur(8px);-webkit-backdrop-filter:blur(8px);cursor:pointer}
#lang span{font-size:10px;font-weight:700;letter-spacing:.1em;line-height:1;padding:4px 8px;border-radius:999px;color:var(--muted);transition:background .25s ease,color .25s ease}
#lang span.on{background:rgba(240,240,234,.92);color:#0a0f0a}
#lang:hover span:not(.on){color:var(--text)}
/* visible keyboard focus ring (keyboard-only via :focus-visible — invisible on mouse click) */
:focus-visible{outline:2px solid var(--accent);outline-offset:2px;border-radius:4px}
.pill{background:rgba(240,240,234,.06);border:1px solid rgba(240,240,234,.18);color:var(--dim);font-family:var(--sans);font-size:12px;padding:6px 13px;border-radius:999px;cursor:pointer;transition:.25s}
.pill:hover{color:var(--text);border-color:var(--accent)}
.pill.on{background:var(--accent);border-color:var(--accent);color:var(--deep)}

/* first-run hint */
/* ⛔ GYE WAS THE ONLY SITE IN THE FLEET WHOSE HINT WAS NOT AT THE BOTTOM. This read
   `top:50%;transform:translate(-50%,-50%)` against `top:auto;bottom:74px` on the template and
   all six other vistas, so the orbit guidance floated in the dead centre of the map, over the
   terrain, at every width. Found by LOOKING at a 1440 screenshot - no gate reports it, because
   a centred element occludes nothing and overflows nothing. It also silently voided the
   html[data-creditstrip] lift below: when `top` is set, `bottom` is ignored for a fixed element,
   so the rule that raises the hint above the credit strip did nothing here. */
#hint{position:fixed;top:auto;bottom:74px;left:50%;transform:translateX(-50%);white-space:nowrap;z-index:20;font-size:12px;letter-spacing:.14em;text-transform:uppercase;color:rgba(255,255,255,.7);text-shadow:0 1px 10px rgba(0,0,0,.9);transition:opacity .8s;pointer-events:none}
#hint.gone{opacity:0}

/* feature labels — each PINNED to its place; the text sits just above its marker.
   Types carry meaning: geography (white), wildlife (species colour), ranger (amber). */
/* ── map labels: traditional cartographic hierarchy, high-tech undertones ──
   Natural features set in Fraunces italic (hydrography/orography convention);
   data values (elevations, populations) in monospace. JS centre-anchors every
   label, so .mlabel is translate(-50%,-50%); placement/declutter live in main.js. */
#labels{position:fixed;inset:0;z-index:8;pointer-events:none}
#leaders{position:fixed;inset:0;z-index:7;pointer-events:none;width:100%;height:100%}
#leaders line{stroke:rgba(232,238,235,.42);stroke-width:1;transition:opacity .25s}
/* species plates — two-tone natural-history icons (base hue + engraved shading + highlights) */
#icons{position:fixed;inset:0;z-index:9;pointer-events:none}
.spicon{position:absolute;width:24px;height:24px;transform:translate(-50%,-50%);opacity:0;cursor:pointer;
  transition:opacity .4s,transform .15s;will-change:left,top,opacity}
.spicon svg{width:100%;height:100%;display:block;
  filter:drop-shadow(0 0 1.4px rgba(6,11,7,.95)) drop-shadow(0 1px 2px rgba(0,0,0,.85)) drop-shadow(0 0 6px rgba(6,11,7,.6))}
.spicon:hover{transform:translate(-50%,-50%) scale(1.22)}
.wl-ic{flex:none;width:23px;height:21px;display:inline-flex;align-items:center;justify-content:center}
.wl-ic svg{width:20px;height:20px;display:block;filter:drop-shadow(0 0 2px color-mix(in srgb,var(--fa,#fff) 45%,transparent))}
/* realistic CC0 silhouettes — tinted to the species hue wherever an iconSVG renders */
.icv path{fill:var(--fa)}
.ldot{position:absolute;width:5px;height:5px;border-radius:50%;background:#fff;box-shadow:0 0 0 1.5px rgba(7,12,8,.6),0 0 6px rgba(0,0,0,.7);transform:translate(-50%,-50%);transition:opacity .3s;pointer-events:none}
.mlabel{position:absolute;transform:translate(-50%,-50%);display:flex;flex-direction:column;align-items:center;gap:0;text-align:center;white-space:nowrap;line-height:1.06;transition:opacity .25s;pointer-events:none}
.mlabel .mn{font-weight:600;font-size:12.5px;letter-spacing:0;color:#fff;text-shadow:0 1px 2px #000,0 0 9px rgba(0,0,0,.95)}
.mlabel .ms{font-family:ui-monospace,'SF Mono',Menlo,monospace;font-weight:500;font-size:9px;letter-spacing:.02em;color:var(--dim);text-shadow:0 1px 3px #000}
/* landforms + hydrography — serif italic */
.mlabel.m-place .mn{font-family:'Fraunces',serif;font-style:italic;font-weight:500;font-size:13px;color:#d4ead9;text-shadow:0 1px 3px #000,0 0 10px rgba(0,0,0,.9)}
.d-place{width:4px;height:4px;background:rgba(220,238,224,.8)}
.mlabel.m-water .mn{font-family:'Fraunces',serif;font-style:italic;font-weight:500;font-size:14px;letter-spacing:.01em;color:#cfe3ef;text-shadow:0 1px 3px #000,0 0 10px rgba(8,20,30,.9)}
.d-water{display:none}
.mlabel.m-river .mn{font-family:'Fraunces',serif;font-style:italic;font-weight:400;font-size:11px;color:#a4cae0}
.d-river{display:none}
/* settlements — small dot, roman */
.mlabel.m-city .mn{font-size:11.5px;font-weight:500;color:#e9eef2;letter-spacing:.005em}
.d-city{width:4px;height:4px;border-radius:50%;background:#eef2f5;box-shadow:0 0 0 1.5px rgba(7,12,8,.6)}
/* peaks — summit triangle + elevation in mono */
.mlabel.m-peak .mn{font-size:12px;font-weight:600}
.mlabel.m-peak .ms{color:var(--muted);font-variant-numeric:tabular-nums}
.d-peak{width:0;height:0;background:none;border-radius:0;border-left:4px solid transparent;border-right:4px solid transparent;border-bottom:7px solid rgba(255,255,255,.92);box-shadow:none;filter:drop-shadow(0 1px 2px rgba(0,0,0,.9))}
/* wildlife — small species diamond + population (quiet, no pill) */
.d-fauna{width:7px;height:7px;border-radius:2px;transform:translate(-50%,-50%) rotate(45deg)}
.mlabel.m-fauna .mn{font-size:11.5px;font-weight:600}
.mlabel.m-fauna .ms{font-size:9px;font-weight:500;color:var(--fa,#9fb8a4)}
/* ranger network — amber square (the data layer) + caps role */
.d-station{width:6px;height:6px;border-radius:1px;background:#FFC24B;box-shadow:0 0 0 1.5px rgba(7,12,8,.7),0 0 7px rgba(255,194,75,.6)}
.mlabel.m-station .mn{font-size:11.5px;font-weight:600;color:#F4E9D2}
.mlabel.m-station .ms{color:#FFC24B;letter-spacing:.07em;text-transform:uppercase;font-size:8px}
/* administrative areas — broad faint tracked caps, no marker */
.d-country,.d-region{display:none}
.mlabel.m-country .mn{font-size:12.5px;font-weight:600;letter-spacing:.34em;text-transform:uppercase;color:rgba(240,240,234,.30);text-shadow:0 1px 6px rgba(0,0,0,.8)}
.mlabel.m-region .mn{font-size:9.5px;font-weight:600;letter-spacing:.3em;text-transform:uppercase;color:rgba(206,226,212,.28);text-shadow:0 1px 6px rgba(0,0,0,.8)}
/* neighbouring rift parks — faint mint serif italic (the connected chain) */
.mlabel.m-park .mn{font-family:'Fraunces',serif;font-style:italic;font-weight:500;font-size:11px;letter-spacing:.01em;color:rgba(150,235,212,.85);text-shadow:0 1px 5px rgba(0,0,0,.95)}
.d-park{display:none}

/* legend (protected-area key) */

/* forest-loss timeline readout */
#lossyear{position:fixed;top:62px;left:50%;transform:translateX(-50%) translateY(-8px);z-index:26;width:min(340px,46vw);padding:12px 16px 11px;border-radius:12px;background:linear-gradient(180deg,rgba(7,15,7,.42),rgba(7,15,7,.58));backdrop-filter:blur(10px);-webkit-backdrop-filter:blur(10px);border:1px solid rgba(240,240,234,.12);opacity:0;pointer-events:none;transition:opacity .5s ease,transform .5s ease}
#lossyear.on{opacity:1;transform:translateX(-50%) translateY(0)}
#lossyear .ly-head{display:flex;justify-content:space-between;align-items:baseline;margin-bottom:8px}
#lossyear .ly-k{font-size:10px;letter-spacing:.14em;text-transform:uppercase;color:var(--muted)}
#ly-num{font-size:22px;font-weight:800;letter-spacing:-.02em;color:#ff6a4d;font-variant-numeric:tabular-nums}
#ly-bar{height:3px;border-radius:3px;background:rgba(240,240,234,.14);overflow:hidden}
#ly-fill{height:100%;width:0;border-radius:3px;background:linear-gradient(90deg,#ffd24a,#ff5a3c);box-shadow:0 0 10px rgba(255,90,60,.6)}
#lossyear .ly-scale{display:flex;justify-content:space-between;margin-top:6px;font-size:9px;letter-spacing:.08em;text-transform:uppercase;color:var(--muted)}

/* locator globe (WebGL renders inside; ring + caption are chrome aligned to it) */
/* locator — a glass text tile (left) + the WebGL globe porthole (right), equal height.
   The globe sits over the bare canvas (no card background behind it) so the Earth stays bright. */
#locator{position:fixed;right:24px;top:78px;z-index:24;width:268px;display:flex;align-items:stretch;gap:10px;pointer-events:none}
#locator .loc-cap{flex:1;min-width:0;display:flex;flex-direction:column;justify-content:center;align-items:center;text-align:center;gap:5px;padding:0 14px;border-radius:11px;background:linear-gradient(180deg,rgba(7,15,7,.5),rgba(7,15,7,.66));backdrop-filter:blur(10px);-webkit-backdrop-filter:blur(10px);border:1px solid rgba(240,240,234,.1)}
#locator .loc-k{font-size:8.5px;letter-spacing:.2em;text-transform:uppercase;color:var(--muted)}
#locator .loc-cap b{font-size:16px;font-weight:700;letter-spacing:.01em;color:var(--text);font-variant-numeric:tabular-nums;line-height:1;text-shadow:0 1px 4px rgba(0,0,0,.8)}
#locator .loc-sub{font-size:9px;letter-spacing:.14em;text-transform:uppercase;color:var(--muted);line-height:1.45}
#globe{position:relative;width:106px;height:106px;flex:none;pointer-events:none}
#globe .g-ring{position:absolute;inset:0;border-radius:50%;border:1px solid rgba(180,210,235,.22);box-shadow:0 6px 26px rgba(0,0,0,.45),0 0 22px rgba(70,150,220,.12)}
#globe .g-ring::after{content:"";position:absolute;inset:4px;border-radius:50%;border:1px solid rgba(180,210,235,.06)}
#globe .g-ring::before{content:"";position:absolute;top:-3px;left:50%;width:1px;height:6px;background:rgba(180,210,235,.5);transform:translateX(-50%)}

/* north arrow + scale bar — cartographic instrument card on the right rail, same width as the map key below */
#cartos{position:fixed;right:24px;top:196px;z-index:24;width:268px;padding:12px 15px;border-radius:11px;background:linear-gradient(180deg,rgba(7,15,7,.5),rgba(7,15,7,.66));backdrop-filter:blur(10px);-webkit-backdrop-filter:blur(10px);border:1px solid rgba(240,240,234,.1);display:flex;align-items:center;justify-content:space-between;gap:14px;pointer-events:none}
#cartos .ct-div{width:1px;align-self:stretch;margin:3px 0;background:rgba(240,240,234,.12);flex:none}

/* wildlife + protection legend (overview) */
#wlegend{position:fixed;right:24px;top:276px;z-index:24;padding:13px 15px 12px;border-radius:11px;background:linear-gradient(180deg,rgba(7,15,7,.5),rgba(7,15,7,.66));backdrop-filter:blur(10px);-webkit-backdrop-filter:blur(10px);border:1px solid rgba(240,240,234,.1);opacity:0;transform:translateY(-6px);transition:opacity .4s,transform .4s;pointer-events:none;width:268px}
#wlegend.show{opacity:1;transform:none}
#wlegend .wl-h{font-size:9px;letter-spacing:.15em;text-transform:uppercase;color:var(--muted);margin-bottom:9px}
/* The wildlife rail says WHAT ITS MARKS ARE, and ranks them by what they prove.
   Two expert readers of the Woodbury build — a director and an ecologist — read the species
   icons as a network of live observations. They are not: place_species.py derives them from a
   written habitat rule, and says so in its own docstring, but the app never said so on screen.
   #wlegend .wl-note is that missing sentence. SCOPED: #hlegend/#plegend already use .wl-note
   for their own footnote, and an unscoped rule here would restyle both.
   .wl-sub groups the list by whether a species distinguishes this forest from a cut-over one.
   Ranking matters more than the roster: the ecologist's point was that wide-ranging generalists
   would score recently logged land just as well, so a flat list argues against its own subject. */
#wlegend .wl-note{font-size:9px;line-height:1.35;letter-spacing:.01em;color:rgba(240,240,234,.58);margin:-2px 0 5px;padding-right:6px;border:0;padding-top:0}
#wlegend .wl-sub{font-size:8.5px;letter-spacing:.13em;text-transform:uppercase;color:rgba(240,240,234,.55);margin:5px 0 3px;display:flex;align-items:center;gap:7px}
#wlegend .wl-sub::after{content:"";flex:1;height:1px;background:rgba(240,240,234,.13)}
#wlegend .wl-sub:first-of-type{margin-top:2px}

#wlegend .wl-row{display:flex;align-items:center;gap:9px;font-size:11px;color:var(--dim);line-height:1.62}
#wlegend .wl-row i{width:11px;height:11px;border-radius:50%;flex:none;box-shadow:0 0 8px currentColor}
#wlegend .wl-row.t i{width:15px;height:0;border-radius:0;border-top:2px solid #dac6a2;box-shadow:none;color:#dac6a2}
#wlegend .wl-row.s i{width:7px;height:7px;border-radius:1px;background:#FFC24B!important;box-shadow:none}
#wlegend .wl-sep{height:1px;background:rgba(240,240,234,.1);margin:7px 0 6px}
#wlegend .wl-h .wl-tap{font-size:8px;letter-spacing:.1em;text-transform:uppercase;color:var(--muted);margin-left:7px;font-weight:600}
#wlegend .wl-row.pk i{width:14px;height:11px;border-radius:3px;background:rgba(110,205,150,.26);border:1px solid rgba(190,245,205,.9);box-shadow:0 0 7px rgba(120,230,160,.4);color:transparent}
#wlegend .wl-row.n i{width:15px;height:0;border-radius:0;border-top:1.5px solid #7FE3C8;box-shadow:none;color:#7FE3C8}
#wlegend .wl-row.fa{pointer-events:auto;cursor:pointer;border-radius:7px;margin:0;padding:3px 6px;gap:8px;transition:background .15s}
#wlegend .wl-row.fa:hover{background:rgba(255,255,255,.07)}
/* iconic wildlife — two columns of tappable species */
#wlegend .wl-grid{display:grid;grid-template-columns:1fr 1fr;gap:1px 4px;margin:0 -6px}
#wlegend .wl-grid .wl-row{font-size:10px;line-height:1.35;letter-spacing:-.005em;gap:6px}
#wlegend .wl-grid .wl-ic{width:16px;height:16px;flex:none;display:inline-flex;align-items:center;justify-content:center}
#wlegend .wl-grid .wl-ic svg{width:100%;height:100%;display:block;filter:drop-shadow(0 1px 1.5px rgba(0,0,0,.6))}
/* mountain-gorilla detail panel (gorilla view) — same chrome as the other legends */
/* the families register — compact two-column roll of every Virunga group */

/* mountain-gorilla family-group markers on the map (gorilla view) */
/* hypsometric (altitude) legend — same chrome as #wlegend, square swatches */
#hlegend{position:fixed;right:24px;top:276px;z-index:24;padding:13px 15px 12px;border-radius:11px;background:linear-gradient(180deg,rgba(7,15,7,.5),rgba(7,15,7,.66));backdrop-filter:blur(10px);-webkit-backdrop-filter:blur(10px);border:1px solid rgba(240,240,234,.1);opacity:0;transform:translateY(-6px);transition:opacity .4s,transform .4s;pointer-events:none;width:268px}
/* #glegend — an OPTIONAL rail panel for a conserved-land / GAP-status key. Styled here so a
   site that needs it only has to add the markup and a `gapKey: true` flag on its chapter.
   Used by Woodbury Mountain, where the surrounding land's management class is the argument. */
#glegend,#ilegend{position:fixed;right:24px;top:276px;z-index:24;padding:13px 15px 12px;border-radius:11px;background:linear-gradient(180deg,rgba(7,15,7,.5),rgba(7,15,7,.66));backdrop-filter:blur(10px);-webkit-backdrop-filter:blur(10px);border:1px solid rgba(240,240,234,.1);opacity:0;transform:translateY(-6px);transition:opacity .4s,transform .4s;pointer-events:none;width:268px}
#ilegend{position:fixed;right:24px;top:276px;z-index:24;padding:13px 15px 12px;border-radius:11px;background:linear-gradient(180deg,rgba(7,15,7,.5),rgba(7,15,7,.66));backdrop-filter:blur(10px);-webkit-backdrop-filter:blur(10px);border:1px solid rgba(240,240,234,.1);opacity:0;transform:translateY(-6px);transition:opacity .4s,transform .4s;pointer-events:none;width:268px}
#plegend{position:fixed;right:24px;top:276px;z-index:24;padding:13px 15px 12px;border-radius:11px;background:linear-gradient(180deg,rgba(7,15,7,.5),rgba(7,15,7,.66));backdrop-filter:blur(10px);-webkit-backdrop-filter:blur(10px);border:1px solid rgba(240,240,234,.1);opacity:0;transform:translateY(-6px);transition:opacity .4s,transform .4s;pointer-events:none;width:268px}
#hlegend.show,#glegend.show,#ilegend.show{opacity:1;transform:none}
#plegend.show{opacity:1;transform:none}
#hlegend .wl-h,#glegend .wl-h,#ilegend .wl-h{font-size:9px;letter-spacing:.15em;text-transform:uppercase;color:var(--muted);margin-bottom:9px}
#plegend .wl-h{font-size:9px;letter-spacing:.15em;text-transform:uppercase;color:var(--muted);margin-bottom:9px}
#hlegend .wl-note,#glegend .wl-note,#ilegend .wl-note{margin-top:9px;padding-top:8px;border-top:1px solid rgba(240,240,234,.1);font-size:9.5px;line-height:1.45;color:var(--muted);letter-spacing:.02em}
#plegend .wl-note{margin-top:9px;padding-top:8px;border-top:1px solid rgba(240,240,234,.1);font-size:9.5px;line-height:1.45;color:var(--muted);letter-spacing:.02em}
#hlegend .wl-note b,#glegend .wl-note b,#ilegend .wl-note b{font-weight:600;color:var(--dim)}
#plegend .wl-note b{font-weight:600;color:var(--dim)}
#hlegend .wl-row,#glegend .wl-row,#ilegend .wl-row{display:flex;align-items:center;gap:9px;font-size:11px;color:var(--dim);line-height:1.9}
#plegend .wl-row{display:flex;align-items:center;gap:9px;font-size:11px;color:var(--dim);line-height:1.9}
#hlegend .wl-row i,#glegend .wl-row i,#ilegend .wl-row i{width:14px;height:11px;border-radius:2px;flex:none}
#plegend .wl-row i{width:14px;height:11px;border-radius:2px;flex:none}
#hlegend .wl-row em,#glegend .wl-row em,#ilegend .wl-row em{margin-left:auto;padding-left:10px;font-style:normal;font-family:ui-monospace,'SF Mono',Menlo,monospace;font-size:8.5px;letter-spacing:.02em;color:var(--muted);font-variant-numeric:tabular-nums}
#plegend .wl-row em{margin-left:auto;padding-left:10px;font-style:normal;font-family:ui-monospace,'SF Mono',Menlo,monospace;font-size:8.5px;letter-spacing:.02em;color:var(--muted);font-variant-numeric:tabular-nums}
#hlegend .wl-sep{height:1px;background:rgba(240,240,234,.1);margin:8px 0 7px}
#plegend .wl-sep{height:1px;background:rgba(240,240,234,.1);margin:8px 0 7px}
#hlegend .wl-row.g i,#glegend .wl-row.g i,#ilegend .wl-row.g i{width:9px;height:9px;border-radius:50%;box-shadow:0 0 8px #4fd17a}
#plegend .wl-row.g i{width:9px;height:9px;border-radius:50%;box-shadow:0 0 8px #4fd17a}
/* wildlife population card — opens on a habitat-marker click; sparkline of the real trajectory */
#species{position:fixed;left:50%;top:50%;transform:translate(-50%,-46%) scale(.97);z-index:40;width:300px;max-width:88vw;padding:20px 22px 18px;border-radius:14px;background:linear-gradient(180deg,rgba(9,17,12,.94),rgba(7,13,9,.97));backdrop-filter:blur(14px);-webkit-backdrop-filter:blur(14px);border:1px solid rgba(240,240,234,.12);box-shadow:0 30px 80px rgba(0,0,0,.55);opacity:0;pointer-events:none;transition:opacity .3s,transform .3s}
#species.show{opacity:1;transform:translate(-50%,-50%) scale(1);pointer-events:auto}
/* hover preview: the same card, but it must never intercept the pointer — otherwise it
   lands under the cursor, fires pointerleave on the icon, and strobes open/closed. */
#species.peek{pointer-events:none!important;box-shadow:0 20px 60px rgba(0,0,0,.45)}
#species.peek #species-close{display:none}
#species.peek::after{content:'click to keep open';position:absolute;left:0;right:0;bottom:-19px;text-align:center;font-size:8.5px;letter-spacing:.14em;text-transform:uppercase;color:var(--muted);opacity:.85}
#species-close{position:absolute;top:12px;right:13px;width:24px;height:24px;border:0;background:rgba(255,255,255,.06);color:var(--dim);border-radius:50%;cursor:pointer;font-size:12px;line-height:1}
.sp-kicker{font-size:9px;letter-spacing:.18em;text-transform:uppercase;color:var(--muted);margin-bottom:8px}
.sp-name{font-family:'Fraunces',serif;font-weight:600;font-size:22px;line-height:1.1;color:#eef0ea;margin:0}
.sp-latin{font-style:italic;font-size:11px;color:var(--muted);margin:2px 0 14px}
.sp-head{display:flex;gap:14px;align-items:center;margin-bottom:13px}
.sp-head-t{min-width:0}
.sp-head .sp-kicker{margin-bottom:5px}
.sp-head .sp-name{font-size:20px}
.sp-head .sp-latin{margin:3px 0 0}
.sp-illus{flex:none;width:62px;height:62px;border-radius:13px;display:flex;align-items:center;justify-content:center;
  background:radial-gradient(circle at 50% 38%,color-mix(in srgb,var(--fa) 24%,transparent),transparent 72%);
  border:1px solid color-mix(in srgb,var(--fa) 26%,transparent)}
.sp-illus svg{width:50px;height:50px;display:block;filter:drop-shadow(0 1px 2px rgba(0,0,0,.45))}
.sp-badges{display:flex;flex-wrap:wrap;gap:6px;margin:0 0 14px}
.sp-badge{font-size:9.5px;letter-spacing:.04em;padding:3px 8px;border-radius:5px;color:#eef0ea;background:rgba(255,255,255,.07);border:1px solid rgba(255,255,255,.1);white-space:nowrap}
.sp-badge.sev-en{color:#ffb38a;background:rgba(255,120,60,.13);border-color:rgba(255,120,60,.32)}
.sp-badge.sev-cr{color:#ff9a9a;background:rgba(255,70,70,.14);border-color:rgba(255,70,70,.34)}
.sp-badge.sev-vu{color:#ffd98a;background:rgba(240,194,68,.13);border-color:rgba(240,194,68,.32)}
.sp-badge.sp-range{color:var(--dim)}
.sp-now{display:flex;align-items:baseline;gap:9px;margin-bottom:14px}
.sp-now b{font-size:26px;font-weight:600;font-variant-numeric:tabular-nums;letter-spacing:-.01em}
.sp-now span{font-size:10px;letter-spacing:.04em;color:var(--dim);text-transform:uppercase}
.sp-chart{margin-bottom:14px}
.sp-chart .spark{display:block}
.sp-ends{display:flex;justify-content:space-between;margin-top:5px;font-family:ui-monospace,'SF Mono',Menlo,monospace;font-size:10px;color:var(--dim)}
.sp-ends i{font-style:normal;color:var(--muted);margin-right:6px}
.sp-note{font-size:12.5px;line-height:1.55;color:var(--dim);margin:0}
.sp-src{margin:11px 0 0;padding-top:9px;border-top:1px solid rgba(240,240,234,.1);font-size:10px;line-height:1.5;color:var(--muted)}
.sp-src em{font-style:normal;color:var(--dim);opacity:.9}
.sp-src-k{display:block;font-size:8.5px;letter-spacing:.14em;text-transform:uppercase;color:var(--muted);margin-bottom:3px}
.mlabel.clickable:hover .mn{text-decoration:underline;text-underline-offset:2px}
/* place record card — opens on a labelled-feature click; same chrome as the species card */
#place{position:fixed;left:50%;top:50%;transform:translate(-50%,-46%) scale(.97);z-index:40;width:336px;max-width:88vw;padding:20px 22px 18px;border-radius:14px;background:linear-gradient(180deg,rgba(9,17,12,.94),rgba(7,13,9,.97));backdrop-filter:blur(14px);-webkit-backdrop-filter:blur(14px);border:1px solid rgba(240,240,234,.12);box-shadow:0 30px 80px rgba(0,0,0,.55);opacity:0;pointer-events:none;transition:opacity .3s,transform .3s}
#place.show{opacity:1;transform:translate(-50%,-50%) scale(1);pointer-events:auto}
#place-close{position:absolute;top:12px;right:13px;width:24px;height:24px;border:0;background:rgba(255,255,255,.06);color:var(--dim);border-radius:50%;cursor:pointer;font-size:12px;line-height:1}
#place .sp-latin{font-style:normal;letter-spacing:.02em;color:var(--dim);margin:3px 0 13px}
.pl-lead{font-size:12.5px;line-height:1.6;color:var(--dim);margin:0 0 15px}
.pl-stats{display:flex;flex-wrap:wrap;gap:13px 22px;border-top:1px solid rgba(240,240,234,.1);padding-top:13px}
.pl-stats>div{display:flex;flex-direction:column;gap:2px}
.pl-stats b{font-size:15px;font-weight:600;color:#eef0ea;font-variant-numeric:tabular-nums;letter-spacing:-.01em}
.pl-stats span{font-size:9px;letter-spacing:.07em;text-transform:uppercase;color:var(--muted)}
#north{position:relative;width:48px;height:48px;border-radius:50%;background:rgba(7,15,7,.5);backdrop-filter:blur(10px);-webkit-backdrop-filter:blur(10px);border:1px solid rgba(240,240,234,.16);box-shadow:0 2px 10px rgba(0,0,0,.35),inset 0 0 0 4px rgba(240,240,234,.04)}
#compass{width:100%;height:100%;overflow:visible;filter:drop-shadow(0 1px 2px rgba(0,0,0,.5))}
#compass-rose{transform-box:fill-box;transform-origin:center}
.cmp-tick{stroke:rgba(240,240,234,.5);stroke-width:1.4;stroke-linecap:round}
.cmp-tick.cmp-minor{stroke:rgba(240,240,234,.28);stroke-width:1.1}
.cmp-n{fill:var(--accent)}
.cmp-s{fill:rgba(240,240,234,.42)}
.cmp-hub{fill:rgba(240,240,234,.6)}
.cmp-lbl{fill:var(--text);font:700 8px/1 var(--sans);letter-spacing:.02em;text-anchor:middle}
#scale{display:flex;flex-direction:column;align-items:flex-end;gap:5px;transition:opacity .3s}
#scalebar{height:7px;width:90px;border:1px solid rgba(255,255,255,.88);border-radius:1px;box-shadow:0 1px 4px rgba(0,0,0,.5);
  background-image:repeating-linear-gradient(90deg,rgba(245,248,245,.95) 0,rgba(245,248,245,.95) 25%,rgba(20,28,22,.55) 25%,rgba(20,28,22,.55) 50%)}
#scale span{font-size:10px;font-weight:500;letter-spacing:.05em;color:var(--text);text-shadow:0 1px 3px rgba(0,0,0,.85);font-variant-numeric:tabular-nums}

/* caption — styled like a map margin / field note, not a glassy card */
/* The caption is anchored to the BOTTOM, so a long one grows UPWARD and off the top of the
   screen — taking the chapter title with it, silently, with no scrollbar and no clipping to
   show it happened. Odzala's People chapter ran 1,465 characters and lost its title entirely
   at 1440x820, and 192px of text at 1280x720. The phone breakpoint already capped this; the
   desktop one never did, so the bug only existed on the screen everyone builds on.
   This is a SAFETY NET, not a licence: a caption that needs to scroll is too long. See the
   caption-length gate in tools/check_retarget.mjs. */
:root{--cap-bottom:92px;--cap-headroom:104px}
#caption{position:fixed;left:28px;bottom:92px;z-index:25;max-width:384px;padding:17px 22px 16px;border-radius:3px;background:linear-gradient(180deg,rgba(6,11,9,.56),rgba(6,11,9,.68));backdrop-filter:blur(7px);-webkit-backdrop-filter:blur(7px);border:1px solid rgba(240,240,234,.09);border-left:2px solid rgba(0,190,90,.6);opacity:0;transition:opacity .5s ease;max-height:calc(100vh - var(--cap-bottom) - var(--cap-headroom));overflow-y:auto;overscroll-behavior:contain}
#caption.in{opacity:1;animation:capin .6s cubic-bezier(.2,.7,.2,1)}
@keyframes capin{from{opacity:0;transform:translateY(8px)}to{opacity:1;transform:none}}
#cap-title{font-family:"Fraunces",Georgia,serif;font-size:clamp(25px,2.9vw,33px);font-weight:500;letter-spacing:-.01em;line-height:1.04;margin-bottom:9px;color:#f4f3ee}
#cap-line{font-size:13px;line-height:1.58;color:var(--dim);max-width:44ch}
#cap-stats{display:flex;flex-wrap:wrap;margin-top:14px;border-top:1px solid rgba(240,240,234,.13);padding-top:12px}
#cap-stats div{display:flex;flex-direction:column;gap:2px;padding:0 15px;border-left:1px solid rgba(240,240,234,.1)}
#cap-stats div:first-child{padding-left:0;border-left:0}
#cap-stats b{font-size:17px;font-weight:600;letter-spacing:0;color:#eef0ea;font-variant-numeric:tabular-nums}
#cap-stats span{font-size:9px;letter-spacing:.1em;text-transform:uppercase;color:var(--muted);margin-top:1px}
/* ⛔ THE STAT ROW WRAPPED, AND WRAPPING IS THE ONE THING THE GATE FORBIDS.
   tools/check_layout_fit.mjs fails when #cap-stats' children land on more than one row: a stat and
   its label belong together, and a row that breaks mid-way reads as two unrelated pairs.
   ⛔⛔ THE CAUSE IS THE CONTAINER, NOT THE VIEWPORT. #caption is max-width:384px at EVERY width, so
   three stats cannot sit in one row on a 1440px desktop any more than on a 393px phone. A viewport
   media query would stack the phone and leave the desktop wrapping — and fixing the phone alone
   would only REVEAL the desktop case, because the gate reports the first wrap it finds and 393 was
   masking 1440.
   ⭐ So the condition is the NUMBER OF STATS, not the screen: at three or more, stack. :has() reads
   the actual children, so the rule cannot drift out of step with what a chapter carries. The gate
   accepts 'stacked-by-design' because a column is a deliberate answer and a wrapped row is an
   accident, and it can tell them apart. Two stats still sit side by side, which is the point. */
/* ⭐ 2026-09-30, the site owner's call: the stacked column strayed from the vista format — Virunga,
   the reference vista, shows its three stats in ONE ROW with a mono-caps label under each. The
   long labels these landscapes carry ("grizzly recovery ecosystem") are what made a flex row wrap,
   so at exactly three the row becomes a 3-column GRID: every stat shares one top edge (the gate's
   one-row test), and a long label wraps INSIDE its column instead of pushing a sibling down. Four
   or more still stack — no site carries four, and a 2x2 would read as two rows to the gate. */
#cap-stats:has(> div:nth-child(3)):not(:has(> div:nth-child(4))){display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:0;flex-wrap:nowrap}
#cap-stats:has(> div:nth-child(3)):not(:has(> div:nth-child(4))) > div{padding:0 12px;border-left:1px solid rgba(240,240,234,.1);min-width:0}
#cap-stats:has(> div:nth-child(3)):not(:has(> div:nth-child(4))) > div:first-child{padding-left:0;border-left:0}
#cap-stats:has(> div:nth-child(3)):not(:has(> div:nth-child(4))) span{line-height:1.3}
#cap-stats:has(> div:nth-child(4)){flex-direction:column;gap:10px;flex-wrap:nowrap}
#cap-stats:has(> div:nth-child(4)) > div{padding:0;border-left:0}

/* boundary science readout */
#readout,.look{position:fixed;right:26px;top:50%;transform:translateY(-50%) translateX(24px);z-index:26;width:380px;max-width:calc(100vw - 52px);max-height:80vh;overflow-y:auto;padding:24px 24px 20px;border-radius:14px;background:linear-gradient(180deg,rgba(7,15,7,.78),rgba(7,15,7,.88));backdrop-filter:blur(16px);-webkit-backdrop-filter:blur(16px);border:1px solid rgba(0,190,90,.28);box-shadow:0 24px 70px rgba(0,0,0,.55);opacity:0;pointer-events:none;transition:opacity .4s cubic-bezier(.2,.7,.2,1),transform .4s cubic-bezier(.2,.7,.2,1)}
#readout.open,.look.open{opacity:1;transform:translateY(-50%) translateX(0);pointer-events:auto}
#readout-close,.look-close{position:absolute;top:14px;right:16px;background:none;border:0;color:var(--muted);font-size:16px;cursor:pointer;transition:.2s}
#readout-close:hover,.look-close:hover{color:var(--text)}
.ro-kicker{font-size:10px;letter-spacing:.16em;text-transform:uppercase;color:var(--accent);margin-bottom:8px}
.ro-title{font-family:"Fraunces",Georgia,serif;font-size:25px;font-weight:500;letter-spacing:-.01em;line-height:1.08;margin-bottom:10px;color:#f4f3ee}
.ro-line{font-size:13.5px;line-height:1.55;color:var(--dim);margin-bottom:18px}
.ro-grid{display:grid;grid-template-columns:1fr 1fr;gap:14px 18px;margin-bottom:18px;padding-bottom:18px;border-bottom:1px solid rgba(240,240,234,.1)}
.ro-grid dt{font-size:9.5px;letter-spacing:.09em;text-transform:uppercase;color:var(--muted);margin-bottom:3px}
.ro-grid dd{font-size:18px;font-weight:700;letter-spacing:-.01em;color:var(--text)}
.ro-grid dd em{font-style:normal;font-size:9.5px;font-weight:500;letter-spacing:.04em;text-transform:uppercase;color:var(--muted);display:block;margin-top:1px}
.ro-facts{list-style:none;display:flex;flex-direction:column;gap:9px;margin-bottom:16px}
.ro-facts li{font-size:12.5px;line-height:1.5;color:var(--dim);padding-left:14px;position:relative}
.ro-facts li::before{content:"";position:absolute;left:0;top:7px;width:5px;height:5px;border-radius:50%;background:var(--accent)}
.ro-facts li b{color:var(--text);font-weight:700}
.ro-src{font-size:10px;line-height:1.6;color:var(--muted)}
/* The two landscape essays (Connectivity, Reintroduction of Wolves) open as reading sheets in
   the park record's format — same box, type and colour tokens as #readout, wider because they
   are long-form. The card at rest is the ordinary #caption. Three shapes the record lacks: */
.look{width:560px}
.ro-line b{color:var(--text)}
.ro-src+.ro-h,.ro-line+.ro-src{margin-top:-8px}
.ro-table{border-collapse:collapse;width:100%;font-size:12.5px;line-height:1.5;color:var(--dim);margin:0 0 16px}
.ro-table td{text-align:left;vertical-align:top;padding:5px 8px 5px 0;border-bottom:1px solid rgba(240,240,234,.1)}
.ro-table b{color:var(--text)}
.ro-pos{border-left:2px solid rgba(0,190,90,.42);padding:2px 0 2px 12px;margin:0 0 14px;font-size:12.5px;line-height:1.5;color:var(--dim)}
.ro-pos b{display:block;color:var(--text);margin-bottom:3px}
.ro-refs{padding-left:18px;font-size:11px;line-height:1.55;color:var(--muted);margin:0 0 8px}
.ro-refs li{margin:0 0 7px;overflow-wrap:anywhere}
.look .cap-link{padding:3px 10px;font-size:11px;vertical-align:baseline}
/* Links inside a closer look, and the hub link: the vocabulary psi-ui gives .rowjump — body colour, a dotted
   accent hairline, no browser blue. Nothing else on the page styles an <a>, so the essay's old cyan was
   the only rule and it left with .essay. */
.look a,.psi-hub{color:var(--text);text-decoration:none;border-bottom:1px dotted rgba(0,190,90,.45);transition:.15s}
.look a:hover,.psi-hub:hover{color:var(--accent);border-bottom-color:var(--accent)}

/* caption action links */
#cap-stats:empty{display:none}   /* a chapter with no figures shows no stat row, not a bare rule */
#cap-links{display:flex;flex-wrap:wrap;gap:9px;margin-top:16px}
#cap-links:empty{display:none}
.cap-link{background:rgba(0,190,90,.1);border:1px solid rgba(0,190,90,.42);color:var(--alive);font-family:var(--sans);font-size:12px;font-weight:600;padding:7px 13px;border-radius:999px;cursor:pointer;transition:.2s}
.cap-link:hover{background:var(--accent);color:var(--deep);border-color:var(--accent)}

/* chapter chips */
#chapters{position:fixed;left:50%;bottom:26px;transform:translateX(-50%);z-index:25;display:flex;gap:8px;padding:6px;border-radius:999px;background:rgba(7,15,7,.4);backdrop-filter:blur(10px);-webkit-backdrop-filter:blur(10px);border:1px solid rgba(240,240,234,.1)}
.chip{background:none;border:0;color:var(--dim);font-family:var(--sans);font-size:13px;font-weight:500;padding:8px 15px;border-radius:999px;cursor:pointer;transition:.2s;white-space:nowrap}
.chip:hover{color:var(--text)}
.chip.on{background:var(--accent);color:var(--deep);font-weight:700}
.chip.dive{color:var(--aqua);border:1px solid rgba(50,200,255,.32)}
.chip.dive:hover{color:#fff;border-color:var(--aqua);background:rgba(50,200,255,.12)}

/* focused-scene chrome */

/* published-maps inset + modal */
/* --mi-anchor tells fitMapInset() which edge this thumb is pinned to. It cannot read that from
   `top`/`bottom`: getComputedStyle returns the USED value for a positioned element, so both
   sides always read back as pixel lengths and `=== 'auto'` is never true. Custom properties are
   returned as authored, so they are the only reliable channel for layout INTENT. */
#mapinset{--mi-anchor:bottom;position:fixed;right:26px;bottom:96px;z-index:25;display:flex;flex-direction:column;align-items:center;gap:6px;padding:0;background:none;border:0;cursor:pointer}
/* Bound the thumb to a BOX, not just a width. `width:104px;height:auto` was written
   against the reference build's landscape thumb (360x203 -> 104x59). A portrait thumb
   — Tejon's is 236x480 — became 104x226, nearly four times as tall, and collided with
   the map key above it on any window shorter than ~880 px. Same mistake as .map-frame,
   in the other element: one dimension constrained, the other left to the aspect ratio
   of whichever park happened to be the reference. */
#mapinset img{max-width:104px;max-height:132px;width:auto;height:auto;object-fit:contain;border-radius:8px;border:1px solid rgba(240,240,234,.2);box-shadow:0 10px 30px rgba(0,0,0,.5);transition:.25s}
#mapinset:hover img{border-color:var(--accent);transform:translateY(-2px)}
#mapinset.mi-hidden{display:none}
#mapinset span{font-size:10px;letter-spacing:.1em;text-transform:uppercase;color:var(--dim)}
#mapmodal{position:fixed;inset:0;z-index:60;background:radial-gradient(circle at 50% 42%,rgba(7,15,7,.82),rgba(0,0,0,.95));backdrop-filter:blur(8px);display:none;align-items:center;justify-content:center;padding:5vh 5vw}
#mapmodal.open{display:flex;animation:modalbg .4s ease}
@keyframes modalbg{from{opacity:0}to{opacity:1}}
.modal-inner{position:relative;max-width:1100px;width:100%;opacity:0;transform:scale(.97);transition:opacity .42s cubic-bezier(.2,.7,.2,1),transform .42s cubic-bezier(.2,.7,.2,1)}
#mapmodal.open .modal-inner{opacity:1;transform:none}
#map-close{position:absolute;top:-34px;right:0;background:none;border:0;color:var(--dim);font-size:20px;cursor:pointer;z-index:2}
#map-close:hover{color:var(--text)}
.mapfig{display:flex;flex-direction:column;align-items:center;gap:13px}
.mapfig figure,.mapfig{position:relative}
/* The frame SHRINK-WRAPS the plate rather than claiming the modal's full width.
   width:100% was written against the reference build, whose plates are landscape
   (2000x1127) and fill it. A taller plate is capped by max-height instead, so the
   frame kept its full 1100px and the image sat in a moat: 42% empty on a square-ish
   plate, 70% on a 2000x4070 portrait one, with the border drawn around the emptiness
   rather than the map. Sizing to the content works for any aspect. */
.map-frame{position:relative;width:auto;min-width:min(100%,364px);max-width:100%;max-height:74svh;overflow:hidden;border-radius:10px;border:1px solid rgba(240,240,234,.16);background:#0a160a;box-shadow:0 30px 90px rgba(0,0,0,.6);display:flex;align-items:center;justify-content:center}
.mapfig img{max-width:100%;max-height:74svh;display:block;transform-origin:center center;will-change:transform;cursor:zoom-in;user-select:none;-webkit-user-drag:none}
#map-stage.kb{animation:kbIn 1.3s ease both}
@keyframes kbIn{from{filter:brightness(.66) saturate(.82)}to{filter:brightness(1) saturate(1.06)}}
.map-hint{position:absolute;left:50%;transform:translateX(-50%);bottom:10px;z-index:2;white-space:nowrap;font-size:9.5px;letter-spacing:.08em;text-transform:uppercase;color:rgba(240,240,234,.62);background:rgba(7,15,7,.62);padding:5px 10px;border-radius:999px;pointer-events:none}
.mapfig figcaption{font-size:13px;color:var(--dim);text-align:center;max-width:72ch}
/* The attribution is a flex child, so it shrink-wraps to whatever the frame is wide.
   With a portrait plate that collapsed it into a ragged ~350px column. Give it its own
   measure, independent of the plate's aspect. */
.mapfig .src{width:100%;max-width:78ch;text-align:center;margin:0 auto}
.map-tabs{display:flex;gap:8px;flex-wrap:wrap;justify-content:center}
.map-btn{background:rgba(240,240,234,.06);border:1px solid rgba(240,240,234,.18);color:var(--dim);font-family:var(--sans);font-size:12px;padding:7px 14px;border-radius:999px;cursor:pointer;transition:.2s}
.map-btn:hover{color:var(--text);border-color:var(--accent)}
.map-btn.on{background:var(--accent);border-color:var(--accent);color:var(--deep)}
.src{font-size:10px;line-height:1.6;color:var(--muted);max-width:62ch;text-align:center;margin-top:4px}

/* ── live data: NASA FIRMS active fire + GFW deforestation alerts ─────────── */
#live-pts{position:fixed;inset:0;z-index:9;pointer-events:none}
.livept{position:absolute;transform:translate(-50%,-50%);will-change:left,top,opacity;opacity:0;transition:opacity .5s}
.livept.fire,.livept.alert{pointer-events:auto;cursor:pointer}
/* active-fire hotspot — a small, clear flame icon (subtle amber, gentle flicker), size set per-cluster in JS */
.livept.fire svg{display:block;width:100%;height:100%;overflow:visible;transform-origin:50% 80%;filter:drop-shadow(0 0 1.5px rgba(255,110,30,.5)) drop-shadow(0 1px 1px rgba(70,18,0,.4));animation:flameflick 2.8s ease-in-out infinite}
/* GFW deforestation alert — a compact target (small ring + centre dot), constant-weight stroke.
   Magenta (the GFW GLAD convention): a cool hue that reads as a DISTINCT layer from the amber fire
   flames and the amber→red Hansen overlay, so the warm Forest-Loss view no longer muddles together.
   No opacity animation (updateLive drives .livept opacity). */
.livept.alert svg{display:block;width:100%;height:100%;overflow:visible;filter:drop-shadow(0 0 2px rgba(0,0,0,.6))}
.livept.alert .ll-ring{fill:rgba(241,95,208,.12);stroke:#f15fd0;stroke-width:1.5px;vector-effect:non-scaling-stroke}
.livept.alert .ll-core{fill:#ff74dc}
@keyframes flameflick{0%,100%{opacity:.9;transform:scale(1) skewX(0deg)}50%{opacity:1;transform:scale(1.04,1.1) skewX(-1.5deg)}}

/* live forest-condition key (Forest Loss) — same right-rail card language as #wlegend / #hlegend */
#llegend{position:fixed;right:24px;top:276px;z-index:24;padding:13px 15px 12px;border-radius:11px;background:linear-gradient(180deg,rgba(7,15,7,.5),rgba(7,15,7,.66));backdrop-filter:blur(10px);-webkit-backdrop-filter:blur(10px);border:1px solid rgba(240,240,234,.1);opacity:0;transform:translateY(-6px);transition:opacity .4s,transform .4s;pointer-events:none;width:268px}
#llegend.show{opacity:1;transform:none}
#llegend .wl-h{font-size:9px;letter-spacing:.15em;text-transform:uppercase;color:var(--muted);margin-bottom:10px}
#llegend .ll-row{display:flex;align-items:center;gap:10px;font-size:11.5px;color:var(--dim);line-height:1.6;padding:3px 0}
#llegend .ll-ic{flex:none;width:20px;height:20px;display:inline-flex;align-items:center;justify-content:center}
#llegend .ll-ic svg{width:17px;height:17px;display:block;overflow:visible}
#llegend .ll-lbl{flex:1}
#llegend .ll-n{font-variant-numeric:tabular-nums;font-weight:700;color:var(--text)}
#llegend .ll-span{font-size:8.5px;letter-spacing:.1em;text-transform:uppercase;color:var(--muted);margin-left:6px;min-width:22px;text-align:right}
#llegend .wl-sep{height:1px;background:rgba(240,240,234,.1);margin:9px 0 8px}
#llegend .ll-foot{font-size:9px;letter-spacing:.04em;color:var(--muted);line-height:1.45}

/* hover tooltip for a live marker */
#livetip{position:fixed;left:0;top:0;z-index:40;transform:translate(-50%,-100%);pointer-events:none;opacity:0;transition:opacity .15s;padding:7px 10px;border-radius:8px;background:linear-gradient(180deg,rgba(7,15,7,.88),rgba(7,15,7,.95));border:1px solid rgba(240,240,234,.14);box-shadow:0 6px 22px rgba(0,0,0,.45);backdrop-filter:blur(8px);-webkit-backdrop-filter:blur(8px);white-space:nowrap}
#livetip.show{opacity:1}
#livetip b{color:var(--text);font-variant-numeric:tabular-nums;font-weight:700;font-size:13px}
#livetip i{display:block;font-style:normal;font-size:8.5px;letter-spacing:.07em;text-transform:uppercase;color:var(--muted);margin-top:3px}
#wlegend .wl-row.la i{width:9px;height:9px;border-radius:2px;background:radial-gradient(circle,rgba(255,122,182,1),rgba(232,52,132,.95));box-shadow:0 0 7px rgba(232,60,140,.6);color:transparent}
#wlegend .wl-row .wl-src{margin-left:auto;padding-left:10px;font-size:8px;letter-spacing:.1em;text-transform:uppercase;color:var(--muted)}

/* ── tablet / large phone ───────────────────────────────────────────────── */
@media(max-width:820px){
  .hud{padding:12px 16px}
  .hud-tag{display:none}
  #locator{top:66px;left:auto;right:12px;width:auto;max-width:48vw;padding:9px 11px;gap:10px}
  #locator .loc-cap b{font-size:14px}
  #globe{width:86px;height:86px}
  #cartos{top:176px;left:auto;right:12px;width:auto;max-width:46vw;padding:10px 13px;gap:12px}
  #north{width:42px;height:42px}
#wlegend,#hlegend,#plegend,#glegend,#ilegend,#llegend{top:248px;left:auto;right:12px;max-width:46vw;padding:11px 13px 10px;width:auto}
#wlegend .wl-row,#hlegend .wl-row,#glegend .wl-row,#ilegend .wl-row,#llegend .ll-row{font-size:10.5px;line-height:1.72}
  #lossyear{width:72vw}
  #caption{left:14px;right:auto;bottom:150px;max-width:58vw;padding:15px 17px 14px}
  #cap-line{max-width:none}
  #mapinset{right:14px;bottom:152px}
  #mapinset img{width:68px}
  #readout,.look{left:14px;right:14px;top:auto;bottom:78px;width:auto;max-width:none;transform:translateY(24px);max-height:60svh}
  #readout.open,.look.open{transform:translateY(0)}
  #chapters{bottom:14px;max-width:94vw;overflow-x:auto;justify-content:flex-start}
  .mlabel .mn{font-size:13px}
}
/* ── phone (portrait) ───────────────────────────────────────────────────── */
@media(max-width:520px){
  #cartos{display:none}
  #locator{top:38px;left:auto;right:12px;width:auto;padding:0;gap:0;background:none;border:0;backdrop-filter:none;-webkit-backdrop-filter:none}
  #locator .loc-cap{display:none}
  #globe{width:78px;height:78px}
#wlegend,#hlegend,#plegend,#glegend,#ilegend,#llegend{top:46px;left:11px;right:auto;max-width:53vw;padding:9px 11px 8px;max-height:60svh;overflow-y:auto}
#wlegend .wl-h,#hlegend .wl-h,#glegend .wl-h,#ilegend .wl-h,#plegend .wl-h,#llegend .wl-h{font-size:8px;margin-bottom:6px}
#wlegend .wl-row,#hlegend .wl-row,#glegend .wl-row,#ilegend .wl-row,#llegend .ll-row{font-size:9.5px;line-height:1.6;gap:7px}
  #mapinset{--mi-anchor:top;right:12px;bottom:auto;top:128px;flex-direction:row;gap:6px}
  #mapinset img{width:52px}
  #mapinset span{display:none}
  #caption{left:11px;right:11px;bottom:116px;max-width:none;padding:13px 15px 12px}
  #cap-title{font-size:19px;margin-bottom:6px}
  #cap-line{font-size:12px;line-height:1.5}
  #cap-stats div{padding:0 11px}
  #cap-stats b{font-size:14px}
  #cap-links{margin-top:10px}
  #chapters{bottom:12px;padding:5px;gap:3px}
  #chapters .chip{font-size:11px;padding:7px 12px}
  #readout,.look{bottom:70px;max-height:64svh}
  #species,#place{width:auto;left:11px;right:11px;max-width:none;transform:translate(0,12px)}
  #species.show,#place.show{transform:none}
  #place{top:auto;bottom:14px}
}
@media(prefers-reduced-motion:reduce){#caption.in{animation:none}}

/* the one thing #plegend needs that no other key has: a continuous density ramp rather than
   discrete swatches. Everything else it inherits from #hlegend's rules above. */
#plegend .wl-row i.pl-ramp{display:block;width:100%;height:9px;border-radius:5px;
  background:linear-gradient(90deg,#6b2211 0%,#a8451a 45%,#ffb83d 100%)}
#plegend .wl-row.ramp{display:block}
#plegend .pl-scale{display:flex;justify-content:space-between;font-size:8.5px;letter-spacing:.08em;
  text-transform:uppercase;color:var(--muted);margin-top:5px}


/* ══ touch layout ═══════════════════════════════════════════════════════════
   Phones and tablets. The governing idea: the bottom of the screen is divided into two
   strips that never overlap — a NAV strip pinned above the home indicator, and the caption
   SHEET immediately above it. Nothing is layered on top of anything else down there.

   The previous version overlaid the nav on a flush-to-edge sheet and tried to reserve space
   with padding-bottom. Padding inside an `overflow-y:auto` box is scroll content, not a
   persistent gutter, so it reserved nothing until the user scrolled to the end — and the
   stats row and Park-record link rendered under the nav. Measured, not guessed.

   --navzone is the full height the nav strip claims, including the safe area. Everything
   above it is positioned from it, so there is one number to change. */
#keytoggle{display:none}

@media(max-width:1024px),(hover:none){
  :root{ --navh:44px; --navgap:10px; --navzone:calc(var(--sab) + var(--navh) + var(--navgap)*2); }

  /* ── nav strip ── */
  /* Full-width and horizontally scrollable: four chapter names do not fit across 402px, and
     the old centred pill clipped the last one ("Wild by Design" showed as "Wild"). Chips keep
     their own glass background now that the containing pill is gone. */
  #chapters{
    left:0;right:0;bottom:calc(var(--sab) + var(--navgap));transform:none;
    justify-content:flex-start;gap:8px;
    padding:0 62px 0 12px;            /* right gutter clears the KEY button */
    background:none;border:0;border-radius:0;backdrop-filter:none;-webkit-backdrop-filter:none;
    overflow-x:auto;overscroll-behavior-x:contain;-webkit-overflow-scrolling:touch;
    scrollbar-width:none;z-index:27}
  #chapters::-webkit-scrollbar{display:none}

  #chapters .chip{
    min-height:var(--navh);display:inline-flex;align-items:center;padding:0 16px;
    background:rgba(8,16,11,.82);border:1px solid rgba(240,240,234,.14);
    backdrop-filter:blur(12px);-webkit-backdrop-filter:blur(12px);
    font-size:12px;white-space:nowrap;-webkit-tap-highlight-color:transparent}
  #chapters .chip.on{background:var(--accent,#00be5a);color:#04180c;border-color:transparent}

  #keytoggle{
    display:inline-flex;align-items:center;position:fixed;right:10px;
    bottom:calc(var(--sab) + var(--navgap));z-index:28;
    min-height:var(--navh);padding:0 15px;border-radius:999px;
    border:1px solid rgba(240,240,234,.16);background:rgba(8,16,11,.88);
    backdrop-filter:blur(12px);-webkit-backdrop-filter:blur(12px);
    color:var(--dim);font:600 11px/1 var(--ui,inherit);letter-spacing:.12em;
    text-transform:uppercase;cursor:pointer;-webkit-tap-highlight-color:transparent}
  #keytoggle[aria-expanded=true]{background:var(--accent,#00be5a);color:#04180c;border-color:transparent}

  /* ── caption sheet, ABOVE the nav strip ── */
  #caption{
    left:8px;right:8px;bottom:var(--navzone);max-width:none;
    border-radius:14px;border-left:1px solid rgba(0,190,90,.28);
    border-top:2px solid rgba(0,190,90,.55);
    padding:14px 16px 15px;
    max-height:calc(52svh - var(--navzone));
    overflow-y:auto;-webkit-overflow-scrolling:touch;overscroll-behavior:contain;
    line-height:1.5;
    background:linear-gradient(180deg,rgba(6,11,9,.86),rgba(6,11,9,.95))}
  #cap-line{line-height:1.55}
  #cap-stats{gap:14px}

  /* keys hide unless asked for. .show is still what the chapter logic sets, so this only
     gates VISIBILITY on small screens — it does not change which key belongs to which chapter. */
#wlegend,#hlegend,#plegend,#glegend,#ilegend,#llegend{
    opacity:0 !important;pointer-events:none !important;transition:opacity .28s ease}
  body.keys-open #wlegend.show,body.keys-open #hlegend.show,#glegend.show,
  body.keys-open #plegend.show,body.keys-open #llegend.show{opacity:1 !important;pointer-events:auto !important}

  /* The onboarding hint moves OFF the map centre, where it sat across the terrain and over
     place labels, to a quiet line under the header. It is guidance, not content. */
  #hint{top:calc(var(--sat) + 58px);bottom:auto;left:12px;right:12px;transform:none;
    text-align:center;font-size:10px;letter-spacing:.1em;line-height:1.5}

  /* The thumb is the ONLY way into the reference maps — openModal() is bound to it and
     nothing else — so it must not be hidden on touch. Below 520px it moves to the top-right
     as a row (see that breakpoint); fitMapInset() detects that and leaves it alone. */
  .lang{min-height:44px;min-width:44px;display:inline-flex;align-items:center;justify-content:center}
}

@media(max-width:520px){
  #caption{max-height:calc(46svh - var(--navzone));padding:13px 14px 14px}
  /* the key, when opened, is a sheet rather than a floating card — and it sits above the nav
     strip for the same reason the caption does */
#wlegend,#hlegend,#plegend,#glegend,#ilegend,#llegend{top:auto !important;bottom:var(--navzone) !important;
    left:8px !important;right:8px !important;
    max-width:none !important;width:auto !important;max-height:56svh;overflow-y:auto;
    border-radius:14px;padding:14px 16px !important}
  body.keys-open #caption{opacity:0;pointer-events:none}   /* one sheet at a time */
}

/* Hint copy follows the INPUT, not the width: a wheel-less screen should never be told to
   scroll. Both strings are real text so both are translated. */
#hint .h-touch{display:none}
@media(hover:none),(max-width:1024px){
  #hint .h-mouse{display:none}
  #hint .h-touch{display:inline}
}

/* A wide bottom sheet (landscape tablet) should use its width rather than running a narrow
   column of prose with dead space beside it. Two columns keeps the sheet SHORT, which is the
   thing that matters on a 724px-tall landscape screen — every row of sheet is a row of map. */
@media(max-width:1024px) and (min-width:700px) and (orientation:landscape){
  #caption{display:grid;grid-template-columns:minmax(0,1fr) auto;grid-template-areas:
      "title stats" "line stats" "links stats";
    column-gap:26px;align-content:start;max-height:38svh;right:96px}
  #cap-title{grid-area:title}
  #cap-line{grid-area:line;max-width:68ch}
  #cap-stats{grid-area:stats;align-self:start;margin-top:0;border-top:0;padding-top:4px;
    flex-direction:column;gap:10px;min-width:150px}
  #cap-stats div{padding:0;border:0}
  #cap-links{grid-area:links;margin-top:12px}
}

/* ── mobile nav/sheet affordances ────────────────────────────────────────────
   Found by looking at a rendered iPhone frame, not by any metric: the chapter strip scrolls
   but did not look scrollable, so the fourth chapter was invisible behind the KEY button. */
@media(max-width:1024px),(hover:none){
  /* Fade the right edge so the strip reads as scrollable. Two properties because Safari still
     wants the prefix; without the standard one Firefox gets no fade (harmless). */
  #chapters{
    -webkit-mask-image:linear-gradient(90deg,transparent 0,#000 22px,#000 calc(100% - 74px),transparent 100%);
    mask-image:linear-gradient(90deg,transparent 0,#000 22px,#000 calc(100% - 74px),transparent 100%);
    scroll-behavior:smooth}

  /* The sheet's call to action stays reachable without scrolling the sheet. */
  /* Opaque, not a soft gradient: a translucent sticky bar let the stat labels
     ("PROTECTED", "ESTABLISHED") show through it, which reads as a rendering fault. */
  #cap-links{position:sticky;bottom:-15px;margin-top:16px;padding:12px 0 2px;
    background:#070f09;box-shadow:0 -10px 14px 6px #070f09;z-index:2}

  /* Keep the hint clear of the locator globe at top-right. */
  #hint{right:104px;text-align:left}
}
@media(max-width:520px){
  #hint{right:96px;font-size:9.5px}
}

/* A band the shader cannot paint must not be claimed in the key — see fitAltitudeKey().
   Set by JS from the in-park range the key header already declares. */
#hlegend .wl-row.hl-unreachable,#glegend .wl-row.hl-unreachable,#ilegend .wl-row.hl-unreachable{display:none}

/* Reviewer credit — lower right, under the reference-map thumb. Rendered by main.js only when
   src/site.config.js names a real reviewer; hidden otherwise, because an unearned credibility
   line is worse than none. fitMapInset() treats it as the floor so the thumb sits above it. */
#reviewcredit{position:fixed;right:26px;bottom:16px;z-index:25;max-width:230px;text-align:right;
  font-size:10px;line-height:1.5;letter-spacing:.04em;color:var(--dim);pointer-events:none}
#reviewcredit b{font-weight:600;color:var(--text);letter-spacing:.02em}
#reviewcredit[hidden]{display:none}
@media (max-width:760px){#reviewcredit{right:14px;bottom:10px;max-width:150px;font-size:9px}}

/* ── the right rail may not grow into the bottom-right corner ──────────────────────────────
   Every rail panel is position:fixed at top:276px and grows DOWNWARD with no bound, while the
   reference-map thumb and the reviewer credit are anchored to the bottom. On a long species
   list — and adding IUCN codes makes every list longer — the legend simply ran into them, and
   the only defence was JS hiding the thumb. That traded a feature away to avoid an overlap.

   Cap the panel instead. --rail-reserve is the space kept clear at the bottom for the maps
   thumb, its label and the credit; the panel scrolls internally past that point rather than
   pushing into it. This is a pure CSS guarantee that holds at any viewport height and for any
   number of species, with JS only enabling the scroll affordance when it is actually needed. */
:root{--rail-top:276px;--rail-reserve:186px}
#wlegend,#hlegend,#plegend,#glegend,#ilegend,#llegend{max-height:calc(var(--appvh) - var(--rail-top) - var(--rail-reserve))}
/* pointer-events stay OFF unless the panel really overflows, so the map can still be dragged
   through the empty part of the rail. main.js adds .rail-scroll only when scrollHeight exceeds
   clientHeight. */
#wlegend.rail-scroll,#hlegend.rail-scroll,#plegend.rail-scroll,#llegend.rail-scroll{
  overflow-y:auto;overscroll-behavior:contain;pointer-events:auto}
#wlegend.rail-scroll::-webkit-scrollbar,#hlegend.rail-scroll::-webkit-scrollbar,
#plegend.rail-scroll::-webkit-scrollbar,#llegend.rail-scroll::-webkit-scrollbar{width:6px}
#wlegend.rail-scroll::-webkit-scrollbar-thumb,#hlegend.rail-scroll::-webkit-scrollbar-thumb,
#plegend.rail-scroll::-webkit-scrollbar-thumb,#llegend.rail-scroll::-webkit-scrollbar-thumb{
  background:rgba(240,240,234,.22);border-radius:3px}
@media (max-width:760px){:root{--rail-reserve:120px}}
/* --rail-reserve is tuned for a tall screen. 186px of bottom clearance costs nothing at 982px of
   viewport and costs three legend rows at 720px, where the rail was already 14px over before the
   wildlife bands were added. Reclaim it by HEIGHT, not width: a 1280x720 laptop is wide and short,
   so the width breakpoint above never fires there. Verified against the nav and the maps thumb —
   the reserve still clears both, which is what fitMapInset() measures. */
@media (max-height:860px){:root{--rail-reserve:150px}}
@media (max-height:780px){:root{--rail-reserve:92px}}   /* 24px rows cost 18px; this pays for them */

/* IUCN Red List code on each wildlife-key row. Graded by severity so the shape of a park's
   threat profile is readable at a glance without opening a single card. */
#wlegend .wl-row.fa{position:relative;padding-right:34px}
.iucn{position:absolute;right:0;top:50%;transform:translateY(-50%);font-size:9px;font-weight:700;
  letter-spacing:.06em;padding:1px 4px;border-radius:3px;border:1px solid currentColor;opacity:.9}
.iucn-CR{color:#ff6b5e}.iucn-EN{color:#ff9d4d}.iucn-VU{color:#ffd24a}
.iucn-NT{color:#9ecf6a}.iucn-LC{color:#7fb0c9}.iucn-DD{color:#9a9a94}

/* A PHONE IN LANDSCAPE is wide and very short — 874x402 on an iPhone 16 Pro — and no width-keyed
   breakpoint catches it: 874px reads as "tablet", so it inherits a full-width caption. Measured
   there, the caption ran 770px wide straight under the locator/compass column at x=582, overlapping
   it by 74px. Key on HEIGHT, and keep the caption clear of the right-hand furniture. The maps thumb
   is left to fitMapInset(), which correctly finds no room at 402px and hides it. */
@media (max-height:560px) and (orientation:landscape){
  :root{--rail-top:120px;--rail-reserve:56px;--cap-headroom:40px;--cap-bottom:58px}
  #caption{max-width:min(56vw,520px);bottom:58px;padding:12px 15px 12px}
}

/* TAP TARGETS. WCAG 2.5.8 asks for 24x24 CSS px minimum; measured on a 402x874 phone the species
   rows were 180x22 and the dialog close buttons 12x18. The rows are the ONLY way to open a species
   card on touch — the map icons are deliberately mouse-only — so being 2px under is not cosmetic.
   Applied on coarse pointers only, so the desktop rail keeps its density. */
/* WCAG 2.5.8 is not a touch-only rule. Measured on desktop: species-close and place-close were
   23x23 — one pixel under — and readout-close 12x18. Coarse pointers get a larger target still. */
#species-close,#place-close,#readout-close,.look-close{
  min-width:24px;min-height:24px;display:inline-flex;align-items:center;justify-content:center}
@media (pointer: coarse){
  #wlegend .wl-row{min-height:28px}
  #wlegend .wl-row.fa{min-height:30px}
  #species-close,#place-close,#readout-close,.look-close{
    min-width:34px;min-height:34px;display:inline-flex;align-items:center;justify-content:center}
}

/* The credit is anchored bottom-right, and at this breakpoint the nav strip above it goes FULL
   WIDTH — so the two share a corner and the credit lands inside the strip. Measured on Chitwan
   (the only site carrying a reviewer, which is why a Woodbury-only check never saw it): 15px of
   overlap at 874x402, 27px at 402x874 and at 744x1133.
   THIS MUST COME AFTER the base #reviewcredit rule: same specificity, so source order decides,
   and the first attempt sat 100 lines above it and lost silently. */
@media (max-width:1024px), (hover:none){
  /* There is no free corner here. The nav strip is full width along the bottom and the caption
     fills the band above it; lifting the badge clear of one put it 14px inside the other. The
     credit is NOT lost — renderReviewCredit() also writes the full statement (name, affiliation,
     scope, date) into #ro-review in the park record, which is verified present and visible at
     402x874. A floating badge is the desktop affordance; the record is the attribution. */
  #reviewcredit{display:none}
}

/* TARGET SIZE, LAST WORD. WCAG 2.5.8 wants 24x24 for every pointer, not only touch, and these
   two groups kept failing after an earlier attempt because that attempt sat above the rules it
   was meant to beat — same specificity, source order decides. Measured before: single-line
   species rows 122x22, species-close and place-close 23x23 with a mouse.
   Placed at the very end of the sheet on purpose. */
#wlegend .wl-row{min-height:24px}
#species-close,#place-close,#readout-close,.look-close,#keytoggle{
  min-width:26px;min-height:26px;display:inline-flex;align-items:center;justify-content:center}

/* DATA CREDIT. The main view drapes USDA NAIP imagery (public domain) across the whole screen
   and draws OpenStreetMap geometry over it; OSM's ODbL requires attribution and NAIP is
   credited for provenance, and until now the only credit lived inside the reference-maps
   modal — a panel a visitor has to open. This is the always-visible line. It is deliberately tiny and dim: attribution has to be present and
   legible, not prominent.
   Hidden below 1024px for the same reason as the reviewer credit — there is no free corner at
   that size, and the full source list is in the park record, linked from every chapter caption. */
#datacredit{position:fixed;left:28px;bottom:12px;z-index:24;font-size:9px;letter-spacing:.02em;
  color:#F0F0EA;background:rgba(13,31,13,.72);padding:3px 7px;border-radius:3px;
  pointer-events:none;line-height:1.3;  /* AA on a plate, not an alpha — see park-map-template 368c2e5 */
  /* must stop short of the CENTRED chapter strip: at 1280 the nav starts at x=445 and a
     44vw box reached 591, overlapping it by 9px. 30vw keeps the right edge under 420. */
  /* 24vw, sized for the WIDEST nav in the fleet: five chapters at 1280 puts the strip's left
     edge near x=360, and a 30vw box still reached 335+ on those sites. Four-chapter sites had
     already measured clean, which is exactly the trap — size for the worst case, not the one
     in front of you. */
  /* min() with a 100vw FALLBACK: if sizeCreditGap never runs this is min(24vw,320px) -
     today's behaviour, a state someone already reported - and never a zero-width credit. */
  max-width:min(24vw,320px,var(--creditgap,100vw))}
/* The Copernicus licence requires "Contains modified Copernicus Sentinel data" wherever the
   imagery is redistributed, OpenStreetMap requires its ODbL notice, and the non-affiliation
   disclaimer has to be readable too — so this block may never be display:none. It used to be
   hidden below 1024px AND on any touch device, which removed the licence notice from most
   visitors. It now shrinks and wraps instead of disappearing. */
@media (max-width:1024px), (hover:none){
  #datacredit{
    font-size:9px; line-height:1.25; max-width:100%; white-space:normal;
    padding:4px 6px;   /* opacity:.72 REMOVED — it compounded with the ink alpha */
  }
  /* THE CREDIT GETS ITS OWN STRIP, AND EVERYTHING BOTTOM-ANCHORED RISES ABOVE IT.
     At the wide tier max-width:min(24vw,320px) keeps this block left of the centred pills so they
     never meet. This tier sets max-width:100% - correct, the licence notice must wrap rather than
     vanish - and it then spans the full width at bottom:12 while #chapters sits at bottom:26, is
     44px tall and carries z-index:25 against this block's 24. The pills are OPAQUE, so the
     Copernicus, ODbL and non-affiliation notices were painted over.
     MEASURED at 1024, 834, 800, 744, 700 and 393 - six of seven viewports, every width below
     1440, which is the one everybody develops at.
     OCCLUSION, NOT OVERFLOW: scrollWidth equals clientWidth at every width, so an overflow check
     reports clean. Two correct rectangles, one painted over the other - which is why no byte
     check, gate or boot probe in this fleet could see it, and why a person found it by looking.
     Moving the credit instead does NOT work; it clears the nav and lands under the caption card.
     THE STRIP HEIGHT CANNOT BE A CONSTANT - the notice wraps to a different depth at each width -
     so --credit-h is measured at runtime. The 0px fallback means a JS failure degrades to the OLD
     overlap rather than to a gap where the nav used to be: a visible wrong beats an invisible one,
     because the overlap is a state someone already reported. From park-map-template 38ae5c9. */
  #chapters, #keytoggle{ bottom:calc(26px + var(--credit-h, 0px)); }
  #caption{ bottom:calc(92px + var(--credit-h, 0px)); }
  body.focus #caption{ bottom:92px; }
}

/* THE SAME STRIP, CLAIMED AT ANY WIDTH WHERE THE NAV LEAVES NO ROOM BESIDE IT.
   sizeCreditGap sets data-creditstrip when the measured gap falls under a readable 210px, so a
   six-chapter nav in a long language gets the <=1024 tier's answer at 1440 too instead of a
   credit crushed to 40px. The selector carries html[...] so it outranks the base rule by
   specificity and does not depend on source order. */
/* max-width from the MEASURED limit, not 100%: a full-bleed strip ran under the plate
   thumbnail at 1440 (an <img> with no id, found by probe, not by reading the markup). */
html[data-creditstrip] #datacredit{max-width:var(--creditgap,100%);white-space:normal}
html[data-creditstrip] #chapters,html[data-creditstrip] #keytoggle{bottom:calc(26px + var(--credit-h,0px))}
/* #hint RISES TOO, and it is the reason the strip was not a strip. It is a 561px line of orbit
   guidance at bottom:74px with z-index:20 - BELOW the credit's 24, so the credit already paints
   over it and nothing was ever hidden. But rightEdgeLimit reserves by geometry, not by paint
   order, so it stopped the strip dead at x=439 and Ndoki's notice stacked 170px high in a 397px
   column. Lift the hint and the same text is ~53px across the full width, with both readable. */
html[data-creditstrip] #hint{bottom:calc(74px + var(--credit-h,0px))}
html[data-creditstrip] #caption{bottom:calc(92px + var(--credit-h,0px))}
html[data-creditstrip] body.focus #caption{bottom:92px}

/* .ro-h — a heading inside the park record. A co-author asked for a place where an investor or
   a minister can see why a park matters GLOBALLY, not just what it contains. That is argument, not
   geography: it has no distinct map, so it is not a chapter. It belongs in the record, which is the
   long-form panel already linked from every chapter caption. */
#readout .ro-h,.look .ro-h{font-size:11px;letter-spacing:.13em;text-transform:uppercase;color:var(--dim);
  margin:20px 0 8px;padding-top:14px;border-top:1px solid rgba(240,240,234,.12);font-weight:600}
#readout .ro-h:first-of-type,.look .ro-h:first-of-type{margin-top:16px}

/* APPLE HIG 44px, ON TOUCH SURFACES ONLY. WCAG 2.5.8's 24x24 is the conformance floor this fleet
   is held to and every control clears it; 44x44 is Apple's TOUCH guideline, and #keytoggle (67x26)
   and #mapinset (52x30) were the two that sat between the two numbers.

   Gated on the SAME query that reveals them — `(max-width:1024px),(hover:none)` — not on a
   narrower one. A first attempt used max-width:760px and only reached the two phones: the button
   is shown from 1024px down, so iPad portrait, the 1000px laptop and the half-screen window kept
   the 26px target and the advisory kept firing. Match the query that shows a control, not a
   breakpoint that sounds like "mobile".

   Deliberately NOT applied to pointer devices: 44px is a thumb measurement, and a 44px pill in a
   1440px HUD is oversized for a mouse that already clears 24. */
@media (max-width:1024px), (hover:none){
  #keytoggle{min-height:44px}
  #mapinset{min-height:44px;min-width:44px}
}

/* GYE context rows in the map key: a neighbour outline the reader cannot name is decoration. */
#wlegend .wl-row.nb i{width:11px;height:2px;border-radius:1px;margin-right:8px;flex:0 0 auto}
#wlegend .wl-row.nb em{font-style:normal;color:var(--muted);font-size:9px;letter-spacing:.04em;margin-left:5px}

/* ── PSI CHAPTER ─────────────────────────────────────────────────────────────
   The paper's results inside the vista (src/psi.js). Optional: no public/psi.json,
   no chapter, no styles applied. Colour and type come from the tokens above — do
   not write a raw font-size, font-weight, letter-spacing or line-height here. */
.psi-panel{max-width:72ch;color:var(--text);font-family:var(--sans)}
.psi-scope,.psi-prov{color:var(--muted)}
.psi-hub{display:inline-block;margin-top:4px}   /* the hub link on the provenance line reads as its own line */
.psi-chart{width:100%;height:auto;overflow:visible}
.psi-grid line{stroke:var(--text);stroke-opacity:.10}
.psi-tick text,.psi-xlab,.psi-axis{fill:var(--muted)}
.psi-nlab{fill:var(--dim)}
.psi-line{fill:none;stroke:var(--accent);stroke-width:2.2;stroke-linejoin:round;stroke-linecap:round}
.psi-dot{fill:var(--accent)}
.psi-band{fill:var(--accent);fill-opacity:.16;stroke:none}
/* An isolated bounded year: a bar, not a filled polygon, so its interval survives. */
.psi-band-bar{fill:none;stroke:var(--accent);stroke-opacity:.45;stroke-width:2}
/* The comparability warning and the sampling caveat carry the same weight as the chart,
   because a chart that shows the line and hides these is not the paper's finding. */
.psi-break,.psi-caveat{border-left:3px solid var(--accent);padding:.6em 0 .6em .9em;margin:1em 0}
.psi-caveat h3{margin:0 0 .3em}
.psi-roster{list-style:none;padding:0;margin:.6em 0}
.psi-sp{display:flex;align-items:center;gap:.6em;padding:.35em 0;border-bottom:1px solid rgba(240,240,234,.08)}
.psi-sp-name{flex:1 1 auto}
.psi-sp-bin{color:var(--muted);flex:0 1 auto}
.psi-iucn{color:var(--deep);background:var(--accent);border-radius:3px;padding:0 .4em;flex:0 0 auto}
.psi-iucn[data-code="CR"],.psi-iucn[data-code="EN"]{background:#E8663D;color:var(--text)}
.psi-iucn[data-code="VU"]{background:#E8A33D;color:var(--deep)}
.psi-iucn[data-code="—"],.psi-iucn[data-code="DD"]{background:var(--muted);color:var(--deep)}
.psi-spark{flex:0 0 auto;width:120px;height:26px}
.psi-sparkline{fill:none;stroke:var(--aqua);stroke-width:1.6}
.psi-nospark{color:var(--muted);flex:0 0 auto;width:120px}

/* Placement on this site. The panel is the chapter's content, over the home plate — it appears
   only on the psi chapter, and only when public/psi.json actually loaded (mountPSI un-hides it).
   A chapter chip with nothing behind it is a dead control, so main.js REMOVES the chip when the
   file is absent rather than leaving it to open an empty panel. */
/* The panel IS this chapter's content and carries its own title, so the standard caption card
   would sit on top of it saying the same thing twice — it did, obscuring the caveat and the
   roster heading. Hide it here rather than shrinking the panel: every other chapter keeps it. */
body[data-view="psi"] #caption{display:none}
/* The reference-maps inset sits top-right and overlapped the panel on a phone, hiding the
   species count in the scope line. The panel IS the chapter here. */
body[data-view="psi"] #mapinset{display:none}

/* the PSI long view (src/psi_long.js) */
.psi-long{margin:.6em 0 1.4em}
.psi-long figcaption{font-size:.9em;color:rgba(240,240,234,.82);margin-bottom:.4em}
.psi-long svg{width:100%;height:auto;display:block}
.pl-tick line{stroke:rgba(240,240,234,.10)}.pl-tick text,.pl-xl,.pl-ax{fill:rgba(240,240,234,.6);font-size:11px}
.pl-ev line{stroke:#F0F0EA;stroke-dasharray:5 4;stroke-width:1.2;opacity:.8}.pl-ev text{fill:#F0F0EA;font-size:11px}
.pl-psi{fill:none;stroke:#F0F0EA;stroke-width:3.2;stroke-dasharray:10 5}
/* x-axis: a minor tick every year so each point has a year under it, a labelled major every 5 */
.pl-axis,.pl-xmaj{stroke:rgba(240,240,234,.45);stroke-width:1}.pl-xmin{stroke:rgba(240,240,234,.3);stroke-width:1}
/* where the index begins: its own line and label, in the accent, so 2001 reads as the start */
.pl-start line{stroke:var(--accent,#00BE5A);stroke-width:1.2;opacity:.85}
.pl-start-t{fill:var(--accent,#00BE5A);font-size:11px}
.pl-start-x{fill:var(--accent,#00BE5A);font-size:11px;font-weight:600}
.pl-psi-dot{fill:#F0F0EA;stroke:#070F07;stroke-width:1.5}
.pl-key{display:flex;flex-wrap:wrap;gap:.3em .9em;font-size:.8em;color:rgba(240,240,234,.78);margin:.4em 0}
.pl-key i{display:inline-block;width:.9em;height:.25em;margin-right:.35em;vertical-align:middle;border-radius:2px}
.pl-key i.pl-key-psi{background:#F0F0EA;height:.3em}
.pl-note{font-size:.8em;color:rgba(240,240,234,.6)}

/* ══ PSI CHAPTER v3 — THE PLATE IS THE HERO ════════════════════════════════════════════════════
   Landed 2026-09-28. Supersedes the v2 "DASHBOARD AROUND THE VISTA" container. Read this before
   changing any number in it.

   THE BRIEF: "the map should be in the middle with species placed, only when one is clicked on does
   it appear in the middle. and reformat so scrolls arent as much of an obstacle on right and left
   menus … use the same methods as the conservation imperatives site to display dense, deep data …
   nothing can look cluttered or overwhelming."

   ⭐ THREE WRONG ANSWERS PRECEDE THIS ONE, all recorded so none is tried again:
     · A FULL-SCREEN PAGE (germany) covers the plate: the index becomes a report hosted in a vista.
     · A SINGLE LEFT COLUMN puts everything in one 520 px strip. "we need to use the whole screen."
     · V2's DASHBOARD kept the plate lit but let both rails SCROLL — measured on live germany at
       1440x746: left rail scrollHeight 1094 against clientHeight 504, right 619 against 546. A rail
       that scrolls hides its own tail behind no visible scrollbar, so the reader is hunting again AND
       the plate is smaller than before. tools/check_psi_surface.mjs now FAILS on that.

   ⛔⛔ THE RULE THAT MAKES THIS LAYOUT TRUE: A RAIL HAS A FIXED BUDGET AND CONTENT MUST FIT OR LEAVE.
   `overflow:hidden` on a rail is deliberate and is NOT a way to hide overflow — the gate measures
   scrollHeight against clientHeight and fails, so anything that does not fit must be moved into a
   dock sheet. If you add a section to a rail and the gate goes red, the answer is never a taller rail
   or a smaller font; it is that the section belongs in a sheet. */

/* The shell catches nothing: every click falls through to the plate unless it lands on a rail, the
   dock, an open sheet or the centre card. Without this the vista is un-orbitable while the chapter is
   open, which is the one thing that would prove it is a document pasted over a map. */
#psipanel{position:fixed;inset:0;z-index:24;display:none;pointer-events:none}
body[data-view="psi"] #psipanel:not([hidden]){display:block}
#psipanel .psi-rail,#psipanel .psi-sheet,#psipanel #psidDetail{pointer-events:auto}

/* ── the two rails ────────────────────────────────────────────────────────────────────────────────
   A column layout with a hard bottom, so the budget is visible in the CSS rather than implied. */
#psipanel .psi-rail{
  position:fixed;display:flex;flex-direction:column;gap:9px;
  overflow:hidden;                                  /* ⛔ see the rule above — the gate enforces it */
  padding:14px 15px 16px;border-radius:var(--r-card,14px);
  background:linear-gradient(180deg,rgba(7,15,7,.80) 0,rgba(7,15,7,.94) 100%);
  backdrop-filter:blur(16px) saturate(.9);-webkit-backdrop-filter:blur(16px) saturate(.9);
  border:1px solid var(--sf-line,rgba(240,240,234,.10));box-shadow:0 20px 60px rgba(0,0,0,.5);
}
/* The left rail stops above the data credit rather than covering it: a licence obligation is not
   furniture, and a fixed credit under a rail is hidden at some position however padding is tuned. */
#psipanel .psi-rail-l{left:calc(24px + var(--sal,0px));width:min(344px,26vw);
  top:calc(76px + var(--sat,0px));bottom:calc(var(--psi-dockzone,92px) + var(--credit-h,0px) + 14px)}
#psipanel .psi-rail-r{right:calc(20px + var(--sar,0px));width:min(352px,26vw);
  top:calc(76px + var(--sat,0px));bottom:calc(var(--psi-dockzone,92px) + 14px)}

/* A rail header is one line: what this column is. Not a heading hierarchy — there is no room for one
   and a second level would only restate the first. */
#psipanel .psi-railh{flex:none;display:flex;align-items:baseline;justify-content:space-between;gap:8px;
  font-size:var(--fs-micro,.62rem);font-weight:var(--fw-bold,700);letter-spacing:var(--ls-caps-x,.16em);
  text-transform:uppercase;color:var(--accent);margin:0}
#psipanel .psi-railh span{color:var(--ink-dim,rgba(240,240,234,.5));font-weight:var(--fw-med,600);
  letter-spacing:var(--ls-wide,.04em);text-transform:none}

/* ── the four tiles: ONE object divided, not four cards ───────────────────────────────────────────
   Border, fill, radius and shadow each say "separate object". Four cards in a 344 px rail would spend
   all four on figures that are one reading, and flatten everything below them. So: one surface, three
   hairlines. */
#psipanel .psi-tiles{flex:none;display:grid;grid-template-columns:repeat(4,1fr);
  border:1px solid var(--sf-line,rgba(240,240,234,.10));border-radius:var(--r-ctl,8px);
  background:rgba(240,240,234,.035);overflow:hidden}
#psipanel .psi-tile{display:flex;flex-direction:column;gap:1px;padding:7px 7px 8px;min-width:0;
  border-left:1px solid var(--sf-line,rgba(240,240,234,.10))}
#psipanel .psi-tile:first-child{border-left:0}
#psipanel .psi-tile-k{font-size:var(--fs-micro,.62rem);font-weight:var(--fw-bold,700);
  letter-spacing:var(--ls-caps,.1em);text-transform:uppercase;color:var(--ink-dim,rgba(240,240,234,.5));
  white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
/* ⛔ NO MONO HERE, AND THAT IS DELIBERATE. The brand asks for JetBrains Mono on figures, and only
   Hanken Grotesk and Fraunces are self-hosted in this repo — fonts are self-hosted as a
   DATA-PROTECTION requirement, so naming a family we do not ship would either fall back silently or
   pull a Google Fonts request that transmits the reader's IP. tabular-nums gives the column alignment
   that was the actual reason for wanting a mono. */
#psipanel .psi-tile-v{font-size:var(--fs-md,1rem);font-weight:var(--fw-bold,700);
  letter-spacing:var(--ls-tight,-.01em);line-height:var(--lh-flat,1.05);color:var(--ink-near,#F0F0EA);
  font-variant-numeric:tabular-nums}
#psipanel .psi-tile-s{font-size:var(--fs-micro,.62rem);line-height:var(--lh-snug,1.35);
  color:var(--ink-faint,rgba(240,240,234,.38))}
/* The one tile that is a refusal rather than a figure reads as one: the em dash is the value, and the
   sub-line says why. It is tinted, not hidden — a reader must see that the question was asked. */
#psipanel .psi-tile-warn .psi-tile-v{color:var(--ink-dim,rgba(240,240,234,.5))}
#psipanel .psi-tile-warn .psi-tile-s{color:#E8B64C}

/* ── the chart, at a fixed height because the rail budget is fixed ──────────────────────────────── */
/* ⛔⛔ `min-height:0` HERE COLLAPSED THE CHART AND NOTHING MEASURED IT. The rail is a flex column, so
   min-height:0 let .psi-panel shrink to 103 px while its SVG — `overflow:visible` by default — went on
   painting its full 146 px, straight through the "what a point on this line is" button below it. Every
   gate stayed green: the RAIL's scrollHeight equalled its clientHeight, because ink that escapes its
   box is not scrollable content. ⭐ It was visible in one screenshot in two seconds and in no number.
   flex:none so the panel keeps its content height; overflow:hidden so an SVG can never again paint
   outside the box the layout gave it. */
#psipanel .psi-rail-l .psi-panel{display:flex;flex-direction:column;gap:10px;flex:none;margin:0;
  max-width:none;overflow:hidden}
#psipanel .psi-rail-l .psi-panel > svg{flex:none;width:100%;height:auto;max-height:150px;
  overflow:hidden}
/* ⛔ THE INDEX CHART IS SHORTER ON A SHORT VIEWPORT, WHICH IS WHAT check_psi_surface ASKS FOR.
   At 1440x746 the left rail's fixed furniture takes 245px of 371 and leaves the scrolling list —
   "psi-moved", the reserves the index could not use — 58px on Ndoki and 81px on Jurua, against a
   threshold of 88 that exists because fewer than two rows reports "fits" while showing almost
   nothing. Both sites PASS at 1440x900 (97px and 134px), so this is a short-viewport problem and
   not a layout one.
   ⭑ The gate names its own remedy and rules out the obvious wrong one: "move a section to a dock
   tab, or make it shorter. Not a taller rail." Taking the second. The chart is the largest single
   piece of fixed furniture at 151px, and 40px of chart height is worth more as two visible rows of
   a list than as vertical resolution on a five-point trend that is already annotated.
   ⚠ Scoped by HEIGHT, not width — the deficit is vertical, and a 1440x900 viewport needs none of
   this. Changing the cap unconditionally would shrink a chart that fits perfectly well. */
@media (max-height: 820px){
  #psipanel .psi-rail-l .psi-panel > svg{max-height:92px}
}
/* ⛔ BELOW ~780px THE RAIL'S FIXED FURNITURE EATS THE LIST, and below ~730px it eats the caveat too.
   Measured here (hit-tested, 2026-09-29): at 1280x720 the sentence "Each species over its own survey
   years … not a ranking" sat at y 519–540 inside a scroller that ended at 491 — below the fold, so
   a reader of the common laptop height did not get the caveat. 1440x746 and 1366x768 were whole.
   ⭐ THE SPACE COMES FROM THE CHART: it is a sparkline of a trend, not a reading surface, and its
   numbers are in the tiles above it and the table below. NOT from .psi-icon-note (a disclosure)
   and NOT from the credit strip (a licence obligation). Same two tiers as jurua-landseed 4b6d70c,
   which found the same thing on its live host.
   ⚠ ORDER MATTERS: at equal specificity the later rule wins, so these sit AFTER the 820 block. */
@media (max-height: 780px){
  #psipanel .psi-rail-l .psi-panel > svg{max-height:40px}
  #psipanel .psi-rail-l .psi-panel{gap:5px}
  #psipanel .psi-rail{gap:7px;padding:12px 14px 14px}
}
@media (max-height: 730px){
  #psipanel .psi-rail-l .psi-panel > svg{max-height:24px}
  #psipanel .psi-rail-l .psi-panel{gap:3px}
  #psipanel .psi-rail{gap:5px;padding:9px 12px 10px}
  #psipanel .psi-tile{padding-top:4px;padding-bottom:4px}
}
/* ⛔ AT 1024x900 TWO CHAPTER TILES FELL OUT OF THE 266px COLUMN. The HUD's narrow block keeps the
   chart to 96px "so eleven one-column tiles still fit"; this record has twelve (it carries a "What this
   record does not carry" section), and check_psi_surface measured "Why it breaks" 10px and "What is
   absent" 49px past the column's content box — the column is overflow:hidden and the tiles are the
   menu, so that is two unreachable chapters. Per PORTING.md the space comes from the chart, never
   from the tile or its type. `body` raises specificity over css/psi-hud.css, which loads after this
   file and must stay verbatim. */
@media (max-width:1120px){
  body #psipanel .psi-rail-l .psi-panel > svg{max-height:36px}
}
@media (max-width:1120px) and (max-height:730px){
  body #psipanel .psi-rail-l .psi-panel > svg{max-height:24px}
}
/* ⛔ THE SIXTH CHAPTER CHIP SAT OUTSIDE ITS BAR. In this view css/psi-hud.css caps #chapters at the gap
   between the columns (680px at 1440) and keeps it on one row; this site has six chapters (~780px of
   chips), so "Northern Range" overflowed the bar's glass and printed over the right column's
   silhouette note — seen in the 1440x900 screenshot, not reported by any gate. The bar wraps instead:
   same pills, same glass, a second row, and the HUD already measures the bar's height (--psi-navh)
   for the panels' bottom edge. Nothing is shrunk. */
@media (min-width:901px){
  body[data-view="psi"] #chapters{flex-wrap:wrap;justify-content:center;border-radius:26px}
}
#psipanel .psi-h{font-size:var(--fs-sm,.82rem);font-weight:var(--fw-bold,700);
  letter-spacing:var(--ls-wide,.04em);line-height:var(--lh-snug,1.35);margin:0;color:var(--ink-near,#F0F0EA)}
/* The scope sentence is 210 px in a 344 px column and is a sources statement, not a finding: it moves
   to the Sources sheet. Kept in the DOM, hidden here, so nothing is deleted by a layout. */
#psipanel .psi-rail-l .psi-scope,#psipanel .psi-rail-l .psi-caveat,
#psipanel .psi-rail-l .psi-prov,#psipanel .psi-rail-l .psi-h3,
#psipanel .psi-rail-l .psi-break{display:none}

/* ── what moved ───────────────────────────────────────────────────────────────────────────────────
   Two short lists. The note under them is not decoration: different species were measured over
   different windows, and a leaderboard that does not say so invites exactly the wrong reading. */
/* ⚠ THE SAME BARGAIN AS THE ROSTER, AND FOR THE SAME REASON. Six rows of species names are a
   different height on every site, so a rail tuned to Germany's names overflows on Ndoki's by a few
   pixels — and I spent three rounds shaving padding to buy 4 px, which is a tuning, not a layout. The
   RAIL still never scrolls; this one list may, with a stable gutter so the affordance is visible. A
   number that has to be re-tuned per site was never the right number. */
#psipanel .psi-moved{flex:0 1 auto;display:flex;flex-direction:column;gap:2px;min-height:0;
  overflow-y:auto;overscroll-behavior:contain;scrollbar-gutter:stable}
#psipanel .psi-mv-h{margin:4px 0 1px;font-size:var(--fs-micro,.62rem);font-weight:var(--fw-bold,700);
  letter-spacing:var(--ls-caps,.1em);text-transform:uppercase;color:var(--ink-dim,rgba(240,240,234,.5))}
#psipanel .psi-mv-l{list-style:none;margin:0;padding:0}
#psipanel .psi-mv{display:grid;grid-template-columns:1fr auto auto;gap:8px;align-items:baseline;
  padding:2px 0;font-size:var(--fs-xs,.72rem);line-height:var(--lh-snug,1.35)}
#psipanel .psi-mv-n{color:var(--ink-soft,rgba(240,240,234,.78));overflow:hidden;text-overflow:ellipsis;
  white-space:nowrap}
#psipanel .psi-mv-p{font-variant-numeric:tabular-nums;font-weight:var(--fw-bold,700)}
#psipanel .psi-mv.up .psi-mv-p{color:var(--accent)}
#psipanel .psi-mv.down .psi-mv-p{color:#E88C6A}
#psipanel .psi-mv-y{font-size:var(--fs-micro,.62rem);color:var(--ink-faint,rgba(240,240,234,.38));
  font-variant-numeric:tabular-nums}
#psipanel .psi-mv-note{margin:5px 0 0;font-size:var(--fs-micro,.62rem);line-height:var(--lh-snug,1.35);
  color:var(--ink-faint,rgba(240,240,234,.38))}

/* ── the right rail: one dense row per species, 44 px, ONE list ──────────────────────────────────── */
/* ⚠ THE ROSTER SCROLLS, THE RAIL DOES NOT, and those are different claims. A rail that scrolls hides
   its tail behind no affordance at all; a LIST with a stable scrollbar gutter inside a fixed rail is an
   ordinary control a reader can see. Where a roster genuinely does not fit — Ndoki has 24 species — the
   honest answer is a scrollable list whose count is stated in the rail header, not a smaller font. */
#psipanel .psi-rail-r .psi-roster{list-style:none;margin:0;padding:0;flex:1 1 auto;min-height:0;
  overflow-y:auto;overscroll-behavior:contain;scrollbar-gutter:stable}
/* ⚠ THE ICON COLUMN IS `auto`, NOT `26px`, SO IT COLLAPSES WHERE THERE IS NO ICON. Germany declares no
   species silhouettes — correctly, they were never sourced — and a fixed column reserved 26 px plus a
   gap of dead space on every row, which reads as an image that failed to load rather than as a
   deliberate absence. Sites WITH icons get the width from .psi-sp-ico itself. */
#psipanel .psi-rail-r .psi-sp{
  display:grid;grid-template-columns:auto 1fr auto 62px;gap:8px;align-items:center;
  height:44px;padding:0 4px;border-bottom:1px solid var(--sf-line,rgba(240,240,234,.10));
  cursor:pointer;transition:background .16s ease}
#psipanel .psi-rail-r .psi-sp:last-child{border-bottom:0}
#psipanel .psi-rail-r .psi-sp:hover,#psipanel .psi-rail-r .psi-sp[data-hl]{
  background:var(--sf-tint,rgba(240,240,234,.07))}
#psipanel .psi-rail-r .psi-sp:focus-visible{outline:2px solid var(--accent);outline-offset:-2px}
/* name and binomial share one cell and two lines, because 44 px is two lines of small type and a
   binomial on its own row would double the list's height for no added meaning. */
#psipanel .psi-sp-name{grid-column:2;font-size:var(--fs-xs,.72rem);font-weight:var(--fw-med,600);
  color:var(--ink-near,#F0F0EA);line-height:1.2;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
#psipanel .psi-sp-bin{grid-column:2;grid-row:2;font-style:italic;font-size:var(--fs-micro,.62rem);
  color:var(--ink-faint,rgba(240,240,234,.38));line-height:1.1;overflow:hidden;text-overflow:ellipsis;
  white-space:nowrap}
#psipanel .psi-rail-r .psi-sp{grid-template-rows:auto auto}
#psipanel .psi-sp-ico{grid-column:1;grid-row:1 / span 2;width:26px;height:26px;opacity:.9}
#psipanel .psi-sp-ico svg{width:100%;height:100%;display:block}
/* ⚠ THE LABELLED GAP. Where no correctly-identified open-licence silhouette exists, there is NO icon
   and the slot says so on hover — a chamois standing in for a serow is a wrong identification served
   as a right one, and a silent blank is indistinguishable from a failed load. */
#psipanel .psi-sp-ico[data-gap]{display:grid;place-items:center;border:1px dashed
  var(--sf-edge,rgba(240,240,234,.2));border-radius:var(--r-hair,4px);
  font-size:var(--fs-micro,.62rem);color:var(--ink-faint,rgba(240,240,234,.38))}
#psipanel .psi-rail-r .psi-iucn{grid-column:3;grid-row:1 / span 2;font-size:var(--fs-micro,.62rem);
  font-weight:var(--fw-bold,700);letter-spacing:var(--ls-caps,.1em);padding:2px 5px;
  border-radius:var(--r-hair,4px);border:1px solid var(--sf-edge,rgba(240,240,234,.2));
  color:var(--ink-soft,rgba(240,240,234,.78))}
#psipanel .psi-rail-r .psi-sp svg:not(.psi-sp-ico svg){grid-column:4;grid-row:1 / span 2;
  width:62px;height:18px}
/* ⛔ THE SPARKLINE WAS var(--aqua) — rgb(50,200,255), a colour the brand does not contain in this
   context, and it made ten cyan lines the loudest thing in the rail. It is also the wrong HIERARCHY:
   the accent belongs to the index line, which is the aggregate claim; a per-species trend is
   supporting evidence. Scoped to this rail rather than changing the shared token, which other views
   use for water. */
#psipanel .psi-rail-r .psi-sparkline{stroke:var(--ink-dim,rgba(240,240,234,.5));stroke-width:1.4}
#psipanel .psi-rail-r .psi-sp:hover .psi-sparkline,
#psipanel .psi-rail-r .psi-sp[data-hl] .psi-sparkline{stroke:var(--accent)}

#psipanel .psi-eval{flex:1 1 auto;min-height:0;overflow-y:auto;overscroll-behavior:contain;
  list-style:none;margin:0;padding:0}
#psipanel .psi-eval[hidden]{display:none}
#psipanel .psi-eval li{display:flex;gap:6px;align-items:baseline;padding:3px 4px;
  font-size:var(--fs-micro,.62rem);color:var(--ink-faint,rgba(240,240,234,.38))}
#psipanel .psi-eval i{font-style:italic}

/* ── v4 (2026-09-29): THE DOCK, THE BOTTOM SHEETS AND THE CENTRE CARD ARE GONE ────────────────────
   The brief: "it is cramped with the menu at bottom of screen on psi section of vistas … the menu items fall
   around the central display, and when they are clicked on a full screen immersive thing pops up, or
   the opposite side menu is replaced." The chapter tiles now live in the left rail, every sheet is a
   full-screen panel, and a species or reserve card REPLACES the right rail. All of that geometry is in
   css/psi-hud.css (loaded after this file, scoped to #psipanel); the rules that used to sit here for
   .psi-dock / .psi-tab / .psi-more / .psi-sheet geometry / #psidDetail centring were DELETED rather
   than orphaned, so tools/check_css_selectors.mjs has no dead selector to pin. The rail furniture
   below (tiles, roster, chart card, network block) is still the shared v3 layer; psi-hud.css restyles
   it at equal specificity. ⚠ tools/psi-v3-layer.css carries the same deletion; do not re-apply it. */

/* ── a survey-effort section, when a site has one ─────────────────────────────────────────────────
   Bars are effort (camera-days), on one shared scale, because the question a reader actually has is
   "how much of this index rests on which reserve" — and that is a shape, not a column of numbers to
   compare by eye. Rendered inside a dock sheet, so it has the width for a proper measure. */
#psipanel .psi-network h3{margin:0 0 .5em;font-size:var(--fs-md,1rem);font-weight:var(--fw-bold,700);
  letter-spacing:var(--ls-tight,-.01em);color:var(--ink-near,#F0F0EA)}
#psipanel .cn-lede{margin:0 0 1.1em;max-width:var(--measure,68ch);font-size:var(--fs-sm,.82rem);
  line-height:var(--lh-body,1.6);color:var(--ink-soft,rgba(240,240,234,.78))}
#psipanel .cn-lede b{color:var(--ink-near,#F0F0EA);font-variant-numeric:tabular-nums}
#psipanel .cn-list{list-style:none;margin:0 0 1em;padding:0;display:flex;flex-direction:column;gap:3px}
#psipanel .cn-row{display:grid;grid-template-columns:minmax(120px,1.1fr) minmax(90px,3fr) auto auto;
  gap:12px;align-items:center;font-size:var(--fs-xs,.72rem)}
#psipanel .cn-nm{color:var(--ink-near,#F0F0EA);display:flex;gap:.5em;align-items:baseline;min-width:0}
#psipanel .cn-nm i{font-style:normal;color:var(--ink-faint,rgba(240,240,234,.38));font-size:.9em}
/* The bar is a single element with a width variable rather than a nested track: one object, one
   measurement, and nothing to get out of step with the number beside it. */
#psipanel .cn-bar{height:9px;border-radius:var(--r-hair,4px);background:rgba(240,240,234,.07);
  position:relative;overflow:hidden}
#psipanel .cn-bar::after{content:"";position:absolute;inset:0 auto 0 0;width:var(--w,0%);
  background:linear-gradient(90deg,#2D5A27,var(--accent));border-radius:inherit}
#psipanel .cn-cd{font-variant-numeric:tabular-nums;color:var(--ink-soft,rgba(240,240,234,.78));
  text-align:right;min-width:6.5em}
#psipanel .cn-yr{font-variant-numeric:tabular-nums;color:var(--ink-faint,rgba(240,240,234,.38));
  font-size:var(--fs-micro,.62rem);min-width:5.5em;text-align:right}
#psipanel .cn-note,#psipanel .cn-src{margin:.8em 0 0;max-width:var(--measure,68ch);
  font-size:var(--fs-micro,.62rem);line-height:var(--lh-snug,1.35);
  color:var(--ink-faint,rgba(240,240,234,.38))}
#psipanel .cn-src{border-top:1px solid var(--sf-line,rgba(240,240,234,.10));padding-top:.7em}

/* Only the furniture that would sit UNDER a rail goes — and since PSI v3 the overview map key does: .psi-rail-r takes the key's slot (right:20px, top:76px), so
   #wlegend sat covered except for a strip below the rail's bottom edge — measured on this site at
   1440x746 by the 2026-09-29 review, key ending at 654 under a rail ending at 640, a species row
   peeking out. applyLayers() still gives the key .show here (the chapter keeps its faint `habitat`
   wash), so hide it by chapter state, not by changing the wash; the overview's key is untouched. */
body[data-view="psi"] #caption,body[data-view="psi"] #hint,body[data-view="psi"] #wlegend{display:none}
body[data-view="psi"] #datacredit{z-index:26}

@media (prefers-reduced-motion:no-preference){
  body[data-view="psi"] #psipanel[data-staged] .psi-rail > *{
    animation:psi-rise .5s cubic-bezier(.16,.84,.34,1) both;animation-delay:var(--psi-in,0ms)}
  @keyframes psi-rise{from{opacity:0;transform:translateY(10px)}to{opacity:1;transform:none}}
}

/* ── narrow screens: rails cannot be rails, so the chapter becomes one scroll ─────────────────────
   And then the hidden sections come BACK, because the reason they were hidden was a rail budget that
   no longer exists. A section hidden on a phone for a desktop's reason is just missing content. */
@media (max-width:900px){
  #psipanel{overflow-y:auto;pointer-events:auto;
    padding:calc(58px + var(--sat,0px)) 14px calc(var(--navzone,96px) + var(--credit-h,0px) + 16px)}
  #psipanel .psi-rail{
    position:static;width:auto;max-width:none;max-height:none;transform:none;margin-left:0;
    border-radius:0;border-left:0;border-right:0;box-shadow:none;overflow:visible;margin:0 0 14px}
  #psipanel .psi-rail-l .psi-scope,#psipanel .psi-rail-l .psi-caveat,
  #psipanel .psi-rail-l .psi-prov,#psipanel .psi-rail-l .psi-h3,
  #psipanel .psi-rail-l .psi-break{display:block}
  #psipanel .psi-tiles{grid-template-columns:repeat(2,1fr)}
  #psipanel .psi-tile:nth-child(3){border-left:0}
  #psipanel .psi-eval{overflow:visible}
}

/* ══ END PSI CHAPTER v3 ═════════════════════════════════════════════════════════════════════ */

/* ── PSI-DEEP ─────────────────────────────────────────────────────────────────────────────────
   The PSI Vista's data section. Tokens only — no raw font-size, weight, letter-spacing or
   line-height here, same rule as psi.js's header states, because a PSI page is cited from a
   journal and must look like the rest of the fleet rather than like one person's afternoon. */
.psid{max-width:none;color:var(--text);font-family:var(--sans)}
.psid-sec{max-width:72ch;margin:2.4em auto 0;padding-top:1.6em;border-top:1px solid rgba(240,240,234,.10)}
.psid-sec h3{color:var(--text);margin:0 0 .5em}
.psid-lede{color:var(--muted);margin:0 0 1em}

/* ⭐ What a point on this line IS. Above the first chart, at heading weight — never a footnote.
   Five PSI landscapes measure five different things and the charts look identical. */
.psid-badge{max-width:72ch;margin:0 auto 1.4em;padding:.9em 1.1em;
  border-left:3px solid var(--accent);background:rgba(240,240,234,.04);border-radius:0 6px 6px 0}
.psid-badge-h{color:var(--muted);margin:0 0 .35em;text-transform:uppercase;letter-spacing:.11em}
.psid-badge-m{color:var(--text);margin:0}
.psid-badge-s{color:var(--muted);margin:.35em 0 0}
.psid-badge--missing{border-left-color:#E8663D}

.psid-warn{color:#E8A33D;margin:0 0 1em}
.psid-caveat{color:var(--muted);margin:1em 0 0}
.psid-mandate{color:var(--text);margin:0 0 1em}
.psid-global{color:var(--muted);margin:1em 0 0}

.psid-dl{display:grid;grid-template-columns:max-content 1fr;gap:.35em 1.2em;margin:0}
.psid-dl dt{color:var(--muted)}
.psid-dl dd{margin:0;color:var(--text)}

/* Wide tables scroll in their own container — the page body must never scroll sideways. */
.psid-scroll{overflow-x:auto;margin:0 -.4em;padding:0 .4em}
.psid-table{width:100%;border-collapse:collapse;font-variant-numeric:tabular-nums}
.psid-table th{color:var(--muted);text-align:left;padding:.45em .6em;white-space:nowrap;
  border-bottom:1px solid rgba(240,240,234,.16);text-transform:uppercase;letter-spacing:.09em}
.psid-table td{padding:.45em .6em;border-bottom:1px solid rgba(240,240,234,.07);vertical-align:top}
.psid-table .psid-c{text-align:center;white-space:nowrap}
.psid-nm{color:var(--text)}
.psid-nm i{display:block;color:var(--muted)}

/* ⭐ The rejected candidates are dimmed, not hidden. A reader who cannot see what was left out
   cannot see that the index was a judgement. */
.psid-out{opacity:.52}
.psid-out .psid-sel{color:var(--muted)}
.psid-in .psid-sel{color:var(--accent)}

/* ⭐⭐ Leave-one-out — the argument of the page. Bars diverge from a centre so a reader sees
   direction before magnitude. */
.psid-loo{list-style:none;margin:0;padding:0;display:grid;gap:.3em}
.psid-loo li{display:grid;grid-template-columns:minmax(9em,14em) 1fr minmax(7em,auto);
  align-items:center;gap:.7em}
.psid-loo-n{color:var(--text)}
.psid-loo-bar{height:.7em;border-radius:2px;width:var(--w);min-width:2px}
.psid-loo-bar.neg{background:#E8663D}
.psid-loo-bar.pos{background:var(--accent)}
.psid-loo-v{color:var(--muted);text-align:right;font-variant-numeric:tabular-nums}
.psid-loo-v i{color:var(--dim);font-style:normal}

.psid-time{list-style:none;margin:0;padding:0;display:grid;gap:.55em}
.psid-time li{display:grid;grid-template-columns:5.5em 1fr;gap:.9em;align-items:baseline}
.psid-time b{color:var(--accent);font-variant-numeric:tabular-nums}
.psid-time span{color:var(--text)}

.psid-foot{max-width:72ch;margin:2em auto 0}
.psid-csv{color:var(--text);background:transparent;border:1px solid rgba(240,240,234,.24);
  border-radius:4px;padding:.55em 1em;cursor:pointer;font:inherit}
.psid-csv:hover{border-color:var(--accent);color:var(--accent)}
.psid-csv:focus-visible{outline:2px solid var(--accent);outline-offset:2px}

@media (max-width:640px){
  .psid-loo li{grid-template-columns:1fr auto}
  .psid-loo-bar{grid-column:1/-1}
  .psid-dl{grid-template-columns:1fr}
}

/* ── PSI-DEEP drill-down panel ────────────────────────────────────────────────────────────────
   Closed panels carry aria-hidden AND inert, matching conservation-imperatives, so keyboard focus
   cannot wander into a hidden drawer. Visibility is toggled via [hidden], never via display in a
   stylesheet that a later rule could win against. */
.psid-dt{position:fixed;right:0;top:0;bottom:0;width:min(30rem,92vw);z-index:40;
  overflow-y:auto;overscroll-behavior:contain;padding:1.4em 1.5em calc(1.4em + var(--sab,0px));
  background:var(--deep);border-left:1px solid rgba(240,240,234,.14);
  box-shadow:-18px 0 44px rgba(0,0,0,.42);color:var(--text);font-family:var(--sans)}
.psid-dt[hidden]{display:none}
.psid-dt-x{position:absolute;top:.7em;right:.8em;width:2em;height:2em;border-radius:50%;
  background:transparent;border:1px solid rgba(240,240,234,.22);color:var(--text);cursor:pointer;font:inherit}
.psid-dt-x:hover{border-color:var(--accent);color:var(--accent)}
.psid-dt-x:focus-visible{outline:2px solid var(--accent);outline-offset:2px}
.psid-dt-k{color:var(--accent);text-transform:uppercase;letter-spacing:.12em;margin-bottom:.3em}
.psid-dt-n{color:var(--text);margin:0 0 .15em}
.psid-dt-sub{color:var(--muted);margin:0 0 1.1em}
.psid-dt h4{color:var(--text);margin:1.3em 0 .4em}
.psid-dt-meth{color:var(--dim);margin:.4em 0 0}
.psid-dt-range{color:var(--muted);margin:.3em 0 0;font-variant-numeric:tabular-nums}
.psid-dt-range span{color:var(--dim)}
.psid-dt-loo{padding:.8em 1em;border-left:3px solid var(--accent);
  background:rgba(240,240,234,.04);border-radius:0 6px 6px 0}
.psid-dt-loo p{margin:0}
.psid-dt-links{list-style:none;margin:0;padding:0;display:flex;flex-wrap:wrap;gap:.4em}
.psid-dt-empty{color:var(--muted)}

/* Every pickable thing looks pickable. A row that opens a panel must not read as static text. */
.psid-table button,.psid-dt-links button{background:transparent;border:0;padding:0;cursor:pointer;
  color:var(--text);font:inherit;text-align:left;border-bottom:1px dotted rgba(240,240,234,.34)}
.psid-table button:hover,.psid-dt-links button:hover{color:var(--accent);border-bottom-color:var(--accent)}
.psid-table button:focus-visible,.psid-dt-links button:focus-visible{outline:2px solid var(--accent);outline-offset:2px}
.psid-dt-links button{border:1px solid rgba(240,240,234,.22);border-radius:4px;padding:.3em .7em}

@media (prefers-reduced-motion:no-preference){.psid-dt{transition:transform .18s ease}}
.psid-badge-c{color:var(--dim);font-size:.82em;margin:.35em 0 0}

/* The cadence note — why the index has six points and the species have eleven years. It sits
   directly under the chart because that is where the miscount happens. */
.psid-cadence{max-width:72ch;margin:1.1em auto 0;color:var(--dim);
  padding-left:.9em;border-left:2px solid var(--accent)}
.psid-cadence b{color:var(--text)}
/* Germany also carries .psid-dt-late, .psid-years and .psi-sp-pick here. GYE's src/ emits none of
   them, so they are left out rather than shipped as dead selectors (check_css_selectors). */

/* ── THE ROSTER'S ANIMALS ──────────────────────────────────────────────────────────────────────
   .psi-sp-ico and its [data-gap] variant were styled before anything emitted them; src/psi.js now
   does. The plate is tinted to the species' own map colour, so the index roster and the globe name
   the same animal in the same hue — the two surfaces stop being separate vocabularies.
   ⚠ fill:currentColor is what makes the tint work: fetch_silhouettes.py strips fill/stroke off the
   PhyloPic path data on purpose, so the shape inherits. Do not add a fill to the SVG. */
#psipanel .psi-sp-ico svg{fill:currentColor}
#psipanel .psi-sp-ico-rank{opacity:.72}
.psi-sp-ico{width:26px;height:26px;flex:0 0 auto;color:var(--sp,var(--accent))}
.psi-sp-ico svg{width:100%;height:100%;display:block;fill:currentColor}
.psi-sp-ico[data-gap]{display:grid;place-items:center;border:1px dashed rgba(240,240,234,.2);
  border-radius:4px;font-size:.62rem;color:rgba(240,240,234,.38)}
/* The roster's own footnote: what the shapes are, which are group-level, and which rows have none.
   Sized as caption text — it is provenance, not body copy, and must not compete with the list. */
.psi-icon-note,#psipanel .psi-icon-note{margin:.5em 0 0;font-size:var(--fs-micro,.62rem);
  line-height:1.45;color:var(--ink-faint,rgba(240,240,234,.38));max-width:62ch}

/* ── WHAT RECOVERY WOULD TAKE (Appendix D) ─────────────────────────────────────────────────────
   Terai Arc is the only landscape whose record carries this, so these rules are inert everywhere
   else BY DESIGN — recoveryFramework() returns nothing without the data, and an absent section is
   not an empty one. Do not delete them as unused on a site that simply has no Appendix D.
   ⚠ Bars are a fraction of each row's OWN maximum: a 40-point action and a 5-point action must
   never share a scale, which is why no single --max variable appears here. */
.psid-rf-rows,.psid-rf-cats{display:grid;gap:2px;margin:.4em 0 .8em}
.psid-rf-row{display:grid;grid-template-columns:minmax(9em,14em) 1fr 2.6em auto;gap:.6em;
  align-items:center;padding:.22em 0;font-size:var(--fs-xs,.72rem)}
.psid-rf-sp{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.psid-rf-sp em{color:var(--ink-faint,rgba(240,240,234,.38));font-style:normal;margin-left:.45em}
.psid-rf-bar{background:rgba(240,240,234,.09);border-radius:2px;height:9px;overflow:hidden}
.psid-rf-bar i{display:block;height:100%;background:var(--accent,#00BE5A);border-radius:2px}
.psid-rf-row b,.psid-rf-cat b{font-variant-numeric:tabular-nums;text-align:right}
/* the disagreement is a LABEL, never a correction — the printed figure keeps the bold slot */
.psid-rf-off{font-style:normal;font-size:var(--fs-micro,.62rem);color:#e0c341;
  border:1px solid rgba(224,195,65,.4);border-radius:3px;padding:0 .35em;cursor:help;white-space:nowrap}
.psid-rf-cat{display:grid;grid-template-columns:minmax(10em,18em) 3.6em 1fr 2.6em;gap:.6em;
  align-items:center;padding:.22em 0;font-size:var(--fs-xs,.72rem)}
.psid-rf-cn{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.psid-rf-cw{font-variant-numeric:tabular-nums;color:var(--ink-near,#F0F0EA)}
.psid-rf-cw em{font-style:normal;color:var(--ink-faint,rgba(240,240,234,.38));margin-left:.2em;font-size:var(--fs-micro,.62rem)}
.psid-rf-cn2{grid-column:1/-1;font-style:normal;font-size:var(--fs-micro,.62rem);
  color:var(--ink-faint,rgba(240,240,234,.38));margin-top:-.15em}
.psid-rf-bands{display:flex;flex-wrap:wrap;gap:.9em;font-size:var(--fs-micro,.62rem);
  color:var(--ink-faint,rgba(240,240,234,.38));margin:.2em 0 .6em}
.psid-rf-bands i{display:inline-block;width:9px;height:9px;border-radius:2px;margin-right:.35em;vertical-align:-1px}
@media (max-width:640px){
  .psid-rf-row{grid-template-columns:minmax(7em,1fr) 1fr 2.4em;row-gap:.1em}
  .psid-rf-off{grid-column:1/-1;justify-self:start}
  .psid-rf-cat{grid-template-columns:1fr 3.2em 2.4em}
  .psid-rf-cat .psid-rf-bar{grid-column:1/-1}
}

/* A provenance footnote under a block — smaller and quieter than .psid-lede, which introduces a
   section. These carry the sentence that says what was checked and what could not be, so they must
   read as an aside and still be legible: no smaller than the micro step, and never below AA on the
   panel ground. Named in this repo's own psid- namespace rather than borrowing the hub's .cap,
   which is not defined here and rendered as unstyled body text until check_css_selectors said so. */
.psid-cap{font-size:var(--fs-micro,.62rem);line-height:1.5;color:var(--ink-faint,rgba(240,240,234,.42));
  margin:.35em 0 .8em;max-width:68ch}
/* scoping hook for the Appendix D block — present so the section can be targeted as a whole */
.psid-rf{display:block}

/* ── THE PLACE + EVERY INDEX POINT ─────────────────────────────────────────────────────────────
   Both blocks render from fields that were in every record and on no page: landscape.description
   (1,900–3,150 characters per site), the monitoring mandate, and the five per-point provenance
   fields of psi_series. Column count varies BY SITE because the records differ — greater-yellowstone
   has an interval and no source columns; the other six are the reverse. That is the data being
   honest about itself, not a layout bug. */
.psid-quote{margin:.6em 0;padding:0 0 0 1em;border-left:2px solid rgba(240,240,234,.18);
  color:var(--ink-near,#F0F0EA);font-size:var(--fs-s,.8rem);line-height:1.62;max-width:72ch}
.psid-facts{display:flex;flex-wrap:wrap;gap:1.4em;margin:0 0 .9em;padding:0}
.psid-facts div{margin:0}
.psid-facts dd{margin:0;font-size:var(--fs-m,1.05rem);font-weight:var(--fw-bold,700);
  font-variant-numeric:tabular-nums;color:var(--ink-near,#F0F0EA);line-height:1.1}
.psid-facts dt{font-size:var(--fs-micro,.62rem);color:var(--ink-faint,rgba(240,240,234,.42));
  letter-spacing:.04em;margin-top:2px}
.psid-mandate{font-size:var(--fs-xs,.72rem);line-height:1.6;color:var(--muted,rgba(240,240,234,.6));max-width:72ch}
/* a carried-forward count is the one number in the row that changes how the line should be read,
   so it is the one number that is not grey */
.psid-carried{color:#e0c341;font-variant-numeric:tabular-nums}
.psid-srcnote{display:block;font-style:normal;font-size:var(--fs-micro,.62rem);
  color:var(--ink-faint,rgba(240,240,234,.38));line-height:1.4;margin-top:2px;max-width:46ch}
.psid-series .psid-table{width:100%;font-variant-numeric:tabular-nums}
.psid-series .psid-table th[scope="row"]{text-align:left;font-weight:var(--fw-med,600)}

/* ── INSIDE PROTECTION / OUTSIDE, AND HOW THE RECORD WAS MADE ──────────────────────────────────
   Both blocks are data-conditional: protectionSplit renders on the two records that carry a split
   (ndoki-likouala, jurua) and recordNotes on the one that carries notes (greater-yellowstone).
   Inert elsewhere BY DESIGN — do not remove as unused on a site whose record simply lacks them.
   ⚠ The two bars in a row share ONE scale per species so they read against each other. If you ever
   normalise them separately, every species becomes a success story and the block stops meaning
   anything — that is the whole reason the gap is also printed as a number. */
.psid-ps-rows{display:grid;gap:.75em;margin:.5em 0 .9em}
.psid-ps-sp{display:grid;grid-template-columns:minmax(8em,12em) 1fr auto;gap:.8em;align-items:center}
.psid-ps-k{font-size:var(--fs-xs,.72rem);font-weight:var(--fw-med,600);overflow:hidden;
  text-overflow:ellipsis;white-space:nowrap}
.psid-ps-bars{display:grid;gap:3px}
.psid-ps-b{display:grid;grid-template-columns:6.2em 1fr 3.2em;gap:.5em;align-items:center;
  font-size:var(--fs-micro,.62rem)}
.psid-ps-st{color:var(--ink-faint,rgba(240,240,234,.42))}
.psid-ps-b[data-st="Protected"] .psid-rf-bar i{background:var(--alive,#00BE5A)}
.psid-ps-b[data-st="Unprotected"] .psid-rf-bar i{background:#8a8f98}
.psid-ps-b b{text-align:right;font-variant-numeric:tabular-nums}
.psid-ps-gap{display:grid;justify-items:end;font-size:var(--fs-s,.8rem);font-weight:var(--fw-bold,700);
  font-variant-numeric:tabular-nums;color:var(--alive,#00BE5A)}
.psid-ps-gap-neg{color:var(--psi-down,#E8663D)}
.psid-ps-gap em{font-style:normal;font-size:var(--fs-micro,.62rem);font-weight:400;
  color:var(--ink-faint,rgba(240,240,234,.38))}
/* ⭑ A HEAVIER TOP RULE THAN THE ONE BETWEEN FINDINGS, and that is the whole job of this class:
   every other .psid-sec border separates one finding from the next, and this one separates the
   findings from a statement ABOUT them. It is not styling added to quiet check_css_selectors --
   the class was reported unstyled, and the honest answers are to give it work or to delete it. */
.psid-absent{border-top-color:rgba(240,240,234,.20)}
.psid-absentlist{margin:.3em 0 .8em;padding:0;list-style:none;max-width:72ch}
.psid-absentlist li{margin:0 0 .55em;padding-left:.9em;
  border-left:2px solid rgba(240,240,234,.22)}
.psid-absentlist li b{display:block;font-weight:var(--fw-bold,700);
  font-size:var(--fs-xs,.72rem);line-height:1.45;color:var(--ink-near,#F0F0EA)}
.psid-absentlist li span{display:block;font-size:var(--fs-xs,.72rem);line-height:1.55;
  color:var(--muted,rgba(240,240,234,.62))}
.psid-absentlist li span b{display:inline;font-size:inherit;color:var(--ink-near,#F0F0EA)}
/* ⛔⛔ THE FIRST VERSION OF THIS RULE WAS `.psid-absent .psid-meth`, AND THAT WOULD HAVE BEEN WORSE
   THAN LEAVING IT UNSTYLED. `psid-meth` is in KIT/css-selector-baseline.json's `unstyled` list, and
   scoping a rule to one of its three uses flips it to "styled" for check_css_selectors while the
   other two — leaveOneOut's WITHHELD explanation and protectionSplit's row count — keep rendering
   as default full-size body text. The gate would have gone quiet on a defect that was still there,
   and an agreeing instrument is worse than a red one because it retires the question.
   ⭐ So it is styled where it is declared, for every use at once, and the baseline line comes out.
   These are method and provenance sentences: the muted small idiom the rest of the chapter uses for
   exactly that, not a third size invented here. */
.psid-meth{font-size:var(--fs-xs,.72rem);line-height:1.55;margin:.2em 0 .6em;max-width:72ch;
  color:var(--muted,rgba(240,240,234,.62))}
.psid-meth b{color:var(--ink-near,#F0F0EA)}
.psid-notelist{margin:.3em 0 .8em;padding-left:1.1em;max-width:72ch}
.psid-notelist li{font-size:var(--fs-xs,.72rem);line-height:1.55;margin:0 0 .5em;
  color:var(--muted,rgba(240,240,234,.62))}
@media (max-width:640px){
  .psid-ps-sp{grid-template-columns:1fr auto;row-gap:.3em}
  .psid-ps-bars{grid-column:1/-1}
}

/* section scoping hooks — each new block gets a named container so the stage can target it as a
   whole and so check_css_selectors sees a rule rather than a class nothing styles. Kept together
   and kept minimal: layout lives in the -* rules above, not here. */
.psid-place,.psid-series,.psid-ps,.psid-notes,.psid-rf{display:block}
.psid-place>h4,.psid-notes>h4{margin-top:1em}

/* the data the record carried and the page was dropping — provenance of the transcription, the
   cite under each policy entry, the UNIT beside every count, and the basis of a leave-one-out row */
.psid-badge-p{margin:.35em 0 0;font-size:.82em;opacity:.78;line-height:1.45}
.psid-badge-p code{font-family:var(--mono,ui-monospace,monospace);font-size:.94em;opacity:.9}
.psid-badge-p b{font-weight:600;opacity:1}
.psid-cite{display:inline-block;margin-left:.5em;font-size:.82em;font-style:normal;opacity:.62;white-space:nowrap}
.psid-u{font-size:.84em;opacity:.8;max-width:22em}
.psid-u b{font-weight:600;opacity:.95}
.psid-loo-b{display:block;font-size:.78em;font-style:normal;opacity:.66;line-height:1.35;margin-top:.1em}
.psid-guild{opacity:.85}
.psid-dt-tr{margin-top:1.1em}
.psid-dt-tr h4{margin:0 0 .4em}

/* the landscape-level protection row, and the intervals the record states */
.psid-ps-head{margin:0 0 .9em;padding:0 0 .9em;border-bottom:1px solid var(--rule,rgba(240,240,234,.12))}
.psid-ps-head .psid-ps-k{font-weight:600}
.psid-ps-ci{display:block;font-style:normal;font-size:.72em;opacity:.6;line-height:1.3;white-space:nowrap}
.psid-ps h4{margin:1.2em 0 .4em}
.psid-ps-ovl{border-left:2px solid var(--accent,#00BE5A);padding-left:.7em}

/* ⛔ `guild` CARRIES TWO DIFFERENT MEANINGS ACROSS THESE RECORDS and only one of them means
   "not a species". On ndoki-likouala the two guild rows (Ungulate, Great Ape) have NO binomial:
   the index genuinely counts a group as one line. On greater-yellowstone all EIGHT index species
   carry a guild too — "large carnivore", "ecosystem engineer" — and every one of them is a real
   binomial. Reading `guild` as the aggregate flag would have described Yellowstone's eight species
   as eight guilds. The discriminator is `binomial`, which is why the line above branches on it. */
/* ⛔ THE FIRST VERSION OF THIS RULE READ `var(--psi-warn, #c8a24a)` AND THAT PROPERTY DOES NOT
   EXIST. An undefined custom property does not warn, it silently takes the fallback — so the block
   rendered in a colour nobody chose, off the warn colour this chapter already uses. That colour is
   #E8A33D (.psid-warn) and the caveat idiom is a left rule with .9em of padding (.psid-withheld,
   .psid-cadence). Both are reused verbatim rather than approximated: a second amber is how one
   palette becomes two. */
.psid-noprov{max-width:72ch;margin:.2em 0 1em;color:var(--dim);padding-left:.9em;
  border-left:2px solid #E8A33D}
.psid-noprov b{color:#E8A33D;display:block;margin-bottom:.25em}

/* ⛔ TWO STATS WRAPPED TO TWO ROWS (gye lap 2, 2026-09-30). Geothermal carries two figures, and the flex row
   above let the long label "geysers · over half the world's" push "10,000+" onto a second, indented row at
   1280x720 and 390x844 (the gate's one-row test fails there too). Exactly two stats become a 2-column grid,
   the same answer the three-stat rule gives: one top edge, a long label wraps inside its own column. */
#cap-stats:has(> div:nth-child(2)):not(:has(> div:nth-child(3))){display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:0;flex-wrap:nowrap}
#cap-stats:has(> div:nth-child(2)):not(:has(> div:nth-child(3))) > div{padding:0 12px;border-left:1px solid rgba(240,240,234,.1);min-width:0}
#cap-stats:has(> div:nth-child(2)):not(:has(> div:nth-child(3))) > div:first-child{padding-left:0;border-left:0}
#cap-stats:has(> div:nth-child(2)):not(:has(> div:nth-child(3))) span{line-height:1.3}
/* THE RECORD AND THE TWO READING SHEETS RISE WITH THE PILLS where they are bottom sheets (<=820px: top:auto).
   At 390x844 the record ran y 234-774 while the pills sat at 710-754 and the licence credit at 768-832, both
   painting over its last lines (gye lap 2, 2026-09-30; the rule germany 4d7cb08 and madre 6b95d34 carry).
   .look shares #readout's box here, so the Connectivity and Wolves sheets take the same bottom. */
@media (max-width:820px){
  #readout,.look{ bottom:calc(36px + var(--credit-h, 0px) + var(--navh, 44px));
    max-height:calc(100svh - 96px - var(--credit-h, 0px) - var(--navh, 44px)); }
}
/* ⛔ THE LANDSCAPE-TABLET SHEET (1024x900) LOST ITS PROSE COLUMN (gye lap 2, 2026-09-30). The two-column sheet above
   sizes the stats column `auto`, and the one-row stat grids (three stats, and two since this lap) are max-content
   wide, so the stats took ~600 of the 920px sheet: every lede ran in a ~290px column beside an empty block, and on
   Connectivity the prose column collapsed, the title ran over "10–140 km", the lede vanished and the four pills
   stacked (cap-connectivity-after-1024x900.png). Cap the stats column; a long label wraps inside its own cell. */
@media(max-width:1024px) and (min-width:700px) and (orientation:landscape){
  #caption{grid-template-columns:minmax(0,1fr) minmax(0,42%)}
}

/* ── THE CHAPTER BAR: EVERY CHIP VISIBLE, AT EVERY WIDTH (ported 2026-09-30 from jurua-landseed
   a4da677 + 77a6236) ──────────────────────────────────────────────────────────────────────────────
   CHAPTER BAR RULE: adding chapters must never push a chip, above all "Priority Species Index", off
   screen or behind a sideways scroll. So the bar WRAPS onto as many centred rows as it needs, at
   every width, including a phone. main.js (sizeChapterBar) publishes the bar's MEASURED height as
   --chap-h and the distance from the viewport bottom to its top edge as --chap-top, and the caption
   is placed from THAT, so a second or third row lifts the caption instead of sliding under it.
   On the <=1024 / touch tier, padding-right reserves the KEY pill (right:10px, ~66px wide) plus a
   10px gap. A 62px gutter suited a scroller that faded under KEY; a wrapped row ends hard, and at
   1024x900 the last chip touched KEY. */
/* ONE ROW WHEREVER IT FITS (greater-yellowstone only, 2026-09-30). left:50% makes the bar's
   shrink-to-fit width the viewport's RIGHT HALF, so six chips (~796px) wrapped at 1440 in a 720px
   box. width:max-content asks for the full row; max-width is where wrapping takes over. The cap
   keeps the centred bar out of the right rail's column (right:24 + 268 wide + 12 clear = 304px, so
   608px for both sides): uncapped, the one-row bar at 1280x720 ran under the rail's live-fire key.
   Never narrower than the old 50vw, so no width wraps more than it did. KEY is display:none on this
   tier. The PSI view keeps its own, more specific, column-gap cap in css/psi-hud.css.
   The caption clears the data credit as well as the bar: a one-row bar is shorter than the credit
   beside it at 1440, and the caption's bottom edge then sat on the credit's first two lines. */
@media(min-width:1025px) and (hover:hover){
  #chapters{flex-wrap:wrap;justify-content:center;border-radius:24px;overflow:visible;
    width:max-content;max-width:max(50vw, calc(100vw - 608px))}
  :root{--cap-bottom:calc(max(var(--chap-top, 73px), var(--credit-h, 0px) + 12px) + 14px)}
  html #caption,html[data-creditstrip] #caption{bottom:var(--cap-bottom)}
  html body.focus #caption,html[data-creditstrip] body.focus #caption{bottom:var(--cap-bottom)}
}
@media(max-width:1024px),(hover:none){
  :root{--navzone:calc(var(--sab) + var(--chap-h, var(--navh)) + var(--navgap)*2)}
  #chapters{flex-wrap:wrap;justify-content:center;row-gap:6px;overflow:visible;max-width:none;
    padding:0 86px 0 12px;-webkit-mask-image:none;mask-image:none}
  #chapters .chip{min-height:38px;padding:0 13px}
  html #caption,html[data-creditstrip] #caption{bottom:calc(var(--chap-top, 120px) + 10px)}
}

/* A PHONE (<=520px): the caption's height is budgeted from the space ABOVE the measured bar, not from
   --navzone. --navzone now carries the bar's measured height (--chap-h), so the old
   calc(46svh - var(--navzone)) shrank the caption by every extra row. With eleven chips on six rows at
   390x844 it left ~80px, and the title sat under its own buttons (terai-arc mini pass on 1a3153d). The budget is
   the one-row budget (46svh less one row) or the room between the bar and the top furniture
   (logo, globe, map inset: 128px), whichever is smaller. Tighter chips take fewer rows. */
@media(max-width:520px){
  #chapters{column-gap:6px;row-gap:5px}
  #chapters .chip{min-height:34px;padding:0 11px}
  #caption{max-height:min(calc(46svh - var(--sab) - var(--navh) - var(--navgap)*2),
    calc(100svh - var(--chap-top, 120px) - 10px - 128px))}
}

/* ── GYE ONLY (2026-09-30): THE PSI VIEW'S BAR ON ONE ROW TOO ─────────────────────────────────────
   Alex: one row, as on Terai. The chips carry short names (Species Index, Wolves); in the PSI view
   psi-hud caps the bar to the gap between its columns (558px at 1280), so the chips there take a
   tighter inset and gap. */
@media(min-width:1025px) and (hover:hover){
  body[data-view="psi"] #chapters{column-gap:2px;padding:4px}
  body[data-view="psi"] #chapters .chip{padding-left:10px;padding-right:10px}
}
