/* Control-room design tokens. The overlay itself is transparent and themeless;
   these only drive the OBS control dock (and its picker modals on <body>). */
:root {
    --cr-radius: 8px;
    --cr-tile: #222;
    --cr-tile-hover: #2e2e2e;
    --cr-border: #2a2a2a;
    --cr-border-hover: #555;
    --cr-muted: #aaa;
}

html, body {
    margin: 0;
    height: 100%;
    background: transparent;
    overflow: hidden;
    font-family: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
}

/* ============== Resolution independence ==============
   The overlay is designed against a 1920-wide canvas, but OBS may render the
   browser source at a larger resolution (e.g. 2560x1440) and downscale the whole
   output to the stream's 1080p. Absolute px don't track the viewport, so under
   that render+downscale they'd end up smaller than a native 1920 render, while
   vw/vh stay correct — leaving the foreground (px) mismatched against the scene
   (vw/vh). To keep both consistent, the overlay's foreground sizes are expressed
   in "reference pixels": `calc(N * var(--u))` equals N px at 1920 wide and scales
   linearly with the canvas width, exactly like the vw units it sits alongside. So
   rendering at any width then downscaling looks identical to a native 1920 render.
   (The control room below keeps plain px — it's a free-sized dock, not the scene.) */
:root {
    --u: calc(100vw / 1920);
    --score-w: calc(140 * var(--u)); /* open width of the in-banner score plate (shared with overlay.js) */
}

/* The team banners set display:flex, which would otherwise beat the [hidden]
   user-agent rule, so enforce hiding for the attribute we toggle from JS. */
[hidden] {
    display: none !important;
}

.team {
    --slant: calc(28 * var(--u));
    position: fixed;
    top: calc(12 * var(--u));
    height: calc(52 * var(--u));
    width: 33vw;
    box-sizing: border-box;
    display: flex;
    align-items: center;
    gap: calc(16 * var(--u));
    background: #1a1a1a; /* neutral fallback until the logo color loads */
    color: #fff;
}

/* Flush to the outer screen edge; the inner edge is cut on a slant that leans
   inward at the top (the inner-top corner is the furthest-in point and the
   bottom is pulled back). --slant is the horizontal run of that diagonal. */
.team-left {
    left: 0;
    flex-direction: row;
    padding: 0 0 0 calc(24 * var(--u));
    clip-path: polygon(0 0, 100% 0, calc(100% - var(--slant)) 100%, 0 100%);
}

.team-right {
    right: 0;
    flex-direction: row-reverse;
    padding: 0 calc(24 * var(--u)) 0 0;
    clip-path: polygon(0 0, 100% 0, 100% 100%, var(--slant) 100%);
}

/* Box that reserves the logo's slot and centres its content (an <img>, or a text
   monogram for teams with no logo). */
.team-logo {
    width: calc(48 * var(--u));
    height: calc(48 * var(--u));
    flex-shrink: 0;
    display: flex;
    align-items: center;
    justify-content: center;
}

.team-logo-img {
    width: 100%;
    height: 100%;
    object-fit: contain;
    /* drop-shadow follows the logo's alpha shape, so transparent PNGs cast a
       soft shadow off their actual silhouette rather than a square box. */
    filter: drop-shadow(0 0px 5px rgba(0, 0, 0, .75));
}

.team-logo-text {
    font-weight: 800;
    font-size: calc(22.4 * var(--u)); /* 1.4rem */
    line-height: 1;
    letter-spacing: .02em;
    text-shadow: 0 1px 3px rgba(0, 0, 0, .6);
}

.team-name {
    font-weight: 600;
    font-size: calc(24 * var(--u)); /* 1.5rem */
    white-space: nowrap;
    text-shadow: 0 1px 2px rgba(0, 0, 0, .5);
}

/* Outer wrapper: the flex item that reserves the plate's layout width and clips the
   reveal. Collapsing its max-width to 0 (big view) wipes the plate horizontally; the
   inner plate keeps its full width so the slant never collapses or inverts. */
.team-score {
    align-self: stretch;
    display: flex;
    justify-content: flex-end; /* pin the plate to the inner edge as it reveals */
    min-width: 0; /* override flex min-width:auto so max-width:0 can truly collapse it */
    max-width: var(--score-w);
    overflow: hidden;
    cursor: pointer;
    user-select: none;
}

.team-score-plate {
    flex-shrink: 0; /* keep full width and get clipped, rather than squeezed, by the wrapper */
    align-self: stretch;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 0 calc(36 * var(--u));
    min-width: 1.2em;
    background: rgba(0, 0, 0, .2); /* slightly darken the team color under the score */
    font-weight: 800;
    font-size: calc(36 * var(--u)); /* 2.25rem */
    line-height: 1;
    text-align: center;
    white-space: nowrap;
    text-shadow: 0 1px 2px rgba(0, 0, 0, .5);
}

/* Push the score plate to the inner end of the banner, leaving the logo and
   name grouped at the outer edge. Both plate edges are slanted parallel to the
   banner's inner edge (same --slant over the same height). */
.team-left .team-score {
    margin-left: auto;
}

.team-left .team-score-plate {
    clip-path: polygon(var(--slant) 0, 100% 0, calc(100% - var(--slant)) 100%, 0 100%);
}

.team-right .team-score {
    margin-right: auto;
}

.team-right .team-score-plate {
    clip-path: polygon(0 0, calc(100% - var(--slant)) 0, 100% 100%, var(--slant) 100%);
}

.overlay-prompt {
    position: fixed;
    inset: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: calc(24 * var(--u));
    color: #fff;
    font-size: calc(20 * var(--u)); /* 1.25rem */
    text-align: center;
    text-shadow: 0 1px 3px rgba(0, 0, 0, .6);
}

.overlay-prompt a {
    color: #4ea1ff;
}

.overlay-error {
    position: fixed;
    top: calc(24 * var(--u));
    left: calc(24 * var(--u));
    color: #fff;
    background: rgba(0, 0, 0, .6);
    padding: calc(8 * var(--u)) calc(12 * var(--u));
    border-radius: var(--cr-radius);
    font-size: calc(14 * var(--u));
}

/* ============== Hero ban badges ============== */
/* Anchored just past each banner's inner (center-facing) edge. The left banner's
   inner edge sits at 33vw; the right banner's at 33vw from the right. */
.ban-slot {
    position: fixed;
    top: calc(12 * var(--u));
    height: calc(52 * var(--u));
    display: flex;
    align-items: center;
}

.ban-slot-left {
    left: calc(33vw + 6 * var(--u));
}

.ban-slot-right {
    right: calc(33vw + 6 * var(--u));
}

.ban-badge {
    position: relative;
    width: calc(48 * var(--u));
    height: calc(48 * var(--u));
    border-radius: calc(6 * var(--u));
    overflow: hidden;
    box-shadow: 0 1px 4px rgba(0, 0, 0, .5);
}

.ban-icon {
    width: 100%;
    height: 100%;
    object-fit: cover;
    display: block;
    filter: grayscale(.3) brightness(.85);
}

/* The badge plays a 3-part intro, but only while it carries the `ban-animate`
   class — added in overlay.js when (and only when) that team's ban actually
   changes, then dropped once it finishes. Keeping the masks/animations off the
   base rules means the static badge stays fully drawn, so unrelated repaints
   (score changes, the other team's ban) never restart it. The portrait fades
   in, the red outline draws on clockwise (conic mask on ::before), then the
   diagonal strike wipes in from the top-right to the bottom-left (mask on
   ::after). The masked custom properties are registered so they can animate. */
@property --ban-ring {
    syntax: '<angle>';
    inherits: false;
    initial-value: 0turn;
}

@property --ban-strike {
    syntax: '<percentage>';
    inherits: false;
    initial-value: 0%;
}

/* Red outline ring (statically full; the intro reveals it clockwise). */
.ban-badge::before {
    content: '';
    position: absolute;
    inset: 0;
    z-index: 1; /* sit in front of the icon (which builds its own context via filter) */
    box-sizing: border-box;
    border: calc(2 * var(--u)) solid rgba(220, 40, 40, .95);
    border-radius: calc(6 * var(--u));
}

/* Diagonal red strike across the portrait to read unmistakably as "banned"
   (statically full; the intro wipes it in top-right to bottom-left). */
.ban-badge::after {
    content: '';
    position: absolute;
    inset: 0;
    z-index: 2; /* above both the icon and the outline ring */
    background: linear-gradient(135deg,
    transparent calc(50% - 1.5px),
    rgba(220, 40, 40, .95) calc(50% - 1.5px),
    rgba(220, 40, 40, .95) calc(50% + 1.5px),
    transparent calc(50% + 1.5px));
}

/* Intro animations — applied only while the badge carries `ban-animate`. */
.ban-badge.ban-animate .ban-icon {
    animation: ban-icon-in .4s ease both;
}

.ban-badge.ban-animate::before {
    -webkit-mask: conic-gradient(#000 var(--ban-ring), #0000 var(--ban-ring));
    mask: conic-gradient(#000 var(--ban-ring), #0000 var(--ban-ring));
    animation: ban-ring .55s ease-out .15s both;
}

.ban-badge.ban-animate::after {
    -webkit-mask: linear-gradient(225deg, #000 var(--ban-strike), #0000 var(--ban-strike));
    mask: linear-gradient(225deg, #000 var(--ban-strike), #0000 var(--ban-strike));
    animation: ban-strike .3s ease-out .8s both;
}

@keyframes ban-icon-in {
    from {
        opacity: 0;
    }
    to {
        opacity: 1;
    }
}

@keyframes ban-ring {
    from {
        --ban-ring: 0turn;
    }
    to {
        --ban-ring: 1turn;
    }
}

@keyframes ban-strike {
    from {
        --ban-strike: 0%;
    }
    to {
        --ban-strike: 100%;
    }
}

/* ============== Powered-by badge ============== */
.powered {
    position: fixed;
    bottom: calc(16 * var(--u));
    display: flex;
    flex-direction: column;
    gap: calc(4 * var(--u));
    color: #fff;
    font-size: calc(13.6 * var(--u)); /* .85rem */
    font-weight: 600;
    text-shadow: 0 1px 2px rgba(0, 0, 0, .6);
    pointer-events: none;
}

.powered-left {
    left: calc(16 * var(--u));
    align-items: flex-start;
}

.powered-right {
    right: calc(16 * var(--u));
    align-items: flex-end;
}

.powered-logo {
    height: calc(28 * var(--u));
    width: auto;
    display: block;
}

/* The "Powered by" badge grows in big view to match the larger banners/score. */
body.scene-big .powered {
    font-size: calc(21.6 * var(--u)); /* 1.35rem */
}

body.scene-big .powered-logo {
    height: calc(56 * var(--u));
}

/* ============== Big view (full-background scene) ==============
   A presentation scene the control room toggles via SCENE_KEY. The overlay keeps
   its normal banner styling (size + slant); entering big view slides the banners
   out to their sides and back in to a staggered layout — team A higher, team B
   lower, each on its normal side — with the in-banner scores hidden and a single
   large score shown between them over a gradient that rises from the bottom.

   Choreography (see paintScene in overlay.js): `scene-exit` shoves the banners
   off their sides, then one step later `scene-big` sets the target layout and
   scene-exit is dropped so they slide back in. Transitions only apply once
   `anim-ready` is set (one frame after load) so reloading straight into big view
   snaps into place rather than replaying the move. */
.scene-bg {
    position: fixed;
    inset: 0;
    z-index: -1;
    overflow: hidden;
    transform: translateY(100%); /* parked below the viewport; rises in big view */
    /* Gradient runs from team A's banner colour (top-left) to team B's
       (bottom-right); the vars are set per-team in paint() (overlay.js). The
       dark fallbacks apply until the logo colours have been sampled. A vignette
       is layered on top to darken the corners and draw the eye to the centre
       scoreboard. */
    background: radial-gradient(ellipse 75% 75% at 50% 50%,
    transparent 40%, rgba(0, 0, 0, .5) 100%),
    linear-gradient(135deg,
            var(--scene-color-0, #0b1733) 0%,
            var(--scene-color-1, #2a0b1f) 100%);
    pointer-events: none;
}

/* Fine monochrome film grain (inline SVG fractalNoise, desaturated) tiled over
   the whole scene. `overlay` blending dithers the smooth gradient so the wide
   colour ramp no longer shows visible banding on stream. */
.scene-bg::after {
    content: '';
    position: absolute;
    inset: 0;
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='200' height='200'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.8' numOctaves='2' stitchTiles='stitch'/%3E%3CfeColorMatrix type='saturate' values='0'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
    opacity: .07;
    mix-blend-mode: overlay;
    pointer-events: none;
}

/* Foreground decoration (snakes, floating shapes, fireworks) layered over the
   gradient. On the maps screen this layer slides right — the *elements* shift while
   the gradient stays full-screen — in step with team B's banner moving left
   (the scene-fx-right class is toggled in overlay.js at that exact moment). */
.scene-fx {
    position: absolute;
    inset: 0;
}

body.scene-fx-right .scene-fx {
    transform: translateX(-14vw);
}

/* Soft white bloom behind the centre scoreboard. Sits above the background but
   below the shapes/score; lifts the middle of the scene and reinforces the
   vignette's pull toward the centre. Hidden until big view. */
.scene-glow {
    position: fixed;
    top: 50%;
    left: 50%;
    z-index: -1;
    width: 70vmax;
    height: 70vmax;
    transform: translate(-50%, -50%);
    background: radial-gradient(circle at 50% 50%,
    rgba(255, 255, 255, .12) 0%, transparent 60%);
    opacity: 0;
    pointer-events: none;
    animation: scene-glow-breathe 8s ease-in-out infinite;
}

/* Gentle pulse so the centre bloom breathes rather than sitting static. Only the
   scale animates here; opacity stays under the big-view fade transition. */
@keyframes scene-glow-breathe {
    0%, 100% {
        transform: translate(-50%, -50%) scale(1);
    }
    50% {
        transform: translate(-50%, -50%) scale(1.12);
    }
}

/* Occasional snaking accent: a fixed diagonal sine wave (the path geometry stays
   put — "waves staying in place") with a single bright dash that travels along
   it like a snake, then a long quiet gap before the next pass. The static rotate
   lives on the wrapper while only stroke-dashoffset/opacity animate on the path,
   keeping the glow off any animated-transform layer. */
.scene-snake {
    position: absolute;
    top: 50%;
    left: 50%;
    width: 150vw;
    height: 26vh;
    overflow: visible;
    pointer-events: none;
}

/* Two snakes on opposite diagonals, each tinted with one team's banner colour
   and offset in height/timing so their passes don't coincide. */
.scene-snake-a {
    transform: translate(-50%, -62%) rotate(20deg);
}

.scene-snake-b {
    transform: translate(-50%, -38%) rotate(-18deg);
}

.scene-snake path {
    fill: none;
    stroke-width: 3;
    stroke-linecap: round;
    /* The path holds 20 full waves; a dash covering half the normalised 1000-unit
       length shows 10 of them at once. The gap is longer than the path so exactly
       one snake is ever on screen. */
    stroke-dasharray: 500 1500;
    opacity: 0;
    animation: scene-snake 22s linear infinite;
}

.scene-snake-a path {
    stroke: var(--scene-color-0, #4ea1ff);
    filter: drop-shadow(0 0 6px var(--scene-color-0, #4ea1ff));
}

.scene-snake-b path {
    stroke: var(--scene-color-1, #b14eff);
    filter: drop-shadow(0 0 6px var(--scene-color-1, #b14eff));
    animation-delay: -11s;
}

@keyframes scene-snake {
    0% {
        stroke-dashoffset: 500;
        opacity: 0;
    }
    5% {
        opacity: .85;
    }
    27% {
        opacity: .85;
    }
    32% {
        stroke-dashoffset: -1000;
        opacity: 0;
    }
    100% {
        stroke-dashoffset: -1000;
        opacity: 0;
    }
}

/* Floating accent shapes drifting over the gradient: stroked-only triangles and
   pentagons (SVG polygons, fill none) tinted with each team's banner colour
   (shape-a = team A / --scene-color-0, shape-b = team B) and given a soft glow.
   The animated transform lives on the outer span while the glow filter sits on
   the inner svg: keeping the filter off the promoted (animated) layer avoids a
   Chromium repaint bug where the drop-shadow vanishes once the background
   transition settles. */
.scene-shape {
    position: absolute;
    width: 12vmax;
    height: 12vmax;
    opacity: .55;
    will-change: transform;
    /* Random per-shape starting orientation (set in overlay.js). This individual
       `rotate` property composes with the animated `transform` drift below, so
       each shape begins at a different angle and keeps spinning from there. */
    rotate: var(--shape-rot, 0deg);
}

.scene-shape svg {
    display: block;
    width: 100%;
    height: 100%;
    overflow: visible; /* let the glow spill past the viewBox */
    fill: none;
    stroke-linejoin: round;
}

/* Two parallax layers build depth: the `far` layer is small, faint, thin and
   blurred and drifts slowly; the `near` layer is large, brighter, sharper and
   moves further. DOM order (far first) keeps the near shapes painting on top. */
.scene-shape-far {
    opacity: .22;
}

.scene-shape-near {
    opacity: .6;
}

.scene-shape.shape-a svg {
    stroke: var(--scene-color-0, #4ea1ff);
}

.scene-shape.shape-b svg {
    stroke: var(--scene-color-1, #b14eff);
}

.scene-shape-far.shape-a svg {
    stroke-width: 1.6;
    filter: drop-shadow(0 0 2px var(--scene-color-0, #4ea1ff)) blur(1.4px);
}

.scene-shape-far.shape-b svg {
    stroke-width: 1.6;
    filter: drop-shadow(0 0 2px var(--scene-color-1, #b14eff)) blur(1.4px);
}

.scene-shape-near.shape-a svg {
    stroke-width: 2.8;
    filter: drop-shadow(0 0 4px var(--scene-color-0, #4ea1ff));
}

.scene-shape-near.shape-b svg {
    stroke-width: 2.8;
    filter: drop-shadow(0 0 4px var(--scene-color-1, #b14eff));
}

/* Firework bursts: a flash + expanding ring + radiating sparks that pop briefly
   then stay quiet for the rest of the cycle, so they read as occasional. Built in
   overlay.js (buildBooms) with random positions and a per-burst negative delay
   (--boom-delay) that desyncs them; boom-a/boom-b tint with the two team colours
   (--boom-color). Living inside #scene-bg, they only show once it rises in big
   view. The flash, ring and sparks share --boom-delay so each burst fires as one. */
.scene-boom {
    position: absolute;
    width: 0;
    height: 0;
    pointer-events: none;
}

.boom-a {
    --boom-color: var(--scene-color-0, #4ea1ff);
}

.boom-b {
    --boom-color: var(--scene-color-1, #b14eff);
}

.boom-flash,
.boom-ring,
.boom-spark {
    position: absolute;
    left: 0;
    top: 0;
    opacity: 0;
    animation-duration: 9s;
    animation-timing-function: ease-out;
    animation-iteration-count: infinite;
    animation-delay: var(--boom-delay, 0s);
}

.boom-flash {
    width: 6vmax;
    height: 6vmax;
    margin: -3vmax 0 0 -3vmax;
    border-radius: 50%;
    background: radial-gradient(circle,
    rgba(255, 255, 255, .95) 0%, var(--boom-color) 35%, transparent 70%);
    animation-name: boom-flash;
}

.boom-ring {
    width: 4vmax;
    height: 4vmax;
    margin: -2vmax 0 0 -2vmax;
    border-radius: 50%;
    border: .3vmax solid var(--boom-color);
    animation-name: boom-ring;
}

/* Each spark is a thin streak rotated to its own angle (--a) and flung outward. */
.boom-spark {
    width: .4vmax;
    height: 2vmax;
    margin: -1vmax 0 0 -.2vmax;
    border-radius: 999px;
    background: linear-gradient(var(--boom-color), transparent);
    animation-name: boom-spark;
}

@keyframes boom-flash {
    0% {
        opacity: 0;
        transform: scale(.2);
    }
    3% {
        opacity: .9;
    }
    12% {
        opacity: 0;
        transform: scale(1);
    }
    100% {
        opacity: 0;
        transform: scale(1);
    }
}

@keyframes boom-ring {
    0% {
        opacity: 0;
        transform: scale(.2);
    }
    3% {
        opacity: .85;
    }
    18% {
        opacity: 0;
        transform: scale(2.6);
    }
    100% {
        opacity: 0;
        transform: scale(2.6);
    }
}

@keyframes boom-spark {
    0% {
        opacity: 0;
        transform: rotate(var(--a)) translateY(0) scaleY(.4);
    }
    4% {
        opacity: 1;
    }
    20% {
        opacity: 0;
        transform: rotate(var(--a)) translateY(-7vmax) scaleY(1);
    }
    100% {
        opacity: 0;
        transform: rotate(var(--a)) translateY(-7vmax) scaleY(1);
    }
}

.shape-1 {
    top: 8%;
    left: 6%;
    width: 14vmax;
    height: 14vmax;
    animation: scene-shape-1 18s ease-in-out -5s infinite;
}

.shape-2 {
    top: 14%;
    right: 10%;
    width: 17vmax;
    height: 17vmax;
    animation: scene-shape-2 19s ease-in-out -12s infinite;
}

.shape-3 {
    bottom: 6%;
    left: 14%;
    width: 6vmax;
    height: 6vmax;
    animation: scene-shape-3 36s ease-in-out -20s infinite;
}

.shape-4 {
    bottom: 10%;
    right: 8%;
    width: 15vmax;
    height: 15vmax;
    animation: scene-shape-4 17s ease-in-out -8s infinite;
}

.shape-5 {
    top: 42%;
    left: 40%;
    width: 6.5vmax;
    height: 6.5vmax;
    animation: scene-shape-5 38s ease-in-out -25s infinite;
}

.shape-6 {
    top: 55%;
    right: 30%;
    width: 13vmax;
    height: 13vmax;
    animation: scene-shape-6 20s ease-in-out -3s infinite;
}

.shape-7 {
    top: 30%;
    left: 22%;
    width: 5.5vmax;
    height: 5.5vmax;
    animation: scene-shape-7 34s ease-in-out -15s infinite;
}

.shape-8 {
    top: 4%;
    left: 46%;
    width: 7vmax;
    height: 7vmax;
    animation: scene-shape-8 32s ease-in-out -18s infinite;
}

.shape-9 {
    bottom: 22%;
    right: 18%;
    width: 6vmax;
    height: 6vmax;
    animation: scene-shape-9 40s ease-in-out -30s infinite;
}

.shape-10 {
    bottom: 30%;
    left: 6%;
    width: 16vmax;
    height: 16vmax;
    animation: scene-shape-10 16s ease-in-out -9s infinite;
}

@keyframes scene-shape-1 {
    0%, 100% {
        transform: translate(0, 0) rotate(0deg);
    }
    50% {
        transform: translate(9vw, 12vh) rotate(120deg);
    }
}

@keyframes scene-shape-2 {
    0%, 100% {
        transform: translate(0, 0) rotate(0deg);
    }
    50% {
        transform: translate(-10vw, 9vh) rotate(-90deg);
    }
}

@keyframes scene-shape-3 {
    0%, 100% {
        transform: translate(0, 0) rotate(0deg);
    }
    50% {
        transform: translate(2.5vw, -3vh) rotate(150deg);
    }
}

@keyframes scene-shape-4 {
    0%, 100% {
        transform: translate(0, 0) rotate(0deg);
    }
    50% {
        transform: translate(-9vw, -8vh) rotate(-120deg);
    }
}

@keyframes scene-shape-5 {
    0%, 100% {
        transform: translate(0, 0) rotate(0deg) scale(1);
    }
    50% {
        transform: translate(3vw, 2vh) rotate(180deg) scale(1.2);
    }
}

@keyframes scene-shape-6 {
    0%, 100% {
        transform: translate(0, 0) rotate(0deg);
    }
    50% {
        transform: translate(-8vw, -11vh) rotate(100deg);
    }
}

@keyframes scene-shape-7 {
    0%, 100% {
        transform: translate(0, 0) rotate(0deg);
    }
    50% {
        transform: translate(3vw, -2.5vh) rotate(-140deg);
    }
}

@keyframes scene-shape-8 {
    0%, 100% {
        transform: translate(0, 0) rotate(0deg) scale(1);
    }
    50% {
        transform: translate(-3vw, 2.5vh) rotate(160deg) scale(1.15);
    }
}

@keyframes scene-shape-9 {
    0%, 100% {
        transform: translate(0, 0) rotate(0deg);
    }
    50% {
        transform: translate(-2.5vw, 2.5vh) rotate(130deg);
    }
}

@keyframes scene-shape-10 {
    0%, 100% {
        transform: translate(0, 0) rotate(0deg);
    }
    50% {
        transform: translate(9vw, -8vh) rotate(-110deg);
    }
}

/* Central scoreboard, shown only once the banners have settled in big view.
   Centred at 50vh; the banners are placed symmetrically above/below it (see below)
   so the score is an equal distance from each. */
.scene-score {
    position: fixed;
    top: 50%;
    left: 50%;
    z-index: 2;
    transform: translate(-50%, -50%) scale(.85);
    display: flex;
    align-items: center;
    gap: calc(24 * var(--u));
    opacity: 0;
    color: #fff;
    font-weight: 800;
    font-size: calc(96 * var(--u)); /* 6rem */
    line-height: 1;
    text-shadow: 0 2px 10px rgba(0, 0, 0, .6);
    pointer-events: none;
}

.scene-score-sep {
    opacity: .5;
    font-size: calc(64 * var(--u)); /* 4rem */
}

/* --- Big view target layout --- */
body.scene-big .scene-bg,
body.scene-maps .scene-bg {
    transform: translateY(0);
}

body.scene-big .scene-score {
    opacity: 1;
    transform: translate(-50%, -50%) scale(1);
}

body.scene-big .scene-glow {
    opacity: 1;
}

/* Bigger banners in big view. Like `top`, these size changes aren't transitioned,
   so they apply instantly while the banner is off-screen (at the scene-big step). */
body.scene-big .team {
    height: calc(112 * var(--u));
    width: 38vw;
}

body.scene-big .team-logo {
    width: calc(88 * var(--u));
    height: calc(88 * var(--u));
}

body.scene-big .team-logo-text {
    font-size: calc(41.6 * var(--u)); /* 2.6rem */
}

body.scene-big .team-name {
    font-size: calc(41.6 * var(--u)); /* 2.6rem */
}

/* Team A (left) higher, team B (right) lower, placed symmetrically about the
   vertical middle (centres at 35vh / 65vh, offset by half the 112px height) so
   the score sits an equal distance from each. Outer edges stay flush to the
   screen edges (left/right 0, inherited); the banners are widened (38vw) so their
   inner edges sit closer to the centre. justify-content + inner padding pull the
   logo and name to the banner's inner, centre-facing edge (clearing the slant). */
body.scene-big .team-left {
    top: calc(35vh - 56 * var(--u));
    justify-content: flex-end;
    padding-right: calc(44 * var(--u));
}

/* Left-anchored (not right:0) so team B can glide horizontally to the maps screen's
   left:0 column; 38vw from the right edge keeps it flush right in big view. */
body.scene-big .team-right {
    top: calc(65vh - 56 * var(--u));
    left: calc(100% - 38vw);
    right: auto;
    justify-content: flex-end;
    padding-left: calc(44 * var(--u));
}

/* The score plate is absent off-screen mid-slide, and in big view (which has its own
   central scoreboard). Keeping it out of the flex flow here means it never disturbs
   the banner's logo/name layout and has no resting side to pop from on the way back. */
body.scene-exit .team-score,
body.scene-big .team-score {
    display: none;
}

/* While wiping closed on the way to big view the plate is briefly shown again
   (collapseScores). Keep team B on the maps (left) slant here so its tilt doesn't
   mirror-flip against the base right-slant the instant the wipe starts. */
body.scene-big .team-right .team-score-plate {
    clip-path: polygon(var(--slant) 0, 100% 0, calc(100% - var(--slant)) 100%, 0 100%);
}

/* Hero-ban badges anchor near the banners' normal spot. Hide them during the
   exit step (banners off-screen), then in big view reveal them grouped at the
   top centre: team A's just left of centre, team B's just right, sitting side by
   side near the top edge. The position isn't transitioned, so it snaps into
   place while the badge is faded out. */
body.scene-exit .ban-slot {
    opacity: 0;
}

body.scene-big .ban-slot-left {
    top: calc(28 * var(--u));
    left: auto;
    right: calc(50% + 8 * var(--u));
}

body.scene-big .ban-slot-right {
    top: calc(28 * var(--u));
    right: auto;
    left: calc(50% + 8 * var(--u));
}

body.scene-big .ban-badge {
    width: calc(76 * var(--u));
    height: calc(76 * var(--u));
}

/* Exit phase: shove each banner off its own side of the screen and drop the
   scoreboard (placed after the scene-big rule above so it wins while moving). */
body.scene-exit .team-left {
    transform: translateX(-160%);
}

body.scene-exit .team-right {
    transform: translateX(160%);
}

body.scene-exit .scene-score {
    opacity: 0;
}

/* --- Transitions (enabled one frame after load) --- */
body.anim-ready .scene-bg {
    transition: transform .6s ease;
}

/* Matches the .team transition so the fx layer slides in lockstep with team B. */
body.anim-ready .scene-fx {
    transition: transform .45s cubic-bezier(.4, 0, .2, 1);
}

body.anim-ready .scene-glow {
    transition: opacity .5s ease;
}

body.anim-ready .ban-slot {
    transition: opacity .35s ease;
}

body.anim-ready .scene-score {
    transition: opacity .4s ease .1s, transform .4s ease .1s;
}

/* Entering the maps scene wipes the score plate open horizontally (the wrapper's
   overflow clips the fully-slanted, fixed-width inner plate). The wipe is triggered
   from JS (revealScores in overlay.js) rather than a class-driven transition, because
   the plate is display:none in big view and an un-hide + animate in the same frame
   won't start reliably. Leaving maps just hides it. */

/* `transform` drives the off-screen slide (normal<->big/maps); top/left/width/
   height/clip-path let the banners glide in place when moving directly between big
   and maps (see paintScene). All share one easing so a combined move reads as one
   motion. The logo/name are repositioned and resized by the FLIP in overlay.js. */
body.anim-ready .team {
    transition: transform .45s cubic-bezier(.4, 0, .2, 1),
                top .45s cubic-bezier(.4, 0, .2, 1),
                left .45s cubic-bezier(.4, 0, .2, 1),
                width .45s cubic-bezier(.4, 0, .2, 1),
                height .45s cubic-bezier(.4, 0, .2, 1),
                clip-path .45s cubic-bezier(.4, 0, .2, 1);
}

/* During a scene swap we momentarily kill the banner transition so a team that
   changes sides (team B on the maps screen) jumps to its new off-screen position
   instead of sliding across the screen (see paintScene in overlay.js). */
body.anim-ready.scene-snap .team {
    transition: none;
}

/* ============== Maps (big-view strip + maps scene) ============== */
/* Both map surfaces are hidden by default; each scene reveals its own. Gating the
   strip's display on :not([hidden]) lets overlay.js hide it (no played maps) even
   though the scene rule is more specific than the `hidden` attribute. */
.maps-strip,
.maps-screen {
    display: none;
}

body.scene-big .maps-strip:not([hidden]) {
    position: fixed;
    left: 50%;
    bottom: 4vh;
    z-index: 2;
    display: flex;
    gap: calc(16 * var(--u));
    transform: translateX(-50%);
    pointer-events: none;
}

/* Shown on the maps scene, and kept shown briefly while leaving (maps-leaving) so
   the cards can fade out instead of being cut by display:none. */
body.scene-maps .maps-screen,
body.maps-leaving .maps-screen {
    position: fixed;
    inset: 0;
    z-index: 2;
    display: flex;
    align-items: center;
    justify-content: center;
    /* Leave the left third clear for the stacked team banners. */
    padding: 0 4vw 0 34vw;
    color: #fff;
    text-shadow: 0 2px 10px rgba(0, 0, 0, .6);
}

/* The hero bans and watermark are irrelevant on the maps screen; the two team
   banners stay, stacked on the left (see below). The central score is left in the DOM
   (not display:none) so it fades/scales out via its opacity transition on the way to
   maps and back in on the way to big — base .scene-score is already opacity 0. */
body.scene-maps .ban-slot,
body.scene-maps .powered {
    display: none;
}

/* Maps scene banners: both on the left, team B normalised to the left-edge look
   and dropped just below team A so they read as a stacked scoreboard. Entering
   and leaving reuses the shared scene-exit slide (overlay.js paintScene). */
body.scene-maps .team {
    width: 30vw;
    height: calc(96 * var(--u));
}

body.scene-maps .team-logo {
    width: calc(72 * var(--u));
    height: calc(72 * var(--u));
}

body.scene-maps .team-logo-text {
    font-size: calc(32 * var(--u)); /* 2rem */
}

body.scene-maps .team-name {
    font-size: calc(32 * var(--u)); /* 2rem */
}

body.scene-maps .team-left {
    top: calc(50vh - 104 * var(--u));
}

body.scene-maps .team-right {
    top: calc(50vh + 8 * var(--u));
    left: 0;
    right: auto;
    flex-direction: row;
    padding: 0 0 0 calc(24 * var(--u));
    clip-path: polygon(0 0, 100% 0, calc(100% - var(--slant)) 100%, 0 100%);
}

body.scene-maps .team-right .team-score {
    margin-left: auto;
    margin-right: 0;
}

body.scene-maps .team-right .team-score-plate {
    clip-path: polygon(var(--slant) 0, 100% 0, calc(100% - var(--slant)) 100%, 0 100%);
}

/* Both banners are on the left here, so team B exits/enters from the left edge too
   (the default scene-exit rule shoves .team-right off the right). */
body.scene-maps.scene-exit .team-right {
    transform: translateX(-160%);
}

.maps-track {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: center;
    gap: 1.6vw;
}

/* Fade/rise the timeline in once the banners have started settling. */
body.scene-maps.anim-ready .maps-track {
    animation: maps-rise .5s ease .12s both;
}

@keyframes maps-rise {
    from {
        opacity: 0;
        transform: translateY(calc(24 * var(--u)));
    }
    to {
        opacity: 1;
        transform: none;
    }
}

/* Leaving the maps scene: mirror the rise so the cards fade/drop out (overlay.js
   holds the screen visible via maps-leaving for the animation's length). A separate
   keyframe name guarantees a fresh animation plays rather than reusing the finished
   rise. */
body.maps-leaving.anim-ready .maps-track {
    animation: maps-fall .4s ease both;
}

@keyframes maps-fall {
    from {
        opacity: 1;
        transform: none;
    }
    to {
        opacity: 0;
        transform: translateY(calc(24 * var(--u)));
    }
}

.maps-empty {
    color: rgba(255, 255, 255, .7);
    font-size: calc(24 * var(--u)); /* 1.5rem */
}

/* A map tile: an outer wrapper that owns layout width and the FLIP move, around an
   inner card that owns the visual (16:9 art + name + winner badge) and the lift. The
   split keeps the move transform (.map-tile) and the scale transform (.map-card) on
   separate elements so neither needs a custom property to compose — plain transforms
   transition reliably in every browser. */
.map-tile {
    position: relative;
    width: calc(200 * var(--u));
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: calc(6 * var(--u));
}

.map-card {
    width: 100%;
    display: flex;
    flex-direction: column;
    border-radius: calc(10 * var(--u));
    overflow: hidden;
    background: rgba(0, 0, 0, .35);
    box-shadow: 0 calc(4 * var(--u)) calc(16 * var(--u)) rgba(0, 0, 0, .5);
}

/* Per-map ban badges stacked above/below the card on the maps screen (reusing the
   banner .ban-badge look at a smaller size). Hidden when that side hasn't banned. */
.map-ban {
    width: calc(36 * var(--u));
    height: calc(36 * var(--u));
    flex-shrink: 0;
}

.map-art {
    position: relative;
    width: 100%;
    aspect-ratio: 16 / 9;
    background: linear-gradient(135deg, #2a2f3a, #1a1d24);
}

.map-art-img {
    display: block;
    width: 100%;
    height: 100%;
    object-fit: cover;
}

.map-noart .map-art-img {
    display: none;
}

.map-name {
    padding: calc(8 * var(--u)) calc(10 * var(--u));
    font-size: calc(16 * var(--u)); /* 1rem */
    font-weight: 600;
    text-align: center;
    text-shadow: none;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

/* Winner badge fills the whole map art over a dimmed image, centring its content
   (the winner's logo, or a text mark for teams with no logo). */
.map-winner {
    position: absolute;
    inset: 0;
    box-sizing: border-box;
    padding: 18%;
    display: flex;
    align-items: center;
    justify-content: center;
    background: rgba(0, 0, 0, .35);
}

.map-winner-img {
    width: 100%;
    height: 100%;
    object-fit: contain;
    filter: drop-shadow(0 2px 6px rgba(0, 0, 0, .8));
}

.map-winner-text {
    font-weight: 800;
    font-size: calc(27.2 * var(--u)); /* 1.7rem */
    color: #fff;
    text-align: center;
    line-height: 1;
    text-shadow: 0 2px 6px rgba(0, 0, 0, .8);
}

/* Timeline states: later = dimmed, upcoming = lifted with a glow, match point
   swaps the glow for a coloured border (gold for a decider, set via --mp-color).
   Applied to the inner card so they sit alongside the FLIP move on the wrapper. */
.maps-track .map-later .map-card {
    opacity: .45;
}

/* scale() grows the card past its layout box, so reserve real vertical space with
   a margin to keep the lifted card clear of its ban badges above/below. */
.maps-track .map-upcoming .map-card {
    transform: scale(1.06);
    margin: calc(10 * var(--u)) 0;
    box-shadow: 0 0 0 calc(3 * var(--u)) rgba(255, 255, 255, .5), 0 calc(6 * var(--u)) calc(24 * var(--u)) rgba(0, 0, 0, .6);
}

.maps-track .map-matchpoint .map-card {
    box-shadow: 0 0 0 calc(4 * var(--u)) var(--mp-color, #ffcf3f), 0 0 calc(24 * var(--u)) var(--mp-color, #ffcf3f);
}

/* The lift/dim/glow when a tile changes kind (later <-> upcoming <-> played) animate
   via a plain class-driven transition on the inner card. The discrete events — moving,
   appearing, the winner reveal — are driven by the Web Animations API in overlay.js
   instead, so they don't depend on transition toggling. Maps scene only. */
body.scene-maps.anim-ready .maps-track .map-card {
    transition: transform .4s cubic-bezier(.4, 0, .2, 1),
                margin .4s cubic-bezier(.4, 0, .2, 1),
                opacity .4s ease,
                box-shadow .4s ease;
}

/* Maps scene tiles are portrait so the whole pool fits beside the scoreboard. */
.maps-track .map-tile {
    width: clamp(calc(130 * var(--u)), 14vw, calc(200 * var(--u)));
}

.maps-track .map-art {
    aspect-ratio: 3 / 4.5;
}

/* Big-view strip tiles are smaller. */
.maps-strip .map-tile {
    width: calc(132 * var(--u));
}

.maps-strip .map-name {
    padding: calc(4 * var(--u)) calc(6 * var(--u));
    font-size: calc(12.8 * var(--u)); /* .8rem */
}

/* ============== Control room ============== */
/* Sized for an OBS browser dock, which is often narrow and tall. `safe center`
   keeps the panel centred when it fits but pins it to the top (scrollable)
   instead of clipping the top off when it doesn't. */
.control-room {
    position: fixed;
    inset: 0;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: safe center;
    gap: 14px;
    padding: 16px;
    overflow-y: auto;
    background: #111;
    color: #fff;
    text-align: center;
}

/* One row per team: name | score controls | ban. The shared grid keeps the three
   columns aligned across both team rows. */
.cr-teams {
    display: grid;
    grid-template-columns: minmax(0, 1fr) auto auto;
    align-items: center;
    gap: 10px 20px;
    width: 100%;
    max-width: 560px;
    margin: 0 auto;
}

.cr-name {
    font-size: 1.1rem;
    font-weight: 600;
    overflow-wrap: anywhere;
}

.cr-score-controls {
    display: flex;
    align-items: center;
    gap: 10px;
}

.cr-score {
    min-width: 2ch;
    font-size: 2rem;
    font-weight: 800;
    line-height: 1;
}

.cr-btn {
    width: 44px;
    height: 44px;
    font-size: 1.4rem;
    line-height: 1;
    border: none;
    border-radius: var(--cr-radius);
    background: #2a2a2a;
    color: #fff;
    cursor: pointer;
}

.cr-swap {
    padding: 12px 28px;
    font-size: 1rem;
    border: none;
    border-radius: var(--cr-radius);
    background: #2a2a2a;
    color: #fff;
    cursor: pointer;
}

/* Per-team "Won" button: replaces the +/- once maps drive the score. The current
   map's winner is marked with a gold outline. */
.cr-won {
    padding: 9px 18px;
    font-size: 1rem;
    font-weight: 700;
    border: 2px solid transparent;
    border-radius: var(--cr-radius);
    background: #2a2a2a;
    color: #fff;
    cursor: pointer;
}

.cr-won:hover {
    background: #3a3a3a;
}

.cr-won-on,
.cr-won-on:hover {
    border-color: #ffcf3f;
    box-shadow: 0 0 10px rgba(255, 207, 63, .5);
}

.cr-btn:hover,
.cr-swap:hover {
    background: #3a3a3a;
}

.cr-btn:active,
.cr-swap:active {
    background: #444;
}

/* Toggle in its "on" state (e.g. Big view active). */
.cr-toggle-on,
.cr-toggle-on:hover {
    background: #2f7d57;
    color: #fff;
}

.cr-empty {
    max-width: 28rem;
    color: var(--cr-muted);
    font-size: 1.1rem;
}

/* ============== Ban selector (control room) ============== */
.cr-ban {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    min-height: 38px;
    padding: 6px 12px;
    border: 1px solid var(--cr-border);
    border-radius: var(--cr-radius);
    background: #1d1d1d;
    color: #fff;
    font-size: .85rem;
    cursor: pointer;
}

.cr-ban:hover {
    background: #262626;
}

.cr-ban-set {
    border-color: #444;
}

.cr-ban-icon {
    width: 24px;
    height: 24px;
    border-radius: 4px;
    object-fit: cover;
}

.ban-picker-overlay {
    position: fixed;
    inset: 0;
    z-index: 10;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 24px;
    background: rgba(0, 0, 0, .6);
}

.ban-picker {
    display: flex;
    flex-direction: column;
    gap: 16px;
    width: min(680px, 100%);
    max-height: 80vh;
    padding: 20px;
    background: #1a1a1a;
    border-radius: 12px;
}

.ban-picker-head {
    display: flex;
    flex-wrap: wrap;
    gap: 12px;
}

.ban-search {
    flex: 1;
    min-width: 0;
    padding: 10px 14px;
    border: 1px solid #444;
    border-radius: var(--cr-radius);
    background: #111;
    color: #fff;
    font-size: 1rem;
}

.ban-clear {
    white-space: nowrap;
}

.ban-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(84px, 1fr));
    gap: 12px;
    overflow-y: auto;
    padding: 4px;
}

.ban-cell {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 6px;
    padding: 8px 4px;
    border: 1px solid transparent;
    border-radius: var(--cr-radius);
    background: var(--cr-tile);
    color: #fff;
    cursor: pointer;
}

.ban-cell:hover {
    background: var(--cr-tile-hover);
    border-color: var(--cr-border-hover);
}

.ban-cell-icon {
    width: 56px;
    height: 56px;
    border-radius: 6px;
    object-fit: cover;
}

.ban-cell-name {
    font-size: .75rem;
    line-height: 1.1;
    text-align: center;
}

/* ============== Match picker (control room) ============== */
.match-picker {
    display: flex;
    flex-direction: column;
    gap: 16px;
    width: min(640px, 100%);
    max-height: 80vh;
    padding: 20px;
    background: #1a1a1a;
    border-radius: 12px;
}

.mp-title {
    margin: 0;
    font-size: 1.1rem;
    font-weight: 700;
}

.mp-sub {
    margin: 0;
    color: var(--cr-muted);
    font-size: .95rem;
}

.mp-days {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
    gap: 10px;
    overflow-y: auto;
    padding: 4px;
}

.mp-day {
    padding: 18px 16px;
    border: 1px solid var(--cr-border);
    border-radius: var(--cr-radius);
    background: var(--cr-tile);
    color: #fff;
    font-size: 1.05rem;
    font-weight: 600;
    cursor: pointer;
}

.mp-day:hover {
    background: var(--cr-tile-hover);
    border-color: var(--cr-border-hover);
}

.mp-head {
    display: flex;
    align-items: center;
    gap: 12px;
}

.mp-back {
    padding: 6px 14px;
    border: 1px solid var(--cr-border);
    border-radius: var(--cr-radius);
    background: #1d1d1d;
    color: #ddd;
    font-size: .9rem;
    white-space: nowrap;
    cursor: pointer;
}

.mp-back:hover {
    background: #262626;
}

.mp-list {
    display: flex;
    flex-direction: column;
    gap: 8px;
    overflow-y: auto;
    padding: 4px;
}

.mp-match {
    display: flex;
    align-items: center;
    gap: 12px;
    padding: 10px 14px;
    border: 1px solid transparent;
    border-radius: var(--cr-radius);
    background: var(--cr-tile);
    color: #fff;
    text-align: left;
    cursor: pointer;
}

.mp-match:hover {
    background: var(--cr-tile-hover);
    border-color: var(--cr-border-hover);
}

.mp-match-current {
    border-color: #4a8;
}

.mp-logo {
    width: 28px;
    height: 28px;
    flex-shrink: 0;
    object-fit: contain;
}

.mp-vs {
    flex: 1;
    font-size: .95rem;
}

/* ============== Maps editor (control room) ============== */
/* Compact control grid: a right-aligned label column beside a wrapping row of
   buttons for each group (best of / view / match actions). */
.cr-control-table {
    display: grid;
    grid-template-columns: auto 1fr;
    align-items: center;
    gap: 6px 12px;
    width: min(420px, 100%);
    margin: 0 auto;
}

.cr-control-label {
    text-align: right;
    color: var(--cr-muted);
    font-size: .85rem;
    font-weight: 600;
}

.cr-control-cell {
    display: flex;
    flex-wrap: wrap;
    gap: 6px;
}

.cr-control-table .cr-swap {
    padding: 6px 12px;
    font-size: .85rem;
}

.cr-select {
    padding: 6px 10px;
    font-size: .85rem;
    border: 1px solid var(--cr-border);
    border-radius: var(--cr-radius);
    background: #2a2a2a;
    color: #fff;
    cursor: pointer;
}

.cr-maps-section {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 8px;
    width: 100%;
}

/* Title + "Add map" on one line. */
.cr-maps-head {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 12px;
}

.cr-maps-title {
    margin: 0;
    font-size: 1rem;
    font-weight: 700;
}

/* Horizontal, scrollable timeline of map slots. Each slot is a thin vertical card;
   the list scrolls sideways and addMap() jumps it to the right (see control.js). */
.cr-maps {
    display: flex;
    flex-direction: row;
    flex-wrap: nowrap;
    align-items: stretch;
    /* Centre the cards, but fall back to flex-start when they overflow so the first
       card never becomes unreachable behind the scroll start. */
    justify-content: safe center;
    gap: 8px;
    width: 100%;
    overflow-x: auto;
    padding-bottom: 6px; /* room for the scrollbar so it doesn't cover the cards */
}

.cr-map-row {
    display: flex;
    flex: 0 0 auto;
    width: 104px;
    padding: 6px;
    border: 1px solid var(--cr-border);
    border-radius: var(--cr-radius);
    background: #161616;
}

.cr-map-upcoming {
    border-color: #4a8;
}

.cr-map-matchpoint {
    border-color: #ffcf3f;
}

.cr-map-pick {
    display: flex;
    flex-direction: column;
    align-items: stretch;
    gap: 4px;
    flex: 1;
    min-width: 0;
    padding: 5px;
    border: 1px solid var(--cr-border);
    border-radius: var(--cr-radius);
    background: #1d1d1d;
    color: #fff;
    font-size: .72rem;
    cursor: pointer;
}

.cr-map-pick:hover {
    background: #262626;
}

.cr-map-pick span {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    text-align: center;
}

.cr-map-thumb {
    width: 100%;
    height: 46px;
    border-radius: 4px;
    object-fit: cover;
}

/* Map picker: mode-grouped variant of the ban grid (reuses .ban-cell). */
.map-grid {
    display: flex;
    flex-direction: column;
    gap: 8px;
    overflow-y: auto;
    padding: 4px;
}

.map-group-title {
    margin: 8px 0 4px;
    font-size: .9rem;
    color: var(--cr-muted);
}

.map-cells {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));
    gap: 12px;
}

.map-cell-icon {
    width: 100%;
    aspect-ratio: 16 / 9;
    border-radius: 6px;
    object-fit: cover;
    background: #222;
}

.map-cell-noart {
    background: #333;
}

/* Tighten the control room further for small, near-square or portrait docks. */
@media (max-width: 380px), (max-height: 540px) {
    .control-room {
        gap: 12px;
        padding: 12px;
    }

    .cr-teams {
        gap: 8px 14px;
    }

    .cr-btn {
        width: 40px;
        height: 40px;
        font-size: 1.3rem;
    }

    .cr-score {
        font-size: 1.8rem;
    }

    .ban-picker,
    .match-picker {
        padding: 14px;
        gap: 12px;
    }

    .ban-grid {
        grid-template-columns: repeat(auto-fill, minmax(72px, 1fr));
        gap: 8px;
    }

    .ban-cell-icon {
        width: 48px;
        height: 48px;
    }
}
