@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 */
/* The orbit hint sits in the band between the caption card (bottom:92px) and the chapter bar
   (bottom:26px), both bottom-anchored, so its clearance does not depend on viewport height. It
   used to be centred on the viewport, where at 1366x850 the caption card (z 25) covered its first
   glyph — every visitor read "RAG TO ORBIT" — and map labels ran through it. Measured on chitwan,
   yellowstone and palau on hardware GL, 2026-09-03; the band below was clean on all three.
   nowrap: a second line would climb into the caption. Small screens override this (top band). */
#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.rm i{width:15px;height:0;border-radius:0;border-top:1px dashed #7fd4e8;box-shadow:none;color:#7fd4e8}
#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{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{opacity:1;transform:translateY(-50%) translateX(0);pointer-events:auto}
#readout-close{position:absolute;top:14px;right:16px;background:none;border:0;color:var(--muted);font-size:16px;cursor:pointer;transition:.2s}
#readout-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)}

/* caption action links */
#cap-links{display:flex;flex-wrap:wrap;gap:9px;margin-top:16px}
/* The chapter RECORD — a per-row table (Conservation History), read one row at a time by a new
   reserve manager. Lives inside #caption, so the caption's own max-height + overflow scroll it. */
#cap-rec:empty{display:none}
#cap-rec{margin-top:12px}
#cap-rec .cr-h{font-size:9px;letter-spacing:.12em;text-transform:uppercase;color:var(--muted);margin-bottom:6px}
#cap-rec table{width:100%;border-collapse:collapse;font-size:11.5px;line-height:1.4;color:var(--dim)}
#cap-rec th,#cap-rec td{text-align:left;vertical-align:top;padding:5px 8px 5px 0;border-top:1px solid rgba(240,240,234,.1)}
#cap-rec thead th{font-size:9px;font-weight:600;letter-spacing:.1em;text-transform:uppercase;color:var(--muted);border-top:0;padding-top:0}
#cap-rec tbody th{font-weight:600;color:#eef0ea;white-space:nowrap;font-variant-numeric:tabular-nums}
#cap-rec td:last-child{color:var(--muted);font-size:10.5px}
#cap-rec .cr-none{margin-top:9px;font-size:11.5px;line-height:1.5;color:var(--dim);border-left:2px solid rgba(232,163,61,.6);padding-left:9px}
#cap-rec .cr-src{margin-top:8px;font-size:10px;line-height:1.45;color:var(--muted)}
#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 */
/* ⛔⛔ THE BAR MUST NOT REACH #datacredit, AND THE CREDIT CANNOT MOVE. 2026-09-09.
   A centred bar grows in BOTH directions, so CHAPTER COUNT alone decides whether its left edge walks
   into #datacredit (position:fixed, left:28, 320px wide, right edge 348). Measured on
   clemson-forest-landseed: 9 chapters, bar 833px, at 1440x900 it started at x=303 and put the green
   "Overview" pill on top of the OpenStreetMap ODbL attribution. kanha-pench (6 chapters, 509px) has
   a 118px gap. Both credits are identical — it is the bar, not the credit.
   ⭑ Narrowing the credit was measured and cannot work: it grows upward from bottom:12 and #caption
   ends 16px above it, so max-width 250 pushes it INTO the caption. overflow-x:auto was also rejected
   — it left the last chapter off-screen behind a hidden scrollbar, trading an occluded credit for an
   unreachable control. Reserve the gutter and WRAP: verified 1281-1920, gap 32-195px, every chip
   inside the bar and none covered. Bars that already fit are untouched. */
#chapters{position:fixed;left:50%;bottom:26px;transform:translateX(-50%);z-index:25;display:flex;flex-wrap:wrap;justify-content:center;max-width:calc(100vw - 760px);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}
  body[data-view=pressure] #locator{display:none}   /* the wide loss-timeline owns the top on tablet too */
  #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{left:14px;right:14px;top:auto;bottom:78px;width:auto;max-width:none;transform:translateY(24px);max-height:60svh}
  #readout.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}
  #lossyear{width:86vw;top:52px}
  /* Forest-Loss view on a phone: the timeline is the hero — stack the live key beneath it and
     drop the globe + map-inset so the top isn't a four-card pile-up. */
  body[data-view=pressure] #llegend{top:150px;max-height:46svh}
  body[data-view=pressure] #locator,body[data-view=pressure] #mapinset{display:none}
  #readout{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. The two stops are the
   shader's own ember ramp (world.js, uPeople: lo 0.42/0.13/0.07, hi 1.00/0.72/0.24). Restored
   2026-09-30 with public/terrain/jurua/population.png, which lap 2 found had never been baked. */
#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. */
  /* ⛔ THE STRIP WRAPPED. This tier says "horizontally scrollable" and sets overflow-x:auto and
     nowrap on the chips, but never overrode the base rule's flex-wrap:wrap — so five chapter
     names on a 390px phone wrapped into THREE rows (measured 2026-09-29: dock 367×148 at y 504),
     and the rows sat over the caption's "Closer look" and "Park record" buttons (review finding
     3) and over the PSI chapter's lower rail rows. One row that scrolls is what every rule below
     — the mask, the smooth scroll, --navzone — already assumes. Candidate to land upstream:
     the template and the six sibling PSI sites carry the same wrap. */
  #chapters{
    left:0;right:0;bottom:calc(var(--sab) + var(--navgap));transform:none;
    flex-wrap:nowrap;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" "rec 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-rec{grid-area:rec}
  #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{
  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{
    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,#keytoggle{
  min-width:26px;min-height:26px;display:inline-flex;align-items:center;justify-content:center}

/* DATA CREDIT. The main view drapes Copernicus Sentinel-2 imagery across the whole screen and
   draws OpenStreetMap geometry over it. (It WAS Esri World Imagery, removed in 2026-08.)
   The element is source-agnostic — only the text in
   index.html names a source, which is why this comment went stale without anything failing.
   Both licences require attribution, and until this line existed 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, AND AGAINST THE IMAGERY RATHER THAN AGAINST A BRAND COLOUR. This sat at
     rgba(240,240,234,.46) with a further `opacity:.72` in the 1024px query, and the two
     COMPOUNDED to an effective .331 — measured 2.08:1 to 4.22:1 against AA's 4.5:1 for
     text this small (9px is normal text; the 3:1 large-text allowance does not apply).
     ⭐ No alpha can fix it. This label sits over the 3-D SCENE, so the ground is arbitrary
     satellite imagery and a translucent ink is unbounded by construction — .46 alone still
     measures 4.13:1 on the dark plate and 2.81:1 on the mid green.
     ⭑ So the ink is now opaque and the CONTRAST IS CARRIED BY A PLATE, which makes the worst
     case computable: at .72 the plate over PURE WHITE imagery resolves to about rgb(81,94,81)
     and #F0F0EA on it is ~5.9:1 — AA with margin, and 15.06:1 over dark. The floor is .70
     (5.60:1); below that the guarantee goes.
     ⚠ The .72 is not a new number — it is the SAME .72, moved off the text and onto the plate,
     which is where it always belonged. And the `padding:4px 6px` already in the 1024px query
     was padding for a plate nobody had added yet. */
  /* 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; it is now the plate's */
  }
  /* 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 on this engine at 1024, 834, 800, 744, 700 and 393 — six of seven viewports, every
     width below 1440, which is the one everybody develops at. Confirmed independently by a
     static read of z-indexes across the lineage.
     It is OCCLUSION, not overflow: scrollWidth equals clientWidth at every width, so an overflow
     check reports clean. Two correct rectangles, one painted over the other.
     AND MOVING THE CREDIT DOES NOT WORK — tried on the marine side: bottom:64px cleared the nav
     and put it under the caption card instead. There is no gap to move into.
     THE STRIP HEIGHT CANNOT BE A CONSTANT: the notice wraps to different heights by 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. Ported from
     MPA-vista-template, where it was verified across three live sites. */
  #chapters, #keytoggle{ bottom:calc(26px + var(--credit-h, 0px)); }
  #caption{ bottom:calc(92px + var(--credit-h, 0px)); }
  body.focus #caption{ bottom:92px; }
}
/* THE RECORD RISES WITH THEM, where it is a bottom sheet (<=820px: top:auto). It sat at bottom:70px while the
   pills rose above the credit, so on a phone the pills (z 27) painted over its last lines (ported from germany
   4d7cb08 via terai-arc d4dfbbc; jurua lap 2, 2026-09-30: record 258-798 under pills at 608). Wider, the record
   is a side panel and is left alone. */
@media (max-width:820px){
  #readout{ bottom:calc(36px + var(--credit-h, 0px) + var(--navh, 44px));
    max-height:calc(100svh - 96px - var(--credit-h, 0px) - var(--navh, 44px)); }
}

/* 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. The paper's lead 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{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{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}
}

/* ── 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)}
/* The hub link had no rule: a6's canonical branch emits <a class="psi-hub"> at the end of the
   provenance paragraph and check_css_selectors caught it on merge. Unstyled it renders as
   default-blue underlined body text inside a muted provenance block. Same rule as the other six
   PSI sites: a quiet outbound step at the end of a citation, not a call to action. */
.psi-hub{display:inline-block;margin-top:.5em;color:var(--accent,#00BE5A);text-decoration:none;
  border-bottom:1px solid rgba(0,190,90,.35);padding-bottom:1px}
.psi-hub:hover{border-bottom-color:var(--accent,#00BE5A)}
.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}
/* 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}




/* ══ 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 ENTIRELY, and the gate's px threshold
   called that a "squeeze" when it was a hide. Measured on ndoki at 1440x746 before this rule: rail
   359px, furniture (railh 12 + tiles 55 + panel 168 + more 27) plus four ~10px gaps left the
   scrolling list 30px — enough for the word "Falling" and nothing else. The data row and the caveat
   "different windows, so not a ranking" were BOTH below the fold. A caveat a reader cannot see is a
   caveat that is not there.
   ⭐ 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, which carries a disclosure ("N are drawn from a wider group than the
   species on the row", "N species have no drawing … a plate of a different animal would be worse
   than a blank"), and NOT from the credit strip, which carries a licence obligation. Hiding either
   to win pixels would delete an honesty note to make a layout fit.
   ⚠ ORDER MATTERS: at equal specificity the later rule wins, so this sits AFTER the max-height:820
   block. Placed before it, that block's max-height:92px kept winning for every viewport under 780
   too — the rule was present, matched, and did nothing. */
@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}
}

/* ⛔ AND AGAIN BELOW ~730px, which is the common laptop height. The 780 tier fixed 1440x746 and left
   1280x720 hiding the same two things. Same budget, same exclusions. */
@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}
}
#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. The map key stays: in this chapter it is the
   bridge between the index and the ground the index is about. */
/* ⛔ AND SINCE v3 THE OVERVIEW MAP KEY GOES TOO. .psi-rail-r takes #wlegend's slot, so the key
   sat COVERED except for a strip below the rail's bottom edge — measured here at 1440x746 as
   key 276..654 against a rail ending at 640, so 14px of species rows showed CUT MID-WORD.
   Nothing overflows and the rail is intact, so every layout gate is silent on it. Hidden by
   CHAPTER STATE, not by changing the habitat wash applyLayers() legitimately sets. */
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-withheld{color:var(--muted);margin:0 0 .6em;padding-left:.9em;border-left:2px solid rgba(240,240,234,.22)}

/* ── 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}
/* JURUA: at 1280 the five chips (596px at 8px 12px; 640px as shipped) did not fit the 520px the bar is allowed
   (100vw - 760px), so "Priority Species Index" wrapped to a second row and the 88px bar climbed 22px into the
   caption's bottom-right corner (bar 380..900 x 530..618, caption ..412 x ..552; lap 2, 2026-09-30, measured and
   seen). Terai's tighter chips (d4dfbbc) alone leave 596 > 520, so outside the PSI view the bar may also take
   100vw - 680px: on ONE row it sits 22px below the caption by construction, and at 1280 its right edge (940)
   stays clear of the key column (988). The PSI view keeps its own max-width (psi-hud.css). Below ~1276 it wraps. */
@media (min-width:1025px) and (max-width:1340px) and (hover:hover){#chapters{gap:4px}#chapters .chip{padding:8px 12px}
  body:not([data-view="psi"]) #chapters{max-width:calc(100vw - 680px)}}

/* SM Table 7 in the Communities chapter: ten narrow columns in a 384px card. */
#cap-rec .cr + .cr{margin-top:14px}
#cap-rec .cr-t7 table{font-size:10.5px}
#cap-rec .cr-t7 th,#cap-rec .cr-t7 td{padding:3px 5px 3px 0}
#cap-rec .cr-t7 td:nth-child(n+4){text-align:right;font-variant-numeric:tabular-nums;color:#eef0ea;font-size:10.5px}
#cap-rec .cr-t7 tbody th{white-space:nowrap;font-size:10px}
#cap-rec .cr-t7 td:nth-child(2),#cap-rec .cr-t7 td:nth-child(3){font-size:9.5px}
#cap-rec .cr-t7 table{table-layout:auto}

/* ── the place-chapters branch (2026-09-30): eight chapters, EVERY ONE VISIBLE ─────────────────
   Eight chips need ~1,040px at 1440 against a bar capped at 680px (the caption card and the right
   rail own the corners). A sideways-scrolling row hid "Global value" and "Priority Species Index"
   off the end of the bar with no visible cue — and the PSI chapter is the one the paper links to.
   So the bar WRAPS onto as many centred rows as it needs, at every width (greater-yellowstone's
   rule, extended past the PSI view). main.js publishes where the bar's top edge actually is as
   --chap-top, and the caption is placed and capped from THAT: a second row lifts the caption
   instead of sliding under it, and the caption's own scroll area can no longer run up under the
   header (it used --cap-bottom:92px while sitting 92px + the credit strip high, so its top rose
   above the logo). */
@media(min-width:1025px) and (hover:hover){
  #chapters{flex-wrap:wrap;justify-content:center;border-radius:24px;overflow:visible}
  :root{--cap-bottom:calc(var(--chap-top, 73px) + 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)}
}
/* Touch and narrow screens: the same wrap in place of the one-row scroller. --navzone (the height
   everything above the nav is positioned from) takes the MEASURED bar height, so a three- or
   four-row bar pushes the caption sheet up rather than covering it. Chips are a little shorter
   so eight names cost fewer rows. */
@media(max-width:1024px),(hover:none){
  :root{--navzone:calc(var(--sab) + var(--chap-h, var(--navh)) + var(--navgap)*2)}
  /* max-width:none — the base rule's calc(100vw - 760px) is 264px at 1024, which the old scroller
     hid and a wrap turns into an eight-row column (measured 2026-09-30, 1024x900: 346px tall). */
  /* padding-right reserves the KEY pill (right:10px, ~66px wide at 11px/.12em, 44px tall) plus a
     10px gap. The narrow tier's 62px gutter was sized for a scroller that faded under KEY; a wrapped
     row ends hard, and at 1024x900 the last chip ended at x~953 with KEY starting at x~948. */
  #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 record whose first column is a sentence (Connectivity, Global value) wraps it; the year and
   short-label records keep theirs on one line. */
#cap-rec .cr-wide tbody th{white-space:normal;font-weight:500;min-width:48%}

/* 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))}
}

/* ── JURUA (2026-09-30, ported from terai-arc f030935 "TERAI ONLY"): ONE ROW ON A DESKTOP, AND THE
   NOTES SHEET STOPS ABOVE THE BAR ─────────────────────────────────────────────────────────────────
   The chips carry short names (each chapter's caption keeps its full title), so the bar is one row
   from 1200px up; the caption already sits on --chap-top, so a one-row bar gives it back the height.
   Below 1200 the bar still wraps (the chapter-bar rule: no chip off-screen, no sideways scroll).
   #notes, where this site has one, is top-anchored and its height budgeted from the measured bar. */
@media(min-width:1200px) and (hover:hover){
  /* html body:not(psi): out-ranks every earlier desktop rule on this site, including a left-aligned
     body:not([data-view="psi"]) #chapters bar where one exists; the bar is centred, as on Terai. */
  html body:not([data-view="psi"]) #chapters{flex-wrap:nowrap;white-space:nowrap;left:50%;right:auto;
    transform:translateX(-50%);max-width:calc(100vw - 40px)}
  /* not in the PSI view: psi-hud caps the bar to its column gap (680px at 1440), and the chips
     unwrapped ran over the species column there (Terai 5b54f52). There it wraps again. */
  body[data-view="psi"] #chapters{flex-wrap:wrap}
  #chapters .chip{padding:8px 12px}
  /* the right-rail panels stop above the one-row bar, which now spans under the rail (probe
     2026-09-30: at 1200-1280 the last chips ran under the map key's bottom edge) */
  #wlegend,#hlegend,#plegend,#glegend,#ilegend,#llegend{max-height:min(calc(var(--appvh) - var(--rail-top) - var(--rail-reserve)),
    calc(var(--appvh) - var(--rail-top) - var(--chap-top, 80px) - 12px))}
}
