: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 */
#hint{position:fixed;top:50%;left:50%;transform:translate(-50%,-50%);z-index:20;font-size:12px;letter-spacing:.14em;text-transform:uppercase;color:rgba(255,255,255,.7);text-shadow:0 1px 10px rgba(0,0,0,.9);transition:opacity .8s;pointer-events:none}
#hint.gone{opacity:0}

/* feature labels — each PINNED to its place; the text sits just above its marker.
   Types carry meaning: geography (white), wildlife (species colour), ranger (amber). */
/* ── map labels: traditional cartographic hierarchy, high-tech undertones ──
   Natural features set in Fraunces italic (hydrography/orography convention);
   data values (elevations, populations) in monospace. JS centre-anchors every
   label, so .mlabel is translate(-50%,-50%); placement/declutter live in main.js. */
#labels{position:fixed;inset:0;z-index:8;pointer-events:none}
#leaders{position:fixed;inset:0;z-index:7;pointer-events:none;width:100%;height:100%}
#leaders line{stroke:rgba(232,238,235,.42);stroke-width:1;transition:opacity .25s}
/* species plates — two-tone natural-history icons (base hue + engraved shading + highlights) */
#icons{position:fixed;inset:0;z-index:9;pointer-events:none}
.spicon{position:absolute;width:24px;height:24px;transform:translate(-50%,-50%);opacity:0;cursor:pointer;
  transition:opacity .4s,transform .15s;will-change:left,top,opacity}
.spicon svg{width:100%;height:100%;display:block;
  filter:drop-shadow(0 0 1.4px rgba(6,11,7,.95)) drop-shadow(0 1px 2px rgba(0,0,0,.85)) drop-shadow(0 0 6px rgba(6,11,7,.6))}
.spicon:hover{transform:translate(-50%,-50%) scale(1.22)}
.wl-ic{flex:none;width:23px;height:21px;display:inline-flex;align-items:center;justify-content:center}
.wl-ic svg{width:20px;height:20px;display:block;filter:drop-shadow(0 0 2px color-mix(in srgb,var(--fa,#fff) 45%,transparent))}
/* realistic CC0 silhouettes — tinted to the species hue wherever an iconSVG renders */
.icv path{fill:var(--fa)}
.ldot{position:absolute;width:5px;height:5px;border-radius:50%;background:#fff;box-shadow:0 0 0 1.5px rgba(7,12,8,.6),0 0 6px rgba(0,0,0,.7);transform:translate(-50%,-50%);transition:opacity .3s;pointer-events:none}
.mlabel{position:absolute;transform:translate(-50%,-50%);display:flex;flex-direction:column;align-items:center;gap:0;text-align:center;white-space:nowrap;line-height:1.06;transition:opacity .25s;pointer-events:none}
.mlabel .mn{font-weight:600;font-size:12.5px;letter-spacing:0;color:#fff;text-shadow:0 1px 2px #000,0 0 9px rgba(0,0,0,.95)}
.mlabel .ms{font-family:ui-monospace,'SF Mono',Menlo,monospace;font-weight:500;font-size:9px;letter-spacing:.02em;color:var(--dim);text-shadow:0 1px 3px #000}
/* landforms + hydrography — serif italic */
.mlabel.m-place .mn{font-family:'Fraunces',serif;font-style:italic;font-weight:500;font-size:13px;color:#d4ead9;text-shadow:0 1px 3px #000,0 0 10px rgba(0,0,0,.9)}
.d-place{width:4px;height:4px;background:rgba(220,238,224,.8)}
.mlabel.m-water .mn{font-family:'Fraunces',serif;font-style:italic;font-weight:500;font-size:14px;letter-spacing:.01em;color:#cfe3ef;text-shadow:0 1px 3px #000,0 0 10px rgba(8,20,30,.9)}
.d-water{display:none}
.mlabel.m-river .mn{font-family:'Fraunces',serif;font-style:italic;font-weight:400;font-size:11px;color:#a4cae0}
.d-river{display:none}
/* settlements — small dot, roman */
.mlabel.m-city .mn{font-size:11.5px;font-weight:500;color:#e9eef2;letter-spacing:.005em}
.d-city{width:4px;height:4px;border-radius:50%;background:#eef2f5;box-shadow:0 0 0 1.5px rgba(7,12,8,.6)}
/* peaks — summit triangle + elevation in mono */
.mlabel.m-peak .mn{font-size:12px;font-weight:600}
.mlabel.m-peak .ms{color:var(--muted);font-variant-numeric:tabular-nums}
.d-peak{width:0;height:0;background:none;border-radius:0;border-left:4px solid transparent;border-right:4px solid transparent;border-bottom:7px solid rgba(255,255,255,.92);box-shadow:none;filter:drop-shadow(0 1px 2px rgba(0,0,0,.9))}
/* wildlife — small species diamond + population (quiet, no pill) */
.d-fauna{width:7px;height:7px;border-radius:2px;transform:translate(-50%,-50%) rotate(45deg)}
.mlabel.m-fauna .mn{font-size:11.5px;font-weight:600}
.mlabel.m-fauna .ms{font-size:9px;font-weight:500;color:var(--fa,#9fb8a4)}
/* ranger network — amber square (the data layer) + caps role */
.d-station{width:6px;height:6px;border-radius:1px;background:#FFC24B;box-shadow:0 0 0 1.5px rgba(7,12,8,.7),0 0 7px rgba(255,194,75,.6)}
.mlabel.m-station .mn{font-size:11.5px;font-weight:600;color:#F4E9D2}
.mlabel.m-station .ms{color:#FFC24B;letter-spacing:.07em;text-transform:uppercase;font-size:8px}
/* administrative areas — broad faint tracked caps, no marker */
.d-country,.d-region{display:none}
.mlabel.m-country .mn{font-size:12.5px;font-weight:600;letter-spacing:.34em;text-transform:uppercase;color:rgba(240,240,234,.30);text-shadow:0 1px 6px rgba(0,0,0,.8)}
.mlabel.m-region .mn{font-size:9.5px;font-weight:600;letter-spacing:.3em;text-transform:uppercase;color:rgba(206,226,212,.28);text-shadow:0 1px 6px rgba(0,0,0,.8)}
/* neighbouring rift parks — faint mint serif italic (the connected chain) */
.mlabel.m-park .mn{font-family:'Fraunces',serif;font-style:italic;font-weight:500;font-size:11px;letter-spacing:.01em;color:rgba(150,235,212,.85);text-shadow:0 1px 5px rgba(0,0,0,.95)}
.d-park{display:none}

/* legend (protected-area key) */

/* forest-loss timeline readout */
#lossyear{position:fixed;top:62px;left:50%;transform:translateX(-50%) translateY(-8px);z-index:26;width:min(340px,46vw);padding:12px 16px 11px;border-radius:12px;background:linear-gradient(180deg,rgba(7,15,7,.42),rgba(7,15,7,.58));backdrop-filter:blur(10px);-webkit-backdrop-filter:blur(10px);border:1px solid rgba(240,240,234,.12);opacity:0;pointer-events:none;transition:opacity .5s ease,transform .5s ease}
#lossyear.on{opacity:1;transform:translateX(-50%) translateY(0)}
#lossyear .ly-head{display:flex;justify-content:space-between;align-items:baseline;margin-bottom:8px}
#lossyear .ly-k{font-size:10px;letter-spacing:.14em;text-transform:uppercase;color:var(--muted)}
#ly-num{font-size:22px;font-weight:800;letter-spacing:-.02em;color:#ff6a4d;font-variant-numeric:tabular-nums}
#ly-bar{height:3px;border-radius:3px;background:rgba(240,240,234,.14);overflow:hidden}
#ly-fill{height:100%;width:0;border-radius:3px;background:linear-gradient(90deg,#ffd24a,#ff5a3c);box-shadow:0 0 10px rgba(255,90,60,.6)}
#lossyear .ly-scale{display:flex;justify-content:space-between;margin-top:6px;font-size:9px;letter-spacing:.08em;text-transform:uppercase;color:var(--muted)}

/* locator globe (WebGL renders inside; ring + caption are chrome aligned to it) */
/* locator — a glass text tile (left) + the WebGL globe porthole (right), equal height.
   The globe sits over the bare canvas (no card background behind it) so the Earth stays bright. */
#locator{position:fixed;right:24px;top:78px;z-index:24;width:268px;display:flex;align-items:stretch;gap:10px;pointer-events:none}
#locator .loc-cap{flex:1;min-width:0;display:flex;flex-direction:column;justify-content:center;align-items:center;text-align:center;gap:5px;padding:0 14px;border-radius:11px;background:linear-gradient(180deg,rgba(7,15,7,.5),rgba(7,15,7,.66));backdrop-filter:blur(10px);-webkit-backdrop-filter:blur(10px);border:1px solid rgba(240,240,234,.1)}
#locator .loc-k{font-size:8.5px;letter-spacing:.2em;text-transform:uppercase;color:var(--muted)}
#locator .loc-cap b{font-size:16px;font-weight:700;letter-spacing:.01em;color:var(--text);font-variant-numeric:tabular-nums;line-height:1;text-shadow:0 1px 4px rgba(0,0,0,.8)}
#locator .loc-sub{font-size:9px;letter-spacing:.14em;text-transform:uppercase;color:var(--muted);line-height:1.45}
#globe{position:relative;width:106px;height:106px;flex:none;pointer-events:none}
#globe .g-ring{position:absolute;inset:0;border-radius:50%;border:1px solid rgba(180,210,235,.22);box-shadow:0 6px 26px rgba(0,0,0,.45),0 0 22px rgba(70,150,220,.12)}
#globe .g-ring::after{content:"";position:absolute;inset:4px;border-radius:50%;border:1px solid rgba(180,210,235,.06)}
#globe .g-ring::before{content:"";position:absolute;top:-3px;left:50%;width:1px;height:6px;background:rgba(180,210,235,.5);transform:translateX(-50%)}

/* north arrow + scale bar — cartographic instrument card on the right rail, same width as the map key below */
#cartos{position:fixed;right:24px;top:196px;z-index:24;width:268px;padding:12px 15px;border-radius:11px;background:linear-gradient(180deg,rgba(7,15,7,.5),rgba(7,15,7,.66));backdrop-filter:blur(10px);-webkit-backdrop-filter:blur(10px);border:1px solid rgba(240,240,234,.1);display:flex;align-items:center;justify-content:space-between;gap:14px;pointer-events:none}
#cartos .ct-div{width:1px;align-self:stretch;margin:3px 0;background:rgba(240,240,234,.12);flex:none}

/* wildlife + protection legend (overview) */
#wlegend{position:fixed;right:24px;top:276px;z-index:24;padding:13px 15px 12px;border-radius:11px;background:linear-gradient(180deg,rgba(7,15,7,.5),rgba(7,15,7,.66));backdrop-filter:blur(10px);-webkit-backdrop-filter:blur(10px);border:1px solid rgba(240,240,234,.1);opacity:0;transform:translateY(-6px);transition:opacity .4s,transform .4s;pointer-events:none;width:268px}
#wlegend.show{opacity:1;transform:none}
#wlegend .wl-h{font-size:9px;letter-spacing:.15em;text-transform:uppercase;color:var(--muted);margin-bottom:9px}
/* The wildlife rail says WHAT ITS MARKS ARE, and ranks them by what they prove.
   Two expert readers of the Woodbury build — a director and an ecologist — read the species
   icons as a network of live observations. They are not: place_species.py derives them from a
   written habitat rule, and says so in its own docstring, but the app never said so on screen.
   #wlegend .wl-note is that missing sentence. SCOPED: #hlegend/#plegend already use .wl-note
   for their own footnote, and an unscoped rule here would restyle both.
   .wl-sub groups the list by whether a species distinguishes this forest from a cut-over one.
   Ranking matters more than the roster: the ecologist's point was that wide-ranging generalists
   would score recently logged land just as well, so a flat list argues against its own subject. */
#wlegend .wl-note{font-size:9px;line-height:1.35;letter-spacing:.01em;color:rgba(240,240,234,.58);margin:-2px 0 5px;padding-right:6px;border:0;padding-top:0}
#wlegend .wl-sub{font-size:8.5px;letter-spacing:.13em;text-transform:uppercase;color:rgba(240,240,234,.55);margin:5px 0 3px;display:flex;align-items:center;gap:7px}
#wlegend .wl-sub::after{content:"";flex:1;height:1px;background:rgba(240,240,234,.13)}
#wlegend .wl-sub:first-of-type{margin-top:2px}

#wlegend .wl-row{display:flex;align-items:center;gap:9px;font-size:11px;color:var(--dim);line-height:1.62}
#wlegend .wl-row i{width:11px;height:11px;border-radius:50%;flex:none;box-shadow:0 0 8px currentColor}
#wlegend .wl-row.t i{width:15px;height:0;border-radius:0;border-top:2px solid #dac6a2;box-shadow:none;color:#dac6a2}
#wlegend .wl-row.s i{width:7px;height:7px;border-radius:1px;background:#FFC24B!important;box-shadow:none}
#wlegend .wl-sep{height:1px;background:rgba(240,240,234,.1);margin:7px 0 6px}
#wlegend .wl-h .wl-tap{font-size:8px;letter-spacing:.1em;text-transform:uppercase;color:var(--muted);margin-left:7px;font-weight:600}
#wlegend .wl-row.pk i{width:14px;height:11px;border-radius:3px;background:rgba(110,205,150,.26);border:1px solid rgba(190,245,205,.9);box-shadow:0 0 7px rgba(120,230,160,.4);color:transparent}
#wlegend .wl-row.n i{width:15px;height:0;border-radius:0;border-top:1.5px solid #7FE3C8;box-shadow:none;color:#7FE3C8}
#wlegend .wl-row.fa{pointer-events:auto;cursor:pointer;border-radius:7px;margin:0;padding:3px 6px;gap:8px;transition:background .15s}
#wlegend .wl-row.fa:hover{background:rgba(255,255,255,.07)}
/* iconic wildlife — two columns of tappable species */
#wlegend .wl-grid{display:grid;grid-template-columns:1fr 1fr;gap:1px 4px;margin:0 -6px}
#wlegend .wl-grid .wl-row{font-size:10px;line-height:1.35;letter-spacing:-.005em;gap:6px}
#wlegend .wl-grid .wl-ic{width:16px;height:16px;flex:none;display:inline-flex;align-items:center;justify-content:center}
#wlegend .wl-grid .wl-ic svg{width:100%;height:100%;display:block;filter:drop-shadow(0 1px 1.5px rgba(0,0,0,.6))}
/* mountain-gorilla detail panel (gorilla view) — same chrome as the other legends */
/* the families register — compact two-column roll of every Virunga group */

/* mountain-gorilla family-group markers on the map (gorilla view) */
/* hypsometric (altitude) legend — same chrome as #wlegend, square swatches */
#hlegend{position:fixed;right:24px;top:276px;z-index:24;padding:13px 15px 12px;border-radius:11px;background:linear-gradient(180deg,rgba(7,15,7,.5),rgba(7,15,7,.66));backdrop-filter:blur(10px);-webkit-backdrop-filter:blur(10px);border:1px solid rgba(240,240,234,.1);opacity:0;transform:translateY(-6px);transition:opacity .4s,transform .4s;pointer-events:none;width:268px}
/* #glegend — an OPTIONAL rail panel for a conserved-land / GAP-status key. Styled here so a
   site that needs it only has to add the markup and a `gapKey: true` flag on its chapter.
   Used by Woodbury Mountain, where the surrounding land's management class is the argument. */
#glegend,#ilegend{position:fixed;right:24px;top:276px;z-index:24;padding:13px 15px 12px;border-radius:11px;background:linear-gradient(180deg,rgba(7,15,7,.5),rgba(7,15,7,.66));backdrop-filter:blur(10px);-webkit-backdrop-filter:blur(10px);border:1px solid rgba(240,240,234,.1);opacity:0;transform:translateY(-6px);transition:opacity .4s,transform .4s;pointer-events:none;width:268px}
#ilegend{position:fixed;right:24px;top:276px;z-index:24;padding:13px 15px 12px;border-radius:11px;background:linear-gradient(180deg,rgba(7,15,7,.5),rgba(7,15,7,.66));backdrop-filter:blur(10px);-webkit-backdrop-filter:blur(10px);border:1px solid rgba(240,240,234,.1);opacity:0;transform:translateY(-6px);transition:opacity .4s,transform .4s;pointer-events:none;width:268px}
#plegend{position:fixed;right:24px;top:276px;z-index:24;padding:13px 15px 12px;border-radius:11px;background:linear-gradient(180deg,rgba(7,15,7,.5),rgba(7,15,7,.66));backdrop-filter:blur(10px);-webkit-backdrop-filter:blur(10px);border:1px solid rgba(240,240,234,.1);opacity:0;transform:translateY(-6px);transition:opacity .4s,transform .4s;pointer-events:none;width:268px}
#hlegend.show,#glegend.show,#ilegend.show{opacity:1;transform:none}
#plegend.show{opacity:1;transform:none}
#hlegend .wl-h,#glegend .wl-h,#ilegend .wl-h{font-size:9px;letter-spacing:.15em;text-transform:uppercase;color:var(--muted);margin-bottom:9px}
#plegend .wl-h{font-size:9px;letter-spacing:.15em;text-transform:uppercase;color:var(--muted);margin-bottom:9px}
#hlegend .wl-note,#glegend .wl-note,#ilegend .wl-note{margin-top:9px;padding-top:8px;border-top:1px solid rgba(240,240,234,.1);font-size:9.5px;line-height:1.45;color:var(--muted);letter-spacing:.02em}
#plegend .wl-note{margin-top:9px;padding-top:8px;border-top:1px solid rgba(240,240,234,.1);font-size:9.5px;line-height:1.45;color:var(--muted);letter-spacing:.02em}
#hlegend .wl-note b,#glegend .wl-note b,#ilegend .wl-note b{font-weight:600;color:var(--dim)}
#plegend .wl-note b{font-weight:600;color:var(--dim)}
#hlegend .wl-row,#glegend .wl-row,#ilegend .wl-row{display:flex;align-items:center;gap:9px;font-size:11px;color:var(--dim);line-height:1.9}
#plegend .wl-row{display:flex;align-items:center;gap:9px;font-size:11px;color:var(--dim);line-height:1.9}
#hlegend .wl-row i,#glegend .wl-row i,#ilegend .wl-row i{width:14px;height:11px;border-radius:2px;flex:none}
#plegend .wl-row i{width:14px;height:11px;border-radius:2px;flex:none}
#hlegend .wl-row em,#glegend .wl-row em,#ilegend .wl-row em{margin-left:auto;padding-left:10px;font-style:normal;font-family:ui-monospace,'SF Mono',Menlo,monospace;font-size:8.5px;letter-spacing:.02em;color:var(--muted);font-variant-numeric:tabular-nums}
#plegend .wl-row em{margin-left:auto;padding-left:10px;font-style:normal;font-family:ui-monospace,'SF Mono',Menlo,monospace;font-size:8.5px;letter-spacing:.02em;color:var(--muted);font-variant-numeric:tabular-nums}
#hlegend .wl-sep{height:1px;background:rgba(240,240,234,.1);margin:8px 0 7px}
#plegend .wl-sep{height:1px;background:rgba(240,240,234,.1);margin:8px 0 7px}
#hlegend .wl-row.g i,#glegend .wl-row.g i,#ilegend .wl-row.g i{width:9px;height:9px;border-radius:50%;box-shadow:0 0 8px #4fd17a}
#plegend .wl-row.g i{width:9px;height:9px;border-radius:50%;box-shadow:0 0 8px #4fd17a}
/* wildlife population card — opens on a habitat-marker click; sparkline of the real trajectory */
#species{position:fixed;left:50%;top:50%;transform:translate(-50%,-46%) scale(.97);z-index:40;width:300px;max-width:88vw;padding:20px 22px 18px;border-radius:14px;background:linear-gradient(180deg,rgba(9,17,12,.94),rgba(7,13,9,.97));backdrop-filter:blur(14px);-webkit-backdrop-filter:blur(14px);border:1px solid rgba(240,240,234,.12);box-shadow:0 30px 80px rgba(0,0,0,.55);opacity:0;pointer-events:none;transition:opacity .3s,transform .3s}
#species.show{opacity:1;transform:translate(-50%,-50%) scale(1);pointer-events:auto}
/* hover preview: the same card, but it must never intercept the pointer — otherwise it
   lands under the cursor, fires pointerleave on the icon, and strobes open/closed. */
#species.peek{pointer-events:none!important;box-shadow:0 20px 60px rgba(0,0,0,.45)}
#species.peek #species-close{display:none}
#species.peek::after{content:'click to keep open';position:absolute;left:0;right:0;bottom:-19px;text-align:center;font-size:8.5px;letter-spacing:.14em;text-transform:uppercase;color:var(--muted);opacity:.85}
#species-close{position:absolute;top:12px;right:13px;width:24px;height:24px;border:0;background:rgba(255,255,255,.06);color:var(--dim);border-radius:50%;cursor:pointer;font-size:12px;line-height:1}
.sp-kicker{font-size:9px;letter-spacing:.18em;text-transform:uppercase;color:var(--muted);margin-bottom:8px}
.sp-name{font-family:'Fraunces',serif;font-weight:600;font-size:22px;line-height:1.1;color:#eef0ea;margin:0}
.sp-latin{font-style:italic;font-size:11px;color:var(--muted);margin:2px 0 14px}
.sp-head{display:flex;gap:14px;align-items:center;margin-bottom:13px}
.sp-head-t{min-width:0}
.sp-head .sp-kicker{margin-bottom:5px}
.sp-head .sp-name{font-size:20px}
.sp-head .sp-latin{margin:3px 0 0}
.sp-illus{flex:none;width:62px;height:62px;border-radius:13px;display:flex;align-items:center;justify-content:center;
  background:radial-gradient(circle at 50% 38%,color-mix(in srgb,var(--fa) 24%,transparent),transparent 72%);
  border:1px solid color-mix(in srgb,var(--fa) 26%,transparent)}
.sp-illus svg{width:50px;height:50px;display:block;filter:drop-shadow(0 1px 2px rgba(0,0,0,.45))}
.sp-badges{display:flex;flex-wrap:wrap;gap:6px;margin:0 0 14px}
.sp-badge{font-size:9.5px;letter-spacing:.04em;padding:3px 8px;border-radius:5px;color:#eef0ea;background:rgba(255,255,255,.07);border:1px solid rgba(255,255,255,.1);white-space:nowrap}
.sp-badge.sev-en{color:#ffb38a;background:rgba(255,120,60,.13);border-color:rgba(255,120,60,.32)}
.sp-badge.sev-cr{color:#ff9a9a;background:rgba(255,70,70,.14);border-color:rgba(255,70,70,.34)}
.sp-badge.sev-vu{color:#ffd98a;background:rgba(240,194,68,.13);border-color:rgba(240,194,68,.32)}
.sp-badge.sp-range{color:var(--dim)}
.sp-now{display:flex;align-items:baseline;gap:9px;margin-bottom:14px}
.sp-now b{font-size:26px;font-weight:600;font-variant-numeric:tabular-nums;letter-spacing:-.01em}
.sp-now span{font-size:10px;letter-spacing:.04em;color:var(--dim);text-transform:uppercase}
.sp-chart{margin-bottom:14px}
.sp-chart .spark{display:block}
.sp-ends{display:flex;justify-content:space-between;margin-top:5px;font-family:ui-monospace,'SF Mono',Menlo,monospace;font-size:10px;color:var(--dim)}
.sp-ends i{font-style:normal;color:var(--muted);margin-right:6px}
.sp-note{font-size:12.5px;line-height:1.55;color:var(--dim);margin:0}
.sp-src{margin:11px 0 0;padding-top:9px;border-top:1px solid rgba(240,240,234,.1);font-size:10px;line-height:1.5;color:var(--muted)}
.sp-src em{font-style:normal;color:var(--dim);opacity:.9}
.sp-src-k{display:block;font-size:8.5px;letter-spacing:.14em;text-transform:uppercase;color:var(--muted);margin-bottom:3px}
.mlabel.clickable:hover .mn{text-decoration:underline;text-underline-offset:2px}
/* place record card — opens on a labelled-feature click; same chrome as the species card */
#place{position:fixed;left:50%;top:50%;transform:translate(-50%,-46%) scale(.97);z-index:40;width:336px;max-width:88vw;padding:20px 22px 18px;border-radius:14px;background:linear-gradient(180deg,rgba(9,17,12,.94),rgba(7,13,9,.97));backdrop-filter:blur(14px);-webkit-backdrop-filter:blur(14px);border:1px solid rgba(240,240,234,.12);box-shadow:0 30px 80px rgba(0,0,0,.55);opacity:0;pointer-events:none;transition:opacity .3s,transform .3s}
#place.show{opacity:1;transform:translate(-50%,-50%) scale(1);pointer-events:auto}
#place-close{position:absolute;top:12px;right:13px;width:24px;height:24px;border:0;background:rgba(255,255,255,.06);color:var(--dim);border-radius:50%;cursor:pointer;font-size:12px;line-height:1}
#place .sp-latin{font-style:normal;letter-spacing:.02em;color:var(--dim);margin:3px 0 13px}
.pl-lead{font-size:12.5px;line-height:1.6;color:var(--dim);margin:0 0 15px}
.pl-stats{display:flex;flex-wrap:wrap;gap:13px 22px;border-top:1px solid rgba(240,240,234,.1);padding-top:13px}
.pl-stats>div{display:flex;flex-direction:column;gap:2px}
.pl-stats b{font-size:15px;font-weight:600;color:#eef0ea;font-variant-numeric:tabular-nums;letter-spacing:-.01em}
.pl-stats span{font-size:9px;letter-spacing:.07em;text-transform:uppercase;color:var(--muted)}
#north{position:relative;width:48px;height:48px;border-radius:50%;background:rgba(7,15,7,.5);backdrop-filter:blur(10px);-webkit-backdrop-filter:blur(10px);border:1px solid rgba(240,240,234,.16);box-shadow:0 2px 10px rgba(0,0,0,.35),inset 0 0 0 4px rgba(240,240,234,.04)}
#compass{width:100%;height:100%;overflow:visible;filter:drop-shadow(0 1px 2px rgba(0,0,0,.5))}
#compass-rose{transform-box:fill-box;transform-origin:center}
.cmp-tick{stroke:rgba(240,240,234,.5);stroke-width:1.4;stroke-linecap:round}
.cmp-tick.cmp-minor{stroke:rgba(240,240,234,.28);stroke-width:1.1}
.cmp-n{fill:var(--accent)}
.cmp-s{fill:rgba(240,240,234,.42)}
.cmp-hub{fill:rgba(240,240,234,.6)}
.cmp-lbl{fill:var(--text);font:700 8px/1 var(--sans);letter-spacing:.02em;text-anchor:middle}
#scale{display:flex;flex-direction:column;align-items:flex-end;gap:5px;transition:opacity .3s}
#scalebar{height:7px;width:90px;border:1px solid rgba(255,255,255,.88);border-radius:1px;box-shadow:0 1px 4px rgba(0,0,0,.5);
  background-image:repeating-linear-gradient(90deg,rgba(245,248,245,.95) 0,rgba(245,248,245,.95) 25%,rgba(20,28,22,.55) 25%,rgba(20,28,22,.55) 50%)}
#scale span{font-size:10px;font-weight:500;letter-spacing:.05em;color:var(--text);text-shadow:0 1px 3px rgba(0,0,0,.85);font-variant-numeric:tabular-nums}

/* caption — styled like a map margin / field note, not a glassy card */
/* The caption is anchored to the BOTTOM, so a long one grows UPWARD and off the top of the
   screen — taking the chapter title with it, silently, with no scrollbar and no clipping to
   show it happened. Odzala's People chapter ran 1,465 characters and lost its title entirely
   at 1440x820, and 192px of text at 1280x720. The phone breakpoint already capped this; the
   desktop one never did, so the bug only existed on the screen everyone builds on.
   This is a SAFETY NET, not a licence: a caption that needs to scroll is too long. See the
   caption-length gate in tools/check_retarget.mjs. */
:root{--cap-bottom:92px;--cap-headroom:104px}
#caption{position:fixed;left:28px;bottom:92px;z-index:25;max-width:384px;padding:17px 22px 16px;border-radius:3px;background:linear-gradient(180deg,rgba(6,11,9,.56),rgba(6,11,9,.68));backdrop-filter:blur(7px);-webkit-backdrop-filter:blur(7px);border:1px solid rgba(240,240,234,.09);border-left:2px solid rgba(0,190,90,.6);opacity:0;transition:opacity .5s ease;max-height:calc(100vh - var(--cap-bottom) - var(--cap-headroom));overflow-y:auto;overscroll-behavior:contain}
#caption.in{opacity:1;animation:capin .6s cubic-bezier(.2,.7,.2,1)}
@keyframes capin{from{opacity:0;transform:translateY(8px)}to{opacity:1;transform:none}}
#cap-title{font-family:"Fraunces",Georgia,serif;font-size:clamp(25px,2.9vw,33px);font-weight:500;letter-spacing:-.01em;line-height:1.04;margin-bottom:9px;color:#f4f3ee}
#cap-line{font-size:13px;line-height:1.58;color:var(--dim);max-width:44ch}
#cap-stats{display:flex;flex-wrap:wrap;margin-top:14px;border-top:1px solid rgba(240,240,234,.13);padding-top:12px}
#cap-stats div{display:flex;flex-direction:column;gap:2px;padding:0 15px;border-left:1px solid rgba(240,240,234,.1)}
#cap-stats div:first-child{padding-left:0;border-left:0}
#cap-stats b{font-size:17px;font-weight:600;letter-spacing:0;color:#eef0ea;font-variant-numeric:tabular-nums}
#cap-stats span{font-size:9px;letter-spacing:.1em;text-transform:uppercase;color:var(--muted);margin-top:1px}

/* 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}
#cap-links:empty{display:none}
.cap-link{background:rgba(0,190,90,.1);border:1px solid rgba(0,190,90,.42);color:var(--alive);font-family:var(--sans);font-size:12px;font-weight:600;padding:7px 13px;border-radius:999px;cursor:pointer;transition:.2s}
.cap-link:hover{background:var(--accent);color:var(--deep);border-color:var(--accent)}

/* chapter chips */
#chapters{position:fixed;left:50%;bottom:26px;transform:translateX(-50%);z-index:25;display:flex;gap:8px;padding:6px;border-radius:999px;background:rgba(7,15,7,.4);backdrop-filter:blur(10px);-webkit-backdrop-filter:blur(10px);border:1px solid rgba(240,240,234,.1)}
.chip{background:none;border:0;color:var(--dim);font-family:var(--sans);font-size:13px;font-weight:500;padding:8px 15px;border-radius:999px;cursor:pointer;transition:.2s;white-space:nowrap}
.chip:hover{color:var(--text)}
.chip.on{background:var(--accent);color:var(--deep);font-weight:700}
.chip.dive{color:var(--aqua);border:1px solid rgba(50,200,255,.32)}
.chip.dive:hover{color:#fff;border-color:var(--aqua);background:rgba(50,200,255,.12)}

/* focused-scene chrome */

/* published-maps inset + modal */
/* --mi-anchor tells fitMapInset() which edge this thumb is pinned to. It cannot read that from
   `top`/`bottom`: getComputedStyle returns the USED value for a positioned element, so both
   sides always read back as pixel lengths and `=== 'auto'` is never true. Custom properties are
   returned as authored, so they are the only reliable channel for layout INTENT. */
#mapinset{--mi-anchor:bottom;position:fixed;right:26px;bottom:96px;z-index:25;display:flex;flex-direction:column;align-items:center;gap:6px;padding:0;background:none;border:0;cursor:pointer}
/* Bound the thumb to a BOX, not just a width. `width:104px;height:auto` was written
   against the reference build's landscape thumb (360x203 -> 104x59). A portrait thumb
   — Tejon's is 236x480 — became 104x226, nearly four times as tall, and collided with
   the map key above it on any window shorter than ~880 px. Same mistake as .map-frame,
   in the other element: one dimension constrained, the other left to the aspect ratio
   of whichever park happened to be the reference. */
#mapinset img{max-width:104px;max-height:132px;width:auto;height:auto;object-fit:contain;border-radius:8px;border:1px solid rgba(240,240,234,.2);box-shadow:0 10px 30px rgba(0,0,0,.5);transition:.25s}
#mapinset:hover img{border-color:var(--accent);transform:translateY(-2px)}
#mapinset.mi-hidden{display:none}
#mapinset span{font-size:10px;letter-spacing:.1em;text-transform:uppercase;color:var(--dim)}
#mapmodal{position:fixed;inset:0;z-index:60;background:radial-gradient(circle at 50% 42%,rgba(7,15,7,.82),rgba(0,0,0,.95));backdrop-filter:blur(8px);display:none;align-items:center;justify-content:center;padding:5vh 5vw}
#mapmodal.open{display:flex;animation:modalbg .4s ease}
@keyframes modalbg{from{opacity:0}to{opacity:1}}
.modal-inner{position:relative;max-width:1100px;width:100%;opacity:0;transform:scale(.97);transition:opacity .42s cubic-bezier(.2,.7,.2,1),transform .42s cubic-bezier(.2,.7,.2,1)}
#mapmodal.open .modal-inner{opacity:1;transform:none}
#map-close{position:absolute;top:-34px;right:0;background:none;border:0;color:var(--dim);font-size:20px;cursor:pointer;z-index:2}
#map-close:hover{color:var(--text)}
.mapfig{display:flex;flex-direction:column;align-items:center;gap:13px}
.mapfig figure,.mapfig{position:relative}
/* The frame SHRINK-WRAPS the plate rather than claiming the modal's full width.
   width:100% was written against the reference build, whose plates are landscape
   (2000x1127) and fill it. A taller plate is capped by max-height instead, so the
   frame kept its full 1100px and the image sat in a moat: 42% empty on a square-ish
   plate, 70% on a 2000x4070 portrait one, with the border drawn around the emptiness
   rather than the map. Sizing to the content works for any aspect. */
.map-frame{position:relative;width:auto;min-width:min(100%,364px);max-width:100%;max-height:74svh;overflow:hidden;border-radius:10px;border:1px solid rgba(240,240,234,.16);background:#0a160a;box-shadow:0 30px 90px rgba(0,0,0,.6);display:flex;align-items:center;justify-content:center}
.mapfig img{max-width:100%;max-height:74svh;display:block;transform-origin:center center;will-change:transform;cursor:zoom-in;user-select:none;-webkit-user-drag:none}
#map-stage.kb{animation:kbIn 1.3s ease both}
@keyframes kbIn{from{filter:brightness(.66) saturate(.82)}to{filter:brightness(1) saturate(1.06)}}
.map-hint{position:absolute;left:50%;transform:translateX(-50%);bottom:10px;z-index:2;white-space:nowrap;font-size:9.5px;letter-spacing:.08em;text-transform:uppercase;color:rgba(240,240,234,.62);background:rgba(7,15,7,.62);padding:5px 10px;border-radius:999px;pointer-events:none}
.mapfig figcaption{font-size:13px;color:var(--dim);text-align:center;max-width:72ch}
/* The attribution is a flex child, so it shrink-wraps to whatever the frame is wide.
   With a portrait plate that collapsed it into a ragged ~350px column. Give it its own
   measure, independent of the plate's aspect. */
.mapfig .src{width:100%;max-width:78ch;text-align:center;margin:0 auto}
.map-tabs{display:flex;gap:8px;flex-wrap:wrap;justify-content:center}
.map-btn{background:rgba(240,240,234,.06);border:1px solid rgba(240,240,234,.18);color:var(--dim);font-family:var(--sans);font-size:12px;padding:7px 14px;border-radius:999px;cursor:pointer;transition:.2s}
.map-btn:hover{color:var(--text);border-color:var(--accent)}
.map-btn.on{background:var(--accent);border-color:var(--accent);color:var(--deep)}
.src{font-size:10px;line-height:1.6;color:var(--muted);max-width:62ch;text-align:center;margin-top:4px}

/* ── live data: NASA FIRMS active fire + GFW deforestation alerts ─────────── */
#live-pts{position:fixed;inset:0;z-index:9;pointer-events:none}
.livept{position:absolute;transform:translate(-50%,-50%);will-change:left,top,opacity;opacity:0;transition:opacity .5s}
.livept.fire,.livept.alert{pointer-events:auto;cursor:pointer}
/* active-fire hotspot — a small, clear flame icon (subtle amber, gentle flicker), size set per-cluster in JS */
.livept.fire svg{display:block;width:100%;height:100%;overflow:visible;transform-origin:50% 80%;filter:drop-shadow(0 0 1.5px rgba(255,110,30,.5)) drop-shadow(0 1px 1px rgba(70,18,0,.4));animation:flameflick 2.8s ease-in-out infinite}
/* GFW deforestation alert — a compact target (small ring + centre dot), constant-weight stroke.
   Magenta (the GFW GLAD convention): a cool hue that reads as a DISTINCT layer from the amber fire
   flames and the amber→red Hansen overlay, so the warm Forest-Loss view no longer muddles together.
   No opacity animation (updateLive drives .livept opacity). */
.livept.alert svg{display:block;width:100%;height:100%;overflow:visible;filter:drop-shadow(0 0 2px rgba(0,0,0,.6))}
.livept.alert .ll-ring{fill:rgba(241,95,208,.12);stroke:#f15fd0;stroke-width:1.5px;vector-effect:non-scaling-stroke}
.livept.alert .ll-core{fill:#ff74dc}
@keyframes flameflick{0%,100%{opacity:.9;transform:scale(1) skewX(0deg)}50%{opacity:1;transform:scale(1.04,1.1) skewX(-1.5deg)}}

/* live forest-condition key (Forest Loss) — same right-rail card language as #wlegend / #hlegend */
#llegend{position:fixed;right:24px;top:276px;z-index:24;padding:13px 15px 12px;border-radius:11px;background:linear-gradient(180deg,rgba(7,15,7,.5),rgba(7,15,7,.66));backdrop-filter:blur(10px);-webkit-backdrop-filter:blur(10px);border:1px solid rgba(240,240,234,.1);opacity:0;transform:translateY(-6px);transition:opacity .4s,transform .4s;pointer-events:none;width:268px}
#llegend.show{opacity:1;transform:none}
#llegend .wl-h{font-size:9px;letter-spacing:.15em;text-transform:uppercase;color:var(--muted);margin-bottom:10px}
#llegend .ll-row{display:flex;align-items:center;gap:10px;font-size:11.5px;color:var(--dim);line-height:1.6;padding:3px 0}
#llegend .ll-ic{flex:none;width:20px;height:20px;display:inline-flex;align-items:center;justify-content:center}
#llegend .ll-ic svg{width:17px;height:17px;display:block;overflow:visible}
#llegend .ll-lbl{flex:1}
#llegend .ll-n{font-variant-numeric:tabular-nums;font-weight:700;color:var(--text)}
#llegend .ll-span{font-size:8.5px;letter-spacing:.1em;text-transform:uppercase;color:var(--muted);margin-left:6px;min-width:22px;text-align:right}
#llegend .wl-sep{height:1px;background:rgba(240,240,234,.1);margin:9px 0 8px}
#llegend .ll-foot{font-size:9px;letter-spacing:.04em;color:var(--muted);line-height:1.45}

/* hover tooltip for a live marker */
#livetip{position:fixed;left:0;top:0;z-index:40;transform:translate(-50%,-100%);pointer-events:none;opacity:0;transition:opacity .15s;padding:7px 10px;border-radius:8px;background:linear-gradient(180deg,rgba(7,15,7,.88),rgba(7,15,7,.95));border:1px solid rgba(240,240,234,.14);box-shadow:0 6px 22px rgba(0,0,0,.45);backdrop-filter:blur(8px);-webkit-backdrop-filter:blur(8px);white-space:nowrap}
#livetip.show{opacity:1}
#livetip b{color:var(--text);font-variant-numeric:tabular-nums;font-weight:700;font-size:13px}
#livetip i{display:block;font-style:normal;font-size:8.5px;letter-spacing:.07em;text-transform:uppercase;color:var(--muted);margin-top:3px}
#wlegend .wl-row.la i{width:9px;height:9px;border-radius:2px;background:radial-gradient(circle,rgba(255,122,182,1),rgba(232,52,132,.95));box-shadow:0 0 7px rgba(232,60,140,.6);color:transparent}
#wlegend .wl-row .wl-src{margin-left:auto;padding-left:10px;font-size:8px;letter-spacing:.1em;text-transform:uppercase;color:var(--muted)}

/* ── tablet / large phone ───────────────────────────────────────────────── */
@media(max-width:820px){
  .hud{padding:12px 16px}
  .hud-tag{display:none}
  #locator{top:66px;left:auto;right:12px;width:auto;max-width:48vw;padding:9px 11px;gap:10px}
  #locator .loc-cap b{font-size:14px}
  #globe{width:86px;height:86px}
  #cartos{top:176px;left:auto;right:12px;width:auto;max-width:46vw;padding:10px 13px;gap:12px}
  #north{width:42px;height:42px}
#wlegend,#hlegend,#plegend,#glegend,#ilegend,#llegend{top:248px;left:auto;right:12px;max-width:46vw;padding:11px 13px 10px;width:auto}
#wlegend .wl-row,#hlegend .wl-row,#glegend .wl-row,#ilegend .wl-row,#llegend .ll-row{font-size:10.5px;line-height:1.72}
  #lossyear{width:72vw}
  #caption{left:14px;right:auto;bottom:150px;max-width:58vw;padding:15px 17px 14px}
  #cap-line{max-width:none}
  #mapinset{right:14px;bottom:152px}
  #mapinset img{width:68px}
  #readout{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}
  #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. */
#plegend .wl-row i.pl-ramp{display:block;width:100%;height:9px;border-radius:5px;
  background:linear-gradient(90deg,#6b2211 0%,#a8451a 45%,#ffb83d 100%)}
#plegend .wl-row.ramp{display:block}
#plegend .pl-scale{display:flex;justify-content:space-between;font-size:8.5px;letter-spacing:.08em;
  text-transform:uppercase;color:var(--muted);margin-top:5px}


/* ══ touch layout ═══════════════════════════════════════════════════════════
   Phones and tablets. The governing idea: the bottom of the screen is divided into two
   strips that never overlap — a NAV strip pinned above the home indicator, and the caption
   SHEET immediately above it. Nothing is layered on top of anything else down there.

   The previous version overlaid the nav on a flush-to-edge sheet and tried to reserve space
   with padding-bottom. Padding inside an `overflow-y:auto` box is scroll content, not a
   persistent gutter, so it reserved nothing until the user scrolled to the end — and the
   stats row and Park-record link rendered under the nav. Measured, not guessed.

   --navzone is the full height the nav strip claims, including the safe area. Everything
   above it is positioned from it, so there is one number to change. */
#keytoggle{display:none}

@media(max-width:1024px),(hover:none){
  :root{ --navh:44px; --navgap:10px; --navzone:calc(var(--sab) + var(--navh) + var(--navgap)*2); }

  /* ── nav strip ── */
  /* Full-width and horizontally scrollable: four chapter names do not fit across 402px, and
     the old centred pill clipped the last one ("Wild by Design" showed as "Wild"). Chips keep
     their own glass background now that the containing pill is gone. */
  #chapters{
    left:0;right:0;bottom:calc(var(--sab) + var(--navgap));transform:none;
    justify-content:flex-start;gap:8px;
    padding:0 62px 0 12px;            /* right gutter clears the KEY button */
    background:none;border:0;border-radius:0;backdrop-filter:none;-webkit-backdrop-filter:none;
    overflow-x:auto;overscroll-behavior-x:contain;-webkit-overflow-scrolling:touch;
    scrollbar-width:none;z-index:27}
  #chapters::-webkit-scrollbar{display:none}

  #chapters .chip{
    min-height:var(--navh);display:inline-flex;align-items:center;padding:0 16px;
    background:rgba(8,16,11,.82);border:1px solid rgba(240,240,234,.14);
    backdrop-filter:blur(12px);-webkit-backdrop-filter:blur(12px);
    font-size:12px;white-space:nowrap;-webkit-tap-highlight-color:transparent}
  #chapters .chip.on{background:var(--accent,#00be5a);color:#04180c;border-color:transparent}

  #keytoggle{
    display:inline-flex;align-items:center;position:fixed;right:10px;
    bottom:calc(var(--sab) + var(--navgap));z-index:28;
    min-height:var(--navh);padding:0 15px;border-radius:999px;
    border:1px solid rgba(240,240,234,.16);background:rgba(8,16,11,.88);
    backdrop-filter:blur(12px);-webkit-backdrop-filter:blur(12px);
    color:var(--dim);font:600 11px/1 var(--ui,inherit);letter-spacing:.12em;
    text-transform:uppercase;cursor:pointer;-webkit-tap-highlight-color:transparent}
  #keytoggle[aria-expanded=true]{background:var(--accent,#00be5a);color:#04180c;border-color:transparent}

  /* ── caption sheet, ABOVE the nav strip ── */
  #caption{
    left:8px;right:8px;bottom:var(--navzone);max-width:none;
    border-radius:14px;border-left:1px solid rgba(0,190,90,.28);
    border-top:2px solid rgba(0,190,90,.55);
    padding:14px 16px 15px;
    max-height:calc(52svh - var(--navzone));
    overflow-y:auto;-webkit-overflow-scrolling:touch;overscroll-behavior:contain;
    line-height:1.5;
    background:linear-gradient(180deg,rgba(6,11,9,.86),rgba(6,11,9,.95))}
  #cap-line{line-height:1.55}
  #cap-stats{gap:14px}

  /* keys hide unless asked for. .show is still what the chapter logic sets, so this only
     gates VISIBILITY on small screens — it does not change which key belongs to which chapter. */
#wlegend,#hlegend,#plegend,#glegend,#ilegend,#llegend{
    opacity:0 !important;pointer-events:none !important;transition:opacity .28s ease}
  body.keys-open #wlegend.show,body.keys-open #hlegend.show,#glegend.show,
  body.keys-open #plegend.show,body.keys-open #llegend.show{opacity:1 !important;pointer-events:auto !important}

  /* The onboarding hint moves OFF the map centre, where it sat across the terrain and over
     place labels, to a quiet line under the header. It is guidance, not content. */
  #hint{top:calc(var(--sat) + 58px);bottom:auto;left:12px;right:12px;transform:none;
    text-align:center;font-size:10px;letter-spacing:.1em;line-height:1.5}

  /* The thumb is the ONLY way into the reference maps — openModal() is bound to it and
     nothing else — so it must not be hidden on touch. Below 520px it moves to the top-right
     as a row (see that breakpoint); fitMapInset() detects that and leaves it alone. */
  .lang{min-height:44px;min-width:44px;display:inline-flex;align-items:center;justify-content:center}
}

@media(max-width:520px){
  #caption{max-height:calc(46svh - var(--navzone));padding:13px 14px 14px}
  /* the key, when opened, is a sheet rather than a floating card — and it sits above the nav
     strip for the same reason the caption does */
#wlegend,#hlegend,#plegend,#glegend,#ilegend,#llegend{top:auto !important;bottom:var(--navzone) !important;
    left:8px !important;right:8px !important;
    max-width:none !important;width:auto !important;max-height:56svh;overflow-y:auto;
    border-radius:14px;padding:14px 16px !important}
  body.keys-open #caption{opacity:0;pointer-events:none}   /* one sheet at a time */
}

/* Hint copy follows the INPUT, not the width: a wheel-less screen should never be told to
   scroll. Both strings are real text so both are translated. */
#hint .h-touch{display:none}
@media(hover:none),(max-width:1024px){
  #hint .h-mouse{display:none}
  #hint .h-touch{display:inline}
}

/* A wide bottom sheet (landscape tablet) should use its width rather than running a narrow
   column of prose with dead space beside it. Two columns keeps the sheet SHORT, which is the
   thing that matters on a 724px-tall landscape screen — every row of sheet is a row of map. */
@media(max-width:1024px) and (min-width:700px) and (orientation:landscape){
  #caption{display:grid;grid-template-columns:minmax(0,1fr) auto;grid-template-areas:
      "title stats" "line stats" "links stats";
    column-gap:26px;align-content:start;max-height:38svh;right:96px}
  #cap-title{grid-area:title}
  #cap-line{grid-area:line;max-width:68ch}
  #cap-stats{grid-area:stats;align-self:start;margin-top:0;border-top:0;padding-top:4px;
    flex-direction:column;gap:10px;min-width:150px}
  #cap-stats div{padding:0;border:0}
  #cap-links{grid-area:links;margin-top:12px}
}

/* ── mobile nav/sheet affordances ────────────────────────────────────────────
   Found by looking at a rendered iPhone frame, not by any metric: the chapter strip scrolls
   but did not look scrollable, so the fourth chapter was invisible behind the KEY button. */
@media(max-width:1024px),(hover:none){
  /* Fade the right edge so the strip reads as scrollable. Two properties because Safari still
     wants the prefix; without the standard one Firefox gets no fade (harmless). */
  #chapters{
    -webkit-mask-image:linear-gradient(90deg,transparent 0,#000 22px,#000 calc(100% - 74px),transparent 100%);
    mask-image:linear-gradient(90deg,transparent 0,#000 22px,#000 calc(100% - 74px),transparent 100%);
    scroll-behavior:smooth}

  /* The sheet's call to action stays reachable without scrolling the sheet. */
  /* Opaque, not a soft gradient: a translucent sticky bar let the stat labels
     ("PROTECTED", "ESTABLISHED") show through it, which reads as a rendering fault. */
  #cap-links{position:sticky;bottom:-15px;margin-top:16px;padding:12px 0 2px;
    background:#070f09;box-shadow:0 -10px 14px 6px #070f09;z-index:2}

  /* Keep the hint clear of the locator globe at top-right. */
  #hint{right:104px;text-align:left}
}
@media(max-width:520px){
  #hint{right:96px;font-size:9.5px}
}

/* A band the shader cannot paint must not be claimed in the key — see fitAltitudeKey().
   Set by JS from the in-park range the key header already declares. */
#hlegend .wl-row.hl-unreachable,#glegend .wl-row.hl-unreachable,#ilegend .wl-row.hl-unreachable{display:none}

/* Reviewer credit — lower right, under the reference-map thumb. Rendered by main.js only when
   src/site.config.js names a real reviewer; hidden otherwise, because an unearned credibility
   line is worse than none. fitMapInset() treats it as the floor so the thumb sits above it. */
#reviewcredit{position:fixed;right:26px;bottom:16px;z-index:25;max-width:230px;text-align:right;
  font-size:10px;line-height:1.5;letter-spacing:.04em;color:var(--dim);pointer-events:none}
#reviewcredit b{font-weight:600;color:var(--text);letter-spacing:.02em}
#reviewcredit[hidden]{display:none}
@media (max-width:760px){#reviewcredit{right:14px;bottom:10px;max-width:150px;font-size:9px}}

/* ── the right rail may not grow into the bottom-right corner ──────────────────────────────
   Every rail panel is position:fixed at top:276px and grows DOWNWARD with no bound, while the
   reference-map thumb and the reviewer credit are anchored to the bottom. On a long species
   list — and adding IUCN codes makes every list longer — the legend simply ran into them, and
   the only defence was JS hiding the thumb. That traded a feature away to avoid an overlap.

   Cap the panel instead. --rail-reserve is the space kept clear at the bottom for the maps
   thumb, its label and the credit; the panel scrolls internally past that point rather than
   pushing into it. This is a pure CSS guarantee that holds at any viewport height and for any
   number of species, with JS only enabling the scroll affordance when it is actually needed. */
:root{--rail-top:276px;--rail-reserve:186px}
#wlegend,#hlegend,#plegend,#glegend,#ilegend,#llegend{max-height:calc(var(--appvh) - var(--rail-top) - var(--rail-reserve))}
/* pointer-events stay OFF unless the panel really overflows, so the map can still be dragged
   through the empty part of the rail. main.js adds .rail-scroll only when scrollHeight exceeds
   clientHeight. */
#wlegend.rail-scroll,#hlegend.rail-scroll,#plegend.rail-scroll,#llegend.rail-scroll{
  overflow-y:auto;overscroll-behavior:contain;pointer-events:auto}
#wlegend.rail-scroll::-webkit-scrollbar,#hlegend.rail-scroll::-webkit-scrollbar,
#plegend.rail-scroll::-webkit-scrollbar,#llegend.rail-scroll::-webkit-scrollbar{width:6px}
#wlegend.rail-scroll::-webkit-scrollbar-thumb,#hlegend.rail-scroll::-webkit-scrollbar-thumb,
#plegend.rail-scroll::-webkit-scrollbar-thumb,#llegend.rail-scroll::-webkit-scrollbar-thumb{
  background:rgba(240,240,234,.22);border-radius:3px}
@media (max-width:760px){:root{--rail-reserve:120px}}
/* --rail-reserve is tuned for a tall screen. 186px of bottom clearance costs nothing at 982px of
   viewport and costs three legend rows at 720px, where the rail was already 14px over before the
   wildlife bands were added. Reclaim it by HEIGHT, not width: a 1280x720 laptop is wide and short,
   so the width breakpoint above never fires there. Verified against the nav and the maps thumb —
   the reserve still clears both, which is what fitMapInset() measures. */
@media (max-height:860px){:root{--rail-reserve:150px}}
@media (max-height:780px){:root{--rail-reserve:92px}}   /* 24px rows cost 18px; this pays for them */

/* IUCN Red List code on each wildlife-key row. Graded by severity so the shape of a park's
   threat profile is readable at a glance without opening a single card. */
#wlegend .wl-row.fa{position:relative;padding-right:34px}
.iucn{position:absolute;right:0;top:50%;transform:translateY(-50%);font-size:9px;font-weight:700;
  letter-spacing:.06em;padding:1px 4px;border-radius:3px;border:1px solid currentColor;opacity:.9}
.iucn-CR{color:#ff6b5e}.iucn-EN{color:#ff9d4d}.iucn-VU{color:#ffd24a}
.iucn-NT{color:#9ecf6a}.iucn-LC{color:#7fb0c9}.iucn-DD{color:#9a9a94}

/* A PHONE IN LANDSCAPE is wide and very short — 874x402 on an iPhone 16 Pro — and no width-keyed
   breakpoint catches it: 874px reads as "tablet", so it inherits a full-width caption. Measured
   there, the caption ran 770px wide straight under the locator/compass column at x=582, overlapping
   it by 74px. Key on HEIGHT, and keep the caption clear of the right-hand furniture. The maps thumb
   is left to fitMapInset(), which correctly finds no room at 402px and hides it. */
@media (max-height:560px) and (orientation:landscape){
  :root{--rail-top:120px;--rail-reserve:56px;--cap-headroom:40px;--cap-bottom:58px}
  #caption{max-width:min(56vw,520px);bottom:58px;padding:12px 15px 12px}
}

/* TAP TARGETS. WCAG 2.5.8 asks for 24x24 CSS px minimum; measured on a 402x874 phone the species
   rows were 180x22 and the dialog close buttons 12x18. The rows are the ONLY way to open a species
   card on touch — the map icons are deliberately mouse-only — so being 2px under is not cosmetic.
   Applied on coarse pointers only, so the desktop rail keeps its density. */
/* WCAG 2.5.8 is not a touch-only rule. Measured on desktop: species-close and place-close were
   23x23 — one pixel under — and readout-close 12x18. Coarse pointers get a larger target still. */
#species-close,#place-close,#readout-close{
  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 Esri World Imagery across the whole screen and draws
   OpenStreetMap geometry over it; both licences require attribution, and until now the only
   credit lived inside the reference-maps modal — a panel a visitor has to open. This is the
   always-visible line. It is deliberately tiny and dim: attribution has to be present and
   legible, not prominent.
   Hidden below 1024px for the same reason as the reviewer credit — there is no free corner at
   that size, and the full source list is in the park record, linked from every chapter caption. */
#datacredit{position:fixed;left:28px;bottom:12px;z-index:24;font-size:9px;letter-spacing:.02em;
  color:rgba(240,240,234,.46);pointer-events:none;line-height:1.3;
  /* 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. */
  max-width:min(24vw,320px)}
@media (max-width:1024px), (hover:none){ #datacredit{display:none} }

/* .ro-h — a heading inside the park record. Eric Dinerstein 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}
}
