/* ===========================
   GLOBAL STYLES
   =========================== */

/* Default UI colours. Accent defaults to the stock gold so every accent part
   (header/footer trim, checker flags, card goldframe) matches out of the box;
   ColorTheme.apply() overrides these inline on <html> when a colour is chosen
   and removes them (falling back to gold) when it's cleared. Theme (the card
   body/wash) stays white/neutral by default, so those vars are left unset. */
:root {
    --accent: #d0d20d;
    --accent-deep: #ac8f0d;
    --accent-soft: #eaeb92;

    /* Height of the fixed header and footer bands. Everything drawn inside
       them is sized from this rather than hard-coded, so a band that shrinks
       on a short viewport takes its contents down with it. Before this, the
       checkerboards kept 44x46px squares at every size and simply showed
       fewer of them — one single row of squares by 620px tall. Overridden in
       the height/width breakpoints at the bottom of this file. */
    --chrome-h: 90px;

    /* How far in from the left edge the fixed wordmark reaches, plus a little
       air. Measured, not guessed: .site-logo is "WikiDerby.io" at 72px italic
       squashed to 0.63 of its width, sitting at left: 26px, so it ends at 299.
       Anything anchored to the top of the screen has to start after this or it
       runs underneath the mark, which stays put for the whole race. Overridden
       wherever the wordmark is resized — the same breakpoints as --chrome-h. */
    --logo-clear: 314px;

    /* Surfaces and ink. The defaults are exactly the literals these replaced,
       so the classic theme renders as it always did; midnight repaints them
       below and every panel follows without a filter. */
    --surface: #fff;
    /* The two steps below white. Half a dozen near-identical lavenders
       (#f2f4ff, #f4f6ff, #eef1ff …) collapsed into one token each: they were
       within a few units of one another, close enough that no one could pick
       them apart, and far enough apart to make a dark palette impossible to
       define. Classic shifts by at most 4/255 on a channel as a result. */
    --surface-2: #f2f4ff;
    --surface-3: #e8e8f0;
    --ink: #111;
    --ink-strong: #000;
    /* The neutral text scale, darkest to faintest. Near-identical values were
       folded together the same way the surfaces were (#333/#37424f/#445 into
       one step); coloured inks — the blue links, the red warnings, the gold
       prices — are left alone, because their hue is the message. */
    --ink-navy: #17233b;
    --ink-mid: #333;
    --ink-muted: #5b6474;
    --ink-faint: #888;

    /* The standard panel gradient: settings rows, dialogs, list rows, cards.
       It was written out thirteen times with alphas between 0.95 and 0.99 that
       nobody could tell apart, which is the same reason the surfaces above were
       folded into tokens — a literal repeated that many times cannot be
       repainted for a dark theme, and every one of those panels carries
       var(--ink) text that midnight turns near-white. White on white. */
    --panel-top: rgba(255, 255, 255, 0.97);
    --panel-bottom: rgba(236, 240, 255, 0.97);

    /* The brand blue, as INK. Only the text uses are tokenised: the same hex is
       also a fill under white text in 32 places, and those want no theming at
       all. On white it is the link colour it always was; midnight needs it
       lifted, because #0032ff on a dark panel is a navy smudge. */
    --ink-link: #0032ff;

    /* Theme slots. ColorTheme.apply() overrides these inline on <html> when the
       player picks a theme colour, and REMOVES them when they clear it — at
       which point these are what shows through. They used to exist only as
       hardcoded white fallbacks inside each var() call, so clearing the theme
       colour under midnight put white cards back under white text. */
    --theme-card: rgba(255, 255, 255, 0.72);
    --theme-tint: #ffffff;
    --theme-tint2: #eef0f4;
    --theme-wash: #fefefe;
    --theme-wash2: #f2f2f2;
}

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

body {
    min-height: 100vh;
    display: flex;
    flex-direction: column;
    overflow-x: clip;
    overflow-y: auto;
}

/* Child-scoped on purpose. An article is rendered as its own <main class="wiki-page">
   inside the stage (see WikiSkin.pageHtml), so a bare `main` selector hits it too and
   turns the article into a centred flex ROW: the title, the CC BY-SA footer and the
   "Target reached!" panel get squeezed to ~140px and centred against a 12,000px-tall
   .wiki-content, i.e. thousands of pixels off screen, while the content itself
   overhangs both edges on a phone. Every rule meant for the app shell is `body > main`. */
body > main {
    flex: 1;
    width: min(100%, 2200px);
    margin: 0 auto;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 20px;
}

/* ===========================
   GRID LAYOUT
   =========================== */

.menu-stage {
    width: 100%;
    overflow: visible;
    padding: 120px 0;
}

.grid-container {
    display: grid;
    grid-template-columns: repeat(3, minmax(420px, 504px));
    gap: 50px 170px;
    width: 100%;
    max-width: 2100px;
    justify-content: center;
}

/* Cards are things you click, not things you read, so a double-click on one
   should not leave its title highlighted the way it would on a web page.
   Scoped to the card class rather than to .grid-container: #menuGrid carries
   .grid-container on EVERY screen (see renderScreen), so a rule there is
   inherited by all of them, which is how the legal text, the About page, the
   shop and the settings all ended up unselectable.
   Keyed on .glass rather than on a direct child of the grid so the training
   difficulty bars — cards that sit two levels down — behave the same way. */
.glass {
    user-select: none;
}

/* ── Card menus never scroll ──────────────────────────────────────────────
   A menu is meant to be taken in at a glance, so the stage is exactly the
   space between the fixed header and footer and the page does not scroll.
   When the cards do not fit, MenuFit in script.js shrinks them via
   --menu-fit rather than letting the page grow.

   Only card screens opt in. The shop, the lobby lists and a race's article
   are lists that are supposed to scroll, so they never get this class — and
   :not(.wiki-reading) is belt-and-braces for a race started from here.

   The stage has to clip: a transform does not affect layout, so the grid's
   unscaled box still overflows even while it looks like it fits. That is the
   same property that lets MenuFit measure the natural size at any time
   without first undoing the scale. */
body.menu-fit:not(.wiki-reading) {
    height: 100dvh;
    min-height: 0;
    overflow: hidden;
}

body.menu-fit:not(.wiki-reading) main {
    min-height: 0;
    overflow: hidden;
}

body.menu-fit:not(.wiki-reading) .menu-stage {
    height: 100%;
    display: flex;
    align-items: center;
    justify-content: center;
    overflow: hidden;
}

body.menu-fit:not(.wiki-reading) .grid-container {
    transform: scale(var(--menu-fit, 1));
    transform-origin: center center;
}

/* Safety valve: below the floor in MenuFit the cards would be unreadable, so
   the page is allowed to scroll again rather than clipping them out of reach. */
body.menu-fit.menu-fit-overflow:not(.wiki-reading),
body.menu-fit.menu-fit-overflow:not(.wiki-reading) main,
body.menu-fit.menu-fit-overflow:not(.wiki-reading) .menu-stage {
    height: auto;
    overflow: visible;
}

.grid-container.is-exiting,
.grid-container.is-entering,
.grid-container.is-exiting-back,
.grid-container.is-entering-back {
    pointer-events: none;
}

.grid-container.is-exiting .glass {
    animation: cardSlideOutLeft 260ms cubic-bezier(0.7, 0, 1, 0.35) forwards;
    animation-delay: var(--row-delay);
}

.grid-container.is-entering .glass {
    animation: cardSlideInRight 260ms cubic-bezier(0.04, 0.82, 0.18, 1) backwards;
    animation-delay: var(--row-delay);
}

.grid-container.is-exiting-back .glass {
    animation: cardSlideOutRight 260ms cubic-bezier(0.7, 0, 1, 0.35) forwards;
    animation-delay: var(--row-delay);
}

.grid-container.is-entering-back .glass {
    animation: cardSlideInLeft 260ms cubic-bezier(0.04, 0.82, 0.18, 1) backwards;
    animation-delay: var(--row-delay);
}

.training-container.is-exiting .training-screen {
    animation: trainingScreenOutLeft 260ms cubic-bezier(0.7, 0, 1, 0.35) forwards;
}

.training-container.is-entering .training-screen {
    animation: trainingScreenInRight 260ms cubic-bezier(0.04, 0.82, 0.18, 1) backwards;
}

.training-container.is-exiting-back .training-screen {
    animation: trainingScreenOutRight 260ms cubic-bezier(0.7, 0, 1, 0.35) forwards;
}

.training-container.is-entering-back .training-screen {
    animation: trainingScreenInLeft 260ms cubic-bezier(0.04, 0.82, 0.18, 1) backwards;
}

@keyframes cardSlideOutLeft {
    from {
        transform: translateX(0);
        opacity: 1;
    }

    68% {
        opacity: 1;
    }

    to {
        transform: translateX(-135vw);
        opacity: 0;
    }
}

@keyframes cardSlideInRight {
    from {
        transform: translateX(135vw);
        opacity: 0;
    }

    30% {
        opacity: 1;
    }

    to {
        transform: translateX(0);
        opacity: 1;
    }
}

@keyframes cardSlideOutRight {
    from {
        transform: translateX(0);
        opacity: 1;
    }

    68% {
        opacity: 1;
    }

    to {
        transform: translateX(135vw);
        opacity: 0;
    }
}

@keyframes cardSlideInLeft {
    from {
        transform: translateX(-135vw);
        opacity: 0;
    }

    30% {
        opacity: 1;
    }

    to {
        transform: translateX(0);
        opacity: 1;
    }
}

@keyframes trainingScreenOutLeft {
    from {
        transform: translateX(0);
        opacity: 1;
    }

    68% {
        opacity: 1;
    }

    to {
        transform: translateX(-115vw);
        opacity: 0;
    }
}

@keyframes trainingScreenInRight {
    from {
        transform: translateX(115vw);
        opacity: 0;
    }

    30% {
        opacity: 1;
    }

    to {
        transform: translateX(0);
        opacity: 1;
    }
}

@keyframes trainingScreenOutRight {
    from {
        transform: translateX(0);
        opacity: 1;
    }

    68% {
        opacity: 1;
    }

    to {
        transform: translateX(115vw);
        opacity: 0;
    }
}

@keyframes trainingScreenInLeft {
    from {
        transform: translateX(-115vw);
        opacity: 0;
    }

    30% {
        opacity: 1;
    }

    to {
        transform: translateX(0);
        opacity: 1;
    }
}

/* ===========================
   GLASS CARD COMPONENTS
   =========================== */

.glass {
    appearance: none;
    font: inherit;
    width: 100%;
    aspect-ratio: 13 / 8;
    background: rgba(255, 255, 255, 0.0);
    border-radius: 7px;
    padding: 10px 15px;
    box-shadow:
        inset 0 0 2px 3px rgba(255, 255, 255, 0.5),
        inset 0 0 12px 9px rgba(255, 255, 255, 0.25),
        inset 0 0 30px 15px rgba(255, 255, 255, 0.15);
    position: relative;
    border-bottom: rgba(0, 0, 0, 0.267) 4px solid;
    transition: transform 0.2s ease;
    cursor: pointer;
    border: 0;
    will-change: transform, opacity;
    container-type: inline-size;
}

.glass:hover {
    transform: scale(1.1);
    z-index: 10;
}

.blur {
    position: absolute;
    top: 10px;
    left: 15px;
    right: 15px;
    bottom: 10px;
    /* --theme-card: the player's theme colour (white by default) */
    background: var(--theme-card, rgba(255, 255, 255, 0.6));
    border-radius: 16px;
    box-shadow: 0 4px 30px rgba(0, 0, 0, 0.1);
    backdrop-filter: blur(15.1px);
    -webkit-backdrop-filter: blur(15.1px);
    border: 1px solid rgba(255, 255, 255, 0.3);
    z-index: -1;
}

.blue-card {
    width: 100%;
    height: 100%;
    padding: 7px 10px;
    border-radius: 7px;
    border: 2px solid black;
    position: relative;
}

.blue-card::before {
    content: '';
    position: absolute;
    top: 0;
    left: 0;
    right: 0;
    bottom: 0;
    background:
        radial-gradient(circle at 0% 0%, var(--theme-tint, #ffffff) 0%, transparent 50%),
        radial-gradient(circle at 100% 0%, var(--theme-tint2, #eef0f4) 0%, transparent 50%),
        radial-gradient(circle at 0% 100%, var(--theme-tint2, #eef0f4) 0%, transparent 50%),
        radial-gradient(circle at 100% 100%, var(--theme-tint, #ffffff) 0%, transparent 50%),
        linear-gradient(135deg, var(--theme-tint, #ffffff), var(--theme-tint2, #eef0f4));
    background-blend-mode: normal;
    border-radius: 5px;
    z-index: -1;
    -webkit-mask:
        linear-gradient(#fff 0 0) exclude,
        linear-gradient(#fff 0 0) 50% 90% / 93% 65% no-repeat;
    mask:
        linear-gradient(#fff 0 0) exclude,
        linear-gradient(#fff 0 0) 50% 87% / 93% 64% no-repeat;
    -webkit-mask-composite: xor;
    mask-composite: exclude;
}

.inner {
    height: 100%;
    background: rgba(255, 255, 255, 0.0);
    border-radius: 7px;
    border: 1.5px solid rgba(63, 63, 63, 0.671);
    box-shadow:
        inset 0 0 2px 3px rgba(255, 255, 255, 0.5),
        inset 0 0 12px 9px rgba(255, 255, 255, 0.25),
        inset 0 0 30px 15px rgba(255, 255, 255, 0.15);
    padding: 1px 1px;
}

.square {
    position: absolute;
    background-color: #0033ff00;
    width: 97%;
    height: 65%;
    top: 65%;
    left: 50%;
    transform: translate(-50%, -50%);
    box-shadow:
        0 0 2px 3px rgba(255, 255, 255, 0.5),
        0 0 12px 9px rgba(255, 255, 255, 0.25),
        0 0 30px 15px rgba(255, 255, 255, 0.15);
    z-index: 2;
}

/* ===========================
   CARD PATTERNS
   =========================== */

.chess {
    position: relative;
    width: 100%;
    height: 30%;
    border-radius: 3px;
    border: 1px solid rgba(0, 0, 0, 0.171);
    background: repeating-conic-gradient(#ffffff27 0 25%, #0000 0 50%) 50% / 30px 30px;
}

/* ===========================
   GOLD FRAME COMPONENTS
   =========================== */

.goldframe {
    width: 100%;
    height: 70%;
    padding: 5px 10px 10px 10px;
    transition: transform 0.2s ease;
}

.glass:hover .goldframe {
    transform: scale(1.09) translateY(5px);
}

.goldframe2 {
    width: 100%;
    height: 100%;
    /* --accent-deep/--accent: the player's accent colour (ColorTheme.apply);
       unset, the frame keeps its stock gold. */
    background:
        radial-gradient(circle at 0% 0%, var(--accent-deep, #ac8f0d) 0%, transparent 50%),
        radial-gradient(circle at 100% 0%, var(--accent, #d0d20d) 0%, transparent 50%),
        radial-gradient(circle at 0% 100%, var(--accent, #d0d20d) 0%, transparent 50%),
        radial-gradient(circle at 100% 100%, var(--accent-deep, #ac8f0d) 0%, transparent 50%),
        linear-gradient(135deg, var(--accent-deep, #ac8f0d), var(--accent, #d0d20d));
    border-radius: 1px;
    border-left: 4px solid black;
    border-right: 4px solid black;
    border-top: 2px solid black;
    border-bottom: 2px solid black;
    padding: 4px 0px;
    box-shadow: 0 6px 0 0 rgba(0, 0, 0, 0.247);
}

.goldframe3 {
    width: 100%;
    height: 100%;
    /* black → accent colour → black (stock gold when no colour is set) */
    background: linear-gradient(to bottom,
            black 0%,
            var(--accent-deep, #ac8f0d) 10%,
            var(--accent-deep, #ac8f0d) 30%,
            var(--accent, #d0d20d) 70%,
            var(--accent, #d0d20d) 90%,
            black 100%);
    padding-left: 4px;
    padding-right: 4px;
    border-radius: 1px;
}

/* `contain`, not `cover`. The frame is wide and short while the card art is
   square, so cover crops the subject's head and feet off — which is fatal when
   the subject is a single figure. Contain shows the whole thing and lets the
   white studio background of the art meet the frame, so the letterboxing is
   invisible rather than looking like a mistake. */
/* <picture> is an inline wrapper with no box of its own, so without this the
   img inside it sizes against the line box instead of the frame. */
.goldframe3 picture {
    display: block;
    width: 100%;
    height: 100%;
}

.goldframe3 img {
    background-color: var(--surface);
    width: 100%;
    height: 100%;
    object-fit: contain;
}

/* ===========================
   TEXT ON SURFACES
   ===========================

   One face throughout. What changes is the fill and the colour of the edge
   around it, and both are read off whatever the text is standing on — the
   same trick the Mario Kart Wii menus play, where a card title and the "Main
   Menu" label share a typeface and share nothing else.

   Over card art the text is white with a hard black edge, because the art
   underneath is arbitrary and only black is guaranteed to separate from it.
   On the flat panels of the multiplayer screens it inverts: dark ink with a
   white edge, which lifts the letters off a panel that is itself translucent
   over the page background, without shouting the way white-on-black would in
   a place with no photo to compete with.

   Set --edge and --edge-w per surface; the recipe below does the rest.
   Eight directions rather than -webkit-text-stroke, which is centred on the
   glyph path and eats into the letterform — this sits entirely outside it. */

.cardtitle,
.cardtitle2,
.mp-title,
.mp-lobby-title {
    text-shadow:
        calc(var(--edge-w) * -1) calc(var(--edge-w) * -1) 0 var(--edge),
        var(--edge-w) calc(var(--edge-w) * -1) 0 var(--edge),
        calc(var(--edge-w) * -1) var(--edge-w) 0 var(--edge),
        var(--edge-w) var(--edge-w) 0 var(--edge),
        calc(var(--edge-w) * -1) 0 0 var(--edge),
        var(--edge-w) 0 0 var(--edge),
        0 calc(var(--edge-w) * -1) 0 var(--edge),
        0 var(--edge-w) 0 var(--edge);
}

/* ===========================
   CARD TITLES
   =========================== */

.cardtitle {
    /* Over card art: white on hard black, the heaviest treatment there is. */
    --edge: #000;
    --edge-w: 3px;
    font-family: Arial, sans-serif;
    font-weight: bold;
    font-size: clamp(16px, 11.5cqw, 55px);
    color: rgb(255, 255, 255);
    text-align: center;
    border-radius: 7px;
    position: absolute;
    top: -7px;
    left: 50%;
    transform: translateX(-50%);
    white-space: nowrap;
    z-index: 3;
}

.cardtitle2 {
    /* The echo behind the title: same colours, wider edge, so it reads as the
       same words further away rather than as a second label. */
    --edge: #000;
    --edge-w: 5px;
    font-family: Arial, sans-serif;
    font-weight: bold;
    font-size: clamp(14px, 10cqw, 48px);
    color: rgba(255, 255, 255, 0.5);
    text-align: center;
    border-radius: 7px;
    opacity: 0.3;
    white-space: nowrap;
    position: absolute;
    bottom: -5px;
    left: 50%;
    transform: translateX(-50%);
    z-index: 1;
}

/* ===========================
   CARD BARS
   ===========================

   The stacked variant of the same card: a menu laid down a column instead of
   across a grid, with the art half taken out. Used by the training
   difficulties and by every card menu below the main one — only the main menu
   keeps the card shape, because only its cards carry art for that shape to be
   built around.

   Everything here overrides the shared card styling above rather than forking
   it, so the colours and the title treatment keep working as they change.
   createCard() in script.js drops .square, .cardtitle2 and .goldframe from the
   markup; what is left is layout. */

.card-bars {
    display: flex;
    flex-direction: column;
    gap: 16px;
    width: min(720px, 92vw);
    margin: 0 auto;
}

.card-bars .glass {
    /* .glass is a 13/8 card by default; these are bars, so the ratio goes and
       a height takes over. */
    aspect-ratio: auto;
    height: 88px;
    padding: 6px 10px;
}

/* The card lift is scale(1.1). On a bar that is 720px wide that throws the
   edges outside the column and under the neighbours; a smaller lift reads the
   same at this width. */
.card-bars .glass:hover {
    transform: scale(1.03);
}

/* The gradient's mask carves a window out of the middle so the colour reads as
   a frame around the card art. There is no art here, so the mask would just be
   a hole down the middle of the bar - drop it and let the colour fill. */
.card-bars .blue-card::before {
    -webkit-mask: none;
    mask: none;
}

.card-bars .chess {
    height: 100%;
    display: flex;
    align-items: center;
    justify-content: center;
}

/* .cardtitle is sized in cqw - a percentage of the container's own width - and
   pinned to the top edge. A bar is two to three times wider than a card, so
   the card's multiplier runs straight into the clamp ceiling and every title
   comes out at the maximum; a smaller one lands back inside the range. The
   pinning goes too, and the flex centring above places it instead. */
.card-bars .cardtitle {
    position: static;
    transform: none;
    font-size: clamp(20px, 4.2cqw, 34px);
}

@media (max-width: 560px) {
    .card-bars {
        gap: 12px;
    }

    .card-bars .glass {
        height: 72px;
    }
}

/* ===========================
   HEADER
   =========================== */

.mkwii-header {
    position: fixed;
    top: 0;
    left: 0;
    right: 0;
    width: 100%;
    height: var(--chrome-h);
    overflow: hidden;
    border-bottom: 4px solid var(--accent-soft, #b0b0ff);
    flex-shrink: 0;
    z-index: 100;
}

.header-bg {
    position: absolute;
    inset: 0;
}

.header-bg::after {
    content: "";
    position: absolute;
    inset: 0;
    background: repeating-linear-gradient(var(--theme-wash, #fefefe),
            var(--theme-wash, #fefefe) 3px,
            var(--theme-wash2, #f2f2f2) 3px,
            var(--theme-wash2, #f2f2f2) 6px);
}

/* MKWii checkered-flag trim, drawn in CSS so it tints with the accent colour.
   A single 4-quadrant conic gradient tiles into a 2×2 checkerboard, then a
   left→right mask fades it in from transparent to fully visible.
   Falls back to the original PNG colours (#ddd9f0 / #696bb6) when unset. */
/* ===========================
   SITE LOGO (TEMPORARY)
   =========================== */

/* Placeholder wordmark until a real one exists — "WikiDerby.io" set nearly the
   full height of the header bar it sits in. Mixed case, not uppercased: the
   camel hump in WikiDerby is what makes the two words readable at a glance.
   Same face and weight as .cardtitle (the menu tiles), just italic, so it reads
   as part of the same set rather than as a pasted-on logo.

   --logo-sx / --logo-sy squash the wordmark without changing its type size, so
   it can be made to fit a band it would otherwise overflow while staying just
   as legible on a stream. Tune them live with ?logotune=1 (public/logo-tuner.js)
   and paste the numbers back here — they are plain multipliers, 1 = untouched. */
/* Fixed to the viewport rather than parented to the header, because the header
   leaves during a race and would take the wordmark with it. The element spans
   the header band (same height, so `align-items: center` reproduces the old
   `top: 50%` centring) and every media query that resizes the bar resizes this
   to match. z-index sits above the article stage (300) and the retreating
   chrome (340), but far below the modal range (900+) so dialogs still cover it. */
.site-logo {
    position: fixed;
    top: 0;
    left: 26px;
    height: 90px;
    display: flex;
    align-items: center;
    /* The bar used to clip this; keep doing so now that it stands alone. */
    overflow: hidden;
    z-index: 341;
    pointer-events: none;
    user-select: none;
    white-space: nowrap;
    font-family: Arial, sans-serif;
    font-weight: bold;
    font-style: italic;
    font-size: 72px;
    line-height: 1;
    /* Squash factors, overridable at runtime by the tuner. Tuned against a live
       race rather than the menu, which is the view that has to hold up. */
    --logo-sx: 0.63;
    --logo-sy: 0.81;
    /* Slid sideways by the back-button rule below rather than by overwriting
       `transform`, so the squash and the slide can coexist. */
    --logo-shift: 0px;
    transform: translateX(var(--logo-shift)) scale(var(--logo-sx), var(--logo-sy));
    /* Squash toward the left edge so the wordmark stays pinned to its margin
       instead of drifting inward as it narrows. */
    transform-origin: left center;
    color: var(--accent, #d0d20d);
    /* Eight-direction shadow, the same outline recipe as .cardtitle. Preferred
       over -webkit-text-stroke here: stroke is centred on the path and eats
       into thin cursive strokes, while this sits entirely outside the glyph.

       The outline colour is NOT fixed at black. The glyphs are painted in the
       player's accent, and a black accent made the fill and the outline the
       same colour: the wordmark collapsed into a silhouette, and on midnight's
       near-black page it vanished completely. ColorTheme.apply() flips this to
       a light outline when the accent is too dark to carry a black one. */
    text-shadow:
        -3px -3px 0 var(--logo-outline, #000),
        3px -3px 0 var(--logo-outline, #000),
        -3px 3px 0 var(--logo-outline, #000),
        3px 3px 0 var(--logo-outline, #000),
        -3px 0 0 var(--logo-outline, #000),
        3px 0 0 var(--logo-outline, #000),
        0 -3px 0 var(--logo-outline, #000),
        0 3px 0 var(--logo-outline, #000);
    transition: transform 120ms ease;
}

/* Back button occupies the same corner once there's history to go back to.
   Slide rather than hide — a wordmark that vanishes on navigation reads as a
   bug. 78px clears the button's 26px offset plus its 64px width.
   Matched via :has() rather than the old sibling combinator, because the two
   no longer share a parent — the wordmark moved out of the header.
   Excluded during a race: the button stays flagged visible while the header
   carries it off-screen, and without the guard the wordmark would shuffle
   sideways to clear something the player can no longer see. */
body:not(.wiki-reading):has(.back-button.is-visible) .site-logo {
    --logo-shift: 78px;
}

/* Race start: the chrome leaves, the wordmark stays — at full size, through
   the part of the session anyone actually broadcasts. It deliberately does NOT
   shrink here: a mark that resizes the moment the race begins reads as smaller
   in every frame that gets streamed, which is backwards. Use --logo-sx /
   --logo-sy to fit it instead; squashing keeps the type size, and with it the
   legibility, that shrinking throws away.
   The route is anchored to y=0 and would run straight under it, so the route
   keeps its hands off this corner — see --logo-clear below. */

@media (max-height: 820px) {
    .site-logo { height: 60px; font-size: 48px; }
}

@media (max-height: 620px) {
    .site-logo { height: 46px; font-size: 36px; }
}

@media (max-width: 480px) {
    .site-logo { height: 50px; font-size: 30px; left: 16px; }
}

/* Width and square both scale off the band height, so the pattern keeps its
   proportions instead of losing rows: two rows of squares at every size, and
   the same ~3.44:1 band that 310px gave against the original 90px header. */
.checkerboard {
    position: absolute;
    top: 0;
    right: 0;
    width: calc(var(--chrome-h) * 3.44);
    height: 100%;
    opacity: 0.85;
    background-image: conic-gradient(
        var(--accent-soft, #ddd9f0) 0deg 90deg,
        transparent 90deg 180deg,
        var(--accent-soft, #ddd9f0) 180deg 270deg,
        transparent 270deg 360deg);
    background-size: calc(var(--chrome-h) / 2) calc(var(--chrome-h) / 2);
    background-position: top right;
    -webkit-mask-image: linear-gradient(to right, transparent, #000 72%);
    mask-image: linear-gradient(to right, transparent, #000 72%);
}

.back-button {
    appearance: none;
    position: absolute;
    top: 50%;
    left: 26px;
    width: 64px;
    height: 54px;
    border: 0;
    border-radius: 6px;
    background: transparent;
    cursor: pointer;
    opacity: 0;
    pointer-events: none;
    transform: translateY(-50%) translateX(-18px);
    transition: opacity 120ms ease, transform 120ms ease;
    z-index: 2;
}

.back-button:disabled {
    cursor: default;
}

.back-button::before {
    content: "";
    position: absolute;
    top: 50%;
    left: 22px;
    width: 24px;
    height: 24px;
    border-left: 7px solid #1f1f1f;
    border-bottom: 7px solid #1f1f1f;
    transform: translateY(-50%) rotate(45deg);
    filter: drop-shadow(2px 2px 0 rgba(255, 255, 255, 0.9));
}

.back-button.is-visible {
    opacity: 1;
    pointer-events: auto;
    transform: translateY(-50%) translateX(0);
}

.back-button:hover::before {
    border-color: #0032ff;
}

/* ===========================
   PROFILE BUTTON
   =========================== */

/* Profile: a square mini-card, styled like a menu tile (wikichannel/settings),
   pinned to the top-right corner. */
.profile-btn {
    appearance: none;
    font: inherit;
    position: fixed;
    background: transparent;
    padding: 0;
    border: 0;
    cursor: pointer;
    z-index: 9999;
    will-change: transform;
    transition: transform 0.2s ease;
    transform-origin: center;
}

.profile-btn:hover {
    z-index: 10000;
    transform: scale(1.08);
}

.profile-card {
    width: 100%;
    height: 100%;
    box-sizing: border-box;
    padding: 7px;
    border-radius: 16px;
    background: var(--theme-card, rgba(255, 255, 255, 0.72));
    border: 3px solid var(--accent, #d0d20d);
    box-shadow:
        0 4px 16px rgba(0, 0, 0, 0.28),
        inset 0 0 0 2px rgba(255, 255, 255, 0.65);
    backdrop-filter: blur(8px);
    -webkit-backdrop-filter: blur(8px);
}

.profile-avatar {
    width: 100%;
    height: 100%;
    border-radius: 9px;
    display: block;
    background-color: var(--surface);
    background-size: cover;
    background-position: center;
    box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.15);
}

/* ===========================
   DISCORD BUTTON
   =========================== */

/* The profile card's twin in the opposite corner: same mini-card language
   (radius, accent border, blur, hover lift), smaller because it is a secondary
   action and shouldn't compete with the profile for attention.
   Sized/placed here rather than in JS — unlike .profile-btn there is nothing
   dynamic about it, so it costs no script and can't flash unpositioned. */
.discord-btn {
    position: fixed;
    width: 64px;
    height: 64px;
    right: 44px;
    /* Clears the 90px fixed footer. Both values track the footer's own
       height breakpoints below — change one, change the other. */
    bottom: 108px;
    padding: 0;
    border: 0;
    background: transparent;
    cursor: pointer;
    z-index: 9999;
    will-change: transform;
    transition: transform 0.2s ease;
    transform-origin: center;
    -webkit-tap-highlight-color: transparent;
}

.discord-btn:hover {
    z-index: 10000;
    transform: scale(1.08);
}

.discord-btn:focus-visible {
    outline: 3px solid var(--accent, #d0d20d);
    outline-offset: 4px;
    border-radius: 14px;
}

.discord-card {
    width: 100%;
    height: 100%;
    box-sizing: border-box;
    padding: 12px;
    border-radius: 14px;
    display: flex;
    align-items: center;
    justify-content: center;
    background: var(--theme-card, rgba(255, 255, 255, 0.72));
    border: 3px solid var(--accent, #d0d20d);
    box-shadow:
        0 4px 16px rgba(0, 0, 0, 0.28),
        inset 0 0 0 2px rgba(255, 255, 255, 0.65);
    backdrop-filter: blur(8px);
    -webkit-backdrop-filter: blur(8px);
}

.discord-logo {
    width: 100%;
    height: auto;
    display: block;
    /* Takes the theme accent rather than Discord blurple, so the mark recolours
       with the rest of the UI. The <path> is fill="currentColor", which is what
       lets a single colour property drive it. */
    color: var(--accent, #d0d20d);
}

@media (max-height: 820px) {
    .discord-btn { bottom: 78px; }
    .notif-layer { bottom: 158px; }
}

@media (max-height: 620px) {
    .discord-btn {
        bottom: 60px;
        width: 54px;
        height: 54px;
    }
    /* Clears the shrunken button (60 + 54 + air) — and the cards themselves get
       shorter here, because at this height a 300px column would fill the screen. */
    .notif-layer { bottom: 130px; }
    .notif-card { width: 260px; }
}

/* The footer also shrinks to 50px on narrow screens — a WIDTH query, so it can
   apply on a tall phone where neither height query above does. Must stay last
   of the three: same specificity, so source order is what decides when a small
   phone matches both this and a max-height rule. */
@media (max-width: 480px) {
    .discord-btn { bottom: 68px; }
    /* The Discord button is already hidden by the 768px rule below, so this
       only has to clear the 50px footer. */
    .notif-layer { right: 16px; bottom: 66px; }
}

/* ===========================
   FRIENDS BUTTON + PANEL
   =========================== */

/* Third of the corner mini-cards, same language as .profile-btn and
   .discord-btn (radius, accent border, blur, hover lift), sized like Discord's
   because it is also a secondary action.

   Placed to the LEFT of the profile card, not below it. Below would put it at
   y 172-236, straight through the multiplayer room header at y 139-181 — the
   collision the room layout already had to solve once for the profile button.
   At 64px tall from y 72 it ends at 136, clear of the header band. */
.friends-btn {
    position: fixed;
    width: 64px;
    height: 64px;
    top: 72px;
    /* 44px (the profile/discord gutter) + 100px (profile width) + 16px gap. */
    right: 160px;
    padding: 0;
    border: 0;
    background: transparent;
    cursor: pointer;
    z-index: 9999;
    will-change: transform;
    transition: transform 0.2s ease;
    transform-origin: center;
    -webkit-tap-highlight-color: transparent;
}

.friends-btn:hover { z-index: 10000; transform: scale(1.08); }

.friends-btn:focus-visible {
    outline: 3px solid var(--accent, #d0d20d);
    outline-offset: 4px;
    border-radius: 14px;
}

.friends-card {
    width: 100%;
    height: 100%;
    box-sizing: border-box;
    padding: 6px;
    border-radius: 14px;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 1px;
    background: var(--theme-card, rgba(255, 255, 255, 0.72));
    border: 3px solid var(--accent, #d0d20d);
    box-shadow:
        0 4px 16px rgba(0, 0, 0, 0.28),
        inset 0 0 0 2px rgba(255, 255, 255, 0.65);
    backdrop-filter: blur(8px);
    -webkit-backdrop-filter: blur(8px);
    color: var(--ink, #16161d);
    overflow: hidden;
}

/* Undrawn icon slots render nothing at all (public/icons.js), so this must not
   reserve space until there is art in it — otherwise the label sits off-centre
   against a gap. :empty is what collapses it. */
.friends-icon { display: block; font-size: 18px; line-height: 1; }
.friends-icon:empty { display: none; }

/* The count of friends online. The label alone would be a button that says what
   it is and nothing more; the number is the reason to look at it. */
.friends-count {
    font-size: 19px;
    font-weight: 800;
    line-height: 1;
}

.friends-label {
    font-size: 8.5px;
    font-weight: 700;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    line-height: 1;
    opacity: 0.72;
}

/* Pending requests: the one thing here that is asking for an answer, so it gets
   the accent and sits proud of the card corner. */
.friends-badge {
    position: absolute;
    top: -5px;
    right: -5px;
    min-width: 21px;
    height: 21px;
    padding: 0 5px;
    box-sizing: border-box;
    border-radius: 11px;
    background: var(--accent, #d0d20d);
    color: var(--ink, #16161d);
    border: 2px solid var(--theme-card, #fff);
    font-size: 11px;
    font-weight: 800;
    line-height: 17px;
    text-align: center;
    pointer-events: none;
}

/* ── The panel ── */

.friends-panel {
    position: fixed;
    /* Below the PROFILE card (y 56-156), not just below the friends button that
       opens it. The two share this corner, and the panel's z-index is above
       both, so anything less would draw over the profile card's bottom edge. */
    top: 164px;
    right: 44px;
    width: 300px;
    max-height: min(60vh, 460px);
    display: flex;
    flex-direction: column;
    z-index: 10001;
    box-sizing: border-box;
    padding: 14px;
    border-radius: 16px;
    background: var(--theme-card, rgba(255, 255, 255, 0.92));
    border: 3px solid var(--accent, #d0d20d);
    box-shadow: 0 10px 34px rgba(0, 0, 0, 0.34);
    backdrop-filter: blur(10px);
    -webkit-backdrop-filter: blur(10px);
    color: var(--ink, #16161d);
}

.friends-panel[hidden] { display: none; }

.friends-panel-head {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 8px;
    margin-bottom: 10px;
}

.friends-panel-title { font-size: 15px; font-weight: 800; letter-spacing: 0.02em; }
.friends-panel-sub { font-size: 11px; opacity: 0.68; }

.friends-add { display: flex; gap: 6px; margin-bottom: 10px; }

.friends-add input {
    flex: 1 1 auto;
    min-width: 0;
    font: inherit;
    font-size: 13px;
    padding: 6px 8px;
    border-radius: 9px;
    border: 2px solid rgba(0, 0, 0, 0.18);
    background: var(--surface, #fff);
    color: inherit;
}

.friends-add input:focus { outline: 2px solid var(--accent, #d0d20d); outline-offset: 1px; }

.friends-btn-sm {
    font: inherit;
    font-size: 12px;
    font-weight: 700;
    padding: 6px 10px;
    border-radius: 9px;
    border: 2px solid var(--accent, #d0d20d);
    background: var(--accent, #d0d20d);
    color: var(--ink, #16161d);
    cursor: pointer;
    white-space: nowrap;
}

.friends-btn-sm.is-quiet { background: transparent; border-color: rgba(0, 0, 0, 0.2); }
.friends-btn-sm:hover { filter: brightness(1.06); }
.friends-btn-sm:disabled { opacity: 0.5; cursor: default; }

/* The list is the only part that scrolls — the add box and the heading stay put
   however many friends there are. */
.friends-scroll { overflow-y: auto; overflow-x: hidden; margin: 0 -4px; padding: 0 4px; }

.friends-section {
    font-size: 10px;
    font-weight: 800;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    opacity: 0.6;
    margin: 8px 0 4px;
}

.friends-row {
    display: flex;
    align-items: center;
    gap: 7px;
    padding: 5px 0;
    border-top: 1px solid rgba(0, 0, 0, 0.08);
}

.friends-row:first-of-type { border-top: 0; }

.friends-dot {
    flex: 0 0 auto;
    width: 8px;
    height: 8px;
    border-radius: 50%;
    background: rgba(0, 0, 0, 0.22);
}

.friends-dot.is-online { background: #3fbf5f; box-shadow: 0 0 0 2px rgba(63, 191, 95, 0.25); }

.friends-name {
    flex: 1 1 auto;
    min-width: 0;
    font-size: 13px;
    font-weight: 600;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.friends-row .friends-btn-sm { font-size: 11px; padding: 4px 8px; }

.friends-empty { font-size: 12px; opacity: 0.7; line-height: 1.45; padding: 6px 0; }

.friends-note { font-size: 11.5px; line-height: 1.45; opacity: 0.8; }

.friends-error { font-size: 12px; color: #c0392b; font-weight: 600; padding: 4px 0 0; }

/* Track the same height breakpoints the Discord button uses — the corner
   furniture has to shrink together or it stops looking like one set. */
@media (max-height: 620px) {
    .friends-btn { width: 54px; height: 54px; top: 64px; right: 150px; }
    .friends-count { font-size: 16px; }
}

/* Narrow screens have no room for a third card in the top-right row, so the
   button moves to the opposite corner and stacks above Discord — NOT hidden.
   Hiding it would make friends unreachable on a phone, and the panel is only
   ever opened from this button. The bottom values clear Discord at each of its
   own breakpoints (64px tall at 108/78/68, 54px at 60). */
@media (max-width: 720px) {
    .friends-btn { top: auto; right: 44px; bottom: 188px; }
    /* A sheet rather than a dropdown: anchoring a 300px panel to a button that
       is now near the bottom edge would push it off-screen. */
    .friends-panel {
        top: auto;
        right: 12px;
        left: 12px;
        bottom: 12px;
        width: auto;
        max-height: min(70vh, 460px);
    }
}

@media (max-width: 720px) and (max-height: 620px) {
    .friends-btn { bottom: 128px; right: 44px; }
}

@media (max-width: 480px) {
    .friends-btn { bottom: 148px; }
}

/* A race takes the whole screen and the corner furniture gets out of the way —
   the same rule the profile and Discord buttons follow. */
body.wiki-reading .friends-btn,
body.wiki-reading .friends-panel { display: none; }

/* ===========================
   TRAINING MODE
   =========================== */

/* The user-select: auto that used to be here is gone with the blanket rule it
   was fighting — training was the one screen that opted back into selectable
   text, and now every screen is selectable by default. */
.training-container {
    display: block;
    max-width: min(1180px, calc(100vw - 40px));
    margin: 0 auto;
}

.training-screen {
    width: 100%;
    padding: 16px 0 120px;
    color: var(--ink);
    will-change: transform, opacity;
}

.training-route {
    position: sticky;
    top: 106px;
    z-index: 20;
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 18px;
    width: min(100%, 980px);
    min-height: 74px;
    margin: 0 auto 24px;
    padding: 10px 18px;
    border: 3px solid #000;
    border-radius: 7px;
    background: linear-gradient(180deg, var(--panel-top), var(--panel-bottom));
    box-shadow:
        0 6px 0 rgba(0, 0, 0, 0.24),
        inset 0 0 0 3px rgba(176, 176, 255, 0.55);
}

.route-pill {
    min-width: 260px;
    max-width: 360px;
    padding: 8px 14px;
    border: 2px solid #101010;
    border-radius: 6px;
    background: var(--surface);
    color: var(--ink-link);
    font-family: Arial, sans-serif;
    font-size: 28px;
    font-weight: bold;
    line-height: 1.1;
    text-align: center;
    text-shadow: 1px 1px 0 rgba(0, 0, 0, 0.16);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.route-pill.is-empty {
    color: rgba(0, 0, 0, 0.38);
}

/* ── Rally: the waypoint ──
   Smaller than the two ends, because it is a place you pass through rather
   than a place the race is between — and it goes green once stamped, which is
   the only feedback the header ever gives. */
.route-pill--via {
    min-width: 180px;
    max-width: 260px;
    font-size: 20px;
    padding: 6px 12px;
}

.route-pill--via.is-stamped {
    border-color: #2e7d32;
    color: #2e7d32;
    background: color-mix(in srgb, #2e7d32 10%, var(--surface));
}

/* The target while the waypoint is still unstamped. Dimmed rather than hidden:
   you are allowed to walk over it, it just will not end anything yet. */
.route-pill.is-shut {
    opacity: 0.5;
    border-style: dashed;
}

.route-target-wrap {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 6px;
}

.route-target-desc {
    max-width: 360px;
    font-family: Arial, sans-serif;
    font-size: 13px;
    font-style: italic;
    color: var(--ink-muted);
    text-align: center;
    line-height: 1.35;
}

.route-to {
    color: var(--ink-strong);
    font-family: Arial, sans-serif;
    font-size: 30px;
    font-weight: bold;
    letter-spacing: 0;
    text-shadow: 2px 2px 0 rgba(176, 176, 255, 0.9);
}

.roulette-area {
    display: flex;
    align-items: center;
    justify-content: center;
    min-height: 280px;
    margin: 16px auto 24px;
}

.roulette-area[hidden],
.article-stage[hidden] {
    display: none;
}

.roulette-card {
    display: grid;
    place-items: center;
    width: min(760px, 100%);
    min-height: 240px;
    padding: 28px;
    border: 5px solid #f6d83f;
    border-radius: 8px;
    background:
        linear-gradient(135deg, rgba(255, 255, 255, 0.14), rgba(255, 255, 255, 0)),
        #55129a;
    box-shadow:
        0 8px 0 rgba(0, 0, 0, 0.36),
        inset 0 0 0 4px rgba(0, 0, 0, 0.22);
    color: #ffe45c;
    font-family: Arial, sans-serif;
    text-align: center;
}

.roulette-label {
    margin-bottom: 18px;
    color: #fff;
    font-size: 24px;
    font-weight: bold;
    text-shadow: 2px 2px 0 #000;
}

.roulette-title {
    min-height: 76px;
    color: #ffe45c;
    font-size: 58px;
    font-weight: bold;
    line-height: 1.05;
    text-shadow:
        -3px -3px 0 #000,
        3px -3px 0 #000,
        -3px 3px 0 #000,
        3px 3px 0 #000;
}

.roulette-title.is-ticking {
    animation: rouletteTick 55ms linear;
}

.roulette-title.is-selected {
    animation: rouletteSelected 420ms ease-out;
}

@keyframes rouletteTick {
    from {
        transform: translateY(-10px);
        opacity: 0.35;
    }

    to {
        transform: translateY(0);
        opacity: 1;
    }
}

@keyframes rouletteSelected {
    0% {
        transform: scale(1);
    }

    45% {
        transform: scale(1.09);
    }

    100% {
        transform: scale(1);
    }
}

/* The only way out of a race once the header has retreated. Kept quiet: it is a
   permanent fixture on screen during every race, including the frames a
   streamer broadcasts, so it reads as chrome rather than as a call to action. */
.training-quit {
    padding: 5px 12px;
    border: 2px solid rgba(0, 0, 0, 0.55);
    border-radius: 6px;
    background: color-mix(in srgb, var(--surface) 70%, transparent);
    color: var(--ink);
    font-family: Arial, sans-serif;
    font-size: 14px;
    font-weight: 700;
    cursor: pointer;
}

.training-quit:hover {
    background: #ffd9d9;
    border-color: #000;
}

.training-progress {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 18px;
    width: min(100%, 980px);
    min-height: 52px;
    margin: 0 auto 14px;
    padding: 10px 14px;
    border: 2px solid rgba(0, 0, 0, 0.34);
    border-radius: 7px;
    background: color-mix(in srgb, var(--surface) 88%, transparent);
    font-family: Arial, sans-serif;
    font-size: 18px;
}

.progress-trail {
    display: flex;
    flex-wrap: wrap;
    justify-content: flex-end;
    gap: 8px;
    color: var(--ink-link);
    font-weight: bold;
}

.progress-step {
    white-space: nowrap;
}

.article-stage {
    width: min(100%, 980px);
    margin: 0 auto;
}

/* Colour theme tokens (light default; dark + auto below). */
.article-stage,
.rc-scroll {
    --wiki-bg: #fff;
    --wiki-fg: #202122;          /* --color-base */
    --wiki-link: #36c;           /* --color-progressive */
    --wiki-border: #a2a9b1;      /* --border-color-base */
    --wiki-input-border: #72777d;
    --wiki-soft: #f8f9fa;        /* --background-color-neutral-subtle */
    --wiki-th: #eaecf0;          /* --background-color-neutral */
    --wiki-muted: #54595d;       /* --color-subtle */
    --wiki-burnt: #3a2f27;       /* charred link (Fahrenheit 451) */
}

/* Wikipedia "night" (skin-theme-clientpref-night) tokens. */
.article-stage[data-wiki-color="dark"],
.rc-scroll[data-wiki-color="dark"] {
    --wiki-bg: #101418;
    --wiki-fg: #eaecf0;
    --wiki-link: #88a3e8;
    --wiki-border: #54595d;
    --wiki-input-border: #72777d;
    --wiki-soft: #27292d;
    --wiki-th: #2a2d31;
    --wiki-muted: #a2a9b1;
    --wiki-burnt: #8d7f72;
}

@media (prefers-color-scheme: dark) {
    .article-stage[data-wiki-color="auto"],
    .rc-scroll[data-wiki-color="auto"] {
        --wiki-bg: #101418;
        --wiki-fg: #eaecf0;
        --wiki-link: #88a3e8;
        --wiki-border: #54595d;
        --wiki-input-border: #72777d;
        --wiki-soft: #27292d;
        --wiki-th: #2a2d31;
        --wiki-muted: #a2a9b1;
        --wiki-burnt: #8d7f72;
    }
}

/* ── Reading an article: it becomes the whole page, in every mode ──
   Fixed layer below the game header (so the header's Back button still works);
   the game HUD (route, racers, moves) floats on top. */

body.wiki-reading .bg-slide { opacity: 0 !important; }
body.wiki-reading .profile-btn,
body.wiki-reading .notif-layer,
body.wiki-reading .discord-btn { display: none; }

/* Drop the will-change containing block (it would trap the fixed article layer
   inside the mode screen) and strip the width caps between screen and article. */
body.wiki-reading .multiplayer-screen,
body.wiki-reading .training-screen,
body.wiki-reading .custom-screen,
body.wiki-reading .challenges-screen { will-change: auto; }
body.wiki-reading .training-container,
body.wiki-reading .grid-container { transform: none; }
body.wiki-reading .multiplayer-container { max-width: none; margin: 0; }
body.wiki-reading > main { max-width: none; width: 100%; padding: 0; }
body.wiki-reading .menu-stage { padding: 0; }
body.wiki-reading .mp-game { position: static; width: 100%; max-width: none; margin: 0; padding: 0; }
body.wiki-reading .mp-game-body { display: block; }

body.wiki-reading .article-stage {
    position: fixed;
    /* Full-bleed: the header and footer retreat when a race starts (see
       "Race start" below), so the article owns the whole viewport. */
    inset: 0;
    z-index: 300;
    width: 100vw;
    max-width: none;
    margin: 0;
    overflow-y: auto;
    overscroll-behavior: contain;
    background: var(--wiki-bg);
}

/* ── Race start: the chrome gets out of the way ───────────────────────────────
   The header pulls up, flinches — as if it catches itself halfway — then
   commits and leaves for good. The footer mirrors it downward. After this the
   player sees nothing but the Wikipedia article.

   Both bars are lifted above the article stage for the duration, because the
   stage is opaque and z-index 300: left at their usual 100 they would simply be
   covered on frame one and the retreat would never be seen.

   The hidden transform is declared OUTSIDE the animation on purpose. Potato
   mode and prefers-reduced-motion both kill animations outright, and the chrome
   still has to end up gone — with the end state on the rule itself, those users
   get the same result instantly instead of a bar stuck across the article. */
body.wiki-reading .mkwii-header,
body.wiki-reading .mkwii-footer {
    z-index: 340;
    pointer-events: none;
}

body.wiki-reading .mkwii-header {
    transform: translateY(-100%);
    animation: chromeRetreatUp 1800ms both;
}

body.wiki-reading .mkwii-footer {
    transform: translateY(100%);
    animation: chromeRetreatDown 1800ms both;
}

@keyframes chromeRetreatUp {
    /* pull away */
    0% { transform: translateY(0); animation-timing-function: cubic-bezier(.55, 0, .85, .25); }
    /* caught halfway */
    30% { transform: translateY(-64%); animation-timing-function: cubic-bezier(.2, .8, .3, 1); }
    /* the flinch: springs back toward where it was */
    44% { transform: translateY(-38%); animation-timing-function: ease-in-out; }
    /* settles, gathers itself */
    60% { transform: translateY(-46%); animation-timing-function: cubic-bezier(.6, 0, .85, .2); }
    /* and goes */
    100% { transform: translateY(-100%); }
}

@keyframes chromeRetreatDown {
    0% { transform: translateY(0); animation-timing-function: cubic-bezier(.55, 0, .85, .25); }
    30% { transform: translateY(64%); animation-timing-function: cubic-bezier(.2, .8, .3, 1); }
    44% { transform: translateY(38%); animation-timing-function: ease-in-out; }
    60% { transform: translateY(46%); animation-timing-function: cubic-bezier(.6, 0, .85, .2); }
    100% { transform: translateY(100%); }
}

/* The race HUD: two article pills and the arrow between them, flush to the top
   of the screen, and nothing else.

   Flush to y=0 — that band belongs to the route, which is why .site-logo now
   leaves for the duration of a race (see "Race start" above).

   No banner: the frame the pill wears everywhere else — border, gradient fill,
   inset outline, drop shadow — and its 74px floor all come off, so what sits on
   the article is the three things the player is actually reading.

   This is deliberately written for BOTH of the route's homes, so the two are
   indistinguishable. The element is stowed out of the stage and re-adopted on
   every single move (its parent's innerHTML is replaced each time), and any
   difference between the two states shows up as a flicker on every click. */
body.wiki-reading .training-route {
    position: fixed;
    top: 0;
    left: 0;
    z-index: 320;
    box-sizing: border-box;
    width: 100%;
    max-width: none;
    min-height: 0;
    margin: 0;
    /* Left padding is the wordmark's corner (see --logo-clear). The route stays
       flush to the top and simply starts after the mark instead of under it. */
    padding: 8px 18px 12px var(--logo-clear);
    transform: none;
    border: 0;
    border-radius: 0;
    background: none;
    box-shadow: none;
}

/* Under ~1100px there is no longer room for both across one line: the pills
   need about 670px between them and the wordmark is eating 314 of it. Below
   that the route stops sharing the row and drops under the wordmark's band
   instead — still the top of the article, just the second line of it. */
@media (max-width: 1100px) {
    body.wiki-reading .training-route {
        margin-top: var(--chrome-h);
        padding-left: 18px;
    }
}

/* Adopted into the stage, it drops out of the viewport and joins the article's
   flow, so it scrolls away instead of covering the text. Pinned, it sat over
   the first ~190px of every article — exactly where the lead paragraph and its
   first links are — and players said so. The stage is the scroll container (JS
   resets its scrollTop on every move), which is why "scrolls away" has to mean
   "lives in the stage's flow" rather than "let the page scroll". Everything
   else is inherited from the shared rule above. */
body.wiki-reading .article-stage > .training-route {
    position: static;
}

/* The target's one-line gloss ("Sport of shooting arrows"), under the target
   pill. It follows the article's own muted ink rather than the menu's, since
   with the banner gone it sits straight on the page; the fallback covers the
   stowed state, where --wiki-* is out of scope. */
body.wiki-reading .training-route .route-target-desc {
    color: var(--wiki-muted, #54595d);
}

/* The target pill and its gloss are a COLUMN inside the route's row, and that
   is a trap: the narrow-width `.route-pill { flex: 1 1 0 }` — written for the
   row — also matches the pill in here, where the main axis is vertical, so the
   basis resolved against its HEIGHT and collapsed the target to 20px, cutting
   the article name in half. (Live on phones since 8abe22e, under the banner.)
   Pinning the pill to its own content height is what stops that; stretch makes
   it span the column so the gloss centres under a full-width pill instead of a
   text-width one. */
body.wiki-reading .training-route .route-target-wrap {
    align-items: stretch;
}

body.wiki-reading .training-route .route-target-wrap .route-pill {
    flex: 0 0 auto;
}

/* The arrow has no pill behind it, so it sits straight on the article and has
   to follow the article's theme: near-black #000 on Wikipedia's night palette
   was all but invisible. --wiki-fg is the same token the article body uses, so
   the arrow flips with light/dark/auto for free. The lavender drop shadow goes
   with it — it read as an outline against the banner and as mush against a
   dark page. The fallback covers the stowed state, where the route sits outside
   the stage and --wiki-fg is not in scope to inherit. */
body.wiki-reading .training-route .route-to {
    color: var(--wiki-fg, #202122);
    text-shadow: none;
}

body.wiki-reading .training-progress {
    position: fixed;
    bottom: 12px;
    left: 12px;
    /* Shrink to its contents while racing. At the base 980px it stretched most
       of the way across the foot of the article for the sake of one "Moves: 3",
       sitting on top of the wallpaper attribution. */
    width: auto;
    margin: 0;
    padding: 6px 14px;
    border: 2px solid #000;
    border-radius: 8px;
    background: color-mix(in srgb, var(--surface) 94%, transparent);
    box-shadow: 0 4px 0 rgba(0, 0, 0, 0.2);
    z-index: 320;
}

body.wiki-reading .progress-trail,
body.wiki-reading .roulette-area { display: none; }

body.wiki-reading .mp-side {
    position: fixed;
    bottom: 12px;
    right: 12px;
    width: 220px;
    max-height: 48vh;
    overflow-y: auto;
    z-index: 320;
}

/* ── Wikipedia skin: Contents | article | Appearance ── */
.wiki-skin {
    display: grid;
    grid-template-columns: 220px minmax(0, 1fr) 232px;
    gap: 0 26px;
    align-items: start;
    min-height: 100%;
    box-sizing: border-box;
    padding: 74px 26px 90px;
    background: var(--wiki-bg);
    color: var(--wiki-fg);
    font-family: sans-serif;
}

.wiki-toc,
.wiki-appearance {
    position: sticky;
    top: 12px;
    align-self: start;
    max-height: calc(100vh - 120px);
    overflow-y: auto;
    font-size: 13px;
    color: var(--wiki-fg);
}

.wiki-toc-head,
.wiki-app-head {
    display: flex;
    align-items: center;
    gap: 6px;
    margin-bottom: 8px;
    padding-bottom: 6px;
    border-bottom: 1px solid var(--wiki-border);
    font-weight: bold;
}

.wiki-toc-toggle,
.wiki-app-toggle {
    margin-left: auto;
    background: none;
    border: 0;
    padding: 0;
    color: var(--wiki-link);
    font-size: 12px;
    cursor: pointer;
}

.wiki-toc-list { list-style: none; margin: 0; padding: 0; }

.wiki-toc-item button {
    display: block;
    width: 100%;
    padding: 4px 8px;
    border: 0;
    border-left: 2px solid transparent;
    background: none;
    color: var(--wiki-fg);
    font: inherit;
    text-align: left;
    cursor: pointer;
}

.wiki-toc-item button:hover {
    border-left-color: var(--wiki-link);
    color: var(--wiki-link);
    background: rgba(128, 128, 128, 0.12);
}

.wiki-toc-item.is-sub button { padding-left: 22px; font-size: 12.5px; color: var(--wiki-muted); }
.wiki-toc-item.is-top button { font-weight: bold; }

.wiki-skin.is-toc-collapsed .wiki-toc-body,
.wiki-skin.is-app-collapsed .wiki-app-body { display: none; }

.wiki-app-group { margin-bottom: 16px; }

.wiki-app-label {
    margin-bottom: 4px;
    font-size: 14px;
    font-weight: bold;
    color: var(--wiki-fg);
}

/* Codex radio: circle + inner dot, exactly like Wikipedia's Appearance menu. */
.wiki-app-opt {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 5px 4px;
    cursor: pointer;
    font-size: 14px;
    color: var(--wiki-fg);
    border-radius: 2px;
}

.wiki-app-opt:hover { background: rgba(128, 128, 128, 0.08); }

.wiki-app-opt input {
    position: absolute;
    width: 1px;
    height: 1px;
    opacity: 0;
    margin: 0;
    pointer-events: none;
}

.wiki-radio {
    position: relative;
    flex: 0 0 auto;
    width: 18px;
    height: 18px;
    border: 1px solid var(--wiki-input-border, #72777d);
    border-radius: 50%;
    background: var(--wiki-bg);
    box-sizing: border-box;
    transition: border-color 100ms ease, box-shadow 100ms ease;
}

.wiki-app-opt:hover .wiki-radio { border-color: var(--wiki-link); }

.wiki-app-opt input:checked ~ .wiki-radio { border-color: var(--wiki-link); }

.wiki-app-opt input:checked ~ .wiki-radio::after {
    content: "";
    position: absolute;
    inset: 3px;
    border-radius: 50%;
    background: var(--wiki-link);
}

.wiki-app-opt input:focus-visible ~ .wiki-radio {
    box-shadow: 0 0 0 2px var(--wiki-bg), 0 0 0 4px var(--wiki-link);
}

.wiki-page { min-width: 0; }

/* CC BY-SA attribution + non-affiliation line under every article. */
.wiki-attrib {
    margin-top: 34px;
    padding-top: 10px;
    border-top: 1px solid var(--wiki-border);
    font-size: 12px;
    line-height: 1.6;
    color: color-mix(in srgb, var(--wiki-fg) 62%, transparent);
}

.wiki-attrib a {
    color: var(--wiki-link);
    text-decoration: none;
}
/* Width setting, done the way Vector 2022 does it: "Standard" caps and centres
   the WHOLE three-column layout, so the rails sit next to the article.

   It used to cap only the middle column (max-width: 820px) while the grid track
   itself stayed 1fr — so on a wide screen the track grew to ~1360px, the article
   used 820 of it, and the leftover ~540px opened as dead space between the
   article and the Appearance rail. Capping the container instead means the
   middle track is exactly the space that's left: no slack, and a ~960px article
   column rather than 820px. */
.wiki-skin[data-width="standard"] {
    max-width: 1520px;   /* 26 + 220 + 26 + ~964 + 26 + 232 + 26 */
    margin-inline: auto;
}

.wiki-skin[data-width="standard"] .wiki-page,
.wiki-skin[data-width="wide"] .wiki-page { max-width: none; }

.wiki-skin[data-text="small"] .wiki-content { font-size: 13px; }
.wiki-skin[data-text="standard"] .wiki-content { font-size: 14.4px; }
.wiki-skin[data-text="large"] .wiki-content { font-size: 16.5px; }

/* ── Phone layout ─────────────────────────────────────────────────────────
   Wikipedia serves the Minerva skin at this width, which has neither Vector
   rail. Ours kept the desktop 220px | 1fr | 232px grid at every width, so on
   a ~390px phone the fixed tracks alone (220 + 232 + 52 gap + 52 padding =
   556px) outran the viewport and `minmax(0, 1fr)` collapsed the article
   column — the one part the player actually has to read — to a sliver.

   The stage stays the scroll container: JS resets `stage.scrollTop` on every
   move, so letting the document scroll instead would break the jump-to-top
   between articles. That means the HUD is placed relative to the stage, not
   the page. The route now scrolls away at every width (see the .article-stage >
   .training-route rule above); here the item slot is the one thing still pinned
   to the viewport. */
@media (max-width: 768px) {
    .wiki-toc,
    .wiki-appearance { display: none; }

    .wiki-skin {
        grid-template-columns: minmax(0, 1fr);
        gap: 0;
        /* The bottom pad clears the pinned item bar. Without it the article's
           last links sit underneath it and can't be tapped. */
        padding: 12px 14px 104px;
    }

    .wiki-skin .wiki-page { padding: 0; }

    .wiki-title { font-size: 26px; }

    /* Tighter side padding for the route; the rest of it is shared. Both of its
       homes, so a stowed loading state matches the article that follows. */
    body.wiki-reading .training-route { padding: 8px 12px 10px; }

    /* The target column takes the row space its pill would have taken, so the
       two sides stay symmetric once .route-pill starts flexing at this width. */
    body.wiki-reading .training-route .route-target-wrap {
        flex: 1 1 0;
        min-width: 0;
    }

    /* The side rail stops floating and just follows the article. */
    body.wiki-reading .mp-side {
        position: static;
        width: auto;
        max-height: none;
        margin: 0 14px 104px;
        overflow-y: visible;
    }

    /* …all except the item, which is the one control that has to stay in
       reach no matter how far down the article you are. */
    body.wiki-reading .mp-side-block--item {
        position: fixed;
        left: 0;
        right: 0;
        bottom: 0;
        margin: 0;
        padding: 8px 12px calc(8px + env(safe-area-inset-bottom, 0px));
        border-top: 3px solid #000;
        background: var(--surface);
        z-index: 330;
    }
}

/* ── The article body (page) ── */
.wiki-article {
    color: var(--wiki-fg);
    font-family: sans-serif;
    font-size: 14.4px;
    line-height: 1.6;
    text-align: left;
}

.wiki-title {
    margin: 0 0 4px;
    padding-bottom: 4px;
    border-bottom: 1px solid var(--wiki-border);
    font-family: "Linux Libertine", "Georgia", "Times", serif;
    font-size: 28px;
    font-weight: normal;
    line-height: 1.3;
}

.training-complete {
    display: grid;
    gap: 4px;
    margin-bottom: 14px;
    padding: 14px 18px;
    border: 3px solid #000;
    border-radius: 7px;
    background: #ffe45c;
    color: var(--ink);
    font-family: Arial, sans-serif;
    font-size: 20px;
    box-shadow: 0 5px 0 rgba(0, 0, 0, 0.22);
}

.training-complete strong {
    font-size: 26px;
}

.article-loading {
    padding: 40px 24px;
    border: 2px solid #a2a9b1;
    border-radius: 3px;
    background: color-mix(in srgb, var(--surface) 96%, transparent);
    color: #54595d;
    font-family: Arial, sans-serif;
    font-size: 20px;
    text-align: center;
    box-shadow: 0 8px 0 rgba(0, 0, 0, 0.22);
}

.article-error {
    display: grid;
    gap: 6px;
    padding: 20px 24px;
    border: 3px solid #d33;
    border-radius: 7px;
    background: #fff8f8;
    color: var(--ink-mid);
    font-family: Arial, sans-serif;
    font-size: 18px;
    box-shadow: 0 4px 0 rgba(0, 0, 0, 0.14);
}

.article-error strong {
    color: #d33;
    font-size: 20px;
}

.wiki-content {
    margin-top: 12px;
    /* Contain floated infoboxes/thumbnails within the article page. */
    display: flow-root;
}

/* Headings (Parsoid wraps them in .mw-heading). */
.wiki-content h2,
.wiki-content .mw-heading2 {
    margin: 24px 0 8px;
    padding-bottom: 4px;
    border-bottom: 1px solid var(--wiki-border);
    font-family: "Linux Libertine", "Georgia", "Times", serif;
    font-size: 22px;
    font-weight: normal;
    line-height: 1.3;
}

.wiki-content h3,
.wiki-content .mw-heading3 { margin: 18px 0 6px; font-size: 17px; font-weight: bold; }
.wiki-content h4,
.wiki-content .mw-heading4 { margin: 14px 0 6px; font-size: 15px; font-weight: bold; }
.wiki-content .mw-heading { border: 0; }

.wiki-content p { margin: 0.5em 0; }

.wiki-content ul,
.wiki-content ol { margin: 0.3em 0 0.6em 1.6em; }

.wiki-content li { margin-bottom: 0.1em; }

.wiki-content dl { margin: 0.2em 0; }
.wiki-content dd { margin-left: 1.6em; }

.wiki-content b,
.wiki-content strong { font-weight: bold; }

/* Links: Wikipedia blue, underline only on hover. */
.wiki-link-inline {
    appearance: none;
    border: 0;
    background: transparent;
    color: var(--wiki-link);
    cursor: pointer;
    font: inherit;
    text-align: left;
    text-decoration: none;
    padding: 0;
    margin: 0;
    line-height: inherit;
    /* Subtle glow in the link's own text color (follows theme overrides). */
    text-shadow: 0 0 6px color-mix(in srgb, currentColor 45%, transparent);
}

.wiki-link-inline:hover { text-decoration: underline; }
.wiki-link-inline:active { color: #faa700; }

/* Non-navigable links (File:, citations, external) read as plain text. */
.wiki-link-dead {
    color: inherit;
    text-decoration: none;
}

/* Images. */
.wiki-content img {
    max-width: 100%;
    height: auto;
}

.wiki-content figure,
.wiki-content .thumb {
    margin: 4px 0 14px;
    font-size: 13.6px;
}

.wiki-content figure[typeof*="mw:File/Thumb"],
.wiki-content figure.mw-default-size,
.wiki-content .tright,
.wiki-content .mw-halign-right {
    float: right;
    clear: right;
    margin: 6px 0 12px 18px;
}

.wiki-content .tleft,
.wiki-content .mw-halign-left {
    float: left;
    clear: left;
    margin: 6px 18px 12px 0;
}

.wiki-content figure img,
.wiki-content .thumbinner img {
    border: 1px solid var(--wiki-border);
    background: var(--surface);
}

.wiki-content figure,
.wiki-content .thumbinner {
    max-width: 320px;
    padding: 3px;
    border: 1px solid var(--wiki-border);
    background: var(--wiki-soft);
}

.wiki-content figcaption,
.wiki-content .thumbcaption {
    padding: 4px 2px 2px;
    color: var(--wiki-muted);
    line-height: 1.4;
    text-align: left;
}

/* Infoboxes. */
.wiki-content .infobox {
    float: right;
    clear: right;
    width: 22em;
    max-width: 45%;
    margin: 4px 0 16px 18px;
    padding: 0;
    border: 1px solid var(--wiki-border);
    border-spacing: 3px;
    background: var(--wiki-soft);
    font-size: 12.6px;
    line-height: 1.5;
}

.wiki-content .infobox caption,
.wiki-content .infobox-title,
.wiki-content .infobox-above {
    padding: 6px 8px;
    font-size: 15px;
    font-weight: bold;
    text-align: center;
}

.wiki-content .infobox th,
.wiki-content .infobox td { padding: 4px 8px; vertical-align: top; text-align: left; }
.wiki-content .infobox-image,
.wiki-content .infobox td[colspan] { text-align: center; }

/* ── Templates that ship their own CSS ────────────────────────────────────────
   Wikipedia articles don't carry their layout in HTML: they carry it in
   TemplateStyles, per-template stylesheets delivered inside the page. The Cat
   article alone ships 21 <style> blocks and 327 more by reference. Every one of
   them is removed by WIKI_STRIP_SELECTOR (script.js) — the right call for
   third-party CSS, but it leaves template markup with no layout at all.

   So the templates that actually matter are reproduced here, using Wikipedia's
   own selectors so the markup needs no rewriting. Frequencies measured across
   20 varied articles: thumbnails 18/20, infoboxes 15/20, multi-image 12/20,
   galleries 7/20, maths 6/20. Everything past that is long tail — it degrades
   to plainly-styled content rather than breaking. */

/* Infoboxes carry an inline width (Cat's says 200px) that beats any ordinary
   rule, and it's narrower than the image grid inside them — so the grid
   overflows and the photos spill down the page. Hence !important. */
.wiki-content .infobox {
    width: 22em !important;
    max-width: 45%;
}

/* {{Multiple image}} — the "various types of cats" grid. Each .trow holds one
   row's images; without the flex every image takes its own line. */
.wiki-content .tmulti .multiimageinner { max-width: 100%; }

.wiki-content .tmulti .trow {
    display: flex;
    flex-direction: row;
    flex-wrap: wrap;
    clear: left;
    width: 100%;
    box-sizing: border-box;
}

.wiki-content .tmulti .tsingle { margin: 1px; }
.wiki-content .tmulti .tsingle img { display: block; max-width: 100%; height: auto; }
.wiki-content .tmulti .thumbcaption { background: transparent; text-align: center; }

/* Galleries: a plain <ul> until told otherwise, which stacks one item per row. */
.wiki-content ul.gallery {
    display: flex;
    flex-wrap: wrap;
    gap: 6px;
    margin: 12px 0;
    padding: 0;
    list-style: none;
}

.wiki-content li.gallerybox { list-style: none; }
.wiki-content li.gallerybox div.thumb { margin: 0; background: transparent; }
.wiki-content .gallerytext { font-size: 12.6px; text-align: center; }

/* Maths and chemistry. The MathML is hidden by an inline style and the visible
   form is an SVG, so formulae survive the strip — but they're black artwork,
   and Wikipedia flips them for dark mode with .skin-invert, which does not.
   Without this every equation is black-on-near-black. */
.wiki-content .mwe-math-element { max-width: 100%; overflow-x: auto; }
.wiki-content .mwe-math-fallback-image-display { display: block; margin: 12px auto; }

.article-stage[data-wiki-color="dark"] .wiki-content img.mw-invert,
.article-stage[data-wiki-color="dark"] .wiki-content img.skin-invert,
.rc-scroll[data-wiki-color="dark"] .wiki-content img.mw-invert,
.rc-scroll[data-wiki-color="dark"] .wiki-content img.skin-invert {
    filter: invert(1) hue-rotate(180deg);
}

@media (prefers-color-scheme: dark) {
    .article-stage[data-wiki-color="auto"] .wiki-content img.mw-invert,
    .article-stage[data-wiki-color="auto"] .wiki-content img.skin-invert,
    .rc-scroll[data-wiki-color="auto"] .wiki-content img.mw-invert,
    .rc-scroll[data-wiki-color="auto"] .wiki-content img.skin-invert {
        filter: invert(1) hue-rotate(180deg);
    }
}

/* Hatnotes / disambig / "for other uses". */
.wiki-content .hatnote,
.wiki-content .dablink,
.wiki-content .rellink {
    margin: 0 0 8px;
    padding-left: 1.6em;
    color: var(--wiki-fg);
    font-style: italic;
}

/* Tables. */
.wiki-content table.wikitable {
    margin: 12px 0;
    border: 1px solid var(--wiki-border);
    border-collapse: collapse;
    background: var(--wiki-soft);
    font-size: 13.6px;
}

.wiki-content table.wikitable > * > tr > th {
    border: 1px solid var(--wiki-border);
    padding: 4px 8px;
    background: var(--wiki-th);
    text-align: center;
    font-weight: bold;
}

.wiki-content table.wikitable > * > tr > td {
    border: 1px solid var(--wiki-border);
    padding: 4px 8px;
}

/* hlist — Wikipedia's horizontal list. Every navbox row is one, and so are
   parts of many infoboxes. It is pure TemplateStyles, which we strip, and
   without it each <li> falls back to list-item: the Volta–Niger navbox laid its
   89 links out one per line and stood 1919px tall. Interpuncts and the
   parentheses around a nested list are how the real thing separates them. */
.wiki-content .hlist ul {
    margin: 0;
    padding: 0;
    list-style: none;
}

.wiki-content .hlist li {
    display: inline;
    margin: 0;
}

.wiki-content .hlist li:not(:last-child)::after {
    content: " · ";
    font-weight: bold;
}

.wiki-content .hlist ul ul { display: inline; }
.wiki-content .hlist ul ul::before { content: "("; }
.wiki-content .hlist ul ul::after { content: ")"; }

/* Navboxes. Wikipedia styles these from TemplateStyles, which ships inside the
   article as a <style> tag we strip on sight — so without this they render as a
   bare stack of links with no structure at all. Rebuilt from the same tokens as
   the tables above, so they follow the light/dark/auto setting like everything
   else. Kept for the links: on a stub the navbox is usually the only way out. */
.wiki-content .navbox {
    clear: both;
    margin: 12px 0;
    border: 1px solid var(--wiki-border);
    background: var(--wiki-soft);
    font-size: 12.6px;
    text-align: center;
}

.wiki-content .navbox-title,
.wiki-content .navbox-abovebelow,
.wiki-content th.navbox-group {
    padding: 3px 10px;
    background: var(--wiki-th);
    font-weight: bold;
}

.wiki-content .navbox-title { font-size: 13.6px; }

/* The group label column: narrow, right-aligned, against the row it labels. */
.wiki-content th.navbox-group {
    white-space: nowrap;
    text-align: right;
}

.wiki-content .navbox-list {
    padding: 3px 10px;
    text-align: left;
}

/* Alternating row wash, the same trick the real navbox uses to separate lists
   that would otherwise run together. */
.wiki-content .navbox-odd { background: transparent; }
.wiki-content .navbox-even { background: var(--wiki-th); }

/* Nested navboxes sit inside a parent's list cell; no double frame. */
.wiki-content .navbox .navbox {
    margin: 0;
    border: 0;
}

/* v-t-e. Every link in it points at Template:, Template talk: or Special:, none
   of which are moves, so the whole thing is dead text here — and the stub
   notice carries one too, which without this hangs under the article as a bare
   "v·t·e" that looks like a rendering fault. */
.wiki-content .navbar {
    display: none;
}

/* References / smaller print. */
.wiki-content .reflist,
.wiki-content .references,
.wiki-content ol.references { font-size: 12.6px; }

.wiki-content sup,
.wiki-content sub { line-height: 1; font-size: 78%; }

.wiki-content blockquote {
    margin: 8px 0 8px 24px;
    padding-left: 12px;
    border-left: 3px solid #eaecf0;
    color: #202122;
}

/* ===========================
   FOOTER
   =========================== */

.mkwii-footer {
    position: fixed;
    bottom: 0;
    left: 0;
    right: 0;
    width: 100%;
    height: var(--chrome-h);
    overflow: hidden;
    border-top: 4px solid var(--accent-soft, #b0b0ff);
    flex-shrink: 0;
    z-index: 100;
}

.footer-bg {
    position: absolute;
    inset: 0;
}

.footer-bg::after {
    content: "";
    position: absolute;
    inset: 0;
    background: repeating-linear-gradient(var(--theme-wash, #fefefe),
            var(--theme-wash, #fefefe) 3px,
            var(--theme-wash2, #f2f2f2) 3px,
            var(--theme-wash2, #f2f2f2) 6px);
}

/* Same derivation as .checkerboard above; 3.33 is what 300px was against the
   original 90px footer, so the two bands keep their slightly different widths. */
.footer-checkerboard {
    position: absolute;
    top: 0;
    right: 0;
    width: calc(var(--chrome-h) * 3.33);
    height: 100%;
    opacity: 0.85;
    background-image: conic-gradient(
        var(--accent, #696bb6) 0deg 90deg,
        transparent 90deg 180deg,
        var(--accent, #696bb6) 180deg 270deg,
        transparent 270deg 360deg);
    background-size: calc(var(--chrome-h) / 2) calc(var(--chrome-h) / 2);
    background-position: bottom right;
    -webkit-mask-image: linear-gradient(to right, transparent, #000 72%);
    mask-image: linear-gradient(to right, transparent, #000 72%);
}

/* ===========================
   RESPONSIVE DESIGN
   =========================== */

@media (max-width: 1400px) {
    .grid-container {
        grid-template-columns: repeat(3, 1fr);
        gap: 40px 70px;
    }
}

@media (max-width: 1200px) {
    .grid-container {
        gap: 30px 60px;
    }
}

@media (max-width: 992px) {
    .grid-container {
        grid-template-columns: repeat(2, 1fr);
        gap: 30px 50px;
    }

    .route-pill {
        min-width: 210px;
        font-size: 24px;
    }
}

@media (max-width: 768px) {
    .grid-container {
        grid-template-columns: 1fr;
        gap: 25px;
    }

    body > main {
        padding: 15px;
    }

    .menu-stage {
        padding: 80px 0;
    }

    .training-container {
        max-width: calc(100vw - 24px);
    }

    .training-route {
        top: 66px;
        gap: 10px;
        padding: 8px;
    }

    .route-pill {
        min-width: 0;
        flex: 1 1 0;
        font-size: 18px;
    }

    .route-to {
        flex: 0 0 auto;
        font-size: 18px;
    }

    .roulette-card {
        min-height: 210px;
        padding: 20px;
    }

    .roulette-title {
        font-size: 42px;
    }

    .training-progress,
    .wiki-status {
        align-items: flex-start;
        flex-direction: column;
    }

    .progress-trail {
        justify-content: flex-start;
    }

    .wiki-article {
        padding: 22px;
    }

    .wiki-article h1 {
        font-size: 34px;
    }
}

/* ── Phone, part two: full-bleed text and no sideways scroll ───────────────
   Placed after the .wiki-content rules above so it wins on order rather than
   on a pile of !important — the infobox is the one exception, because its own
   width is already !important (see the {{Infobox}} note above).

   The stage is `overflow-y: auto`, and CSS resolves the other axis of a
   scroll container to `auto` too, so anything wider than the viewport gave
   the article its own horizontal scrollbar. Clipping that axis is only half
   an answer — on its own it turns overflow into content you cannot reach at
   all — so everything below is about making the wide things genuinely fit,
   with the clip as a backstop rather than the mechanism. */
@media (max-width: 768px) {
    /* Corner furniture: gone. A phone has no room for it beside the article,
       and Discord is a tap-through to somewhere else entirely. */
    .discord-btn,
    .friends-btn { display: none; }

    .notif-layer { right: 16px; bottom: 92px; }

    /* Edge to edge — on a screen this narrow the side gutters were costing
       more in reading width than they bought in comfort. */
    .wiki-skin { padding: 12px 0 104px; }
    .wiki-skin .wiki-page { padding: 0; }
    .wiki-title,
    .wiki-content,
    .wiki-attrib { padding-inline: 0; }

    /* Belt to the braces below: clip whatever still manages to be too wide. */
    body.wiki-reading .article-stage { overflow-x: hidden; }

    /* The menu screens have their own leak. Letting the card menu scroll
       means the fit valve resets the body to `overflow: visible`, and that
       also undoes the base `overflow-x: clip` — so a card a few pixels too
       wide could drag the whole menu sideways. `clip` rather than `hidden`:
       hidden on one axis makes the other a scroll container, which would
       take the page's vertical scrolling with it. */
    body.menu-fit.menu-fit-overflow:not(.wiki-reading) { overflow-x: clip; }

    /* 1. The infobox. Floated right at 45%, it squeezed the lead paragraphs
       into barely half a phone's width — the main reason the text did not run
       side to side. Full width and out of the flow of the prose instead, the
       way Minerva stacks it. */
    .wiki-content .infobox {
        float: none;
        clear: both;
        width: 100% !important;
        max-width: 100% !important;
        margin: 12px 0;
    }

    /* 2. Wide tables — EVERY table, not just .wikitable. Scoping this to that
       class was the bug behind the cropping: real articles barely use it.
       Fetched through our own proxy, Cat has 112 tables and 0 wikitables,
       Chess 40 and 0, Telephone 2 and 0. So the rule matched almost nothing,
       and everything it missed was clipped by the overflow-x above instead of
       being scrollable. A table cannot be told to wrap, so each becomes its
       own scroll box: the table slides, the page does not. */
    .wiki-content table {
        display: block;
        width: max-content;
        max-width: 100%;
        overflow-x: auto;
    }

    /* 3. Hard-coded pixel widths, which Wikipedia's markup is full of — 384
       inline ones in Chess, 488 in Periodic table. An inline `width` beats any
       stylesheet `width`, but not `max-width`: that clamps the used value
       afterwards, so the whole class of them is reined in without needing
       !important on each. */
    .wiki-content img,
    .wiki-content table,
    .wiki-content figure,
    .wiki-content div,
    .wiki-content pre,
    .wiki-content blockquote { max-width: 100%; }

    /* 3b. Clamping a floated figure to max-width:100% does not stop it
       floating: it fills the column and leaves the paragraph beside it a gutter
       a few pixels wide to wrap in, one letter per line. Measured on Combustion
       at 500px: figures 449-467px in a 485px column. Give them their own row. */
    .wiki-content figure[typeof*="mw:File/Thumb"],
    .wiki-content figure.mw-default-size,
    .wiki-content .tright,
    .wiki-content .mw-halign-right,
    .wiki-content .tleft,
    .wiki-content .mw-halign-left {
        float: none;
        margin-left: 0;
        margin-right: 0;
    }

    /* 4. Unbreakable strings: long URLs in references, chemical names, the
       German ones. */
    .wiki-content { overflow-wrap: break-word; }

    /* Image rows and galleries lay out with flex and would otherwise run off. */
    .wiki-content .tmulti .trow,
    .wiki-content ul.gallery { flex-wrap: wrap; }
}

/* Shorter viewports (e.g. 4:3 monitors) — nothing above reacts to height,
   so the fixed header/footer and menu padding ate too much vertical space. */
@media (max-height: 820px) {
    /* Band height is the only knob — the checkerboards' width and square size
       follow it, so they shrink whole rather than losing rows. */
    :root { --chrome-h: 60px; --logo-clear: 222px; }  /* wordmark 48px -> ends at 208 */

    .menu-stage {
        padding: 50px 0;
    }
}

@media (max-height: 620px) {
    :root { --chrome-h: 46px; --logo-clear: 176px; }  /* wordmark 36px -> ends at 162 */

    .menu-stage {
        padding: 30px 0;
    }

    .back-button {
        width: 48px;
        height: 38px;
    }
}

/* ===========================
   MODE SELECTION
   =========================== */

.training-mode-select {
  width: 100%;
  padding: 16px 0 80px;
  will-change: transform, opacity;
}

.training-container.is-exiting .training-mode-select {
  animation: trainingScreenOutLeft 260ms cubic-bezier(0.7, 0, 1, 0.35) forwards;
}

.training-container.is-entering .training-mode-select {
  animation: trainingScreenInRight 260ms cubic-bezier(0.04, 0.82, 0.18, 1) backwards;
}

.training-container.is-exiting-back .training-mode-select {
  animation: trainingScreenOutRight 260ms cubic-bezier(0.7, 0, 1, 0.35) forwards;
}

.training-container.is-entering-back .training-mode-select {
  animation: trainingScreenInLeft 260ms cubic-bezier(0.04, 0.82, 0.18, 1) backwards;
}

/* The four difficulty bars. Shape and layout come from .card-bars in the CARD
   BARS section, which this grid carries too; what is left here is the entrance
   animation and the per-mode colours.

   The animation is declared rather than inherited because this stack lives
   inside .training-container, not .grid-container, so it never gets the
   is-entering/is-exiting card animations the menu screens have. */
.mode-select-grid .glass {
  animation: cardSlideInRight 260ms cubic-bezier(0.04, 0.82, 0.18, 1) backwards;
  animation-delay: var(--row-delay);
}

/* Per-mode card color variants */
.mode-card--easy .blur {
  background: rgba(0, 200, 80, 0.534);
}

.mode-card--easy .blue-card::before {
  background:
    radial-gradient(circle at 0% 0%, #0d8f3f 0%, transparent 50%),
    radial-gradient(circle at 100% 0%, #00c840 0%, transparent 50%),
    radial-gradient(circle at 0% 100%, #00c840 0%, transparent 50%),
    radial-gradient(circle at 100% 100%, #0d8f3f 0%, transparent 50%),
    linear-gradient(135deg, #0d8f3f, #00c840);
}

.mode-card--hard .blur {
  background: rgba(255, 140, 0, 0.534);
}

.mode-card--hard .blue-card::before {
  background:
    radial-gradient(circle at 0% 0%, #b85000 0%, transparent 50%),
    radial-gradient(circle at 100% 0%, #e07000 0%, transparent 50%),
    radial-gradient(circle at 0% 100%, #e07000 0%, transparent 50%),
    radial-gradient(circle at 100% 100%, #b85000 0%, transparent 50%),
    linear-gradient(135deg, #b85000, #e07000);
}

.mode-card--fully-random .blur {
  background: rgba(160, 0, 240, 0.534);
}

.mode-card--fully-random .blue-card::before {
  background:
    radial-gradient(circle at 0% 0%, #6600b8 0%, transparent 50%),
    radial-gradient(circle at 100% 0%, #9800e0 0%, transparent 50%),
    radial-gradient(circle at 0% 100%, #9800e0 0%, transparent 50%),
    radial-gradient(circle at 100% 100%, #6600b8 0%, transparent 50%),
    linear-gradient(135deg, #6600b8, #9800e0);
}

/* .goldframe3-text and .mode-card-desc lived here to style the description
   inside each card's gold frame. Both went with the frame; the stack has no
   text but the name. The narrow-screen tightening moved to .card-bars with the
   rest of the shape. */

/* ===========================
   SETTINGS MODE
   =========================== */

.settings-container {
    display: block;
    max-width: min(900px, calc(100vw - 40px));
    margin: 0 auto;
}

.settings-screen {
    width: 100%;
    padding: 16px 0 120px;
    will-change: transform, opacity;
}

.settings-container.is-exiting .settings-screen {
    animation: trainingScreenOutLeft 260ms cubic-bezier(0.7, 0, 1, 0.35) forwards;
}

.settings-container.is-entering .settings-screen {
    animation: trainingScreenInRight 260ms cubic-bezier(0.04, 0.82, 0.18, 1) backwards;
}

.settings-container.is-exiting-back .settings-screen {
    animation: trainingScreenOutRight 260ms cubic-bezier(0.7, 0, 1, 0.35) forwards;
}

.settings-container.is-entering-back .settings-screen {
    animation: trainingScreenInLeft 260ms cubic-bezier(0.04, 0.82, 0.18, 1) backwards;
}

.settings-list {
    display: flex;
    flex-direction: column;
    gap: 16px;
}

.settings-row {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 24px;
    padding: 20px 28px;
    border: 3px solid #000;
    border-radius: 7px;
    background: linear-gradient(180deg, var(--panel-top), var(--panel-bottom));
    box-shadow:
        0 6px 0 rgba(0, 0, 0, 0.24),
        inset 0 0 0 3px rgba(176, 176, 255, 0.55);
    transition: opacity 120ms;
}

.settings-row.is-disabled {
    opacity: 0.4;
    pointer-events: none;
}

.settings-info {
    display: flex;
    flex-direction: column;
    gap: 5px;
}

.settings-label {
    font-family: Arial, sans-serif;
    font-weight: bold;
    font-size: 22px;
    color: var(--ink);
    text-shadow: 1px 1px 0 rgba(176, 176, 255, 0.7);
}

.settings-desc {
    font-family: Arial, sans-serif;
    font-size: 14px;
    color: var(--ink-muted);
}

.settings-toggle {
    appearance: none;
    flex-shrink: 0;
    min-width: 80px;
    padding: 9px 18px;
    border: 3px solid #000;
    border-radius: 6px;
    font-family: Arial, sans-serif;
    font-weight: bold;
    font-size: 18px;
    cursor: pointer;
    background: #c0c0c8;
    color: var(--ink-muted);
    box-shadow: 0 5px 0 rgba(0, 0, 0, 0.28);
    transition: background 120ms, color 120ms;
    text-align: center;
}

.settings-toggle.is-on {
    background: #0032ff;
    color: #fff;
    text-shadow: 1px 1px 0 rgba(0, 0, 0, 0.3);
}

.settings-toggle:active {
    box-shadow: 0 2px 0 rgba(0, 0, 0, 0.28);
    transform: translateY(3px);
}

.settings-choice {
    display: flex;
    flex-shrink: 0;
    border: 3px solid #000;
    border-radius: 6px;
    overflow: hidden;
    box-shadow: 0 5px 0 rgba(0, 0, 0, 0.28);
}

.settings-choice button {
    appearance: none;
    border: 0;
    border-left: 2px solid #000;
    padding: 9px 18px;
    font-family: Arial, sans-serif;
    font-weight: bold;
    font-size: 16px;
    cursor: pointer;
    background: var(--surface-3);
    color: var(--ink-muted);
    transition: background 120ms, color 120ms;
    white-space: nowrap;
}

.settings-choice button:first-child {
    border-left: 0;
}

.settings-choice button.is-active {
    background: #0032ff;
    color: #fff;
    text-shadow: 1px 1px 0 rgba(0, 0, 0, 0.3);
}

.settings-choice button:disabled {
    cursor: default;
}

@media (max-width: 600px) {
    .settings-row {
        flex-direction: column;
        align-items: flex-start;
        gap: 14px;
    }

    .settings-label {
        font-size: 18px;
    }
}

/* ===========================
   SHARED SCREEN ANIMATIONS
   (about, wikichannel, shop, custom, challenges, multiplayer)
   =========================== */

.about-container.is-exiting .about-screen,
.wikichannel-container.is-exiting .wikichannel-screen,
.shop-container.is-exiting .shop-screen,
.custom-container.is-exiting .custom-screen,
.challenges-container.is-exiting .challenges-screen,
.multiplayer-container.is-exiting .multiplayer-screen {
    animation: trainingScreenOutLeft 260ms cubic-bezier(0.7, 0, 1, 0.35) forwards;
}

.about-container.is-entering .about-screen,
.wikichannel-container.is-entering .wikichannel-screen,
.shop-container.is-entering .shop-screen,
.custom-container.is-entering .custom-screen,
.challenges-container.is-entering .challenges-screen,
.multiplayer-container.is-entering .multiplayer-screen {
    animation: trainingScreenInRight 260ms cubic-bezier(0.04, 0.82, 0.18, 1) backwards;
}

.about-container.is-exiting-back .about-screen,
.wikichannel-container.is-exiting-back .wikichannel-screen,
.shop-container.is-exiting-back .shop-screen,
.custom-container.is-exiting-back .custom-screen,
.challenges-container.is-exiting-back .challenges-screen,
.multiplayer-container.is-exiting-back .multiplayer-screen {
    animation: trainingScreenOutRight 260ms cubic-bezier(0.7, 0, 1, 0.35) forwards;
}

.about-container.is-entering-back .about-screen,
.wikichannel-container.is-entering-back .wikichannel-screen,
.shop-container.is-entering-back .shop-screen,
.custom-container.is-entering-back .custom-screen,
.challenges-container.is-entering-back .challenges-screen,
.multiplayer-container.is-entering-back .multiplayer-screen {
    animation: trainingScreenInLeft 260ms cubic-bezier(0.04, 0.82, 0.18, 1) backwards;
}

.about-container, .wikichannel-container, .shop-container,
.custom-container, .challenges-container,
.multiplayer-container {
    display: block;
    max-width: min(980px, calc(100vw - 40px));
    margin: 0 auto;
}

.about-screen, .wikichannel-screen, .shop-screen,
.custom-screen, .challenges-screen,
.multiplayer-screen {
    width: 100%;
    padding: 16px 0 120px;
    will-change: transform, opacity;
}

/* ===========================
   ABOUT
   =========================== */

.about-card {
    padding: 36px 40px;
    border: 3px solid #000;
    border-radius: 7px;
    background: linear-gradient(180deg, var(--panel-top), var(--panel-bottom));
    box-shadow: 0 6px 0 rgba(0,0,0,0.22), inset 0 0 0 3px rgba(176,176,255,0.45);
}

.about-title {
    font-family: Arial, sans-serif;
    font-size: 52px;
    font-weight: bold;
    color: var(--ink-link);
    text-shadow: 3px 3px 0 rgba(176,176,255,0.5);
    margin-bottom: 14px;
}

/* ── The team, above the tabs ──
   Each person is their pointer. The cursor is the portrait: it is what other
   players actually see of them in a lobby, and the creator's is live — it
   changes whenever they equip something else. */
.about-team {
    display: flex;
    flex-direction: column;
    gap: 8px;
    margin-bottom: 20px;
}

.about-team:empty { display: none; }

.team-member {
    display: flex;
    align-items: center;
    gap: 12px;
    padding: 8px 14px 8px 10px;
    border: 2px solid #000;
    border-radius: 10px;
    background: var(--surface);
    text-decoration: none;
}

/* Only a row that goes somewhere lifts. A plain portrait row must not look
   pressable — see teamRowHtml, which picks the element to match. */
a.team-member,
button.team-member {
    width: 100%;
    font: inherit;
    text-align: left;
    cursor: pointer;
    box-shadow: 0 3px 0 rgba(0, 0, 0, 0.2);
    transition: transform 0.12s ease, box-shadow 0.12s ease;
}

a.team-member:hover,
button.team-member:hover {
    transform: translateY(-2px);
    box-shadow: 0 5px 0 rgba(0, 0, 0, 0.22);
}

/* A fixed box, so a tall cursor and a short one leave the names on the same
   line — the sprites are authored at different heights. */
.team-cursor {
    display: grid;
    place-items: center;
    width: 48px;
    height: 48px;
    flex: none;
}

.team-cursor img {
    max-width: 100%;
    max-height: 100%;
    image-rendering: pixelated;
}

.team-who {
    display: flex;
    flex-direction: column;
    min-width: 0;
}

.team-name {
    font-family: Arial, sans-serif;
    font-size: 17px;
    font-weight: 900;
    color: var(--ink-navy);
}

.team-role {
    font-family: Arial, sans-serif;
    font-size: 12px;
    font-weight: bold;
    letter-spacing: 1px;
    text-transform: uppercase;
    color: var(--ink-muted);
}

.about-tabs {
    display: flex;
    gap: 8px;
    margin-bottom: 18px;
}

/* ── One racer's profile ──
   Reuses the About card's shell, since it is the same kind of page: a single
   panel of read-only facts. Everything on it comes from the server's own race
   record — see PlayerProfileController for why achievements are not here. */
.pp-head {
    display: flex;
    align-items: center;
    gap: 16px;
    padding-bottom: 16px;
    margin-bottom: 16px;
    border-bottom: 2px solid #dde;
}

.pp-portrait {
    display: grid;
    place-items: center;
    width: 84px;
    height: 84px;
    flex: none;
    border: 3px solid #000;
    border-radius: 12px;
    background: radial-gradient(120% 100% at 50% 0%, #eef1ff, #fdfdff 70%);
}

.pp-portrait img {
    max-width: 100%;
    max-height: 100%;
    image-rendering: pixelated;
}

.pp-name {
    font-family: Arial, sans-serif;
    font-size: 28px;
    font-weight: 900;
    line-height: 1.1;
    color: var(--ink-navy);
}

.pp-tier {
    font-family: Arial, sans-serif;
    font-size: 13px;
    font-weight: bold;
    letter-spacing: 1px;
    text-transform: uppercase;
    color: var(--ink-muted);
}

.pp-tiles {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
    gap: 10px;
}

.pp-tile {
    padding: 12px 14px;
    border: 2px solid #000;
    border-radius: 10px;
    background: var(--surface);
    font-family: Arial, sans-serif;
}

.pp-tile-value {
    font-size: 24px;
    font-weight: 900;
    line-height: 1.15;
    color: var(--ink-link);
}

.pp-tile-label {
    font-size: 11px;
    font-weight: 900;
    letter-spacing: 1px;
    text-transform: uppercase;
    color: var(--ink-muted);
}

/* Course names and "wins / played" — long enough to need their own line. */
.pp-tile-note {
    margin-top: 4px;
    font-size: 12px;
    color: var(--ink-mid);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.pp-empty {
    font-family: Arial, sans-serif;
    font-size: 14px;
    line-height: 1.5;
    color: var(--ink-muted);
}

.about-tab {
    font-family: Arial, sans-serif;
    font-size: 14px;
    font-weight: bold;
    color: #556;
    background: var(--surface-2);
    border: 2px solid #b9c2e8;
    border-radius: 8px;
    padding: 7px 20px;
    cursor: pointer;
}

.about-tab:hover { border-color: #6a68fe; }

.about-tab.on {
    background: #0032ff;
    border-color: #0032ff;
    color: #fff;
}

.about-tab-body {
    max-height: 52vh;
    overflow-y: auto;
    padding-right: 6px;
}

.legal-body {
    font-family: Arial, sans-serif;
    font-size: 13.5px;
    line-height: 1.65;
    color: var(--ink-mid);
    text-align: left;
}

.legal-body h3 {
    margin: 16px 0 6px;
    font-size: 15px;
    color: var(--ink-link);
}

.legal-body h3:first-child { margin-top: 0; }

.legal-body p { margin: 0 0 10px; }

.legal-body a { color: var(--ink-link); }

.credits-intro {
    font-family: Arial, sans-serif;
    font-size: 13.5px;
    color: var(--ink-mid);
    margin: 0 0 14px;
}

.credits-status {
    font-family: Arial, sans-serif;
    font-size: 13.5px;
    color: var(--ink-faint);
}

.credit-list {
    display: flex;
    flex-direction: column;
    gap: 6px;
}

.credit-row {
    border: 2px solid #dde1f0;
    border-radius: 8px;
    overflow: hidden;
    background: var(--surface);
}

/* A link now rather than a button: the row opens that package's notice on
   /acknowledgements, where the licence texts live. Kept looking exactly like
   the row it replaced. */
.credit-head {
    display: flex;
    align-items: center;
    gap: 10px;
    width: 100%;
    padding: 8px 12px;
    border: 0;
    background: none;
    cursor: pointer;
    font-family: Arial, sans-serif;
    text-align: left;
    text-decoration: none;
}

a.credit-head:hover { background: var(--surface-2); }

.credit-name {
    font-size: 13px;
    font-weight: bold;
    color: var(--ink-navy);
    flex: 1 1 auto;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.credit-version {
    font-size: 11px;
    color: #8891a3;
    flex: 0 0 auto;
}

.credit-license {
    font-size: 10.5px;
    font-weight: bold;
    letter-spacing: 0.5px;
    text-transform: uppercase;
    color: var(--ink-link);
    background: rgba(0, 50, 255, 0.08);
    border-radius: 999px;
    padding: 2px 9px;
    flex: 0 0 auto;
}

/* The one-line "what this is" under a row, for the two libraries that are
   bundled rather than installed. */
.credit-note {
    margin: 0;
    padding: 0 12px 9px;
    font-family: Arial, sans-serif;
    font-size: 11.5px;
    line-height: 1.45;
    color: var(--ink-faint);
}

/* ===========================
   WIKICHANNEL
   =========================== */

.channel-topbar {
    display: flex;
    align-items: center;
    justify-content: space-between;
    margin-bottom: 20px;
    padding: 12px 18px;
    border: 3px solid #000;
    border-radius: 7px;
    background: linear-gradient(180deg, var(--panel-top), var(--panel-bottom));
    box-shadow: 0 4px 0 rgba(0,0,0,0.2), inset 0 0 0 3px rgba(176,176,255,0.45);
}

.channel-brand {
    font-family: Arial, sans-serif;
    font-size: 22px;
    font-weight: bold;
    color: var(--ink-link);
    letter-spacing: 2px;
    text-shadow: 1px 1px 0 rgba(176,176,255,0.7);
}

.channel-next-btn {
    appearance: none;
    padding: 10px 20px;
    border: 3px solid #000;
    border-radius: 6px;
    background: #0032ff;
    color: #fff;
    font-family: Arial, sans-serif;
    font-size: 16px;
    font-weight: bold;
    cursor: pointer;
    box-shadow: 0 4px 0 rgba(0,0,0,0.28);
    transition: background 120ms;
}

.channel-next-btn:hover { background: #0028cc; }
.channel-next-btn:active { transform: translateY(2px); box-shadow: 0 2px 0 rgba(0,0,0,0.28); }

.channel-content {
    min-height: 360px;
}

.channel-card {
    display: flex;
    gap: 28px;
    padding: 28px;
    border: 3px solid #000;
    border-radius: 7px;
    background: linear-gradient(180deg, var(--panel-top), var(--panel-bottom));
    box-shadow: 0 6px 0 rgba(0,0,0,0.22), inset 0 0 0 3px rgba(176,176,255,0.45);
}

.channel-thumb {
    flex-shrink: 0;
    width: 220px;
    height: 160px;
    object-fit: cover;
    border: 2px solid #a2a9b1;
    border-radius: 4px;
}

.channel-thumb-placeholder {
    flex-shrink: 0;
    width: 220px;
    height: 160px;
    background: repeating-conic-gradient(#ddd 0 25%, #eee 0 50%) 0 0 / 20px 20px;
    border: 2px solid #a2a9b1;
    border-radius: 4px;
}

.channel-info {
    display: flex;
    flex-direction: column;
    gap: 8px;
}

.channel-eyebrow {
    font-family: Arial, sans-serif;
    font-size: 13px;
    font-weight: bold;
    color: var(--ink-link);
    letter-spacing: 1px;
}

.channel-title {
    font-family: Arial, sans-serif;
    font-size: 30px;
    font-weight: bold;
    color: var(--ink);
    line-height: 1.2;
}

.channel-desc {
    font-family: Arial, sans-serif;
    font-size: 14px;
    font-style: italic;
    color: var(--ink-muted);
}

.channel-extract {
    font-family: Georgia, serif;
    font-size: 16px;
    color: var(--ink-mid);
    line-height: 1.6;
    margin: 0;
}

.channel-loading, .channel-error {
    display: flex;
    align-items: center;
    justify-content: center;
    min-height: 200px;
    font-family: Arial, sans-serif;
    font-size: 20px;
    color: var(--ink-faint);
    border: 2px solid #dde;
    border-radius: 7px;
    background: color-mix(in srgb, var(--surface) 70%, transparent);
}

@media (max-width: 700px) {
    .channel-card { flex-direction: column; }
    .channel-thumb, .channel-thumb-placeholder { width: 100%; }
}

/* ===========================
   SHOP
   =========================== */

.shop-header {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    margin-bottom: 24px;
    padding-bottom: 12px;
    border-bottom: 3px solid #000;
}

.shop-title {
    font-family: Arial, sans-serif;
    font-size: 36px;
    font-weight: bold;
    color: var(--ink);
}

.shop-stat {
    font-family: Arial, sans-serif;
    font-size: 15px;
    color: var(--ink-muted);
}

.shop-section {
    margin-bottom: 32px;
}

.shop-category-label {
    font-family: Arial, sans-serif;
    font-size: 13px;
    font-weight: bold;
    letter-spacing: 2px;
    text-transform: uppercase;
    color: var(--ink-faint);
    margin-bottom: 12px;
}

.shop-items-grid {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    gap: 14px;
}

.shop-item {
    padding: 18px 20px;
    border: 3px solid #000;
    border-radius: 7px;
    background: linear-gradient(180deg, var(--panel-top), var(--panel-bottom));
    box-shadow: 0 4px 0 rgba(0,0,0,0.2);
    display: flex;
    flex-direction: column;
    gap: 5px;
}

.shop-item-name {
    font-family: Arial, sans-serif;
    font-size: 18px;
    font-weight: bold;
    color: var(--ink);
}

.shop-item-desc {
    font-family: Arial, sans-serif;
    font-size: 13px;
    color: var(--ink-muted);
}

.shop-item-badge {
    margin-top: 6px;
    font-family: Arial, sans-serif;
    font-size: 13px;
    font-weight: bold;
    padding: 4px 10px;
    border-radius: 4px;
    align-self: flex-start;
}

.shop-item.is-unlocked .shop-item-badge {
    background: #d4f5d4;
    color: #1a6b1a;
    border: 1px solid #1a6b1a;
}

.shop-item.is-locked {
    opacity: 0.6;
}

.shop-item.is-locked .shop-item-badge {
    background: #f0f0f0;
    color: var(--ink-faint);
    border: 1px solid #ccc;
}

.shop-note {
    font-family: Arial, sans-serif;
    font-size: 13px;
    color: #aaa;
    text-align: center;
    margin-top: 8px;
}

@media (max-width: 700px) {
    .shop-items-grid { grid-template-columns: repeat(2, 1fr); }
}

/* ===========================
   CUSTOM MODE
   =========================== */

.custom-form {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 16px;
    max-width: 520px;
    margin: 0 auto;
    padding: 36px 40px;
    border: 3px solid #000;
    border-radius: 7px;
    background: linear-gradient(180deg, var(--panel-top), var(--panel-bottom));
    box-shadow: 0 6px 0 rgba(0,0,0,0.22), inset 0 0 0 3px rgba(176,176,255,0.45);
}

.custom-field {
    width: 100%;
    display: flex;
    flex-direction: column;
    gap: 6px;
}

.custom-label {
    font-family: Arial, sans-serif;
    font-size: 13px;
    font-weight: bold;
    letter-spacing: 1px;
    text-transform: uppercase;
    color: var(--ink-muted);
}

.custom-input {
    appearance: none;
    width: 100%;
    padding: 12px 16px;
    border: 3px solid #000;
    border-radius: 6px;
    font-family: Arial, sans-serif;
    font-size: 20px;
    color: var(--ink);
    background: var(--surface);
    box-shadow: inset 0 2px 4px rgba(0,0,0,0.06);
    transition: border-color 120ms;
}

.custom-input:focus {
    outline: none;
    border-color: #0032ff;
}

.custom-err {
    font-family: Arial, sans-serif;
    font-size: 13px;
    color: #d33;
    min-height: 18px;
}

.custom-divider {
    font-family: Arial, sans-serif;
    font-size: 22px;
    font-weight: bold;
    color: var(--ink-link);
    text-shadow: 1px 1px 0 rgba(176,176,255,0.7);
}

.custom-play-btn {
    appearance: none;
    margin-top: 8px;
    width: 100%;
    padding: 14px;
    border: 3px solid #000;
    border-radius: 6px;
    background: #0032ff;
    color: #fff;
    font-family: Arial, sans-serif;
    font-size: 20px;
    font-weight: bold;
    cursor: pointer;
    box-shadow: 0 5px 0 rgba(0,0,0,0.28);
    transition: background 120ms;
}

.custom-play-btn:hover { background: #0028cc; }
.custom-play-btn:active { transform: translateY(3px); box-shadow: 0 2px 0 rgba(0,0,0,0.28); }
.custom-play-btn:disabled { background: #aab; cursor: default; }

/* ===========================
   STORY MODE
   =========================== */

.story-intro-btns {
    display: flex;
    gap: 12px;
}

.story-back-btn {
    appearance: none;
    padding: 12px 20px;
    border: 3px solid #000;
    border-radius: 6px;
    background: var(--surface-3);
    color: var(--ink-mid);
    font-family: Arial, sans-serif;
    font-size: 16px;
    font-weight: bold;
    cursor: pointer;
    box-shadow: 0 4px 0 rgba(0,0,0,0.2);
}

.story-back-btn:hover { background: var(--surface-3); }

.story-begin-btn {
    appearance: none;
    flex: 1;
    padding: 12px 28px;
    border: 3px solid #000;
    border-radius: 6px;
    background: #0032ff;
    color: #fff;
    font-family: Arial, sans-serif;
    font-size: 18px;
    font-weight: bold;
    cursor: pointer;
    box-shadow: 0 4px 0 rgba(0,0,0,0.28);
}

.story-begin-btn:hover { background: #0028cc; }

@media (max-width: 700px) {
}

/* ===========================
   CHALLENGES
   =========================== */

.challenge-card {
    max-width: 600px;
    margin: 0 auto;
    padding: 36px 40px;
    border: 3px solid #000;
    border-radius: 7px;
    background: linear-gradient(180deg, var(--panel-top), var(--panel-bottom));
    box-shadow: 0 6px 0 rgba(0,0,0,0.22), inset 0 0 0 3px rgba(176,176,255,0.45);
    display: flex;
    flex-direction: column;
    gap: 16px;
}

.challenge-eyebrow {
    font-family: Arial, sans-serif;
    font-size: 13px;
    font-weight: bold;
    letter-spacing: 2px;
    text-transform: uppercase;
    color: var(--ink-link);
}

.challenge-date {
    font-family: Arial, sans-serif;
    font-size: 28px;
    font-weight: bold;
    color: var(--ink);
}

.challenge-route {
    display: flex;
    align-items: center;
    gap: 14px;
    flex-wrap: wrap;
}

.challenge-pill {
    padding: 10px 18px;
    border: 2px solid #000;
    border-radius: 6px;
    background: var(--surface);
    font-family: Arial, sans-serif;
    font-size: 22px;
    font-weight: bold;
    color: var(--ink-link);
    text-shadow: 1px 1px 0 rgba(0,0,0,0.1);
}

.challenge-arrow {
    font-family: Arial, sans-serif;
    font-size: 18px;
    font-weight: bold;
    color: var(--ink-strong);
}

.challenge-play-btn {
    appearance: none;
    margin-top: 8px;
    padding: 14px 28px;
    border: 3px solid #000;
    border-radius: 6px;
    background: #0032ff;
    color: #fff;
    font-family: Arial, sans-serif;
    font-size: 18px;
    font-weight: bold;
    cursor: pointer;
    box-shadow: 0 5px 0 rgba(0,0,0,0.28);
    align-self: flex-start;
}

.challenge-play-btn:hover { background: #0028cc; }
.challenge-play-btn:active { transform: translateY(3px); box-shadow: 0 2px 0 rgba(0,0,0,0.28); }

/* ===========================
   MULTIPLAYER
   =========================== */

.mp-home {
    max-width: 620px;
    margin: 0 auto;
    padding: 36px 40px;
    border: 3px solid #000;
    border-radius: 7px;
    background: linear-gradient(180deg, var(--panel-top), var(--panel-bottom));
    box-shadow: 0 6px 0 rgba(0,0,0,0.22), inset 0 0 0 3px rgba(176,176,255,0.45);
}

.mp-title {
    /* On a light panel the polarity flips: dark ink, white edge. A black edge
       here would be a black box round every letter, and white ink would
       disappear — there is no photograph underneath to hide against. */
    --edge: #fff;
    --edge-w: 3px;
    font-family: Arial, sans-serif;
    font-size: 36px;
    font-weight: bold;
    color: var(--ink-link);
    margin-bottom: 6px;
}

.mp-subtitle {
    font-family: Arial, sans-serif;
    font-size: 16px;
    color: var(--ink-muted);
    margin-bottom: 28px;
}

.mp-options {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 16px;
    margin-bottom: 20px;
}

.mp-option {
    appearance: none;
    padding: 24px 20px;
    border: 3px solid #000;
    border-radius: 7px;
    background: var(--surface-2);
    cursor: pointer;
    font: inherit;
    text-align: center;
    box-shadow: 0 4px 0 rgba(0,0,0,0.18);
    transition: transform 0.15s ease, background 120ms;
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 6px;
}

.mp-option:hover { background: #eaeaff; transform: scale(1.02); }

.mp-option-icon {
    font-size: 32px;
    font-weight: bold;
    color: var(--ink-link);
    line-height: 1;
}

.mp-option-label {
    font-family: Arial, sans-serif;
    font-size: 18px;
    font-weight: bold;
    color: var(--ink);
}

.mp-option-desc {
    font-family: Arial, sans-serif;
    font-size: 13px;
    color: #777;
}

.mp-note, .mp-gate-text {
    font-family: Arial, sans-serif;
    font-size: 14px;
    color: #aaa;
    text-align: center;
    margin-top: 4px;
}

.mp-gate-text {
    color: var(--ink-muted);
    margin-bottom: 10px;
    text-align: left;
}

.mp-back-btn {
    appearance: none;
    margin-top: 16px;
    padding: 10px 20px;
    border: 3px solid #000;
    border-radius: 6px;
    background: var(--surface-3);
    color: var(--ink-mid);
    font-family: Arial, sans-serif;
    font-size: 16px;
    font-weight: bold;
    cursor: pointer;
    box-shadow: 0 4px 0 rgba(0,0,0,0.2);
}

.mp-back-btn:hover { background: var(--surface-3); }

@media (max-width: 600px) {
    .mp-options { grid-template-columns: 1fr; }
}

@media (max-width: 480px) {
    .grid-container {
        gap: 20px;
    }

    body > main {
        padding: 10px;
    }

    :root { --chrome-h: 50px; --logo-clear: 144px; }  /* wordmark 30px at left:16 -> ends at 130 */

    .back-button {
        left: 12px;
        width: 48px;
        height: 42px;
    }

    .back-button::before {
        left: 18px;
        width: 18px;
        height: 18px;
        border-left-width: 5px;
        border-bottom-width: 5px;
    }

    /* Narrow screens keep a wider band than the 3.44 ratio would give, so the
       pattern still reads as a pattern on a phone. The square stays derived
       from the height, so it never loses rows. */
    .checkerboard,
    .footer-checkerboard {
        width: 200px;
    }
}

/* ===========================
   CHALLENGE LIST
   =========================== */

.challenge-daily-strip {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 20px;
    flex-wrap: wrap;
    margin-bottom: 20px;
    padding: 16px 22px;
    border: 3px solid #000;
    border-radius: 7px;
    background: linear-gradient(180deg, var(--panel-top), var(--panel-bottom));
    box-shadow: 0 4px 0 rgba(0,0,0,0.2), inset 0 0 0 3px rgba(176,176,255,0.45);
}

.challenge-daily-info {
    display: flex;
    flex-direction: column;
    gap: 10px;
}

.challenge-pill--sm {
    font-size: 16px;
    padding: 6px 12px;
}

.challenge-grid {
    display: grid;
    grid-template-columns: repeat(2, 1fr);
    gap: 20px;
}

.ch-card {
    appearance: none;
    position: relative;
    overflow: hidden;
    display: flex;
    align-items: center;
    gap: 18px;
    border: 3px solid #000;
    border-radius: 7px;
    padding: 20px 24px 20px 30px;
    background: linear-gradient(135deg, rgba(255,255,255,0.97) 55%, color-mix(in srgb, var(--ch-accent, #0032ff) 10%, rgba(255,255,255,0.97)));
    box-shadow: 0 5px 0 rgba(0,0,0,0.22), inset 0 0 0 3px rgba(176,176,255,0.45);
    text-align: left;
    cursor: pointer;
    font: inherit;
    transition: transform 0.15s ease, box-shadow 0.15s ease;
    animation: cardSlideInRight 260ms cubic-bezier(0.04, 0.82, 0.18, 1) backwards;
    animation-delay: var(--row-delay);
}

.ch-card::before {
    content: '';
    position: absolute;
    left: 0;
    top: 0;
    bottom: 0;
    width: 8px;
    background: var(--ch-accent, #0032ff);
}

.ch-card:hover {
    transform: scale(1.03);
    box-shadow:
        0 5px 0 rgba(0,0,0,0.22),
        0 0 22px color-mix(in srgb, var(--ch-accent, #0032ff) 50%, transparent),
        inset 0 0 0 3px rgba(176,176,255,0.45);
}

.ch-card:hover .ch-name { color: var(--ch-accent, #0032ff); }

.ch-card:hover .ch-icon span {
    display: inline-block;
    animation: fxWiggle 0.6s ease;
}

.ch-icon {
    flex-shrink: 0;
    width: 62px;
    height: 62px;
    border-radius: 50%;
    display: grid;
    place-items: center;
    font-size: 32px;
    line-height: 1;
    background: color-mix(in srgb, var(--ch-accent, #0032ff) 16%, #fff);
    border: 2px solid color-mix(in srgb, var(--ch-accent, #0032ff) 45%, #fff);
    box-shadow: inset 0 -4px 0 color-mix(in srgb, var(--ch-accent, #0032ff) 22%, #fff);
}

@keyframes fxWiggle {
    0%, 100% { transform: rotate(0) scale(1); }
    25%      { transform: rotate(-13deg) scale(1.12); }
    55%      { transform: rotate(11deg) scale(1.15); }
    80%      { transform: rotate(-5deg) scale(1.05); }
}

.ch-name {
    font-family: Arial, sans-serif;
    font-size: 20px;
    font-weight: bold;
    color: var(--ink);
    margin-bottom: 5px;
    transition: color 0.15s ease;
}

.ch-desc {
    font-family: Arial, sans-serif;
    font-size: 13px;
    line-height: 1.45;
    color: var(--ink-muted);
}

.ch-intro {
    position: relative;
    overflow: hidden;
    max-width: 620px;
    margin: 0 auto;
    padding: 40px 40px 36px;
    border: 3px solid #000;
    border-radius: 7px;
    background: linear-gradient(160deg, rgba(255,255,255,0.97) 60%, color-mix(in srgb, var(--ch-accent, #0032ff) 10%, rgba(255,255,255,0.97)));
    box-shadow: 0 6px 0 rgba(0,0,0,0.22), inset 0 0 0 3px rgba(176,176,255,0.45);
}

.ch-intro::before {
    content: '';
    position: absolute;
    left: 0;
    right: 0;
    top: 0;
    height: 8px;
    background: var(--ch-accent, #0032ff);
}

.ch-intro .challenge-eyebrow { color: var(--ch-accent, #0032ff); }

.ch-intro-icon {
    width: 96px;
    height: 96px;
    border-radius: 50%;
    display: grid;
    place-items: center;
    font-size: 48px;
    line-height: 1;
    margin-bottom: 18px;
    background: color-mix(in srgb, var(--ch-accent, #0032ff) 16%, #fff);
    border: 2px solid color-mix(in srgb, var(--ch-accent, #0032ff) 45%, #fff);
    box-shadow: inset 0 -5px 0 color-mix(in srgb, var(--ch-accent, #0032ff) 22%, #fff);
}

.ch-intro-icon span {
    display: inline-block;
    animation: fxFloatIdle 3s ease-in-out infinite;
}

@keyframes fxFloatIdle {
    0%, 100% { transform: translateY(3px); }
    50%      { transform: translateY(-5px); }
}

.ch-intro-name {
    font-family: Arial, sans-serif;
    font-size: 30px;
    font-weight: bold;
    color: var(--ink);
    margin: 6px 0 14px;
}

.ch-intro-desc {
    font-family: Georgia, serif;
    font-size: 18px;
    color: var(--ink-mid);
    line-height: 1.7;
    margin-bottom: 14px;
}

.ch-intro-note {
    font-family: Arial, sans-serif;
    font-size: 14px;
    color: var(--ink-faint);
    margin-bottom: 26px;
}

@media (max-width: 700px) {
    .challenge-grid { grid-template-columns: 1fr; }
}

/* ===========================
   CHALLENGE EFFECTS
   =========================== */

/* Alone in the Dark */
.fx-darkness {
    position: fixed;
    inset: 0;
    z-index: 950;
    pointer-events: none;
    background: radial-gradient(
        circle 190px at var(--fx-x, 50%) var(--fx-y, 50%),
        rgba(0, 0, 0, 0) 0%,
        rgba(0, 0, 0, 0.25) 55%,
        rgba(0, 0, 0, 0.93) 80%,
        rgba(0, 0, 0, 0.995) 100%
    );
}

/* The Files */
/* The classified document itself stays fully readable — it's the links that
   are censored. Every link (navigable move or dead reference) becomes a solid
   black bar; the navigable ones are still clickable underneath, so you have to
   guess which redactions actually lead somewhere. */
body.fx-redacted .wiki-link-inline,
body.fx-redacted .wiki-link-dead {
    color: transparent !important;
    background: #1a1a1a !important;
    border-radius: 2px;
    text-decoration: none !important;
    box-shadow: 0 0 0 1px #1a1a1a;
    box-decoration-break: clone;
    -webkit-box-decoration-break: clone;
}

body.fx-redacted .wiki-link-inline img,
body.fx-redacted .wiki-link-dead img {
    filter: brightness(0);
}

/* Upside Down */
/* Only the page-level <main> — the wiki article renders inside its own nested
   <main class="wiki-page">, and a bare `main` selector would rotate the
   article text a second time, flipping it back upright. */
body.fx-upside-down > main {
    transform: rotate(180deg);
}

/* Earthquake */
/* Shake the page content, not the stage itself — the stage carries the
   equipped theme's background art, which should hold still. */
body.fx-quake .article-stage .wiki-skin {
    animation: fxQuake 0.28s linear infinite;
}

@keyframes fxQuake {
    0%   { transform: translate(0, 0) rotate(0); }
    20%  { transform: translate(-7px, 4px) rotate(-0.4deg); }
    40%  { transform: translate(6px, -5px) rotate(0.35deg); }
    60%  { transform: translate(-5px, -6px) rotate(0.3deg); }
    80%  { transform: translate(7px, 5px) rotate(-0.3deg); }
    100% { transform: translate(0, 0) rotate(0); }
}

/* Virtual cursor (wind / storm / ice / tasty) */
body.fx-hide-cursor,
body.fx-hide-cursor * {
    cursor: none !important;
}

.fx-cursor {
    position: fixed;
    left: 0;
    top: 0;
    z-index: 990;
    width: 26px;
    height: 26px;
    pointer-events: none;
    transform-origin: 0 0;
    filter: drop-shadow(1px 2px 2px rgba(0, 0, 0, 0.4));
    transition: none;
}

.fx-cursor svg {
    display: block;
    width: 100%;
    height: 100%;
}

.fx-cursor--skin {
    filter: drop-shadow(1px 2px 2px rgba(0, 0, 0, 0.4));
}

.fx-cursor--skin img {
    /* Size set inline by fillFxCursor at a whole-number scale — stretching to
       100% of the 26px box would land on a fractional one. */
    display: block;
    image-rendering: pixelated;
    image-rendering: crisp-edges;
}

/* Emoji cursor skins: nudge so the glyph sits where its hotspot would be. */
.fx-cursor--emoji span {
    display: block;
    font-size: 24px;
    line-height: 26px;
    transform: translate(-6px, -4px);
}

/* Math quiz */
/* Pop Quiz link trap (multiplayer): a bare purple calculator pinned onto the
   cursed link, inside the article scroll — deliberately primitive. */
.fx-mathpop {
    position: absolute;
    z-index: 350;
    width: 168px;
    padding: 8px;
    background: #7a1fd0;
    font-family: Arial, sans-serif;
}

.fx-mathpop-eq {
    margin-bottom: 6px;
    color: #fff;
    font-size: 18px;
    font-weight: bold;
    text-align: center;
}

.fx-mathpop-input {
    display: block;
    width: 100%;
    height: 30px;
    padding: 0 6px;
    margin-bottom: 4px;
    border: 0;
    border-radius: 0;
    box-sizing: border-box;
    background: var(--surface);
    color: var(--ink-strong);
    font-family: Arial, sans-serif;
    font-size: 18px;
    font-weight: bold;
    text-align: right;
}

.fx-mathpop-msg {
    min-height: 14px;
    margin-bottom: 4px;
    color: #ff2020;
    font-size: 12px;
    font-weight: bold;
    text-align: center;
}

.fx-mathpop-pad {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    gap: 4px;
}

.fx-mathpop-key {
    appearance: none;
    padding: 8px 0;
    border: 0;
    border-radius: 0;
    background: var(--surface);
    color: var(--ink-strong);
    font-family: Arial, sans-serif;
    font-size: 16px;
    font-weight: bold;
    cursor: pointer;
}

.fx-mathpop-key:active { background: #ccc; }

.fx-mathpop-key--zero { grid-column: span 2; }

.fx-mathpop-go {
    appearance: none;
    border: 0;
    border-radius: 0;
    background: #1faf3c;
    color: #fff;
    font-family: Arial, sans-serif;
    font-size: 14px;
    font-weight: bold;
    cursor: pointer;
}

.fx-mathpop-go:active { background: #178a2f; }

.fx-math-overlay {
    position: fixed;
    inset: 0;
    z-index: 970;
    display: grid;
    place-items: center;
    background: rgba(10, 15, 40, 0.55);
}

.fx-math-card {
    min-width: min(400px, calc(100vw - 48px));
    padding: 30px 38px;
    border: 3px solid #000;
    border-radius: 7px;
    background: linear-gradient(180deg, var(--panel-top), var(--panel-bottom));
    box-shadow: 0 6px 0 rgba(0,0,0,0.3), inset 0 0 0 3px rgba(176,176,255,0.45);
    text-align: center;
}

.fx-math-card.is-shake {
    animation: fxMathShake 0.3s linear;
}

@keyframes fxMathShake {
    0%   { transform: translateX(0); }
    25%  { transform: translateX(-9px); }
    50%  { transform: translateX(8px); }
    75%  { transform: translateX(-5px); }
    100% { transform: translateX(0); }
}

.fx-math-title {
    font-family: Arial, sans-serif;
    font-size: 13px;
    font-weight: bold;
    letter-spacing: 2px;
    color: var(--ink-link);
    margin-bottom: 12px;
}

.fx-math-eq {
    font-family: Arial, sans-serif;
    font-size: 42px;
    font-weight: bold;
    color: var(--ink);
    margin-bottom: 20px;
}

.fx-math-row {
    display: flex;
    gap: 10px;
    justify-content: center;
}

.fx-math-input {
    width: 130px;
    padding: 10px 14px;
    border: 3px solid #000;
    border-radius: 6px;
    font-family: Arial, sans-serif;
    font-size: 22px;
    font-weight: bold;
    text-align: center;
}

.fx-math-input:focus {
    outline: none;
    border-color: #0032ff;
}

.fx-math-btn {
    appearance: none;
    padding: 10px 22px;
    border: 3px solid #000;
    border-radius: 6px;
    background: #0032ff;
    color: #fff;
    font-family: Arial, sans-serif;
    font-size: 17px;
    font-weight: bold;
    cursor: pointer;
    box-shadow: 0 4px 0 rgba(0,0,0,0.28);
}

.fx-math-btn:hover { background: #0028cc; }
.fx-math-btn:active { transform: translateY(2px); box-shadow: 0 2px 0 rgba(0,0,0,0.28); }

.fx-math-feedback {
    min-height: 22px;
    margin-top: 12px;
    font-family: Arial, sans-serif;
    font-size: 15px;
    font-weight: bold;
    color: #c62828;
}

/* Toast (déjà vu, babel, tasty) */
.fx-toast {
    position: fixed;
    bottom: 116px;
    left: 50%;
    transform: translateX(-50%);
    z-index: 985;
    padding: 12px 24px;
    border: 3px solid #000;
    border-radius: 7px;
    background: linear-gradient(180deg, var(--panel-top), var(--panel-bottom));
    box-shadow: 0 4px 0 rgba(0,0,0,0.25);
    font-family: Arial, sans-serif;
    font-size: 18px;
    font-weight: bold;
    color: var(--ink);
    opacity: 0;
    transition: opacity 200ms ease;
    pointer-events: none;
}

.fx-toast.is-visible {
    opacity: 1;
}

/* Alone in the Dark: eyes watching from the darkness */
.fx-eyes {
    position: fixed;
    z-index: 960;
    display: flex;
    gap: 10px;
    pointer-events: none;
    animation: fxEyesBlink 3.6s ease forwards;
}

.fx-eyes span {
    width: 11px;
    height: 15px;
    border-radius: 50%;
    background: radial-gradient(circle at 50% 40%, #fffde7, #ffca28);
    box-shadow: 0 0 14px 4px rgba(255, 202, 40, 0.45);
}

@keyframes fxEyesBlink {
    0%, 100% { opacity: 0; }
    12%, 40% { opacity: 1; transform: scaleY(1); }
    46%      { opacity: 0.9; transform: scaleY(0.08); }
    52%, 86% { opacity: 1; transform: scaleY(1); }
}

/* Déjà Vu: glitch flash on article swap */
.fx-glitch {
    position: fixed;
    inset: 0;
    z-index: 968;
    pointer-events: none;
    background:
        repeating-linear-gradient(0deg,
            rgba(142, 36, 170, 0.16) 0 3px,
            transparent 3px 8px),
        rgba(142, 36, 170, 0.08);
    animation: fxGlitch 650ms steps(7) forwards;
}

@keyframes fxGlitch {
    0%   { opacity: 1; transform: translateX(0) skewX(0); }
    18%  { transform: translateX(-16px) skewX(4deg); }
    36%  { transform: translateX(12px) skewX(-3deg); opacity: 0.85; }
    54%  { transform: translateX(-8px) skewX(2deg); }
    72%  { transform: translateX(5px) skewX(-1deg); opacity: 0.6; }
    100% { opacity: 0; transform: translateX(0) skewX(0); }
}

/* ═══ Multiplayer ═══════════════════════════════════════════════════════════ */

.mp-error {
    margin: 0;
    padding: 8px 14px;
    border: 2px solid #b71c1c;
    border-radius: 6px;
    background: #ffebee;
    color: #b71c1c;
    font-family: Arial, sans-serif;
    font-weight: bold;
    font-size: 14px;
}

.mp-profile-strip {
    display: flex;
    align-items: center;
    gap: 14px;
    flex-wrap: wrap;
    justify-content: center;
}

.mp-name-input,
.mp-code-input {
    padding: 10px 14px;
    border: 3px solid #000;
    border-radius: 8px;
    background: var(--surface);
    color: var(--ink-link);
    font-family: Arial, sans-serif;
    font-size: 18px;
    font-weight: bold;
    text-align: center;
    outline: none;
}

.mp-name-input:focus,
.mp-code-input:focus {
    border-color: #0032ff;
}

.mp-name-input.is-error {
    border-color: #b71c1c;
    background: #ffebee;
}

.mp-code-input {
    width: 160px;
    font-size: 26px;
    letter-spacing: 6px;
    text-transform: uppercase;
}

/* A username is not a room code: it needs room for real words, and forcing it
   to caps would misrepresent the name being searched for. */
.mp-code-input--name {
    width: 260px;
    font-size: 18px;
    letter-spacing: 0;
    text-transform: none;
}

.mp-rank-badge {
    display: flex;
    flex-direction: column;
    align-items: center;
    padding: 8px 18px;
    border: 3px solid #000;
    border-radius: 8px;
    background: linear-gradient(180deg, #ffe082, #f6d83f);
    box-shadow: 0 3px 0 rgba(0, 0, 0, 0.25);
}

.mp-rank-tier {
    font-family: Arial, sans-serif;
    font-size: 17px;
    font-weight: 900;
    color: #4a3200;
    text-transform: uppercase;
}

.mp-rank-rating {
    font-family: Arial, sans-serif;
    font-size: 13px;
    font-weight: bold;
    color: #6d5400;
}

.mp-options--grid {
    display: grid;
    grid-template-columns: repeat(2, minmax(220px, 280px));
    gap: 16px;
    justify-content: center;
}

.mp-queue-spinner {
    width: 54px;
    height: 54px;
    border: 6px solid rgba(0, 50, 255, 0.18);
    border-top-color: #0032ff;
    border-radius: 50%;
    animation: mpSpin 0.9s linear infinite;
}

@keyframes mpSpin {
    to { transform: rotate(360deg); }
}

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

.mp-play-btn {
    padding: 12px 30px;
    border: 3px solid #000;
    border-radius: 8px;
    background: linear-gradient(180deg, #43d95e, #1faf3c);
    color: #fff;
    font-family: Arial, sans-serif;
    font-size: 19px;
    font-weight: 900;
    text-transform: uppercase;
    cursor: pointer;
    box-shadow: 0 4px 0 rgba(0, 0, 0, 0.3);
    transition: transform 0.1s;
}

.mp-play-btn:hover { transform: scale(1.04); }
.mp-play-btn:active { transform: translateY(2px); box-shadow: 0 2px 0 rgba(0, 0, 0, 0.3); }

.mp-room-code {
    padding: 10px 34px;
    border: 4px dashed #0032ff;
    border-radius: 10px;
    background: var(--surface);
    color: var(--ink-link);
    font-family: Arial, sans-serif;
    font-size: 44px;
    font-weight: 900;
    letter-spacing: 12px;
}

.mp-lobby-list,
.mp-result-list {
    display: flex;
    flex-direction: column;
    gap: 8px;
    width: min(100%, 420px);
}

.mp-lobby-row {
    display: flex;
    justify-content: space-between;
    align-items: center;
    gap: 10px;
    padding: 9px 16px;
    border: 2px solid #000;
    border-radius: 8px;
    background: var(--surface);
    font-family: Arial, sans-serif;
    font-weight: bold;
}

/* Trade, on a rival's row in the lobby. Only appears for players who could
   actually take it — signed-in accounts, which is what canTrade carries. */
.mp-lobby-trade {
    appearance: none;
    padding: 4px 10px;
    border: 2px solid #000;
    border-radius: 7px;
    background: linear-gradient(180deg, #ffe082, #f6d83f);
    font-family: Arial, sans-serif;
    font-size: 11px;
    font-weight: 900;
    letter-spacing: 0.5px;
    text-transform: uppercase;
    color: #4a3200;
    cursor: pointer;
}

.mp-lobby-trade:hover { filter: brightness(1.07); }

/* .mp-bot-add keeps its name: the lobby's bot bar is gone, but this is also
   the button style for Refresh, Join and Watch in the room and spectate
   browsers. */
.mp-bot-add {
    appearance: none;
    padding: 7px 12px;
    border: 2px solid #000;
    border-radius: 8px;
    background: var(--surface);
    font-family: Arial, sans-serif;
    font-size: 13px;
    font-weight: bold;
    cursor: pointer;
    box-shadow: 0 3px 0 rgba(0, 0, 0, 0.2);
}

.mp-bot-add:hover {
    background: #e3f2fd;
}

.mp-bot-add:active {
    transform: translateY(2px);
    box-shadow: 0 1px 0 rgba(0, 0, 0, 0.2);
}

.mp-lobby-row.is-me {
    background: #e3f2fd;
    border-color: #0032ff;
}

.mp-lobby-name { color: #17233b; font-size: 16px; }
.mp-lobby-rating { color: #5b6474; font-size: 13px; }

.mp-lobby-btns {
    display: flex;
    align-items: center;
    gap: 16px;
}

/* ── Room lobby: header, tabs, settings, chat ── */

.mp-lobby-home {
    max-width: 940px;
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 14px;
    /* Containing block for the rival-cursor layer below. */
    position: relative;
}

/* ── Rival cursors in the lobby ──
   A full-size overlay whose box is what positions are measured against, on
   both the sending and receiving side, so a fraction means the same place in
   two different windows. It must never take a click: the settings panel and
   the start button live underneath it. */
.mp-lobby-cursors {
    position: absolute;
    inset: 0;
    pointer-events: none;
    overflow: hidden;
    z-index: 4;
}

.mp-lobby-cursor {
    position: absolute;
    top: 0;
    left: 0;
    /* Positions arrive about every 90ms; the transition carries the cursor
       between them so it glides instead of teleporting. Transform only —
       animating offsets here would relayout the panel under it. */
    transition: transform 90ms linear;
    will-change: transform;
    /* A column, so the name hangs under the pointer rather than beside it —
       beside, a long name ran out over the settings panel and the start
       button, and two rivals standing close overlapped each other's tags. */
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    opacity: 0;
}

.mp-lobby-cursor.is-live { opacity: 1; }

.mp-lobby-cursor img {
    image-rendering: pixelated;
    display: block;
}

/* Plain text under the pointer, in that player's favourite colour — the one
   they answered the signup question with, set inline by lobbyCursorEl.

   No pill, no border: the tag is meant to read as a label on the person, not
   as another piece of the game's furniture, and the colour is the thing doing
   the identifying. What the pill was really buying was contrast, so that comes
   back as a hard dark outline instead — the lobby panel sits on the player's
   own wallpaper, which can be anything, and a mid-tone photo would otherwise
   swallow half the hues in the wheel. */
.mp-lobby-cursor-name {
    /* Clears the pointer art, which is drawn up and left of its layout box by
       the hotspot offset and so hangs below what the column reserves for it. */
    margin-top: 6px;
    font-family: Arial, Helvetica, sans-serif;
    font-size: 12px;
    font-weight: bold;
    line-height: 1.2;
    white-space: nowrap;
    color: #e8eaf5;
    text-shadow:
        -1px -1px 0 rgba(6, 8, 16, .9), 1px -1px 0 rgba(6, 8, 16, .9),
        -1px 1px 0 rgba(6, 8, 16, .9), 1px 1px 0 rgba(6, 8, 16, .9),
        0 2px 3px rgba(6, 8, 16, .55);
}

/* ── A label that changes colour as one ───────────────────────────────────
   Rainbow 2's name, written the way Rainbow 2 looks: not a spectrum spread
   across the letters but a single colour on all of them, moving through the
   wheel together.

   Animating `color` and not a clipped gradient, because that is the whole
   difference being drawn — and because it needs no background, so the three
   labels' own `color` rules lose to it on specificity alone. */
.is-solidcycle,
.cos-name.is-solidcycle,
.inv-item-name.is-solidcycle,
.mp-trade-item-name.is-solidcycle {
    animation: cosSolidCycle 3s linear infinite;
}

@keyframes cosSolidCycle {
    0%   { color: #f91f1f; }
    17%  { color: #f9c11f; }
    33%  { color: #90f91f; }
    50%  { color: #1ff9c1; }
    67%  { color: #1f60f9; }
    83%  { color: #c11ff9; }
    100% { color: #f91f1f; }
}

@media (prefers-reduced-motion: reduce) {
    /* Left on the colour its arrow ships in, rather than mid-sweep. */
    .is-solidcycle { animation: none; color: #f91f1f; }
}

/* ── The Chameleon's tile ─────────────────────────────────────────────────
   A shop tile has no page to sample, so it cannot show the skin doing its job
   by doing its job. It shows the RELATIONSHIP instead: a patch of colour with
   the arrow sitting on it, both rotated by one filter so they travel together
   and the arrow stays matched to its background the whole way round.

   The patch is the item's restHex (server/cursors.js CHAMELEON); the arrow is
   painted a lightened version of the same colour at ingest, which is the same
   move the live painter makes over a dark surface. Change one and change the
   other or the tile stops being a demonstration and starts being a lie. */
.cos-chamdemo {
    background: #4caf50;
    border-radius: 3px;
    animation: cosHueCycle 3s linear infinite;
}

@media (prefers-reduced-motion: reduce) {
    .cos-chamdemo { animation: none; }
}

/* ── A skin whose colours travel ──────────────────────────────────────────
   Worn by anything the catalog marks with hueCycleMs. This covers every
   surface that draws the sprite as an image — shop tile, inventory tile, trade
   window, and a rival's pointer in a lobby — with one compositor-level filter
   rather than repainting pixels in JS.

   The real pointer cannot come here: `cursor: url()` takes a still image, so
   that one walks a ring of pre-rendered PNGs instead (public/script.js
   startCursorHueCycle). Keep the 3s here in step with the item's hueCycleMs,
   or your own pointer and your tile in the shop drift apart. */
.cos-huecycle {
    animation: cosHueCycle 3s linear infinite;
}

@keyframes cosHueCycle {
    from { filter: hue-rotate(0deg); }
    to   { filter: hue-rotate(360deg); }
}

@media (prefers-reduced-motion: reduce) {
    /* Left on its authored colours rather than mid-rotation, so a skin nobody
       asked to see moving still looks like the thing they bought. */
    .cos-huecycle { animation: none; }
}

/* ── The Rainbow name ─────────────────────────────────────────────────────
   Worn with the Rainbow pointer and nothing else (public/script.js
   RAINBOW_CURSOR_ID). A name is normally written in one colour, taken from the
   last colour cursor its owner wore; this is the one skin that cannot be said
   in a hex, so it is painted as a gradient clipped to the glyphs instead.

   `-webkit-text-fill-color` and not just `color`: the chat name is also a
   button and the lobby row has its own ink, both of which set `color` at equal
   or higher specificity. text-fill-color is what actually paints the glyph, so
   it settles the argument wherever the class lands.

   The selectors are doubled up on purpose. `.mp-chat-who--action` sets
   `background: none`, and the three item labels each set their own `color`
   further down the file, all at equal specificity — this rule has to win from
   wherever it sits, so it says the element and the class rather than trusting
   source order. */
.is-rainbow,
.mp-chat-who.is-rainbow,
.cos-name.is-rainbow,
.inv-item-name.is-rainbow,
.mp-trade-item-name.is-rainbow {
    background-image: linear-gradient(90deg,
        #ff2d2d, #ff9f1c, #ffe600, #3ddc57, #23b5ff, #7b5cff, #ff2d2d);
    /* Double width, scrolled a full width per cycle, with the same red at both
       ends: that is what makes the loop seamless instead of snapping back. */
    background-size: 200% 100%;
    -webkit-background-clip: text;
    background-clip: text;
    color: transparent;
    -webkit-text-fill-color: transparent;
    animation: rainbowName 4s linear infinite;
}

@keyframes rainbowName {
    from { background-position: 0 50%; }
    to   { background-position: 200% 50%; }
}

/* The cursor tag carries a hard four-way text-shadow for contrast, because it
   is read against whatever wallpaper the room's host chose. Clipped-to-text
   backgrounds and text-shadow do not mix — the shadow paints behind glyphs
   that are transparent, so it comes through as a smear where the letters
   should be. drop-shadow filters the rendered result instead, which keeps the
   name legible on a pale photo without touching the gradient. */
.mp-lobby-cursor-name.is-rainbow {
    text-shadow: none;
    filter:
        drop-shadow(0 0 2px rgba(6, 8, 16, 0.95))
        drop-shadow(0 1px 2px rgba(6, 8, 16, 0.7));
}

@media (prefers-reduced-motion: reduce) {
    /* The gradient stays, the travel stops. The colours are the item; the
       movement is decoration. */
    .is-rainbow,
    .mp-chat-who.is-rainbow,
    .cos-name.is-rainbow,
    .inv-item-name.is-rainbow,
    .mp-trade-item-name.is-rainbow { animation: none; }
}

@media (prefers-reduced-motion: reduce) {
    .mp-lobby-cursor { transition: none; }
}

.mp-lobby-head {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: center;
    gap: 10px 18px;
    width: 100%;
}

.mp-lobby-title {
    margin-bottom: 0;
    max-width: 100%;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.mp-lobby-meta {
    display: flex;
    align-items: center;
    gap: 10px;
}

.mp-room-code--small {
    padding: 4px 16px;
    font-size: 22px;
    letter-spacing: 6px;
    border-width: 3px;
}

/* The room code doubles as the copy-the-invite-link button. It has to keep
   reading as the code first — that is what people say out loud to each other —
   so this only adds the affordances a button needs and leaves the look alone. */
.mp-room-copy {
    display: inline-flex;
    align-items: center;
    gap: 10px;
    cursor: pointer;
    /* The trailing icon sits outside the tracking, which would otherwise push
       it a full letter-space away from the code. */
    padding-right: 12px;
    transition: transform 90ms ease, background 90ms ease, color 90ms ease;
}

.mp-room-copy svg {
    width: 0.7em;
    height: 0.7em;
    letter-spacing: normal;
}

.mp-room-copy:hover {
    background: #0032ff;
    color: var(--surface);
}

.mp-room-copy:active {
    transform: translateY(2px);
}

.mp-room-copy.is-copied {
    border-style: solid;
    border-color: #00a350;
    background: #00a350;
    color: #fff;
    /* "COPIED" is a word, not a code — the code's wide tracking makes it
       unreadable at this size. */
    letter-spacing: 2px;
}

.mp-room-copy.is-copy-failed {
    border-style: solid;
    border-color: #d10000;
    color: #d10000;
}

.mp-chip {
    padding: 5px 12px;
    border: 2px solid #000;
    border-radius: 999px;
    font-family: Arial, sans-serif;
    font-size: 12px;
    font-weight: 900;
    letter-spacing: 1px;
}

.mp-chip--public { background: #d7f5dc; color: #1b5e20; }
.mp-chip--private { background: #ffe9c7; color: #7a4b00; }

.mp-lobby-cols {
    display: grid;
    grid-template-columns: minmax(260px, 340px) minmax(320px, 1fr);
    gap: 18px;
    width: 100%;
    align-items: start;
    text-align: left;
}

/* ── The room at full width ───────────────────────────────────────────────
   A room is a workspace, not a card: the roster, the settings and the chat
   are all wanted at once, so it takes the whole window instead of sitting in
   the 980px box every other menu screen uses. Three columns, a header strip
   and an action bar — the shape a lobby wants, and the shape TETR.IO uses for
   the same job.

   Scoped to .mp-wide, which only the room view sets (multiplayer's own menu,
   the queue and the results stay centred cards). */
/* The stage normally pads 120px top and bottom to keep cards clear of the
   header and footer. A room wants that space, so in room mode it clears the
   chrome by exactly the chrome's height and nothing more. :not(.wiki-reading)
   because a race started from a room must get its scrolling article back. */
body.mp-room:not(.wiki-reading) {
    height: 100dvh;
    min-height: 0;
    overflow: hidden;
}

body.mp-room:not(.wiki-reading) main {
    min-height: 0;
    padding: 0 14px;
}

body.mp-room:not(.wiki-reading) .menu-stage {
    height: 100%;
    padding: calc(var(--chrome-h) + 10px) 0;
    overflow: hidden;
}

.multiplayer-container.mp-wide {
    max-width: none;
    width: 100%;
    height: 100%;
}

.mp-wide .multiplayer-screen { padding: 0; height: 100%; }

.mp-wide .mp-lobby-home {
    max-width: none;
    width: 100%;
    height: 100%;
    align-items: stretch;
    gap: 12px;
}

/* Header strip: title left, code and privacy chip right. The right padding is
   a gutter for the fixed profile button, which floats over this corner at
   right:44px and would otherwise sit on top of the room code. */
.mp-wide .mp-lobby-head {
    justify-content: space-between;
    flex-wrap: nowrap;
    gap: 16px;
    padding: 0 120px 0 4px;
    flex: 0 0 auto;
}
.mp-wide .mp-lobby-title { text-align: left; }

/* roster | settings | chat */
.mp-wide .mp-lobby-cols {
    grid-template-columns: minmax(220px, 300px) minmax(360px, 1fr) minmax(260px, 360px);
    align-items: stretch;
    flex: 1 1 auto;
    min-height: 0;
}

.mp-wide .mp-lobby-left,
.mp-wide .mp-lobby-right,
.mp-wide .mp-chat { height: 100%; min-height: 0; }

/* The roster is a list that can outgrow its column; it scrolls, the page does not. */
.mp-wide .mp-lobby-left { overflow-y: auto; }
.mp-wide .mp-lobby-left .mp-lobby-list { width: 100%; }

/* Settings keep their own scroll — but against the full column height rather
   than the 380px cap the narrow layout needed, so on an ordinary screen there
   is nothing left to scroll. Without dropping the cap the panel would stop
   short and leave the column half empty. */
.mp-wide .mp-set-panel {
    flex: 1 1 auto;
    min-height: 0;
    max-height: none;
    overflow-y: auto;
}

/* Chat grows into its column instead of being a fixed 150px box. */
.mp-wide .mp-chat { justify-content: stretch; }
.mp-wide .mp-chat-log { flex: 1 1 auto; height: auto; min-height: 0; }

/* Action bar across the bottom. Start sits centred rather than hard right:
   it is the one thing everyone in the room is looking for, and the corner it
   would otherwise occupy belongs to the fixed Discord button. */
.mp-wide .mp-lobby-btns {
    flex: 0 0 auto;
    display: grid;
    grid-template-columns: 1fr auto 1fr;
    align-items: center;
    width: 100%;
    padding: 0 4px;
}

.mp-wide .mp-lobby-btns .mp-back-btn { justify-self: start; }

.mp-wide .mp-lobby-btns .mp-play-btn,
.mp-wide .mp-lobby-btns .mp-note {
    grid-column: 2;
    justify-self: center;
}

/* Below three columns' worth of room, fall back to the stacked layout rather
   than crushing all three. Chat returns to the bottom, where it started. */
@media (max-width: 1100px) {
    .mp-wide .mp-lobby-cols {
        grid-template-columns: minmax(220px, 300px) minmax(0, 1fr);
    }
    .mp-wide .mp-chat { grid-column: 1 / -1; height: 220px; }
    .mp-wide .mp-lobby-home { height: auto; }
    .mp-wide .mp-lobby-left { overflow-y: visible; }
    /* Stacked, the room is taller than the window again, so give the page its
       scrolling back rather than clipping the action bar off the bottom. */
    body.mp-room:not(.wiki-reading),
    body.mp-room:not(.wiki-reading) .menu-stage {
        height: auto;
        overflow: visible;
    }
}

.mp-lobby-left,
.mp-lobby-right {
    display: flex;
    flex-direction: column;
    gap: 10px;
    min-width: 0;
}

.mp-lobby-left .mp-lobby-list { width: 100%; }

.mp-lobby-row--empty {
    justify-content: center;
    border-style: dashed;
    background: color-mix(in srgb, var(--surface) 55%, transparent);
    color: #8a92a5;
    font-size: 13px;
    text-transform: uppercase;
    letter-spacing: 1px;
}

/* Chat */

.mp-chat {
    display: flex;
    flex-direction: column;
    gap: 6px;
    border: 2px solid #000;
    border-radius: 8px;
    background: var(--surface);
    padding: 8px;
}

.mp-chat-log {
    height: 150px;
    overflow-y: auto;
    display: flex;
    flex-direction: column;
    gap: 3px;
    font-family: Arial, sans-serif;
    font-size: 13px;
    color: var(--ink-navy);
}

.mp-chat-line { word-break: break-word; }

/* Your own line used to be marked by painting the name #0032ff. The name now
   carries the colour of the cursor you are wearing (see chatInk), so the
   marker moves off the name and onto the row: it did not need to be a colour
   to do its job, and as a colour it was overwriting the one thing this line is
   now supposed to show. A fixed blue also ignored the accent and theme colours,
   so it was already the odd one out. */
.mp-chat-line.is-me {
    padding: 0 4px;
    border-radius: 3px;
    background: color-mix(in srgb, var(--ink-navy) 8%, transparent);
}
.mp-chat-who { font-weight: 900; }

.mp-chat-line--system {
    color: var(--ink-faint);
    font-style: italic;
}

/* Another player's name is the handle for muting/reporting them: it has to
   read as a name first and a button second, so it only reveals itself on
   hover. */
.mp-chat-who--action {
    padding: 0;
    border: 0;
    background: none;
    font: inherit;
    font-weight: 900;
    color: inherit;
    cursor: pointer;
}

.mp-chat-who--action:hover,
.mp-chat-who--action:focus-visible {
    text-decoration: underline;
    color: var(--ink-link);
}

/* ── Trading ──
   The window sits on <body>, not in the multiplayer panel: a lobby re-renders
   on every join, chat line and settings edit, and a trade can outlive the
   screen it started on. It is deliberately modal — a trade is the one thing in
   this game that permanently moves something between two accounts, and it
   should not be possible to do it by accident while doing something else. */
.mp-trade-wrap {
    position: fixed;
    inset: 0;
    z-index: 950;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 20px;
    background: rgba(6, 10, 26, 0.55);
    font-family: Arial, sans-serif;
}

.mp-trade {
    width: min(720px, 100%);
    max-height: 100%;
    overflow-y: auto;
    padding: 16px 18px 18px;
    border: 3px solid #000;
    border-radius: 14px;
    background: var(--surface);
    box-shadow: 0 8px 0 rgba(0, 0, 0, 0.3);
}

.mp-trade-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
    margin-bottom: 12px;
}

.mp-trade-title {
    font-size: 20px;
    font-weight: 900;
    text-transform: uppercase;
    letter-spacing: 1px;
    color: var(--ink-navy);
}

.mp-trade-close {
    appearance: none;
    width: 30px;
    height: 30px;
    border: 2px solid #000;
    border-radius: 8px;
    background: var(--surface);
    font-size: 16px;
    font-weight: 900;
    line-height: 1;
    color: var(--ink-navy);
    cursor: pointer;
}

/* Two ends of one table, side by side and the same size — a trade window that
   gave one side more room would be reading as an opinion about the deal. */
.mp-trade-sides {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 12px;
}

.mp-trade-side {
    display: flex;
    flex-direction: column;
    padding: 10px;
    border: 2px solid #000;
    border-radius: 10px;
    background: color-mix(in srgb, var(--surface) 92%, #000);
}

/* Ready is a state worth seeing from across the window, since it is the thing
   that decides whether the next click moves anything. */
.mp-trade-side.is-confirmed {
    border-color: #1faf3c;
    background: color-mix(in srgb, #43d95e 12%, var(--surface));
}

.mp-trade-side-head {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 8px;
    margin-bottom: 8px;
}

.mp-trade-who {
    font-weight: 900;
    color: var(--ink-navy);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.mp-trade-ready {
    font-size: 11px;
    font-weight: 900;
    letter-spacing: 1px;
    color: #1faf3c;
}

.mp-trade-slots {
    display: flex;
    flex-wrap: wrap;
    gap: 8px;
    min-height: 92px;
    align-content: flex-start;
}

.mp-trade-item {
    position: relative;
    width: 84px;
    padding: 6px 4px 5px;
    border: 2px solid #000;
    border-radius: 8px;
    background: radial-gradient(120% 90% at 50% 0%,
        color-mix(in srgb, var(--rar, #9aa3b2) 42%, #fff) 0%, #fdfdff 78%);
    font-family: Arial, sans-serif;
    text-align: center;
    cursor: default;
}

button.mp-trade-item { cursor: pointer; }

.mp-trade-item-art {
    display: grid;
    place-items: center;
    height: 58px;
    overflow: hidden;
}

.mp-trade-item-name {
    font-size: 10px;
    font-weight: bold;
    line-height: 1.2;
    color: var(--ink-navy);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* Only on your own side: theirs is not yours to edit. */
.mp-trade-item-x {
    position: absolute;
    top: -7px;
    right: -7px;
    width: 18px;
    height: 18px;
    border: 2px solid #000;
    border-radius: 50%;
    background: #e53935;
    font-size: 12px;
    font-weight: 900;
    line-height: 15px;
    color: #fff;
}

[data-trade-drop] { cursor: pointer; }

.mp-trade-add {
    appearance: none;
    width: 84px;
    min-height: 88px;
    border: 2px dashed #98a0bb;
    border-radius: 8px;
    background: transparent;
    font-size: 24px;
    font-weight: 900;
    color: #98a0bb;
    cursor: pointer;
}

.mp-trade-add:hover {
    border-color: #0032ff;
    color: var(--ink-link);
}

.mp-trade-empty {
    margin: 4px 2px;
    font-size: 12px;
    color: var(--ink-muted);
}

.mp-trade-picker {
    margin-top: 12px;
    padding: 10px;
    border: 2px solid #000;
    border-radius: 10px;
    background: color-mix(in srgb, var(--surface) 92%, #000);
}

.mp-trade-picker-head {
    margin-bottom: 8px;
    font-size: 12px;
    font-weight: 900;
    letter-spacing: 1px;
    text-transform: uppercase;
    color: var(--ink-muted);
}

.mp-trade-picker-grid {
    display: flex;
    flex-wrap: wrap;
    gap: 8px;
    max-height: 210px;
    overflow-y: auto;
}

.mp-trade-note {
    margin: 12px 2px 0;
    font-size: 12px;
    line-height: 1.45;
    color: var(--ink-muted);
}

.mp-trade-btns {
    display: flex;
    align-items: center;
    justify-content: flex-end;
    gap: 10px;
    margin-top: 12px;
}

.mp-trade-invite {
    padding: 6px 2px 2px;
}

.mp-trade-invite-text {
    margin: 0;
    font-size: 15px;
    color: var(--ink-navy);
}

@media (max-width: 620px) {
    .mp-trade-sides { grid-template-columns: 1fr; }
}

.mp-mod-menu {
    position: fixed;
    z-index: 900;
    min-width: 150px;
    padding: 6px;
    border: 2px solid #000;
    border-radius: 8px;
    background: var(--surface);
    box-shadow: 0 6px 18px rgba(0, 0, 0, 0.25);
    font-family: Arial, sans-serif;
    font-size: 13px;
}

.mp-mod-head {
    padding: 2px 8px 6px;
    border-bottom: 1px solid #d8dced;
    margin-bottom: 4px;
    font-weight: 900;
    color: var(--ink-navy);
    max-width: 200px;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.mp-mod-item {
    display: block;
    width: 100%;
    padding: 7px 8px;
    border: 0;
    border-radius: 6px;
    background: none;
    text-align: left;
    font: inherit;
    font-weight: bold;
    color: var(--ink-navy);
    cursor: pointer;
}

.mp-mod-item:hover { background: var(--surface-2); }
.mp-mod-item--report { color: #b3261e; }
.mp-mod-item--report:hover { background: #fdecea; }

.mp-chat-input {
    width: 100%;
    padding: 8px 10px;
    border: 2px solid #000;
    border-radius: 6px;
    background: var(--surface-2);
    font-family: Arial, sans-serif;
    font-size: 13px;
    font-weight: bold;
    color: var(--ink-navy);
}

.mp-chat-input:focus { outline: none; border-color: #0032ff; background: var(--surface); }

.mp-chat-input--game { margin-top: 8px; }

/* Settings tabs */

.mp-set-tabs {
    display: flex;
    gap: 6px;
}

.mp-set-tab {
    appearance: none;
    flex: 1;
    padding: 9px 6px;
    border: 3px solid #000;
    border-radius: 8px 8px 0 0;
    border-bottom: none;
    background: #dfe3f2;
    color: #4a5268;
    font-family: Arial, sans-serif;
    font-size: 14px;
    font-weight: 900;
    letter-spacing: 1px;
    cursor: pointer;
}

.mp-set-tab.is-active {
    background: var(--surface);
    color: var(--ink-link);
    box-shadow: inset 0 3px 0 #0032ff;
}

.mp-set-panel {
    display: flex;
    flex-direction: column;
    border: 3px solid #000;
    border-radius: 0 0 8px 8px;
    background: var(--surface);
    padding: 6px 12px;
    max-height: 380px;
    overflow-y: auto;
}

.mp-set-row {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
    padding: 9px 0;
    border-bottom: 1px solid #e2e6f2;
}

.mp-set-row:last-child { border-bottom: none; }

.mp-set-row--stack {
    flex-direction: column;
    align-items: stretch;
    gap: 8px;
}

.mp-set-info { min-width: 0; }

.mp-set-label {
    font-family: Arial, sans-serif;
    font-size: 14px;
    font-weight: 900;
    color: var(--ink-navy);
}

.mp-set-desc {
    font-family: Arial, sans-serif;
    font-size: 12px;
    color: var(--ink-faint);
    margin-top: 1px;
}

/* ── The article picker ──
   What the custom start/target boxes offer while you type. Parented to <body>
   and positioned over the page, like .mp-mod-menu, because the settings panel
   scrolls and would clip a dropdown hanging out of it. */
.wiki-picker {
    position: fixed;
    z-index: 1400;
    max-height: 300px;
    overflow-y: auto;
    border: 2px solid #000;
    border-radius: 8px;
    background: var(--surface);
    box-shadow: 0 6px 0 rgba(0, 0, 0, 0.18);
}

.wiki-pick {
    display: flex;
    align-items: center;
    gap: 10px;
    width: 100%;
    padding: 7px 10px;
    border: 0;
    border-bottom: 1px solid var(--line, #dde1f0);
    background: none;
    font-family: Arial, sans-serif;
    text-align: left;
    cursor: pointer;
}

.wiki-pick:last-child { border-bottom: 0; }
.wiki-pick:hover,
.wiki-pick.is-active { background: var(--surface-2); }

/* Wikimedia hands back 60px thumbnails, so this is their natural size. The
   placeholder keeps the same box for an article that has no picture — a list
   where some rows are indented and some are not reads as broken. */
.wiki-pick-thumb {
    flex: 0 0 auto;
    width: 34px;
    height: 34px;
    border-radius: 5px;
    object-fit: cover;
    background: var(--surface-2);
}

.wiki-pick-thumb--none {
    display: block;
    border: 1px dashed #c3c9dd;
}

.wiki-pick-text {
    min-width: 0;
    display: flex;
    flex-direction: column;
}

.wiki-pick-title {
    font-size: 13px;
    font-weight: bold;
    color: var(--ink-navy);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.wiki-pick-desc {
    font-size: 11px;
    color: var(--ink-faint);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* ── The match row: start, rule, target ──
   Three selects sharing one row, in the order the race happens. They were one
   control until the two questions in it were separated — where a race runs, and
   what the rule is — so showing them side by side is not decoration: it is the
   thing that makes it obvious they are independent. */
.mp-match {
    display: flex;
    align-items: flex-end;
    gap: 10px;
    margin: 4px 0 12px;
}

.mp-match-slot {
    flex: 1 1 0;
    min-width: 0;
}

/* The middle one is the interesting choice and the widest word, so it gets the
   room. */
.mp-match-slot:nth-child(2) {
    flex: 1.2 1 0;
}

.mp-match-cap {
    font-family: Arial, sans-serif;
    font-size: 10px;
    font-weight: 900;
    letter-spacing: 1.5px;
    text-transform: uppercase;
    color: var(--ink-muted);
    margin-bottom: 5px;
}

/* The shared select is built for a label-on-the-left settings row, so it caps
   at 180px and refuses to shrink. In this row it is the whole column: it fills
   its slot, and the cap comes off or three selects sit in a third of the panel
   with the rest of it empty. */
/* Scoped to .mp-match so it outranks the shared .mp-set-select rule, which is
   further down this file and would otherwise win the max-width on equal
   specificity. */
.mp-match .mp-match-select {
    width: 100%;
    max-width: none;
    flex-shrink: 1;
}

@media (max-width: 560px) {
    .mp-match { flex-wrap: wrap; }
    .mp-match-slot { flex: 1 1 100%; }
}

.mp-set-hint {
    margin: 8px 0;
    font-family: Arial, sans-serif;
    font-size: 12px;
    color: var(--ink-faint);
    font-style: italic;
}

.mp-set-toggle {
    appearance: none;
    flex-shrink: 0;
    min-width: 58px;
    padding: 6px 0;
    border: 2px solid #000;
    border-radius: 999px;
    background: var(--surface-3);
    color: var(--ink-muted);
    font-family: Arial, sans-serif;
    font-size: 12px;
    font-weight: 900;
    letter-spacing: 1px;
    cursor: pointer;
    box-shadow: 0 2px 0 rgba(0, 0, 0, 0.2);
}

.mp-set-toggle.is-on {
    background: linear-gradient(180deg, #43d95e, #1faf3c);
    color: #fff;
}

.mp-set-toggle:disabled,
.mp-set-select:disabled,
.mp-set-text:disabled,
.mp-set-slider input:disabled,
.mp-item-chip:disabled {
    opacity: 0.6;
    cursor: default;
    box-shadow: none;
}

.mp-set-select,
.mp-set-text {
    flex-shrink: 0;
    padding: 6px 8px;
    border: 2px solid #000;
    border-radius: 6px;
    background: var(--surface-2);
    font-family: Arial, sans-serif;
    font-size: 13px;
    font-weight: bold;
    color: var(--ink-navy);
    max-width: 180px;
}

.mp-set-text { text-align: right; }
.mp-set-text:focus, .mp-set-select:focus { outline: none; border-color: #0032ff; background: var(--surface); }

.mp-set-slider {
    display: flex;
    align-items: center;
    gap: 8px;
    font-family: Arial, sans-serif;
    font-size: 10px;
    font-weight: 900;
    color: var(--ink-faint);
    letter-spacing: 1px;
}

.mp-set-slider input {
    flex: 1;
    accent-color: #0032ff;
}

.mp-item-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(130px, 1fr));
    gap: 6px;
}

.mp-item-chip {
    appearance: none;
    display: flex;
    align-items: center;
    gap: 6px;
    padding: 6px 8px;
    border: 2px solid #000;
    border-radius: 6px;
    background: var(--surface);
    font-family: Arial, sans-serif;
    font-size: 12px;
    font-weight: bold;
    color: var(--ink-navy);
    cursor: pointer;
    text-align: left;
}

.mp-item-chip-name {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.mp-item-chip.is-banned {
    background: #f3f3f3;
    color: #aaa;
    border-color: #999;
    text-decoration: line-through;
}

.mp-set-readonly-note { font-size: 12px; margin: 0; }

/* In-game backtrack button */

.mp-goback-btn {
    appearance: none;
    width: 100%;
    padding: 10px;
    border: 2px solid #000;
    border-radius: 8px;
    background: linear-gradient(180deg, #ffd66b, #f7b733);
    font-family: Arial, sans-serif;
    font-size: 14px;
    font-weight: 900;
    color: #5b3a00;
    cursor: pointer;
    box-shadow: 0 3px 0 rgba(0, 0, 0, 0.25);
}

.mp-goback-btn:disabled {
    background: var(--surface-3);
    color: #999;
    cursor: default;
    box-shadow: none;
}

/* Public room browser */

.mp-roomlist-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    width: min(100%, 460px);
    margin: 18px auto 8px;
}

.mp-roomlist-title {
    font-family: Arial, sans-serif;
    font-size: 15px;
    font-weight: 900;
    letter-spacing: 1px;
    text-transform: uppercase;
    color: var(--ink-mid);
}

.mp-roomlist {
    display: flex;
    flex-direction: column;
    gap: 8px;
    width: min(100%, 460px);
    margin: 0 auto;
    max-height: 260px;
    overflow-y: auto;
}

.mp-room-row {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 8px 12px;
    border: 2px solid #000;
    border-radius: 8px;
    background: var(--surface);
    font-family: Arial, sans-serif;
    text-align: left;
}


.mp-room-row-info {
    flex: 1;
    min-width: 0;
    display: flex;
    flex-direction: column;
}

.mp-room-row-name {
    font-size: 14px;
    font-weight: 900;
    color: var(--ink-navy);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.mp-room-row-meta {
    font-size: 11px;
    color: var(--ink-faint);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.mp-room-row-count {
    font-size: 13px;
    font-weight: 900;
    color: var(--ink-link);
    flex-shrink: 0;
}

.mp-room-row-live {
    font-size: 11px;
    font-weight: 900;
    color: #b71c1c;
    flex-shrink: 0;
}

/* ── Spectator mode ── */

.mp-option--wide { grid-column: 1 / -1; }

.mp-ghost-cursor {
    position: absolute;
    left: 0;
    top: 0;
    z-index: 340;
    pointer-events: none;
    will-change: transform;
}

.mp-ghost-skin {
    position: relative; /* .fx-cursor is fixed by default — pin it to the ghost */
    left: auto;
    top: auto;
    z-index: auto;
}

/* The watched racer's name, hung directly under their pointer and centred on
   it — `top` is set inline by buildGhost, which knows how far down the sprite
   actually reaches.

   No pill: the tag labels a person moving over an article, and a blue lozenge
   over the text read as a piece of the page rather than as part of the cursor.
   The pill was really buying contrast, so that comes back as a hard dark
   outline, the same one the lobby tags wear — the article underneath can be
   any theme, from white paper to somebody's photo wallpaper. The ink itself is
   set inline: that player's colour, exactly as the chat writes them. */
.mp-ghost-name {
    position: absolute;
    left: 0;
    transform: translateX(-50%);
    color: #e8eaf5;
    font-family: Arial, Helvetica, sans-serif;
    font-size: 13px;
    font-weight: bold;
    line-height: 1.2;
    white-space: nowrap;
    text-shadow:
        -1px -1px 0 rgba(6, 8, 16, .9), 1px -1px 0 rgba(6, 8, 16, .9),
        -1px 1px 0 rgba(6, 8, 16, .9), 1px 1px 0 rgba(6, 8, 16, .9),
        0 2px 3px rgba(6, 8, 16, .55);
}

/* Same trade as the lobby tag: a clipped gradient cannot carry a text-shadow
   (it paints through the transparent glyphs), so the outline becomes a filter
   on the rendered result. */
.mp-ghost-name.is-rainbow {
    text-shadow: none;
    filter:
        drop-shadow(0 0 2px rgba(6, 8, 16, 0.95))
        drop-shadow(0 1px 2px rgba(6, 8, 16, 0.7));
}

.mp-spectate-badge {
    padding: 4px 10px;
    border: 2px solid #000;
    border-radius: 6px;
    background: #d40000;
    color: #fff;
    font-family: Arial, sans-serif;
    font-size: 12px;
    font-weight: 900;
    letter-spacing: 1px;
    animation: mpLivePulse 1.6s ease-in-out infinite;
}

@keyframes mpLivePulse {
    0%, 100% { opacity: 1; }
    50%      { opacity: 0.55; }
}

.mp-rival--watch {
    appearance: none;
    display: block;
    width: 100%;
    text-align: left;
    cursor: pointer;
}

.mp-rival--watch:hover { border-color: #8fa3ff; }

.mp-rival--watch.is-followed {
    border-color: #0032ff;
    background: #e3f2fd;
    box-shadow: 0 0 0 2px rgba(0, 50, 255, 0.25);
}

.mp-spectate .wiki-link-inline { cursor: default; }

.mp-spectate-leave { width: 100%; }

.mp-result-btns {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 12px;
}

.mp-result-btns .mp-back-btn { margin-top: 0; }

@media (max-width: 860px) {
    .mp-lobby-cols { grid-template-columns: 1fr; }
    .mp-set-panel { max-height: none; }
}

/* ── In-game layout ── */

.mp-game {
    position: relative;
    width: min(100%, 1240px);
    margin: 0 auto;
    padding-bottom: 40px;
}

.mp-game-body {
    display: grid;
    grid-template-columns: minmax(0, 1fr) 260px;
    gap: 18px;
    align-items: start;
}

.mp-article-stage {
    width: 100%;
}

.mp-side {
    position: sticky;
    top: 196px;
    display: flex;
    flex-direction: column;
    gap: 14px;
}

.mp-side-block {
    border: 3px solid #000;
    border-radius: 10px;
    background: color-mix(in srgb, var(--surface) 94%, transparent);
    box-shadow: 0 5px 0 rgba(0, 0, 0, 0.22);
    padding: 10px 12px;
}

.mp-side-title {
    font-family: Arial, sans-serif;
    font-size: 12px;
    font-weight: 900;
    letter-spacing: 2px;
    text-transform: uppercase;
    color: var(--ink-muted);
    margin-bottom: 8px;
}

/* ── Tandem: whose turn it is ──
   The mode puts the whole room on one page and lets one racer click, which
   means the screen has to answer "is this mine?" from across the desk and
   without being read. Two answers, both loud: the panel, and the article
   itself going quiet when the page is somebody else's. */
.mp-turn {
    display: flex;
    flex-direction: column;
    gap: 6px;
}

.mp-turn-who {
    font-family: Arial, sans-serif;
    font-size: 15px;
    font-weight: 900;
    letter-spacing: 0.5px;
    color: var(--ink-mid);
}

.mp-turn.is-mine .mp-turn-who {
    color: #0032ff;
    text-transform: uppercase;
}

/* The block itself lights up, so the answer is readable in the corner of the
   eye of someone whose attention is on the article. */
.mp-side-block--turn {
    transition: border-color 0.15s ease, box-shadow 0.15s ease;
}

.mp-side-block--turn:has(.mp-turn.is-mine) {
    border-color: #0032ff;
    box-shadow: 0 5px 0 rgba(0, 50, 255, 0.3);
    background: color-mix(in srgb, #0032ff 8%, var(--surface));
}

/* A bar, not a number. What a racer needs off the clock is how much of their
   turn is left, which a shrinking bar answers without being read — and a
   number ticking down in the corner of the eye is a second pressure the mode
   does not need on top of the one it already has. */
.mp-turn-clock {
    height: 8px;
    border: 2px solid #000;
    border-radius: 5px;
    background: var(--surface-3);
    overflow: hidden;
}

.mp-turn-bar {
    display: block;
    height: 100%;
    width: 100%;
    background: #0032ff;
    /* Matches the 250ms tick in script.js, so the bar slides instead of
       stepping four times a second. */
    transition: width 0.25s linear, background-color 0.2s ease;
}

.mp-turn-bar.is-low { background: #d32f2f; }

.mp-turn-next {
    font-family: Arial, sans-serif;
    font-size: 11px;
    font-weight: bold;
    color: var(--ink-faint);
}

/* Somebody else's turn: the links stop pretending a click will do anything.
   Kept as a dimming rather than pointer-events:none, because the racer is
   still reading the page and planning their own turn on it — scrolling,
   selecting and hovering all still work, and a dead link that eats the cursor
   reads as the page having crashed. */
.article-stage.is-not-my-turn .wiki-link-inline {
    opacity: 0.55;
    cursor: not-allowed;
}

.mp-rivals {
    display: flex;
    flex-direction: column;
    gap: 8px;
}

.mp-rival {
    padding: 7px 10px;
    border: 2px solid #c8ccd1;
    border-radius: 7px;
    background: var(--surface);
    font-family: Arial, sans-serif;
}

.mp-rival.is-me {
    border-color: #0032ff;
    background: #e3f2fd;
}

.mp-rival-top {
    display: flex;
    justify-content: space-between;
    gap: 6px;
}

.mp-rival-name {
    font-weight: 900;
    font-size: 14px;
    color: var(--ink-navy);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* A rival's name doubles as the mute/report handle (see renderRivals) — it
   must still read as a label, so the affordance only shows on hover. */
.mp-rival-name--action {
    padding: 0;
    border: 0;
    background: none;
    font: inherit;
    font-weight: 900;
    font-size: 14px;
    color: inherit;
    text-align: left;
    cursor: pointer;
}

.mp-rival-name--action:hover,
.mp-rival-name--action:focus-visible {
    text-decoration: underline;
    color: var(--ink-link);
}

.mp-rival-moves {
    flex: 0 0 auto;
    font-weight: bold;
    font-size: 12px;
    color: var(--ink-link);
}

/* ── Golf and Bloodhound: the one line of state that has to be on screen ──
   Both modes break a rule the racer already knows — reaching the target does
   not end golf, and a hound cannot move for the first twenty seconds — so the
   correction lives in the sidebar rather than in a toast that has faded by the
   time it matters. */
.mp-mode-line {
    font-family: Arial, sans-serif;
    font-size: 15px;
    font-weight: 900;
    letter-spacing: 0.5px;
    color: var(--ink-mid);
}

.mp-mode-line.is-good {
    color: #0032ff;
    text-transform: uppercase;
}

.mp-mode-note {
    font-family: Arial, sans-serif;
    font-size: 11px;
    font-weight: bold;
    color: var(--ink-faint);
    margin-top: 2px;
}

/* A number, not a bar — unlike the tandem turn clock. These two count seconds
   the racer is waiting OUT rather than seconds they are spending, and the
   question is "how long until I can move", which wants an answer you can read. */
.mp-mode-clock {
    font-family: Consolas, monospace;
    font-size: 12px;
    font-weight: bold;
    color: var(--ink-link);
    margin-top: 6px;
}

.mp-mode-clock.is-low { color: #d32f2f; }

/* Role tags on a racer's name, in the panel and on the results card: the hare,
   a stamped waypoint, a card already handed in. */
.mp-rival-badge {
    flex: 0 0 auto;
    margin-left: 6px;
    padding: 1px 6px;
    border-radius: 999px;
    font-family: Arial, sans-serif;
    font-size: 9.5px;
    font-weight: 900;
    letter-spacing: 0.6px;
    text-transform: uppercase;
    background: rgba(0, 50, 255, 0.1);
    color: var(--ink-link);
}

.mp-rival-badge.is-hare {
    background: rgba(211, 47, 47, 0.12);
    color: #d32f2f;
}

.mp-rival-badge.is-in {
    background: rgba(46, 125, 50, 0.14);
    color: #2e7d32;
}

.mp-rival-article {
    font-size: 12px;
    color: var(--ink-muted);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.mp-item-slot {
    display: flex;
    align-items: center;
    gap: 10px;
    width: 100%;
    padding: 10px 12px;
    border: 3px solid #f6d83f;
    border-radius: 9px;
    background: linear-gradient(180deg, #fff8dc, #ffe082);
    cursor: pointer;
    font-family: Arial, sans-serif;
    text-align: left;
    animation: mpItemPulse 1.1s ease-in-out infinite;
}

.mp-item-slot.is-empty {
    border-color: #c8ccd1;
    background: var(--surface-2);
    cursor: default;
    animation: none;
    opacity: 0.75;
}

@keyframes mpItemPulse {
    0%, 100% { box-shadow: 0 0 0 0 rgba(246, 216, 63, 0.8); }
    50%      { box-shadow: 0 0 0 7px rgba(246, 216, 63, 0); }
}

.mp-item-icon { font-size: 26px; }

.mp-item-name {
    font-weight: 900;
    font-size: 13px;
    color: var(--ink-navy);
}

.mp-feed {
    display: flex;
    flex-direction: column;
    gap: 5px;
    min-height: 30px;
}

.mp-feed-entry {
    font-family: Arial, sans-serif;
    font-size: 12px;
    font-weight: bold;
    color: var(--ink-mid);
    padding: 4px 8px;
    border-radius: 5px;
    background: rgba(0, 50, 255, 0.06);
}

/* ── Countdown ── */

.mp-countdown {
    position: fixed;
    inset: 0;
    z-index: 9000;
    display: grid;
    place-items: center;
    background: rgba(10, 14, 34, 0.55);
    pointer-events: none;
}

.mp-countdown span {
    font-family: Arial, sans-serif;
    font-size: 150px;
    font-weight: 900;
    color: #fff;
    -webkit-text-stroke: 5px #0032ff;
    text-shadow: 0 8px 0 rgba(0, 0, 0, 0.4);
    animation: mpCountPop 1s ease-in-out infinite;
}

.mp-countdown.is-go { background: transparent; }
.mp-countdown.is-go span { color: #43d95e; -webkit-text-stroke: 5px #0b5c1e; }

@keyframes mpCountPop {
    0%   { transform: scale(0.6); opacity: 0; }
    30%  { transform: scale(1.15); opacity: 1; }
    60%  { transform: scale(1); }
    100% { transform: scale(1); opacity: 1; }
}

/* ── Rainbow power-up links ── */

/* A power-up link. The spectrum runs through the LETTERS rather than behind
   them: this sits inside a Wikipedia article, and a filled pill with a glowing
   box around it stops reading as a link in a sentence and starts reading as a
   button someone dropped on the page.

   The ramp is a stop darker than the obvious one all the way round. The old
   version could afford #ffe93d because white text sat on top of it; with the
   gradient clipped to the glyphs the colours ARE the text, and a light yellow
   word on an article's white background is a word nobody can read.

   Nothing here paints a box, so the padding, the radius and the pulsing
   box-shadow are gone with it — a glow with no background to sit on draws a
   rectangle in mid-air. Definition comes from a drop-shadow instead, because
   text-shadow paints behind glyphs that are transparent and comes through as a
   smear where the letters should be. */
.wiki-link-inline.wiki-link-rainbow {
    background-image: linear-gradient(90deg,
        #e03131, #e8590c, #b8a000, #2f9e44, #1c7ed6, #7048e8, #d6336c, #e03131);
    background-size: 300% 100%;
    -webkit-background-clip: text;
    background-clip: text;
    color: transparent;
    -webkit-text-fill-color: transparent;
    font-weight: bold;
    text-decoration: none;
    /* Overrides the inline link's own coloured glow, which would smear now. */
    text-shadow: none;
    animation: mpRainbowSlide 2s linear infinite, mpRainbowHalo 2s linear infinite;
}

@keyframes mpRainbowSlide {
    to { background-position: 300% 0; }
}

/* The glow. drop-shadow and not box-shadow, because drop-shadow takes the
   shape of what is actually painted — with the gradient clipped to the glyphs
   that is the letters themselves, so the halo hugs the word instead of drawing
   a rectangle around it, which is what the old pill's box-shadow did.

   Two shadows on every frame: a tight dark one that keeps the letters defined
   against a white article, and a wide coloured one that is the glow. The
   colour walks the same spectrum as the gradient over the same two seconds, so
   the halo is always about the colour the word is currently wearing rather
   than drifting against it. Animated as one `filter` because that is one
   property — the halves cannot be given separate timings. */
@keyframes mpRainbowHalo {
    0%   { filter: drop-shadow(0 1px 1px rgba(0, 0, 0, 0.35)) drop-shadow(0 0 6px rgba(224, 49, 49, 0.9)); }
    17%  { filter: drop-shadow(0 1px 1px rgba(0, 0, 0, 0.35)) drop-shadow(0 0 6px rgba(232, 89, 12, 0.9)); }
    33%  { filter: drop-shadow(0 1px 1px rgba(0, 0, 0, 0.35)) drop-shadow(0 0 6px rgba(184, 160, 0, 0.9)); }
    50%  { filter: drop-shadow(0 1px 1px rgba(0, 0, 0, 0.35)) drop-shadow(0 0 6px rgba(47, 158, 68, 0.9)); }
    67%  { filter: drop-shadow(0 1px 1px rgba(0, 0, 0, 0.35)) drop-shadow(0 0 6px rgba(28, 126, 214, 0.9)); }
    83%  { filter: drop-shadow(0 1px 1px rgba(0, 0, 0, 0.35)) drop-shadow(0 0 6px rgba(112, 72, 232, 0.9)); }
    100% { filter: drop-shadow(0 1px 1px rgba(0, 0, 0, 0.35)) drop-shadow(0 0 6px rgba(224, 49, 49, 0.9)); }
}

@media (prefers-reduced-motion: reduce) {
    /* Both animations stop, so the glow has to be stated once as a still. */
    .wiki-link-inline.wiki-link-rainbow {
        animation: none;
        filter: drop-shadow(0 1px 1px rgba(0, 0, 0, 0.35)) drop-shadow(0 0 6px rgba(224, 49, 49, 0.9));
    }
}

/* ── Fahrenheit 451 ── */
/* The clicked link chars in place instead of navigating. `burning` is the
   one-off flash on the link that fired the trap; `burnt` is the state every
   later render re-applies, so a burnt article reads as ash on every page it
   turns up on for the rest of the race. The button is disabled underneath,
   which is what actually makes it unclickable — this is the telling. */
.wiki-link-inline.wiki-link-burnt {
    color: var(--wiki-burnt);
    /* A burnt rainbow drops its gradient, so the text has to take colour
       normally again rather than staying clipped-transparent. */
    background-image: none;
    -webkit-text-fill-color: currentColor;
    text-decoration: line-through;
    text-decoration-color: #d9480f;
    text-shadow: none;
    cursor: not-allowed;
}

.wiki-link-inline.wiki-link-burning {
    animation: mpBurnChar 900ms ease-out forwards;
}

@keyframes mpBurnChar {
    0%   { color: #ff922b; text-shadow: 0 0 10px rgba(255, 146, 43, 0.95); }
    30%  { color: #e8590c; text-shadow: 0 0 14px rgba(232, 89, 12, 0.9); transform: translateY(-1px); }
    65%  { color: #7f3b12; text-shadow: 0 0 8px rgba(217, 72, 15, 0.55); }
    100% { color: var(--wiki-burnt); text-shadow: none; transform: translateY(0); }
}

@media (prefers-reduced-motion: reduce) {
    /* No flicker: the link is simply ash the moment it is clicked. */
    .wiki-link-inline.wiki-link-burning { animation: none; }
}

/* ── Untraceable: an article a rival got to first ──
   Dead like ash, but not ash. A burnt link is a thing that happened TO you and
   is written in one charred colour; a taken link is a thing a particular person
   did, so it is written in that person's colour — the same ink their name is in
   in the chat, set inline as --taken-ink by MPFX.takeLink. Reading the page in
   this mode then tells you who has been where, not merely that somebody has.

   The fallback is the burnt colour, for a claimer with no colour of their own
   (a guest who never answered the signup question, or a bot). */
.wiki-link-inline.wiki-link-taken {
    text-decoration: line-through;
    text-decoration-color: currentColor;
    text-decoration-thickness: 2px;
    text-shadow: none;
    cursor: not-allowed;
    opacity: 0.9;
}

/* Split from the rule above so a rainbow claim keeps its clipped gradient:
   `color` and text-fill here would beat .is-rainbow on specificity and paint
   the whole thing one flat colour. */
.wiki-link-inline.wiki-link-taken:not(.is-rainbow) {
    color: var(--taken-ink, var(--wiki-burnt));
    -webkit-text-fill-color: currentColor;
    background-image: none;
}

/* A rainbow claimer takes the link in the gradient the rest of the game writes
   their name in. The strike-through has to be given a colour of its own here:
   the gradient makes currentColor transparent, so a decoration inheriting it
   would leave the link looking merely coloured rather than closed. */
.wiki-link-inline.wiki-link-taken.is-rainbow {
    text-decoration-color: #8a8f9c;
}

/* The moment it goes: a quick pulse in the claimer's own colour, so an article
   dying while you are staring at the list is something you see happen rather
   than something you find already true. Colour is left alone — the animation
   is the event, the ink is the identity. */
.wiki-link-inline.wiki-link-taken.is-claiming {
    animation: mpClaimStruck 700ms ease-out;
}

@keyframes mpClaimStruck {
    0%   { opacity: 1; transform: translateY(0) scale(1.06); filter: brightness(1.5); }
    40%  { opacity: 1; filter: brightness(1.2); }
    100% { opacity: 0.9; transform: translateY(0) scale(1); filter: none; }
}

@media (prefers-reduced-motion: reduce) {
    /* The link is simply shut, with no flourish about it. */
    .wiki-link-inline.wiki-link-taken.is-claiming { animation: none; }
}

/* ── Result overlay ── */

.mp-result-overlay {
    position: fixed;
    inset: 0;
    z-index: 9100;
    display: grid;
    place-items: center;
    background: rgba(10, 14, 34, 0.6);
    animation: mpFadeIn 0.3s ease-out;
}

@keyframes mpFadeIn {
    from { opacity: 0; }
}

.mp-result-card {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 16px;
    width: min(92vw, 480px);
    padding: 30px 34px;
    border: 5px solid #000;
    border-radius: 14px;
    background: var(--surface);
    box-shadow: 0 12px 0 rgba(0, 0, 0, 0.35);
}

.mp-result-card.is-win {
    border-color: #f6d83f;
    background: linear-gradient(180deg, #fffdf2, #fff3bd);
}

.mp-result-title {
    font-family: Arial, sans-serif;
    font-size: 34px;
    font-weight: 900;
    color: var(--ink-navy);
    text-align: center;
}

.mp-result-elo {
    padding: 6px 18px;
    border-radius: 8px;
    font-family: Arial, sans-serif;
    font-size: 19px;
    font-weight: 900;
}

.mp-result-elo.is-up { background: #e8f5e9; color: #1b7d34; }
.mp-result-elo.is-down { background: #ffebee; color: #b71c1c; }

@media (max-width: 900px) {
    .mp-game-body {
        grid-template-columns: 1fr;
    }

    .mp-side {
        position: static;
        flex-direction: row;
        flex-wrap: wrap;
    }

    .mp-side-block { flex: 1 1 180px; }

    .mp-options--grid {
        grid-template-columns: 1fr;
    }

    .mp-countdown span { font-size: 90px; }
}

/* ═══ Profile menu & Badges ═════════════════════════════════════════════════ */

.profile-menu {
    position: fixed;
    z-index: 9200;
    min-width: 190px;
    border: 3px solid #000;
    border-radius: 12px;
    background: var(--surface);
    box-shadow: 0 8px 0 rgba(0, 0, 0, 0.3);
    overflow: hidden;
    animation: pmPop 0.16s ease-out;
    font-family: Arial, sans-serif;
}

@keyframes pmPop {
    from { opacity: 0; transform: translateY(-6px) scale(0.95); }
}

.profile-menu-head {
    display: flex;
    justify-content: space-between;
    gap: 12px;
    padding: 9px 13px;
    background: linear-gradient(180deg, #746FFC, #6A68FE);
    color: #fff;
    font-size: 13px;
    font-weight: 900;
}

.profile-menu-item {
    display: flex;
    align-items: center;
    gap: 9px;
    width: 100%;
    padding: 11px 14px;
    border: 0;
    background: var(--surface);
    color: var(--ink-navy);
    font-family: Arial, sans-serif;
    font-size: 16px;
    font-weight: bold;
    text-align: left;
    cursor: pointer;
}

.profile-menu-item:hover {
    background: #e3f2fd;
    color: var(--ink-link);
}

/* ── Badges overlay ── */

.badges-overlay {
    position: fixed;
    inset: 0;
    z-index: 9300;
    display: grid;
    place-items: center;
    background: rgba(10, 14, 34, 0.6);
    animation: mpFadeIn 0.25s ease-out;
}

.badges-card {
    display: flex;
    flex-direction: column;
    width: min(94vw, 760px);
    max-height: 84vh;
    border: 5px solid #000;
    border-radius: 14px;
    background: var(--surface-2);
    box-shadow: 0 12px 0 rgba(0, 0, 0, 0.35);
    overflow: hidden;
}

.badges-head {
    position: relative;
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 3px;
    padding: 14px 52px;
    background: linear-gradient(180deg, #746FFC, #6A68FE);
    color: #fff;
    border-bottom: 3px solid #000;
}

.badges-title {
    font-family: Arial, sans-serif;
    font-size: 26px;
    font-weight: 900;
}

.badges-summary {
    font-family: Arial, sans-serif;
    font-size: 13px;
    font-weight: bold;
    opacity: 0.9;
}

.badges-close {
    position: absolute;
    top: 12px;
    right: 12px;
    width: 36px;
    height: 36px;
    border: 3px solid #000;
    border-radius: 9px;
    background: var(--surface);
    color: var(--ink-navy);
    font-size: 17px;
    font-weight: 900;
    cursor: pointer;
    box-shadow: 0 3px 0 rgba(0, 0, 0, 0.3);
}

.badges-close:hover { background: #ffebee; color: #b71c1c; }

.badges-grid {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 10px;
    padding: 16px;
    overflow-y: auto;
}

.badges-section-label {
    grid-column: 1 / -1;
    margin-top: 6px;
    font-family: Arial, sans-serif;
    font-size: 13px;
    font-weight: 900;
    letter-spacing: 2px;
    text-transform: uppercase;
    color: var(--ink-muted);
}

.badge-card {
    display: flex;
    gap: 11px;
    align-items: flex-start;
    padding: 10px 12px;
    border: 2px solid #000;
    border-radius: 10px;
    background: var(--surface);
    font-family: Arial, sans-serif;
}

.badge-card.is-earned {
    border-color: #b8860b;
    background: linear-gradient(180deg, #fffdf2, #ffedad);
    box-shadow: 0 3px 0 rgba(184, 134, 11, 0.45);
}

.badge-card.is-locked {
    border-color: #b9bec7;
    background: #eceef1;
}

.badge-card.is-locked .badge-icon {
    filter: grayscale(1);
    opacity: 0.55;
}

.badge-card.is-locked .badge-name,
.badge-card.is-locked .badge-desc {
    color: #868e99;
}

.badge-icon {
    flex: 0 0 auto;
    font-size: 27px;
    line-height: 1.2;
}

.badge-info {
    flex: 1 1 auto;
    min-width: 0;
}

.badge-name {
    font-size: 14px;
    font-weight: 900;
    color: var(--ink-navy);
}

.badge-desc {
    font-size: 12px;
    color: #4d5766;
    margin-bottom: 6px;
}

.badge-bar {
    height: 9px;
    border: 1px solid rgba(0, 0, 0, 0.35);
    border-radius: 6px;
    background: rgba(0, 0, 0, 0.1);
    overflow: hidden;
}

.badge-bar-fill {
    height: 100%;
    border-radius: 5px;
    background: linear-gradient(90deg, #9aa5b5, #c3ccd9);
    transition: width 0.3s;
}

.badge-card.is-earned .badge-bar-fill {
    background: linear-gradient(90deg, #f6d83f, #ffb300);
}

.badge-progress {
    margin-top: 3px;
    font-size: 11px;
    font-weight: bold;
    color: var(--ink-muted);
}

.badge-card.is-earned .badge-progress {
    color: #8a6d00;
}

/* ── Trophy unlock toast ── */

.trophy-toast {
    position: fixed;
    left: 50%;
    bottom: 74px;
    z-index: 9500;
    display: flex;
    align-items: center;
    gap: 12px;
    padding: 12px 22px;
    border: 3px solid #000;
    border-radius: 12px;
    background: linear-gradient(180deg, #fff8dc, #ffe082);
    box-shadow: 0 6px 0 rgba(0, 0, 0, 0.35);
    font-family: Arial, sans-serif;
    transform: translate(-50%, 24px);
    opacity: 0;
    transition: transform 0.35s cubic-bezier(0.2, 1.4, 0.4, 1), opacity 0.3s;
    pointer-events: none;
}

.trophy-toast.is-visible {
    transform: translate(-50%, 0);
    opacity: 1;
}

.trophy-toast-icon { font-size: 30px; }

.trophy-toast-text {
    display: flex;
    flex-direction: column;
}

.trophy-toast-label {
    font-size: 11px;
    font-weight: 900;
    letter-spacing: 2px;
    text-transform: uppercase;
    color: #8a6d00;
}

.trophy-toast-name {
    font-size: 17px;
    font-weight: 900;
    color: var(--ink-navy);
}

@media (max-width: 700px) {
    .badges-grid {
        grid-template-columns: 1fr;
    }
}

/* ═══ Inventory (profile menu) ══════════════════════════════════════════════ */

.inv-card { width: min(94vw, 860px); }

.inv-body {
    padding: 14px 16px 18px;
    overflow-y: auto;
}

.inv-body .badges-section-label { display: block; margin: 12px 0 8px; }

.inv-bundles {
    display: flex;
    flex-direction: column;
    gap: 8px;
}

.inv-bundle {
    display: flex;
    align-items: center;
    gap: 10px;
    background: var(--surface);
    border: 2px solid #cdd3ec;
    border-radius: 10px;
    padding: 8px 10px;
}

.inv-bundle--new { border-style: dashed; }

.inv-bundle-name {
    flex: 0 0 190px;
    min-width: 0;
    background: var(--surface-2);
    border: 1px solid #cdd3ec;
    border-radius: 7px;
    padding: 7px 9px;
    font-size: 13px;
    font-weight: 600;
    color: var(--ink-navy);
    font-family: inherit;
}

.inv-bundle .cos-mini-row { flex: 1; margin-left: 0; flex-wrap: wrap; }

.inv-none { font-size: 12px; color: #9aa0bd; }

.inv-btn {
    background: var(--surface-2);
    border: 2px solid #b9c2e8;
    color: var(--ink-navy);
    border-radius: 8px;
    padding: 7px 12px;
    font-size: 12.5px;
    font-weight: 700;
    cursor: pointer;
    white-space: nowrap;
}

.inv-btn:hover { border-color: #6a68fe; }

.inv-btn--on {
    background: #3ddc84;
    border-color: #1f9d5a;
    color: #08331c;
}

.inv-btn--del {
    color: #d43a52;
    padding: 7px 9px;
}

.inv-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(136px, 1fr));
    gap: 10px;
}

.inv-item {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 6px;
    background: var(--surface);
    border: 2px solid var(--rar, #cdd3ec);
    border-radius: 12px;
    padding: 10px 8px;
    text-align: center;
}

.inv-item.is-equipped { box-shadow: 0 0 0 3px rgba(61, 220, 132, 0.35); }

.inv-item-preview {
    display: grid;
    place-items: center;
    min-height: 50px;
    font-size: 30px; /* emoji items */
    line-height: 1;
}

.inv-item-name {
    font-size: 12.5px;
    font-weight: 700;
    color: var(--ink-navy);
    line-height: 1.25;
}

.inv-item .inv-btn { width: 100%; }

.inv-empty {
    color: #7a80a5;
    font-size: 14px;
    text-align: center;
    padding: 22px 0;
}

.loadout-name {
    display: block;
    font-size: 10px;
    font-weight: 700;
    margin-top: 3px;
    max-width: 84px;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* ═══ Shop, W-Bucks & Cosmetics ═════════════════════════════════════════════ */

/* The storefront's purple wash: a full-bleed tint that turns whatever photo the
   wallpaper is showing purple for as long as you're in the shop.

   z-index -1 is the wallpaper's own layer, and that is the point. The veil has
   to beat the photo and lose to everything else, and -1 is the only rung that
   does both: the .bg-slide photos sit at -1 too, so DOM order settles it (the
   veil is appended to <body>, the slides are inserted at the front), while a
   single step up to 0 would put it over the menu cards, which are painted as
   ordinary in-flow content. It never takes a click — the whole storefront is
   behind it.

   Parked one screen-width to the right and slid home on entry, so the tint
   arrives as a sweep across the page rather than a flash. Duration comes from
   JS as --shop-veil-ms, tied to the player's transition-speed setting. */
.shop-veil {
    position: fixed;
    inset: 0;
    z-index: -1;
    pointer-events: none;
    transform: translateX(100%);
    transition: transform var(--shop-veil-ms, 720ms) cubic-bezier(0.22, 0.61, 0.36, 1);
    will-change: transform;
    /* The storefront sky: bright overhead, falling off to deep navy in the
       corners. Three stops rather than a flat fill because a single colour
       across 2000px reads as a painted wall, and the light needs somewhere to
       come from for the tiles to sit under it. Stops and strength arrive from
       JS as custom properties so the whole field is repaintable. */
    background:
        radial-gradient(150% 108% at 50% 16%,
            var(--shop-field-hi, #3fa4f2) 0%,
            var(--shop-field-mid, #1670d8) 54%,
            var(--shop-field-lo, #0a337f) 100%);
    opacity: var(--shop-veil-alpha, 0.94);
}

.shop-veil.is-in {
    transform: translateX(0);
}

/* Nobody asked for a coloured pane flying across the screen. */
@media (prefers-reduced-motion: reduce) {
    .shop-veil {
        transition: opacity var(--shop-veil-ms, 720ms) ease;
        transform: none;
        opacity: 0;
    }

    .shop-veil.is-in {
        opacity: var(--shop-veil-alpha, 0.92);
    }
}

/* The shop is a full-bleed storefront, not a menu card: break out of the
   3-column menu grid so it spans the whole page instead of one narrow column.

   It is also the one screen with a hard height. The header and footer are
   fixed 90px bands, so the storefront takes exactly what is left and never a
   pixel more — the whole shop has to be readable at a glance, which a
   scrollbar defeats. Everything inside sizes down to fit rather than
   overflowing. */
.grid-container.shop-container {
    display: block;
    width: 100%;
    max-width: none;
    height: calc(100vh - 180px);
    overflow: hidden;
}

/* The menu stage pads 120px top and bottom to float the menu cards; on the
   shop that padding is what pushed the page past the viewport in the first
   place. */
.menu-stage:has(> .shop-container) {
    padding: 0;
}

/* The storefront's own typeface rule. Fortnite sets everything in Burbank Big
   Condensed — proprietary to Epic, so it cannot ship here. The read that
   actually carries is heavy + condensed + italic + uppercase, and a narrow
   system face gets most of the way there. One custom property so the whole
   storefront changes voice in one line if a real face is ever licensed. */
.shopx {
    --shop-face: 'Arial Narrow', 'Liberation Sans Narrow', Arial, sans-serif;

    display: flex;
    flex-direction: column;
    width: 100%;
    height: 100%;
    padding-bottom: 10px;
    font-family: Arial, sans-serif;
}

/* The rectangle proper: upright panes laid out left to right, dividing the
   width between them. Flex rather than fixed grid tracks so a pane that has
   nothing in it today simply isn't there and the others take its room —
   Cursor of the Day and the W-Bucks packs both come and go. */
.shopx-board {
    display: flex;
    flex: 1;
    gap: 16px;
    min-height: 0; /* lets the panes shrink instead of pushing the box taller */
}

.shopx-pane {
    display: flex;
    flex-direction: column;
    min-width: 0;
    min-height: 0;
    /* The heading type sizes itself off the pane width — see
       .shopx-daily-title's clamp(). */
    container-type: inline-size;
}

.shopx-pane--feat  { flex: 1.15; }
.shopx-pane--daily { flex: 2.3; }
.shopx-pane--side  { flex: 1; }

/* A pane's section fills its pane, and the tile grid fills what the heading
   leaves behind. */
.shopx-pane > .shopx-daily,
.shopx-pane > .shopx-wbx {
    display: flex;
    flex: 1;
    flex-direction: column;
    min-height: 0;
    margin-bottom: 0;
}

.shopx-pane--side > * + * {
    margin-top: 14px;
}

.shopx-pane .shopx-grid {
    flex: 1;
    min-height: 0;
    /* Rows share the leftover height evenly, so two rows of tiles in a short
       pane are simply shorter tiles rather than a clipped second row. */
    grid-auto-rows: minmax(0, 1fr);
}

/* Inside the fixed board, tiles keep their portrait proportion and sit centred
   in whatever height the pane has. Letting them stretch to fill turned a
   230px-wide Featured tile into a 640px-tall sliver. */
.shopx-pane .shopx-grid {
    align-content: center;
    grid-auto-rows: auto;
}

.shopx-board .cos-preview {
    aspect-ratio: 3 / 4;
    flex: none;
}

/* Daily is the exception: six tiles over two rows will not fit the pane at a
   fixed proportion, so there the cell height wins and the art gives way. */
.shopx-pane--daily .shopx-grid {
    align-content: stretch;
    grid-auto-rows: minmax(0, 1fr);
}

.shopx-pane--daily .cos-card {
    height: 100%;
}

.shopx-pane--daily .cos-preview {
    aspect-ratio: auto;
    flex: 1;
    min-height: 0;
}

/* Color of the Day gets a softer version of the same trade. Its tile lives in
   the side pane, which is split with Get W-Bucks whenever payments are
   configured — so in production the section is half as tall as it is on a dev
   box with no Stripe keys, and there the 3/4 art wanted 268px inside a 252px
   cell. .cos-card is overflow:hidden, so what got cut was the foot: the
   colour's NAME and its Equip button. The item was not merely unlabelled, it
   was unclickable, and only in production.
   `flex: none` from .shopx-board is what pinned it — the aspect ratio stays as
   the preferred size, this just lets it lose when the cell is shorter. Given
   room (no Get W-Bucks) the tile is identical to before. */
.shopx-daily--color .cos-preview {
    flex: 0 1 auto;
    min-height: 0;
}

/* Preview art carries an inline pixel size from cosmeticPreview, which is
   larger than a shrunken cell; cap it so the sprite scales down with the tile
   instead of spilling out of it. */
.shopx-board .cos-sprite,
.shopx-board .cos-theme-thumb {
    max-width: 100%;
    max-height: 100%;
    object-fit: contain;
}

.shopx-board .cos-stage {
    max-width: 100%;
    max-height: 100%;
}

/* The chrome is dropped throughout this screen: no black frames, no 3px
   borders, no drop-shadow ledges. The rest of the game is Mario Kart; the shop
   is the one screen pretending to be a different game, and the MKWii furniture
   is what most gave it away. */
/* Not sticky any more: nothing scrolls, so a sticky bar with a 106px offset
   just floated itself down on top of the pane headings. */
.shopx-topbar {
    z-index: 20;
    display: flex;
    align-items: center;
    gap: 16px;
    flex-wrap: wrap;
    flex-shrink: 0;
    /* Right padding clears the profile card, which floats over this corner and
       was sitting on top of the wallet chip. */
    padding: 6px 150px 8px 4px;
    margin-bottom: 6px;
    /* Fades the field out under the sticky bar so tiles scrolling up don't
       collide with the title. Kept weak on purpose — any more and it reads as
       a panel, which is the exact furniture this pass is removing. */
    background: linear-gradient(180deg,
        rgba(6, 26, 68, 0.5) 0%, rgba(6, 26, 68, 0.22) 60%, rgba(6, 26, 68, 0) 100%);
}

.shopx-title {
    font-family: var(--shop-face);
    font-size: 34px;
    font-style: italic;
    font-weight: 900;
    letter-spacing: 1px;
    text-transform: uppercase;
    color: #fff;
    text-shadow: 0 3px 0 rgba(2, 10, 34, 0.55);
}

.shopx-hint {
    flex: 1 1 260px;
    font-size: 12px;
    font-weight: bold;
    color: rgba(214, 231, 255, 0.72);
}

/* The wallet, styled as the V-Bucks chip is: a pill outlined in bright blue,
   sitting on the field rather than on a panel. */
.wbucks-chip {
    display: flex;
    align-items: center;
    gap: 7px;
    padding: 7px 15px;
    border: 2px solid #3ea4ff;
    border-radius: 8px;
    background: rgba(6, 22, 62, 0.72);
    font-family: var(--shop-face);
    font-size: 21px;
    font-style: italic;
    font-weight: 900;
    color: #fff;
    white-space: nowrap;
}

.wbucks-chip small {
    font-size: 11px;
    font-weight: 900;
    font-style: normal;
    letter-spacing: 1px;
    text-transform: uppercase;
    color: #8fc4ff;
}

/* ── Daily rotation ── */

.shopx-daily {
    padding: 0;
    margin-bottom: 34px;
    border: 0;
    border-radius: 0;
    background: none;
    box-shadow: none;
}

/* The section header is the only separator: no panel, no rule, no box. The
   items sit straight on the field, which is what makes the wall of tiles read
   as one storefront instead of a stack of drawers. */
.shopx-daily-head {
    display: flex;
    justify-content: flex-start;
    align-items: baseline;
    gap: 10px;
    flex-wrap: wrap;
    flex-shrink: 0;
    margin-bottom: 8px;
}

/* Sized to the pane it sits in rather than set once: "Color of the Day" in the
   narrow side pane and "Daily" in the wide one cannot take the same type size
   without one of them wrapping into the tiles below it. */
.shopx-daily-title {
    font-family: var(--shop-face);
    font-size: clamp(20px, 2.2cqw + 12px, 34px);
    font-style: italic;
    font-weight: 900;
    line-height: 1.05;
    color: #fff;
    text-transform: uppercase;
    letter-spacing: 1px;
    text-shadow: 0 3px 0 rgba(2, 10, 34, 0.5);
}

/* The countdown, set as the timer chip beside the header. */
.shopx-daily-reset {
    padding: 4px 11px;
    border-radius: 999px;
    background: rgba(6, 22, 62, 0.55);
    font-family: var(--shop-face);
    font-size: 15px;
    font-style: italic;
    font-weight: 900;
    letter-spacing: 0.5px;
    text-transform: uppercase;
    color: #56b8ff;
}

/* ── Color of the Day: no filter/animation — it would tint the cursor preview
   itself, which needs to show the day's real color ── */

.shopx-daily--color .shopx-daily-title { color: #fff; }

/* ── Cursor of the Day: the shop's headline — one country cursor, centered
   and larger than a normal grid cell so it reads as a feature, not stock ── */
.shopx-daily--cotd .shopx-daily-title { color: #eaf6ff; }

/* Doubled class on the modifiers so they outrank the base .shopx-grid, which
   is declared further down the file: at equal specificity the later rule won,
   so neither the featured sizing nor this centering ever actually applied. */
.shopx-grid.shopx-grid--cotd {
    grid-template-columns: min(300px, 100%);
    justify-content: center;
}

/* Featured row — the shop's headline items, bigger cells than the daily
   grid so they read as the main event (early-Fortnite Featured/Daily split).
   Fixed-width tracks rather than 1fr: only two items are featured, and
   stretching them across the row would blow them up to billboard size instead
   of reading as two large tiles. */
.shopx-grid.shopx-grid--featured {
    grid-template-columns: repeat(auto-fit, minmax(0, 1fr));
}

.shopx-section {
    margin-bottom: 26px;
}

/* Section header: label, a rule that runs out to the count, and the count.
   The rule takes its colour from the text, so the same header works on the
   dark page and inside the white loadouts panel. */
.shopx-cat-label {
    display: flex;
    align-items: center;
    gap: 12px;
    margin: 6px 2px 12px;
    font-family: var(--shop-face);
    font-size: 22px;
    font-style: italic;
    font-weight: 900;
    letter-spacing: 1.5px;
    text-transform: uppercase;
    color: var(--ink-navy);
}

.shopx-cat-label::after {
    content: "";
    flex: 1;
    height: 3px;
    border-radius: 2px;
    background: linear-gradient(90deg,
        color-mix(in srgb, currentColor 40%, transparent), transparent);
}

.shopx-section > .shopx-cat-label {
    color: #fff;
    text-shadow: 0 2px 0 rgba(0, 0, 0, 0.55);
}

.shopx-cat-count {
    order: 1; /* after the rule */
    font-size: 12px;
    letter-spacing: 1px;
    opacity: 0.75;
}

.shopx-daily .shopx-cat-label { color: #fff; }

.shopx-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(min(210px, 100%), 1fr));
    gap: 12px;
}

/* Daily is the wide pane: three across, so six items land as two even rows.
   Scoped to that pane — the Color of the Day grid reuses the same --daily
   class in the narrow side pane, where three columns crushed the tiles to
   130px and collided their corner tags. */
.shopx-pane--daily .shopx-grid.shopx-grid--daily {
    grid-template-columns: repeat(3, minmax(0, 1fr));
}

.shopx-pane--side .shopx-grid {
    grid-template-columns: repeat(2, minmax(0, 1fr));
}

/* ── Item tiles ─────────────────────────────────────────────────────────────
   Early-Fortnite storefront tiles. A tall portrait pane of rarity-coloured
   light with the cosmetic standing in it, a hard rarity strip across the foot
   of the art, and a dark plate under that carrying the name and the price.

   No black frame and almost no corner radius: the tile is a slide, not a card.
   Rarity is the whole backdrop rather than a trim colour, which is what lets
   you read a wall of these at a glance — the shape never changes, only the
   light behind the item does. */

.cos-card {
    position: relative;
    display: flex;
    flex-direction: column;
    border-radius: 4px;
    overflow: hidden;
    background: #0d1730;
    box-shadow: 0 6px 16px rgba(2, 9, 30, 0.45);
    transition: transform 0.14s ease, box-shadow 0.14s ease;
}

.cos-card:hover {
    transform: translateY(-5px);
    box-shadow: 0 14px 28px rgba(2, 9, 30, 0.6);
}

/* The tiles arrive the same way the field does — in from the right, dealt one
   after another rather than all at once. Scoped to .shopx--intro so it plays
   on the way into the shop and not on the re-render after every purchase.

   `backwards`, not `forwards`: the fill has to cover the stagger delay before
   the tile's turn, but must NOT persist afterwards, or the animation's final
   `transform: none` would outrank the hover lift for the rest of the session. */
@keyframes cosDealIn {
    from { opacity: 0; transform: translateX(64px); }
    to   { opacity: 1; transform: none; }
}

.shopx--intro .cos-card {
    animation: cosDealIn 360ms cubic-bezier(0.22, 0.61, 0.36, 1) backwards;
    animation-delay: calc(var(--i, 0) * 55ms);
}

.cos-card.is-equipped {
    outline: 3px solid #37e1ff;
    outline-offset: -3px;
}

.rarity--common    { --rar: #7f8c9e; }
.rarity--uncommon  { --rar: #4ac736; }
.rarity--rare      { --rar: #35a2ff; }
.rarity--epic      { --rar: #b14dff; }
.rarity--legendary { --rar: #f0821e; }
.rarity--mythic    { --rar: #f2c93b; }
.rarity--exotic    { --rar: #22d3c5; }

.cos-preview {
    position: relative;
    display: grid;
    place-items: center;
    /* Portrait, like a character tile. A fixed height made every tile the same
       shape regardless of column width; an aspect ratio keeps the proportion
       as the grid reflows. */
    aspect-ratio: 3 / 4;
    padding: 16px 14px 26px;
    overflow: hidden;
    /* Rarity as the light in the pane: blown out behind the item, saturating
       to full colour at the edges and darkening into the corners. */
    background:
        radial-gradient(86% 68% at 50% 34%,
            color-mix(in srgb, var(--rar) 18%, #fff) 0%,
            color-mix(in srgb, var(--rar) 58%, #fff) 38%,
            var(--rar) 74%,
            color-mix(in srgb, var(--rar) 62%, #06122e) 100%);
}

/* The hard rarity strip across the foot of the art — the one piece of chrome
   that tells you what you're looking at from across the room. */
.cos-preview::before {
    content: "";
    position: absolute;
    inset: auto 0 0 0;
    z-index: 1;
    height: 7px;
    background: color-mix(in srgb, var(--rar) 78%, #fff);
    box-shadow: 0 0 16px color-mix(in srgb, var(--rar) 65%, transparent);
}

/* The contact shadow that makes the art an object rather than a picture. */
.cos-preview::after {
    content: "";
    position: absolute;
    left: 50%;
    bottom: 22px;
    width: 46%;
    height: 14px;
    transform: translateX(-50%);
    border-radius: 50%;
    background: radial-gradient(ellipse at center,
        rgba(6, 14, 38, 0.42), rgba(6, 14, 38, 0) 72%);
    transition: transform 0.18s ease, opacity 0.18s ease;
}

.cos-stage {
    position: relative; /* above the floor and its shadow */
    display: grid;
    place-items: center;
    transition: transform 0.18s ease;
}

/* Smaller than it used to be: the whole tile lifts now, so the art only needs
   to drift enough to separate from its shadow. */
.cos-card:hover .cos-stage { transform: translateY(-4px); }
.cos-card:hover .cos-preview::after { transform: translateX(-50%) scale(0.82); opacity: 0.75; }

.cos-sprite {
    /* Size comes from the inline width/height cosmeticPreview writes: it fits the
       sprite to the tile at a whole-number scale, which a CSS height would undo. */
    image-rendering: pixelated;
    image-rendering: crisp-edges;
    filter: drop-shadow(1px 1px 1px rgba(0, 0, 0, 0.3));
}

/* Blocky art wants a blocky shadow — no blur at this size. */
.cos-preview .cos-sprite {
    filter: drop-shadow(3px 4px 0 rgba(20, 16, 40, 0.22));
}

.cos-emoji {
    display: block;
    line-height: 1;
    filter: drop-shadow(2px 3px 0 rgba(20, 16, 40, 0.2));
}

/* The rarity tag: pinned to the case, legible over any rarity light. */
.cos-rarity {
    position: absolute;
    top: 8px;
    z-index: 2;
    padding: 3px 8px;
    border: 0;
    border-radius: 3px;
    background: rgba(5, 14, 36, 0.66);
    font-family: var(--shop-face);
    font-size: 10px;
    font-style: italic;
    font-weight: 900;
    letter-spacing: 1.2px;
    text-transform: uppercase;
    left: 8px;
    color: color-mix(in srgb, var(--rar) 45%, #fff);
}

/* The plate under the art. Flush to the tile edges with no padding of its own —
   the name and the price bar each own their band, so the price runs edge to
   edge the way a Fortnite tile's does. */
.cos-foot {
    display: flex;
    flex-direction: column;
    gap: 0;
    padding: 0;
    background: linear-gradient(180deg, #16203c, #0c1428);
}

/* A cursor and its hand version, stacked rather than side by side: the arrow
   is what is on show, and hovering the tile turns it into the hand — the same
   thing the cursor itself does when it passes over something clickable, so the
   tile demonstrates the item instead of describing it.

   One grid cell holding both, so the stage takes the size of the larger and
   the swap cannot resize the tile or nudge the caption under it. */
.cos-swap {
    display: grid;
    place-items: center;
}

.cos-swap > .cos-sprite {
    grid-area: 1 / 1;
    transition: opacity 0.12s ease;
}

.cos-swap > .cos-sprite--hand { opacity: 0; }

.cos-card:hover .cos-swap > .cos-sprite { opacity: 0; }
.cos-card:hover .cos-swap > .cos-sprite--hand { opacity: 1; }

/* The inventory row shows the same pair and swaps the same way. */
.inv-item:hover .cos-swap > .cos-sprite { opacity: 0; }
.inv-item:hover .cos-swap > .cos-sprite--hand { opacity: 1; }

/* A tile nobody can hover would only ever show the arrow, so the hand gets its
   turn on tap instead — the tile is the only place either sprite is visible. */
@media (hover: none) {
    .cos-card:active .cos-swap > .cos-sprite,
    .inv-item:active .cos-swap > .cos-sprite { opacity: 0; }

    .cos-card:active .cos-swap > .cos-sprite--hand,
    .inv-item:active .cos-swap > .cos-sprite--hand { opacity: 1; }
}

/* Mini cosmetic icons: a racer's loadout in rival panels and result rows. */
.cos-mini-row {
    display: inline-flex;
    align-items: center;
    gap: 3px;
    margin-left: 7px;
    vertical-align: middle;
}

.cos-mini {
    font-size: 12px;
    line-height: 1;
}

.cos-mini--sprite {
    /* Whole-number size set inline by cosmeticMiniIcon. */
    vertical-align: middle;
    image-rendering: pixelated;
}

.cos-mini--swatch {
    width: 14px;
    height: 14px;
    border-radius: 4px;
    display: inline-block;
    box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.3);
}

/* Custom theme tile: a swatch of the article background, sized inline. */
.cos-theme-thumb {
    display: block;
    border-radius: 8px;
    box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.25),
                3px 4px 0 rgba(20, 16, 40, 0.22);
}

/* The top three rarities light their own case: the glow lives inside the
   frame, so it reads as the lamp above the art getting brighter. */
.rarity--legendary .cos-preview { animation: cosLegendGlow 1.6s ease-in-out infinite; }

@keyframes cosLegendGlow {
    0%, 100% { box-shadow: inset 0 14px 26px -12px rgba(255, 140, 26, 0.55); }
    50%      { box-shadow: inset 0 22px 40px -10px rgba(255, 140, 26, 0.95); }
}

.rarity--mythic .cos-preview { animation: cosMythicGlow 1.4s ease-in-out infinite; }

@keyframes cosMythicGlow {
    0%, 100% { box-shadow: inset 0 14px 26px -12px rgba(255, 206, 31, 0.6); }
    50%      { box-shadow: inset 0 24px 44px -10px rgba(255, 215, 61, 1); }
}

/* Exotic pulses between teal and gold */
.rarity--exotic .cos-preview { animation: cosExoticGlow 2.2s ease-in-out infinite; }

@keyframes cosExoticGlow {
    0%, 100% { box-shadow: inset 0 14px 28px -12px rgba(31, 208, 192, 0.7); }
    50%      { box-shadow: inset 0 24px 44px -10px rgba(255, 224, 61, 0.85); }
}

.cos-name {
    padding: 9px 10px 8px;
    font-family: var(--shop-face);
    font-size: 15px;
    font-style: italic;
    font-weight: 900;
    letter-spacing: 0.6px;
    line-height: 1.15;
    text-transform: uppercase;
    color: #fff;
    text-align: center;
}

.cos-btn {
    width: 100%;
    padding: 9px 8px;
    border: 0;
    border-radius: 0;
    font-family: var(--shop-face);
    font-size: 15px;
    font-style: italic;
    font-weight: 900;
    letter-spacing: 0.6px;
    text-transform: uppercase;
    cursor: pointer;
    transition: filter 0.12s ease;
}

.cos-btn s { opacity: 0.6; font-weight: bold; }

.cos-btn--buy {
    background: linear-gradient(180deg, #ffd75e, #efb01a);
    color: #3a2900;
}

.cos-btn--buy:hover { filter: brightness(1.08); }

.cos-btn--equip {
    background: linear-gradient(180deg, #37b6ff, #0d76e0);
    color: #fff;
}

.cos-btn--equip:hover { filter: brightness(1.08); }

.cos-btn--equipped {
    background: linear-gradient(180deg, #2fd06a, #17a049);
    color: #fff;
}

.cos-btn.is-shake {
    animation: cosShake 0.4s;
}

@keyframes cosShake {
    0%, 100% { transform: translateX(0); }
    25% { transform: translateX(-5px); }
    50% { transform: translateX(5px); }
    75% { transform: translateX(-3px); }
}

/* Sits square on the tile rather than tilted off its corner — the tile clips
   its own overflow now, and a rotated tag hanging outside a 4px radius was
   the last bit of sticker-book left on the screen. */
.cos-sale-tag {
    position: absolute;
    top: 8px;
    left: 50%;
    z-index: 3;
    transform: translateX(-50%);
    padding: 3px 12px;
    border: 0;
    border-radius: 3px;
    background: #ffd400;
    font-family: var(--shop-face);
    font-size: 12px;
    font-style: italic;
    font-weight: 900;
    letter-spacing: 0.8px;
    color: #2a1c00;
}

/* Keep the shop legible for anyone who asked the OS for less movement: the
   case stops breathing, the art stops lifting. */
@media (prefers-reduced-motion: reduce) {
    .cos-preview,
    .rarity--legendary .cos-preview,
    .rarity--mythic .cos-preview,
    .rarity--exotic .cos-preview {
        animation: none;
    }

    .cos-stage,
    .cos-preview::after {
        transition: none;
    }

    .cos-card:hover .cos-stage { transform: none; }

    /* The tiles still fade up, they just stop flying. */
    .shopx--intro .cos-card {
        animation-name: cosFadeIn;
    }

    @keyframes cosFadeIn {
        from { opacity: 0; }
        to   { opacity: 1; }
    }
}

/* ── W-Bucks purchase packs ── */

.shopx-wbx {
    padding: 0;
    margin-bottom: 34px;
    border: 0;
    border-radius: 0;
    background: none;
    box-shadow: none;
}

.wbx-row {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
    gap: 12px;
}

.wbx-card {
    position: relative;
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 4px;
    padding: 14px 10px 12px;
    border: 3px solid #f6d83f;
    border-radius: 12px;
    background: #fffbe8;
    box-shadow: 0 4px 0 rgba(0, 0, 0, 0.22);
}

.wbx-card--tagged {
    border-color: #ff9800;
}

.wbx-tag {
    position: absolute;
    top: -10px;
    right: -7px;
    padding: 3px 8px;
    border: 2px solid #000;
    border-radius: 7px;
    background: #ff9800;
    color: #fff;
    font-size: 11px;
    font-weight: 900;
    transform: rotate(6deg);
}

.wbx-amount {
    font-size: 20px;
    font-weight: 900;
    color: #4a3200;
}

.wbx-bonus {
    min-height: 14px;
    font-size: 11px;
    font-weight: 700;
    color: #1faf3c;
}

.wbx-name {
    font-size: 12px;
    font-weight: 700;
    color: #6b5300;
}

.wbx-buy {
    margin-top: 4px;
}

/* ── Loadouts ── */

.shopx-loadouts {
    padding: 12px 16px;
    margin-bottom: 20px;
    border: 3px solid #000;
    border-radius: 12px;
    background: color-mix(in srgb, var(--surface) 94%, transparent);
    box-shadow: 0 4px 0 rgba(0, 0, 0, 0.22);
}

.loadout-row {
    display: flex;
    flex-wrap: wrap;
    gap: 10px;
    align-items: center;
}

.loadout-slot {
    position: relative;
}

.loadout-apply {
    min-width: 74px;
    padding: 9px 12px;
    border: 2px solid #000;
    border-radius: 9px;
    background: var(--surface);
    font-size: 17px;
    cursor: pointer;
    box-shadow: 0 3px 0 rgba(0, 0, 0, 0.22);
}

.loadout-apply:hover { background: #e3f2fd; }

.loadout-del {
    position: absolute;
    top: -8px;
    right: -8px;
    width: 20px;
    height: 20px;
    border: 2px solid #000;
    border-radius: 50%;
    background: #e53935;
    color: #fff;
    font-size: 10px;
    font-weight: 900;
    line-height: 1;
    cursor: pointer;
}

.loadout-save {
    padding: 10px 16px;
    border: 2px dashed #5b6474;
    border-radius: 9px;
    background: transparent;
    font-family: Arial, sans-serif;
    font-size: 13px;
    font-weight: 900;
    color: var(--ink-muted);
    cursor: pointer;
}

.loadout-save:hover { border-color: #0032ff; color: var(--ink-link); }

/* ── W-Bucks toast ── */

.wbucks-toast {
    position: fixed;
    right: 22px;
    bottom: 74px;
    z-index: 9500;
    padding: 10px 18px;
    border: 3px solid #000;
    border-radius: 10px;
    background: linear-gradient(180deg, #ffe082, #f6d83f);
    color: #4a3200;
    font-family: Arial, sans-serif;
    font-size: 15px;
    font-weight: 900;
    box-shadow: 0 5px 0 rgba(0, 0, 0, 0.3);
    transform: translateY(16px);
    opacity: 0;
    transition: transform 0.3s cubic-bezier(0.2, 1.4, 0.4, 1), opacity 0.25s;
    pointer-events: none;
}

.wbucks-toast.is-visible {
    transform: translateY(0);
    opacity: 1;
}

.profile-menu-bucks { white-space: nowrap; }

/* ── Live cosmetic effects ── */

.cos-trail {
    position: fixed;
    z-index: 9350;
    font-size: 15px;
    line-height: 1;
    pointer-events: none;
    animation: cosTrailFade 0.75s ease-out forwards;
}

.cos-trail--dot {
    width: 9px;
    height: 9px;
    border-radius: 50%;
}

@keyframes cosTrailFade {
    to {
        transform: translate(var(--dx, 0), var(--dy, 22px)) scale(0.3) rotate(35deg);
        opacity: 0;
    }
}

/* Pixel flag trails are rendered by trail-ribbon.js on a canvas overlay. */

.cos-click-p {
    position: fixed;
    z-index: 9350;
    font-size: 15px;
    line-height: 1;
    pointer-events: none;
    animation: cosBurst 0.6s ease-out forwards;
}

@keyframes cosBurst {
    to {
        transform: translate(var(--dx), var(--dy)) scale(0.35);
        opacity: 0;
    }
}

.cos-ring {
    position: fixed;
    z-index: 9350;
    width: 14px;
    height: 14px;
    margin: -7px 0 0 -7px;
    border: 3px solid #fff;
    border-radius: 50%;
    box-shadow: 0 0 8px 2px rgba(120, 180, 255, 0.9), inset 0 0 6px rgba(120, 180, 255, 0.9);
    pointer-events: none;
    animation: cosRing 0.55s ease-out forwards;
}

@keyframes cosRing {
    to {
        transform: scale(6);
        opacity: 0;
    }
}

.cos-aura {
    position: fixed;
    top: 0;
    left: 0;
    z-index: 9340;
    width: 110px;
    height: 110px;
    border-radius: 50%;
    pointer-events: none;
    mix-blend-mode: screen;
}

/* Custom pixel auras: square art, no blending or circular clipping. */
.cos-aura--pixel {
    border-radius: 0;
    mix-blend-mode: normal;
    image-rendering: pixelated;
}

/* Custom click animations: one spritesheet loop at the click point. */
.cos-sheet-fx {
    position: fixed;
    z-index: 9350;
    pointer-events: none;
    image-rendering: pixelated;
}

.cos-keyring {
    position: fixed;
    top: 0;
    left: 0;
    z-index: 9345;
    font-size: 21px;
    line-height: 1;
    pointer-events: none;
    transform-origin: top center;
    text-shadow: 0 2px 3px rgba(0, 0, 0, 0.35);
}

/* ── Card themes ── */

body[data-ctheme="sunset"]    .blue-card { filter: hue-rotate(150deg) saturate(1.15); }
body[data-ctheme="forest"]    .blue-card { filter: hue-rotate(-100deg); }
body[data-ctheme="bubblegum"] .blue-card { filter: hue-rotate(100deg) saturate(1.1); }
body[data-ctheme="crimson"]   .blue-card { filter: hue-rotate(140deg) saturate(1.3); }
body[data-ctheme="nebula"]    .blue-card { filter: hue-rotate(60deg); }
body[data-ctheme="midnight"]  .blue-card { filter: saturate(0.5) brightness(0.65); }
body[data-ctheme="goldrush"]  .blue-card { filter: hue-rotate(185deg) saturate(1.5) brightness(1.08); }

@media (max-width: 768px) {
    .shopx-topbar { top: 66px; }
}

/* ═══ WikiChannel ═══════════════════════════════════════════════════════════ */

.wch {
    width: min(100%, 1080px);
    margin: 0 auto;
    padding-bottom: 50px;
    font-family: Arial, sans-serif;
}

.wch-topbar {
    display: flex;
    align-items: center;
    gap: 18px;
    flex-wrap: wrap;
    padding: 12px 18px;
    margin-bottom: 16px;
    border: 3px solid #000;
    border-radius: 14px;
    background: linear-gradient(160deg, #10142e, #232b5c);
    box-shadow: 0 6px 0 rgba(0, 0, 0, 0.3);
}

.wch-tabs {
    display: flex;
    gap: 8px;
    flex-wrap: wrap;
}

.wch-tab {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 1px;
    padding: 7px 14px;
    border: 2px solid rgba(255, 255, 255, 0.35);
    border-radius: 9px;
    background: rgba(255, 255, 255, 0.08);
    color: #cfd4ff;
    font-family: Arial, sans-serif;
    cursor: pointer;
    transition: background 0.15s, transform 0.1s;
}

.wch-tab:hover { background: rgba(255, 255, 255, 0.18); transform: translateY(-1px); }

.wch-tab.is-active {
    background: var(--surface);
    border-color: #000;
    color: var(--ink-link);
    box-shadow: 0 3px 0 rgba(0, 0, 0, 0.35);
}

.wch-tab-num {
    font-size: 10px;
    font-weight: 900;
    letter-spacing: 2px;
}

.wch-tab-label {
    font-size: 13px;
    font-weight: 900;
}

.wch-screen {
    min-height: 300px;
}

.wch-panel {
    padding: 16px 18px 20px;
    border: 3px solid #000;
    border-radius: 14px;
    background: color-mix(in srgb, var(--surface) 95%, transparent);
    box-shadow: 0 6px 0 rgba(0, 0, 0, 0.25);
}

.wch-panel-title {
    margin-bottom: 14px;
    font-size: 20px;
    font-weight: 900;
    color: var(--ink-navy);
}

/* ── Leaderboard ── */

.wch-board {
    display: flex;
    flex-direction: column;
    gap: 7px;
}

.wch-row {
    display: flex;
    align-items: center;
    gap: 14px;
    padding: 9px 14px;
    border: 2px solid #c8ccd1;
    border-radius: 9px;
    background: var(--surface);
}

.wch-row:nth-child(1) { border-color: #ffb300; background: linear-gradient(180deg, #fffdf2, #fff3bd); }
.wch-row:nth-child(2) { border-color: #9fb0c3; background: linear-gradient(180deg, #fafbfc, #eef2f6); }
.wch-row:nth-child(3) { border-color: #c98a53; background: linear-gradient(180deg, #fff8f2, #ffe8d4); }

.wch-row.is-me {
    outline: 3px solid #0032ff;
    outline-offset: 1px;
}

.wch-rank {
    flex: 0 0 44px;
    font-size: 18px;
    font-weight: 900;
    color: var(--ink-mid);
    text-align: center;
}

/* ── Player pictures on the channels ──
   One slot, four places: the board rows, the replay list, the racer strip
   inside a replay, and your own head on CH 02. It always occupies the same box
   whether or not there is a picture in it, so a board does not reflow when the
   faces arrive a moment after the names — and a channel where nobody has one
   still looks laid out rather than broken.

   The empty state is a plain tinted circle — the `user` glyph is asked for and
   the icon set has no art for it yet, so nothing draws and that is the right
   outcome either way. Not initials: a name here can be any script and any
   length, and a letter in a circle would be the one piece of the page that
   renders differently per player for no reason. */
.wch-pfp {
    flex: 0 0 28px;
    width: 28px;
    height: 28px;
    display: grid;
    place-items: center;
    overflow: hidden;
    border: 2px solid #c8ccd1;
    border-radius: 50%;
    background: var(--surface-2, #e9edf2) center / cover no-repeat;
    color: var(--ink-mid);
    font-size: 14px;
    line-height: 1;
}

/* Filled: the border tightens to the picture and the fallback glyph, which is
   still in the DOM, must not show through a photo with transparency. */
.wch-pfp.has-pic {
    border-color: #0032ff;
    color: transparent;
}

.wch-pfp svg,
.wch-pfp img { width: 16px; height: 16px; }

.wch-pfp.has-pic svg,
.wch-pfp.has-pic img { display: none; }

/* Bigger where there is room for it: your own card, and the racer strip in a
   replay, both of which show one face rather than a column of them. */
.wch-me-head .wch-pfp {
    flex: 0 0 56px;
    width: 56px;
    height: 56px;
    border-color: #ffb300;
    font-size: 26px;
}

.wch-me-head .wch-pfp svg,
.wch-me-head .wch-pfp img { width: 30px; height: 30px; }

.wch-racer-top .wch-pfp {
    flex: 0 0 22px;
    width: 22px;
    height: 22px;
}

.wch-racer-top .wch-pfp svg,
.wch-racer-top .wch-pfp img { width: 13px; height: 13px; }

/* The strip's row is a space-between pair (name | moves). Adding a third child
   would spread all three across the width, so the name takes the slack instead
   and the picture stays tucked against it. */
.wch-racer-top {
    align-items: center;
}

.wch-racer-top .wch-name {
    margin-right: auto;
}

.wch-player {
    flex: 1 1 auto;
    display: flex;
    flex-direction: column;
    min-width: 0;
}

.wch-name {
    font-size: 15px;
    font-weight: 900;
    color: var(--ink-navy);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.wch-tier {
    font-size: 11px;
    font-weight: bold;
    color: #8a6d00;
    text-transform: uppercase;
    letter-spacing: 1px;
}

.wch-rating {
    font-size: 17px;
    font-weight: 900;
    color: var(--ink-link);
}

.wch-wl {
    flex: 0 0 74px;
    font-size: 12px;
    font-weight: bold;
    color: var(--ink-muted);
    text-align: right;
}

/* ── Replays list ── */

.wch-replay-list {
    display: flex;
    flex-direction: column;
    gap: 8px;
}

.wch-replay {
    display: flex;
    align-items: center;
    gap: 14px;
    width: 100%;
    padding: 10px 14px;
    border: 2px solid #c8ccd1;
    border-radius: 10px;
    background: var(--surface);
    font-family: Arial, sans-serif;
    text-align: left;
    cursor: pointer;
    transition: transform 0.1s, border-color 0.15s;
}

.wch-replay:hover {
    border-color: #0032ff;
    transform: translateX(3px);
}

.wch-replay-mode { font-size: 24px; }

.wch-replay-info {
    flex: 1 1 auto;
    display: flex;
    flex-direction: column;
    gap: 1px;
    min-width: 0;
}

.wch-replay-title {
    font-size: 15px;
    font-weight: 900;
    color: var(--ink-navy);
}

.wch-replay-course {
    font-size: 13px;
    font-weight: bold;
    color: var(--ink-link);
}

.wch-replay-meta {
    font-size: 11px;
    font-weight: bold;
    color: var(--ink-muted);
}

.wch-replay-play {
    display: grid;
    place-items: center;
    width: 38px;
    height: 38px;
    border: 2px solid #000;
    border-radius: 50%;
    background: linear-gradient(180deg, #43d95e, #1faf3c);
    color: #fff;
    font-size: 15px;
    font-weight: 900;
    box-shadow: 0 3px 0 rgba(0, 0, 0, 0.25);
}

/* ── Replay player ── */

.wch-player-head {
    display: flex;
    align-items: center;
    gap: 14px;
    flex-wrap: wrap;
    margin-bottom: 14px;
}

.wch-back {
    padding: 7px 14px;
    border: 2px solid #000;
    border-radius: 8px;
    background: #eee;
    font-family: Arial, sans-serif;
    font-size: 13px;
    font-weight: 900;
    cursor: pointer;
    box-shadow: 0 3px 0 rgba(0, 0, 0, 0.22);
}

.wch-back:hover { background: #e3f2fd; }

.wch-player-course {
    font-size: 17px;
    font-weight: 900;
    color: var(--ink-link);
}

.wch-racers {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
    gap: 10px;
    margin-bottom: 14px;
}

.wch-racer {
    padding: 10px 13px;
    border: 2px solid #c8ccd1;
    border-radius: 10px;
    background: var(--surface);
    transition: border-color 0.2s, background 0.2s;
}

.wch-racer.is-finished {
    border-color: #ffb300;
    background: linear-gradient(180deg, #fffdf2, #fff3bd);
}

.wch-racer-top {
    display: flex;
    justify-content: space-between;
    gap: 8px;
    margin-bottom: 3px;
}

.wch-racer-moves {
    font-size: 12px;
    font-weight: 900;
    color: var(--ink-link);
}

.wch-racer-article {
    font-size: 16px;
    font-weight: bold;
    color: var(--ink-mid);
    font-family: Georgia, serif;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.wch-timeline {
    height: 16px;
    margin-bottom: 10px;
    border: 2px solid #000;
    border-radius: 9px;
    background: #e6e9ef;
    overflow: hidden;
    cursor: pointer;
}

.wch-timeline-fill {
    height: 100%;
    width: 0;
    background: linear-gradient(90deg, #64b5f6, #0032ff);
    transition: width 0.1s linear;
}

.wch-controls {
    display: flex;
    align-items: center;
    gap: 12px;
    flex-wrap: wrap;
}

.wch-ctl {
    padding: 7px 13px;
    border: 2px solid #000;
    border-radius: 8px;
    background: var(--surface);
    font-family: Arial, sans-serif;
    font-size: 14px;
    font-weight: 900;
    cursor: pointer;
    box-shadow: 0 3px 0 rgba(0, 0, 0, 0.22);
}

.wch-ctl:hover { background: #e3f2fd; }

.wch-speed.is-active {
    background: #0032ff;
    color: #fff;
}

.wch-speeds { display: flex; gap: 6px; }

.wch-time {
    font-size: 13px;
    font-weight: 900;
    color: var(--ink-mid);
    font-variant-numeric: tabular-nums;
}

.wch-finish {
    margin-top: 12px;
    padding: 10px 16px;
    border: 3px solid #ffb300;
    border-radius: 10px;
    background: linear-gradient(180deg, #fffdf2, #fff3bd);
    font-size: 16px;
    font-weight: 900;
    color: var(--ink-navy);
    text-align: center;
}

/* ── Records ── */

.wch-records {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(210px, 1fr));
    gap: 12px;
}

.wch-record {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 3px;
    padding: 18px 14px;
    border: 3px solid #000;
    border-radius: 12px;
    background: linear-gradient(180deg, var(--panel-top), var(--panel-bottom));
    box-shadow: 0 4px 0 rgba(0, 0, 0, 0.2);
    text-align: center;
}

.wch-record-emoji { font-size: 32px; }

.wch-record-label {
    font-size: 12px;
    font-weight: 900;
    letter-spacing: 2px;
    text-transform: uppercase;
    color: var(--ink-muted);
}

.wch-record-value {
    font-size: 27px;
    font-weight: 900;
    color: var(--ink-link);
}

.wch-record-holder {
    font-size: 12px;
    font-weight: bold;
    color: var(--ink-mid);
}

/* ── Channel sub-tabs, panel subtitles ── */

.wch-panel-sub {
    margin: -10px 0 14px;
    font-size: 12px;
    font-weight: bold;
    letter-spacing: 1px;
    text-transform: uppercase;
    color: var(--ink-faint);
}

.wch-section-title {
    margin: 20px 0 10px;
    font-size: 15px;
    font-weight: 900;
    letter-spacing: 1px;
    text-transform: uppercase;
    color: var(--ink-mid);
}

.wch-subtabs {
    display: flex;
    gap: 8px;
    flex-wrap: wrap;
    margin-bottom: 14px;
}

.wch-subtab {
    padding: 6px 14px;
    border: 2px solid #c8ccd1;
    border-radius: 999px;
    background: var(--surface);
    font-family: Arial, sans-serif;
    font-size: 13px;
    font-weight: 900;
    color: var(--ink-mid);
    cursor: pointer;
    transition: background 0.15s, transform 0.1s;
}

.wch-subtab:hover { background: #e3f2fd; transform: translateY(-1px); }

.wch-subtab.is-active {
    border-color: #000;
    background: #0032ff;
    color: #fff;
    box-shadow: 0 2px 0 rgba(0, 0, 0, 0.3);
}

.wch-records--strip { margin-bottom: 16px; }

/* ── Me: profile header, XP bar, recent games ── */

.wch-me-head {
    display: flex;
    align-items: center;
    gap: 16px;
    flex-wrap: wrap;
    padding: 14px 16px;
    margin-bottom: 4px;
    border: 3px solid #000;
    border-radius: 12px;
    background: linear-gradient(160deg, #10142e, #232b5c);
    box-shadow: 0 4px 0 rgba(0, 0, 0, 0.25);
}

.wch-me-level {
    padding: 8px 14px;
    border: 2px solid #ffb300;
    border-radius: 10px;
    background: rgba(255, 179, 0, 0.15);
    font-size: 20px;
    font-weight: 900;
    color: #ffd257;
    white-space: nowrap;
}

.wch-me-xp {
    flex: 1 1 220px;
    min-width: 180px;
}

.wch-me-xp-bar {
    height: 14px;
    border: 2px solid rgba(255, 255, 255, 0.4);
    border-radius: 8px;
    background: rgba(0, 0, 0, 0.35);
    overflow: hidden;
}

.wch-me-xp-fill {
    height: 100%;
    background: linear-gradient(90deg, #f6d83f, #ffb300);
    transition: width 0.3s;
}

.wch-me-xp-label {
    margin-top: 4px;
    font-size: 11px;
    font-weight: bold;
    letter-spacing: 1px;
    color: #cfd4ff;
}

.wch-me-league {
    flex: 0 1 auto;
    font-size: 13px;
    font-weight: 900;
    color: #fff;
}

.wch-recent {
    display: flex;
    flex-direction: column;
    gap: 7px;
}

.wch-recent-row {
    display: flex;
    align-items: center;
    gap: 12px;
    padding: 9px 14px;
    border: 2px solid #c8ccd1;
    border-radius: 9px;
    background: var(--surface);
}

.wch-recent-mode { font-size: 22px; }

.wch-recent-info {
    flex: 1 1 auto;
    display: flex;
    flex-direction: column;
    min-width: 0;
}

.wch-recent-course {
    font-size: 14px;
    font-weight: 900;
    color: var(--ink-navy);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.wch-recent-meta {
    font-size: 11px;
    font-weight: bold;
    color: var(--ink-muted);
}

.wch-recent-result {
    flex: 0 0 auto;
    padding: 3px 10px;
    border-radius: 999px;
    border: 2px solid #000;
    font-size: 11px;
    font-weight: 900;
    letter-spacing: 1px;
}

.wch-recent-result.is-win  { background: #d4f7d9; color: #0c7a24; border-color: #0c7a24; }
.wch-recent-result.is-loss { background: #ffe1e1; color: #b3261e; border-color: #b3261e; }
.wch-recent-result.is-draw { background: #eef0f4; color: #5b6474; border-color: #5b6474; }

.wch-recent-ago {
    flex: 0 0 64px;
    font-size: 11px;
    font-weight: bold;
    color: var(--ink-faint);
    text-align: right;
}

/* ── Achievements grid (reuses the badge cards) ── */

.wch-awards-grid {
    padding: 0;
    overflow-y: visible;
}

.wch-surf-next {
    display: block;
    margin: 14px auto 0;
}

@media (max-width: 768px) {
    .wch-topbar { justify-content: center; }
    .wch-wl { display: none; }
    .wch-recent-ago { display: none; }
}

/* ===========================
   FAVOURITE COLOR PICKER
   (join gate + Settings → Customization)
   =========================== */

.fav-hex-row {
    display: flex;
    align-items: center;
    gap: 12px;
}

.fav-hex {
    appearance: none;
    width: 52px;
    height: 44px;
    padding: 0;
    border: 3px solid rgba(0, 0, 0, 0.55);
    border-radius: 10px;
    background: none;
    cursor: pointer;
}

.fav-hex-name {
    font-size: 16px;
    font-weight: 900;
    color: var(--ink-navy);
    min-width: 90px;
    text-align: left;
}

.fav-hex-row--join {
    justify-content: center;
    margin: 14px 0 12px;
}

/* Signup pure-colour swatches */
.join-swatches {
    display: grid;
    grid-template-columns: repeat(4, 1fr);
    gap: 10px;
    margin: 16px auto 12px;
    /* Fills the card's inner width (440 card - 72 padding - 10 border): big,
       easy targets rather than fiddly little dots. */
    max-width: 342px;
    width: 100%;
}

.join-swatch {
    appearance: none;
    aspect-ratio: 1;
    border: 3px solid rgba(0, 0, 0, 0.35);
    border-radius: 4px;
    background: var(--sw, #ccc);
    cursor: pointer;
    transition: transform 0.12s ease, box-shadow 0.12s ease;
}

.join-swatch:hover {
    transform: translateY(-2px) scale(1.05);
}

.join-swatch.is-picked {
    border-color: #17233b;
    box-shadow: 0 0 0 3px #fff, 0 0 0 6px #17233b;
    transform: scale(1.06);
}

/* ===========================
   JOIN SCREEN (login gate)
   =========================== */

.join-overlay {
    position: fixed;
    inset: 0;
    z-index: 100000;
    display: grid;
    place-items: center;
    background: rgba(10, 14, 34, 0.72);
    backdrop-filter: blur(6px);
    animation: mpFadeIn 0.25s ease-out;
}

.join-overlay.is-leaving {
    opacity: 0;
    transition: opacity 0.3s ease-out;
    pointer-events: none;
}

.join-card {
    position: relative;
    display: flex;
    flex-direction: column;
    align-items: center;
    width: min(92vw, 440px);
    padding: 36px 36px 24px;
    border: 5px solid #000;
    border-radius: 16px;
    background: var(--surface-2);
    box-shadow: 0 12px 0 rgba(0, 0, 0, 0.35);
    overflow: hidden;
    text-align: center;
    font-family: Arial, sans-serif;
}

.join-checkerboard {
    position: absolute;
    top: 0;
    left: 0;
    right: 0;
    height: 14px;
    background: repeating-conic-gradient(#111 0% 25%, #fff 0% 50%) 0 0 / 14px 14px;
}

.join-logo {
    margin: 6px 0 4px;
    font-size: 42px;
    font-weight: 900;
    font-style: italic;
    color: var(--ink-link);
    letter-spacing: 1px;
    text-shadow: 2px 2px 0 rgba(0, 0, 0, 0.15);
}

.join-tagline {
    margin-bottom: 22px;
    font-size: 14px;
    font-weight: bold;
    color: var(--ink-mid);
}

.join-label {
    align-self: flex-start;
    margin: 0 0 6px 2px;
    font-size: 12px;
    font-weight: 900;
    letter-spacing: 2px;
    text-transform: uppercase;
    color: var(--ink-muted);
}

.join-input {
    width: 100%;
    padding: 12px 14px;
    border: 3px solid #000;
    border-radius: 8px;
    background: var(--surface);
    color: var(--ink-link);
    font-family: Arial, sans-serif;
    font-size: 20px;
    font-weight: bold;
    text-align: center;
    outline: none;
}

.join-input:focus {
    border-color: #0032ff;
}

.join-btn {
    appearance: none;
    width: 100%;
    margin-top: 16px;
    padding: 13px;
    border: 3px solid #000;
    border-radius: 8px;
    background: linear-gradient(180deg, #2bd158, #12a53c);
    color: #fff;
    font-family: Arial, sans-serif;
    font-size: 20px;
    font-weight: 900;
    letter-spacing: 2px;
    text-transform: uppercase;
    cursor: pointer;
    box-shadow: 0 5px 0 rgba(0, 0, 0, 0.25);
}

.join-btn:hover {
    filter: brightness(1.07);
}

.join-btn:active {
    transform: translateY(3px);
    box-shadow: 0 2px 0 rgba(0, 0, 0, 0.25);
}

.join-legal {
    margin-top: 16px;
    font-size: 11px;
    color: #778;
}

.join-link {
    text-decoration: underline;
    cursor: pointer;
    color: #556;
}

.join-link:hover {
    color: var(--ink-link);
}

/* ── Join screen: multi-step login ── */

.join-logo--sm {
    font-size: 30px;
    margin: 4px 0 2px;
}

.join-welcome {
    margin: 2px 0 6px;
    font-size: 26px;
    font-weight: 900;
    font-style: italic;
    letter-spacing: 1px;
    color: #12a53c;
    text-shadow: 2px 2px 0 rgba(0, 0, 0, 0.12);
}

.join-welcome--ask {
    color: var(--ink-link);
    /* The question is long enough to wrap on a 440px card and much longer on a
       phone. Balancing splits it evenly instead of leaving "color?" alone on a
       second line; browsers without support just wrap as before. */
    text-wrap: balance;
}

.join-tagline--sm {
    margin-bottom: 16px;
    font-weight: normal;
}

.join-tagline--sm strong {
    color: var(--ink-link);
}

.join-label--mt {
    margin-top: 14px;
}

.join-error {
    align-self: stretch;
    margin: 12px 0 0;
    padding: 8px 10px;
    border: 2px solid #b71c1c;
    border-radius: 7px;
    background: #ffe5e5;
    color: #b71c1c;
    font-size: 13px;
    font-weight: bold;
}

/* The age/terms line under the register button: has to be legible and
   unmissable, without competing with the button it sits below. */
.join-legal {
    align-self: stretch;
    margin: 14px 0 0;
    font-size: 12px;
    line-height: 1.5;
    color: #4a5468;
    text-align: center;
}

.join-legal a {
    color: var(--ink-link);
    font-weight: bold;
}

.join-claim-text {
    margin: 0 0 10px;
    font-size: 14px;
    line-height: 1.45;
    color: #334;
}

.join-claim-text strong {
    color: var(--ink-link);
}

.join-claim-note {
    margin: 0 0 8px;
    padding: 9px 11px;
    border: 2px dashed #b0863b;
    border-radius: 7px;
    background: #fff6e0;
    font-size: 12px;
    line-height: 1.4;
    color: #7a5a1e;
}

.join-btn-stack {
    display: flex;
    flex-direction: column;
    gap: 10px;
    width: 100%;
    margin-top: 12px;
}

.join-btn-stack .join-btn {
    margin-top: 0;
}

.join-btn--neutral {
    background: linear-gradient(180deg, #4a90e2, #2e6fc7);
}

.join-btn--ghost {
    background: #e9ecf5;
    color: #334;
    box-shadow: 0 5px 0 rgba(0, 0, 0, 0.18);
}

.join-btn--ghost:hover {
    filter: none;
    background: #dfe3ef;
}

.join-btn:disabled,
.join-btn.is-busy {
    opacity: 0.6;
    cursor: default;
    filter: none;
    transform: none;
}

.settings-guest-tag,
.profile-menu-guest {
    display: inline-block;
    margin-left: 6px;
    padding: 1px 6px;
    border-radius: 5px;
    background: #b0863b;
    color: #fff;
    font-size: 10px;
    font-weight: 900;
    letter-spacing: 1px;
    vertical-align: middle;
}

.settings-register {
    background: #12a53c !important;
    color: #fff;
}

/* ── Logged-in name displays ── */

.mp-name-display {
    padding: 10px 18px;
    border: 3px solid #000;
    border-radius: 8px;
    background: var(--surface);
    color: var(--ink-link);
    font-family: Arial, sans-serif;
    font-size: 18px;
    font-weight: bold;
}

.profile-menu-user {
    padding: 9px 13px;
    background: #0032ff;
    color: #fff;
    font-size: 14px;
    font-weight: 900;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

/* ===========================
   SETTINGS 2.0 (tabbed)
   =========================== */

.settings-screen {
    display: flex;
    align-items: flex-start;
    gap: 22px;
}

.settings-tabs {
    display: flex;
    flex-direction: column;
    gap: 9px;
    flex-shrink: 0;
    width: 250px;
    position: sticky;
    top: 20px;
}

.settings-tab {
    appearance: none;
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 12px 16px;
    border: 3px solid #000;
    border-radius: 7px;
    background: linear-gradient(180deg, var(--panel-top), var(--panel-bottom));
    color: var(--ink-navy);
    font-family: Arial, sans-serif;
    font-size: 16px;
    font-weight: bold;
    text-align: left;
    cursor: pointer;
    box-shadow: 0 4px 0 rgba(0, 0, 0, 0.22);
}

.settings-tab:hover {
    background: #e3f2fd;
}

.settings-tab.is-active {
    background: #0032ff;
    color: #fff;
    text-shadow: 1px 1px 0 rgba(0, 0, 0, 0.3);
}

.settings-tab-icon {
    font-size: 18px;
}

.settings-list {
    flex: 1;
    min-width: 0;
}

.settings-section-title {
    margin-bottom: 4px;
    padding: 0 4px;
    font-family: Arial, sans-serif;
    font-size: 26px;
    font-weight: 900;
    letter-spacing: 2px;
    text-transform: uppercase;
    color: #fff;
    text-shadow: 2px 2px 0 rgba(0, 0, 0, 0.55);
}

/* ── Sliders ── */

.settings-slider {
    display: flex;
    align-items: center;
    gap: 10px;
    flex-shrink: 0;
}

.settings-slider-end {
    font-family: Arial, sans-serif;
    font-size: 11px;
    font-weight: 900;
    letter-spacing: 1px;
    color: var(--ink-muted);
}

.settings-slider input[type="range"] {
    width: 170px;
    accent-color: #0032ff;
    cursor: pointer;
}

.settings-slider-val {
    min-width: 52px;
    padding: 5px 8px;
    border: 2px solid #000;
    border-radius: 5px;
    background: var(--surface);
    font-family: Arial, sans-serif;
    font-size: 14px;
    font-weight: bold;
    color: var(--ink-link);
    text-align: center;
}

/* ── Text input ── */

.settings-textwrap {
    flex: 1;
    max-width: 560px;
}

.settings-text {
    width: 100%;
    padding: 10px 14px;
    border: 3px solid #000;
    border-radius: 7px;
    background: var(--surface);
    color: var(--ink-link);
    font-family: Arial, sans-serif;
    font-size: 15px;
    font-weight: bold;
    outline: none;
}

.settings-text:focus {
    border-color: #0032ff;
}

/* ── Select ──
   Borrows the text input's shell so the two read as the same control family.
   appearance:none drops the platform chrome, which otherwise ignores the
   border radius and the bold face; the caret is drawn back on as a background
   so it still looks like something you can open. */

.settings-selectwrap {
    flex: 1;
    max-width: 560px;
}

.settings-select {
    width: 100%;
    padding: 10px 38px 10px 14px;
    border: 3px solid #000;
    border-radius: 7px;
    background: var(--surface);
    color: var(--ink-link);
    font-family: Arial, sans-serif;
    font-size: 15px;
    font-weight: bold;
    outline: none;
    cursor: pointer;
    appearance: none;
    -webkit-appearance: none;
    background-image: linear-gradient(45deg, transparent 50%, #0032ff 50%),
                      linear-gradient(135deg, #0032ff 50%, transparent 50%);
    background-position: calc(100% - 20px) calc(50% + 2px),
                         calc(100% - 14px) calc(50% + 2px);
    background-size: 6px 6px, 6px 6px;
    background-repeat: no-repeat;
}

.settings-select:focus {
    border-color: #0032ff;
}

.settings-select:disabled {
    cursor: not-allowed;
    opacity: .55;
}

/* ── Controls (kbd) & account ── */

.settings-kbd {
    flex-shrink: 0;
    padding: 8px 16px;
    border: 3px solid #000;
    border-radius: 6px;
    background: linear-gradient(180deg, #ffe082, #f6d83f);
    font-family: Arial, sans-serif;
    font-size: 15px;
    font-weight: bold;
    color: var(--ink-navy);
    box-shadow: 0 3px 0 rgba(0, 0, 0, 0.25);
    white-space: nowrap;
}

.settings-account {
    display: flex;
    align-items: center;
    gap: 14px;
    flex-shrink: 0;
    /* Only the rename editor ever wraps: it adds an error line under the row. */
    flex-wrap: wrap;
}

.settings-account-name {
    padding: 9px 16px;
    border: 3px solid #000;
    border-radius: 7px;
    background: var(--surface);
    font-family: Arial, sans-serif;
    font-size: 17px;
    font-weight: bold;
    color: var(--ink-link);
}

/* ── Messages from the admin ──
   Bottom-right, stacked upward, above the footer band AND above the Discord
   button that already lives in this corner: 108px + its 64px + air. Both the
   footer height and the Discord offset are tracked by the breakpoints at the
   bottom of this file, so this follows them there too.

   Column-reverse so a new card appears nearest the corner and pushes older
   ones up, which is the direction attention is already pointing. */

.notif-layer {
    position: fixed;
    right: 44px;
    bottom: 188px;
    z-index: 9998;
    display: flex;
    flex-direction: column-reverse;
    gap: 10px;
    /* The layer spans the corner; only the cards inside it take clicks, or an
       invisible column would swallow them for the whole height of the screen. */
    pointer-events: none;
}

.notif-card {
    position: relative;
    pointer-events: auto;
    width: 300px;
    max-width: calc(100vw - 88px);
    padding: 14px 38px 14px 16px;
    border: 3px solid #000;
    border-radius: 9px;
    background: linear-gradient(180deg, var(--panel-top), var(--panel-bottom));
    box-shadow: 0 5px 0 rgba(0, 0, 0, 0.25);
    /* Slides in from off-frame to the right. Paired with .is-in rather than an
       animation so a card that is removed mid-flight cannot leave the layer
       holding a finished animation's end state. */
    transform: translateX(24px);
    opacity: 0;
    transition: transform 200ms ease, opacity 200ms ease;
}

.notif-card.is-in {
    transform: translateX(0);
    opacity: 1;
}

.notif-card.is-leaving {
    transform: translateX(24px);
    opacity: 0;
}

.notif-title {
    font-family: Arial, sans-serif;
    font-size: 16px;
    font-weight: 900;
    color: var(--ink-navy);
    margin-bottom: 4px;
}

.notif-body {
    font-family: Arial, sans-serif;
    font-size: 14px;
    font-weight: bold;
    line-height: 1.35;
    color: var(--ink-mid);
    /* An admin can type 400 characters and a long word should not widen the
       card past the corner it lives in. */
    overflow-wrap: anywhere;
}

.notif-close {
    position: absolute;
    top: 8px;
    right: 10px;
    appearance: none;
    border: 0;
    background: transparent;
    color: var(--ink-muted);
    font-family: Arial, sans-serif;
    font-size: 21px;
    line-height: 1;
    font-weight: bold;
    cursor: pointer;
    padding: 0 2px;
}

.notif-close:hover { color: var(--ink-strong); }

/* ── Recovery email row ──
   An address is far longer than a username, so the badge is capped and clipped
   rather than allowed to push the button off the row. The full value is on the
   element's title. */

.settings-recovery {
    max-width: 260px;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    font-size: 15px;
}

/* "None" is a state to fix, not a value: greyed and italic so it does not read
   as an address someone already set. */
.settings-recovery--none {
    color: var(--ink-faint);
    font-style: italic;
}

/* ── Renaming ──
   The input replaces the name badge inside .settings-account, so it has to sit
   in the same flex row without stretching it: a fixed width rather than the
   100% .settings-text carries, which is written for a full-width row. */

.settings-rename {
    background: linear-gradient(180deg, #4a9eff, #1565c0) !important;
}

.settings-rename-input {
    width: 200px;
    padding: 9px 14px;
    font-size: 17px;
}

/* Full-basis so it wraps onto a line of its own instead of squeezing the input
   and the row's label into columns a few characters wide. */
.settings-rename-error {
    flex-basis: 100%;
    padding: 6px 10px;
    border: 2px solid #b71c1c;
    border-radius: 6px;
    background: #ffe5e5;
    color: #b71c1c;
    font-family: Arial, sans-serif;
    font-size: 13px;
    font-weight: bold;
}

.settings-rename-error[hidden] {
    display: none;
}

.settings-avatar {
    display: flex;
    align-items: center;
    gap: 12px;
    flex-shrink: 0;
}

.settings-avatar-preview {
    width: 44px;
    height: 44px;
    border-radius: 50%;
    border: 3px solid #000;
    object-fit: cover;
    background: var(--surface);
    flex-shrink: 0;
}

.settings-avatar-preview--empty {
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 20px;
    background: var(--surface-3);
}

.settings-logout {
    appearance: none;
    padding: 9px 18px;
    border: 3px solid #000;
    border-radius: 6px;
    background: linear-gradient(180deg, #ff6b6b, #d63030);
    color: #fff;
    font-family: Arial, sans-serif;
    font-size: 16px;
    font-weight: bold;
    cursor: pointer;
    box-shadow: 0 4px 0 rgba(0, 0, 0, 0.25);
    text-shadow: 1px 1px 0 rgba(0, 0, 0, 0.3);
}

.settings-logout:active {
    transform: translateY(2px);
    box-shadow: 0 2px 0 rgba(0, 0, 0, 0.25);
}

.settings-mono {
    font-family: Consolas, monospace;
    font-size: 15px;
    font-weight: bold;
    color: var(--ink-mid);
    padding: 8px 14px;
    border: 2px dashed #999;
    border-radius: 6px;
    background: color-mix(in srgb, var(--surface) 80%, transparent);
}

.settings-static {
    font-size: 22px;
}

@media (max-width: 900px) {
    .settings-screen {
        flex-direction: column;
    }

    .settings-tabs {
        position: static;
        width: 100%;
        flex-direction: row;
        flex-wrap: wrap;
    }

    .settings-tab {
        flex: 1 1 auto;
    }
}

/* ===========================
   UI THEMES
   =========================== */

/* Midnight.
   This used to be `filter: invert(1) hue-rotate(180deg)` on main, with images
   inverted a second time to cancel it out. That is why the shop looked wrong:
   an inverter has no idea what it is looking at, so every swatch, every piece
   of cosmetic art and every status colour came out as its opposite — a green
   ON pill read red.

   It repaints the palette instead, which is the mirror of what the theme
   colour already does. tintHex() mixes a chosen colour toward white and never
   reaches it, so a pitch-black pick lands on grey; these mix toward black on
   the same ramp and never reach it, so the lightest surface in the app lands
   on grey rather than turning into a hole. Nothing is inverted, so a green
   pill stays green and the shop shows the art the player actually bought. */
html.theme-midnight {
    --surface: #4d5163;
    /* The steps carry on downward, mirroring how classic steps up from white.
       The floor is still nowhere near black — a panel is meant to read as a
       panel, not as a hole cut in the page. */
    --surface-2: #434857;
    --surface-3: #363a49;
    --ink: #eceef7;
    /* Even the strongest ink stops short of pure white — the ceiling applies
       to text as much as to panels. */
    --ink-strong: #f5f7ff;
    /* The text scale runs the other way here, but keeps its order: what was
       darkest stays the most prominent. */
    --ink-navy: #dfe3f2;
    --ink-mid: #c6ccdd;
    --ink-muted: #a9b0c4;
    --ink-faint: #8f96aa;

    /* Mirrors the light gradient's move: a step down and a shade bluer from top
       to bottom. Both ends sit well above the #0c0e1a page so a panel still
       reads as a panel rather than as a hole. */
    --panel-top: rgba(77, 81, 99, 0.97);
    --panel-bottom: rgba(58, 62, 79, 0.97);

    /* Same hue, lifted until it reads on the panels above. */
    --ink-link: #8fb0ff;

    /* Slightly more opaque than classic's 0.72: these cards are translucent
       over the wallpaper, and light text needs more backing behind it than dark
       text does before a bright patch of photo starts eating the letters. */
    --theme-card: rgba(34, 37, 53, 0.78);
    --theme-tint: #2b2f42;
    --theme-tint2: #1f2331;
    --theme-wash: #161927;
    --theme-wash2: #11131f;

    background: #0c0e1a;
}

/* ===========================
   CUSTOM MODE 2.0
   =========================== */

.custom-form--builder {
    max-width: 720px;
    align-items: stretch;
    gap: 20px;
}

.cm-section {
    display: flex;
    flex-direction: column;
    gap: 12px;
    padding: 16px 18px;
    border: 3px solid #000;
    border-radius: 7px;
    background: color-mix(in srgb, var(--surface) 72%, transparent);
    box-shadow: 0 4px 0 rgba(0, 0, 0, 0.15);
}

.cm-section-title {
    font-family: Arial, sans-serif;
    font-size: 17px;
    font-weight: 900;
    letter-spacing: 1.5px;
    text-transform: uppercase;
    color: var(--ink-navy);
}

.cm-input-row {
    display: flex;
    gap: 8px;
}

.cm-input-row .custom-input {
    flex: 1;
}

.cm-dice {
    appearance: none;
    flex-shrink: 0;
    width: 52px;
    border: 3px solid #000;
    border-radius: 6px;
    background: linear-gradient(180deg, #ffe082, #f6d83f);
    font-size: 22px;
    cursor: pointer;
    box-shadow: 0 4px 0 rgba(0, 0, 0, 0.22);
}

.cm-dice:hover { filter: brightness(1.06); }
.cm-dice:disabled { opacity: 0.6; cursor: default; }
.cm-dice.is-rolling { animation: cmDiceSpin 0.5s linear infinite; }

@keyframes cmDiceSpin {
    to { transform: rotate(360deg); }
}

.cm-rule {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 14px;
    flex-wrap: wrap;
}

.cm-rule-label {
    font-family: Arial, sans-serif;
    font-size: 15px;
    font-weight: bold;
    color: var(--ink-mid);
}

.cm-rule-label small {
    display: block;
    font-size: 11px;
    font-weight: normal;
    color: var(--ink-faint);
}

.cm-chips {
    display: flex;
    border: 3px solid #000;
    border-radius: 6px;
    overflow: hidden;
    box-shadow: 0 4px 0 rgba(0, 0, 0, 0.2);
}

.cm-chip {
    appearance: none;
    border: 0;
    border-left: 2px solid #000;
    padding: 8px 14px;
    font-family: Arial, sans-serif;
    font-size: 14px;
    font-weight: bold;
    cursor: pointer;
    background: var(--surface-3);
    color: var(--ink-muted);
}

.cm-chip:first-child { border-left: 0; }

.cm-chip.is-active {
    background: #0032ff;
    color: #fff;
    text-shadow: 1px 1px 0 rgba(0, 0, 0, 0.3);
}

.cm-presets {
    display: flex;
    gap: 8px;
    flex-wrap: wrap;
}

.cm-preset {
    appearance: none;
    flex: 1 1 auto;
    padding: 9px 12px;
    border: 3px solid #000;
    border-radius: 6px;
    background: linear-gradient(180deg, var(--panel-top), var(--panel-bottom));
    font-family: Arial, sans-serif;
    font-size: 14px;
    font-weight: bold;
    color: var(--ink-navy);
    cursor: pointer;
    box-shadow: 0 4px 0 rgba(0, 0, 0, 0.2);
}

.cm-preset:hover { background: #e3f2fd; }
.cm-preset:active { transform: translateY(2px); box-shadow: 0 2px 0 rgba(0, 0, 0, 0.2); }

.cm-hazards {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
    gap: 8px;
}

.cm-hazard {
    appearance: none;
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 8px 10px;
    border: 3px solid #000;
    border-radius: 6px;
    background: var(--surface);
    font-family: Arial, sans-serif;
    font-size: 12.5px;
    font-weight: bold;
    color: var(--ink-mid);
    text-align: left;
    cursor: pointer;
    box-shadow: 0 3px 0 rgba(0, 0, 0, 0.18);
}

.cm-hazard:hover { background: #f0f4ff; }

.cm-hazard.is-active {
    background: var(--hz, #0032ff);
    color: #fff;
    text-shadow: 1px 1px 0 rgba(0, 0, 0, 0.35);
}

.cm-hazard-icon { font-size: 18px; }

.cm-hazard-name {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* ── In-run HUD ── */

.cm-hud {
    position: fixed;
    top: 86px;
    left: 50%;
    transform: translateX(-50%);
    z-index: 9050;
    display: flex;
    gap: 8px;
    pointer-events: none;
}

.cm-hud-chip {
    /* inline-flex + gap rather than a literal space in the markup, so a chip
       whose ICONS slot is still empty renders with no stray leading gap. */
    display: inline-flex;
    align-items: center;
    gap: 6px;
    padding: 8px 14px;
    border: 3px solid #000;
    border-radius: 7px;
    background: color-mix(in srgb, var(--surface) 95%, transparent);
    font-family: Arial, sans-serif;
    font-size: 15px;
    font-weight: 900;
    color: var(--ink-navy);
    box-shadow: 0 4px 0 rgba(0, 0, 0, 0.25);
    white-space: nowrap;
}

.cm-hud-chip.is-danger {
    background: #d63030;
    color: #fff;
    animation: cmDangerPulse 0.7s ease-in-out infinite;
}

@keyframes cmDangerPulse {
    50% { transform: scale(1.08); }
}

.cm-hud-chip--rainbow {
    background: linear-gradient(90deg,
        #ff3d3d, #ff9f1a, #ffe93d, #3dff5e, #3dc9ff, #a03dff, #ff3dcf, #ff3d3d);
    background-size: 300% 100%;
    color: #fff;
    text-shadow: 0 1px 2px rgba(0, 0, 0, 0.55);
    animation: mpRainbowSlide 3s linear infinite;
}

/* ── Toast & fail overlay ── */

.cm-toast {
    position: fixed;
    bottom: 130px;
    left: 50%;
    transform: translateX(-50%) translateY(16px);
    z-index: 9350;
    padding: 12px 22px;
    border: 3px solid #000;
    border-radius: 8px;
    background: #17233b;
    color: #fff;
    font-family: Arial, sans-serif;
    font-size: 17px;
    font-weight: bold;
    box-shadow: 0 6px 0 rgba(0, 0, 0, 0.35);
    opacity: 0;
    transition: opacity 0.25s ease-out, transform 0.25s ease-out;
    pointer-events: none;
}

.cm-toast.is-visible {
    opacity: 1;
    transform: translateX(-50%) translateY(0);
}

.cm-fail-overlay {
    position: fixed;
    inset: 0;
    z-index: 9300;
    display: grid;
    place-items: center;
    background: rgba(10, 14, 34, 0.7);
    animation: mpFadeIn 0.3s ease-out;
}

.cm-fail-card {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 14px;
    width: min(92vw, 460px);
    padding: 32px 36px;
    border: 5px solid #000;
    border-radius: 14px;
    background: var(--surface-2);
    box-shadow: 0 12px 0 rgba(0, 0, 0, 0.35);
    text-align: center;
    font-family: Arial, sans-serif;
}

.cm-fail-icon { font-size: 54px; }
.cm-fail-icon:empty { display: none; }

.cm-fail-title {
    font-size: 30px;
    font-weight: 900;
    letter-spacing: 2px;
    color: #d63030;
    text-shadow: 2px 2px 0 rgba(0, 0, 0, 0.15);
}

.cm-fail-stats {
    font-size: 15px;
    line-height: 1.5;
    color: var(--ink-mid);
}

.cm-fail-btns {
    display: flex;
    gap: 12px;
    margin-top: 6px;
}

.cm-fail-btn {
    appearance: none;
    padding: 11px 20px;
    border: 3px solid #000;
    border-radius: 7px;
    background: var(--surface-3);
    color: var(--ink-mid);
    font-family: Arial, sans-serif;
    font-size: 16px;
    font-weight: bold;
    cursor: pointer;
    box-shadow: 0 4px 0 rgba(0, 0, 0, 0.25);
}

.cm-fail-btn--retry {
    background: linear-gradient(180deg, #2bd158, #12a53c);
    color: #fff;
    text-shadow: 1px 1px 0 rgba(0, 0, 0, 0.3);
}

.cm-fail-btn:active {
    transform: translateY(2px);
    box-shadow: 0 2px 0 rgba(0, 0, 0, 0.25);
}

/* ─── Cinematic winner replay (ReplayCinema) ─────────────────────────────────── */

/* Floating chrome (the profile card) sits above 9400 — hide it while the
   cinema is on screen. */
body.rc-open .profile-btn,
body.rc-open .discord-btn { display: none; }

.rc-overlay {
    position: fixed;
    inset: 0;
    z-index: 9400;
    display: flex;
    flex-direction: column;
    background: #000;
    font-family: Arial, sans-serif;
}

.rc-head {
    display: flex;
    align-items: center;
    gap: 12px;
    padding: 10px 16px;
    border-bottom: 3px solid #000;
    background: linear-gradient(180deg, #262a33, #14161b);
    color: #fff;
}

.rc-film { font-size: 18px; }

.rc-title {
    font-size: 16px;
    font-weight: bold;
    white-space: nowrap;
}

.rc-course {
    min-width: 0;
    overflow: hidden;
    color: #9fd0ff;
    font-size: 13px;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.rc-step {
    margin-left: auto;
    color: #ffd95e;
    font-size: 13px;
    font-weight: bold;
    white-space: nowrap;
}

.rc-btn {
    appearance: none;
    padding: 6px 12px;
    border: 2px solid #000;
    border-radius: 8px;
    background: var(--surface);
    color: var(--ink);
    font-family: Arial, sans-serif;
    font-size: 13px;
    font-weight: bold;
    cursor: pointer;
    box-shadow: 0 3px 0 rgba(0, 0, 0, 0.35);
}

.rc-btn:active {
    transform: translateY(2px);
    box-shadow: 0 1px 0 rgba(0, 0, 0, 0.35);
}

.rc-btn--big {
    padding: 11px 20px;
    font-size: 16px;
}

.rc-stage {
    position: relative;
    flex: 1;
    overflow: hidden;
}

/* The article layer: never scrolls or reacts — the run pans it via transform. */
.rc-scroll {
    position: absolute;
    inset: 0;
    overflow: hidden;
    pointer-events: none;
    background: var(--wiki-bg);
    transition: opacity 0.3s;
}

.rc-scroll.is-warping { opacity: 0.12; }

.rc-pan {
    min-height: 100%;
    will-change: transform;
}

/* The winner's cursor, locked at the centre of the stage. */
.rc-cursor {
    position: absolute;
    left: 50%;
    top: 50%;
    z-index: 5;
    pointer-events: none;
    filter: drop-shadow(0 2px 3px rgba(0, 0, 0, 0.45));
    transition: transform 0.12s;
}

.rc-cursor.is-press { transform: scale(0.8); }

.rc-cursor img { display: block; }

.rc-cursor img { image-rendering: pixelated; }

.rc-cursor-emoji {
    display: block;
    font-size: 30px;
    line-height: 1;
}

.rc-click-ring {
    position: absolute;
    left: 50%;
    top: 50%;
    z-index: 4;
    width: 14px;
    height: 14px;
    margin: -7px 0 0 -7px;
    border: 3px solid #ffd95e;
    border-radius: 50%;
    pointer-events: none;
    animation: rcRing 0.65s ease-out forwards;
}

@keyframes rcRing {
    from { transform: scale(0.4); opacity: 0.95; }
    to   { transform: scale(3.6); opacity: 0; }
}

/* The link the winner clicked lights up under the cursor. */
button.wiki-link-inline.rc-hit {
    padding: 0 4px;
    border-radius: 4px;
    background: #ffe93d;
    outline: 3px solid #ff9f1a;
    color: #202122;
    text-decoration: none;
}

.rc-toast {
    position: absolute;
    left: 50%;
    top: 22%;
    z-index: 6;
    transform: translateX(-50%);
    padding: 10px 18px;
    border: 2px solid #000;
    border-radius: 10px;
    background: var(--surface);
    color: var(--ink);
    font-size: 15px;
    font-weight: bold;
    box-shadow: 0 4px 0 rgba(0, 0, 0, 0.3);
}

.rc-toast[hidden] { display: none; }

.rc-finish {
    position: absolute;
    inset: 0;
    z-index: 7;
    display: flex;
    align-items: center;
    justify-content: center;
    background: rgba(10, 12, 18, 0.72);
}

.rc-finish[hidden] { display: none; }

.rc-finish-card {
    max-width: min(520px, 88vw);
    padding: 26px 38px;
    border: 3px solid #000;
    border-radius: 14px;
    background: var(--surface);
    color: var(--ink);
    text-align: center;
    box-shadow: 0 8px 0 rgba(0, 0, 0, 0.35);
}

.rc-finish-flag { font-size: 44px; }

.rc-finish-title {
    margin: 6px 0 2px;
    font-size: 20px;
    font-weight: bold;
}

.rc-finish-sub {
    margin-bottom: 16px;
    color: var(--ink-muted);
    font-size: 14px;
}

.rc-finish-btns {
    display: flex;
    gap: 10px;
    justify-content: center;
}

/* Victory-banner replay button (training / custom / challenges). */
.training-replay-btn {
    appearance: none;
    justify-self: start;
    margin-top: 4px;
    padding: 8px 16px;
    border: 3px solid #000;
    border-radius: 7px;
    background: var(--surface);
    color: var(--ink);
    font-family: Arial, sans-serif;
    font-size: 15px;
    font-weight: bold;
    cursor: pointer;
    box-shadow: 0 4px 0 rgba(0, 0, 0, 0.22);
}

.training-replay-btn:active {
    transform: translateY(2px);
    box-shadow: 0 2px 0 rgba(0, 0, 0, 0.22);
}

/* ═══ POTATO graphics ════════════════════════════════════════════════════════
   The lowest "Graphics" level. Strips every expensive effect — blur, shadows,
   gradients, rounded corners, animations, the animated card previews and the
   photo wallpaper — down to a flat, ultra-primitive UI for the weakest laptops.
   Deliberately plain. Toggled by applyGraphics() adding body.gfx-potato. */
body.gfx-potato *,
body.gfx-potato *::before,
body.gfx-potato *::after {
    animation: none !important;
    transition: none !important;
    box-shadow: none !important;
    text-shadow: none !important;
    backdrop-filter: none !important;
    -webkit-backdrop-filter: none !important;
    filter: none !important;
    border-radius: 0 !important;
    mask-image: none !important;
    -webkit-mask-image: none !important;
    -webkit-mask: none !important;
    mask: none !important;
}

/* Drop the animated wallpaper and the decorative checker flags entirely */
body.gfx-potato .bg-slide,
body.gfx-potato .checkerboard,
body.gfx-potato .footer-checkerboard { display: none !important; }

body.gfx-potato,
body.gfx-potato .menu-stage { background: #6b6b8a !important; }

/* Menu cards → flat grey tiles. (The card art they used to carry was a
   copyrighted GIF, removed 2026-08-01.) */
body.gfx-potato .blur { background: #dcdcdc !important; border: 2px solid #000 !important; }
body.gfx-potato .blue-card,
body.gfx-potato .goldframe,
body.gfx-potato .goldframe2,
body.gfx-potato .goldframe3 { background: #c4c4c4 !important; border: 1px solid #000 !important; }

/* Header / footer / profile → plain bars and a flat square badge */
body.gfx-potato .header-bg::after,
body.gfx-potato .footer-bg::after { background: #e8e8e8 !important; }
body.gfx-potato .mkwii-header,
body.gfx-potato .mkwii-footer { border-color: #000 !important; }
body.gfx-potato .profile-card,
body.gfx-potato .discord-card { background: #dcdcdc !important; border: 2px solid #000 !important; }
body.gfx-potato .discord-card { backdrop-filter: none !important; -webkit-backdrop-filter: none !important; }

/* Potato also flattens the multiplayer and in-race screens: drop the article's
   themed background art and knock decorative panels back to flat fills. The
   universal rule above already strips blur/shadow/radius/animation everywhere,
   so only leftover background art needs handling here. */
body.gfx-potato .wiki-skin { background: #ffffff !important; }
body.gfx-potato .article-stage,
body.gfx-potato .mp-article-stage { background: #8a8aa0 !important; }
body.gfx-potato .mp-result-card,
body.gfx-potato .mp-side-block,
body.gfx-potato .mp-rival,
body.gfx-potato .mp-lobby-row,
body.gfx-potato .mp-item-slot { background: #dcdcdc !important; border: 1px solid #000 !important; }

/* ═══ LOW / MINIMAL graphics ═════════════════════════════════════════════════
   Two lighter tiers that trim the most expensive effects without going fully
   primitive. LOW just drops backdrop-blur (the priciest GPU effect). MINIMAL
   also drops the animated 1.9 MB card previews and all CSS transitions. Both
   keep colours, gradients, shadows, rounded corners and the wallpaper. */
body.gfx-low *,
body.gfx-low *::before,
body.gfx-low *::after,
body.gfx-minimal *,
body.gfx-minimal *::before,
body.gfx-minimal *::after {
    backdrop-filter: none !important;
    -webkit-backdrop-filter: none !important;
}

body.gfx-minimal *,
body.gfx-minimal *::before,
body.gfx-minimal *::after {
    transition: none !important;
}

/* ===========================
   ICONS
   =========================== */

/* Drawn glyphs from public/icons.js, in place of the emoji the UI used to
   print. Sized in em so each one tracks whatever text it sits beside — the
   containers that already set a font-size for their old emoji (.badge-icon at
   27px, .mp-item-icon at 26px, and so on) keep working untouched. Colour comes
   from currentColor, so an icon follows the player's accent without a variant. */
.wd-icon {
    width: 1em;
    height: 1em;
    display: inline-block;
    vertical-align: -0.14em;
    flex: 0 0 auto;
}

/* An icon slot with no art renders nothing (see public/icons.js), which leaves
   these wrappers empty. Collapse them so a text-only label sits flush instead
   of carrying a blank gap where a picture used to be. */
.badge-icon:empty,
.settings-tab-icon:empty,
.trophy-toast-icon:empty,
.wch-recent-mode:empty,
.cm-hazard-icon:empty,
.ch-icon:empty,
.ch-intro-icon:empty,
.mp-item-icon:empty,
.cos-emoji:empty,
.cos-mini:empty,
.wiki-app-icon:empty {
    display: none;
}

/* ── /reset: where the emailed recovery link lands ─────────────────────────
   A standalone page, not the game, so it borrows the join card's look rather
   than its markup — same face, same frame, same buttons, on its own body. */
.reset-page {
    display: flex;
    align-items: center;
    justify-content: center;
    min-height: 100vh;
    margin: 0;
    padding: 24px;
    background: var(--surface-3);
    font-family: Arial, sans-serif;
}

.reset-card {
    display: flex;
    flex-direction: column;
    /* The base `body > main { flex: 1 }` would stretch this to the full height
       of the viewport; it is a card, not a page. */
    flex: 0 0 auto;
    width: min(92vw, 440px);
    padding: 32px 32px 24px;
    border: 5px solid #000;
    border-radius: 16px;
    background: var(--surface-2);
    box-shadow: 0 12px 0 rgba(0, 0, 0, 0.35);
    text-align: center;
}

.reset-logo {
    margin: 0;
    color: var(--ink-link);
    font-size: 34px;
    font-style: italic;
    font-weight: bold;
    text-shadow: 2px 2px 0 rgba(176, 176, 255, 0.9);
}

.reset-title {
    margin: 6px 0 10px;
    color: var(--ink-strong);
    font-size: 20px;
    font-weight: bold;
    letter-spacing: 0.5px;
}

.reset-note {
    margin: 0 0 16px;
    color: var(--ink-muted);
    font-size: 14px;
    line-height: 1.45;
}

.reset-label {
    margin-bottom: 4px;
    color: var(--ink);
    font-size: 13px;
    font-weight: bold;
    text-align: left;
}

.reset-input {
    box-sizing: border-box;
    width: 100%;
    margin-bottom: 12px;
    padding: 10px 12px;
    border: 3px solid #000;
    border-radius: 8px;
    background: var(--surface);
    color: var(--ink);
    font-family: Arial, sans-serif;
    font-size: 16px;
}

.reset-error {
    margin: 0 0 10px;
    color: #b00020;
    font-size: 13px;
    font-weight: bold;
}

.reset-btn {
    display: block;
    margin-top: 8px;
    padding: 12px 16px;
    border: 3px solid #000;
    border-radius: 10px;
    background: #35c04a;
    box-shadow: 0 4px 0 rgba(0, 0, 0, 0.3);
    color: #fff;
    font-family: Arial, sans-serif;
    font-size: 17px;
    font-weight: bold;
    text-align: center;
    text-decoration: none;
    cursor: pointer;
}

.reset-btn:disabled { opacity: 0.6; cursor: default; }

.reset-btn--ghost {
    background: var(--surface);
    color: var(--ink);
}

/* ── Recovery email dialog (profile menu) ── */
.recovery-card { width: min(92vw, 460px); }

.recovery-body { padding: 18px 20px 20px; }

.recovery-note {
    margin: 0 0 12px;
    color: var(--ink-muted);
    font-size: 14px;
    line-height: 1.45;
}

.recovery-note--fine { margin: 12px 0 0; font-size: 12px; }

.recovery-input {
    box-sizing: border-box;
    width: 100%;
    padding: 10px 12px;
    border: 3px solid #000;
    border-radius: 8px;
    background: var(--surface);
    color: var(--ink);
    font-family: Arial, sans-serif;
    font-size: 16px;
}

.recovery-msg {
    margin: 10px 0 0;
    color: #1c7c31;
    font-size: 13px;
    font-weight: bold;
}

.recovery-msg.is-bad { color: #b00020; }

/* Admin-only Refresh in the shop topbar (public/script.js adminRefreshHtml).
   Deliberately quieter than the wallet chip beside it: it is a workshop tool
   that happens to live on a player-facing screen, not part of the storefront. */
.shopx-refresh {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    padding: 5px 12px;
    border: 1px solid rgba(255, 255, 255, 0.28);
    border-radius: 999px;
    background: rgba(6, 26, 68, 0.55);
    color: #cfe0ff;
    font: inherit;
    font-size: 13px;
    cursor: pointer;
    transition: background 0.15s, border-color 0.15s, color 0.15s;
}

.shopx-refresh:hover:not(:disabled) {
    background: rgba(12, 44, 110, 0.8);
    border-color: rgba(255, 255, 255, 0.5);
    color: #fff;
}

.shopx-refresh:disabled {
    opacity: 0.6;
    cursor: default;
}

/* The reroll badge doubles as the reset button. Deliberately loud: while it is
   on screen every player's shop is on an admin-dealt hand rather than its
   ordinary daily draw, and that is worth a colour nobody mistakes for chrome. */
.shopx-refresh--preview {
    background: rgba(150, 90, 0, 0.75);
    border-color: rgba(255, 190, 90, 0.75);
    color: #ffe6bd;
}

.shopx-refresh--preview:hover:not(:disabled) {
    background: rgba(190, 115, 0, 0.9);
    border-color: #ffd79a;
    color: #fff;
}

/* ── Settings: an explanatory line above a group of rows ──────────────────
   The menu-card rows need one sentence of orientation before six identical
   URL boxes, which a per-row description cannot carry without repeating
   itself six times. */
.settings-note {
    margin: 0 0 14px;
    padding: 10px 14px;
    border: 2px solid #000;
    border-radius: 6px;
    background: var(--surface);
    font-family: Arial, sans-serif;
    font-size: 13px;
    line-height: 1.55;
    color: var(--ink);
}

/* ── Settings: a named group inside one tab ───────────────────────────────
   Deliberately subordinate to .settings-section-title above it: same family
   and weight so it reads as the same system, two thirds the size and with a
   rule above it so it reads as a division of that section rather than as a
   second section. */
.settings-subhead {
    margin: 26px 0 10px;
    padding: 14px 4px 0;
    border-top: 2px solid rgba(0, 0, 0, 0.35);
    font-family: Arial, sans-serif;
    font-size: 17px;
    font-weight: 900;
    letter-spacing: 1.5px;
    text-transform: uppercase;
    color: #fff;
    text-shadow: 2px 2px 0 rgba(0, 0, 0, 0.55);
}

/* ── Room playlist dock ───────────────────────────────────────────────────────
   Fixed to the bottom-left and parented to <body>, never to a screen. Two
   reasons, and both are load-bearing:

   · An <iframe> reloads when it is reparented, and the lobby re-renders on
     every settings message. Living outside those trees is what lets the music
     survive a settings change and the whole lobby → race transition.
   · YouTube's embed terms want the player visible at 200×200 or larger and not
     covered by anything, for as long as it is playing. A fixed corner with its
     own stacking context is the only place that can be promised.

   Bottom-LEFT specifically: during a race .mp-side is already fixed to the
   bottom-right, and these two must not overlap.                              */
.room-music-dock {
    position: fixed;
    left: 12px;
    bottom: 12px;
    width: 224px;
    z-index: 320;
    border: 3px solid #000;
    border-radius: 10px;
    background: color-mix(in srgb, var(--surface) 94%, transparent);
    box-shadow: 0 5px 0 rgba(0, 0, 0, 0.22);
    padding: 8px 10px 10px;
}

.room-music-dock[hidden] { display: none; }

.room-music-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 8px;
    margin-bottom: 8px;
}

.room-music-title {
    font-family: Arial, sans-serif;
    font-size: 12px;
    font-weight: 900;
    letter-spacing: 2px;
    text-transform: uppercase;
    color: var(--ink-muted);
}

.room-music-hide {
    appearance: none;
    border: 2px solid #000;
    border-radius: 6px;
    background: var(--surface-3);
    font-size: 14px;
    font-weight: 900;
    line-height: 1;
    padding: 2px 7px 4px;
    color: var(--ink);
    cursor: pointer;
}

.room-music-what {
    font-size: 13px;
    font-weight: 700;
    color: var(--ink);
    margin: 0 0 4px;
}

/* The sentence naming the third party. Deliberately not styled down to a
   whisper — it is the disclosure the privacy page promises, so it reads at the
   same size as everything else in the card. */
.room-music-warn {
    font-size: 12px;
    line-height: 1.4;
    color: var(--ink-muted);
    margin: 0 0 8px;
}

.room-music-go,
.room-music-no {
    appearance: none;
    width: 100%;
    padding: 8px;
    border: 2px solid #000;
    border-radius: 8px;
    font-family: Arial, sans-serif;
    font-size: 13px;
    font-weight: 900;
    cursor: pointer;
    box-shadow: 0 3px 0 rgba(0, 0, 0, 0.25);
}

.room-music-go {
    background: linear-gradient(180deg, #ffd66b, #f7b733);
    color: #5b3a00;
}

.room-music-no {
    margin-top: 6px;
    background: var(--surface-3);
    color: var(--ink-muted);
    box-shadow: none;
}

/* 200×200 is a floor set by YouTube's terms, not a look. Do not shrink this to
   fit a layout; move the dock instead. */
.room-music-frame {
    display: block;
    width: 200px;
    height: 200px;
    border: 0;
    border-radius: 6px;
    background: #000;
}

/* Transport. These exist because YouTube's own controls are inside the iframe
   and go inert with it: when an item is blocked the embed shows "Video
   unavailable" and ignores every click, so without a row of our own there is
   no way out of a dead track. Kept outside .room-music-body, which the builder
   empties when it swaps the iframe in. */
.room-music-transport {
    display: flex;
    gap: 6px;
    margin-top: 8px;
}

.room-music-btn {
    appearance: none;
    flex: 1;
    padding: 6px 0 7px;
    border: 2px solid #000;
    border-radius: 7px;
    background: var(--surface-3);
    color: var(--ink);
    font-size: 12px;
    font-weight: 900;
    line-height: 1;
    cursor: pointer;
    box-shadow: 0 2px 0 rgba(0, 0, 0, 0.2);
}

.room-music-btn:active {
    transform: translateY(2px);
    box-shadow: none;
}

/* The box and its button on one line. Adding music is an explicit press, so
   the button has to sit where the eye already is after typing. */
.mp-playlist-actions {
    display: flex;
    flex-wrap: wrap;
    gap: 6px;
    align-items: stretch;
}

.mp-playlist-actions .mp-set-text {
    flex: 1 1 180px;
    min-width: 0;
}

.mp-playlist-go,
.mp-playlist-off {
    appearance: none;
    flex: 0 0 auto;
    padding: 0 12px;
    border: 2px solid #000;
    border-radius: 7px;
    font-family: Arial, sans-serif;
    font-size: 12px;
    font-weight: 900;
    cursor: pointer;
    box-shadow: 0 2px 0 rgba(0, 0, 0, 0.22);
}

.mp-playlist-go {
    background: linear-gradient(180deg, #ffd66b, #f7b733);
    color: #5b3a00;
}

.mp-playlist-off {
    background: var(--surface-3);
    color: var(--ink-muted);
}

.mp-playlist-go:disabled,
.mp-playlist-off:disabled {
    opacity: 0.5;
    cursor: default;
    box-shadow: none;
}

.mp-playlist-go:not(:disabled):active,
.mp-playlist-off:not(:disabled):active {
    transform: translateY(2px);
    box-shadow: none;
}

.mp-playlist-msg { min-height: 1.2em; }
.mp-playlist-msg.is-bad { color: var(--bad, #c0392b); font-weight: 700; }

@media (max-width: 900px) {
    /* The item slot becomes a fixed bar across the bottom on small screens, so
       the dock steps above it rather than under it. Still 200×200, still
       uncovered — shrinking it here would break the embed terms. */
    .room-music-dock {
        bottom: 78px;
    }
}
