: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}
/* Machine-translation notice (D-092). Absolute inside the fixed HUD so it is not a third flex item;
   sits under the wordmark, the one empty region on desktop and phone (the locator globe is right).
   max-width keeps it clear of the globe at 390 px. Hidden unless the page is in an unreviewed
   second language. Native line first, English second, direct not apologetic. */
#mtnote{position:absolute;left:24px;top:56px;max-width:min(260px,calc(100vw - 140px));display:flex;flex-direction:column;gap:2px;font-size:10px;line-height:1.35;letter-spacing:.02em;color:var(--muted);pointer-events:none}
#mtnote[hidden]{display:none}
@media (max-width:760px){#mtnote{left:16px;top:50px}}
@media (max-width:360px){#mtnote{max-width:calc(100vw - 106px);font-size:9px;letter-spacing:0}}
.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}
/* capfit.js fitHint(): the hint is hidden (not moved) where it would still touch the caption or other furniture, e.g. a phone whose caption reaches the header */
html[data-hintfit="no"] #hint{visibility:hidden}
/* THE HINT FITS THE BAND IT SITS IN, IN ANY LANGUAGE (backlog 42). It was sized for English: nowrap, centred,
   561px of text. French, Spanish and Kiswahili run 640-730px, so on a desktop the line reached left over the
   credit box (which is taller than the 74px band) and right toward the key, and on a phone it ran under the
   locator. Now the box is the FREE BAND, not the text: --hint-clear is the caption's right edge (left 28 +
   max-width 384) plus 16px, kept from both sides so it stays centred over the chapter bar. The text wraps
   inside that band, so what a language adds is a second line, which rises into space nothing else uses
   (the caption, the credit and the key are all outside the band). Below 1156px the band stops shrinking at
   300px and sits against the caption edge. 77px = one line above the chapter bar (its top is 73-76px up). */
:root{--hint-clear:428px}
@media(min-width:1025px) and (hover:hover){
  #hint{bottom:77px;left:var(--hint-clear);right:max(28px,min(var(--hint-clear),calc(100vw - var(--hint-clear) - 300px)));
    transform:none;width:fit-content;margin-inline:auto;white-space:normal;text-align:center}
}

/* 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:normal}
.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. */
/* The caption clears the attribution strip at the wide tier too: a long required credit (the DEM's
   USGS + NOAA lines) wraps #datacredit to ~100px and ran under the card at 1440. --credit-h is
   measured at runtime (sizeCreditStrip); the 92px floor keeps every site with a short credit where it was. */
:root{--cap-bottom:max(92px, calc(var(--credit-h, 0px) + 24px));--cap-headroom:104px}
#caption{position:fixed;left:28px;bottom:var(--cap-bottom);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:var(--cap-title);font-weight:500;letter-spacing:-.01em;line-height:var(--cap-title-lh);margin-bottom:9px;color:#f4f3ee}
#cap-line{font-size:var(--cap-body);line-height:var(--cap-body-lh);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:var(--cap-stat);font-weight:600;letter-spacing:0;color:#eef0ea;font-variant-numeric:tabular-nums}
#cap-stats span{font-size:var(--cap-small);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 */
/* ⛔ THE BAR MUST NOT REACH #datacredit. Taken from park-map-template 280d39c, 2026-09-09.
   A centred bar grows in BOTH directions, so chapter count alone decides whether its left edge
   walks into #datacredit (fixed, left:28, 320px wide). This site has 6 chapters and a 118px gap
   at 1440 — it did NOT show the defect; clemson-forest-landseed with 9 chapters overlapped by
   45px and painted the "Overview" pill over the OpenStreetMap ODbL attribution. Taken here so the
   two lineages hold one rule and so adding a chapter cannot reintroduce it silently. */
#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{margin-bottom:6px}
  #cap-stats div{padding:0 11px}
  #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. */
#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:500 12px/1 var(--sans);letter-spacing:0;
    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-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;white-space:normal}

  /* 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). */
  /* ⛔ ONLY WHEN IT OVERFLOWS (CoS, 19 Oct 2026): a fade on a row whose chips all fit hints at chips that are
     not there. src/capfit.js (fitChips) sets data-overflow when scrollWidth > clientWidth. */
  #chapters{scroll-behavior:smooth}
  #chapters[data-overflow]{
    -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%)}

  /* 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. */
  max-width:min(24vw,320px)}
/* 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; }
}

/* .ro-h — a heading inside the park record. The 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)}
.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}

/* Machine-translation notice (D-092): where the top strip is shared, the hint and the forest-loss
   readout step down by the note's height. --mt-h is set by fitMtNote() in main.js. */
@media (max-width:1024px),(hover:none){ html[data-mt] #hint{top:calc(var(--sat) + 58px + var(--mt-h,0px))} }
@media (max-width:820px){ html[data-mt] #lossyear{top:calc(62px + var(--mt-h,0px))} }
@media (max-width:520px){ html[data-mt] #lossyear{top:calc(52px + var(--mt-h,0px))} }

/* ── the credit-strip lift, from vista-template da9d718 (CoS 19 Oct: syncs with the caption fit) ── */
/* 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(77px + var(--credit-h,0px))}
/* the lift above is a bottom-band rule; the top-band tiers place the hint by top and must not be stretched by it */
@media (max-width:1024px),(hover:none){ html[data-creditstrip] #hint{bottom:auto} }
html[data-creditstrip] #caption{bottom:calc(92px + var(--credit-h,0px))}
html[data-creditstrip] body.focus #caption{bottom:92px}

/* ⛔ THE PHONE KEY SHEET AND CAPTION SAT UNDER THE NAV. Both were anchored to constants: the key
   at bottom:var(--navzone), the caption at 92px + --credit-h. But the nav is itself lifted by the
   MEASURED credit strip, and its chips WRAP — at 390x844 on this template they make two rows,
   669-765. Measured on 56dd2bf: the key sheet ended at 780, so the opaque chips and the KEY toggle
   covered its bottom 96px (Ecosystems showed one row of its key), and the caption ran 30px under
   the chips in all four views. A constant was the cause; a better constant is the same bug at the
   next chip count, language or credit length.
   So sizeCreditStrip MEASURES the nav's top edge (--nav-clear: viewport bottom to the top of
   #chapters or #keytoggle, whichever is higher) and the reference-map thumb's bottom edge
   (--thumb-clear), and both sheets hang off those. The fallbacks are the OLD values, so a JS
   failure degrades to the old overlap, not to a sheet floating somewhere new.
   The max-height keeps the sheet's TOP below the thumb: the thumb (z 25) paints over the key (z 24),
   and on a short phone a full-height key reached under it. */
@media(max-width:520px){
  #wlegend,#hlegend,#plegend,#llegend{
    bottom:calc(var(--nav-clear, var(--navzone)) + 8px) !important;
    max-height:min(56svh, calc(100svh - var(--nav-clear, var(--navzone)) - 16px - var(--thumb-clear, 0px))) !important}
  #caption,html[data-creditstrip] #caption{bottom:calc(var(--nav-clear, 92px + var(--credit-h, 0px)) + 8px)}
}

/* ══ CAPTION FIT — THE CHAPTER CAPTION NEVER SCROLLS (Alex, 18 Oct 2026) ═══════════════════════════
   It was a scroller with a max-height built from constants, and measured on borana it scrolled in 32
   of 70 cells (7 chapters x 2 languages x 390x844, 1100x700, 1100x900, 1440x900, 1920x1080): every phone
   chapter, most chapters at 1100x700 with the Park record button out of sight. The answer is ROOM and
   TYPE, never a scrollbar:
   · the room is MEASURED by src/capfit.js (--cap-room on #caption: its own bottom edge up to the lowest
     top furniture over its column) and only applies once measured, under html[data-capfit]; without it
     the older max-height and scroll above still hold, so a JS failure degrades to the old safety net;
   · the type steps down at short heights through the --cap-* tokens below, which every caption size reads;
   · overflow stays AUTO, as the safety net and only that. A caption that still outgrows its measured
     room is too long and is cut; until it is, it scrolls inside its own box. ⛔ NOT overflow:visible
     (borana's first form): a bottom-anchored box overflows DOWNWARD, so the excess text and the Park
     record button landed on the data credit, covering the licence notice (minkebe's French Forest
     Loss caption at 1100x700, measured), and a probe that counts scrollers reads that state as clean.
   Phones under 760px tall are not measured (capfit.js) and keep the old max-height. */
:root{--cap-title:clamp(25px,2.9vw,33px);--cap-title-lh:1.04;--cap-body:13px;--cap-body-lh:1.58;--cap-stat:17px;--cap-small:9px}
@media(max-width:1024px),(hover:none){:root{--cap-body-lh:1.55}}
@media(max-width:520px){:root{--cap-title:19px;--cap-body:12px;--cap-stat:14px}}
/* short-height steps: never below 11px on a phone or 12px on a desktop (12px on a phone was measured on
   borana and one Kiswahili chapter still did not fit at 390x844) */
@media(max-width:520px) and (max-height:900px){:root{--cap-body:11px;--cap-body-lh:1.45}}
@media(min-width:1025px) and (max-height:760px){:root{--cap-body:12px;--cap-body-lh:1.5}}
html[data-capfit] #caption{max-height:var(--cap-room);overflow-y:auto}
/* PHONE: the sheet may rise over the reference-map thumb, which hides while the sheet shows (KEY hides
   the sheet, and the thumb comes back with it); the chapter nav is ONE swipeable row, not wrapped rows. */
@media(max-width:520px){
  body:not(.keys-open) #mapinset{display:none}
  #chapters{flex-wrap:nowrap}
  /* The fade ends where the KEY pill begins (the row's 62px right gutter): the old one sat under KEY and was never
     seen, so kanha's six chips read as four complete ones. The chip under the fade is the hint that more follow. */
  #chapters[data-overflow]{
    -webkit-mask-image:linear-gradient(90deg,#000 calc(100% - 118px),transparent calc(100% - 62px));
    mask-image:linear-gradient(90deg,#000 calc(100% - 118px),transparent calc(100% - 62px))}
}
/* ⛔ THE SPECIES DIALOG RAN OFF THE SCREEN: it had no max-height, and on borana the elephant card ended at
   y=1067 on an 844px phone with no way to reach the rest. It is capped to the viewport and scrolls INSIDE
   (a dialog, not the caption); on a phone it hangs from the top instead of the middle. */
#species{max-height:calc(var(--appvh, 100vh) - 24px);overflow-y:auto;overscroll-behavior:contain}
@media(max-width:520px){#species{top:calc(var(--sat, 0px) + 12px);max-height:calc(var(--appvh, 100vh) - var(--sat, 0px) - 24px)}}

/* 1025-1280 with the credit strip on: the caption WIDENS to 520px and comes down to the nav's baseline, and
   the nav moves right, into the free space between the caption and its right-hand neighbour.
   src/main.js (sizeNavRow) MEASURES that space (the caption's right edge to the nearest fixed chrome sharing
   the nav's band, 12px clear each side) and writes --nav-l and --nav-w under html[data-navrow]. The chips
   WRAP inside it, so every chip shows; a narrow space makes a taller block (rows grow upward, beside the
   caption, never over it). Without the strip the attribute is off and the base rules hold (centred, as live).
   ⛔ Not a stacked fallback: putting the caption back above a wrapped nav cost it the room it needs at
   1025-1200 (Chitwan, measured: 6 new scrolling cells and 9 caption x nav collisions).
   ⛔ THE FIXED-WIDTH SCROLLER THIS REPLACES (#27) HID CHIPS LIVE SHOWED. It gave the row 100vw - 868px
   at every 1025-1280 desktop, strip or not, as a swipeable row with an edge fade: majete at 1280x720 (no
   strip) showed "Forest Lo|" where live showed all four chips; at 1100x700 borana showed 2 of 7. The CoS
   ruled a hidden chip worse than live (19 Oct 2026); a chip row is now never a scroller at these widths.
   Built on the credit-strip lift: sizeNavRow only runs with html[data-creditstrip] on. */
@media(min-width:1025px) and (max-width:1280px) and (hover:hover){
  html[data-navrow] #caption{max-width:520px;bottom:calc(26px + var(--credit-h,0px))}
  html[data-navrow] #cap-line{max-width:none}
  html[data-navrow] #chapters{left:var(--nav-l,560px);right:auto;width:var(--nav-w,auto);transform:none;max-width:none;
    flex-wrap:wrap;justify-content:flex-start}
  html[data-navrow] #chapters .chip{flex:none}
  /* the orbit hint was centred across the caption's new column; it moves to the top of the open map,
     between the caption column and the rail, as the phone tier already does. */
  html[data-navrow] #hint{top:72px;bottom:auto;left:560px;right:308px;transform:none;white-space:normal;text-align:center}
}

/* THE STAT ROW STACKS ONLY WHEN IT WRAPS (vista-template #33): src/capfit.js (fitStats) lays the row out as a
   row and sets .stacked only if a stat left the first line, the row overflows, or a value broke. */
#cap-stats.stacked{flex-direction:column;gap:10px;flex-wrap:nowrap}
#cap-stats.stacked > div{padding:0;border-left:0}
