/* ===========================================================================
   THE HUD SHELL — one shape system, one colour system, for every page.

   WHY THIS FILE EXISTS. index.html, drive-lab and car-lab each carried their
   own copy of the same base: the reset, the body type, .hud, button, kbd, the
   boot screen. Three copies drift, and they had: the accent was #d8722f on one
   page and #e2703a on the other two, the page black was #0e0d0c / #10100f /
   #16130f, and the button was written three times with three different borders.
   A shared control that looks like a different control on the next screen is
   not a style problem, it is a *product* problem — it reads as three prototypes
   rather than one game.

   THE THEMES. There are two, and a page never knows which one it is in. Every
   colour below is a TOKEN; `:root` carries the light theme and
   `:root[data-theme="dark"]` carries the dark one, and the two blocks define
   the SAME set of names (tests/pages.test.js checks it — a token that exists in
   one theme and not the other is a rule that is right by day and invisible by
   night). src/theme.js is the one writer of `data-theme`; nothing else sets a
   colour scheme, and no page, no module and no canvas painter names a colour —
   a canvas reads the same tokens through Theme.token().

   Light is the default. The world is a daylight city — pale sky, pale
   concrete, laterite, a yellow taxi — and a near-black instrument panel over it
   read as a debug overlay laid on a game rather than as the game's own
   dashboard. The dark theme is the original, kept for the player who wants the
   night-time cockpit, and `auto` follows the world's own clock.

   TWO KINDS OF COLOUR. A FILL (--sango, --pick, --drop, --bad) is the same in
   both themes: a bar, a dot, a glow, a border is brand colour and brand colour
   does not change with the wallpaper. A TEXT colour (--sango-text, --pick-text,
   --drop-text, --bad-text) is the same hue pushed to where it reads on the
   panel it sits on: the amber that glows on near-black is unreadable on paper.
   Use the -text token for words, the plain token for everything else.

   AND THE SHAPE. Every radius in the old pages was invented at the point of
   use: 1, 2, 3, 4 and 5 px, chosen per rule. At those values a corner is not
   round, it is a square with the sharpness filed off — the eye still reads a
   box, and a screen of boxes over a photographic 3D world reads as a debug
   overlay laid on a game rather than as the game's own instrument panel. There
   is no amount of colour work that fixes that; the fix is a SHAPE SCALE, used
   everywhere, with values big enough to actually be seen.

     --r-chip   a thing the size of a word — a kbd, a tag, a pip. Pill.
     --r-sm     a control or a nested block inside a panel
     --r-md     a panel
     --r-lg     a card that owns the screen — the offer, the result, the garage
     --r-pill   any track, bar, segment or meter. Never a square-ended bar:
                nothing in the physical world that fills up has square ends.

   NESTING. An inner corner concentric with its parent is inner = outer − pad.
   A 14 px block inside a 14 px panel with 10 px of padding reads WRONG — the
   gap between the two curves pinches at the corner. Every nested value here is
   derived with that subtraction rather than picked.

   EDGES. The old panels drew an opaque 1px #322c25 border. Against Douala's
   pale sky and pale concrete that is a drawn rectangle: a hard dark line with
   nothing behind it. A floating pane instead gets a translucent hairline plus a
   soft drop shadow — the border says "edge", the shadow says "above", and
   together they read as a physical object rather than a rectangle of CSS.
   =========================================================================== */

:root {
  color-scheme: light;

  /* ---- Brand and semantic FILLS. Theme-invariant. ------------------------- */
  --sango: #d8722f;          /* the brand, the money, the player */
  --sango-hi: #ec8339;
  --pick: #e8b04b;           /* the pickup leg, and every warning */
  --drop: #5fbf8a;           /* the delivery leg, and every good outcome */
  --bad: #e0503a;            /* the clock, the damage, the failure */
  --maps: #2f7fd6;           /* Sango Maps: the route, the search, the guide */
  --water: #4f8ab0;          /* the estuary, while you are in it */
  --on-accent: #14120f;      /* type printed ON a brand-coloured fill */

  /* ---- The same hues as TEXT, pushed to where they read on this panel. ---- */
  --sango-text: #b3561a;
  --pick-text: #8f6210;
  --drop-text: #257a4f;
  --bad-text: #bd3a24;
  --maps-text: #1d63b5;

  /* ---- Ink. ---------------------------------------------------------------- */
  --ink: #241d16;            /* body text */
  --ink-strong: #120d09;     /* the number you drive by: speed, distance, name */
  --ink-2: #3f362e;          /* secondary text that is still meant to be read */
  --dim: #6a6157;            /* labels, captions */
  --faint: #9b9186;          /* hints, footers, the thing you read once */
  --page: #f4efe6;           /* the boot screen, and the page under a lab */

  /* ---- Surfaces. Warm off-white at high opacity, not a neutral grey: the
     world behind them is ochre and laterite, and a cool grey pane over a warm
     scene is the classic tell of an overlay that was never art-directed. ---- */
  --panel: rgba(251, 247, 239, .92);
  --panel-soft: rgba(251, 247, 239, .78);
  --panel-strong: rgba(251, 247, 239, .97);
  --panel-faint: rgba(251, 247, 239, .55);
  --edge: rgba(58, 40, 22, .14);
  --edge-hi: rgba(58, 40, 22, .28);
  --track: rgba(58, 40, 22, .12);       /* the empty half of any bar */
  --wash: rgba(58, 40, 22, .06);        /* a row under the pointer */
  --wash-hi: rgba(58, 40, 22, .10);
  --accent-wash: rgba(216, 114, 47, .10);   /* a chosen row */
  --accent-wash-hi: rgba(216, 114, 47, .18);
  --accent-edge: rgba(216, 114, 47, .50);
  --maps-wash: rgba(47, 127, 214, .14);
  --maps-ring: rgba(47, 127, 214, .28);

  /* ---- Controls. ----------------------------------------------------------- */
  --control: rgba(255, 253, 249, .90);
  --control-hi: #ffffff;
  --kbd-bg: linear-gradient(#ffffff, #efe9dd);
  --kbd-ink: #4a4138;
  --kbd-edge: rgba(58, 40, 22, .22);

  /* ---- Over the world. Text that floats with no pane under it carries a halo
     in the OPPOSITE value to the ink, so it survives both a pale sky and a
     dark tarmac; a veil dims the game under a document; a scrim under a card. */
  --halo: rgba(255, 251, 243, .92);
  --halo-soft: rgba(255, 251, 243, .70);
  --halo-solid: #fffaf0;
  --veil: rgba(240, 234, 222, .86);
  --scrim: rgba(52, 40, 28, .50);
  --shadow-drop: rgba(40, 26, 12, .28);

  /* ---- The map's ink. What the radar and the city map DRAW over the sheet:
     the halo behind every mark, the labels, the bezel, the cone, you. The
     ground itself (water, fabric, roads) is MapSheet's palette, keyed by the
     same theme name, because a worker cannot read a stylesheet. ------------- */
  --map-accent: #c9621f;
  --map-halo: rgba(255, 252, 245, .92);
  --map-label: #2a231c;
  --map-label-2: #5a5147;
  --map-quartier: #3a2c1c;
  --map-quartier-2: #6a6055;
  --map-landmark: #3a2c1c;
  --map-compass: #3a3229;
  --map-oneway: #8f6210;
  --map-bezel: rgba(250, 246, 238, .94);
  --map-bezel-rim: rgba(58, 40, 22, .22);
  --map-tick: rgba(58, 40, 22, .40);
  --map-vig-0: rgba(236, 229, 214, 0);
  --map-vig-1: rgba(236, 229, 214, .55);
  --map-cone-0: rgba(120, 80, 30, .16);
  --map-cone-1: rgba(120, 80, 30, .07);
  --map-cone-2: rgba(120, 80, 30, 0);
  --map-cone-edge: rgba(120, 80, 30, .28);
  --map-player: #e0702a;
  --map-mark-sango: #c9621f;
  --map-mark-pick: #c98a12;
  --map-mark-drop: #2e8b5c;

  /* ---- Shape. The whole point of this file. ------------------------------- */
  --r-chip: 999px;
  --r-sm: 9px;
  --r-md: 14px;
  --r-lg: 20px;
  --r-pill: 999px;

  /* ---- Depth. Two layers: a tight contact shadow and a wide ambient one. --- */
  --lift-1: 0 2px 6px rgba(40, 26, 12, .12), 0 10px 28px rgba(40, 26, 12, .14);
  --lift-2: 0 4px 12px rgba(40, 26, 12, .16), 0 22px 60px rgba(40, 26, 12, .20);
  --lift-3: 0 8px 22px rgba(40, 26, 12, .20), 0 34px 96px rgba(40, 26, 12, .26);

  /* Motion. One easing for everything that arrives — an overshoot, because a
     card that springs reads as a thing that ARRIVED and a card that fades reads
     as a thing that was always there and you just noticed it. */
  --spring: cubic-bezier(.2, .9, .3, 1.2);
}

/* The dark theme: the original cockpit. Same names, every one of them. */
:root[data-theme="dark"] {
  color-scheme: dark;

  --sango: #d8722f;
  --sango-hi: #ec8339;
  --pick: #e8b04b;
  --drop: #5fbf8a;
  --bad: #e0503a;
  --maps: #4da3ff;
  --water: #4f8ab0;
  --on-accent: #14120f;

  --sango-text: #d8722f;
  --pick-text: #e8b04b;
  --drop-text: #5fbf8a;
  --bad-text: #f0725c;
  --maps-text: #4da3ff;

  --ink: #f2ede5;
  --ink-strong: #ffffff;
  --ink-2: #ded7cc;
  --dim: #a49b8e;
  --faint: #6f685e;
  --page: #0e0d0c;

  --panel: rgba(20, 17, 14, .90);
  --panel-soft: rgba(20, 17, 14, .74);
  --panel-strong: rgba(20, 17, 14, .97);
  --panel-faint: rgba(16, 14, 12, .50);
  --edge: rgba(240, 226, 200, .11);
  --edge-hi: rgba(240, 226, 200, .22);
  --track: rgba(240, 226, 200, .10);
  --wash: rgba(240, 226, 200, .07);
  --wash-hi: rgba(240, 226, 200, .10);
  --accent-wash: rgba(216, 114, 47, .08);
  --accent-wash-hi: rgba(216, 114, 47, .15);
  --accent-edge: rgba(216, 114, 47, .42);
  --maps-wash: rgba(77, 163, 255, .18);
  --maps-ring: rgba(77, 163, 255, .25);

  --control: rgba(34, 29, 24, .90);
  --control-hi: rgba(46, 39, 31, .92);
  --kbd-bg: linear-gradient(rgba(255, 240, 220, .10), rgba(255, 240, 220, .02)) #2a251f;
  --kbd-ink: #cdc4b8;
  --kbd-edge: rgba(240, 226, 200, .18);

  --halo: rgba(10, 9, 8, .92);
  --halo-soft: rgba(10, 9, 8, .70);
  --halo-solid: #0c0a09;
  --veil: rgba(8, 7, 6, .86);
  --scrim: rgba(8, 7, 6, .58);
  --shadow-drop: rgba(0, 0, 0, .60);

  --map-accent: #ff9d4d;
  --map-halo: rgba(12, 11, 9, .90);
  --map-label: #f4ead6;
  --map-label-2: #c9bda8;
  --map-quartier: #ffd9a8;
  --map-quartier-2: #cbbfa8;
  --map-landmark: #f0dcc0;
  --map-compass: #e6dac4;
  --map-oneway: #e8b070;
  --map-bezel: rgba(17, 15, 12, .94);
  --map-bezel-rim: rgba(240, 226, 200, .20);
  --map-tick: rgba(200, 190, 175, .45);
  --map-vig-0: rgba(12, 11, 9, 0);
  --map-vig-1: rgba(12, 11, 9, .50);
  --map-cone-0: rgba(255, 228, 190, .19);
  --map-cone-1: rgba(255, 228, 190, .09);
  --map-cone-2: rgba(255, 228, 190, 0);
  --map-cone-edge: rgba(255, 228, 190, .22);
  --map-player: #ffb066;
  --map-mark-sango: #d8722f;
  --map-mark-pick: #e8b04b;
  --map-mark-drop: #5fbf8a;

  --r-chip: 999px;
  --r-sm: 9px;
  --r-md: 14px;
  --r-lg: 20px;
  --r-pill: 999px;

  --lift-1: 0 2px 6px rgba(0,0,0,.35), 0 10px 28px rgba(0,0,0,.32);
  --lift-2: 0 4px 12px rgba(0,0,0,.42), 0 22px 60px rgba(0,0,0,.5);
  --lift-3: 0 8px 22px rgba(0,0,0,.5),  0 34px 96px rgba(0,0,0,.62);

  --spring: cubic-bezier(.2, .9, .3, 1.2);
}

* { box-sizing: border-box; }

html, body {
  margin: 0; height: 100%; background: var(--page); color: var(--ink);
  font: 13px/1.45 ui-monospace, "SF Mono", Menlo, Consolas, monospace;
  -webkit-font-smoothing: antialiased;
  -webkit-user-select: none; user-select: none;
  /* A long press on a phone is not "save image / look up". A tap is not a
     grey flash over whatever was under the finger: every control here draws
     its own pressed state. */
  -webkit-touch-callout: none;
  -webkit-tap-highlight-color: transparent;
}
/* THE VISIBLE VIEWPORT, not the largest one. On iOS `100%` of the page and
   `100vh` are the viewport with the toolbars hidden, so a canvas sized by
   them runs under the toolbar and the HUD pinned to its bottom edge is
   clipped. `dvh` is the height actually showing, and it follows the
   toolbars as they come and go. */
@supports (height: 100dvh) { html, body { height: 100dvh; } }
#cv { display: block; width: 100%; height: 100%; touch-action: none; }

/* Every floating element carries its own halo rather than relying on the
   scene behind it — the world is pale sky over pale concrete AND dark tarmac
   under a lamp, so text with no ground of its own is the DEFAULT failure here,
   not an edge case. The halo is the opposite value to the ink. */
.hud {
  position: fixed; pointer-events: none;
  text-shadow: 0 1px 2px var(--halo), 0 0 12px var(--halo-soft);
}

/* ---- The panel primitive --------------------------------------------------
   Everything that is a floating pane is one of these. Size sets the radius, so
   a readout and a card are visibly the same family without being the same box. */
.pane {
  background: var(--panel); border: 1px solid var(--edge);
  border-radius: var(--r-md); box-shadow: var(--lift-1);
}
.pane--card { border-radius: var(--r-lg); box-shadow: var(--lift-3); }
.pane--quiet { background: var(--panel-soft); border-color: transparent; box-shadow: none; }

/* ---- Controls -------------------------------------------------------------
   A pill, because a button is the one thing on screen a finger is meant to go
   to, and roundness is what makes a shape read as pressable rather than as a
   label with a line round it. */
button {
  font: inherit; font-size: 12px; cursor: pointer;
  padding: 6px 14px; border-radius: var(--r-pill);
  border: 1px solid var(--edge-hi); background: var(--control);
  color: var(--ink-2);
  transition: background .16s, border-color .16s, color .16s, transform .1s;
}
button:hover { border-color: var(--edge-hi); color: var(--ink);
  background: var(--control-hi); }
button:active { transform: translateY(1px); }
button[aria-pressed="true"] { background: var(--sango); border-color: var(--sango); color: var(--on-accent); }
button[aria-pressed="true"]:hover { background: var(--sango-hi); border-color: var(--sango-hi); color: var(--on-accent); }
button:focus-visible { outline: 2px solid var(--sango); outline-offset: 2px; }

/* A keycap. The old one was a 3px rectangle with a heavy bottom border trying
   to look like a key; at 11px type that reads as a typo, not as a key. A pill
   with an inset highlight along the top reads as a moulded cap at any size. */
kbd {
  display: inline-block; font: inherit; font-size: 10.5px; line-height: 1.6;
  min-width: 1.55em; padding: 0 6px; text-align: center;
  border-radius: var(--r-chip); color: var(--kbd-ink);
  background: var(--kbd-bg);
  border: 1px solid var(--kbd-edge);
  box-shadow: inset 0 1px 0 rgba(255,245,230,.10), 0 1px 1px rgba(0,0,0,.18);
}

/* ---- Meters ---------------------------------------------------------------
   One shape for every bar in the game. Pill track, pill fill, and the fill
   keeps its own radius so a bar at 4% is a dot rather than a sliver with two
   square corners — which is the frame where a square-ended bar looks worst. */
.meter { height: 7px; border-radius: var(--r-pill); background: var(--track); overflow: hidden; }
.meter > i, .meter > span { display: block; height: 100%; width: 0;
  border-radius: var(--r-pill); background: var(--drop); }

/* A countable meter: separate pill segments, because a smooth bar at 70% and at
   62% look identical and segments can be counted at a glance. */
.segs { display: flex; gap: 3px; height: 7px; }
.segs i { flex: 1; background: var(--track); border-radius: var(--r-pill);
  transition: background .18s; }
.segs i.on { background: var(--drop); }

/* A meter that floats on the WORLD rather than inside a pane needs its own
   ground. `--track` is a wash tuned to read against a panel; over the sky and
   the concrete it is invisible, which is exactly how drive-lab's rev bar
   disappeared. Same shape, own ground — the rule the .hud text-shadow already
   follows for the same reason. */
.meter--float { background: var(--panel);
  box-shadow: 0 1px 3px var(--shadow-drop), inset 0 0 0 1px var(--edge); }

/* A pip: a dot, always a circle. */
.pips { display: flex; gap: 3px; align-items: center; }
.pips i { width: 5px; height: 5px; border-radius: 50%; background: var(--track); }
.pips i.on { background: var(--drop); box-shadow: 0 0 6px var(--drop); }

/* ---- Sprite glyphs --------------------------------------------------------
   Round every cap and every join, globally. The sprite sheet set linejoin on
   most glyphs and linecap on almost none, so a stroked icon ended in a chopped
   square end at 1.6px width — invisible as a decision and unmistakable as a
   texture: it is what makes a set of hand-drawn icons look like clip art. */
svg [stroke], svg[stroke] { stroke-linecap: round; stroke-linejoin: round; }

/* ---- Motion ---------------------------------------------------------------- */
@keyframes riseIn { from { opacity: 0; transform: translateY(16px) scale(.97); }
                      to { opacity: 1; transform: none; } }
@keyframes popIn  { from { opacity: 0; transform: scale(.9); }
                      to { opacity: 1; transform: none; } }
@keyframes shake  { 10%, 90% { transform: translateX(-1.5px); }
                    30%, 70% { transform: translateX(2.5px); }
                    50% { transform: translateX(-3px); } }

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after { animation-duration: .001ms !important;
    animation-iteration-count: 1 !important; transition-duration: .001ms !important; }
}

/* ---- Boot ------------------------------------------------------------------ */
#boot { position: fixed; inset: 0; z-index: 90; display: grid; place-items: center;
  background: var(--page); color: var(--dim);
  font-size: 12px; letter-spacing: .14em; text-transform: uppercase; }
#boot.gone { display: none; }
/* When the boot cannot finish: a sentence a player can read, and one thing to
   do about it (src/douala-lab.js, bootFail). Not the loading line's capitals. */
.boot-fail { display: grid; gap: 10px; justify-items: center; max-width: 30em; padding: 0 24px;
  text-align: center; text-transform: none; letter-spacing: 0; font-size: 15px; line-height: 1.45; color: var(--ink); }
.boot-fail strong { font-size: 18px; color: var(--ink-strong); }
.boot-fail p { margin: 0; color: var(--dim); }
.boot-fail button { margin-top: 6px; font-size: 15px; padding: 10px 26px;
  background: var(--sango); border-color: var(--sango); color: var(--on-accent); }

/* ---- The dock ----------------------------------------------------------------
   A column that owns the top-right of the screen: the garage, the cast. They
   are the same question asked twice — what are you driving, and who is driving
   it — so they are ONE component, not two panels that happened to be written
   alike and then drifted (the cast had lost its pane and was printing straight
   onto the sky). Mutually exclusive, so sharing the slot costs nothing.

   The rows are inset cards, not full-bleed strips divided by hairlines. Eight
   full-bleed strips inside a rounded card is a rounded card with a stack of
   rectangles printed in it — and it was 800 px tall, so it ran off the bottom
   of a 900 px window with two vehicles you could never see. It is a column
   that fits the viewport and scrolls the list, the only part that can grow. */
.dock { position: fixed; right: 16px; top: 14px; width: 276px; z-index: 45;
  max-height: calc(100vh - 28px); display: flex; flex-direction: column;
  pointer-events: auto; overflow: hidden;
  animation: riseIn .22s var(--spring); }
.dock[hidden] { display: none; }
.dock h4 { margin: 0; padding: 12px 16px 10px; font: 400 10px/1 ui-monospace, monospace;
  letter-spacing: .24em; text-transform: uppercase; color: var(--sango-text);
  display: flex; justify-content: space-between; flex: none; }
.dock h4 span { color: var(--faint); letter-spacing: .1em; }
.dock .list { flex: 1; min-height: 0; overflow-y: auto; overscroll-behavior: contain;
  padding: 0 8px 2px; display: flex; flex-direction: column; gap: 3px;
  scrollbar-width: thin; scrollbar-color: var(--edge-hi) transparent; }
.dock .list::-webkit-scrollbar { width: 6px; }
.dock .list::-webkit-scrollbar-thumb { background: var(--edge-hi); border-radius: var(--r-pill); }
.dock .foot { flex: none; padding: 10px 16px 12px; font-size: 10px; color: var(--faint);
  border-top: 1px solid var(--edge); margin-top: 6px; }
.dock .link { display: block; padding: 8px 16px 4px; font-size: 11px; color: var(--sango-text);
  text-decoration: none; }
.dock .link:hover { text-decoration: underline; }

/* ---- The status pill -------------------------------------------------------
   "Something is happening to the car and here is how bad": blocked, in the
   water, bent. One shape for the three, because two different chrome languages
   for one idea is how a HUD stops being read. Centre-bottom, above the key
   hints — where the eye already is when the car has stopped. */
.status { position: fixed; left: 50%; transform: translateX(-50%);
  display: flex; align-items: center; gap: 10px;
  background: var(--panel); border: 1px solid var(--edge-hi); border-radius: var(--r-pill);
  box-shadow: var(--lift-1); padding: 8px 16px; font-size: 12px; color: var(--ink);
  animation: popIn .18s ease-out; white-space: nowrap; }
.status[hidden] { display: none; }
.status .dot { width: 7px; height: 7px; border-radius: 50%; flex: none;
  background: var(--pick); box-shadow: 0 0 9px var(--pick); }
.status b { color: var(--ink-strong); font-weight: 600; }
.status .esc { color: var(--dim); border-left: 1px solid var(--edge); padding-left: 10px; }
/* Its bar FILLS or DRAINS, so it keeps its rounded end. */
.status .bar { width: 84px; height: 5px; border-radius: var(--r-pill); overflow: hidden;
  background: var(--track); flex: none; }
.status .bar i { display: block; height: 100%; border-radius: var(--r-pill); width: 0;
  background: var(--drop); transition: width .12s linear; }
.status.bad, .status.deep { border-color: var(--bad); color: var(--bad-text); }
.status.bad .dot, .status.deep .dot { background: var(--bad); box-shadow: 0 0 9px var(--bad); }
.status.bad .bar i, .status.deep .bar i { background: linear-gradient(90deg, #b8543f, var(--bad)); }

/* ---- The MboaPlay head unit -----------------------------------------------
   A car radio, and shaped like one: a pill that is a line of text with a light
   on it. It lives here rather than in a page's <style> because it is a
   component now — src/radio-ui.js builds this markup, and a class rename in one
   file with the styles in another is the change where neither file is wrong on
   its own and the control is invisible anyway.

   IT HAS THE TWO CONTROLS A HEAD UNIT HAS: a power key and a station selector.
   It was keys only (R, then [ and ]), which is a fine shortcut and a poor
   control — nothing on screen said the strip could be touched, the station list
   was invisible until you pressed a key to find it, and on a phone there is no
   [ to press at all.

   The page it sits on owns only its PLACE and its width. */
.radio { display: flex; align-items: center; gap: 2px;
  border-radius: var(--r-pill); padding: 4px 13px 4px 5px;
  font-size: 11.5px; color: var(--ink-2); position: relative; }

/* The power key. Round, because it is the one thing here a finger aims at, and
   the lamp IS the key rather than a tell-tale sitting beside one — a head unit
   does not have a separate light for "on". */
.radio .pw { padding: 0; width: 26px; height: 26px; flex: none;
  border-radius: 50%; display: grid; place-items: center;
  background: var(--control); border: 1px solid var(--edge-hi); }
.radio .pw:hover { border-color: var(--edge-hi); background: var(--control-hi); }
/* The shared button paints aria-pressed solid orange. Here the state is the
   lamp, and a filled orange disc would read as a warning rather than as power. */
.radio .pw[aria-pressed="true"] { background: var(--control); color: inherit; }
.radio .dot { width: 7px; height: 7px; border-radius: 50%; background: var(--track);
  transition: background .18s, box-shadow .18s; }
.radio.on .pw, .radio.on .pw[aria-pressed="true"] { border-color: rgba(95,191,138,.45); }
.radio.on .dot { background: var(--drop); box-shadow: 0 0 8px var(--drop); }
/* Tuning is not silence: a set that is switched on but still finding the
   station gets a lamp that is lit and not yet green, so waiting and off are
   never the same picture — otherwise the first second of every session looks
   broken. */
.radio.tuning .dot { background: var(--pick); box-shadow: 0 0 8px var(--pick);
  animation: radioPulse 1.1s ease-in-out infinite; }
@keyframes radioPulse { 50% { opacity: .35; } }

/* The selector: the station's own name, and a caret that says there are others.
   Flat until hovered — a second bordered box inside a bordered pill is one box
   too many. */
.radio .pick { padding: 4px 9px; border-radius: var(--r-sm);
  border: 1px solid transparent; background: none; color: inherit;
  display: flex; align-items: center; gap: 7px; flex: none; }
.radio .pick:hover { background: var(--wash); border-color: var(--edge); }
.radio .pick[aria-expanded="true"] { background: var(--wash-hi);
  border-color: var(--edge-hi); }
.radio .brand { color: var(--sango-text); letter-spacing: .14em; font-size: 10px;
  text-transform: uppercase; white-space: nowrap; }
.radio .caret { color: var(--faint); font-size: 9px; transition: transform .18s; }
.radio .pick[aria-expanded="true"] .caret { transform: rotate(180deg); }
.radio .pick[disabled] { opacity: .45; }
.radio .pick[disabled] .caret { display: none; }

.radio .now { white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  padding-left: 8px; margin-left: 2px; border-left: 1px solid var(--edge);
  /* A flex item will not shrink below its content without this, so the ellipsis
     never fires and one long title pushes the pill off the screen. */
  min-width: 0; }
.radio .now b { font-weight: 400; color: var(--ink); }
.radio.off .now { color: var(--faint); }

/* ---- The dial -------------------------------------------------------------
   Above the strip, because the strip sits at the bottom of the screen and a
   list that opens downwards opens off the world. Its own panel: while it is
   open it is a card that owns the corner, not a bulge on the pill. */
/* A .pane and not a .pane--card, so the nesting rule holds with the tokens
   already here: 14 px panel − 5 px padding = a 9 px row, which is --r-sm
   exactly. At --r-lg the rows would have had to be 15 px and every corner in
   the list would have pinched against the one behind it. It keeps the card's
   shadow, though — it floats over the world, not over the strip. */
.radio .dial { position: absolute; left: 0; bottom: calc(100% + 9px);
  box-shadow: var(--lift-3);
  min-width: 20rem; max-width: 25rem; padding: 5px; z-index: 3;
  display: flex; flex-direction: column; gap: 2px;
  transform-origin: 0 100%; animation: dialIn .18s var(--spring); }
@keyframes dialIn { from { opacity: 0; transform: translateY(6px) scale(.97); } }
.radio .dial[hidden] { display: none; }
.radio .dial button { display: block; width: 100%; text-align: left;
  padding: 8px 11px; border-radius: var(--r-sm); border: 1px solid transparent;
  background: none; color: var(--dim); }
.radio .dial button:hover { background: var(--wash-hi); color: var(--ink);
  border-color: var(--edge); }
.radio .dial .nm { color: var(--ink); display: block; }
.radio .dial .tg { color: var(--faint); font-size: 10.5px; display: block;
  margin-top: 2px; white-space: normal; line-height: 1.35; }
/* The station you are on is marked on the LEFT edge, not with a tick on the
   right: the eye runs down the names, and the mark belongs where the run
   starts. Rounded ends, because a 2 px square-ended bar is a scrollbar. */
.radio .dial button[aria-checked="true"] { background: var(--accent-wash-hi);
  border-color: var(--accent-edge); box-shadow: inset 3px 0 0 var(--sango); }
.radio .dial button[aria-checked="true"] .nm { color: var(--sango-text); }

/* ---- The arrival card -----------------------------------------------------
   The first thing the game asks you: you have just landed at Douala
   International, and there are three ways into Bonanjo. It is the card the
   shape scale already names — "a card that owns the screen — the offer, the
   result, the garage" — so it is a .pane--card and nothing here re-invents a
   radius, a panel colour or a focus ring.

   It sits CENTRED and LOW rather than in the middle of the screen: the reason
   to look up is the terminal behind you and the city ahead, and a panel over
   the horizon hides the one thing the choice is about. */
.arrival { position: fixed; left: 50%; transform: translateX(-50%);
  bottom: 8vh; width: min(520px, calc(100vw - 32px)); z-index: 40; }
.arrival-card { background: var(--panel); border: 1px solid var(--edge);
  border-radius: var(--r-lg); box-shadow: var(--lift-3); overflow: hidden; }
.arrival-head { padding: 13px 16px 10px; display: grid; gap: 2px;
  border-bottom: 1px solid var(--edge); }
.arrival-head b { font-size: 15px; color: var(--ink); }
.arrival-head span { font-size: 11.5px; color: var(--dim); }
.arrival-body { padding: 7px; display: grid; gap: 4px; }

/* A row is a button, because it is one. The number is the shortcut and the
   affordance at once — the keys and the buttons are the same two commands, and
   showing one without the other is how a player learns only half the control. */
.arrival-opt { display: grid; grid-template-columns: auto 1fr auto; align-items: center;
  gap: 11px; width: 100%; text-align: left; padding: 9px 12px;
  border-radius: var(--r-sm); background: var(--panel-soft);
  border: 1px solid transparent; color: var(--ink); cursor: pointer; }
.arrival-opt:hover:not(:disabled) { border-color: var(--edge-hi); background: var(--control-hi); }
.arrival-opt.on { border-color: var(--sango); }
.arrival-opt.off, .arrival-opt:disabled { opacity: .42; cursor: default; }
.arrival-mid { display: grid; gap: 1px; }
.arrival-mid b { font-size: 13px; }
.arrival-mid small, .arrival-right small { font-size: 10.5px; color: var(--dim); }
/* The money is the brand colour everywhere else in this game; a fare is money. */
.arrival-right { display: grid; gap: 1px; justify-items: end; text-align: right; }
.arrival-right b { font-size: 12px; color: var(--sango-text); }
/* The label over a second question — which car, pilot or passenger. Quieter
   than the card's own title, because it is a question inside an answer. */
.arrival-ask { font-size: 10.5px; color: var(--dim); letter-spacing: .04em;
  text-transform: uppercase; padding: 4px 12px 1px; }
.arrival-hint { display: flex; flex-wrap: wrap; gap: 4px 14px; padding: 6px 12px 4px;
  font-size: 11px; color: var(--dim); }
/* The folded offer: one line at the same place, so it is out of the frame but
   not out of reach. It is a button, because clicking it is what it is for. */
.arrival-tab { display: flex; align-items: center; gap: 12px; margin: 0 auto;
  padding: 7px 14px; border-radius: 999px; background: var(--panel);
  border: 1px solid var(--edge); color: var(--ink); cursor: pointer; box-shadow: var(--lift-3); }
.arrival-tab:hover { border-color: var(--edge-hi); }
.arrival-tab b { font-size: 12px; }
.arrival-tab span { font-size: 10.5px; color: var(--dim); white-space: nowrap; }

/* The developer's panels, folded away with I (index.html). The key sheet
   keeps a one-line reminder so the way back is on screen. */
body.panels-off #perf { display: none; }
body.panels-off #keys { display: none; }
body.panels-off #bottomleft::after { content: "I — commandes"; display: block; margin-top: 6px; font-size: 11px; color: var(--dim); letter-spacing: .04em; }

/* ---- The touch controls ------------------------------------------------------
   Built by src/touch-controls.js into #touch, and shown only to a finger
   (body.touch). The page owns nothing here but where the rest of its HUD steps
   aside (css/douala-lab.css).

   THE LAYOUT IS THE THUMBS'. A phone in landscape is held at both ends, and
   each thumb sweeps an arc from its corner: the left one steers, the right one
   drives. So the stick lives in the left third and is born wherever the thumb
   lands; the pedals sit in the bottom-right corner where the thumb rests; the
   verbs that come and go (descendre, accepter…) stack just above them, within
   the same thumb's reach; and the things pressed once a minute — the view,
   the horn, the menu — sit in the top-right, out of the road's way.

   SIZES are the platforms' own minimums and then some: 48 px for anything a
   finger aims at (Material's 48 dp, Apple's 44 pt), pedals twice that, because
   a pedal is held for seconds while the other thumb is busy and must be found
   without looking. Every edge respects the notch and the home indicator —
   env(safe-area-inset-*), with viewport-fit=cover on the page.

   NOTHING IS BLURRED. A backdrop-filter is a per-frame GPU pass over what is
   behind it (css/douala-lab.css measured one at 27% of a frame); translucent
   panel tokens read the same on a phone for nothing. */
.tc {
  --tc-sl: env(safe-area-inset-left, 0px);
  --tc-sr: env(safe-area-inset-right, 0px);
  --tc-st: env(safe-area-inset-top, 0px);
  --tc-sb: env(safe-area-inset-bottom, 0px);
  --tc-edge: 12px;                /* gap to the screen's usable edge */
  --tc-hit: 52px;                 /* a round button */
  --tc-pedal-w: 78px;
  position: fixed; inset: 0; z-index: 42; pointer-events: none;
  -webkit-user-select: none; user-select: none;
}
.tc[hidden] { display: none; }
.tc button { touch-action: none; -webkit-user-select: none; user-select: none; }

/* The left thumb's ground. Invisible: the stick is drawn, not the zone. It
   starts below the top-left instruments and stops short of the middle, so a
   finger on the world in the middle of the screen is still the camera. */
.tc-zone { position: absolute; left: 0; bottom: 0; width: 42%;
  top: calc(var(--tc-st) + 132px); pointer-events: auto; touch-action: none; }
.tc-stick { position: absolute; width: 124px; height: 124px; border-radius: 50%;
  left: calc(var(--tc-sl) + 88px); top: calc(100% - var(--tc-sb) - 84px);
  transform: translate(-50%, -50%);
  background: var(--panel-faint); border: 1.5px solid var(--edge-hi);
  box-shadow: var(--lift-1); opacity: .55; transition: opacity .15s; }
.tc-zone.on .tc-stick { opacity: 1; transition: none; }
.tc-knob { position: absolute; left: 50%; top: 50%; width: 54px; height: 54px;
  margin: -27px 0 0 -27px; border-radius: 50%;
  background: var(--panel-strong); border: 1px solid var(--edge-hi);
  box-shadow: var(--lift-2); }
.tc-zone.on .tc-knob { background: var(--control-hi); border-color: var(--sango); }

/* ---- Round buttons: view, horn, menu, handbrake, run ---------------------- */
.tc-round { position: relative; width: var(--tc-hit); height: var(--tc-hit);
  padding: 0; border-radius: 50%; display: grid; place-items: center;
  background: var(--panel-soft); border: 1px solid var(--edge-hi); color: var(--ink);
  box-shadow: var(--lift-1); pointer-events: auto; }
.tc-round svg { width: 24px; height: 24px; }
/* The label is for a screen reader and for the one-second "what is that" —
   a caption under a 52 px glyph on a 390 px-tall screen is clutter. */
.tc-round span { position: absolute; width: 1px; height: 1px; overflow: hidden;
  clip: rect(0 0 0 0); white-space: nowrap; }
.tc-round.on, .tc-round[aria-pressed="true"] { background: var(--sango);
  border-color: var(--sango); color: var(--on-accent); }
.tc-round:hover { background: var(--panel-soft); color: var(--ink); }
.tc-round.on:hover, .tc-round[aria-pressed="true"]:hover { background: var(--sango); color: var(--on-accent); }

.tc-menu-btn { position: absolute; top: calc(var(--tc-st) + 10px);
  right: calc(var(--tc-sr) + var(--tc-edge)); }

/* The right hand: the verbs above, the pedals below, hung from the
   bottom-right corner. */
.tc-cluster { position: absolute; right: calc(var(--tc-sr) + var(--tc-edge));
  bottom: calc(var(--tc-sb) + var(--tc-edge));
  display: flex; flex-direction: column; align-items: flex-end; gap: 12px; }
.tc-pedals { display: flex; align-items: flex-end; gap: 10px; }
/* The aux group is not a row: each of its buttons is placed on the corner it
   belongs to. View and horn in the top-right beside the menu, the handbrake
   beside the brake, the run button where the gas pedal is on foot. */
.tc-aux { display: contents; }
.tc-view { position: fixed; top: calc(var(--tc-st) + 10px);
  right: calc(var(--tc-sr) + var(--tc-edge) + var(--tc-hit) + 10px); }
.tc-horn { position: fixed; top: calc(var(--tc-st) + 10px);
  right: calc(var(--tc-sr) + var(--tc-edge) + 2 * (var(--tc-hit) + 10px)); }
.tc-hand { position: fixed; width: 58px; height: 58px;
  right: calc(var(--tc-sr) + var(--tc-edge) + 2 * var(--tc-pedal-w) + 20px);
  bottom: calc(var(--tc-sb) + var(--tc-edge) + 14px); }
.tc-run { position: fixed; width: 76px; height: 76px;
  right: calc(var(--tc-sr) + var(--tc-edge) + 6px);
  bottom: calc(var(--tc-sb) + var(--tc-edge) + 12px); }

/* The pedals. Tall, because the thumb slides UP a pedal to feed in more
   (src/input.js, Input.pedal) and the fill shows how much it is giving. */
.tc-pedal { --v: 0; position: relative; width: var(--tc-pedal-w); padding: 0 0 11px;
  display: flex; flex-direction: column; align-items: center; justify-content: flex-end;
  gap: 4px; overflow: hidden;
  border-radius: 22px; background: var(--panel-soft); border: 1px solid var(--edge-hi);
  color: var(--ink); box-shadow: var(--lift-1); pointer-events: auto;
  font-size: 9.5px; letter-spacing: .12em; text-transform: uppercase; }
.tc-pedal:hover { background: var(--panel-soft); color: var(--ink); border-color: var(--edge-hi); }
.tc-pedal svg { width: 26px; height: 26px; position: relative; }
.tc-pedal span { position: relative; }
.tc-gas { height: 128px; }
.tc-brake { height: 100px; }
.tc-fill { position: absolute; left: 0; right: 0; bottom: 0; height: calc(var(--v) * 100%);
  background: var(--sango); transition: height .05s linear; }
.tc-brake .tc-fill { background: var(--bad); }
.tc-pedal.on { border-color: var(--sango); color: var(--on-accent); }
.tc-brake.on { border-color: var(--bad); }
.tc-pedal:active { transform: none; }

/* ---- The verbs that come and go -------------------------------------------
   Pills, right-aligned above the pedals, the last one nearest the thumb.
   `go` is the one to press (accepter, monter), `door` the door, `warn` the
   one that costs you (remise sur la route). */
.tc-acts { display: flex; flex-direction: column; align-items: flex-end; gap: 8px;
  max-width: 230px; }
.tc-act { min-height: 48px; min-width: 96px; padding: 0 18px; pointer-events: auto;
  border-radius: var(--r-pill); font-size: 12.5px; font-weight: 600; letter-spacing: .02em;
  background: var(--panel); border: 1px solid var(--edge-hi); color: var(--ink);
  box-shadow: var(--lift-1); animation: popIn .16s ease-out; text-align: center; }
.tc-act:hover { background: var(--panel); color: var(--ink); }
.tc-act.tc-go, .tc-act.tc-door { background: var(--sango); border-color: var(--sango); color: var(--on-accent); }
.tc-act.tc-go:hover, .tc-act.tc-door:hover { background: var(--sango-hi); color: var(--on-accent); }
.tc-act.tc-warn { border-color: var(--bad); color: var(--bad-text); }
.tc-act.on { transform: scale(.96); }

/* ---- Which controls, for who you are ---------------------------------------
   drive: stick, pedals, handbrake · walk: stick, run · boat: stick, pedals ·
   fly: stick (the cyclic), pedals (the collective) · ride: a passenger's
   hands are free — the verbs and the menu. */
.tc-run { display: none; }
.tc[data-mode="walk"] .tc-pedals, .tc[data-mode="walk"] .tc-hand { display: none; }
.tc[data-mode="walk"] .tc-run { display: grid; }
.tc[data-mode="walk"] .tc-cluster { bottom: calc(var(--tc-sb) + var(--tc-edge) + 100px); }
.tc[data-mode="boat"] .tc-hand, .tc[data-mode="fly"] .tc-hand,
.tc[data-mode="walk"] .tc-horn, .tc[data-mode="fly"] .tc-horn, .tc[data-mode="boat"] .tc-horn { display: none; }
.tc[data-mode="ride"] .tc-zone, .tc[data-mode="ride"] .tc-pedals, .tc[data-mode="ride"] .tc-hand,
.tc[data-mode="ride"] .tc-horn, .tc[data-mode="ride"] .tc-view { display: none; }

/* ---- The menu ------------------------------------------------------------------
   What you reach for once a session — the garage, the radio, the theme — as a
   card from the top-right, over the controls. Rows are 48 px: the menu is the
   one place a finger reads a list. */
.tc-menu { position: absolute; top: calc(var(--tc-st) + 10px);
  right: calc(var(--tc-sr) + var(--tc-edge)); z-index: 2;
  width: min(300px, calc(100vw - 32px)); max-height: calc(100% - var(--tc-st) - var(--tc-sb) - 20px);
  display: flex; flex-direction: column; pointer-events: auto; overflow: hidden;
  animation: riseIn .2s var(--spring); }
.tc-menu[hidden] { display: none; }
.tc-menu-head { display: flex; align-items: center; justify-content: space-between;
  padding: 8px 8px 6px 18px; flex: none; }
.tc-menu-head b { font: 400 10px/1 ui-monospace, monospace; letter-spacing: .24em;
  text-transform: uppercase; color: var(--sango-text); }
.tc-menu-close { width: 44px; height: 44px; padding: 0; border-radius: 50%; font-size: 15px; }
.tc-menu-list { overflow-y: auto; overscroll-behavior: contain; touch-action: pan-y;
  padding: 0 8px 8px; display: flex; flex-direction: column; gap: 2px; }
.tc-menu-list button { touch-action: pan-y; }
/* On its side a phone is wide and short: two columns of rows fit a dozen
   verbs in the height one column needs for six, so nothing has to scroll. */
@media (min-width: 600px) and (orientation: landscape) {
  .tc-menu { width: min(500px, calc(100vw - 32px)); }
  .tc-menu-list { display: grid; grid-template-columns: 1fr 1fr; align-content: start; }
}
.tc-item { flex: none; min-height: 48px; text-align: left; padding: 0 14px; border-radius: var(--r-sm);
  border: 1px solid transparent; background: none; color: var(--ink); font-size: 13px; }
.tc-item:hover { background: var(--wash); border-color: transparent; }
.tc-item.on { background: var(--wash-hi); }
.tc-item[aria-pressed="true"] { background: var(--accent-wash-hi); border-color: var(--accent-edge);
  color: var(--ink); }
.tc-item[aria-pressed="true"]:hover { background: var(--accent-wash-hi); border-color: var(--accent-edge); color: var(--ink); }

/* ---- Held upright --------------------------------------------------------------
   The city is driven in landscape: two thumbs need the width, and a 390 px
   wide road with a HUD on it is a corridor. In portrait the controls step
   aside and say so, rather than cramming a layout nobody can drive with. */
.tc-rotate { display: none; }
@media (orientation: portrait) {
  .tc > :not(.tc-rotate) { display: none !important; }
  .tc-rotate { position: absolute; inset: 0; pointer-events: auto; touch-action: none;
    display: grid; place-content: center; justify-items: center; gap: 10px;
    padding: 24px; text-align: center; background: var(--veil); color: var(--ink); }
  .tc-rotate b { font-size: 16px; letter-spacing: .02em; }
  .tc-rotate span { font-size: 12px; color: var(--dim); max-width: 22em; }
  .tc-phone { width: 46px; height: 76px; border: 2.5px solid var(--ink); border-radius: 10px;
    position: relative; margin-bottom: 8px; animation: tcTurn 2.4s ease-in-out infinite; }
  .tc-phone i { position: absolute; left: 50%; bottom: 6px; width: 12px; height: 2.5px;
    margin-left: -6px; border-radius: var(--r-pill); background: var(--ink); }
}
@keyframes tcTurn { 0%, 30% { transform: rotate(0); } 55%, 85% { transform: rotate(-90deg); } 100% { transform: rotate(0); } }
