/* Ported from docs/mockup-all.html — no new colors/fonts/sizes outside css/tokens.css.
   Duplicate .seg / .legend declarations from the mockup are resolved here by keeping
   the later (map-section) rule, per docs/mockup-all.html source order. */

/* Self-hosted fonts (P3 deploy item, replaces the Google Fonts <link>; owner 2026-09-13).
   vendor/fonts/<family>/ + vendor/fonts/OFL.txt; latin + latin-ext subsets only, the
   weights/styles actually used in this file (see css/tokens.css --serif/--sans). */
/* Variable fonts: one rule per subset carries the whole weight range the file holds
   (IBM Plex Sans wght 100-700, Newsreader wght 200-800 + opsz; italic is opsz only). */
@font-face{
  font-family:"IBM Plex Sans";font-style:normal;font-weight:100 700;font-display:swap;
  src:url("../vendor/fonts/ibm-plex-sans/ibmplexsans-latin-ext.woff2") format("woff2");
  unicode-range:U+0100-02BA,U+02BD-02C5,U+02C7-02CC,U+02CE-02D7,U+02DD-02FF,U+0304,U+0308,U+0329,U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF;
}
@font-face{
  font-family:"IBM Plex Sans";font-style:normal;font-weight:100 700;font-display:swap;
  src:url("../vendor/fonts/ibm-plex-sans/ibmplexsans-latin.woff2") format("woff2");
  unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD;
}
@font-face{
  font-family:"Newsreader";font-style:italic;font-weight:400;font-display:swap;
  src:url("../vendor/fonts/newsreader/newsreader-italic-400-latin-ext.woff2") format("woff2");
  unicode-range:U+0100-02BA,U+02BD-02C5,U+02C7-02CC,U+02CE-02D7,U+02DD-02FF,U+0304,U+0308,U+0329,U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF;
}
@font-face{
  font-family:"Newsreader";font-style:italic;font-weight:400;font-display:swap;
  src:url("../vendor/fonts/newsreader/newsreader-italic-400-latin.woff2") format("woff2");
  unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD;
}
@font-face{
  font-family:"Newsreader";font-style:normal;font-weight:200 800;font-display:swap;
  src:url("../vendor/fonts/newsreader/newsreader-500-600-latin-ext.woff2") format("woff2");
  unicode-range:U+0100-02BA,U+02BD-02C5,U+02C7-02CC,U+02CE-02D7,U+02DD-02FF,U+0304,U+0308,U+0329,U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF;
}
@font-face{
  font-family:"Newsreader";font-style:normal;font-weight:200 800;font-display:swap;
  src:url("../vendor/fonts/newsreader/newsreader-500-600-latin.woff2") format("woff2");
  unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD;
}

*{box-sizing:border-box}
dialog{border:0;padding:0;margin:0;max-width:none;max-height:none;width:auto;height:auto;background:none;color:inherit}
dialog::backdrop{background:transparent}
html,body{margin:0;background:var(--paper);color:var(--ink);font-family:var(--sans);font-size:16px;line-height:1.35;-webkit-font-smoothing:antialiased}
button,select{font:inherit;color:inherit}
button{cursor:pointer;background:none;border:0;padding:0}
/* Focus rings only after keyboard interaction (UI batch #6, 2026-08-26): WebKit counts
   programmatic focus in a freshly opened dialog as :focus-visible, so the lightbox link
   and the search input lit up on touch. body.kb is set on the first Tab/arrow key and
   cleared on pointer/touch. Focus still moves into dialogs for assistive tech. */
body.kb :focus-visible{outline:2px solid var(--moss);outline-offset:2px}
:focus{outline:none}
.search input:focus{border-color:var(--moss)}
@media (prefers-reduced-motion:reduce){*{transition:none!important}}

/* ---------- shell ---------- */
/* UI batch #1 (2026-08-26): 100dvh with the -webkit-fill-available fallback — iOS
   standalone occasionally left a strip under the tab bar (100vh vs the visual
   viewport). The mobile tab bar is fixed (see the mobile block below). */
.app{height:100vh;height:-webkit-fill-available;height:100dvh;display:flex;flex-direction:column}
/* A1 (mobile touch pass, 2026-08-25): the scroll pane never scrolls horizontally —
   overflow-x:hidden is the structural backstop regardless of what a card/grid item
   tries to grow to; touch-action:pan-y keeps a vertical drag from being interpreted
   as a horizontal pan by the browser. */
.scroll{flex:1;overflow:auto;overflow-x:hidden;min-height:0;touch-action:pan-y}
.head{display:contents}
.head>*{padding:0 calc(var(--u)*2)}
/* mobile: the brand row collapses to a zero-height anchor (wordmark hidden) that carries
   the PWA status-bar inset; the gear floats top-right and the location block is the
   first visible row. Desktop restores the wordmark row. */
.head .brand{position:relative;height:0;padding-top:env(safe-area-inset-top)}
.head .where{padding-right:60px;margin-top:12px}
.head .chips{margin-left:0;margin-right:0;padding:0 calc(var(--u)*2) var(--u);border-bottom:1px solid var(--line)}
.brand{display:flex;align-items:baseline;justify-content:space-between;gap:var(--u)}
.brand h1{font:600 22px/1 var(--serif);margin:0;letter-spacing:-.01em;display:none}
.brand h1 span{color:var(--moss)}
.gear{width:44px;height:44px;display:grid;place-items:center;border-radius:var(--r);margin:0;position:absolute;right:6px;top:calc(8px + env(safe-area-inset-top))}
.gear:active{background:var(--moss-soft)}
.where{display:flex;align-items:center;gap:var(--u);margin-top:var(--u);font-size:14px;color:var(--ink-2)}
.where b{color:var(--ink);font-weight:500}
.where .pin{width:8px;height:8px;border-radius:50%;background:var(--moss);flex:none}
.where-pack{font-size:12px}
/* P2 mobile header (owner 2026-08-25): the whole two-line location block is the
   tappable header on mobile — see docs/design.md. Desktop resets it back to
   today's plain 14px text row. */
.loc{display:flex;align-items:flex-start;gap:var(--u);flex:1;text-align:left;min-height:44px;padding:4px 0}
.loc .pin{margin-top:8px}
.loc-text{display:block}
.loc b{font:600 20px/1.15 var(--serif);color:var(--ink)}
.loc small{display:block;font-size:12px;color:var(--ink-2);margin-top:2px}
.loc .loc-tail{white-space:nowrap}   /* admin trail + chevron never split across lines */
/* A4 (owner ruling 2026-08-25): the place name itself never wraps mid-name; a long
   name + tail wraps AFTER the name as a unit (the zero-width space in index.html's
   markup, between #where-b and .loc-tail, is the only break point). */
#where-b{white-space:nowrap}
.loc .chev{display:inline-block;width:6px;height:6px;border-top:1.5px solid var(--moss);border-right:1.5px solid var(--moss);transform:rotate(45deg);margin-left:6px;vertical-align:2px}
#change-location-btn{margin-left:auto;text-decoration:underline;text-underline-offset:3px;color:var(--moss);min-height:44px;font-size:14px;display:none}

/* Controls row = radius chip + species search (owner UI #17, 2026-08-27; built
   2026-09-09): one layout rule at both breakpoints, no List/Map toggle in the header
   any more (that is the .view-pill below). The native <select> sits invisibly over the
   chip so the picker still lists the full "2.5 km · 30 min walk" labels; the chip
   shows the short "2.5 km" (#radius-label, set by app.js) with the same caret. */
.controls{display:flex;gap:var(--u);margin-top:var(--u);align-items:center}
.radius-chip{position:relative;flex:none;height:44px;display:flex;align-items:center;padding:0 30px 0 12px;border:1px solid var(--line);border-radius:var(--r);background:var(--card);font-size:14px;color:var(--ink);white-space:nowrap;
  background-image:linear-gradient(45deg,transparent 50%,var(--ink-2) 50%),linear-gradient(135deg,var(--ink-2) 50%,transparent 50%);
  background-position:calc(100% - 16px) 19px,calc(100% - 11px) 19px;background-size:5px 5px;background-repeat:no-repeat}
.radius-chip{display:none}   /* owner 2026-09-13: no radius picker at either breakpoint; "Search this area" sets the radius (js updateAreaPill / searchThisArea) */
.radius-chip .radius{position:absolute;inset:0;width:100%;height:100%;margin:0;padding:0;border:0;opacity:0;appearance:none;-webkit-appearance:none;cursor:pointer;font-size:16px}
body.kb .radius-chip:focus-within{outline:2px solid var(--moss);outline-offset:2px}
.controls .spsearch{flex:1;min-width:0;margin:0}
.seg{margin-left:auto;display:flex;border:1px solid var(--line);border-radius:var(--r);overflow:hidden;height:40px;background:var(--card)}
.seg button{font:500 14px var(--sans);padding:0 14px;border:0;background:none;color:var(--ink-2);cursor:pointer}
.seg button[aria-pressed=true]{background:var(--moss);color:#fff}

.chips{display:flex;gap:6px;overflow-x:auto;margin:calc(var(--u)*1.75) 0 0;padding:0;scrollbar-width:none;-ms-overflow-style:none;overscroll-behavior-x:contain;touch-action:pan-x}
.chips::-webkit-scrollbar{display:none}
.chip{flex:none;height:36px;padding:0 14px;border-radius:18px;border:1px solid var(--line);background:var(--card);font-size:14px;white-space:nowrap;display:inline-flex;align-items:center;gap:6px}
.chip[aria-pressed=true]{background:var(--moss-soft);border-color:var(--moss);color:var(--moss);font-weight:500}
/* Category glyphs (owner 2026-09-12): 16px moss line icons in the .locate register —
   one accent, no fills, no new colors. Only the five categories carry one; "All" (the
   reset) and the toggles (modifiers, not categories) stay text. */
.chip svg{width:16px;height:16px;margin-left:-3px;stroke:var(--moss);fill:none;stroke-width:1.8;stroke-linecap:round;stroke-linejoin:round;flex:none}
.chips .gap{flex:none;width:1px;height:24px;background:var(--line);align-self:center;margin:0 4px}
/* Floating filter pills (owner 2026-09-12): on DESKTOP, and on mobile in expand mode,
   the chip row leaves the header and floats top-left of the map inside .map-top
   (js/app.js placeChips moves the one #chips node). Desktop wraps: categories on the
   first line, the forced .gap break keeps the toggles on their own line. Mobile expand
   mode keeps the scrolling row. "Search this area" sits centered beneath the pills in
   the same wrapper, so it never collides with them. Mobile normal mode keeps the row in
   the header above the list or the map, unchanged. A right margin keeps the pills clear
   of the locate/expand/base stack. */
.map-top{position:absolute;top:12px;left:12px;right:12px;z-index:3;display:grid;gap:8px;justify-items:center;pointer-events:none}
.map-top>*{pointer-events:auto}
.map-top .chips{margin:0 48px 0 0;padding:0;border:0;justify-self:stretch}
.map-top .area-pill{position:static;transform:none;order:9}   /* after the chips, whose desktop header order:4 follows them here */

/* ---------- notices ---------- */
.notice{margin:var(--u) calc(var(--u)*2) 0;font-size:13px;color:var(--warn);display:block;padding:12px 12px;background:var(--warn-soft);border-radius:var(--r)}
/* Owner ruling 2026-08-25: red is reserved for safety content. Data/pack availability
   notices are quiet grey; the conservation-data notice uses the calm legal style
   (same as the species .legalbox). */
.notice.quiet{color:var(--ink-2);background:none;border:1px solid var(--line)}
.notice.calm{color:var(--ink-2);background:none;border:0;border-left:2px solid var(--line);border-radius:0;padding:0 0 0 10px}
.notice summary{list-style:none;cursor:pointer;display:flex;gap:var(--u);align-items:center;min-height:20px;line-height:1.4}
.notice summary::-webkit-details-marker{display:none}
.notice[open] summary .more{display:none}
.notice p{margin:8px 0 0;color:var(--ink)}


.count{padding:calc(var(--u)*2) calc(var(--u)*2) var(--u);font-size:13px;color:var(--ink-2)}
.count b{color:var(--ink);font-weight:500}
.count a{color:var(--moss);text-underline-offset:3px}

/* ---------- cards ---------- */
.list{padding:0 calc(var(--u)*2) calc(var(--u)*4);display:grid;gap:var(--u)}
/* A1 (mobile touch pass, 2026-08-25): a grid item's automatic minimum size defaults to
   its content's min-content — for .card that includes .dist's white-space:nowrap text,
   which was propagating up through .list's implicit column and pushing the whole card
   ~13px past a 390px viewport (measured via scripts/layout_check.mjs). min-width:0 on
   the card and its own grid children breaks that propagation; the nowrap cells still
   don't wrap, they just stop forcing their ancestors wider than the viewport. */
.card{display:grid;grid-template-columns:72px 1fr auto;gap:12px;align-items:start;padding:12px;background:var(--card);border:1px solid var(--line);border-radius:var(--r);text-align:left;width:100%;position:relative;min-width:0}
.card>div{min-width:0}
.card.peak{border-left:4px solid var(--moss);padding-left:9px}
.card.hush{background:var(--hush-soft);border-style:dashed}
.card:active{transform:translateY(1px)}
.photo{width:72px;height:72px;border-radius:4px;object-fit:cover;background:#cfd6c4}
.card.hush .photo{filter:grayscale(1);opacity:.7}
.name{font:500 20px/1.1 var(--serif);margin:0}
/* A1: some pack latin names are slash-joined ("Brassica/Hirschfeldia/Sisymbrium spp.")
   with no space break opportunities — overflow-wrap:anywhere lets them wrap instead of
   spilling past the card's edge (measured: scrollWidth 205 vs clientWidth 176). */
.latin{font:italic 400 14px/1.2 var(--serif);color:var(--ink-2);margin:2px 0 0;overflow-wrap:anywhere}
.meta{display:flex;gap:6px;flex-wrap:wrap;margin-top:8px;font-size:12px;color:var(--ink-2);align-items:center}
.tag{padding:2px 7px;border-radius:3px;font-weight:500;letter-spacing:.01em}
.tag.peak{background:var(--moss);color:#fff}
.tag.native{background:var(--moss-soft);color:var(--moss)}
.tag.invasive{background:var(--ink);color:var(--paper)}
.tag.hush{background:var(--hush);color:#fff}
.tag.warn{background:var(--warn);color:#fff}
.tag.unverified,.tag.planted{background:var(--line);color:var(--ink-2)} /* sighting tiers (2026-09-05): quiet register, existing tokens only */
/* Group PARENT card species count (rule 3, owner-approved mockup 2026-09-09): "7
   species" — outlined, never filled, because it is a count and not a status. Existing
   tokens only (transparent ground, --line hairline, --ink-2 text). */
.tag.species-n{background:transparent;border:1px solid var(--line);color:var(--ink-2);padding:1px 6px}
/* Rule 6: "one of <parent>" on a LEAF's own search-result row — the only place a leaf
   card renders. Quiet, same register as the site-count span beside it. */
.meta .one-of{color:var(--ink-2)}
.dist{text-align:right;font-size:14px;font-variant-numeric:tabular-nums;white-space:nowrap;min-width:0}
.dist small{display:block;font-size:11px;color:var(--ink-2)}
.months{grid-column:2/4;display:grid;grid-template-columns:repeat(12,1fr);gap:2px;margin-top:2px}
.months i{height:4px;background:var(--line);border-radius:1px}
.months i.on{background:var(--moss)}
.months i.now{outline:1px solid var(--ink);outline-offset:1px}
.card.hush .months i.on{background:var(--hush)}

/* ---------- map ---------- */
/* A2 (mobile touch pass, 2026-08-25): touch-action:none on both the MapLibre container
   (#map, where MapLibre's own gesture handlers bind) and its parent .map section — the
   browser's own pinch-zoom-the-page gesture was racing MapLibre's pinch-to-zoom-the-map
   handler on iOS Safari, and mid-gesture the page-zoom gesture would win, jolting the
   view. MapLibre owns all touch gestures on the map itself now. */
#map{position:absolute;inset:0;touch-action:none}
.hud{z-index:4}   /* above .map-top (3): the expand-mode pill row scrolls under the locate/×/base stack */
.maplibregl-ctrl-attrib{font:11px/1.3 var(--sans);background:rgba(246,243,234,.85)!important;color:var(--ink-2)}
.maplibregl-ctrl-attrib a{color:var(--ink-2)}
.maplibregl-ctrl-bottom-right .maplibregl-ctrl-group{display:none}

.hud{position:absolute;top:12px;left:12px;right:12px;display:flex;justify-content:space-between;align-items:flex-start;pointer-events:none;gap:8px}
.hud > *{pointer-events:auto}
.hud-r{display:grid;gap:8px;justify-items:end}
.hud-l{display:grid;gap:8px;justify-items:start}   /* the base tile: top-left on a phone, under the right stack on desktop */
.locate{width:40px;height:40px;border:1px solid var(--line);border-radius:var(--r);background:var(--card);display:grid;place-items:center;cursor:pointer}
.locate svg{width:20px;height:20px;stroke:var(--moss);fill:none;stroke-width:1.8;stroke-linecap:round;stroke-linejoin:round}
/* Base-layer tile (owner 2026-09-12, replacing the desktop Map/Satellite segmented
   control and the mobile layers-icon toggle): one 40px thumbnail of the OTHER layer, no
   text (aria-label only), 2px card border + line hairline. Desktop: LAST in the
   top-right stack under locate (.hud-l ordered after .hud-r in the column). Phone
   (owner 2026-09-12, second pass): top-LEFT of the map in both map modes, opposite
   the locate/expand stack. Map thumbnail = inline SVG in the
   map tokens; satellite = a static NASA Landsat crop (GIBS WELD true color, public
   domain, icons/base-sat.png), never a live tile, so it works offline and costs no
   request. body.sat flips which one shows. */
.base-tile{width:40px;height:40px;padding:0;border:2px solid var(--card);outline:1px solid var(--line);border-radius:var(--r);background:var(--card);overflow:hidden;position:relative;cursor:pointer;display:block}
.base-tile .to-sat,.base-tile .to-map{display:block;position:absolute;inset:0;border-radius:3px;overflow:hidden}
.base-tile img,.base-tile svg{display:block;width:100%;height:100%;object-fit:cover}
.base-tile .to-map,body.sat .base-tile .to-sat{display:none}
body.sat .base-tile .to-map{display:block}
/* WP3 offline surfaces — quiet/attribution register, never red. */
/* Offline / loading pill: bottom-center, just above the attribution row, same in
   expand mode (owner 2026-08-26). */
/* "Search this area" pill (owner UI #8, 2026-09-09): top-center of the map, beneath the
   floating pills (inside .map-top since 2026-09-12). Filled moss (the view-pill action
   register, owner 2026-09-12) so it reads as an ACTION next to the outlined filter pills —
   outlined = a state you toggle, filled = a thing you do. Same at both breakpoints;
   hidden until the viewport drifts from the current search. */
.area-pill{position:absolute;top:12px;left:50%;transform:translateX(-50%);z-index:3;height:36px;padding:0 16px;border:1px solid var(--line);border-radius:18px;background:var(--card);color:var(--moss);font:500 13px var(--sans);cursor:pointer;white-space:nowrap}
/* Center cross (owner 2026-09-12, minimal by ruling: a bare 1.5px moss cross, no dot,
   no ring, paper halo) at the exact center of the map — the center of the circle
   "Search this area" would search. Shown and hidden with #area-pill; pointer-events
   none so it never eats a tap. DESKTOP ONLY (owner): never on a phone. */
.area-cross{display:none;position:absolute;left:50%;top:50%;width:22px;height:22px;margin:-11px 0 0 -11px;z-index:3;pointer-events:none;
  background:linear-gradient(var(--moss),var(--moss)) center/1.5px 100% no-repeat,linear-gradient(var(--moss),var(--moss)) center/100% 1.5px no-repeat;
  filter:drop-shadow(0 0 1px var(--paper)) drop-shadow(0 0 1px var(--paper))}
.map-pill{position:absolute;left:50%;transform:translateX(-50%);bottom:44px;z-index:3;padding:3px 10px;border-radius:12px;background:rgba(246,243,234,.85);font:11px/1.4 var(--sans);color:var(--ink-2);width:max-content;max-width:calc(100% - 24px);text-align:center}
.install-line{font-size:13px;color:var(--ink-2)}
/* ?debug=1 perf line (owner 2026-08-26) — fixed above the tab bar, monospace, never in the mockup */
#debug-line{position:fixed;left:0;right:0;bottom:calc(56px + env(safe-area-inset-bottom));z-index:30;padding:4px 8px;background:rgba(246,243,234,.92);border-top:1px solid var(--line);font:11px/1.3 ui-monospace,Menlo,Consolas,monospace;color:var(--ink);pointer-events:none}
@media (min-width:900px){ #debug-line{bottom:0} }
.install-line a{color:var(--moss)}
.hint.offline{margin-top:12px}
.sheet .hero-empty.offline{display:none}
.sheet.offline-photo .hero{display:none}
.sheet.offline-photo .hero-empty.offline{display:block}
/* Map expand control (owner 2026-08-25): mobile only, below locate. */
#map-expand{display:none}
#map-expand .x-on{display:none}
body.mapx #map-expand .x-off{display:none}
body.mapx #map-expand .x-on{display:block}

/* Legend: desktop always-visible rows; mobile a pill in the attribution "i" register
   (24px round, paper .85, no border) that toggles the rows (owner 2026-08-25). */
.legend{position:absolute;left:12px;bottom:28px;z-index:3;display:grid;gap:6px;justify-items:start}   /* above MapLibre's ctrl containers (z 2) so the pill isn't under the attribution strip */
.legend .rows{background:var(--card);border:1px solid var(--line);border-radius:var(--r);padding:8px 10px;font-size:12px;display:grid;gap:5px}
.legend.collapsed .rows{display:none}   /* owner 2026-09-13: desktop collapses too, remembered across visits (js initLegend) */
.legend i{display:inline-block;width:10px;height:10px;border-radius:50%;margin-right:6px;vertical-align:-1px}
.legend i.native{background:var(--moss)}
.legend i.invasive{background:var(--ink)}
.legend i.protected{border:2px solid var(--hush);background:none}
.legend .tr{width:18px;height:0;border-top:3px solid var(--moss);border-radius:0;vertical-align:3px}
.legend .pk{width:18px;height:0;border-top:2px dotted var(--ink-2);border-radius:0;vertical-align:3px}
.legend-toggle{display:grid;width:24px;height:24px;border-radius:12px;background:rgba(246,243,234,.85);place-items:center}
.legend-toggle svg{width:16px;height:16px;fill:var(--moss);stroke:var(--moss);stroke-width:2.5;stroke-linecap:round}
@media (max-width:899px){
  .sheet > div{-webkit-user-select:none;user-select:none}   /* UI #7: a text-selection drag would cancel the sheet drag */
  /* UI batch #1: fixed tab bar; the scroll pane and the map keep clear of it. */
  .tabs{position:fixed;left:0;right:0;bottom:0;z-index:5}
  .scroll{padding-bottom:calc(56px + env(safe-area-inset-bottom))}
  .map{margin-bottom:calc(56px + env(safe-area-inset-bottom))}
  body.mapx .map{margin-bottom:0}
  .legend{left:10px;bottom:10px}
  .legend .rows{font-size:11px;padding:5px 8px;gap:3px}   /* attribution register (UI #4) */
  /* (legend-toggle shows at both breakpoints since 2026-09-13; collapsed rule is global) */

  #map-expand{display:grid}
  /* Expanded map: the header stack (incl. the tab bar inside it) is gone; the map
     section is the only flex child left and fills 100dvh. Safe-area insets move onto
     the map controls because the header/tab bar that carried them are hidden. */
  body.mapx .head>*{display:none}
  body.mapx .hud{top:calc(12px + env(safe-area-inset-top))}
  body.mapx .map-top{top:calc(12px + env(safe-area-inset-top))}
  /* Phone (owner 2026-09-12, second pass): no filter pills over the map in expand mode
     (they stay in the header, which expand mode hides); "Search this area" is the one
     thing top-center and reads WHITE here — card fill, moss text — between the base
     tile (left) and locate/× (right), so the three corners balance. */
  /* .area-pill reads white at both breakpoints since 2026-09-13 (base rule) */
  body.mapx .legend{bottom:calc(10px + env(safe-area-inset-bottom))}
  body.mapx .map-pill{bottom:calc(44px + env(safe-area-inset-bottom))}
  body.mapx .maplibregl-ctrl-bottom-right{bottom:env(safe-area-inset-bottom)}
}
/* List/Map floating pill (owner UI #13, 2026-08-26; built 2026-09-09): mobile only, no
   header toggle. One fixed button, bottom-center 12px above the tab bar in BOTH views
   (owner 2026-09-09: same spot, the map's attribution row stays reachable via its "i"),
   labeled "Map" over the list and "List" over the map. Over the list it is Plants-only (Trails has
   its per-trail Map button); over the map it shows on any tab, since the map needs a
   way back. Hidden in expand mode (the close control is the way back) and on desktop
   (the map is always beside the list). The offline/loading status pill stacks above it. */
.view-pill{display:none}
@media (max-width:899px){
  .view-pill{position:fixed;left:50%;transform:translateX(-50%);bottom:calc(68px + env(safe-area-inset-bottom));z-index:4;height:40px;padding:0 18px;border:0;border-radius:20px;background:var(--moss);color:#fff;font:500 14px var(--sans);display:block;cursor:pointer}
  .view-pill span{display:flex;align-items:center;gap:8px}
  .view-pill svg{width:18px;height:18px;stroke:currentColor;fill:none;stroke-width:1.8;stroke-linecap:round;stroke-linejoin:round}
  body.showmap .view-pill .to-map,body:not(.showmap) .view-pill .to-list{display:none}
  body.mapx .view-pill{display:none}   /* owner UI #6 2026-09-09: the pill keeps its spot on every tab's list, Trails included */
  /* No radius chip on a phone (owner 2026-09-12, third pass; it had been a read-only
     label since 2026-09-09): "Search this area" sets the radius and its label says what
     a tap would search, the dashed ring draws it on the map, and the list's count line
     states it. The search box takes the whole controls row. Desktop keeps the picker. */
  /* .radius-chip hidden at both breakpoints since 2026-09-13 (global rule below) */
  body:not(.showmap) .scroll{padding-bottom:calc(120px + env(safe-area-inset-bottom))}   /* the last card scrolls clear of the pill, on every tab */
  .map-pill{bottom:60px}
  body.mapx .map-pill{bottom:calc(44px + env(safe-area-inset-bottom))}
}

.maplibregl-popup-content{font-family:var(--sans);background:var(--card);border:1px solid var(--line);border-radius:var(--r);box-shadow:none;padding:10px 12px;min-width:180px}
.maplibregl-popup-tip{border-top-color:var(--card)}
.pp h3{font:500 18px/1.1 var(--serif);margin:0}
.pp .lt{font:italic 400 13px var(--serif);color:var(--ink-2);margin:1px 0 6px}
.pp .m{font-size:12px;color:var(--ink-2)}
.pp .tag{display:inline-block;padding:1px 6px;border-radius:3px;font-size:11px;font-weight:500;margin-right:4px}
.pp .peak{background:var(--moss);color:#fff}.pp .inv{background:var(--ink);color:var(--paper)}.pp .hush{background:var(--hush);color:#fff}
.pp .acts{display:flex;gap:6px;margin-top:8px}
.pp .acts a{flex:1;text-align:center;padding:7px 0;border:1px solid var(--moss);border-radius:var(--r);color:var(--moss);text-decoration:none;font-size:13px;font-weight:500}
.pp .acts a.fill{background:var(--moss);color:#fff}
.pp .acts a.dis{border-color:var(--line);color:var(--hush);pointer-events:none}
/* Per-site photo (owner 2026-09-07; layout pick from docs/mockup-map.html ?popup=top):
   the sighting's own photo full-bleed 16:9 across the popup top, text in the padded
   block below; in the sheet a 44px square in the site row's first column. Tap =
   lightbox. Same soft tint behind a loading image as the list cards. */
.maplibregl-popup.has-ph .maplibregl-popup-content{padding:0;overflow:hidden;min-width:220px}
.pp .ph{display:block;width:100%;aspect-ratio:16/9;object-fit:cover;background:var(--moss-soft);cursor:zoom-in}
.pp.has-ph .pp-body{padding:10px 12px}

/* Map hover label (owner 2026-09-07, docs/mockup-map.html): one reused element above
   the hovered pin/cluster after a 300 ms dwell (js/app.js wireHoverLabels, pointer
   devices only — the element is never created without hover:hover). Card/line/ink
   tokens only. */
.hl{position:absolute;left:0;top:0;z-index:4;pointer-events:none;opacity:0;transition:opacity .12s ease-out;
    transform:translate(-50%,calc(-100% - 14px));background:var(--card);border:1px solid var(--line);border-radius:var(--r);
    padding:6px 9px;font:400 12px/1.3 var(--sans);color:var(--ink-2);white-space:nowrap;box-shadow:0 1px 2px rgba(30,38,31,.08)}
.hl.on{opacity:1;transition-duration:.08s}
.hl b{display:block;font:500 13px/1.25 var(--sans);color:var(--ink)}
.hl b i{font:italic 400 12px var(--serif);color:var(--ink-2)}
.hl .row{display:flex;justify-content:space-between;gap:12px}
.hl .row span:last-child{color:var(--hush);font-variant-numeric:tabular-nums}
.hl .more{color:var(--hush)}
.hl::after{content:"";position:absolute;left:50%;bottom:-5px;width:8px;height:8px;background:var(--card);border-right:1px solid var(--line);border-bottom:1px solid var(--line);transform:translateX(-50%) rotate(45deg)}

.map{display:none;position:relative;background:#E7E4D6;flex:1;min-height:0;touch-action:none}

/* A3 (mobile touch pass, 2026-08-25): the temporary "Search here" marker — a small
   moss pin plus a card-background chip, tokens only. Anchored "left" on the MapLibre
   Marker so the pin's center sits on the tapped coordinate (margin-left:-7px, half its
   own width) with the chip trailing to the right. */
.temp-marker{display:flex;align-items:center;gap:8px;pointer-events:auto}
/* Live position dot (owner 2026-09-12): moss, card ring, a soft moss halo — the
   temp-marker pin's register, one size up. Never a tap target. */
.user-dot{width:16px;height:16px;border-radius:50%;background:var(--moss);border:3px solid var(--card);box-shadow:0 0 0 4px rgba(46,102,56,.18),0 1px 3px rgba(0,0,0,.3);pointer-events:none}
.temp-marker .pin{width:14px;height:14px;margin-left:-7px;border-radius:50%;background:var(--moss);border:3px solid var(--card);box-shadow:0 1px 3px rgba(0,0,0,.3);flex:none;cursor:pointer}
.temp-marker .chip{height:44px;padding:0 14px;border-radius:22px;border:1px solid var(--moss);background:var(--card);color:var(--moss);font:500 13px var(--sans);white-space:nowrap;display:flex;align-items:center;cursor:pointer}
body.showmap .map{display:block}
body.showmap .scroll{display:none}

/* ---------- tabs ---------- */
.tabs{flex:none;order:99;display:grid;grid-template-columns:1fr 1fr 1fr;background:var(--paper);border-top:1px solid var(--line);padding-bottom:env(safe-area-inset-bottom)}
.tabs button{height:56px;font-size:13px;font-weight:500;color:var(--ink-2);display:grid;place-items:center;gap:2px}
.tabs button[aria-current=page]{color:var(--moss)}
.tabs svg{width:22px;height:22px;stroke:currentColor;fill:none;stroke-width:1.8;stroke-linecap:round;stroke-linejoin:round}

/* ---------- detail sheet ---------- */
.sheet{position:fixed;inset:0;background:rgba(30,38,31,.45);display:none;z-index:20}
.sheet[open]{display:block}
.sheet > div{position:absolute;left:0;right:0;bottom:0;max-height:90dvh;overflow:auto;background:var(--card);border-radius:14px 14px 0 0;padding:var(--u) calc(var(--u)*2) calc(var(--u)*4);touch-action:pan-y;overscroll-behavior:contain}
/* UI batch #7: the handle is the drag-to-dismiss target (44px hit area around the 4px bar) */
.handle{width:40px;height:4px;background:var(--line);border-radius:2px;margin:4px auto 12px;position:relative;touch-action:none}
.handle::before{content:"";position:absolute;inset:-14px -40px;cursor:grab}
.sheet > div.dragging{will-change:transform}   /* owner 2026-09-13: the swipe-down runs on its own layer (js initSheetDrag) */
.sheet img{-webkit-user-drag:none;user-select:none}   /* a native image drag would cancel the sheet drag */
/* z-index above .hero .frame (own stacking context, unelevated) and .car-nav
   (z-index:2) — the close × sits earlier in the DOM than the hero and was rendering
   under the carousel controls (owner 2026-09-02 item 3). */
.close{display:none;position:absolute;top:14px;right:14px;width:36px;height:36px;border:1px solid var(--line);border-radius:var(--r);background:var(--card);font-size:18px;line-height:1;z-index:5}
/* Entry report (owner 2026-09-12): the flag sits top-right of the sheet at both
   breakpoints (left of the desktop ×), same 36px outline register as .close. The box
   opens under the handle, above the hero: label, textarea, Send / Cancel. */
.sheet .flag{position:absolute;top:14px;right:14px;width:36px;height:36px;border:1px solid var(--line);border-radius:var(--r);background:var(--card);display:grid;place-items:center;z-index:5;cursor:pointer}
.sheet .flag svg{width:18px;height:18px;stroke:var(--moss);fill:none;stroke-width:1.8;stroke-linecap:round;stroke-linejoin:round}
.sheet .flag[aria-expanded=true]{background:var(--moss-soft);border-color:var(--moss)}
.report{margin:30px 0 16px;padding:12px;border:1px solid var(--moss);border-radius:var(--r);background:var(--moss-soft)}
.report label{display:block;font-size:14px;font-weight:500;color:var(--ink);margin-bottom:8px}
.report textarea{display:block;width:100%;box-sizing:border-box;resize:vertical;min-height:72px;padding:8px 10px;border:1px solid var(--line);border-radius:var(--r);background:var(--card);font:inherit;font-size:15px;color:var(--ink)}
.report .row{display:flex;align-items:center;gap:8px;margin-top:10px;flex-wrap:wrap}
.report .row small{flex:1;min-width:120px;font-size:12px;color:var(--ink-2)}
.sheet h2{font:600 28px/1.05 var(--serif);margin:0}
.sheet .h:focus{outline:none}   /* Settings focuses its heading on open (owner 2026-09-12: iOS was opening the Language select) */
.sheet .latin{font-size:16px}
.how{font-size:16px;margin:16px 0 0}
/* Species score line (docs/pack-spec.md "description" worth_it, owner ruling
   2026-09-01; relocated under the parts/members line 2026-09-02 item 5; the caution
   word dropped 2026-09-04 — the red warning box is the caution). Dots are drawn as
   equal-size CSS circles (item 4 — the ●/○ glyphs render at visibly different sizes
   in most fonts); the unfilled ring's diameter is the base size for both. */
.score{margin-top:10px;font-size:14px;color:var(--ink-2)}
.score .dots{display:inline-flex;align-items:center;gap:4px;vertical-align:middle}
.score .dot{width:9px;height:9px;border-radius:50%;box-sizing:border-box;border:1.5px solid var(--ink-2);background:transparent}
.score .dot.on{background:var(--ink-2)}
/* Worth-it reason (owner ruling 2026-09-09): one fixed-vocab phrase after the dots,
   same 14px --ink-2 register as the rest of the line. The label + dots stay on one
   line (.dots is inline-flex, the label a plain word), so a long phrase wraps onto
   its own line under the label instead of pushing the dots off. */
.score .why{color:var(--ink-2)}
/* Group-row members line (item 8, owner 2026-09-02): entry.members = [{latin,
   common, status}] on a group entry ("Opuntia spp."). Same register as .parts.
   Absent field renders nothing. Protected members reuse the root "dig" marker's
   warn color — the existing protected-text treatment. */
.members{font-size:14px;color:var(--ink-2);margin:6px 0 0}
.members .protected{color:var(--warn)}
/* Re-formed as CHIPS 2026-09-09 (rule 4 of the owner-approved parent/leaf mockup): the
   members line is one tappable chip per member — tapping opens that member's own (leaf)
   sheet. Paper ground, --line hairline, --r/2 radius, ink text; the status word rides in
   --ink-2 small text. A member the groups table has not checked is dashed; a protected
   member keeps the warn color the plain text line used; on a LEAF sheet the current
   member is filled with --moss-soft. The row caps at 8 chips and a "+ N" chip reveals
   the rest in place. No new tokens. */
.members{display:flex;flex-wrap:wrap;gap:6px;align-items:center;margin:8px 0 0}
.members .mchip{display:inline-flex;align-items:baseline;gap:5px;padding:3px 8px;border:1px solid var(--line);border-radius:3px;background:var(--paper);color:var(--ink);font-size:13px;line-height:1.25;text-align:left}
.members .mchip small{font-size:11px;color:var(--ink-2)}
.members .mchip.unchecked{border-style:dashed}
.members .mchip.absent{opacity:.55}   /* owner 2026-09-13: a member with no record in this radius */
/* One "not in this area:" label ahead of the absent chips (owner 2026-09-13), borderless small text. */
.members .mchip.label{flex:0 0 100%;border:0;background:none;padding:2px 0 0;font-size:11px;color:var(--ink-2)}
.members .mchip.protected,.members .mchip.protected small{color:var(--warn)}
.members .mchip.sel,.members .mchip.sel small{color:var(--moss)}
.members .mchip.sel{background:var(--moss-soft);border-color:var(--moss)}
/* The "+ N" expander is .plus, never .more — .more is the description disclosure
   block, and wireMoreToggle() takes the FIRST .more in the sheet. */
.members .mchip.plus{color:var(--ink-2)}
.members .mchip[hidden]{display:none}   /* .mchip sets display, so it must opt back out */
/* Rule 5 — leaf sheet: the "‹ <parent>" back link above the hero, and the parent's
   shared edibility paragraph under a quiet uppercase label. The shared block reuses the
   .legalbox left-border treatment in --moss-soft so it reads as inherited, not as a
   warning (which is always red) and not as this species' own prose. */
.parent-back{display:inline-block;margin:2px 0 8px;font-size:14px;font-weight:500;color:var(--moss)}
.shared{margin-top:12px;padding-left:10px;border-left:2px solid var(--moss-soft)}
.shared p{margin:0;font-size:16px}
.shared .shared-lbl{font-size:11px;text-transform:uppercase;letter-spacing:.06em;color:var(--ink-2);margin:0 0 4px}
.warnbox{margin-top:12px;padding:10px 12px;background:var(--warn-soft);color:var(--warn);border-radius:var(--r);font-size:14px;font-weight:500}
/* Species-scope legal line (owner ruling 2026-08-25): "one statement per box, by
   kind" — safety stays in .warnbox (red), legal renders calm, never red. */
.legalbox{margin-top:12px;padding-left:10px;border-left:2px solid var(--line);font-size:14px;color:var(--ink-2)}
/* Species description "More" disclosure (docs/pack-spec.md `description`, owner
   ruling 2026-09-01; restructured 2026-09-02 item 6): the "More" control is an inline
   button appended to the end of the .how paragraph's text, not a control of its own
   below the prose — so this is a plain toggle pair (.more-toggle/.less-toggle), not
   <details>/<summary> (summary can't sit inline inside a <p> without breaking it).
   The revealed .more block (identification paragraph + Sources) sits directly under
   .how, above the warning/legal boxes, so it never displaces a safety notice.
   Collapsed by default (`hidden`). No new colors/fonts — same tokens as
   .legalbox/.srcs. Overflow-wrap keeps a long source label from forcing horizontal
   scroll (A1 invariant). */
.more-toggle,.less-toggle{border:0;background:none;padding:0;font:inherit;font-weight:500;color:var(--moss);cursor:pointer;text-decoration:underline;text-underline-offset:3px}
.more{margin-top:10px;font-size:16px;overflow-wrap:break-word}
.more p{margin:0;color:var(--ink)}
.more .desc-src-lbl{margin:10px 0 0;font-size:11px;text-transform:uppercase;letter-spacing:.08em;color:var(--ink-2)}
.more .desc-srcs{list-style:none;margin:6px 0 0;padding:0;display:grid;gap:6px;font-size:14px}
.more .desc-srcs a{color:var(--moss);text-underline-offset:3px}
sup.cite{font-size:.7em;line-height:0}
.srcs{display:flex;gap:14px;flex-wrap:wrap;margin-top:14px;font-size:14px}
.srcs a{color:var(--moss);text-underline-offset:3px}
.cal{margin-top:18px}
.cal .lbl{font-size:11px;text-transform:uppercase;letter-spacing:.08em;color:var(--ink-2);margin-bottom:6px}
.cal .months{grid-column:auto;grid-template-columns:repeat(12,1fr);gap:3px}
.cal .months i{height:10px}
.cal .mm{display:grid;grid-template-columns:repeat(12,1fr);font-size:10px;color:var(--ink-2);text-align:center;margin-top:4px}
.sites{margin-top:18px;display:grid;gap:8px}
.site{display:grid;grid-template-columns:1fr auto auto;gap:8px;align-items:center;padding:10px 12px;border:1px solid var(--line);border-radius:var(--r);font-size:14px;cursor:pointer}   /* owner 2026-09-13: the row shows the sighting on the map */
.site small{display:block;color:var(--ink-2);font-size:12px}
.site .btn{height:40px;padding:0 14px;border-radius:var(--r);border:1px solid var(--moss);color:var(--moss);font-weight:500;font-size:14px}
.site .btn.fill{background:var(--moss);color:#fff}
.site .btn:disabled{border-color:var(--line);color:var(--hush);cursor:default}
/* Sites header (owner UI #5, 2026-09-09): the .cal-style uppercase label with the
   "Show only this plant" action right-aligned as a plain moss link — it filters the
   map and list to this plant, so it lives with the sites, not as a lone button at the
   bottom of the sheet. */
.sites-hd{display:flex;align-items:baseline;justify-content:space-between;gap:12px;margin-top:18px}
.sites-hd .lbl{font-size:11px;text-transform:uppercase;letter-spacing:.08em;color:var(--ink-2)}
.sites-hd+.sites{margin-top:8px}
.only-link{border:0;background:none;padding:0;font:inherit;font-size:13px;font-weight:500;color:var(--moss);cursor:pointer;text-decoration:underline;text-underline-offset:3px}
/* Sighting-tier note (owner UI #12, 2026-09-09): a labelled block under the site rows
   in the .legalbox register — calm left border, uppercase label, never red. */
.hint.tiernote{padding:0 0 0 10px;border-left:2px solid var(--line);font-size:13px;color:var(--ink-2)}   /* .hint.tiernote outranks the later generic .hint padding */
.tiernote p{margin:0}
.tiernote .lbl{font-size:11px;text-transform:uppercase;letter-spacing:.08em;margin:0 0 3px}
.more .more-less-row{margin-top:6px}
.site.has-ph{grid-template-columns:auto 1fr auto auto}
.site .ph{width:44px;height:44px;border-radius:4px;object-fit:cover;background:var(--moss-soft);cursor:zoom-in;display:block}
.protect{margin-top:14px;padding:12px;border:1px dashed var(--hush);border-radius:var(--r);font-size:14px;color:var(--ink-2)}
.protect a{color:var(--moss)}

/* ---------- desktop: list column + permanent map ---------- */
@media (min-width:900px){
  .app{display:grid;grid-template-columns:var(--col) 1fr;grid-template-rows:auto 1fr;height:100dvh}
  .head{display:flex;flex-direction:column;grid-column:1;border-bottom:1px solid var(--line);padding:12px 16px 8px}
  .head>*{padding:0}.head .brand{padding-top:0;height:auto;position:static}
  .gear{position:static;margin:-10px -10px -10px 0}
  .head .where{padding-right:0;margin-top:var(--u)}
  .scroll{grid-row:2;grid-column:1}
  .head .chips{padding:0 0 0 2px;border:0}
  .scroll{grid-column:1;overflow:auto}
  .map{display:block;grid-column:2;grid-row:1/3;min-height:0}
  .head{grid-row:1}
  .chips{flex-wrap:wrap;overflow:visible;gap:6px 6px}
  /* keep "In season" / "Worth it" on one line (STATUS "P2 — chip row
     layout"): force a break before the toggle pair rather than letting the
     wrap fall between category chips and toggles arbitrarily. */
  .chips .gap{flex-basis:100%;width:auto;height:0;background:none;margin:0}
  .map-top{justify-items:start}
  .map-top .area-pill{justify-self:center}
  .area-cross{display:block}
  .hud{flex-direction:column;align-items:flex-end}   /* desktop: one right-hand stack, base tile last (unchanged look) */
  .hud-l{order:2}
  .brand{padding-right:0}
  .head{display:flex;flex-direction:column}
  .tabs{order:1;position:static;z-index:auto;grid-template-columns:1fr 1fr 1fr;border:1px solid var(--line);border-radius:var(--r);background:var(--card);padding:0;margin:12px 0 2px;overflow:hidden}
  .tabs button{height:44px;font-size:15px;font-weight:500;display:block}
  .tabs button + button{border-left:1px solid var(--line)}
  .tabs svg{display:none}
  .tabs button[aria-current=page]{background:var(--moss);color:#fff}
  .where{order:2}.controls{order:3}.terms{order:3}.chips{order:4}
  .where span:not(.pin){flex:1}
  .brand h1{display:block}
  .loc{align-items:center;padding:0;min-height:0}
  .loc .pin{margin-top:0}
  .loc b{font:inherit;font-size:14px;font-weight:500}
  .loc small{display:inline;font-size:12px;margin-top:0}
  .loc .chev{display:none}
  #change-location-btn{display:block}
  .app{position:relative}
  body.showmap .scroll{display:block}
  .sheet:not(.modal) > div{left:0;right:auto;top:0;bottom:0;width:440px;max-height:none;border-radius:0;border-right:1px solid var(--line);padding:24px 24px 32px}
  .sheet:not(.modal){background:none;inset:auto;left:var(--col);top:0;bottom:0;width:440px;pointer-events:none}
  .sheet:not(.modal) > div{pointer-events:auto}
  .sheet .handle{display:none}
  .sheet .close{display:block}
  .sheet .flag{right:58px}
  .sheet.modal{background:rgba(30,38,31,.45);inset:0;width:auto}
  .sheet.modal > div{left:50%;top:50%;bottom:auto;right:auto;transform:translate(-50%,-50%);width:520px;max-height:calc(100dvh - 48px);border:1px solid var(--line);border-radius:14px;padding:20px 24px 24px}
  .sheet.modal .close{display:none}
  .sheet.modal .handle{display:none}
}

/* ---------- tab panels ---------- */
/* Plants-only controls (owner 2026-08-25): radius, List/Map, species search and chips
   render only under body.tab-plants; About also drops the location block. Scoped to the
   HEADER since 2026-09-12 (owner): the filter pills floating over the map stay on every
   tab, they filter the pins. */
body:not(.tab-plants) .head :is(.controls,.spsearch,.terms,.chips){display:none}
body.tab-about .where{display:none}
/* UI batch #10: Trails tab on mobile has no chip row, so a hairline separates the
   location block from the trail count line. */
@media (max-width:899px){ body.tab-trails .head .where{border-bottom:1px solid var(--line);padding-bottom:10px} }
.panel{display:none}.panel.on{display:block}
body.showmap .panel.on{display:none}
@media (min-width:900px){ body.showmap .panel.on{display:block} }

/* ---------- trails ---------- */
.trail{display:grid;grid-template-columns:1fr auto;gap:4px 12px;padding:14px;background:var(--card);border:1px solid var(--line);border-radius:var(--r);text-align:left;width:100%;min-width:0}
.trail>div{min-width:0}
.trail .name{font-size:20px}
.trail .sub{font-size:13px;color:var(--ink-2)}
.trail .n{font:600 26px/1 var(--serif);color:var(--moss);text-align:right}
.trail .n small{display:block;font:500 11px/1.3 var(--sans);color:var(--ink-2);letter-spacing:.04em;text-transform:uppercase}
.trail .dens{grid-column:1/3;display:flex;align-items:center;gap:8px;font-size:12px;color:var(--ink-2);margin-top:6px}
.trail .dens i{flex:1;height:6px;background:var(--line);border-radius:3px;overflow:hidden;display:block}
.trail .dens i b{display:block;height:100%;background:var(--moss)}
.trail .chips2{grid-column:1/3;display:flex;gap:6px;flex-wrap:wrap;margin-top:8px}
.trail .chips2 span{font-size:12px;padding:2px 8px;border:1px solid var(--line);border-radius:10px;background:var(--paper)}
.trail .chips2 span.peak{border-color:var(--moss);color:var(--moss)}
/* B2 (trails desktop hover, 2026-08-25): a moss border on the card matching a hovered
   map line (or vice versa) — pointer devices only, JS gates the class add/remove on
   matchMedia("(hover:hover)"), this rule just paints it. */
@media (hover:hover){ .trail.hover{border-color:var(--moss)} }
.trail.hush-land .n{color:var(--warn)}
.landtag{grid-column:1/3;font-size:12px;color:var(--warn);margin-top:6px}

/* trail-selected state (owner ruling 2026-08-24): compact trail summary above the
   trail's species list, ported verbatim from docs/mockup-all.html */
.trailhead{background:var(--card);border:1px solid var(--line);border-radius:10px;padding:12px 14px;margin:0 0 10px;display:grid;gap:8px}
.trailhead .name{font:600 20px/1.1 var(--serif);margin:0}
.trailhead .sub{font-size:13px;color:var(--ink-2);margin-top:2px}
.trailhead .dens{display:flex;gap:8px;align-items:center;font-size:12px;color:var(--ink-2)}
.trailhead .dens i{flex:1;height:4px;background:var(--line);border-radius:2px;overflow:hidden;display:block}
.trailhead .dens b{display:block;height:100%;background:var(--moss)}
/* B4: the trailhead's link row (.srcs — outbound only, never buttons, per
   design.md "Contributions") and Walk button, ported to the mockup verbatim. */
.trailhead .srcs{margin:0}
.trailhead .btn2{margin-top:2px}
.trail-acts{display:flex;gap:8px}
#trail-map{display:none}   /* mobile only: List/Map is Plants-only, this reaches the map */
@media (max-width:899px){ #trail-map{display:inline-flex;align-items:center} }

/* ---------- about ---------- */
.about{padding:calc(var(--u)*2);max-width:640px}
.about h2{font:600 24px/1.1 var(--serif);margin:28px 0 8px}
.about h2:first-child{margin-top:8px}
.about p,.about li{font-size:15px;line-height:1.5;color:var(--ink)}
.about ul{padding-left:18px;margin:6px 0}
.about a{color:var(--moss);text-underline-offset:3px}
.about .lede{font:400 18px/1.4 var(--serif)}
/* About "Questions" (owner 2026-09-12): native <details> rows, hairline-separated, the
   question in the body register with a moss +/– at the right; answers in --ink-2. */
.about .qa{margin-top:4px}
.about .qa details{border-top:1px solid var(--line);padding:10px 0}
.about .qa details:last-child{border-bottom:1px solid var(--line)}
.about .qa summary{cursor:pointer;list-style:none;display:flex;justify-content:space-between;gap:12px;font-size:15px;font-weight:500;color:var(--ink)}
.about .qa summary::-webkit-details-marker{display:none}
.about .qa summary::after{content:"+";color:var(--moss);font-size:18px;line-height:1;flex:none}
.about .qa details[open] summary::after{content:"\2013"}
.about .qa p{margin:8px 0 0;font-size:14px;line-height:1.5;color:var(--ink-2)}
.about .src{display:grid;grid-template-columns:auto 1fr;gap:4px 14px;font-size:14px;margin-top:8px}
.about .src b{font-weight:500}
.about .src span{color:var(--ink-2)}
/* biome/ecoregion ambient line (moved from header, item 5) — italic serif, same
   pattern as .latin/.pp .lt; hidden when the pack has neither field. */
.donate{margin-top:10px;display:grid;gap:8px}
.donate a{display:block;padding:12px 14px;border:1px solid var(--line);border-radius:var(--r);background:var(--card);text-decoration:none;color:var(--ink)}
.donate a small{display:block;color:var(--ink-2);font-size:13px;margin-top:2px}
.about .commons{font-size:13px;color:var(--ink-2);margin-top:10px}   /* the fixed commons line under the local orgs (owner 2026-09-12) */
.about .commons a{color:var(--moss)}
.fine{font-size:13px;color:var(--ink-2);margin-top:24px;padding-top:14px;border-top:1px solid var(--line)}

/* ---------- settings sheet rows ---------- */
.row{display:flex;align-items:center;justify-content:space-between;gap:12px;min-height:52px;padding:6px 0;border-bottom:1px solid var(--line);font-size:15px}
.row:last-child{border:0}
.row small{display:block;font-size:12px;color:var(--ink-2);margin-top:2px}
.row select{height:40px;border:1px solid var(--line);border-radius:var(--r);background:var(--card);padding:0 10px}
.sw{width:48px;height:28px;border-radius:14px;background:var(--line);position:relative;flex:none}
.sw::after{content:"";position:absolute;top:3px;left:3px;width:22px;height:22px;border-radius:50%;background:#fff;transition:left .15s}
.sw[aria-checked=true]{background:var(--moss)}.sw[aria-checked=true]::after{left:23px}
.sheet .h{font:600 22px/1.1 var(--serif);margin:4px 0 14px}
.btn2{height:44px;padding:0 16px;border:1px solid var(--line);border-radius:var(--r);background:var(--card);font-size:14px;font-weight:500}
a.btn2{display:inline-flex;align-items:center;justify-content:center;text-decoration:none;width:fit-content}
.btn2.fill{background:var(--moss);border-color:var(--moss);color:#fff}
.btn2.full{width:100%;margin-top:14px}
.btn2:disabled{opacity:.45;cursor:default}

/* ---------- pledge modal ---------- */
.pledge{position:fixed;inset:0;background:rgba(30,38,31,.55);display:none;z-index:30;align-items:flex-end;justify-content:center}
.pledge[open]{display:flex}
.pledge > div{background:var(--card);width:100%;max-width:520px;border-radius:14px 14px 0 0;margin:0 auto;padding:20px calc(var(--u)*2) calc(24px + env(safe-area-inset-bottom));max-height:92dvh;overflow:auto}
@media (min-width:900px){.pledge{align-items:center}.pledge > div{border-radius:14px}}
.pledge h2{font:600 26px/1.1 var(--serif);margin:0}
.pledge .lede{font:400 16px/1.45 var(--serif);color:var(--ink-2);margin:8px 0 16px}
.pledge .install-step{font:400 15px/1.45 var(--sans);color:var(--ink);margin:0 0 16px;padding:12px;border:1px solid var(--line);border-radius:var(--r)}
.pledge .btn2.full+.btn2.full{margin-top:8px}
.ck{display:flex;gap:12px;align-items:flex-start;padding:12px 0;border-top:1px solid var(--line);font-size:15px;line-height:1.4;cursor:pointer}
.ck input{width:22px;height:22px;margin:1px 0 0;accent-color:var(--moss);flex:none}
.ck b{font-weight:500}
.ck small{display:block;color:var(--ink-2);font-size:13px;margin-top:2px}

/* ---------- location search ---------- */
/* Location dialog (UI batch #8, fixed UI item 18 2026-08-26): head + input pinned,
   results scroll, help below; capped to the visual viewport (--vvh set from
   visualViewport in app.js) so the keyboard never pushes results off-screen. max-height
   (not height) so the empty dialog shrink-wraps; overflow:hidden keeps the scroll
   inside .res, not on this outer div too. */
.search > div{display:flex;flex-direction:column;max-height:calc(var(--vvh,100dvh) - 24px);overflow:hidden}
.dlg-head{display:flex;align-items:baseline;justify-content:space-between;gap:12px}
.dlg-head .h{margin-right:auto}
.cancel{color:var(--moss);font-size:15px;font-weight:500;min-height:44px;padding:0 4px}
.search .fld{display:flex;gap:8px;margin-top:10px;flex:none}
.search input{flex:1;height:48px;border:1px solid var(--line);border-radius:var(--r);background:var(--card);padding:0 14px;font:inherit;font-size:16px}
.search .fld .locate{width:48px;height:48px;flex:none}
.search .fld .locate.fill{background:var(--moss);border-color:var(--moss)}
.search .fld .locate.fill svg{stroke:#fff}
.search .hint{font-size:13px;color:var(--ink-2);margin:10px 0 0;flex:none}
.search .hint b{color:var(--ink);font-weight:500}
/* flex:1 + min-height:0 + overflow:auto = the list absorbs all remaining dialog
   height and scrolls internally, so the keyboard-shrunk dialog never bumps rows
   below the fold (UI item 18). */
.res{margin-top:14px;display:grid;gap:6px;align-content:start;overflow:auto;min-height:0;flex:1 1 auto;-webkit-overflow-scrolling:touch}
.res .lbl{font-size:11px;text-transform:uppercase;letter-spacing:.08em;color:var(--ink-2);margin:8px 0 2px}
.res button{display:grid;grid-template-columns:1fr auto;gap:2px 10px;text-align:left;padding:10px 12px;border:1px solid var(--line);border-radius:var(--r);background:var(--card);font-size:15px;width:100%}
.res button small{display:block;font-size:12px;color:var(--ink-2)}
.res button .n{font-size:12px;color:var(--moss);font-weight:500;align-self:center;white-space:nowrap}
.res .loc{border-color:var(--moss);color:var(--moss);font-weight:500;grid-template-columns:auto 1fr;align-items:center}

/* ---------- species search (batch B, owner-approved 2026-08-25) ---------- */
/* Ported verbatim from build/visual/species-search-preview.html, with one text
   change ("Search near it" instead of "Search there" — owner ruling). NOT the
   location search (.search dialog above) — this is an in-page fuzzy filter over the
   currently loaded pack, no network. */
.spsearch{display:flex;align-items:center;gap:8px;height:44px;padding:0 12px;border:1px solid var(--line);border-radius:var(--r);background:var(--card);margin-top:var(--u)}
.spsearch svg{width:18px;height:18px;stroke:var(--ink-2);fill:none;stroke-width:1.8;flex:none}
.spsearch input{flex:1;border:0;background:none;font:inherit;font-size:15px;color:var(--ink);min-width:0;appearance:none;-webkit-appearance:none}
.spsearch input::-webkit-search-cancel-button,.spsearch input::-webkit-search-decoration{-webkit-appearance:none;display:none}  /* our × only, not the browser's */
.spsearch input::placeholder{color:var(--ink-2)}
.spsearch .x{width:28px;height:28px;display:grid;place-items:center;color:var(--ink-2);font-size:18px}
/* "+" = pin the typed plant as a filter pill (owner 2026-09-12, multi-plant search);
   moss because it is an action, beside the quiet ×. Both follow the input's text. */
.spsearch .add{width:28px;height:28px;display:grid;place-items:center;color:var(--moss);font-size:22px;font-weight:500;line-height:1}
/* Pinned plant pills: the pressed-chip register (moss-soft tint, moss text), one row
   under the controls, wrapping; hidden while nothing is pinned. */
.terms{display:flex;flex-wrap:wrap;gap:6px;margin-top:var(--u)}
.term{height:32px;padding:0 6px 0 12px;border-radius:16px;border:1px solid var(--moss);background:var(--moss-soft);color:var(--moss);font-size:13px;font-weight:500;display:inline-flex;align-items:center;gap:2px;white-space:nowrap}
.term button{width:24px;height:24px;display:grid;place-items:center;color:var(--moss);font-size:16px;line-height:1;border-radius:12px}
.card.notfound{border-style:dashed;grid-template-columns:72px 1fr}
.card.notfound .meta{color:var(--ink-2)}
.card.notfound .acts{grid-column:1/3;display:flex;gap:8px;margin-top:10px}
.card.notfound .acts a{flex:1;text-align:center;padding:8px 0;border:1px solid var(--moss);border-radius:var(--r);color:var(--moss);text-decoration:none;font-size:14px;font-weight:500}
.card.notfound .acts a.fill{background:var(--moss);color:#fff}
.head .controls .spsearch{margin:0}
@media (min-width:900px){ .head .chips{order:5} }

/* ---------- misc app-specific (not in mockup: loading/error text, hidden attr) ---------- */
[hidden]{display:none!important}
.hint{font-size:13px;color:var(--ink-2);padding:calc(var(--u)*2)}
.hint a{color:var(--moss)}
.sheet .parts{font-size:14px;color:var(--ink-2);margin:6px 0 0;text-transform:capitalize}
.sheet .parts small{font-size:11px;text-transform:uppercase;letter-spacing:.06em;color:var(--warn)}
.srcs.contrib{font-size:13px;margin-top:16px}          /* contributions: outbound links only (design.md) */
.trail .srcs.contrib{grid-column:1/3;margin-top:10px;font-size:12px}
.trail.check-land .n{color:var(--ink-2)}
.trail.check-land .landtag{color:var(--ink-2)}
.trail.learning .n{color:var(--ink-2)}
.trail .learntag{grid-column:1/3;font-size:12px;color:var(--moss);margin-top:6px}

/* ---------- P2-A photos: sheet hero, credit line, lightbox, empty state ---------- */
.sheet .hero{margin:8px 0 10px;cursor:zoom-in}
.sheet .hero .frame{position:relative;touch-action:pan-y}
.sheet .hero .frame img{display:block;width:100%;height:210px;object-fit:cover;border-radius:8px;background:#cfd6c4}
.sheet .hero figcaption{font-size:12px;color:var(--ink-2);margin-top:5px}
.sheet .hero-empty{font-size:13px;color:var(--ink-2);margin:8px 0 10px}
.sheet .hero-empty a{color:var(--moss)}
.lightbox{position:fixed;inset:0;z-index:40;width:100%;height:100%;max-width:none;max-height:none;border:0;padding:0;background:rgba(8,10,8,.92);display:none}
.lightbox[open]{display:grid;place-items:center}
.lightbox figure{margin:0;max-width:min(94vw,1100px)}
.lightbox .frame{position:relative;display:inline-block;line-height:0;touch-action:pan-y}
.lightbox img{display:block;width:auto;height:auto;max-width:100%;max-height:84dvh;border-radius:10px;background:#cfd6c4;transition:opacity .12s}
.lightbox img.swap{opacity:0}   /* blank while the next photo loads — no flash of the previous one (2026-09-07) */
.lightbox figcaption{color:var(--card);font-size:13px;margin-top:10px;display:flex;gap:16px;align-items:baseline;flex-wrap:wrap}
.lightbox figcaption a{color:inherit}
.lightbox .close{display:block;position:absolute;top:calc(14px + env(safe-area-inset-top));right:calc(18px + env(safe-area-inset-right));width:auto;height:auto;font-size:30px;line-height:1;color:var(--card);background:none;border:0;cursor:pointer;z-index:1}
@media (max-width:899px){
  /* mobile: full-screen viewer — image letterboxed on black, credit under, x out */
  .lightbox{background:rgba(0,0,0,.9)}   /* UI batch #9: ~90 % black, not solid */
  .lightbox figure{max-width:100vw;width:100vw}
  .lightbox .frame{display:block;width:100vw}
  .lightbox img{width:100vw;max-height:calc(100dvh - 72px);object-fit:contain;border-radius:0}
  .lightbox figcaption{padding:0 16px 16px}
}
.lightbox::backdrop{background:none}   /* the dialog itself is the dimmer */

/* Photo carousel (P2-A2, owner 2026-09-02): dots + arrows over the image, 2-6
   photos only (no markup at all for one photo — same as before). Legible on any
   photo via the same translucent paper/ink treatment as the credit line. */
.car-nav{position:absolute;inset:0;pointer-events:none;z-index:2}
.car-nav .car-arrow{pointer-events:auto;position:absolute;top:50%;transform:translateY(-50%);width:40px;height:40px;padding:0;border:0;border-radius:50%;background:rgba(8,10,8,.45);display:flex;align-items:center;justify-content:center;cursor:pointer}
.car-nav .car-arrow svg{width:20px;height:20px;stroke:var(--card);fill:none}
.car-nav .car-arrow.prev{left:8px}
.car-nav .car-arrow.next{right:8px}
.car-nav .car-dots{pointer-events:auto;position:absolute;left:50%;bottom:10px;transform:translateX(-50%);display:flex;gap:6px;padding:5px 8px;border-radius:10px;background:rgba(8,10,8,.35)}
.car-nav .car-dots i{display:block;width:6px;height:6px;border-radius:50%;background:rgba(246,243,234,.55)}
.car-nav .car-dots i.on{background:var(--card)}
/* A failed slot (offline, not saved) with 2+ photos: hide just that image and show
   the existing offline copy in its place; the carousel controls (z-index above)
   stay usable so navigating to a cached photo still works (design.md). */
.sheet .hero.slot-offline .frame img{visibility:hidden}
.sheet .hero.slot-offline .frame::after{content:"Photo not saved for offline. It will load when you're connected.";position:absolute;inset:0;z-index:1;display:flex;align-items:center;justify-content:center;text-align:center;padding:12px;font-size:13px;color:var(--ink-2);background:#cfd6c4;border-radius:8px;pointer-events:none}
