/* Frame — "Field Notebook": warm, paper-like, field-friendly UI. No framework.
   Fully variable-driven so Dawn Mode is a token swap on [data-theme="dark"]. */
:root {
  --bg: #ebe1cf; --bg2: #f2ead9; --card: #faf4e8; --card2: #f2ead9;
  /* --dim carries 12-13px caption text everywhere; #8a7b64 measured 3.18:1 on
     --bg (AA needs 4.5). Darkened to 4.79/5.67 (bg/card) keeping the warmth. */
  --ink: #2c2318; --dim: #6d5f49; --line: #e2d6bf; --track: #e6dcc7;
  --accent: #cf7f22; --accent-ink: #7a4a10; --accent-wash: #f4e6cd; --accent2: #b56a15;
  /* Map/planner SCORE colour — a hotter, more luminous orange than the muddy
     UI accent, so the best spots read as bright highlights, not brown shadows. */
  /* Nudged #ff6a00 → #f25c00 for the 3:1 non-text mark ratio vs plain cells/
     pins (2.62 → 3.04) — visually near-identical heat; cell ink stays 5.2:1. */
  --score-hot: #f25c00;
  --slate: #3f77a4; --slate-wash: #dbe7f0; --warn: #a9781f; --live: #c0563a;
  /* Trust badge palette — mirrors the labels in model/scoring.js. Light-theme
     thin/inferred/exploratory are darkened from the reference hexes so the white
     badge text clears WCAG AA on 11px bold. */
  --trust-documented: #3f7d54; --trust-opportunity: #3f77a4; --trust-exploratory: #7f5fa2;
  --trust-thin: #7d7260; --trust-inferred: #8a6219;
  --badge-ink: #fff; /* badge text: dark on the lightened Dawn-Mode tokens (below) */
  /* Destructive-action colour (Delete region): themed so the label clears
     contrast in BOTH modes — the pale-pink reference hex was invisible on cream. */
  --danger: #9c3320; --danger-dim: #7a2e2e;
  /* Distant (out-of-region) counties on the hotspot map: a lifted floor so
     they read as present-but-quiet instead of dissolving into the --bg2 map
     background. Kept below the active region's --card2 so the region still
     dominates — shadows alive, not crushed. Recomputes per theme. */
  --far: color-mix(in srgb, var(--card2) 55%, var(--bg2));
  /* Offline basemap landmarks (data/basemap.js) — quiet, so they orient
     without fighting the counties or the score colours. Roads are a clay red
     no other layer uses, so they can NEVER be mistaken for the tan county
     lines or the orange region outline. Recompute per theme. */
  --water: #6f9fc0; --road: #b0523a; --park: #7aa863;
  --label-ink: #8a7b64; --label-halo: var(--card);
  /* County boundaries: near-black and dashed — a man-made line should read as
     one, distinct from red roads and blue rivers. */
  --boundary: #2e2618;
  /* Tab dock (v27) — the nav is its OWN surface. Everything else in the app is
     warm cream (bright cards, tan page): any warm-and-light dock blended into
     one or the other in bright light. So the dock breaks from the palette
     entirely — a neutral, desaturated deep taupe with LIGHT icons, a bar that
     can't be mistaken for either the cards or the background, in either theme.
     --tab-ink / --tab-ink-active relight the tab glyphs for the dark surface
     (kept separate from the app-wide --dim / --accent). Recompute per theme. */
  /* Deepened for AA (v32): #8f7f66 left the 11px labels at 3.14:1. #695c47 is
     the same taupe family, further toward "more toolbar" (the direction Noah
     left open in v27) — inactive ink 5.26:1, active gold 4.54:1. */
  --dock: #695c47;
  --dock-line: #4f4433;
  --dock-shadow: 0 1px 2px rgba(40,30,10,.22), 0 8px 18px rgba(40,30,10,.18), 0 22px 44px rgba(40,30,10,.24);
  --dock-lip: inset 0 1px 0 rgba(255,255,255,.16);
  --tab-ink: #efe6d4;
  --tab-ink-active: #ffd08a;
  /* Facet icons (v23) — brightness = how present a bird group is; want/exclude
     for the tri-state filter. All are :root tokens so Dawn Mode swaps them. */
  --facet-lots: var(--accent);
  --facet-some: color-mix(in srgb, var(--accent) 52%, var(--dim));
  --facet-ghost: color-mix(in srgb, var(--dim) 40%, transparent);
  --facet-want: var(--accent);
  --facet-block: #a0472f;
  --facet-mode: var(--park);
  --facet-mode-wash: color-mix(in srgb, var(--park) 16%, var(--card));
  /* Accessible companions (see /ACCESSIBILITY.md): the park green as TEXT
     (4.57:1 on card — --facet-mode itself is a mark/fill colour only), the
     must-include count-badge ground (4.57:1 under card-coloured digits), and
     the keyboard focus ring (slate: 3.69:1 on bg, cool against the warm UI). */
  --facet-mode-ink: #55793f;
  --count-want: #55793f;
  --focus: var(--slate);
  /* Presence traffic-light (card "Here this month" row). Hue carries the state
     (green here / amber maybe / red not-expected) AND value steps monotonically
     dark→light so it stays legible in grayscale — colour-blind users read the
     lightness ramp when the hue is ambiguous. Per-theme so Dawn swaps. */
  --pi-here: #4e7b41;   /* darkest */
  --pi-maybe: #b6852b;  /* medium  */
  --pi-none: #dd9077;   /* lightest */
  /* Type superfamily — IBM Plex (serif = wordmark/names/titles, sans = chrome,
     mono = every number). */
  --sans: 'IBM Plex Sans', -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
  --serif: 'IBM Plex Serif', Georgia, "Times New Roman", serif;
  --mono: 'IBM Plex Mono', ui-monospace, "SFMono-Regular", Menlo, monospace;
  --r: 16px; --tab-h: 60px;
}

/* Dawn Mode — a warm near-black low-light palette (never green). */
[data-theme="dark"] {
  --bg: #141210; --bg2: #2a2318; --card: #211b14; --card2: #2a2318;
  --ink: #f2e9da; --dim: #a3957c; --line: #352c20; --track: #2f2718;
  --accent: #f0a94e; --accent-ink: #f0d9b0; --accent-wash: #3a2c15; --accent2: #e0a94e;
  /* Dawn Mode: on the dark map a bright hot orange glows against the ground. */
  --score-hot: #ff8321;
  --slate: #7fb0da; --slate-wash: #1e2c38; --warn: #e0a94e; --live: #e8795a;
  --trust-documented: #5fae79; --trust-opportunity: #7fb0da; --trust-exploratory: #b492d8;
  --trust-thin: #a89a80; --trust-inferred: #e0a94e;
  --badge-ink: var(--bg); /* dark ink on the lightened dark-theme badge tokens */
  --danger: #e58b8b; --danger-dim: #7a2e2e;
  --water: #5f8bb0; --road: #d9836a; --park: #5f8a55;
  --label-ink: #a3957c; --label-halo: var(--card);
  --boundary: #e8ddc8; /* Dawn Mode: boundaries go light against the dark ground */
  /* --far derives from --card2/--bg2 via var(), which resolve where --far is
     DECLARED — so the :root copy bakes in the light values. Redeclare it here so
     the dark map (and the forced-dark hotspot map in light mode) dims correctly. */
  --far: color-mix(in srgb, var(--card2) 55%, var(--bg2));
  /* Same neutral-taupe bar, tuned for low light: a deep taupe clearly lifted
     off the near-black warm cards, with light glyphs. */
  --dock: #4a4436;
  --dock-line: #6a6047;
  --dock-shadow: 0 1px 2px rgba(0,0,0,.45), 0 8px 20px rgba(0,0,0,.5), 0 22px 46px rgba(0,0,0,.55);
  --dock-lip: inset 0 1px 0 rgba(255,255,255,.09);
  --tab-ink: #d8ccb5;
  --tab-ink-active: #f0a94e;
  --facet-lots: var(--accent);
  --facet-some: color-mix(in srgb, var(--accent) 52%, var(--dim));
  --facet-ghost: color-mix(in srgb, var(--dim) 40%, transparent);
  --facet-want: var(--accent);
  --facet-block: #e8795a;
  --facet-mode: var(--park);
  --facet-mode-wash: color-mix(in srgb, var(--park) 22%, var(--card));
  /* Dawn-mode accessible companions: green text 6.18:1 on the dark card;
     badge green lightened so the dark digits read 5.26:1; slate ring 8.1:1. */
  --facet-mode-ink: #7aa863;
  --count-want: #6f9a63;
  --focus: var(--slate);
  /* Presence traffic-light, Dawn: same dark→light value ramp, lifted so all
     three read on the near-black ground. */
  --pi-here: #5f9a50;   /* darkest of the three, still bright enough on dark */
  --pi-maybe: #d49a44;  /* medium  */
  --pi-none: #f0a68d;   /* lightest */
}
* { box-sizing: border-box; }
html, body { margin: 0; background: var(--bg); color: var(--ink);
  font-family: var(--sans); font-size: 0.9375rem; line-height: 1.45; }
/* The root stays at 100% = whatever the reader set as their browser/OS default
   font size; body renders at 0.9375rem (15px at the default) as designed. Every
   font-size below is in rem, so the app scales with that preference. This rule
   must stay AFTER the one above, which would otherwise pin the root to 0.9375rem
   and shrink every rem in the app. Never set a px font-size on the root. */
html { font-size: 100%; }
/* Clear the floating dock: its height + 10px float + a little breathing room. */
body { padding-bottom: calc(var(--tab-h) + 24px + env(safe-area-inset-bottom)); }
/* Installed on a phone, the page runs full-bleed under the status bar and
   camera cutout (viewport-fit=cover + black-translucent) — content must start
   below the top inset, and a fixed --bg strip backs the clock/battery so
   scrolled rows never slide up behind them. In a browser tab the inset is 0
   and none of this changes anything. */
body { padding-top: env(safe-area-inset-top); }
body::before { content: ""; position: fixed; top: 0; left: 0; right: 0;
  height: env(safe-area-inset-top); background: var(--bg); z-index: 10; }
.view { max-width: 760px; margin: 0 auto; padding: 12px 12px 24px; }
h1 { font-size: 1.375rem; margin: 0; font-family: var(--serif); font-weight: 600; }
h2 { font-size: 1rem; margin: 0 0 6px; }
/* Hotspot names on cards read as titles too (screen h1s are serif above). */
.card-head-main h2 { font-family: var(--serif); font-weight: 600; }
/* Every number is IBM Plex Mono, tabular — scores, freq %, cells, pills,
   month initials, legends. */
.tsp-freq, .cell, .spark-mon, .si-count-num, .div-chip,
.legend, .rel-ver, .about-formula { font-family: var(--mono); font-variant-numeric: tabular-nums; }
a { color: var(--accent-ink); }

/* Keyboard focus is always visible: one slate ring everywhere (3.7:1 on the
   light bg, 8:1 on Dawn). Individual rules must never re-suppress it. */
:focus-visible { outline: 2px solid var(--focus); outline-offset: 2px; }
#app:focus-visible { outline: none; } /* the landmark target itself needs no ring */

/* Skip-to-content: the first Tab stop, hidden until focused, then a solid pill
   at the top so keyboard users jump past nothing-yet to the main view. */
.skip-link { position: fixed; top: -60px; left: 12px; z-index: 100;
  background: var(--card); color: var(--accent-ink); border: 1px solid var(--focus);
  border-radius: 8px; padding: 9px 14px; font-size: 0.875rem; font-weight: 600;
  text-decoration: none; transition: top .12s ease; }
.skip-link:focus { top: calc(10px + env(safe-area-inset-top)); }

/* Respect the OS motion preference: kill animations & transitions wholesale. */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: .01ms !important; animation-iteration-count: 1 !important;
    transition-duration: .01ms !important; scroll-behavior: auto !important;
  }
}
.dim { color: var(--dim); }
.empty { color: var(--dim); text-align: center; padding: 40px 0; }
/* dead-end states — never a blank screen: what happened + a way forward */
.dead-end { background: var(--card); border: 1px solid var(--line); border-radius: var(--r);
  padding: 20px 16px; margin: 10px 0; text-align: center; }
.dead-end h2 { margin-bottom: 8px; }
.dead-end p { margin: 0 auto 14px; max-width: 42ch; }
.dead-end .btn { display: inline-block; }

/* header / controls */
/* The sticky header is SOLID behind its own content (title, badges, guild row,
   month selector) so scrolled content never shows through over the icons — the
   old `linear-gradient(--bg 70%, transparent)` faded the bottom third of the
   header itself to clear, and rows sliding up behind it blended into the icons
   (Noah's screenshot). The soft hand-off to the scrolling content lives in a
   thin fade strip just BELOW the header instead. */
.bar { position: sticky; top: env(safe-area-inset-top); z-index: 5; background: var(--bg);
  padding: 8px 0 10px; margin-bottom: 8px; }
.bar::after { content: ""; position: absolute; left: 0; right: 0; top: 100%; height: 14px;
  background: linear-gradient(var(--bg), transparent); pointer-events: none; }
.title-row { display: flex; align-items: baseline; gap: 10px; flex-wrap: wrap; }
.subtitle { color: var(--dim); font-size: 0.8125rem; }
.back { background: none; border: 0; color: var(--accent-ink); font-size: 0.9375rem; padding: 4px 0; cursor: pointer; }

.months { display: grid; grid-template-columns: repeat(12, 1fr); gap: 4px; margin-top: 8px; }
.month { background: var(--card); border: 1px solid var(--line); color: var(--dim);
  border-radius: 8px; padding: 6px 0; font-size: 0.75rem; cursor: pointer; }
.month.active { background: var(--accent); color: #06160f; border-color: var(--accent); font-weight: 700; }

.filters { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 10px; align-items: center; }
.filter { background: var(--card); border: 1px solid var(--line); color: var(--ink);
  border-radius: 999px; padding: 6px 12px; font-size: 0.8125rem; cursor: pointer; }
.filter.active { background: var(--ink); border-color: var(--ink); color: var(--bg); font-weight: 600; }
.filter-hint { flex-basis: 100%; color: var(--dim); font-size: 0.75rem; margin-top: 2px; }

/* cards */
.cards { display: flex; flex-direction: column; gap: 12px; }
.show-more { align-self: center; margin: 4px 0 2px; }
.card { background: var(--card); border: 1px solid var(--line); border-radius: 16px; padding: 14px;
  box-shadow: 0 1px 2px rgba(0,0,0,.04), 0 6px 18px rgba(60,40,10,.05); }
/* The whole card is a tap-to-open navigation tile. Affordance is NOT hue-only:
   the pointer cursor, a shadow/border lift on hover, and a press on :active all
   signal it (the reduced-motion kill-switch above neutralizes the transition).
   Keyboard users reach the same page via the location-name link (.card-title-link). */
.card.tappable { cursor: pointer; transition: box-shadow .15s ease, transform .15s ease, border-color .15s ease; }
.card.tappable:hover { border-color: color-mix(in srgb, var(--accent) 40%, var(--line));
  box-shadow: 0 2px 4px rgba(0,0,0,.06), 0 10px 26px rgba(60,40,10,.09); }
.card.tappable:active { transform: translateY(1px); }
.card-title-link { color: inherit; text-decoration: none; }
.card-title-link:hover { text-decoration: underline; text-decoration-thickness: 1px; text-underline-offset: 2px; }
.card-head { display: flex; justify-content: space-between; gap: 12px; align-items: flex-start; }
.card-head-main { min-width: 0; }
.tags { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 6px; align-items: center; }
/* "N birds likely" chip — the headline that replaced the 0–100 score ring.
   Tap for the icon explainer; a quiet "?" makes that discoverable. */
.div-chip { position: relative; background: var(--accent-wash); border: 1px solid var(--accent);
  color: var(--accent-ink); border-radius: 999px; padding: 2px 10px; font-size: 0.75rem; font-weight: 700;
  cursor: pointer; display: inline-flex; align-items: center; gap: 6px; }
.div-chip:active { transform: translateY(1px); }
/* A quiet "?" affordance so the tap-for-meaning is discoverable without hover.
   SUB-11px EXEMPT (A14): a single-glyph affordance inside a 14px circle, in the
   same family as the chevrons A10 exempted — it labels nothing on its own and the
   whole chip it sits on is the tap target, with its own full-size label. */
.score-q { width: 14px; height: 14px; border-radius: 50%; background: var(--accent); color: var(--card);
  font-size: 0.625rem; font-weight: 700; line-height: 0.875rem; text-align: center; }
/* Inline tappable "species likely" (hotspot detail header). */
.score-inline { background: var(--bg2); border: 1px solid var(--line); color: var(--ink);
  border-radius: 8px; padding: 2px 8px 2px 10px; font-size: 0.8125rem; cursor: pointer;
  font-family: var(--mono); font-variant-numeric: tabular-nums; display: inline-flex; align-items: center; gap: 6px; }
.score-inline:active { transform: translateY(1px); }

.badge { --c: #555; font-size: 0.6875rem; font-weight: 700; color: var(--badge-ink); background: var(--c);
  padding: 2px 8px; border-radius: 6px; }
.badge.live { --c: var(--live); }
/* Trust badges pull from theme tokens so Dawn Mode reskins them too. */
.badge.trust-documented  { --c: var(--trust-documented); }
.badge.trust-opportunity { --c: var(--trust-opportunity); }
.badge.trust-exploratory { --c: var(--trust-exploratory); }
.badge.trust-thin        { --c: var(--trust-thin); }
.badge.trust-inferred    { --c: var(--trust-inferred); }
.chip { font-size: 0.6875rem; color: var(--ink); background: var(--card2); border: 1px solid var(--line);
  padding: 2px 7px; border-radius: 6px; }
.chip.dim { color: var(--dim); }
.chip.inferred { color: var(--warn); border-color: var(--warn); }

.habs { display: flex; flex-wrap: wrap; gap: 6px; margin: 10px 0; }
.hab { font-size: 0.6875rem; color: var(--dim); background: var(--bg2); border: 1px solid var(--line);
  padding: 2px 8px; border-radius: 999px; }

.top-species { display: flex; flex-direction: column; gap: 5px; margin-bottom: 12px; }
.tsp { display: grid; grid-template-columns: 1fr auto; column-gap: 8px; align-items: center; font-size: 0.8125rem; }
.tsp-freq { color: var(--dim); font-variant-numeric: tabular-nums; }
.tsp-bar { grid-column: 1 / -1; height: 4px; background: var(--bg2); border-radius: 3px; overflow: hidden; }
.tsp-bar i { display: block; height: 100%; background: var(--accent); }

.btn { background: var(--card2); border: 1px solid var(--line); color: var(--ink);
  border-radius: 8px; padding: 7px 12px; font-size: 0.8125rem; cursor: pointer; text-decoration: none; display: inline-block; }
.btn.ghost { background: none; }
.btn:active { transform: translateY(1px); }

.provenance { margin-top: 18px; padding: 12px; background: var(--bg2); border: 1px dashed var(--line);
  border-radius: 10px; font-size: 0.75rem; color: var(--dim); }
.provenance strong { color: var(--warn); }

/* matrix */
.matrix-wrap, .table-wrap { overflow-x: auto; -webkit-overflow-scrolling: touch; }
table.matrix { border-collapse: collapse; width: 100%; font-size: 0.75rem; }
table.matrix th, table.matrix td { padding: 6px 4px; text-align: center; }
/* Month headers sort their column; the corner restores the default order.
   The tappable part is a real BUTTON inside the th (keyboard-reachable, focus
   ring from the global :focus-visible rule) that inherits the header look. */
table.matrix th.mth, table.matrix .corner { cursor: pointer; }
table.matrix .th-btn { font: inherit; color: inherit; background: none; border: 0;
  padding: 0; cursor: pointer; letter-spacing: inherit; text-transform: inherit; }
table.matrix .rowhead .th-btn { text-align: left; white-space: nowrap; }
table.matrix th.mth.sorted { color: var(--accent-ink); }
table.matrix th.mth.sorted::after { content: ' ▾'; }
table.matrix .rowhead { text-align: left; white-space: nowrap; color: var(--ink); cursor: pointer;
  position: sticky; left: 0; background: var(--bg); padding-right: 8px; }
table.matrix .corner { text-align: left; }
/* Two states only — an ordinary month is a plain quiet cell; a month in the
   hotspot's HOT tier (the natural top of that month's ranking) is solid
   --score-hot with dark ink. No in-between shades, no scale to read. */
.cell { color: var(--ink); font-weight: 700; cursor: pointer; border: 1px solid var(--bg);
  background: var(--card); }
.cell.hot { background: var(--score-hot); color: #2a1503; }
.col-active { outline: 2px solid var(--accent); }
.legend { color: var(--dim); font-size: 0.75rem; margin-top: 10px; }
/* Map caption + the ⓘ that jumps to the Planner's report-count view. */
.legend-row { display: flex; align-items: flex-start; gap: 8px; }
.legend-row .legend { flex: 1 1 auto; }
.stats-info { flex: 0 0 auto; width: 30px; height: 30px; margin-top: 8px; border-radius: 50%;
  cursor: pointer; border: 1px solid var(--line); background: var(--card); color: var(--dim);
  font-size: 0.9375rem; display: grid; place-content: center; }

/* Planner numbers toggle: which number fills the cells (species likely /
   reports). A standing, self-announcing control — never a hidden mode. */
.num-toggle { display: flex; gap: 6px; margin: 2px 0 10px; }
.num-btn { padding: 6px 13px; border-radius: 999px; cursor: pointer; font-size: 0.78125rem;
  border: 1px solid var(--line); background: var(--card); color: var(--dim); }
.num-btn.on { border-color: var(--accent); background: var(--accent-wash);
  color: var(--accent-ink); font-weight: 700; }

/* Score colour key — the bar uses the SAME accent→card mix the pins and planner
   cells use, so the map matches the key. One direction everywhere: more colour
   = higher score. */

/* species tables */
table.species-table { border-collapse: collapse; width: 100%; font-size: 0.8125rem; }
table.species-table th { text-align: left; color: var(--dim); font-weight: 600; padding: 6px 8px; border-bottom: 1px solid var(--line); }
table.species-table td { padding: 6px 8px; border-bottom: 1px solid var(--bg2); vertical-align: middle; }
table.species-table tr[onclick] { cursor: pointer; }
.star { color: var(--warn); }
/* Informational facet mini-icons in tables / panels (type·size·nest·behaviour). */
.sp-facets { display: inline-flex; align-items: center; gap: 5px; }
/* Labelled variant (icon+word pills): let the pills wrap instead of stretching a
   table column, and keep them tight so a row shows two-up rather than one wide line. */
.sp-facets-labelled { display: inline-flex; flex-wrap: wrap; align-items: center; gap: 4px; vertical-align: middle; }
.sp-facets-labelled .sp-fi-btn.labelled { padding: 2px 8px 2px 6px; font-size: 0.6875rem; }
/* Species facets: a guild silhouette leads the row (in front of the text, a
   clear "kind of bird" cue — dim so it stays quieter than the species name),
   then a short italic info line. Same bird leads the Target/Seen pill rows and
   sits by the name on a species' own page. */
.sp-facet-line { display: inline-flex; align-items: flex-start; gap: 6px; }
.sp-facet-bird { flex: 0 0 auto; color: var(--dim); opacity: .7; }
.sp-facet-bird svg { display: block; }
.sp-facet-line .sp-facet-bird { margin-top: 1px; }
.sp-facet-note { font-style: italic; font-size: 0.71875rem; color: var(--dim); line-height: 1.3; }

/* Species photo thumbnail (Wikimedia Commons) — a leading avatar beside a bird's
   name. A rounded square with a hairline frame; falls back to the dim guild
   silhouette (.is-fallback) when there's no photo. Decorative (name is the
   label): aria-hidden, empty alt. Not a hue-encoding — the frame is neutral. */
.sp-thumb { flex: 0 0 auto; width: var(--thumb, 30px); height: var(--thumb, 30px);
  border-radius: 7px; overflow: hidden; background: var(--card2);
  display: inline-flex; align-items: center; justify-content: center;
  box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--ink) 14%, transparent); }
.sp-thumb-img { width: 100%; height: 100%; object-fit: contain; display: block; }
.sp-thumb.is-fallback { color: var(--dim); }
.sp-thumb.is-fallback .sp-thumb-glyph { opacity: .72; }
.sp-thumb-glyph svg { display: block; }
.sp-facet-line .sp-thumb { margin-top: 1px; }
/* Tap the photo to open the species (mirrors the name link). */
.sp-thumb.clickable { cursor: pointer; transition: box-shadow .12s ease; }
.sp-thumb.clickable:hover { box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--ink) 30%, transparent); }
@media (prefers-reduced-motion: reduce) { .sp-thumb.clickable { transition: none; } }

/* Species page header: photo + a name/status block, then a labelled action row
   for the shot-list / seen toggles (so a bare camera by the photo can't read as
   "edit photo", and the + isn't a mystery). */
.sp-head-main { display: flex; flex-direction: column; gap: 4px; min-width: 0; }
.sp-head-main h2 { margin: 0; }
.sp-head-sub { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; }
.sp-actions { display: flex; flex-wrap: wrap; gap: 8px; margin: 10px 0 2px; }
.star-btn.labelled, .seen-btn.labelled {
  width: auto; height: auto; border-radius: 999px; display: inline-flex; align-items: center;
  gap: 6px; padding: 7px 13px; border: 1px solid var(--line); background: var(--card2);
  color: var(--ink); font-size: 0.8125rem; font-weight: 600; }
.star-btn.labelled .mark-ico, .seen-btn.labelled .mark-ico { display: inline-flex; align-items: center; }
.star-btn.labelled .mark-ico svg { width: 18px; height: 18px; }
.seen-btn.labelled .mark-ico { font-size: 1rem; line-height: 1; }
/* Install ("add to home screen") — banner + steps. Banner mirrors .freshbar but
   in the accent family (an invitation, not a warning). */
.installbar { display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
  margin: 0 0 12px; padding: 9px 12px; border-radius: var(--r);
  border: 1px solid var(--accent); background: var(--accent-wash); }
.install-ico { flex: 0 0 auto; color: var(--accent-ink); display: inline-grid; place-content: center; }
.install-ico svg { display: block; }
.install-msg { flex: 1 1 180px; font-size: 0.8125rem; color: var(--ink); line-height: 1.4; }
.install-msg strong { font-weight: 700; color: var(--accent-ink); }
.install-actions { display: flex; gap: 4px; flex: 0 0 auto; align-items: center; }
.install-dialog { border: none; border-radius: 14px; padding: 0; max-width: 420px; width: calc(100% - 32px);
  background: var(--card); color: var(--ink); box-shadow: 0 12px 40px rgba(0,0,0,.28); }
.install-dialog::backdrop { background: rgba(0,0,0,.4); }
.install-body { padding: 22px 20px; }
.install-body h2 { font-size: 1.125rem; margin: 0 0 12px; padding-right: 30px; }
.install-body p { font-size: 0.875rem; margin: 8px 0; }
.install-body .btn.primary { margin-bottom: 12px; }
.install-steps { margin: 8px 0; padding-left: 22px; font-size: 0.875rem; }
.install-steps li { margin: 7px 0; line-height: 1.4; }
.inline-ico { display: inline-flex; vertical-align: -3px; color: var(--accent-ink); }
.install-done { color: var(--facet-mode-ink, var(--accent-ink)); font-weight: 600; font-size: 0.875rem; }
.setting .install-steps strong, .install-body .install-steps strong { color: var(--accent-ink); }

.star-btn.labelled.on { background: var(--accent-wash); border-color: var(--accent); color: var(--accent-ink); }
/* seen-on: the slate cue is the wash + border; text stays --ink so it clears AA
   (slate-on-slate-wash is only 3.8:1 in the light theme). */
.seen-btn.labelled.on { background: var(--slate-wash); border-color: var(--slate); color: var(--ink); }
.sp-fi { display: inline-grid; place-content: center; color: var(--dim); }
.sp-fi svg { display: block; }
.spark { display: block; }
.spark polyline { stroke: var(--accent); stroke-width: 1.5; }
.spark.inferred polyline { stroke: var(--warn); stroke-dasharray: 2 2; opacity: .8; }
.spark-grid { stroke: var(--dim); stroke-width: .75; opacity: .55; }
.spark-mon { fill: var(--dim); font-size: 7px; }

/* clickable bird names -> open that species inside the planner (subtle until hovered) */
.bird-link { color: inherit; text-decoration: none; cursor: pointer; }
.bird-link:hover, .bird-link:focus-visible { color: var(--accent-ink); text-decoration: underline; }
/* In the species table the NAME is the headline each row supports — bigger,
   bolder, full ink, so the facet line / freq / marks read as secondary data. */
.species-table td .bird-link { font-size: 0.9375rem; font-weight: 600; color: var(--ink); line-height: 1.25; }
/* Right-justify the name so it sits against its data (the guild bird + facets). */
.species-table th:nth-child(2), .species-table td.name-cell { text-align: right; }
.species-table td.freq-cell { color: var(--dim); }
/* external eBird link, shown on the species page */
.ebird-link { font-size: 0.6875rem; color: var(--accent-ink); text-decoration: none;
  border: 1px solid var(--line); padding: 2px 7px; border-radius: 6px; white-space: nowrap; }
.ebird-link:hover { background: var(--card2); }

.access-box { background: var(--card); border: 1px solid var(--line); border-radius: var(--r); padding: 12px; margin-bottom: 12px; font-size: 0.8125rem; }
.access-box .btn { margin-top: 8px; }
.access-label { margin: 0; font-size: 0.75rem; color: var(--dim); text-transform: uppercase; letter-spacing: .04em; }
.access-links { display: flex; gap: 8px; flex-wrap: wrap; }

/* species search */
.search-wrap { margin-bottom: 12px; }
.search { width: 100%; padding: 12px; font-size: 1rem; border-radius: 10px; border: 1px solid var(--line);
  background: var(--card); color: var(--ink); }

/* Species combobox: an in-app suggestion list that overlays the results, so it
   never shoves the page and behaves the same on every device (unlike a native
   <datalist>). Anchored to the search field. */
.sp-search { position: relative; }
.sp-suggest { position: absolute; left: 0; right: 0; top: calc(100% + 4px); z-index: 40;
  max-height: 46vh; overflow-y: auto; -webkit-overflow-scrolling: touch;
  background: var(--card); border: 1px solid var(--line); border-radius: 12px;
  box-shadow: 0 10px 28px rgba(0, 0, 0, .22); padding: 4px; }
.sp-suggest[hidden] { display: none; }
.sp-suggest-row { display: block; width: 100%; text-align: left; padding: 11px 12px;
  background: none; border: none; border-radius: 8px; font-size: 0.9375rem; color: var(--ink);
  cursor: pointer; -webkit-tap-highlight-color: transparent; }
.sp-suggest-row:active { background: var(--accent-wash); }
.sp-panel { background: var(--card); border: 1px solid var(--line); border-radius: var(--r); padding: 14px; }
.sp-head { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; }
.sp-note { color: var(--dim); font-size: 0.8125rem; }
.sp-best { background: var(--bg2); padding: 8px 10px; border-radius: 8px; }
.sp-live { font-size: 0.8125rem; margin-top: 10px; }

/* settings */
.settings { display: flex; flex-direction: column; gap: 12px; }
.setting .row { display: flex; justify-content: space-between; align-items: center; gap: 10px; margin: 8px 0; }
.setting input[type=text], .setting input:not([type]) { background: var(--bg2); border: 1px solid var(--line);
  color: var(--ink); border-radius: 8px; padding: 8px; min-width: 0; }

/* region switcher pills (prepended to every view) */
.regionbar { display: flex; gap: 6px; flex-wrap: wrap; align-items: center;
  padding: 2px 84px 10px 0; margin-bottom: 2px; }
.region-pill { background: var(--card); border: 1px solid var(--line); color: var(--ink);
  border-radius: 999px; padding: 5px 14px; font-size: 0.8125rem; cursor: pointer;
  max-width: 46vw; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.region-pill.active { background: var(--accent); border-color: var(--accent); color: #06160f; font-weight: 700; }
.region-pill.add { color: var(--dim); font-weight: 700; padding: 5px 12px; }
.region-pill:active { transform: translateY(1px); }

/* county picker map */
.map-wrap { position: relative; width: 100%; margin: 4px 0 10px; border: 1px solid var(--line);
  border-radius: var(--r); overflow: hidden; background: var(--bg2);
  /* pan-y = cooperative gestures: one finger scrolls the PAGE (on small screens
     the map fills the viewport, and touch-action:none left nowhere to scroll
     from), two fingers pan/pinch the MAP (panzoom.js ignores one-touch pans). */
  touch-action: pan-y; -webkit-user-select: none; user-select: none; }
.county-map { display: block; width: 100%; height: auto; }
.county-map .county { fill: var(--card2); stroke: var(--boundary); stroke-width: 1;
  vector-effect: non-scaling-stroke; /* 208px strokes at deep zoom froze Safari's restyle */
  stroke-opacity: .55; /* dashed = man-made boundary; roads solid red, rivers solid blue; dash counter-scales so 256x zoom doesn't make monsters */
  stroke-dasharray: calc(3px * var(--fb, 1)) calc(1.6px * var(--fb, 1));
  cursor: pointer; transition: fill .1s; vector-effect: non-scaling-stroke; }
.county-map .county:hover { fill: var(--accent-wash); }
.county-map .county.sel { fill: var(--accent); stroke: var(--accent2); }
/* hotspot map view */
.map-tall .county-map { max-height: 62vh; }
.county-map .county.far { fill: var(--far); cursor: default; }
.county-map .county.far:hover { fill: var(--far); }
/* Active-region counties visibly change colour (a warm accent wash), not just a
   border — the outline is re-stroked on top in .county-outline.region. */
.county-map .county.region { fill: var(--accent-wash); stroke: var(--line); cursor: default; }
.county-map .county.region:hover { fill: var(--accent-wash); }

/* Offline orientation basemap — clipped to the state, always pointer-transparent
   so taps fall through to the counties. Strokes stay hairline at every zoom. */
.basemap, .county-labels, .landmark-labels, .pin-names, .sel-outlines, .county-outline { pointer-events: none; }

/* --- Zoom-stable sizing ------------------------------------------------------
   panzoom computes the sizing factors as PLAIN NUMBERS each frame (--fk labels,
   --fc county names, --fp pins/dots, --fb boundary dashes — all fold the zoom
   cap and the Aa text size in). CSS only ever MULTIPLIES by them: Safari
   mis-renders min()/division-by-var inside calc (it gave iPad fat black pins),
   so no arithmetic beyond multiplication lives here. Fallback values match the
   statewide look before the first frame. */
.county-map .county-label { font-size: calc(var(--fs, 8) * 1px * var(--fc, 1.3)); }
.county-map .lm-water, .county-map .lm-park, .county-map .lm-shield-t,
.county-map .pin-name { font-size: calc(var(--fs, 5) * 1px * var(--fk, 1.3)); }
.lm-shield rect { width: calc(var(--w, 10) * 1px * var(--fk, 1.3)); height: calc(var(--h, 7.5) * 1px * var(--fk, 1.3));
  x: calc(var(--cx, 0) * 1px - var(--w, 10) * .5px * var(--fk, 1.3));
  y: calc(var(--cy, 0) * 1px - var(--h, 7.5) * .5px * var(--fk, 1.3));
  rx: calc(var(--h, 7.5) * .5px * var(--fk, 1.3)); }
.lm-dot { r: calc(2.2px * var(--fk, 1.3)); stroke-width: calc(.6px * var(--fk, 1.3)); }
/* Hold each pin at a constant ON-SCREEN size as you zoom, via a TRANSFORM — not
   the CSS `r` geometry property. Safari/WebKit ignores CSS r/cx/cy, so `r:
   calc(--pr*--fp)` did nothing on iPad: pins fell back to their fixed `r`
   ATTRIBUTE and ballooned with zoom (giant blobs that lagged the map to a halt
   AND grew big enough to cover their own labels — Noah's deep-zoom screenshot).
   scale(--fp) around each pin's own centre works in every engine; the base size
   is the `r` attribute set in JS. stroke scales with the transform too. */
.hotspot-map .pin, .hotspot-map .you-dot {
  transform: scale(var(--fp, 1)); transform-box: fill-box; transform-origin: center; }

/* Hotspot names: every dot has one in the DOM, but they're hidden by default —
   mapview's declutter pass adds .lbl-show to the non-overlapping subset it wants
   visible for the current view (so a dense county never paints all 597 at once,
   which was unreadable and slow). The viewport cull can still hide an on-screen
   label if it scrolls off (inline visibility wins over the class). */
.pin-name { visibility: hidden; fill: var(--ink); fill-opacity: .9; text-anchor: middle;
  font-family: var(--sans); font-weight: 600; paint-order: stroke; stroke: var(--label-halo);
  stroke-width: .34em; stroke-opacity: .9; stroke-linejoin: round; }
.hotspot-map .pin-name.lbl-show { visibility: visible; }

/* Map scale — bottom-left overlay: a distance bar for users, with the zoom
   factor tucked in small (screenshot-debuggable). Updated only at rest. */
.map-scale { position: absolute; left: 10px; bottom: 8px; display: flex; align-items: center;
  gap: 7px; padding: 4px 9px; border-radius: 7px; cursor: pointer; /* tap = diagnostics window */
  background: color-mix(in srgb, var(--card) 88%, transparent);
  border: 1px solid var(--line); font-family: var(--mono); font-size: 0.6875rem; color: var(--ink); }

/* Hold-to-load line (Noah's design): while the box is quiet but fingers are
   still down, a thin bar fills over ~3s — at full, the map loads anyway (also
   the self-heal for iOS's swallowed pointerups, which otherwise gated loading
   forever). Hidden the moment the box moves or the swap starts. */
.map-hold { position: absolute; left: 10px; bottom: 38px; width: 130px; height: 5px;
  border-radius: 4px; border: 1px solid var(--line); overflow: hidden; pointer-events: none;
  background: color-mix(in srgb, var(--card) 88%, transparent); }
.map-hold-fill { display: block; height: 100%; width: 0; background: var(--accent); }

/* Diagnostics window (tap the scale bar): live internals over the map for
   screenshot-debugging a freeze. The box positions the overlay and stays
   pointer-events none so it never eats a gesture; only the copy-log button
   inside takes taps (grab the runtime trace without leaving the map). */
.map-debug-box { position: absolute; top: 10px; left: 10px; padding: 8px 10px;
  border-radius: 7px; pointer-events: none; text-align: left;
  background: color-mix(in srgb, var(--card) 90%, transparent);
  border: 1px solid var(--line); }
.map-debug { margin: 0; white-space: pre;
  font-family: var(--mono); font-size: 0.6875rem; line-height: 1.45; color: var(--ink); }
.map-debug-copy { display: block; margin-top: 6px; padding: 3px 8px; pointer-events: auto;
  border-radius: 6px; border: 1px solid var(--line); cursor: pointer;
  background: var(--card); font-family: var(--mono); font-size: 0.6875rem; color: var(--ink); }
.map-scale-bar { display: inline-block; height: 5px; min-width: 12px;
  border: 1.5px solid var(--ink); border-top: none; }
.map-scale-zf { color: var(--dim); }

/* Map update progress — per-type live counts while the stop-swap runs, above
   the scale. The spinner animates transform on the compositor, so it keeps
   turning even when the main thread is busy: spinner turning + counts stuck =
   the stuck phase names the culprit; both frozen = renderer stall. */
.map-progress { position: absolute; left: 10px; bottom: 38px; display: flex; align-items: center;
  gap: 7px; padding: 4px 9px; border-radius: 7px; pointer-events: none;
  background: color-mix(in srgb, var(--card) 88%, transparent);
  border: 1px solid var(--line); font-family: var(--mono); font-size: 0.6875rem; color: var(--ink); }
.map-progress-spin { width: 10px; height: 10px; border-radius: 50%; flex: 0 0 auto;
  border: 2px solid var(--track); border-top-color: var(--accent);
  animation: mpspin .8s linear infinite; }
@keyframes mpspin { to { transform: rotate(360deg); } }

/* Deep-zoom PERFORMANCE: the map still draws everything IN VIEW — long lines are
   chunked (ui/basemap.js) so the viewport cull drops the off-screen pieces, fills
   cull whole by bbox, and every element's cull bbox is precomputed from its data
   (no getBBox layout storm). The clip stays on but uses a decimated, cheap region. */

/* Rubber-band pulse when a pinch pushes past the zoom limit — feedback, not lag. */
.county-map.pz-limit-in { animation: pzLimitIn .22s ease-out; }
.county-map.pz-limit-out { animation: pzLimitOut .22s ease-out; }
@keyframes pzLimitIn { 40% { transform: scale(1.025); } }
@keyframes pzLimitOut { 40% { transform: scale(.978); } }
.county-map { transform-origin: 50% 50%; }
.bm-park { fill: var(--park); fill-opacity: .42; stroke: var(--park); stroke-width: .6; stroke-opacity: .7; vector-effect: non-scaling-stroke; }
.bm-lake { fill: var(--water); fill-opacity: .62; stroke: var(--water); stroke-width: .5; vector-effect: non-scaling-stroke; }
.bm-river { fill: none; stroke: var(--water); stroke-width: .9; stroke-opacity: .7; vector-effect: non-scaling-stroke; }
.bm-coast { fill: none; stroke: var(--water); stroke-width: 1.1; stroke-opacity: .8; vector-effect: non-scaling-stroke; }
.bm-road { fill: none; stroke: var(--road); stroke-width: 1; stroke-opacity: .55; vector-effect: non-scaling-stroke; }

/* Soft county-name labels — deliberately LIGHT (dim ink, half opacity) so they
   whisper behind the landmarks instead of dominating them. Font-size is set
   per-label in viewBox units (data-driven), so names are quiet statewide and
   resolve as you pinch in. */
.county-label { fill: var(--label-ink); fill-opacity: .5; text-anchor: middle; dominant-baseline: central;
  font-family: var(--sans); font-weight: 500; paint-order: stroke;
  stroke: var(--label-halo); stroke-width: 2; stroke-opacity: .6; stroke-linejoin: round;
  vector-effect: non-scaling-stroke; }

/* Landmark name labels — identify the lines and blobs. Scaled in viewBox units
   like county names, with a paper halo for legibility; quiet statewide, readable
   as you pinch in. Each type carries its layer's colour. */
.lm-water, .lm-park { text-anchor: middle; dominant-baseline: central; font-family: var(--sans);
  font-weight: 600; paint-order: stroke; stroke: var(--label-halo); stroke-width: 2;
  stroke-opacity: .9; stroke-linejoin: round; vector-effect: non-scaling-stroke; }
.lm-water { fill: var(--water); }
.lm-river { font-style: italic; }
.lm-park { fill: var(--park); }
/* Curated reservoir / refuge points: a small dot at the true spot + its name. */
.lm-dot { stroke: var(--card); stroke-width: .5; }
.lm-dot-water { fill: var(--water); }
.lm-dot-park { fill: var(--park); }
/* Road-number shield: a pill in the SAME clay red as the road lines (that
   shared colour is what ties the number to its road), cream text, soft edge. */
.lm-shield rect { fill: var(--road); fill-opacity: .92; stroke: var(--card); stroke-width: .6; }
.lm-shield-t { text-anchor: middle; dominant-baseline: central; font-family: var(--mono);
  font-weight: 700; fill: var(--card); }
/* On the statewide picker (.gated), keep landmark names hidden until pinched in
   so it isn't a wall of shields; the hotspot map shows them always. */
.county-map.gated:not(.lm-visible) .landmark-labels { display: none; }

/* Selection / region outlines, re-stroked ON TOP of the basemap so a selected or
   active county is always fully outlined (neighbours can't paint over it). */
.county-outline.sel { fill: none; stroke: var(--accent2); stroke-width: 2.4; stroke-linejoin: round; vector-effect: non-scaling-stroke; }
.county-outline.region { fill: none; stroke: var(--accent2); stroke-width: 2; stroke-linejoin: round; vector-effect: non-scaling-stroke; }

/* Two states only: ordinary spots are quiet uniform dots; the month's HOT tier
   is a solid --score-hot dot, a third larger, drawn on top of its neighbours. */
/* Plain pins are a soft, semi-transparent dot so a dense cluster reads as a
   light stipple instead of a solid black mass (Noah's Humboldt screenshot); the
   hot tier keeps a firm dark edge + a bigger dot so it still pops on top. */
.hotspot-map .pin { cursor: pointer; fill: var(--card); fill-opacity: .78;
  stroke: color-mix(in srgb, var(--ink) 52%, transparent); stroke-width: .4; }
.hotspot-map .pin.hot { fill: var(--score-hot); fill-opacity: 1;
  /* A firmer dark edge + a bigger dot (via a larger scale, not CSS r) so the hot
     tier pops for grayscale / colour-blind eyes. */
  stroke: #06160f; stroke-width: .6;
  transform: scale(calc(var(--fp, 1) * 1.5)); }
.hotspot-map .pin:hover { stroke: var(--ink); }
.hotspot-map .you-dot { fill: var(--slate); stroke: var(--card); stroke-width: 1; pointer-events: none; }

.map-zoom { position: absolute; top: 8px; right: 8px; display: flex; flex-direction: column; gap: 4px; }
.map-zbtn { width: 32px; height: 32px; border-radius: 8px; border: 1px solid var(--line);
  background: color-mix(in srgb, var(--card) 88%, transparent); color: var(--ink);
  font-size: 1.125rem; line-height: 1; cursor: pointer; display: grid; place-content: center; }
.map-zbtn:active { transform: translateY(1px); }

/* standing mode indicator over the map bottom — announces the picker mode,
   never steals a county tap (pointer-events: none) */
.pick-mode { position: absolute; left: 50%; bottom: 10px; transform: translateX(-50%);
  max-width: 90%; padding: 5px 12px; border-radius: 999px; pointer-events: none; white-space: nowrap;
  background: color-mix(in srgb, var(--card2) 88%, transparent); color: var(--ink); border: 1px solid var(--accent2);
  font-size: 0.75rem; box-shadow: 0 6px 18px rgba(0, 0, 0, .4); backdrop-filter: blur(6px); }

.pick-toolbar { display: flex; align-items: center; justify-content: space-between; gap: 10px; margin: 4px 0 10px; }
.pick-count { color: var(--dim); font-size: 0.8125rem; }
.btn.small { padding: 4px 10px; font-size: 0.75rem; }

.county-list { display: grid; grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); gap: 2px 12px; margin-bottom: 14px; }
.county-item { display: flex; align-items: center; gap: 8px; padding: 6px 4px; font-size: 0.875rem; cursor: pointer; }
.county-item input { width: 18px; height: 18px; accent-color: var(--accent); flex: 0 0 auto; }

.pick-actions { display: flex; flex-wrap: wrap; align-items: flex-end; gap: 10px; }
.region-name-wrap { display: flex; flex-direction: column; gap: 4px; font-size: 0.75rem; color: var(--dim); flex: 1 1 220px; }
.region-name { background: var(--bg2); border: 1px solid var(--line); color: var(--ink);
  border-radius: 8px; padding: 10px; font-size: 1rem; }
.btn.primary { background: var(--accent); border-color: var(--accent); color: #06160f; font-weight: 700; }
.btn.primary:disabled { opacity: .4; cursor: not-allowed; }
.btn.danger { background: none; border-color: var(--danger-dim); color: var(--danger); }

/* settings → region rows */
.region-rows { display: flex; flex-direction: column; gap: 6px; margin: 8px 0 10px; }
.region-row { display: flex; align-items: center; justify-content: space-between; gap: 10px;
  padding: 8px 10px; background: var(--bg2); border: 1px solid var(--line); border-radius: 8px; }
.region-row-main { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; min-width: 0; }
.region-row-actions { display: flex; align-items: center; gap: 6px; flex: 0 0 auto; }
.dim.small, .small { font-size: 0.75rem; }

/* toast (auto-switch notices etc.) */
.toast { position: fixed; left: 50%; bottom: calc(var(--tab-h) + env(safe-area-inset-bottom) + 26px);
  transform: translate(-50%, 8px); max-width: 88vw; padding: 10px 16px; z-index: 20;
  background: var(--card2); color: var(--ink); border: 1px solid var(--accent2); border-radius: 999px;
  font-size: 0.8125rem; box-shadow: 0 8px 26px rgba(0, 0, 0, .45); opacity: 0; transition: opacity .25s, transform .25s;
  pointer-events: none; }
.toast.show { opacity: 1; transform: translate(-50%, 0); }
/* actionable toast (Undo etc.) — must catch taps, so it opts back in */
.toast.has-action { display: inline-flex; align-items: center; gap: 12px; padding: 8px 8px 8px 16px; pointer-events: auto; }
.toast-action { flex: 0 0 auto; background: var(--accent); color: #06160f; border: 0; border-radius: 999px;
  padding: 6px 14px; font-size: 0.8125rem; font-weight: 700; cursor: pointer; }
.toast-action:active { transform: translateY(1px); }

/* tab bar — a floating dock in the same card language as the content: same
   width as the cards (760px column minus the view's 12px side padding), same
   radius and border, floated above the home-indicator area. No more full-bleed
   hairline — the bottom shares the column boundary like everything else. */
.tabs { position: fixed; bottom: calc(10px + env(safe-area-inset-bottom)); left: 0; right: 0;
  margin-inline: auto; width: min(736px, calc(100% - 24px)); height: var(--tab-h);
  background: color-mix(in srgb, var(--dock) 97%, transparent);
  -webkit-backdrop-filter: blur(12px) saturate(1.04); backdrop-filter: blur(12px) saturate(1.04);
  border: 1px solid var(--dock-line); border-radius: var(--r); overflow: hidden;
  box-shadow: var(--dock-shadow), var(--dock-lip);
  display: grid; grid-template-columns: repeat(5, 1fr); z-index: 10; }
.tab { background: none; border: 0; color: var(--tab-ink); display: flex; flex-direction: column;
  align-items: center; justify-content: center; gap: 2px; cursor: pointer; font-size: 0.6875rem; }
.tab.active { color: var(--tab-ink-active); }
/* The active tab carries a SHAPE (small bar), never hue alone. */
.tab.active .tab-icon::after { content: ""; display: block; position: absolute;
  left: 50%; transform: translateX(-50%); bottom: -4px; width: 16px; height: 3px;
  border-radius: 2px; background: currentColor; }
.tab.active .tab-icon { position: relative; }
.tab-icon { display: flex; align-items: center; justify-content: center; line-height: 0; }

/* Floating corner buttons hug the content column on wide screens (the .view
   is max-width 760px with 12px side padding), and fall back to the screen
   corner on narrow ones — same boundary as everything else. */
:root { --col-edge: max(10px, (100% - 760px) / 2 + 12px); }

/* about — unobtrusive floating info button + modal */
.about-btn { position: fixed; top: calc(8px + env(safe-area-inset-top)); right: var(--col-edge); z-index: 8;
  width: 30px; height: 30px; padding: 0; border-radius: 50%; border: 1px solid var(--line);
  background: var(--card2); color: var(--dim); font-size: 0.9375rem; line-height: 1; cursor: pointer;
  display: grid; place-content: center; opacity: .55; transition: opacity .15s, color .15s, border-color .15s; }
.about-btn:hover, .about-btn:focus-visible { opacity: 1; color: var(--accent); border-color: var(--accent); }
.about-btn:active { transform: translateY(1px); }

/* Dawn Mode toggle — floating moon/sun, sits just left of the About button so
   the theme is switchable from any screen. */
.theme-btn { position: fixed; top: calc(8px + env(safe-area-inset-top)); right: calc(var(--col-edge) + 38px); z-index: 8;
  width: 30px; height: 30px; padding: 0; border-radius: 50%; border: 1px solid var(--line);
  background: var(--card2); color: var(--dim); cursor: pointer;
  display: grid; place-content: center; opacity: .55; transition: opacity .15s, color .15s, border-color .15s; }
.theme-btn:hover, .theme-btn:focus-visible { opacity: 1; color: var(--accent); border-color: var(--accent); }
.theme-btn:active { transform: translateY(1px); }
.theme-btn svg { display: block; }

.about-dialog { position: fixed; inset: 0; margin: auto; width: min(92vw, 560px); max-height: 84vh;
  padding: 0; overflow: hidden;
  background: var(--card); color: var(--ink); border: 1px solid var(--line); border-radius: var(--r);
  box-shadow: 0 18px 50px rgba(0, 0, 0, .5); }
/* Only lay out as a flex column when actually open — otherwise the browser's
   `dialog:not([open]) { display:none }` must win, or it covers the page. */
.about-dialog[open] { display: flex; flex-direction: column; }
.about-dialog::backdrop { background: rgba(4, 12, 9, .66); backdrop-filter: blur(2px); }
.about-close { position: absolute; top: 10px; right: 10px; z-index: 2; width: 30px; height: 30px;
  border-radius: 50%; border: 1px solid var(--line); background: var(--bg2); color: var(--ink);
  font-size: 1.125rem; line-height: 1; cursor: pointer; }
.about-close:active { transform: translateY(1px); }
.about-body { padding: 22px 20px; overflow-y: auto; -webkit-overflow-scrolling: touch; }
.about-body h2 { font-size: 1.1875rem; margin: 0 0 10px; padding-right: 34px; }
.about-body h3 { font-size: 0.875rem; margin: 16px 0 4px; color: var(--accent-ink); }
.about-body p { margin: 6px 0; font-size: 0.875rem; }
.about-body ul { margin: 6px 0; padding-left: 20px; font-size: 0.875rem; }
.about-body li { margin: 3px 0; }
.about-formula { background: var(--bg2); border: 1px solid var(--line); border-radius: 8px;
  padding: 10px 12px; font-weight: 700; color: var(--accent-ink); text-align: center;
  font-variant-numeric: tabular-nums; }
.about-inferred { color: var(--warn); font-weight: 700; }
.about-foot { color: var(--dim); font-size: 0.75rem; margin-top: 16px; border-top: 1px solid var(--line); padding-top: 10px; }
.whatsnew-h { margin-top: 20px !important; border-top: 1px solid var(--line); padding-top: 14px; }
.whatsnew-dialog { max-width: 420px; }
.whatsnew-dialog .rel-date { color: var(--dim); font-size: 0.75rem; margin-top: -6px; }
.whatsnew-ok { margin-top: 14px; width: 100%; }
.about-body .roadmap li { margin: 7px 0; }
.about-body .roadmap strong { color: var(--accent-ink); }
.about-body .rel { margin: 8px 0 12px; }
.rel-head { display: flex; align-items: baseline; gap: 8px; margin: 0 0 2px !important; }
.rel-ver { font-weight: 700; color: var(--accent-ink); font-variant-numeric: tabular-nums; }
.rel-date { color: var(--dim); font-size: 0.75rem; }
.rel-more { font-size: 0.8125rem; margin: 4px 0 0 !important; }

/* Species photo credits — a collapsed section listing each thumbnail's
   photographer + licence (Wikimedia Commons). Scrolls within itself so 270-odd
   rows don't blow out the dialog. */
.photo-credits { margin-top: 18px; border-top: 1px solid var(--line); padding-top: 12px; }
.photo-credits > summary { cursor: pointer; font-size: 0.875rem; font-weight: 600; color: var(--accent-ink); }
.photo-credits[open] > summary { margin-bottom: 8px; }
.pc-intro { font-size: 0.75rem; color: var(--dim); margin: 4px 0 8px !important; }
.pc-empty { font-size: 0.8125rem; color: var(--dim); }
.pc-list { max-height: 260px; overflow-y: auto; -webkit-overflow-scrolling: touch;
  margin: 0 !important; padding-left: 0 !important; list-style: none; }
.pc-row { font-size: 0.75rem; margin: 4px 0; line-height: 1.35; }
.pc-name { font-weight: 600; }
.pc-cred { color: var(--dim); }

/* Score explainer — tap a score to see what it means and how it was worked out. */
.si-dialog { position: fixed; inset: 0; margin: auto; width: min(92vw, 480px); max-height: 86vh;
  padding: 0; overflow: hidden; background: var(--card); color: var(--ink);
  border: 1px solid var(--line); border-radius: var(--r); box-shadow: 0 18px 50px rgba(0,0,0,.5); }
.si-dialog[open] { display: flex; flex-direction: column; }
.si-dialog::backdrop { background: rgba(4,12,9,.66); backdrop-filter: blur(2px); }
.si-close { position: absolute; top: 10px; right: 10px; z-index: 2; width: 30px; height: 30px;
  border-radius: 50%; border: 1px solid var(--line); background: var(--bg2); color: var(--ink);
  font-size: 1.125rem; line-height: 1; cursor: pointer; }
.si-close:active { transform: translateY(1px); }
.si-head { display: flex; align-items: center; gap: 14px; padding: 20px 20px 14px;
  border-bottom: 1px solid var(--line); }
.si-count { flex: 0 0 auto; width: 60px; height: 60px; border-radius: 50%; display: grid;
  place-content: center; text-align: center; background: var(--accent-wash); border: 1px solid var(--accent); }
.si-count-num { font-size: 1.375rem; font-weight: 700; line-height: 1; color: var(--accent-ink); }
.si-count-cap { font-size: 0.6875rem; color: var(--dim); text-transform: uppercase; letter-spacing: .4px; }
/* Guild icon at the head of each explainer row, inheriting the fi-* state colour. */
.si-icon { flex: 0 0 auto; display: inline-grid; place-content: center; }
.si-icon svg { display: block; }
.si-title h2 { font-family: var(--serif); font-weight: 600; font-size: 1.125rem; margin: 0; padding-right: 30px; }
.si-sub { color: var(--dim); font-size: 0.75rem; }
.si-body { padding: 16px 20px 22px; overflow-y: auto; -webkit-overflow-scrolling: touch; }
.si-body p { margin: 8px 0; font-size: 0.875rem; }
.si-lead { margin-top: 0 !important; }
.si-formula { background: var(--bg2); border: 1px solid var(--line); border-radius: 8px;
  padding: 10px 12px; font-weight: 700; color: var(--accent-ink); text-align: center;
  font-family: var(--mono); font-variant-numeric: tabular-nums; }
.si-h { font-size: 0.8125rem; margin: 18px 0 8px; color: var(--accent-ink); text-transform: uppercase; letter-spacing: .4px; }
.si-note { color: var(--dim); font-size: 0.8125rem !important; }
.si-note.si-dim { font-size: 0.75rem !important; border-top: 1px solid var(--line); padding-top: 10px; margin-top: 14px; }
.si-legend { color: var(--dim); font-size: 0.6875rem !important; margin-top: 6px !important; }
.si-trust { display: flex; align-items: baseline; gap: 8px; flex-wrap: wrap; }
.si-contribs { display: flex; flex-direction: column; gap: 6px; }
.si-row { display: grid; grid-template-columns: auto 1fr 84px auto; align-items: center; gap: 8px; font-size: 0.8125rem; }
.si-sp { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.si-bar { height: 6px; background: var(--track); border-radius: 3px; overflow: hidden; }
.si-bar i { display: block; height: 100%; background: var(--accent); }
.si-vals { font-family: var(--mono); font-variant-numeric: tabular-nums; color: var(--dim); font-size: 0.75rem; }

/* =============================================================================
   TARGET BIRDS (v21) — a personal species list that re-weights the ranking.
   All colours are :root tokens, so Dawn Mode needs no extra overrides.
   ========================================================================== */

/* Star toggle — reused on species pages, the hotspot matrix and the picker.
   Big enough to tap; gold and filled when the bird is on your list. */
.star-btn { flex: 0 0 auto; width: 34px; height: 34px; padding: 0; border: none;
  background: none; cursor: pointer; font-size: 1.25rem; line-height: 1; color: var(--dim);
  display: grid; place-content: center; border-radius: 50%; transition: color .12s, transform .1s; }
.star-btn:hover, .star-btn:focus-visible { color: var(--accent); }
.star-btn.on { color: var(--accent); }
.star-btn:active { transform: scale(.86); }
/* The target mark is now a camera (a photographer's shot list). Size the SVG per
   context; on = solid accent (from .star-btn.on), off = the faint outline. */
.star-btn svg { width: 19px; height: 19px; display: block; }
.species-table td.mark-cell .star-btn svg { width: 16px; height: 16px; }
.tb-mark svg { width: 16px; height: 16px; display: block; }
.tg-entry-mark svg { width: 20px; height: 20px; display: block; }
.legend .leg-ico { display: inline-grid; place-content: center; color: var(--accent); vertical-align: -3px; }
.legend .leg-ico svg { width: 14px; height: 14px; display: block; }

/* Standing mode indicator on the ranked views. Engaged = accent-washed so the
   "your birds are driving this" state is unmistakable; disengaged = quiet. */
.targetbar { display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
  margin: 0 0 12px; padding: 9px 12px; border-radius: var(--r);
  border: 1px solid var(--line); background: var(--card2); }
.targetbar.engaged { border-color: var(--accent); background: var(--accent-wash); }
.tb-mark { flex: 0 0 auto; font-size: 1rem; color: var(--dim); }
.targetbar.engaged .tb-mark { color: var(--accent); }
.tb-label { flex: 1 1 180px; font-size: 0.8125rem; color: var(--ink); }
.tb-label strong { font-weight: 700; }
.tb-actions { display: flex; gap: 6px; flex: 0 0 auto; }

/* Data-freshness notice — calm honesty when the eBird data has aged. Slate for
   "older", a warmer --warn for the archive state; never loud. */
.freshbar { display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
  margin: 0 0 12px; padding: 9px 12px; border-radius: var(--r);
  border: 1px solid var(--slate); background: var(--slate-wash); }
.freshbar.archived { border-color: var(--warn);
  background: color-mix(in srgb, var(--warn) 12%, var(--card)); }
.fresh-ico { flex: 0 0 auto; color: var(--slate); display: inline-grid; place-content: center; }
.freshbar.archived .fresh-ico { color: var(--warn); }
.fresh-ico svg { display: block; }
.fresh-msg { flex: 1 1 180px; font-size: 0.8125rem; color: var(--ink); line-height: 1.4; }
.fresh-msg strong { font-weight: 700; }
.fresh-actions { display: flex; gap: 4px; flex: 0 0 auto; align-items: center; }
.fresh-x { background: none; border: 0; color: var(--dim); font-size: 1.25rem; line-height: 1;
  padding: 2px 6px; cursor: pointer; border-radius: 6px; }
.fresh-x:hover, .fresh-x:focus-visible { color: var(--ink); }

/* Species-tab entry card into the picker. */
.tg-entry { display: flex; align-items: center; gap: 12px; width: 100%; text-align: left;
  margin: 0 0 14px; padding: 12px 14px; border-radius: var(--r);
  border: 1px solid var(--line); background: var(--card); color: var(--ink); cursor: pointer; }
.tg-entry:active { transform: translateY(1px); }
.tg-entry-mark { flex: 0 0 auto; font-size: 1.25rem; color: var(--accent); }
.tg-entry-main { flex: 1 1 auto; display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.tg-entry-main strong { font-family: var(--serif); font-weight: 600; font-size: 0.9375rem; }
.tg-entry-main .dim { font-size: 0.75rem; }
.tg-entry-go { flex: 0 0 auto; color: var(--dim); font-size: 1.375rem; }

/* Picker screen. */
.tg-summary { display: flex; align-items: center; gap: 12px; flex-wrap: wrap;
  margin: 0 0 12px; padding: 10px 12px; border-radius: var(--r);
  border: 1px solid var(--line); background: var(--card2); }
.tg-count { font-weight: 700; font-size: 0.875rem; }
.tg-toggle { display: flex; align-items: center; gap: 8px; margin-left: auto; font-size: 0.8125rem; color: var(--dim); }
.tg-toggle input { width: 18px; height: 18px; accent-color: var(--accent); }
.tg-hint { font-size: 0.75rem; margin: 0 0 12px; }
.tg-group { font-family: var(--sans); font-size: 0.75rem; font-weight: 700; text-transform: uppercase;
  letter-spacing: .5px; color: var(--accent-ink); margin: 16px 0 6px; }
.tg-list { display: flex; flex-direction: column; }
.tg-row { display: flex; align-items: center; gap: 10px; padding: 7px 6px;
  border-bottom: 1px solid var(--line); }
.tg-row.on { background: var(--accent-wash); border-radius: 8px; border-bottom-color: transparent; }
.tg-row-main { flex: 1 1 auto; display: flex; align-items: center; gap: 8px; min-width: 0; }
.tg-name { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 0.875rem; }

/* Collapsible habitat sections in the browse list (v31): the picker opens as a
   short index; tap a section header to unfold its birds. Header reuses the
   .tg-group typography (accent, uppercase) and adds a chevron + count badges. */
.tg-browsehead { display: flex; align-items: center; gap: 10px; margin: 16px 0 2px; }
.tg-browselabel { font-family: var(--sans); font-size: 0.75rem; font-weight: 700;
  text-transform: uppercase; letter-spacing: .5px; color: var(--dim); }
.tg-foldall { margin-left: auto; }
.tg-fold { display: flex; align-items: center; gap: 9px; width: 100%;
  margin: 0; padding: 11px 6px; background: none; border: none;
  border-bottom: 1px solid var(--line); cursor: pointer; text-align: left;
  -webkit-tap-highlight-color: transparent; }
.tg-fold:active { background: var(--accent-wash); }
.tg-fold.open { border-bottom-color: transparent; }
.tg-fold-chev { flex: none; width: 12px; font-size: 0.6875rem; color: var(--accent-ink); }
.tg-fold-label { flex: 1 1 auto; min-width: 0; overflow: hidden;
  text-overflow: ellipsis; white-space: nowrap; }
.tg-fold-counts { flex: none; display: flex; align-items: center; gap: 6px; }
.tg-fold-count { font-family: var(--mono); font-size: 0.75rem; font-weight: 600;
  letter-spacing: 0; text-transform: none; color: var(--dim);
  background: var(--card2); border: 1px solid var(--line);
  border-radius: 999px; padding: 1px 8px; }
.tg-fold-starred { display: inline-flex; align-items: center; gap: 3px;
  font-family: var(--mono); font-size: 0.75rem; font-weight: 700;
  letter-spacing: 0; text-transform: none; color: var(--accent-ink);
  background: var(--accent-wash); border: 1px solid var(--accent);
  border-radius: 999px; padding: 1px 7px 1px 5px; }
.tg-fold-starred svg { width: 12px; height: 12px; }
.tg-group-body { display: flex; flex-direction: column; }
.tg-group-body[hidden] { display: none; }

/* Star column in the hotspot species matrix + a target row highlight. */
.species-table td.star-cell { padding: 0 2px; width: 34px; }
.species-table tr.is-target { background: var(--accent-wash); }

/* Species-page header star sits before the name. */
.sp-head { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }

/* Targeting note in the score explainer. */
.si-targeting { background: var(--accent-wash); border: 1px solid var(--accent);
  border-radius: 8px; padding: 9px 11px; font-size: 0.8125rem !important; color: var(--ink); }
.si-targeting strong { color: var(--accent-ink); }

/* ==========================================================================
   BIRD LISTS (v22) — informational stars, the seen/life list & its modes.
   Stars are gold (want); seen is slate (got) — two clearly different jobs.
   ========================================================================== */

/* Seen toggle — the slate sibling of the gold star. ＋ to add, ✓ when on. */
.seen-btn { flex: 0 0 auto; width: 34px; height: 34px; padding: 0; border: none;
  background: none; cursor: pointer; font-size: 1.125rem; line-height: 1; color: var(--dim);
  display: grid; place-content: center; border-radius: 50%; transition: color .12s, transform .1s; }
.seen-btn:hover, .seen-btn:focus-visible { color: var(--slate); }
.seen-btn.on { color: var(--slate); }
.seen-btn:active { transform: scale(.86); }

/* "New for me" bar — same shape as the target bar, in slate so the two modes
   never read as the same thing when both are engaged. */
.newbar { display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
  margin: 0 0 12px; padding: 9px 12px; border-radius: var(--r);
  border: 1px solid var(--slate); background: var(--slate-wash); }
.newbar .tb-mark { color: var(--slate); }
.newbar .tb-label strong { color: var(--slate); }

/* Seen birds: dimmed but kept visible everywhere. Dim the TEXT, never the
   toggle cell, so the ✓/★ stays crisp and tappable. */
.tsp.is-seen { opacity: .5; }
.species-table tr.is-seen td:not(.mark-cell) { opacity: .5; }
.tg-row.is-seen .tg-row-main { opacity: .5; }
.tl-card.is-seen .tl-main { opacity: .55; }

/* Star + seen stacked in one narrow matrix cell. */
.species-table td.mark-cell { padding: 0 2px; width: 34px; }
.species-table td.mark-cell { display: table-cell; }
/* Marks are supporting affordances, not the focus — small and faint when off,
   still gold/slate when the bird is on your list/seen. */
.species-table td.mark-cell .star-btn,
.species-table td.mark-cell .seen-btn { width: 24px; height: 22px; font-size: 0.8125rem; }
.species-table td.mark-cell .star-btn:not(.on),
.species-table td.mark-cell .seen-btn:not(.on) { color: var(--dim); opacity: .5; }

/* Seen-list picker rows check off in slate (targets star on in gold). */
.tg-row.seen-row.on { background: var(--slate-wash); }

/* Seen-list entry card mark on the Species tab. */
.tg-entry.seen-entry .tg-entry-mark { color: var(--slate); }

/* Your-list "where & when" info cards on the target picker. */
.tg-yourlist { margin: 0 0 6px; }
.tl-card { display: flex; align-items: flex-start; gap: 10px; padding: 10px 8px;
  border-bottom: 1px solid var(--line); }
.tl-main { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; gap: 3px; }
.tl-name { font-family: var(--serif); font-weight: 600; font-size: 0.9375rem; color: var(--ink);
  text-decoration: none; }
.tl-name:hover { color: var(--accent-ink); }
.tl-where { font-size: 0.8125rem; margin: 0; }
.tl-where strong { font-weight: 700; }
.tl-more { font-size: 0.75rem; margin: 0; }
.tl-card .spark { margin-top: 2px; }

/* Bulk-import a life list. */
.tg-import { margin: 0 0 12px; border: 1px solid var(--line); border-radius: var(--r);
  background: var(--card2); padding: 6px 12px; }
.tg-import summary { cursor: pointer; font-size: 0.8125rem; font-weight: 700; color: var(--slate);
  padding: 6px 0; }
.import-wrap { padding: 4px 0 10px; }
.import-ta { width: 100%; box-sizing: border-box; font-family: var(--mono); font-size: 0.75rem;
  border: 1px solid var(--line); border-radius: 10px; padding: 8px; background: var(--card);
  color: var(--ink); resize: vertical; }
.import-actions { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; margin-top: 8px; }
.import-status { margin: 0; font-size: 0.75rem; }

/* ==========================================================================
   FACET ICONS (v23) — per-site guild rows + the tri-state icon filter.
   Every state is distinguishable in greyscale, not by colour alone: brightness
   (glyph colour) + a bottom level-bar for presence, a dashed glyph for
   inferred, a ring+✓ for wanted and a diagonal slash for excluded.
   ========================================================================== */

/* A row of tappable guild icons on a card / hotspot header. */
.facet-row { display: flex; flex-wrap: wrap; gap: 2px; }
.guild-row { margin: 4px 0 10px; }
.card-head + .guild-row { margin-top: 0; }

/* One icon button — a full ≥44px tap target. */
.fi { position: relative; width: 44px; height: 44px; min-width: 44px; padding: 0; border: 0;
  background: none; cursor: pointer; display: grid; place-content: center; border-radius: 10px;
  color: var(--facet-ghost); -webkit-tap-highlight-color: transparent; }
.fi svg { display: block; }
.fi:active { transform: scale(.92); }
.fi:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }

/* Presence level: glyph colour + a bottom bar whose width reads in greyscale. */
.fi::after { content: ""; position: absolute; left: 50%; transform: translateX(-50%); bottom: 5px;
  height: 2px; border-radius: 2px; }
.fi-none  { color: var(--facet-ghost); }
.fi-none::after  { width: 12px; border-top: 2px dotted var(--facet-ghost); }
.fi-some  { color: var(--facet-some); }
.fi-some::after  { width: 8px; background: var(--facet-some); }
.fi-lots  { color: var(--facet-lots); }
.fi-lots::after  { width: 18px; background: var(--facet-lots); }
.fi-lots { background: color-mix(in srgb, var(--accent) 12%, transparent); }

/* Inferred (modeled, not real eBird): ghost the filled glyph + a dashed tile
   outline so it reads as a sketch. A separate channel from the dotted level-bar
   (so a lit-but-modeled icon still shows its level) and works on filled icons,
   which have no stroke to dash. */
.fi.inferred svg { opacity: .5; }
.fi.inferred { outline: 1.5px dashed currentColor; outline-offset: -3px; }

/* Tri-state filter overlay — shape, not just colour. */
.fi.wanted { box-shadow: inset 0 0 0 2px var(--facet-want); }
.fi.wanted::before { content: "✓"; position: absolute; top: 0; right: 3px; font-size: 0.6875rem;
  font-weight: 700; color: var(--facet-want); line-height: 1; }
.fi.excluded { color: var(--facet-block); opacity: .75; }
.fi.excluded::before { content: ""; position: absolute; left: 8px; right: 8px; top: 50%; height: 2px;
  background: var(--facet-block); transform: rotate(-45deg); }

/* Present-guild tiles carry a caption under the glyph (absent ones stay bare).
   The tile grows into a column; captions align to the top so a wrapped label
   never shoves its neighbours' icons down. */
.guild-row { align-items: flex-start; gap: 2px 4px; }
.fi-glyph { display: grid; place-content: center; }
.fi.has-cap { width: auto; height: auto; min-width: 44px; max-width: none; min-height: 44px;
  display: flex; flex-direction: column; align-items: center; justify-content: flex-start;
  gap: 1px; padding: 3px 6px 4px; }
.fi.has-cap::after { content: none; }            /* the caption + glyph colour carry presence */
/* The word stays whole (one unit with the icon) — never hyphen-wraps mid-word. */
.fi-cap { font-size: 0.6875rem; line-height: 1.05; text-align: center; color: var(--dim);
  white-space: nowrap; letter-spacing: .1px; }
.fi.has-cap.fi-lots .fi-cap { color: var(--ink); }
.fi.has-cap.wanted .fi-cap { color: var(--accent-ink); }
.fi.has-cap.excluded .fi-cap { color: var(--facet-block); text-decoration: line-through; }
.fi.has-cap.excluded::before { top: 14px; }      /* slash crosses the icon, not the word */

/* Standing filter bar — same shape as the target/new bars, in the park-green
   facet identity so the three modes never read as one another. */
.facetbar { display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
  margin: 0 0 12px; padding: 9px 12px; border-radius: var(--r);
  border: 1px solid var(--facet-mode); background: var(--facet-mode-wash); }
.facetbar .tb-mark { color: var(--facet-mode); display: inline-grid; place-content: center; }
.facetbar .tb-mark svg { display: block; }
.facet-chip { display: inline-block; font-size: 0.75rem; color: var(--ink); margin-right: 8px; }
.facet-chip strong { color: var(--facet-mode-ink); font-weight: 700; }

/* Collapsed entry chip in the Ranking header — summarises or invites, opens the
   picker. Sits on its own row under the opportunity filters. */
.facet-entry { display: flex; align-items: center; gap: 10px; width: 100%; text-align: left;
  margin-top: 8px; padding: 8px 12px; border-radius: 999px; cursor: pointer;
  border: 1px dashed var(--facet-mode); background: var(--facet-mode-wash); color: var(--ink); }
.facet-entry:active { transform: translateY(1px); }
.facet-entry-mark { flex: 0 0 auto; color: var(--facet-mode); display: inline-grid; place-content: center; }
.facet-entry-mark svg { display: block; }
.facet-entry-main { flex: 1 1 auto; min-width: 0; font-size: 0.8125rem; overflow: hidden; }
.facet-entry-main.dim { color: var(--dim); }
.facet-entry-go { flex: 0 0 auto; color: var(--facet-mode); font-size: 1.125rem; font-weight: 700; line-height: 1; }

/* The picker dialog — reuses the explainer dialog frame + close button. */
.facet-dialog { position: fixed; inset: 0; margin: auto; width: min(92vw, 480px); max-height: 86vh;
  padding: 0; overflow: hidden; background: var(--card); color: var(--ink);
  border: 1px solid var(--line); border-radius: var(--r); box-shadow: 0 18px 50px rgba(0,0,0,.5); }
.facet-dialog[open] { display: flex; flex-direction: column; }
.facet-dialog::backdrop { background: rgba(4,12,9,.66); backdrop-filter: blur(2px); }
.facet-dialog-head { padding: 18px 20px 8px; border-bottom: 1px solid var(--line); }
.facet-dialog-head h2 { font-family: var(--serif); font-weight: 600; font-size: 1.125rem; margin: 0 30px 4px 0; }
.facet-dialog-head .dim { font-size: 0.75rem; margin: 0; }
.facet-sections { padding: 8px 16px 12px; overflow-y: auto; -webkit-overflow-scrolling: touch; }
.facet-sec { margin: 12px 0; }
.facet-sec h3 { font-size: 0.75rem; text-transform: uppercase; letter-spacing: .4px; color: var(--accent-ink);
  margin: 0 0 8px; }
.facet-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(74px, 1fr)); gap: 8px; }
.facet-dialog-foot { display: flex; gap: 8px; justify-content: flex-end; align-items: center;
  padding: 10px 16px calc(14px + env(safe-area-inset-bottom)); border-top: 1px solid var(--line); }

/* A picker tile — icon + label + state word. Same state language as .fi. */
.facet-pick { position: relative; display: flex; flex-direction: column; align-items: center; gap: 3px;
  min-height: 66px; padding: 8px 4px; border: 1px solid var(--line); border-radius: 12px;
  background: var(--card2); color: var(--dim); cursor: pointer; }
.facet-pick:active { transform: scale(.96); }
.facet-pick .fi-glyph { display: inline-grid; place-content: center; }
.facet-pick .fi-glyph svg { display: block; }
.facet-pick .fi-label { font-size: 0.6875rem; color: var(--ink); text-align: center; line-height: 1.15; }
.facet-pick .fi-state { font-size: 0.6875rem; text-transform: uppercase; letter-spacing: .4px; min-height: 11px; }
.facet-pick.wanted { border-color: var(--facet-want); box-shadow: inset 0 0 0 1px var(--facet-want);
  color: var(--facet-want); background: var(--accent-wash); }
.facet-pick.wanted .fi-state { color: var(--accent-ink); }
.facet-pick.excluded { border-color: var(--facet-block); color: var(--facet-block); opacity: .8; }
.facet-pick.excluded .fi-label { text-decoration: line-through; }
.facet-pick.excluded .fi-state { color: var(--facet-block); }

.sp-facet-chips { display: flex; flex-wrap: wrap; gap: 6px; margin: 2px 0 8px; }

/* A species-facet icon that is itself a tri-state filter button (v28) — the bare
   form sits in the species matrix and the target / seen picker rows; the
   `.labelled` pill form is the species-page chip. Same want/exclude language as
   the guild-row `.fi`, so a facet icon means the same thing everywhere. */
.sp-fi-btn { position: relative; display: inline-grid; place-content: center; padding: 5px;
  min-width: 30px; min-height: 30px; border: 0; background: none; border-radius: 9px;
  color: var(--dim); cursor: pointer; -webkit-tap-highlight-color: transparent; }
.sp-fi-btn svg { display: block; }
.sp-fi-btn .sp-fi { color: inherit; }
.sp-fi-btn:active { transform: scale(.9); }
.sp-fi-btn:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
.sp-fi-btn:not(.labelled).wanted { color: var(--facet-want); box-shadow: inset 0 0 0 2px var(--facet-want); }
/* SUB-11px EXEMPT (A14): this ✓ is a THIRD channel on top of the colour and the
   2px inset ring, both of which already carry "wanted" — it is redundant, not the
   sole non-hue carrier, so its size is not load-bearing. */
.sp-fi-btn:not(.labelled).wanted::before { content: "✓"; position: absolute; top: -1px; right: 1px;
  font-size: 0.625rem; font-weight: 700; color: var(--facet-want); line-height: 1; }
.sp-fi-btn:not(.labelled).excluded { color: var(--facet-block); opacity: .8; }
.sp-fi-btn:not(.labelled).excluded::before { content: ""; position: absolute; left: 6px; right: 6px;
  top: 50%; height: 2px; background: var(--facet-block); transform: rotate(-45deg); }

/* Pill (labelled) form — icon + label, on the species page. */
.sp-fi-btn.labelled { display: inline-flex; align-items: center; gap: 5px; padding: 3px 10px 3px 8px;
  min-width: 0; min-height: 0; border-radius: 999px; font-size: 0.75rem; color: var(--ink);
  background: var(--card2); border: 1px solid var(--line); }
.sp-fi-btn.labelled .sp-fi { color: var(--dim); }
/* Selected (wanted) = strong DARK-INK outline so the kept pills read as chosen.
   Excluded (filtered out) = LIGHT outline so removed pills recede (red struck
   label still marks them). This emphasis used to be reversed. */
.sp-fi-btn.labelled.wanted { border-color: var(--ink); box-shadow: inset 0 0 0 1px var(--ink);
  color: var(--facet-want); background: var(--accent-wash); }
.sp-fi-btn.labelled.wanted .sp-fi { color: var(--facet-want); }
.sp-fi-btn.labelled.excluded { border-color: var(--line); color: var(--facet-block); opacity: .7; }
.sp-fi-btn.labelled.excluded .sp-fi-txt { text-decoration: line-through; }

/* =============================================================================
   PHOTO-FIRST (v24) — the standing camera chip + the weight-table dialog.
   The chip reuses the .facet-entry chassis; the camera wears the app accent
   when photo-first is steering the ranking, and goes quiet when every bird
   counts equally (or the ★ target-presence promise suspends it).
   ========================================================================== */
.photo-entry .facet-entry-mark, .photo-entry .facet-entry-go { color: var(--dim); }
.photo-entry.photo-on .facet-entry-mark, .photo-entry.photo-on .facet-entry-go { color: var(--accent); }
.photo-entry.photo-on .facet-entry-main { color: var(--accent-ink); }

/* === Photo-first: a REAL toggle (not a menu) ============================== */
.photo-row { display: flex; align-items: center; gap: 8px; margin-top: 8px; }
.photo-toggle { flex: 1 1 auto; display: flex; align-items: center; gap: 10px; text-align: left;
  padding: 8px 12px; border-radius: 999px; cursor: pointer; color: var(--ink);
  border: 1px solid var(--line); background: var(--card2); }
.photo-toggle-mark { flex: 0 0 auto; color: var(--dim); display: inline-grid; place-content: center; }
.photo-toggle-mark svg { width: 20px; height: 20px; display: block; }
.photo-toggle-main { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; line-height: 1.15; }
.photo-toggle-main strong { font-size: 0.8125rem; }
.photo-toggle-sub { font-size: 0.6875rem; color: var(--dim); }
.photo-toggle-state { flex: 0 0 auto; font-size: 0.625rem; font-weight: 700; letter-spacing: .6px;
  padding: 3px 9px; border-radius: 999px; background: var(--line); color: var(--dim); }
.photo-toggle:active { transform: scale(.99); }
.photo-toggle.on { border-color: var(--accent); background: var(--accent-wash); }
.photo-toggle.on .photo-toggle-mark { color: var(--accent); }
.photo-toggle.on .photo-toggle-main strong { color: var(--accent-ink); }
.photo-toggle.on .photo-toggle-state { background: var(--accent); color: var(--card); }
.photo-toggle-info { flex: 0 0 auto; width: 30px; height: 30px; border-radius: 50%; cursor: pointer;
  border: 1px solid var(--line); background: var(--card); color: var(--dim); font-size: 0.9375rem;
  display: grid; place-content: center; }

/* === Card presence row — INFO ONLY (not the filter) ======================= */
.presence-row { display: flex; flex-direction: column; align-items: flex-start; gap: 3px; margin: 4px 0 10px; }
.presence-label { font-size: 0.6875rem; text-transform: uppercase; letter-spacing: .5px; color: var(--dim); }
.presence-icons { display: flex; align-items: center; gap: 1px; flex-wrap: nowrap; width: 100%;
  overflow-x: auto; -webkit-overflow-scrolling: touch; }
.pi { position: relative; flex: 0 0 auto; display: inline-grid; place-content: center;
  width: 24px; height: 24px; color: var(--facet-ghost); }
.pi-glyph, .pi-glyph svg { display: block; }
/* Semantic traffic-light scale — colour MEANS the state (green here / amber maybe
   / red not-expected), and value steps dark→light so it survives grayscale for
   colour-blind users. Tokens defined per theme above. */
.pi-lots { color: var(--pi-here); }   /* here — green, darkest */
.pi-some { color: var(--pi-maybe); }  /* maybe — amber, medium */
.pi-none { color: var(--pi-none); }   /* not expected — red, lightest */
.pi-modeled .pi-glyph { opacity: .55; }         /* modeled-only: fainter */

/* === Always-on accordion filter (.ffp) ==================================== */
.ffp { margin-top: 8px; padding: 8px 10px 10px; border-radius: var(--r);
  border: 1px solid var(--facet-mode); background: var(--facet-mode-wash); }
.ffp-cats { display: flex; gap: 5px; }
.ffp-cat { position: relative; flex: 1 1 0; min-width: 0; display: flex; flex-direction: column;
  align-items: center; gap: 2px; padding: 5px 4px 7px; border: 1px solid transparent;
  border-radius: 9px; background: var(--card); color: var(--dim); cursor: pointer; }
.ffp-cat-ico { color: var(--facet-mode); display: inline-grid; place-content: center; }
.ffp-cat-ico svg { width: 18px; height: 18px; display: block; transition: width .1s, height .1s; }
.ffp-cat-label { font-size: 0.6875rem; color: var(--ink); }
.ffp-cat-counts { position: absolute; top: 2px; left: 2px; display: flex; gap: 2px; }
.ffp-cat-count { min-width: 13px; height: 13px; padding: 0 3px;
  border-radius: 7px; color: var(--card); font-size: 0.53125rem; font-weight: 700;
  display: grid; place-content: center; }
/* Colour is the bonus, the SYMBOL is the meaning: badges read "+N" / "−N"
   (colour-blind-safe), on grounds measured ≥4.5:1 under their digits. */
.ffp-cat-count.want { background: var(--count-want); }    /* green +N — must-include */
.ffp-cat-count.block { background: var(--facet-block); }  /* red −N — exclude */
.ffp-cat-chev { position: absolute; right: 4px; bottom: 2px; font-size: 0.5625rem; line-height: 1; color: var(--facet-mode-ink); }
.ffp-cat.open { border-color: var(--facet-mode); background: var(--facet-mode-wash); }
.ffp-cat.open .ffp-cat-ico svg { width: 21px; height: 21px; }
.ffp-vals { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 8px; }
.ffp-help { font-size: 0.6875rem; color: var(--dim); margin: 6px 2px 0; }
.ffp-lights { display: flex; flex-wrap: wrap; align-items: center; gap: 5px; margin-top: 8px;
  padding-top: 8px; border-top: 1px solid color-mix(in srgb, var(--facet-mode) 30%, transparent); }
.ffp-lights-empty { font-size: 0.6875rem; color: var(--dim); }
.ffp-light { position: relative; display: inline-grid; place-content: center; width: 22px; height: 22px; border-radius: 6px; }
.ffp-light-ico svg { width: 15px; height: 15px; display: block; }
.ffp-light.want { color: var(--facet-want); background: var(--accent-wash); box-shadow: inset 0 0 0 1px var(--facet-want); }
.ffp-light.exclude { color: var(--facet-block); background: color-mix(in srgb, var(--facet-block) 12%, transparent); box-shadow: inset 0 0 0 1px var(--facet-block); }
/* Excluded lights carry the slash — the same shape language as the facet
   pills, so want vs exclude never rests on hue alone. */
.ffp-light.exclude::after { content: ""; position: absolute; left: 3px; right: 3px; top: 50%;
  height: 2px; background: var(--facet-block); transform: rotate(-45deg); }
.ffp-clear { margin-left: auto; font-size: 0.6875rem; color: var(--facet-mode-ink); background: none; border: 0;
  cursor: pointer; text-decoration: underline; padding: 2px 4px; }

.pw-rows { display: flex; flex-direction: column; gap: 6px; margin-top: 4px; }
.pw-row { display: flex; align-items: center; gap: 10px; }
.pw-icon { flex: 0 0 auto; color: var(--ink); display: inline-grid; place-content: center; }
.pw-label { flex: 1 1 auto; font-size: 0.8125rem; }
.pw-w { flex: 0 0 auto; font-family: var(--mono); font-variant-numeric: tabular-nums; color: var(--accent-ink); font-weight: 600; }
.photo-dialog .si-note { font-size: 0.8125rem; color: var(--ink); margin: 8px 0; }
.photo-dialog .si-dim { color: var(--dim); }
.pw-toggle { flex: 1 1 auto; display: flex; align-items: center; justify-content: space-between; gap: 10px; font-size: 0.8125rem; }

/* Species panel: the honest one-line weight readout. */
.sp-photo { font-size: 0.75rem; margin: 0 0 8px; font-family: var(--mono); font-variant-numeric: tabular-nums; }

/* Build tag — a tiny version stamp in the corner so every screenshot says which
   build it came from. Decorative (aria-hidden); the version story lives in About. */
/* MEASURED 2026-07-28: at 9.5px with opacity .65 this rendered at 2.54:1 — a real
   AA failure the contrast gate could not see, because the gate checks the TOKEN
   pair (--dim on --bg = 4.79:1) and never composites the opacity. The stamp's whole
   job is to be readable in a screenshot, so it is now 11px at full --dim (4.79:1).
   Photo Pointer hit the identical bug in its 1.14.2 and fixed it the same way.
   Never put opacity back on text — dim it with a token, which the gate can see. */
.ver-tag { position: fixed; top: calc(2px + env(safe-area-inset-top)); right: 8px; z-index: 60; pointer-events: none;
  font-family: var(--mono); font-size: 0.6875rem; color: var(--dim); }

/* Map diagnostics actions (Settings) */
.diag-actions { display: flex; gap: 8px; }
