/*
  Shared terminal/cockpit theme - dark background, phosphor-green text,
  bordered panels reminiscent of an old Apple II/Atari-era console.
  Global (not a Blazor CSS-isolation *.razor.css file) because more than
  one page uses it: /play originated it, /lobby reuses it, and any future
  page should too rather than duplicating this theme locally.
*/

:root {
    --cockpit-bg: #01120a;
    --cockpit-panel-bg: #041c10;
    --cockpit-green: #33ff77;
    --cockpit-green-dim: #1c8a49;
    --cockpit-amber: #ffb000;
    --cockpit-red: #ff3b3b;
    --cockpit-font: 'Consolas', 'Courier New', monospace;

    /* One step brighter than --cockpit-green, for the moment a control is
       under the pointer or holding focus - the base green is already the
       resting text color, so "lit up" needs somewhere above it to go. */
    --cockpit-green-bright: #8affb4;
    /* Phosphor bloom. Always used as a box-shadow/glow color, never as a
       fill, so it carries its own alpha. */
    --cockpit-glow: rgba(51, 255, 119, 0.35);
    /* .cockpit-panel-bg lifted just enough to read as "this one" when the
       pointer is over it, without looking like a selected state. */
    --cockpit-panel-hover-bg: #062716;

    /* Motion. Every transition in the cockpit reads its duration from one of
       these three rather than hardcoding its own, so the reduced-motion
       block at the bottom of this file can neutralize the whole UI in one
       place instead of each effect carrying its own media query.
       Fast = a press (must feel like the click itself, not a response to
       it), the default = hover/focus, slow = anything that sweeps. */
    --cockpit-dur-fast: 90ms;
    --cockpit-dur: 160ms;
    --cockpit-dur-slow: 320ms;
    --cockpit-ease: cubic-bezier(0.2, 0.7, 0.3, 1);

    /* Shared by every overlay in the app (CockpitSelect's dropdown, the
       arena Inspect bubble) - everything else stacks at 0/1, so this just
       needs to clear the highest of those, not compete with a real
       stacking context. */
    --cockpit-z-popup: 5;

    /* The one deliberate departure from all-phosphor-green: a traditional
       loot-game rarity ladder (grey/green/blue/purple/orange), the first
       place this cockpit uses hues beyond its own accent. Shared across
       every 5-tier quality/rarity concept in the game - RosterQuality
       (Common..Legendary) and VehicleQualityTier (Beater..Legendary) map
       onto it by ladder position (RarityCssClass.cs); weapons have no such
       tier yet, so nothing here applies to them until that model exists.
       Uncommon deliberately reuses --cockpit-green rather than a second
       green - the cockpit's own accent color already sits exactly where
       "uncommon" belongs on this ladder. The others are picked to still
       read clearly against the near-black background rather than matching
       any specific game's exact hex values. */
    --rarity-common: #9aa3a8;
    --rarity-uncommon: var(--cockpit-green);
    --rarity-rare: #3fa9ff;
    --rarity-epic: #b266ff;
    --rarity-legendary: #ff9425;
}

/* Every cockpit page fills the browser edge-to-edge - no default white
   page margin/background should ever show around .cockpit-shell. */
html, body {
    margin: 0;
    padding: 0;
    height: 100%;
    background: var(--cockpit-bg);
}

/* This is where `.camera-viewport-svg * { pointer-events: none }` used to
   live. The bug it worked around was real - a pointer event's
   OffsetX/OffsetY are measured against event.target, the specific shape
   the finger landed on, not against the element the listener sits on, so
   a tap landing on a painted car outline got measured against that
   shape's own bounding box instead of the canvas (found on a real phone:
   the drag handle only answered taps offset to one side of the car).
   Switching every shape off was the cheapest fix at the time.

   It was also the wrong layer. The defect was the coordinate *source*,
   not the hit-testing: CameraViewport now reads ClientX/ClientY, which
   are viewport-relative and don't care what was hit, and subtracts the
   container's own origin (see ContainerPoint there). With that in place
   nothing needs the shapes switched off, so they're switched back on -
   which is what makes hovering and tapping things in the arena possible
   at all, and what the observation/inspect UI is built on. */

/* Turn playback moves each car 20 times a second (30 steps over 1.5s, each
   one a full Blazor re-render) and the cars visibly stuttered across the
   field as a result. Rather than re-rendering more often - which multiplies
   the cost of every panel on the page, not just the arena, and lands hardest
   on the phones this game is actually played on - the browser fills in the
   gaps between steps for free.

   --car-step is set per-car by the owning page to its own step interval, so
   a transition finishes exactly as the next step arrives. The 0ms default
   means a caller that hasn't set it gets today's snapping rather than a
   guessed cadence. `linear` because CurvedTurnPlayback has already paced the
   motion correctly; easing here would fight it and make constant speed look
   like it surges. */
.car-glyph {
    /* `translate`/`rotate`, not `transform` - see CarGlyph.razor. The SVG
       transform attribute isn't mapped to the CSS transform property in
       WebKit at all, so transitioning `transform` here was a silent no-op
       for every Safari player. fill-box keeps rotation about the glyph's own
       centre rather than the viewBox corner that auto-fit moves each render. */
    transform-box: fill-box;
    transform-origin: center;
    transition: translate var(--car-step, 0ms) linear,
                rotate var(--car-step, 0ms) linear;
}

/* Weapons fire. Previously a static line that simply existed for the rest of
   the turn, which made a hit and a miss differ only in colour and dash
   pattern - and gave a shot no moment of happening at all.

   A hit's tracer is drawn on: --tracer-length (set per-shot by the page, in
   engine units, since both endpoints move during playback) becomes a single
   dash as long as the whole shot, and walking its offset to zero travels the
   line from shooter to target. A miss keeps the old dashed look and fades,
   so the two are distinguishable by behaviour before you read the colour.
   Both are brief: this happens ~20 times a second alongside everything else
   on the field, and anything slower would still be playing when the next
   one starts. */
.shot-tracer.is-hit {
    stroke-dasharray: var(--tracer-length);
    animation: shot-fly 150ms linear;
}

@keyframes shot-fly {
    from { stroke-dashoffset: var(--tracer-length); }
    to { stroke-dashoffset: 0; }
}

.shot-tracer.is-miss {
    stroke-dasharray: 4 3;
    animation: shot-miss-fade 600ms ease-out forwards;
}

@keyframes shot-miss-fade {
    0% { opacity: 0.9; }
    40% { opacity: 0.55; }
    100% { opacity: 0.25; }
}

/* Muzzle flash and impact flash. Both expand and vanish rather than
   persisting - the tracer is the record of the shot, these are only the
   moment it happened. fill-box origins so each scales about its own centre
   and not about the viewBox corner, which auto-fit moves every render (the
   same trap CarGlyph avoids by staying on the transform attribute). */
.shot-muzzle,
.shot-impact {
    transform-box: fill-box;
    transform-origin: center;
}

.shot-muzzle {
    animation: shot-flash 200ms ease-out forwards;
}

.shot-impact {
    /* Starts as the tracer lands rather than as it leaves. */
    animation: shot-flash 320ms ease-out 150ms forwards;
    opacity: 0;
}

@keyframes shot-flash {
    from { transform: scale(0.4); opacity: 0.95; }
    to { transform: scale(2.2); opacity: 0; }
}

/* Boot splash. Sits at the bottom of the stacking order; .cockpit-shell and
   the title screen both claim a layer above it and both paint an opaque
   background, so it is covered the instant a page renders rather than needing
   to be dismissed. */
.app-boot {
    position: fixed;
    inset: 0;
    z-index: 0;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 10px;
    background: var(--cockpit-bg);
    font-family: var(--cockpit-font);
    color: var(--cockpit-green-dim);
}

.app-boot-title {
    font-size: clamp(18px, 4vw, 30px);
    letter-spacing: 8px;
    color: var(--cockpit-green);
    text-shadow: 0 0 18px var(--cockpit-glow);
}

.app-boot-status {
    font-size: 12px;
    letter-spacing: 3px;
    text-transform: uppercase;
}

/* Three dots that fill in, so a slow connection shows something alive rather
   than a frozen word. content, not opacity, so there is no layout shift. */
.app-boot-status::after {
    content: "";
    animation: app-boot-dots 1.2s steps(1, end) infinite;
}

@keyframes app-boot-dots {
    0% { content: ""; }
    25% { content: "."; }
    50% { content: ".."; }
    75% { content: "..."; }
}

@media (prefers-reduced-motion: reduce) {
    .app-boot-status::after {
        animation: none;
        content: "...";
    }
}

/* A cell that just lost points. Brief, and on the cell itself rather than the
   whole panel - the point is to say *which* readout changed, which a panel
   flash would lose. Paired with a @key on the same token in Play.razor, since
   a class alone would not replay the animation on a second hit. */
.cockpit-cell.is-damaged {
    animation: cockpit-damage-flash 620ms ease-out;
}

@keyframes cockpit-damage-flash {
    0% { background: var(--cockpit-red); color: var(--cockpit-bg); }
    35% { background: var(--cockpit-red); color: var(--cockpit-bg); }
    100% { background: transparent; }
}

.cockpit-bar-track.is-damaged {
    animation: cockpit-damage-bar 620ms ease-out;
}

@keyframes cockpit-damage-bar {
    0% { border-color: var(--cockpit-red); box-shadow: 0 0 10px var(--cockpit-red); }
    100% { border-color: var(--cockpit-green-dim); box-shadow: none; }
}

/* The power bar's own width used to snap between turns. */
.cockpit-bar-fill {
    transition: width var(--cockpit-dur-slow) var(--cockpit-ease);
}

/* The topbar phase readout. It changes text on every phase change and was
   otherwise indistinguishable from the turn counter beside it; @key'd on the
   phase in Play.razor so each new state announces itself once. */
.cockpit-phase {
    animation: cockpit-phase-in var(--cockpit-dur) var(--cockpit-ease);
}

.cockpit-phase.is-resolving { color: var(--cockpit-amber); }
.cockpit-phase.is-spectating { color: var(--cockpit-green-dim); }
.cockpit-phase.is-over { color: var(--cockpit-green-bright); }

@keyframes cockpit-phase-in {
    from { opacity: 0; transform: translateY(-4px); }
    to { opacity: 1; transform: none; }
}

@media (prefers-reduced-motion: reduce) {
    .cockpit-cell.is-damaged,
    .cockpit-bar-track.is-damaged,
    .cockpit-phase {
        animation: none;
    }

    .cockpit-bar-fill {
        transition: none;
    }
}

.cockpit-shell {
    font-family: var(--cockpit-font);
    color: var(--cockpit-green);
    background: var(--cockpit-bg);
    /* Above the boot splash - see .app-boot. */
    position: relative;
    z-index: 1;
    padding: 10px;
    border: 2px solid var(--cockpit-green-dim);
    box-sizing: border-box;
    /* Fills the browser viewport rather than a fixed footprint - min-
       height (not height) so a page whose content genuinely needs more
       room (e.g. Garage's crew panels) still just scrolls normally
       rather than clipping. display:flex column is what lets /play's
       .cockpit-main claim the remaining vertical space below the topbar
       (see its own flex:1) and lets a lighter page like /lobby center
       its content in that same remaining space. */
    width: 100%;
    min-height: 100vh;
    display: flex;
    flex-direction: column;
}

.cockpit-topbar {
    display: flex;
    flex-wrap: wrap;
    justify-content: space-between;
    /* A floor on the gap between the left (Home/page name) and right
       (Prestige/status/menu) groups, and the row gap once a topbar with
       enough content wraps to two lines at a narrow width - without either,
       the two groups could sit flush against each other with no visible
       separation ("HOME ROSTERPRESTIGE" running together) rather than
       reflowing. Found live on a tablet, on /roster's heavier topbar (a
       Prestige bar plus an unlock count on the right, alongside Home/Lobby/
       Roster on the left) - every page sharing this shell benefits. */
    gap: 4px 10px;
    padding: 4px 10px;
    margin-bottom: 10px;
    border-bottom: 1px solid var(--cockpit-green-dim);
    letter-spacing: 2px;
    text-transform: uppercase;
}

/* Lobby topbar's Prestige readout (Arena progression Phase 3) - the rank
   number plus a small horizontal XP bar toward the next one. */
.lobby-prestige-badge {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    font-size: 12px;
}

.lobby-prestige-bar {
    display: inline-block;
    width: 48px;
    height: 6px;
    background: var(--cockpit-panel-bg);
    border: 1px solid var(--cockpit-green-dim);
}

/* The same bar at panel width - GameOver's Prestige XP bar and /roster's
   "next Prestige rank" panel, which are the same quantity as the badge's,
   just given room. A modifier rather than a second class so there is one
   XP bar in the app. */
.lobby-prestige-bar.is-wide {
    display: block;
    width: 100%;
    height: 8px;
}

.lobby-prestige-bar-fill {
    display: block;
    height: 100%;
    background: var(--cockpit-green);
}

/* Standard hamburger-menu affordance for reaching Settings without a
   keyboard (a tablet has no Escape key). Sized as a real tap target, not
   a text-sized cockpit-button - it needs to work on a phone. */
.cockpit-menu-button {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 32px;
    height: 32px;
    padding: 0;
    font-size: 20px;
    line-height: 1;
    background: transparent;
    color: var(--cockpit-green);
    border: 2px solid var(--cockpit-green);
    cursor: pointer;
    flex-shrink: 0;
}

.cockpit-menu-button:hover {
    background: var(--cockpit-green-dim);
    color: var(--cockpit-bg);
    box-shadow: 0 0 8px var(--cockpit-glow);
}

.cockpit-menu-button:active {
    background: var(--cockpit-green);
    color: var(--cockpit-bg);
    box-shadow: none;
}

.cockpit-main {
    display: flex;
    gap: 12px;
    /* flex:1/min-height:0 claims the shell's remaining vertical space
       below the topbar (and above .cockpit-log, on /play) - stretch (not
       flex-start) is what hands .cockpit-field a genuine, non-circular
       height so the arena canvas inside it can size itself against a
       real number instead of a percentage with nothing definite to
       resolve against. Pages without a canvas (/lobby, /garage) are
       unaffected in practice - their own content just centers or flows
       normally in the same reclaimed space. */
    align-items: stretch;
    flex: 1;
    min-height: 0;
}

.cockpit-field {
    border: 1px solid var(--cockpit-green-dim);
    padding: 4px;
    background: #000;
    line-height: 0;
    max-width: 100%;
    /* Grows to fill whatever width .cockpit-main has left over (next to
       .cockpit-side's fixed 340px) and centers CameraViewport's own
       canvas within that space - CameraViewport.razor measures this
       element's own rendered rect (via viewport.js's getSquareSize) and
       explicitly sizes itself to the largest square that fits both axes.
       A no-op for pages with no canvas child. */
    display: flex;
    align-items: center;
    justify-content: center;
    flex: 1;
    min-width: 0;
    min-height: 0;
}

/* The body of a non-arena page (/overworld, /lobby...) - the space below
   the topbar. Declares itself a container so LayoutGrid's container
   queries have something to measure: a grid collapses to one column by
   how wide *this* is, not the viewport. Pages compose HStack/VStack/
   LayoutGrid inside it rather than writing flex/grid inline. */
.cockpit-page {
    flex: 1;
    min-width: 0;
    min-height: 0;
    padding: 16px;
    container-type: inline-size;
    container-name: cockpit-page;
    /* Same size the /play rail (.cockpit-side) reads at - the form pages
       used to borrow that class purely for this, and its fixed 340px width
       came along for the ride. */
    font-size: 15px;
}

/* A single centred form or notice (settings, a countdown, an error) -
   capped so it reads as a card on a wide screen, full width on a phone.
   Sizing, not layout: put it inside a VStack/LayoutGrid, don't nest more
   layout inside it. */
.cockpit-col-narrow {
    width: min(440px, 100%);
}

.cockpit-side {
    display: flex;
    flex-direction: column;
    gap: 8px;
    width: 340px;
    flex-shrink: 0;
    font-size: 15px;
}

.cockpit-weapon-row,
.cockpit-status-grid,
.cockpit-nav-row,
.cockpit-opponent-row {
    display: flex;
    justify-content: space-between;
}

/* Every Combatants row centers the camera on that car when clicked,
   regardless of .out/.hoverable - a wreck still has a real position
   worth jumping to. Scoped to those two classes rather than the bare
   .cockpit-opponent-row, which InspectBubble.razor's own header row also
   uses (inert there only because the bubble is pointer-events:none) - a
   pointer cursor over a row that can't actually be clicked would be
   misleading even if harmless. */
.cockpit-opponent-row.hoverable,
.cockpit-opponent-row.out {
    cursor: pointer;
}

.cockpit-status-grid {
    flex-wrap: wrap;
    row-gap: 4px;
}

/* The inspect bubble's Damage row: one item per line rather than a single
   "1 TIRE OUT | 1 WPN OUT" string, which read as a run-on in the bubble's
   narrow width. Same "- " marker as .cockpit-participant-list, indented a
   little further since this sits under its own "Damage" label line rather
   than starting a panel. */
.cockpit-damage-list {
    list-style: none;
    margin: 0;
    padding: 0 0 0 12px;
}

.cockpit-damage-list li::before {
    content: "- ";
    color: var(--cockpit-green-dim);
}

.cockpit-status-grid span {
    width: 48%;
}

.cockpit-opponent-row.out,
.cockpit-weapon-row.out {
    opacity: 0.45;
    text-decoration: line-through;
}

/* An opponent row that answers back: pointing at it rings that car on the
   arena (Play.razor's _hoveredOpponentId). The negative margin lets the
   highlight sit slightly proud of the text without moving anything - the
   row lives in a fixed-width rail, so growing it would reflow the panel.
   Only rows for cars still in the match get this class. */
.cockpit-opponent-row.hoverable {
    margin: 0 -4px;
    padding: 0 4px;
    transition: background-color var(--cockpit-dur) var(--cockpit-ease);
}

/* The Inspect panel, floated over the car on the arena instead of the side
   rail - see InspectBubble.razor/BubblePlacement.cs. left/top are set
   inline as percentages of CameraViewport's own container; everything
   here is the fixed visual shell. pointer-events:none - it's a readout,
   must never block a click/hover reaching the arena underneath it.
   Default sits above the car (tail on the bottom edge, pointing down at
   it); .tail-top is BubblePlacement's flip for a car too close to the top
   of the screen to fit the bubble above it. */
.inspect-bubble {
    position: absolute;
    transform: translate(-50%, calc(-100% - 12px));
    min-width: 160px;
    max-width: 220px;
    /* 70% transparent (30% opaque) - reported live as too opaque, hiding
       the arena and cars underneath a hovered/pinned car. Same
       color-mix-over-transparent idiom .cockpit-spinner's border already
       uses, so this fixed --cockpit-bg thins out the same way a per-theme
       rgba() literal couldn't without hand-updating if that variable
       ever changed. */
    background: color-mix(in srgb, var(--cockpit-bg) 30%, transparent);
    border: 1px solid var(--cockpit-green);
    box-shadow: 0 0 8px var(--cockpit-glow);
    padding: 6px 8px;
    font-size: 12px;
    /* .cockpit-field (this element's ancestor) sets line-height:0 to kill
       the inline-SVG descender gap - inherited here it collapses every
       row's line box to nothing, stacking all the text on top of itself.
       .camera-recenter resets it the same way for the same reason. */
    line-height: normal;
    z-index: var(--cockpit-z-popup);
    pointer-events: none;
}

.inspect-bubble.tail-top {
    transform: translate(-50%, 12px);
}

/* The notch - a real child of .inspect-bubble now (not a ::after), so its
   `left` can be driven by --notch-offset (InspectBubble.razor, a fraction
   of the whole view set by BubblePlacement) instead of a fixed 50%: a car
   near/off the side edge clamps the bubble's own LeftPercent to stay
   readable, but the notch still has to track the car's true position past
   that clamp. `100cqw` is 1% of CameraViewport's own rendered width per
   unit (see its container-type:inline-size, set specifically for this) -
   the same "percent of the whole view" unit --notch-offset is already
   expressed in - converted here to real pixels and added to 50% of *this
   element's own containing block* (the bubble, since it's a child now),
   then clamped to [7px, 100%-7px] so the notch travels the bubble's
   entire rendered width tracking the car exactly, but can never render
   outside the bubble's own rounded border no matter how far the car's
   true position has diverged from the bubble's clamped anchor. A rotated
   square, half-overlapping the bubble's edge with only the two borders
   that end up facing outward drawn, the same "diamond with two borders"
   technique every CSS speech-bubble tail uses. No existing tail/tooltip
   precedent anywhere else in this codebase to diverge from or match. */
.inspect-bubble-notch {
    position: absolute;
    left: clamp(7px, calc(50% + var(--notch-offset, 0) * 100cqw), calc(100% - 7px));
    width: 10px;
    height: 10px;
    /* Same transparency as .inspect-bubble itself, so the notch reads as
       part of the bubble rather than an opaque patch stuck to it. */
    background: color-mix(in srgb, var(--cockpit-bg) 30%, transparent);
    transform: translateX(-50%) rotate(45deg);
}

.inspect-bubble:not(.tail-top) .inspect-bubble-notch {
    bottom: -6px;
    border-right: 1px solid var(--cockpit-green);
    border-bottom: 1px solid var(--cockpit-green);
}

.inspect-bubble.tail-top .inspect-bubble-notch {
    top: -6px;
    border-left: 1px solid var(--cockpit-green);
    border-top: 1px solid var(--cockpit-green);
}

/* The car is genuinely outside the visible view horizontally - a notch
   would have nothing on-screen left to point at, so BubblePlacement swaps
   it for this instead: a small arrowhead sitting right at the screen's
   own edge, its tip touching the edge exactly and its base fanning inward
   - a directional cue ("keep looking that way") rather than a mere
   marker. A classic CSS border-triangle: a zero-size box with only one
   border colored and the two perpendicular to it transparent, which
   mitres them into the taper. edge-left points left (border-right is the
   flat base, no border-left at all collapses that side to the tip) with
   no further shift needed, since the tip already sits exactly on the box's
   own left%:0 anchor; edge-right mirrors that (border-left is the base)
   but then has to shift itself left by its own border width, since a
   box's near-zero-width anchor is always its *left* edge regardless of
   which side the triangle's point ends up on. */
.inspect-bubble-edge-arrow {
    position: absolute;
    width: 0;
    height: 0;
    border-top: 16px solid transparent;
    border-bottom: 16px solid transparent;
    filter: drop-shadow(0 0 3px var(--cockpit-glow));
    z-index: var(--cockpit-z-popup);
    pointer-events: none;
}

.inspect-bubble-edge-arrow.edge-left {
    border-right: 14px solid var(--cockpit-green);
    transform: translateY(-50%);
}

.inspect-bubble-edge-arrow.edge-right {
    border-left: 14px solid var(--cockpit-green);
    transform: translate(-14px, -50%);
}

.cockpit-bar-track {
    background: #062513;
    border: 1px solid var(--cockpit-green-dim);
    height: 14px;
    width: 100%;
    position: relative;
}

.cockpit-bar-fill {
    background: var(--cockpit-green);
    height: 100%;
}

.cockpit-bar-label {
    position: absolute;
    inset: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 11px;
    color: #eafff2;
    mix-blend-mode: difference;
}

.cockpit-log {
    margin-top: 10px;
    border: 1px solid var(--cockpit-green-dim);
    background: #000;
    padding: 6px 10px;
    height: 120px;
    overflow-y: auto;
    font-size: 14px;
}

/* The log is the one panel that actually scrolls, so it's the one place a
   default OS scrollbar shows up - a pale chrome strip down the side of an
   otherwise black terminal. `scrollbar-color` covers Firefox; the
   ::-webkit- rules cover Chrome/Edge/Safari, which ignore it. Applied to
   any cockpit scroll region, not just the log, so a future one matches. */
.cockpit-log,
.cockpit-scroll {
    scrollbar-width: thin;
    scrollbar-color: var(--cockpit-green-dim) #000;
}

.cockpit-log::-webkit-scrollbar,
.cockpit-scroll::-webkit-scrollbar {
    width: 10px;
}

.cockpit-log::-webkit-scrollbar-track,
.cockpit-scroll::-webkit-scrollbar-track {
    background: #000;
    border-left: 1px solid var(--cockpit-green-dim);
}

.cockpit-log::-webkit-scrollbar-thumb,
.cockpit-scroll::-webkit-scrollbar-thumb {
    background: var(--cockpit-green-dim);
    border: 2px solid #000;
}

.cockpit-log::-webkit-scrollbar-thumb:hover,
.cockpit-scroll::-webkit-scrollbar-thumb:hover {
    background: var(--cockpit-green);
}

/* Windows Chrome puts stepper arrows at both ends of a scrollbar and
   renders them in OS chrome that no scrollbar property reaches. Nothing
   else in this UI has them; dropping them also matches Firefox's thin
   scrollbar, so the two browsers agree. */
.cockpit-log::-webkit-scrollbar-button,
.cockpit-scroll::-webkit-scrollbar-button {
    display: none;
}

.cockpit-checkbox-row input[type="checkbox"] {
    accent-color: var(--cockpit-green);
}

.cockpit-log-line::before {
    content: "> ";
    color: var(--cockpit-green-dim);
}

/* The log was forty lines of identical green, which made "did I hit
   anything" a reading exercise rather than a glance. Colour is by event
   kind, and the palette is the one already in use on the field, so a red
   line in the log and a red marker on the arena mean the same thing. */
.cockpit-log-line.is-hit { color: #ff8a7a; }
.cockpit-log-line.is-miss { color: #7f8c8d; }
.cockpit-log-line.is-collision { color: #f6ad55; }
.cockpit-log-line.is-control { color: #ecc94b; }
.cockpit-log-line.is-impact { color: var(--cockpit-red); }
.cockpit-log-line.is-hazard { color: var(--cockpit-amber); }

/* Turn separators are the log's structure rather than its content - dimmer
   and letter-spaced so they read as rules between turns, not as events. */
.cockpit-log-line.is-structural {
    color: var(--cockpit-green-dim);
    letter-spacing: 1px;
}

/* Age banding, newest first. Older lines recede rather than competing with
   what just happened; three coarse bands rather than a per-line opacity,
   because this list re-renders ~20 times a second during playback. */
.cockpit-log-line.is-older { opacity: 0.62; }
.cockpit-log-line.is-oldest { opacity: 0.38; }

/* The newest line arrives rather than appearing. Only the inserted element
   runs this - see the @key note in Play.razor - so the list doesn't
   re-animate wholesale every time a line lands. */
.cockpit-log-line {
    animation: cockpit-log-arrive var(--cockpit-dur) var(--cockpit-ease);
    transition: opacity var(--cockpit-dur-slow) var(--cockpit-ease);
}

@keyframes cockpit-log-arrive {
    from { opacity: 0; transform: translateX(-6px); }
    to { opacity: 1; transform: none; }
}

@media (prefers-reduced-motion: reduce) {
    .cockpit-log-line {
        animation: none;
        transition: none;
    }
}

.cockpit-select,
.cockpit-input,
.cockpit-button {
    font-family: var(--cockpit-font);
    background: #000;
    color: var(--cockpit-green);
    border: 1px solid var(--cockpit-green);
    padding: 3px 6px;
    box-sizing: border-box;
}

.cockpit-select,
.cockpit-input {
    /* Below 16px, iOS Safari auto-zooms the whole page on focus (these
       inherit 15px from .cockpit-side otherwise) - an explicit 16px here
       is imperceptible on desktop but stops that zoom on a phone. */
    font-size: 16px;
    transition: border-color var(--cockpit-dur) var(--cockpit-ease),
                box-shadow var(--cockpit-dur) var(--cockpit-ease);
}

/* Strips the native control chrome (macOS bevel + arrow) so the closed
   select reads as cockpit rather than OS UI - Safari draws its own bevel
   here whenever appearance isn't overridden. The open option list is still
   OS-drawn either way (Safari allows no CSS on <option> or the popup),
   so this only covers the closed control - see PLAN.md "Dropdowns don't
   match the cockpit". Arrow is two gradient triangles rather than an SVG
   so it can reference var(--cockpit-green) directly. */
.cockpit-select {
    appearance: none;
    -webkit-appearance: none;
    padding-right: 24px;
    background-image:
        linear-gradient(45deg, transparent 50%, var(--cockpit-green) 50%),
        linear-gradient(135deg, var(--cockpit-green) 50%, transparent 50%);
    background-position: right 13px top 55%, right 8px top 55%;
    background-size: 5px 5px, 5px 5px;
    background-repeat: no-repeat;
}

/* The browser's own focus ring is suppressed here and re-added by the
   :focus-visible block further down, so a click leaves only the phosphor
   glow while a keyboard tab still gets a real, visible ring. */
.cockpit-select:focus,
.cockpit-input:focus {
    outline: none;
    border-color: var(--cockpit-green-bright);
    box-shadow: 0 0 8px var(--cockpit-glow);
}

/* CockpitSelect - the div/button/ARIA-listbox replacement for <select>,
   built specifically because Safari allows no CSS on <option> or the open
   popup itself. .cockpit-select above already covers the trigger's border/
   background/color/padding/arrow/focus/hover (class-scoped, so it applies
   to the trigger <button> for free); everything here is what's new. */
.cockpit-select-wrap {
    display: inline-block;
    position: relative;
}

.cockpit-select-trigger {
    text-align: left;
    cursor: pointer;
}

.cockpit-select-trigger:disabled {
    opacity: 0.4;
    cursor: default;
}

/* A <button> sizes to its current content, unlike a native <select> which
   sizes to its widest possible <option> - without this the trigger would
   visibly jump width as the value changes (e.g. Garage's Role select:
   "Passenger" vs. "Gunner: Slot 1 (MachineGun)"). Stacking every item's
   content in the same grid cell makes the cell (and so the button) size
   to the widest one, while only the actually-selected item stays visible. */
.cockpit-select-display {
    display: grid;
}

.cockpit-select-display > * {
    grid-area: 1 / 1;
}

.cockpit-select-ghost {
    visibility: hidden;
}

.cockpit-select-popup {
    position: absolute;
    top: calc(100% + 2px);
    left: 0;
    min-width: 100%;
    width: max-content;
    max-height: 240px;
    overflow-y: auto;
    background: var(--cockpit-bg);
    border: 1px solid var(--cockpit-green);
    box-shadow: 0 0 8px var(--cockpit-glow);
    z-index: var(--cockpit-z-popup);
}

.cockpit-select-option {
    padding: 4px 8px;
    cursor: pointer;
}

.cockpit-select-option.is-highlighted {
    background: rgba(51, 255, 119, 0.12);
}

/* An option that exists but can't be chosen (CockpitSelect's IsDisabled) -
   struck through and dim. This replaces the per-call-site
   .roster-picker-overcap span RosterCrewPicker used to draw, back when the
   select itself had no way to say "no". No pointer cursor and no hover
   highlight: it must not look clickable. */
.cockpit-select-option.is-disabled {
    text-decoration: line-through;
    color: var(--cockpit-green-dim);
    opacity: 0.6;
    cursor: not-allowed;
}

@media (hover: hover) {
    .cockpit-select-option:not(.is-disabled):hover {
        background: rgba(51, 255, 119, 0.12);
    }
}

.cockpit-button {
    text-transform: uppercase;
    letter-spacing: 1px;
    cursor: pointer;
    transition: background-color var(--cockpit-dur) var(--cockpit-ease),
                color var(--cockpit-dur) var(--cockpit-ease),
                border-color var(--cockpit-dur) var(--cockpit-ease),
                box-shadow var(--cockpit-dur) var(--cockpit-ease),
                translate var(--cockpit-dur-fast) var(--cockpit-ease);
}

.cockpit-button:hover:not(:disabled) {
    background: var(--cockpit-green-dim);
    color: var(--cockpit-bg);
    box-shadow: 0 0 8px var(--cockpit-glow);
}

/* A terminal key going down: 1px, no scale, and fast enough to read as the
   click itself rather than a reaction to it.

   `translate` (the standalone property), not `transform` - .camera-recenter
   already owns `transform` for its translateX(-50%) centering, and a press
   rule here would otherwise clobber it and fling the button off-center.
   The two properties compose instead of competing. */
.cockpit-button:active:not(:disabled) {
    background: var(--cockpit-green);
    color: var(--cockpit-bg);
    box-shadow: none;
    translate: 0 1px;
    transition-duration: var(--cockpit-dur-fast);
}

.cockpit-button:disabled {
    opacity: 0.4;
    cursor: default;
}

/* Hold Fire / drop-caltrop: a full-width toggle rather than a small
   checkbox+label, both for a bigger tap target and so "on" is unmistakable -
   same inverted-fill language as .cockpit-flag-button.is-ready. */
.cockpit-toggle-button {
    display: block;
    width: 100%;
    padding: 8px;
    text-align: center;
    border: 2px solid var(--cockpit-green);
}

.cockpit-toggle-button.is-active {
    background: var(--cockpit-green);
    color: var(--cockpit-bg);
    box-shadow: 0 0 8px var(--cockpit-glow);
}

/* Specificity (0,4,0), beating the shared .cockpit-button:hover rule's
   (0,3,0) - without this, hovering an active toggle (or a tap's sticky
   post-touch hover, since this rule isn't confined to @media (hover: hover))
   would wash the "on" state out to the plain hover fill. */
.cockpit-button.cockpit-toggle-button.is-active:hover {
    background: var(--cockpit-green);
    color: var(--cockpit-bg);
}

.cockpit-checkbox-row {
    display: flex;
    align-items: center;
    gap: 6px;
    transition: color var(--cockpit-dur) var(--cockpit-ease);
}

.cockpit-checkbox-row label,
.cockpit-checkbox-row input[type="checkbox"] {
    cursor: pointer;
}

/* A commit-style button that owns its whole row rather than sitting at
   its text's natural width - the primary action of a panel. */
.cockpit-button-wide {
    display: block;
    width: 100%;
    padding: 8px;
    font-size: 15px;
}

.cockpit-speed-row {
    display: flex;
    align-items: center;
    gap: 8px;
}

.cockpit-speed-row label {
    text-transform: uppercase;
    letter-spacing: 0.5px;
    font-size: 12px;
}

/* The big READY button: a checkered-flag banner with the label on a
   solid plate across the middle so it stays legible over the pattern.
   The checker is img/checker-tile.svg rather than a CSS gradient so the
   same artwork backs the button, race-flags.svg, and anything later. */
.cockpit-flag-button {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 10px;
    width: 100%;
    min-height: 64px;
    padding: 0;
    border: 3px solid var(--cockpit-green);
    background-color: #0d0d0d;
    background-image: url("../img/checker-tile.svg");
    background-size: 24px 24px;
    font-family: var(--cockpit-font);
    cursor: pointer;
    transition: box-shadow var(--cockpit-dur) var(--cockpit-ease),
                border-color var(--cockpit-dur) var(--cockpit-ease),
                translate var(--cockpit-dur-fast) var(--cockpit-ease);
}

.cockpit-flag-button-label {
    display: flex;
    align-items: center;
    gap: 10px;
    /* Opaque, so the checker never runs behind the text. */
    background: var(--cockpit-bg);
    color: var(--cockpit-green);
    border-left: 2px solid var(--cockpit-green);
    border-right: 2px solid var(--cockpit-green);
    padding: 12px 20px;
    font-size: 22px;
    font-weight: bold;
    letter-spacing: 6px;
    text-transform: uppercase;
    white-space: nowrap;
    transition: background-color var(--cockpit-dur) var(--cockpit-ease),
                color var(--cockpit-dur) var(--cockpit-ease);
}

.cockpit-flag-button-flags {
    /* Small enough to sit inside the 64px banner, big enough that the
       flags still read as flags rather than as a smudge. */
    height: 40px;
    width: auto;
    flex-shrink: 0;
    /* The artwork is black-on-white like a real race flag, so it needs a
       light ground: straight on the dark label plate, its black half -
       poles, and half the squares - simply disappears. */
    background: #ffffff;
    border-radius: 3px;
    padding: 2px 4px;
}

.cockpit-flag-button:hover:not(:disabled) .cockpit-flag-button-label {
    background: var(--cockpit-green);
    color: var(--cockpit-bg);
}

/* Hovering waves the flag: the checker scrolls one full tile (24px, the
   background-size above) so the loop is seamless. Only on hover - ambient
   motion on a resting button would pull the eye off the arena. */
.cockpit-flag-button:hover:not(:disabled) {
    box-shadow: 0 0 14px var(--cockpit-green-dim);
    animation: cockpit-checker-scroll 1.6s linear infinite;
}

@keyframes cockpit-checker-scroll {
    from { background-position: 0 0; }
    to { background-position: 24px 0; }
}

.cockpit-flag-button:active:not(:disabled) {
    translate: 0 1px;
    transition-duration: var(--cockpit-dur-fast);
}

.cockpit-flag-button:disabled {
    opacity: 0.35;
    cursor: default;
    filter: grayscale(1);
}

/* Already committed - the button stays live (the build can still be
   edited and re-submitted) but reads as armed rather than as a
   call to action. */
.cockpit-flag-button.is-ready .cockpit-flag-button-label {
    background: var(--cockpit-green);
    color: var(--cockpit-bg);
}

/* ---- Countdowns ---------------------------------------------------------
   Two of them, sharing an urgency language but little else: the garage's
   3-2-1 before the arena (a three-beat event) and /play's auto-pilot
   takeover warning (a slow drain). */

/* A numeral inside a ring that sweeps away over each second. Both the
   numeral and the sweep are re-created every tick - Garage.razor puts an
   @key on the count - which is what restarts their animations. A CSS
   animation on an element that merely changes its text plays once and
   never again. */
.cockpit-countdown {
    position: relative;
    width: 170px;
    height: 170px;
    margin: 4px auto 0;
}

.cockpit-countdown-ring {
    width: 100%;
    height: 100%;
    /* Start the sweep at twelve o'clock rather than at three. */
    transform: rotate(-90deg);
}

.cockpit-countdown-track {
    fill: none;
    stroke: var(--cockpit-green-dim);
    stroke-width: 2;
    opacity: 0.5;
}

.cockpit-countdown-sweep {
    fill: none;
    stroke: var(--cockpit-green);
    stroke-width: 4;
    stroke-linecap: round;
    /* 2 * pi * 45, the radius in the 100x100 viewBox - the entire
       circumference as a single dash, so animating the offset wipes it
       away exactly once with no repeat. */
    stroke-dasharray: 282.743;
    animation: cockpit-countdown-sweep 1s linear forwards;
}

@keyframes cockpit-countdown-sweep {
    from { stroke-dashoffset: 0; }
    to { stroke-dashoffset: 282.743; }
}

.cockpit-countdown-number {
    position: absolute;
    inset: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 62px;
    font-weight: bold;
    color: var(--cockpit-green);
    text-shadow: 0 0 20px var(--cockpit-glow);
    animation: cockpit-countdown-punch var(--cockpit-dur-slow) var(--cockpit-ease);
}

/* Arrives slightly oversized and settles, rather than growing into place -
   a numeral that lands reads as a clock tick, one that scales up reads as
   a zoom effect. */
@keyframes cockpit-countdown-punch {
    from { opacity: 0; transform: scale(1.6); }
    to { opacity: 1; transform: scale(1); }
}

/* The final beat. Stays green - this is the go signal, not a warning - and
   holds rather than sweeping, since nothing follows it. */
.cockpit-countdown.is-go .cockpit-countdown-number {
    font-size: 46px;
    letter-spacing: 4px;
}

.cockpit-countdown.is-go .cockpit-countdown-sweep {
    animation: none;
    stroke-dashoffset: 0;
}

/* /play's auto-pilot warning. .cockpit-connection-banner supplies the
   frame; this adds the depleting bar and three urgency tiers, which is the
   difference between "you have a minute" and "this is happening now" -
   information the old static amber strip carried only in its text. */
.cockpit-countdown-banner {
    position: relative;
    overflow: hidden;
    border-color: var(--cockpit-green-dim);
    color: var(--cockpit-green);
}

.cockpit-countdown-bar {
    position: absolute;
    left: 0;
    bottom: 0;
    height: 3px;
    background: var(--cockpit-green);
    /* 1s is not a motion token and must not become one: it matches the tick
       interval of the loop that re-renders the width, so a once-a-second
       step renders as a continuous drain. Driving the bar from the deadline
       this way is what avoids re-rendering the page at 60fps just to move a
       progress bar. Because it isn't a token, the reduced-motion block at
       the bottom of this file has to switch it off by hand. */
    transition: width 1s linear, background-color var(--cockpit-dur) var(--cockpit-ease);
}

.cockpit-countdown-banner.is-warning {
    border-color: var(--cockpit-amber);
    color: var(--cockpit-amber);
}

.cockpit-countdown-banner.is-warning .cockpit-countdown-bar {
    background: var(--cockpit-amber);
}

.cockpit-countdown-banner.is-critical {
    border-color: var(--cockpit-red);
    color: var(--cockpit-red);
    animation: cockpit-countdown-alarm 1s ease-in-out infinite;
}

.cockpit-countdown-banner.is-critical .cockpit-countdown-bar {
    background: var(--cockpit-red);
}

/* One pulse per second, in step with the countdown's own tick. */
@keyframes cockpit-countdown-alarm {
    0%, 100% { opacity: 1; }
    50% { opacity: 0.55; }
}

.cockpit-error {
    color: var(--cockpit-amber);
}

/* Same color as .cockpit-error - amber reads as caution regardless of
   whether the underlying issue blocks Ready (error) or not (warning) -
   just a distinct class so the two concepts don't share a name. */
.cockpit-warning {
    color: var(--cockpit-amber);
}

.cockpit-hint {
    color: var(--cockpit-green);
    font-size: 12px;
    margin-top: 4px;
}

/* Small inline indicator for a brief async wait inside a .cockpit-hint line
   (e.g. Lobby.razor's cold-start "checking for a saved sign-in" state) -
   a spinning ring rather than text alone, so a multi-second network round
   trip doesn't look identical to a static, already-resolved message. */
.cockpit-spinner {
    display: inline-block;
    width: 10px;
    height: 10px;
    margin-right: 4px;
    border: 2px solid color-mix(in srgb, var(--cockpit-green) 30%, transparent);
    border-top-color: var(--cockpit-green);
    border-radius: 50%;
    vertical-align: -1px;
    animation: cockpit-spin 0.7s linear infinite;
}

@keyframes cockpit-spin {
    to {
        transform: rotate(360deg);
    }
}

/* The result screen's win/lose sentence - reads as a headline against the
   plain body text of everything else in the Controls panel. */
.cockpit-result-headline {
    font-size: 16px;
    font-weight: bold;
    letter-spacing: 1px;
    text-transform: uppercase;
    margin-bottom: 8px;
}

/* Default link blue is the one bit of browser chrome that still shows
   through inside a phosphor terminal. */
.cockpit-link {
    color: var(--cockpit-green);
    text-decoration: underline;
    transition: color var(--cockpit-dur) var(--cockpit-ease),
                background-color var(--cockpit-dur) var(--cockpit-ease);
}

/* The QR itself has to stay dark-on-light to scan reliably, so the
   terminal styling goes on the frame around it: a green-bordered plate
   with a little white padding, which doubles as the quiet zone scanners
   need to find the code's edges. */
.cockpit-qr {
    width: 170px;
    margin: 8px auto 4px;
    padding: 6px;
    background: #ffffff;
    border: 2px solid var(--cockpit-green);
    line-height: 0;
}

.cockpit-qr svg {
    display: block;
    width: 100%;
    height: auto;
}

.cockpit-link:hover {
    color: var(--cockpit-bg);
    background: var(--cockpit-green);
    text-decoration: none;
}

.cockpit-connection-banner {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 10px;
    padding: 6px 10px;
    margin-bottom: 10px;
    border: 1px solid var(--cockpit-amber);
    color: var(--cockpit-amber);
    text-transform: uppercase;
    letter-spacing: 1px;
}

.cockpit-controls > div {
    margin-top: 6px;
}

.cockpit-controls > div:first-child {
    margin-top: 0;
}

.cockpit-field-row {
    margin-top: 8px;
}

.cockpit-field-row:first-child {
    margin-top: 0;
}

.cockpit-field-row label {
    display: block;
    text-transform: uppercase;
    letter-spacing: 0.5px;
    font-size: 12px;
    margin-bottom: 3px;
}

/* First tabbed panel in the app - Settings' General/Key Bindings split.
   No underline-style indicator; the active tab just gets the same
   inverted-fill "armed" look every other selected control in the cockpit
   uses (.cockpit-flag-button.is-ready, .cockpit-toggle-button.is-active). */
.cockpit-tab {
    font-family: var(--cockpit-font);
    padding: 6px 14px;
    text-transform: uppercase;
    letter-spacing: 0.5px;
    font-size: 12px;
    border: 2px solid var(--cockpit-green);
    background: transparent;
    color: var(--cockpit-green);
    cursor: pointer;
}

.cockpit-tab.is-active {
    background: var(--cockpit-green);
    color: var(--cockpit-bg);
}

.cockpit-participant-list {
    list-style: none;
    padding: 0;
    margin: 0;
}

.cockpit-participant-list li {
    padding: 2px 0;
}

.cockpit-participant-list li::before {
    content: "- ";
    color: var(--cockpit-green-dim);
}

/* Ambient hover effects on things that aren't buttons. Gated on a device
   that actually has a pointer: a touch browser leaves :hover latched on
   whatever was tapped last until something else is tapped, so on a phone
   these would read as a stuck selection rather than as feedback. The
   button/input hovers above are deliberately left ungated - they predate
   this and a latched button hover is self-explanatory. */
@media (hover: hover) {
    .cockpit-checkbox-row:hover {
        color: var(--cockpit-green-bright);
    }

    .cockpit-select:hover:not(:disabled),
    .cockpit-input:hover:not(:disabled) {
        border-color: var(--cockpit-green-bright);
    }

    .cockpit-opponent-row.hoverable:hover {
        background: rgba(51, 255, 119, 0.12);
    }
}

/* Keyboard focus. Nothing in this UI had a focus ring at all - the browser
   default is suppressed on the inputs above, and CameraViewport used to set
   outline:none outright - so the whole cockpit was unnavigable by keyboard
   without watching the caret. :focus-visible (not :focus) is what keeps a
   mouse click from also painting a ring: the browser only matches it when
   focus arrived by keyboard. Declared after the :focus rules above so it
   wins for a keyboard focus that matches both. */
.cockpit-button:focus-visible,
.cockpit-flag-button:focus-visible,
.cockpit-select:focus-visible,
.cockpit-input:focus-visible,
.cockpit-link:focus-visible,
.cockpit-checkbox-row input[type="checkbox"]:focus-visible {
    outline: 2px solid var(--cockpit-green-bright);
    outline-offset: 2px;
}

/* One switch for the whole cockpit. Durations collapse to zero, which
   settles every transition instantly at its end state; the two things that
   aren't durations - an infinite loop and a press offset - have to be
   turned off by hand. */
@media (prefers-reduced-motion: reduce) {
    :root {
        --cockpit-dur-fast: 0ms;
        --cockpit-dur: 0ms;
        --cockpit-dur-slow: 0ms;
    }

    .cockpit-flag-button:hover:not(:disabled) {
        animation: none;
    }

    .cockpit-button:active:not(:disabled),
    .cockpit-flag-button:active:not(:disabled) {
        translate: none;
    }

    /* The countdowns. The numeral still changes and the bar still shrinks -
       the information is intact - they just stop moving to get there. The
       bar's transition is listed explicitly because its duration is a
       tick-sync value rather than a motion token, so zeroing the tokens
       doesn't reach it. */
    .cockpit-countdown-number,
    .cockpit-countdown-sweep,
    .cockpit-countdown-banner.is-critical {
        animation: none;
    }

    .cockpit-countdown-bar {
        transition: none;
    }

    /* Cars snap between playback steps again rather than gliding. The
       playback itself still runs - the turn still animates, and at the same
       pace - this only removes the browser's interpolation between frames. */
    .car-glyph {
        transition: none;
    }

    /* Weapons fire loses its motion but keeps its meaning: the tracer is
       still drawn and still coloured by outcome. The two flashes end at
       opacity 0, so switching their animation off has to hide them by hand
       or they'd sit on the field permanently - and .shot-impact starts at
       opacity 0, so it would otherwise simply never appear. */
    .shot-tracer.is-hit,
    .shot-tracer.is-miss {
        animation: none;
    }

    .shot-muzzle,
    .shot-impact {
        display: none;
    }
}

/* Phones and small tablets in portrait - desktop (above this) is
   untouched. .cockpit-main goes from side-by-side to stacked so nothing
   ever needs horizontal scrolling to reach the controls. */
@media (max-width: 700px) {
    .cockpit-shell {
        padding: 6px;
    }

    .cockpit-main {
        flex-direction: column;
        /* Desktop's flex:1/stretch exists purely so the arena canvas can
           grow to fill remaining space next to the side panel - in a
           stacked column that same growth would fight the canvas's own
           aspect-ratio sizing (see .cockpit-field below), so mobile goes
           back to ordinary content-driven sizing. */
        flex: none;
        align-items: stretch;
    }

    .cockpit-field {
        flex: none;
        /* .cockpit-field's own width is still definite here (.cockpit-main's
           align-items:stretch fills it to the shell's full width), but its
           height would otherwise be content-driven (auto) in the stacked
           column - circular, since CameraViewport measures *this* element's
           rect to size itself. aspect-ratio derives a real, non-circular
           height from the already-definite width instead. */
        aspect-ratio: 1 / 1;
    }

    .cockpit-side {
        width: 100%;
    }

    .cockpit-log {
        height: 90px;
    }
}

/* ==========================================================================
   Roster (Arena progression Phase 5) - /roster's grid, the "closest to
   unlocking" strip, the garage's per-seat crew picker, and the GameOver
   Prestige panel. See docs/ARENA-PROGRESSION.md "The grid" for the card
   states this styles (unlocked / locked-visible / hidden).
   ========================================================================== */

.character-portrait {
    width: 100%;
    height: 64px;
    display: flex;
    align-items: center;
    justify-content: center;
    border: 1px solid var(--cockpit-green-dim);
    background: #000;
}

.character-portrait-silhouette {
    width: 28px;
    height: 28px;
    fill: var(--cockpit-green-dim);
}

.character-portrait-question {
    font-size: 24px;
    color: var(--cockpit-green-dim);
}

/* wraps to 2x2 rather than overflowing horizontally - at the roster
   grid's 130px card floor (Roster.razor's LayoutGrid), the four columns'
   combined width doesn't quite fit alongside the card's own padding, and
   without wrap the row simply spilled past the card's edge into whatever
   sat beside/below it. Found live on a tablet. */
.skill-pips {
    display: flex;
    flex-wrap: wrap;
    gap: 8px;
}

.skill-pips-column {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 2px;
}

.skill-pips-squares {
    display: flex;
    gap: 2px;
}

.skill-pips-square {
    width: 7px;
    height: 7px;
    border: 1px solid var(--cockpit-green-dim);
    background: transparent;
}

.skill-pips-square.is-filled {
    background: var(--cockpit-green);
    border-color: var(--cockpit-green);
}

.skill-pips-letter {
    font-size: 9px;
    color: var(--cockpit-green-dim);
}

/* Rarity-colored text - the --rarity-* ladder (see :root), generic so it
   applies to a Roster character's Quality label, a vehicle's
   VehicleQualityTier, or anything else that lands on this same five-tier
   scale later. RarityCssClass.cs picks the suffix. */
.rarity-text-common { color: var(--rarity-common); }
.rarity-text-uncommon { color: var(--rarity-uncommon); }
.rarity-text-rare { color: var(--rarity-rare); }
.rarity-text-epic { color: var(--rarity-epic); }
.rarity-text-legendary {
    color: var(--rarity-legendary);
    text-shadow: 0 0 6px var(--rarity-legendary);
}

/* The garage's per-seat Roster picker (Phase 5) - Driver's own "this is
   you" badge, and the struck-through/dimmed look for an unlocked-but-
   over-cap option (see RosterCrewPicker's own doc comment - picking one is
   silently ignored, so this is purely a visual "why can't I pick this"
   cue). */
.roster-primary-badge {
    margin-left: 8px;
    padding: 1px 6px;
    font-size: 10px;
    color: var(--cockpit-bg);
    background: var(--cockpit-amber);
    text-transform: uppercase;
    letter-spacing: 0.5px;
}

.roster-card {
    display: flex;
    flex-direction: column;
    gap: 4px;
    padding: 8px;
    border: 1px solid var(--cockpit-green-dim);
    background: var(--cockpit-panel-bg);
}

.roster-card.is-locked {
    opacity: 0.85;
}

.roster-card.is-hidden {
    border-style: dashed;
    border-color: var(--cockpit-green-dim);
    align-items: center;
    text-align: center;
}

/* Border color is rarity, not unlock state, for every non-hidden card -
   is-locked's own opacity above already dims it, and is-unlocked no
   longer needs a border-color of its own. Chained with .roster-card
   (not a bare .roster-card-rarity-* class) to outrank .roster-card's own
   base border-color at equal specificity. A hidden card never carries one
   of these (its Quality is redacted), so is-hidden's dim/dashed border
   above is untouched. */
.roster-card.roster-card-rarity-common { border-color: var(--rarity-common); }
.roster-card.roster-card-rarity-uncommon { border-color: var(--rarity-uncommon); }
.roster-card.roster-card-rarity-rare { border-color: var(--rarity-rare); }
.roster-card.roster-card-rarity-epic { border-color: var(--rarity-epic); }
.roster-card.roster-card-rarity-legendary {
    border-color: var(--rarity-legendary);
    box-shadow: 0 0 6px var(--rarity-legendary);
}

.roster-card-name {
    font-weight: bold;
    text-transform: uppercase;
    font-size: 13px;
}

.roster-card.is-locked .roster-card-name {
    color: var(--cockpit-green-dim);
}

.roster-card-condition {
    font-size: 11px;
    color: var(--cockpit-green-dim);
}

.roster-card-progress {
    height: 4px;
    background: #000;
    border: 1px solid var(--cockpit-green-dim);
}

.roster-card-progress-fill {
    display: block;
    height: 100%;
    background: var(--cockpit-green-dim);
}

/* Arena crew progression Phase 2 (docs/ARENA-CREW-PROGRESSION-PLAN.md
   decision 8) - "your growth" on an unlocked roster card. Amber (matching
   the app's existing --cockpit-amber accent, e.g. the Driver's own
   "PRIMARY" badge) rather than the unlock-condition bar's dim green, so a
   card's own earned-progress line/bar reads as a distinct kind of
   information from "how close to unlocking this" above it. */
.crew-growth-line {
    font-size: 10px;
    color: var(--cockpit-amber);
}

.crew-growth-caption {
    margin-top: 2px;
}

.roster-card-progress-fill.is-crew-growth {
    background: var(--cockpit-amber);
}

/* GameOver's Prestige breakdown lines (Phase 5) - additive lines in the
   ordinary log colour, multiplier lines dim, the total line separated by a
   dashed rule above it. */
.prestige-breakdown-line {
    display: flex;
    justify-content: flex-end;
    gap: 8px;
}

.prestige-breakdown-line.is-multiplier {
    color: var(--cockpit-green-dim);
}

.prestige-breakdown-total {
    display: flex;
    justify-content: flex-end;
    margin-top: 4px;
    padding-top: 4px;
    border-top: 1px dashed var(--cockpit-green-dim);
    font-weight: bold;
}

.prestige-rankup-banner {
    text-align: center;
    padding: 8px;
    margin: 8px 0;
    border: 1px solid var(--cockpit-amber);
    color: var(--cockpit-amber);
    text-shadow: 0 0 8px var(--cockpit-amber);
    letter-spacing: 2px;
    text-transform: uppercase;
}
