/* RailPass light theme: FlightQ parchment (#edede8) + navy. Light only. */
:root {
  --bg: #edede8;
  --panel: #f4f4f0;
  --ink: #16233a;
  --muted: #6b7383;
  --faint: #9aa1ad;
  --line: #d8d8d1;
  --line-soft: #e3e3dc;
  --accent: #1d3557;
  --accent-soft: #dfe4ec;
  /* Warm = yours, cool = merely possible. Nothing you have not ridden may be
     warm - the high-speed option used to be a warm brick (#9c5346), which put
     three reds on the map doing three different jobs and made your own route
     indistinguishable from a route you were merely offered. */
  --route: #7c2d12;      /* the journey you've actually made - deep, settled */
  --route-soft: #f6e8d5;
  --preview: #be123c;    /* the option you're considering right now - bright */
  --goal: #2f6b4f;
  --goal-soft: #e0ebe4;
  --night: #4a4370;
  --hs: #3c6e8f;        /* high-speed OPTION - cool, like every other option */
}

* { margin: 0; padding: 0; box-sizing: border-box; }
html, body { height: 100%; background: var(--bg); overflow: hidden; }
body {
  font-family: ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif;
  color: var(--ink);
  -webkit-font-smoothing: antialiased;
}

#map { position: absolute; inset: 0; }

/* Boot cover. Same parchment as the host page's route loader, so the handoff
   from the Next loading state into the game is one continuous surface rather
   than a flash of half-built map. */
#boot {
  position: absolute; inset: 0; z-index: 60;
  background: var(--bg);
  opacity: 1; pointer-events: none;
  transition: opacity 420ms ease;
  display: flex; flex-direction: column;
  align-items: center; justify-content: center; gap: 26px;
  padding: 0 16px;
}
body.booted #boot { opacity: 0; }
@media (prefers-reduced-motion: reduce) { #boot { transition: none; } }

/* Split-flap boot loader, echoing the site's departure-board route loader so
   the Next loading overlay hands off into an identical-looking surface. Pure
   CSS: it must read as "loading" before app.js has even been fetched. */
.boot-brand { display: flex; gap: 4px; }
.boot-brand span {
  width: 34px; height: 46px;
  display: flex; align-items: center; justify-content: center;
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: 4px;
  box-shadow: 0 1px 0 rgba(0,0,0,0.08) inset;
  font-family: ui-monospace, "SF Mono", Menlo, monospace;
  font-weight: 700; font-size: 19px; color: var(--ink);
}
.boot-caption { display: flex; gap: 3px; }
.boot-caption i { width: 10px; }
.boot-caption span {
  width: 18px; height: 26px;
  display: flex; align-items: center; justify-content: center;
  background: var(--panel);
  border: 1px solid var(--line-soft);
  border-radius: 3px;
  font-family: ui-monospace, "SF Mono", Menlo, monospace;
  font-weight: 700; font-size: 11px; color: var(--muted);
}
.boot-dots { display: flex; gap: 6px; }
.boot-dots b {
  width: 6px; height: 6px; border-radius: 50%;
  background: var(--faint);
  animation: bootPulse 1s ease-in-out infinite;
}
.boot-dots b:nth-child(2) { animation-delay: 0.3s; }
.boot-dots b:nth-child(3) { animation-delay: 0.6s; }
@keyframes bootPulse {
  0%, 100% { opacity: 1; }
  50% { opacity: 0.3; }
}
@media (prefers-reduced-motion: reduce) {
  .boot-dots b { animation: none; }
}
@media (max-width: 420px) {
  .boot-brand span { width: 28px; height: 40px; font-size: 16px; }
  .boot-caption span { width: 15px; height: 23px; font-size: 10px; }
}

/* ---------- top bar (FlightQ NavBar pattern) ---------- */
/* No header bar: controls float straight over the map so the page reads as one
   continuous surface. The container ignores pointer events; children take them. */
#topbar {
  position: absolute; top: 0; left: 0; right: 0; z-index: 30;
  display: grid; grid-template-columns: minmax(0,1fr) auto minmax(0,1fr);
  align-items: center; gap: 8px; min-height: 52px; padding: 6px 12px 0 8px;
  background: none; pointer-events: none;
}
#topbar > * { pointer-events: auto; }
.tb-left { display: flex; align-items: center; min-width: 0; }
#moregames, #moregames-link {
  display: inline-flex; align-items: center; gap: 4px; min-height: 44px;
  padding: 0 10px; font: inherit; font-size: 14px; font-weight: 600;
  color: var(--muted); background: none; border: 0; border-radius: 8px;
  cursor: pointer; white-space: nowrap; text-decoration: none;
  text-shadow: 0 1px 3px var(--bg), 0 0 10px var(--bg);
}
#moregames:hover, #moregames-link:hover { color: var(--ink); }
#moregames svg { width: 14px; height: 14px; }

/* Desktop/laptop: plain link straight to the homepage. Below that, the
   dropdown trigger (the menu itself is still a stub in this prototype). */
#moregames-link { display: none; }
@media (min-width: 1024px) {
  #moregames-link { display: inline-flex; }
  #moregames { display: none; }
}
.tb-title {
  font-size: 18px; font-weight: 600; letter-spacing: 0.18em; text-transform: uppercase;
  color: var(--ink); white-space: nowrap; text-align: center;
  text-shadow: 0 1px 3px var(--bg), 0 0 10px var(--bg);
}
.tb-right { display: flex; align-items: center; gap: 6px; justify-content: flex-end; min-width: 0; }
/* The clock lives with the journey, top-right of the panel - it is game state,
   not site chrome. */
.quest-head {
  display: flex; align-items: baseline; justify-content: space-between; gap: 12px;
}
.quest-head .label { margin-bottom: 0; }
#clockwrap {
  display: flex; gap: 6px; align-items: baseline;
  font-family: ui-monospace, "SF Mono", Menlo, monospace;
}
#day { font-size: 10px; color: var(--faint); letter-spacing: 0.2em; }
#clock { font-size: 18px; font-weight: 700; color: var(--ink); font-variant-numeric: tabular-nums; }
#profile {
  width: 44px; height: 44px; display: flex; align-items: center; justify-content: center;
  background: none; border: 0; border-radius: 10px; color: var(--muted); cursor: pointer;
  text-decoration: none;   /* it is an <a> now, not a <button> */
}
#profile:hover { color: var(--ink); }
#profile svg { width: 20px; height: 20px; }

/* ---------- panel ---------- */
/* Floating card over the map rather than a docked column, so the map is one
   continuous surface behind the whole UI. */
#panel {
  /* Ends roughly halfway down rather than reaching for the bottom of the
     window. A full-height column of departures reads as an endless list and
     buries the map behind it; stopping at ~55% keeps the route visible, which
     is half of how the day is actually read.
     The list itself still holds EVERY pattern and scrolls - nothing is
     truncated. Dusseldorf rolls 1008 departures into 128 patterns with the
     Paris Eurostar at #122, so a row cap would silently hide the train that
     wins the day. This caps the BOX, not the contents. */
  position: absolute; top: 56px; left: 12px; bottom: auto;
  height: min(68vh, calc(100vh - 68px));
  /* Min 332px = 300px MPU + 16px padding each side, so the ad never overflows. */
  width: clamp(332px, 33%, 390px);
  display: flex; flex-direction: column; overflow: hidden;
  background: rgba(244, 244, 240, 0.93);
  border: 1px solid var(--line-soft); border-radius: 14px;
  box-shadow: 0 6px 28px rgba(22, 35, 58, 0.10);
  backdrop-filter: blur(8px);
  z-index: 10;
}

.label {
  font-size: 10px; letter-spacing: 0.18em; color: var(--faint);
  text-transform: uppercase; margin-bottom: 8px;
}

/* ---------- quest ---------- */
#quest { padding: 14px 16px 14px; border-bottom: 1px solid var(--line); }
#quest-route { font-size: 17px; font-weight: 650; line-height: 1.4; letter-spacing: -0.01em; margin-top: 8px; }
#quest-route .arrow { color: var(--faint); font-weight: 400; font-size: 12px; padding: 0 7px; }
#quest-to { color: var(--goal); }
/* Plain inline stats, no chrome: the numbers carry the weight and a hairline
   middot does the separating. */
#quest-deadline {
  display: flex; flex-wrap: wrap; align-items: baseline;
  margin-top: 9px; font-size: 12px; color: var(--muted);
}
#quest-deadline .stat { white-space: nowrap; }
#quest-deadline .stat + .stat::before {
  content: "·"; color: var(--line); padding: 0 9px;
}
#quest-deadline .stat b {
  color: var(--ink); font-size: 13.5px; font-weight: 650;
  font-variant-numeric: tabular-nums; letter-spacing: -0.01em;
}
#quest-deadline .stat.bad b { color: #a2483c; }
#quest-deadline .stat.dim { color: var(--faint); }

.challabel { margin-top: 14px; }
.chall {
  display: flex; gap: 9px; align-items: center; font-size: 12.5px;
  padding: 5px 0; color: var(--ink);
}
.chall .tick {
  width: 15px; height: 15px; flex: none; border-radius: 4px;
  border: 1px solid #c3c9d2; background: #fff; color: #fff;
  font-size: 10px; font-weight: 700; line-height: 14px; text-align: center;
}
.chall .clabel { min-width: 0; }
.chall.ok { color: var(--goal); }
.chall.ok .tick { background: var(--goal); border-color: var(--goal); }
.chall .prog { margin-left: auto; font-size: 10px; color: var(--faint); font-variant-numeric: tabular-nums; }
.chall.ok .prog { color: var(--goal); }

/* ---------- departure board ---------- */
#board { flex: 1; display: flex; flex-direction: column; min-height: 170px; }
#station-head { padding: 14px 16px 8px; }
#station-name { font-size: 19px; font-weight: 650; letter-spacing: -0.01em; }
#station-sub { font-size: 11.5px; color: var(--muted); margin-top: 3px; }
.boardlabel { padding: 0 16px; }

/* The list shrinks to whatever is left after the quest, journey bar and the
   reserved ad slot; it scrolls rather than pushing the ad off-screen.
   min-height MUST be 0 - a positive min-height here makes the list overflow its
   flex parent and slide under the ad. */
/* Snap to row boundaries so a scroll never rests with a departure sliced in
   half. `proximity` rather than `mandatory`: mandatory fights the user on a
   long list and makes flicking feel sticky, whereas proximity only tidies up
   when the scroll already ends near an edge. */
#departures {
  flex: 1; min-height: 0; overflow-y: auto; padding: 0 8px 12px;
  scroll-snap-type: y proximity;
}
.dep { scroll-snap-align: start; }
.dep {
  border-radius: 8px; margin: 1px 2px; cursor: pointer;
  border-left: 2px solid transparent;
}
.dep:hover { background: var(--bg); border-left-color: var(--preview); }
.dep:hover .dest { color: #9f1239; }
.dep.open { background: var(--bg); border-left-color: var(--preview); }
.dep.open .dest { color: #9f1239; }
/* Persistent selection from a map click: stays marked in place so you keep the
   surrounding timetable and can scroll away. */
.dep.selected { background: #fbe9ed; border-left-color: var(--preview); }
.dep.selected .dest { color: #9f1239; }
.deprow {
  display: grid; grid-template-columns: 66px 38px 1fr; gap: 9px;
  align-items: baseline; padding: 9px 10px;
}
.dep .t {
  font-family: ui-monospace, "SF Mono", Menlo, monospace;
  font-weight: 650; font-size: 13px; color: var(--ink);
  font-variant-numeric: tabular-nums; white-space: nowrap;
}
.dep .t .plus { color: var(--faint); font-size: 9px; margin-left: 2px; vertical-align: super; }
.cat {
  font-size: 9.5px; font-weight: 700; text-align: center; padding: 2px 3px;
  border-radius: 3px; background: var(--accent-soft); color: var(--accent);
  letter-spacing: 0.03em; overflow: hidden; white-space: nowrap;
}
.cat.hs { background: #f0e2df; color: var(--hs); }
.cat.night { background: #e6e3ef; color: var(--night); }
.cat.eur { background: #dfe9e6; color: #2c5f57; }
/* Never clip a station name. The tail is usually what separates two stations
   in the same city - Hbf from Bahnhof Sud - so an ellipsis hides the exact
   word the row is being read for. Wrap and let the row grow instead. */
.dep .dest {
  font-size: 13px; overflow-wrap: anywhere;
  color: var(--ink);
}
.nighttag { font-size: 9px; color: var(--night); letter-spacing: 0.12em; margin-left: 6px; }
.later { font-size: 10px; color: var(--faint); margin-left: 6px; }

.stops { padding: 0 10px 8px 18px; }
.alsoat { font-size: 10.5px; color: var(--faint); padding: 2px 8px 8px; }
.stop {
  display: flex; align-items: baseline; padding: 6px 8px;
  font-size: 12.5px; cursor: pointer; border-radius: 5px; color: var(--ink);
}
.stop:hover { background: var(--accent-soft); }
.stop .sn { min-width: 0; overflow-wrap: anywhere; }
.stop .skm { margin-left: auto; margin-right: 10px; font-size: 10.5px; color: var(--faint); white-space: nowrap; }
.stop .sa {
  font-family: ui-monospace, "SF Mono", Menlo, monospace;
  color: var(--muted); font-variant-numeric: tabular-nums; font-size: 12px;
}
/* The destination, wherever it shows up.
   It was green bold text and nothing else, which is easy to miss in a list of
   twenty stops that are all place names. Outlined and tinted instead, so the
   stop that finishes the day is the one thing on the board you cannot scan
   past. */
.stop.isdest {
  color: var(--goal); font-weight: 600;
  background: rgba(47, 107, 79, 0.08);
  outline: 1.5px solid var(--goal); outline-offset: -1.5px;
  border-radius: 8px;
}
.stop.isdest b {
  font-size: 9px; letter-spacing: 0.12em; margin-left: 6px;
  color: var(--goal);
}

/* A collapsed departure that CALLS at the destination. Marked on the row
   itself so the train that ends the journey can be found without opening
   every pattern - a busy station rolls 1000+ departures into ~128 of them. */
.dep.todest > .deprow {
  background: rgba(47, 107, 79, 0.07);
  box-shadow: inset 3px 0 0 var(--goal);
}
.dep.todest > .deprow .dest { color: var(--goal); font-weight: 600; }
.desttag {
  font-size: 9px; letter-spacing: 0.12em; margin-left: 6px;
  padding: 1px 5px; border-radius: 4px;
  border: 1px solid var(--goal); color: var(--goal);
  vertical-align: 1px;
}

/* ---------- journey bar ---------- */
#journeybar {
  border-top: 1px solid var(--line); padding: 10px 16px 14px;
  max-height: 22vh; overflow-y: auto; background: rgba(237, 237, 232, 0.75);
  flex: none;
}

/* Reserved Venatus MPU slot, pinned to the bottom of the panel. Hidden on short
   viewports: a 250px unit plus the quest block leaves no usable departure list
   under ~840px of height, and a one-row list is worse than no ad. */
#adslot { display: none; }
@media (min-height: 840px) {
  #adslot {
    display: block; flex: none; border-top: 1px solid var(--line);
    padding: 8px 16px 14px; background: rgba(237, 237, 232, 0.75);
  }
}
/* Premium / ad-free: no reserved slot at any height, list reclaims the space. */
body.no-ads #adslot { display: none !important; }
.ad-label {
  font-size: 9px; letter-spacing: 0.18em; color: var(--faint);
  text-align: center; margin-bottom: 6px;
}
.ad-box {
  width: 300px; height: 250px; margin: 0 auto;
  display: flex; align-items: center; justify-content: center;
  background: #e7e7e1; border: 1px dashed var(--line);
  border-radius: 6px; color: var(--faint); font-size: 12px;
}
.leg { font-size: 11.5px; color: var(--muted); padding: 3px 0; display: flex; gap: 8px; }
.leg {
  border-left: 2px solid var(--route); padding-left: 8px; margin-left: 1px;
}
.leg .lt {
  font-family: ui-monospace, "SF Mono", Menlo, monospace;
  color: var(--route); font-variant-numeric: tabular-nums; white-space: nowrap;
}
.leg .lc { color: var(--muted); }

/* ---------- destination chip ---------- */
#destchip {
  position: absolute; top: 0; left: 0; z-index: 15; pointer-events: none;
  padding: 5px 10px; font-size: 11px; font-weight: 650; white-space: nowrap;
  background: var(--goal-soft); color: var(--goal);
  border: 1px solid #b9d2c4; border-radius: 999px;
}
#destchip .arr { width: 11px; height: 11px; vertical-align: -1px; display: inline-block; }

/* ---------- win overlay ----------
   Covers the whole game and centres, rather than filling the left panel.
   It used to be a child of #panel, so `inset: 0` resolved to the panel box:
   the result card opened hard against the left edge with the departure list
   still legible behind it, which read as a layer on the board rather than
   the end of the run. */
#win {
  position: fixed; inset: 0; z-index: 40;
  display: flex; align-items: center; justify-content: center; padding: 24px;
  background: rgba(237, 237, 232, 0.82);
  backdrop-filter: blur(6px);
}
#win-card {
  display: flex; flex-direction: column; align-items: center; gap: 14px;
  text-align: center; width: min(420px, 100%);
  padding: 30px 28px 26px;
  background: rgba(244, 244, 240, 0.98);
  border: 1px solid var(--line-soft); border-radius: 18px;
  box-shadow: 0 18px 60px rgba(22, 35, 58, 0.18);
}
#win-title { font-size: 22px; font-weight: 700; letter-spacing: 0.22em; color: var(--goal); }
#win-title.late { color: #a2483c; }
#win-score { font-size: 46px; font-weight: 700; color: var(--ink); line-height: 1; letter-spacing: -0.02em; }
#win-score span { font-size: 18px; font-weight: 600; color: var(--faint); }
#win-breakdown { width: 100%; max-width: 300px; }
.scorerow {
  display: flex; align-items: baseline; gap: 8px; font-size: 12px;
  padding: 5px 0; border-bottom: 1px solid var(--line-soft);
}
.scorerow .sk { color: var(--ink); font-weight: 600; }
.scorerow .sv { color: var(--faint); margin-left: auto; font-size: 11px; }
.scorerow .sp { color: var(--ink); font-weight: 700; font-variant-numeric: tabular-nums; min-width: 44px; text-align: right; }
.scorerow .sp i { color: var(--faint); font-style: normal; font-weight: 400; font-size: 10px; }
#win-stats { font-size: 12px; line-height: 1.9; color: var(--muted); }
#win-stats b { color: var(--ink); }

/* Two pills of identical size. They were a button and a button with a stray
   margin-top, so they sat at different heights and different widths. */
.win-actions { display: flex; gap: 10px; margin-top: 6px; }
.win-actions > * {
  flex: 1 1 0; min-width: 132px;
  display: inline-flex; align-items: center; justify-content: center;
  height: 42px; padding: 0 22px; margin: 0;
  font: inherit; font-size: 13px; letter-spacing: 0.08em; font-weight: 600;
  border: 0; border-radius: 999px; cursor: pointer; text-decoration: none;
  transition: background 140ms ease;
}
#win-share { background: var(--route); color: #fff; }
#win-share:hover { background: #96450a; }
#win-archive { background: var(--accent); color: #fff; }
#win-archive:hover { background: #17293f; }
.win-actions > *:focus-visible { outline: 2px solid var(--ink); outline-offset: 2px; }

.hidden { display: none !important; }

.maplibregl-ctrl-attrib { background: rgba(237,237,232,0.8) !important; }
.maplibregl-ctrl-attrib a { color: var(--faint) !important; }

/* ============================================================
   PHONE: the panel stops being a left column and becomes a
   bottom sheet, so the map keeps the whole screen behind it.
   Keep MOBILE_BP in app.js in step with this breakpoint.
   ============================================================ */
#sheet-grip { display: none; }

@media (max-width: 720px) {
  #panel {
    /* Sized by top+bottom, NOT height.
       vh proved unreliable here: an embedded pane reported innerHeight 1633
       against a document height of 812, and `height: 46vh` and `height: 88vh`
       both computed to the same 373.5px, so the sheet could never expand. With
       position:absolute, giving both top and bottom makes the height fall out
       of the geometry and removes the viewport-unit guesswork entirely. */
    /* JS sets `top` in pixels (see the sheet() block in app.js); CSS only
       pins the sides and supplies the transition. Viewport-relative units are
       not trustworthy here: vh and % both failed to resolve in an embedded
       pane, and on mobile Safari 100vh famously counts the collapsing URL bar,
       so a sheet sized in vh jumps as the toolbar hides. Pixels off
       documentElement.clientHeight behave the same everywhere. */
    /* top is set from JS in pixels; bottom is pinned here. Specify NEITHER
       height NOR max-height: with top, height and bottom all present the box
       is over-constrained and the browser discards one of them - it kept
       height+bottom and threw away the top we were animating, so the sheet
       never moved. top + bottom alone define it unambiguously. */
    top: 54%; left: 0; right: 0; bottom: 0;
    width: 100%;
    height: auto; max-height: none;
    border-radius: 16px 16px 0 0;
    border-left: 0; border-right: 0; border-bottom: 0;
    background: rgba(244, 244, 240, 0.97);
    box-shadow: 0 -6px 28px rgba(22, 35, 58, 0.16);
    transition: top 240ms cubic-bezier(.22,.61,.36,1);
    /* Respect the home indicator / gesture bar. */
    padding-bottom: env(safe-area-inset-bottom, 0px);
  }
  /* State heights are applied from JS as pixel `top` values. */
  /* Dragged down to get the map back: the grip stays reachable. */


  #sheet-grip {
    display: flex; flex-direction: column; align-items: center; gap: 3px;
    width: 100%; padding: 8px 0 4px; flex: none;
    background: none; border: 0; cursor: pointer;
    -webkit-tap-highlight-color: transparent;
  }
  .grip-bar {
    width: 38px; height: 4px; border-radius: 2px; background: #c8c8c0;
  }
  #grip-hint {
    font-size: 9.5px; letter-spacing: 0.16em; text-transform: uppercase;
    color: var(--faint);
  }

  /* Tighten the quest block - on a phone this is a status line, not a card. */
  #quest { padding: 8px 14px 12px; }
  #quest-route { font-size: 15.5px; margin-top: 4px; }
  #quest-deadline { margin-top: 6px; font-size: 11.5px; }
  .challabel { margin-top: 10px; }
  .chall { font-size: 12px; padding: 3.5px 0; }

  /* Compact board. The default spacing is tuned for a laptop panel and reads
     as very airy on a phone, where vertical space is the scarce resource - it
     pushed the sheet taller than it needed to be and squeezed the map. */
  #station-head { padding: 8px 14px 2px; }
  #station-name { font-size: 16.5px; line-height: 1.2; }
  #station-sub { font-size: 11px; margin-top: 1px; }
  /* "DEPARTURES" is redundant under a station name and a departure count. */
  .boardlabel { display: none; }
  #departures { padding: 2px 6px 10px; }
  /* 40px still clears the 44px guideline once the 2px row margins are counted,
     and fits roughly one extra train per screen. */
  .dep { margin: 0 2px; }
  .dep .deprow { min-height: 40px; padding: 6px 9px; gap: 8px; }
  .dep .t { font-size: 12.5px; }
  .dep .dest { font-size: 13.5px; }
  .stop { min-height: 34px; }

  /* The journey bar eats scarce vertical space; keep it to a glance. */
  #journeybar { max-height: 15vh; padding: 8px 14px 10px; }

  /* No MPU on a phone. 300x250 inside a 46vh sheet leaves no list at all, and
     the height-only rule below did not catch phones - an iPhone 14 is 844px
     tall, which cleared the 840px bar and reserved the slot on a device that
     has no room for it. */
  #adslot { display: none !important; }

  /* NOTE: do not set top/right/bottom on #destchip - updateDestChip positions
     it with a transform, and any offset here is applied on top of that, which
     silently pushed it away from the point it is supposed to be marking. */
  .tb-title { font-size: 15px; letter-spacing: 0.12em; }
}

/* Very short phones (SE-class): peek less, or the map disappears entirely. */


@media (max-width: 720px) {
  /* Clear the browser's floating toolbar. Without this the last departures sit
     under Safari's URL bar and cannot be scrolled into view, because the sheet
     is pinned to the LAYOUT viewport which extends behind that chrome. */
  /* Applied by JS only when the list actually overflows - see renderBoard.
     A fixed 84px was dead space at a four-train halt. */
  #departures.needs-chrome-gap { padding-bottom: 84px; }
  #journeybar { padding-bottom: calc(16px + env(safe-area-inset-bottom, 0px)); }
}

/* ---------- phone: quest moves to a top drop-down ---------- */
#topsheet { display: none; }
#quest-summary { display: none; }

@media (max-width: 720px) {
  /* Status at the top, actions at the bottom. The bottom sheet is then purely
     "which train next", which is the only thing you actually tap. */
  #topsheet {
    display: block; position: absolute; z-index: 11;
    top: 46px; left: 8px; right: 8px;
    background: rgba(244, 244, 240, 0.97);
    border: 1px solid var(--line-soft); border-radius: 12px;
    box-shadow: 0 4px 18px rgba(22, 35, 58, 0.12);
    overflow: hidden;
  }
  #quest-summary {
    display: flex; align-items: center; gap: 10px; width: 100%;
    padding: 9px 12px; background: none; border: 0; cursor: pointer;
    font: inherit; color: var(--ink); text-align: left;
    -webkit-tap-highlight-color: transparent;
  }
  /* Wraps onto a second line rather than ellipsising. The goal station comes
     first in the string, so a truncated bar cut the time remaining and the
     challenge count - the two numbers the collapsed bar exists to show. */
  #qs-text {
    min-width: 0; flex: 1; font-size: 13px; font-weight: 600;
    letter-spacing: -0.01em; line-height: 1.35;
    overflow-wrap: anywhere;
  }
  #qs-text .qs-goal { color: var(--goal); }
  #qs-text .qs-dim { color: var(--muted); font-weight: 500; }
  #qs-text .qs-bad { color: #a2483c; }
  #qs-chev {
    flex: none; color: var(--faint); font-size: 11px;
    transition: transform 160ms ease;
  }
  #topsheet.open #qs-chev { transform: rotate(180deg); }

  /* Collapsed by default: only the summary line shows. */
  #topsheet #quest { display: none; padding: 2px 12px 12px; border-bottom: 0; }
  #topsheet.open #quest { display: block; }
  /* The clock is core game state, not chrome. Collapsed, the summary line
     carries time-LEFT (the number that actually matters while playing);
     expanded, the real clock comes back with the rest of the detail. */
  #topsheet #quest .quest-head {
    display: flex; align-items: baseline; justify-content: space-between;
    margin-bottom: 2px;
  }

  /* The bottom sheet now holds only the board, so it can sit lower. */
  #panel #quest { display: none; }
}

@media (max-width: 720px) {
  /* The off-screen goal chip was laid out for a laptop: with a long station
     name it measured 365px on a 375px screen and spilled past the right edge,
     which is what made the document scroll sideways. Cap the width to stop the
     sideways scroll, but wrap the name over two lines rather than clipping it -
     the chip names the destination, so the name has to survive intact. */
  #destchip {
    max-width: 62vw; font-size: 11px; padding: 5px 9px;
    white-space: normal; overflow-wrap: anywhere; line-height: 1.3;
  }
}

/* ---------- phone: persistent goal compass ---------- */
#compass { display: none; }
@media (max-width: 720px) {
  /* The destination chip only appears when the goal is off-screen. This is
     always on, so the direction and remaining distance are never something you
     have to go looking for. */
  #compass {
    display: flex; align-items: center; gap: 5px;
    position: absolute; right: 10px; z-index: 12;
    padding: 5px 9px 5px 7px;
    background: var(--goal-soft); color: var(--goal);
    border: 1px solid #b9d2c4; border-radius: 999px;
    font-size: 11px; font-weight: 650; font-variant-numeric: tabular-nums;
    pointer-events: none; white-space: nowrap;
    box-shadow: 0 2px 8px rgba(22, 35, 58, 0.10);
  }
  #compass.hidden { display: none; }
  /* Set from the sheet() block in app.js: both chips are map overlays that
     outrank the sheet, so they stand down while it covers the map. */
  body.sheet-open #compass, body.sheet-open #destchip { display: none; }
  #compass-arrow { width: 13px; height: 13px; flex: none; transition: transform 200ms ease; }
}

/* Cross-town links: metro, RER, a bus or a walk between the big stations of
   one city. Visually distinct from a train row because it is not a train -
   no timetable, no operator, and it never counts toward a challenge. */
.dep.xtown .t { font-variant-numeric: normal; opacity: .75; }
.dep.xtown .cat.xt {
  background: transparent;
  border: 1px dashed rgba(31, 51, 80, .45);
  color: rgba(31, 51, 80, .75);
}
.dep.xtown .dest { font-style: italic; }
.dep.xtown .later { font-style: normal; opacity: .6; }

/* Keyboard focus. The board is div-based (a departure row contains its own
   expandable stop list, which cannot legally nest inside a <button>), so the
   focus ring has to be drawn explicitly - without it a keyboard player has no
   idea where they are on the board. */
.deprow:focus-visible,
.stop:focus-visible {
  outline: 2px solid #1f3350;
  outline-offset: -2px;
  border-radius: 6px;
}

/* Once the run is over the departure board is not something you can act on,
   so it goes rather than sitting greyed behind the result. The map stays:
   the route you actually rode is the thing worth looking at. */
body.finished #panel,
body.finished #topsheet,
body.finished #journeybar { display: none !important; }

/* The way back. Only exists once the host card has been closed - before that
   the card itself is the endgame, and a second set of controls under it would
   just be clutter. */
#endbar { display: none; }
body.finished.result-dismissed #endbar {
  display: flex; align-items: center; gap: 12px;
  position: absolute; z-index: 30;
  left: 50%; transform: translateX(-50%);
  bottom: calc(18px + env(safe-area-inset-bottom, 0px));
  max-width: calc(100vw - 24px);
  padding: 9px 10px 9px 16px;
  background: rgba(244, 244, 240, 0.97);
  border: 1px solid var(--line-soft); border-radius: 999px;
  box-shadow: 0 6px 24px rgba(22, 35, 58, 0.16);
}
#endbar-score { font-size: 13px; font-weight: 650; color: var(--ink); white-space: nowrap; }
#endbar button, #endbar a {
  font: inherit; font-size: 12.5px; font-weight: 600; cursor: pointer;
  padding: 6px 13px; border-radius: 999px; white-space: nowrap; text-decoration: none;
}
#endbar-result { border: 0; background: var(--accent); color: #fff; }
#endbar-archive { border: 1px solid var(--line); background: transparent; color: var(--ink); }
#endbar-result:hover { background: #16283f; }
#endbar-archive:hover { background: var(--bg); }

/* Endgame challenge list: which ones you got, not just how many. Same tick
   language as the in-game list so the result reads as the same object. */
#win-challenges { width: 100%; max-width: 300px; display: flex; flex-direction: column; gap: 2px; }
.wc {
  display: flex; gap: 9px; align-items: center; font-size: 12.5px;
  padding: 5px 0; color: var(--muted);
}
.wc .tick {
  width: 15px; height: 15px; flex: none; border-radius: 4px;
  border: 1px solid #c3c9d2; background: #fff; color: #fff;
  font-size: 10px; font-weight: 700; line-height: 14px; text-align: center;
}
.wc .clabel { min-width: 0; text-align: left; }
.wc .prog { margin-left: auto; font-size: 10px; color: var(--faint); font-variant-numeric: tabular-nums; }
.wc.ok { color: var(--goal); }
.wc.ok .tick { background: var(--goal); border-color: var(--goal); }
.wc.ok .prog { color: var(--goal); }
.wc.miss .clabel { text-decoration: line-through; text-decoration-color: var(--line); }
