.map-wrap { position: absolute; inset: 0; }
  #map { height: 100%; width: 100%; background: var(--surface-2); }
  /* A properly designed dark basemap (CARTO Dark Matter) rather than a CSS
     filter over light tiles: inverting tiles turns place labels into
     unreadable mush, and the brand's lime and teal were made for dark
     backgrounds anyway. */
  .leaflet-container { font-family: var(--font-sans); background: var(--coal); }
  /* Dark Matter is tuned for maps that carry their own data, so at city zoom
     the street grid all but disappears. Lifting the tiles brings roads and
     labels back without swapping to a light basemap. */
  .leaflet-tile-pane { filter: brightness(1.75) contrast(1.08) saturate(1.15); }
  /* Clear of the nav and the single chip row above it. */
  .leaflet-top.leaflet-left { margin-top: 5.3rem; }
  .leaflet-bar a, .leaflet-control-layers { border-radius: 0 !important; }
  .leaflet-bar a {
    background: var(--surface-2); color: var(--linen); border-bottom-color: var(--border-2);
  }
  .leaflet-bar a:hover { background: var(--surface-3); color: var(--lime); }
  .leaflet-control-layers {
    background: var(--surface-2); color: var(--linen); border: 1px solid var(--border-2);
  }
  .leaflet-control-attribution {
    background: rgba(10,10,10,.75) !important; color: var(--text-muted) !important;
  }
  .leaflet-control-attribution a { color: var(--text-secondary) !important; }
  .leaflet-popup-content-wrapper, .leaflet-popup-tip {
    background: var(--surface-2); color: var(--linen); border-radius: 0;
    border: 1px solid var(--border-2);
  }
  .leaflet-popup-content-wrapper a { color: var(--lime); }
  .leaflet-popup-close-button { color: var(--text-muted) !important; }

  /* --- line picker --- */
  /* Floating over the map, clear of the nav above and the zoom control below
     it. Scrollable on its own when 31 lines will not fit the width. */
  .lines {
    position: absolute; z-index: 600; top: 2.4rem; left: 0; right: 0;
    padding: .3rem 1rem .6rem;
    background: linear-gradient(rgba(10,10,10,.9), rgba(10,10,10,0));
    pointer-events: none;
  }
  .chips { pointer-events: auto; }
  .lines h2 {
    font-family: var(--font-mono); font-size: .6875rem; font-weight: 500;
    text-transform: uppercase; letter-spacing: var(--tracking-label);
    color: var(--text-muted); margin: 0 0 .6rem;
  }
  /* One row that scrolls sideways rather than wrapping: wrapping made the
     overlay's height depend on the window width, and everything positioned
     below it -- the zoom control, the detail box -- then had to guess. */
  .chips {
    display: flex; flex-wrap: nowrap; gap: .3rem;
    overflow-x: auto; scrollbar-width: none; padding-bottom: 2px;
  }
  .chips::-webkit-scrollbar { display: none; }
  .chip { flex: none; }
  .chip {
    font-family: var(--font-mono); font-size: .8125rem; font-weight: 500;
    font-variant-numeric: tabular-nums;
    min-width: 2.75rem; padding: .35rem .55rem;
    border: 1px solid var(--border-2); background: var(--surface-2); color: var(--text-secondary);
    cursor: pointer; transition: background .15s, border-color .15s, color .15s;
  }
  .chip:hover { border-color: var(--lime); color: var(--linen); }
  .chip:focus-visible { outline: 2px solid var(--lime); outline-offset: 1px; }
  .chip[aria-pressed="true"] {
    background: var(--lime); border-color: var(--lime); color: var(--coal);
  }
  /* Mode is carried by shape as well as colour: square for trolleybuses
     (fixed to the wires), round for buses. Colour alone excludes anyone with
     a colour vision deficiency, and teal against grey is exactly the pair
     that goes first. */
  .chip .kind {
    display: block; width: 7px; height: 7px; margin: .32rem auto 0;
  }
  .kind.bus { border-radius: 50%; }
  .chip[aria-pressed="true"] .kind { background: rgba(10,10,10,.6) !important; }
  .chip.all { min-width: auto; letter-spacing: var(--tracking-label); text-transform: uppercase;
              font-size: .6875rem; }

  /* --- detail + status --- */
  /* Sits on the map, not above it. As a block element it changed the page
     height whenever a line was picked, moving the map out from under you.
     Cleared to the right of the zoom buttons, which own the top-left corner:
     Leaflet leaves only the bottom-left free, and that is the status bar's. */
  .detail {
    position: absolute; left: 10px; top: 9.6rem; z-index: 500;
    padding: .5rem .7rem;
    border: 1px solid var(--border-2); border-left: 2px solid var(--lime);
    background: rgba(10,10,10,.85); font-size: .8125rem;
    max-width: calc(100% - 64px);
  }
  .detail[hidden] { display: none; }
  .detail .muted { color: var(--text-muted); }
  .detail-line { font-family: var(--font-mono); font-weight: 700; color: var(--lime); }

  /* On the map, bottom right -- the last corner Leaflet leaves free once the
     zoom (top left), layers (top right) and status bar (bottom left) have
     taken theirs. Attribution sits under it. */
  .legend { position: absolute; right: 10px; bottom: 10px; z-index: 500;
            display: flex; flex-wrap: wrap; gap: .5rem 1rem; max-width: 40%;
            background: rgba(10,10,10,.85); border: 1px solid var(--border-2);
            padding: .4rem .6rem;
            font-size: .75rem; color: var(--text-muted); }
  .legend span { display: inline-flex; align-items: center; gap: .45rem; }
  .legend .kind { display: inline-block; width: 8px; height: 8px; margin: 0; flex: none; }
  /* <i> defaults to italic, which on a numeric marker reads as a typo. */
  .legend .veh { display: inline-block; flex: none; font-style: normal; box-shadow: none; }
  .swatch { width: 1.1rem; height: 3px; display: inline-block; }
  .dot { width: .6rem; height: .6rem; border-radius: 50%; display: inline-block; }
  /* Overlaid on the map rather than sitting under it: this is the one part of
     the page that changes by itself, and below the fold nobody notices it
     move. Bottom-left is the only corner Leaflet leaves alone -- zoom is top
     left, layers top right, attribution bottom right. */
  #map-status {
    position: absolute; left: 10px; bottom: 10px; z-index: 500;
    font-family: var(--font-mono); font-size: .75rem; color: var(--text-secondary);
    background: rgba(10,10,10,.85); border: 1px solid var(--border-2);
    padding: .4rem .6rem;
    display: flex; flex-wrap: wrap; align-items: center; gap: .5rem;
    max-width: calc(100% - 20px);
  }
  #map-status .sep { color: var(--border-2); }
  #map-status .fresh { color: var(--text-secondary); }
  #map-status .stale { color: var(--warning); }
  /* The countdown is the one number that changes every second. Brand lime and
     fixed-width digits mark it well enough; inside a panel that already has a
     border, boxing it again was just noise. */
  .countdown {
    color: var(--lime); font-variant-numeric: tabular-nums; font-weight: 700;
    display: inline-flex; align-items: center; gap: .4rem;
  }
  .countdown.stale { color: var(--warning); }
  .pulse {
    width: .5rem; height: .5rem; border-radius: 50%; background: var(--lime);
    display: inline-block; flex: none; animation: pulse 2s ease-in-out infinite;
  }
  .pulse.stale { background: var(--warning); animation: none; }
  .pulse.dead { background: var(--error); animation: none; }
  @keyframes pulse { 0%, 100% { opacity: 1 } 50% { opacity: .25 } }
  @media (prefers-reduced-motion: reduce) { .pulse { animation: none } }
  .popup-title { font-family: var(--font-mono); font-weight: 700; color: var(--lime); }
  .popup-meta { color: var(--text-muted); font-size: .85em; }

  /* Vehicles are labelled with their line number: a plain dot tells you a bus
     is there, not which one, and "which line is that" is the first thing
     anyone asks of a live map. */
  .veh {
    font-family: var(--font-mono); font-size: 11px; font-weight: 700;
    line-height: 18px; text-align: center;
    min-width: 20px; height: 20px; padding: 0 3px;
    position: relative;
    border: 1px solid var(--coal); color: var(--coal);
    box-shadow: 0 1px 4px rgba(0,0,0,.6);
    white-space: nowrap;
  }
  /* Same shape language as the chips: buses are rounded, trolleybuses are
     square, so mode survives on a small marker without relying on colour. */
  .veh.bus { background: var(--warning); border-radius: 10px; }
  .veh.trolley { background: var(--teal); }

  /* Stop labels for the selected line. Rendered as markers rather than
     tooltips so they stay put while panning and can be read at a glance. */
  .stop-label {
    font-family: var(--font-mono); font-size: 10.5px; line-height: 1.25;
    color: var(--linen); background: rgba(10,10,10,.82);
    padding: 1px 4px;
    white-space: nowrap; transform: translate(9px, -9px);
    pointer-events: none;
  }

  /* Vehicle popup: a small table of where it has been and where it is going. */
  .veh-pop { min-width: 190px; }
  .veh-pop dl { margin: .4rem 0 0; display: grid; grid-template-columns: auto 1fr;
                gap: .15rem .6rem; font-size: .8rem; }
  .veh-pop dt { color: var(--text-muted); }
  .veh-pop dd { margin: 0; }
  /* Three distinct states, three colours: late is a warning, on time is
     reassurance, and running early is neither -- it means the vehicle may
     leave before a passenger expects it, which is worth noticing. */
  .veh-pop .late { color: var(--warning); font-weight: 700; }
  .veh-pop .early { color: var(--lime); font-weight: 700; }
  .veh-pop .ontime { color: var(--teal); font-weight: 700; }
  .veh-pop .unknown { color: var(--text-muted); }
  /* Secondary detail beside the live figures: the scheduled time a vehicle is
     being judged against, how far along its run it is, and the note that it
     has not left yet. Quieter on purpose -- it is context for the number next
     to it, not the answer. */
  .veh-pop .est { font-size: .72rem; color: var(--text-muted); }

  /* Departure board in a stop popup. */
  .dep-pop { min-width: 268px; max-width: 340px; }
  .dep-board { margin: .45rem 0 0; display: grid;
               grid-template-columns: auto auto minmax(0, 1fr); gap: .2rem .5rem;
               font-size: .8rem; align-items: baseline; }
  .dep-time { font-family: var(--font-mono); font-variant-numeric: tabular-nums;
              color: var(--linen); }
  .dep-time.shifted { color: var(--warning); }
  .dep-line { font-family: var(--font-mono); font-weight: 700; color: var(--lime);
              white-space: nowrap; }
  .dep-plat { color: var(--text-muted); font-weight: 500; }
  /* Only the destination truncates. The delay note after it is the shortest
     part of the row and the one worth reading, so clipping that instead was
     exactly backwards. */
  .dep-to { color: var(--text-secondary); display: flex; gap: .3rem; min-width: 0; }
  .dep-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  .dep-in { color: var(--text-muted); font-variant-numeric: tabular-nums;
            white-space: nowrap; flex: none; }
  .dep-none { color: var(--text-muted); font-size: .8rem; margin-top: .4rem; }

  /* Direction of travel: an arrow orbiting the marker, pointing at the next
     stop. Outside the badge rather than on top of it -- drawn over the fill it
     read as a bite taken out of the circle, not as a heading.

     The bearing comes from the server, because the upstream's gps_course is
     null on every vehicle ever recorded. */
  /* Hidden until the map is zoomed in far enough for it to mean something;
     see HEADING_MIN_ZOOM. */
  .veh-arrow { display: none; }
  #map.show-headings .veh-arrow {
    display: block;
    position: absolute; left: 50%; top: 50%;
    width: 46px; height: 46px; margin: -23px 0 0 -23px;
    transform-origin: 50% 50%; pointer-events: none; z-index: 1;
  }
  #map.show-headings .veh-arrow::before {
    content: ""; position: absolute; left: 50%; top: -2px; margin-left: -8px;
    border-left: 8px solid transparent; border-right: 8px solid transparent;
    border-bottom: 13px solid var(--linen);
    /* An outline as well as a shadow: the arrow crosses lime route lines, pale
       tiles and the dark background, and a shadow alone vanishes on the first
       of those. */
    filter: drop-shadow(0 0 1px rgba(10,10,10,1)) drop-shadow(0 0 2px rgba(10,10,10,.9));
  }
