/* The script face of the logo and the venue names, served from here (see
   lab/lab.css). block, not swap: on stream a name drawn in Comic Sans for a
   second is worse than a name that waits a moment. Where lab.css loads after
   this file its own copy of the rule (swap) wins - which is why the venue
   caption checks for the face itself rather than trusting this line. */
@font-face {
    font-family: 'Mr Dafoe';
    src: url('fonts/MrDafoe-Regular.woff2') format('woff2');
    font-display: block;
}

/* ===========================================================
   Stream Racers - viewer racing widget
   Cars are 112x64 (or 128x64) pixel-art side views. Every rig is
   laid out in NATURAL sprite pixels and scaled as a whole, so all
   wheel offsets from assets.js can be used verbatim.
   =========================================================== */

:root {
    --hot: #ff2d6f;            /* gauge fill / progress accent (brand constant) */
    --ink: #16161c;            /* HUD body */
    --ink-2: #23232c;          /* HUD inner wells */
    --stroke: #000;
    --team-a: #8bd427;
    --team-b: #ff5a1f;
    --car-scale: 3;
}

* { box-sizing: border-box; }

body {
    margin: 0;
    font-family: 'Jersey 10', 'Trebuchet MS', system-ui, sans-serif;
    background: #101014;
    color: #fff;
    -webkit-font-smoothing: antialiased;
}

body.obs { background: transparent; }
body.obs #control-panel { display: none; }

#widget-shell {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 14px;
    padding: 14px 0 0;
    min-height: 100vh;      /* lets the bottom-track banner sit on the floor */
}

img { image-rendering: pixelated; image-rendering: crisp-edges; }

.hidden { display: none !important; }

/* ===================== Control panel ===================== */

/* Compact by design: the panel plus the stage has to fit one screen without
   scrolling, so everything here is one line of controls per concern. */
#control-panel {
    width: min(980px, calc(100% - 28px));
    padding: 8px 12px 10px;
    border: 2px solid #2c2c36;
    border-radius: 12px;
    background: #1a1a21;
    box-shadow: 0 8px 24px rgba(0, 0, 0, .4);
}

.panel-top {
    display: flex;
    align-items: center;
    gap: 10px;
    padding-bottom: 8px;
    border-bottom: 2px solid #2c2c36;
}

.game-title {
    margin: 0;
    font-size: 26px;
    letter-spacing: 2px;
    color: #fff;
    text-shadow: 0 2px 0 var(--hot);
    white-space: nowrap;
}

.game-button {
    font-family: inherit;
    font-size: 19px;
    letter-spacing: 2px;
    padding: 2px 16px;
    border: 0;
    border-radius: 8px;
    color: #10101a;
    cursor: pointer;
    white-space: nowrap;
    transition: filter .15s ease;
}
.game-button.race  { background: var(--hot); color: #fff; }
.game-button.reset { background: #3a3a46; color: #d8d8e2; }
.game-button:hover:not(:disabled) { filter: brightness(1.12); }
.game-button:disabled { opacity: .45; cursor: not-allowed; }

#status-text {
    flex: 1;
    font-size: 19px;
    color: #9a9aa8;
    letter-spacing: 1px;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.lab-link {
    font-size: 18px; letter-spacing: 2px;
    color: #7c7c8c; text-decoration: none;
    padding: 2px 10px;
    border: 2px solid #33333f; border-radius: 8px;
    white-space: nowrap;
}
.lab-link:hover { color: #fff; border-color: #55556a; }

.panel-fields {
    display: flex;
    flex-wrap: wrap;
    gap: 6px 14px;
    padding-top: 8px;
}

.field { display: flex; align-items: center; gap: 4px; }

.settings-label {
    font-size: 17px;
    color: #7c7c8c;
    letter-spacing: 1px;
    margin-right: 2px;
    white-space: nowrap;
}

.opt-btn {
    font-family: inherit;
    font-size: 17px;
    letter-spacing: 1px;
    padding: 1px 9px;
    border: 2px solid #33333f;
    border-radius: 7px;
    background: #23232c;
    color: #b6b6c4;
    cursor: pointer;
    white-space: nowrap;
}
.opt-btn:hover { border-color: #4a4a5a; color: #fff; }
.opt-btn.active { background: var(--hot); border-color: var(--hot); color: #fff; }

.pick {
    font-family: inherit;
    font-size: 17px;
    max-width: 150px;
    padding: 1px 6px;
    border: 2px solid #33333f;
    border-radius: 7px;
    background: #23232c;
    color: #e6e6f0;
    cursor: pointer;
}
.pick-narrow { max-width: 118px; }
.pick-color {
    width: 34px; height: 24px; padding: 0;
    border: 2px solid #33333f; border-radius: 7px;
    background: #23232c; cursor: pointer;
}
.pick-name {
    font-family: inherit; font-size: 17px;
    width: 104px; padding: 1px 6px;
    border: 2px solid #33333f; border-radius: 7px;
    background: #23232c; color: #e6e6f0;
}
.pick-name::placeholder { color: #5e5e70; }

.pick-range { width: 84px; accent-color: var(--hot); cursor: pointer; }

/* the HUD placement controls only mean anything in the bottom-track view */
body:not(.view-track) .field.track-only { display: none; }

/* ===================== Stage ===================== */

/* The stage always renders at its natural pixel size and is scaled as a whole,
   so one --zoom setting resizes the scene without re-deriving every offset.
   The wrapper reserves the scaled footprint so nothing below it is orphaned. */
#stage-wrap {
    --zoom: .72;
    width: calc(var(--stage-w) * var(--zoom));
    height: calc(var(--stage-h) * var(--zoom));
    max-width: 100%;
    /* drag offset, written by the pointer handlers and remembered per mode */
    transform: translate(var(--drag-x, 0px), var(--drag-y, 0px));
    cursor: grab;
    touch-action: none;
}
#stage-wrap.dragging { cursor: grabbing; }

/* Height is per mode: the pit layout is a tall two-tier composition, the track
   layout is a short banner meant to sit along the bottom of a stream. */
#stage {
    position: relative;
    width: var(--stage-w);
    height: var(--stage-h);
    border-radius: 14px;
    overflow: hidden;
    transform: scale(var(--zoom));
    transform-origin: top left;
}
/* The pit composition is a self-contained badge. The track one is a banner:
   full viewport width, parked on the bottom edge. Its stage is sized at
   100%/zoom so that scaling it back down lands exactly on the full width,
   which leaves --zoom controlling how big the cars and HUD are, not how wide
   the strip is. */
/* The pit stage carries a clear strip along its top for the session board, so
   the label never lands on a car or under a HUD badge.
   The frame is measured from the composition rather than guessed: the two dials
   and their bars span 464x494, and the box is sized to hold that with an equal
   margin on every side. At 568px tall the lower bar overhung the frame by three
   pixels and was sliced off at every scale, while 118px sat unused on the right
   - which is what made the scene read as shoved into a corner. */
#stage-wrap.mode-pit   { --stage-w: 720px; --stage-h: 600px; }
/* The banner is three bands: a little sky, the road, and a footer that carries
   every readout. The footer grows with the HUD scale so the strip is exactly as
   tall as its contents at any size. */
#stage-wrap.mode-track {
    --stage-w: calc(100% / var(--zoom));
    --footer-h: calc(118px * var(--hud-scale, .78));
    --stage-h: calc(222px + var(--footer-h));
    width: 100%;
    margin-top: auto;
}
/* with no dials down there the footer only has to hold the session label */
#stage-wrap.mode-track.hudpos-follow,
#stage-wrap.mode-track.hudpos-off { --footer-h: 54px; }
#stage-wrap.mode-track #stage { border-radius: 0; }
#stage.bg-none  { background: transparent; }
#stage.bg-dark  { background: #17171d; }
#stage.bg-light { background: #f2f2f2; }
/* The fourth backdrop: the venue's own landscape behind the cars, in the
   race's venue and sky (game.js syncScene, venue-scene.js). Dark underneath
   for the moment before the picture is drawn. */
#stage.bg-scene { background: #0b0716; }

.view { display: none; position: absolute; inset: 0; }
/* Nudged so the dials sit centred in what is left under the session strip:
   128px clear on the left and right, 19px under the lower bar. The view keeps
   its right edge at 0 and only moves its left one, so the speed streaks still
   sweep the full width of the frame behind the cars. */
#stage-wrap.mode-pit   #pit-view   { display: block; top: 78px; left: -10px; }
#stage-wrap.mode-track #track-view { display: block; }

/* ===================== Session board ===================== */
/* Circuit, sky and the perks the two of them apply. Deliberately text: it has
   to stay readable at every widget scale, over any backdrop, on a stream that
   may be re-encoded twice on its way to the viewer. */

#session-board {
    position: absolute;
    z-index: 8;
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 2px 12px 3px;
    background: var(--ink);
    border: 3px solid var(--stroke);
    border-radius: 9px;
    box-shadow: 0 6px 16px rgba(0, 0, 0, .45);
    white-space: nowrap;
    pointer-events: none;
}
/* In the pit layout the board is a corner plate, so it stacks: one strong line
   naming the session, one quiet line of perks. Cramming both into a single row
   next to a 24px track name is what made it read as noise. */
#stage-wrap.mode-pit #session-board {
    left: 14px; top: 10px;
    max-width: 460px;
    flex-direction: column;
    align-items: flex-start;
    gap: 2px;
    padding: 3px 12px 5px;
}
#stage-wrap.mode-pit .sb-sub { display: none; }

/* In the banner it sits in the footer row, between the two dials, and is laid
   out by the flex row rather than pinned to a corner. */
#stage-wrap.mode-track #session-board {
    position: relative;
    order: 2;
    flex: 0 1 auto;
    min-width: 0;
    box-shadow: none;
}

.sb-main { display: flex; align-items: baseline; gap: 7px; }
.sb-wx { font-size: 19px; line-height: 1; }
.sb-track { font-size: 24px; line-height: 1; letter-spacing: 2px; color: #fff; }
.sb-cond { font-size: 15px; letter-spacing: 2px; color: var(--hot); }
.sb-sub  { font-size: 15px; letter-spacing: 1px; color: #7c7c8c; }

.sb-perks { display: flex; gap: 5px; }
.perk {
    padding: 0 7px 1px;
    border-radius: 5px;
    font-size: 14px;
    line-height: 16px;
    letter-spacing: 1px;
    background: var(--ink-2);
    color: #b6b6c4;
}
/* the perk row is supporting detail, so in the stacked layout it is quieter
   than the line above it rather than another thing shouting */
#stage-wrap.mode-pit .sb-perks { opacity: .92; }
.perk.up   { background: #24391c; color: #a7dc63; }
.perk.down { background: #3a1c26; color: #ff8fa8; }

/* the sub-heading is the first thing to go when the banner gets narrow */
/* the flavour line is the first thing to go once the footer row gets tight */
@media (max-width: 1180px) { .sb-sub { display: none; } }

/* ----- the venue's picture ("Vice '91", 2026-10-01) -----
   game.js hands the board the venue's sunset and landmark (venue-art.js) as
   --sb-art and adds .has-art; without venue-art.js the board above is all
   there is. The picture is pinned to the right edge at the board's full height
   and keeps its own shape, so the landmark is never squashed whatever the
   layout does to the board's width - and the board makes room for it with
   padding rather than laying text over the skyline. The picture fades out
   towards its left end, so the words always sit on panel, not on sunset. */
/* `#stage` in front for weight: the pit layout sets the board's padding with
   two ids, and the room for the picture has to win over it */
#stage #session-board.has-art {
    --sb-panel: #150c2c;
    --sb-room: 116px;
    background: var(--sb-panel);
    padding-right: var(--sb-room);
}
#session-board.has-art::before {
    content: '';
    position: absolute;
    top: 0; right: 0; bottom: 0;
    aspect-ratio: 308 / 100;          /* venueArtUrl's crop of the plate */
    border-radius: 0 6px 6px 0;       /* the board's corner, inside its stroke */
    background: var(--sb-art) center / cover no-repeat;
    /* Faded out by a mask rather than painted over in the panel's colour: a
       wash laid on top left a hairline where the picture's edge fell between
       two pixels, which is exactly the size OBS scales the board to. */
    -webkit-mask-image: linear-gradient(90deg, transparent 0, transparent 10%, rgba(0, 0, 0, .45) 36%, #000 64%);
    mask-image: linear-gradient(90deg, transparent 0, transparent 10%, rgba(0, 0, 0, .45) 36%, #000 64%);
    pointer-events: none;
}
/* the plate's thin neon keyline, in the venue's colour */
#session-board.has-art::after {
    content: '';
    position: absolute;
    inset: 1px;
    border: 1px solid var(--venue-neon, #ff2d95);
    border-radius: 5px;
    opacity: .45;
    pointer-events: none;
}
/* positioned, so the words paint over the picture in plain document order */
#session-board.has-art > * { position: relative; }
/* A hard shadow under the words: the fade keeps them on panel, but the end
   of a long line can still reach the skyline's edge. */
#session-board.has-art .sb-track,
#session-board.has-art .sb-sub { text-shadow: 2px 2px 0 #000; }
#session-board.has-art .sb-cond { color: #ff4fae; text-shadow: 1px 1px 0 #000; }
#session-board.has-art .sb-sub { color: #b9acd6; }
/* The group race's board is one long row across the top, at a third of the
   pit board's height, so its picture is a postcard on the end. The flavour
   line gives way before anything runs off the stage. */
#stage-wrap.mode-field #session-board.has-art { --sb-room: 74px; max-width: calc(100% - 6px); }
#stage-wrap.mode-field #session-board.has-art .sb-sub { min-width: 0; overflow: hidden; text-overflow: ellipsis; }
#stage-wrap.mode-track #session-board.has-art { --sb-room: 78px; }
/* The banner's footer row is a fixed width and runs past both edges of a
   narrow source - the dials first. The picture is the one part of that row
   nobody needs, so it goes before the row gets any wider than it was. */
@media (max-width: 960px) {
    #stage-wrap.mode-track #session-board.has-art { --sb-room: 12px; }
    #stage-wrap.mode-track #session-board.has-art::before { display: none; }
}

/* ===================== Car rig ===================== */
/* Laid out in natural sprite pixels, scaled as one unit. */

.car-rig {
    position: absolute;
    transform-origin: top left;
    transform: scale(var(--car-scale));
    will-change: transform;
}

.car-body {
    position: absolute;
    left: 0; bottom: 0;
    transform-origin: bottom center;
}

.chassis { position: absolute; left: 0; top: 0; display: block; }

.wheel {
    position: absolute;
    width: 18px; height: 18px;
    animation: wheel-spin .3s linear infinite;
    will-change: transform;
    /* Standing still, the wheel is pixel-snapped like everything else - at the
       garage's 4x scale a blurred 72px disc beside a razor-sharp body was the
       worse of the two, which is why this was made crisp in the first place. */
}

/* Turning, it is the other way round: rotating an 18px disc through
   nearest-neighbour steps the tyre ring into a visible octagon, and there is no
   still frame to protect - the eye is tracking motion, not edges. So the
   smoothing comes back exactly while the wheel is spinning and goes again the
   moment it stops. `.rolling` is already set by both callers - the garage bay
   (opts.rolling) and the race itself, above 4 km/h - so this needs no new
   plumbing.

   The one-frame swap at the transition is invisible: it happens on a wheel that
   is either just starting to turn or has just stopped. */
.car-body.rolling .wheel { image-rendering: auto; }

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

/* suspension bounce - amplitude is sub-pixel at 1x, visible once scaled */
@keyframes body-bob {
    0%, 100% { transform: translateY(0) rotate(0deg); }
    25%      { transform: translateY(-.5px) rotate(-.25deg); }
    50%      { transform: translateY(0); }
    75%      { transform: translateY(-.35px) rotate(.2deg); }
}
.car-body.rolling { animation: body-bob .26s linear infinite; }

/* car shadow, drawn under the rig in natural px */
.car-shadow {
    position: absolute;
    left: 6px; height: 3px;
    border-radius: 50%;
    background: rgba(0, 0, 0, .34);
    filter: blur(1px);
}
#stage.bg-none .car-shadow { background: rgba(0, 0, 0, .22); }

/* ---- FX ---- */

.exhaust {
    position: absolute;
    width: 5px; height: 5px;
    border-radius: 50%;
    background: rgba(210, 210, 220, .5);
    pointer-events: none;
}
@keyframes puff {
    from { transform: translate(0, 0) scale(.5); opacity: .55; }
    to   { transform: translate(-26px, -9px) scale(2.6); opacity: 0; }
}

/* The cars face right, so the tailpipe is on the LEFT and the flame blows away
   from the car, leftwards. Every part of this was mirrored: the white-hot end
   sat at the tip instead of at the nozzle, the taper pointed into the bodywork,
   and the stretch grew in both directions at once - so half of each pulse went
   forwards, underneath the car. */
.nitro-flame {
    position: absolute;
    width: 10px; height: 4px;
    /* rounded at the tip, square where it leaves the pipe */
    border-radius: 60% 3px 3px 60%;
    /* coolest at the tip, white-hot at the nozzle */
    background: linear-gradient(90deg, #ff5a1f 0%, #ffd34d 60%, #fff 100%);
    /* pinned at the pipe, so a pulse only ever grows backwards out of it */
    transform-origin: right center;
    opacity: 0;
    pointer-events: none;
}
.car-rig.nitro .nitro-flame { animation: flame .09s steps(2) infinite; }
@keyframes flame {
    0%   { opacity: .95; transform: scaleX(1); }
    100% { opacity: .7;  transform: scaleX(1.5); }
}

.car-rig.spun .car-body { animation: wobble .3s ease-in-out infinite; }
@keyframes wobble {
    0%, 100% { transform: rotate(0deg)   translateY(0); }
    30%      { transform: rotate(-2.5deg) translateY(-1px); }
    70%      { transform: rotate(2deg)   translateY(0); }
}

/* ---- speed streaks (pit view) ---- */

.speed-lines { position: absolute; left: 0; right: 0; overflow: hidden; pointer-events: none; }
#lines-a { top: 132px; height: 130px; }
#lines-b { top: 248px; height: 150px; }
.streak {
    position: absolute;
    height: 2px;
    border-radius: 2px;
    background: linear-gradient(90deg, transparent, rgba(255, 255, 255, .85));
}
#stage.bg-light .streak { background: linear-gradient(90deg, transparent, rgba(20, 20, 30, .55)); }
@keyframes streak-run {
    from { transform: translateX(0);       opacity: 0; }
    12%  { opacity: .9; }
    to   { transform: translateX(-560px);  opacity: 0; }
}

/* ===================== HUD badge ===================== */

.hud {
    position: absolute;
    display: flex;
    align-items: center;
    filter: drop-shadow(0 6px 14px rgba(0, 0, 0, .45));
}

/* Everything right of the dial is one column, so the meters row and the plate
   share an edge by construction rather than by hand-tuned offsets. */
.hud-stack {
    display: flex;
    flex-direction: column;
    align-items: stretch;
    gap: 6px;
    margin-left: -2px;
}

.hud-top {
    display: flex;
    align-items: center;
    gap: 8px;
    height: 15px;
}

/* tick gauge */
.gauge { position: relative; width: 96px; height: 96px; flex: 0 0 96px; }

.gauge .g-tick {
    position: absolute;
    left: 50%; top: 50%;
    width: 4px; height: 9px;
    margin: -4.5px 0 0 -2px;
    border-radius: 1px;
    background: #33333d;
    transition: background-color .12s linear;
}
#stage.bg-light .gauge .g-tick { background: #c9c9d2; }
.gauge .g-tick.on { background: var(--hot); }
.gauge .g-tick.peak { background: #fff; }

.gauge-core {
    position: absolute;
    left: 50%; top: 50%;
    width: 54px; height: 54px;
    margin: -27px 0 0 -27px;
    border-radius: 50%;
    background: var(--accent);
    border: 3px solid var(--stroke);
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 34px;
    line-height: 1;
    /* Jersey 10 reserves descender space it never uses, which parks the
       numerals low in a flex-centred box - this puts them back on centre */
    padding-bottom: 5px;
    /* the dial core is painted in the team colour, so its numerals cannot be a
       fixed near-black - see setAccent() in cars-core.js */
    color: var(--on-accent, #16161c);
    letter-spacing: 1px;
}
.gauge-unit {
    position: absolute;
    left: 50%; bottom: 4px;
    transform: translateX(-50%);
    background: #fff;
    border: 2px solid var(--stroke);
    border-radius: 6px;
    padding: 0 7px;
    font-size: 17px;
    line-height: 1.15;
    color: #16161c;
    letter-spacing: 1px;
    z-index: 2;
}

/* readouts - fixed width so team A and team B badges match regardless of how
   long the driver name is */
.hud-plate {
    display: flex;
    align-items: stretch;
    width: 330px;
    height: 52px;
    background: var(--ink);
    border: 3px solid var(--stroke);
    border-radius: 10px;
    overflow: hidden;
}

/* the dark cell absorbs the slack - leftover space is invisible there, whereas
   slack inside the coloured team block reads as an empty patch */
.hud-cell {
    flex: 1;
    min-width: 0;
    padding: 0 0 0 16px;
    display: flex; flex-direction: column; justify-content: center;
}

.hud-value { font-size: 30px; line-height: 1; color: #fff; letter-spacing: 1px; }
.hud-label { font-size: 13px; line-height: 1; color: #8a8a99; letter-spacing: 2px; }

.hud-team {
    flex: 0 0 182px;
    display: flex; align-items: center; gap: 8px;
    padding: 0 10px 0 7px;
    border-left: 3px solid var(--stroke);
    background: var(--accent);
}
.hud-team .avatar {
    width: 34px; height: 34px;
    border: 2px solid var(--stroke);
    border-radius: 7px;
    background: #fff center/cover no-repeat;
    display: flex; align-items: center; justify-content: center;
    font-size: 22px; color: #16161c;
}
.hud-team .who { display: flex; flex-direction: column; }
.hud-team .who .name,
.hud-team .who .tag { white-space: nowrap; }
.hud-team .who .name {
    font-size: 21px; line-height: .95; color: var(--on-accent, #16161c); letter-spacing: 1px;
    max-width: 122px; overflow: hidden; text-overflow: ellipsis;
}
.hud-team .who .tag  { font-size: 13px; line-height: 1; color: var(--on-accent-dim, rgba(0, 0, 0, .55)); letter-spacing: 2px; }

/* The track of this bar is the race plan, painted segment by segment from
   game.js. The fill eats through it, so one bar answers both "how far along?"
   and "what has this car committed to next?". */
.hud-progress {
    flex: 1;
    min-width: 40px;
    height: 9px;
    background-color: var(--ink-2);
    background-repeat: no-repeat;
    border: 2px solid var(--stroke);
    border-radius: 5px;
    overflow: hidden;
}
.hud-progress i {
    display: block; height: 100%; width: 0;
    background: var(--hot);
    transition: width .16s linear;
}

/* ---- energy row: the plan, the tank, and what it is doing right now ---- */

.hud-nrg { display: flex; align-items: center; gap: 7px; height: 15px; }

.nrg-strat {
    flex: 0 0 auto;
    max-width: 112px;
    padding: 0 7px;
    border-radius: 5px;
    background: var(--ink);
    font-size: 14px; line-height: 16px;
    color: #c8c8d8; letter-spacing: 2px;
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}

.nrg-tank {
    flex: 1;
    min-width: 34px;
    height: 9px;
    background: var(--ink-2);
    border: 2px solid var(--stroke);
    border-radius: 5px;
    overflow: hidden;
}
.nrg-tank i {
    display: block; height: 100%; width: 100%;
    background: linear-gradient(90deg, #00d4b8, #6fd3ff);
    transition: width .1s linear;
}
/* the last fifth reads as a warning, because from there the plan is on borrowed
   time - the car drops to cruise the moment it hits zero */
.nrg-tank.low i { background: linear-gradient(90deg, #ff2d6f, #ff8f5a); }

.nrg-mode {
    flex: 0 0 auto;
    min-width: 64px;
    padding: 0 6px;
    border: 2px solid var(--stroke);
    border-radius: 5px;
    background: var(--mode, #6fd3ff);
    color: #16161c;
    font-size: 14px; line-height: 15px; letter-spacing: 2px;
    text-align: center;
}
.nrg-mode.dry { color: #fff; }
/* backed by its own pill: in the pit layout this label can end up over the
   other car's bodywork, and plain grey text on paint is unreadable */
.hud-meters {
    flex: 0 0 auto;
    padding: 0 6px;
    border-radius: 5px;
    background: var(--ink);
    font-size: 15px;
    line-height: 15px;
    color: #9a9aa8;
    letter-spacing: 1px;
}

/* draft / nitro state tags */
.hud-state {
    flex: 0 0 auto;
    display: flex; gap: 4px;
    align-items: center;
}
.state-tag {
    font-size: 14px;
    line-height: 15px;
    letter-spacing: 2px;
    padding: 0 6px;
    border-radius: 5px;
    border: 2px solid var(--stroke);
    background: #fff;
    color: #16161c;
    display: none;
}
.state-tag.show { display: block; }
.state-tag.show { opacity: 1; transform: translateY(0); }
.state-tag.nitro { background: #ffd34d; }
.state-tag.draft { background: #6fd3ff; }
.state-tag.spin  { background: #ff5a5a; color: #fff; }

/* ===================== Pit view layout ===================== */

#pit-view .racer-a { --accent: var(--team-a); }
#pit-view .racer-b { --accent: var(--team-b); }

/* Cars are anchored top-left and grow downward, so every offset below is the
   sprite's own top-left corner before scaling.
   Each team owns a lane and keeps it for the whole race. --car-dx is written
   every frame from the current gap, so the cars draw level and edge past each
   other the way they would on a real track. The HUDs never move: they are UI,
   and a badge that slides under the other car is unreadable. */
#pit-view .racer { position: absolute; top: 0; left: 0; }

#pit-view .car-rig {
    transform: translateX(var(--car-dx, 0px)) scale(var(--car-scale));
}

/* Far lane, upper - slightly smaller so the two lanes read as depth.
   The two base offsets differ by exactly the width gap between the scales, so
   with the race level both noses sit on the same vertical line and any visible
   difference really is one car being ahead. */
/* The far car and its HUD sit 28px lower than they first did: the gap down to
   the near car's roof read as two separate pictures (owner, 2026-10-01). They
   move together, so the far car keeps the same distance to its own HUD as the
   near one. */
#pit-view .racer-a { --car-scale: 2.35; }
#pit-view .racer-a .car-rig { left: 250px; top: 124px; z-index: 1; }
#pit-view .racer-a .hud     { left: 175px; top: 34px; }

/* near lane, lower - larger and drawn over the far car */
#pit-view .racer-b { --car-scale: 2.75; }
#pit-view .racer-b .car-rig { left: 205px; top: 236px; z-index: 2; }
#pit-view .racer-b .hud     { left: 135px; top: 404px; }

/* above every car, in the shared stacking context of the view */
#pit-view .hud { z-index: 5; }

/* ===================== Track view layout ===================== */

#track-view { display: none; }

#road {
    position: absolute;
    left: 0; right: 0;
    bottom: var(--footer-h, 0px);
    height: 210px;
}

#road-surface {
    position: absolute;
    left: 0; right: 0; bottom: 0;
    height: 130px;
    background: linear-gradient(#3a3a44 0%, #2b2b33 22%, #212129 100%);
    border-top: 3px solid #4d4d5a;
}
#stage.bg-none #road-surface {
    background: linear-gradient(rgba(40, 40, 50, .82), rgba(20, 20, 28, .88));
}
#stage.bg-light #road-surface {
    background: linear-gradient(#5a5a66 0%, #44444e 100%);
}

#road-dashes {
    position: absolute;
    left: -74px; bottom: 56px;
    width: 300%; height: 4px;
    background: repeating-linear-gradient(90deg,
        rgba(255, 255, 255, .55) 0 34px,
        transparent 34px 74px);
    will-change: transform;
}

#road-cars { position: absolute; inset: 0; }

#road-cars .racer-a { --accent: var(--team-a); }
#road-cars .racer-b { --accent: var(--team-b); }

#road-cars .racer { position: absolute; bottom: 0; }

/* Anchored bottom-left, then dropped by the sprite's empty skirt so the tyres
   sit exactly on the lane instead of hovering above it. */
#road-cars .car-rig {
    left: 0;
    top: auto;
    bottom: calc(-1 * var(--ground-drop, 0px) * var(--car-scale));
    transform-origin: bottom left;
}

/* The finish is paint on the tarmac, not a banner across the sky: it lives
   inside the road surface only, and the trapezoid gives it the same recession
   as the lanes - narrower at the far edge, wider at the near one. */
#finish-line {
    position: absolute;
    bottom: 0;
    width: 44px;
    height: 130px;            /* matches #road-surface */
    opacity: 0;
    transition: opacity .25s ease;
    pointer-events: none;
}
#finish-line.show { opacity: 1; }

/* One crisp painted line, nothing else. The trapezoid keeps it in the same
   perspective as the lane dashes: narrower at the far kerb, wider at the near. */
#finish-line .paint {
    position: absolute;
    top: 0; bottom: 0;
    left: 50%;
    width: 9px;
    margin-left: -4.5px;
    background: #eef0f2;
    clip-path: polygon(28% 0, 72% 0, 100% 100%, 0 100%);
    opacity: .92;
}

/* The footer: one centred row holding driver A, the session, driver B. Putting
   the label between the two dials rather than in a corner is what stops it
   competing with them - it is read as part of the same strip. */
#track-footer {
    position: absolute;
    left: 0; right: 0; bottom: 0;
    height: var(--footer-h);
    display: flex;
    align-items: center;
    justify-content: center;
}

#track-row {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 26px;
    /* scaled as one unit, so the gaps shrink with the dials instead of the row
       tearing itself apart at small sizes */
    transform: scale(var(--hud-scale, .78));
    transform-origin: center center;
}

/* pre-divided by the scale so that after scaling it lands on exactly 100% and
   space-between really does reach the edges of the banner */
/* Three children and space-between puts the label dead centre on its own, so
   "sides" only has to widen the row - no second positioning scheme. */
#stage-wrap.hudpos-sides #track-row {
    width: calc(100% / var(--hud-scale, .78));
    justify-content: space-between;
    padding: 0 46px;   /* keeps the dials off the very edge of the frame */
}
#stage-wrap.hudpos-follow #track-row .racer-a,
#stage-wrap.hudpos-follow #track-row .racer-b,
#stage-wrap.hudpos-off    #track-row .racer-a,
#stage-wrap.hudpos-off    #track-row .racer-b { display: none; }

/* "Follow": the badge rides above its own car and travels with it */
#road-cars .hud {
    position: absolute;
    left: 0;
    bottom: 128px;
    z-index: 6;
    transform: scale(var(--hud-scale, .78));
    transform-origin: bottom left;
}
#track-row .racer-a { --accent: var(--team-a); order: 1; }
#track-row .racer-b { --accent: var(--team-b); order: 3; }
#track-row .hud { position: relative; }

/* ===================== Countdown & winner ===================== */

#countdown {
    position: absolute;
    inset: 0;
    z-index: 20;              /* over the cars, which carry lane z-indexes */
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 150px;
    color: #fff;
    text-shadow: 0 6px 0 var(--hot), 0 12px 30px rgba(0, 0, 0, .6);
    pointer-events: none;
}
#countdown span { animation: count-pop .8s ease-out forwards; }
@keyframes count-pop {
    0%   { transform: scale(2.1); opacity: 0; }
    22%  { transform: scale(1);   opacity: 1; }
    75%  { transform: scale(1);   opacity: 1; }
    100% { transform: scale(.72); opacity: 0; }
}

/* Translucent on purpose: the result lands the instant the winner crosses, and
   the other car is still driving home underneath it. A solid panel hid that
   entirely and the finish read as a freeze-frame. */
#winner-banner {
    position: absolute;
    left: 50%; top: 50%;
    z-index: 20;
    max-width: 88%;
    transform: translate(-50%, -50%);
    padding: 12px 30px 10px;
    background: rgba(22, 22, 28, .82);
    backdrop-filter: blur(2px);
    border: 4px solid var(--stroke);
    border-radius: 14px;
    text-align: center;
    box-shadow: 0 14px 40px rgba(0, 0, 0, .6);
    animation: banner-in .35s cubic-bezier(.2, 1.4, .4, 1) both;
}
#winner-banner .crown { font-size: 30px; letter-spacing: 6px; color: var(--hot); }
#winner-banner .who   {
    font-size: 46px; line-height: 1; color: #fff; letter-spacing: 2px;
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
#winner-banner .car   { font-size: 22px; color: #8a8a99; letter-spacing: 2px; }

/* the purse: the number first, then why it is that number */
#winner-banner .purse {
    margin-top: 8px;
    padding-top: 7px;
    border-top: 2px solid #2c2c36;
}
#winner-banner .purse b {
    display: block;
    font-size: 30px; font-weight: normal; line-height: 1;
    color: #ffcc00; letter-spacing: 2px;
}
#winner-banner .purse span { font-size: 15px; color: #7c7c8c; letter-spacing: 2px; }
@keyframes banner-in {
    from { transform: translate(-50%, -50%) scale(.6); opacity: 0; }
    to   { transform: translate(-50%, -50%) scale(1);  opacity: 1; }
}

/* ===================== District caption ===================== */
/* The venue's name at the green light, bottom right, like a district sign
   (game.js, showVenueCaption). It sits in the clear ground the scene
   leaves on its right: in the pit layout that is above the lower car's dials
   and ahead of its nose, so neither readout is covered while the cars launch.
   Drawn over a game, a backdrop or nothing at all, so every line of it carries
   its own dark edge instead of relying on what is behind it. Only opacity and
   transform move - the source may be running on a weak PC. */
.venue-caption {
    /* how wide the clear ground is; game.js shrinks a name that would not fit */
    --cap-room: 166px;
    position: absolute;
    z-index: 7;               /* over the cars and dials, under the board and countdown */
    right: 10px; bottom: 124px;
    display: flex;
    flex-direction: column;
    align-items: flex-end;
    text-align: right;
    white-space: nowrap;
    opacity: 0;
    pointer-events: none;
    isolation: isolate;       /* keeps the group race's shade (below) under the words */
}
.venue-caption b {
    /* script swashes reach outside their box; the padding keeps them in it */
    padding: 0 6px 0 12px;
    font-family: 'Mr Dafoe', 'Segoe Script', cursive;
    font-weight: normal;
    font-size: 56px;
    line-height: .95;
    color: #ff4fae;
    -webkit-text-stroke: 1.5px #fff3fb;
    paint-order: stroke fill;
    text-shadow: 3px 3px 0 #000, 0 0 12px var(--venue-neon, #ff2d95);
}
/* the flavour line wraps rather than widen the caption into the cars */
.venue-caption span {
    max-width: var(--cap-room);
    margin: 1px 6px 0 0;
    white-space: normal;
    text-wrap: balance;
    font-size: 14px;
    line-height: 1.05;
    letter-spacing: 2px;
    color: #fff;
    text-shadow: 2px 2px 0 #000, -1px -1px 0 #000, 1px -1px 0 #000, -1px 1px 0 #000;
}
.venue-caption.show { animation: venue-caption 4s ease-out forwards; }
@keyframes venue-caption {
    0%   { opacity: 0; transform: translateY(8px); }
    10%  { opacity: 1; transform: none; }
    85%  { opacity: 1; transform: none; }
    100% { opacity: 0; transform: none; }
}
/* The group race keeps its standings down the right, and they are all level at
   the green light: the caption takes the foot of that column for its three
   seconds rather than sit on a car. The room is the row's 268px less the
   shade's 40px feather (below), so the shade never spills out of the row. */
#stage-wrap.mode-field .venue-caption { --cap-room: 220px; bottom: 14px; right: 14px; }
/* ...and over a standings row the script fought the row's numbers, so here it
   brings a shade of its own. Inside the caption, so it fades with it.
   A panel, not a round glow: a glow thinned out exactly where the sub-line's
   ends sat, and the row's bar and plan tag showed between its letters. Square
   to the row's own right and bottom edges, feathered only where it opens onto
   the row (left and top), so the rank and the name stay readable beside it. */
#stage-wrap.mode-field .venue-caption::before {
    content: '';
    position: absolute;
    inset: -12px 0 0 -40px;
    z-index: -1;
    border-radius: 0 6px 6px 0;
    background: linear-gradient(90deg, rgba(12, 7, 26, 0), rgba(12, 7, 26, .9) 44px);
    -webkit-mask-image: linear-gradient(transparent, #000 16px);
    mask-image: linear-gradient(transparent, #000 16px);
}
/* The banner's cars start on the left and its readouts live in the footer, so
   the caption goes at the far end of the road, above the footer. */
#stage-wrap.mode-track .venue-caption { --cap-room: 420px; bottom: calc(var(--footer-h) + 12px); right: 28px; }

@media (prefers-reduced-motion: reduce) {
    .venue-caption.show { animation-name: venue-caption-still; }
}
@keyframes venue-caption-still {
    0%   { opacity: 0; }
    8%   { opacity: 1; }
    88%  { opacity: 1; }
    100% { opacity: 0; }
}

/* ===================== Field view (group race) ===================== */
/* The same idea as the pit duel - the cars hold station and the world streaks
   past them - with room for four to six of them instead of two. Position is the
   horizontal offset inside the scene, so a group race needs one piece of road,
   not one per entrant.

   The standings live in a column beside the cars rather than on them: at four
   cars a 424px HUD each is most of the frame, and what a viewer wants from a
   group race is the order. */
#stage-wrap.mode-field {
    --lanes: 4;
    /* Type scale for the standings. Four rows have room for a readout you can
       take in from across a room; six have to give some of it back. Set from
       the entrant count in mountRacers() rather than guessed per breakpoint. */
    --fg-type: 1;
    --stage-w: 720px;
    --stage-h: 600px;
}
#stage-wrap.mode-field #field-view { display: block; }

.fg-scene {
    position: absolute;
    left: 0; top: 78px; right: 286px; bottom: 10px;
    overflow: hidden;
}
.fg-scene .speed-lines { top: 0; bottom: 0; }

/* one row per entrant, stacked top to bottom in starting order */
.fg-car {
    position: absolute;
    left: 0; right: 0;
    height: calc(100% / var(--lanes));
}
.fg-car .car-rig {
    position: absolute;
    left: 122px; bottom: 6px; top: auto;
    transform: translateX(var(--car-dx, 0px)) scale(var(--car-scale));
    transform-origin: bottom left;
}
/* the rank badge travels with the car, so the scene can be read without
   crossing to the standings column */
/* Above the cars: a car running well behind the pack slides left far enough to
   sit on its own badge otherwise.
   Sized like the standings number, not like a caption - it is the one thing in
   the scene a viewer has to be able to read at a glance and at stream bitrate. */
.fg-badge {
    position: absolute;
    z-index: 9;
    left: 8px; bottom: 8px;
    min-width: calc(48px * var(--fg-type));
    padding: 2px 9px 4px;
    text-align: center;
    font-size: calc(36px * var(--fg-type));
    line-height: 1;
    color: var(--on-accent, #16161c);
    background: var(--accent);
    border: 3px solid var(--stroke); border-radius: 10px;
    box-shadow: 0 3px 0 rgba(0, 0, 0, .5);
}

/* ===== standings ===== */

.fg-board {
    position: absolute;
    right: 10px; top: 78px; bottom: 10px;
    width: 268px;
    display: flex; flex-direction: column; gap: 5px;
}
.fg-row {
    flex: 1 1 0;
    min-height: 0;
    display: grid;
    grid-template-columns: calc(38px * var(--fg-type)) minmax(0, 1fr);
    grid-template-areas: "rank name" "rank nums" "bar bar";
    align-content: center;
    gap: 1px 8px;
    padding: 5px 9px 6px;
    background: rgba(0, 0, 0, .30);
    border: 2px solid var(--stroke);
    border-radius: 8px;
}
.fg-row.leading { border-color: var(--accent); background: rgba(0, 0, 0, .46); }
.fg-row.home { opacity: .74; }

/* The number is the whole point of a standings board, so it is the biggest
   thing on the row - not a caption in front of the name. */
.fg-rank {
    grid-area: rank;
    align-self: center;
    font-size: calc(40px * var(--fg-type));
    line-height: .9; color: #c8c8d8; text-align: center;
}
/* the colour as text this time, so it takes the lifted variant: a near-black
   team is as invisible here as white-on-white is above */
.fg-row.leading .fg-rank { color: var(--accent-lift, var(--accent)); }

.fg-name {
    grid-area: name;
    display: flex; align-items: baseline; gap: 6px;
    min-width: 0;
}
.fg-name b {
    flex: 1; min-width: 0;
    font-size: calc(22px * var(--fg-type));
    font-weight: normal; letter-spacing: 1px; color: #e8e8f2; line-height: 1.05;
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* Two numbers, both large: how far round, and how fast. The metres were a third
   number saying the same thing as the percentage in smaller type. */
.fg-nums { grid-area: nums; display: flex; align-items: baseline; gap: 9px; line-height: 1; }
.fg-nums .pct {
    font-size: calc(27px * var(--fg-type));
    color: #e8e8f2; letter-spacing: 1px;
}
.fg-kmh {
    font-size: calc(27px * var(--fg-type));
    color: #ffcc00; letter-spacing: 1px;
}
.fg-kmh::after {
    content: " km/h";
    font-size: calc(13px * var(--fg-type));
    color: #6f6f80; letter-spacing: 1px;
}
.fg-m { display: none; }
.fg-state { display: flex; align-items: center; gap: 3px; margin-left: auto; }
.fg-state .state-tag { position: static; font-size: calc(13px * var(--fg-type)); padding: 0 5px 1px; }

.fg-bars { grid-area: bar; display: flex; flex-direction: column; gap: 3px; margin-top: 5px; }
.fg-bar, .fg-tank {
    height: calc(8px * var(--fg-type));
    background-color: #12121a;
    background-repeat: no-repeat;
    border: 2px solid var(--stroke);
    border-radius: 4px;
    overflow: hidden;
}
.fg-bar i { display: block; height: 100%; width: 0; background: var(--accent); }

/* the plan's name beside its tank, so the row says which plan is being run as
   well as which segment of it is running right now */
.fg-nrg { display: flex; align-items: center; gap: 6px; }
.fg-plan {
    flex: 0 0 auto;
    max-width: 45%;
    padding: 0 6px 1px;
    border-radius: 4px;
    background: rgba(0, 0, 0, .45);
    font-size: calc(14px * var(--fg-type));
    color: #c8c8d8; letter-spacing: 2px;
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.fg-tank { flex: 1; min-width: 30px; height: calc(7px * var(--fg-type)); }
.fg-tank i {
    display: block; height: 100%; width: 100%;
    background: linear-gradient(90deg, #00d4b8, #6fd3ff);
}
.fg-tank.low i { background: linear-gradient(90deg, #ff2d6f, #ff8f5a); }

.fg-row .mode-tag { position: static; font-size: calc(15px * var(--fg-type)); padding: 0 6px 1px; }

/* A board of six cannot afford the duel's banner: at that size it sat on half
   of them. Here it is a plate, not a poster. */
#stage-wrap.mode-field #winner-banner { padding: 6px 20px 7px; max-width: 62%; }
#stage-wrap.mode-field #winner-banner .crown { font-size: 18px; letter-spacing: 4px; }
#stage-wrap.mode-field #winner-banner .who { font-size: 28px; }
#stage-wrap.mode-field #winner-banner .car { font-size: 16px; }
#stage-wrap.mode-field #winner-banner .purse b { font-size: 22px; }
#stage-wrap.mode-field #winner-banner .purse span { font-size: 12px; }

/* ===== the house card and the overlay notice =====
   Both of these draw on top of somebody's live broadcast, which is the whole
   design brief: fixed so they never move the game underneath, bottom and top
   edges so they never land on the cars, and short. */

.promo {
    position: fixed;
    left: 50%; bottom: 26px;
    transform: translate(-50%, 130%);
    z-index: 90;
    display: flex; align-items: center; gap: 16px;
    padding: 12px 22px 12px 14px;
    background: linear-gradient(180deg, #1e1e26 0%, #141419 100%);
    border: 3px solid #000;
    border-left: 6px solid #ff2d6f;
    border-radius: 14px;
    box-shadow: 0 24px 60px -24px #000, 0 0 0 1px rgba(255, 45, 111, .35);
    font-family: 'Jersey 10', 'Trebuchet MS', system-ui, sans-serif;
    color: #e8e8f2;
    opacity: 0;
    transition: transform .5s cubic-bezier(.16, .9, .3, 1), opacity .4s ease-out;
    pointer-events: none;
}
.promo.in { transform: translate(-50%, 0); opacity: 1; }

.promo-bay {
    flex: 0 0 auto;
    display: flex; align-items: flex-end; justify-content: center;
    min-width: 168px;
}
.promo-body { display: flex; flex-direction: column; line-height: 1.05; }
.promo-kicker { font-size: 16px; letter-spacing: 5px; color: #ff2d6f; }
.promo-title { font-size: 40px; letter-spacing: 2px; }
.promo-line { font-size: 21px; letter-spacing: 1px; color: #8a8a99; margin-top: 2px; }
.promo-site { font-size: 24px; letter-spacing: 3px; color: #ffcc00; margin-top: 4px; }

/* Whose question this was, tucked into the corner - it turns an advert into an
   answer to somebody in the room. */
.promo-by {
    position: absolute; top: -11px; right: 14px;
    padding: 0 10px 1px;
    font-size: 16px; letter-spacing: 2px;
    background: #ff2d6f; color: #fff;
    border: 3px solid #000; border-radius: 8px;
}

/* A strip along the top for everything the game says back, since chat cannot be
   replied to. */
.sr-notice {
    position: fixed;
    left: 50%; top: 18px;
    transform: translate(-50%, -150%);
    z-index: 90;
    display: flex; align-items: baseline; gap: 12px;
    padding: 6px 18px 7px;
    background: #1e1e26;
    border: 3px solid #000;
    border-bottom: 4px solid #ffcc00;
    border-radius: 12px;
    box-shadow: 0 18px 44px -20px #000;
    font-family: 'Jersey 10', 'Trebuchet MS', system-ui, sans-serif;
    opacity: 0;
    transition: transform .45s cubic-bezier(.16, .9, .3, 1), opacity .35s ease-out;
    pointer-events: none;
    max-width: min(92vw, 760px);
}
.sr-notice.in { transform: translate(-50%, 0); opacity: 1; }
.sr-notice b { font-size: 26px; font-weight: normal; letter-spacing: 2px; color: #ffcc00; white-space: nowrap; }
.sr-notice span { font-size: 21px; letter-spacing: 1px; color: #c8c8d8; }

/* In the strip layout the race IS the bottom of the screen, so a banner anchored
   there lands on top of the cars. The card goes above instead, and the notice
   steps down to make room for it. */
body.view-track .promo { bottom: auto; top: 18px; }
body:has(.promo.in) .sr-notice { top: 150px; }
body.view-track:has(.promo.in) .sr-notice { top: 150px; }

@media (prefers-reduced-motion: reduce) {
    .promo, .sr-notice { transition: opacity .3s ease-out; transform: translate(-50%, 0); }
}


/* ===================== The scene backdrop =====================
   Where the landscape's horizon, road and centre line fall in each layout, in
   stage pixels (venue-scene.css takes them as custom properties).

   Duel and cup (720 x 600): the far car's wheels are at about y 343 and the
   near car's at 479, so the skyline stands just above the far lane, the road
   runs from 298 down, and the centre line passes between the two cars. The sun
   goes to the right edge, clear of the upper HUD. */
#stage-wrap.mode-pit #stage .venue-scene {
    --vs-horizon: 330px;
    --vs-road: 302px;
    --vs-dash-top: 113px;
    --vs-sun: 190px;
    --vs-sun-right: 2%;
}
/* Group race: a lane per row from y 78 down, so the horizon sits high (y 106,
   just under the board), the road takes every row from the first one's roof, and the lines are drawn under each row (game.js
   sceneLanes) instead of down the middle. */
#stage-wrap.mode-field #stage .venue-scene {
    --vs-horizon: 494px;
    --vs-road: 474px;
    --vs-sun: 150px;
    --vs-sun-right: 42%;
}
#stage-wrap.mode-field #stage .venue-scene .vs-dashes:not(.vs-lane) { display: none; }

/* No text shadow on the HUD over the landscape: every word in it already
   sits on its own pill or plate. One was tried, and it reached the dark ink
   on the bright team plates and badges too - a black shadow under black
   letters reads as a blur (owner, 2026-10-01). */
/* The board's own little picture repeats the landscape right behind it, so
   on this backdrop the board is just its panel and its words. */
#stage.bg-scene #session-board.has-art { --sb-room: 12px; }
#stage.bg-scene #session-board.has-art::before { display: none; }
