/* THE GAME'S OWN STYLE — what is specific to index.html. The shell (the
   reset, the type, .hud, .pane, button, kbd, the meters, the boot screen, the
   SHAPE SCALE and the two THEMES) lives in css/hud.css and is shared with every
   page. Nothing here names a colour: every colour is a token from hud.css, so
   the light and the dark theme are one stylesheet and a rule can never be
   right in one theme and wrong in the other. */
  /* The shell — the reset, the type, .hud, button, kbd, the meters, the boot
     screen and the SHAPE SCALE — lives in css/hud.css and is shared with
     drive-lab and car-lab. Only what is specific to Douala is below. */
  html, body { overflow: hidden; }

  /* The street you are on. This is the recognition payload — more than any
     building, a name you have driven under is what makes a place yours. */
  /* #street no longer positions itself: it is the third row of #topcentre, so
     the mission bar and the satnav strip push it down rather than printing over
     it. Two elements each pinned to "top: 18px, centred" is two owners for one
     piece of screen, and the second one always wins. */
  #streetName { font: 600 19px/1.2 ui-monospace, Menlo, monospace; letter-spacing: .02em; }
  #district { font-size: 11px; letter-spacing: .22em; text-transform: uppercase; color: var(--pick-text); margin-top: 3px; }

  /* The minimap takes the bottom-right corner, so the speedo moves in beside
     it — the two together read as one instrument cluster. */
  #speedo { right: 254px; bottom: 18px; text-align: right; }
  /* .hud sets pointer-events: none so the overlay never eats a drag on the
     canvas underneath. The map is the one piece of HUD you are meant to CLICK,
     so it has to opt back in — without this the click listener is attached to
     an element the mouse can never reach. */
  /* The radar has NO css frame. Its bezel, its rim and its compass are drawn
     into the canvas, because a circular map inside a square bordered box is the
     single most obvious tell of a first-pass minimap: you see the corners of the
     element behind the circle of the map. Draw the instrument, don't box it. */
  #mapwrap { right: 18px; bottom: 16px; width: 216px; pointer-events: auto; }
  #map { display: block; width: 216px; height: 216px; cursor: pointer;
    filter: drop-shadow(0 7px 20px var(--shadow-drop)); }

  /* Expanded: the whole city, north up, over a dimmed game. The corner map
     tells you what is around the next bend; this one tells you where in Douala
     you actually are, and it is a document you READ — so it pans, it zooms, and
     it sits on a veil that takes the click that dismisses it. */
  /* No backdrop-filter. A blur here is a full-screen GPU pass EVERY FRAME over
     a 2880x1626 canvas that is still animating behind it — measured at 3.9 ms,
     which was 27% of the frame, spent on decoration that is about to be covered
     by a map. A darker scrim reads the same and costs nothing. */
  #mapveil { position: fixed; inset: 0; z-index: 38; display: none;
    background: var(--veil); }
  body.mapfull #mapveil { display: block; }
  body.mapfull #mapwrap { right: auto; bottom: auto; left: 50%; top: 50%; width: auto;
    transform: translate(-50%, -50%); z-index: 40; }
  /* The expanded map IS a square sheet — drawFull paints the ground to all four
     corners — so it is the one surface here allowed to keep its corners. They
     are rounded to the card radius and the frame is the hairline every other
     floating pane uses, so it belongs to the same set. */
  body.mapfull #map { width: min(84vh, 84vw); height: min(84vh, 84vw);
    border-radius: var(--r-lg); cursor: grab; filter: none;
    box-shadow: var(--lift-3), 0 0 0 1px var(--edge); }
  body.mapfull #map.drag { cursor: grabbing; }
  body.mapfull #mapfoot { font-size: 11px; margin-top: 9px; padding: 0 6px; }

  #maphead { display: none; }
  body.mapfull #maphead { display: flex; align-items: center; gap: 14px;
    margin-bottom: 10px; padding: 0 6px; }
  #mapTitle { font-size: 15px; letter-spacing: .16em; text-transform: uppercase;
    color: var(--ink); }
  #mapHint { flex: 1; font-size: 10.5px; letter-spacing: .06em; color: var(--dim); }
  /* SANGO MAPS: the search box lives in the head of the city map, its
     results drop over the map. The route line on the maps is the same blue. */
  #mapSearchWrap { position: relative; flex: 1 1 auto; min-width: 240px; }
  #mapSearch { width: 100%; box-sizing: border-box; padding: 7px 34px 7px 12px; border-radius: 999px;
    border: 1px solid var(--edge); background: var(--panel); color: var(--ink);
    font: 500 13px/1.2 ui-monospace, SFMono-Regular, Menlo, monospace; outline: none; }
  #mapSearch:focus { border-color: var(--maps); box-shadow: 0 0 0 2px var(--maps-ring); }
  #mapSearch::placeholder { color: var(--dim); }
  #mapResults { position: absolute; left: 0; right: 0; top: 36px; z-index: 5; max-height: 46vh; overflow: auto;
    background: var(--panel-strong); border: 1px solid var(--edge); border-radius: 12px; padding: 4px; display: none; }
  #mapResults.on { display: block; }
  #mapResults .res { display: grid; grid-template-columns: 1fr auto; gap: 2px 10px; padding: 7px 10px; border-radius: 8px; cursor: pointer; }
  #mapResults .res:hover, #mapResults .res.sel { background: var(--maps-wash); }
  #mapResults .res .n { font: 600 13px/1.2 ui-monospace, Menlo, monospace; color: var(--ink); }
  #mapResults .res .k { font-size: 10.5px; letter-spacing: .08em; text-transform: uppercase; color: var(--dim); grid-column: 1; }
  #mapResults .res .d { font-size: 11px; color: var(--maps-text); grid-column: 2; grid-row: 1 / span 2; align-self: center; text-align: right; }
  #mapResults .hd { font-size: 10px; letter-spacing: .14em; text-transform: uppercase; color: var(--dim); padding: 6px 10px 2px; }
  #mapResults .hd.grp { cursor: pointer; display: flex; align-items: center; gap: 6px; color: var(--ink); font-size: 11px; padding: 8px 10px 4px; border-top: 1px solid var(--edge); }
  #mapResults .hd.grp:first-child { border-top: 0; }
  #mapResults .hd.grp .tri { color: var(--maps-text); width: 10px; }
  #mapResults .hd.grp .cnt { margin-left: auto; color: var(--dim); font-size: 10px; }
  #mapGo { display: none; align-items: center; gap: 10px; font-size: 11px; letter-spacing: .06em; color: var(--maps-text); }
  body.mapfull #mapGo.on { display: flex; }
  #mapGo b { color: var(--ink); font-weight: 600; letter-spacing: 0; text-transform: none; }
  #mapGo button { padding: 2px 8px; font-size: 10.5px; }
  /* THE NAMEPLATE: the landmark you are looking at, named where it stands. */
  #lmTag { position: fixed; left: 0; top: 0; transform: translate(-50%, -100%); pointer-events: none; z-index: 6;
    display: none; padding: 6px 12px; border-radius: 10px; background: var(--panel); border: 1px solid var(--edge);
    color: var(--ink); font: 600 13px/1.25 ui-monospace, SFMono-Regular, Menlo, monospace; white-space: nowrap; text-align: center; }
  #lmTag.on { display: block; }
  #lmTag small { display: block; font-weight: 400; font-size: 10px; letter-spacing: .1em; text-transform: uppercase; color: var(--dim); margin-top: 2px; }
  #lmTag::after { content: ''; position: absolute; left: 50%; bottom: -6px; width: 1px; height: 6px; background: var(--edge-hi); }
  /* A round close button: an ✕ in a square is a window chrome control, an ✕ in
     a circle is a dismiss. They mean different things and this one dismisses. */
  #mapClose { width: 26px; height: 26px; padding: 0; line-height: 1;
    display: grid; place-items: center; flex: none; }

  #mapfoot { display: flex; justify-content: space-between; margin-top: 6px;
    font-size: 10.5px; letter-spacing: .1em; color: var(--dim); text-transform: uppercase; }
  /* The scale is a measurement, not a caption: "115 m", never "115 M". */
  #mapScale { text-transform: none; letter-spacing: .04em; }
  #kmh { font: 700 58px/0.9 ui-monospace, Menlo, monospace; letter-spacing: -.02em; color: var(--ink-strong);
    font-variant-numeric: tabular-nums;
    text-shadow: 0 2px 4px var(--halo), 0 0 22px var(--halo-soft); }
  #kmh small { font-size: 14px; font-weight: 400; color: var(--ink-2); letter-spacing: .1em; margin-left: 10px; }
  #gear { font-size: 14px; color: var(--sango-text); letter-spacing: .16em; margin-top: 2px; }

  #perf { left: 16px; top: 14px; padding: 11px 14px; min-width: 17rem; }
  #perf .r { display: flex; justify-content: space-between; gap: 1.4rem; }
  #perf b { font-weight: 400; color: var(--dim); }
  #perf span { font-variant-numeric: tabular-nums; }
  #perf .warn { color: var(--pick-text); }
  #perf .good { color: var(--drop-text); }
  #perf hr { border: 0; border-top: 1px solid var(--edge); margin: 8px 0 7px; }

  #places { right: 16px; top: 14px; display: flex; flex-direction: column; gap: 6px;
    align-items: flex-end; pointer-events: auto; }
  #places button { text-align: center; }
  .sep { height: 6px; }

  /* ---- The bottom-left stack ------------------------------------------------
     The radio and the control hints are ONE column, not two elements each
     pinned to the bottom-left corner with a hand-guessed offset. They were: the
     radio sat at bottom 74px and the hints were four lines tall, so the two
     printed straight through each other. A stack cannot collide with itself. */
  #bottomleft { left: 16px; bottom: 16px; display: flex; flex-direction: column;
    align-items: flex-start; gap: 9px; }

  #keys { color: var(--ink-2); font-size: 11.5px; line-height: 2;
    padding: 9px 13px; transition: opacity .8s; }
  #keys.dim { opacity: .3; }

  /* Landmark labels, drawn as DOM over the canvas — cheaper than any in-scene
     text and always legible, which matters more here than realism does. */
  #labels { position: fixed; inset: 0; pointer-events: none; overflow: hidden; }

  /* The radio. Bottom-left above the control hints — where a dashboard head
     unit sits relative to the driver, and out of the road's way. The head unit
     itself is a component (.radio, in css/hud.css, built by src/radio-ui.js);
     all this page decides is where in the corner it sits and how wide it may
     grow before the title has to elide. */
  #radio { max-width: 34rem; pointer-events: auto; }
  /* The share pane takes taps; a hint pane beside it does not (.hud is none). */
  #share { pointer-events: auto; }

  /* A landmark label is a tag on a thing in the world, so it is a pill — the
     same shape as a luggage tag, and the shape that survives being small. */
  .lbl { position: absolute; transform: translate(-50%, -100%); white-space: nowrap;
    font-size: 11.5px; letter-spacing: .04em; color: var(--ink-strong); padding: 3px 10px;
    background: var(--panel-soft); border: 1px solid var(--accent-edge);
    border-radius: var(--r-pill);
    text-shadow: 0 1px 2px var(--halo); }
  .lbl.far { opacity: .42; border-color: var(--edge-hi); background: var(--panel-faint); }


  /* ======================================================================
     COURSES — the delivery interface.

     The rules this follows, and they are the ones every studio that ships a
     driving game arrives at:

       · ONE thing is the primary read at any moment. Driving to a pickup, that
         is the destination and the distance. Ten seconds from the deadline, it
         is the clock. Arriving, it is the ring filling. Everything else steps
         back rather than competing.
       · Nothing important lives where the road is. The centre of the screen is
         the windscreen; the interface owns the top strip and the corners.
       · Numbers are tabular and never reflow. A distance that jumps sideways as
         it counts down is read as a glitch even when it is right.
       · Every state change is announced twice — once in vision (a colour, a
         flash) and once in sound — because the player is looking at the road.
       · Motion has a reason. The cards spring in because a spring reads as a
         thing arriving; the numbers count up because a total that lands
         instantly cannot be watched.

     Colour is the grammar. Amber is the pickup leg, green the delivery leg,
     terracotta the brand and the money, red the clock and the damage. A player
     who never reads a word of it still knows which half of the job they are on.

     Shape is the other half of the grammar, and it is the half that was
     missing. Every pane here is on the scale in css/hud.css; every track, every
     segment and every pip is a pill or a circle. Nothing that fills up, counts
     down or drains has a square end, because nothing in the world does.
     ====================================================================== */
  @keyframes pulseRing { from { box-shadow: 0 0 0 0 rgba(224,80,58,.55); }
                           to { box-shadow: 0 0 0 14px rgba(224,80,58,0); } }
  @keyframes gradeIn { 0% { opacity: 0; transform: scale(2.4) rotate(-8deg); letter-spacing: .3em; }
                      60% { opacity: 1; transform: scale(.94) rotate(0); }
                     100% { opacity: 1; transform: scale(1); letter-spacing: 0; } }

  /* ---- The top-centre stack ------------------------------------------------
     Mission, then the next instruction, then the street you are on. In that
     order because that is the order of urgency, and top-down is how a strip
     under the horizon gets read. */
  #topcentre { left: 50%; transform: translateX(-50%); top: 14px;
    display: flex; flex-direction: column; align-items: center; gap: 8px;
    width: min(560px, 92vw); }
  #street { text-align: center; }

  #mission { width: 100%; padding: 11px 15px 12px;
    animation: riseIn .28s var(--spring);
    border-top: 2px solid var(--pick); }
  #mission[data-leg="drop"] { border-top-color: var(--drop-text); }
  #mission[hidden] { display: none; }
  #mission .row1 { display: flex; align-items: baseline; gap: 10px; }
  #mLeg { font-size: 10px; letter-spacing: .2em; text-transform: uppercase;
    color: var(--pick-text); white-space: nowrap; }
  #mission[data-leg="drop"] #mLeg { color: var(--drop-text); }
  #mName { flex: 1; font: 600 15.5px/1.2 ui-monospace, Menlo, monospace; color: var(--ink-strong);
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  #mClock { font: 700 19px/1 ui-monospace, Menlo, monospace; font-variant-numeric: tabular-nums;
    color: var(--ink); letter-spacing: -.01em; }
  #mission.warn #mClock { color: var(--pick-text); }
  #mission.hot #mClock { color: var(--bad-text); animation: pulseRing 1s ease-out infinite;
    border-radius: var(--r-chip); }
  #mission .row2 { display: flex; justify-content: space-between; gap: 10px;
    font-size: 11px; color: var(--dim); margin-top: 3px; }
  #mDist { font-variant-numeric: tabular-nums; color: var(--ink); }

  /* Cargo. A segmented bar rather than a smooth one: segments are countable at
     a glance and a smooth bar at 70% and at 62% look identical. */
  #mCargo { display: flex; align-items: center; gap: 9px; margin-top: 9px; }
  #mCargo .ico { width: 15px; height: 15px; color: var(--dim); flex: none; }
  #mSegs { flex: 1; }
  #mission.hurt #mSegs i.on { background: var(--pick); }
  #mission.crit #mSegs i.on { background: var(--bad); }
  #mCargoPct { font-size: 10.5px; color: var(--dim); font-variant-numeric: tabular-nums; min-width: 3.2em; text-align: right; }
  #mission.knock { animation: shake .32s; }
  #mFragile { font-size: 9px; letter-spacing: .16em; color: var(--bad-text);
    border: 1px solid rgba(224,80,58,.5); border-radius: var(--r-chip); padding: 1px 8px; }

  /* The dwell ring. Arriving is a manoeuvre, so it gets its own read: the whole
     bar turns into one progress track and says what is happening. */
  #mDwell { display: none; margin-top: 9px; align-items: center; gap: 10px; }
  #mission.dwelling #mDwell { display: flex; }
  #mission.dwelling #mCargo { display: none; }
  #mDwellBar { flex: 1; }
  #mDwellBar span { background: var(--drop); box-shadow: 0 0 10px var(--drop);
    transition: width .06s linear; }
  #mDwellLabel { font-size: 10px; letter-spacing: .2em; text-transform: uppercase; color: var(--drop-text); }

  /* ---- The satnav strip -----------------------------------------------------
     A chevron, a distance, and the NAME. The name is the payload: an arrow tells
     you what to do and the name is what teaches you the city, which is the whole
     reason this game is set in a surveyed Douala rather than in a grid.

     A pill, and deliberately not the same shape as the mission panel above it:
     the mission is a document that stays, this is one instruction that is true
     for the next 180 m. Different lifespan, different shape. */
  #nav { display: flex; align-items: center; gap: 11px; padding: 7px 18px 7px 12px;
    border-radius: var(--r-pill);
    animation: popIn .2s ease-out; }
  #nav[hidden] { display: none; }
  #navArrow { width: 26px; height: 26px; color: var(--sango); flex: none;
    transition: transform .2s; }
  #nav.left #navArrow { transform: scaleX(-1); }
  #navDist { font: 700 15px/1 ui-monospace, Menlo, monospace; font-variant-numeric: tabular-nums;
    color: var(--ink-strong); min-width: 3.4em; }
  #navStreet { font-size: 12px; color: var(--ink); max-width: 22rem;
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  #nav.near { border-color: var(--accent-edge); }
  #nav.arrive #navArrow { color: var(--drop); }

  /* ---- The offer card -------------------------------------------------------
     A ride-hail request, because that is the interaction everyone already knows:
     who, what, where from, where to, how much, and a countdown that answers for
     you if you do not. It sits bottom-centre — the one place the eye returns to
     between corners — and it never covers the road. */
  #offer { position: fixed; left: 50%; bottom: 24px; transform: translateX(-50%);
    width: min(430px, 94vw); pointer-events: auto; z-index: 30;
    animation: riseIn .3s var(--spring); }
  #offer[hidden] { display: none; }
  /* The countdown is a TRACK with a fill in it, not a bare div stretched across
     the top of the card. Stretched across the top it had to be clipped by the
     card's corners, which is why the card could not be properly rounded: at
     20 px of radius a 3 px full-bleed bar is sliced into two crescents. Inset
     it and the shape problem disappears — and a countdown that visibly empties
     between two fixed ends is easier to read than one that runs off the edge. */
  #offerTrack { height: 4px; margin: 13px 15px 0; }
  /* A countdown that steps once a second reads as a stutter — it is meant to
     drain. The transition lived on the old full-bleed bar and had to come with
     it when the bar became a fill inside a track. */
  #offerTime { background: var(--sango); box-shadow: 0 0 10px rgba(216,114,47,.6);
    transition: width .1s linear; }
  #offer .pad { padding: 12px 15px 14px; }
  #offer .head { display: flex; align-items: center; gap: 11px; }
  #offer .head .ico { width: 26px; height: 26px; color: var(--sango-text); flex: none; }
  #offer .who { flex: 1; min-width: 0; }
  #offer .kicker { font-size: 9.5px; letter-spacing: .22em; text-transform: uppercase; color: var(--sango-text); }
  #offer .contact { font-size: 12.5px; color: var(--ink); overflow: hidden;
    text-overflow: ellipsis; white-space: nowrap; }
  #offerTier { font-size: 9.5px; letter-spacing: .14em; text-transform: uppercase;
    padding: 3px 9px; border-radius: var(--r-chip); border: 1px solid var(--edge-hi); color: var(--dim); }
  #offerTier.express { color: var(--on-accent); background: var(--bad); border-color: var(--bad-text); }

  /* From/to as a route stub — two dots and a line. Cheaper to read than two
     labelled rows, and it says "journey" without a word. */
  .leg { display: grid; grid-template-columns: 14px 1fr auto; gap: 0 11px;
    margin-top: 12px; font-size: 12.5px; }
  .leg .dots { grid-row: 1 / 3; position: relative; }
  .leg .dots::before, .leg .dots::after { content: ''; position: absolute; left: 3px;
    width: 8px; height: 8px; border-radius: 50%; }
  .leg .dots::before { top: 4px; background: var(--pick); }
  .leg .dots::after { bottom: 4px; background: var(--drop); }
  .leg .dots i { position: absolute; left: 6px; top: 12px; bottom: 12px; width: 2px;
    border-radius: var(--r-pill); background: linear-gradient(var(--pick), var(--drop)); }
  .leg .a, .leg .b { color: var(--ink); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  .leg .b { margin-top: 4px; }
  .leg .qa, .leg .qb { color: var(--dim); font-size: 11px; text-align: right; white-space: nowrap; }
  .leg .qb { margin-top: 4px; }

  #offer .meta { display: flex; align-items: center; gap: 10px; margin-top: 13px;
    padding-top: 11px; border-top: 1px solid var(--edge); font-size: 11.5px; color: var(--dim); }
  #offer .meta .ico { width: 15px; height: 15px; flex: none; color: var(--dim); }
  #offerCargo { flex: 1; color: var(--ink); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  #offerKm { font-variant-numeric: tabular-nums; }
  #offerFare { font: 700 16px/1 ui-monospace, Menlo, monospace; color: var(--sango-text);
    font-variant-numeric: tabular-nums; }
  #offerFare small { font-size: 10px; font-weight: 400; margin-left: 3px; color: var(--dim); }

  #offer .acts { display: flex; gap: 9px; margin-top: 13px; }
  #offer .acts button { flex: 1; display: flex; align-items: center; justify-content: center;
    gap: 8px; padding: 10px 0; font-size: 12px; letter-spacing: .1em; text-transform: uppercase; }
  #offerYes { background: var(--sango); border-color: var(--sango-text); color: var(--on-accent); font-weight: 600; }
  #offerYes:hover { background: var(--sango-hi); border-color: var(--sango-hi); color: var(--on-accent); }
  #offerYes kbd { background: rgba(0,0,0,.22); border-color: rgba(0,0,0,.2); color: var(--on-accent);
    box-shadow: none; }
  #offerYes[disabled] { background: var(--control); border-color: var(--edge); color: var(--faint);
    cursor: not-allowed; }
  #offerYes[disabled] kbd { background: var(--control); border-color: var(--edge); color: var(--faint); }
  #offerWarn { display: none; margin-top: 11px; font-size: 11px; color: var(--bad-text);
    background: rgba(224,80,58,.09);
    border: 1px solid rgba(224,80,58,.4); border-radius: var(--r-sm); padding: 7px 11px; }
  #offer.unfit #offerWarn { display: block; }

  /* ---- Wallet ---------------------------------------------------------------
     Above the minimap, so the money, the map and the speedo read as one cluster
     on the right rather than as three separate overlays. */
  #wallet { position: fixed; right: 18px; bottom: 252px; text-align: right; }
  #walletVal { font: 700 21px/1 ui-monospace, Menlo, monospace; color: var(--sango-text);
    font-variant-numeric: tabular-nums; }
  #walletVal small { font-size: 11px; font-weight: 400; color: var(--dim); margin-left: 4px; }
  #streak { margin-top: 5px; font-size: 10px; letter-spacing: .14em; text-transform: uppercase;
    color: var(--dim); display: flex; gap: 6px; justify-content: flex-end; align-items: center; }
  #walletDelta { position: absolute; right: 0; top: -18px; font: 700 14px ui-monospace, monospace;
    color: var(--drop-text); opacity: 0; transition: opacity .3s, transform .6s; pointer-events: none; }
  #walletDelta.show { opacity: 1; transform: translateY(-12px); }
  #walletDelta.neg { color: var(--bad-text); }
  /* THE CAREER LINE (src/career.js). Under the money, because rank is what the
     money is for: the title, and the one next step, as a button onto the panel. */
  .rank-line { display: flex; flex-direction: column; align-items: flex-end; gap: 2px; margin-top: 7px;
    padding: 5px 8px; border: 1px solid var(--edge); border-radius: var(--r-sm); background: var(--panel-soft);
    color: var(--ink); font: inherit; cursor: pointer; pointer-events: auto; max-width: 230px; text-align: right; }
  .rank-line:hover { background: var(--accent-wash); }
  .rank-line b { font-size: 11.5px; letter-spacing: .06em; text-transform: uppercase; color: var(--sango-text); }
  .rank-line span { font-size: 10.5px; color: var(--dim); line-height: 1.3; }
  .rank-line.up { animation: rank-up 1.6s var(--spring) 1; }
  @keyframes rank-up { 0% { box-shadow: 0 0 0 0 var(--halo); } 30% { box-shadow: 0 0 0 8px var(--halo-soft); } 100% { box-shadow: 0 0 0 0 transparent; } }
  @media (prefers-reduced-motion: reduce) { .rank-line.up { animation: none; } }

  /* The career panel: the route, the ladder (a real sequence, so numbered),
     and what the next rank asks, each with how far along you are. */
  /* The body is a .dock .list (css/hud.css): it scrolls and pads like the garage. */
  .dock .list.career { gap: 12px; padding: 2px 16px 14px; }
  .career .route { font-size: 11px; color: var(--dim); }
  .career .route b { color: var(--ink); font-weight: 600; }
  .career ol.ladder { list-style: none; margin: 0; padding: 0; display: grid; gap: 4px; }
  .career ol.ladder li { display: grid; grid-template-columns: 18px 1fr auto; gap: 8px; align-items: baseline; font-size: 12px; color: var(--dim); }
  .career ol.ladder li .n { font: 700 11px/1 ui-monospace, Menlo, monospace; color: var(--faint); }
  .career ol.ladder li.reached { color: var(--ink); }
  .career ol.ladder li.reached .n { color: var(--sango-text); }
  .career ol.ladder li.now { font-weight: 700; }
  .career ol.ladder li .soon { font-size: 9.5px; letter-spacing: .08em; text-transform: uppercase; color: var(--faint); }
  .career .next h5 { margin: 0 0 6px; font-size: 11px; letter-spacing: .08em; text-transform: uppercase; color: var(--dim); font-weight: 600; }
  .career .cond { display: grid; grid-template-columns: 1fr auto; gap: 2px 10px; font-size: 11.5px; margin-bottom: 7px; }
  .career .cond .bar { grid-column: 1 / -1; height: 4px; border-radius: var(--r-pill); background: var(--track); overflow: hidden; }
  .career .cond .bar i { display: block; height: 100%; border-radius: inherit; background: var(--sango); }
  .career .cond.done { color: var(--drop-text); }
  .career .cond.done .bar i { background: var(--drop); }
  .career .cond .of { font-variant-numeric: tabular-nums; color: var(--dim); }
  .career .rent { font-size: 11px; color: var(--pick-text); }
  .career .later { font-size: 11.5px; color: var(--dim); }

  /* The garage row's ownership line: yours, on versement, or its price. */
  .veh .own { display: block; margin-top: 6px; font-size: 10.5px; color: var(--dim); }
  .veh .own.mine { color: var(--drop-text); }
  .veh .own.rent { color: var(--pick-text); }
  .veh .own.price { color: var(--ink); }
  .veh.locked .nm, .veh.locked .note { color: var(--dim); }
  .veh.confirm { border-color: var(--accent-edge); background: var(--accent-wash-hi); }
  .veh.confirm .own { color: var(--sango-text); font-weight: 600; }

  /* ---- Toasts ---------------------------------------------------------------
     Short, and they never queue more than three deep. A toast that is still on
     screen when the thing it describes is over is noise.

     A pill with a dot, not a rectangle with a coloured left border. The border
     was there to carry the good/bad colour, and a 2 px straight edge is exactly
     what cannot survive being rounded — so the colour moves onto a dot, which
     is a stronger signal anyway at the size a toast is actually read. */
  #toasts { position: fixed; left: 50%; top: 50%; transform: translate(-50%, -50%);
    display: flex; flex-direction: column; align-items: center; gap: 7px;
    pointer-events: none; z-index: 25; }
  .toast { display: flex; align-items: center; gap: 9px;
    background: var(--panel); border: 1px solid var(--edge); border-radius: var(--r-pill);
    box-shadow: var(--lift-1);
    padding: 7px 16px 7px 13px; font-size: 12px; color: var(--ink);
    animation: popIn .18s ease-out; white-space: nowrap; }
  .toast .dot { width: 6px; height: 6px; border-radius: 50%; flex: none;
    background: var(--sango); box-shadow: 0 0 8px rgba(216,114,47,.7); }
  .toast.bad { color: var(--bad-text); }
  .toast.bad .dot { background: var(--bad); box-shadow: 0 0 8px rgba(224,80,58,.7); }
  .toast.good { color: var(--drop-text); }
  .toast.good .dot { background: var(--drop); box-shadow: 0 0 8px rgba(95,191,138,.7); }
  .toast.fade { opacity: 0; transition: opacity .5s, transform .5s; transform: translateY(-8px); }

  /* ---- Blocked, in the water, bent ----------------------------------------
     Three .status pills (css/hud.css). The page decides only WHERE each one
     sits and which colour its dot and its bar carry.

     BLOQUÉ. Centre-bottom, above the key hints — where the eye already is when
     the car has stopped and nothing is happening. */
  #blocked { bottom: 132px; z-index: 26; border-color: var(--bad-text); color: var(--bad-text); }
  #blocked .dot { background: var(--bad); box-shadow: 0 0 9px var(--bad); }
  /* THE CARD OWNS THE BOTTOM OF THE SCREEN WHILE IT IS UP. `#blocked` sits at a
     fixed 132px and the arrival card at 8vh, which on a tall window puts the
     pill INSIDE the card — the two stacked on top of one another and the one
     underneath, which was the message saying WHY the taxi had stopped, could not
     be read at all. One owner: the card says it is there and everything that
     lives at the bottom steps over it. */
  /* EVERY bottom-centred thing, not just the one I noticed first. #foot at
     92px, #blocked at 132px and the arrival card at 8vh are three elements that
     each decided independently to own the bottom of the screen, and on a tall
     window all three land on top of one another — the message saying WHY the
     taxi had stopped ("sortie bloquée — paroi du tunnel") was underneath the
     ride card and unreadable. They stack now, in the order you read them. */
  body.card-bottom #foot    { bottom: calc(8vh + 152px); }
  body.card-bottom #blocked { bottom: calc(8vh + 196px); }
  body.card-bottom #toasts  { top: 34%; }

  /* IN THE WATER. Two pieces of feedback and they do different jobs. The BAND
     says how deep you are and what is about to happen, so that going in reads
     as a sequence you could have interrupted rather than as the game breaking.
     The TINT (#murk) says where the camera is: once the chase camera goes under
     the surface there is nothing to see but opaque silt, and a black screen
     with no explanation is the worst two seconds an open world can give you.
     Douala's river is brown, so the tint is brown. */
  #wading { bottom: 132px; z-index: 27; }
  #wading .dot { background: var(--water); box-shadow: 0 0 9px var(--water); }
  #wading .bar i { background: linear-gradient(90deg, var(--water), #7fb0c8); }

  /* THE BODYWORK. Same shape as the wading band on purpose: both are "something
     is happening to the car and here is how bad". It sits above the wading band
     rather than beside it — you can be in the water AND bent. Hidden on a
     straight car: a permanent 100% gauge is a gauge nobody looks at. */
  #bodywork { bottom: 174px; z-index: 27; }
  #bodywork .dot { background: #c8973f; box-shadow: 0 0 9px rgba(200,151,63,.75); }
  /* It DRAINS as the car is damaged, so it starts full. */
  #bodywork .bar i { width: 100%; background: linear-gradient(90deg, #7a9a4f, #a8c46a);
    transition: width .18s linear; }
  /* A knock is felt, not read. */
  @keyframes bodyKnock { 0% { transform: translateX(-50%) scale(1); }
    35% { transform: translateX(-50%) scale(1.045); }
    100% { transform: translateX(-50%) scale(1); } }
  #bodywork.knock { animation: bodyKnock .22s ease-out; }

  #murk { position: fixed; inset: 0; pointer-events: none; z-index: 24; opacity: 0;
    transition: opacity .35s ease-out;
    background: radial-gradient(120% 90% at 50% 40%,
      rgba(78,70,52,.55) 0%, rgba(43,38,28,.93) 70%, rgba(28,25,18,.99) 100%); }

  /* ---- Off-screen target arrow ---------------------------------------------
     When the marker is behind you or off the side of the screen it still has to
     be findable without opening the map. Standard in every open-world game, and
     it is the single cheapest orientation aid there is. */
  #edgeArrow { position: fixed; width: 46px; height: 46px; margin: -23px 0 0 -23px;
    pointer-events: none; z-index: 20; color: var(--pick); transition: color .2s;
    filter: drop-shadow(0 2px 6px var(--shadow-drop)); }
  #edgeArrow[data-leg="drop"] { color: var(--drop); }
  #edgeArrow[data-leg="arrival"] { color: #7fb4e8; }        /* ARRIVAL_MARK — the way to your vehicle */
  #edgeArrow[hidden] { display: none; }
  #edgeArrow[data-on] { opacity: .92; animation: edgeBob 1.1s ease-in-out infinite; }
  @keyframes edgeBob { 0%, 100% { margin-top: -23px; } 50% { margin-top: -31px; } }
  #edgeArrow .dist { position: absolute; left: 50%; top: 50%; transform: translate(-50%, -50%);
    font: 700 9.5px ui-monospace, monospace; color: var(--ink); font-variant-numeric: tabular-nums;
    text-shadow: 0 1px 3px var(--halo-solid); }

  /* ---- Result ---------------------------------------------------------------
     The one full-attention moment in the loop, so it is allowed to take the
     screen — and it is the only thing here that ever does. It reads top to
     bottom: what you got, how you drove, what you were paid. */
  #result { position: fixed; inset: 0; display: grid; place-items: center; z-index: 50;
    background: var(--scrim); backdrop-filter: blur(2px); pointer-events: auto; }
  #result[hidden] { display: none; }
  #resultCard { width: min(400px, 92vw); padding: 22px 24px 20px;
    border-top: 3px solid var(--drop);
    animation: riseIn .3s var(--spring); }
  #result.failed #resultCard { border-top-color: var(--bad-text); }
  #resGrade { text-align: center; font: 800 76px/1 ui-monospace, Menlo, monospace;
    color: var(--drop-text); animation: gradeIn .5s cubic-bezier(.2,.9,.3,1.3) both; }
  #result.failed #resGrade { color: var(--bad-text); font-size: 54px; }
  #resVerdict { text-align: center; font-size: 11px; letter-spacing: .3em; text-transform: uppercase;
    color: var(--dim); margin-top: 4px; }
  #resWhere { text-align: center; font-size: 13px; color: var(--ink); margin-top: 8px; }
  #resWhere span { color: var(--dim); }
  #resStats { margin: 16px 0 0; padding-top: 13px; border-top: 1px solid var(--edge); }
  #resStats .r, #resLines .r { display: flex; justify-content: space-between; gap: 12px;
    font-size: 12px; padding: 3px 0; }
  #resStats .r b, #resLines .r b { font-weight: 400; color: var(--dim); }
  #resStats .r span { font-variant-numeric: tabular-nums; color: var(--ink); }
  #resLines { margin-top: 13px; padding-top: 11px; border-top: 1px solid var(--edge); }
  #resLines .r span { font-variant-numeric: tabular-nums; }
  #resLines .r.pos span { color: var(--drop-text); }
  #resLines .r.neg span { color: var(--bad-text); }
  #resTotal { display: flex; justify-content: space-between; align-items: baseline;
    margin-top: 13px; padding-top: 12px; border-top: 1px solid var(--edge); }
  #resTotal b { font-weight: 400; font-size: 11px; letter-spacing: .22em;
    text-transform: uppercase; color: var(--dim); }
  #resTotalVal { font: 800 27px/1 ui-monospace, Menlo, monospace; color: var(--sango-text);
    font-variant-numeric: tabular-nums; }
  #resTotalVal small { font-size: 12px; font-weight: 400; color: var(--dim); margin-left: 4px; }
  #resFoot { margin-top: 15px; text-align: center; font-size: 10.5px; color: var(--faint); }
  #resFoot kbd { margin: 0 3px; }


  /* ======================================================================
     THE OTHER THREE MISSIONS, and the garage.

     One rule governs all of this: EACH MISSION GETS ONE PRIMARY READ, and it is
     a different one each time, so the same strip of screen has to become a
     different instrument rather than grow four permanent rows.

       course      the destination and the parcel
       ramassage   the passenger list — who is aboard and how close each is to
                   walking. The list IS the mission, so it gets real estate
       filature    the GAP. Not a number: a band with you sliding along it, with
                   the safe stretch in the middle. A driver holding a distance
                   needs a position, not a readout to do arithmetic on
       chrono      the clock, and the gate that just paid into it

     Everything not being asked for is not merely dimmed, it is absent.
     ====================================================================== */

  /* ---- Ramassage: the passenger list ---------------------------------------
     Selected first, then in the order they will be reached. The patience bar is
     the whole game: it is the only thing on screen that gets worse while you do
     nothing about it. */
  #fares { width: 100%; padding: 7px; display: flex; flex-direction: column; gap: 3px;
    animation: popIn .2s ease-out; }
  #fares[hidden] { display: none; }
  /* The row radius is the panel radius minus the panel's padding — concentric
     with the pane it sits in, rather than a second radius invented here. */
  .fare { display: grid; grid-template-columns: 14px 1fr 62px auto; align-items: center;
    gap: 9px; padding: 5px 9px; border-radius: 7px; font-size: 11.5px; color: var(--dim); }
  .fare.on { background: rgba(95,191,138,.12); color: var(--ink); }
  .fare .n { text-align: center; font-size: 10px; color: var(--faint); }
  .fare.on .n { color: var(--drop-text); }
  .fare .who { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  .fare .who b { font-weight: 400; color: var(--ink); }
  .fare .pat { height: 5px; border-radius: var(--r-pill); background: var(--track); overflow: hidden; }
  .fare .pat i { display: block; height: 100%; border-radius: var(--r-pill);
    background: var(--drop); transition: width .2s, background .2s; }
  .fare.warn .pat i { background: var(--pick); }
  .fare.crit .pat i { background: var(--bad); }
  .fare.crit { animation: pulseRing 1.1s ease-out infinite; border-radius: 7px; }
  .fare .km { font-variant-numeric: tabular-nums; font-size: 10.5px; min-width: 3.6em; text-align: right; }
  .fare.gone { opacity: .35; text-decoration: line-through; }
  #faresHint { font-size: 9.5px; letter-spacing: .14em; text-transform: uppercase;
    color: var(--faint); text-align: center; margin-top: 3px; }

  /* ---- Filature: the gap ---------------------------------------------------
     A band, not a number. The green stretch is where you want to be; the red
     ends are "he can see you" and "you have lost him". You slide along it. This
     is the single most important instrument in the game and it is four elements. */
  #gap { display: none; margin-top: 9px; }
  #mission[data-kind="filature"] #gap { display: block; }
  #mission[data-kind="filature"] #mCargo { display: none; }
  #gapTrack { position: relative; height: 9px; border-radius: var(--r-pill); overflow: hidden;
    background: linear-gradient(to right,
      var(--bad) 0%, var(--bad) 14%, var(--pick) 20%,
      var(--drop) 30%, var(--drop) 62%, var(--pick) 72%,
      var(--bad) 82%, var(--bad) 100%); opacity: .5; }
  #gapWrap { position: relative; }
  /* The needle is a lozenge — a rounded bar riding a rounded track. A square
     3x15 pin on a pill track is a splinter, and it is the element the player
     watches for the entire mission. */
  #gapPin { position: absolute; top: -3px; width: 4px; height: 15px; background: var(--ink-strong);
    border-radius: var(--r-pill);
    box-shadow: 0 0 8px var(--halo), 0 1px 3px var(--halo-solid);
    transform: translateX(-2px); transition: left .08s linear; }
  #gapLabels { display: flex; justify-content: space-between; margin-top: 4px;
    font-size: 9.5px; letter-spacing: .1em; text-transform: uppercase; color: var(--faint); }
  #gapState { color: var(--drop-text); font-size: 10px; letter-spacing: .16em; text-transform: uppercase; }
  #gapState.bad { color: var(--bad-text); }
  #gapState.warn { color: var(--pick-text); }
  /* Being made is the one moment this mission raises its voice. */
  #mission.blown { border-top-color: var(--bad-text); animation: shake .3s; }

  /* ---- Chrono: gates -------------------------------------------------------
     The counter is small; the TIME the gate just paid is not. Every game that
     tops a clock up flashes the number over the clock, because the number is
     the reward and the clock is only where it lands. */
  #gates { display: none; align-items: center; gap: 8px; font-size: 11px; color: var(--dim); }
  #mission[data-kind="chrono"] #gates { display: flex; }
  #mission[data-kind="chrono"] #mWhere { display: none; }
  #gatePips { display: flex; gap: 3px; }
  #gatePips i { width: 4px; height: 9px; border-radius: var(--r-pill); background: var(--track); }
  #gatePips i.on { background: var(--sango); box-shadow: 0 0 6px rgba(216,114,47,.6); }
  #gateBonus { position: absolute; right: 14px; top: 32px; font: 800 22px ui-monospace, Menlo, monospace;
    color: var(--drop-text); text-shadow: 0 2px 8px var(--halo); opacity: 0;
    pointer-events: none; font-variant-numeric: tabular-nums; }
  #gateBonus.pop { animation: bonusPop 1.05s ease-out; }
  @keyframes bonusPop { 0% { opacity: 0; transform: translateY(6px) scale(.8); }
                       18% { opacity: 1; transform: translateY(-2px) scale(1.12); }
                       70% { opacity: 1; transform: translateY(-8px) scale(1); }
                      100% { opacity: 0; transform: translateY(-20px) scale(.95); } }
  #mission { position: relative; }
  #mRecord { font-size: 10px; color: var(--dim); font-variant-numeric: tabular-nums; }
  #mRecord.up { color: var(--bad-text); }
  #mRecord.down { color: var(--drop-text); }

  /* ---- The garage and the cast --------------------------------------------
     Both are a .dock (css/hud.css): the column that owns the top-right of the
     screen. What is below is only the ROW — a vehicle, or a driver — and the
     row is shared too, because the two panels ask the same shape of question:
     what fits, how many seats, how invisible / who, what they are like. A list
     of names would make the choice look cosmetic, which is exactly what it is
     not. */
  .veh { display: block; width: 100%; text-align: left; background: none; border: 1px solid transparent;
    border-radius: 12px; padding: 9px 11px 10px; cursor: pointer; color: var(--dim); }
  .veh:hover { background: var(--accent-wash); border-color: transparent; }
  .veh[aria-pressed="true"] { background: var(--accent-wash-hi); color: var(--ink);
    border-color: var(--accent-edge); }
  .veh[aria-pressed="true"]:hover { background: var(--accent-wash-hi); }
  .veh .top { display: flex; align-items: baseline; gap: 8px; }
  .veh .nm { font-size: 13px; color: var(--ink); flex: 1; }
  .veh .key { font-size: 10px; color: var(--faint); }
  .veh .note { font-size: 10.5px; color: var(--dim); margin-top: 3px; line-height: 1.35; }
  .veh .axes { display: flex; gap: 12px; margin-top: 7px; font-size: 9.5px;
    letter-spacing: .1em; text-transform: uppercase; color: var(--faint); }
  .veh .axes b { font-weight: 400; color: var(--ink); font-variant-numeric: tabular-nums; }
  .veh .dots { display: inline-flex; gap: 3px; vertical-align: middle; margin-left: 4px; }
  .veh .dots i { width: 4px; height: 4px; border-radius: 50%; background: var(--track); }
  .veh .dots i.on { background: var(--sango); }
  .veh.unfit { opacity: .45; }
  .veh.unfit .nm::after { content: ' — ne convient pas'; font-size: 10px; color: var(--bad-text); }
  .veh .role { font-size: 10px; color: var(--sango-text); letter-spacing: .08em;
    text-transform: uppercase; margin-top: 2px; }
  .veh .traits { margin-top: 7px; display: flex; flex-direction: column; gap: 3px; }
  .veh .traits i { font-style: normal; font-size: 10px; color: var(--dim);
    display: flex; gap: 6px; align-items: baseline; }
  .veh .traits i::before { content: '·'; color: var(--sango-text); }

  /* ---- On foot --------------------------------------------------------------
     One line, bottom centre, that says the only thing the player cannot work
     out by looking: which key does the thing they are standing next to. It
     appears when the action is available and not before — a prompt that is
     always up is chrome, and a prompt that appears is an invitation. */
  #foot { position: fixed; left: 50%; transform: translateX(-50%); bottom: 92px;
    z-index: 44; pointer-events: none; padding: 7px 14px;
    font-size: 12px; color: var(--ink); letter-spacing: .02em;
    background: var(--panel-soft); border: 1px solid var(--edge);
    border-radius: var(--r-pill); backdrop-filter: blur(6px);
    animation: riseIn .18s var(--spring); }
  #foot[hidden] { display: none; }
  #foot kbd { font: 400 11px/1 ui-monospace, monospace; color: var(--sango-text);
    border: 1px solid var(--accent-edge); border-radius: 5px; padding: 2px 5px;
    margin-right: 7px; }
  #foot small { font-size: 11px; opacity: .62; margin-left: 6px; }
  #foot.warn { color: var(--bad-text); }
  #foot.warn kbd { display: none; }

  /* ---- The board ------------------------------------------------------------
     A garage forecourt or a filling station: the one place in the city you can
     drive to ON PURPOSE for work. The offer card answers a question somebody
     asked you; this answers a question you asked, which is why it is a LIST and
     not a card — three of them read side by side, compared, then chosen.

     Same slot as the offer card, because the two can never be up at once: the
     board only opens while idle, and taking one off it is the acceptance. */
  #board { position: fixed; left: 50%; bottom: 24px; transform: translateX(-50%);
    width: min(470px, 94vw); pointer-events: auto; z-index: 30;
    animation: riseIn .3s var(--spring); }
  #board[hidden] { display: none; }
  #board .pad { padding: 12px 15px 13px; }
  #board .head { display: flex; align-items: center; gap: 11px; }
  #board .head .ico { width: 26px; height: 26px; color: var(--sango-text); flex: none; }
  #boardList { display: flex; flex-direction: column; gap: 4px; margin-top: 12px; }
  /* A row is a button, not a div with a click handler: it is focusable, it is
     announced, and the keyboard path and the mouse path are the same path. */
  /* Three rows, and every cell placed EXPLICITLY. Auto-placement put the fare in
     the destination's column the moment one child asked for a row span, which is
     the usual way a grid of five children in three columns goes wrong. */
  .job { display: grid; grid-template-columns: 20px 1fr auto;
    grid-template-rows: auto auto auto; gap: 3px 11px; align-items: baseline;
    width: 100%; text-align: left; background: none; cursor: pointer;
    border: 1px solid transparent; border-radius: 12px; padding: 9px 11px; color: var(--dim); }
  .job:hover { background: var(--accent-wash); }
  .job[aria-pressed="true"] { background: var(--accent-wash-hi); color: var(--ink);
    border-color: var(--accent-edge); }
  .job .ico { grid-area: 1 / 1 / 4 / 2; width: 20px; height: 20px;
    align-self: center; color: var(--sango-text); }
  .job .k { grid-area: 1 / 2 / 2 / 3; font-size: 9.5px; letter-spacing: .18em;
    text-transform: uppercase; color: var(--sango-text); white-space: nowrap; }
  /* Flex rather than inline text: an inline <small> after a number in a grid
     cell sized by max-content lands on top of it as soon as the number is wide
     enough. Two flex items and a gap cannot do that. */
  .job .fare { grid-area: 1 / 3 / 2 / 4; display: flex; align-items: baseline;
    justify-content: flex-end; gap: 4px; white-space: nowrap;
    font: 700 14.5px/1 ui-monospace, Menlo, monospace; color: var(--sango-text);
    font-variant-numeric: tabular-nums; }
  .job .fare small { font-size: 9px; font-weight: 400; color: var(--dim); }
  .job .where { grid-area: 2 / 2 / 3 / 4; font-size: 12px; color: var(--ink);
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  .job .sub { grid-area: 3 / 2 / 4 / 4; font-size: 10.5px; color: var(--faint);
    font-variant-numeric: tabular-nums; }
  .job.unfit { opacity: .5; }
  .job.unfit .k::after { content: ' · véhicule'; color: var(--bad-text); }
  #boardFoot { margin-top: 11px; padding-top: 10px; border-top: 1px solid var(--edge);
    font-size: 10px; color: var(--faint); display: flex; gap: 12px; }

  /* The offer card's countdown only exists for a DISPATCHED offer. One you drove
     up to has no clock — it ends when you drive away — and a bar that never
     moves is worse than no bar, so it is removed rather than frozen. */
  #offer[data-clock="none"] #offerTrack { display: none; }
  #offer[data-clock="none"] .pad { padding-top: 15px; }

  /* ---- Palabre ---------------------------------------------------------------
     A conflict settled on the kerb — three choices, no chase. Centred and
     high, over the windscreen but under the mission bar. */
  #palabre { position: fixed; left: 50%; top: 22%; transform: translateX(-50%);
    max-width: 520px; text-align: center; padding: 14px 18px; }
  #palabre #palabreKind { font-weight: 700; letter-spacing: .08em; text-transform: uppercase;
    font-size: 12px; opacity: .8; margin-bottom: 6px; }
  #palabre #palabreText { font-size: 15px; line-height: 1.4; margin-bottom: 10px; }
  #palabre .keys { opacity: .85; font-size: 13px; }

  /* The off-screen arrow's outline: the halo, so it reads on sky and on tarmac
     in either theme. Set here rather than on the sprite, because a presentation
     attribute cannot name a token. */
  #i-edge { stroke: var(--halo-solid); }

  /* ---- Narrow screens -------------------------------------------------------
     The lab is a desktop page, but a 1,000 px window is not unusual and the
     cards must not collide with the map or the speedo when it happens. */
  @media (max-width: 1080px) {
    #wallet { bottom: 246px; }
    #offer { width: min(390px, 94vw); }
    #topcentre { width: min(460px, 92vw); }
  }
  @media (max-height: 720px) {
    #offer { bottom: 14px; }
    #wallet { bottom: 240px; }
  }

  /* ======================================================================
     A PHONE, IN LANDSCAPE.

     Two sets of rules, for two different facts:

       · A SHORT SCREEN (max-height 520 px) — a phone on its side, or a small
         window. Everything above was laid out on a 900 px-tall desktop: a
         216 px radar, a 58 px speedo, a mission bar 560 px wide and cards
         that stack 250 px up from the bottom. On 844 × 390 those four alone
         cover the road. So the instruments shrink and the cards get shorter.
       · A FINGER (body.touch, set by src/touch-controls.js) — the stick owns
         the bottom-left, the pedals the bottom-right, the menu the top-right.
         The HUD moves out of the thumbs' way: the radar to the top-left, the
         speed to the bottom-centre between the two hands, and the key sheet,
         the developer panels and the fast-travel buttons go, because there is
         no keyboard to explain and no mouse to click them.
     ====================================================================== */

  /* THE PAGE IS A GAME SURFACE. No pan, no pinch, no pull-to-refresh, no
     rubber-band: the canvas and the controls take every touch themselves
     (Pointer Events). A list that must scroll says so on itself. */
  html, body { touch-action: none; overscroll-behavior: none; }
  .dock .list, #mapResults, #boardList, #resultCard { touch-action: pan-y; }
  button { touch-action: manipulation; }

  /* The docks' close button is a finger's: a keyboard has V and C, and says so
     in the header. */
  .dock-close { display: none; }
  body.touch .dock h4 span { display: none; }
  body.touch .dock h4 { align-items: center; padding: 6px 6px 6px 16px; }
  body.touch .dock-close { display: grid; place-items: center; width: 44px; height: 44px;
    padding: 0; border-radius: 50%; font-size: 15px; letter-spacing: 0; }

  @media (max-height: 520px) {
    #kmh { font-size: 36px; }
    #kmh small { font-size: 11px; margin-left: 6px; }
    #gear { font-size: 11px; }
    #speedo { right: 162px; bottom: 12px; }
    #mapwrap { right: 12px; bottom: 10px; width: 140px; }
    #map { width: 140px; height: 140px; }
    #wallet { bottom: 158px; right: 14px; }
    #walletVal { font-size: 17px; }
    #topcentre { top: 8px; gap: 5px; width: min(420px, 58vw); }
    #streetName { font-size: 14px; }
    #district { font-size: 9.5px; margin-top: 1px; }
    #mission { padding: 7px 12px 8px; }
    #mName { font-size: 13px; }
    #mClock { font-size: 16px; }
    #mCargo, #mDwell, #gap { margin-top: 6px; }
    #nav { padding: 4px 14px 4px 9px; gap: 8px; }
    #navArrow { width: 20px; height: 20px; }
    #navDist { font-size: 13px; }
    #fares { max-height: 30vh; overflow: hidden; }
    #offer { bottom: 8px; width: min(360px, 60vw); }
    #offer .pad { padding: 8px 13px 10px; }
    #offerTrack { margin: 9px 13px 0; }
    .leg { margin-top: 7px; font-size: 11.5px; }
    #offer .meta { margin-top: 8px; padding-top: 7px; }
    #offer .acts { margin-top: 8px; }
    #offer .acts button { padding: 7px 0; }
    #board { bottom: 8px; width: min(400px, 62vw); max-height: calc(100% - 20px); overflow: auto; }
    .job { padding: 6px 10px; }
    #result { place-items: center; }
    #resultCard { max-height: calc(100% - 16px); overflow: auto; padding: 14px 18px 14px; }
    #resGrade { font-size: 46px; }
    #result.failed #resGrade { font-size: 36px; }
    #resStats { margin-top: 10px; padding-top: 8px; }
    #resLines, #resTotal { margin-top: 8px; padding-top: 8px; }
    #resTotalVal { font-size: 21px; }
    #resFoot { margin-top: 8px; }
    .arrival { bottom: 8px; width: min(420px, 62vw); }
    .arrival-head { padding: 9px 14px 7px; }
    .arrival-opt { padding: 7px 11px; }
    .arrival-body { max-height: calc(100vh - 110px); overflow: auto; touch-action: pan-y; }
    #palabre { top: 26%; max-width: min(520px, 60vw); padding: 10px 14px; }
    #palabre #palabreText { font-size: 13px; }
    .dock { top: 8px; max-height: calc(100% - 16px); }
    .dock .list { gap: 2px; }
    .cast-portrait { height: 96px !important; }
    #foot { bottom: 64px; }
    #blocked, #wading { bottom: 100px; }
    #bodywork { bottom: 140px; }
    body.card-bottom #foot { bottom: calc(8px + 172px); }
    body.card-bottom #blocked { bottom: calc(8px + 212px); }
    #toasts { top: 42%; }
    .toast { font-size: 11px; padding: 5px 13px 5px 11px; }
  }

  /* ---- A finger: where the HUD goes to leave the thumbs their corners ---- */
  body.touch #perf, body.touch #places, body.touch #bottomleft { display: none; }
  /* The radar, top-left under the notch. It is still the map's door: a tap on
     it opens the city map, exactly as a click does. */
  body.touch #mapwrap { right: auto; bottom: auto;
    left: calc(env(safe-area-inset-left, 0px) + 10px); top: calc(env(safe-area-inset-top, 0px) + 8px);
    width: 116px; }
  body.touch #map { width: 116px; height: 116px; }
  body.touch.mapfull #mapwrap { left: 50%; top: 50%; width: auto; }
  body.touch.mapfull #map { width: min(78vh, 78vw); height: min(78vh, 78vw); }
  body.touch.mapfull #maphead { flex-wrap: wrap; gap: 8px; }
  body.touch.mapfull #mapHint { display: none; }
  body.touch #mapClose { width: 44px; height: 44px; }
  /* The money under the radar, left-aligned: an instrument cluster on the
     driver's side, and nowhere near the pedals. */
  body.touch #wallet { right: auto; bottom: auto; text-align: left;
    left: calc(env(safe-area-inset-left, 0px) + 14px); top: calc(env(safe-area-inset-top, 0px) + 130px); }
  body.touch #streak { justify-content: flex-start; }
  body.touch #walletDelta { right: auto; left: 0; }
  body.touch .rank-line { align-items: flex-start; text-align: left; min-height: 44px; }
  /* The speed between the two hands, at the bottom — where a phone driving
     game puts it, because both thumbs frame it. */
  body.touch #speedo { right: auto; left: 50%; transform: translateX(-50%);
    bottom: calc(env(safe-area-inset-bottom, 0px) + 6px); text-align: center; }
  body.touch #kmh { font-size: 34px; }
  body.touch #gear { display: none; }
  /* The top strip stops short of the view / horn / menu buttons. */
  body.touch #topcentre { top: calc(env(safe-area-inset-top, 0px) + 8px); width: min(380px, 46vw); }
  /* The prompt and the status pills stack above the speed. */
  body.touch #foot { bottom: calc(env(safe-area-inset-bottom, 0px) + 106px); }
  body.touch #foot kbd { display: none; }
  body.touch #blocked, body.touch #wading { bottom: calc(env(safe-area-inset-bottom, 0px) + 148px); }
  body.touch #bodywork { bottom: calc(env(safe-area-inset-bottom, 0px) + 190px); }
  /* A status pill names its key; on glass the key is the red button by the
     pedals, so the key half of the pill goes. */
  body.touch .status .esc { display: none; }
  /* The offer and the board: the ANSWERS are the buttons by the right thumb
     (Accepter / Refuser, Prendre / Fermer), so the card keeps what it says
     and drops the key hints. The offer's own two buttons stay tappable. */
  body.touch #offer kbd, body.touch #boardFoot, body.touch #resFoot kbd { display: none; }
  body.touch #offer { width: min(340px, 44vw); bottom: calc(env(safe-area-inset-bottom, 0px) + 8px); }
  body.touch #board { width: min(360px, 46vw); bottom: calc(env(safe-area-inset-bottom, 0px) + 8px); }
  /* The arrival card sits ABOVE the speed and between the two thumbs' ground:
     narrow enough to clear the handbrake, low enough to leave the road. */
  body.touch .arrival { width: min(330px, 39vw); bottom: calc(env(safe-area-inset-bottom, 0px) + 56px); }
  body.touch .arrival-opt kbd, body.touch .arrival-hint, body.touch .arrival-tab span { display: none; }
  /* Folded, it is one line — the three ways in, elided — and the line is the
     button that unfolds it. */
  body.touch .arrival-tab { max-width: 100%; padding: 0 16px; min-height: 44px; }
  body.touch .arrival-tab b { font-size: 11px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; min-width: 0; }
  body.touch.card-bottom #foot { bottom: calc(env(safe-area-inset-bottom, 0px) + 56px + 236px); }
  /* The radar's caption (scale, district) is for the 216 px desktop radar; at
     116 px it prints over the wallet beneath it. */
  body.touch:not(.mapfull) #mapfoot { display: none; }
  body.touch .status { font-size: 11px; padding: 6px 13px; gap: 8px; }
  body.touch .status .bar { width: 60px; }
  body.touch .arrival-opt { min-height: 48px; }
  body.touch #palabre .keys { display: none; }
  body.touch #resFoot { font-size: 10px; }
  /* The docks sit over the right-hand controls while they are open — you are
     choosing, not driving — and leave the radar uncovered. */
  body.touch .dock { right: calc(env(safe-area-inset-right, 0px) + 10px); top: calc(env(safe-area-inset-top, 0px) + 8px);
    width: min(300px, 44vw); max-height: calc(100% - 16px); }
  body.touch .veh { padding: 10px 12px; }
  /* Landmark labels are words floating in the world; at phone scale fewer,
     smaller words read better than the desktop's. */
  body.touch .lbl { font-size: 10px; padding: 2px 8px; }
  /* The city map is a document read with both hands; the controls step away
     while it is open (its own ✕, the veil and Échap close it). */
  body.mapfull #touch { display: none; }
