:root {
  --bg: #05070d;
  --ink: #e8edf6;
  --muted: #8a94a8;
  --accent: #5ad1ff;
  --grass: #5a8f2e;   /* darker grasshopper green - join screen theme */
  --panel: rgba(10, 14, 22, 0.72);
  --line: rgba(120, 140, 170, 0.18);
  /* Extended palette - the CSS mirror of theme.js (window.Theme). These name the
     recurring gold / green / warning hues that were previously hand-typed inline
     throughout this file. Keep in sync with theme.js by hand. New CSS should
     reference these vars instead of re-typing the literal. */
  --gold: #ffd98a;          /* credits / mission target gold (Theme.nav.mission) */
  --waypoint: #3fe07a;      /* nav-console waypoint green (Theme.nav.waypoint) */
  --nav-accent: #5fd0ff;    /* nav-console cyan glow (Theme.nav.accent) */
  --warn: #ff6a5a;          /* low-fuel / error / critical red (also the fuel readout) */
  --tag-mars: #e08a64;      /* Mars region tag (Theme.target.byZone.mars) */
  --tag-open: #8fb4ff;      /* outer-system region tag (Theme.target.byZone.open) */
  /* Flight-HUD fuel droplet glyph size (em of the HUD font). The SHOP_ICONS.fuel
     droplet fills most of its 24x24 viewBox, so ~1.3em keeps it in scale with the
     credits glyph beside it. One knob; bump it to grow/shrink the fuel glyph. */
  --fuel-glyph-em: 1.3;
  /* iPhone safe-area insets exposed as custom properties so canvas-drawn overlays
     (navmap, colony) can read them via getComputedStyle and clear the notch / home bar. */
  --sat: env(safe-area-inset-top);
  --sar: env(safe-area-inset-right);
  --sab: env(safe-area-inset-bottom);
  --sal: env(safe-area-inset-left);
}

* { box-sizing: border-box; margin: 0; padding: 0; }

html, body {
  /* Lock the page to the viewport so on-screen controls can never be scrolled
     or zoomed off-screen on mobile. position:fixed + 100% w/h pins it; dvh/svh
     track iOS's dynamic toolbars (with a 100vh fallback for old browsers). */
  position: fixed;
  inset: 0;
  width: 100%;
  height: 100vh;
  height: 100svh;
  height: 100dvh;
  background: var(--bg);
  color: var(--ink);
  font-family: ui-monospace, "SF Mono", Menlo, Consolas, monospace;
  overflow: hidden;
  overscroll-behavior: none;       /* no rubber-band / pull-to-refresh scroll */
  touch-action: none;              /* no pinch-zoom / double-tap-zoom on the page */
  -webkit-text-size-adjust: 100%;  /* stop iOS auto-inflating text */
  text-size-adjust: 100%;
  -webkit-user-select: none;
  user-select: none;
}

#view {
  position: fixed;
  inset: 0;
  display: block;
  width: 100%;
  height: 100%;
  touch-action: none; /* we own all gestures (pinch-zoom); no browser scroll/zoom */
}

/* iOS: never let a tap select text, pop the callout menu, or flash a tap-highlight
   on the game surfaces (a tap in the station interior was "selecting" on iPhone).
   #interior-view owns its own pointer gestures, so kill the browser's too. */
#view, #interior, #interior-view, #touch-controls, #sandbox-ui, #dock-prompt, #hud {
  -webkit-user-select: none;
  user-select: none;
  -webkit-touch-callout: none;
  -webkit-tap-highlight-color: transparent;
}
#interior-view { touch-action: none; }

/* iOS auto-zooms the whole page when you focus an input under 16px. Keep text
   inputs at 16px so that focus zoom never triggers in the first place. */
input[type="text"], input[type="email"], input[type="password"], input:not([type]), textarea {
  font-size: 16px;
  /* The body sets `user-select: none` (to stop taps selecting the game canvas),
     which WebKit/iOS INHERITS into form fields and makes them impossible to type
     in (no cursor, no keyboard input). Re-enable selection on text inputs so the
     login email/password fields are actually editable. */
  -webkit-user-select: text;
  user-select: text;
  -webkit-touch-callout: default;
}

.hidden { display: none !important; }

/* ---------- HUD ----------
   Minimal: the three vitals top-left (credits / fuel / cargo), one font size, no
   panel chrome - a small glow so it reads over the starfield. The block is sized
   to the MENU BUTTON opposite it: cap tops of the ₡ row on the button's top edge,
   the bottom row's baseline on its bottom edge (--hud-h), rows tight between. */
#hud {
  position: fixed;
  /* Insets keep the HUD clear of the iPhone Dynamic Island / status bar in
     fullscreen, and a landscape notch on the left. */
  top: calc(12px + env(safe-area-inset-top));
  left: calc(14px + env(safe-area-inset-left));
  display: flex;
  flex-direction: column;
  --hud-h: 40px;                  /* #menubtn height (52px on touch below) */
  --hud-capf: 0.75;               /* ink cap height / font-size - JS-calibrated */
  --hud-dyf: 0.10;                /* glyph downshift / font-size - JS-calibrated */
  gap: calc((var(--hud-h) - 3 * var(--hud-capf) * 1em) / 2);
  /* Two-tier sizing: trimmer on desktop (where fixed-px chrome reads chunky on a big
     window); larger on phones (body.touch below), where the corner HUD was too small. */
  font-size: 14px;
  font-weight: 600;
  letter-spacing: 0.04em;
  /* The droplet runs smaller INSIDE the vitals grid than the tutorial chip's 1.3
     (root var): a 1.3em glyph overflows the cap-height row into the ₡ line above. */
  --fuel-glyph-em: 1.1;
}
/* Each row renders in a box exactly one cap-height tall, glyphs nudged down so the
   digit tops meet the box top and the baseline meets the box bottom - so the flex
   gap above is the TRUE visual gap and the block's edges are the TEXT's edges.
   The two ratios are font metrics CSS can't reach; game.js measures the device's
   actual HUD font once at boot (calibrateHud) and overrides the fallbacks above. */
#hud > * {
  height: calc(var(--hud-capf) * 1em);
  line-height: calc(var(--hud-capf) * 1em);
  transform: translateY(calc(var(--hud-dyf) * 1em));
}
.hud-line { display: flex; align-items: baseline; }
#hud-credits {
  color: #ffd98a;
  text-shadow: 0 1px 8px rgba(0, 0, 0, 0.7), 0 0 14px rgba(255, 217, 138, 0.18);
}
/* Fuel: the droplet sits in a fixed one-character-wide cell so it centers directly
   under the credits ₡ symbol, and the % number begins exactly where the credits
   number does (the HUD font is monospace, so one char advance lines the columns up).
   The droplet art is narrow and centered in its viewBox, so the larger SVG can
   overflow the cell without touching the number. */
.hud-fuel {
  display: inline-flex;
  align-items: center;
  font-variant-numeric: tabular-nums;
  color: #ff6a5a;
  text-shadow: 0 1px 8px rgba(0, 0, 0, 0.7);
}
.hud-fuel-ico {
  flex: 0 0 auto;
  width: 0.643em;                 /* one monospace char advance - matches the ₡ column */
  display: inline-flex;
  justify-content: center;
  align-items: center;
}
.hud-fuel-ico svg { flex: 0 0 auto; width: calc(var(--fuel-glyph-em) * 1em); height: calc(var(--fuel-glyph-em) * 1em); display: block; }
/* A little breathing room past the droplet, and tighter tracking so the digits +
   decimal read as a normal number (monospace cells otherwise leave the point
   floating with too much air around it). No vertical nudge: the cap-height row
   grid already centers the % against the droplet exactly. */
.hud-fuel-pct { margin-left: 0.08em; letter-spacing: -0.045em; }
/* ANTIMATTER: a SECOND tank reading on ITS OWN ROW between fuel and cargo (Aug 2026,
   user call - it used to ride the fuel row's right end, which kept the block three
   rows but grew it so wide while mated that the docked station sign covered it; see
   the updateAmHud comment in game.js). The row is the fuel row's twin on the column
   grid: ring glyph centered in the same one-character cell as the droplet, the %
   starting in exactly the fuel %'s column. Mated-only, so the block is one row
   taller than the menu-button span only where #hud-speed already accepts that.
   Violet (the booster accent), read-only. The ring+star is the ANTIMATTER glyph
   game-wide; the touch button's own alt glyph is the engine, not the fuel. */
#hud-am {
  display: inline-flex;
  align-items: center;
  color: #c893ff;
  text-shadow: 0 1px 8px rgba(0, 0, 0, 0.7), 0 0 14px rgba(176, 108, 255, 0.25);
}
#hud-am.hidden { display: none; }
.hud-am-ico {
  flex: 0 0 auto;
  width: 0.643em;                 /* the fuel droplet's cell - same icon center */
  display: inline-flex;
  justify-content: center;
  align-items: center;
}
.hud-am-ico svg { width: 0.92em; height: 0.92em; display: block; }
.hud-am-ico .am-ring { fill: #b06cff; }
.hud-am-ico .am-core { fill: #f2eaff; }
.hud-am-pct { margin-left: 0.08em; letter-spacing: -0.045em; }   /* the fuel %'s column */
/* Cargo: the third vital - "◆ mass/capacity t" only (the ore breakdown lives in the
   menu's cargo strip). Hidden at 0t; .full turns it credits-gold as the "go sell" cue.
   Same size as the credits/fuel rows (it used to run 13px to their 15px). */
#hud-cargo {
  display: inline-flex;
  align-items: baseline;
  font-variant-numeric: tabular-nums;
  color: #cdd6e4;
  text-shadow: 0 1px 8px rgba(0, 0, 0, 0.7);
}
/* The ◆ sits in a fixed one-character cell (same as the fuel droplet's) so the
   mass number begins in the same column as the credits/fuel numbers above; the
   glyph itself rides low/left in its monospace cell, so nudge it to optical
   center - vertically against its own digits, horizontally under the ₡/droplet. */
.hud-cargo-ico {
  flex: 0 0 auto;
  width: 0.643em;
  display: inline-flex;
  justify-content: center;
  position: relative;
  top: -0.075em;
  left: 0.02em;
}
.hud-cargo-val { margin-left: 0.08em; }   /* same start column as the fuel % */
#hud-cargo.full {
  color: #ffd98a;
  text-shadow: 0 1px 8px rgba(0, 0, 0, 0.7), 0 0 14px rgba(255, 217, 138, 0.18);
}
#hud-cargo.hidden { display: none; }
/* RELATIVISTIC SPEED - the 4th vital, and the only one that is not always-possible:
   it appears past 0.001c, which in this game means the antimatter booster and
   nothing else. Same cap-height row grid as the three above (it inherits #hud > *),
   in the booster's violet. ⚠️ Known and accepted: with this row up the HUD block is
   one row TALLER than the menu button it is otherwise pinned to span - the
   alternative was crowding the reading onto the cargo line. It is only ever up
   deep in the endgame, where nothing else is competing for that corner. */
#hud-speed {
  display: inline-flex;
  align-items: baseline;
  font-variant-numeric: tabular-nums;
  color: #c893ff;
  text-shadow: 0 1px 8px rgba(0, 0, 0, 0.7), 0 0 14px rgba(176, 108, 255, 0.2);
}
#hud-speed.hidden { display: none; }
/* Phones: bump the corner HUD up - on a small screen the desktop size reads too
   small - and span the taller touch menu button. */
body.touch #hud { font-size: 17px; --hud-h: 52px; }

/* ---------- MAP SCALE BAR (the very top, centred, above the TRACKING chip) ----------
   The cartographer's scale bar: two end ticks joined by a line, with the distance
   that span covers beside it. game.js (updateScaleBar) picks the rung and writes
   #scale-line's width; everything here is just the look.

   ⚠️ IT IS THE FIRST OCCUPANT OF THE TOP-CENTER BAND, and the band is single-file:
   the chips below it (the TRACKING chip, and the RACING / DAILY panels that stack
   under it) each add `var(--scale-h)` to their own top, which game.js publishes as
   this element's live height plus a gap - 0px whenever it is not drawn, so the band
   snaps back to exactly where it was. It is deliberately NOT placed by publishBand:
   that walk runs BELOW the chips, and this one sits above them.

   ⚠️ THE STATION SIGN IS THE ONE CHIP THAT DOES *NOT* SHIFT - the bar stands down
   for it instead (game.js gates on the sign being hidden). #dock-chip's top and
   height are calibrated to span the MENU BUTTON exactly, the same extent the HUD
   block opposite it spans (see calibrateHud / qc_hud_align); nudging the sign down
   to make room for a scale reading would break a measured alignment to buy the
   least useful reading there is - you are parked. The band is single-occupancy
   while docked, exactly as it already is for the TRACKING chip.

   Laid out INLINE (bar, then the reading) rather than stacked: it is only ~13px tall
   that way, so the chip under it moves down by half what a stacked block would cost.

   ⚠️ The measured span is #scale-line's BORDER-BOX width - the global reset makes
   every box border-box, so the two 1.5px end ticks have their OUTER edges at
   exactly 0 and `width`. That is what the label describes: outer edge to outer
   edge, the map convention. Changing the box model here silently makes the bar lie
   by the width of its own ticks. */
#scalebar {
  position: fixed;
  top: calc(8px + env(safe-area-inset-top));
  left: 50%;
  /* ⚠️ THE LINE IS CENTRED, NOT THE STRIP. --scale-half is half the drawn line
     width (published by updateScaleBar), so the element's left edge sits at
     50% - half and the line spans the screen centre symmetrically: it grows left
     and right at the same rate, and the reading keeps a fixed 9px gap off its
     right end. translateX(-50%) would centre the LINE PLUS THE LABEL, so a wider
     reading ("100 Gm" vs "1 ly") shifts the bar sideways for no reason. */
  transform: translateX(calc(-1 * var(--scale-half, 0px)));
  display: flex;
  align-items: center;
  gap: 9px;
  z-index: 6;
  pointer-events: none;            /* pure readout - never eats a tap meant for the chip */
  user-select: none;
  -webkit-user-select: none;
}
#scale-num {
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.06em;
  line-height: 1;
  /* The TRACKING chip's own quiet gray: this is a reference reading sitting directly
     above that chip, and the two should read as one quiet stack. */
  color: rgba(152, 162, 182, 0.92);
  text-shadow: 0 1px 8px rgba(0, 0, 0, 0.8);
  white-space: nowrap;
}
#scale-line {
  flex: none;
  height: 7px;
  width: 0;                        /* set per frame by game.js */
  /* The ticks rise from the line: border-left/right are the two end bars, and with
     border-box their OUTER edges are the measured span. */
  border-left: 1.5px solid rgba(152, 162, 182, 0.8);
  border-right: 1.5px solid rgba(152, 162, 182, 0.8);
  border-bottom: 1.5px solid rgba(152, 162, 182, 0.8);
  /* drop-shadow follows the drawn ALPHA (so it shadows the three borders and not a
     phantom rectangle, which is what box-shadow would do on this U shape). */
  filter: drop-shadow(0 1px 2px rgba(0, 0, 0, 0.9));
}
body.touch #scale-num { font-size: 12px; }
body.touch #scale-line { height: 8px; }
/* Never compete with the stranded pill (which takes the whole band), and stand down
   for the tutorial's own deliberately minimal lesson chrome (the approach timer's
   rule). Both are display:none, so the element's rect goes to zero and --scale-h
   follows it to 0 - the chips come straight back up with no second rule needed. */
body.fuel-stranded #scalebar,
body.in-tutorial #scalebar { display: none; }

/* ---------- Pickup / delivery toast ----------
   The pill WRAPS (July 2026, user call - the old nowrap + ellipsis cap silently ate
   the tail of any line over ~36 chars, e.g. the outer-haul pickup copy "The deep dark
   pays ▸ Ares Station - ₡14,400" on a 390px phone). Copy still wants to be short, but
   nothing is ever clipped now: short lines shrink-wrap to the same small pill they
   always were, longer ones stack onto a second/third line inside a rounded card.
   Genuinely long ANNOUNCEMENTS still belong in the rect quest-card idiom (#burn-note). */
#toast {
  position: fixed;
  /* TOP-CENTER BAND: sits under whatever chrome is up there (the station SIGN, the
     TRACKING chip, the OUT OF FUEL pill, the hull-temp banner) - game.js publishes
     that stack's live bottom as --band-bot, so ONE rule covers every occupant and
     there are no per-state variants to keep in sync. --band-bot already carries the
     safe-area inset (it comes from a real getBoundingClientRect). */
  top: max(calc(22px + env(safe-area-inset-top)), calc(var(--band-bot, 0px) + 8px));
  left: 50%;
  transform: translateX(-50%) translateY(-6px);
  padding: 9px 18px;
  box-sizing: border-box;
  /* Narrower than the viewport: a long line wraps into a readable stack instead of
     stretching edge-to-edge on a phone (and desktop reads the same shape). ~36 chars
     per line at 13px mono; the box still shrink-wraps to short copy.
     `width: max-content` is REQUIRED - this box is `left: 50%` with `right: auto`, so
     its shrink-to-fit space is only the half-viewport to the right of centre (195px on
     a 390px phone) and every long line would wrap at that instead of at the max-width.
     (The old nowrap sidestepped this by forcing max-content, then clipping.) */
  width: max-content;
  max-width: min(330px, calc(100vw - 24px));
  white-space: normal;
  overflow-wrap: break-word;
  text-align: center;
  /* Explicit line box so the radius below is an EXACT pill at one line. */
  line-height: 19px;
  background: var(--panel);
  border: 1px solid var(--line);
  /* One line = 19 + 9 + 9 + 2 = 39px tall, so 20px is the same stadium pill as the
     old 999px; 2+ lines square off into a rounded card rather than a fat oval. */
  border-radius: 20px;
  font-size: 13px;
  letter-spacing: 0.05em;
  color: var(--ink);
  backdrop-filter: blur(8px);
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.4s ease, transform 0.4s ease;
}
#toast.show {
  opacity: 1;
  transform: translateX(-50%) translateY(0);
}
/* Semantic kinds - one meaning per tint: delivered = credits moved (amber),
   loaded = action succeeded (teal), deny = refused / can't / ran out (soft red),
   launch = utility dispatch (gold), no kind = neutral notice. */
#toast.delivered { color: #ffd98a; border-color: rgba(255, 217, 138, 0.35); }
#toast.loaded { color: #6fe3c4; border-color: rgba(111, 227, 196, 0.35); }
#toast.deny { color: #ff9a8a; border-color: rgba(255, 154, 138, 0.35); }
/* Fuel-drone launch - warm amber/gold (the drone glyph colour), NOT the teal
   "loaded" tint, so the "Drone launched" toast reads as a utility dispatch. */
#toast.launch { color: #ffd23f; border-color: rgba(255, 210, 63, 0.4); }

/* ---------- Afterburner-unlock notice ----------
   The unlock announcement used to ride the pill toast and read as a wrapping OVAL
   (user call: rect, quest-dialogue idiom instead). This is a small RECT card in the
   popup-quest language - dark gradient panel, gold border, gold kicker/title - shown
   top-center at Ridge's accept (over the colony) and again on stepping back out to
   the ship (paired with the yellow ring on the BURN button, .burn-alert below). */
#burn-note,
#nav-note,
#mine-dock-note,
#deposit-note {
  position: fixed;
  /* Clears the top-center band the same way the toast does (--band-bot). */
  top: max(calc(22px + env(safe-area-inset-top)), calc(var(--band-bot, 0px) + 12px));
  left: 50%;
  transform: translateX(-50%) translateY(-8px);
  width: min(330px, calc(100vw - 28px));
  padding: 12px 16px 13px;
  box-sizing: border-box;
  text-align: left;
  background: linear-gradient(180deg, #0e131c 0%, #0a0e15 100%);
  border: 1px solid rgba(255, 210, 51, 0.55);
  border-radius: 10px;
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.4s ease, transform 0.4s ease;
  z-index: 21;
}
#burn-note.show,
#nav-note.show,
#mine-dock-note.show,
#deposit-note.show { opacity: 1; transform: translateX(-50%) translateY(0); }
/* Rides above the interior overlay like the toast does. */
body.in-colony #burn-note { z-index: 23; }
.bn-kicker { font-size: 10px; font-weight: 700; letter-spacing: 0.16em; color: #ffd98a; }
.bn-title { margin: 3px 0 5px; font-size: 16px; font-weight: 700; letter-spacing: 0.04em; color: #ffe2a6; }
.bn-body { font-size: 13px; line-height: 1.45; color: rgba(214, 224, 240, 0.95); }
.bn-body b { color: #ffd233; font-weight: 700; }
/* The mining cards name the ring's two stop zones in their DRAWN colors: green = the
   iron window, yellow (.bn-body b) = the platinum notch. (Must out-specify the
   .bn-body b gold rule above.) */
.bn-body b.bn-green { color: #60e07a; }
/* The comet card's blue notch (Aug 2026) - the HEAVY POCKET's own drawn azure. A shade
   lighter than the ring's NOTCH_INK body (70,150,255), which is too dark to read as
   text on the card's dark glass; still plainly the ring's blue, and plainly not the
   pale frost tint the ice notch was retired from. */
.bn-body b.bn-blue { color: #6fadff; }
/* The approach-timer card names its two tints in the colours the READOUT actually
   draws in (.at-gold/.at-green below) - .bn-green above is the MINING ring's green,
   a visibly different shade, so the timer card carries its own. */
.bn-body b.bn-gold { color: #ffd98a; }
.bn-body b.bn-wpgreen { color: #78e68c; }

/* ---------- Hint ---------- */
#hint {
  position: fixed;
  /* Sits ABOVE the docked oval (#dock-prompt, bottom:64px) when that's present. */
  bottom: 117px;
  left: 50%;
  transform: translateX(-50%);
  padding: 10px 18px;
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: 10px;     /* rectangular frame, not a round pill */
  font-size: 15px;
  color: var(--muted);
  transition: opacity 1.2s ease;
  pointer-events: none;   /* informational only - never swallow a pan drag */
}
#hint b { color: var(--ink); font-weight: 600; }
#hint .nw { white-space: nowrap; }   /* keep each gesture phrase unbroken; wrap only at · */
#hint.fade { opacity: 0; }

/* ---------- Connection banner ---------- */
#conn {
  position: fixed;
  /* Under the band - it used to paint straight over the TRACKING chip, which is
     exactly what is on screen when the socket drops mid-flight. game.js sets the
     live top inline (it is a band member itself, so it extends --band-bot rather
     than reading it); this is the empty-band fallback. */
  top: calc(14px + env(safe-area-inset-top));
  left: 50%;
  transform: translateX(-50%);
  padding: 6px 14px;
  background: rgba(120, 40, 40, 0.85);
  border-radius: 8px;
  font-size: 12px;
  letter-spacing: 0.05em;
}

/* "Leave the solar system to use the interstellar booster" - the quiet violet pill
   under the top-center band while a MATED stage is inside the interlock (Aug 2026
   rework: in-system the burn button is the ordinary afterburner, and this is how
   the silent swap explains itself). Built by game.js updateBoostNote; the live top
   is set inline by the band walk (placeInBand - it EXTENDS --band-bot, so it must
   never read it); this top is the empty-band fallback. */
#boost-note {
  position: fixed;
  top: calc(14px + env(safe-area-inset-top));
  left: 50%;
  transform: translateX(-50%);
  max-width: min(340px, calc(100vw - 24px));
  padding: 5px 13px;
  text-align: center;
  background: rgba(30, 16, 50, 0.82);
  border: 1px solid rgba(176, 108, 255, 0.45);
  border-radius: 14px;
  font-size: 11px;
  letter-spacing: 0.04em;
  color: rgba(216, 190, 255, 0.92);
  pointer-events: none;
  z-index: 8;
}
#boost-note.hidden { display: none; }

/* ---- #he-chip: the He-3 detector readout (Aug 2026, the frontier harvest) ----
   The rush chip's pill in the detector's own colourway: quiet gray while the
   world under you reads cold, keyed green on a find. One line, on screen only
   while a green orbit is held at an exo planet or moon with the detector wired
   in (fe-call). Placed by the band walk under whatever chrome is up. */
#he-chip {
  position: fixed;
  top: calc(14px + env(safe-area-inset-top));
  left: 50%;
  transform: translateX(-50%);
  padding: 5px 13px;
  white-space: nowrap;
  background: rgba(10, 16, 12, 0.78);
  border: 1px solid rgba(148, 164, 184, 0.42);
  border-radius: 13px;
  font: 700 12px/1 ui-monospace, monospace;
  letter-spacing: 0.14em;
  color: #9fb0c4;
  z-index: 60;
  pointer-events: none;
}
#he-chip.on {
  border-color: rgba(143, 226, 116, 0.62);
  color: #9fe27a;
  background: rgba(10, 20, 10, 0.8);
}

/* ---- #rush-chip: the RUSH delivery countdown ---------------------------------
   The top-center band, under whatever chrome is already up there (the live top is
   set inline by the band walk - placeInBand; this top is the empty-band fallback,
   the #boost-note contract). Mission GOLD, because that is the colour the nav
   marker and the tracked-mission outline already wear for the same job. One line,
   no title, no chrome beyond a hairline pill: it is on screen for the whole run.
   The clock is tabular-figure monospace so the digits do not jitter every second.
   Two escalations and no strobe: WARN at 2 min (orange), CRIT at 1 min (red + a
   slow 1.8s breath, the heat banner's rule). GONE is the blown bonus - gray, said
   once, gone in fifteen seconds. */
#rush-chip {
  position: fixed;
  top: calc(14px + env(safe-area-inset-top));
  left: 50%;
  transform: translateX(-50%);
  /* Symmetric 5px, then the ink-centering nudge (game.js calibrateRushChip) split
     across the two so the BOX height never moves - the band walk measures this rect. */
  padding: calc(5px + var(--rush-dy, 0px)) 13px calc(5px - var(--rush-dy, 0px));
  white-space: nowrap;
  background: rgba(28, 20, 6, 0.78);
  border: 1px solid rgba(255, 198, 74, 0.5);
  border-radius: 13px;
  font: 700 13px/1 ui-monospace, monospace;
  font-variant-numeric: tabular-nums;
  letter-spacing: 0.1em;
  color: #ffc94a;
  text-shadow: 0 0 10px rgba(255, 180, 40, 0.35);
  pointer-events: none;
  z-index: 8;
}
#rush-chip.warn {
  color: #ff9d3c;
  border-color: rgba(255, 157, 60, 0.6);
  background: rgba(34, 17, 4, 0.8);
}
#rush-chip.crit {
  color: #ff6a5a;
  border-color: rgba(255, 106, 90, 0.65);
  background: rgba(36, 12, 10, 0.82);
  animation: rush-breathe 1.8s ease-in-out infinite;
}
@keyframes rush-breathe { 0%, 100% { opacity: 1; } 50% { opacity: 0.58; } }
#rush-chip.gone {
  color: rgba(160, 168, 184, 0.9);
  border-color: rgba(120, 132, 152, 0.35);
  background: rgba(12, 15, 22, 0.72);
  text-shadow: none;
}
#rush-chip.hidden { display: none; }
/* Above the colony overlay, the dock-sign rule: the clock keeps running while you
   are inside doing the hand-over, which is exactly when it decides what you do. */
body.in-colony #rush-chip { z-index: 23; }

/* "APPROACHING THE SOLAR SYSTEM" - the interlock's approach warning (Aug 2026, user
   call: "when 5min or closer show warning with (x) to close"). The burn-note card
   idiom in the drive's own violet, with two differences that follow from what it is:
   it PERSISTS until it stops being true or the pilot dismisses it (so no .show fade
   cycle - game.js toggles .hidden), and it is the one card in that family that takes
   a tap, so the ✕ turns pointer events back on. Built by game.js updateBoostWarn. */
#boost-warn {
  position: fixed;
  top: max(calc(22px + env(safe-area-inset-top)), calc(var(--band-bot, 0px) + 12px));
  left: 50%;
  transform: translateX(-50%);
  width: min(330px, calc(100vw - 28px));
  padding: 12px 34px 13px 16px;   /* right pad clears the ✕ */
  box-sizing: border-box;
  text-align: left;
  background: linear-gradient(180deg, #150e21 0%, #0b0810 100%);
  border: 1px solid rgba(176, 108, 255, 0.6);
  border-radius: 10px;
  pointer-events: none;
  z-index: 21;
}
#boost-warn.hidden { display: none; }
#boost-warn .bn-kicker { color: #c9a6ff; }
#boost-warn .bn-title { color: #e0c8ff; font-size: 15px; }
#boost-warn .bn-body b { color: #d8beff; }
#boost-warn .bn-x {
  position: absolute;
  top: 5px;
  right: 5px;
  width: 26px;
  height: 26px;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 0;
  background: none;
  border: 0;
  border-radius: 6px;
  color: rgba(200, 176, 240, 0.75);
  font: inherit;
  font-size: 13px;
  line-height: 1;
  cursor: pointer;
  pointer-events: auto;
}
body.touch #boost-warn .bn-x { width: 32px; height: 32px; }   /* tap-miss floor */
#boost-warn .bn-x:active { color: #fff; }

/* ---------- Join screen ---------- */
#join {
  position: fixed;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--bg); /* plain black - no glow behind the launch card */
}
.join-card {
  text-align: center;
  padding: 40px 44px;
  width: min(92vw, 380px);
}
.join-card h1 {
  font-size: 30px;
  font-weight: 700;
  letter-spacing: 0.04em;
  color: var(--grass);
  text-shadow: 0 0 26px rgba(143, 194, 74, 0.40);
  margin-bottom: 6px;
  white-space: nowrap;
}
.join-card .tag {
  color: var(--muted);
  font-size: 13px;
  line-height: 1.7;
  margin-bottom: 26px;
}
#name {
  width: 100%;
  padding: 12px 14px;
  font: inherit;
  font-size: 15px;
  text-align: center;
  color: var(--ink);
  background: rgba(255, 255, 255, 0.04);
  border: 1px solid var(--line);
  border-radius: 10px;
  outline: none;
  margin-bottom: 12px;
}
/* 16px keeps iOS Safari from auto-zooming the page when the field is focused. */
@media (pointer: coarse) { #name { font-size: 16px; } }
#name:focus { border-color: var(--grass); }
#launch {
  width: 100%;
  padding: 13px;
  font: inherit;
  font-size: 15px;
  font-weight: 600;
  letter-spacing: 0.08em;
  color: #0c1505;
  background: var(--grass);
  border: none;
  border-radius: 10px;
  cursor: pointer;
  transition: transform 0.08s ease, filter 0.15s ease;
}
#launch:hover { filter: brightness(1.08); }
#launch:active { transform: scale(0.98); }

/* ---------- Reopen screen: Continue / Start new ---------- */
/* #continue reuses the primary green button styling (see the shared rule below). */
#continue {
  width: 100%;
  padding: 13px;
  font: inherit;
  font-size: 15px;
  font-weight: 600;
  letter-spacing: 0.08em;
  color: #0c1505;
  background: var(--grass);
  border: none;
  border-radius: 10px;
  cursor: pointer;
  transition: transform 0.08s ease, filter 0.15s ease;
  margin-bottom: 10px;
}
#continue:hover { filter: brightness(1.08); }
#continue:active { transform: scale(0.98); }
/* Secondary / destructive: a muted "ghost" button. */
.ghost-btn {
  width: 100%;
  padding: 10px;
  font: inherit;
  font-size: 12.5px;
  letter-spacing: 0.04em;
  color: rgba(138, 148, 168, 0.85);
  background: transparent;
  border: 1px solid var(--line);
  border-radius: 10px;
  cursor: pointer;
  transition: color 0.15s ease, border-color 0.15s ease;
}
.ghost-btn:hover { color: #ff8a7a; border-color: rgba(255, 106, 90, 0.5); }
/* Sandbox entry on the join card: a calmer, cricket-teal accent (not the red of
   Start-new), with a little breathing room above it. */
.sandbox-btn { margin-top: 14px; }
.sandbox-btn:hover { color: #6fe3c4; border-color: rgba(111, 227, 196, 0.5); }

/* ---------- Cricket select (post-login landing) ----------
   Minimal: the green wordmark + a stack of big, tappable buttons. Mobile-first. */
#cricket-select {
  position: fixed;
  inset: 0;
  z-index: 41;                 /* above the intro/join, below the gate (60) */
  background: var(--bg);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: max(28px, env(safe-area-inset-top), env(safe-area-inset-bottom)) 22px max(28px, env(safe-area-inset-top), env(safe-area-inset-bottom));   /* SYMMETRIC (the bigger inset both ends), so the column sits at true screen centre, where the app launch image draws it */
}
#cs-stars { position: absolute; inset: 0; width: 100%; height: 100%; display: block; z-index: 0; }
.cs-inner { position: relative; z-index: 1; width: min(92vw, 340px); display: flex; flex-direction: column; text-align: center; }
.cs-title {
  font-size: 32px; font-weight: 700; letter-spacing: 0.03em;
  color: #7ed957; text-shadow: 0 0 28px rgba(126, 217, 87, 0.4);
  margin-bottom: 28px; white-space: nowrap;
}
.cs-btn {
  width: 100%; padding: 15px; margin-bottom: 12px;
  font: inherit; font-size: 15px; font-weight: 600; letter-spacing: 0.04em;
  color: var(--ink); background: rgba(255, 255, 255, 0.05);
  border: 1px solid var(--line); border-radius: 12px; cursor: pointer;
  transition: filter 0.15s ease, border-color 0.15s ease, transform 0.08s ease;
}
.cs-btn:hover { border-color: rgba(143, 194, 74, 0.5); }
.cs-btn:active { transform: scale(0.985); }
.cs-btn.cs-primary { color: #0c1505; font-weight: 700; background: #6fcf57; border: none; }
.cs-btn.cs-primary:hover { filter: brightness(1.08); }
#cs-resume-name { font-weight: 700; }
/* Quiet "Log out" pinned to the BOTTOM of the screen - sits well apart from the cricket
   actions (which stay vertically centered), low-emphasis. */
.cs-logout {
  position: absolute; left: 50%; transform: translateX(-50%);
  bottom: max(22px, env(safe-area-inset-bottom)); z-index: 1;
  padding: 10px; font: inherit; font-size: 13px; letter-spacing: 0.04em;
  color: var(--muted, #8a99a6); background: transparent; border: none; cursor: pointer;
  opacity: 0.75; transition: opacity 0.15s ease, color 0.15s ease;
}
.cs-logout:hover { opacity: 1; color: var(--ink); text-decoration: underline; }

/* ---------- Sandbox controls (right edge, sandbox mode only) ---------- */
#sandbox-ui {
  position: fixed;
  right: 12px;
  top: 50%;
  transform: translateY(-50%);
  z-index: 14;
  display: flex;
  flex-direction: column;
  gap: 14px;
  width: 168px;
  padding: 14px 14px 16px;
  background: rgba(10, 16, 22, 0.82);
  border: 1px solid rgba(111, 227, 196, 0.28);
  border-radius: 12px;
  box-shadow: 0 8px 30px rgba(0, 0, 0, 0.5);
  backdrop-filter: blur(4px);
  font-size: 11px;
  letter-spacing: 0.06em;
}
#sandbox-ui .sb-tag {
  text-align: center;
  font-weight: 700;
  letter-spacing: 0.22em;
  color: #6fe3c4;
  opacity: 0.9;
}
#sandbox-ui .sb-row { display: flex; flex-direction: column; gap: 7px; }
#sandbox-ui .sb-label { color: rgba(200, 214, 230, 0.92); font-weight: 600; }
#sandbox-ui input[type="range"] { width: 100%; accent-color: #6fe3c4; cursor: pointer; }
#sandbox-ui select {
  width: 100%;
  padding: 7px 8px;
  font: inherit;
  font-size: 12px;
  color: #d6e0f0;
  background: rgba(6, 10, 16, 0.9);
  border: 1px solid rgba(111, 227, 196, 0.3);
  border-radius: 8px;
  cursor: pointer;
}
/* On a phone the right-pinned panel sits clear of the bottom touch thrust button. */
body.touch #sandbox-ui { top: calc(16px + env(safe-area-inset-top)); transform: none; width: 150px; }
.join-card .fine {
  margin-top: 18px;
  color: rgba(138, 148, 168, 0.6);
  font-size: 11px;
  letter-spacing: 0.03em;
}

/* ---------- Launch loading screen ----------
   A calm moving-starfield + "loading…", raised the instant a launch is committed and held
   until the world has spawned and the camera is parked on the ship, then cross-faded off the
   live game. Replaces the bare "entering orbit…" hold and removes the old Sun-at-origin flash
   between the menu and the game. */
#loading {
  position: fixed;
  inset: 0;
  background: var(--bg);
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 50;                 /* above the intro (40) + join, below the login gate (60) */
  /* EXACTLY the gate's padding (and the select screen's): on a phone the notch pads the
     gate's top by ~59px and the home bar its bottom by ~34px, which shifts its centred
     column down by half the difference - without the same padding here the wordmark
     jumped ~12px at the gate -> loading hand-over (Sep 2026 user report). */
  padding: max(28px, env(safe-area-inset-top), env(safe-area-inset-bottom)) 22px max(28px, env(safe-area-inset-top), env(safe-area-inset-bottom));   /* SYMMETRIC (the bigger inset both ends), so the column sits at true screen centre, where the app launch image draws it */
  box-sizing: border-box;
  opacity: 1;
  transition: opacity 0.45s ease;
}
#loading.fade-out { opacity: 0; pointer-events: none; }   /* let input reach the game during the fade */
#loading-stars { position: absolute; inset: 0; width: 100%; height: 100%; display: block; z-index: 0; }
/* The screen wears the GATE's two lines (.gate-title + .gate-resolving, defined with
   the gate below) so the gate -> loading hand-over is pixel-identical: same wordmark,
   same "loading" with the same stepping dots. The old pulsing 15px label is retired. */
.loading-inner { z-index: 1; flex-shrink: 0; }   /* the gate's column keeps its 350px inside the side padding; so does this one */
.loading-label { position: relative; }

/* ---------- Docked UI (the colony's own elements, worn outside) ----------
   Top-center chip = the STATION SIGN: dark 72% panel, station-accent border (set
   inline from interiorTheme), grass-light name, letter-spaced muted "ORBITING
   <host>" sub. ONE element + one function (game.js updateStationSign), also shown
   INSIDE the colony (body.in-colony raises it above the overlay, fuel-chip style)
   - the colony's old canvas drawTitleChip is retired.
   Bottom buttons = drawBottomAction's recipe: vertical
   gradient, glow, dark text, stacked upward from the primary. Same station voice
   on both sides of the airlock (inside: BOARD SHIP; outside: ENTER STATION). */
#dock-chip {
  position: fixed;
  top: calc(12px + env(safe-area-inset-top));
  left: 50%;
  transform: translateX(-50%);
  /* Exactly the menu button's height (52px on touch below), so the sign's bottom
     edge lines up with the button's; content centered in it. */
  height: 40px;
  padding: 0 22px;
  display: flex;
  flex-direction: column;
  justify-content: center;
  text-align: center;
  background: rgba(10, 14, 22, 0.72);
  border: 1px solid rgba(120, 140, 170, 0.35);   /* re-tinted per station from JS */
  border-radius: 14px;
  backdrop-filter: blur(8px);
  z-index: 8;
  pointer-events: none;
}
#dock-chip-name {
  font-size: 16px;
  font-weight: 700;
  line-height: 1;
  letter-spacing: 0.02em;
  color: #c7e6a0;
  white-space: nowrap;
}
.dock-chip-sub {
  margin-top: 3px;
  font-size: 9px;
  font-weight: 600;
  line-height: 1;
  letter-spacing: 0.32em;
  color: rgba(138, 148, 168, 0.9);
}
body.touch #dock-chip { height: 52px; }

/* The TRACKING chip (#frame-chip): the dock-sign slot, worn in FLIGHT. JUST quiet
   gray text (user call - no panel, no border, no fill): the body the coast-
   prediction line is framed on; when other frames qualify the side chevrons
   appear and a tap cycles onto them (game.js TRACKING-FRAME SWITCHER). The
   element keeps the dock sign's height/padding purely as a finger-sized hit
   target - the visual is the text alone. */
#frame-chip {
  position: fixed;
  top: calc(12px + var(--scale-h, 0px) + env(safe-area-inset-top));
  left: 50%;
  transform: translateX(-50%);
  height: 40px;
  padding: 0 16px;
  display: flex;
  flex-direction: column;
  justify-content: center;
  text-align: center;
  z-index: 8;
  cursor: default;
  user-select: none;
  -webkit-user-select: none;
  -webkit-tap-highlight-color: transparent;
}
body.touch #frame-chip { height: 52px; }
#frame-chip.sw { cursor: pointer; }
#frame-chip-name {
  font-size: 13px;
  font-weight: 600;
  line-height: 1;
  letter-spacing: 0.08em;
  color: rgba(152, 162, 182, 0.92);
  white-space: nowrap;
}
/* Brackets are ALWAYS worn, tight to the name, no spaces (user call, even for a
   lone Sun/Earth frame): "[EARTH]" when fixed, "<EARTH>" when switchable - the
   glyph swap lives in game.js updateFrameChip. */
#frame-chip .fc-arr { color: rgba(152, 162, 182, 0.5); }
/* SWITCHABLE (a tap has somewhere to go): the brackets go angled "<EARTH>" (the
   tap-to-cycle affordance), the gray goes a shade LIGHTER, plus the pointer cursor. */
#frame-chip.sw #frame-chip-name { color: rgba(205, 214, 230, 0.98); }
#frame-chip.sw .fc-arr { color: rgba(205, 214, 230, 0.7); }
/* The OUT OF FUEL pill takes the whole band while stranded - the chip yields. */
body.fuel-stranded #frame-chip { display: none; }

#dock-prompt {
  position: fixed;
  bottom: 64px;
  left: 50%;
  transform: translateX(-50%);
  display: flex;
  flex-direction: column;    /* extras stack UPWARD off the primary, colony-style */
  align-items: center;
  gap: 10px;
  z-index: 8;
}
/* On touch (iPhone) the buttons sit just above the thrust/turn controls: the
   76px-tall .tc-btn row is anchored at bottom max(22px, safe-inset).
   WIDTH IS THE TOUCH ROW'S OWN GRID (Aug 2026, user call - "shrink width so left
   and right edges align with edges of buttons below"): the column is anchored to
   --tc-slot2 on BOTH sides, i.e. exactly the left edge of the second touch button
   and the right edge of the third, so an action button and the row beneath it read
   as one stack instead of overhanging each gap by ~12px. Anchoring per side (rather
   than centring a computed width) is also what keeps it honest in landscape, where
   the two safe-area insets differ and the aligned span's centre is NOT the
   viewport's. Desktop is untouched - there is no button row down there to align to,
   so .dock-act keeps its shrink-to-fit 184px pill. */
body.touch #dock-prompt {
  bottom: calc(max(22px, env(safe-area-inset-bottom)) + 76px + 16px);
  left: calc(env(safe-area-inset-left) + var(--tc-slot2));
  right: calc(env(safe-area-inset-right) + var(--tc-slot2));
  transform: none;
  align-items: stretch;
}
/* The label WRAPS rather than clipping (the #toast rule): every ordinary label fits
   one line inside the aligned span - down to a 320px phone, which is what the 6px
   gutter buys (the widest one-liner, "RELEASE BOOSTER", measures 139px against the
   143px that leaves) - and the one outlier, the gas plant's disabled "ALLOCATION
   COLLECTED" at 185px, takes two lines inside the same 46px box rather than losing
   its tail or dragging the whole family down a font size. The gutter is a WRAP
   FLOOR, not the gap you see: the box is fixed and the text is centred, so a short
   label still sits in the middle of it. */
body.touch #dock-prompt .dock-act {
  width: 100%;
  min-width: 0;
  padding: 0 6px;
  white-space: normal;
  line-height: 1.15;
}
/* The colony bottom-action button (drawBottomAction), green BOARD-SHIP flavor:
   #3fcf6a lightened/darkened gradient, lit border, glow, near-black text. */
.dock-act {
  display: block;
  min-width: 184px;
  height: 46px;
  padding: 0 26px;
  font: inherit;
  font-size: 14px;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-align: center;
  white-space: nowrap;
  color: #08160d;
  background: linear-gradient(180deg, #5ad67f 0%, #32a655 100%);
  border: 1.5px solid rgba(130, 224, 158, 0.85);
  border-radius: 11px;
  box-shadow: 0 0 16px rgba(63, 207, 106, 0.5);
  cursor: pointer;
  touch-action: none;          /* pointerdown drives hold-to-mine on touch */
  -webkit-tap-highlight-color: transparent;
  -webkit-user-select: none;
  user-select: none;
  transition: filter 0.12s ease, transform 0.08s ease, opacity 0.12s ease;
}
.dock-act:hover { filter: brightness(1.07); }
.dock-act:active { transform: scale(0.97); }
.dock-act.disabled { opacity: 0.45; filter: saturate(0.4); cursor: default; }
.dock-act.disabled:hover { filter: saturate(0.4); }
.dock-act.hidden { display: none; }
/* THE INTERSTELLAR BOOSTER's bottom actions (ENGAGE / FUEL / RELEASE): the same
   .dock-act shape in the stage's own VIOLET, so they never read as "board the
   station". Violet is the one unowned HUD accent (see the #tc-boost
   block) and it is the colour the button icon and the world glyph both wear. */
.dock-act.boost-act {
  color: #150a24;
  background: linear-gradient(180deg, #c893ff 0%, #8a4ce0 100%);
  border-color: rgba(200, 147, 255, 0.85);
  box-shadow: 0 0 16px rgba(176, 108, 255, 0.5);
}
/* Held mine button: label swaps to MINING… and the glow breathes. */
.dock-act.mining { animation: mine-pulse 0.9s ease-in-out infinite; }
@keyframes mine-pulse {
  0%, 100% { box-shadow: 0 0 12px rgba(63, 207, 106, 0.35); }
  50% { box-shadow: 0 0 26px rgba(63, 207, 106, 0.75); }
}
/* ---------- Station interior ("the colony") overlay ----------
   A fullscreen top-down cutaway of the docked station, crew crickets walking
   around. The scene (incl. its title chip + all action buttons) is drawn on the
   canvas by colony.js; the DOM here is just the canvas. You leave only by
   walking out (board your ship) - there is no close button or Esc exit. */
#interior {
  position: fixed;
  inset: 0;
  z-index: 22;            /* above the shop (20) */
  background: #05070d;
  animation: interiorFade 0.45s ease;
}
@keyframes interiorFade { from { opacity: 0; } to { opacity: 1; } }
#interior-view { display: block; width: 100%; height: 100%; }
/* Inside the colony, the fullscreen interior overlay (z 22) would cover the page
   HUD. Raise the REAL HUD + menu button above it (but still below the menu/shop/
   market at 24 and popups at 45) so flight and station share ONE HUD - no canvas
   redraw. body.in-colony is toggled by openInterior/closeInterior in game.js. */
body.in-colony #hud, body.in-colony #menubtn { z-index: 23; }
/* The station SIGN (#dock-chip) rides above the colony the same way - one element
   + one function (game.js updateStationSign) on both sides of the airlock, so the
   sign inside the station matches the docked one exactly. */
body.in-colony #dock-chip { z-index: 23; }
/* Toasts ride above the colony too - refine/sell at the fuel-bay service counter
   confirm through the same toast channel as everywhere else. */
body.in-colony #toast { z-index: 23; }

/* Credits "bump" when the HUD balance changes. */
@keyframes credBump {
  0%   { transform: scale(1); }
  35%  { transform: scale(1.22); color: #9ad36a; }
  100% { transform: scale(1); }
}
#hud-credits.bump { display: inline-block; animation: credBump 0.5s ease; }

/* ---------- On-screen touch controls (mobile) ----------
   Fixed buttons: rotate ◄ ► under the left thumb, THRUST under the right.
   Discrete rotate/thrust maps 1:1 onto the boolean input and lets you pre-aim
   then burn without a finger over the target. Hidden entirely off touch; on
   touch they appear with the rest of the HUD at reveal (JS removes .hidden).
   The container is click-through (pointer-events:none); only the buttons grab
   input, so the canvas still receives pinch-to-zoom between them. */
#touch-controls { display: none; }
body.touch #touch-controls:not(.hidden) {
  display: block;
  position: fixed;
  inset: 0;
  z-index: 5;
  pointer-events: none;
}
.tc-btn {
  position: fixed;
  bottom: max(22px, env(safe-area-inset-bottom));
  display: flex;
  align-items: center;
  justify-content: center;
  width: 76px;
  height: 76px;
  font: inherit;
  font-size: 15px;
  font-weight: 600;
  letter-spacing: 0.08em;
  color: var(--ink);
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: 16px;
  backdrop-filter: blur(8px);
  pointer-events: auto;
  touch-action: none;
  -webkit-tap-highlight-color: transparent;
  -webkit-user-select: none;
  user-select: none;
  -webkit-touch-callout: none; /* no iOS long-press selection/callout on the buttons */
  outline: none;
  cursor: pointer;
  /* Press feedback is colour ONLY - no size change (the buttons must not shrink on tap). */
  transition: background-color 0.1s ease, border-color 0.1s ease;
}
.tc-btn.pressed {
  background: rgba(90, 209, 255, 0.22);
  border-color: var(--accent);
}
/* Rotate arrows drawn as CSS border-triangles so the glyph is PERFECTLY centered
   in the button (a text "◄"/"►" sat off-center due to its font side-bearing). */
.tc-arrow::before {
  content: "";
  width: 0;
  height: 0;
  border-top: 13px solid transparent;
  border-bottom: 13px solid transparent;
}
#tc-left::before  { border-right: 19px solid var(--ink); margin-right: 3px; }
#tc-right::before { border-left: 19px solid var(--ink); margin-left: 3px; }

/* THRUST shown as a fire glyph (inline SVG, not text → nothing to select). */
.tc-flame {
  width: 34px;
  height: 34px;
  display: block;
  fill: #ff8a3d;
  pointer-events: none;       /* taps fall through to the button, never the svg */
  -webkit-user-select: none;
  user-select: none;
}
#tc-thrust.pressed .tc-flame { fill: #ffd24a; }

/* AFTERBURNER (slot 3): an ORANGE flame (same orange as THRUST - the burner reads as
   the burner via its dressing, not a different flame colour) whose inner cutout burns
   a DEEP BLUE (the hottest part of the plume - .burn-core fills the glyph's hole),
   ringed by a THIN yellow glow; subtle ×N max-boost tag top-right in the same yellow.
   Hidden until the Afterburner upgrade is bought (updateBurnBtn in game.js).
   The BUTTON itself stays the neutral dark panel (like the other controls) - only the
   flame + its glow carry the colour; held just brightens the flame, and the core
   lightens but must STAY clearly blue (a pale core reads as white at button size). */
.tc-flame-burn { filter: drop-shadow(0 0 3.5px rgba(255, 216, 74, 0.8)); }
.tc-flame-burn .burn-outer { fill: #ff8a3d; }
.tc-flame-burn .burn-core { fill: #041a4e; }
#tc-burn.pressed { background: rgba(90, 209, 255, 0.14); border-color: rgba(255, 210, 63, 0.7); }
#tc-burn.pressed .tc-flame-burn { filter: drop-shadow(0 0 5px rgba(255, 224, 110, 1)); }
#tc-burn.pressed .burn-outer { fill: #ffa35c; }
#tc-burn.pressed .burn-core { fill: #2a5cd6; }
/* INTERSTELLAR BOOSTER (#tc-boost - its OWN button since the Aug 2026 split, one
   row ABOVE THRUST; it used to wear #tc-burn via a .boost-stage glyph swap):
   a thin VIOLET containment ring around a WHITE-HOT annihilation star - geometric
   where the flame is organic, so the two never read as each other. Button stays
   the neutral dark panel; only ring + star + glow carry the color. Held = the
   trap opens: star flashes full white, ring lightens, glow widens - the core
   BRIGHTENS where the flame's core stayed dark (the deliberate inversion).
   Violet is the one unowned HUD accent (thrust orange / mission gold / waypoint
   green / fuel red / freight teal are all taken) and it already exists in-world
   in Theme.PAINTS, so it reads native rather than foreign. */
.tc-star-boost { filter: drop-shadow(0 0 3.5px rgba(176, 108, 255, 0.8)); }
.tc-star-boost .boost-ring { fill: #b06cff; }
.tc-star-boost .boost-core { fill: #f2eaff; }
/* The THRUST column, stacked one button-height + 10px above (the #tc-dbgburn
   idiom, same z bump clear of the control bar); a SIBLING of #touch-controls so
   the drive keeps a visible face on desktop too. Shown only while ARMED
   (updateBoostBtn), so a button you can see is a drive that will light. */
#tc-boost {
  bottom: calc(max(22px, env(safe-area-inset-bottom)) + 86px);
  right: calc(env(safe-area-inset-right) + var(--tcm));
  z-index: 6;
}
#tc-boost.pressed { background: rgba(176, 108, 255, 0.12);
                    border-color: rgba(200, 147, 255, 0.7); }
#tc-boost.pressed .tc-star-boost {
  filter: drop-shadow(0 0 6px rgba(216, 178, 255, 1)); }
#tc-boost.pressed .boost-ring { fill: #c893ff; }
#tc-boost.pressed .boost-core { fill: #ffffff; }
/* Endgame-only control, but the tutorial/replay belt-and-braces hide still
   applies (updateBoostBtn also gates both). */
body.in-tutorial #tc-boost,
body.in-replay #tc-boost { display: none; }


/* Attention ring when the unlock notice repeats out at the ship (flashBurnBtn in
   game.js adds/removes the class) - runs the popup timing: 3 s of quick pulses,
   then the ring fades out over 0.4 s (the box-shadow transition below). THIN and
   YELLOW (the burner's own glow gold, user call - the fat red version read as an
   alarm), so it points at the button in the button's own colour language. */
@keyframes burnAlertPulse {
  0%, 100% { box-shadow: 0 0 0 1px rgba(255, 210, 51, 0.85), 0 0 8px 1px rgba(255, 216, 74, 0.35); }
  50% { box-shadow: 0 0 0 2px rgba(255, 210, 51, 0.95), 0 0 14px 3px rgba(255, 216, 74, 0.5); }
}
/* box-shadow eases so the ring's removal is a fade, not a snap; background/border
   keep the .tc-btn 0.1s press feedback. */
#tc-burn { transition: background-color 0.1s ease, border-color 0.1s ease, box-shadow 0.4s ease; }
#tc-burn.burn-alert {
  border-color: rgba(255, 210, 51, 0.9);
  animation: burnAlertPulse 0.5s ease-in-out 6;
  box-shadow: 0 0 0 1.5px rgba(255, 210, 51, 0.9), 0 0 10px 2px rgba(255, 216, 74, 0.4);
}
.tc-mult {
  position: absolute;
  top: 6px;
  right: 8px;
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.03em;
  color: rgba(255, 220, 130, 0.85);
  pointer-events: none;
}
/* The booster's tag in its own accent (the ×N gold belongs to the burner).
   ⚠️ MUST sit AFTER .tc-mult: same specificity, so the later rule wins - declared
   above it (as it was until Aug 2026) the amber silently took the tag back and the
   booster's tag rendered in the afterburner's colour. .tc-mult-dbg below is the
   same shape and has always been on this side of the line. */
.tc-mult-c { color: rgba(200, 160, 255, 0.9); font-style: normal; }

/* FOUR-SLOT GRID: one spacing rule - the SAME margin at the left edge, between every
   button, and at the right edge. m = (width - 4·76px) / 5, clamped so tablets keep
   thumb-reach corner clusters instead of a full-width spread. Steering = slots 1-2,
   AFTERBURNER = slot 3 (anchored off the RIGHT edge so it hugs THRUST even when the
   clamp kicks in), THRUST = slot 4. Slots are fixed, so nothing shifts when the
   burner unlocks. Safe-area insets keep a landscape notch / home bar clear. */
/* --tcm lives on :root, not #touch-controls, so the APPROACH TIMER (a sibling of the
   control bar - it must draw on desktop too, where #touch-controls is display:none)
   can sit on the afterburner's exact slot with the same one spacing rule. */
/* --tc-slot2 is the SLOT-2 offset from a side: the distance from the left edge to
   the LEFT edge of the second button (and, mirrored, from the right edge to the
   RIGHT edge of the third). It is the one expression the bottom-center action
   buttons (.dock-act / #deploy-btn) anchor to, so their edges line up with the
   inner pair of touch buttons BY CONSTRUCTION - a hand-tuned width could only
   ever match at one viewport, since --tcm moves with the screen. */
:root {
  --tcm: min(calc((100vw - env(safe-area-inset-left) - env(safe-area-inset-right) - 304px) / 5), 30px);
  --tc-slot2: calc(var(--tcm) * 2 + 76px);
}
#tc-left   { left: calc(env(safe-area-inset-left) + var(--tcm)); }
#tc-right  { left: calc(env(safe-area-inset-left) + var(--tc-slot2)); }
#tc-burn   { right: calc(env(safe-area-inset-right) + var(--tc-slot2)); }
#tc-thrust { right: calc(env(safe-area-inset-right) + var(--tcm)); }

/* ---- KEY HINTS (#key-hints): the desktop's control legend ----
   (Sep 2026, user call - "when flying on bottom left should show a hint for
   controls ... when one is pressed then show it like button is being pushed - kinda
   like how it looks on phone ... you don't click on these, its just feedback".)
   Tiles in the bottom-left corner wearing the touch buttons' own glyphs, the key
   named UNDER each: [◀] A · [▶] D · [flame] W, SPACE · [burner] SHIFT once the
   afterburner is unlocked (it follows #tc-burn's own gate) · [booster] X exactly
   while #tc-boost is armed. A held key wears the .tc-btn.pressed colours. DESKTOP ONLY: body.touch never shows
   it and game.js refuses it inside the iPhone app, so a phone is pixel-unchanged.
   Hidden in the tutorial (it teaches the keys itself) and in a replay. HIDE is the
   one clickable thing here; the choice is remembered per browser. */
#key-hints {
  position: fixed;
  left: calc(env(safe-area-inset-left) + 22px);
  bottom: max(22px, env(safe-area-inset-bottom));
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 4px;
  z-index: 5;
  pointer-events: none;
  user-select: none;
  -webkit-user-select: none;
}
#key-hints.hidden, body.touch #key-hints, body.in-tutorial #key-hints, body.in-replay #key-hints { display: none; }
.kh-row { display: flex; align-items: flex-start; gap: 8px; }
.kh-col { display: flex; flex-direction: column; align-items: center; gap: 4px; width: 44px; }
.kh-col.kh-col-thrust { width: 84px; }   /* room for the two-key label under one tile */
.kh-col.hidden { display: none; }
/* The tile is the touch button in miniature: same panel, same glyphs, same
   .pressed colours - and PRESS IS COLOUR ONLY (user call - "dont move button when
   clicked just show blue outline"), exactly the .tc-btn rule. */
.kh-key {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 44px;
  height: 44px;
  color: var(--ink);
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: 11px;
  backdrop-filter: blur(8px);
  transition: background-color 0.08s ease, border-color 0.08s ease;
}
.kh-key.pressed { background: rgba(90, 209, 255, 0.22); border-color: var(--accent); }
.kh-tri { width: 0; height: 0; border-top: 8px solid transparent; border-bottom: 8px solid transparent; }
.kh-tri-l { border-right: 12px solid var(--ink); margin-right: 2px; }
.kh-tri-r { border-left: 12px solid var(--ink); margin-left: 2px; }
.kh-flame { width: 22px; height: 22px; display: block; fill: #ff8a3d; }
#kh-thrust.pressed .kh-flame { fill: #ffd24a; }
.kh-flame-burn { filter: drop-shadow(0 0 2.5px rgba(255, 216, 74, 0.8)); }
.kh-flame-burn .burn-outer { fill: #ff8a3d; }
.kh-flame-burn .burn-core { fill: #1d3f9e; }
#kh-burn.pressed .burn-outer { fill: #ffa35c; }
#kh-burn.pressed .burn-core { fill: #2a5cd6; }
.kh-star-boost { filter: drop-shadow(0 0 2.5px rgba(176, 108, 255, 0.8)); }
.kh-star-boost .boost-ring { fill: #b06cff; }
.kh-star-boost .boost-core { fill: #f2eaff; }
#kh-boost.pressed { background: rgba(176, 108, 255, 0.12); border-color: #c893ff; }
#kh-boost.pressed .boost-ring { fill: #c893ff; }
#kh-boost.pressed .boost-core { fill: #ffffff; }
.kh-lab { font-size: 10px; font-weight: 700; letter-spacing: 0.08em; color: var(--muted); white-space: nowrap; }
#kh-hide {
  pointer-events: auto;
  margin-top: 2px;
  padding: 2px 4px;
  font: inherit;
  font-size: 10px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--muted);
  background: transparent;
  border: none;
  cursor: pointer;
  opacity: 0.7;
}
#kh-hide:hover { opacity: 1; color: var(--ink); }
/* SHOW CONTROLS: what stands in the corner once the legend is hidden (user call -
   "once hidden include tiny text like hide"). Same gates as the legend itself. */
#kh-show {
  position: fixed;
  left: calc(env(safe-area-inset-left) + 22px);
  bottom: max(22px, env(safe-area-inset-bottom));
  z-index: 5;
  padding: 2px 4px;
  font: inherit;
  font-size: 10px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--muted);
  background: transparent;
  border: none;
  cursor: pointer;
  opacity: 0.7;
}
#kh-show:hover { opacity: 1; color: var(--ink); }
#kh-show.hidden, body.touch #kh-show, body.in-tutorial #kh-show, body.in-replay #kh-show { display: none; }
/* The localhost-only dev pills (#debug-drone / #debug-belt) share the corner: while
   the legend is up they step above it, so neither covers the other. Dev chrome
   only - none of it ships. */
body:has(#key-hints:not(.hidden)) #debug-drone, body:has(#kh-show:not(.hidden)) #debug-drone { bottom: 112px; }
body:has(#key-hints:not(.hidden)) #debug-belt, body:has(#kh-show:not(.hidden)) #debug-belt { bottom: 148px; }

/* ---- DEBUG BOOSTER (#tc-dbgburn): the dev-only free ×300 burn ----
   Same slot as the afterburner, stacked one button-height + 10px ABOVE it, so it
   reads as "the row above BURN" on a phone and holds that same screen spot on
   desktop (it is a SIBLING of #touch-controls, which is display:none there - the
   approach-timer idiom). .tc-btn already gives it the 76px box, the press feedback
   and pointer-events:auto; everything below is the DEV COAT that keeps it visually
   unmistakable next to the real burner: a dashed LIME frame (an accent the HUD owns
   nowhere else) over the same flame glyph in green. */
#tc-dbgburn {
  bottom: calc(max(22px, env(safe-area-inset-bottom)) + 86px);
  right: calc(env(safe-area-inset-right) + var(--tc-slot2));
  border-style: dashed;
  border-color: rgba(150, 240, 120, 0.6);
  z-index: 6;   /* clear of the control bar's own layer */
}
#tc-dbgburn .dbg-outer { fill: rgba(150, 240, 120, 0.75); }
#tc-dbgburn .dbg-core { fill: #0a1408; }
#tc-dbgburn.pressed {
  background: rgba(150, 240, 120, 0.2);
  border-color: rgba(150, 240, 120, 0.95);
}
#tc-dbgburn.pressed .dbg-outer { fill: #b6ff8c; }
.tc-mult-dbg { color: rgba(150, 240, 120, 0.9); }
/* The tutorial teaches steer/thrust and nothing else - the real burner hides there
   (below), and its dev twin must not be the one control left standing. */
body.in-tutorial #tc-dbgburn,
body.in-replay #tc-dbgburn { display: none; }

/* ---- APPROACH TIMER (#approach-timer): the ship system's readout ----
   JUST the number - no panel, no border, no label (user call: minimal), the
   TRACKING-chip treatment worn at the other end of the screen. It occupies the
   afterburner button's slot (same width + right offset) and sits low in it, under
   the flame glyph, which is where "right below the afterburner button" lands on a
   phone: the control bar is already on the bottom margin, so there is no room
   BENEATH the button that isn't the home-indicator strip. On desktop the buttons
   aren't drawn at all and the number holds the same screen spot on its own.
   Non-interactive, so a tap on it still fires the burner underneath.
   It sits in the strip BENEATH the button (user call), not on the panel: the
   control bar's own bottom offset is max(22px, safe-area-inset-bottom), so the
   timer takes the 15px directly under the button's bottom edge with ~2px of
   clearance either side. On a phone with a home bar that lands ~17px up from the
   screen edge - above the home indicator, not on it - and on everything else it
   sits in the plain 22px margin. */
#approach-timer {
  position: fixed;
  bottom: calc(max(22px, env(safe-area-inset-bottom)) - 17px);
  right: calc(env(safe-area-inset-right) + var(--tc-slot2));
  width: 76px;
  height: 15px;
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 6;
  pointer-events: none;
  user-select: none;
  -webkit-user-select: none;
}
#approach-timer-val {
  font-size: 13px;
  font-weight: 700;
  line-height: 1;
  /* NO letter-spacing: CSS appends it after the LAST glyph too, so a centred box
     ends up with its ink sitting half a space left of true centre - which is
     exactly the "a bit to the left" this element had. Tabular figures already
     hold the digits steady, which is all the spacing was buying. */
  letter-spacing: 0;
  font-variant-numeric: tabular-nums;   /* digits don't jitter as the count ticks */
  /* The reading is TWO terms ("1m 51s"), so it carries a space that a 76px flex box
     would happily break at - and a second line has nowhere to go in a 15px slot. */
  white-space: nowrap;
  color: #ffd98a;                       /* mission gold, the default of the two */
}
/* THE COLOUR IS THE IDENTITY: the number is tinted for WHICH target it is counting
   to, matching the cue drawn on that object in the world - mission gold, or the
   waypoint's own green. Nothing else about the readout changes. */
#approach-timer.at-gold  #approach-timer-val { color: #ffd98a; }
#approach-timer.at-green #approach-timer-val { color: #78e68c; }
/* Never compete with the stranded pill / the tutorial's own chrome. */
body.fuel-stranded #approach-timer,
body.in-tutorial #approach-timer { display: none; }

body.touch #hint {
  top: auto;
  /* Clear the touch dock-prompt (which rides above the thrust controls). */
  bottom: 173px;
  /* Roomier on phones so the gesture legend doesn't wrap into a cramped pill. */
  max-width: 94vw;
  padding: 10px 18px;
  font-size: 15px;
  line-height: 1.5;
  text-align: center;
}

/* ---------- Mission board (top-left, under credits) ----------
   The active contract sits on top (gold), the available offers list below with
   TAKE buttons. Compact + glassy so it never crowds the world. */
#missions {
  position: fixed;
  /* Sits under the credit balance; share its safe-area insets so the gap holds. */
  top: calc(40px + env(safe-area-inset-top));
  left: calc(14px + env(safe-area-inset-left));
  width: 244px;
  max-width: 60vw;
  z-index: 6;
  font-size: 12px;
  letter-spacing: 0.02em;
}
.mb-head {
  font-size: 10px;
  letter-spacing: 0.22em;
  color: var(--muted);
  margin-bottom: 6px;
  text-shadow: 0 1px 6px rgba(0, 0, 0, 0.7);
}
#mb-list { display: flex; flex-direction: column; gap: 5px; }
.mb-row {
  display: flex;
  align-items: center;
  gap: 8px;
  background: rgba(10, 14, 22, 0.62);
  border: 1px solid var(--line);
  border-radius: 9px;
  padding: 7px 9px 7px 8px;
  backdrop-filter: blur(6px);
  box-shadow: 0 2px 12px rgba(0, 0, 0, 0.35);
}
/* A loaded (carrying) mission glows gold - "deliver this". */
.mb-row.loaded { border-color: rgba(255, 217, 138, 0.45); background: rgba(34, 28, 14, 0.6); }
.mb-tag {
  flex: none;
  font-size: 8.5px;
  font-weight: 700;
  letter-spacing: 0.08em;
  padding: 2px 5px;
  border-radius: 5px;
  color: #0c1505;
}
.mb-tag.earth { background: #6fe3c4; }
.mb-tag.mars  { background: #e08a64; }
.mb-tag.open  { background: #8fb4ff; }
.mb-info { flex: 1; min-width: 0; }
.mb-info .mb-stat { color: #9ad36a; font-weight: 700; font-size: 11.5px; letter-spacing: 0.03em; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.mb-row.loaded .mb-info .mb-stat { color: #ffd98a; }
.mb-info .mb-route { color: var(--muted); font-size: 10.5px; margin-top: 1px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
body.touch #missions { top: calc(38px + env(safe-area-inset-top)); width: 210px; }

/* (The old dock-prompt pill buttons - REFUEL / ⛏ / REFINE / SELL - are retired:
   the docked UI is the colony-styled chip + .dock-act buttons above, and refine/
   sell/refuel all happen INSIDE the station now.) */

/* ---------- Unified menu ----------
   One 4-square button (top-right in flight) opens the fullscreen menu. Safe-area insets
   keep it clear of the iPhone Dynamic Island; the HUD/credits + fuel sit top-LEFT. */
#menubtn {
  position: fixed;
  top: calc(12px + env(safe-area-inset-top));
  right: calc(12px + env(safe-area-inset-right));
  z-index: 9;
  /* Two-tier: trimmer on desktop, larger finger-target on phones (body.touch below). */
  width: 40px;
  height: 40px;
  display: flex;
  align-items: center;
  justify-content: center;
  color: #cfe0d2;
  background: rgba(14, 20, 16, 0.72);
  border: 1px solid rgba(143, 194, 74, 0.3);
  border-radius: 10px;
  cursor: pointer;
  backdrop-filter: blur(4px);
  box-shadow: 0 4px 16px rgba(0, 0, 0, 0.4);
  transition: border-color 0.12s ease, transform 0.08s ease;
}
#menubtn:hover { border-color: rgba(143, 194, 74, 0.6); }
#menubtn:active { transform: scale(0.95); }
body.touch #menubtn { width: 52px; height: 52px; border-radius: 13px; }
body.touch #menubtn svg { width: 26px; height: 26px; }
/* Hamburger → X morph while the quick stack is open (game.js toggles .open).
   Each line rotates/fades around its own center (transform-box: fill-box). */
#menubtn svg path { transition: transform 0.16s ease, opacity 0.12s ease; transform-box: fill-box; transform-origin: center; }
#menubtn.open .mb-l1 { transform: translateY(5.5px) rotate(45deg); }
#menubtn.open .mb-l2 { opacity: 0; }
#menubtn.open .mb-l3 { transform: translateY(-5.5px) rotate(-45deg); }
/* ---------- QUICK MENU ----------
   First tap on the hamburger slides this stack down under it: Nav / Mission Log /
   Chat / Full Menu (the 4-square), every button the menu button's exact size -
   a detached icon column in the menu button's own style. */
#quickmenu {
  position: fixed;
  top: calc(12px + env(safe-area-inset-top) + 40px + 8px);
  right: calc(12px + env(safe-area-inset-right));
  z-index: 9;
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: 8px;
}
body.touch #quickmenu { top: calc(12px + env(safe-area-inset-top) + 52px + 8px); }
body.in-colony #quickmenu { z-index: 23; }   /* ride above the colony overlay like #menubtn */
.qm-btn {
  position: relative;
  width: 40px;
  height: 40px;
  display: flex;
  align-items: center;
  justify-content: center;
  color: #cfe0d2;
  background: rgba(14, 20, 16, 0.72);
  border: 1px solid rgba(143, 194, 74, 0.3);
  border-radius: 10px;
  cursor: pointer;
  backdrop-filter: blur(4px);
  box-shadow: 0 4px 16px rgba(0, 0, 0, 0.4);
  transition: border-color 0.12s ease, transform 0.08s ease;
  animation: qm-drop 0.16s ease backwards;
}
.qm-btn:nth-child(2) { animation-delay: 0.04s; }
.qm-btn:nth-child(3) { animation-delay: 0.08s; }
.qm-btn:nth-child(4) { animation-delay: 0.12s; }
.qm-btn:nth-child(5) { animation-delay: 0.16s; }
.qm-btn:hover { border-color: rgba(143, 194, 74, 0.6); }
.qm-btn:active { transform: scale(0.95); }
.qm-btn .qm-ic-map { color: #46a6ff; }       /* the Nav tile's compass blue */
body.touch .qm-btn { width: 52px; height: 52px; border-radius: 13px; }
body.touch .qm-btn svg { width: 26px; height: 26px; }
.qm-btn .menu-unread { top: -5px; right: -5px; }
@keyframes qm-drop {
  from { opacity: 0; transform: translateY(-10px); }
  to { opacity: 1; transform: translateY(0); }
}
/* Faint name labels riding left of each button: shown while #quickmenu carries
   .qm-labs (game.js adds it on open, drops it 4 s later), then they fade away.
   Pure text - no pill - so they read as a whisper, not more chrome. */
.qm-lab {
  position: absolute;
  right: 100%;
  margin-right: 10px;
  white-space: nowrap;
  font-size: 10.5px;
  font-weight: 700;
  letter-spacing: 0.18em;
  color: rgba(199, 230, 160, 0.55);
  text-shadow: 0 1px 4px rgba(0, 0, 0, 0.8);
  opacity: 0;
  transition: opacity 0.7s ease;
  pointer-events: none;
}
#quickmenu.qm-labs .qm-lab { opacity: 1; transition: opacity 0.2s ease; }
body.touch .qm-lab { font-size: 11.5px; }
/* Unread-chat count pill: rides the menu button's top-right corner (and the menu's
   Chat tile), so a message that lands mid-flight has a quiet, persistent cue. */
/* Same badge recipe as chat's tab/row pills (chat.js CSS + calibrateBadges, which
   publishes the --sc-badge-* padding ratios on :root from the real device font):
   a TRUE circle at one digit, stadium pill at two, digit centred on both axes.
   Without chat.js the 0 fallbacks apply and it is simply an uncorrected circle. */
.menu-unread {
  position: absolute;
  top: -5px;
  right: -5px;
  box-sizing: border-box;
  min-width: 17px;
  height: 17px;
  border-radius: 999px;
  padding: var(--sc-badge-pt, 0px) 4px var(--sc-badge-pb, 0px);
  background: #e0463a;
  color: #fff;
  font-size: 10px;
  font-weight: 700;
  line-height: 1;
  letter-spacing: 0;
  align-items: center;
  justify-content: center;
  box-shadow: 0 0 0 2px rgba(14, 20, 16, 0.85);
  pointer-events: none;
}
.menu-unread:not(.hidden) { display: flex; }
.menu-item .menu-unread { top: 8px; right: 10px; }

/* CLAIM flag (Aug 2026): a bare TEAL DOT - the berth scanner's own color - riding
   the hamburger's opposite corner from the red unread pill (badges never stack),
   and the quick stack's NAV button. A dot, not a count: this is "you can do
   something", not an inbox. game.js updateClaimBadge lights it once per
   opportunity (beacon aboard at home / entering a claimable star) and opening
   the nav console clears it - the unseen-news rule the chat badge follows. */
.menu-claimdot {
  position: absolute;
  bottom: -4px;
  left: -4px;
  width: 12px;
  height: 12px;
  border-radius: 999px;
  background: #8ff0d0;
  border: 2px solid rgba(6, 10, 16, 0.9);
  box-sizing: border-box;
  display: none;
  pointer-events: none;
  z-index: 2;
}
.menu-claimdot:not(.hidden) { display: block; }

/* Choose-contract pill: shown ONLY when a contract choice is deferred (top-center, below
   the notch). Amber, gently pulsing, to draw you back to pick the next haul. */
#logbtn {
  position: fixed;
  top: calc(10px + env(safe-area-inset-top));
  left: 50%;
  transform: translateX(-50%);
  z-index: 9;
  height: 34px;
  padding: 0 15px;
  font: inherit;
  font-size: 12px;
  font-weight: 700;
  letter-spacing: 0.06em;
  color: #ffd98a;
  background: rgba(40, 30, 8, 0.85);
  border: 1px solid rgba(255, 217, 138, 0.5);
  border-radius: 999px;
  cursor: pointer;
  backdrop-filter: blur(4px);
  animation: choose-pill-pulse 1.6s ease-in-out infinite;
}
#logbtn:hover { border-color: rgba(255, 217, 138, 0.85); }
#logbtn:active { transform: translateX(-50%) scale(0.96); }

/* Nav console overlay - fullscreen solar-system map; navmap.js draws all of it (title,
   buttons, footer) on the canvas. */
#nav {
  position: fixed;
  inset: 0;
  z-index: 24;            /* above the colony (22) so the menu's Map opens over it */
  background: #04060c;
  animation: interiorFade 0.3s ease;
}
/* Waypoint typeahead (navmap.js): a real DOM <input> over the canvas - the one
   way a phone keyboard comes up - anchored above the + WAYPOINT button, with the
   suggestion list growing UPWARD from it. Styled to read as one of the console's
   own canvas slabs (dark fill, cyan hairline, monospace). */
#nav-search {
  position: absolute;
  display: none;
  z-index: 2;
  font-family: ui-monospace, Menlo, Consolas, monospace;
}
#nav-search-input {
  width: 100%;
  box-sizing: border-box;
  background: rgba(10, 16, 26, 0.92);
  border: 1px solid rgba(95, 208, 255, 0.55);
  border-radius: 9px;
  color: #dfe8ff;
  font: 600 12px ui-monospace, Menlo, Consolas, monospace;
  padding: 11px 12px;
  outline: none;
}
#nav-search-input::placeholder { color: rgba(160, 185, 215, 0.5); }
#nav-search-list {
  display: none;
  margin-bottom: 6px;
  background: rgba(6, 10, 18, 0.94);
  border: 1px solid rgba(95, 208, 255, 0.35);
  border-radius: 9px;
  overflow: hidden;
}
#nav-search-list .ns-row {
  display: flex;
  align-items: baseline;
  gap: 8px;
  padding: 9px 12px;
  cursor: pointer;
}
#nav-search-list .ns-row + .ns-row { border-top: 1px solid rgba(95, 208, 255, 0.12); }
#nav-search-list .ns-row:hover,
#nav-search-list .ns-row.sel { background: rgba(95, 208, 255, 0.14); }
#nav-search-list .ns-name {
  color: #dfe8ff;
  font-size: 12px;
  font-weight: 600;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
#nav-search-list .ns-kind {
  color: rgba(160, 185, 215, 0.5);
  font-size: 9px;
  letter-spacing: 0.5px;
  flex: none;
}
#nav-search-list .ns-dist {
  margin-left: auto;
  color: rgba(95, 208, 255, 0.8);
  font-size: 11px;
  white-space: nowrap;
  flex: none;
}
#nav-view {
  display: block;
  width: 100%;
  height: 100%;
  touch-action: none;
  -webkit-user-select: none;
  user-select: none;
  -webkit-tap-highlight-color: transparent;
}
@keyframes choose-pill-pulse {
  0%, 100% { box-shadow: 0 4px 16px rgba(0, 0, 0, 0.4); }
  50% { box-shadow: 0 4px 16px rgba(0, 0, 0, 0.4), 0 0 14px rgba(255, 217, 138, 0.45); }
}
/* ---------- Unified menu + shared menu-panel theme ----------
   The menu itself and every panel opened from it (Map / Mission Log / Pilots Online /
   Profile) share this look: a clean full-screen panel, the title top-left, an X
   (top-right) that jumps back to the game, and a Close button (bottom-right) that
   returns to the menu. Map / Mission Log / Pilots / Profile are mutually exclusive with
   the menu (opening one hides the menu), so a single z-layer is fine. */
#menu, .menu-panel {
  position: fixed;
  inset: 0;
  z-index: 24;
  display: flex;
  flex-direction: column;
  background: linear-gradient(180deg, #0b1210 0%, #080b11 100%);
  animation: interiorFade 0.22s ease;
}
.mp-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: calc(14px + env(safe-area-inset-top)) calc(16px + env(safe-area-inset-right)) 14px calc(18px + env(safe-area-inset-left));
  border-bottom: 1px solid var(--line);
}
.mp-title { font-size: 19px; font-weight: 700; letter-spacing: 0.02em; color: #c7e6a0; }
.mp-x {
  flex: none;
  width: 38px;
  height: 38px;
  font: inherit;
  font-size: 16px;
  line-height: 1;
  color: var(--muted);
  background: rgba(255, 255, 255, 0.05);
  border: 1px solid var(--line);
  border-radius: 10px;
  cursor: pointer;
  transition: color 0.12s ease, border-color 0.12s ease, transform 0.08s ease;
}
.mp-x:hover { color: var(--ink); border-color: rgba(200, 210, 230, 0.4); }
.mp-x:active { transform: scale(0.94); }
/* Anytime-SOS in the MENU header: a rescue-red pill in the middle of the head row
   (the head's space-between spreads title / SOS / X). Opens Rescue Dispatch;
   dimmed while docked or while a rescue craft is already inbound. */
.menu-sos {
  flex: none;
  height: 38px;
  padding: 0 22px;
  font: inherit;
  font-size: 14px;
  font-weight: 800;
  letter-spacing: 0.14em;
  color: #ff7b6e;
  background: rgba(120, 26, 18, 0.22);
  border: 1px solid rgba(255, 110, 95, 0.5);
  border-radius: 10px;
  cursor: pointer;
  transition: color 0.12s ease, border-color 0.12s ease, background 0.12s ease, transform 0.08s ease;
}
.menu-sos:hover { color: #ffa398; border-color: rgba(255, 140, 120, 0.75); background: rgba(150, 34, 24, 0.3); }
.menu-sos:active { transform: scale(0.94); }
.menu-sos.disabled { opacity: 0.35; cursor: default; }
.menu-sos.disabled:hover { color: #ff7b6e; border-color: rgba(255, 110, 95, 0.5); background: rgba(120, 26, 18, 0.22); }
.mp-body { flex: 1; overflow-y: auto; padding: 14px 16px; -webkit-overflow-scrolling: touch; }
.mp-foot {
  display: flex;
  justify-content: flex-end;
  gap: 10px;
  padding: 12px calc(16px + env(safe-area-inset-right)) calc(14px + env(safe-area-inset-bottom)) 16px;
  border-top: 1px solid var(--line);
}
.mp-close {
  padding: 11px 28px;
  font: inherit;
  font-size: 13px;
  font-weight: 600;
  letter-spacing: 0.05em;
  color: #c7e6a0;
  background: rgba(143, 194, 74, 0.1);
  border: 1px solid rgba(143, 194, 74, 0.35);
  border-radius: 10px;
  cursor: pointer;
  transition: filter 0.15s ease, border-color 0.15s ease, transform 0.08s ease;
}
.mp-close:hover { border-color: rgba(143, 194, 74, 0.6); }
/* The ship page's CHANGE COLOR (Sep 2026): hands over to the shop, in the shop's gold. */
.mp-close.ship-color { color: var(--gold); border-color: rgba(255, 210, 51, 0.35); background: rgba(255, 210, 51, 0.08); }
.mp-close.ship-color:hover { border-color: rgba(255, 210, 51, 0.6); }
.mp-close:active { transform: scale(0.97); }
/* Sign-up call-to-action bar (anonymous pilots only): a full-width rectangle above the
   square menu tiles. Accented so it reads as the one thing worth doing; hidden when
   signed in, at which point the grid slides back up to fill the space. */
.menu-signup {
  flex: none;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 3px;
  margin: 16px calc(16px + env(safe-area-inset-right)) 0 calc(16px + env(safe-area-inset-left));
  padding: 15px 14px;
  font: inherit;
  color: #0b1210;
  background: linear-gradient(180deg, #b7e07a 0%, #8fc24a 100%);
  border: 1px solid rgba(143, 194, 74, 0.55);
  border-radius: 14px;
  cursor: pointer;
  transition: filter 0.14s ease, transform 0.08s ease;
}
.menu-signup:hover { filter: brightness(1.06); }
.menu-signup:active { transform: scale(0.98); }
.menu-signup-title { font-size: 15px; font-weight: 700; letter-spacing: 0.03em; }
.menu-signup-sub { font-size: 11.5px; font-weight: 600; letter-spacing: 0.02em; opacity: 0.72; }
/* The menu grid: big, clean, half-width cards. */
.menu-grid {
  flex: 1;
  overflow-y: auto;
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 12px;
  align-content: start;
  padding: 18px calc(16px + env(safe-area-inset-right)) calc(20px + env(safe-area-inset-bottom)) calc(16px + env(safe-area-inset-left));
}
.menu-item {
  position: relative;   /* anchors the Chat tile's unread pill */
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 11px;
  min-height: 108px;
  padding: 18px 10px;
  font: inherit;
  color: var(--ink);
  background: rgba(255, 255, 255, 0.04);
  border: 1px solid var(--line);
  border-radius: 14px;
  cursor: pointer;
  transition: border-color 0.14s ease, background 0.14s ease, transform 0.08s ease;
}
.menu-item:hover { border-color: rgba(143, 194, 74, 0.5); background: rgba(143, 194, 74, 0.06); }
.menu-item:active { transform: scale(0.97); }
.menu-ic { color: #c7e6a0; display: flex; }
.menu-ic.map { color: #46a6ff; }     /* Map: single-colour blue, Safari-style compass */
.menu-ic.clist { color: var(--gold); }  /* Cricketslist: contracts wear mission gold */
.menu-ic.help { color: var(--nav-accent); }  /* Help: the console cyan - a utility, not a possession */
.menu-label { font-size: 13.5px; font-weight: 600; letter-spacing: 0.02em; }
.menu-ic.shop { color: var(--gold); }   /* Shop: the ₡ gold, the one tile that costs */
/* "Main Menu" rect button pinned at the bottom of the unified menu. */
.menu-foot {
  padding: 12px calc(16px + env(safe-area-inset-right)) calc(16px + env(safe-area-inset-bottom)) calc(16px + env(safe-area-inset-left));
  border-top: 1px solid var(--line);
}
.menu-mainbtn {
  width: 100%;
  font: inherit; font-size: 14px; font-weight: 700; letter-spacing: 0.06em;
  color: #c7e6a0;
  background: rgba(143, 194, 74, 0.1);
  border: 1px solid rgba(143, 194, 74, 0.35);
  border-radius: 12px; padding: 14px; cursor: pointer;
  transition: background 0.12s ease, border-color 0.12s ease, transform 0.08s ease;
}
.menu-mainbtn:hover { background: rgba(143, 194, 74, 0.2); }
.menu-mainbtn:active { transform: scale(0.98); }
/* HELP panel (help.js): the FAQ as a one-open-at-a-time accordion. Closed, it is
   a scannable list of questions grouped into sections; the open one unfolds its
   answer in place. Sized for reading on a phone - the answer column caps its
   measure so a paragraph never runs the full width of a desktop window. */
.help-body { padding-bottom: calc(24px + env(safe-area-inset-bottom)); }
.help-sec {
  margin: 18px 0 8px;
  font-size: 11px; font-weight: 700; letter-spacing: 0.14em; text-transform: uppercase;
  color: #7f8fa6;
}
.help-sec:first-child { margin-top: 4px; }
.help-item { border-bottom: 1px solid var(--line); }
.help-q {
  display: flex; align-items: center; gap: 12px;
  width: 100%;
  padding: 14px 2px;
  font: inherit; text-align: left;
  color: var(--ink);
  background: none; border: 0;
  cursor: pointer;
  transition: color 0.12s ease;
}
.help-q:hover { color: #c7e6a0; }
.help-qt { flex: 1; min-width: 0; font-size: 14.5px; font-weight: 600; line-height: 1.35; }
/* Chevron: a CSS caret, so there is no glyph to go missing in a font stack. */
.help-chev {
  flex: none; width: 9px; height: 9px;
  border-right: 2px solid currentColor; border-bottom: 2px solid currentColor;
  border-radius: 1px;
  opacity: 0.5;
  transform: rotate(45deg) translate(-2px, -2px);
  transition: transform 0.16s ease, opacity 0.12s ease;
}
.help-item.open .help-chev { opacity: 0.9; transform: rotate(225deg) translate(-2px, -2px); }
.help-item.open .help-qt { color: #c7e6a0; }
.help-a { padding: 0 2px 16px; max-width: 62ch; animation: helpUnfold 0.16s ease; }
.help-p { margin: 0 0 10px; font-size: 13.5px; line-height: 1.55; color: #b9c4d4; }
.help-p:last-child { margin-bottom: 0; }
.help-em { color: var(--ink); font-weight: 600; }
.help-steps { margin: 0 0 10px; padding-left: 20px; }
.help-steps li { margin-bottom: 7px; font-size: 13.5px; line-height: 1.55; color: #b9c4d4; }
.help-steps li::marker { color: #7f8fa6; font-weight: 700; }
@keyframes helpUnfold { from { opacity: 0; transform: translateY(-3px); } to { opacity: 1; transform: none; } }
/* CARGO panel (opened from the quick stack's CARGO button): the hold as a MANIFEST -
   HOLD n/cap readout + slim capacity bar, then one row per held stack (tinted dot or
   gold ⚙, name, qty · tonnage, per-row EJECT). Built in game.js updateMenuCargo.
   Hidden at 0t (the panel shows the empty note instead). */
#cargopanel #menu-cargo { margin: 0; }
.cargo-empty { color: var(--muted); font-size: 13px; padding: 6px 2px; }
#menu-cargo {
  flex: none;
  padding: 11px 14px 6px;
  background: rgba(255, 255, 255, 0.035);
  border: 1px solid var(--line);
  border-radius: 12px;
}
.menu-cargo-lead { display: flex; align-items: baseline; margin-bottom: 8px; }
.menu-cargo-lab { font-size: 10px; letter-spacing: 0.22em; color: var(--muted); }
.menu-cargo-amt {
  margin-left: auto;
  font-size: 13px;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  color: var(--ink);
}
.menu-cargo-amt.full { color: var(--gold); }
/* Slim capacity bar under the readout: green fill, gold when the hold is full. */
.cargo-capbar { height: 4px; border-radius: 2px; background: rgba(255, 255, 255, 0.08); overflow: hidden; margin-bottom: 4px; }
.cargo-capbar i { display: block; height: 100%; border-radius: 2px; background: var(--grass); transition: width 0.2s ease; }
.cargo-capbar i.full { background: var(--gold); }
/* Manifest rows: hairline-separated, mission-card rhythm. */
.cargo-rows { display: flex; flex-direction: column; }
.cg-row { display: flex; align-items: center; gap: 10px; padding: 11px 0; border-top: 1px solid rgba(120, 140, 170, 0.13); }
.cg-row:first-child { border-top: none; }
.cg-dot { flex: none; width: 10px; height: 10px; border-radius: 3px; }
.cg-gear { flex: none; width: 10px; font-size: 12px; line-height: 1; color: var(--gold); font-style: normal; text-align: center; }
.cg-name { font-size: 13.5px; color: var(--ink); min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.cg-name.freight { color: #8ff0d0; }
.cg-name.haul { color: #ffd98a; }   /* sealed mission freight - the mission gold, so the row reads as the contract's */
.cg-qty { margin-left: auto; flex: none; font-size: 12.5px; font-variant-numeric: tabular-nums; color: var(--ink); }
.cg-qty .cg-t { color: var(--muted); font-size: 11px; }
/* THE SCHEMATICS FOLDER (Aug 2026): weightless rows under the manifest - a caption,
   then one line per module design. Owned rows read like cargo; unowned ones dim back
   to a shopping list carrying their price and the room that sells them. No verbs:
   drawings cannot be ejected, stored or sold. */
/* The caption reads at manifest weight (user call): same size and face as a cargo
   row's name, ordinary capitals, so the folder sits in the list rather than above it. */
.cg-cap {
  font: 400 13.5px/1 inherit;
  color: var(--ink);
  padding: 14px 0 4px;
  border-top: 1px solid rgba(120, 140, 170, 0.13);
  margin-top: 4px;
}
/* The schematics caption is the folder tab - tappable, chevron turns when open. */
.cg-fold { display: flex; align-items: center; gap: 7px; cursor: pointer; user-select: none; }
.cg-chev {
  flex: none; font-style: normal; font-size: 10px; line-height: 1;
  color: rgba(190, 202, 220, 0.6);
  transition: transform 0.14s ease;
}
.cg-fold.open .cg-chev { transform: rotate(90deg); }
.cg-row.cg-schem { padding: 8px 0; border-top: none; }
.cg-plan { flex: none; width: 10px; font-size: 11px; line-height: 1; color: #8fcfe6; font-style: normal; text-align: center; }
.cg-plan.off { color: rgba(140, 156, 180, 0.45); }
.cg-name.plan { font-size: 12.5px; color: #cfe4ee; }
.cg-name.plan.off { color: rgba(190, 202, 220, 0.5); }
.cg-row.cg-schem .cg-qty { font-size: 11.5px; color: rgba(190, 202, 220, 0.72); }
.cg-eject {
  flex: none;
  font: 700 10.5px/1 inherit;
  letter-spacing: 0.14em;
  color: #ff9d94;
  background: rgba(255, 107, 107, 0.07);
  border: 1px solid rgba(255, 107, 107, 0.45);
  border-radius: 999px;
  padding: 7px 13px;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}
.cg-eject:active { transform: scale(0.95); }
/* STORE - docked at YOUR OWN claim only (Aug 2026 simplification pass, user
   call): every storable stack banks into the station locker from right here,
   beside its EJECT. A DARK PILL rather than EJECT's red outline - it is the
   quiet everyday verb next to the destructive one, and it matches the terminal
   card's own dark-pill STORE/TAKE verbs. The TAKE side stays on the claim
   terminal's STATION CARGO tab, where the locker's contents live. */
.cg-store {
  flex: none;
  font: 700 10.5px/1 inherit;
  letter-spacing: 0.14em;
  color: #8ff0d0;
  background: rgba(8, 20, 26, 0.85);
  border: 1px solid rgba(70, 214, 201, 0.3);
  border-radius: 999px;
  padding: 7px 13px;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}
.cg-store:active { transform: scale(0.95); }
/* SMELT - the FIELD SMELTER (Aug 2026, user call): a raw metal row's own verb
   once Brace's fitting is aboard. The works' gold, worn as a quiet pill beside
   STORE/EJECT - one shared flex column, so the iron and platinum buttons line
   up at the same spot (user call). */
.cg-smelt {
  flex: none;
  font: 700 10.5px/1 inherit;
  letter-spacing: 0.14em;
  color: #ffd98a;
  background: rgba(26, 20, 8, 0.85);
  border: 1px solid rgba(255, 217, 138, 0.35);
  border-radius: 999px;
  padding: 7px 13px;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}
.cg-smelt:active { transform: scale(0.95); }
/* CRACK - the SHIP STILL (Aug 2026, the return-calls pass): the ice row's own
   verb once Ratchet's fitting is aboard. Same pill, the electrolyzer's cyan
   instead of the works' gold - the color the fuel machinery already wears. */
.cg-crack {
  color: #9fd6e8;
  background: rgba(10, 22, 26, 0.85);
  border-color: rgba(159, 214, 232, 0.35);
}
/* A row carrying BOTH verbs has two pills to fit beside the name, which on a
   390px phone is exactly enough to start ellipsizing "Refined platinum" - so
   the pair goes COMPACT (tighter padding + tracking, tighter row gap) only on
   those rows. Every other berth's manifest is pixel-unchanged. The smelter's
   third pill rides the same compact rule (cg-cansmelt). */
.cg-row.cg-canstore,
.cg-row.cg-cansmelt { gap: 6px; }
.cg-row.cg-canstore .cg-store,
.cg-row.cg-cansmelt .cg-smelt,
.cg-row.cg-cansmelt .cg-store,
.cg-row.cg-cansmelt .cg-eject,
.cg-row.cg-canstore .cg-eject { padding: 7px 8px; letter-spacing: 0.06em; }
/* ---------- EJECT amount popup ----------
   The mission-dialogue card (popup-card/popup-quest geometry - same padding, radius,
   title rhythm as the crew conversations) trimmed to the jettison red, holding ONLY
   a − n + stepper and KEEP / DUMP. Overlay mirrors #popup's centered scrim. */
#eject-pop,
#amt-pop {
  position: fixed;
  inset: 0;
  z-index: 46;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 22px;
  background: rgba(4, 6, 11, 0.72);
  backdrop-filter: blur(4px);
}
/* The card keeps the mission gold frame + gold title (popup-quest, later in this
   file, wins those ties on purpose); only the stepper + buttons are ours. */
.eject-step { display: flex; align-items: center; justify-content: center; gap: 16px; padding: 6px 0 2px; }
.eject-pm {
  flex: none;
  width: 52px;
  height: 52px;
  font: 600 24px/1 inherit;
  color: var(--ink);
  background: rgba(255, 255, 255, 0.05);
  border: 1px solid rgba(120, 140, 170, 0.3);
  border-radius: 12px;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
  touch-action: none;           /* a held stepper must not turn into a scroll */
  user-select: none;
}
.eject-pm:active { background: rgba(255, 255, 255, 0.1); }
.eject-mid { min-width: 96px; text-align: center; }
.eject-n { font-size: 34px; font-weight: 700; font-variant-numeric: tabular-nums; color: var(--ink); }
.eject-of { font-size: 11.5px; color: var(--muted); font-variant-numeric: tabular-nums; margin-top: 2px; }
.popup-actions.eject-actions { flex-direction: row; }   /* KEEP · DUMP side by side (specificity beats the later .popup-actions column) */
.eject-actions .popup-btn { flex: 1; }
.popup-btn.eject-keep { color: var(--ink); background: rgba(255, 255, 255, 0.07); border: 1px solid rgba(120, 140, 170, 0.3); }
.popup-btn.eject-dump {
  color: #2b0b08;
  font-weight: 700;
  letter-spacing: 0.08em;
  background: linear-gradient(180deg, #ff8a7a 0%, #e05545 100%);
}
.popup-btn.eject-dump:hover { filter: brightness(1.06); }
/* ---------- AMOUNT popup (sell / refine counters) ----------
   The EJECT card generalized: same stepper, gold confirm (the mission-card action
   yellow - a sale/refine is a CAN-DO, not a jettison), plus a live value/fee line
   between stepper and buttons ("+₡45 · 5 kept" / "Service fee: ₡12"). */
.amt-line {
  text-align: center;
  font-size: 12.5px;
  font-variant-numeric: tabular-nums;
  color: var(--gold);
  padding: 2px 0 4px;
  min-height: 16px;
}
.popup-btn.amt-go {
  color: #241a04;
  font-weight: 700;
  letter-spacing: 0.08em;
  background: linear-gradient(180deg, #ffe08a 0%, #f0b429 100%);
}
.popup-btn.amt-go:hover { filter: brightness(1.05); }
/* Chat panel body: the chat widget (chat.js, menu mode) mounts here and fills it. */
.chat-body { padding: 0; display: flex; }
/* Mobile soft-keyboard handling for the chat panel. Two layers:
   1) `interactive-widget=resizes-content` (viewport meta) lets Chrome/Android shrink
      the LAYOUT viewport, so this fixed flex panel shrinks on its own - header stays,
      list shrinks, no JS needed.
   2) iOS Safari ignores that and instead shrinks only the VISUAL viewport, so game.js
      pins the panel to it via an inline height + translateY (transforms survive iOS's
      keyboard scroll where `top` does not), re-fitted by a per-frame tracking loop
      while the composer holds the keyboard (iOS delivers visualViewport events too
      sparsely to trust alone) with the layout scroll pinned back to 0, and we lock
      page scroll so the panel can't drift back up under the focus auto-scroll. */
#chat.kb-fit { bottom: auto; }   /* inline height (set by JS) takes over from inset:0 */
html.kb-lock, body.kb-lock { overflow: hidden; }
.sc-chat-list { overscroll-behavior: contain; }
.log-list { flex: 1; overflow-y: auto; padding: 8px 14px; }
.log-empty {
  padding: 26px 10px;
  text-align: center;
  color: var(--muted);
  font-size: 13px;
  letter-spacing: 0.04em;
}
.log-row {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 13px 6px;
  border-bottom: 1px solid rgba(120, 140, 170, 0.1);
}
.log-row:last-child { border-bottom: none; }
/* Rows tap-through to the giver-dialogue recap popup - show the affordance. */
.log-row.clickable { cursor: pointer; border-radius: 8px; transition: background 0.12s ease; }
.log-row.clickable:hover { background: rgba(143, 194, 74, 0.07); }
.log-row.clickable:active { background: rgba(143, 194, 74, 0.13); }
.log-tag {
  flex: none;
  min-width: 52px;
  text-align: center;
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.1em;
  padding: 4px 6px;
  border-radius: 6px;
  color: #cfe6ff;
  background: rgba(127, 184, 255, 0.12);
  border: 1px solid rgba(127, 184, 255, 0.28);
}
.log-tag.earth { color: #6fe3c4; background: rgba(111, 227, 196, 0.12); border-color: rgba(111, 227, 196, 0.28); }
.log-tag.mars { color: #e08a64; background: rgba(224, 138, 100, 0.12); border-color: rgba(224, 138, 100, 0.28); }
.log-info { flex: 1; min-width: 0; }
.log-route { font-size: 14px; font-weight: 600; color: var(--ink); }
.log-pay { font-size: 12px; color: #ffd98a; margin-top: 2px; letter-spacing: 0.02em; }
.log-abandon {
  flex: none;
  align-self: center;
  padding: 7px 12px;
  font: inherit;
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.03em;
  color: rgba(255, 138, 122, 0.9);
  background: transparent;
  border: 1px solid rgba(255, 106, 90, 0.3);
  border-radius: 8px;
  cursor: pointer;
  transition: color 0.14s ease, border-color 0.14s ease;
}
.log-abandon:hover { color: #ff9a8a; border-color: rgba(255, 106, 90, 0.55); }
.log-abandon:active { transform: scale(0.96); }

/* ---------- Mission-log collapsible groups (Active / Completed → by zone) ---------- */
.log-group { margin: 4px 0 6px; }
.log-group-head {
  display: flex;
  align-items: center;
  gap: 8px;
  width: 100%;
  padding: 9px 6px;
  background: none;
  border: none;
  border-bottom: 1px solid rgba(120, 140, 170, 0.14);
  color: #c7e6a0;
  font: inherit;
  font-size: 12px;
  font-weight: 700;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  cursor: pointer;
  text-align: left;
}
.log-group-head .log-caret { flex: none; width: 12px; transition: transform 0.15s ease; color: var(--muted); }
.log-group.collapsed .log-caret { transform: rotate(-90deg); }
.log-group-head .log-count {
  margin-left: auto;
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.04em;
  color: var(--muted);
}
.log-group.completed .log-group-head { color: #9fb6cf; }
.log-group-body { overflow: hidden; }
.log-group.collapsed .log-group-body { display: none; }
.log-subhead {
  padding: 8px 6px 3px;
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.18em;
  color: var(--muted);
  text-transform: uppercase;
}
.log-row.done .log-route { color: #9fb6cf; }
.log-row.done .log-pay { color: #8fb24a; }
.log-row .log-n {
  flex: none;
  font-size: 11px;
  color: var(--muted);
  letter-spacing: 0.02em;
}
.log-tag.outer { color: #c7a0ff; background: rgba(199, 160, 255, 0.12); border-color: rgba(199, 160, 255, 0.28); }
.log-tag.secret { color: #ffd98a; background: rgba(255, 217, 138, 0.12); border-color: rgba(255, 217, 138, 0.3); }
/* Onboarding storyline (the mission spine) + a met-objective "ready to turn in" badge.
   Mission cues are uniformly yellow across the game. */
.log-tag.spine { color: #ffd233; background: rgba(255, 210, 51, 0.12); border-color: rgba(255, 210, 51, 0.32); }
.log-tag.ready { color: #ffd98a; background: rgba(255, 217, 138, 0.16); border-color: rgba(255, 217, 138, 0.4); }

/* ---- Storyline mission CARD (Mission Log): always-expanded step checklist ----
   Header row (badge + title + reward), the giver's pitch in quiet small type,
   then the live steps from the missions.js hierarchy engine: done ✓ dim green,
   the CURRENT leg(s) ◆ gold with target + live distance (the same target the
   in-flight gold marker points at), later steps ○ muted. */
.log-row.log-mission { display: block; }
.log-mission-head { display: flex; align-items: center; gap: 10px; min-width: 0; }
.log-mission-head .log-route { flex: 1; min-width: 0; }
.log-reward { flex: none; font-size: 12px; color: #ffd98a; letter-spacing: 0.02em; }
.log-blurb { margin: 6px 0 0; font-size: 11.5px; line-height: 1.45; color: var(--muted); }
.log-steps { margin-top: 7px; display: flex; flex-direction: column; gap: 3px; }
/* One line when it fits (label left, target · distance right); when the meta
   would truncate, it WRAPS to its own right-aligned second line instead - the
   live distance is the point of the row, so it never ellipsizes (July 2026,
   user call). flex:none on both spans makes the flex line-breaker do the work. */
.log-step { display: flex; flex-wrap: wrap; align-items: baseline; gap: 2px 7px; font-size: 12.5px; min-width: 0; }
.log-step .st-glyph { flex: none; width: 14px; text-align: center; font-size: 11px; }
.log-step .st-label { flex: none; max-width: calc(100% - 21px); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.log-step .st-meta { flex: none; margin-left: auto; max-width: 100%; text-align: right; font-size: 11px; color: var(--muted); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.log-step.done { color: #7da653; }
.log-step.done .st-meta { color: rgba(125, 166, 83, 0.75); }
.log-step.current { color: #ffd98a; }
.log-step.current .st-glyph { text-shadow: 0 0 6px rgba(255, 217, 138, 0.45); }
.log-step.current .st-meta { color: rgba(255, 217, 138, 0.8); }
.log-step.todo { color: #74869e; }

/* ---- TRACKED mission (July 2026): the ONE followed mission wears a thin gold
   outline with an inline TRACKING legend sitting on the top border; every other
   active card folds to its header + one objective line (tap to expand) and
   carries a small gold TRACK button to switch what the map follows. ---- */
.log-row.tracking {
  position: relative;
  border: 1px solid rgba(255, 210, 51, 0.55);
  border-radius: 10px;
  background: rgba(255, 210, 51, 0.04);
  margin: 12px 0 10px;
  padding: 15px 10px 13px;
}
.log-row.tracking:last-child { border-bottom: 1px solid rgba(255, 210, 51, 0.55); }
.log-track-legend {
  position: absolute;
  top: -8px;
  left: 12px;
  padding: 1px 8px;
  font-size: 9.5px;
  font-weight: 800;
  letter-spacing: 0.24em;
  color: #ffd233;
  background: #0b100e;   /* covers the outline so the text reads INLINE on it */
  border-radius: 4px;
}
.log-track-btn {
  flex: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  height: 24px;
  /* letter-spacing pads the right edge and the mono font's descender space
     floats caps-only text high - rebalance both so TRACK sits dead-center */
  padding: 3px 9px 0 11px;
  font: inherit;
  font-size: 10px;
  font-weight: 700;
  line-height: 1;
  letter-spacing: 0.12em;
  color: rgba(255, 210, 51, 0.85);
  background: transparent;
  border: 1px solid rgba(255, 210, 51, 0.35);
  border-radius: 7px;
  cursor: pointer;
  transition: color 0.14s ease, border-color 0.14s ease, background 0.14s ease;
}
.log-track-btn:hover { color: #ffe27a; border-color: rgba(255, 210, 51, 0.7); background: rgba(255, 210, 51, 0.08); }
.log-track-btn:active { transform: scale(0.96); }
.log-chev { flex: none; width: 12px; text-align: center; color: var(--muted); font-size: 11px; }

/* ---------- Profile panel content ----------
   (Pilots Online is chat's PILOTS tab now - its styles live in chat.js's scoped CSS.) */
.pf-cur { display: flex; align-items: center; gap: 14px; margin-bottom: 10px; }
.pf-info { min-width: 0; }
.pf-name { font-size: 17px; font-weight: 700; color: var(--ink); }
.pf-email { font-size: 12.5px; color: var(--muted); margin-top: 2px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* The career list: one label-and-value row per lifetime number (SOS calls, stars
   visited, top speed). Muted labels, bright tabular values on the right. */
.pf-stats { padding: 6px 0 14px; border-bottom: 1px solid var(--line); margin-bottom: 16px; }
.pf-stat { display: flex; align-items: baseline; justify-content: space-between; gap: 12px; font-size: 12.5px; color: var(--muted); line-height: 1.9; }
.pf-stat b { font-weight: 700; color: var(--ink); font-variant-numeric: tabular-nums; letter-spacing: 0.02em; }
.pf-rename { display: flex; gap: 8px; margin-bottom: 12px; }
.pf-input { flex: 1; min-width: 0; padding: 11px 12px; font: inherit; font-size: 16px; color: var(--ink); background: rgba(8, 12, 18, 0.8); border: 1px solid var(--line); border-radius: 9px; outline: none; }
.pf-input:focus { border-color: rgba(143, 194, 74, 0.55); }
.pf-btn { flex: none; padding: 0 16px; font: inherit; font-size: 13px; font-weight: 700; color: #0c1505; background: #6fcf57; border: none; border-radius: 9px; cursor: pointer; }
.pf-btn:active { transform: scale(0.97); }
.pf-action {
  width: 100%; margin-top: 10px; padding: 12px; font: inherit; font-size: 13px; font-weight: 600; letter-spacing: 0.04em;
  color: rgba(200, 210, 230, 0.9); background: transparent; border: 1px solid var(--line); border-radius: 10px; cursor: pointer;
  transition: color 0.15s ease, border-color 0.15s ease;
}
.pf-action:hover { border-color: rgba(200, 210, 230, 0.4); color: var(--ink); }
.pf-action.danger { color: rgba(255, 138, 122, 0.95); border-color: rgba(255, 106, 90, 0.3); }
.pf-action.danger:hover { color: #ff9a8a; border-color: rgba(255, 106, 90, 0.55); }
.pf-retire { margin-top: 18px; }   /* push the destructive action to the bottom, set apart */

/* ---- Ship panel: the INSPECTION page - static ship + one-line-per-system sheet ---- */
.ship-body { display: flex; flex-direction: column; align-items: stretch; gap: 12px; }
.ship-preview {
  width: 100%; height: 120px; display: block; flex: none;
  border-radius: 12px;
  background: radial-gradient(120% 120% at 50% 38%, rgba(40, 60, 90, 0.28) 0%, rgba(6, 10, 16, 0.6) 70%);
  border: 1px solid rgba(143, 194, 74, 0.18);
}
.ship-specs { display: flex; flex-direction: column; gap: 8px; }
.ship-spec-head { display: flex; align-items: center; gap: 9px; }
.ship-spec-swatch { width: 14px; height: 14px; border-radius: 50%; border: 1px solid rgba(255, 255, 255, 0.25); flex: none; }
.ship-spec-name { font-size: 15px; font-weight: 700; color: var(--ink); }
/* Stat tiles (fuel / boost / mass) */
.ship-stats { display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 6px; }
.ship-stat {
  display: flex; flex-direction: column; gap: 1px; align-items: center;
  padding: 7px 4px; border: 1px solid var(--line); border-radius: 10px;
  background: rgba(10, 16, 24, 0.45);
}
.ship-stat b { font-size: 14px; font-weight: 700; color: var(--ink); font-variant-numeric: tabular-nums; white-space: nowrap; }
.ship-stat span { font-size: 9px; letter-spacing: 0.14em; text-transform: uppercase; color: var(--muted); }
/* System rows: label | current effect | level pips - one line each */
.ship-spec-row { display: flex; align-items: center; gap: 10px; padding: 2px 0; }
.ship-spec-label { flex: 1; min-width: 0; font-size: 13px; color: var(--ink); white-space: nowrap; }
.ship-spec-eff { font-size: 12px; color: var(--muted); white-space: nowrap; font-variant-numeric: tabular-nums; }
.ship-spec-pips { display: flex; gap: 4px; min-width: 35px; justify-content: flex-end; }
.ship-spec-pips i { width: 9px; height: 9px; border-radius: 50%; border: 1px solid rgba(143, 194, 74, 0.4); background: transparent; }
.ship-spec-pips i.on { background: #8fc24a; border-color: #8fc24a; }
/* The rig card: the Mining Rig row + its ore chips boxed together */
.ship-rig {
  display: flex; flex-direction: column; gap: 8px;
  padding: 8px 10px; border: 1px solid rgba(143, 194, 74, 0.32); border-radius: 10px;
  background: rgba(10, 16, 24, 0.35);
}
/* Ore chips: what the fitted rig can pull (locked = dimmed, tagged with rig level) */
.ship-ores { display: flex; flex-wrap: wrap; gap: 6px; }
.ship-ore {
  display: inline-flex; align-items: baseline; gap: 5px;
  font-size: 11.5px; font-weight: 600; letter-spacing: 0.02em;
  padding: 3px 9px; border-radius: 999px; border: 1px solid var(--line);
  white-space: nowrap;
}
.ship-ore.locked { color: rgba(140, 155, 175, 0.6); border-style: dashed; }
.ship-ore.locked i { font-style: normal; font-size: 9px; font-weight: 700; letter-spacing: 0.06em; color: rgba(140, 155, 175, 0.75); }

#roster-screen {
  position: fixed;
  inset: 0;
  z-index: 26;                 /* above #interior (22), the in-colony chrome (23) and #menu/#nav (24): Main Menu → Switch cricket from INSIDE a station used to open the picker UNDER the colony deck, which read as "it resumed instead" (Sep 2026 user report) */
  display: flex;
  align-items: center;
  justify-content: center;
  background: rgba(4, 6, 11, 0.74);
  backdrop-filter: blur(4px);
}
.roster-card {
  display: flex;
  flex-direction: column;
  width: min(94vw, 460px);
  max-height: 88vh;
  background: linear-gradient(180deg, #0c1410 0%, #0a0e15 100%);
  border: 1px solid rgba(143, 194, 74, 0.28);
  border-radius: 16px;
  box-shadow: 0 18px 60px rgba(0, 0, 0, 0.6), 0 0 40px rgba(143, 194, 74, 0.06);
  overflow: hidden;
}
.roster-head {
  display: flex;
  align-items: center;
  padding: 16px 16px 14px 18px;
  border-bottom: 1px solid var(--line);
}
.roster-title { font-size: 18px; font-weight: 700; letter-spacing: 0.03em; color: #c7e6a0; }
.roster-x {
  margin-left: auto;
  width: 30px; height: 30px;
  font-size: 15px;
  color: var(--muted);
  background: rgba(255, 255, 255, 0.04);
  border: 1px solid var(--line);
  border-radius: 8px;
  cursor: pointer;
}
.roster-x:hover { color: var(--ink); border-color: rgba(200, 210, 230, 0.4); }
.roster-list { flex: 1; overflow-y: auto; padding: 10px 14px; }
.roster-row {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 12px 10px;
  margin-bottom: 8px;
  background: rgba(255, 255, 255, 0.025);
  border: 1px solid rgba(120, 140, 170, 0.14);
  border-radius: 12px;
}
.roster-row.active { border-color: rgba(143, 194, 74, 0.5); background: rgba(143, 194, 74, 0.06); }
.roster-swatch {
  flex: none;
  width: 30px; height: 30px;
  border-radius: 50%;
  border: 2px solid rgba(255, 255, 255, 0.25);
  box-shadow: 0 0 10px rgba(0, 0, 0, 0.4);
}
.roster-main { flex: 1; min-width: 0; }
.roster-name {
  font-size: 15px;
  font-weight: 700;
  color: var(--ink);
  display: flex;
  align-items: center;
  gap: 7px;
}
.roster-badge {
  font-size: 9px;
  font-weight: 700;
  letter-spacing: 0.1em;
  padding: 2px 6px;
  border-radius: 5px;
  text-transform: uppercase;
}
.roster-badge.rebuild { color: #ffc24d; background: rgba(255, 194, 77, 0.12); border: 1px solid rgba(255, 194, 77, 0.32); }
.roster-meta { font-size: 12px; color: var(--muted); margin-top: 3px; letter-spacing: 0.02em; }
.roster-actions { flex: none; display: flex; flex-direction: column; gap: 6px; }
.roster-actions button {
  font: inherit;
  font-size: 12px;
  font-weight: 600;
  letter-spacing: 0.04em;
  padding: 7px 12px;
  color: var(--ink);
  background: rgba(255, 255, 255, 0.04);
  border: 1px solid var(--line);
  border-radius: 8px;
  cursor: pointer;
  white-space: nowrap;
  transition: border-color 0.12s ease, transform 0.08s ease;
}
.roster-actions button.primary { color: #c7e6a0; border-color: rgba(143, 194, 74, 0.45); }
.roster-actions button.danger:hover { border-color: rgba(255, 120, 100, 0.5); color: #ff9a8a; }
.roster-actions button:hover { border-color: rgba(200, 210, 230, 0.4); }
.roster-actions button:active { transform: scale(0.97); }
.roster-actions button:disabled { opacity: 0.5; cursor: default; }
.roster-note {
  padding: 10px 16px 16px;
  font-size: 11px;
  line-height: 1.4;
  color: var(--muted);
  letter-spacing: 0.02em;
}

/* ---------- Out-of-fuel overlay ----------
   A compact pill pinned at the TOP-CENTER, only as wide as its content. Stays up
   while stranded. The wrapper is click-through (the world + gestures still work
   behind it); only the pill/button grab input. */
#fuel-out {
  position: fixed;
  top: 0; left: 0; right: 0;
  z-index: 18;
  display: flex;
  justify-content: center;
  pointer-events: none;
}
#fuel-out .fo-card {
  pointer-events: auto;
  display: flex;
  align-items: center;
  gap: 9px;
  margin-top: calc(env(safe-area-inset-top, 0px) + 12px);
  padding: 8px 8px 8px 14px;
  background: rgba(14, 12, 10, 0.92);
  border: 1px solid rgba(255, 150, 120, 0.5);
  border-radius: 999px;
  box-shadow: 0 8px 30px rgba(0, 0, 0, 0.55), 0 0 22px rgba(255, 120, 90, 0.12);
  backdrop-filter: blur(6px);
}
#fuel-out .fo-title {
  font-size: 12px;
  font-weight: 700;
  letter-spacing: 0.06em;
  color: #ff9d6c;
  text-shadow: 0 0 14px rgba(255, 120, 90, 0.3);
}
/* The one action: SOS - emergency red; opens the Rescue Dispatch popup. While a
   rescue is inbound it turns into a disabled status chip (TUG INBOUND… etc.). */
#fuel-sos {
  font: inherit;
  font-size: 11px;
  font-weight: 800;
  letter-spacing: 0.08em;
  color: #fff;
  background: linear-gradient(180deg, #ff7a5c 0%, #e04a30 100%);
  border: none;
  border-radius: 999px;
  padding: 7px 14px;
  cursor: pointer;
  transition: filter 0.12s ease, transform 0.08s ease;
}
#fuel-sos:hover { filter: brightness(1.1); }
#fuel-sos:active { transform: scale(0.97); }
#fuel-sos.disabled {
  opacity: 0.55;
  cursor: default;
  pointer-events: none;
}

/* Emergency-tow blackout: covers the "burn home" + the re-seed at the berth. */
#tow-fade {
  position: fixed;
  inset: 0;
  z-index: 40;
  background: #000;
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.9s ease;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 24px;
}
#tow-fade.show { opacity: 1; pointer-events: auto; }
/* Tow status caption, centered on the blackout (replaces the top toast, which
   collided with the OUT OF FUEL pill). */
#tow-fade .tow-msg {
  max-width: 22em;
  text-align: center;
  font-size: 15px;
  font-weight: 600;
  letter-spacing: 0.06em;
  line-height: 1.5;
  color: #ff9d6c;
  text-shadow: 0 0 18px rgba(255, 120, 90, 0.35);
}

/* A rescue taken on credit leaves the balance negative - tint it until repaid. */
#hud-credits.debt { color: #ff8d7a; }

/* DEBUG: always-on free fuel-drone launcher (testing aid). Pinned bottom-left. */
#debug-drone {
  position: fixed;
  left: 12px;
  bottom: 12px;
  z-index: 60;
  font: inherit;
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.08em;
  color: #ffcf6a;
  background: rgba(20, 16, 6, 0.72);
  border: 1px solid rgba(255, 207, 106, 0.55);
  border-radius: 999px;
  padding: 7px 13px;
  cursor: pointer;
  opacity: 0.7;
  transition: filter 0.12s ease, transform 0.08s ease, opacity 0.12s ease;
}
#debug-drone:hover { opacity: 1; filter: brightness(1.12); }
#debug-drone:active { transform: scale(0.97); }
/* The debug pills are z 60 (over everything) and pinned bottom-left - exactly the
   nav console's + WAYPOINT / DELETE buttons. Hide them while the console is open
   (body.nav-open, game.js openNav/closeNav) so they can't steal the taps. */
body.nav-open #debug-drone, body.nav-open #debug-belt { display: none; }
/* Same story for the MAIN MENU (cricket select), which is reachable mid-flight
   (Profile -> Main Menu) and pins its own account button to the bottom centre. */
body.menu-screen #debug-drone, body.menu-screen #debug-belt, body.menu-screen #diag-copy { display: none; }
#debug-drone.disabled {
  opacity: 0.3;
  cursor: not-allowed;
  pointer-events: none;
}
#debug-drone.hidden { display: none; }

#debug-belt {
  position: fixed;
  left: 168px;
  bottom: 12px;
  z-index: 60;
  font: inherit;
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.08em;
  color: #9fd6e8;
  background: rgba(8, 16, 22, 0.72);
  border: 1px solid rgba(143, 207, 230, 0.55);
  border-radius: 999px;
  padding: 7px 13px;
  cursor: pointer;
  opacity: 0.7;
  transition: filter 0.12s ease, transform 0.08s ease, opacity 0.12s ease;
}
#debug-belt:hover { opacity: 1; filter: brightness(1.12); }
#debug-belt:active { transform: scale(0.97); }
#debug-belt.hidden { display: none; }

/* ---------- Join: name-in-use error + ship-color picker ---------- */
.name-err {
  color: #ff8a7a;
  font-size: 12px;
  letter-spacing: 0.03em;
  margin: -4px 0 10px;
}
/* ---------- Cinematic intro ----------
   Fresh-launch only. Lore lines fade in one at a time, then the first delivery
   target (which glows for a couple seconds), then the ship name + color pinned at
   the bottom. Tap / any key to skip. Pure presentation - sits over the dark join
   backdrop while the world spawns behind it. */
#intro {
  position: fixed;
  inset: 0;
  z-index: 40;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  background: radial-gradient(ellipse 120% 90% at 50% 38%, #0a1016 0%, #05070d 72%);
  cursor: pointer;
  transition: opacity 0.6s ease;
}
#intro.fade-out { opacity: 0; }
.intro-inner {
  flex: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 10px;
  text-align: center;
  padding: 0 24px;
}
/* Each beat starts hidden + slightly low, then rises in when JS adds .show. Slow
   fades (~1.2s) so the reveal reads as a calm, deliberate cold open. */
.intro-step {
  opacity: 0;
  transform: translateY(8px);
  transition: opacity 1.2s ease, transform 1.2s ease;
}
.intro-step.show { opacity: 1; transform: translateY(0); }
.intro-line {
  font-size: 18px;
  line-height: 1.5;
  letter-spacing: 0.03em;
  color: var(--ink);
}
.intro-line.you { margin-top: 28px; color: var(--ink); }
/* Slightly smaller on phones so the wider name box still keeps each line single. */
@media (max-width: 480px) { .intro-line { font-size: 14px; letter-spacing: 0.01em; } }

/* The refuel instruction reads as the next line of the lore; the home station name
   glows yellow to match its in-world highlight. */
.intro-mission { margin-top: 4px; }
.intro-home {
  font-weight: 700;
  letter-spacing: 0.03em;
  color: #ffd98a;
}
/* The station name flares bright when its line appears, then settles to a soft glow. */
.intro-step.show .intro-home { animation: destGlow 3s ease 0.1s 1 forwards; }
@keyframes destGlow {
  0%   { text-shadow: 0 0 0 rgba(255, 217, 138, 0); }
  30%  { text-shadow: 0 0 24px rgba(255, 217, 138, 0.95), 0 0 46px rgba(255, 217, 138, 0.55); }
  100% { text-shadow: 0 0 11px rgba(255, 217, 138, 0.32); }
}

/* Inline pilot-name field, woven into "You, [name], are a space cricket." Sized to its
   content so it reads as part of the sentence rather than a separate form. */
.intro-name-inline {
  width: 6em;
  max-width: 40vw;
  padding: 0 6px;
  margin: 0 2px;
  font: inherit;
  text-align: center;
  color: #c7e6a0;
  background: rgba(143, 194, 74, 0.08);
  border: 1.5px solid var(--grass);
  border-radius: 7px;
  outline: none;
  vertical-align: baseline;
}
.intro-name-inline::placeholder { color: rgba(199, 230, 160, 0.45); font-weight: 400; }
/* The "name" hint is centered, so it sits right where the caret lands - drop it the
   instant the field is focused so you type into a clean box (not "into" the word). */
.intro-name-inline:focus::placeholder { color: transparent; }
/* iOS Safari IGNORES the viewport meta's maximum-scale/user-scalable (since iOS 10),
   so a focused field under 16px makes Safari auto-zoom the page in - and it often
   doesn't zoom back out, leaving the player stuck. Bump to 16px on touch so focusing
   the inline name field never triggers that (the page-text stays 14px via .intro-line). */
@media (pointer: coarse) { .intro-name-inline { font-size: 16px; } }
.intro-name-inline:focus { background: rgba(143, 194, 74, 0.16); }
.intro .name-err { margin: 6px 0 0; text-align: center; }

/* Begin: the primary launch button, revealed (and made clickable) only at ready. Sits
   right below the intro text inside .intro-inner. */
.intro-begin {
  margin-top: 18px;
  font: inherit;
  font-size: 14px;
  font-weight: 600;
  letter-spacing: 0.08em;
  color: #0c1505;
  background: var(--grass);
  border: none;
  border-radius: 10px;
  padding: 11px 32px;
  cursor: pointer;
  opacity: 0;
  transform: translateY(6px);
  pointer-events: none;
  transition: opacity 0.5s ease, transform 0.5s ease, filter 0.12s ease;
}
.intro-begin:hover { filter: brightness(1.08); }
.intro-begin:active { transform: scale(0.98); }
/* Disabled until a valid (non-empty, unused) pilot name is entered. */
.intro-begin:disabled, .intro-begin.disabled { opacity: 0.38; cursor: default; filter: none; }
#intro.ready .intro-begin { opacity: 1; transform: translateY(0); pointer-events: auto; }
#intro.ready .intro-skip { opacity: 0; }
.intro-skip {
  position: absolute;
  bottom: calc(12px + env(safe-area-inset-bottom));
  right: 16px;
  font-size: 11px;
  letter-spacing: 0.1em;
  color: rgba(138, 148, 168, 0.5);
  transition: opacity 0.4s ease;
}

/* ---------- Generic centered popup ----------
   Reused for the onboarding "supplies loaded" hand-off and the choose-your-next-
   contract picker. A blurred backdrop + a grass-themed card with a title, body, and
   a column of action buttons. Sits above the shop/log so it reads as a decision. */
#popup {
  position: fixed;
  inset: 0;
  z-index: 45;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 22px;
  background: rgba(4, 6, 11, 0.72);
  backdrop-filter: blur(4px);
}
.popup-card {
  position: relative;             /* anchors the optional corner ✕ (.popup-x) */
  width: min(94vw, 420px);
  max-height: 86vh;
  overflow-y: auto;
  padding: 22px 22px 20px;
  text-align: center;
  background: linear-gradient(180deg, #0c1410 0%, #0a0e15 100%);
  border: 1px solid rgba(143, 194, 74, 0.28);
  border-radius: 16px;
  box-shadow: 0 18px 60px rgba(0, 0, 0, 0.6);
}
/* The optional corner ✕ (game.js openPopup's `closeX`, worn by the SOS dialogs).
   The .rb-x / #boost-warn recipe: a quiet borderless glyph in the top-right corner,
   26px desktop and 32px on the touch tier - the tap-miss floor the race panel's EXIT
   documents. `font: inherit` then a size, because a <button> does not inherit type.
   Deliberately neutral rather than tinted per card variant: the title is the card's
   colour, and a second coloured mark in the corner would compete with it. The title
   is centred and short, so the corner box never crowds it. */
.popup-x {
  position: absolute;
  top: 8px;
  right: 8px;
  width: 26px;
  height: 26px;
  padding: 0;
  border: 0;
  border-radius: 8px;
  background: none;
  color: #8fa2b8;
  font: inherit;
  font-size: 13px;
  line-height: 26px;
  text-align: center;
  cursor: pointer;
}
.popup-x:hover { color: #e8eef7; background: rgba(255, 255, 255, 0.08); }
body.touch .popup-x { width: 32px; height: 32px; line-height: 32px; top: 6px; right: 6px; }
/* Reserve the corner so a LONG title can never run under the ✕ - and the inset has to
   follow the title's own alignment, which differs by card variant. The default card
   CENTRES its title, so it takes a SYMMETRIC inset (the .rb-notice lesson: a one-sided
   inset shoves centred copy off-centre to protect a corner it does not reach). A quest
   card - which is what both SOS dialogs are - LEFT-aligns its title, so it pads only
   the side the ✕ is on; padding the left too would break the title's alignment with
   the body prose underneath it. */
.popup-card.has-popup-x .popup-title { padding: 0 30px; }
body.touch .popup-card.has-popup-x .popup-title { padding: 0 34px; }
.popup-card.popup-quest.has-popup-x .popup-title { padding: 0 30px 0 0; }
body.touch .popup-card.popup-quest.has-popup-x .popup-title { padding: 0 34px 0 0; }
.popup-title {
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.28em;
  color: #9fd16a;
  margin-bottom: 12px;
}
.popup-body {
  font-size: 15px;
  line-height: 1.55;
  color: var(--ink);
}
.popup-body p { margin: 0 0 11px; }
.popup-body p:last-child { margin-bottom: 0; }
/* Emphasis stays the SAME colour & size as the prose - only the slant changes, so
   the card reads in one calm grey with a single gold accent (the station name). */
.popup-body i { font-style: italic; color: inherit; }
.popup-body b { color: #ffd98a; font-weight: 700; }
.popup-dest { color: #ffd98a; }
.popup-sub {
  margin-top: 10px;
  font-size: 12.5px;
  color: var(--muted);
}
.popup-actions {
  display: flex;
  flex-direction: column;
  gap: 10px;
  margin-top: 18px;
}
.popup-btn {
  font: inherit;
  font-size: 14px;
  font-weight: 600;
  letter-spacing: 0.06em;
  color: #0c1505;
  background: var(--grass);
  border: none;
  border-radius: 10px;
  padding: 11px 18px;
  cursor: pointer;
  transition: filter 0.12s ease, transform 0.08s ease;
}
.popup-btn:hover { filter: brightness(1.1); }
.popup-btn:active { transform: scale(0.98); }
/* Contract-offer buttons: a two-line layout (station name + orbit/reward), themed as
   a selectable panel rather than the solid primary action. */
.popup-btn.popup-offer {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 3px;
  padding: 12px 16px;
  color: var(--ink);
  background: rgba(143, 194, 74, 0.1);
  border: 1px solid rgba(143, 194, 74, 0.35);
}
.popup-btn.popup-offer:hover { background: rgba(143, 194, 74, 0.2); filter: none; }
/* Secondary "Refill for ₡N" action: an outline button that sits ABOVE the solid green
   primary, so a fresh low-fuel cricket can top off without leaving the card. */
.popup-btn.popup-refill {
  color: var(--ink);
  background: rgba(143, 194, 74, 0.1);
  border: 1px solid rgba(143, 194, 74, 0.35);
}
.popup-btn.popup-refill:hover { background: rgba(143, 194, 74, 0.2); filter: none; }
.popup-btn.disabled { opacity: 0.5; pointer-events: none; }
/* "Look around first": a quiet, secondary action that defers the contract choice so you
   can scout the map before committing. Outline/muted so it never competes with the offers. */
.popup-btn.popup-later {
  color: var(--muted);
  background: transparent;
  border: 1px solid rgba(143, 194, 74, 0.22);
  font-weight: 500;
}
.popup-btn.popup-later:hover { color: var(--ink); border-color: rgba(143, 194, 74, 0.45); filter: none; }
/* Quiet parenthetical hint under a popup's main line (e.g. the shop nudge). */
.popup-aside { color: var(--muted); }

/* ---------- Quest / mission dialogue box (shared styling) ----------
   Mirrors the in-colony canvas mission-giver card (drawSpineModal in colony.js): a
   dark, gold-bordered speech card with a gold speaker title, left-aligned prose, and
   a single solid-gold action. Apply via openPopup({ cardCls: 'popup-quest' }) so any
   quest-flavoured popup (e.g. Hopper's post-First-Berth nudge) reads like the crew
   conversations inside the station. */
.popup-card.popup-quest {
  text-align: left;
  background: linear-gradient(180deg, #0e131c 0%, #0a0e15 100%);
  border: 1px solid rgba(255, 210, 51, 0.55);
}
.popup-card.popup-quest .popup-title {
  font-size: 17px;
  font-weight: 700;
  letter-spacing: 0.01em;
  text-transform: none;
  color: #ffe2a6;
  margin-bottom: 10px;
}
.popup-card.popup-quest .popup-body { color: rgba(214, 224, 240, 0.95); }
/* Mission-log dialogue recap: a "<giver> at <station> said:" attribution over the
   giver's quoted line (a completed mission stacks the offer + turn-in exchanges). */
.popup-say-who {
  margin: 14px 0 4px;
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: #ffd98a;
}
.popup-say-who:first-child { margin-top: 0; }
.popup-say { margin: 0; color: rgba(214, 224, 240, 0.95); }
/* The bottom action: the solid gold button from the canvas modalBtn. */
.popup-btn.popup-quest-ok {
  color: #1a1405;
  font-weight: 700;
  letter-spacing: 0.08em;
  background: linear-gradient(180deg, #ffe06b 0%, #e8b400 100%);
}
.popup-btn.popup-quest-ok:hover { filter: brightness(1.06); }
/* Quiet gray secondary on a quest card - the canvas mission modal's DECLINE gray
   (NOT YET / NOT HERE / CLOSE on the charter + deed sheets). */
.popup-btn.popup-quiet {
  color: #cfd6e4;
  background: rgba(255, 255, 255, 0.08);
  border: 1px solid rgba(120, 140, 170, 0.35);
  font-weight: 600;
}
.popup-btn.popup-quiet:hover { filter: none; background: rgba(255, 255, 255, 0.13); }

/* ---------- SOS "Rescue Dispatch" dialog ----------
   Shares the quest-dialogue card (openPopup cardCls 'popup-quest popup-sos') so it
   reads like the crew conversations inside a station - recolored from the crew gold
   to the emergency fuel red (these rules sit AFTER .popup-quest so the recolor wins
   the specificity tie). Options are full-width rows: name · fee* + a sub-line. */
.popup-card.popup-sos {
  border-color: rgba(255, 140, 110, 0.55);
  background: linear-gradient(180deg, #171010 0%, #0c0a0e 100%);
}
.popup-card.popup-sos .popup-title { color: #ff9d6c; }
.popup-btn.sos-opt {
  display: block;
  width: 100%;
  text-align: left;
  color: #ffe6da;
  background: rgba(255, 122, 92, 0.10);
  border: 1px solid rgba(255, 140, 110, 0.45);
}
.popup-btn.sos-opt:hover { background: rgba(255, 122, 92, 0.20); }
.sos-opt-name { display: block; font-weight: 700; letter-spacing: 0.06em; color: #ffb08e; }
.popup-btn .sos-sub {
  display: block;
  margin-top: 3px;
  font-size: 12px;
  font-weight: 400;
  letter-spacing: 0.01em;
  color: rgba(214, 224, 240, 0.75);
}
/* Quote note: accepting this fee puts your balance into debt. */
.popup-btn .sos-sub.sos-warn { color: rgba(214, 224, 240, 0.75); }
/* The roguelike wipe: present but deliberately quiet. */
.popup-btn.sos-abandon {
  background: none;
  border: 1px solid rgba(255, 255, 255, 0.14);
  color: rgba(214, 224, 240, 0.6);
  font-size: 12px;
}
.popup-btn.sos-abandon:hover { border-color: rgba(255, 255, 255, 0.3); }
/* Generic popup small print BELOW the actions (the SOS "* on your tab" footnote). */
.popup-foot {
  margin-top: 12px;
  font-size: 12px;
  line-height: 1.5;
  color: var(--muted);
  text-align: left;
}
.popup-foot.hidden { display: none; }

/* ---------- Gas-pump refuel popup (openFuelPump) ----------
   A clean fuel terminal inside the generic #popup card: a digital readout, a tank
   gauge, a scrub slider + quick presets + custom-credits entry, then PUMP. */
.pump { display: flex; flex-direction: column; gap: 13px; text-align: left; }
/* Digital readout - dark screen, big monospace CREDITS total + a smaller result line. */
.pump-screen {
  border-radius: 12px;
  padding: 14px 16px;
  background: radial-gradient(120% 140% at 50% 0%, #0a1620 0%, #060c12 100%);
  border: 1px solid rgba(143, 194, 74, 0.22);
  box-shadow: inset 0 0 24px rgba(0, 0, 0, 0.55);
}
.pump-total {
  font: 700 34px ui-monospace, Menlo, monospace;
  letter-spacing: 0.02em;
  color: #7fe7c4;
  text-shadow: 0 0 12px rgba(127, 231, 196, 0.45);
  line-height: 1.05;
}
.pump-sub { margin-top: 5px; font-size: 12.5px; color: var(--muted); }
.pump-sub b { color: #ffd98a; font-weight: 700; }
/* The single tank bar IS the slider: a tall rounded track painted yellow (have) |
   green (adding) | empty inline in JS. Drag the thumb to set how much to pump. */
/* Custom bar (not a native range input): a div track painted yellow|green|empty in JS
   with an absolutely-positioned thumb, so the fill spans the TRUE edges (0%→100%) and
   the thumb hides cleanly while pumping. */
.pump-bar {
  position: relative;
  width: 100%; height: 24px; border-radius: 0; outline: none; cursor: pointer;
  background-color: rgba(255, 255, 255, 0.10);
  background-repeat: no-repeat;        /* no wrap-around seam of the fill's start colour */
  background-clip: padding-box;        /* keep the fill OFF the border so it stays a clean uniform edge */
  border: 1px solid rgba(127, 231, 196, 0.30);
  box-shadow: inset 0 0 10px rgba(0, 0, 0, 0.4);
  touch-action: none;
}
.pump-thumb {
  position: absolute; top: 50%; left: 0;
  width: 16px; height: 34px; border-radius: 6px;
  transform: translate(-50%, -50%);
  background: #ffffff; border: 2px solid #0a1218;
  box-shadow: 0 1px 5px rgba(0, 0, 0, 0.6);
  pointer-events: none;
}
.pump-bar.pumping { cursor: default; }
.pump-bar.pumping .pump-thumb { display: none; }
/* PUMP primary action - cyan to match the cryo fuel theme. */
.popup-btn.pump-go {
  background: #7fe7c4; color: #062018;
  letter-spacing: 0.08em;
}
.popup-btn.pump-go.pumping { background: #5cc7a6; cursor: default; }
/* STATION TANK gauge (player depots): the berth's own stock as a slim cyan bar
   below the CANCEL button - cyan, never the ship bar's yellow/green, so the two
   tanks can't read as one control. Built by openFuelPump into #popup-actions
   (which every popup opener clears); it drains live while the pump runs. */
.pump-station { text-align: left; margin-top: 4px; }
.pump-station-head {
  display: flex; justify-content: space-between; align-items: baseline;
  margin-bottom: 5px;
}
.pump-station-name { font-size: 11px; letter-spacing: 0.14em; font-weight: 700; color: #8fb8ad; }
.pump-station-units { font: 600 12.5px ui-monospace, Menlo, monospace; color: #7fe7c4; }
.pump-station-units .pump-station-cap { color: #6c8a80; font-weight: 400; }
.pump-station-bar {
  position: relative; height: 12px;
  background: rgba(255, 255, 255, 0.08);
  border: 1px solid rgba(127, 231, 196, 0.25);
  box-shadow: inset 0 0 8px rgba(0, 0, 0, 0.4);
}
.pump-station-fill {
  position: absolute; top: 0; bottom: 0; left: 0;
  background: linear-gradient(180deg, #63d9e8, #3fb4c9);
  box-shadow: 0 0 10px rgba(99, 217, 232, 0.35);
}
/* the bright reading edge - the level mark the eye tracks as the tank drains */
.pump-station-fill::after {
  content: ''; position: absolute; top: -3px; bottom: -3px; right: -1px;
  width: 2px; background: #bff3fb;
}

/* ---------- The ANTIMATTER pump (the booster stage's tank) ----------
   The same terminal in booster violet (#b06cff, the containment-ring accent the
   burn button and the HUD's antimatter chip already wear), so the second pump at
   the fuel-bay kiosk reads as the same machine for a different fluid. Only the
   INK changes - every box, gauge and animation is the fuel pump's own. */
/* The CARD wears the violet too (applied as cardCls in openAmPump) - the fuel pump's
   green frame around a violet gauge read as two machines bolted together. Same
   geometry as .popup-card, only the ink moves: the popup-quest variant's pattern. */
.popup-card.popup-am { border: 1px solid rgba(176, 108, 255, 0.5); }
.popup-card.popup-am .popup-title { color: #c9a2ff; }
.pump-am .pump-screen { border-color: rgba(176, 108, 255, 0.24); }
.pump-am .pump-total { color: #c9a2ff; text-shadow: 0 0 12px rgba(176, 108, 255, 0.45); }
.pump-am .pump-sub b { color: #c9a2ff; }
/* The unit word rides at the readout's own size class, not the 34px numeral - the
   number is the reading, "units" is just what it is counted in. */
.pump-am .pump-unit { font-size: 15px; color: var(--muted); letter-spacing: 0.04em; }
.pump-am .pump-bar { border-color: rgba(176, 108, 255, 0.34); }
.popup-btn.pump-go-am { background: #b06cff; color: #12042a; }
.popup-btn.pump-go-am.pumping { background: #8f52d6; cursor: default; }
/* RING STOCK gauge (Aug 2026, user call - "similar to your ship pump also display
   tank level"): the fuel pump's STATION TANK graphic, violet, so the ring reads as
   this machine's own tank rather than a second control. */
.pump-station-am .pump-station-name { color: #a58bc4; }
.pump-station-am .pump-station-units { color: #c9a2ff; }
.pump-station-am .pump-station-units .pump-station-cap { color: #7d6a96; }
.pump-station-am .pump-station-bar { border-color: rgba(176, 108, 255, 0.28); }
.pump-station-am .pump-station-fill {
  background: linear-gradient(180deg, #b06cff, #7a4fd0);
  box-shadow: 0 0 10px rgba(176, 108, 255, 0.35);
}
.pump-station-am .pump-station-fill::after { background: #e2ccff; }

/* ---------- Login gate ----------
   The first screen everyone sees: a full-screen sign-up / sign-in over a slow
   starfield. Login is REQUIRED - nothing is reachable behind it. Mobile-first:
   one centered column, comfortably tappable, "space cricket" in cricket-green. */
#gate {
  position: fixed;
  inset: 0;
  z-index: 60;                 /* above everything; the game is unreachable until signed in */
  background: #04060b;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: max(28px, env(safe-area-inset-top), env(safe-area-inset-bottom)) 22px max(28px, env(safe-area-inset-top), env(safe-area-inset-bottom));   /* SYMMETRIC (the bigger inset both ends), so the column sits at true screen centre, where the app launch image draws it */
}
#gate-stars { position: absolute; inset: 0; width: 100%; height: 100%; display: block; }
.gate-inner {
  position: relative;          /* above the stars canvas */
  z-index: 1;
  width: min(92vw, 350px);
  display: flex;
  flex-direction: column;
  text-align: center;
}
.gate-title {
  font-size: 34px;
  font-weight: 700;
  letter-spacing: 0.03em;
  color: #7ed957;              /* bright cricket green on black */
  text-shadow: 0 0 30px rgba(126, 217, 87, 0.45);
  margin-bottom: 28px;
  white-space: nowrap;
}
.gate-resolving { color: var(--muted); font-size: 13px; letter-spacing: 0.14em; }
/* "loading" with dots that step . .. ... (user call, Sep 2026 - the iPhone app's
   loading screen). The dots live in ::after so the text node stays "loading"; a
   fixed-width slot keeps the word from shifting as the dots come and go. */
.gate-resolving::after { content: '...'; display: inline-block; width: 1.4em; text-align: left; animation: gate-dots 1.8s steps(1, end) infinite; }
@keyframes gate-dots { 0% { content: '...'; } 33% { content: '.'; } 66% { content: '..'; } }
.gate-form { display: flex; flex-direction: column; gap: 11px; text-align: left; }
/* "Continue with Google" - a white button with the official multi-colour G mark. */
.gate-google {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 10px;
  width: 100%;
  padding: 13px;
  font: inherit;
  font-size: 14.5px;
  font-weight: 600;
  letter-spacing: 0.01em;
  color: #1f2733;
  background: #fff;
  border: 1px solid rgba(255, 255, 255, 0.85);
  border-radius: 11px;
  cursor: pointer;
  transition: filter 0.15s ease, transform 0.08s ease, opacity 0.15s ease;
}
.gate-google:hover { filter: brightness(0.96); }
.gate-google:active { transform: scale(0.98); }
.gate-google svg { display: block; flex: none; }
.gate-google:disabled { opacity: 0.6; cursor: default; }
/* "Sign in with Apple" (app only) - Apple's black button, the same footprint as Google's. */
.gate-apple {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 10px;
  width: 100%;
  padding: 13px;
  font: inherit;
  font-size: 14.5px;
  font-weight: 600;
  letter-spacing: 0.01em;
  color: #fff;
  background: #000;
  border: 1px solid rgba(255, 255, 255, 0.35);
  border-radius: 11px;
  cursor: pointer;
  transition: filter 0.15s ease, transform 0.08s ease, opacity 0.15s ease;
}
.gate-apple:hover { filter: brightness(1.15); }
.gate-apple:active { transform: scale(0.98); }
.gate-apple svg { display: block; flex: none; }
.gate-apple:disabled { opacity: 0.6; cursor: default; }
.gate-apple.hidden { display: none; }
.gate-divider {
  display: flex;
  align-items: center;
  gap: 10px;
  color: var(--muted);
  font-size: 11px;
  letter-spacing: 0.12em;
  margin: 2px 0;
}
.gate-divider::before, .gate-divider::after { content: ''; flex: 1; height: 1px; background: var(--line); }
#gate-email, #gate-pass {
  width: 100%;
  padding: 13px 14px;
  font: inherit;
  font-size: 16px;             /* 16px so iOS Safari never auto-zooms the page on focus */
  color: var(--ink);
  background: rgba(255, 255, 255, 0.05);
  border: 1px solid var(--line);
  border-radius: 11px;
  outline: none;
  transition: border-color 0.14s ease;
}
#gate-email:focus, #gate-pass:focus { border-color: #7ed957; }
.gate-err {
  font-size: 12.5px;
  line-height: 1.45;
  color: #ff8a7a;
  letter-spacing: 0.02em;
  margin: -2px 2px 0;
}
.gate-err.ok { color: #7ee0a0; }
.gate-go {
  width: 100%;
  margin-top: 4px;
  padding: 14px;
  font: inherit;
  font-size: 15px;
  font-weight: 700;
  letter-spacing: 0.06em;
  color: #0c1505;
  background: #6fcf57;
  border: none;
  border-radius: 11px;
  cursor: pointer;
  transition: filter 0.15s ease, transform 0.08s ease, opacity 0.15s ease;
}
.gate-go:hover { filter: brightness(1.08); }
.gate-go:active { transform: scale(0.98); }
.gate-go.busy { opacity: 0.6; cursor: default; }
.gate-forgot {
  align-self: center;
  font: inherit;
  font-size: 12px;
  color: var(--muted);
  background: transparent;
  border: none;
  cursor: pointer;
  padding: 4px;
  transition: color 0.14s ease;
}
.gate-forgot:hover { color: var(--accent); }
.gate-alt {
  text-align: center;
  font-size: 13px;
  color: var(--muted);
  margin-top: 8px;
}
.gate-toggle {
  font: inherit;
  font-size: 13px;
  font-weight: 600;
  color: #7ed957;
  background: transparent;
  border: none;
  cursor: pointer;
  padding: 2px;
}
.gate-toggle:hover { filter: brightness(1.12); }

/* ---------- Ceres Exchange overlay (player market: browse / sell / listings) ----------
   A buy-it-now exchange, phone-first: full-screen safe-area sheet under 640px,
   centered card on desktop. Header + tabs + tools pin; only the list scrolls. */
#market {
  position: fixed;
  inset: 0;
  z-index: 24;
  display: flex;
  align-items: center;
  justify-content: center;
  background: rgba(4, 6, 11, 0.72);
  backdrop-filter: blur(4px);
}
.market-card {
  display: flex;
  flex-direction: column;
  width: min(94vw, 520px);
  height: min(88vh, 800px);
  overflow: hidden;
  background: linear-gradient(180deg, #0a1116 0%, #080d14 100%);
  border: 1px solid rgba(159, 214, 232, 0.3);
  border-radius: 16px;
  box-shadow: 0 18px 60px rgba(0, 0, 0, 0.6), 0 0 40px rgba(159, 214, 232, 0.07);
}
/* phones: the exchange is the whole page (notch/home-bar aware) */
@media (max-width: 640px) {
  .market-card {
    width: 100%;
    height: 100%;
    border: none;
    border-radius: 0;
    padding-top: env(safe-area-inset-top);
    padding-bottom: env(safe-area-inset-bottom);
  }
}
.market-head { display: flex; align-items: center; gap: 14px; padding: 14px 16px 10px; }
.market-titles { flex: 1; min-width: 0; }
.market-name { font-size: 17px; font-weight: 700; letter-spacing: 0.04em; color: #bfe8f3; }
.market-sub { font-size: 10px; letter-spacing: 0.18em; color: var(--muted); margin-top: 2px; text-transform: uppercase; }
.market-bal { text-align: right; font-size: 9px; letter-spacing: 0.14em; color: var(--muted); line-height: 1.5; }
.market-bal span { font-size: 16px; font-weight: 700; color: #ffd98a; }
#market-close {
  width: 34px; height: 34px; flex: none;
  font: inherit; font-size: 15px;
  color: var(--muted);
  background: rgba(255, 255, 255, 0.04);
  border: 1px solid var(--line);
  border-radius: 10px; cursor: pointer;
  transition: color 0.12s ease, border-color 0.12s ease;
}
#market-close:hover { color: var(--ink); border-color: rgba(200, 210, 230, 0.4); }
/* tab strip */
.market-tabs { display: flex; border-bottom: 1px solid var(--line); padding: 0 10px; flex: none; }
.market-tab {
  flex: 1; min-height: 42px;
  font: inherit; font-size: 12px; font-weight: 700; letter-spacing: 0.08em;
  color: var(--muted);
  background: transparent; border: none; border-bottom: 2px solid transparent;
  cursor: pointer; white-space: nowrap;
}
.market-tab.active { color: #bfe8f3; border-bottom-color: #9fd6e8; }
/* tools strip (BROWSE tab): search box + kind / ore / sort chip rows */
.market-tools { padding: 10px 12px 2px; border-bottom: 1px solid var(--line); flex: none; }
#mk-search {
  width: 100%; box-sizing: border-box;
  font: inherit; font-size: 16px;   /* ≥16px so iOS never focus-zooms the page */
  color: var(--ink); padding: 9px 12px; margin-bottom: 8px;
  background: rgba(0, 0, 0, 0.3);
  border: 1px solid var(--line); border-radius: 10px;
  -webkit-appearance: none;
}
#mk-search:focus { outline: none; border-color: rgba(159, 214, 232, 0.45); }
.mk-chips {
  display: flex; gap: 6px; align-items: center;
  overflow-x: auto; -webkit-overflow-scrolling: touch;
  scrollbar-width: none; margin-bottom: 8px;
}
.mk-chips::-webkit-scrollbar { display: none; }
.mk-chip {
  flex: none; display: inline-flex; align-items: center; gap: 6px;
  font: inherit; font-size: 11px; font-weight: 600; letter-spacing: 0.04em;
  color: #cdd6e6; padding: 7px 11px; min-height: 30px;
  background: rgba(255, 255, 255, 0.04);
  border: 1px solid var(--line); border-radius: 999px;
  cursor: pointer; white-space: nowrap;
}
.mk-chip.active { color: #06121a; background: #9fd6e8; border-color: #9fd6e8; }
.mk-slab { flex: none; font-size: 9px; letter-spacing: 0.16em; color: var(--muted); margin-right: 2px; }
.mk-dot { width: 8px; height: 8px; border-radius: 50%; display: inline-block; flex: none; }
/* note strip (action feedback) */
.market-note {
  flex: none;
  margin: 8px 12px 0;
  padding: 8px 12px;
  font-size: 12px;
  color: #bfe8f3;
  background: rgba(159, 214, 232, 0.08);
  border: 1px solid rgba(159, 214, 232, 0.2);
  border-radius: 8px;
}
/* the one scrolling region */
.market-body {
  flex: 1; min-height: 0;
  overflow-y: auto; -webkit-overflow-scrolling: touch; overscroll-behavior: contain;
  padding: 8px 12px 14px;
}
.mk-count { font-size: 10px; letter-spacing: 0.14em; text-transform: uppercase; color: var(--muted); margin: 4px 2px 8px; }
.mk-count .mk-clear {
  font: inherit; font-size: 10px; letter-spacing: 0.1em; text-transform: uppercase;
  color: #9fd6e8; background: transparent; border: none; cursor: pointer; padding: 0;
  text-decoration: underline;
}
.mk-empty { font-size: 12px; color: var(--muted); padding: 22px 8px; text-align: center; font-style: italic; }
.mk-invite { font-size: 13px; color: var(--ink); padding: 28px 16px; text-align: center; line-height: 1.7; }
.mk-invite span { font-size: 12px; color: var(--muted); }
.mk-hint { font-size: 11px; color: var(--muted); line-height: 1.55; margin: 2px 2px 10px; }
/* one listing / hold row: [dot name ⚙ ×qty / seller · unit] [price / button] */
.mk-row {
  display: flex; align-items: center; gap: 10px;
  padding: 9px 10px; margin-bottom: 6px;
  background: rgba(255, 255, 255, 0.03);
  border: 1px solid var(--line); border-radius: 12px;
}
.mk-row.mk-own { border-color: rgba(255, 217, 138, 0.28); background: rgba(255, 217, 138, 0.045); }
.mk-l { flex: 1; min-width: 0; }
.mk-line1 { display: flex; align-items: center; gap: 6px; min-width: 0; }
.mk-nm { font-size: 13px; font-weight: 600; color: var(--ink); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.mk-gear { font-size: 11px; color: #ffd98a; }
.mk-qty { font-size: 12px; color: #cdd6e6; }
.mk-yours {
  font-size: 8px; font-weight: 700; letter-spacing: 0.12em;
  color: #ffd98a; padding: 2px 5px;
  border: 1px solid rgba(255, 217, 138, 0.4); border-radius: 4px;
}
.mk-line2 { font-size: 11px; color: var(--muted); margin-top: 3px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.mk-r { flex: none; display: flex; flex-direction: column; align-items: flex-end; gap: 5px; }
.mk-r.mk-sellbtns { flex-direction: row; align-items: center; gap: 6px; }
.mk-price { font-size: 14px; font-weight: 700; color: #ffd98a; }
.mk-price.broke { color: #8a93a6; }
.mk-btn {
  font: inherit; font-size: 11px; font-weight: 700; letter-spacing: 0.04em;
  color: #bfe8f3; padding: 8px 14px; min-height: 34px;
  background: rgba(159, 214, 232, 0.08);
  border: 1px solid rgba(159, 214, 232, 0.28);
  border-radius: 9px; cursor: pointer; white-space: nowrap;
  transition: background 0.12s ease, border-color 0.12s ease, color 0.12s ease;
}
.mk-btn:hover { background: rgba(159, 214, 232, 0.18); border-color: rgba(159, 214, 232, 0.5); }
.mk-btn:disabled { opacity: 0.38; cursor: default; }
.mk-buy, .mk-list { color: #c7e6a0; border-color: rgba(143, 194, 74, 0.4); background: rgba(143, 194, 74, 0.1); }
/* the armed second-tap state: unmissably green */
.mk-buy.confirm {
  color: #06140a; background: #7ed957; border-color: #7ed957;
  animation: mkArm 0.18s ease;
}
@keyframes mkArm { from { transform: scale(0.94); } to { transform: scale(1); } }
.mk-cancel { color: #ff9a8a; border-color: rgba(255, 106, 90, 0.35); background: rgba(255, 106, 90, 0.07); }
/* inline "list a stack" form (expands under its SELL row) */
.mk-form {
  padding: 10px 12px 12px; margin: -2px 0 8px;
  background: rgba(159, 214, 232, 0.05);
  border: 1px solid rgba(159, 214, 232, 0.25); border-radius: 12px;
}
.mk-frow { display: flex; align-items: center; justify-content: space-between; gap: 10px; margin-bottom: 10px; }
.mk-flab { font-size: 9px; letter-spacing: 0.16em; color: var(--muted); }
.mk-stepper { display: flex; align-items: center; gap: 6px; }
.mk-step {
  min-width: 38px; min-height: 34px;
  font: inherit; font-size: 15px; font-weight: 700;
  color: #bfe8f3; background: rgba(255, 255, 255, 0.05);
  border: 1px solid var(--line); border-radius: 9px; cursor: pointer;
}
.mk-step.mk-maxbtn { font-size: 10px; letter-spacing: 0.08em; padding: 0 10px; }
.mk-qval { min-width: 34px; text-align: center; font-size: 15px; font-weight: 700; color: var(--ink); }
.mk-pricebox {
  display: flex; align-items: center; gap: 4px;
  padding: 4px 10px;
  background: rgba(0, 0, 0, 0.3);
  border: 1px solid var(--line); border-radius: 9px;
}
.mk-pricebox span { color: #ffd98a; font-weight: 700; font-size: 14px; }
.mk-pricebox input {
  width: 96px; font: inherit; font-size: 16px; font-weight: 700;
  color: var(--ink); padding: 5px 2px;
  background: transparent; border: none; outline: none;
}
.mk-fhint { font-size: 11px; color: var(--muted); line-height: 1.55; margin: 0 0 10px; }
.mk-fhint b { color: #ffd98a; font-weight: 700; }
.mk-fbtns { display: flex; gap: 8px; }
.mk-fbtns .mk-btn { flex: 1; min-height: 40px; text-align: center; }
.mk-fbtns .mk-post { color: #c7e6a0; border-color: rgba(143, 194, 74, 0.4); background: rgba(143, 194, 74, 0.1); }


/* ---------- CLAIM FLOW (player stations: charter office + beacon deploy) ---------- */
/* (The Ceres dock pill's CHARTER OFFICE button is retired - the Charter Office is a
   service counter INSIDE the Ceres colony now, at the hangar fuel bay.) */

/* BUILD STATION - the one bottom-center action while flying with the beacon aboard
   and an open berth in range. Same fixed spot the dock prompt uses (they can't
   coexist: deploy needs UNDOCKED), and since Aug 2026 (user call) it wears the
   .dock-act recipe EXACTLY - the ENTER STATION slab: 46px, 11px corners, gradient +
   lit border + glow, near-black text, no emoji, no pulse - just in the berth
   scanner's own CLAIM TEAL instead of the boarding green (the .boost-act pattern:
   one shape, the flavor is the color). */
#deploy-btn {
  position: fixed;
  bottom: 64px;
  left: 50%;
  transform: translateX(-50%);
  display: block;
  min-width: 184px;
  height: 46px;
  padding: 0 26px;
  font: inherit;
  font-size: 14px;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-align: center;
  white-space: nowrap;
  color: #081612;
  background: linear-gradient(180deg, #62e2bd 0%, #2fab85 100%);
  border: 1.5px solid rgba(143, 240, 208, 0.85);
  border-radius: 11px;
  cursor: pointer;
  z-index: 8;
  box-shadow: 0 0 16px rgba(143, 240, 208, 0.5);
  -webkit-tap-highlight-color: transparent;
  -webkit-user-select: none;
  user-select: none;
  transition: filter 0.12s ease, transform 0.08s ease;
}
#deploy-btn:hover { filter: brightness(1.07); }
#deploy-btn:active { transform: translateX(-50%) scale(0.97); }
#deploy-btn.hidden { display: none; }
/* Phones: clear the 76px touch-control row, like the dock pill does - and take the
   same --tc-slot2 edges, so BUILD STATION lines up with the button row under it
   exactly as MINE / ENTER STATION / RELEASE BOOSTER do (they can never coexist,
   but they share the spot and must share its geometry - the body.touch
   #dock-prompt .dock-act rule mirrored, wrap floor included). */
body.touch #deploy-btn {
  bottom: calc(max(22px, env(safe-area-inset-bottom)) + 76px + 16px);
  left: calc(env(safe-area-inset-left) + var(--tc-slot2));
  right: calc(env(safe-area-inset-right) + var(--tc-slot2));
  transform: none;
  min-width: 0;
  padding: 0 6px;
  white-space: normal;
  line-height: 1.15;
}
/* The base :active nudge re-applies the centring transform this tier no longer uses. */
body.touch #deploy-btn:active { transform: scale(0.97); }

/* The deploy sheet's name field: 16px so iOS never auto-zooms the page on focus. */
.claim-name {
  width: 100%;
  box-sizing: border-box;
  font: inherit;
  font-size: 16px;
  color: var(--ink);
  background: rgba(255, 255, 255, 0.05);
  border: 1px solid var(--line);
  border-radius: 10px;
  padding: 10px 12px;
  margin: 10px 0 2px;
  outline: none;
}
.claim-name:focus { border-color: rgba(143, 240, 208, 0.6); }
.claim-err { font-size: 12px; color: #ff9a8a; min-height: 15px; margin: 4px 2px 0; }
.claim-row { display: flex; justify-content: space-between; gap: 12px; margin: 3px 0; font-size: 13px; }
.claim-row .k { color: var(--muted); }
.claim-row .v { font-weight: 700; }
/* EXOPLANET NAMING CARD: prose between the fact rows and the input. Reuses the
   deploy sheet's field + error styles above - it is the same shape of decision
   (a permanent name on a thing you found), so it should look like one. */
.name-note { font-size: 12.5px; line-height: 1.45; color: var(--muted); margin: 9px 2px 2px; }
.name-note.dim { opacity: 0.7; margin-top: 2px; }

/* ============================================================
   FLIGHT TUTORIAL - the no-account "learn to fly" lesson (game.js FLIGHT
   TUTORIAL section). Entries live on the login gate / join card / cricket
   select; the in-lesson chrome below (top bar, goal line, match-speed
   readout, tips, celebration, completion card) is hidden in normal play.
   ============================================================ */

/* The LANDING (gate) - Tutorial / Play stacked under the title, log in quiet below. */
.gate-landing { display: flex; flex-direction: column; gap: 12px; }
.gate-mainbtn {
  width: 100%;
  font: inherit;
  font-size: 16px;
  font-weight: 600;
  letter-spacing: 0.04em;
  color: var(--ink);
  background: rgba(255, 255, 255, 0.04);
  border: 1px solid var(--line);
  border-radius: 12px;
  padding: 13px 16px;
  cursor: pointer;
  transition: filter 0.12s ease, transform 0.08s ease, border-color 0.15s ease;
}
.gate-mainbtn:hover { border-color: rgba(143, 194, 74, 0.5); }
.gate-mainbtn:active { transform: scale(0.985); }
.gate-mainbtn.gate-play {
  color: #0c1505;
  font-weight: 700;
  background: #6fcf57;
  border: none;
}
.gate-mainbtn.gate-play:hover { filter: brightness(1.08); }
.gate-login {
  align-self: center;
  margin-top: 6px;
  font: inherit;
  font-size: 13px;
  letter-spacing: 0.06em;
  color: var(--muted);
  background: transparent;
  border: none;
  cursor: pointer;
  padding: 5px 10px;
  transition: color 0.14s ease;
}
.gate-login:hover { color: var(--ink); }
.gate-back {
  align-self: center;
  margin-top: 2px;
  font: inherit;
  font-size: 12px;
  color: var(--muted);
  background: transparent;
  border: none;
  cursor: pointer;
  padding: 4px 8px;
  transition: color 0.14s ease;
}
.gate-back:hover { color: var(--ink); }

/* Entry on the JOIN CARD (dev / anonymous resume screen). */
.tut-join-btn { margin-top: 10px; }
.tut-join-btn:hover { color: #7fe8c9; border-color: rgba(111, 227, 196, 0.5); }

/* "log in" at the BOTTOM of the join card - the landing gate's quiet link, carried
   through to the screen an anonymous pilot lands on after the tutorial. */
.join-login {
  display: block; margin: 12px auto 0;
  font: inherit; font-size: 13px; letter-spacing: 0.06em;
  color: var(--muted); background: transparent; border: none;
  cursor: pointer; padding: 5px 10px;
  transition: color 0.14s ease;
}
.join-login:hover { color: var(--ink); }
.join-login.hidden { display: none; }

/* Entry on the CRICKET SELECT (signed-in landing) - quiet, under Start new. */
.cs-btn.cs-tut {
  font-size: 13px;
  padding: 10px 16px;
  color: rgba(160, 220, 200, 0.85);
  border-color: rgba(111, 227, 196, 0.25);
}
.cs-btn.cs-tut:hover { border-color: rgba(111, 227, 196, 0.5); }

/* ---------- MENU HIERARCHY (July 2026 daily-race rework) ----------
   Three weights, one visual language, on BOTH entry screens: the primary green
   action (PLAY / RESUME), a medium secondary (TODAY'S RACE / SWITCH CRICKET), and
   a quiet row of small links (How to fly · Race results) over the account row. */

/* Gate: TODAY'S RACE sits under PLAY as the medium secondary; "How to fly" drops to
   the quiet weight (it used to be a full-size button competing with Play). */
.gate-mainbtn.gate-daily {
  font-size: 15px;
  padding: 12px 16px;
  color: rgba(160, 220, 200, 0.95);
  border-color: rgba(111, 227, 196, 0.35);
}
.gate-mainbtn.gate-daily:hover { border-color: rgba(111, 227, 196, 0.6); }
.gate-mainbtn.gate-quiet {
  font-size: 13px;
  padding: 9px 14px;
  color: var(--muted);
  background: transparent;
  border-color: rgba(255, 255, 255, 0.1);
}
.gate-mainbtn.gate-quiet:hover { color: var(--ink); border-color: rgba(143, 194, 74, 0.4); }

/* Cricket select: the medium secondary pair. */
.cs-btn.cs-second {
  font-size: 14px;
  padding: 13px 16px;
  color: rgba(200, 224, 216, 0.95);
  border-color: rgba(111, 227, 196, 0.28);
}
.cs-btn.cs-second:hover { border-color: rgba(111, 227, 196, 0.55); }
/* The quiet bottom row: small links separated by a dot, well under the actions. */
.cs-quiet {
  display: flex; align-items: center; justify-content: center; gap: 8px;
  margin-top: 10px;
}
.cs-quiet .cs-dot { color: var(--muted); opacity: 0.6; }
.cs-link {
  font: inherit; font-size: 13px; letter-spacing: 0.03em;
  color: var(--muted); background: transparent; border: none;
  cursor: pointer; padding: 6px 4px;
  transition: color 0.14s ease;
}
.cs-link:hover { color: var(--ink); text-decoration: underline; }

/* The stable list's "+ New cricket" slot (SWITCH CRICKET is the only way to a new
   pilot now - the standalone Start-new button is retired from the landing). */
.roster-row.roster-newrow { opacity: 0.9; }
.roster-swatch.roster-newswatch {
  display: flex; align-items: center; justify-content: center;
  background: transparent;
  border: 1px dashed rgba(143, 194, 74, 0.5);
  color: #8fc24a; font-weight: 700;
}

/* ============================================================
   DAILY RACE ("Today's race") - the instanced time trial's own bar and cards.
   Deliberately its own element rather than borrowing #race-chip: a regatta and a
   daily can never be up at once, but sharing the node would couple two state
   machines that must stay independent (see daily.js). Same LANGUAGE as the race
   bar though - one rectangle, three cells, one font, colour as the only role mark.
   ============================================================ */
#daily-chip {
  position: fixed;
  top: calc(56px + var(--scale-h, 0px) + env(safe-area-inset-top));
  left: 50%;
  transform: translateX(-50%);
  max-width: calc(100vw - 20px);
  height: 40px;
  display: flex;
  align-items: stretch;
  font: 700 13px ui-monospace, SFMono-Regular, Menlo, monospace;
  letter-spacing: 0.08em;
  color: #ffd166;
  background: rgba(10, 14, 22, 0.72);
  border: 1px solid rgba(255, 209, 102, 0.45);
  border-radius: 14px;
  backdrop-filter: blur(8px);
  z-index: 8;
  pointer-events: none;          /* only the EXIT cell takes taps */
  font-variant-numeric: tabular-nums;
  overflow: hidden;
}
#daily-chip.hidden { display: none; }
#daily-chip .dc-clock,
#daily-chip .dc-target {
  display: flex; align-items: center;
  padding: 0 10px;
  white-space: nowrap;
  border-left: 1px solid rgba(255, 209, 102, 0.28);
}
#daily-chip .dc-clock { flex: none; }
#daily-chip .dc-target {
  min-width: 0;
  color: #cbd8ea;
  overflow: hidden;
  text-overflow: ellipsis;
}
#daily-chip .dc-target.hidden { display: none; }
/* <button> does not inherit font - re-declare it so all three cells match. */
#daily-exit {
  font: inherit;
  letter-spacing: inherit;
  display: flex; align-items: center;
  padding: 0 10px;
  color: #ff9a9a;
  background: transparent;
  border: none;
  cursor: pointer;
  pointer-events: auto;
}
#daily-exit:hover { color: #ffc0c0; }
#daily-chip.daily-armed { color: #ff8a8a; border-color: rgba(255, 110, 110, 0.5); }
#daily-chip.daily-armed .dc-clock, #daily-chip.daily-armed .dc-target { border-left-color: rgba(255, 110, 110, 0.32); }
#daily-chip.daily-locked { color: #78e68c; border-color: rgba(120, 230, 140, 0.55); }
#daily-chip.daily-locked .dc-clock, #daily-chip.daily-locked .dc-target { border-left-color: rgba(120, 230, 140, 0.32); }
body.touch #daily-chip { height: 52px; top: calc(68px + var(--scale-h, 0px) + env(safe-area-inset-top)); }
body.touch #daily-chip .dc-clock, body.touch #daily-chip .dc-target { padding: 0 12px; }
body.touch #daily-exit { padding: 0 12px; }
body.docked #daily-chip, body.in-tutorial #daily-chip { display: none; }

/* The daily's cards ride the race-card scrim; they open over the title screen (41)
   on a ?daily=1 cold boot, so they are lifted the same way a race result card is. */
.daily-scrim { z-index: 43; }
.daily-card .daily-course { color: #ffd166; font-weight: 700; }
.daily-card .daily-share {
  font: 700 15px ui-monospace, SFMono-Regular, Menlo, monospace;
  color: #ffe2a6;
  letter-spacing: 0.01em;
}
.daily-namelab {
  display: block; margin: 12px 0 5px;
  font-size: 11px; font-weight: 700; letter-spacing: 0.12em;
  color: #ffd98a;
}
.daily-name {
  width: 100%; box-sizing: border-box;
  padding: 10px 12px;
  font: inherit; font-size: 15px;
  color: var(--ink);
  background: rgba(255, 255, 255, 0.05);
  border: 1px solid rgba(255, 210, 51, 0.35);
  border-radius: 10px;
}
.daily-name:focus { outline: none; border-color: rgba(255, 210, 51, 0.7); }

/* ---- In-lesson chrome (all inside #tut-ui; informational bits are
   pointer-events:none so they never eat a pan/steer touch) ---- */
#tut-ui { position: fixed; inset: 0; z-index: 30; pointer-events: none; }
#tut-ui.hidden { display: none; }

/* Top row: fuel chip top LEFT (the real HUD spot), the stage tag CENTERED, and
   RESET / EXIT top RIGHT. The message card sits just under the centered tag. */
.tut-topbar {
  position: absolute;
  top: calc(10px + env(safe-area-inset-top));
  left: calc(10px + env(safe-area-inset-left));
  right: calc(10px + env(safe-area-inset-right));
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: 10px;
  pointer-events: none;
}
.tut-stage-tag {
  position: fixed;
  top: calc(10px + env(safe-area-inset-top));
  left: 50%;
  transform: translateX(-50%);
  /* Match the RESET / EXIT button box height (same font-size + vertical padding +
     border) so the centered tag lines up on the same top row. */
  font-size: 12px;
  letter-spacing: 0.14em;
  color: rgba(140, 210, 190, 0.85);
  background: var(--panel);
  border: 1px solid rgba(111, 227, 196, 0.25);
  border-radius: 999px;
  padding: 8px 14px;
  backdrop-filter: blur(8px);
}
.tut-top-btns { display: flex; gap: 8px; pointer-events: auto; }
.tut-btn {
  font: inherit;
  font-size: 12px;
  font-weight: 700;
  letter-spacing: 0.08em;
  color: var(--ink);
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: 999px;
  padding: 8px 14px;
  cursor: pointer;
  backdrop-filter: blur(8px);
  transition: border-color 0.15s ease, color 0.15s ease, transform 0.08s ease;
}
.tut-btn:hover { border-color: rgba(111, 227, 196, 0.5); color: #7fe8c9; }
.tut-btn:active { transform: scale(0.95); }
.tut-btn.tut-exit:hover { border-color: rgba(255, 106, 90, 0.5); color: #ff8a7a; }
.tut-btn.hidden { display: none; }

/* Tutorial fuel readout - top LEFT, exactly where the real HUD fuel lives,
   same droplet + red. */
.tut-fuel {
  position: absolute;
  /* Share the top edge + a matching box height with the RESET / EXIT buttons so
     the fuel chip and those buttons sit on one vertical centerline. */
  top: calc(10px + env(safe-area-inset-top));
  left: calc(14px + env(safe-area-inset-left));
  height: 32px;
  display: inline-flex;
  align-items: center;
  font-size: 16px;
  font-weight: 600;
  letter-spacing: 0.04em;
  font-variant-numeric: tabular-nums;
  color: #ff6a5a;
  text-shadow: 0 1px 8px rgba(0, 0, 0, 0.7);
  pointer-events: none;
}
body.touch .tut-fuel { font-size: 18px; }
.tut-fuel.hidden { display: none; }
/* The word "fuel" in tutorial copy reads in the fuel red, matching the chip. */
.tut-goal-text .fuel-word { color: #ff6a5a; font-weight: 700; }
.tut-fuel .hud-fuel-ico { margin-right: 4px; }
.tut-fuel.pulse { animation: tutFuelPulse 1.1s ease 3; }
@keyframes tutFuelPulse {
  0%, 100% { transform: scale(1); }
  50% { transform: scale(1.22); text-shadow: 0 0 16px rgba(255, 106, 90, 0.8); }
}

/* Stage-advance NEXT - bottom center, above the touch controls on phones. */
.tut-next {
  position: absolute;
  bottom: 30px;
  left: 50%;
  transform: translateX(-50%);
  font: inherit;
  font-size: 15px;
  font-weight: 800;
  letter-spacing: 0.08em;
  color: #0c1505;
  background: #6fcf57;
  border: none;
  border-radius: 999px;
  padding: 13px 30px;
  cursor: pointer;
  pointer-events: auto;
  box-shadow: 0 6px 24px rgba(0, 0, 0, 0.45), 0 0 24px rgba(111, 207, 87, 0.25);
  transition: filter 0.12s ease, transform 0.08s ease;
  animation: tutNextIn 0.3s ease;
}
@keyframes tutNextIn { from { transform: translateX(-50%) translateY(8px); opacity: 0; } to { transform: translateX(-50%) translateY(0); opacity: 1; } }
.tut-next:hover { filter: brightness(1.08); }
.tut-next:active { transform: translateX(-50%) scale(0.97); }
.tut-next.hidden { display: none; }
body.touch .tut-next { bottom: calc(max(22px, env(safe-area-inset-bottom)) + 76px + 14px); }

/* The ONE message card per stage - always centered, just under the stage tag.
   game.js cycles its text through the stage's messages (opacity crossfade). */
.tut-goal {
  position: absolute;
  top: calc(52px + env(safe-area-inset-top));
  left: 50%;
  transform: translateX(-50%);
  width: min(92vw, 620px);
  text-align: center;
  pointer-events: none;
}
.tut-goal-text {
  display: inline-block;
  font-size: 15px;
  font-weight: 600;
  color: var(--ink);
  background: var(--panel);
  border: 1px solid rgba(111, 227, 196, 0.3);
  border-radius: 12px;
  padding: 9px 16px;
  backdrop-filter: blur(8px);
  transition: opacity 0.25s ease;
}
.tut-goal-text.swap { opacity: 0; }
.tut-goal-text b { color: #7fe8c9; }
/* The projection line in tutorial copy reads in the trajectory line's pale gray. */
.tut-goal-text .line-word { color: #c3cfdf; font-weight: 700; }
/* Attention nudge: a gated card that's been waiting on its action grows ~30% and
   shrinks back (twice) to pull the eye - fired from game.js tutMsgNudge. */
.tut-goal-text.nudge { animation: tutGoalNudge 1.1s ease 2; }
@keyframes tutGoalNudge {
  0%, 100% { transform: scale(1); }
  50% { transform: scale(1.3); box-shadow: 0 0 22px rgba(111, 227, 196, 0.35); }
}

/* Reactive tip - a single amber nudge, shown only when the player is stuck. */
.tut-tip {
  position: absolute;
  top: 33%;
  left: 50%;
  transform: translateX(-50%) translateY(6px);
  width: min(88vw, 380px);
  text-align: center;
  font-size: 14px;
  line-height: 1.45;
  color: var(--gold);
  background: var(--panel);
  border: 1px solid rgba(255, 217, 138, 0.4);
  border-radius: 12px;
  padding: 11px 16px;
  backdrop-filter: blur(8px);
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.35s ease, transform 0.35s ease;
}
.tut-tip.show { opacity: 1; transform: translateX(-50%) translateY(0); }
.tut-tip.hidden { display: none; }

/* Celebration flash (DOCKED / CRASHED / RESET) - sits LOW so it never stacks on
   the mid-screen tip. */
.tut-cele {
  position: absolute;
  top: 62%;
  left: 50%;
  transform: translate(-50%, -50%) scale(0.92);
  text-align: center;
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.3s ease, transform 0.3s ease;
}
.tut-cele.show { opacity: 1; transform: translate(-50%, -50%) scale(1); }
.tut-cele.hidden { display: none; }
.tut-cele-big {
  font-size: 34px;
  font-weight: 800;
  letter-spacing: 0.12em;
  color: #8ff0c8;
  text-shadow: 0 0 24px rgba(111, 227, 196, 0.45), 0 2px 10px rgba(0, 0, 0, 0.6);
}
.tut-cele-sub { margin-top: 8px; font-size: 15px; color: var(--ink); text-shadow: 0 1px 8px rgba(0, 0, 0, 0.7); }

/* Completion card - the conversion CTA at the moment of accomplishment. */
#tut-done {
  position: fixed;
  inset: 0;
  z-index: 46;             /* above the generic popup (45), below loading (50) */
  display: flex;
  align-items: center;
  justify-content: center;
  background: rgba(4, 6, 11, 0.62);
  backdrop-filter: blur(3px);
}
#tut-done.hidden { display: none; }
.tut-done-card {
  width: min(90vw, 380px);
  text-align: center;
  background: rgba(12, 17, 26, 0.96);
  border: 1px solid rgba(111, 227, 196, 0.35);
  border-radius: 18px;
  padding: 26px 24px 22px;
  box-shadow: 0 12px 60px rgba(0, 0, 0, 0.6), 0 0 40px rgba(111, 227, 196, 0.08);
  animation: tutDonePop 0.35s ease;
}
@keyframes tutDonePop { from { transform: scale(0.92); opacity: 0; } to { transform: scale(1); opacity: 1; } }
.tut-done-title { font-size: 20px; font-weight: 800; letter-spacing: 0.1em; color: #8ff0c8; margin-bottom: 18px; }
.tut-done-signup {
  width: 100%;
  font: inherit;
  font-size: 16px;
  font-weight: 800;
  color: #0c1505;
  background: #6fcf57;
  border: none;
  border-radius: 12px;
  padding: 13px 16px;
  cursor: pointer;
  transition: filter 0.12s ease, transform 0.08s ease;
}
.tut-done-signup:hover { filter: brightness(1.08); }
.tut-done-signup:active { transform: scale(0.98); }

/* While the tutorial owns the screen, dev-only chrome (the flight-diag copy button,
   ?qc/localhost only) stays out of the lesson's readout space. */
body.in-tutorial #diag-copy { display: none; }
/* The tutorial teaches steer/thrust only - the BURN button (always present in normal
   play now, stock ×2) stays out of the lesson controls (the stage-1 toy engine
   doesn't read it anyway). */
body.in-tutorial #tc-burn { display: none; }

/* Narrow screens: trim the chrome; layout is already collision-free (fuel left,
   tag centered, buttons right, message on its own row below). */
.tut-stage-tag { white-space: nowrap; }
@media (max-width: 480px) {
  .tut-topbar { gap: 6px; }
  .tut-stage-tag { font-size: 11px; padding: 7px 10px; letter-spacing: 0.1em; }
  .tut-btn { font-size: 11px; padding: 7px 10px; }
  .tut-goal { top: calc(48px + env(safe-area-inset-top)); }
  .tut-goal-text { font-size: 13.5px; padding: 8px 13px; }
}

/* ============================================================
   RACING (regattas) - the race clock chip, the flight banner, the Racing
   menu panel, result cards and the replay viewer. See racing.js.
   ============================================================ */
/* THE RACE PANEL - a titled, framed box (user call July 2026: "outline around
   all and say 'Racing' on top as like title of the ui thing. Then a clear exit
   button, time (just time) and destination" - SUPERSEDES the three-text-cell
   bar). It wears the game's own "UI element with a name on it" language: the
   dock SIGN's top-band panel (dark 72% fill, radius 14, blur) carrying the
   MISSION CARD's gold frame + gold title. TWO ROWS (user call: "title is RACING
   then exit button is top right, like next to racing. Bottom left says 'Time:
   x:xx' then bottom right says Next Stop: <mark> Mars"):

     RACING                       [ EXIT ]
     Time: 2:11   Next Stop: ◌ Mars

   The old STATE SUB-LINE under the title is DELETED (user call: "dont say
   RACING under way just 'RACING'"), so the title is the bare word in every
   phase. That line was carrying the countdown-vs-count-up distinction, which
   colour alone cannot give a colour-blind pilot - so the phase words moved DOWN
   onto row 2's two LABELS, each now sitting against the number it describes:
     Starts: / Time:        the clock counts DOWN pre-gun, UP from the gun
     Next Stop: / In Orbit: the destination cell, before and after the lock
   Both rows are `space-between` over a stretched column, so row 2 (the wider)
   sizes the box and EXIT lands flush with the destination cell's right edge.
   The exit is a real BUTTON (the racing panel's own .race-quit recipe, below);
   the destination is the drawn ORBIT MARK (one dot in an open green arc - the
   same mark for every world, see .rc-glyph) between its label and the finish
   body's name, which is what says WHICH body; distance dropped (user call).
   It sits BELOW the TRACKING chip (which stays up while racing) and keeps the
   id #race-chip: the band roster (--band-bot takes the LOWEST live bottom, so
   the taller auto-height panel is covered with no numbers to drift), the
   keep-out set and every test read that id. The panel itself takes NO taps -
   only the exit button turns pointer-events back on, so the clock can never
   eat one. 12px band top + the frame chip's 40px (52px on touch) + 4px air. */
#race-chip {
  position: fixed;
  top: calc(56px + var(--scale-h, 0px) + env(safe-area-inset-top));
  left: 50%;
  transform: translateX(-50%);
  max-width: calc(100vw - 20px);
  display: flex;
  flex-direction: column;
  align-items: stretch;          /* both rows span the panel, so row 2's
                                    `space-between` reaches both edges and EXIT
                                    lands on the same LEFT edge the clock cell
                                    sits on (was `center`, from the retired
                                    title-over-sub-line head) */
  gap: 7px;
  padding: 8px 16px 10px;
  font: 700 13px ui-monospace, SFMono-Regular, Menlo, monospace;
  color: #ffd166;
  background: rgba(10, 14, 22, 0.72);
  border: 1px solid rgba(255, 209, 102, 0.5);
  border-radius: 14px;
  backdrop-filter: blur(8px);
  z-index: 8;
  pointer-events: none;          /* only the EXIT button takes taps */
  font-variant-numeric: tabular-nums;
  /* Internal hairline colour for the ALT layout's section rules, re-declared by
     .race-armed / .race-locked so a rule is always a quieter member of the colour
     the frame is wearing. Unused by the shipped layout (which has no rules). */
  --rc-rule: rgba(255, 209, 102, 0.28);
}
/* ROW 1 - the TITLE and the EXIT button on ONE line ("title is RACING then exit
   button ... next to racing"; the button opened at the right and moved to the LEFT
   in July 2026 - see .rc-head). The title is a real heading at the sign-name size -
   NOT the 9px eyebrow whisper an earlier draft tried (rejected). The old STATE
   sub-line under it is DELETED (user call: "dont say RACING under way just
   'RACING'") - the phase words moved onto row 2's labels, where each one sits
   against the number it describes. */
/* THE TITLE IS CENTRED ON THE PANEL, NOT ON THE LEFTOVER SPACE (user call:
   "Racing put middle center"). It is taken OUT OF FLOW and stretched across the
   whole head, so it centres on the BOX - flow-centring would centre it in what
   the Exit button leaves behind and sit it visibly off the panel's midline.
   The head's height still comes from Exit (the taller of the two), exactly as it
   did when both were flex items - and being out of flow is also why moving Exit
   from one end of the head to the other left the title exactly where it was. */
/* EXIT sits at the head's LEFT end (user call July 2026: "move exit button to top
   left"). It is the row's only flow item, so `flex-start` is the whole move - the
   title is out of flow and stays centred on the box either way. */
.rc-head { position: relative; display: flex; align-items: center; justify-content: flex-start; }
.rc-title-wrap {
  position: absolute;
  left: 0; right: 0; top: 0; bottom: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  pointer-events: none;
}
/* 19.5px = the sign-name 15px x 1.3 (user call: "make RACING 1.3x bigger"). It is
   the panel's headline - bigger than everything else in the box, which is right:
   the clock is a number you read, the title is what the box IS.
   TRACKING CUT 0.14em -> 0.05em with the switch to Title Case (user call:
   "nothing in all caps"): wide tracking is a CAPS device - it opens up a row of
   even-height letterforms - and on lowercase with ascenders and descenders the
   same spacing just reads as a gappy word. */
.rc-title { font-size: 19.5px; line-height: 1; letter-spacing: 0.05em; }
/* ROW 2 - the labelled clock left, the labelled destination right. This is the
   panel's WIDEST row, so it sets the box width and row 1's EXIT lands flush with
   the destination cell's right edge. One family/weight (inherited from the
   panel); the CLOCK steps up in size - it is the headline number the whole
   element exists for - and the LABELS step down. */
.rc-row { display: flex; align-items: center; justify-content: space-between; gap: 20px; }
.rc-time { display: flex; align-items: center; gap: 6px; }
/* The two labels ("Time:"/"Starts:" and "Next Stop:"/"In Orbit:"). Deliberately
   NOT state-tinted: they are the words that tell a colour-blind pilot which
   phase the clock is in, so they must read the same against all three panel
   tints - and they sit in the destination cell's own reference grey, which is
   also what stops them competing with the gold clock beside them. */
#race-chip .rc-label {
  /* SIZE/WEIGHT/TRACKING ARE INHERITED ON PURPOSE (user call: "adjust all font on
     this to be same size - the time, the text in exit, the 'Time:' - all except
     have RACING be the larger size. Same font for all too"). The panel's own
     `font: 700 13px …` + 0.08em is now the ONE type declaration for every cell,
     and .rc-title is the ONLY thing that overrides it. What still separates a
     label from the number beside it is COLOUR alone - the same rule the retired
     three-cell bar ran on. */
  line-height: 1;
  color: rgba(203, 216, 234, 0.62);
  white-space: nowrap;
  flex: none;
}
#race-chip .rc-clock {
  /* Was 17px - stepped down to the panel size with everything else. Tracking is
     inherited now too: the brake-clock's `letter-spacing: 0` exists because CSS
     appends tracking after the LAST glyph, which sits the ink half a space left
     of a CENTRED box - this clock is a left-aligned flex item beside its label,
     so the trailing advance is just a hair of gap and centring never enters it.
     `font-variant-numeric: tabular-nums` (on the panel) still stops digit jitter. */
  line-height: 1;
  white-space: nowrap;
}
#race-chip .rc-target {
  display: flex;
  align-items: center;
  gap: 6px;
  color: #cbd8ea;                /* the destination is reference, not the headline */
  white-space: nowrap;
  min-width: 0;                  /* a flex item must be allowed to shrink before
                                    text-overflow can ever apply */
  overflow: hidden;
  text-overflow: ellipsis;
}
/* The destination MARK - the drawn orbit glyph (racing.js ORBIT_MARK): a dot with
   an open, fading arc sweeping past it, in the coast line's own #4caf63 at a
   0.85 CSS px round-capped hairline, a filled dart at the arc's leading tip, and
   the dot itself in the DESTINATION BODY's own colour. It replaced the
   astronomical planet sigils (♂ read as the male symbol - user call), which also
   retires the whole font-coverage question: this is SVG, so it is crisp at 2x/3x
   and cannot tofu. Sized in em INSIDE the markup so it tracks the row's type; the
   span is just a flex box that holds it on the text's centre line (line-height 0
   keeps the inline-block's baseline gap from padding the row out). The rail's
   green is hardcoded, NOT currentColor - see ORBIT_MARK's header. */
#race-chip .rc-glyph { display: flex; align-items: center; line-height: 0; }
#race-chip .rc-glyph svg { display: block; overflow: visible; }
#race-chip .rc-glyph.hidden, #race-chip .rc-clock.hidden, #race-chip .rc-target.hidden { display: none; }
/* THE SWEEP. The whole svg rotates about its own centre, which IS the dot (square
   viewBox, square box, dot at 10,10), so the arc + dart + the baked-in fade all
   turn RIGIDLY: a comet on a rail, not a runner passing a static ring. Rotating
   the <svg> element rather than an inner <g> also sidesteps the SVG
   transform-box / transform-origin quirk entirely.
   -360deg = COUNTERCLOCKWISE on screen = PROGRADE: every body's screen angle
   atan2(y,x) DECREASES with time on this +y-down canvas (see ORBIT_MARK's header
   and game.js ringWGame), and SVG/CSS rotate() is positive-clockwise there.
   12s LINEAR is drift, not a spinner - ~3.8 px/s at the head, ~3s to cross the
   18px mark; this is chrome a pilot flies beside for minutes.
   Transform-only keyframes run on the compositor thread, so it costs no
   main-thread frame time, and both .hidden rules are `display:none`, which stops
   the animation dead - the explicit `animation:none` below just says so. */
@keyframes sc-orbit-spin { from { transform: rotate(0deg); } to { transform: rotate(-360deg); } }
#race-chip .rc-glyph svg.rc-orb {
  transform-origin: 50% 50%;
  animation: sc-orbit-spin 12s linear infinite;
}
#race-chip.hidden .rc-orb, #race-chip .rc-target.hidden .rc-orb { animation: none; }
/* Parked at rotate(0) - the dart at the upper left, which is mark 2's own pose. */
@media (prefers-reduced-motion: reduce) {
  #race-chip .rc-glyph svg.rc-orb { animation: none; }
}
/* State tints ride the whole panel - frame, title, clock - exactly as the old
   bar's colour swap did: red-tinged while armed pre-start, gold running, green
   once the finish orbit locks (.race-locked, further down with its sibling
   rules). The two .rc-label spans are exempt by their own rule above (they are
   the REDUNDANT, colour-independent phase signal). The EXIT button deliberately
   stays red in all three - it is the danger action whatever the race is doing. */
#race-chip.race-armed { color: #ff8a8a; border-color: rgba(255, 110, 110, 0.55); --rc-rule: rgba(255, 110, 110, 0.3); }

/* ============ THE ALT PANEL LAYOUT (`?racealt=1`, OFF by default) ============
   A design alternative held beside the shipped panel on the codebase's own review
   convention (`?flooralt=` for deck finishes) - user call: "an alt with lines
   separating header and (3) sections ... put exit button on right - bottom right".
   racing.js's PANEL_ALT adds `.rc-alt` and builds EXIT into row 2 instead of the
   head; everything here is that one class. Nothing outside `.rc-alt` is touched,
   so the shipped layout is bit-identical with the flag off.

     head   the title ALONE, a real centred heading over a horizontal RULE
     row 2  THREE ruled sections - clock | destination | EXIT (bottom right)

   SECOND PASS (user calls): the destination label reads `Next:` (racing.js trims
   it under PANEL_ALT), the panel is SKINNIER at the same width (the "Racing" head
   keeps its height, the section row below is shorter, EXIT shorter with it), and
   the rules run ALL THE WAY - the horizontal rule frame-edge to frame-edge, the
   vertical ones from that rule down to the panel's bottom border. That reach is
   why the ALT zeroes the PANEL's own padding and moves every inset into the
   sections themselves: a border can never paint through its box's padding, so
   panel-level padding is exactly what was cutting the lines short.

   THE RULES FOLLOW THE FRAME. `--rc-rule` is declared on #race-chip and re-declared
   by the two state classes, so an internal line is always a quieter member of
   whatever colour the border is wearing (gold running / red armed / green locked) -
   a hardcoded gold hairline would sit wrong inside a green panel. */
#race-chip.rc-alt { padding: 0; gap: 0; }
/* body.touch #race-chip out-specifies #race-chip.rc-alt (its extra element tag),
   so the touch tier's padding/gap need their own override - the touch TOP and the
   phone WIDTH deliberately stay inherited ("keep width"). */
body.touch #race-chip.rc-alt { padding: 0; gap: 0; }
/* The head carries the inset the panel gave up; its own height is unchanged
   ("Racing height is ok"). Its border-bottom now spans the full frame width. */
#race-chip.rc-alt .rc-head {
  justify-content: center;
  padding: 8px 16px 8px;
  border-bottom: 1px solid var(--rc-rule);
}
body.touch #race-chip.rc-alt .rc-head { padding: 10px 18px 9px; }
/* THE TITLE GOES BACK IN FLOW. In the shipped head it is absolutely positioned so
   it centres on the BOX rather than on the space EXIT leaves - but with EXIT moved
   to row 2 the head has no flow content at all and would collapse to zero height.
   Static + the head's own `justify-content: center` centres it for free, and the
   head takes its height from the title, which is now the only thing in it. */
#race-chip.rc-alt .rc-title-wrap { position: static; }
/* The SECTION ROW - shorter than the head ("section below should be shorter"):
   each cell carries its own small vertical padding, and `align-items: stretch` is
   what makes every border-left a full-height line - rule to bottom edge, since
   the panel no longer pads below the row. The `gap` goes too: a rule should sit
   midway between two sections, not hard against one of them. */
#race-chip.rc-alt .rc-row { gap: 0; align-items: stretch; }
#race-chip.rc-alt .rc-row > * {
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 6px 13px;
}
body.touch #race-chip.rc-alt .rc-row > * { padding: 7px 13px; }
#race-chip.rc-alt .rc-row > * + * { border-left: 1px solid var(--rc-rule); }
/* The outer two carry the frame inset the panel padding used to provide. */
#race-chip.rc-alt .rc-row > :first-child { padding-left: 16px; justify-content: flex-start; }
#race-chip.rc-alt .rc-row > :last-child { padding-right: 12px; justify-content: flex-end; }
body.touch #race-chip.rc-alt .rc-row > :first-child { padding-left: 18px; }
/* EXIT rides a plain wrapper (racing.js `.rc-exitcell`) so the section rule is a
   border on a box - hanging it off the <button> would read as a second frame
   beside the frame the button already draws. The head's negative nudge is dropped:
   in here row 2's cells define the content box, so a negative margin would pull the
   button outside the padding every other cell is aligned to.
   SHORTER (user call): 22px desktop / 24px touch. ⚠️ The touch value waives the
   32px miss-rate floor the shipped layout pins - deliberate, per the call; the
   shipped #race-exit rules are untouched. */
#race-chip.rc-alt .rc-exitcell { flex: none; }
#race-chip.rc-alt #race-exit { margin-left: 0; min-height: 22px; padding: 0 8px; }
body.touch #race-chip.rc-alt #race-exit { min-height: 24px; padding: 0 8px; }
/* PHONE WIDTH (user call July 2026: "make wider so right edge extends to be flush
   w right edge of menu button. Make left edge further out too so it's centered -
   just for phones"). The panel is `left:50%` + `translateX(-50%)`, i.e. centred on
   the VIEWPORT, so one width does both halves: the menu button's right edge sits at
   100vw - (12px + safe-area-right) (see #menubtn), and a centred box reaches it when
   its width is twice the gap from centre - 100vw minus that inset on BOTH sides.
   So the right edge lands flush with the hamburger and the left edge comes out the
   same distance, still centred. Desktop keeps the shrink-to-fit box (the row-2
   content sizes it) - this is a phone rule only, where the band is tightest.
   Deliberately a hard width rather than a min-width: the point is the flush edge,
   and `box-sizing: border-box` (the global reset) means the 18px padding is inside
   it, so row 2's cells still keep their inset. */
body.touch #race-chip {
  gap: 9px;
  padding: 10px 18px 12px;
  top: calc(68px + var(--scale-h, 0px) + env(safe-area-inset-top));
  width: calc(100vw - 24px - 2 * env(safe-area-inset-right));
}
body.docked #race-chip, body.in-tutorial #race-chip { display: none; }   /* dock sign owns the slot */
/* NO `body.racing #toast` rule here: #race-chip is in game.js's HEAT_BAND_IDS
   roster, so the published --band-bot already carries this chip's live bottom and
   the base #toast rule clears it (July 2026 - the last surviving per-occupant
   toast offset, deleted with body.docked/framing/fuel-stranded/hull-hot for the
   same reason: a hardcoded 112px ignored the band, so a hull-temp banner or the
   reconnecting pill stacked under the chip painted straight through the toast). */

/* Flight banner: "RACE OPEN - tap to enter" (below the top chips).

   THE TOP HERE IS ONLY THE EMPTY-BAND FALLBACK. This element is a member of the
   top-center band, so game.js's publishBand PLACES it (inline `top`) under whatever
   is above it and folds its bottom into --band-bot - which is what makes the
   announcement toast land BELOW it instead of straight through it. It deliberately
   does NOT read --band-bot itself: an element that both reads and extends that
   number chases its own tail and walks down the screen a gap per frame. Same
   contract as #conn - see placeInBand(). */
.race-banner {
  position: fixed;
  top: calc(64px + env(safe-area-inset-top));
  left: 50%;
  transform: translateX(-50%);
  /* WIDTH IS REQUIRED, exactly as on #toast: the box is `left:50%` with `right:auto`,
     so its shrink-to-fit space is only the half-viewport right of centre (~195px on a
     phone) and every line wrapped there instead of at the max-width. max-content sizes
     it to the copy, then max-width clamps it. */
  width: max-content;
  max-width: min(360px, calc(100vw - 24px));
  padding: 10px 16px;
  font: 500 13px ui-monospace, SFMono-Regular, Menlo, monospace;
  color: #ffe2a6;
  text-align: center;
  background: linear-gradient(180deg, #0e131c, #0a0e15);
  border: 1px solid rgba(255, 210, 51, 0.55);
  border-radius: 12px;
  cursor: pointer;
  z-index: 9;
  animation: raceBannerIn 0.3s ease;
}
.race-banner b { color: #ffd166; letter-spacing: 0.08em; }
.race-banner .rb-sub { display: block; margin-top: 3px; font-size: 12px; color: #b6c4d6; }
/* THE UPCOMING-RACE NOTICE's ✕ (Aug 2026): only the all-day advance state wears
   a close button (racing.js adds .rb-notice + the .rb-x child there); the OPEN
   state never does - five minutes of invite is not noise. It sits in the TOP
   RIGHT CORNER (user call), the conventional dismiss spot, rather than centred
   on the right edge. `font: inherit`-then-size because a <button> does not
   inherit type (the #race-exit lesson). Touch tier takes the 32px floor the
   race panel's EXIT documents - below it, tap miss rates genuinely climb.

   THE PADDING IS SYMMETRIC so the copy stays centred in the box while still
   clearing the corner the ✕ occupies - the ✕ only overlaps the HEADLINE's line,
   and a one-sided inset would shove every line left of centre to protect it.
   And the body of the notice is NOT clickable any more (racing.js: only the ✕
   acts), so the banner's own `cursor: pointer` would be a lie here. */
.race-banner.rb-notice { padding-left: 34px; padding-right: 34px; cursor: default; }
.rb-x {
  position: absolute;
  top: 3px;
  right: 3px;
  width: 26px; height: 26px;
  padding: 0;
  border: 0;
  border-radius: 8px;
  background: none;
  color: #8fa2b8;
  font: inherit;
  font-size: 13px;
  line-height: 26px;
  text-align: center;
  cursor: pointer;
}
.rb-x:hover { color: #e8eef7; background: rgba(255, 255, 255, 0.07); }
body.touch .rb-x { width: 32px; height: 32px; line-height: 32px; top: 2px; right: 2px; }
body.touch .race-banner.rb-notice { padding-left: 38px; padding-right: 38px; }
@keyframes raceBannerIn { from { opacity: 0; transform: translate(-50%, -6px); } to { opacity: 1; transform: translate(-50%, 0); } }
body.in-tutorial .race-banner { display: none; }
/* THE INVITE HAS TO REACH A PILOT INSIDE A STATION. The join window is five real
   minutes; a pilot doing missions on a colony deck used to see nothing at all for
   the whole of it (this was `display:none` in here) and simply missed the event.
   So it rides above the colony overlay (22) like the HUD, the station sign and the
   toast do - and below the panels at 24, which is where tapping it goes. Entering
   from in here steps you back outside: raceEntryBegin closes the interior, because
   the tender show happens in the world, not on a deck. */
body.in-colony .race-banner { z-index: 23; }

/* Racing panel content */
.race-sec { margin-bottom: 22px; }
/* THE FOCUSED BOARD: the one race a ?results=<raceId> address names - a result
   card's VIEW RESULTS button, or someone else's share link. renderPanel lifts it
   to the front of PAST RACES and scrolls to it; this is what then says "this one"
   when you are looking at a list of them. Deliberately quiet - a tinted gutter,
   not a box - since the surrounding rows are the same data, just older. */
.race-sec.race-focus {
  padding: 2px 12px 8px;
  border-left: 2px solid rgba(255, 210, 51, 0.55);
  border-radius: 0 10px 10px 0;
  background: rgba(255, 210, 51, 0.05);
}
.race-sec.race-focus > .race-h:first-child { margin-top: 10px; color: #ffd166; }
.race-title { font-size: 18px; font-weight: 700; color: #ffe2a6; margin-bottom: 3px; }
.race-sub { font-size: 12.5px; color: #8fa3bb; margin: 2px 0; }
.race-count { font: 600 15px ui-monospace, monospace; color: #dfe7f2; margin: 10px 0 4px; }
.race-count b { color: #ffd166; font-variant-numeric: tabular-nums; }
.race-note { font-size: 12.5px; color: #a9bdd4; margin: 8px 0; line-height: 1.45; }
.race-h { font-size: 11px; font-weight: 700; letter-spacing: 0.18em; color: #7f93aa; margin: 16px 0 6px; }
.race-row {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 10px;
  padding: 7px 2px;
  border-bottom: 1px solid rgba(120, 140, 170, 0.14);
}
.race-name { font-size: 13.5px; color: #dfe7f2; }
.race-st { font: 500 12px ui-monospace, monospace; color: #8fa3bb; text-align: right; }
.race-st.finished { color: #9cff8a; }
.race-st.racing { color: #ffd166; }
.race-st.dq { color: #ff8a8a; }
.race-st.adrift { color: #9fdbe8; }
.race-btns { display: flex; gap: 10px; margin: 14px 0 4px; flex-wrap: wrap; }
.race-btn {
  padding: 10px 18px;
  font: 700 13px ui-monospace, monospace;
  letter-spacing: 0.06em;
  color: #c7e6a0;
  background: rgba(60, 90, 60, 0.18);
  border: 1px solid rgba(130, 224, 158, 0.5);
  border-radius: 10px;
  cursor: pointer;
}
.race-btn:hover { background: rgba(63, 207, 106, 0.16); }
.race-btn.race-go {
  color: #08160d;
  background: linear-gradient(180deg, #5ad67f 0%, #32a655 100%);
  border: 1.5px solid rgba(130, 224, 158, 0.85);
  box-shadow: 0 0 16px rgba(63, 207, 106, 0.4);
}
.race-btn.race-quit { color: #ffb3b3; border-color: rgba(255, 110, 110, 0.45); background: rgba(90, 40, 40, 0.16); }
/* THE HOMECOMING: while the tender is inbound the panel is a status board, so the
   one action wears the tender's yellow and takes no taps. */
.race-btn.race-wait {
  color: #ffe2a6;
  border-color: rgba(255, 210, 51, 0.4);
  background: rgba(86, 74, 40, 0.18);
}
.race-btn[disabled] { opacity: 0.7; cursor: default; box-shadow: none; }
.race-btn.race-wait:hover { background: rgba(86, 74, 40, 0.18); }
.race-home .race-h { margin-top: 0; }

/* Result cards (finish / DQ / DNF) */
.race-card-scrim {
  position: fixed; inset: 0;
  display: flex; align-items: center; justify-content: center;
  background: rgba(4, 6, 10, 0.6);
  /* JUST BELOW #tow-fade (40) on purpose. The card is the LAST beat of the
     homecoming - ship already home, fade already lifting - and the blackout must
     win any overlap, because a results card floating on a black screen reads as a
     bug (Addendum A5: this scrim used to tie #tow-fade at 40 and beat it on DOM
     order, since it is appended to body at show time). Structural, so it holds
     whatever order game.js's ceremony and the relay's terminal message arrive in.
     Still above the menu panels (24) and below #popup (45), so a confirm popup
     keeps its place over it.
     NOTE this base value loses to the FULLSCREEN menu chrome - the title screen
     (41) and the menu-screen racing panel (42) - which is why a card is lifted
     while one is up: see the `body.race-card-up` block below (Addendum C7). */
  z-index: 39;
}
.race-card-line { font-size: 14px; color: #dfe7f2; margin: 8px 0; }
.race-card-line b { color: #ffd166; }

/* Replay viewer. Its own z (over the title screen's 41): a ?replay= share link
   lands before any pilot exists, so the viewer must clear the cricket-select /
   main-menu chrome wherever it opens - not only under body.menu-screen. Still
   under #popup (45) and the race-card lift (43/44). */
/* THE WORLD IS THE GAME'S OWN CANVAS (July 2026 rework): the panel is a
   TRANSPARENT overlay - header + status line + bar - worn over the flight
   renderer, so it must not eat the pan/pinch/wheel that drive the camera.
   pointer-events go back to auto on the bits you actually touch. */
#replaypanel {
  z-index: 42;
  background: none;
  pointer-events: none;
  justify-content: space-between;
}
#replaypanel .mp-head, #replaypanel .rp-bar, #replaypanel #rp-msg { pointer-events: auto; }
/* ...and the two chrome strips carry their own scrim, so the title and the bar
   stay readable over a starfield instead of a solid panel. */
#replaypanel .mp-head { background: linear-gradient(180deg, rgba(6, 9, 14, 0.82) 0%, rgba(6, 9, 14, 0) 100%); }
#replaypanel .rp-bar { background: linear-gradient(0deg, rgba(6, 9, 14, 0.86) 55%, rgba(6, 9, 14, 0) 100%); }
#replaypanel.rp-opaque .mp-head, #replaypanel.rp-opaque .rp-bar { background: none; }
/* Loading / unavailable: no world to see through to yet, so wear the panel coat
   (this is also what covers the title screen a share link can land on). */
#replaypanel.rp-opaque {
  background: linear-gradient(180deg, #0b1210 0%, #080b11 100%);
  pointer-events: auto;
}
#replaypanel #rp-msg {
  flex: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  font: 13px ui-monospace, monospace;
  color: #9fb2c8;
}
#replaypanel #rp-msg.hidden { display: none; }
#replaypanel .rp-bar {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 10px 14px calc(10px + env(safe-area-inset-bottom));
  border-top: 1px solid var(--line);
}
#rp-slider { flex: 1; accent-color: #ffd166; }
.rp-clock { font: 600 13px ui-monospace, monospace; color: #ffd166; min-width: 70px; text-align: center; font-variant-numeric: tabular-nums; }
/* SHARE LINK joined the bar (July 2026): tighten the row on phones so
   play / slider / clock / speed / share all fit at 390px. */
@media (max-width: 480px) {
  #replaypanel .rp-bar { gap: 7px; padding-left: 10px; padding-right: 10px; }
  #replaypanel .rp-bar .race-btn { padding: 9px 9px; font-size: 11px; }
  .rp-clock { min-width: 52px; }
}

/* Racing from the TITLE screen: the cricket-select sits at z 41, so the race
   panel (and its replay viewer) rise above it while the main menu is up. */
body.menu-screen #racepanel, body.menu-screen #replaypanel { z-index: 42; }

/* ------------------------------------------------------------------
   REPLAY OWNS THE SCREEN (body.in-replay, set by game.js's REPLAY WORLD)

   The world under the transparent viewer is a HISTORICAL race, so every piece
   of live flight chrome and every menu surface stands down: none of it is
   about what is on screen, and a share link landing before any pilot exists
   must show the WORLD, not the title screen behind the panel.
   The viewer's own panel is not in this list (it IS the surface), and neither
   is #popup - a confirm keeps its place over everything.
   ------------------------------------------------------------------ */
body.in-replay #hud,
body.in-replay #scalebar,
body.in-replay #menubtn,
body.in-replay #quickmenu,
body.in-replay #missions,
body.in-replay #logbtn,
body.in-replay #hint,
body.in-replay #touch-controls,
body.in-replay #dock-chip,
body.in-replay #frame-chip,
body.in-replay #rush-chip,
body.in-replay #race-chip,
body.in-replay #daily-chip,
body.in-replay #fuel-out,
body.in-replay #approach-timer,
body.in-replay #toast,
body.in-replay #dock-prompt,
body.in-replay #deploy-btn,
body.in-replay #conn,
body.in-replay #race-exit,
body.in-replay .race-banner,
body.in-replay #burn-note,
body.in-replay #nav-note,
body.in-replay #mine-dock-note,
body.in-replay #deposit-note,
body.in-replay #boost-warn,
body.in-replay #cargopanel,
body.in-replay #missionlog,
body.in-replay #ship-panel,
body.in-replay #profile,
body.in-replay #market,
body.in-replay #chat,
body.in-replay #nav,
body.in-replay #menu,
body.in-replay #racepanel,
body.in-replay #cricket-select,
body.in-replay #roster-screen,
body.in-replay #gate,
body.in-replay #intro,
body.in-replay #join,
body.in-replay #eject-pop,
body.in-replay #amt-pop,
body.in-replay #debug-drone,
body.in-replay #debug-belt,
body.in-replay #diag-copy { display: none !important; }

/* ------------------------------------------------------------------
   THE RESULTS CARD MUST BE READABLE (Addendum C7)

   Three rules above are individually right and were jointly broken:
     #cricket-select               41  fullscreen, OPAQUE title screen
     body.menu-screen #racepanel   42  must clear it - the title screen's Racing
                                       button is a real shipped entry point, so
                                       this can never simply go below 41
     #tow-fade                     40  the homecoming blackout
     .race-card-scrim              39  deliberately UNDER the fade (Addendum A5)

   So a race ending while the pilot sat on Main Menu -> Racing buried the card
   under the panel, and even with that panel closed the opaque title screen (41)
   buried it too: a black screen with a menu on it and no result, ever.

   FIX: lift BOTH the card and the blackout, but only for exactly as long as a
   card exists. racing.js owns showResult, so it owns this class - it goes on
   with the card and comes off with it (plus a cheap reconcile on its 500 ms
   tick, so the class can never be stranded).

   The fade rides up WITH the card, one step above it, on purpose: A5's promise
   ("a card can never paint over the blackout") has to stay STRUCTURAL, not an
   ordering convention. Lifting only the card would put it above the z-40
   blackout and re-open the exact bug A5 closed. Both stay below #popup (45) -
   game.js dismisses any open popup when the ceremony begins (C7), so a popup
   over the black is handled there, not by out-stacking it here.

   TRADE-OFFS, so this is not silently "simplified" later:
   - Scoped to the class rather than raising #tow-fade outright, because that
     element is SHARED with the SOS emergency tow. A global raise would black out
     the title screen during an ordinary tow - a regression in someone else's
     feature. With no card up, nothing here moves at all.
   - Not fixed by closing the racing panel when the ceremony starts: while the
     tender is inbound that panel IS the status board (spec §8's last row, the
     HOMECOMING block in racing.js), and on the menu screen it is the only
     surface telling the pilot their ship is on its way home.
   - Keyed off the CARD, not off the ceremony phase (homeState/tenderInbound),
     even though those seams exist. Two reasons: racing.js only sees the phase on
     its 500 ms tick, and a lift that lands late is a visible flash of the wrong
     layer; and those seams are optional-guarded because racing.js must run
     against an older game.js - keying the fix to them would make the card
     unreadable exactly where the seam is missing. A card's own existence is the
     precise, zero-latency trigger, and C6 (a terminal reported with no ceremony
     at all) shows a card can outlive any phase anyway.
   - Consequence, accepted: while a card is up AND the blackout is up, the fade
     now covers the menu panel / title screen too. That window is exactly the
     "card must not show yet" beat, and the ceremony owning the whole screen
     there is the right direction.
   ------------------------------------------------------------------ */
body.race-card-up .race-card-scrim { z-index: 43; }   /* over the panel (42) + title screen (41) */
body.race-card-up #tow-fade { z-index: 44; }          /* ...and the blackout still beats the card */

.race-when { font-weight: 400; letter-spacing: 0; color: #7f93aa; font-size: 11px; }

/* RACING = your own ship, governed for the event: the HUD carries FUEL ONLY.
   The cargo chip stands down because the hold really IS empty (the tender stows
   your freight in escrow for the duration), and credits because a race is sport,
   not commerce - nothing here spends. game.js restores both on the homecoming. */
body.racing #hud-credits { display: none; }
body.racing #hud-cargo { display: none !important; }
/* Orbit locked at the finish body: the whole panel goes green while the server's
   finish debounce runs - the "you've done it" signal. The clock keeps ticking
   (the pilot watches their finishing time land), and the destination cell STAYS
   UP with its label swapped to "In Orbit:" - with the state sub-line gone it is
   the only thing that says you have arrived, and dropping it would collapse the
   row EXIT is aligned against. */
#race-chip.race-locked { color: #9cff8a; border-color: rgba(120, 230, 140, 0.6); --rc-rule: rgba(120, 230, 140, 0.32); }

/* EXIT (user call July 2026): the one visible way out, without digging into the
   menu - a REAL BUTTON inside the race panel now ("a clear exit button"), not a
   flat text cell on a divider. The skin is the racing panel's own exit action,
   .race-btn.race-quit (red ink, red-tinted fill, lit border, rounded), sized as
   a compact row control; racing.js's buildDom moves this node inside #race-chip,
   so it is positioned by that flex row (the TITLE row, at its LEFT end since the
   July 2026 "move exit button to top left"), not by the viewport.
   It stays its own entry in game.js's HEAT_BAND_IDS (harmless - its rect is
   inside the panel's) and in KEEP_OUT_SEL, so destination cues keep dodging it.
   The panel is pointer-events:none so the clock never eats a tap; this button
   turns them back on - the only interactive part of the box.

   SIZE (user call July 2026): "font and styling ok but make a bit smaller - like
   shrink margin around EXIT by 20%". The FONT is untouched; what shrank is the
   whitespace between the label and the border, on both axes, by exactly 20%:
     horizontal  14px -> 11px          (14 x 0.8 = 11.2)
     vertical    10.5px -> 8.4px each side, i.e. min-height 13px text + 16.8
                 = 34px -> 30px        (touch: 13.5 -> 10.8, 40px -> 35px)
   The vertical trim is taken off the REAL gap ((min-height - 13px type) / 2)
   rather than off min-height itself, since min-height includes the text and
   scaling it would have cut more than the margin the user was looking at.
   MEASURED on the live button: 59.5x30 CSS px desktop, 63.5x35 on touch (the
   label's ink is 35.5x15, so the gaps really are 12/7.5 and 14/10 per side).
   NOTE this puts the touch target at 63.5x35 CSS px - under Apple's 44x44 HIG
   guidance on the short axis, though still a comfortable finger-sized control
   and well clear of the ~30px where miss rates climb. Flagged rather than
   silently overridden; the two `min-height` values are the one-line revert. */
#race-exit {
  display: flex;
  align-items: center;
  justify-content: center;
  /* SMALLER AGAIN + PUSHED OUT (user call: "Exit font make smaller and shrink
     margin and shift a bit more to the right", then July 2026 "move exit button to
     top left" - the nudge MIRRORED with it). The negative margin eats into the
     panel's own side padding rather than changing it, so row 2 keeps its inset and
     only the button moves. */
  min-height: 26px;              /* body.touch grows it; see the size note above */
  padding: 0 8px;
  margin-left: -7px;
  border: 1px solid rgba(255, 110, 110, 0.5);
  border-radius: 9px;
  background: rgba(90, 40, 40, 0.35);
  font: inherit;                 /* a <button> does not inherit type - see above */
  /* The ONE size exception besides the title (user call: "Exit font make
     smaller"): 11px against the 13px every other cell reads at, so the way out
     is the quietest type in the panel. Must come AFTER the `font` shorthand,
     which resets font-size. */
  font-size: 11px;
  letter-spacing: inherit;
  white-space: nowrap;
  flex: none;
  color: #ffb3b3;
  z-index: auto;
  pointer-events: auto;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}
#race-exit:hover { color: #ffc9c9; background: rgba(104, 46, 46, 0.5); }
#race-exit:active { background: rgba(112, 50, 50, 0.65); transform: scale(0.97); }
/* The touch tier takes the smaller FONT and the tighter padding, but its HEIGHT
   stops at 32px - the floor below which miss rates genuinely climb, and the one
   qc_race_join pins. Desktop is free to go to 26 (a cursor has no fat finger);
   this is the one place the shrink is refused. */
body.touch #race-exit { min-height: 32px; padding: 0 10px; }
/* The dock sign owns the whole band while docked, and the tutorial shows no race
   chrome at all - same rules #race-chip already follows. */
body.docked #race-exit, body.in-tutorial #race-exit { display: none; }

/* ============================================================
   CRICKETSLIST - the contracts board (cricketslist.js). A menu-panel: it wears
   .menu-panel / .mp-head / .mp-body / .mp-foot from the shared theme above, so
   only the tabs, the rows and the POST form are declared here. Phone-first: the
   body is the one scrolling region, every tap target clears the 32px floor, and
   nothing here is fixed-width.
   ============================================================ */
.cl-bal {
  flex: 1;
  text-align: right;
  padding-right: 12px;
  font-size: 10px;
  letter-spacing: 0.14em;
  color: var(--muted);
  line-height: 1.5;
}
.cl-bal span { font-size: 15px; font-weight: 700; color: #ffd98a; letter-spacing: 0.02em; }
.cl-tabs {
  display: flex;
  gap: 6px;
  padding: 10px calc(14px + env(safe-area-inset-right)) 8px calc(14px + env(safe-area-inset-left));
  border-bottom: 1px solid var(--line);
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
}
.cl-tab {
  flex: 1 1 auto;
  min-height: 34px;
  padding: 0 10px;
  white-space: nowrap;
  font: 700 11px/1 ui-monospace, monospace;
  letter-spacing: 0.12em;
  color: var(--muted);
  background: rgba(255, 255, 255, 0.04);
  border: 1px solid var(--line);
  border-radius: 9px;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}
.cl-tab.active { color: #ffd98a; border-color: rgba(255, 210, 51, 0.5); background: rgba(255, 210, 51, 0.09); }
.cl-tab:active { transform: scale(0.97); }
.cl-note {
  padding: 8px 16px;
  font-size: 12px;
  color: #ffb3b3;
  border-bottom: 1px solid var(--line);
}
.cl-note.hidden { display: none; }
.cl-body { padding-bottom: 18px; }
.cl-sec { margin-bottom: 18px; }
.cl-h { font-size: 11px; font-weight: 700; letter-spacing: 0.18em; color: #7f93aa; margin: 6px 0 8px; }
.cl-empty { padding: 26px 6px; text-align: center; font-size: 13px; line-height: 1.6; color: #8fa3bb; }
.cl-note-line { font-size: 13px; line-height: 1.5; color: #a9bdd4; margin: 4px 0 10px; }
/* One contract: what is wanted + where (left), what it pays (right). A row you
   can act on is clickable; one you cannot (your own post, a slice you already
   hold, nothing left) goes INERT in the dim coat - the fit-out card's rule. */
.cl-row {
  display: flex;
  align-items: flex-start;
  gap: 12px;
  padding: 11px 8px;
  border-bottom: 1px solid rgba(120, 140, 170, 0.14);
  border-radius: 8px;
}
.cl-row.clickable { cursor: pointer; transition: background 0.12s ease; }
.cl-row.clickable:hover { background: rgba(255, 210, 51, 0.06); }
.cl-row.clickable:active { background: rgba(255, 210, 51, 0.12); }
.cl-row.cl-off { opacity: 0.5; cursor: default; }
.cl-row-main { flex: 1; min-width: 0; }
.cl-row-title { font-size: 14px; font-weight: 600; color: var(--ink); }
.cl-row-sub { margin-top: 3px; font-size: 12px; color: #8fa3bb; }
.cl-tail { margin-top: 4px; font-size: 11.5px; color: #7f93aa; }
/* The ACCEPT GATE's reason on a blocked (cl-off) row - the fit-out card's red. */
.cl-tail.cl-why { color: #ff8a8a; }
.cl-row-right { flex: none; text-align: right; }
.cl-price { font: 700 14px ui-monospace, monospace; color: #ffd98a; font-variant-numeric: tabular-nums; }
.cl-qty { margin-top: 3px; font: 500 11px ui-monospace, monospace; color: #8fa3bb; font-variant-numeric: tabular-nums; }
.cl-acts { display: flex; gap: 10px; margin: 10px 0 2px; flex-wrap: wrap; }
.cl-btn {
  min-height: 34px;
  padding: 0 16px;
  font: 700 12px ui-monospace, monospace;
  letter-spacing: 0.08em;
  color: #c7e6a0;
  background: rgba(60, 90, 60, 0.18);
  border: 1px solid rgba(130, 224, 158, 0.5);
  border-radius: 10px;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}
.cl-btn:active { transform: scale(0.97); }
.cl-btn.cl-go {
  color: #241a04;
  background: linear-gradient(180deg, #ffd98a 0%, #e0ac3d 100%);
  border: 1.5px solid rgba(255, 210, 51, 0.8);
}
.cl-btn.cl-quit { color: #ffb3b3; border-color: rgba(255, 110, 110, 0.45); background: rgba(90, 40, 40, 0.16); }
/* The blocked-button coat: dim gold, no gradient, no taps (game.js's ROW_OFF_GOLD). */
.cl-btn.cl-off {
  color: #8a7728;
  background: rgba(70, 62, 26, 0.3);
  border-color: rgba(138, 119, 40, 0.5);
  cursor: default;
}
/* POST form */
.cl-lab { font-size: 10.5px; font-weight: 700; letter-spacing: 0.18em; color: #7f93aa; margin: 14px 0 6px; }
.cl-chips { display: flex; flex-wrap: wrap; gap: 7px; }
.cl-chip {
  min-height: 32px;
  padding: 0 12px;
  font: 600 12px ui-monospace, monospace;
  color: var(--muted);
  background: rgba(255, 255, 255, 0.04);
  border: 1px solid var(--line);
  border-radius: 9px;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}
.cl-chip.on { color: #ffd98a; border-color: rgba(255, 210, 51, 0.55); background: rgba(255, 210, 51, 0.1); }
.cl-chip:active { transform: scale(0.97); }
.cl-fields { display: flex; gap: 12px; margin-top: 14px; flex-wrap: wrap; }
.cl-field { flex: 1 1 120px; display: flex; flex-direction: column; gap: 5px; min-width: 0; }
.cl-field span { font-size: 10.5px; font-weight: 700; letter-spacing: 0.16em; color: #7f93aa; }
.cl-field input {
  width: 100%;
  min-height: 40px;
  padding: 0 12px;
  font: 700 16px ui-monospace, monospace;   /* 16px: iOS zooms a focused field under it */
  color: var(--ink);
  background: rgba(255, 255, 255, 0.05);
  border: 1px solid var(--line);
  border-radius: 10px;
}
.cl-field input:focus { outline: none; border-color: rgba(255, 210, 51, 0.55); }
.cl-hint { margin-top: 10px; font-size: 12px; line-height: 1.5; color: #8fa3bb; }
.cl-escrow {
  margin-top: 10px;
  font: 700 13.5px ui-monospace, monospace;
  color: #ffd98a;
  font-variant-numeric: tabular-nums;
}
.cl-escrow.cl-short { color: #ff8a8a; }
/* The board's own amount card (see #cl-amt in index.html) - the #amt-pop scrim,
   declared separately because that rule is an id list game.js owns. */
#cl-amt {
  position: fixed;
  inset: 0;
  z-index: 46;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 22px;
  background: rgba(4, 6, 11, 0.72);
  backdrop-filter: blur(4px);
}
#cl-amt.hidden { display: none; }
/* The dev debug pills are z-60 pinned bottom, straight over this panel's footer
   (the body.nav-open / body.menu-screen rule, applied to the board). Dev-only -
   they never render in production. */
body.cl-open #debug-drone, body.cl-open #debug-belt, body.cl-open #diag-copy { display: none; }

/* ============================================================
   THE SHOP (Sep 2026): hull paint for real money. The preview rides above the
   scrolling body (like the Ship page's) and the rack is a grid of swatch tiles;
   the ONE action button under the caption reads WORN / WEAR IT / BUY · $x.
   ============================================================ */
.shop-top { flex: none; padding: 14px 16px 0; }
.shop-preview { height: 150px; }
.shop-body { padding-top: 10px; }
.shop-cap { display: flex; align-items: baseline; justify-content: space-between; gap: 12px; padding: 2px 2px 10px; }
.shop-cap b { font-size: 17px; font-weight: 700; color: var(--ink); letter-spacing: 0.02em; }
.shop-cap span { font-size: 12.5px; color: var(--muted); letter-spacing: 0.04em; text-transform: uppercase; }
.shop-act {
  width: 100%;
  padding: 13px 16px;
  font: inherit;
  font-size: 14px;
  font-weight: 800;
  letter-spacing: 0.1em;
  border-radius: 12px;
  cursor: pointer;
  transition: filter 0.14s ease, transform 0.08s ease;
  margin-bottom: 16px;
}
.shop-act:active:not(:disabled) { transform: scale(0.98); }
.shop-act.buy { color: #1a1300; background: linear-gradient(180deg, #ffe08a 0%, var(--gold) 100%); border: 1px solid rgba(255, 210, 51, 0.6); }
.shop-act.wear { color: #0b1210; background: linear-gradient(180deg, #b7e07a 0%, #8fc24a 100%); border: 1px solid rgba(143, 194, 74, 0.55); }
.shop-act.worn { color: var(--muted); background: rgba(255, 255, 255, 0.04); border: 1px solid var(--line); cursor: default; }
.shop-act:disabled:not(.worn) { opacity: 0.55; cursor: default; }
.shop-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(96px, 1fr)); gap: 10px; }
.shop-tile {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 7px;
  padding: 12px 6px 10px;
  font: inherit;
  color: var(--ink);
  background: rgba(255, 255, 255, 0.04);
  border: 1px solid var(--line);
  border-radius: 12px;
  cursor: pointer;
  transition: border-color 0.12s ease, background 0.12s ease, transform 0.08s ease;
}
.shop-tile:hover { border-color: rgba(200, 210, 230, 0.35); }
.shop-tile:active { transform: scale(0.97); }
.shop-tile.sel { border-color: rgba(255, 255, 255, 0.85); background: rgba(255, 255, 255, 0.07); }
.shop-sw { width: 34px; height: 34px; border-radius: 50%; border: 1px solid rgba(255, 255, 255, 0.25); box-shadow: inset 0 -6px 10px rgba(0, 0, 0, 0.28), 0 0 0 3px rgba(0, 0, 0, 0.25); position: relative; }
.shop-sw.locked { opacity: 0.62; }
.shop-sw.locked::after { content: ''; position: absolute; right: -3px; bottom: -3px; width: 12px; height: 12px; border-radius: 50%; background: #0b1210 url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23ffd233' stroke-width='2.6' stroke-linecap='round' stroke-linejoin='round'%3E%3Crect x='5' y='11' width='14' height='10' rx='2'/%3E%3Cpath d='M8 11V7a4 4 0 0 1 8 0v4'/%3E%3C/svg%3E") center / 8px 8px no-repeat; border: 1px solid rgba(255, 210, 51, 0.5); }
.shop-tile.worn .shop-sw { outline: 2px solid #8fc24a; outline-offset: 4px; }   /* the worn ring (an outline, so a finish keeps its ::before layer) */
/* PREMIUM FINISHES (Sep 2026): the swatch animates like the hull does. The
   background itself is SHOP.swatchCss (inline); these classes add the motion.
   Reused on the ship page's .ship-spec-swatch. */
@keyframes shop-hue { to { filter: hue-rotate(360deg); } }
.shop-sw.fx-rainbow, .ship-spec-swatch.fx-rainbow { animation: shop-hue 12s linear infinite; }
.shop-sw.fx-rgb, .ship-spec-swatch.fx-rgb { animation: shop-hue 8.4s steps(6) infinite; }
.ship-spec-swatch { position: relative; }
.shop-name { font-size: 12.5px; font-weight: 700; letter-spacing: 0.02em; }
.shop-sub { font-size: 11px; color: var(--gold); font-variant-numeric: tabular-nums; letter-spacing: 0.05em; text-transform: uppercase; }
.shop-sub.owned { color: var(--muted); }
.shop-tile.worn .shop-sub { color: #8fc24a; }
.shop-all {
  display: flex; align-items: center; justify-content: space-between; gap: 12px;
  margin-top: 16px; padding: 14px 14px;
  background: rgba(255, 210, 51, 0.06);
  border: 1px solid rgba(255, 210, 51, 0.32);
  border-radius: 12px;
}
.shop-all-txt { display: flex; flex-direction: column; gap: 3px; min-width: 0; }
.shop-all-txt b { font-size: 14px; font-weight: 700; color: var(--ink); }
.shop-all-txt span { font-size: 12px; color: var(--muted); }
.shop-all-btn {
  flex: none; padding: 10px 16px; font: inherit; font-size: 13px; font-weight: 800; letter-spacing: 0.06em;
  color: #1a1300; background: linear-gradient(180deg, #ffe08a 0%, var(--gold) 100%);
  border: 1px solid rgba(255, 210, 51, 0.6); border-radius: 10px; cursor: pointer;
}
.shop-all-btn:active:not(:disabled) { transform: scale(0.97); }
.shop-all-btn:disabled { color: var(--muted); background: rgba(255, 255, 255, 0.04); border-color: var(--line); cursor: default; }
.shop-note { margin-top: 16px; font-size: 11.5px; line-height: 1.55; color: var(--muted); }
.shop-restore { margin-top: 8px; padding: 0; font: inherit; font-size: 12px; color: var(--nav-accent); background: none; border: none; cursor: pointer; text-decoration: underline; }
