/* ============================================================================
   SG Transit — app shell styles (WS-F owns this file)

   Design language: Transit 6.0 — "bigger, bolder, rounder".
   https://blog.transitapp.com/six-o/

   The four things that make it read as Transit rather than as a generic card
   layout, and which everything below is in service of:

     1. THE ETA IS THE INTERFACE. Transit 6.0 took the departure time from 18pt
        to 60pt and pushed everything else into the card around it. `.eta-card`
        does the same: `--eta-size` is the largest type in the app by a factor
        of three, and nothing is allowed to compete with it.
     2. ROUNDED EVERYTHING. Puffin (Transit's commissioned typeface) is not
        redistributable and this repo has no build step and ships no external
        assets, so `--font` reaches for the rounded faces that ship with the OS
        — ui-rounded on Apple, Segoe UI Variable on Windows — and falls back to
        system-ui. The roundness that we CAN control is geometry: --radius runs
        18-30px rather than the 12px this file used before.
     3. ROUTE COLOUR IS THE INDEX. Transit picks up local agency colours so a
        route is recognisable before it is read. `--line-*` carries the six
        official SMRT/SBST line colours; buses get a deterministic hue from
        their service number (see routeHue() in tabs/arrivals.js) fed in as
        `--route-h`, so service 15 is the same colour on every screen.
     4. NEON DARK. Over half of Transit's use is after dark. The dark palette
        here is a true midnight (#05070d) with saturated foregrounds, not the
        washed grey that a naive inversion produces.

   Theming (three mechanisms, explicit attribute always wins):
     :root                              -> light tokens (also the no-JS default)
     :root[data-theme="dark"]           -> forced dark, in any OS setting
     @media (prefers-color-scheme: dark)
        :root:not([data-theme="light"]) -> dark for `system` and for no-JS

   WHAT THE SIZE INCREASE DOES NOT CHANGE: a 60pt number carries more authority
   than an 18pt one, so CLAUDE.md rule 3 gets *stricter* here, not looser. An
   `.eta-card[data-live="false"]` — an ETA that is timetable arithmetic rather
   than a tracked vehicle — is drawn with a dashed edge, a muted number and a
   permanent amber footer. It must never be possible to mistake one for the
   other at a glance, which is the only distance a 60pt number is read from.

   Rule 6 likewise: every crowd indicator carries colour AND a glyph AND a word.

   Tab modules (WS-H/WS-I) may reuse everything under "shared components".
   Both naming conventions are supported — `.card__title` and `.card-title`,
   `.btn--primary` and `.btn-primary` — because the tabs were written against
   both and unstyled markup is a worse outcome than a slightly wide selector.
   ========================================================================= */

/* ------------------------------------------------------------- tokens ---- */

:root {
  color-scheme: light;

  /* Rounded, friendly, high-x-height — the closest we can get to Puffin from
     fonts already on the device. No webfont: no build step, no external asset
     fetch, nothing for a CDN to log. */
  --font: ui-rounded, "SF Pro Rounded", -apple-system, "Segoe UI Variable Display",
          "Segoe UI", system-ui, Roboto, "Helvetica Neue", Arial, sans-serif;

  --bg: #eef1f5;
  --surface: #ffffff;
  --surface-2: #e6eaf0;
  --surface-3: #dbe1ea;
  --border: #d3dae3;
  --border-strong: #b6c0cd;
  --text: #0d1117;
  --text-muted: #56616f;
  --accent: #0b7d6e;
  --accent-weak: #d3f2ec;
  --accent-fg: #ffffff;
  --focus: #0b6bcb;
  --danger: #b3261e;
  --danger-weak: #fbe6e4;
  --warn: #b45309;
  --warn-weak: #fdf0d5;

  /* Rounder and softer than a material shadow — the card should look like it
     was placed on the surface, not punched into it. */
  --shadow: 0 1px 2px rgba(16, 24, 40, .06), 0 4px 14px rgba(16, 24, 40, .07);
  --shadow-lg: 0 2px 6px rgba(16, 24, 40, .07), 0 12px 32px rgba(16, 24, 40, .12);

  /* Crowding — green / amber / red, plus readable fg + tinted bg per band. */
  --crowd-comfortable: #17803d;
  --crowd-comfortable-bg: #dcf5e4;
  --crowd-comfortable-fg: #0d5527;
  --crowd-moderate: #b45309;
  --crowd-moderate-bg: #fdefd3;
  --crowd-moderate-fg: #7c3a06;
  --crowd-packed: #c02626;
  --crowd-packed-bg: #fce0e0;
  --crowd-packed-fg: #8c1a1a;
  --crowd-unknown: #6b7280;
  --crowd-unknown-bg: #e5e9ef;
  --crowd-unknown-fg: #414a57;

  /* Official Singapore rail line colours. `-fg` is the text that sits on them:
     CCL's amber and BPLRT's grey-green fail against white, so they carry ink. */
  --line-nsl: #d42e12;  --line-nsl-fg: #ffffff;   /* North-South       */
  --line-ewl: #009645;  --line-ewl-fg: #ffffff;   /* East-West         */
  --line-nel: #9900aa;  --line-nel-fg: #ffffff;   /* North East        */
  --line-ccl: #fa9e0d;  --line-ccl-fg: #2c1a00;   /* Circle            */
  --line-dtl: #005ec4;  --line-dtl-fg: #ffffff;   /* Downtown          */
  --line-tel: #9d5b25;  --line-tel-fg: #ffffff;   /* Thomson-East Coast*/
  --line-lrt: #748477;  --line-lrt-fg: #ffffff;   /* LRT               */

  /* Bus badges. The hue arrives per-element as --route-h; saturation and
     lightness are fixed here so every badge has the same weight and contrast
     regardless of which hue the service number hashed to. */
  --route-s: 58%;
  --route-l: 34%;
  --route-fg: #ffffff;

  --tap: 44px;
  --radius-sm: 12px;
  --radius: 18px;
  --radius-lg: 24px;
  --radius-xl: 30px;
  --pill: 999px;
  --gap: 12px;
  --maxw: 34rem;

  /* Transit 6.0 dialled the ETA from 18pt to 60pt. This is that number, minus
     enough at the small end that a three-character ETA ("60+") still fits a
     320px viewport. */
  --eta-size: clamp(2.9rem, 15vw, 3.9rem);

  --tabbar-h: 62px;
}

/* Neon dark: a true midnight rather than a lifted grey, with foregrounds
   saturated enough to hold their own against it. */
:root[data-theme="dark"] {
  color-scheme: dark;

  --bg: #05070d;
  --surface: #111725;
  --surface-2: #1a2232;
  --surface-3: #243043;
  --border: #2a3549;
  --border-strong: #3d4b63;
  --text: #f2f6ff;
  --text-muted: #9dabc2;
  --accent: #2ff2c8;
  --accent-weak: #06322c;
  --accent-fg: #002b25;
  --focus: #7cc0ff;
  --danger: #ff8f86;
  --danger-weak: #3a1618;
  --warn: #ffc247;
  --warn-weak: #3a2b0f;

  --shadow: 0 1px 2px rgba(0, 0, 0, .6), 0 6px 18px rgba(0, 0, 0, .45);
  --shadow-lg: 0 2px 8px rgba(0, 0, 0, .6), 0 16px 40px rgba(0, 0, 0, .6);

  --crowd-comfortable: #34e57f;
  --crowd-comfortable-bg: #06301c;
  --crowd-comfortable-fg: #9ff5c1;
  --crowd-moderate: #ffc247;
  --crowd-moderate-bg: #362708;
  --crowd-moderate-fg: #ffdf9d;
  --crowd-packed: #ff6b60;
  --crowd-packed-bg: #3a1416;
  --crowd-packed-fg: #ffb9b3;
  --crowd-unknown: #8b98ad;
  --crowd-unknown-bg: #1e2736;
  --crowd-unknown-fg: #c6d0e0;

  /* Neon: the same hues, pushed brighter so they read as lit rather than
     printed. Line identity survives; the ink flips to dark. */
  --line-nsl: #ff5a3c;  --line-nsl-fg: #2b0700;
  --line-ewl: #22d97a;  --line-ewl-fg: #002913;
  --line-nel: #d95cf0;  --line-nel-fg: #2e0036;
  --line-ccl: #ffb52e;  --line-ccl-fg: #2c1a00;
  --line-dtl: #4b9dff;  --line-dtl-fg: #001c3d;
  --line-tel: #d18d55;  --line-tel-fg: #2a1400;
  --line-lrt: #a3b6a6;  --line-lrt-fg: #10190f;

  --route-s: 72%;
  --route-l: 62%;
  --route-fg: #05070d;
}

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    color-scheme: dark;

    --bg: #05070d;
    --surface: #111725;
    --surface-2: #1a2232;
    --surface-3: #243043;
    --border: #2a3549;
    --border-strong: #3d4b63;
    --text: #f2f6ff;
    --text-muted: #9dabc2;
    --accent: #2ff2c8;
    --accent-weak: #06322c;
    --accent-fg: #002b25;
    --focus: #7cc0ff;
    --danger: #ff8f86;
    --danger-weak: #3a1618;
    --warn: #ffc247;
    --warn-weak: #3a2b0f;

    --shadow: 0 1px 2px rgba(0, 0, 0, .6), 0 6px 18px rgba(0, 0, 0, .45);
    --shadow-lg: 0 2px 8px rgba(0, 0, 0, .6), 0 16px 40px rgba(0, 0, 0, .6);

    --crowd-comfortable: #34e57f;
    --crowd-comfortable-bg: #06301c;
    --crowd-comfortable-fg: #9ff5c1;
    --crowd-moderate: #ffc247;
    --crowd-moderate-bg: #362708;
    --crowd-moderate-fg: #ffdf9d;
    --crowd-packed: #ff6b60;
    --crowd-packed-bg: #3a1416;
    --crowd-packed-fg: #ffb9b3;
    --crowd-unknown: #8b98ad;
    --crowd-unknown-bg: #1e2736;
    --crowd-unknown-fg: #c6d0e0;

    --line-nsl: #ff5a3c;  --line-nsl-fg: #2b0700;
    --line-ewl: #22d97a;  --line-ewl-fg: #002913;
    --line-nel: #d95cf0;  --line-nel-fg: #2e0036;
    --line-ccl: #ffb52e;  --line-ccl-fg: #2c1a00;
    --line-dtl: #4b9dff;  --line-dtl-fg: #001c3d;
    --line-tel: #d18d55;  --line-tel-fg: #2a1400;
    --line-lrt: #a3b6a6;  --line-lrt-fg: #10190f;

    --route-s: 72%;
    --route-l: 62%;
    --route-fg: #05070d;
  }
}

/* --------------------------------------------------------------- base ---- */

*, *::before, *::after { box-sizing: border-box; }

html, body { height: 100%; }

body {
  margin: 0;
  background: var(--bg);
  color: var(--text);
  font: 16px/1.4 var(--font);
  font-weight: 500;
  -webkit-text-size-adjust: 100%;
  overflow-wrap: break-word;
  letter-spacing: -.005em;
}

/* Puffin's whole point is weight and roundness. Headings lean hard on both. */
h1, h2, h3, h4 { margin: 0; line-height: 1.18; font-weight: 800; letter-spacing: -.02em; }
p { margin: 0 0 .6em; }
p:last-child { margin-bottom: 0; }
ul, ol { margin: 0; padding: 0; list-style: none; }
a { color: var(--accent); font-weight: 650; }

:focus-visible {
  outline: 3px solid var(--focus);
  outline-offset: 2px;
  border-radius: 8px;
}

.sr-only {
  position: absolute; width: 1px; height: 1px; margin: -1px;
  padding: 0; overflow: hidden; clip-path: inset(50%); white-space: nowrap;
}

.skip-link {
  position: absolute; left: -9999px; top: 0; z-index: 60;
  padding: 12px 18px; background: var(--surface); color: var(--text);
  border: 2px solid var(--focus); border-radius: 0 0 var(--radius) 0;
  font-weight: 750; text-decoration: none;
}
.skip-link:focus { left: 0; }

.noscript {
  margin: var(--gap); padding: 14px; border-radius: var(--radius);
  background: var(--warn-weak); border: 1px solid var(--border);
}

/* ------------------------------------------------------------- header ---- */

.app-header {
  position: sticky; top: 0; z-index: 20;
  display: flex; align-items: center; gap: 8px;
  padding: max(8px, env(safe-area-inset-top)) 12px 8px;
  padding-left: max(14px, env(safe-area-inset-left));
  padding-right: max(14px, env(safe-area-inset-right));
  /* Opaque first: a browser without color-mix() drops the second declaration
     and keeps this one, rather than rendering a transparent header with the
     page scrolling through it. Same pattern at .tabbar and .eta-card below. */
  background: var(--bg);
  background: color-mix(in srgb, var(--bg) 86%, transparent);
  -webkit-backdrop-filter: saturate(180%) blur(14px);
  backdrop-filter: saturate(180%) blur(14px);
  border-bottom: 1px solid transparent;
}
/* Only draw the rule once there is something scrolled under it. */
@supports (animation-timeline: scroll()) {
  .app-header {
    animation: header-edge linear both;
    animation-timeline: scroll();
    animation-range: 0 40px;
  }
  @keyframes header-edge { to { border-bottom-color: var(--border); } }
}

.app-title {
  flex: 1 1 auto; min-width: 0;
  display: flex; align-items: center; gap: 8px;
  font-size: 1.3rem; font-weight: 800; letter-spacing: -.03em;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.app-title__logo { font-size: 1.35rem; }

.icon-btn {
  flex: 0 0 auto;
  min-width: var(--tap); min-height: var(--tap);
  display: inline-flex; align-items: center; justify-content: center;
  font-size: 1.25rem; line-height: 1;
  background: var(--surface); color: inherit;
  border: 1px solid var(--border); border-radius: var(--pill);
  cursor: pointer;
  transition: background .14s ease, transform .14s ease;
}
.icon-btn:hover { background: var(--surface-2); }
.icon-btn:active { transform: scale(.94); }
.icon-btn[aria-expanded="true"] { background: var(--surface-2); border-color: var(--border-strong); }

/* ------------------------------------------------------------ tab bar ----
   Transit keeps its controls in the bottom third of the screen, where a thumb
   reaches without regripping the phone. The bar floats above the content as a
   rounded slab rather than sitting flush, so a scrolling list visibly runs
   underneath it. */

.tabbar {
  position: fixed; z-index: 30;
  left: 50%; transform: translateX(-50%);
  bottom: max(10px, env(safe-area-inset-bottom));
  width: min(calc(100% - 20px), var(--maxw));
  display: grid; grid-auto-flow: column; grid-auto-columns: 1fr;
  gap: 2px;
  padding: 6px;
  background: var(--surface);
  background: color-mix(in srgb, var(--surface) 92%, transparent);
  -webkit-backdrop-filter: saturate(180%) blur(18px);
  backdrop-filter: saturate(180%) blur(18px);
  border: 1px solid var(--border);
  border-radius: var(--radius-xl);
  box-shadow: var(--shadow-lg);
}

.tabbar__tab {
  min-height: 50px;
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 2px; padding: 5px 2px;
  background: transparent; color: var(--text-muted);
  border: 0; border-radius: var(--radius);
  font: inherit; font-size: .68rem; font-weight: 750; letter-spacing: -.01em;
  cursor: pointer;
  transition: color .14s ease, background .14s ease, transform .14s ease;
}
.tabbar__icon { font-size: 1.2rem; line-height: 1.1; }
.tabbar__tab:hover { color: var(--text); }
.tabbar__tab:active { transform: scale(.93); }
.tabbar__tab[aria-selected="true"] {
  color: var(--accent-fg);
  background: var(--accent);
}
.tabbar__tab:focus-visible { outline-offset: -2px; }

/* --------------------------------------------------------------- main ---- */

.app-main {
  width: 100%; max-width: var(--maxw); margin: 0 auto;
  padding: var(--gap) max(var(--gap), env(safe-area-inset-left))
           calc(var(--tabbar-h) + 40px)
           max(var(--gap), env(safe-area-inset-right));
}

.tab-panel { display: block; }
.tab-panel:focus { outline: none; }
.tab-panel:focus-visible { outline: 3px solid var(--focus); outline-offset: 4px; }

.boot { color: var(--text-muted); padding: 24px 4px; }

/* ================================================== ETA CARDS (headline) ==
   One card per service, hero ETA at --eta-size, everything else arranged
   around it. `data-live` is the honesty switch and is never cosmetic:

     data-live="true"   tracked vehicle    — solid edge, full-strength number
     data-live="false"  timetable estimate — dashed edge, muted number, and a
                        permanent amber footer that cannot be dismissed

   The dashed edge is deliberately the loudest difference between the two
   states, because it survives at the distance a 60pt number is actually read
   from, which a caption underneath does not. */

.eta-list { display: flex; flex-direction: column; gap: 10px; }

.eta-card {
  position: relative;
  display: flex; flex-direction: column; gap: 10px;
  padding: 14px 16px;
  background: var(--surface);
  border: 1.5px solid var(--border);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow);
  overflow: hidden;
}

/* A hairline of route colour down the leading edge, so a scrolled list is
   scannable by colour before any text is read. */
.eta-card::before {
  content: "";
  position: absolute; inset: 0 auto 0 0; width: 5px;
  background: var(--route, var(--border-strong));
}

.eta-card__head {
  display: flex; align-items: center; gap: 9px;
  min-width: 0;
}
.eta-card__dest {
  flex: 1 1 auto; min-width: 0;
  font-size: .95rem; font-weight: 700; color: var(--text);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.eta-card__feat {
  flex: 0 0 auto;
  font-size: 1rem;
  color: var(--text-muted);
}

.eta-card__body {
  display: flex; align-items: center; gap: 14px;
  min-width: 0;
}

.eta-card__eta {
  flex: 0 0 auto;
  display: flex; align-items: baseline; gap: 5px;
  margin: 0;
  min-width: 0;
}
.eta-card__num {
  font-size: var(--eta-size);
  font-weight: 800;
  line-height: .9;
  letter-spacing: -.045em;
  font-variant-numeric: tabular-nums;
  color: var(--text);
}
.eta-card__unit {
  font-size: 1rem; font-weight: 750;
  color: var(--text-muted);
  letter-spacing: -.01em;
}
/* "Arriving" / "No estimate" are words, not numbers — they must wrap and must
   not be set at 60pt or they overflow every phone in existence. */
.eta-card__num--word {
  font-size: 1.5rem;
  line-height: 1.15;
  letter-spacing: -.02em;
  white-space: normal;
}

.eta-card__side {
  flex: 1 1 auto; min-width: 0;
  display: flex; flex-direction: column; align-items: flex-end; gap: 6px;
  text-align: right;
}
.eta-card__then {
  margin: 0;
  font-size: .84rem; font-weight: 650;
  color: var(--text-muted);
  font-variant-numeric: tabular-nums;
}

/* --- the not-live treatment ------------------------------------------- */

.eta-card[data-live="false"] {
  border-style: dashed;
  border-color: var(--warn);
  background: var(--warn-weak);
  background: color-mix(in srgb, var(--warn-weak) 34%, var(--surface));
}
.eta-card[data-live="false"]::before { background: var(--warn); }
.eta-card[data-live="false"] .eta-card__num { color: var(--crowd-moderate-fg); }

.eta-card__flag {
  display: flex; align-items: center; gap: 6px;
  margin: 0;
  padding: 7px 10px;
  border-radius: var(--radius-sm);
  background: var(--warn-weak);
  color: var(--crowd-moderate-fg);
  font-size: .78rem; font-weight: 750;
  letter-spacing: -.005em;
}
.eta-card__flag::before { content: "\23F1"; }          /* ⏱ */

/* Board-level provenance: one line describing the whole list. Never hidden,
   because it is the sentence that says whether "live" applies at all. */
.eta-caption {
  margin: 0 2px 2px;
  font-size: .82rem; font-weight: 650;
  color: var(--text-muted);
}

/* --------------------------------------------------------- route badge ---
   The recognisable unit. Rail lines take their official colour from a token;
   buses get --route-h from a hash of the service number so the same service is
   always the same colour, in both themes. */

.route-badge {
  flex: 0 0 auto;
  display: inline-flex; align-items: center; justify-content: center;
  min-width: 2.9rem; height: 2.05rem;
  padding: 0 .6rem;
  border-radius: var(--radius-sm);
  background: var(--route, var(--surface-3));
  color: var(--route-ink, var(--text));
  font-size: 1.05rem; font-weight: 800; letter-spacing: -.02em;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}
.route-badge--sm { min-width: 2.3rem; height: 1.65rem; font-size: .85rem; padding: 0 .45rem; }

/* A badge that expands its card into a per-bus breakdown is a real <button>,
   because a <span> with a click handler is unreachable by keyboard and is
   announced as nothing. It must still read as a badge, so the button chrome is
   stripped rather than the button element avoided. The expanded state gets a
   ring, but `aria-expanded` — not the ring — is what carries the meaning. */
button.route-badge {
  appearance: none; -webkit-appearance: none;
  border: 0;
  font-family: inherit;
  cursor: pointer;
}
button.route-badge:active { transform: scale(.94); }
button.route-badge[aria-expanded="true"] {
  box-shadow: 0 0 0 2px var(--surface), 0 0 0 4px var(--route, var(--border-strong));
}

/* Bus: hue in, full colour out. */
.route-badge[data-mode="bus"] {
  --route: hsl(var(--route-h, 200) var(--route-s) var(--route-l));
  --route-ink: var(--route-fg);
}

/* Rail: one rule per line, both on the badge and on any ancestor that wants
   to tint itself (the ETA card's leading edge reads --route from here). */
[data-line="NSL"] { --route: var(--line-nsl); --route-ink: var(--line-nsl-fg); }
[data-line="EWL"] { --route: var(--line-ewl); --route-ink: var(--line-ewl-fg); }
[data-line="NEL"] { --route: var(--line-nel); --route-ink: var(--line-nel-fg); }
[data-line="CCL"] { --route: var(--line-ccl); --route-ink: var(--line-ccl-fg); }
[data-line="DTL"] { --route: var(--line-dtl); --route-ink: var(--line-dtl-fg); }
[data-line="TEL"] { --route: var(--line-tel); --route-ink: var(--line-tel-fg); }
[data-line="LRT"] { --route: var(--line-lrt); --route-ink: var(--line-lrt-fg); }

/* ------------------------------------------------------------ map tab ---- */

.map-tab {
  display: flex;
  flex-direction: column;
  gap: 10px;
  min-height: max(22rem, calc(100dvh - 12rem));
}
.map-tab.empty, .map-tab.error-state {
  justify-content: center;
  min-height: min(28rem, calc(100dvh - 12rem));
}
.map-tab__head {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  justify-content: space-between;
  gap: 4px 12px;
}
.map-tab__head h2 { font-size: 1.15rem; }
.map-tab__status { margin: 0; }

/* A coarse location fix is a caveat, not an error — amber, and never colour
   alone: the warning text itself carries the meaning (see CLAUDE.md rule 6). */
.map-tab__status--warn,
.stop-picker__status--warn {
  color: var(--crowd-moderate-fg);
  background: var(--warn-weak);
  border-radius: var(--radius-sm);
  padding: 0.4rem 0.6rem;
  font-weight: 650;
}
.map-tab__canvas {
  flex: 1 1 auto;
  width: 100%;
  min-height: 22rem;
  /* Leaflet sets this itself once it initialises, but the basemap badge is a
     child from the moment the shell is built and must be positioned against
     the canvas even before then. */
  position: relative;
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  background: var(--surface-2);
  box-shadow: var(--shadow);
  overflow: hidden;
  isolation: isolate;
}

/* "No street map", stated on the canvas it describes.
   Below the map it was under the fold on a phone, so an empty background read
   as a map that had failed to load. Leaflet's own controls sit at z-index 400
   through 800; this clears them. The badge itself is transparent to pointer
   events so it never eats a drag — only the button takes clicks. */
.map-tab__basemap-badge {
  position: absolute;
  left: 10px; bottom: 10px;
  z-index: 900;
  display: flex; align-items: center; gap: 8px;
  max-width: calc(100% - 20px);
  padding: 6px 8px 6px 12px;
  border: 1px solid var(--border);
  border-radius: var(--pill);
  background: var(--surface);
  box-shadow: var(--shadow);
  pointer-events: none;
}
.map-tab__basemap-badge[hidden] { display: none; }
.map-tab__basemap-text {
  font-size: .8rem; font-weight: 700; color: var(--text-muted);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.map-tab__basemap-btn {
  pointer-events: auto;
  flex: 0 0 auto;
  min-height: 32px;
  padding: 4px 12px;
  border: 1px solid var(--accent); border-radius: var(--pill);
  background: var(--accent-weak); color: var(--accent);
  font: inherit; font-size: .8rem; font-weight: 750;
  cursor: pointer;
}
.map-tab__basemap-btn:active { transform: scale(.96); }
/* Map search. The results panel overlays the canvas rather than pushing it
   down, so choosing a result does not make the map jump under the finger. */
.map-search {
  position: relative;
  z-index: 2;
}
.map-search__row {
  display: flex;
  gap: 8px;
}
.map-search__input {
  flex: 1 1 auto;
  min-width: 0;
  padding: 0.55rem 0.7rem;
  font: inherit;
  color: var(--text);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
}
.map-search__input:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 1px;
}
.map-search__go { flex: 0 0 auto; }

.map-search__results {
  position: absolute;
  inset-inline: 0;
  top: calc(100% + 6px);
  z-index: 3;
  max-height: min(22rem, 50dvh);
  overflow-y: auto;
  display: flex;
  flex-direction: column;
  padding: 4px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
}
.map-search__results[hidden] { display: none; }

.map-search__group {
  margin: 6px 6px 2px;
  font-size: 0.78rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--text-muted);
}
.map-search__result {
  display: flex;
  flex-direction: column;
  gap: 2px;
  width: 100%;
  padding: 0.5rem 0.6rem;
  text-align: start;
  font: inherit;
  color: var(--text);
  background: none;
  border: 0;
  border-radius: var(--radius-sm);
  cursor: pointer;
}
.map-search__result:hover { background: var(--surface-2); }
.map-search__result:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: -2px;
}
.map-search__result-title { font-weight: 600; }
.map-search__result-detail {
  font-size: 0.85rem;
  color: var(--text-muted);
}
.map-search__note {
  margin: 6px;
  font-size: 0.85rem;
  color: var(--text-muted);
}
.map-search__note--warn {
  color: var(--crowd-moderate-fg);
  background: var(--warn-weak);
  border-radius: var(--radius-sm);
  padding: 0.4rem 0.6rem;
  font-weight: 650;
}

.map-tab__recentre { align-self: flex-start; }
.map-tab__recentre[hidden] { display: none; }
/* Shown only while we have no fix: keeps the permission ask on screen without
   taking the search bar away from someone who would rather type a place. */
.map-tab__locate {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 8px 12px;
  padding: 0.6rem 0.75rem;
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: var(--radius);
}
.map-tab__locate[hidden] { display: none; }
.map-tab__locate-text {
  margin: 0;
  flex: 1 1 14rem;
  font-size: 0.9rem;
  color: var(--text-muted);
}


.map-popup {
  display: flex;
  min-width: 12rem;
  flex-direction: column;
  gap: 3px;
  color: var(--text);
}
.map-popup__title { margin-bottom: 2px; }
.map-popup__button { margin-top: 8px; }
/* Shared Leaflet theme: used by both the Map tab and the reusable stop picker. */
:is(.map-tab, .stop-picker) .leaflet-container { background: var(--surface-2); font: inherit; }
:is(.map-tab, .stop-picker) .leaflet-control-zoom a,
:is(.map-tab, .stop-picker) .leaflet-control-attribution,
:is(.map-tab, .stop-picker) .leaflet-popup-content-wrapper,
:is(.map-tab, .stop-picker) .leaflet-popup-tip {
  background: var(--surface);
  color: var(--text);
}
:is(.map-tab, .stop-picker) .leaflet-popup-content-wrapper { border-radius: var(--radius); }
:is(.map-tab, .stop-picker) .leaflet-control-zoom a { border-color: var(--border); }
:is(.map-tab, .stop-picker) .leaflet-control-zoom a:hover { background: var(--surface-2); }
:is(.map-tab, .stop-picker) .leaflet-control-attribution a,
:is(.map-tab, .stop-picker) .leaflet-popup-content a { color: var(--accent); }
:is(.map-tab, .stop-picker) .leaflet-popup-close-button { color: var(--text-muted); }

/* Neon dark pushes the tiles further down than the old dark mode did — a paper
   map at full brightness is the brightest thing on a midnight screen. */
:root[data-theme="dark"] :is(.map-tab, .stop-picker) .leaflet-tile-pane {
  filter: brightness(.62) contrast(1.2) saturate(.6) hue-rotate(180deg) invert(.92);
}

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) :is(.map-tab, .stop-picker) .leaflet-tile-pane {
    filter: brightness(.62) contrast(1.2) saturate(.6) hue-rotate(180deg) invert(.92);
  }
}

/* -------------------------------------------------- shared stop picker ---- */

.stop-picker {
  position: fixed;
  inset: 0;
  z-index: 10000;
  display: grid;
  grid-template-rows: auto auto minmax(0, 1fr) auto;
  gap: 10px;
  padding: max(12px, env(safe-area-inset-top))
           max(12px, env(safe-area-inset-right))
           max(12px, env(safe-area-inset-bottom))
           max(12px, env(safe-area-inset-left));
  background: var(--bg);
  color: var(--text);
}
.stop-picker__header,
.stop-picker__tools {
  display: flex;
  align-items: center;
  gap: 8px;
}
.stop-picker__header { justify-content: space-between; }
/* Title and the location-privacy line, stacked in the header's first slot so
   the row stays two children wide and the grid keeps its four rows. */
.stop-picker__headings { min-width: 0; display: flex; flex-direction: column; gap: 2px; }
.stop-picker__privacy { margin: 0; color: var(--text-muted); }
.stop-picker__title { margin: 0; font-size: 1.25rem; }
.stop-picker__close {
  flex: 0 0 var(--tap);
  width: var(--tap);
  height: var(--tap);
  border: 1px solid var(--border);
  border-radius: 50%;
  background: var(--surface);
  color: var(--text);
  font: inherit;
  font-size: 1.2rem;
  cursor: pointer;
}
.stop-picker__tools { position: relative; }
/* Reuses the Map tab's result-row styling; only the anchoring differs. */
.stop-picker__places {
  position: absolute;
  inset-inline: 0;
  top: calc(100% + 6px);
  z-index: 4;
}
.stop-picker__search { flex: 1 1 auto; min-width: 0; }
.stop-picker__location { flex: 0 0 auto; white-space: nowrap; }
.stop-picker__map {
  /* Leaflet sets this once it initialises; the basemap badge is a child from
     the moment the overlay is built and needs the containing block earlier. */
  position: relative;
  min-height: 15rem;
  overflow: hidden;
  isolation: isolate;
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  background: var(--surface-2);
  box-shadow: var(--shadow);
}
/* Label only — see the comment in stoppicker.js for why this one has no
   button. Transparent to pointer events throughout, so it cannot eat a drag
   on a map whose whole purpose is to be panned. */
.stop-picker__basemap-badge {
  position: absolute;
  left: 8px; bottom: 8px;
  z-index: 900;
  max-width: calc(100% - 16px);
  padding: 5px 11px;
  border: 1px solid var(--border);
  border-radius: var(--pill);
  background: var(--surface);
  box-shadow: var(--shadow);
  font-size: .76rem; font-weight: 700; color: var(--text-muted);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  pointer-events: none;
}
.stop-picker__basemap-badge[hidden] { display: none; }
.stop-picker__map-error {
  display: grid;
  place-items: center;
  height: 100%;
  min-height: 15rem;
  padding: 1rem;
  color: var(--text-muted);
  text-align: center;
}
.stop-picker__status { min-height: 1.25rem; margin: 0; color: var(--text-muted); }
.stop-picker__popup { min-width: 13rem; }

@media (max-width: 28rem) {
  .stop-picker__tools { align-items: stretch; flex-direction: column; }
  .stop-picker__location { width: 100%; }
}

/* ------------------------------------------------------------ banners ---- */

.banners { display: flex; flex-direction: column; gap: 8px; }
.banners:not(:empty) { margin-bottom: var(--gap); }

.banner {
  display: flex; flex-wrap: wrap; align-items: center; gap: 8px;
  padding: 12px 14px;
  background: var(--surface); color: var(--text);
  border: 1px solid var(--border); border-left: 5px solid var(--accent);
  border-radius: var(--radius);
  font-size: .92rem; font-weight: 600;
  box-shadow: var(--shadow);
}
.banner[data-kind="warn"] { border-left-color: var(--crowd-moderate); background: var(--warn-weak); }
.banner[data-kind="error"] { border-left-color: var(--danger); background: var(--danger-weak); }
.banner__text { flex: 1 1 12rem; min-width: 0; }
.banner__actions { display: flex; gap: 6px; flex-wrap: wrap; }

.progress {
  flex: 1 1 100%;
  height: 7px; border-radius: var(--pill);
  background: var(--surface-2); overflow: hidden;
}
.progress__fill {
  height: 100%; width: 0%;
  background: var(--accent);
  border-radius: var(--pill);
  transition: width .25s ease;
}
.progress[data-indeterminate="true"] .progress__fill {
  width: 35%;
  animation: slide 1.1s ease-in-out infinite alternate;
}
@keyframes slide { from { transform: translateX(-100%); } to { transform: translateX(300%); } }

/* ------------------------------------------------------------- toasts ----
   Lifted clear of the floating tab bar so it never covers the nav. */

.toasts {
  position: fixed; z-index: 50;
  left: 50%; transform: translateX(-50%);
  bottom: calc(var(--tabbar-h) + max(18px, env(safe-area-inset-bottom)));
  width: min(calc(100% - 24px), var(--maxw));
  display: flex; flex-direction: column; gap: 8px;
  pointer-events: none;
}

.toast {
  pointer-events: auto;
  padding: 12px 16px;
  border-radius: var(--radius);
  background: var(--surface); color: var(--text);
  border: 1px solid var(--border); border-left: 5px solid var(--accent);
  box-shadow: var(--shadow-lg);
  font-size: .93rem; font-weight: 650;
  animation: rise .18s ease-out;
}
.toast[data-kind="success"] { border-left-color: var(--crowd-comfortable); }
.toast[data-kind="warn"] { border-left-color: var(--crowd-moderate); }
.toast[data-kind="error"] { border-left-color: var(--danger); }
@keyframes rise { from { opacity: 0; transform: translateY(8px); } to { opacity: 1; transform: none; } }

/* --------------------------------------------------- shared components ----
   Both naming conventions are live. The tabs were written against a mix of
   BEM (`card__title`) and single-dash (`card-title`) names; supporting both
   costs a few selectors and removes a whole class of "why is this unstyled"
   bug. New code should prefer the BEM spelling. */

.card {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  padding: 16px;
  box-shadow: var(--shadow);
}
.card + .card { margin-top: var(--gap); }
.card__title, .card-title { font-size: 1.1rem; margin-bottom: 10px; }

.stack { display: flex; flex-direction: column; gap: var(--gap); }
.row { display: flex; align-items: center; gap: 10px; }
.row--between { justify-content: space-between; }
.row--wrap { flex-wrap: wrap; }
.spacer { flex: 1 1 auto; }

.section-title {
  font-size: .74rem; font-weight: 800; letter-spacing: .08em;
  text-transform: uppercase; color: var(--text-muted);
  margin: 20px 2px 8px;
}

.list > li {
  display: flex; align-items: center; gap: 10px;
  min-height: var(--tap);
  padding: 9px 0;
  border-bottom: 1px solid var(--border);
}
.list > li:last-child { border-bottom: 0; }

/* ---------------------------------------------------------- .disclosure --
   The shared home for a caveat that is true, load-bearing, and too long to
   sit beside the number it qualifies.

   CLAUDE.md rule 3 requires the qualifier to be visible, not that the whole
   derivation is. The pattern the app now uses everywhere is: a short, always
   visible label on the figure itself ("Not a tracked train — published
   headway"), and the method behind it one tap away in here. crowd.js already
   made this argument for its own station list — "unread caveats do not
   discharge rule 3" — and thirty restated paragraphs are precisely what does
   not get read. This component is that argument applied app-wide.

   It is a <details>, so the text is in the DOM and reachable by find-in-page
   and by a screen reader whether or not it is expanded. Nothing is hidden
   from anyone; it is folded. */

.disclosure {
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--surface);
  padding: 0 14px;
  font-size: .86rem;
}
.disclosure + .disclosure { margin-top: 8px; }
.disclosure[open] { padding-bottom: 12px; }
.disclosure > summary {
  min-height: var(--tap);
  display: flex; align-items: center; gap: 8px;
  cursor: pointer;
  font-weight: 750; font-size: .88rem;
  color: var(--text-muted);
  list-style: none;
}
.disclosure > summary::-webkit-details-marker { display: none; }
.disclosure > summary::before {
  content: "\25B8";                 /* ▸ */
  font-size: .8em;
  transition: transform .14s ease;
}
.disclosure[open] > summary::before { transform: rotate(90deg); }
.disclosure > summary:hover { color: var(--text); }
.disclosure > :not(summary) { margin: 0 0 8px; color: var(--text-muted); line-height: 1.5; }
.disclosure > :last-child { margin-bottom: 0; }
.disclosure ul { display: flex; flex-direction: column; gap: 6px; padding-left: 18px; }
.disclosure li { list-style: disc; }

.kv { display: grid; grid-template-columns: auto 1fr; gap: 5px 12px; font-size: .92rem; }
.kv dt { color: var(--text-muted); }
.kv dd { margin: 0; }

.muted { color: var(--text-muted); }
.small { font-size: .84rem; }
.mono { font-variant-numeric: tabular-nums; }

/* `.hint` is used both standalone and inside .field by different tabs. */
.hint, .field > .hint { font-size: .82rem; color: var(--text-muted); font-weight: 550; }
.label { font-size: .88rem; font-weight: 750; }
.loading { color: var(--text-muted); font-weight: 650; padding: 10px 2px; }

.btn {
  min-height: var(--tap); min-width: var(--tap);
  display: inline-flex; align-items: center; justify-content: center; gap: 6px;
  padding: 9px 18px;
  background: var(--surface-2); color: var(--text);
  border: 1px solid var(--border); border-radius: var(--pill);
  font: inherit; font-weight: 750; font-size: .93rem; letter-spacing: -.01em;
  cursor: pointer;
  transition: background .14s ease, border-color .14s ease, transform .14s ease;
}
.btn:hover { border-color: var(--border-strong); }
.btn:active { transform: scale(.96); }
.btn:disabled { opacity: .5; cursor: default; transform: none; }
.btn--primary, .btn-primary { background: var(--accent); color: var(--accent-fg); border-color: transparent; }
.btn--ghost, .btn-ghost { background: transparent; }
.btn--danger, .btn-danger { background: var(--danger-weak); color: var(--danger); border-color: var(--danger); }
.btn--block, .btn-block { width: 100%; }
.btn--small, .btn-small { min-height: 38px; padding: 6px 14px; font-size: .85rem; }

.field { display: flex; flex-direction: column; gap: 6px; }
.field > label { font-size: .88rem; font-weight: 750; }
input[type="text"], input[type="url"], input[type="search"], input[type="number"],
input[type="time"], input[type="datetime-local"], select, .input {
  min-height: var(--tap);
  width: 100%;
  padding: 9px 13px;
  background: var(--surface); color: var(--text);
  border: 1.5px solid var(--border); border-radius: var(--radius-sm);
  font: inherit; font-weight: 600;
}
input:focus, select:focus, .input:focus { border-color: var(--accent); }

.routes-stop-input { display: flex; align-items: stretch; gap: 6px; }
.routes-stop-input > input { min-width: 0; }
.routes-map-button { flex: 0 0 var(--tap); padding: 0; border-radius: var(--radius-sm); font-size: 1.1rem; }

/* Segmented control (theme switcher, small mode pickers). */
.segmented {
  display: flex; gap: 3px; padding: 3px;
  border: 1px solid var(--border); border-radius: var(--pill);
  background: var(--surface-2);
}
.segmented > button {
  flex: 1 1 0; min-height: 38px;
  padding: 7px 10px;
  background: transparent; color: var(--text-muted);
  border: 0; border-radius: var(--pill);
  font: inherit; font-weight: 750; font-size: .88rem; cursor: pointer;
  transition: background .14s ease, color .14s ease;
}
.segmented > button[aria-pressed="true"] { background: var(--accent); color: var(--accent-fg); }

/* Crowd chip — colour + glyph + text label (never colour alone). Both the
   dom.js spelling (.chip--crowd[data-band]) and the tabs' own local spelling
   (.crowd-chip.crowd-band-*) resolve to the same visual. */
.chip, .crowd-chip {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 4px 11px;
  border-radius: var(--pill);
  border: 1.5px solid var(--crowd-unknown);
  background: var(--crowd-unknown-bg); color: var(--crowd-unknown-fg);
  font-size: .8rem; font-weight: 750; white-space: nowrap;
}
.chip__index { font-variant-numeric: tabular-nums; opacity: .8; }
.chip__glyph::before { content: "?"; font-size: .9em; }

.chip--crowd[data-band="comfortable"], .crowd-chip.crowd-band-comfortable {
  background: var(--crowd-comfortable-bg); color: var(--crowd-comfortable-fg);
  border-color: var(--crowd-comfortable);
}
.chip--crowd[data-band="comfortable"] .chip__glyph::before { content: "\25CF"; }   /* ● */
.chip--crowd[data-band="moderate"], .crowd-chip.crowd-band-moderate {
  background: var(--crowd-moderate-bg); color: var(--crowd-moderate-fg);
  border-color: var(--crowd-moderate);
}
.chip--crowd[data-band="moderate"] .chip__glyph::before { content: "\25D0"; }      /* ◐ */
.chip--crowd[data-band="packed"], .crowd-chip.crowd-band-packed {
  background: var(--crowd-packed-bg); color: var(--crowd-packed-fg);
  border-color: var(--crowd-packed);
}
.chip--crowd[data-band="packed"] .chip__glyph::before { content: "\25B2"; }        /* ▲ */
.chip--crowd[data-band="unknown"], .crowd-chip.crowd-chip-unknown {
  background: var(--crowd-unknown-bg); color: var(--crowd-unknown-fg);
  border-color: var(--crowd-unknown);
}
/* A load reported by an untracked bus. Dashed, matching the ETA card that
   carries it — the chip and the number have to agree about what they are. */
.crowd-chip.crowd-unmonitored { border-style: dashed; }

.badge, .tag {
  display: inline-block; padding: 3px 10px; border-radius: var(--pill);
  background: var(--surface-2); color: var(--text-muted);
  border: 1px solid var(--border);
  font-size: .74rem; font-weight: 750;
}
.tag-high { border-color: var(--crowd-comfortable); color: var(--crowd-comfortable-fg); background: var(--crowd-comfortable-bg); }
.tag-medium { border-color: var(--crowd-moderate); color: var(--crowd-moderate-fg); background: var(--warn-weak); }
.tag-low, .tag-user { border-color: var(--border-strong); }

.notice {
  padding: 12px 14px; border-radius: var(--radius);
  background: var(--surface-2); border: 1px solid var(--border);
  border-left: 5px solid var(--accent);
  font-weight: 600;
}
.notice-warn { background: var(--warn-weak); border-left-color: var(--crowd-moderate); }
.notice-error { background: var(--danger-weak); border-left-color: var(--danger); }

/* Crowding index bar (Crowd tab station list). */
.bar { height: 10px; border-radius: var(--pill); background: var(--surface-2); overflow: hidden; }
.bar__fill { height: 100%; border-radius: var(--pill); background: var(--crowd-unknown); }
.bar[data-band="comfortable"] .bar__fill { background: var(--crowd-comfortable); }
.bar[data-band="moderate"] .bar__fill { background: var(--crowd-moderate); }
.bar[data-band="packed"] .bar__fill { background: var(--crowd-packed); }

.skeleton {
  border-radius: var(--radius-sm);
  background: linear-gradient(90deg, var(--surface-2) 25%, var(--border) 37%, var(--surface-2) 63%);
  background-size: 400% 100%;
  animation: shimmer 1.3s ease-in-out infinite;
  min-height: 1em;
}
@keyframes shimmer { from { background-position: 100% 0; } to { background-position: 0 0; } }

.empty, .error-state {
  padding: 22px 18px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  text-align: left;
}
.error-state, .card.error { border-left: 5px solid var(--danger); }
.error-state__title { font-size: 1.08rem; margin-bottom: 6px; }
.error-state__detail {
  margin: 10px 0;
  padding: 10px 12px;
  background: var(--surface-2);
  border-radius: var(--radius-sm);
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: .8rem;
  white-space: pre-wrap;
  overflow-x: auto;
}

/* ------------------------------------------------------------ routes tab --
   Transit 6.0's trip planner "emphasises transit legs with prominent
   departure/arrival times" and dropped the walk/wait iconography that used to
   clutter each row. Here that means the duration/fare/crowd line — the three
   numbers a choice is actually made on — is set larger than the prose around
   it, and the explanatory copy recedes to muted body text. */

.routes-notice:not(:empty) { margin-bottom: var(--gap); }
.routes-results { display: flex; flex-direction: column; gap: var(--gap); }

.option-card { display: flex; flex-direction: column; gap: 8px; }

.option-badge {
  align-self: flex-start;
  margin: 0;
  padding: 4px 12px;
  border-radius: var(--pill);
  background: var(--accent-weak); color: var(--accent);
  font-size: .76rem; font-weight: 800;
  letter-spacing: .02em;
}
/* The recommendation is the default action; the alternatives are alternatives. */
.option-card[data-card="recommended"] { border-color: var(--accent); border-width: 2px; }
.option-card[data-card="recommended"] .option-badge { background: var(--accent); color: var(--accent-fg); }

.option-route {
  display: flex; align-items: center; flex-wrap: wrap; gap: 6px;
  margin: 0;
  font-size: 1.05rem; font-weight: 750; letter-spacing: -.02em;
}
.option-arrow { color: var(--text-muted); font-weight: 800; }
.option-walk { font-size: .9rem; font-weight: 700; color: var(--text-muted); }
.option-facts {
  display: flex; align-items: center; flex-wrap: wrap; gap: 8px;
  margin: 0;
  font-size: 1.15rem; font-weight: 800; letter-spacing: -.025em;
  font-variant-numeric: tabular-nums;
}
.option-basis, .option-blurb { margin: 0; font-size: .82rem; }
.option-tradeoff {
  margin: 0;
  padding: 10px 12px;
  border-radius: var(--radius-sm);
  background: var(--surface-2);
  font-size: .85rem; font-weight: 650;
}
.option-card .btn { align-self: flex-start; }

/* The live balance, shown in the collapsed summary — it is the reason one
   route outranked another, so it stays readable without opening anything. */
.routes-weight-readout {
  margin-left: auto;
  font-weight: 650; font-size: .8rem;
  color: var(--text-muted);
  font-variant-numeric: tabular-nums;
}
.routes-priorities { margin-top: var(--gap); }

.slider-row { display: flex; flex-direction: column; gap: 6px; padding: 8px 0; }
.slider-row + .slider-row { border-top: 1px solid var(--border); }
.slider {
  width: 100%; height: var(--tap);
  accent-color: var(--accent);
  cursor: pointer;
}
/* "selected on map" — the app filled this in. */
.chip-detected { background: var(--accent-weak); color: var(--accent); border-color: var(--accent); }
/* "edited" — you overrode it, and your value wins. Deliberately neutral: it is
   a statement of fact about whose input is in force, not a warning. */
.chip-edited { background: var(--surface-2); color: var(--text-muted); border-color: var(--border-strong); }

/* ---------------------------------------------------------------- me tab -- */

.me-loading { color: var(--text-muted); font-weight: 650; padding: 16px 2px; }
.me-list { display: flex; flex-direction: column; gap: 6px; font-size: .9rem; }
.me-list > li { display: flex; gap: 8px; }
.me-list > li::before { content: "\2022"; color: var(--accent); font-weight: 800; }

.me-place { padding: 10px 0; }
.me-place + .me-place { border-top: 1px solid var(--border); }
.me-place-head { display: flex; align-items: center; gap: 8px; margin-bottom: 4px; }
.me-place-icon { font-size: 1.3rem; }
.me-place-label { font-size: .95rem; font-weight: 800; letter-spacing: -.01em; }
.me-place-head .tag { margin-left: auto; }
.me-place-name { margin: 0 0 4px; font-size: 1.15rem; font-weight: 750; letter-spacing: -.02em; }
.me-place-editing { background: var(--surface-2); border-radius: var(--radius); padding: 12px; }
.me-select { margin: 8px 0; }

.me-privacy-headline { margin: 0 0 10px; font-size: 1.05rem; font-weight: 750; letter-spacing: -.02em; }

.me-window + .me-window { margin-top: 14px; padding-top: 14px; border-top: 1px solid var(--border); }
.me-window h3 { font-size: .95rem; margin-bottom: 4px; }

/* The commute prompt is the one card that asks for something rather than
   reporting it, so it is the one card tinted with the accent. */
.card-prompt { border-color: var(--accent); border-width: 2px; background: var(--accent-weak); }
.me-prompt-text { margin: 0 0 4px; font-size: 1.15rem; font-weight: 800; letter-spacing: -.02em; }

/* ------------------------------------------------------ settings dialog --
   src/ui/settings.js builds a plain div overlay with its own `.settings-*`
   class names and injects no CSS of its own, so every rule the panel needs
   lives here. (It previously matched nothing in this file and rendered as an
   unstyled column of divs over the app.)

   Shaped as a bottom sheet on phones — same reasoning as the tab bar: it
   opens under the thumb and is dismissed by reaching down, not up. */

.settings-overlay {
  position: fixed; inset: 0; z-index: 100;
  display: flex; align-items: flex-end; justify-content: center;
  background: rgba(0, 0, 0, .55);
  -webkit-backdrop-filter: blur(3px);
  backdrop-filter: blur(3px);
  animation: fade .16s ease-out;
}
@keyframes fade { from { opacity: 0; } to { opacity: 1; } }

.settings-panel {
  width: min(100vw, var(--maxw));
  max-height: 92dvh;
  display: flex; flex-direction: column;
  background: var(--surface); color: var(--text);
  border: 1px solid var(--border); border-bottom: 0;
  border-radius: var(--radius-xl) var(--radius-xl) 0 0;
  box-shadow: var(--shadow-lg);
  overflow: hidden;
  animation: sheet-up .2s cubic-bezier(.2, .8, .3, 1);
}
@keyframes sheet-up { from { transform: translateY(14px); } to { transform: none; } }

.settings-header {
  position: sticky; top: 0; z-index: 1;
  display: flex; align-items: center; gap: 8px;
  padding: 14px 16px;
  background: var(--surface);
  border-bottom: 1px solid var(--border);
}
.settings-header > h2, #settings-title { flex: 1 1 auto; font-size: 1.2rem; }
.settings-close {
  flex: 0 0 var(--tap);
  width: var(--tap); height: var(--tap);
  display: inline-flex; align-items: center; justify-content: center;
  background: var(--surface-2); color: var(--text);
  border: 1px solid var(--border); border-radius: 50%;
  font: inherit; font-size: 1.2rem; cursor: pointer;
}

.settings-body {
  padding: 18px 16px max(24px, env(safe-area-inset-bottom));
  overflow-y: auto;
  display: flex; flex-direction: column; gap: 24px;
  -webkit-overflow-scrolling: touch;
}

.settings-section { display: flex; flex-direction: column; gap: 10px; }
.settings-section > h3 { font-size: 1rem; }
.settings-note { margin: 0; font-size: .82rem; color: var(--text-muted); font-weight: 550; }
.settings-label { font-size: .88rem; font-weight: 750; }

/* The Advanced fold sits inside the panel's own column flow, so it needs the
   same block spacing the sibling `.settings-section`s get from the body gap. */
.settings-advanced { background: var(--surface-2); }
.settings-advanced > .settings-section + .settings-rows { margin-top: 14px; }
.settings-subhead { font-size: .92rem; font-weight: 800; }

.settings-rows { display: flex; flex-direction: column; gap: 8px; }
.settings-row {
  display: flex; align-items: baseline; justify-content: space-between; gap: 12px;
  padding: 8px 0;
  border-bottom: 1px solid var(--border);
  font-size: .9rem;
}
.settings-row:last-child { border-bottom: 0; }
.settings-key { color: var(--text-muted); font-weight: 650; }
.settings-value { font-weight: 750; text-align: right; overflow-wrap: anywhere; }

.settings-actions { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; }
.settings-input { flex: 1 1 12rem; min-width: 0; }

/* A second field inside one section (the app token under the proxy URL). The
   rule above it is what separates the two without inventing another heading. */
.settings-subsection {
  display: flex; flex-direction: column; gap: 10px;
  margin-top: 6px; padding-top: 14px;
  border-top: 1px solid var(--border);
}

/* Theme picker: segmented-control look, real radios underneath so it stays
   keyboard- and screen-reader-navigable. */
.settings-radiogroup {
  display: flex; gap: 3px; padding: 3px;
  background: var(--surface-2);
  border: 1px solid var(--border); border-radius: var(--pill);
}
.settings-radio {
  flex: 1 1 0;
  display: flex; align-items: center; justify-content: center; gap: 6px;
  min-height: 38px; padding: 6px 10px;
  border-radius: var(--pill);
  font-size: .88rem; font-weight: 750;
  color: var(--text-muted);
  cursor: pointer;
}
.settings-radio:has(input:checked) { background: var(--accent); color: var(--accent-fg); }
.settings-radio:has(input:focus-visible) { outline: 3px solid var(--focus); outline-offset: 2px; }
/* The input stays in the accessibility tree and keeps the control operable
   where :has() is unsupported — it is hidden only once :has() can do the job. */
@supports selector(:has(*)) {
  .settings-radio input { position: absolute; opacity: 0; pointer-events: none; }
}

.settings-danger {
  display: flex; flex-direction: column; gap: 10px;
  padding: 14px;
  background: var(--danger-weak);
  border: 1px solid var(--danger);
  border-radius: var(--radius);
}

@media (min-width: 40rem) {
  .settings-overlay { align-items: center; }
  .settings-panel { border-bottom: 1px solid var(--border); border-radius: var(--radius-xl); }
}

/* --------------------------------------------------- motion / contrast --- */

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: .001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: .001ms !important;
    scroll-behavior: auto !important;
  }
  .progress[data-indeterminate="true"] .progress__fill { width: 100%; opacity: .5; }
}

@media (prefers-contrast: more) {
  .card, .banner, .toast, input, .btn, .eta-card { border-width: 2px; }
  .eta-card[data-live="false"] { border-width: 3px; }
}

@media print {
  .app-header, .tabbar, .toasts, .banners { display: none; }
  .app-main { padding-bottom: var(--gap); }
}
