:root {
  /* Tells the browser which palette to paint native UI in - select option
     lists, checkboxes, scrollbars, focus rings, text-field carets. Without
     it the UA paints every one of those light no matter what the tokens
     below say, which is what made the agency filter's dropdown unreadable
     in dark mode on mobile: the popup came up on the platform's white
     ground while the option text inherited the near-white --ink. */
  color-scheme: light;

  /* Colour by role, not by appearance. --ink rather than --text-dark so the
     name still makes sense when it is light, and so it cannot be confused
     with the --text-* type scale above. */
  --bg: #ffffff;
  --surface: #ffffff;

  /* Status colours, as roles rather than literals. These are interface
     meaning - "this succeeded", "this is full", "this is a beta control" -
     and are separate from the --accent-* set, which encodes *data* (which
     agency runs a campground) and deliberately does not change per theme.

     Each is a foreground paired with the tint it sits on, so a chip can be
     built from the pair and stay legible when the dark block swaps both. */
  --ok:            #1f7a3d;
  --ok-tint:       #e3f3e6;
  --danger:        #a8384a;
  --danger-tint:   #f3e6e6;
  --notice:        #8a5a12;
  --notice-tint:   #fdf3e2;
  --notice-edge:   #e0a53a;

  /* Translucent panels that float over the map or the hero photo: the site
     header, the map hint, the layers panel, the region pills. These need
     their own token rather than --surface because they are deliberately
     see-through, and an opaque token would flatten the blur behind them.

     They are the mirror of the status colours above and broke the same way:
     each one hardcoded white and then set "color: var(--ink)", so in dark
     mode the panel stayed white while the text went near-white. Two alphas
     because the header and hint sit at 0.92 and the panels at 0.95; keeping
     both preserves the light theme exactly as it was. */
  --overlay:       rgba(255, 255, 255, 0.95);
  --overlay-soft:  rgba(255, 255, 255, 0.92);

  /* Type scale. Derived from the 23 distinct font sizes the
     stylesheet already used - the dense clusters sat at .85, .9
     and .8rem, so those became steps rather than being snapped away. */
  --text-3xs: .7rem;
  --text-2xs: .78rem;
  --text-xs: .85rem;
  --text-sm: .92rem;
  --text-md: 1rem;
  --text-lg: 1.15rem;
  --text-xl: 1.35rem;
  --text-2xl: 1.5rem;
  --text-3xl: 1.75rem;

  /* Spacing scale, derived the same way. .5rem was used 32 times,
     .75 / .9 / 1rem 17 times each. */
  --sp-1: .15rem;
  --sp-2: .3rem;
  --sp-3: .5rem;
  --sp-4: .75rem;
  --sp-5: 1rem;
  --sp-6: 1.5rem;
  --sp-7: 2rem;
  --sp-8: 2.5rem;
  --sp-9: 3.5rem;
  --sp-10: 5rem;
  --accent-blue: #1c6e8c;
  --accent-blue-dark: #14536a;
  --accent-green: #2e8b40;
  --accent-tva: #d67e26;
  --accent-nps: #8b5e34;
  --accent-blm: #6b4c9a;
  --accent-fs: #a8384a;
  --accent-pc: #3457a6;
  --accent-bcparks: #0e8a7d;
  /* One color for all state park systems (was 4 separate ones - FLSP/TNSP/
     KSSP/MOSP - before the STATEPARKS consolidation) - kept Florida's
     original pink since it's the most visually distinct from every other
     agency hue still in use. */
  --accent-statepark: #c2447a;
  /* City & Municipal Parks - a brand-new, manually-curated agency (see
     scripts/fetch-city-parks.py). Civic cyan-teal, clearly apart from
     accent-bcparks' darker green-teal and accent-pc's steel blue - low
     collision risk in practice since this category starts at a couple of
     pins, not a full-state dataset. */
  --accent-cityparks: #176cb8;
  /* Tribal Lands - a brand-new, manually-curated agency (see
     scripts/fetch-tribal-camping.py), distinct from accent-cityparks'
     cyan and every other warm hue already in use (accent-tva #d9822b,
     accent-fws #b9861f, accent-fs #a8384a). Clay/terracotta, evoking the
     Southwest without leaning on a stereotyped palette. */
  --accent-tribal: #b5602f;
  /* Canadian city/municipal parks ("CACITYPARKS") - a distinct agency
     from accent-cityparks (see site/app.js's AGENCY_LABELS comment for
     why Canada gets its own codes rather than merging into the US ones).
     A clear, saturated red rather than another blue/green/teal - reads
     as distinctly Canadian without leaning on the flag literally, and
     stays apart from accent-fs's muted brick and accent-statepark's
     pink-magenta. */
  --accent-cacityparks: #d64545;
  /* Canadian First Nations lands ("CATRIBAL") - distinct from
     accent-tribal's terracotta and every other warm hue in use. Olive-
     moss green, apart from accent-green's brighter pure green and
     accent-bcparks'/accent-cityparks' teal/cyan family. */
  --accent-catribal: #6e7b3f;
  /* Fish & Wildlife Service - a brand-new agency as of the Cabins feature
     (Kenai NWR's public-use cabins, etc.), never had a color before. Amber-
     gold rather than another green/blue/brown - every warm-neutral and
     cool hue nearby was already spoken for by an existing agency. */
  --accent-fws: #b9861f;
  /* Kansas State Parks cabins (Cabins Phase 2, the "KSSPC" agency code -
     see site/app.js's AGENCY_LABELS comment for why this is a distinct
     code from the campsite-map's "KSSP"/STATEPARKS). A sunflower/wheat
     olive-gold - a nod to Kansas's state flower - chosen to sit clearly
     apart from every other hue already in use here, including the FWS
     amber-gold right above it. */
  --accent-ksspc: #7c8f2e;
  /* Kentucky State Parks cabins (Cabins Phase 2's second state - the "KYSPC"
     agency code, see site/app.js's AGENCY_LABELS comment for why this is
     distinct from the campsite-map's "KYSP"/STATEPARKS). A copper/bourbon-
     barrel amber, a nod to Kentucky's Bourbon Trail, chosen to sit clearly
     apart from every other hue already in use here, including the
     olive-gold KSSPC and amber-gold FWS right above it. */
  --accent-kyspc: #a85832;
  /* Tennessee State Parks cabins (Cabins Phase 2's third state - the
     "TNSPC" agency code, see site/app.js's AGENCY_LABELS comment for why
     this is distinct from the campsite-map's "TNSP"/STATEPARKS). A deep
     forest green, a nod to Tennessee's CCC/WPA-era mountain state parks,
     chosen to sit clearly apart from every other hue already in use here,
     including the olive-gold KSSPC and copper KYSPC right above it. */
  --accent-tnspc: #2f6b4f;
  /* Georgia State Parks cabins (Cabins Phase 2's fourth state - the
     "GASPC" agency code, see site/app.js's AGENCY_LABELS comment for why
     this is distinct from the campsite-map's "GASP"/STATEPARKS). A bright
     peach-coral, a nod to Georgia's state fruit, kept noticeably lighter
     and more saturated than the darker earth-toned KSSPC/KYSPC/TNSPC
     accents right above it so it still reads as a distinct color at a
     glance rather than blending into that same brown/olive family. */
  --accent-gaspc: #e5724b;
  /* New Mexico State Parks cabins (Cabins Phase 2's seventh state - the
     "NMSPC" agency code, see site/app.js's AGENCY_LABELS comment for why
     this is distinct from the campsite-map's "NMSP"/STATEPARKS). A bright
     turquoise-sky blue, a nod to New Mexico's turquoise/big-sky palette,
     chosen (and re-checked against every accent above right before use) to
     sit clearly apart from the muted teal accent-bcparks and the steel-blue
     accent-blue/accent-pc already in use nearby. */
  --accent-nmspc: #0f6dc4;
  /* Arizona State Parks cabins (Cabins Phase 2's sixth state - the "AZSPC"
     agency code, see site/app.js's AGENCY_LABELS comment for why this is
     distinct from the campsite-map's "AZSP"/STATEPARKS). An orchid/prickly-
     pear-blossom magenta-purple, chosen (and re-checked against every
     accent above right before use, including a possible concurrent UTSPC
     addition) to sit roughly equidistant between the muted purple
     accent-blm and the pink accent-statepark, rather than blending into
     either, while staying clearly apart from the earth-toned KSSPC/KYSPC/
     TNSPC/GASPC accents and NMSPC's sky-blue right above it. */
  --accent-azspc: #96389c;
  /* Utah State Parks cabins (Cabins Phase 2's fifth state - the "UTSPC"
     agency code, see site/app.js's AGENCY_LABELS comment for why this is
     distinct from the campsite-map's "UTSP"/STATEPARKS). A muted, desaturated
     canyon-shale slate-blue - deliberately grayer/less saturated than every
     other accent above (all vivid) so it reads as clearly distinct at a
     glance rather than adding a fourth entry to the already-crowded blue/
     teal/purple family (accent-blue, accent-pc, accent-bcparks, accent-blm,
     accent-nmspc, accent-azspc) - re-checked against every accent above
     right before use, including NMSPC/AZSPC's own recent additions. */
  --accent-utspc: #4d7298;
  /* Florida State Parks cabins (Cabins Phase 2's eighth state - the "FLSPC"
     agency code, see site/app.js's AGENCY_LABELS comment for why this is
     distinct from the campsite-map's "FLSP"/STATEPARKS). A vivid flame red,
     re-checked against every accent above right before use - deliberately
     NOT another entry in the already-crowded blue/teal/purple/earth-tone
     family (accent-blue, accent-pc, accent-bcparks, accent-blm, accent-nmspc,
     accent-azspc, accent-utspc) or the olive/copper/coral cluster (ksspc,
     kyspc, tnspc, gaspc) - more saturated and purely red than accent-fs's
     dustier brick-crimson and noticeably deeper/less orange than gaspc's
     light coral, so it reads as its own color at a glance. */
  --accent-flspc: #d1592e;
  /* Oklahoma State Parks cabins (Cabins Phase 2's ninth state - the "OKSPC"
     agency code, see site/app.js's AGENCY_LABELS comment for why this is
     distinct from the campsite-map's "OKSP"/STATEPARKS). A golden wheat-
     field yellow, a nod to Oklahoma's wheat harvest, re-checked against
     every accent above right before use: it sits in the real gap between
     accent-fws's browner amber-gold (#b9861f) and accent-ksspc's yellow-
     green olive (#7c8f2e), but is pushed noticeably more saturated/bright
     than either so it reads as true "gold/yellow" rather than "brown" or
     "olive" at a glance - deliberately not another entry in the already-
     crowded red family (accent-fs, accent-flspc), pink (accent-statepark),
     purple family (accent-blm, accent-azspc), or blue/teal family
     (accent-blue, accent-pc, accent-bcparks, accent-nmspc, accent-utspc). */
  --accent-okspc: #b38f00;
  /* West Virginia State Parks cabins (Cabins Phase 2's tenth state - the
     "WVSPC" agency code, see site/app.js's AGENCY_LABELS comment for why
     this is distinct from the campsite-map's "WVSP"/STATEPARKS). A fresh
     spring-fern green, a nod to West Virginia's densely forested Allegheny
     highlands, re-checked against every accent above right before use: it
     sits in the real ~60-degree gap (the single largest unclaimed span on
     the wheel) between accent-ksspc's yellow-olive (#7c8f2e) and
     accent-green's true forest green (#2e8b40), pushed toward true
     yellow-green/fern rather than blending into either - deliberately not
     another entry in the already-crowded blue/teal/purple family
     (accent-blue, accent-pc, accent-bcparks, accent-nmspc, accent-azspc,
     accent-utspc), the earth-toned KSSPC/KYSPC cluster, or the red/pink
     family (accent-fs, accent-flspc, accent-statepark). */
  --accent-wvspc: #57a630;
  /* Missouri State Parks cabins (Cabins Phase 2's twelfth state - the
     "MOSPC" agency code, see site/app.js's AGENCY_LABELS comment for why
     this is distinct from the campsite-map's "MOSP"/STATEPARKS). A redbud-
     blossom rose-magenta, re-checked against every accent above right
     before use (including WVSPC/TXSPC's own recent/concurrent additions):
     it sits in the real gap between accent-azspc's purple-magenta
     (#96389c) and accent-statepark's pink (#c2447a), pushed toward true
     rose rather than blending into either - deliberately not another entry
     in the already-crowded blue/teal/purple family (accent-blue,
     accent-pc, accent-bcparks, accent-nmspc, accent-blm, accent-azspc,
     accent-utspc), the red/coral/gold cluster (accent-fs, accent-flspc,
     accent-gaspc, accent-kyspc, accent-tva, accent-fws, accent-okspc), or
     the green family (accent-green, accent-tnspc, accent-ksspc,
     accent-wvspc). */
  --accent-mospc: #a6308b;
  /* Texas State Parks cabins (Cabins Phase 2's eleventh state - the "TXSPC"
     agency code, see site/app.js's AGENCY_LABELS comment for why this is
     distinct from the campsite-map's "TXSP"/STATEPARKS). A deep bluebonnet
     indigo, a nod to Texas's state flower, re-checked against every accent
     above right before use (including WVSPC/MOSPC's own recent/concurrent
     additions): it sits in the real gap between accent-pc's steel blue
     (#3457a6) and accent-blm's purple (#6b4c9a) - the single largest
     unclaimed span on the wheel at this point - pushed darker/more
     saturated than either neighbor so it reads as its own true indigo-
     violet rather than blending into the blue or purple family on either
     side of it. */
  --accent-txspc: #363399;
  /* Virginia State Parks cabins (Cabins Phase 2's thirteenth state - the
     "VASPC" agency code, see site/app.js's AGENCY_LABELS comment for why
     this is distinct from the campsite-map's "VASP"/STATEPARKS). A
     periwinkle-violet, a nod to the Virginia bluebell (Mertensia virginica,
     a wildflower native to Virginia's floodplains and literally named for
     the state), re-checked against every accent above right before use: it
     sits in the real gap between accent-blm's muted purple (#6b4c9a) and
     accent-azspc's magenta-orchid (#96389c) - the single largest unclaimed
     span on the wheel at this point - pushed noticeably lighter and more
     blue-leaning than either neighbor so it reads as its own true
     periwinkle rather than blending into the purple/magenta family beside
     it (accent-blm, accent-azspc, accent-mospc, accent-statepark) or the
     indigo accent-txspc just above. */
  --accent-vaspc: #a86bc7;
  /* Colorado State Parks cabins (Cabins Phase 2's fourteenth state - the
     "COSPC" agency code, see site/app.js's AGENCY_LABELS comment for why
     this is distinct from the campsite-map's "COSP"/STATEPARKS). A vivid
     aspen-grove green, a nod to Colorado's summer aspen groves, re-checked
     against every accent above right before use: it sits in the real gap
     between accent-wvspc's fern green (#57a630) and accent-green's forest
     green (#2e8b40) - the single largest unclaimed span on the wheel at
     this point - pushed more saturated/vivid than either neighbor so it
     reads as its own true spring green rather than blending into either. */
  --accent-cospc: #28a920;
  /* South Carolina State Parks cabins ("SCSPC" - see site/app.js's
     AGENCY_LABELS comment for why this is distinct from the campsite-map's
     "SCSP"). Re-checked against every accent above right before use: it
     sits in the real gap between accent-pc's blue (#3457a6, hue ~220) and
     accent-txspc's indigo (#363399, hue ~245) - the largest unclaimed span
     on the wheel at this point - pushed brighter/lighter than either
     neighbor (a periwinkle rather than a dark navy) so it reads as its own
     color instead of blending into either. */
  --accent-scspc: #4a63d8;
  /* North Carolina State Parks cabins (Cabins Phase 2's fifteenth state - the
     "NCSPC" agency code, see site/app.js's AGENCY_LABELS comment for why
     this is distinct from the campsite-map's "NCSP"/STATEPARKS). A Blue
     Ridge rhododendron violet-blue, a nod to the Catawba rhododendron blooms
     found along North Carolina's mountain state parks (Hanging Rock, Stone
     Mountain, Gorges), re-checked against every accent above right before
     use (including VASPC/COSPC/SCSPC's own recent/concurrent additions): it
     sits in the real gap between accent-txspc's indigo (#363399, hue ~242)
     and accent-blm's purple (#6b4c9a, hue ~264) - the largest unclaimed span
     on the wheel at this point after VASPC/COSPC/SCSPC each claimed the
     three gaps this build's own first pass had identified - pushed more
     saturated/vivid than either neighbor so it reads as its own true
     violet-blue rather than blending into the indigo or purple family on
     either side of it. */
  --accent-ncspc: #563fa6;
  /* Pennsylvania State Parks cabins (Cabins Phase 2's next state - the
     "PASPC" agency code, see site/app.js's AGENCY_LABELS comment for why
     this is distinct from the campsite-map's "PASP"/STATEPARKS). A dusty
     khaki-bronze, a nod to the weathered timber of PA's own 1930s CCC-built
     rustic cabins, re-checked against every accent above right before use
     (including NCSPC's own recent/concurrent addition): it sits in the real
     gap between accent-okspc's bright gold (#d4aa00, hue ~46) and
     accent-ksspc's olive-green (#7c8f2e, hue ~72) - deliberately pushed much
     less saturated than either neighbor (a muted khaki rather than a vivid
     gold or green) so it reads as its own dusty, weathered tone rather than
     blending into either. */
  --accent-paspc: #918e30;
  /* New York State Parks cabins (Cabins Phase 2's next state - the
     "NYSPC" agency code, see site/app.js's AGENCY_LABELS comment for why
     this is distinct from the campsite-map's "NYSP"/STATEPARKS). Re-checked
     against every accent above right before use (including PASPC's own
     recent/concurrent addition, plus whatever Michigan/Minnesota's sibling
     builds may have just added): a cool granite-slate blue-gray, evoking
     the Adirondack/Catskill mountain stone this state's cabin program sits
     in, deliberately desaturated rather than another vivid hue - the
     numerically farthest color from this entire crowded palette that was
     checked (minimum distance to any existing accent above roughly double
     the next-best candidate tried), so it reads as its own quiet, cool tone
     rather than blending into any of the palette's many warm
     browns/golds/reds or saturated blues/greens/purples. */
  --accent-nyspc: #4f6373;
  /* Michigan cabins/yurts/lodges (Cabins Phase 2). Re-checked against every
     other accent above right before picking - this crowded palette's
     colors span a wide hue/lightness/saturation range already, so this
     value was chosen by numerically maximizing minimum perceptual (Lab)
     distance to all of them rather than eyeballing a gap. A fresh
     teal-green, distinct from both existing teals (bcparks, tnspc) in
     lightness/saturation, evocative of Michigan's Great Lakes shoreline
     without duplicating var(--accent-nmspc)'s brighter cyan. */
  --accent-midnrc: #28a580;
  /* Minnesota cabins/camper cabins/yurts/guest houses (Cabins Phase 2,
     "MNSPC"). Re-checked against every accent above right before picking,
     including NYSPC's/MIDNRC's own recent/concurrent additions - this is a
     very crowded palette by now, so the choice was verified numerically
     (CIE Lab distance to every existing accent, not just an eyeballed hue
     gap): a deep maroon-raspberry, a nod to the University of Minnesota's
     maroon-and-gold colors (and to the deep red of wild rice and Minnesota-
     grown cranberries), sitting in the real gap between accent-fs's dustier
     brick-crimson and accent-statepark's rose-pink - both close enough to
     have been candidates, but this value pushed distinctly deeper/less pink
     than either so it reads as its own true maroon rather than blending
     into the existing red/pink cluster. */
  --accent-mnspc: #d1265a;
  /* Indiana State Parks cabins (Cabins Phase 2, "INSPC" - see
     site/app.js's AGENCY_LABELS comment for why this is distinct from the
     campsite-map's "INSP"/STATEPARKS). This palette is now large enough
     (30+ agency colors) that no hue-only gap reads as clearly distinct
     anymore, so this value was picked the same numeric way MIDNRC/MNSPC/
     WISPC's own comments describe: maximizing minimum CIE Lab distance to
     every existing accent above, re-checked immediately before use
     (including MNSPC's and WISPC's own recent/concurrent additions) and
     again immediately before commit. A deep wine/plum, sitting almost
     exactly equidistant between accent-fs's dusty brick-crimson and
     accent-statepark's pink (each ~28.5 Lab units away, the two nearest
     colors in the whole palette) but pushed noticeably darker/less pink
     than either so it reads as its own true wine tone rather than
     blending into the existing red or pink family. */
  --accent-inspc: #6f2a50;
  /* Wisconsin State Parks cabins/yurts (Cabins Phase 2, "WISPC"). Re-checked
     against every accent above right before picking, including whatever
     Minnesota/New York's sibling builds may have just added - this is a
     very crowded palette by now (13+ entries already sit in the blue/
     purple/teal family alone), so rather than add a 14th, this value picks
     a genuinely fresh material: a true neutral warm-gray, near-zero
     saturation, unlike anything else in this otherwise all-saturated
     palette - a nod to Wisconsin's own nickname, the Badger State (and to
     the limestone bluffs at Blue Mound/High Cliff/Devil's Lake and the
     glacial stone throughout the Kettle Moraine, where several of this
     file's own cabins sit). Verified numerically (CIE Lab distance to every
     existing accent): its nearest neighbor is accent-nyspc's cooler,
     blue-leaning granite-slate, but pushed warmer and less blue so it reads
     as a true neutral gray rather than another slate-blue. */
  --accent-wispc: #828680;
  /* Ohio State Parks cabins/yurts/cabents (Cabins Phase 2, "OHSPC" - see
     site/app.js's AGENCY_LABELS comment for why this is distinct from the
     campsite-map's "OHSP"/STATEPARKS). Re-checked against every other
     accent above right before picking, including whatever Wisconsin/
     Indiana's sibling builds may have just added - this is now a very
     crowded, mostly-saturated palette, so this value was picked the same
     numeric way MIDNRC/MNSPC/INSPC/WISPC's own comments describe:
     maximizing minimum CIE Lab distance to every existing accent above. A
     rich indigo-violet blue - the single most under-claimed hue region left
     in this palette (its nearest neighbors, TXSPC's indigo and NCSPC's
     violet-blue, are each ~37+ Lab units away, comfortably past the ~28.5
     bar INSPC's own comment set) - pushed more saturated/vivid than either
     neighbor so it reads as its own true blue-violet. Re-checked again
     immediately before commit per this session's own instructions, since
     other state cabin builds were running concurrently in this same repo. */
  --accent-ohspc: #2320c9;
  /* Illinois State Parks cabins (Cabins Phase 2, "ILSPC" - see
     site/app.js's AGENCY_LABELS comment for why this is distinct from the
     campsite-map's "ILSP"/STATEPARKS). Re-checked against every accent
     above right before picking, including Ohio's own just-added indigo-
     violet - this is a very crowded, mostly-saturated palette (34 accents
     including that one), so this value was picked the same numeric way
     MIDNRC/MNSPC/INSPC/WISPC/OHSPC's own comments describe: maximizing
     minimum CIE Lab distance to every existing accent above (verified with
     a small script, not eyeballed). A golden yellow-green, evoking ripening
     Illinois corn/soybean fields (the Prairie State's own signature crop
     colors) - the most under-claimed hue region left in this palette even
     after checking the loudest, most-saturated candidates in that region;
     its nearest neighbor, PASPC's muted olive, is over 23 Lab units away,
     comfortably distinct. Re-checked again immediately before commit per
     this session's own instructions, since other state cabin builds
     (Ohio, Washington) were running concurrently in this same repo. */
  --accent-ilspc: #92992d;
  /* Washington State Parks cabins/yurts/platform tents/teepees/vacation
     houses (Cabins Phase 2, "WASPC" - distinct from the campsite map's own
     "WASP", same collision-avoidance reasoning as every other *SPC entry
     above). Re-checked fresh against every accent above right before
     picking (36 entries at that point, including whatever Indiana/Ohio's
     own concurrent sibling builds had just added) and again immediately
     before commit. Picked the same numeric way MIDNRC/MNSPC/INSPC/WISPC/
     OHSPC's own comments describe: an RGB-space grid search maximizing
     minimum distance to every existing accent, constrained to a similar
     medium saturation/value range as the rest of this palette (a very
     light pastel scored higher in an unconstrained search but would read
     poorly as a marker pin with white text). A jade/teal-green sitting in
     the real gap between this palette's pure-green cluster (accent-green,
     accent-wvspc, accent-cospc) and its blue-teal cluster (accent-blue,
     accent-bcparks) - nearest neighbors are accent-green and accent-midnrc,
     each about 63-64 RGB units away. */
  --accent-waspc: #4ba671;
  /* Oregon State Parks cabins/yurts (Cabins Phase 2, "ORSPC" - distinct
     from the campsite map's own "ORSP", same collision-avoidance reasoning
     as every other *SPC entry above). Re-checked fresh against every
     accent above right before picking (38 entries at that point, including
     Illinois/Washington's own already-merged entries and whatever Ohio's
     concurrent sibling build had just added) and again immediately before
     commit per this session's own instructions. Picked the same numeric
     way MIDNRC/MNSPC/INSPC/WISPC/OHSPC/ILSPC/WASPC's own comments
     describe: a script-driven CIE Lab grid search maximizing minimum
     distance to every existing accent, constrained to a similar medium
     saturation/value range as the rest of this palette. A vivid violet-
     purple - this palette's purple/magenta cluster (accent-blm,
     accent-azspc, accent-mospc, accent-ncspc, accent-vaspc) was the least-
     crowded remaining region once every blue/green/red/yellow hue had a
     close neighbor; nearest neighbors are accent-ohspc's indigo-blue and
     accent-azspc's magenta-purple, each about 26-27 Lab units away -
     closer than this project's earlier, less-crowded picks but still a
     clearly separate, distinguishable hue from both. */
  --accent-orspc: #8a26bf;
  /* California State Parks cabins/yurts/cottages (Cabins Phase 2, "CASPC" -
     distinct from the campsite map's own "CASP", same collision-avoidance
     reasoning as every other *SPC entry above). Re-checked fresh against
     every accent above right before picking (39 entries at that point,
     including Illinois/Washington/Oregon's own already-merged entries) and
     again immediately before commit, since sibling state builds (Illinois,
     Oregon) were running concurrently in this same repo. Picked the same
     numeric way MIDNRC/MNSPC/INSPC/WISPC/OHSPC/ILSPC/WASPC/ORSPC's own
     comments describe: a script-driven CIE Lab grid search maximizing
     minimum distance to every existing accent, constrained to a similar
     medium saturation/value range as the rest of this palette. A bright
     turquoise/aqua - a nod to the Pacific coastline many of this file's own
     cabins sit along (Crystal Cove, Steep Ravine, Sue-meg, Prairie Creek,
     Jedediah Smith, Hendy Woods, Richardson Grove) - sitting in this
     palette's least-crowded teal/cyan region (only accent-bcparks and
     accent-nmspc nearby); nearest neighbor is accent-bcparks at about 22
     Lab units away, comfortably distinguishable from both. */
  --accent-caspc: #3da0a0;
  /* New Jersey State Parks cabins (Cabins Phase 2, "NJSPC" - distinct from
     the campsite map's own "NJSP", same collision-avoidance reasoning as
     every other *SPC entry above). Re-checked fresh against every accent
     above right before picking (40 entries at that point, including
     California's own just-merged turquoise/aqua CASPC, which ruled out this
     build's first-choice cyan candidate - it landed only ~12 Lab units from
     CASPC, a real near-collision caught by re-reading the palette live
     rather than reusing an earlier, now-stale scan) and again immediately
     before commit, since sibling state builds (Oregon, California) were
     running concurrently in this same repo. Picked the same numeric way
     MIDNRC/MNSPC/INSPC/WISPC/OHSPC/ILSPC/WASPC/ORSPC/CASPC's own comments
     describe: a script-driven CIE Lab grid search maximizing minimum
     distance to every existing accent. A vivid magenta-pink, this palette's
     single most under-claimed hue region once cyan/teal was ruled out by
     CASPC; nearest neighbors are accent-orspc's violet-purple and
     accent-mospc's rose-magenta, each about 33-34 Lab units away - the
     largest margin of any entry added so far this session. */
  --accent-njspc: #e600c3;
  /* Idaho State Parks cabins (Cabins Phase 2, "IDSPC" - distinct from the
     campsite map's own "IDSP", same collision-avoidance reasoning as every
     other *SPC entry above). Re-checked fresh against every accent above
     right before picking (41 entries at that point, including New Jersey's
     just-merged magenta-pink NJSPC and California's turquoise CASPC) via
     the same script-driven CIE Lab grid search MIDNRC/MNSPC/INSPC/WISPC/
     OHSPC/ILSPC/WASPC/ORSPC/CASPC/NJSPC's own comments describe, maximizing
     minimum distance to every existing accent. A clear sky/azure blue -
     this palette's blues (accent-blue, accent-pc, accent-nmspc, accent-
     utspc) all sit noticeably deeper/muted; this one is brighter and more
     saturated. Nearest neighbor is accent-pc's Parks Canada blue, about 25
     Lab units away - re-verified again immediately before commit since
     sibling state builds were running concurrently in this same repo. */
  --accent-idspc: #1f8fdf;
  /* Massachusetts DCR cabins/yurts (Cabins Phase 2, "MADCRC" - distinct from
     the campsite map's own "MADCR", same collision-avoidance reasoning as
     every other *SPC/*C entry above). Re-checked fresh against every accent
     above right before picking (42 entries at that point, including Idaho's
     just-merged sky-blue IDSPC) via the same script-driven CIE Lab grid
     search MIDNRC/MNSPC/INSPC/WISPC/OHSPC/ILSPC/WASPC/ORSPC/CASPC/NJSPC/
     IDSPC's own comments describe, maximizing minimum distance to every
     existing accent. A vivid emerald/spring green, a nod to Massachusetts's
     densely forested state forests (Mohawk Trail, October Mountain, the
     Berkshires) where most of this file's own cabins/yurts sit. This
     palette's green family is already the most crowded region (green,
     wvspc, cospc, tnspc, midnrc, waspc), but re-checked numerically anyway,
     matching this project's established precedent (ORSPC/NJSPC/IDSPC all
     landed in an already-populated region too): nearest neighbors are
     accent-green and accent-wvspc, each about 23.4-23.5 Lab units away -
     comfortably past the ~22 Lab unit bar CASPC's own comment already
     established as acceptable. Re-checked again immediately before commit
     per this session's own instructions, since sibling state builds (Idaho,
     Maryland) were running concurrently in this same repo. */
  --accent-madcrc: #11a85a;
  /* Nebraska State Parks cabins/cabooses/glamping (Cabins Phase 2, "NESPC" -
     distinct from the campsite map's own "NESP", same collision-avoidance
     reasoning as every other *SPC/*C entry above). Re-checked fresh against
     every accent above right before picking (43 entries at that point,
     including Massachusetts's just-merged emerald-green MADCRC and Idaho's
     sky-blue IDSPC) via the same script-driven CIE Lab grid search
     MIDNRC/MNSPC/INSPC/WISPC/OHSPC/ILSPC/WASPC/ORSPC/CASPC/NJSPC/IDSPC/
     MADCRC's own comments describe, maximizing minimum distance to every
     existing accent, constrained to a similar medium saturation/value range
     as the rest of this palette. A dusty rose/mauve pink - this palette's
     pink/magenta cluster (accent-statepark, accent-mospc, accent-mnspc,
     accent-njspc, accent-vaspc) is already crowded, but re-checked
     numerically anyway rather than assumed uncrowded, matching this
     project's established precedent (ORSPC/NJSPC/IDSPC/MADCRC all landed in
     already-populated regions too): nearest neighbors are accent-statepark
     (about 28 Lab units away) and accent-vaspc (about 30 units away),
     clearly past the ~22-23 unit bar CASPC/MADCRC's own comments already
     established as acceptable. Re-checked again immediately before commit
     per this session's own instructions, since sibling state builds may be
     running concurrently in this same repo. */
  --accent-nespc: #c979a8;
  /* Maryland State Parks cabins/cottages/yurts/glamping (Cabins Phase 2,
     "MDSPC" - distinct from the campsite map's own "MDSP", same
     collision-avoidance reasoning as every other *SPC/*C entry above).
     Re-checked fresh against every accent above right before picking (41
     entries at that point, including Nebraska's just-merged dusty-rose
     NESPC) via the same script-driven CIE Lab grid search MIDNRC/MNSPC/
     INSPC/WISPC/OHSPC/ILSPC/WASPC/ORSPC/CASPC/NJSPC/IDSPC/MADCRC/NESPC's own
     comments describe, maximizing minimum distance to every existing
     accent. A vivid chartreuse/lime-green - this palette's yellow-green
     region (accent-cospc, accent-ilspc, accent-wvspc, accent-ksspc) is
     already populated, but re-checked numerically anyway rather than
     assumed uncrowded, matching this project's established precedent
     (ORSPC/NJSPC/IDSPC/MADCRC/NESPC all landed in already-populated regions
     too): nearest neighbor is accent-cospc, about 28.5 Lab units away -
     comfortably past the ~22-23 unit bar CASPC/MADCRC/NESPC's own comments
     already established as acceptable, and the largest margin found this
     pass. Re-checked again immediately before commit per this session's own
     instructions, since sibling state builds may be running concurrently in
     this same repo. */
  --accent-mdspc: #72a007;
  /* Iowa State Parks cabins/camper cabins/yurts (Cabins Phase 2, "IASPC" -
     distinct from the campsite map's own "IASP", same collision-avoidance
     reasoning as every other *SPC/*C entry above). Re-checked fresh
     against every accent above right before picking (45 entries at that
     point, including Nebraska's already-merged dusty-rose NESPC) via a
     script-driven CIE Lab grid search maximizing minimum distance to every
     existing accent, constrained to the same medium saturation/value range
     as the rest of this palette. A wheat/cornfield gold-tan, a nod to
     Iowa's own farmland - the numerically most distinct candidate found
     (its nearest neighbor, over 30 Lab units away, is the largest margin
     of any entry added so far in this whole palette, comfortably past the
     ~22-23 unit bar prior additions established as acceptable). Re-checked
     again immediately before commit per this session's own instructions,
     since sibling state cabin builds may be running concurrently in this
     same repo. */
  --accent-iaspc: #bb8b3e;
  /* Connecticut State Parks cabins (Cabins Phase 2, "CTSPC" - distinct from
     the campsite map's own "CTSP", same collision-avoidance reasoning as
     every other *SPC/*C entry above). Re-checked fresh against every accent
     above right before picking (46 entries at that point, including Iowa's
     just-merged wheat-tan IASPC, which ruled out this build's first-choice
     sandy-tan candidate - it landed only a few Lab units from IASPC, a real
     near-collision caught by re-reading the palette live rather than
     reusing an earlier, now-stale scan) via the same script-driven CIE Lab
     grid search MIDNRC/MNSPC/.../NESPC/MDSPC/IASPC's own comments describe,
     maximizing minimum distance to every existing accent. A soft
     sage/pistachio green - this palette's green family (green, wvspc,
     cospc, tnspc, midnrc, waspc, madcrc, ksspc, ilspc, paspc) is already the
     most crowded region in the whole palette, but re-checked numerically
     anyway rather than assumed uncrowded, matching this project's
     established precedent (every *SPC/*C entry from ORSPC onward landed in
     an already-populated region and was kept anyway once the numeric bar
     was cleared): nearest neighbors are accent-ilspc and accent-wvspc, each
     about 25 Lab units away - comfortably past the ~22-23 unit bar prior
     additions established as acceptable. Re-checked again immediately
     before commit per this session's own instructions, since sibling state
     cabin builds (Maryland, Nebraska) were running concurrently in this
     same repo. */
  --accent-ctspc: #68a32f;
  /* New Hampshire State Parks cabins/yurts (Cabins Phase 2, "NHSPC" -
     distinct from the campsite map's own "NHSP", same collision-avoidance
     reasoning as every other *SPC/*C entry above). Re-checked fresh against
     every accent above right before picking (47 entries at that point,
     including Iowa's and Connecticut's own just-merged IASPC/CTSPC) via the
     same script-driven CIE Lab grid search MIDNRC/MNSPC/.../NESPC/MDSPC/
     IASPC/CTSPC's own comments describe, maximizing minimum distance to
     every existing accent, constrained to the same medium saturation/value
     range as the rest of this palette. A dusty brick-rose/mauve, a nod to
     the weathered red brick of New Hampshire's old mill towns near several
     of this file's own parks - the pink/rose/wine region of this palette
     (accent-nespc, accent-inspc, accent-fs, accent-statepark) is already
     populated, but re-checked numerically anyway rather than assumed
     uncrowded, matching this project's established precedent: nearest
     neighbor is accent-nespc's dusty rose, about 26 Lab units away -
     comfortably past the ~22-23 unit bar prior additions established as
     acceptable. Re-checked again immediately before commit per this
     session's own instructions, since sibling state cabin builds
     (Connecticut, Iowa) were running concurrently in this same repo. */
  --accent-nhspc: #98626a;
  /* Maine State Parks cabins (Cabins Phase 2, "MESPC" - distinct from the
     campsite map's own "MESP", same collision-avoidance reasoning as every
     other *SPC/*C entry above). Re-checked fresh against every accent above
     right before picking (48 entries at that point) via a script-driven CIE
     Lab grid search over hue/saturation/value maximizing minimum distance to
     every existing accent, matching this palette's established precedent -
     nearest neighbor is accent-ksspc's sunflower-olive, about 24.7 Lab units
     away, past the ~22-23 unit bar prior additions established as
     acceptable. A muted sage/moss green, a nod to the Camden Hills pine
     woods this file's one real cabin sits in. Re-checked again immediately
     before commit per this session's own instructions, since sibling state
     cabin builds (Vermont) were running concurrently in this same repo. */
  --accent-mespc: #899968;
  /* Louisiana State Parks cabins (Cabins Phase 2, "LASPC" - distinct from
     the campsite map's own "LASP", same collision-avoidance reasoning as
     every other *SPC/*C entry above). Re-checked fresh against every
     accent above right before picking (50 entries at that point) via a
     script-driven CIE Lab grid search over hue/saturation/value
     maximizing minimum distance to every existing accent, matching this
     palette's established precedent - nearest neighbor is accent-fs'
     brick red, about 29.2 Lab units away, comfortably past the ~22-23
     unit bar prior additions established as acceptable. A deep brick/
     terracotta red, a nod to French Quarter brick and cast-iron Cajun
     cookery. Re-checked again immediately before commit per this
     session's own instructions, since sibling state cabin builds
     (Vermont, Maine) were running concurrently in this same repo. */
  --accent-laspc: #591b11;
  /* Vermont State Parks cabins/cottages (Cabins Phase 2, "VTSPC" - distinct
     from the campsite map's own "VTSP", same collision-avoidance reasoning
     as every other *SPC/*C entry above). Re-checked fresh against every
     accent above right before picking (51 entries at that point, including
     Maine's and Louisiana's just-merged MESPC/LASPC) via a script-driven CIE
     Lab grid search over hue/saturation/value maximizing minimum distance to
     every existing accent, matching this palette's established precedent -
     nearest neighbor is accent-utspc's slate-blue, about 25.2 Lab units
     away, comfortably past the ~22-23 unit bar prior additions established
     as acceptable. A soft periwinkle/lavender-blue - this palette's
     blue-violet region (blm, pc, txspc, ncspc, orspc, utspc, vaspc, nespc,
     inspc) is already populated, but re-checked numerically anyway rather
     than assumed uncrowded, matching this project's established precedent.
     Re-checked again immediately before commit per this session's own
     instructions, since other sibling state cabin builds were running
     concurrently in this same repo. */
  --accent-vtspc: #958aca;
  /* Montana State Parks - Cabins (MTSPC), scripts/fetch-montana-cabins.py.
     Same Lab grid search over hue/saturation/value maximizing minimum
     distance to every existing accent, matching this palette's established
     precedent - nearest neighbor is accent-tnspc's dark green, about 28.6
     Lab units away, comfortably past the ~22-23 unit bar prior additions
     established as acceptable. A deep olive/pine green, reading as
     Montana's forested mountains and distinct from every brighter or
     lighter green already in this palette (green, wvspc, cospc, waspc,
     midnrc, madcrc, tnspc). Re-checked numerically immediately before
     commit per this session's own instructions, since other sibling state
     cabin builds (Vermont, Louisiana) were running concurrently in this
     same repo. */
  --accent-mtspc: #3e4c0b;
  /* Arkansas State Parks - Cabins (ARSPC), scripts/fetch-arkansas-cabins.py.
     Re-checked fresh against every accent above right before picking (53
     entries at that point, including Montana's already-merged olive-pine
     MTSPC) via the same script-driven CIE Lab grid search this palette's
     established precedent describes, maximizing minimum distance to every
     existing accent, constrained to the same medium saturation/value range
     as the rest of this palette. A bright orchid-amethyst magenta, a nod to
     the quartz crystal Arkansas is famous for mining (Mount Ida, the "Quartz
     Crystal Capital of the World," near several of this file's own cabin
     parks) - nearest neighbors are accent-azspc's orchid-purple and
     accent-vaspc's periwinkle, each about 22 Lab units away, at this
     palette's established acceptable bar. Re-checked again immediately
     before commit per this session's own instructions, since other sibling
     state cabin builds (Montana, Alabama) were running concurrently in this
     same repo. */
  --accent-arspc: #d962d9;
  /* Alabama State Parks - Cabins (ALSPC), scripts/fetch-alabama-cabins.py.
     Re-checked fresh against every accent above right before picking (54
     entries at that point, including Arkansas' just-merged orchid-magenta
     ARSPC) via the same script-driven CIE Lab grid search this palette's
     established precedent describes, maximizing minimum distance to every
     existing accent, constrained to the same medium saturation/value range
     as the rest of this palette. A deep indigo-navy, a nod to the night sky
     over Cheaha Mountain - Alabama's highest point, where this file's
     Bluff Side Cabins sit along a bluff known for its sunset views -
     nearest neighbor is accent-txspc's deep blue-violet, about 26.8 Lab
     units away, comfortably past the ~22-23 unit bar prior additions
     established as acceptable. Re-checked again immediately before commit
     per this session's own instructions, since other sibling state cabin
     builds (Vermont, Montana, Arkansas) were running concurrently in this
     same repo. */
  --accent-alspc: #30305a;
  /* Mississippi State Parks cabins/cottages/townhouses/golf villas (Cabins
     Phase 2, "MSSPC" - distinct from the campsite map's own "MSSP", same
     collision-avoidance reasoning as every other *SPC/*C entry above).
     Re-checked fresh against every accent above right before picking (54
     entries at that point, including Arkansas's and Alabama's own just-
     merged ARSPC/ALSPC, which ruled out this build's first-choice deep
     purple candidate - it landed only ~23.4 Lab units from accent-blm, in
     this palette's already-crowded purple/indigo cluster) via a script-
     driven CIE Lab grid search maximizing minimum distance to every
     existing accent. A dusty terracotta/salmon, a nod to Mississippi's red
     clay bluffs and hill country (Tishomingo, Trace, Tombigbee) - its
     nearest neighbor is accent-nhspc's brick-rose, about 23.3 Lab units
     away, comfortably past the ~22-23 unit bar prior additions established
     as acceptable. Re-checked again immediately before commit per this
     session's own instructions, since sibling state cabin builds (Alabama,
     Arkansas) were running concurrently in this same repo. */
  --accent-msspc: #cf7e6f;
  /* Wyoming State Parks cabins/camp shacks/yurts/wall tent/treehouse/lodge
     (Cabins Phase 2, "WYSPC" - distinct from the campsite map's own "WYSP",
     same collision-avoidance reasoning as every other *SPC/*C entry above).
     Re-checked fresh against every accent in the palette (57 entries at
     that point, including Mississippi's own just-merged MSSPC) right
     before picking, via the same script-driven CIE Lab grid search this
     palette's prior additions used, maximizing minimum distance to every
     existing accent while staying inside a muted, legible chroma/lightness
     band consistent with the rest of this palette (rather than the search's
     raw global-maximum result, a saturated neon magenta/red that would have
     clashed with every other accent's tone). A dusty plum/heather hue,
     evoking Wyoming's high-elevation wildflower fields and sagebrush-steppe
     dusk light - its nearest neighbor is accent-inspc's maroon-purple,
     about 22.8 Lab units away, at the same ~22-23 unit bar prior additions
     have treated as acceptable in this now-crowded palette. Re-checked
     again immediately before commit per this session's own instructions,
     since sibling state cabin builds (Arkansas, Nevada) were running
     concurrently in this same repo. */
  --accent-wyspc: #785a82;
  /* Delaware State Parks cabins/cottages/yurts (Cabins Phase 2, "DESPC" -
     distinct from the campsite map's own "DESP", same collision-avoidance
     reasoning as every other *SPC/*C entry above). Re-checked fresh against
     every accent in the palette (58 entries at that point, including
     Wyoming's own just-merged WYSPC) right before picking, via the same
     script-driven CIE Lab grid search this palette's prior additions used,
     maximizing minimum distance to every existing accent. A warm
     mustard-chartreuse, a nod to "buff" - half of Delaware's official state
     colors, "colonial blue and buff," dating to the Blue Hen Regiment's
     Revolutionary War uniforms (blue itself was long since taken by
     accent-blue) - its nearest neighbor is accent-iaspc's tan, about 22.1
     Lab units away, at the same ~22-23 unit bar prior additions have
     treated as acceptable in this now-crowded palette. Re-checked again
     immediately against the fresh palette right before commit per this
     session's own instructions, since sibling state cabin builds (Nevada,
     Wyoming) were running concurrently in this same repo. */
  --accent-despc: #9f962e;
  /* Nevada State Parks cabins (Cabins Phase 2, "NVSPC" - distinct from the
     campsite map's own "NVSP", same collision-avoidance reasoning as every
     other *SPC/*C entry above). Re-checked fresh against every accent in
     the palette (59 entries at that point, including Wyoming's and
     Delaware's own just-merged WYSPC/DESPC) right before picking, via the
     same script-driven CIE Lab grid search this palette's prior additions
     used, maximizing minimum distance to every existing accent. A vivid
     sunset/red-rock orange, evoking Nevada's high-desert canyon country
     (Valley of Fire, Cathedral Gorge) - its nearest neighbor is
     accent-tva's orange-gold, about 31 Lab units away, well past the
     ~22-23 unit bar prior additions have treated as acceptable in this
     now-crowded palette. Re-checked again immediately against the fresh
     palette right before commit per this session's own instructions,
     since sibling state cabin builds (Wyoming, Delaware, and whichever
     else land first) were running concurrently in this same repo. */
  --accent-nvspc: #fc6203;
  /* Rhode Island State Parks cabins (Cabins Phase 2, "RISPC" - distinct
     from the campsite map's own "RISP", same collision-avoidance reasoning
     as every other *SPC/*C entry above). Re-checked fresh against the full
     palette (60+ entries at that point) right before picking, via the same
     CIE Lab grid search this palette's prior additions used, maximizing
     minimum distance to every existing accent - a vivid raspberry/rose
     pink, evoking the beach roses (Rosa rugosa) that line Rhode Island's
     own coastal state parks near Burlingame and Charlestown. Its nearest
     neighbor is accent-mospc's magenta, about 21 Lab units away, in line
     with the ~21-23 unit bar this now-crowded palette's recent additions
     have treated as the practical ceiling. Re-checked again immediately
     against the fresh palette right before commit per this session's own
     instructions, since sibling state cabin builds were running
     concurrently in this same repo. */
  --accent-rispc: #d81f8f;
  /* North Dakota State Parks cabins/yurts/wagon (Cabins Phase 2, "NDSPC" -
     distinct from the campsite map's own "NDSP", same collision-avoidance
     reasoning as every other *SPC/*C entry above). Re-checked fresh against
     the full palette (61 entries at that point, including Rhode Island's
     own already-merged RISPC) right before picking, via the same
     script-driven CIE Lab grid search this palette's prior additions used,
     maximizing minimum distance to every existing accent within this
     palette's established medium saturation/value band. A soft slate/sky
     blue, evoking North Dakota's "Peace Garden State" big sky and the
     Missouri River/Lake Sakakawea reservoirs several of this file's cabin
     parks (Cross Ranch, Fort Stevenson, Lake Sakakawea, Lewis and Clark)
     sit on. Its nearest neighbor is accent-nmspc's teal-blue, about 21 Lab
     units away, in line with the ~21-23 unit bar this now-crowded palette's
     recent additions have treated as the practical ceiling. Re-checked
     again immediately against the fresh palette right before commit per
     this session's own instructions, since sibling state cabin builds
     (Rhode Island, South Dakota) were running concurrently in this same
     repo. */
  --accent-ndspc: #6696cb;
  /* South Dakota State Parks cabins/suites/lodges (Cabins Phase 2, "SDSPC" -
     distinct from the campsite map's own "SDSP", same collision-avoidance
     reasoning as every other *SPC/*C entry above). Re-checked fresh against
     the full palette (62 entries at that point, including North Dakota's
     and Rhode Island's own already-merged NDSPC/RISPC) right before
     picking, via the same script-driven CIE Lab grid search this palette's
     prior additions used, maximizing minimum distance to every existing
     accent within this palette's established medium saturation/value band.
     A deep blue-violet, evoking twilight over the Black Hills and the
     Badlands' banded rock at dusk. Its nearest neighbor is accent-scspc's
     blue, about 19.7 Lab units away - below the ~21-23 unit bar earlier
     additions used, but this is now the actual best-available candidate
     across the whole hue wheel in this palette's saturation/value band at
     62 entries, not an under-checked shortcut. Re-checked again
     immediately against the fresh palette right before commit per this
     session's own instructions, since sibling state cabin builds (North
     Dakota, Rhode Island, Delaware) were running concurrently in this same
     repo. */
  --accent-sdspc: #634cd9;
  /* Hawaii State Parks cabins/lodging (Cabins Phase 2's 50th and final
     state, "HISPC" - distinct from the campsite map's own "HISP", same
     collision-avoidance reasoning as every other *SPC/*C entry above).
     Picked via the same script-driven CIE Lab grid search this palette's
     prior additions used, run fresh against the full palette (63 entries
     at that point, including North Dakota's and South Dakota's own
     already-merged NDSPC/SDSPC) right before picking, maximizing minimum
     distance to every existing accent across the whole hue wheel rather
     than restricting to this palette's usual medium saturation/value band
     - restricting to that band alone kept landing directly on top of this
     already-crowded palette's many existing purples/blues by hue, so the
     search was widened to a darker value tier (~40% V) to find genuine
     open space instead. A deep taro/kalo purple - evoking the purple-
     fleshed staple crop central to Native Hawaiian culture, and, at this
     depth, the deep violet of Haleakalā at twilight. Its nearest neighbor
     is accent-blm's federal purple-brown, about 22.5 Lab units away -
     back above the ~21-23 unit bar earlier additions used, and comfortably
     ahead of the last few states' accepted floor (South Dakota's 19.7,
     Rhode Island's precedent before it) despite 63 existing entries to
     clear. Re-checked again immediately against the fresh palette right
     before commit per this session's own instructions, since sibling
     state cabin builds (South Dakota, Alaska) were running concurrently
     in this same repo. */
  --accent-hispc: #520f66;
  /* Alaska State Parks cabins (Cabins Phase 2, "AKSPC" - distinct from the
     campsite map's own "AKSP", same collision-avoidance reasoning as every
     other *SPC/*C entry above). Picked via the same script-driven CIE Lab
     grid search this palette's prior additions used, run fresh against the
     full palette (64 entries at that point, including Hawaii's own
     already-merged HISPC) right before picking, restricted to this
     palette's usual medium saturation/lightness band (L 28-62, chroma
     35-75) rather than the whole color space, since an unrestricted search
     only turns up near-white or neon-saturated candidates unusable as a UI
     accent at this palette's density. A warm coral-salmon color, evoking
     the salmon runs central to so many of these cabins' own descriptions
     (Pasagshak River, Grindall Island, and others are described directly
     in terms of their salmon fishing). Its nearest neighbor is accent-fs's
     brick-red, about 21.5 Lab units away - in line with the ~19.7-22.5 unit
     floor this palette's last several additions (South Dakota, Hawaii) have
     had to accept at this density, not an under-checked shortcut. Re-checked
     again immediately against the fresh palette right before commit per
     this session's own instructions, since sibling state cabin builds
     could still be landing in this same repo. */
  --accent-akspc: #ea6a74;
  /* Canada's four cabin agencies (2026-08-30 architecture buildout).
     Computed against the full existing palette for a minimum ~18 Lab-unit
     separation from every prior color (this palette's typical floor at
     current density, per the akspc note above), then paired as a darker/
     richer sibling shade of each agency's own CAMPSITE accent color so the
     two related agencies (e.g. accent-pc's blue, accent-pccabins' navy)
     read as visually connected without being identical. */
  --accent-pccabins: #2c2f77;
  --accent-bcparkscabins: #843d7d;
  --accent-cacitycabins: #8d3d1f;
  --accent-catribalcabins: #7b333d;
  /* US city/county cabins. Deliberately not a lighter CITYPARKS blue
     (#176cb8): the two are different modes, never on screen together, and
     a near-miss of an existing hue is harder to name than a separate one. */
  --accent-citycabins: #15665e;
  /* Dispersed Camping mode's one color ("FS-DISPERSED", see
     site/app.js's AGENCY_LABELS comment) - deliberately NOT another vivid
     hue fighting for space in this already-dense palette. A muted, warm
     gray-taupe instead, chosen specifically because the data itself is
     informal/unverified (see DISPERSED_DETAIL_DISCLOSURE in app.js) -
     the desaturated color is itself part of communicating "this is a
     different kind of listing than everything else on this map," not
     just one more agency added to the set. */
  --accent-fs-dispersed: #8a7d6b;
  /* Generic "this is a cabin" brand color, independent of which agency
     actually manages a given cabin (agency is still shown via its own true
     color on the small agency-badge pill) - used for the cabin marker's
     default/USACE color and the cabin card's top border, same role
     var(--accent-blue) plays for campsite cards/pins. A warm, wood-toned
     hue to read as distinct from every cool campsite accent above. */
  --accent-cabin: #8a6a3f;
  /* Active NWS weather alerts (see fetchWeatherAlerts/renderWeatherInto in
     app.js) - a genuine life-safety color, kept distinct from every other
     accent above so it reads as "pay attention" at a glance. Severe/Extreme
     alerts use this directly; Minor/Moderate use a lighter tint of it. */
  --accent-warn: #b3261e;
  /* The brand's own dark: the forest green of the Park Otter lettering and
     outlines (brand/park-otter-logo.png, #012f1a), lifted a step so it is
     a paintable ground rather than near-black. Was a teal (#0d3b46) until
     2026-09-07, which read as a different brand next to the otter. Also
     the theme-color meta, the manifest and the icon ground in
     scripts/generate-brand-assets.py - change all four together. */
  --accent-deep: #0f3b21;
  /* The same green as text rather than ground: section headings, hover
     text, the mobile nav link. One token in light, where the two are the
     same colour, so the dark theme can lift this one to a pale green while
     --accent-deep stays a ground under white text (active pills, footer,
     state bubbles). Before this split the headings were painted in the
     ground colour on the dark page and all but vanished. */
  --accent-deep-ink: #0f3b21;
  --tint: #eef6f3;
  --ink: #1a1a1a;
  --ink-muted: #5a6570;
  --border: #e3e6e8;
  --radius: 10px;
  --shadow-sm: 0 1px 3px rgba(15, 59, 33, 0.08);
  --shadow-md: 0 10px 28px rgba(15, 59, 33, 0.14);
  --font-heading: "Fraunces", Georgia, serif;
  --font-body: "Inter", system-ui, -apple-system, "Segoe UI", sans-serif;
}

* {
  box-sizing: border-box;
}

html {
  scroll-behavior: smooth;
}

body {
  margin: 0;
  color: var(--ink);
  font-family: var(--font-body);
  line-height: 1.55;
  background: var(--bg);
}

/* Form controls.

   Every select and text input in this stylesheet sets "background:
   var(--surface)" and then lets its text colour inherit. That is fine while
   the inherited colour is dark on a light surface, and it broke the moment a
   dark theme existed: the agency filter's dropdown rendered near-white
   option text, and on mobile the platform painted the popup on its own light
   ground, so the list read as blank.

   color-scheme (top of file) repaints the popup itself. These rules cover
   the other half - browsers that paint the option list from author styles
   instead - so the pairing is stated once here rather than depending on
   inheritance reaching every control correctly.

   Deliberately not including <button>: several buttons carry white text on a
   saturated agency colour and would lose it to a bare-tag rule. */
select,
input,
textarea {
  color: var(--ink);
}

option {
  background: var(--surface);
  color: var(--ink);
}

::placeholder {
  color: var(--ink-muted);
  opacity: 1;   /* Firefox dims placeholders by default; --ink-muted already is. */
}

section {
  padding: var(--sp-9) var(--sp-6);
  max-width: 1100px;
  margin: 0 auto;
}

.section-inner {
  max-width: 1100px;
  margin: 0 auto;
}

h1, h2, h3 {
  font-family: var(--font-heading);
  line-height: 1.2;
  font-weight: 600;
}

h2 {
  font-size: var(--text-3xl);
  margin: 0 0 var(--sp-3);
}

a {
  color: var(--accent-blue);
}

/* Header / nav */

#site-header {
  position: sticky;
  top: 0;
  /* Above the map chrome (Leaflet's controls, #map-hint, #map-layers,
     #map-region-switcher - all 1000) so that on a phone, once the page
     is scrolled and the map's top edge runs under this sticky header,
     the zoom buttons and region pills stop showing through the logo.
     Still below the mobile #detail-panel sheet (1500) and the state
     modal (2000), which are meant to cover everything. */
  z-index: 1200;
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: var(--sp-5) var(--sp-6);
  background: var(--overlay-soft);
  backdrop-filter: blur(6px);
  border-bottom: 1px solid var(--border);
}

#site-header .brand {
  display: flex;
  align-items: center;
  flex-shrink: 0;
  text-decoration: none;
}

/* The Park Otter logo (site/images/park-otter-logo*.png, cut from
   brand/park-otter-logo.png by scripts/generate-brand-assets.py): the
   otter mark and the wordmark, tagline dropped at this size. Height is
   the only dimension set - the width attribute on the <img> is there so
   the header lays out before the file arrives, and auto width keeps the
   mark and wordmark in the master's proportion. The dark-theme copy is
   chosen by the <picture> source in the markup, not here. */
.brand-logo {
  display: block;
  height: 48px;
  width: auto;
}

/* Top-level Campsites/Cabins dataset switch - same pill-button-group visual
   language as .view-toggle-btn/.region-toggle-btn elsewhere on the page,
   placed in the sticky header so it's reachable from anywhere on the page,
   not just at the top. */
#mode-toggle {
  display: flex;
  gap: var(--sp-2);
  flex-shrink: 0;
}

.mode-toggle-btn {
  padding: var(--sp-3) var(--sp-4);
  border: 1px solid var(--border);
  border-radius: 999px;
  background: var(--surface);
  color: var(--ink-muted);
  font-size: var(--text-xs);
  font-weight: 600;
  cursor: pointer;
  transition: background 0.15s ease, color 0.15s ease, border-color 0.15s ease;
}

.mode-toggle-btn.active {
  background: var(--accent-deep);
  border-color: var(--accent-deep);
  color: white;
}

.mode-toggle-btn:not(.active):hover {
  border-color: var(--accent-deep-ink);
  color: var(--accent-deep-ink);
}

.mode-toggle-btn:disabled {
  cursor: default;
  opacity: 0.7;
}

/* Camping | Parks page switch (site/index.html and site/parks.html) -
   same pill group as #mode-toggle, but each pill is a link to the other
   map rather than a button that swaps data in place, so it needs the
   link defaults undone. Sits between the brand and the dataset toggle;
   the active pill is the page you're on. */
#page-switch {
  display: flex;
  gap: var(--sp-2);
  flex-shrink: 0;
}

a.mode-toggle-btn {
  display: inline-flex;
  align-items: center;
  text-decoration: none;
}

/* On phones the pill group is swapped for one link at the end of the
   scrolling nav row (see the mobile block near the end of this file):
   the header there already wraps to three rows, and the two extra pills
   made it four. Hidden on desktop where the pills do the job. */
.nav-page-link {
  display: none;
}

/* Positioning context for #saved-sites-panel below, same relative-wrap
   role as .name-search-wrap/.zip-search-wrap elsewhere in this file. */
#saved-sites-wrap {
  position: relative;
  flex-shrink: 0;
}

#saved-sites-btn {
  padding: var(--sp-3) var(--sp-4);
  border: 1px solid var(--border);
  border-radius: 999px;
  background: var(--surface);
  color: var(--ink-muted);
  font-size: var(--text-xs);
  font-weight: 600;
  cursor: pointer;
  transition: border-color 0.15s ease, color 0.15s ease;
}

/* Beta-only header link (see #header-feedback-link in index.html) - a
   warm amber tint (not this file's neutral header-button style) so it
   reads as a temporary beta affordance, easy to spot and easy to remove
   entirely once the beta period ends. */
#header-feedback-link {
  flex-shrink: 0;
  padding: var(--sp-3) var(--sp-4);
  border: 1px solid var(--notice-edge);
  border-radius: 999px;
  background: var(--notice-tint);
  color: var(--notice);
  font-size: var(--text-xs);
  font-weight: 600;
  text-decoration: none;
  transition: background 0.15s ease;
}

#header-feedback-link:hover {
  background: #fbe8c8;
}

/* Beta feedback form - shared between the standalone site/feedback.html
   (linked from every detail panel's "Report an issue with this listing")
   and the embedded copy at the bottom of index.html (linked from the
   persistent header button, for general feedback with no navigation
   away). Both pages use these same class/id names against their own
   copy of the form markup - see wireEmbeddedFeedbackForm() in app.js for
   the embedded copy's submit handling, feedback.html's own inline
   <script> for the standalone page's. */
#feedback-section {
  max-width: 640px;
  margin: 0 auto;
  padding: var(--sp-8) var(--sp-6) var(--sp-10);
}

#feedback-section p.subhead {
  margin-bottom: var(--sp-5);
}

#feedback-reporting-on {
  margin: 0 0 var(--sp-7);
  padding: var(--sp-3) var(--sp-4);
  background: var(--tint);
  border: 1px solid var(--border);
  border-radius: 8px;
  font-size: var(--text-xs);
}

#feedback-reporting-on a {
  word-break: break-all;
}

.feedback-field {
  display: flex;
  flex-direction: column;
  gap: var(--sp-2);
  margin-bottom: var(--sp-5);
}

.feedback-field label {
  font-weight: 600;
  font-size: var(--text-sm);
}

.feedback-field .optional {
  font-weight: 400;
  color: var(--ink-muted);
}

.feedback-field input[type="text"],
.feedback-field input[type="email"],
.feedback-field textarea,
.feedback-field select {
  font: inherit;
  padding: var(--sp-3) var(--sp-4);
  border: 1px solid var(--border);
  border-radius: 8px;
  background: var(--surface);
}

.feedback-field textarea {
  resize: vertical;
  min-height: 140px;
}

.rating-options {
  display: flex;
  gap: var(--sp-3);
}

.rating-options label {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  font-weight: 400;
  padding: var(--sp-3) var(--sp-4);
  border: 1px solid var(--border);
  border-radius: 999px;
  cursor: pointer;
}

.rating-options input {
  margin: 0;
}

.feedback-form button[type="submit"] {
  font: inherit;
  font-weight: 600;
  padding: var(--sp-4) var(--sp-6);
  border: none;
  border-radius: 999px;
  background: var(--accent-deep);
  color: white;
  cursor: pointer;
}

.feedback-form button[type="submit"]:disabled {
  opacity: 0.6;
  cursor: default;
}

.feedback-status {
  margin-top: var(--sp-5);
  font-weight: 600;
}

.feedback-status.success {
  color: var(--ok);
}

.feedback-status.error {
  color: var(--danger);
}

/* Embedded copy only - a visible divider/heading treatment separating it
   from the campground data above, since (unlike the standalone page)
   this one sits at the bottom of a long, unrelated page rather than
   being the whole point of the page it's on. */
#embedded-feedback-section {
  background: var(--tint);
  border-top: 1px solid var(--border);
}

#saved-sites-btn:hover {
  border-color: var(--accent-deep-ink);
  color: var(--accent-deep-ink);
}

#saved-sites-count {
  font-variant-numeric: tabular-nums;
}

/* Same floating-dropdown language as .name-search-results/
   .zip-search-results - toggled on #saved-sites-btn click, closed on an
   outside click, both wired in wireSavedSitesPanel() in app.js. */
.saved-sites-panel {
  position: absolute;
  top: calc(100% + 0.4rem);
  right: 0;
  width: 20rem;
  max-width: 90vw;
  max-height: 22rem;
  overflow-y: auto;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 10px;
  box-shadow: var(--shadow-md);
  z-index: 600;
}

.saved-sites-empty {
  padding: var(--sp-5);
  font-size: var(--text-xs);
  color: var(--ink-muted);
  margin: 0;
}

.saved-site-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-3);
  padding: var(--sp-3) var(--sp-5);
  cursor: pointer;
  border-bottom: 1px solid #f0f0f0;
}

.saved-site-row:last-child {
  border-bottom: none;
}

.saved-site-row:hover {
  background: var(--tint);
}

.saved-site-info {
  display: flex;
  flex-direction: column;
  gap: var(--sp-1);
  min-width: 0;
}

.saved-site-name {
  font-size: var(--text-xs);
  font-weight: 500;
  color: var(--ink);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.saved-site-location {
  font-size: var(--text-2xs);
  color: var(--ink-muted);
}

.saved-site-remove {
  flex-shrink: 0;
  width: 1.5rem;
  height: 1.5rem;
  border: none;
  border-radius: 50%;
  background: transparent;
  color: var(--ink-muted);
  font-size: var(--text-md);
  line-height: 1;
  cursor: pointer;
}

.saved-site-remove:hover {
  background: rgba(179, 38, 30, 0.1);
  color: var(--accent-warn);
}

/* Persistent state, not transient like .copy-link-btn's "Copied!" flash -
   a filled/green look while saved makes the toggle's current state
   readable at a glance, not just from the label text. */
.save-site-btn.is-saved {
  background: var(--accent-green);
  border-color: var(--accent-green);
  color: white;
}

#site-header nav {
  display: flex;
  gap: var(--sp-6);
}

#site-header nav a {
  color: var(--ink-muted);
  text-decoration: none;
  font-size: var(--text-sm);
  font-weight: 500;
  transition: color 0.15s ease;
}

#site-header nav a:hover {
  color: var(--accent-blue);
}

/* Hero */

#hero {
  position: relative;
  max-width: none;
  padding: 0;
  text-align: center;
  /* No overflow:hidden here (removed - user-reported: it was clipping the
     name-search/zip-search dropdowns, both position:absolute children that
     can grow taller than the remaining hero space below them). Nothing in
     this section actually needs clipping to justify it: the background
     below is a CSS `background` property, already auto-clipped to the
     box regardless of `overflow`, and .hero-wave sits flush within bounds
     on its own. Same bug class as the #state-map-wrapper fix elsewhere in
     this file - overflow clipping follows DOM containment, not the
     positioning/containing-block chain, so an absolutely positioned child
     gets cut off by an ancestor's overflow:hidden even though it's
     positioned against a different element entirely.
     Just the fallback gradient here now - the actual photo(s) moved to
     #hero-bg's stacked .hero-bg-layer divs below (multiple originals,
     contributed by the site owner/a photographer, crossfading - see
     that block for why this had to become real elements instead of a
     CSS `background` shorthand). This gradient still renders immediately
     underneath everything else and stays visible if a photo file is ever
     missing or before JS finishes wiring up the cycle - gradients can't
     fail to load. */
  background: linear-gradient(180deg, #eef7f5 0%, #dcedee 55%, #cde6e9 100%);
}

/* Multiple hero photos crossfading, replacing the old single-photo
   `background` shorthand on #hero. Each layer is its own absolutely
   positioned, full-bleed div (a CSS `background-image` transition isn't
   reliably animatable across browsers, which real stacked elements sidestep
   entirely) - wireHeroPhotoCycle() in app.js toggles which one carries
   `.active` on a timer. #hero-scrim is a separate, constant element (not
   JS-driven, always in the DOM) providing the dark-for-white-text contrast
   every photo needs regardless of which one is currently showing - a
   single neutral scrim rather than a per-photo tuned one (the old waterfall
   scrim was mossy-green-tinted; that stopped making sense once the sunset/
   leaf photos joined the rotation), verified for readability against all
   four photos in this rotation.
   #hero already has position:relative, so these position against it
   directly. #hero-bg gets its own overflow:hidden (NOT re-added to #hero
   itself - see the removal note above, still valid) since photo edges past
   the cover-sized box would otherwise be visible during the very first
   layout paint. */
#hero-bg {
  position: absolute;
  inset: 0;
  overflow: hidden;
  z-index: 0;
}

.hero-bg-layer {
  position: absolute;
  inset: 0;
  background-size: cover;
  background-position: center 30%;
  background-repeat: no-repeat;
  opacity: 0;
  transition: opacity 2s ease;
}

.hero-bg-layer.active {
  opacity: 1;
}

#hero-scrim {
  position: absolute;
  inset: 0;
  z-index: 1;
  background: linear-gradient(rgba(8, 20, 22, 0.52), rgba(8, 20, 22, 0.68));
  pointer-events: none;
}

/* Above #hero-bg/#hero-scrim (z-index 0/1) so the headline, search, and
   stats stay on top of the photo layers instead of getting buried under
   them - #hero-bg's position:absolute otherwise paints above this
   plain-flow content regardless of DOM order, since non-positioned
   elements always sit below positioned ones in the same stacking context. */
#hero .section-inner {
  position: relative;
  z-index: 2;
  /* Bottom padding halved (was 6.5rem, 2026-09-04) so the map sits closer
     under the search panel; the wave below shrank to match. */
  padding: var(--sp-9) var(--sp-6) 3.25rem;
}

@media (prefers-reduced-motion: reduce) {
  .hero-bg-layer {
    transition: none;
  }
}

/* The stats bar follows the headline directly (the hero subhead came out
   on 2026-09-04), so the headline carries the gap the paragraph used to. */
#hero h1 {
  font-size: clamp(2rem, 4vw, 2.9rem);
  margin: 0 0 var(--sp-7);
  color: white;
  text-shadow: 0 1px 12px rgba(0, 0, 0, 0.25);
}

/* The accented word is the logo's cream (the otter's muzzle) rather than
   the pale blue it was, so the headline and the mark share a palette. */
#hero h1 em {
  color: #f8e8c8;
  font-style: italic;
}

.subhead {
  color: #e4eef0;
  max-width: 640px;
  margin: 0 auto var(--sp-7);
  font-size: var(--text-lg);
}

.hero-wave {
  position: absolute;
  left: 0;
  right: 0;
  bottom: -1px;
  line-height: 0;
  z-index: 2;
}

.hero-wave svg {
  width: 100%;
  height: 28px;
  display: block;
}

.hero-wave path {
  fill: white;
}

#stats-bar {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: var(--sp-8);
  margin-bottom: var(--sp-8);
}

.stat {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--sp-1);
}

.stat-icon {
  width: 22px;
  height: 22px;
  color: #a8ddf0;
  margin-bottom: var(--sp-1);
}

.stat-number {
  font-family: var(--font-heading);
  font-size: var(--text-3xl);
  font-weight: 700;
  color: white;
}

.stat-label {
  font-size: var(--text-2xs);
  color: #e4eef0;
}

.stat-breakdown {
  font-size: var(--text-3xs);
  color: #b9d3d6;
  text-align: center;
  max-width: 160px;
}

/* Stats tied to a mode (campsites/cabins/dispersed counts) double as
   shortcuts into that mode - same destination as #mode-toggle's tabs,
   just reachable from the hero without scrolling up to the header
   first. Purely additive: the plain .stat styling above still applies. */
.stat-clickable {
  cursor: pointer;
  border-radius: 10px;
  padding: var(--sp-2) var(--sp-3);
  margin: -0.35rem -0.5rem;
  transition: background 0.15s ease;
}

.stat-clickable:hover,
.stat-clickable:focus-visible {
  background: rgba(255, 255, 255, 0.08);
}

.stat-clickable:hover .stat-icon,
.stat-clickable:focus-visible .stat-icon {
  color: white;
}

.stat-clickable:hover .stat-label,
.stat-clickable:focus-visible .stat-label {
  text-decoration: underline;
  text-decoration-color: #a8ddf0;
  text-underline-offset: 2px;
}

.stat-clickable:focus-visible {
  outline: 2px solid white;
  outline-offset: 2px;
}

/* Search panel: one frosted card under the stats holding both ways to
   search, side by side. Its ground is the scrim's own colour at a higher
   alpha rather than a theme token, because it sits on the photo in both
   themes and has to read the same on each. The two columns share a
   hairline; under 720px they stack and the line turns horizontal. */
#search-panel {
  display: grid;
  grid-template-columns: 1fr 1fr;
  max-width: 860px;
  margin: 0 auto;
  padding: var(--sp-6) var(--sp-7);
  background: rgba(8, 20, 22, 0.45);
  border: 1px solid rgba(255, 255, 255, 0.14);
  border-radius: 16px;
  backdrop-filter: blur(10px);
  text-align: left;
}

.search-block {
  display: flex;
  flex-direction: column;
  gap: var(--sp-3);
  min-width: 0;
}

.search-block:first-child {
  padding-right: var(--sp-7);
}

#search-form {
  padding-left: var(--sp-7);
  border-left: 1px solid rgba(255, 255, 255, 0.14);
}

.search-block-label {
  font-size: var(--text-2xs);
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: #b9d3d6;
}

.search-block-hint {
  margin: 0;
  font-size: var(--text-2xs);
  color: #b9d3d6;
}

.name-search-wrap,
.zip-search-wrap {
  position: relative;
  width: 100%;
}

#name-search-input,
#zip-input {
  width: 100%;
  padding: var(--sp-3) var(--sp-5);
  border: 1px solid var(--border);
  border-radius: 999px;
  /* 1rem, not smaller: iOS Safari auto-zooms the page on focus for any
     text input under 16px font-size. */
  font-size: var(--text-md);
  background: var(--surface);
  box-sizing: border-box;
}

#name-search-input:focus,
#zip-input:focus,
#radius-select:focus {
  outline: 2px solid var(--accent-blue);
  outline-offset: 1px;
}

.search-actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  gap: var(--sp-3);
}

.radius-field {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-2);
  font-size: var(--text-xs);
  color: #e4eef0;
}

.name-search-results {
  position: absolute;
  top: calc(100% + 0.4rem);
  left: 0;
  right: 0;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 10px;
  box-shadow: var(--shadow-md);
  max-height: 320px;
  overflow-y: auto;
  /* z-index:50 was losing to whatever Leaflet content sits in the map
     section wherever this dropdown's absolute positioning carries it past
     the hero's bottom edge, since #map-section doesn't isolate its own
     stacking context (position:relative with no z-index doesn't) - the
     exact same bug class already fixed for #map-hint/#map-layers/
     #map-region-switcher and the mobile #detail-panel sheet elsewhere in
     this file, all bumped to 1000+ to reliably beat Leaflet's own panes
     (up to 700) for the same reason. Removing #hero's old overflow:hidden
     (a separate, real fix, kept) wasn't enough on its own - this was the
     actual visible-cutoff cause. */
  z-index: 1000;
  text-align: left;
}

.name-search-result {
  display: flex;
  flex-direction: column;
  gap: var(--sp-1);
  padding: var(--sp-3) var(--sp-5);
  cursor: pointer;
  border-bottom: 1px solid #f0f0f0;
}

.name-search-result:last-child {
  border-bottom: none;
}

.name-search-result:hover, .name-search-result:focus {
  background: var(--tint);
  outline: none;
}

.name-search-result-name {
  font-size: var(--text-sm);
  font-weight: 500;
  color: var(--ink);
}

.name-search-result-location {
  font-size: var(--text-2xs);
  color: var(--ink-muted);
}

#radius-select {
  padding: var(--sp-2) var(--sp-4);
  border: 1px solid var(--border);
  border-radius: 999px;
  font-size: var(--text-md); /* same iOS zoom rule as the text inputs */
  background: var(--surface);
  color: var(--ink);
}

#search-form button {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-2);
  padding: var(--sp-3) var(--sp-5);
  border: 1px solid var(--accent-blue);
  border-radius: 999px;
  background: var(--accent-blue);
  color: white;
  font-size: var(--text-sm);
  font-weight: 500;
  cursor: pointer;
  transition: background 0.15s ease, box-shadow 0.15s ease, transform 0.1s ease;
}

#search-form button:hover {
  background: var(--accent-blue-dark);
  box-shadow: var(--shadow-sm);
}

#search-form button:active {
  transform: translateY(1px);
}

/* The secondary action: a ghost pill on its own line, centred under the
   place search, so Search is the one solid button in the actions row. */
#use-location-btn {
  align-self: center;
  margin-top: var(--sp-2);
  background: transparent;
  border-color: rgba(255, 255, 255, 0.4);
  color: white;
}

#use-location-btn svg {
  width: 14px;
  height: 14px;
}

#use-location-btn:hover {
  background: rgba(255, 255, 255, 0.12);
  border-color: rgba(255, 255, 255, 0.6);
}

/* While its own search is on the map the button reads "Clear my location"
   (JS swaps the label and this class - see syncSearchChrome) and fills in,
   so the hero shows the state the map is in without scrolling to it. */
#use-location-btn.active {
  background: rgba(255, 255, 255, 0.18);
  border-color: white;
}

#search-status {
  min-height: 1.2rem;
  margin: var(--sp-4) 0 0;
  font-size: var(--text-sm);
  color: #e4eef0;
}

/* Map section */

#map-section {
  position: relative;
  max-width: none;
  padding: 0;
  height: 70vh;
  min-height: 420px;
}

#map {
  height: 100%;
  width: 100%;
}

/* State polygons are nearly invisible (3% fill) until hovered, so there's
   no visual cue on their own that they're clickable - this hint fixes
   that. It's purely informational (pointer-events:none so it never blocks
   map interaction) and JS hides it once the user has clicked a state, so
   it doesn't linger for people who've already learned the interaction. */
#map-hint {
  position: absolute;
  top: 1rem;
  left: 1rem;
  background: var(--overlay-soft);
  color: var(--ink);
  font-size: var(--text-xs);
  font-weight: 500;
  padding: var(--sp-3) var(--sp-5);
  border-radius: 999px;
  box-shadow: var(--shadow-sm);
  /* #map-section doesn't isolate its own stacking context (position with
     no z-index doesn't, same issue hit with the mobile detail sheet
     against Leaflet's panes), so this was directly losing to Leaflet's
     own overlay-pane (z-index 400, where the state polygons render) and
     marker-pane (600, where the green pins render) wherever they overlap
     this corner of the map - which is most of the time, since the pins
     are scattered across the whole country. */
  z-index: 1000;
  pointer-events: none;
}

/* Per-agency show/hide toggles for the national map, one row per agency
   actually present in the loaded data (JS populates this - empty markup
   here). Bottom-left: top-left already holds #map-hint, and top/right is
   where #detail-panel lives (full-width on mobile, so anything pinned to a
   top corner would sit on top of it) - JS hides this panel while
   #detail-panel is open, same idea as #map-hint hiding on state-modal open.
   Unlike #map-hint this needs pointer-events, and sits over the same
   Leaflet panes, so it needs the same z-index fix. */
#map-layers {
  position: absolute;
  bottom: 1rem;
  left: 1rem;
  z-index: 1000;
  background: var(--overlay);
  border-radius: 10px;
  box-shadow: var(--shadow-sm);
  padding: var(--sp-3) var(--sp-4);
  display: flex;
  flex-direction: column;
  gap: var(--sp-2);
  font-size: var(--text-2xs);
  color: var(--ink);
}

/* Sets "display" unconditionally above, which - as hit several times
   already in this file - beats the UA stylesheet's [hidden] { display:
   none } at equal specificity. */
#map-layers[hidden] {
  display: none;
}

/* Collapses/expands .map-layer-controls, on both desktop and mobile - the
   national map's default view now leans on the per-state count icons (see
   renderStateIcons in app.js), so this panel is worth being able to tuck
   away on a big screen too, not just on a phone where it used to cover
   most of the map. */
.map-layer-toggle-btn {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-3);
  width: 100%;
  background: none;
  border: none;
  font: inherit;
  font-weight: 600;
  color: var(--ink);
  padding: 0;
  cursor: pointer;
}

.map-layer-toggle-caret {
  transition: transform 0.15s ease;
}

#map-layers.collapsed .map-layer-toggle-caret {
  transform: rotate(-90deg);
}

#map-layers.collapsed .map-layer-controls {
  display: none;
}

/* Wraps everything that used to be a direct flex child of #map-layers
   (bulk actions, one row per agency) - needs its own flex-column layout
   now that it's one level deeper, on both desktop and mobile, not just
   where the collapse behavior above applies.

   One row per agency present in the loaded data (see
   renderMapLayerControls in app.js) - this project started with ~5
   RIDB-family agencies but now has 45+ once every state-parks source is
   counted, so a fully expanded panel is now far taller than any
   reasonable viewport. User-reported: this panel, uncapped, was tall
   enough on desktop to sit over real map content (e.g. Hawai'i pins near
   the bottom-left of that region's view). Capped with its own scrollbar
   rather than letting the panel grow unbounded - the toggle button and
   bulk-action row above stay fixed/reachable, only the per-agency
   checkbox list scrolls. */
.map-layer-controls {
  display: flex;
  flex-direction: column;
  gap: var(--sp-2);
  max-height: min(60vh, 420px);
  overflow-y: auto;
}

/* Top-right: the only corner #detail-panel (full-height on the right edge
   on desktop, full-width on mobile) doesn't already sit under, other than
   the ones #map-hint/#map-layers already use - JS hides this the same way
   while #detail-panel is open, same reasoning as #map-layers. */
#map-region-switcher {
  position: absolute;
  top: 1rem;
  right: 1rem;
  z-index: 1000;
  display: flex;
  flex-wrap: wrap;
  justify-content: flex-end;
  max-width: calc(100% - 2rem);
  gap: var(--sp-3);
}

/* Sets "display" unconditionally above, which - as hit several times
   already in this file - beats the UA stylesheet's [hidden] { display:
   none } at equal specificity. This is exactly what let the region
   switcher stay visible (and cover #detail-panel's close button) even
   though JS was already setting .hidden = true while the detail panel
   is open. */
#map-region-switcher[hidden] {
  display: none;
}

.region-toggle-btn {
  padding: var(--sp-2) var(--sp-4);
  border: 1px solid var(--border);
  border-radius: 999px;
  background: var(--overlay);
  color: var(--ink-muted);
  font-size: var(--text-2xs);
  font-weight: 500;
  cursor: pointer;
  box-shadow: var(--shadow-sm);
  transition: background 0.15s ease, color 0.15s ease, border-color 0.15s ease;
}

.region-toggle-btn.active {
  background: var(--accent-blue);
  border-color: var(--accent-blue);
  color: white;
}

.region-toggle-btn:not(.active):hover {
  border-color: var(--accent-blue);
  color: var(--accent-blue);
}

/* "Clear search" pill, only present while a ZIP/location search is on the
   map (JS toggles [hidden]; see syncSearchChrome in app.js). Bottom-centre
   is the one spot with nothing else in it on either layout: the top-right
   region row already wraps to two lines on a phone and runs into the
   top-left hint and zoom control - user-reported, this pill sat under the
   "Lower 48" button when it was first put in that row. Styled like the
   region pills so it reads as map chrome, with full-strength ink and a
   bolder weight so it reads as an action rather than a toggle. */
#map-clear-search {
  position: absolute;
  bottom: 1rem;
  left: 50%;
  transform: translateX(-50%);
  z-index: 1000;
  display: inline-flex;
  align-items: center;
  gap: var(--sp-2);
  padding: var(--sp-2) var(--sp-4);
  border: 1px solid var(--border);
  border-radius: 999px;
  background: var(--overlay);
  color: var(--ink);
  font-size: var(--text-2xs);
  font-weight: 600;
  white-space: nowrap;
  cursor: pointer;
  box-shadow: var(--shadow-sm);
  transition: background 0.15s ease, color 0.15s ease, border-color 0.15s ease;
}

#map-clear-search span {
  font-size: 1.2em;
  line-height: 1;
}

#map-clear-search:hover {
  border-color: var(--accent-blue);
  color: var(--accent-blue);
}

/* Sets "display" unconditionally above - same [hidden] override every
   other overlay control in this file needs. The sibling rule hides it
   while #detail-panel is open (desktop sidebar or mobile bottom sheet)
   without the JS open/close paths having to know it exists - it sits
   right after the panel in the markup for exactly this. */
#map-clear-search[hidden],
#detail-panel:not([hidden]) ~ #map-clear-search {
  display: none;
}

/* Leaflet's own default layer-switcher control (Map/Satellite toggle,
   bottom-right on both the main map and the state-modal map - see
   addBaseLayers() in app.js) ships with generous stock padding that reads
   as bulky next to this site's own tighter, custom-styled overlays
   (#map-layers, .region-toggle-btn above). Overriding its built-in
   leaflet.css rules to match this site's visual language instead of
   replacing the control outright - Leaflet's default expand/collapse and
   radio-button behavior stays exactly as-is, only the spacing/type
   changes. */
.leaflet-control-layers {
  border-radius: 10px !important;
  box-shadow: var(--shadow-sm) !important;
  font-family: var(--font-body);
}

.leaflet-control-layers-toggle {
  width: 32px !important;
  height: 32px !important;
}

.leaflet-control-layers-expanded {
  padding: var(--sp-3) var(--sp-4) !important;
  color: var(--ink);
}

/* Leaflet 1.9.4 renders the expanded layer list as a bare <section>, which
   this page's own `section { padding: var(--sp-9) var(--sp-6); max-width: 1100px;
   margin: 0 auto; }` tag selector (near the top of this file, meant for the
   page's own hero/campgrounds/history/etc. sections) was matching too -
   that's what was actually making the control balloon out huge, not
   anything above. A class selector beats a bare-tag one regardless of
   source order, so this alone is enough to win without !important. */
.leaflet-control-layers-list {
  padding: 0;
  margin: 0;
  max-width: none;
}

.leaflet-control-layers-list label {
  font-size: var(--text-2xs);
  margin-bottom: var(--sp-1);
}

.leaflet-control-layers-selector {
  margin-top: 0;
}

.map-layer-toggle {
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  cursor: pointer;
  white-space: nowrap;
}

.map-layer-bulk-actions {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  font-size: var(--text-3xs);
  color: var(--ink-muted);
  margin-bottom: var(--sp-1);
  /* .map-layer-controls (its parent) now scrolls once the per-agency
     checkbox list grows past max-height - sticky keeps Show all/Hide all
     reachable without scrolling back to the top every time. */
  position: sticky;
  top: 0;
  background: var(--overlay);
  z-index: 1;
}

.map-layer-bulk-actions button {
  background: none;
  border: none;
  padding: 0;
  font-size: inherit;
  font-family: inherit;
  color: var(--accent-blue);
  cursor: pointer;
  text-decoration: underline;
}

.map-layer-bulk-actions button:hover {
  color: var(--accent-blue-dark);
}

.map-layer-swatch {
  display: inline-block;
  width: 10px;
  height: 10px;
  border-radius: 50%;
  background: var(--accent-green);
  flex-shrink: 0;
}

.map-layer-swatch-tva { background: var(--accent-tva); }
.map-layer-swatch-nps { background: var(--accent-nps); }
.map-layer-swatch-blm { background: var(--accent-blm); }
.map-layer-swatch-fs { background: var(--accent-fs); }
.map-layer-swatch-pc { background: var(--accent-pc); }
.map-layer-swatch-bcparks { background: var(--accent-bcparks); }
/* Both spellings, here and in every other -statepark rule: agencyClass()
   lowercases the campsite agency code "STATEPARKS" (so "-stateparks"),
   while cabins mode's consolidated row passes the bare "STATEPARK" key.
   Until 2026-09-04 only the second existed, so every state park on the
   campsite map fell through to the default green - the same green as
   USACE, whose sites sit right next to state parks on many lakes. */
.map-layer-swatch-statepark, .map-layer-swatch-stateparks { background: var(--accent-statepark); }
.map-layer-swatch-cityparks { background: var(--accent-cityparks); }
.map-layer-swatch-tribal { background: var(--accent-tribal); }
.map-layer-swatch-cacityparks { background: var(--accent-cacityparks); }
.map-layer-swatch-catribal { background: var(--accent-catribal); }
.map-layer-swatch-fws { background: var(--accent-fws); }
.map-layer-swatch-ksspc { background: var(--accent-ksspc); }
.map-layer-swatch-kyspc { background: var(--accent-kyspc); }
.map-layer-swatch-tnspc { background: var(--accent-tnspc); }
.map-layer-swatch-gaspc { background: var(--accent-gaspc); }
.map-layer-swatch-nmspc { background: var(--accent-nmspc); }
.map-layer-swatch-azspc { background: var(--accent-azspc); }
.map-layer-swatch-utspc { background: var(--accent-utspc); }
.map-layer-swatch-flspc { background: var(--accent-flspc); }
.map-layer-swatch-okspc { background: var(--accent-okspc); }
.map-layer-swatch-wvspc { background: var(--accent-wvspc); }
.map-layer-swatch-mospc { background: var(--accent-mospc); }
.map-layer-swatch-txspc { background: var(--accent-txspc); }
.map-layer-swatch-vaspc { background: var(--accent-vaspc); }
.map-layer-swatch-cospc { background: var(--accent-cospc); }
.map-layer-swatch-scspc { background: var(--accent-scspc); }
.map-layer-swatch-ncspc { background: var(--accent-ncspc); }
.map-layer-swatch-paspc { background: var(--accent-paspc); }
.map-layer-swatch-nyspc { background: var(--accent-nyspc); }
.map-layer-swatch-midnrc { background: var(--accent-midnrc); }
.map-layer-swatch-mnspc { background: var(--accent-mnspc); }
.map-layer-swatch-inspc { background: var(--accent-inspc); }
.map-layer-swatch-wispc { background: var(--accent-wispc); }
.map-layer-swatch-ohspc { background: var(--accent-ohspc); }
.map-layer-swatch-waspc { background: var(--accent-waspc); }
.map-layer-swatch-ilspc { background: var(--accent-ilspc); }
.map-layer-swatch-orspc { background: var(--accent-orspc); }
.map-layer-swatch-caspc { background: var(--accent-caspc); }
.map-layer-swatch-njspc { background: var(--accent-njspc); }
.map-layer-swatch-idspc { background: var(--accent-idspc); }
.map-layer-swatch-madcrc { background: var(--accent-madcrc); }
.map-layer-swatch-nespc { background: var(--accent-nespc); }
.map-layer-swatch-mdspc { background: var(--accent-mdspc); }
.map-layer-swatch-iaspc { background: var(--accent-iaspc); }
.map-layer-swatch-ctspc { background: var(--accent-ctspc); }
.map-layer-swatch-nhspc { background: var(--accent-nhspc); }
.map-layer-swatch-mespc { background: var(--accent-mespc); }
.map-layer-swatch-laspc { background: var(--accent-laspc); }
.map-layer-swatch-vtspc { background: var(--accent-vtspc); }
.map-layer-swatch-mtspc { background: var(--accent-mtspc); }
.map-layer-swatch-arspc { background: var(--accent-arspc); }
.map-layer-swatch-alspc { background: var(--accent-alspc); }
.map-layer-swatch-msspc { background: var(--accent-msspc); }
.map-layer-swatch-wyspc { background: var(--accent-wyspc); }
.map-layer-swatch-despc { background: var(--accent-despc); }
.map-layer-swatch-nvspc { background: var(--accent-nvspc); }
.map-layer-swatch-rispc { background: var(--accent-rispc); }
.map-layer-swatch-ndspc { background: var(--accent-ndspc); }
.map-layer-swatch-sdspc { background: var(--accent-sdspc); }
.map-layer-swatch-hispc { background: var(--accent-hispc); }
.map-layer-swatch-akspc { background: var(--accent-akspc); }
.map-layer-swatch-pccabins { background: var(--accent-pccabins); }
.map-layer-swatch-bcparkscabins { background: var(--accent-bcparkscabins); }
.map-layer-swatch-cacitycabins { background: var(--accent-cacitycabins); }
.map-layer-swatch-catribalcabins { background: var(--accent-catribalcabins); }
.map-layer-swatch-citycabins { background: var(--accent-citycabins); }
.map-layer-swatch-fs-dispersed { background: var(--accent-fs-dispersed); }

.site-marker {
  width: 12px;
  height: 12px;
  border-radius: 50%;
  background: var(--accent-green);
  border: 2px solid white;
  box-shadow: 0 0 3px rgba(0, 0, 0, 0.5);
  cursor: pointer;
}

/* Non-USACE agencies get a color-modified pin so they read as a distinct
   category at a glance, before opening the detail panel. Applied alongside
   .site-marker/.site-marker-numbered/.state-list-number, not in place of
   them, so size/shape stay identical. (.state-count-icon, the national
   map's per-state badge, is a deliberate exception - see its own comment -
   since its count is an aggregate across agencies, not one agency.) */
.site-marker-tva {
  background: var(--accent-tva);
}

.site-marker-nps {
  background: var(--accent-nps);
}

.site-marker-blm {
  background: var(--accent-blm);
}

.site-marker-fs {
  background: var(--accent-fs);
}

.site-marker-pc {
  background: var(--accent-pc);
}

.site-marker-bcparks {
  background: var(--accent-bcparks);
}

.site-marker-cityparks {
  background: var(--accent-cityparks);
}

.site-marker-tribal {
  background: var(--accent-tribal);
}

.site-marker-cacityparks {
  background: var(--accent-cacityparks);
}

.site-marker-catribal {
  background: var(--accent-catribal);
}

.site-marker-statepark,
.site-marker-stateparks {
  background: var(--accent-statepark);
}

.site-marker-fws {
  background: var(--accent-fws);
}

.site-marker-ksspc {
  background: var(--accent-ksspc);
}

.site-marker-kyspc {
  background: var(--accent-kyspc);
}

.site-marker-tnspc {
  background: var(--accent-tnspc);
}

.site-marker-gaspc {
  background: var(--accent-gaspc);
}

.site-marker-nmspc {
  background: var(--accent-nmspc);
}

.site-marker-azspc {
  background: var(--accent-azspc);
}

.site-marker-utspc {
  background: var(--accent-utspc);
}

.site-marker-flspc {
  background: var(--accent-flspc);
}

.site-marker-okspc {
  background: var(--accent-okspc);
}

.site-marker-wvspc {
  background: var(--accent-wvspc);
}

.site-marker-mospc {
  background: var(--accent-mospc);
}

.site-marker-txspc {
  background: var(--accent-txspc);
}

.site-marker-vaspc {
  background: var(--accent-vaspc);
}

.site-marker-cospc {
  background: var(--accent-cospc);
}

.site-marker-scspc {
  background: var(--accent-scspc);
}

.site-marker-ncspc {
  background: var(--accent-ncspc);
}
.site-marker-paspc {
  background: var(--accent-paspc);
}
.site-marker-nyspc {
  background: var(--accent-nyspc);
}
.site-marker-midnrc {
  background: var(--accent-midnrc);
}
.site-marker-mnspc {
  background: var(--accent-mnspc);
}
.site-marker-inspc {
  background: var(--accent-inspc);
}
.site-marker-wispc {
  background: var(--accent-wispc);
}
.site-marker-ohspc {
  background: var(--accent-ohspc);
}
.site-marker-waspc {
  background: var(--accent-waspc);
}
.site-marker-ilspc {
  background: var(--accent-ilspc);
}
.site-marker-orspc {
  background: var(--accent-orspc);
}
.site-marker-caspc {
  background: var(--accent-caspc);
}
.site-marker-njspc {
  background: var(--accent-njspc);
}
.site-marker-idspc {
  background: var(--accent-idspc);
}
.site-marker-madcrc {
  background: var(--accent-madcrc);
}
.site-marker-nespc {
  background: var(--accent-nespc);
}
.site-marker-mdspc {
  background: var(--accent-mdspc);
}
.site-marker-iaspc {
  background: var(--accent-iaspc);
}
.site-marker-ctspc {
  background: var(--accent-ctspc);
}
.site-marker-nhspc {
  background: var(--accent-nhspc);
}
.site-marker-mespc {
  background: var(--accent-mespc);
}
.site-marker-laspc {
  background: var(--accent-laspc);
}
.site-marker-vtspc {
  background: var(--accent-vtspc);
}
.site-marker-mtspc {
  background: var(--accent-mtspc);
}
.site-marker-arspc {
  background: var(--accent-arspc);
}
.site-marker-alspc {
  background: var(--accent-alspc);
}
.site-marker-msspc {
  background: var(--accent-msspc);
}
.site-marker-wyspc {
  background: var(--accent-wyspc);
}
.site-marker-despc {
  background: var(--accent-despc);
}
.site-marker-nvspc {
  background: var(--accent-nvspc);
}
.site-marker-rispc {
  background: var(--accent-rispc);
}
.site-marker-ndspc {
  background: var(--accent-ndspc);
}
.site-marker-sdspc {
  background: var(--accent-sdspc);
}
/* Actually used, unlike most of the .site-marker-<cabin-agency> entries
   above (cabins short-circuit to .cabin-marker-* before ever reaching
   here) - iconForSite()'s generic per-agency fallback genuinely produces
   this class for dispersed pins, since isDispersedRecord() doesn't get
   its own branch there. */
.site-marker-fs-dispersed {
  background: var(--accent-fs-dispersed);
  /* A dashed outline (in place of the solid white ring every other pin
     uses) is the one shape-level cue, on top of the muted color, that
     this is a different kind of listing - "informal," not just another
     agency. */
  border-style: dashed;
}

/* Cabin pins - a house silhouette (peaked-roof pentagon via clip-path)
   rather than the round campsite dot, so a cabin reads as visually distinct
   from a campsite at a glance on the map, not just a different color of the
   same shape. Colored per agency the same way .site-marker-* colors
   campsite pins (base/unmodified = USACE, using the generic cabin brand
   color rather than a specific agency's, same as .site-marker's own base
   rule uses the generic green rather than "USACE's color"). */
.cabin-marker {
  width: 15px;
  height: 15px;
  background: var(--accent-cabin);
  border: 2px solid white;
  box-shadow: 0 0 3px rgba(0, 0, 0, 0.5);
  cursor: pointer;
  clip-path: polygon(50% 0%, 100% 38%, 100% 100%, 0% 100%, 0% 38%);
}

.cabin-marker-nps {
  background: var(--accent-nps);
}

.cabin-marker-blm {
  background: var(--accent-blm);
}

.cabin-marker-fs {
  background: var(--accent-fs);
}

.cabin-marker-fws {
  background: var(--accent-fws);
}

.cabin-marker-ksspc {
  background: var(--accent-ksspc);
}

.cabin-marker-kyspc {
  background: var(--accent-kyspc);
}

.cabin-marker-tnspc {
  background: var(--accent-tnspc);
}

.cabin-marker-gaspc {
  background: var(--accent-gaspc);
}

.cabin-marker-nmspc {
  background: var(--accent-nmspc);
}

.cabin-marker-azspc {
  background: var(--accent-azspc);
}

.cabin-marker-utspc {
  background: var(--accent-utspc);
}

.cabin-marker-flspc {
  background: var(--accent-flspc);
}

.cabin-marker-okspc {
  background: var(--accent-okspc);
}

.cabin-marker-wvspc {
  background: var(--accent-wvspc);
}

.cabin-marker-mospc {
  background: var(--accent-mospc);
}

.cabin-marker-txspc {
  background: var(--accent-txspc);
}

.cabin-marker-vaspc {
  background: var(--accent-vaspc);
}

.cabin-marker-cospc {
  background: var(--accent-cospc);
}

.cabin-marker-scspc {
  background: var(--accent-scspc);
}

.cabin-marker-ncspc {
  background: var(--accent-ncspc);
}
.cabin-marker-paspc {
  background: var(--accent-paspc);
}
.cabin-marker-nyspc {
  background: var(--accent-nyspc);
}
.cabin-marker-midnrc {
  background: var(--accent-midnrc);
}
.cabin-marker-mnspc {
  background: var(--accent-mnspc);
}
.cabin-marker-inspc {
  background: var(--accent-inspc);
}
.cabin-marker-wispc {
  background: var(--accent-wispc);
}
.cabin-marker-ohspc {
  background: var(--accent-ohspc);
}
.cabin-marker-waspc {
  background: var(--accent-waspc);
}
.cabin-marker-ilspc {
  background: var(--accent-ilspc);
}
.cabin-marker-orspc {
  background: var(--accent-orspc);
}
.cabin-marker-caspc {
  background: var(--accent-caspc);
}
.cabin-marker-njspc {
  background: var(--accent-njspc);
}
.cabin-marker-idspc {
  background: var(--accent-idspc);
}
.cabin-marker-madcrc {
  background: var(--accent-madcrc);
}
.cabin-marker-nespc {
  background: var(--accent-nespc);
}
.cabin-marker-mdspc {
  background: var(--accent-mdspc);
}
.cabin-marker-iaspc {
  background: var(--accent-iaspc);
}
.cabin-marker-ctspc {
  background: var(--accent-ctspc);
}
.cabin-marker-nhspc {
  background: var(--accent-nhspc);
}
.cabin-marker-mespc {
  background: var(--accent-mespc);
}
.cabin-marker-laspc {
  background: var(--accent-laspc);
}
.cabin-marker-vtspc {
  background: var(--accent-vtspc);
}
.cabin-marker-mtspc {
  background: var(--accent-mtspc);
}
.cabin-marker-arspc {
  background: var(--accent-arspc);
}
.cabin-marker-alspc {
  background: var(--accent-alspc);
}
.cabin-marker-msspc {
  background: var(--accent-msspc);
}
.cabin-marker-wyspc {
  background: var(--accent-wyspc);
}
.cabin-marker-despc {
  background: var(--accent-despc);
}
.cabin-marker-nvspc {
  background: var(--accent-nvspc);
}
.cabin-marker-rispc {
  background: var(--accent-rispc);
}
.cabin-marker-ndspc {
  background: var(--accent-ndspc);
}
.cabin-marker-sdspc {
  background: var(--accent-sdspc);
}
.cabin-marker-hispc {
  background: var(--accent-hispc);
}
.cabin-marker-akspc {
  background: var(--accent-akspc);
}

.cabin-marker-pccabins {
  background: var(--accent-pccabins);
}

.cabin-marker-bcparkscabins {
  background: var(--accent-bcparkscabins);
}

.cabin-marker-cacitycabins {
  background: var(--accent-cacitycabins);
}

.cabin-marker-citycabins {
  background: var(--accent-citycabins);
}

.cabin-marker-catribalcabins {
  background: var(--accent-catribalcabins);
}

/* Marks the ZIP/geolocation search center, distinct from the green
   campground pins - blue to match the search radius circle drawn
   alongside it. */
.search-center-marker {
  width: 14px;
  height: 14px;
  border-radius: 50%;
  background: var(--accent-blue);
  border: 2px solid white;
  box-shadow: 0 0 4px rgba(0, 0, 0, 0.5);
}

/* Marks whichever site's detail panel is currently open, so it's not just
   one identical green dot among however many others are nearby once the
   map has zoomed in. The halo (the second, wider box-shadow ring) is what
   makes it read as "selected" rather than just "a slightly bigger pin". */
.site-marker-highlighted {
  width: 18px;
  height: 18px;
  border-radius: 50%;
  background: var(--accent-blue);
  border: 3px solid white;
  box-shadow: 0 0 0 4px rgba(28, 110, 140, 0.35), 0 1px 6px rgba(0, 0, 0, 0.5);
  cursor: pointer;
}


#detail-panel {
  position: absolute;
  top: 0;
  right: 0;
  width: 320px;
  max-width: 90vw;
  height: 100%;
  overflow-y: auto;
  background: var(--surface);
  box-shadow: -2px 0 8px rgba(0, 0, 0, 0.15);
  padding: var(--sp-5);
  box-sizing: border-box;
  z-index: 400;
}

/* Unlike #detail-panel (which overlays the main map), this sits in normal
   document flow below the state map rather than popping up over it. */
#state-detail-panel {
  position: relative;
  flex: 0 0 auto;
  max-height: 45%;
  overflow-y: auto;
  background: var(--surface);
  border-top: 1px solid var(--border);
  box-shadow: 0 -2px 8px rgba(0, 0, 0, 0.08);
  padding: var(--sp-5);
  box-sizing: border-box;
}

/* Same [hidden]-vs-ID-selector specificity issue as the modal backdrop:
   this rule must exist or "flex: 0 0 auto" would win and the panel would
   never actually disappear when .hidden is set. */
#state-detail-panel[hidden] {
  display: none;
}

#detail-panel h2, #state-detail-panel h2 {
  font-family: var(--font-heading);
  margin: 0 var(--sp-6) var(--sp-4) 0;
  font-size: var(--text-lg);
}

#detail-panel h2 .agency-badge, #state-detail-panel h2 .agency-badge {
  vertical-align: middle;
  margin-right: var(--sp-3);
}

.detail-image {
  display: block;
  width: 100%;
  height: 150px;
  object-fit: cover;
  border-radius: 8px;
  margin-bottom: var(--sp-4);
}

/* Class and attribute selectors have equal specificity, but author
   styles always beat the UA stylesheet's [hidden] { display: none }
   regardless of order - same issue hit twice before with IDs, applies
   here too since this sets "display" unconditionally. */
.detail-image[hidden] {
  display: none;
}

.detail-close-btn {
  position: absolute;
  top: 0.75rem;
  right: 0.75rem;
  width: 2rem;
  height: 2rem;
  border: none;
  border-radius: 50%;
  /* Solid backdrop, not transparent: this sits directly on top of the
     campground photo (top-right corner), and a plain gray "X" with no
     background has no guaranteed contrast against an arbitrary photo. */
  background: rgba(0, 0, 0, 0.45);
  box-shadow: 0 1px 4px rgba(0, 0, 0, 0.3);
  font-size: var(--text-2xl);
  line-height: 1;
  cursor: pointer;
  color: white;
  display: flex;
  align-items: center;
  justify-content: center;
  transition: background 0.15s ease;
}

.detail-close-btn:hover {
  background: rgba(0, 0, 0, 0.65);
}

.detail-description {
  white-space: pre-line;
  color: var(--ink);
  font-size: var(--text-sm);
  margin-bottom: var(--sp-5);
}

.detail-notices {
  margin: var(--sp-3) 0;
  font-size: var(--text-xs);
}

.detail-notices summary {
  cursor: pointer;
  color: var(--accent-blue);
  font-weight: 500;
}

.detail-notices ul {
  margin: var(--sp-3) 0 0;
  padding-left: var(--sp-5);
  color: var(--ink);
}

.detail-notices li {
  margin-bottom: var(--sp-3);
  white-space: pre-line;
}

.detail-row {
  display: flex;
  justify-content: space-between;
  gap: var(--sp-4);
  padding: var(--sp-3) 0;
  border-bottom: 1px solid #eee;
  font-size: var(--text-sm);
}

.detail-label {
  color: var(--ink-muted);
  flex-shrink: 0;
}

.detail-note {
  font-size: var(--text-2xs);
  color: var(--ink-muted);
  font-style: italic;
  margin: var(--sp-1) 0 var(--sp-3);
}

/* The one disclosure every dispersed-site detail panel opens with -
   deliberately NOT the same treatment as a routine .detail-note (this is
   the single most important piece of context in the whole panel, not a
   minor caveat), and NOT --accent-warn's red either (that's reserved for
   real safety alerts elsewhere on this site - a data-quality disclosure
   isn't that). Same tinted-box-with-border-left language as
   .weather-alert, just in this mode's own muted accent color instead. */
.dispersed-disclosure {
  font-style: normal;
  color: var(--ink);
  background: rgba(138, 125, 107, 0.12);
  border-left: 3px solid var(--accent-fs-dispersed);
  padding: var(--sp-3) var(--sp-4);
  border-radius: 6px;
  margin: var(--sp-3) 0 var(--sp-5);
}

.availability-block {
  margin: var(--sp-5) 0;
  padding: var(--sp-5);
  background: var(--tint);
  border-radius: 8px;
}

/* Current conditions + 7-day forecast (see fetchWeatherFor/renderWeatherInto
   in app.js) - same boxed-callout treatment as .availability-block above,
   placed right under the title since it's glanceable info worth seeing
   before wading into amenity rows. */
.weather-block {
  display: block;
  margin: var(--sp-5) 0;
  padding: var(--sp-5);
  background: var(--tint);
  border-radius: 8px;
  font-size: var(--text-sm);
}

.weather-block > .detail-label {
  display: block;
  margin-bottom: var(--sp-3);
  font-weight: 600;
}

.weather-current {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: var(--sp-3);
  margin-bottom: var(--sp-4);
}

.weather-current-temp {
  font-family: var(--font-heading);
  font-size: var(--text-2xl);
  font-weight: 600;
  color: var(--ink);
}

.weather-current-desc {
  color: var(--ink);
}

.weather-current-extra {
  flex-basis: 100%;
  font-size: var(--text-2xs);
  color: var(--ink-muted);
}

/* Wide content (7 day-cards) scrolls in its own container rather than
   widening the whole detail panel - same convention this project already
   uses for other horizontally-overflowing content. */
.weather-forecast {
  display: flex;
  gap: var(--sp-3);
  overflow-x: auto;
  padding-bottom: var(--sp-1);
}

.weather-day {
  flex: 0 0 auto;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--sp-1);
  min-width: 4.2rem;
  padding: var(--sp-3) var(--sp-2);
  background: var(--surface);
  border-radius: 6px;
  font-size: var(--text-2xs);
  text-align: center;
}

.weather-day-label {
  font-weight: 600;
  color: var(--ink);
}

.weather-day-desc {
  color: var(--ink-muted);
  line-height: 1.2;
}

.weather-day-temps {
  color: var(--ink);
  font-variant-numeric: tabular-nums;
}

.weather-day-wind {
  color: var(--ink-muted);
  font-variant-numeric: tabular-nums;
}

/* Active NWS weather alerts, rendered just below the forecast strip (see
   renderWeatherInto in app.js). Only ever present for US sites - Canadian
   sites get an empty alerts array, so this block simply never renders
   there rather than needing its own conditional CSS. */
.weather-alerts {
  display: flex;
  flex-direction: column;
  gap: var(--sp-3);
  margin-top: var(--sp-4);
  padding-top: var(--sp-4);
  border-top: 1px solid rgba(0, 0, 0, 0.08);
}

.weather-alert {
  display: flex;
  flex-direction: column;
  gap: var(--sp-1);
  padding: var(--sp-3) var(--sp-3);
  border-radius: 6px;
  font-size: var(--text-2xs);
  border-left: 3px solid var(--accent-warn);
}

.weather-alert strong {
  color: var(--accent-warn);
}

.weather-alert-severe {
  background: rgba(179, 38, 30, 0.1);
}

.weather-alert-minor {
  background: rgba(179, 38, 30, 0.05);
}

.weather-alerts > a {
  align-self: flex-start;
  color: var(--accent-blue);
  font-size: var(--text-2xs);
  text-decoration: none;
}

.weather-alerts > a:hover {
  text-decoration: underline;
}

/* Links out to weather.com's 10-day forecast and Windy's radar for this
   exact coordinate (see externalWeatherLinksHtml in app.js) - renders
   immediately since neither depends on this file's own weather fetch. */
.weather-external-links {
  display: flex;
  gap: var(--sp-5);
  margin-top: var(--sp-3);
  font-size: var(--text-2xs);
}

.weather-external-links a {
  color: var(--accent-blue);
  text-decoration: none;
}

.weather-external-links a:hover {
  text-decoration: underline;
}

.availability-btn {
  width: 100%;
  padding: var(--sp-3) var(--sp-4);
  border: 1px solid var(--accent-green);
  border-radius: 999px;
  background: var(--accent-green);
  color: white;
  font-size: var(--text-xs);
  font-weight: 500;
  cursor: pointer;
  transition: opacity 0.15s ease;
}

.availability-btn:disabled {
  opacity: 0.6;
  cursor: default;
}

.availability-result {
  margin-top: var(--sp-3);
}

.availability-rows {
  display: flex;
  flex-direction: column;
  gap: var(--sp-2);
}

.availability-row {
  display: flex;
  justify-content: space-between;
  padding: var(--sp-2) var(--sp-3);
  border-radius: 4px;
  font-size: var(--text-xs);
}

.availability-row.avail-open {
  background: var(--ok-tint);
  color: var(--ok);
  font-weight: 600;
}

.availability-row.avail-none {
  background: var(--danger-tint);
  color: var(--danger);
}

.availability-row.avail-unknown {
  color: var(--ink-muted);
}

.availability-note, .availability-empty {
  font-size: var(--text-2xs);
  color: var(--ink-muted);
  margin: var(--sp-3) 0 0;
}

/* Season block in the detail panel (open season, winter access, and - for
   cabins only - whether the unit is heated). Deliberately placed first,
   before every other field row, since whether somewhere is even open is the
   single fact that most determines whether a trip is possible at all.
   Same tinted-box treatment as .availability-block above so it still reads
   as "important panel", but its own class since it has no button/live-check
   state of its own - and note .availability-block is a genuinely different
   thing (Recreation.gov's live 7-night check), not a variant of this.

   Shared by Campgrounds and Cabins mode. Named for what it shows rather
   than for either mode. */
.season-block {
  margin: 0 0 var(--sp-5);
  padding: var(--sp-5);
  background: var(--tint);
  border-radius: 8px;
}

.season-heading {
  margin: 0 0 var(--sp-2);
  font-size: var(--text-xs);
  font-family: var(--font-body);
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.03em;
  color: var(--accent-green);
}

/* Cabins mode tints the heading to match its own accent, the same way the
   rest of that mode's chrome does. */
.season-heading-cabin {
  color: var(--accent-cabin);
}

.season-block .detail-row {
  border-bottom-color: rgba(0, 0, 0, 0.06);
}

.season-note {
  margin: var(--sp-3) 0 0;
}

/* Season badge (card + tooltip). Three honest states, see seasonStatus() in
   app.js: a real year-round/seasonal claim when the data supports it, and a
   neutral "Check availability" when it doesn't - never a fabricated status
   for a record whose season simply isn't known yet. */
.season-badge {
  display: inline-flex;
  align-items: center;
  padding: var(--sp-1) var(--sp-3);
  border-radius: 999px;
  font-size: var(--text-3xs);
  font-weight: 600;
}

.season-badge-unknown {
  background: rgba(90, 101, 112, 0.12);
  color: var(--ink-muted);
}

.season-badge-open {
  background: rgba(46, 139, 64, 0.12);
  color: var(--accent-green);
}

.season-badge-seasonal {
  background: rgba(217, 130, 43, 0.14);
  color: var(--accent-tva);
}

.detail-links {
  display: flex;
  flex-direction: column;
  gap: var(--sp-3);
  margin-top: var(--sp-5);
}

.detail-links a {
  font-size: var(--text-xs);
  font-weight: 500;
}

.detail-value {
  text-align: right;
  word-break: break-word;
}

/* Campgrounds / cards */

#campgrounds-section {
  background: var(--tint);
  border-radius: 20px;
  margin-top: var(--sp-7);
  margin-bottom: var(--sp-7);
}

#campgrounds-section h2 {
  margin-bottom: var(--sp-2);
  color: var(--accent-deep-ink);
}

#campgrounds-subhead {
  color: var(--ink-muted);
  margin-top: 0;
  font-size: var(--text-sm);
}

#campgrounds-view-toggle {
  display: flex;
  gap: var(--sp-3);
  margin: var(--sp-5) 0;
}

.view-toggle-btn {
  padding: var(--sp-3) var(--sp-5);
  border: 1px solid var(--border);
  border-radius: 999px;
  background: var(--surface);
  color: var(--ink-muted);
  font-size: var(--text-xs);
  font-weight: 500;
  cursor: pointer;
  transition: background 0.15s ease, color 0.15s ease, border-color 0.15s ease;
}

.view-toggle-btn.active {
  background: var(--accent-blue);
  border-color: var(--accent-blue);
  color: white;
}

.view-toggle-btn:not(.active):hover {
  border-color: var(--accent-blue);
  color: var(--accent-blue);
}

/* No display override here, so the default [hidden]{display:none} from the
   UA stylesheet just works - the specificity bug hit repeatedly elsewhere
   in this file only happens when *my own* rule also sets "display". */
#directory-filters {
  background: var(--tint);
  border-radius: 10px;
  padding: var(--sp-5);
  margin-bottom: var(--sp-5);
}

.filter-row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--sp-3);
  margin-bottom: var(--sp-3);
}

.filter-row:last-of-type {
  margin-bottom: 0;
}

.filter-row label {
  font-size: var(--text-xs);
  color: var(--ink);
  display: flex;
  align-items: center;
  gap: var(--sp-2);
}

#filter-state, #filter-agency, #filter-sort, #filter-search {
  padding: var(--sp-3) var(--sp-3);
  border: 1px solid var(--border);
  border-radius: 6px;
  font-size: var(--text-sm);
  background: var(--surface);
}

#filter-search {
  flex: 1 1 220px;
  min-width: 160px;
}

.filter-checkboxes label {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 999px;
  padding: var(--sp-2) var(--sp-4);
  font-size: var(--text-xs);
  cursor: pointer;
}

/* Wraps the campsite-only vs. cabin-only amenity checkboxes (#campsite-only-
   amenities/#cabin-only-amenities) - display: contents makes each <label>
   inside behave as a direct flex item of .filter-checkboxes, wrapping
   exactly like its un-wrapped siblings (Reservable only, Pet-friendly)
   rather than being laid out as one long inline run. Needs its own [hidden]
   override, same specificity issue hit repeatedly elsewhere in this file,
   since this rule also sets "display" unconditionally. */
.filter-checkboxes-group {
  display: contents;
}

.filter-checkboxes-group[hidden] {
  display: none;
}

#directory-count {
  margin: var(--sp-3) 0 0;
  font-size: var(--text-xs);
  color: var(--ink-muted);
}

#load-more-btn {
  display: block;
  margin: var(--sp-6) auto 0;
  padding: var(--sp-3) var(--sp-6);
  border: 1px solid var(--accent-blue);
  border-radius: 999px;
  background: var(--surface);
  color: var(--accent-blue);
  font-size: var(--text-sm);
  font-weight: 500;
  cursor: pointer;
  transition: background 0.15s ease, color 0.15s ease;
}

#load-more-btn:hover {
  background: var(--accent-blue);
  color: white;
}

/* This one DOES set display, so it needs the same [hidden] override fix
   hit several times before. */
#load-more-btn[hidden] {
  display: none;
}

#campground-cards {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
  gap: var(--sp-5);
  margin-top: var(--sp-6);
}

/* All three set "display" unconditionally above/below, which - as hit
   several times already in this file - beats the UA stylesheet's
   [hidden] { display: none } at equal specificity. Only #campground-cards
   and #back-to-states-link are newly toggled via JS here; #state-grid is
   new outright. */
#campground-cards[hidden], #state-grid[hidden], #back-to-states-link[hidden] {
  display: none;
}

#state-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
  gap: var(--sp-4);
  margin-top: var(--sp-6);
}

.state-tile {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--sp-2);
  padding: var(--sp-5) var(--sp-3);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  box-shadow: var(--shadow-sm);
  cursor: pointer;
  font-family: inherit;
  transition: transform 0.15s ease, box-shadow 0.15s ease, border-color 0.15s ease;
}

.state-tile:hover {
  transform: translateY(-2px);
  box-shadow: var(--shadow-md);
  border-color: var(--accent-blue);
}

.state-tile-name {
  font-family: var(--font-heading);
  font-weight: 600;
  font-size: var(--text-sm);
  color: var(--ink);
  text-align: center;
}

.state-tile-count {
  font-size: var(--text-2xs);
  color: var(--ink-muted);
}

.state-tile-breakdown {
  font-size: var(--text-3xs);
  color: var(--ink-muted);
  opacity: 0.8;
  text-align: center;
  line-height: 1.35;
}

#back-to-states-link {
  display: inline-block;
  margin-top: var(--sp-3);
  font-size: var(--text-xs);
  font-weight: 500;
  text-decoration: none;
}

#back-to-states-link:hover {
  text-decoration: underline;
}

.campground-card {
  position: relative;
  background: var(--surface);
  border: 1px solid var(--border);
  border-top: 3px solid var(--accent-blue);
  border-radius: var(--radius);
  padding: var(--sp-5);
  display: flex;
  flex-direction: column;
  gap: var(--sp-2);
  box-shadow: var(--shadow-sm);
  transition: transform 0.15s ease, box-shadow 0.15s ease;
}

/* Ties a card to its matching numbered pin on the map above (search
   results only - same green/number pairing used for the state modal's
   numbered pins and list). */
.card-number {
  position: absolute;
  top: -10px;
  left: -10px;
  width: 1.75rem;
  height: 1.75rem;
  border-radius: 50%;
  background: var(--accent-green);
  color: white;
  font-size: var(--text-xs);
  font-weight: 700;
  display: flex;
  align-items: center;
  justify-content: center;
  box-shadow: var(--shadow-sm);
  border: 2px solid white;
}

.campground-card:hover {
  transform: translateY(-3px);
  box-shadow: var(--shadow-md);
}

/* Same card shell as a campsite card, just a wood-toned top border instead
   of the campsite blue - the agency badge inside still shows the record's
   real managing agency in its own true color, this border is a generic
   "this is a cabin" cue at the card-grid level. */
.cabin-card {
  border-top-color: var(--accent-cabin);
}

.card-location {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-3);
  font-size: var(--text-2xs);
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--ink-muted);
}

/* Small pill marking a site as TVA-managed rather than USACE - used on both
   campground cards and the detail panel header, next to the site name. */
.agency-badge {
  display: inline-flex;
  align-items: center;
  padding: var(--sp-1) var(--sp-3);
  border-radius: 999px;
  font-size: var(--text-3xs);
  font-weight: 700;
  letter-spacing: 0.03em;
  text-transform: uppercase;
  color: white;
  flex-shrink: 0;
}

.agency-badge-tva {
  background: var(--accent-tva);
}

.agency-badge-nps {
  background: var(--accent-nps);
}

.agency-badge-blm {
  background: var(--accent-blm);
}

.agency-badge-fs {
  background: var(--accent-fs);
}

.agency-badge-pc {
  background: var(--accent-pc);
}

.agency-badge-bcparks {
  background: var(--accent-bcparks);
}

.agency-badge-statepark,
.agency-badge-stateparks {
  background: var(--accent-statepark);
}

.agency-badge-cityparks {
  background: var(--accent-cityparks);
}

.agency-badge-tribal {
  background: var(--accent-tribal);
}

.agency-badge-cacityparks {
  background: var(--accent-cacityparks);
}

.agency-badge-catribal {
  background: var(--accent-catribal);
}

.agency-badge-fws {
  background: var(--accent-fws);
}

.agency-badge-ksspc {
  background: var(--accent-ksspc);
}

.agency-badge-kyspc {
  background: var(--accent-kyspc);
}

.agency-badge-tnspc {
  background: var(--accent-tnspc);
}

.agency-badge-gaspc {
  background: var(--accent-gaspc);
}

.agency-badge-nmspc {
  background: var(--accent-nmspc);
}

.agency-badge-azspc {
  background: var(--accent-azspc);
}

.agency-badge-utspc {
  background: var(--accent-utspc);
}

.agency-badge-flspc {
  background: var(--accent-flspc);
}

.agency-badge-okspc {
  background: var(--accent-okspc);
}

.agency-badge-wvspc {
  background: var(--accent-wvspc);
}

.agency-badge-mospc {
  background: var(--accent-mospc);
}

.agency-badge-txspc {
  background: var(--accent-txspc);
}

.agency-badge-vaspc {
  background: var(--accent-vaspc);
}

.agency-badge-cospc {
  background: var(--accent-cospc);
}

.agency-badge-scspc {
  background: var(--accent-scspc);
}

.agency-badge-ncspc {
  background: var(--accent-ncspc);
}
.agency-badge-paspc {
  background: var(--accent-paspc);
}
.agency-badge-nyspc {
  background: var(--accent-nyspc);
}
.agency-badge-midnrc {
  background: var(--accent-midnrc);
}
.agency-badge-mnspc {
  background: var(--accent-mnspc);
}
.agency-badge-inspc {
  background: var(--accent-inspc);
}
.agency-badge-wispc {
  background: var(--accent-wispc);
}
.agency-badge-ohspc {
  background: var(--accent-ohspc);
}
.agency-badge-waspc {
  background: var(--accent-waspc);
}
.agency-badge-ilspc {
  background: var(--accent-ilspc);
}
.agency-badge-orspc {
  background: var(--accent-orspc);
}
.agency-badge-caspc {
  background: var(--accent-caspc);
}
.agency-badge-njspc {
  background: var(--accent-njspc);
}
.agency-badge-idspc {
  background: var(--accent-idspc);
}
.agency-badge-madcrc {
  background: var(--accent-madcrc);
}
.agency-badge-nespc {
  background: var(--accent-nespc);
}
.agency-badge-mdspc {
  background: var(--accent-mdspc);
}
.agency-badge-iaspc {
  background: var(--accent-iaspc);
}
.agency-badge-ctspc {
  background: var(--accent-ctspc);
}
.agency-badge-nhspc {
  background: var(--accent-nhspc);
}
.agency-badge-mespc {
  background: var(--accent-mespc);
}
.agency-badge-laspc {
  background: var(--accent-laspc);
}
.agency-badge-vtspc {
  background: var(--accent-vtspc);
}
.agency-badge-mtspc {
  background: var(--accent-mtspc);
}
.agency-badge-arspc {
  background: var(--accent-arspc);
}
.agency-badge-alspc {
  background: var(--accent-alspc);
}
.agency-badge-msspc {
  background: var(--accent-msspc);
}
.agency-badge-wyspc {
  background: var(--accent-wyspc);
}
.agency-badge-despc {
  background: var(--accent-despc);
}
.agency-badge-nvspc {
  background: var(--accent-nvspc);
}
.agency-badge-rispc {
  background: var(--accent-rispc);
}
.agency-badge-ndspc {
  background: var(--accent-ndspc);
}
.agency-badge-sdspc {
  background: var(--accent-sdspc);
}
.agency-badge-hispc {
  background: var(--accent-hispc);
}
.agency-badge-akspc {
  background: var(--accent-akspc);
}

.agency-badge-pccabins {
  background: var(--accent-pccabins);
}

.agency-badge-bcparkscabins {
  background: var(--accent-bcparkscabins);
}

.agency-badge-cacitycabins {
  background: var(--accent-cacitycabins);
}

.agency-badge-citycabins {
  background: var(--accent-citycabins);
}

.agency-badge-catribalcabins {
  background: var(--accent-catribalcabins);
}

.agency-badge-fs-dispersed {
  background: var(--accent-fs-dispersed);
}

/* Whether reservation_url is populated - not a guess, a real signal from
   the source data (RIDB's Reservable flag, or Campspot for TVA), so this
   is shown either way rather than only badging the positive case. */
.reservable-badge {
  display: inline-flex;
  align-items: center;
  padding: var(--sp-1) var(--sp-3);
  border-radius: 999px;
  font-size: var(--text-3xs);
  font-weight: 600;
}

.reservable-badge-yes {
  background: rgba(46, 139, 64, 0.12);
  color: var(--accent-green);
}

.reservable-badge-no {
  background: rgba(90, 101, 112, 0.12);
  color: var(--ink-muted);
}

.site-tooltip-reservable-yes {
  color: var(--accent-green);
  font-weight: 600;
}

.site-tooltip-reservable-no {
  color: var(--ink-muted);
}

.card-name {
  font-family: var(--font-heading);
  margin: 0;
  font-size: var(--text-lg);
}

.card-lake {
  color: var(--accent-blue);
  font-size: var(--text-xs);
  font-weight: 500;
}

.card-stats {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-3);
  font-size: var(--text-xs);
  color: var(--ink-muted);
  margin: var(--sp-2) 0;
}

.card-distance {
  font-size: var(--text-xs);
  font-weight: 600;
  color: var(--accent-green);
}

.card-detail-btn {
  align-self: flex-start;
  margin-top: var(--sp-3);
  padding: var(--sp-3) var(--sp-5);
  border: 1px solid var(--accent-blue);
  border-radius: 999px;
  background: var(--surface);
  color: var(--accent-blue);
  font-size: var(--text-xs);
  font-weight: 500;
  cursor: pointer;
  transition: background 0.15s ease, color 0.15s ease;
}

.card-detail-btn:hover {
  background: var(--accent-blue);
  color: white;
}

/* Sits directly under the detail panel's <h2>, not inside a flex column
   the way .card-detail-btn's base rule assumes (detail panels stack in
   normal block flow) - .card-detail-btn's own padding/border/color still
   apply as-is, this just controls this specific placement and the
   post-click "Copied!" state. */
.copy-link-btn {
  display: inline-block;
  margin: var(--sp-2) 0 var(--sp-4);
  font-size: var(--text-2xs);
  padding: var(--sp-2) var(--sp-4);
}

/* Beta-only "Report an issue with this listing" link (feedbackLinkHtml()
   in app.js) - an <a>, not a <button>, so it needs its own
   text-decoration reset on top of the shared .card-detail-btn look. */
.feedback-page-link {
  display: inline-block;
  margin: var(--sp-2) 0 var(--sp-4);
  font-size: var(--text-2xs);
  padding: var(--sp-2) var(--sp-4);
  text-decoration: none;
}

.copy-link-btn:disabled {
  cursor: default;
  border-color: var(--accent-green);
  color: var(--accent-green);
  background: var(--surface);
}

/* "In <Park> · hours, fees & park info" - the campground panel's link
   over to the parks map (fillParkLink() in app.js). Filled in after the
   panel renders, so the slot is always in the markup and simply stays
   empty for a listing that isn't inside any park. */
.detail-park-link {
  margin: 0 0 var(--sp-3);
  font-size: var(--text-xs);
  color: var(--ink-muted);
}

.detail-park-link a {
  font-weight: 600;
}

/* Agencies */

#agencies-section h2 {
  color: var(--accent-deep-ink);
}

/* Same card-grid language #purpose-list used to use (bg-tint, accent-green
   left border, auto-fit grid) - reused here rather than inventing a new
   pattern, since it's the same "several short parallel facts" shape. */
#agency-blurbs {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
  gap: var(--sp-5);
  margin: var(--sp-6) 0;
}

.agency-blurb {
  background: var(--tint);
  border-left: 3px solid var(--accent-green);
  border-radius: 8px;
  padding: var(--sp-5) var(--sp-5);
}

.agency-blurb h3 {
  font-family: var(--font-heading);
  font-size: var(--text-md);
  font-weight: 600;
  margin: 0 0 var(--sp-3);
  color: var(--ink);
}

.agency-blurb p {
  font-size: var(--text-xs);
  color: var(--ink-muted);
  margin: 0;
}

/* Footer. Text and links are literals rather than tokens because the
   ground is --accent-deep in both themes: pale sage for the text, and the
   logo's cream (brand/park-otter-logo.png, the otter's muzzle) for links,
   so the one solid band of brand colour on the page carries the brand's
   own pairing. Both were pale teals when the ground was teal. */

#site-footer {
  max-width: none;
  padding: 0;
  background: var(--accent-deep);
  color: #d6e3d6;
}

#site-footer .section-inner {
  padding: var(--sp-8) var(--sp-6) var(--sp-8);
  font-size: var(--text-xs);
}

#site-footer a {
  color: #f2dcaf;
}

#footer-updated {
  font-size: var(--text-2xs);
  color: #9fbcc2;
}

/* State drill-down modal */

#state-modal-backdrop {
  position: fixed;
  inset: 0;
  background: rgba(0, 0, 0, 0.5);
  z-index: 2000;
  display: flex;
  align-items: center;
  justify-content: center;
}

/* The [hidden] attribute selector needs to beat the plain ID selector
   above, or "display: flex" wins and the modal never actually hides. */
#state-modal-backdrop[hidden] {
  display: none;
}

#state-modal {
  position: relative;
  background: var(--surface);
  width: 92vw;
  max-width: 1100px;
  height: 85vh;
  border-radius: 14px;
  box-shadow: 0 4px 24px rgba(0, 0, 0, 0.3);
  display: flex;
  flex-direction: column;
  padding: var(--sp-5);
  box-sizing: border-box;
}

#state-modal h2 {
  margin: 0 var(--sp-7) var(--sp-4) 0;
  font-size: var(--text-xl);
  color: var(--accent-deep-ink);
}

#state-modal-close {
  position: absolute;
  top: 0.75rem;
  right: 0.75rem;
  width: 2rem;
  height: 2rem;
  border: none;
  background: transparent;
  font-size: var(--text-2xl);
  line-height: 1;
  cursor: pointer;
  color: var(--ink-muted);
}

#state-modal-close:hover {
  color: var(--ink);
}

/* One chip per agency present in this state (rendered by
   renderStateAgencyFilter) - a color swatch that doubles as a legend for
   the map's numbered pins (they had no key anywhere before this), plus a
   checkbox that filters both the map and the list below. Hidden entirely
   when a state only has one agency, so there's nothing to filter/key. */
#state-agency-filter {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--sp-3) var(--sp-5);
  margin: 0 0 var(--sp-4);
  padding-bottom: var(--sp-4);
  border-bottom: 1px solid var(--border);
  font-size: var(--text-2xs);
  color: var(--ink);
}

/* Same [hidden]-vs-flex specificity issue hit repeatedly elsewhere in this
   file - the unconditional "display: flex" above would otherwise win. */
#state-agency-filter[hidden] {
  display: none;
}

.state-agency-filter-bulk {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  font-size: var(--text-3xs);
  color: var(--ink-muted);
}

.state-agency-filter-bulk button {
  background: none;
  border: none;
  padding: 0;
  font-size: inherit;
  font-family: inherit;
  color: var(--accent-blue);
  cursor: pointer;
  text-decoration: underline;
}

.state-agency-filter-bulk button:hover {
  color: var(--accent-blue-dark);
}

.state-agency-toggle {
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  cursor: pointer;
  white-space: nowrap;
}

#state-modal-body {
  flex: 1;
  display: flex;
  gap: var(--sp-5);
  min-height: 0;
}

#state-map-wrapper {
  display: flex;
  flex-direction: column;
  flex: 1 1 68%;
  border-radius: var(--radius);
  overflow: hidden;
  min-height: 0;
}

#state-map {
  flex: 1 1 auto;
  min-height: 0;
  width: 100%;
}

#state-list {
  flex: 1 1 32%;
  overflow-y: auto;
  border-left: 1px solid #eee;
  padding-left: var(--sp-5);
}

.state-list-lake-heading {
  font-family: var(--font-heading);
  margin: var(--sp-5) 0 var(--sp-2);
  font-size: var(--text-sm);
  text-transform: uppercase;
  letter-spacing: 0.03em;
  color: var(--accent-green);
  border-bottom: 2px solid var(--accent-green);
  padding-bottom: var(--sp-2);
}

.state-list-lake-heading:first-child {
  margin-top: 0;
}

/* Only ever appears when a state's real site count exceeds
   STATE_MODAL_MARKER_CAP in app.js (Montana/Idaho's dispersed-camping
   counts as of this writing - see that constant's own comment) - an
   honest "you're seeing a subset" note, not a silent truncation. */
.state-list-truncated-note {
  font-size: var(--text-2xs);
  color: var(--ink-muted);
  font-style: italic;
  margin: 0 0 var(--sp-4);
  padding-bottom: var(--sp-3);
  border-bottom: 1px solid var(--border);
}

.state-list-item {
  display: flex;
  align-items: baseline;
  gap: var(--sp-3);
  padding: var(--sp-3) 0;
  border-bottom: 1px solid #f0f0f0;
  font-size: var(--text-sm);
  cursor: pointer;
  transition: background 0.12s ease;
}

.state-list-item:hover {
  background: var(--tint);
}

.state-list-item:hover .state-list-name {
  color: var(--accent-blue);
}

.state-list-number {
  flex-shrink: 0;
  width: 1.5rem;
  height: 1.5rem;
  border-radius: 50%;
  background: var(--accent-green);
  color: white;
  font-size: var(--text-2xs);
  font-weight: bold;
  display: flex;
  align-items: center;
  justify-content: center;
}

.state-list-number.unplaced {
  background: #999;
}

.state-list-number-tva {
  background: var(--accent-tva);
}

.state-list-number-nps {
  background: var(--accent-nps);
}

.state-list-number-blm {
  background: var(--accent-blm);
}

.state-list-number-fs {
  background: var(--accent-fs);
}

.state-list-number-pc {
  background: var(--accent-pc);
}

.state-list-number-bcparks {
  background: var(--accent-bcparks);
}

.state-list-number-statepark,
.state-list-number-stateparks {
  background: var(--accent-statepark);
}

.state-list-number-cityparks {
  background: var(--accent-cityparks);
}

.state-list-number-tribal {
  background: var(--accent-tribal);
}

.state-list-number-cacityparks {
  background: var(--accent-cacityparks);
}

.state-list-number-catribal {
  background: var(--accent-catribal);
}

.state-list-number-fws {
  background: var(--accent-fws);
}

.state-list-number-ksspc {
  background: var(--accent-ksspc);
}

.state-list-number-kyspc {
  background: var(--accent-kyspc);
}

.state-list-number-tnspc {
  background: var(--accent-tnspc);
}

.state-list-number-gaspc {
  background: var(--accent-gaspc);
}

.state-list-number-nmspc {
  background: var(--accent-nmspc);
}

.state-list-number-azspc {
  background: var(--accent-azspc);
}

.state-list-number-utspc {
  background: var(--accent-utspc);
}

.state-list-number-flspc {
  background: var(--accent-flspc);
}

.state-list-number-okspc {
  background: var(--accent-okspc);
}

.state-list-number-wvspc {
  background: var(--accent-wvspc);
}

.state-list-number-mospc {
  background: var(--accent-mospc);
}

.state-list-number-txspc {
  background: var(--accent-txspc);
}

.state-list-number-vaspc {
  background: var(--accent-vaspc);
}

.state-list-number-cospc {
  background: var(--accent-cospc);
}

.state-list-number-scspc {
  background: var(--accent-scspc);
}

.state-list-number-ncspc {
  background: var(--accent-ncspc);
}
.state-list-number-paspc {
  background: var(--accent-paspc);
}
.state-list-number-nyspc {
  background: var(--accent-nyspc);
}
.state-list-number-midnrc {
  background: var(--accent-midnrc);
}
.state-list-number-mnspc {
  background: var(--accent-mnspc);
}
.state-list-number-inspc {
  background: var(--accent-inspc);
}
.state-list-number-wispc {
  background: var(--accent-wispc);
}
.state-list-number-ohspc {
  background: var(--accent-ohspc);
}
.state-list-number-waspc {
  background: var(--accent-waspc);
}
.state-list-number-ilspc {
  background: var(--accent-ilspc);
}
.state-list-number-orspc {
  background: var(--accent-orspc);
}
.state-list-number-caspc {
  background: var(--accent-caspc);
}
.state-list-number-njspc {
  background: var(--accent-njspc);
}
.state-list-number-idspc {
  background: var(--accent-idspc);
}
.state-list-number-madcrc {
  background: var(--accent-madcrc);
}
.state-list-number-nespc {
  background: var(--accent-nespc);
}
.state-list-number-mdspc {
  background: var(--accent-mdspc);
}
.state-list-number-iaspc {
  background: var(--accent-iaspc);
}
.state-list-number-ctspc {
  background: var(--accent-ctspc);
}
.state-list-number-nhspc {
  background: var(--accent-nhspc);
}
.state-list-number-mespc {
  background: var(--accent-mespc);
}
.state-list-number-laspc {
  background: var(--accent-laspc);
}
.state-list-number-vtspc {
  background: var(--accent-vtspc);
}
.state-list-number-mtspc {
  background: var(--accent-mtspc);
}
.state-list-number-arspc {
  background: var(--accent-arspc);
}
.state-list-number-alspc {
  background: var(--accent-alspc);
}
.state-list-number-msspc {
  background: var(--accent-msspc);
}
.state-list-number-wyspc {
  background: var(--accent-wyspc);
}
.state-list-number-despc {
  background: var(--accent-despc);
}
.state-list-number-nvspc {
  background: var(--accent-nvspc);
}
.state-list-number-rispc {
  background: var(--accent-rispc);
}
.state-list-number-ndspc {
  background: var(--accent-ndspc);
}
.state-list-number-sdspc {
  background: var(--accent-sdspc);
}
.state-list-number-hispc {
  background: var(--accent-hispc);
}
.state-list-number-akspc {
  background: var(--accent-akspc);
}

.state-list-number-pccabins {
  background: var(--accent-pccabins);
}

.state-list-number-bcparkscabins {
  background: var(--accent-bcparkscabins);
}

.state-list-number-cacitycabins {
  background: var(--accent-cacitycabins);
}

.state-list-number-citycabins {
  background: var(--accent-citycabins);
}

.state-list-number-catribalcabins {
  background: var(--accent-catribalcabins);
}

.state-list-number-fs-dispersed {
  background: var(--accent-fs-dispersed);
}

.state-list-name {
  flex: 1;
}

.site-marker-numbered {
  width: 22px;
  height: 22px;
  border-radius: 50%;
  background: var(--accent-green);
  border: 2px solid white;
  box-shadow: 0 0 3px rgba(0, 0, 0, 0.5);
  color: white;
  font-size: var(--text-3xs);
  font-weight: bold;
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
}

.site-marker-numbered-tva {
  background: var(--accent-tva);
}

.site-marker-numbered-nps {
  background: var(--accent-nps);
}

.site-marker-numbered-blm {
  background: var(--accent-blm);
}

.site-marker-numbered-fs {
  background: var(--accent-fs);
}

.site-marker-numbered-pc {
  background: var(--accent-pc);
}

.site-marker-numbered-bcparks {
  background: var(--accent-bcparks);
}

.site-marker-numbered-statepark,
.site-marker-numbered-stateparks {
  background: var(--accent-statepark);
}

.site-marker-numbered-cityparks {
  background: var(--accent-cityparks);
}

.site-marker-numbered-tribal {
  background: var(--accent-tribal);
}

.site-marker-numbered-cacityparks {
  background: var(--accent-cacityparks);
}

.site-marker-numbered-catribal {
  background: var(--accent-catribal);
}

.site-marker-numbered-fws {
  background: var(--accent-fws);
}

.site-marker-numbered-ksspc {
  background: var(--accent-ksspc);
}

.site-marker-numbered-kyspc {
  background: var(--accent-kyspc);
}

.site-marker-numbered-tnspc {
  background: var(--accent-tnspc);
}

.site-marker-numbered-gaspc {
  background: var(--accent-gaspc);
}

.site-marker-numbered-nmspc {
  background: var(--accent-nmspc);
}

.site-marker-numbered-azspc {
  background: var(--accent-azspc);
}

.site-marker-numbered-utspc {
  background: var(--accent-utspc);
}

.site-marker-numbered-flspc {
  background: var(--accent-flspc);
}

.site-marker-numbered-okspc {
  background: var(--accent-okspc);
}

.site-marker-numbered-wvspc {
  background: var(--accent-wvspc);
}

.site-marker-numbered-mospc {
  background: var(--accent-mospc);
}

.site-marker-numbered-txspc {
  background: var(--accent-txspc);
}

.site-marker-numbered-vaspc {
  background: var(--accent-vaspc);
}

.site-marker-numbered-cospc {
  background: var(--accent-cospc);
}

.site-marker-numbered-scspc {
  background: var(--accent-scspc);
}

.site-marker-numbered-ncspc {
  background: var(--accent-ncspc);
}
.site-marker-numbered-paspc {
  background: var(--accent-paspc);
}
.site-marker-numbered-nyspc {
  background: var(--accent-nyspc);
}
.site-marker-numbered-midnrc {
  background: var(--accent-midnrc);
}
.site-marker-numbered-mnspc {
  background: var(--accent-mnspc);
}
.site-marker-numbered-inspc {
  background: var(--accent-inspc);
}
.site-marker-numbered-wispc {
  background: var(--accent-wispc);
}
.site-marker-numbered-ohspc {
  background: var(--accent-ohspc);
}
.site-marker-numbered-waspc {
  background: var(--accent-waspc);
}
.site-marker-numbered-ilspc {
  background: var(--accent-ilspc);
}
.site-marker-numbered-orspc {
  background: var(--accent-orspc);
}
.site-marker-numbered-caspc {
  background: var(--accent-caspc);
}
.site-marker-numbered-njspc {
  background: var(--accent-njspc);
}
.site-marker-numbered-idspc {
  background: var(--accent-idspc);
}
.site-marker-numbered-madcrc {
  background: var(--accent-madcrc);
}
.site-marker-numbered-nespc {
  background: var(--accent-nespc);
}
.site-marker-numbered-mdspc {
  background: var(--accent-mdspc);
}
.site-marker-numbered-iaspc {
  background: var(--accent-iaspc);
}
.site-marker-numbered-ctspc {
  background: var(--accent-ctspc);
}
.site-marker-numbered-nhspc {
  background: var(--accent-nhspc);
}
.site-marker-numbered-mespc {
  background: var(--accent-mespc);
}
.site-marker-numbered-laspc {
  background: var(--accent-laspc);
}
.site-marker-numbered-vtspc {
  background: var(--accent-vtspc);
}
.site-marker-numbered-mtspc {
  background: var(--accent-mtspc);
}
.site-marker-numbered-arspc {
  background: var(--accent-arspc);
}
.site-marker-numbered-alspc {
  background: var(--accent-alspc);
}
.site-marker-numbered-msspc {
  background: var(--accent-msspc);
}
.site-marker-numbered-wyspc {
  background: var(--accent-wyspc);
}
.site-marker-numbered-despc {
  background: var(--accent-despc);
}
.site-marker-numbered-nvspc {
  background: var(--accent-nvspc);
}
.site-marker-numbered-rispc {
  background: var(--accent-rispc);
}
.site-marker-numbered-ndspc {
  background: var(--accent-ndspc);
}
.site-marker-numbered-sdspc {
  background: var(--accent-sdspc);
}
.site-marker-numbered-hispc {
  background: var(--accent-hispc);
}
.site-marker-numbered-akspc {
  background: var(--accent-akspc);
}

.site-marker-numbered-pccabins {
  background: var(--accent-pccabins);
}

.site-marker-numbered-bcparkscabins {
  background: var(--accent-bcparkscabins);
}

.site-marker-numbered-cacitycabins {
  background: var(--accent-cacitycabins);
}

.site-marker-numbered-citycabins {
  background: var(--accent-citycabins);
}

.site-marker-numbered-catribalcabins {
  background: var(--accent-catribalcabins);
}

.site-marker-numbered-fs-dispersed {
  background: var(--accent-fs-dispersed);
}

/* Same halo treatment as .site-marker-highlighted, but layered onto a
   numbered pin (state modal) instead of replacing it - keeps the number
   and agency color, just adds the ring plus a raised z-index (set in JS
   via setZIndexOffset) so the selected site's pin doesn't stay hidden
   under closely-packed neighbors once the state modal zooms in on it.
   numberedIcon() also sizes this pin at 30px instead of the usual 22px
   (font bumped to match) - the ring alone read as too subtle to notice at
   a glance among a dozen same-size, similarly-colored pins; a visibly
   bigger pin registers as "selected" even before the ring does.
   Deliberately placed AFTER .site-marker-numbered and its agency
   modifiers above: this and .site-marker-numbered are both single-class
   selectors (equal specificity), so source order decides which one's
   border/box-shadow wins on a pin carrying both classes - it has to come
   last, or the base rule silently cancels the ring. */
.site-marker-numbered-highlighted {
  box-shadow: 0 0 0 4px rgba(28, 110, 140, 0.45), 0 1px 6px rgba(0, 0, 0, 0.6);
  border: 3px solid white;
  font-size: var(--text-xs);
}

/* One badge per state on the national map (see renderStateIcons in app.js),
   replacing the old per-agency cluster bubbles - 30px, 25% smaller than the
   40px cluster bubbles it replaces. A single neutral color rather than the
   old per-agency palette, since the count it shows is now an aggregate
   across whichever agencies are checked in #map-layers, not one agency. */
.state-count-icon {
  width: 30px;
  height: 30px;
  border-radius: 50%;
  background: var(--accent-deep);
  border: 3px solid white;
  box-shadow: 0 1px 4px rgba(0, 0, 0, 0.5);
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
}

.state-count-icon div {
  color: white;
  font-weight: 700;
  font-size: var(--text-2xs);
}

.site-tooltip {
  font-size: var(--text-xs);
  line-height: 1.4;
}

.site-tooltip-agency {
  font-size: var(--text-3xs);
  font-weight: 700;
  letter-spacing: 0.03em;
  color: var(--ink-muted);
  margin-left: var(--sp-2);
}

/* Mobile. There were zero responsive rules anywhere before this - the
   worst offender was the state modal's side-by-side map+list layout,
   which would squeeze both into unusable ~100px slivers on a phone
   screen width, and the header nav, which has no room for 4 links plus
   the brand text at narrow widths. */
/* ---- Tablets and small laptops -------------------------------------------
   Between the phone layout below and the desktop default there was nothing.
   These sit between the two rather than repeating either. */
@media (max-width: 1024px) {
  section {
    padding: var(--sp-8) var(--sp-5);
  }

  #hero .section-inner {
    padding: var(--sp-8) var(--sp-5) var(--sp-7);
  }

  .subhead {
    margin-bottom: var(--sp-6);
  }

  #stats-bar {
    gap: var(--sp-7);
  }

  /* A 320px drawer over a ~750px map leaves too little map to navigate.
     Narrowing it keeps the two-pane layout usable a while longer before
     the 720px rules turn it into a bottom sheet. */
  #detail-panel {
    width: 268px;
  }

  #map-section {
    height: 64vh;
    min-height: 380px;
  }
}

@media (max-width: 720px) {
  /* Wrap rather than stack. A column gave all five children a row each and,
     because the header is sticky, that held about a fifth of a phone screen
     for the whole session. Wrapping lets the two narrow controls - "Saved"
     and the beta chip - share a line, which is one row back at no cost to
     anything else: the brand and the mode toggle are each too wide to pair
     with a neighbour, and the nav is width: 100% below, so it keeps its own
     line either way.

     justify-content has to be overridden. The base rule is space-between,
     which is right for one row of items but would shove "Saved" and the
     beta chip to opposite edges of their shared line. */
  #site-header {
    flex-wrap: wrap;
    align-items: center;
    justify-content: flex-start;
    gap: var(--sp-3);
    padding: var(--sp-4) var(--sp-5);
  }

  /* 38px keeps the logo (3.3:1) and the two mode pills on one line at
     360px; taller, they wrap and cost a third header row. */
  .brand-logo {
    height: 38px;
  }

  #site-header nav {
    width: 100%;
    gap: var(--sp-5);
    overflow-x: auto;
    -webkit-overflow-scrolling: touch;
  }

  #site-header nav::-webkit-scrollbar {
    display: none;
  }

  /* Camping | Parks: pills off, nav link on (see .nav-page-link). */
  #page-switch {
    display: none;
  }

  #site-header nav .nav-page-link {
    display: inline;
    white-space: nowrap;
    color: var(--accent-deep-ink);
    font-weight: 600;
  }

  /* The saved-sites panel anchors to its own button with "right: 0" and a
     20rem width. That works while the button sits at the right end of a
     single header row. In this column layout the wrapper is only as wide as
     the button and sits hard against the left edge, so a 320px panel opened
     leftward and hung most of the way off the screen - covering the "Beta
     feedback" chip and the first nav links instead of showing its own
     contents, which is what made the header look broken rather than merely
     cramped.

     Dropping the wrapper's own positioning hands the panel to #site-header,
     which is sticky and therefore a valid containing block. The panel then
     spans the header's content width and drops below the whole header
     rather than below one button - on a phone that is the better anchor
     anyway, since the button is not where the eye is. */
  #saved-sites-wrap {
    position: static;
  }

  .saved-sites-panel {
    left: var(--sp-5);
    right: var(--sp-5);
    width: auto;
    max-width: none;
  }

  section {
    padding: var(--sp-8) var(--sp-5);
  }

  #hero .section-inner {
    padding: var(--sp-8) var(--sp-5) var(--sp-8);
  }

  #hero h1 {
    font-size: var(--text-3xl);
  }

  .subhead {
    font-size: var(--text-sm);
  }

  #stats-bar {
    gap: var(--sp-6);
  }

  .stat-number {
    font-size: var(--text-xl);
  }

  /* One column: the divider between the two ways to search goes from the
     form's left edge to its top. */
  #search-panel {
    grid-template-columns: 1fr;
    padding: var(--sp-5);
    border-radius: 14px;
  }

  .search-block:first-child {
    padding-right: 0;
    padding-bottom: var(--sp-5);
  }

  #search-form {
    padding-left: 0;
    padding-top: var(--sp-5);
    border-left: 0;
    border-top: 1px solid rgba(255, 255, 255, 0.14);
  }

  #map-section {
    height: 60vh;
    min-height: 340px;
  }

  /* On a phone the top-left corner is already taken: Leaflet's zoom
     control sits there, and the four region buttons wrap to two lines
     from the right and reach it. User-reported: the hint drawn across the
     +/- buttons and under "Lower 48". Bottom-centre instead, above the
     collapsed "Map layers" pill, where its only neighbour is the
     clear-search pill - and that one only exists during a search, when
     the hint is beside the point anyway (hidden below for that case). */
  #map-hint {
    top: auto;
    left: 50%;
    bottom: 4.25rem;
    transform: translateX(-50%);
    max-width: calc(100% - 2rem);
    box-sizing: border-box;
    text-align: center;
  }

  #map-section:has(#map-clear-search:not([hidden])) #map-hint {
    display: none;
  }

  /* Bottom sheet that peeks the map above it, same idea as
     #state-detail-panel's mobile treatment below - no need to close the
     sheet just to see the map again. #detail-panel's base rule is
     "position: absolute; height: 100%" relative to #map-section, which on
     mobile is only 60vh tall (see above), so without escaping that box it
     was capped to the map's own height with the hero/rest of the page
     sitting right there around it - a different, worse-looking bug than
     the one this fixes. position: fixed measures top/bottom against the
     real viewport instead; renderDetail() scrolls #map-section flush with
     the viewport top first so that peeked strip reliably shows the map,
     not whatever the page happened to be scrolled to. 52% (map just over
     half, sheet just under) was a direct user request, tuned up from an
     initial 32% - z-index has to clear #site-header's sticky 500 as well
     as Leaflet's own panes, same reasoning as #state-detail-panel's
     z-index fix below. */
  #detail-panel {
    position: fixed;
    top: 52%;
    left: 0;
    right: 0;
    bottom: 0;
    width: 100%;
    max-width: 100%;
    height: auto;
    border-radius: 14px 14px 0 0;
    box-shadow: 0 -4px 16px rgba(0, 0, 0, 0.18);
    z-index: 1500;
  }

  #campgrounds-section, #trip-planner-section {
    border-radius: 12px;
  }

  .detail-row, .availability-row {
    flex-wrap: wrap;
  }

  /* Full agency names ("National Park Service (607)") get tight on a phone
     width - smaller text plus a width cap with wrapping keeps this from
     overflowing the map. */
  #map-layers {
    font-size: var(--text-3xs);
    padding: var(--sp-3) var(--sp-4);
    max-width: calc(100% - 2rem);
    min-width: 9rem;
  }

  /* Collapsed by default on mobile (see the matchMedia check in
     renderMapLayerControls) - up to 8 agency rows plus bulk actions was
     tall enough to cover most of the map on a phone, which is what this
     was built to fix. The toggle button/collapse behavior itself is now
     desktop+mobile (see the base, non-mobile rules above); this just sets
     mobile's default starting state and wraps long agency names instead of
     letting them overflow a phone-width panel. */
  .map-layer-toggle {
    white-space: normal;
  }

  /* The state modal's map+list are a row-flex 68%/32% split, meant for
     a ~1000px-wide modal. At mobile widths that leaves both panes too
     narrow to use, so this stacks them into a full-screen sheet instead. */
  #state-modal {
    width: 100vw;
    height: 100vh;
    max-width: none;
    border-radius: 0;
    padding: var(--sp-4);
  }

  #state-modal-body {
    flex-direction: column;
  }

  /* overflow:visible + no radius: #state-modal is already edge-to-edge
     with no rounding on mobile, so neither is needed for their original
     desktop purpose (clipping the map's corners to match the rounded
     modal) - and overflow:hidden here was clipping the absolutely
     positioned #state-detail-panel sheet below, since it's nested inside
     this wrapper. Overflow clipping follows DOM containment, not the
     positioning/containing-block chain, so positioning the sheet against
     #state-modal instead didn't exempt it from being cut off by this
     ancestor's overflow:hidden. */
  #state-map-wrapper {
    flex: 1 1 50%;
    overflow: visible;
    border-radius: 0;
  }

  #state-list {
    flex: 1 1 50%;
    border-left: none;
    border-top: 1px solid #eee;
    padding-left: 0;
    padding-top: var(--sp-4);
    margin-top: var(--sp-4);
  }

  /* #state-detail-panel is nested inside #state-map-wrapper, which is
     already capped to 50% of the modal above. Sizing it as a fraction of
     that (as on desktop, and as a first mobile attempt at 80% also did)
     necessarily fights the map for the same cramped budget - either the
     detail is unreadable or the map is "mostly obscured" (reported after
     the 80% attempt). Pulling it out as an absolutely-positioned overlay
     against #state-modal (position:relative) sidesteps that fight
     entirely: the map/list stay at their normal 50/50 flow split
     underneath, unaffected by whether detail is open, and the sheet
     leaves the map peeking through above it - no need to close the sheet
     just to see the map again.
     top: 50% lines the sheet's top edge up with the map/list boundary
     itself (#state-map-wrapper and #state-list are each flex-basis 50%
     above), so opening detail overlays essentially just the list, not
     the map - same "map stays fully visible" goal as #detail-panel's own
     52% mobile split above, previously reported as still "taking over
     the map" at the old 26% (which ate deep into the map's own share).
     Bottom sheet math: #state-modal has 0.75rem padding on mobile, so
     inset 0 lines this up with that edge exactly. */
  #state-detail-panel {
    position: absolute;
    top: 50%;
    left: 0;
    right: 0;
    bottom: 0;
    max-height: none;
    border-radius: 14px 14px 0 0;
    box-shadow: 0 -4px 16px rgba(0, 0, 0, 0.18);
    /* Neither #state-map-wrapper nor #state-modal establishes its own
       stacking context (position:relative alone doesn't - it also needs
       a non-auto z-index), so this sheet's z-index is compared directly
       against Leaflet's own internal panes, which aren't isolated either
       and go up to 700 (.leaflet-popup-pane). z-index:20 was losing to
       them in the region where the sheet overlaps the map (its top
       portion, exactly where the photo and close button live) - that's
       the actual cause of both, not a visibility/clipping issue. */
    z-index: 1000;
  }

  /* 32px falls short of the ~44px touch target guideline. */
  .detail-close-btn, #state-modal-close {
    width: 2.75rem;
    height: 2.75rem;
    font-size: var(--text-3xl);
  }
}

/* ---- Wide monitors --------------------------------------------------------
   Content is capped at 1100px while the map runs full-bleed, so past about
   1600px the page reads as a very wide short map with a narrow column of text
   stranded underneath. Let the text breathe and stop the map letterboxing. */
@media (min-width: 1600px) {
  section,
  .section-inner {
    max-width: 1320px;
  }

  #map-section {
    height: 62vh;
    max-height: 780px;
  }

  /* The drawer can afford to be readable rather than merely present. */
  #detail-panel {
    width: 380px;
  }

  #stats-bar {
    gap: var(--sp-9);
  }
}

/* ---- Dark theme -----------------------------------------------------------
   Only the role tokens are redefined; nothing else in the stylesheet needs to
   know a theme exists. The page ground sits darker than the card surfaces on
   it, which is why --bg and --surface are separate tokens.

   Agency accent colours are deliberately left alone here: they encode data,
   and shifting them per theme would mean a campground's colour on the map
   changed depending on the reader's OS setting. Phase 4 gives that set a
   contrast floor that holds on both grounds instead. */
@media (prefers-color-scheme: dark) {
  :root {
    /* The counterpart to the light declaration at the top of the file. This
       is what actually repaints the native select popups dark; the token
       swap below only ever reached elements the stylesheet draws itself. */
    color-scheme: dark;

    --bg:         #0f1719;
    --surface:    #172325;
    --tint:       #16292a;
    --ink:        #e6efee;
    --ink-muted:  #9db1b3;
    --border:     #2a3b3e;
    --shadow-sm:  0 1px 3px rgba(0, 0, 0, 0.4);
    --shadow-md:  0 10px 28px rgba(0, 0, 0, 0.45);

    /* --accent-deep itself is not redefined: it is a ground under white
       text and holds as one. Its text role lifts to a pale green, a step
       lighter than --ok so a heading and a status line stay distinct. */
    --accent-deep-ink: #a7dcb6;

    /* Status roles invert: the foreground lightens and the tint it sits on
       goes dark, so each pair keeps its contrast instead of becoming a
       bright chip glaring out of a dark page. --ok also has to work with no
       tint behind it at all, because .feedback-status sets only a colour. */
    --ok:            #7fd396;
    --ok-tint:       #14301c;
    --danger:        #f2a0aa;
    --danger-tint:   #351a1f;
    --notice:        #e8c07a;
    --notice-tint:   #33260f;
    --notice-edge:   #7a5c22;

    /* Same alphas, --surface's colour instead of white. This is what the
       agency layer panel was missing: it kept a white ground under
       near-white label text, so the whole list read as blank. */
    --overlay:       rgba(23, 35, 37, 0.95);
    --overlay-soft:  rgba(23, 35, 37, 0.92);
  }

  /* The one hover that names a literal rather than a token - the lit state
     of the beta chip, one step up from --notice-tint. */
  #header-feedback-link:hover {
    background: #47340f;
  }

  /* The hero scrim already darkens a photograph; it needs to go further so
     white headline text keeps its contrast on a dark page. */
  #hero-scrim {
    background: linear-gradient(rgba(6, 12, 13, 0.55), rgba(6, 12, 13, 0.78));
  }

  /* Leaflet's default tiles are bright. Damp them rather than swapping the
     tile provider, which would be a data change rather than a style one. */
  #map .leaflet-tile-pane {
    filter: brightness(0.78) saturate(0.85) contrast(1.05);
  }
}
