.error-page {
    max-width: 32rem;
    margin: 4rem auto 0;
    text-align: center;
}

.error-page h1 {
    font-size: 1.4rem;
}

:root {
    color-scheme: dark;
    --border: #33384a;
    --muted: #8a90a3;
    --accent: #6ea8fe;
    --win: #4caf7d;
    --loss: #e0616b;
    --heading-font: system-ui, -apple-system, "Segoe UI", sans-serif;
    --bg: #0c0e15;
    --bg-glow-1: rgba(110, 168, 254, 0.08);
    --bg-glow-2: rgba(122, 63, 160, 0.08);
    --text: #e6e8ef;
    --text-strong: #fff;
    --surface: #161925;
    --surface-hover: #181b26;
}

/*
 * Standard selectable themes (account preference, cookie-backed, see ThemeCookie) - own palettes,
 * not a copy of any other site's specific colors. The default (no data-theme attribute) is this
 * project's original look and never changes. Semantic/brand colors (race icons, tier badges, role
 * pills, the subscribe/support nav buttons) intentionally stay fixed across every theme - only
 * general page chrome (background, text, panels, borders) follows the theme.
 */
html[data-theme="midnight"] {
    --border: #232a4a;
    --muted: #7a86b3;
    --accent: #4fd1ff;
    --bg: #05060f;
    --bg-glow-1: rgba(79, 209, 255, 0.10);
    --bg-glow-2: rgba(122, 63, 220, 0.10);
    --text: #e4e8ff;
    --text-strong: #fff;
    --surface: #10132a;
    --surface-hover: #141833;
}

/* Muted, deliberately desaturated dark theme - grayscale panels rather than the blue/purple tint
 * "default" and "midnight" share, with a warm amber accent instead of another blue so the three
 * dark themes stay visually distinct from each other. */
html[data-theme="graphite"] {
    --border: #4a4f5a;
    --muted: #9a9fa8;
    --accent: #d4a054;
    --win: #6ea87d;
    --loss: #c96b71;
    --bg: #1a1a1a;
    --bg-glow-1: rgba(180, 150, 100, 0.05);
    --bg-glow-2: rgba(120, 120, 130, 0.05);
    --text: #d8dadd;
    --text-strong: #f5f5f5;
    --surface: #3a3f4d;
    --surface-hover: #3a3a3a;
}

html[data-theme="light"] {
    color-scheme: light;
    --border: #d8dce6;
    --muted: #6b7280;
    --accent: #3d6fd1;
    --win: #2f8f5e;
    --loss: #c9414d;
    --bg: #f4f5fa;
    --bg-glow-1: rgba(61, 111, 209, 0.06);
    --bg-glow-2: rgba(122, 63, 160, 0.05);
    --text: #1a1d29;
    --text-strong: #05060a;
    --surface: #ffffff;
    --surface-hover: #eef0f6;
}

/* Form controls don't inherit the page font by default (the browser's own UI font, e.g. Arial on
   Windows Chrome, wins instead) - only <a>-styled buttons were picking up the site's real font,
   which made <button>/<summary> versions of the same .favorite-btn render a couple pixels
   shorter. font-family alone wasn't enough either - line-height/font-size still fell back to the
   UA's <button> defaults, so a <button class="favorite-btn"> and an <a class="favorite-btn">
   next to it (e.g. the profile page's favorite + Steam profile buttons) ended up different
   heights. The "font" shorthand pulls in every sub-property, not just the family. */
button, input, select, textarea {
    font: inherit;
}

/* One width for every page (used to vary 1000/1200/1500px depending on page, which read as
 * inconsistent) - 1500px is the widest a page ever actually needed (race-stats' wide table),
 * and narrower pages just end up with more breathing room, never overflow. */
body {
    font-family: system-ui, -apple-system, "Segoe UI", sans-serif;
    max-width: 1500px;
    margin: 0 auto;
    padding: 0 1.5rem 3rem;
    background:
        radial-gradient(ellipse 900px 500px at 15% -10%, var(--bg-glow-1), transparent),
        radial-gradient(ellipse 700px 400px at 100% 20%, var(--bg-glow-2), transparent),
        var(--bg);
    background-attachment: fixed;
    color: var(--text);
}

.ladder-layout {
    display: grid;
    grid-template-columns: 1fr;
    gap: 1.5rem;
    align-items: start;
}

.ladder-main {
    min-width: 0;
}

@media (min-width: 1400px) {
    .ladder-layout {
        grid-template-columns: 1fr 280px;
    }
}

.chat-sidebar {
    border: 1px solid var(--border);
    border-radius: 8px;
    background: var(--surface);
    padding: 1rem;
    display: flex;
    flex-direction: column;
    gap: 0.6rem;
    position: sticky;
    top: 1rem;
    max-height: calc(100vh - 2rem);
}

.chat-sidebar h2 {
    margin: 0;
}

.sidebar-stack {
    display: flex;
    flex-direction: column;
    gap: 1rem;
}

.featured-poll {
    border: 1px solid var(--border);
    border-radius: 8px;
    background: var(--surface);
    padding: 1rem;
}

.featured-poll h2 {
    margin: 0 0 0.5rem;
}

.chat-messages {
    flex: 1;
    overflow-y: auto;
    display: flex;
    flex-direction: column;
    gap: 0.4rem;
    min-height: 200px;
    max-height: 50vh;
}

.chat-message {
    font-size: 0.85rem;
    line-height: 1.4;
    word-break: break-word;
}

.chat-time {
    color: var(--muted);
    font-size: 0.75rem;
    margin-inline-end: 0.35rem;
}

.chat-author {
    font-weight: 600;
    margin-inline-end: 0.35rem;
}

.chat-form {
    margin-bottom: 0;
}

.chat-form input[type="text"] {
    max-width: none;
}

.chat-error {
    color: var(--loss);
    font-size: 0.8rem;
    margin: 0;
}

h1, h2 {
    font-family: var(--heading-font);
    letter-spacing: 0.02em;
    text-transform: uppercase;
}

.site-nav {
    display: flex;
    flex-direction: column;
    gap: 0.7rem;
    padding: 1rem 0;
    border-bottom: 1px solid var(--border);
    margin-bottom: 1rem;
}

.deploy-notice-banner {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 0.4rem;
    padding: 0.5rem 1rem;
    border-radius: 6px;
    background: var(--loss);
    color: #fff;
    font-size: 0.85rem;
    font-weight: 600;
    text-align: center;
}

.nav-row {
    display: flex;
    align-items: center;
    gap: 1rem;
    row-gap: 0.5rem;
    flex-wrap: wrap;
}

.nav-row-top {
    justify-content: space-between;
}

.nav-brand-group {
    display: flex;
    align-items: center;
    gap: 0.9rem;
    flex-wrap: wrap;
}

.nav-account {
    gap: 0.9rem;
}

/* Deliberately NOT positioned. Making this row `position: relative` (it briefly was, as an anchor
   for the narrow-screen dropdown below) puts it in the positioned-elements paint layer, where it
   sits *after* .nav-account in document order and therefore paints over the language menu hanging
   down from it - which silently made the EN entry, the one whose rectangle overlaps this row,
   impossible to click on every page of the site. */
.nav-row-links {
    gap: 1.1rem;
}

/* Dropdown for the "Useful links" nav item (fragments/nav.html). Opens on hover and on keyboard
   focus with no script involved; .open is added by the nav script for touch, which has neither.
   The trigger is styled as one more nav link rather than as a bordered control. */
.nav-dropdown {
    position: relative;
    /* Its own stacking context, for the same reason .lang-dropdown has one: the open menu has to
       paint above the sticky chat sidebar instead of competing with it in the root context. */
    isolation: isolate;
}

.nav-dropdown-toggle {
    padding: 0;
    border: 0;
    background: none;
    color: var(--muted);
    font: inherit;
    cursor: pointer;
    white-space: nowrap;
}

.nav-dropdown-toggle::after {
    content: " \25BE";
}

.nav-dropdown:hover .nav-dropdown-toggle,
.nav-dropdown:focus-within .nav-dropdown-toggle,
.nav-dropdown.open .nav-dropdown-toggle {
    color: var(--accent);
}

.nav-dropdown-menu {
    display: none;
    position: absolute;
    top: calc(100% + 0.4rem);
    left: 0;
    flex-direction: column;
    gap: 0.15rem;
    padding: 0.35rem;
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: 4px;
    box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
    z-index: 999;
    min-width: 12rem;
}

/* The gap above is visual only - this bridges it, so travelling from the trigger down to the menu
   never passes over anything outside .nav-dropdown. Without it the pointer leaves the dropdown
   mid-way and the menu vanishes exactly as the reader reaches for it. */
.nav-dropdown-menu::before {
    content: "";
    position: absolute;
    left: 0;
    right: 0;
    top: -0.6rem;
    height: 0.6rem;
}

.nav-dropdown:hover .nav-dropdown-menu,
.nav-dropdown:focus-within .nav-dropdown-menu,
.nav-dropdown.open .nav-dropdown-menu {
    display: flex;
}

.nav-dropdown-menu a {
    display: block;
    padding: 0.3rem 0.5rem;
    border-radius: 4px;
    color: var(--muted);
    text-decoration: none;
    white-space: nowrap;
}

.nav-dropdown-menu a:hover {
    background: var(--surface-hover);
    color: var(--accent);
}

/* On a narrow screen the nav row is only as wide as the viewport, so a menu floating out from a
   trigger near either edge would hang off it. Dropping out of absolute positioning entirely makes
   it an ordinary block that opens the nav downwards - no overflow to guard against, and no
   positioned ancestor to introduce, which is what broke the language switcher the first time. */
@media (max-width: 700px) {
    .nav-dropdown-menu {
        position: static;
        min-width: 0;
        box-shadow: none;
    }

    .nav-dropdown-menu::before {
        content: none;
    }
}

.site-nav .brand {
    font-family: var(--heading-font);
    font-weight: 700;
    font-size: 1.2rem;
    letter-spacing: 0.03em;
    text-transform: uppercase;
    color: var(--text-strong);
}

.site-nav a {
    color: var(--muted);
    text-decoration: none;
}

.site-nav a:hover {
    color: var(--accent);
}

.nav-external {
    gap: 0.5rem;
}

.nav-external a {
    display: inline-flex;
    align-items: center;
    gap: 0.25rem;
    padding: 0.25rem 0.7rem;
    border-radius: 999px;
    font-size: 0.8rem;
    text-decoration: none;
    white-space: nowrap;
}

.nav-discord {
    border: 1px solid #5865f2;
    color: #5865f2;
}

.nav-discord:hover {
    background: rgba(88, 101, 242, 0.12);
    color: #7f8dfa;
}

.nav-support {
    border: 1px solid #ff6b9d;
    color: #ff6b9d;
}

.nav-support:hover {
    background: rgba(255, 107, 157, 0.12);
    color: #ff8fb3;
}

.nav-future {
    border: 1px solid var(--accent);
    color: var(--accent);
}

.nav-future:hover {
    background: rgba(110, 168, 254, 0.12);
    color: #8fbcff;
}

.nav-contact {
    border: 1px solid var(--win);
    color: var(--win);
}

.nav-contact:hover {
    background: rgba(76, 175, 125, 0.12);
    color: #6fd6a2;
}

.lang-select {
    padding: 0.2rem 0.4rem;
    border: 1px solid var(--border);
    border-radius: 4px;
    background: var(--surface);
    color: var(--muted);
    font-size: 0.75rem;
    cursor: pointer;
}

.lang-dropdown {
    position: relative;
    font-size: 0.75rem;
    /* Own stacking context so the open menu (z-index below) reliably paints above the sticky
       chat sidebar next to it, instead of the two competing inside the shared root context -
       that's what let the sidebar's own text show through the open menu before this. */
    isolation: isolate;
    /* Explicit level, not document order. The menu hangs down over the rows below it, so any
       later sibling that merely becomes positioned would otherwise paint on top of it and eat the
       clicks - which is exactly what happened to the EN entry once .nav-row-links gained a
       `position`. This switcher is the one thing on the page that must always stay reachable:
       someone who cannot read the current language cannot navigate their way out of it. */
    z-index: 1000;
}

.lang-dropdown summary {
    padding: 0.2rem 0.4rem;
    border: 1px solid var(--border);
    border-radius: 4px;
    background: var(--surface);
    color: var(--muted);
    cursor: pointer;
    list-style: none;
    font: inherit;
}

.lang-dropdown summary::-webkit-details-marker {
    display: none;
}

.lang-dropdown summary::marker {
    content: "";
}

.lang-dropdown summary::after {
    content: " \25BE";
}

.lang-dropdown-menu {
    position: absolute;
    top: calc(100% + 0.3rem);
    right: 0;
    display: flex;
    flex-direction: column;
    gap: 0.15rem;
    padding: 0.35rem;
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: 4px;
    box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
    z-index: 999;
    min-width: 3rem;
    font: inherit;
}

.lang-dropdown-menu a {
    padding: 0.2rem 0.4rem;
    border-radius: 4px;
    color: var(--muted);
    text-decoration: none;
    white-space: nowrap;
    font: inherit;
}

.lang-dropdown-menu a:hover {
    background: var(--border);
}

.lang-dropdown-menu a.active {
    color: var(--text);
}

.theme-select {
    padding: 0.2rem 0.4rem;
    border: 1px solid var(--border);
    border-radius: 4px;
    background: var(--surface);
    color: var(--muted);
    font-size: 0.75rem;
    cursor: pointer;
}

.rating-chart-controls {
    margin: 0.5rem 0 0.75rem;
}

.ratinghistory-locked {
    display: inline-block;
    margin: 0.5rem 0 1rem;
    padding: 0.5rem 0.9rem;
    border-radius: 4px;
    text-decoration: none;
}

.rating-chart-controls select {
    padding: 0.35rem 0.6rem;
    border: 1px solid var(--border);
    border-radius: 4px;
    background: var(--surface);
    color: var(--text);
    font-size: 0.85rem;
    cursor: pointer;
}

.online-badge {
    display: inline-flex;
    align-items: center;
    gap: 0.4rem;
    padding: 0.25rem 0.6rem;
    border: 1px solid var(--border);
    border-radius: 999px;
    color: var(--muted);
    font-size: 0.8rem;
    white-space: nowrap;
}

.online-dot {
    width: 0.5rem;
    height: 0.5rem;
    border-radius: 50%;
    background: var(--win);
    box-shadow: 0 0 4px var(--win);
}

.nav-user {
    color: var(--text);
    font-size: 0.9rem;
    text-decoration: none;
}

.nav-user:hover {
    color: var(--accent);
}

.nav-logout-form {
    display: inline;
}

.nav-logout-btn {
    background: none;
    border: none;
    padding: 0;
    color: var(--muted);
    text-decoration: none;
    font: inherit;
    cursor: pointer;
}

.nav-logout-btn:hover {
    color: var(--accent);
}

table {
    width: 100%;
    border-collapse: collapse;
    font-size: 0.95rem;
}

th, td {
    padding: 0.55rem 0.75rem;
    text-align: start;
    vertical-align: middle;
    border-bottom: 1px solid var(--border);
    line-height: 1.5rem;
}

th {
    color: var(--muted);
    font-weight: 600;
    text-transform: uppercase;
    font-size: 0.75rem;
    letter-spacing: 0.04em;
}

tbody tr:nth-child(even) td {
    background: rgba(255, 255, 255, 0.015);
}

tr:hover td {
    background: var(--surface-hover);
}

table.sortable th.sortable-col {
    cursor: pointer;
    user-select: none;
}

table.sortable th.sortable-col:hover {
    color: var(--text-strong);
}

table.sortable th.sorted-asc::after {
    content: ' \25B2';
}

table.sortable th.sorted-desc::after {
    content: ' \25BC';
}

.scroll-table {
    max-height: 600px;
    overflow-y: auto;
    border: 1px solid var(--border);
    border-radius: 6px;
}

.replay-report {
    max-height: 400px;
    max-width: 70vw;
    overflow: auto;
    margin-top: 0.5rem;
    padding: 0.75rem;
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: 6px;
    font-size: 0.8rem;
    line-height: 1.4;
    white-space: pre;
}

.scroll-table table {
    border: none;
}

.scroll-table thead th {
    position: sticky;
    top: 0;
    background: var(--bg);
    z-index: 1;
}

.table-card {
    border: 1px solid var(--border);
    border-radius: 8px;
    overflow-x: auto;
}

.clan-battle-columns {
    display: grid;
    /* minmax(0, 1fr), not plain 1fr - a grid track's default min-width is auto (fit its content),
     * so with wide tables inside, plain 1fr columns refuse to shrink below their content and blow
     * out past max-width instead of respecting it, scrolling the whole page sideways rather than
     * centering. minmax(0, 1fr) lets the tracks actually shrink to max-width, and each side's own
     * .table-card (overflow-x:auto) takes over from there with its own per-table scrollbar. */
    grid-template-columns: minmax(0, 1fr) minmax(0, 1fr) minmax(0, 1fr);
    gap: 1.5rem;
    align-items: start;
    margin-top: 1rem;
}

/* Three tables need more room than the original two did before cramping - an extra step down to
   two columns before the mobile one-column stack, rather than jumping straight from three to one. */
@media (max-width: 1100px) {
    .clan-battle-columns {
        grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
    }
}

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

.clan-battle-side-header {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    font-weight: 600;
    margin-bottom: 0.5rem;
}

.clan-battle-count {
    color: var(--muted);
    font-weight: 400;
}

/* .col-player's 280px min-width is sized for the full ladder row (clan icon, role/tier/achievement
 * badges, ...) - this table only ever shows an avatar, flag, name, and badge emoji, so the wide
 * reservation just wastes space needed to fit two full tables side by side without either one
 * needing its own horizontal scroll. white-space:nowrap (inherited from .col-player) still stops
 * the cell itself from wrapping, but the name link gets its own fixed width + ellipsis so one
 * unusually long alias (clan tag included, e.g. "fanat [ELM]MERCENARY-god") can't force the whole
 * column - and so the whole table - wider than the two side by side actually fit. */
.clan-battle-side .col-player {
    min-width: 130px;
}

.clan-battle-side .col-player .player-link {
    display: inline-block;
    max-width: 130px;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    vertical-align: middle;
    /* Same reasoning as .race-col: player aliases are Latin-script, and ellipsis truncation
       follows this element's own direction, not the page's - pin LTR so a long alias truncates
       at its own end ("...MERCENARY-g...") instead of the front under dir="rtl". */
    direction: ltr;
}

/* Same reasoning as .col-player above, applied to every column's padding - shaving a few px off
 * each one adds up to the difference between fitting three tables without a per-table scrollbar
 * and not (still comfortably above the 44px touch-target guideline at this row height). */
.clan-battle-side th,
.clan-battle-side td {
    padding-inline: 6px;
}

/* This table only ever needs the race icon (a tooltip on the cell itself carries the full name -
 * see clan-battle-side.html) - dropping the "Space Marines"/"Chaos Marines"-length name text
 * .race-col reserves room for on the main ladder shrinks this column from ~110px to icon-width,
 * the single biggest win toward fitting three tables side by side without scrolling. */
.clan-battle-side .race-col {
    max-width: none;
    width: 1px;
    white-space: nowrap;
}

.table-card table {
    margin: 0;
}

.table-card tbody tr:last-child td {
    border-bottom: none;
}

/* Player name (with its badge row - clan icon, role/tier/achievement badges), best mode, and
 * last-match (timestamp + win/loss + rating delta) are the cells most likely to wrap onto a second
 * line at the page's default 1000px width. min-width alone didn't stop that - table cells default
 * to wrapping their inline content, and a clan-tagged alias like "BAD|Bhaltair_Roshaun" plus its
 * badge row is easily wider than the min-width once clans shipped, wrapping the row and breaking
 * its height alignment with the rest of the table. white-space:nowrap forces the browser to
 * actually reserve the cell's full natural width instead, so .table-card's overflow-x:auto grows
 * the table and scrolls horizontally rather than wrapping. */
.col-player {
    min-width: 280px;
    white-space: nowrap;
}

.col-mode {
    min-width: 190px;
    white-space: nowrap;
}

.col-lastmatch {
    min-width: 230px;
}

.col-rankchange {
    min-width: 2.2rem;
}

.filters {
    display: flex;
    gap: 0.75rem;
    margin-bottom: 1.25rem;
    flex-wrap: wrap;
}

/* nowrap on purpose - a row of equal-weight single choices (race pills, sort options, mode tabs)
   reads as a ragged multi-row block once it wraps. overflow-x:auto instead lets it scroll as one
   tidy row whenever it doesn't fit, the standard app-store-style filter-chip pattern, rather than
   wrapping unevenly or silently clipping. */
.filters-tight {
    flex-wrap: nowrap;
    gap: 0.4rem;
    overflow-x: auto;
    padding-bottom: 0.2rem;
}

.filters a, .filters select {
    padding: 0.3rem 0.6rem;
    border: 1px solid var(--border);
    border-radius: 6px;
    color: var(--muted);
    text-decoration: none;
    background: var(--surface);
    white-space: nowrap;
    flex: none;
}

.filters a.active {
    color: var(--text-strong);
    border-color: var(--accent);
}

/* Selected "All races" / general-rating tab on the leaderboard - a slow shimmering gradient
   sweep instead of one flat color, since no single race color represents "every race combined". */
.tab-allraces.active {
    color: #fff;
    border-color: transparent;
    background: linear-gradient(120deg, #5B8DEF, #B9A6F5, #F2994A, #4CAF7D, #5B8DEF);
    background-size: 300% 300%;
    animation: allraces-shimmer 6s ease infinite;
}

@keyframes allraces-shimmer {
    0% { background-position: 0% 50%; }
    50% { background-position: 100% 50%; }
    100% { background-position: 0% 50%; }
}

/* Selected race tab on the leaderboard - solid fill in that race's signature color, so switching
   between "all races" and a specific race's rating is unmistakable at a glance. */
.race-tab-space_marine_race.active { background: #C0392B; border-color: #C0392B; color: #fff; }
.race-tab-eldar_race.active { background: #2F6FED; border-color: #2F6FED; color: #fff; }
.race-tab-ork_race.active { background: #3F9142; border-color: #3F9142; color: #fff; }
.race-tab-dark_eldar_race.active { background: #7A3FA0; border-color: #7A3FA0; color: #fff; }
.race-tab-tau_race.active { background: #E07A29; border-color: #E07A29; color: #fff; }
.race-tab-chaos_marine_race.active { background: #7A1F3D; border-color: #7A1F3D; color: #fff; }
.race-tab-guard_race.active { background: #9C7A0A; border-color: #9C7A0A; color: #fff; }
.race-tab-sisters_race.active { background: #F4D03F; border-color: #F4D03F; color: #1a1a1a; }
.race-tab-necron_race.active { background: #0c0c0f; border-color: #4a4a52; color: #fff; }

.replay-filter-panel {
    border: 1px solid var(--border);
    border-radius: 8px;
    background: var(--surface);
    padding: 1rem 1.2rem;
    margin-bottom: 1.5rem;
}

.filters button:disabled {
    opacity: 0.6;
    cursor: not-allowed;
}

.upload-progress {
    display: flex;
    align-items: center;
    gap: 0.6rem;
    margin: -0.5rem 0 1.25rem;
}

.upload-progress-track {
    position: relative;
    flex: 1;
    min-width: 140px;
    max-width: 320px;
    height: 6px;
    border-radius: 3px;
    background: var(--border);
    overflow: hidden;
}

.upload-progress-fill {
    height: 100%;
    width: 0;
    background: var(--accent);
    border-radius: 3px;
    transition: width 0.2s ease;
}

.upload-progress-label {
    color: var(--muted);
    font-size: 0.9rem;
    white-space: nowrap;
}

.replay-toolbar {
    display: flex;
    flex-direction: column;
    gap: 0.75rem;
    margin-bottom: 1rem;
}

.replay-stat-bar {
    border: 1px solid var(--accent);
    border-radius: 6px;
    padding: 0.6rem 1rem;
    color: var(--text-strong);
    background: color-mix(in srgb, var(--accent) 10%, transparent);
    margin: 0 0 1rem;
    font-weight: 600;
}

.replay-filter-field {
    display: flex;
    align-items: center;
    gap: 0.4rem;
    color: var(--muted);
}

.replay-filter-field input, .replay-filter-field select {
    padding: 0.3rem 0.6rem;
    border: 1px solid var(--border);
    border-radius: 6px;
    background: var(--surface);
    color: var(--text);
}

.filter-pill {
    display: inline-flex;
    align-items: center;
    gap: 0.35rem;
    padding: 0.3rem 0.6rem;
    border: 1px solid var(--border);
    border-radius: 6px;
    color: var(--muted);
    background: var(--surface);
    cursor: pointer;
    white-space: nowrap;
    flex: none;
}

.filter-pill input {
    display: none;
}

.filter-pill:has(input:checked) {
    color: var(--text-strong);
    border-color: var(--accent);
}

.replay-elo-filter {
    display: flex;
    align-items: center;
    gap: 0.6rem;
    color: var(--muted);
}

.replay-elo-filter input[type="number"] {
    width: 4.5rem;
    padding: 0.3rem 0.5rem;
    border: 1px solid var(--border);
    border-radius: 6px;
    background: var(--surface);
    color: var(--text);
}

.elo-slider {
    /* Pinned LTR: replay-filters.js positions the fill/thumbs with raw left/right math
       (low value = left), and browsers don't consistently mirror native range-input
       rendering under dir="rtl" - keeping this subtree LTR avoids the fill and the
       native thumbs disagreeing on which side is "min" for Arabic. */
    direction: ltr;
    position: relative;
    /* Deliberately narrow, not stretch-to-fit: the two number inputs beside it take a typed
       range just as well, so the slider is the coarse control and does not need the width. */
    flex: 0 1 190px;
    min-width: 110px;
    max-width: 190px;
    height: 1.4rem;
    /* Insets the whole 0%/100% range by the thumb's own radius, so a handle sitting at either
       extreme (very common here - most searches leave ELO at its full min/max) still renders as
       a full circle instead of getting its outer half clipped flush against the container edge.
       Percentage left/width on the absolutely-positioned track/fill/inputs below all resolve
       against this padding box, so they automatically line up with the inset range. */
    padding: 0 7px;
}

.elo-slider-track {
    position: absolute;
    top: 50%;
    left: 0;
    right: 0;
    height: 3px;
    background: var(--border);
    border-radius: 2px;
    transform: translateY(-50%);
}

.elo-slider-fill {
    position: absolute;
    top: 50%;
    height: 3px;
    background: var(--accent);
    border-radius: 2px;
    transform: translateY(-50%);
}

.elo-slider input[type="range"] {
    position: absolute;
    top: 50%;
    left: 0;
    width: 100%;
    margin: 0;
    background: none;
    pointer-events: none;
    -webkit-appearance: none;
    appearance: none;
    transform: translateY(-50%);
}

.elo-slider input[type="range"]::-webkit-slider-runnable-track {
    height: 3px;
    background: transparent;
}

.elo-slider input[type="range"]::-moz-range-track {
    height: 3px;
    background: transparent;
}

.elo-slider input[type="range"]::-webkit-slider-thumb {
    pointer-events: auto;
    -webkit-appearance: none;
    appearance: none;
    width: 14px;
    height: 14px;
    border-radius: 50%;
    background: var(--accent);
    cursor: pointer;
    margin-top: -5.5px;
}

.elo-slider input[type="range"]::-moz-range-thumb {
    pointer-events: auto;
    width: 14px;
    height: 14px;
    border-radius: 50%;
    background: var(--accent);
    cursor: pointer;
    border: none;
}

.num {
    text-align: right;
    /* Pinned LTR: under dir="rtl" a signed number like "-4" bidi-reorders to display as "4-"
       (the digit run resolves to behave like the surrounding RTL text with no strong LTR
       anchor). Forcing this box's own bidi paragraph direction to LTR keeps sign-before-digit
       regardless of page direction. */
    direction: ltr;
    font-variant-numeric: tabular-nums;
    min-width: 3.2rem;
}

.win-rate {
    color: var(--win);
}

.loss-text {
    color: var(--loss);
}

/* Filled-square version of the same >50/<48 win-rate split, for a dense grid (the race matchup
 * table) where a whole tinted cell reads at a glance faster than colored text alone.
 *
 * Scoped under .matchup-grid (rather than the bare class) so its specificity beats the generic
 * `tbody tr:nth-child(even) td` zebra-striping rule above - at equal specificity the zebra rule's
 * extra element selectors (tbody, tr, td) win the tiebreak, which silently overrode this fill on
 * every other row (odd rows tinted correctly, even rows fell back to the zebra tint instead). */
.matchup-grid .matchup-cell-win {
    background: color-mix(in srgb, var(--win) 40%, transparent);
}

.matchup-grid .matchup-cell-loss {
    background: color-mix(in srgb, var(--loss) 40%, transparent);
}

.matchup-grid th, .matchup-grid td {
    text-align: center;
    white-space: nowrap;
}

.matchup-grid .race-col,
[dir="rtl"] .matchup-grid .race-col {
    /* This is the grid's own row-header column (full race names, not truncated) - reset the
       LTR pin from the [dir="rtl"] .race-col rule so it keeps following the page's actual
       direction, same as every other locale. The [dir="rtl"] variant is needed for specificity:
       it must outweigh [dir="rtl"] .race-col (same specificity as plain .matchup-grid .race-col,
       and later in the file) or Arabic would silently lose this reset. */
    direction: inherit;
    text-align: start;
}

.matchup-cell-pct {
    font-weight: 600;
}

.matchup-cell-games {
    font-size: 0.78rem;
    opacity: 0.75;
}

.rank-col {
    text-align: start;
    font-variant-numeric: tabular-nums;
    width: 2.5rem;
}

.podium-icon {
    display: inline-block;
    width: 1.3rem;
    height: 1.3rem;
    vertical-align: middle;
}

.rating-delta {
    font-variant-numeric: tabular-nums;
}

.rating-delta.up {
    color: var(--win);
}

.rating-delta.down {
    color: var(--loss);
}

.rating-delta.flat {
    color: var(--muted);
}

.flag {
    margin-inline-end: 0.4rem;
}

/* Twemoji-replaced emoji: raw regional-indicator flag glyphs don't render as flags on Windows
   Chrome/Edge (no color-flag glyphs in the bundled font), so every emoji on the site is swapped
   for a Twemoji image at runtime - this sizes those images to sit inline with text. */
img.emoji-icon {
    height: 1.1em;
    width: 1.1em;
    vertical-align: -0.15em;
    margin: 0 0.05em;
}

/* A clan's icon, shown before a player's name (like .flag) rather than after it (like
   .role-badge/.tier-badge). Sized in rem, not em, like .role-badge/.tier-badge already are -
   the same physical size everywhere (leaderboard row, battle card, profile h1 header) instead of
   scaling up with the surrounding element's font-size, which made it look badly oversized in the
   big profile-heading h1. Fixed height with auto width (not a fixed square with object-fit:cover)
   - admins upload anything from square crests to wide wordmark banners, and cover was cropping
   the sides off wide logos. max-width + object-fit:contain is a safety net for a pathologically
   wide upload, but it has to stay generous: a real uploaded banner (133x26, a ~5:1 wordmark with
   the clan tag baked into the image) hit a 2.8rem cap and got squashed down to an unreadable ~9px
   tall, defeating the point of a wide-banner icon entirely. 6rem covers that shape with room to
   spare while still bounding a truly pathological (e.g. 2000px wide) upload. */
.clan-icon {
    height: 1.3rem;
    width: auto;
    max-width: 6rem;
    vertical-align: -0.25rem;
    margin-inline-end: 0.3rem;
    border-radius: 3px;
    object-fit: contain;
}

.profile-flag {
    font-size: 1.5rem;
}

.race-dot {
    display: inline-block;
    width: 0.6rem;
    height: 0.6rem;
    border-radius: 50%;
    margin-inline-end: 0.5rem;
}

.race-icon {
    display: inline-block;
    width: 1.15rem;
    height: 1.15rem;
    vertical-align: middle;
    margin-inline-end: 0.4rem;
    object-fit: cover;
}

.race-col {
    white-space: nowrap;
    max-width: 110px;
    overflow: hidden;
    text-overflow: ellipsis;
}

/* Race names are untranslated English proper nouns (Chaos Marines, etc.) - text-overflow's
   truncation side follows the element's own `direction`, not the page's. Under dir="rtl" with
   no override, ellipsis cuts from the wrong end ("...os Marines" instead of "Chaos Mari...").
   Scoped to [dir="rtl"] only - every other locale keeps its original text-align:start (left)
   from the generic th,td rule, unaffected. */
[dir="rtl"] .race-col {
    direction: ltr;
    text-align: right;
}

.map-tile {
    display: inline-flex;
    vertical-align: middle;
    margin-inline-end: 0.6rem;
}

.map-art {
    width: 1.6rem;
    height: 1.6rem;
    display: block;
    border-radius: 6px;
    object-fit: cover;
}

.last-game {
    color: var(--muted);
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
}

.player-link {
    color: var(--text);
    text-decoration: none;
    font-weight: 500;
}

.player-link:hover {
    color: var(--accent);
}

.ai-player {
    color: var(--muted);
    font-style: italic;
}

.empty-state {
    color: var(--muted);
    padding: 0.5rem 0;
}

/* Site-wide tournament strip, above the nav like the deploy notice. Accent-tinted rather than a
 * solid fill: it sits on every page for months, so it has to read as an invitation, not an alarm. */
.devcup-banner {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 0.6rem;
    flex-wrap: wrap;
    padding: 0.55rem 1rem;
    text-decoration: none;
    color: var(--text-strong);
    background: linear-gradient(90deg, rgba(110, 168, 254, 0.18), rgba(122, 63, 160, 0.18));
    border-bottom: 1px solid var(--border);
}

.devcup-banner:hover {
    background: linear-gradient(90deg, rgba(110, 168, 254, 0.28), rgba(122, 63, 160, 0.28));
}

.devcup-banner-badge {
    font-size: 0.72rem;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    padding: 0.15rem 0.5rem;
    border-radius: 999px;
    background: var(--accent);
    color: #0c0e15;
    font-weight: 700;
}

.devcup-banner-text {
    font-weight: 600;
}

.devcup-banner-cta {
    color: var(--accent);
    font-weight: 600;
}

.devcup-hero {
    max-width: 900px;
    margin: 2rem auto 1rem;
    padding: 2rem 1.5rem;
    text-align: center;
    border: 1px solid var(--border);
    border-radius: 12px;
    background:
        radial-gradient(1200px 300px at 50% -40%, var(--bg-glow-1), transparent),
        var(--surface);
}

.devcup-kicker {
    display: inline-block;
    font-size: 0.78rem;
    letter-spacing: 0.14em;
    text-transform: uppercase;
    color: var(--accent);
    margin-bottom: 0.6rem;
}

.devcup-title {
    margin: 0 0 0.4rem;
    font-size: clamp(2rem, 6vw, 3.2rem);
    line-height: 1.05;
}

.devcup-tagline {
    margin: 0 auto 1.6rem;
    max-width: 46ch;
    color: var(--muted);
}

.devcup-facts {
    display: flex;
    justify-content: center;
    gap: 1rem;
    flex-wrap: wrap;
    margin-bottom: 1.6rem;
}

.devcup-fact {
    min-width: 12rem;
    padding: 1rem 1.2rem;
    border: 1px solid var(--border);
    border-radius: 10px;
    background: var(--bg);
}

.devcup-fact-prize {
    border-color: var(--accent);
}

.devcup-fact-label {
    display: block;
    font-size: 0.78rem;
    text-transform: uppercase;
    letter-spacing: 0.08em;
    color: var(--muted);
}

.devcup-fact-value {
    display: block;
    font-size: 1.7rem;
    font-weight: 700;
    font-variant-numeric: tabular-nums;
    margin: 0.2rem 0;
}

.devcup-fact-note {
    display: block;
    font-size: 0.85rem;
    color: var(--muted);
}

.devcup-btn-primary {
    font-size: 1.02rem;
    padding: 0.7rem 1.4rem;
}

.devcup-body {
    max-width: 760px;
    margin: 0 auto 3rem;
    padding: 0 1.5rem;
}

.devcup-note {
    color: var(--muted);
}

.devcup-list {
    margin: 0 0 1.2rem;
    padding-left: 1.2rem;
    line-height: 1.65;
}

.devcup-list li {
    margin-bottom: 0.35rem;
}

/* Repeated under the summary, where the reader has just finished it and is deciding whether to
 * enter - the hero copy is above the fold, this one is where the question actually gets answered. */
.devcup-body .devcup-actions {
    margin-top: 1.4rem;
}

/* A prerequisite the reader has to act on before the surrounding section works at all - deliberately
 * not muted like .empty-state, which is what a skippable aside looks like here. Accent rather than
 * --loss: nothing has gone wrong yet, there's just a step missing. */
.prereq-note {
    color: var(--text);
    background: var(--surface);
    border-left: 3px solid var(--accent);
    border-radius: 0 4px 4px 0;
    padding: 0.6rem 0.8rem;
    margin: 0.5rem 0;
}

.search-form {
    display: flex;
    gap: 0.75rem;
    margin-bottom: 1.5rem;
}

.search-form input {
    flex: 1;
    max-width: 320px;
    padding: 0.45rem 0.75rem;
    border: 1px solid var(--border);
    border-radius: 6px;
    background: var(--surface);
    color: var(--text);
}

/* Filled with the accent color (like .replay-btn-primary) rather than the same plain
 * var(--surface) background the input next to it uses - side by side, same fill + a barely-
 * visible dark border made the two look like one merged box instead of an input plus a button. */
.search-form button {
    padding: 0.45rem 1rem;
    border: 1px solid var(--accent);
    border-radius: 6px;
    background: var(--accent);
    color: #0b1220;
    font-weight: 600;
    cursor: pointer;
    flex-shrink: 0;
}

.search-form button:hover {
    filter: brightness(1.08);
}

.player-search-wrap {
    position: relative;
    flex: 1;
    /* Flex items default to min-width:auto, i.e. "never shrink below your content's natural
     * width" - wrapping the input in this div (for the dropdown) made that content-based minimum
     * win over max-width below, so the wrap overflowed its allotted space and visually overlapped
     * the button next to it. min-width:0 lets max-width actually be the limit again. */
    min-width: 0;
    max-width: 320px;
}

.player-search-wrap input {
    width: 100%;
    /* Without this, width:100% sizes the content box to the wrap's full width and then adds the
     * caller's own input padding/border on top under the default content-box model - the input
     * ends up wider than its own wrapper and physically overlaps whatever sits next to it (a
     * button on the ladder/search boxes, the next filter field on replays' player filter).
     * border-box keeps padding/border inside the declared width instead, regardless of which
     * page's own input styling (padding, border-width) is applied. */
    box-sizing: border-box;
}

/* The site has no other breakpoints, but this one's worth having on its own: 320px input +
 * button + gap simply doesn't fit next to each other on a phone-width screen (confirmed by
 * measuring real layout widths, not guessing) - side by side, past 100% one item overlaps or
 * clips the other regardless of min-width/flex-shrink tuning. Stacking removes the possibility
 * of that entirely instead of chasing an exact pixel threshold. */
@media (max-width: 600px) {
    .search-form {
        flex-wrap: wrap;
    }

    .player-search-wrap {
        flex-basis: 100%;
        max-width: none;
    }

    /* Same stacking as .player-search-wrap above, for search-form inputs that never get wrapped
     * by player-search-autocomplete.js (it only targets input[name="alias"] - the account page's
     * linked-player search uses name="search" and so never gets a .player-search-wrap, meaning it
     * kept the desktop 320px-cap layout and still touched/overlapped the button on narrow
     * screens). A no-op on the wrapped inputs since flex-basis/max-width don't affect an element
     * that isn't a direct flex child of .search-form. */
    .search-form > input {
        flex-basis: 100%;
        max-width: none;
    }

    .search-form button {
        flex: 1;
    }

    /* Every data table on the site (leaderboard, player standings, race win-rates, the matchup
     * grid) is wide enough on a phone to need horizontal scroll no matter what - .table-card
     * already handles that (overflow-x:auto). What's specifically wrong at 480px is that the
     * *first* two columns alone (player name with avatar/flag/badges, or a race name truncated to
     * a fixed 110px) already eat the whole viewport, pushing the one number people actually came
     * for (Rating) off-screen with zero indication there's more to scroll to. These three rules
     * shrink those columns so the important stuff lands in the initial viewport instead. */
    .col-player {
        min-width: 0;
        white-space: normal;
    }

    .row-avatar {
        display: none;
    }

    .race-col {
        max-width: none;
        white-space: normal;
    }

    /* Rank-change (±) and last-game timestamp are the least-consulted columns on a leaderboard
     * glanced at on a phone - drop them so Rating/W/L/Win%/Streak fit without scrolling instead of
     * every column getting a little scroll-required sliver. Still there at desktop widths, and
     * still reachable by rotating to landscape or switching to a wider device. */
    .col-rankchange, .last-game {
        display: none;
    }

}

.player-search-dropdown {
    position: absolute;
    top: calc(100% + 4px);
    inset-inline: 0;
    z-index: 40;
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: 8px;
    overflow: hidden;
    box-shadow: 0 8px 24px rgba(0, 0, 0, 0.35);
}

.player-search-item {
    display: flex;
    align-items: center;
    gap: 0.35rem;
    padding: 0.5rem 0.75rem;
    color: var(--text);
    text-decoration: none;
    border-bottom: 1px solid var(--border);
}

.player-search-item:last-child {
    border-bottom: none;
}

.player-search-item:hover,
.player-search-item.active {
    background: var(--surface-hover);
}

.player-search-aka {
    font-size: 0.85em;
}

.favorite-btn {
    display: inline-block;
    padding: 0.4rem 0.9rem;
    margin-bottom: 1rem;
    border: 1px solid var(--border);
    border-radius: 6px;
    background: var(--surface);
    color: var(--muted);
    text-decoration: none;
    cursor: pointer;
}

.favorite-btn:hover {
    color: var(--text-strong);
    border-color: var(--accent);
}

/* Scoped to the Replays page (filter "Refresh" + each row's "Download") - a solid-fill primary
   look for the page's main actions, matching dowstats.ru, without changing .favorite-btn's plain
   outline look everywhere else it's used site-wide. */
.replay-btn-primary {
    border-color: var(--accent);
    background: var(--accent);
    color: #0b1220;
    font-weight: 600;
}

.replay-btn-primary:hover {
    color: #0b1220;
    filter: brightness(1.08);
}

.profile-actions {
    display: flex;
    align-items: center;
    gap: 0.6rem;
    flex-wrap: wrap;
}

.profile-actions form,
.profile-actions .favorite-btn {
    margin-bottom: 1rem;
}

/* The favorite button sits inside a <form> (needed for its own CSRF field), while the Steam
   button next to it is a bare <a> - both flex items get the same margin-bottom above, but the
   button's own base .favorite-btn margin-bottom also applied *inside* the form, on the button
   itself. Flex items establish their own block-formatting context, so that inner margin doesn't
   collapse away like it would in normal flow - it just makes the form's box taller than the bare
   anchor's, so align-items: center centered them at different heights. Zero it here instead. */
.profile-actions form .favorite-btn {
    margin-bottom: 0;
}

.steam-profile-btn {
    display: inline-flex;
    align-items: center;
    gap: 0.4rem;
    border-color: #1b2838;
    background: linear-gradient(135deg, #1b2838, #2a475e);
    color: #66c0f4;
}

.steam-profile-btn::before {
    content: '';
    width: 0.9rem;
    height: 0.9rem;
    background-color: #66c0f4;
    -webkit-mask: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M11.979 0C5.678 0 0.511 4.86 0.022 11.037l6.432 2.658a3.387 3.387 0 0 1 1.923-.593c.064 0 .125.005.187.007l2.862-4.15v-.058a4.83 4.83 0 0 1 4.828-4.828 4.833 4.833 0 0 1 4.828 4.828 4.83 4.83 0 0 1-4.828 4.828h-.11l-4.087 2.92c.002.056.006.111.006.169a3.395 3.395 0 0 1-3.396 3.396 3.4 3.4 0 0 1-3.35-2.878L.216 15.1c1.113 5.098 5.658 8.9 11.09 8.9 6.626 0 12-5.373 12-12s-5.374-12-12-12M7.54 18.21l-1.473-.61a2.55 2.55 0 0 0 4.622-1.744l1.472.61a2.55 2.55 0 0 1-4.62 1.744m7.128-8.895a3.22 3.22 0 0 1-3.219-3.219 3.221 3.221 0 0 1 3.219-3.22 3.222 3.222 0 0 1 3.219 3.22 3.22 3.22 0 0 1-3.219 3.219m0-8.048a4.833 4.833 0 0 0-4.828 4.83 4.833 4.833 0 0 0 4.828 4.828 4.833 4.833 0 0 0 4.828-4.829 4.833 4.833 0 0 0-4.828-4.829'/></svg>") no-repeat center;
    mask: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M11.979 0C5.678 0 0.511 4.86 0.022 11.037l6.432 2.658a3.387 3.387 0 0 1 1.923-.593c.064 0 .125.005.187.007l2.862-4.15v-.058a4.83 4.83 0 0 1 4.828-4.828 4.833 4.833 0 0 1 4.828 4.828 4.83 4.83 0 0 1-4.828 4.828h-.11l-4.087 2.92c.002.056.006.111.006.169a3.395 3.395 0 0 1-3.396 3.396 3.4 3.4 0 0 1-3.35-2.878L.216 15.1c1.113 5.098 5.658 8.9 11.09 8.9 6.626 0 12-5.373 12-12s-5.374-12-12-12M7.54 18.21l-1.473-.61a2.55 2.55 0 0 0 4.622-1.744l1.472.61a2.55 2.55 0 0 1-4.62 1.744m7.128-8.895a3.22 3.22 0 0 1-3.219-3.219 3.221 3.221 0 0 1 3.219-3.22 3.222 3.222 0 0 1 3.219 3.22 3.22 3.22 0 0 1-3.219 3.219m0-8.048a4.833 4.833 0 0 0-4.828 4.83 4.833 4.833 0 0 0 4.828 4.828 4.833 4.833 0 0 0 4.828-4.829 4.833 4.833 0 0 0-4.828-4.829'/></svg>") no-repeat center;
}

.steam-profile-btn:hover {
    color: #ffffff;
    border-color: #66c0f4;
}

.cta-banner {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 1rem;
    flex-wrap: wrap;
    padding: 0.9rem 1.1rem;
    margin-bottom: 1.5rem;
    border: 1px solid var(--accent);
    border-radius: 8px;
    background: rgba(110, 168, 254, 0.08);
}

.cta-banner p {
    margin: 0;
}

.cta-banner .favorite-btn {
    margin-bottom: 0;
    white-space: nowrap;
}

.role-badge {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 1.4rem;
    height: 1.4rem;
    margin-inline-start: 0.3rem;
    border: 1px solid var(--border);
    border-radius: 50%;
    font-size: 0.85rem;
    text-decoration: none;
    vertical-align: middle;
}

.role-row {
    display: flex;
    gap: 0.5rem;
    margin-bottom: 1rem;
    flex-wrap: wrap;
}

.role-pill-label input {
    position: absolute;
    opacity: 0;
    width: 0;
    height: 0;
}

.role-pill {
    display: inline-flex;
    align-items: center;
    gap: 0.3rem;
    padding: 0.35rem 0.75rem;
    border: 2px solid var(--border);
    border-radius: 999px;
    cursor: pointer;
    color: var(--muted);
    background: var(--surface);
}

.role-pill-label input:checked + .role-pill {
    color: var(--text-strong);
}

.auth-form {
    display: flex;
    flex-direction: column;
    gap: 0.9rem;
    max-width: 320px;
    margin: 1rem 0 1.5rem;
}

.auth-form label {
    display: flex;
    flex-direction: column;
    gap: 0.3rem;
    color: var(--muted);
    font-size: 0.85rem;
}

.auth-form input {
    padding: 0.5rem 0.75rem;
    border: 1px solid var(--border);
    border-radius: 6px;
    background: var(--surface);
    color: var(--text);
    font-size: 0.95rem;
}

.auth-form button {
    padding: 0.5rem 1rem;
    border: 1px solid var(--border);
    border-radius: 6px;
    background: var(--surface);
    color: var(--muted);
    cursor: pointer;
    align-self: flex-start;
}

.auth-form button:hover {
    color: var(--text-strong);
    border-color: var(--accent);
}

.customization-preview {
    display: flex;
    align-items: center;
    gap: 0.6rem;
    padding: 0.75rem;
    margin-bottom: 1rem;
    border: 1px solid var(--border);
    border-radius: 8px;
    background: var(--surface);
    font-weight: 600;
}

.avatar-preview {
    width: 2rem;
    height: 2rem;
    border-radius: 50%;
}

.row-avatar {
    width: 1.4rem;
    height: 1.4rem;
    border-radius: 50%;
    margin-inline-end: 0.4rem;
    vertical-align: middle;
}

.profile-heading {
    display: flex;
    align-items: center;
    gap: 0.6rem;
}

.profile-heading .avatar-preview {
    width: 2.5rem;
    height: 2.5rem;
}

.swatch-row {
    display: flex;
    gap: 0.5rem;
    margin-bottom: 1rem;
}

.swatch-label input {
    position: absolute;
    opacity: 0;
    width: 0;
    height: 0;
}

.swatch {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 2rem;
    height: 2rem;
    border-radius: 50%;
    border: 2px solid transparent;
    cursor: pointer;
    background: var(--surface);
}

.swatch-none {
    border: 1px solid var(--border);
    color: var(--muted);
}

.badge-swatch {
    border: 1px solid var(--border);
    font-size: 1.1rem;
}

.swatch-label input:checked + .swatch {
    border-color: var(--text-strong);
}

.lfg-form {
    display: flex;
    gap: 0.5rem;
    margin-bottom: 1.5rem;
    flex-wrap: wrap;
}

.lfg-form select {
    padding: 0.45rem 0.6rem;
    border: 1px solid var(--border);
    border-radius: 6px;
    background: var(--surface);
    color: var(--text);
}

.lfg-form input {
    flex: 1;
    min-width: 200px;
    padding: 0.45rem 0.75rem;
    border: 1px solid var(--border);
    border-radius: 6px;
    background: var(--surface);
    color: var(--text);
}

.lfg-form button {
    padding: 0.45rem 1rem;
    border: 1px solid var(--border);
    border-radius: 6px;
    background: var(--surface);
    color: var(--muted);
    cursor: pointer;
}

.lfg-form button:hover {
    color: var(--text-strong);
    border-color: var(--accent);
}

.lfg-list {
    display: flex;
    flex-direction: column;
    gap: 0.75rem;
}

.lfg-post {
    padding: 0.75rem 1rem;
    border: 1px solid var(--border);
    border-radius: 8px;
    background: var(--surface);
}

.lfg-author {
    font-weight: 600;
}

.lfg-mode {
    margin-inline-start: 0.5rem;
    padding: 0.1rem 0.5rem;
    border: 1px solid var(--border);
    border-radius: 999px;
    font-size: 0.75rem;
    color: var(--muted);
}

.lfg-time {
    float: right;
    color: var(--muted);
    font-size: 0.85rem;
}

[dir="rtl"] .lfg-time {
    float: left;
}

.lfg-message {
    margin: 0.4rem 0 0;
}

.role-badge.role-developer {
    width: 1.7rem;
    height: 1.7rem;
    font-size: 1rem;
    border: 2px solid #ffd24c;
    background: radial-gradient(circle at 30% 30%, #3a2a05, #14161f 70%);
    box-shadow: 0 0 6px rgba(255, 210, 76, 0.85), 0 0 14px rgba(255, 150, 40, 0.5);
    animation: dev-glow 2.4s ease-in-out infinite;
}

@keyframes dev-glow {
    0%, 100% { box-shadow: 0 0 6px rgba(255, 210, 76, 0.85), 0 0 14px rgba(255, 150, 40, 0.5); }
    50% { box-shadow: 0 0 11px rgba(255, 210, 76, 1), 0 0 24px rgba(255, 150, 40, 0.85); }
}

.my-standing {
    margin-bottom: 1.5rem;
    padding: 0.9rem 1.1rem;
    border: 1px solid #ffd24c;
    border-radius: 8px;
    background: linear-gradient(135deg, rgba(255, 210, 76, 0.1), rgba(110, 168, 254, 0.06));
}

.my-standing-header {
    display: block;
    margin-bottom: 0.5rem;
    font-size: 0.8rem;
    font-weight: 700;
    letter-spacing: 0.04em;
    text-transform: uppercase;
    color: #ffd24c;
}

.my-standing-body {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 0.9rem;
}

.my-standing-rank {
    font-weight: 700;
    font-size: 1.1rem;
    min-width: 2rem;
}

.my-standing-stat {
    color: var(--muted);
    font-size: 0.9rem;
}

.my-standing-stat b {
    color: var(--text-strong);
    font-weight: 700;
    /* See .num - a negative streak/rating value here needs a pinned LTR run too, or the sign
       bidi-reorders to after the digit under dir="rtl". */
    direction: ltr;
    unicode-bidi: embed;
}

.my-standing-hint {
    margin-top: -0.8rem;
    margin-bottom: 1.5rem;
}

.news-form {
    max-width: 640px;
}

.news-form textarea {
    padding: 0.5rem 0.75rem;
    border: 1px solid var(--border);
    border-radius: 6px;
    background: var(--surface);
    color: var(--text);
    font-size: 0.95rem;
    font-family: inherit;
    resize: vertical;
}

.battles-loadmore {
    display: flex;
    justify-content: center;
    margin-top: 1.5rem;
    margin-bottom: 1.5rem;
}

.battles-loadmore .favorite-btn {
    margin-bottom: 0;
}

.news-list {
    display: flex;
    flex-direction: column;
    gap: 1.2rem;
    margin-top: 1rem;
}

.news-card {
    padding: 1rem 1.2rem;
    border: 1px solid var(--border);
    border-radius: 8px;
    background: rgba(255, 255, 255, 0.02);
}

.news-thumb {
    display: block;
    width: 100%;
    max-height: 220px;
    object-fit: cover;
    border-radius: 6px;
    margin-bottom: 0.8rem;
}

.news-card h2 {
    margin: 0.5rem 0 0.3rem;
}

.news-card h2 a {
    color: var(--text);
    text-decoration: none;
}

.news-card h2 a:hover {
    color: var(--accent);
}

.battle-header {
    display: flex;
    align-items: center;
    gap: 0.6rem;
    flex-wrap: wrap;
    color: var(--muted);
    font-size: 0.85rem;
    margin-bottom: 0.7rem;
}

.battle-mode {
    padding: 0.1rem 0.5rem;
    border: 1px solid var(--border);
    border-radius: 4px;
}

.battle-pill {
    padding: 0.1rem 0.5rem;
    border-radius: 999px;
    font-size: 0.78rem;
    font-weight: 600;
    border: 1px solid var(--border);
}

.battle-pill-automatch,
.battle-pill-linked {
    color: var(--win);
    border-color: currentColor;
}

.battle-pill-custom,
.battle-pill-unlinked {
    color: var(--muted);
}

.battle-pill-event {
    color: #ffd24c;
    border-color: currentColor;
    background: rgba(255, 210, 76, 0.15);
}

/* A custom replay's win/loss coloring (see .win-rate/.loss-text on .battle-player) comes from a
 * best-effort guess, not a real ranked result - this pill makes sure that's never mistaken for a
 * ranked match just because the colors now look the same. */
.battle-pill-notranked {
    color: #ff9f43;
    border-color: currentColor;
    background: rgba(255, 159, 67, 0.15);
}

.player-search-results {
    position: absolute;
    z-index: 10;
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: 4px;
    max-height: 12rem;
    overflow-y: auto;
    width: 100%;
}

.player-search-item {
    padding: 0.35rem 0.6rem;
    cursor: pointer;
}

.player-search-item:hover {
    background: var(--surface-hover);
}

.battle-teams {
    display: flex;
    gap: 1.2rem;
    flex-wrap: wrap;
    align-items: center;
}

.battle-teams-1v1 {
    row-gap: 0.3rem;
}

.battle-team {
    flex: 1;
    min-width: 160px;
    display: flex;
    flex-direction: column;
    gap: 0.2rem;
}

.battle-vs, .battle-vs-inline {
    display: inline-flex;
    flex: none;
    align-items: center;
    justify-content: center;
    align-self: center;
    min-width: 2.4rem;
    height: 1.55rem;
    padding: 0 0.4rem;
    font-family: var(--heading-font);
    font-weight: 800;
    font-size: 0.7rem;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--accent);
    background: var(--surface-hover);
    border: 1px solid var(--accent);
    clip-path: polygon(14% 0%, 100% 0%, 86% 100%, 0% 100%);
}

.battle-player {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    min-width: 0;
    max-width: 100%;
    gap: 0.35rem;
    padding: 0.1rem 0;
}

.battle-player .player-link {
    overflow-wrap: anywhere;
}

.battle-rating {
    color: var(--muted);
    font-variant-numeric: tabular-nums;
    font-size: 0.85em;
}

/* Replay-card-only footer, sitting below the shared battle-card header/team layout (same as
   /battles) - small print pinned to the tile's bottom edge instead of living in an extra
   mid-card row, and action links styled like .battle-replay-link's inline "Replay" link rather
   than boxy buttons, matching how the Battles page already treats a replay download. */
.replay-card-footer {
    display: flex;
    align-items: center;
    justify-content: space-between;
    flex-wrap: wrap;
    gap: 0.4rem 1rem;
    margin-top: 0.8rem;
    padding-top: 0.6rem;
    border-top: 1px solid var(--border);
}

.replay-card-meta {
    display: flex;
    flex-wrap: wrap;
    gap: 0.35rem;
    color: var(--muted);
    font-size: 0.78rem;
}

.replay-card-actions {
    display: flex;
    align-items: center;
    gap: 0.9rem;
    font-size: 0.8rem;
}

.replay-action-link {
    background: none;
    border: none;
    padding: 0;
    margin: 0;
    color: var(--muted);
    text-decoration: none;
    font-size: 0.8rem;
    font-family: inherit;
    cursor: pointer;
}

.replay-action-link:hover {
    text-decoration: underline;
    color: var(--text-strong);
}

.replay-action-primary {
    color: var(--accent);
    font-weight: 600;
}

.replay-action-primary:hover {
    color: var(--accent);
}

.replay-action-disabled {
    cursor: default;
    opacity: 0.7;
}

.replay-action-form {
    display: inline-flex;
}

/* Kept out of .replay-card-actions on purpose - nesting the report's <pre> inside that flex row
   made every sibling button jump to vertically re-center against it the moment it opened. As its
   own block below the footer, opening it only ever pushes later cards down, never the buttons. */
.replay-report-details {
    margin-top: 0.5rem;
}

.replay-report-details summary {
    list-style: none;
}

.replay-report-details summary::-webkit-details-marker {
    display: none;
}

.battle-observers {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 0.4rem;
    margin-top: 0.3rem;
    color: var(--muted);
    font-size: 0.8rem;
}

.battle-observers-label {
    font-weight: 600;
}

.battle-observer {
    padding: 0.1rem 0.5rem;
    border: 1px solid var(--border);
    border-radius: 999px;
}

.battle-replay-link {
    margin-inline-start: auto;
    color: var(--accent);
    text-decoration: none;
    font-size: 0.85rem;
}

.battle-replay-link:hover {
    text-decoration: underline;
}

.news-badge {
    display: inline-block;
    padding: 0.15rem 0.6rem;
    border-radius: 999px;
    font-size: 0.75rem;
    font-weight: 700;
    letter-spacing: 0.02em;
    text-transform: uppercase;
}

.news-badge-steam {
    background: rgba(110, 168, 254, 0.15);
    color: var(--accent);
}

.news-badge-tournament {
    background: rgba(255, 210, 76, 0.15);
    color: #ffd24c;
}

.news-meta {
    display: flex;
    gap: 0.8rem;
    color: var(--muted);
    font-size: 0.85rem;
    margin: 0 0 0.6rem;
}

.news-excerpt {
    color: var(--muted);
    white-space: pre-line;
    margin: 0 0 0.6rem;
}

.news-detail {
    max-width: 760px;
}

.news-detail-image {
    display: block;
    width: 100%;
    max-height: 380px;
    object-fit: cover;
    border-radius: 8px;
    margin: 0.8rem 0;
}

.news-body {
    white-space: pre-line;
    line-height: 1.6;
    margin: 1rem 0 1.2rem;
}

/* Honeypot field: off-screen rather than display:none, since some bots specifically skip
   fields hidden that way. Real users never see or reach this. */
.hp-field {
    position: absolute;
    inset-inline-start: -9999px;
    width: 1px;
    height: 1px;
    overflow: hidden;
}

.nav-subscribe {
    border: 1px solid #6ea8fe !important;
    color: #ffffff !important;
    background: linear-gradient(135deg, #6ea8fe, #a970ff, #6ea8fe);
    background-size: 200% 200%;
    animation: subscribe-shine 3s ease infinite;
    font-weight: 700;
}

.nav-subscribe:hover {
    color: #ffffff !important;
}

@keyframes subscribe-shine {
    0% { background-position: 0% 50%; }
    50% { background-position: 100% 50%; }
    100% { background-position: 0% 50%; }
}

.nav-user-ultra {
    font-weight: 700;
    background: linear-gradient(90deg, #ffd24c, #ff6b9d, #6ea8fe, #ffd24c);
    background-size: 300% auto;
    -webkit-background-clip: text;
    background-clip: text;
    color: transparent !important;
    animation: ultra-name-shine 4s linear infinite;
}

@keyframes ultra-name-shine {
    0% { background-position: 0% center; }
    100% { background-position: 300% center; }
}

.nav-user-pro {
    font-weight: 700;
    background: linear-gradient(90deg, #ffd24c, #fff2c2, #c9a227, #ffd24c);
    background-size: 300% auto;
    -webkit-background-clip: text;
    background-clip: text;
    color: transparent !important;
    animation: pro-name-shine 4s linear infinite;
}

@keyframes pro-name-shine {
    0% { background-position: 0% center; }
    100% { background-position: 300% center; }
}

.tier-badge {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 1.4rem;
    height: 1.4rem;
    margin-inline-start: 0.3rem;
    border-radius: 50%;
    font-size: 0.85rem;
    vertical-align: middle;
}

.tier-light {
    border: 1px solid #8fbcff;
    background: rgba(110, 168, 254, 0.12);
}

.tier-pro {
    border: 1px solid #c9a227;
    background: rgba(201, 162, 39, 0.15);
}

.tier-ultra {
    border: 1px solid #ff6b9d;
    background: linear-gradient(135deg, rgba(255, 210, 76, 0.25), rgba(255, 107, 157, 0.25));
    box-shadow: 0 0 6px rgba(255, 107, 157, 0.6);
}

.tier-grid {
    display: flex;
    gap: 1.2rem;
    flex-wrap: wrap;
    margin: 1.5rem 0;
}

.tier-card {
    flex: 1 1 220px;
    padding: 1.2rem;
    border: 1px solid var(--border);
    border-radius: 8px;
    background: rgba(255, 255, 255, 0.02);
}

.tier-card h2 {
    padding: 0.4rem 0.7rem;
    border-radius: 6px;
    font-size: 1rem;
    margin: 0 0 0.8rem;
}

.tier-card ul {
    margin: 0;
    padding-inline-start: 1.2rem;
    color: var(--muted);
    font-size: 0.9rem;
    line-height: 1.6;
}

.features-showcase {
    display: flex;
    flex-direction: column;
    gap: 1.5rem;
    margin: 1.5rem 0 2rem;
}

.feature-block {
    padding: 1.4rem 1.6rem;
    border: 1px solid var(--border);
    border-radius: 8px;
    background: rgba(255, 255, 255, 0.02);
}

.feature-quote {
    margin: 0 0 0.4rem;
    color: var(--accent);
    font-style: italic;
    font-size: 0.85rem;
}

.feature-block h3 {
    margin: 0 0 0.5rem;
    font-size: 1.15rem;
}

.feature-block p.feature-desc {
    margin: 0 0 0.8rem;
    color: var(--muted);
    max-width: 60ch;
}

.feature-checklist {
    list-style: none;
    margin: 0;
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: 0.35rem;
}

.feature-checklist li {
    padding-inline-start: 1.4rem;
    position: relative;
}

.feature-checklist li::before {
    content: '✓';
    position: absolute;
    inset-inline-start: 0;
    color: var(--win);
    font-weight: 700;
}

.poll-options {
    display: flex;
    flex-direction: column;
    gap: 0.5rem;
    margin: 1rem 0;
}

.poll-option-label {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    padding: 0.5rem 0.8rem;
    border: 1px solid var(--border);
    border-radius: 6px;
    cursor: pointer;
}

.poll-option-label:hover {
    border-color: var(--accent);
}

.poll-results {
    margin: 1rem 0;
}

.poll-result-row {
    margin-bottom: 0.7rem;
}

.poll-result-label {
    display: flex;
    justify-content: space-between;
    gap: 0.5rem;
    font-size: 0.9rem;
    margin-bottom: 0.25rem;
}

.poll-result-bar {
    height: 0.5rem;
    border-radius: 999px;
    background: rgba(255, 255, 255, 0.06);
    overflow: hidden;
}

.poll-result-fill {
    height: 100%;
    background: var(--accent);
}

.expand-toggle {
    cursor: pointer;
}

.expand-toggle:hover {
    background: var(--surface-hover);
}

.expand-caret {
    color: var(--muted);
    font-size: 0.75rem;
    margin-inline-start: 0.4rem;
}

.expand-detail-row td {
    padding: 0.5rem 0.75rem 1rem;
    background: rgba(0, 0, 0, 0.15);
}

.expand-detail-table {
    width: 100%;
    font-size: 0.85rem;
}

.expand-detail-table th {
    color: var(--muted);
    font-weight: 500;
    text-align: start;
    padding: 0.25rem 0.5rem;
}

.expand-detail-table td {
    padding: 0.25rem 0.5rem;
}

.matrix-cell-clickable {
    cursor: pointer;
}

.matrix-cell-clickable:hover {
    background: var(--surface-hover);
}

.expand-detail-panel {
    margin: 0.75rem 0 1.5rem;
    padding: 0.75rem 1rem;
    border: 1px solid var(--border);
    border-radius: 6px;
    background: var(--surface);
}

.expand-detail-panel h3 {
    margin: 0 0 0.5rem;
    font-size: 1rem;
}

.profile-tabs {
    display: flex;
    gap: 0.25rem;
    margin: 1.5rem 0 0;
    border-bottom: 1px solid var(--border);
    /* Four tabs do not fit a 390px phone: measured, the last one ended 7px past the viewport and
       took the whole page into sideways scroll - the one thing a page body must never do. Scrolls as
       one row instead of wrapping, for the reason spelled out on .filters-tight: a wrapped row of
       equal-weight choices reads as a ragged block. */
    overflow-x: auto;
    flex-wrap: nowrap;
}

.profile-tab {
    padding: 0.6rem 1.1rem;
    border: none;
    background: transparent;
    color: var(--muted);
    font-size: 0.9rem;
    font-weight: 500;
    cursor: pointer;
    border-bottom: 2px solid transparent;
    margin-bottom: -1px;
    /* Keeps each tab its own width inside the scrolling row rather than being squeezed until the
       labels break across lines. */
    flex: none;
    white-space: nowrap;
}

.profile-tab:hover {
    background: var(--surface-hover);
    color: var(--text);
}

.profile-tab.active {
    color: var(--text);
    border-bottom-color: var(--accent);
}

.profile-tab-maps:not(.active) {
    color: var(--accent);
}

.map-stats-cta p {
    display: flex;
    align-items: center;
    gap: 0.5rem;
}

.profile-tab-panel {
    padding-top: 1rem;
}

.replay-auto-steps {
    margin: 0.5rem 0 0.75rem;
    padding-inline-start: 1.25rem;
}

.replay-auto-steps li {
    margin-bottom: 0.35rem;
}

.replay-auto-buttons {
    display: flex;
    align-items: center;
    gap: 1rem;
    margin: 0.75rem 0;
}

/* Double-elimination bracket (WB/LB/GF) - no drawn connector lines between cards (v1 scope cut,
   see gentle-noodling-book plan); each round is just a column of match cards in slot order. */
.bracket-section {
    margin-bottom: 2rem;
}

.bracket-section-title {
    margin-bottom: 0.75rem;
}

.bracket-columns {
    display: flex;
    gap: 1.5rem;
    overflow-x: auto;
    padding-bottom: 0.5rem;
}

.bracket-round-col {
    display: flex;
    flex-direction: column;
    gap: 1rem;
    min-width: 220px;
    flex: 0 0 auto;
}

.bracket-round-title {
    color: var(--muted);
    font-size: 0.85rem;
    text-transform: uppercase;
    letter-spacing: 0.03em;
}

.bracket-card {
    border: 1px solid var(--border);
    border-radius: 8px;
    background: var(--surface);
    padding: 0.75rem;
}

.bracket-card-label {
    color: var(--muted);
    font-size: 0.8rem;
    margin-bottom: 0.5rem;
}

.bracket-side {
    display: flex;
    justify-content: space-between;
    align-items: center;
    gap: 0.5rem;
    padding: 0.25rem 0;
}

.bracket-side-winner {
    color: var(--win);
    font-weight: 600;
}

.bracket-side-loser {
    color: var(--loss);
}

.bracket-side-names {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.bracket-vs {
    text-align: center;
    color: var(--muted);
    font-size: 0.8rem;
    margin: 0.15rem 0;
}

.bracket-score {
    font-weight: 700;
}

.bracket-delta {
    /* See .num for why - a signed delta like "-7" needs a pinned LTR run to keep the sign
       before the digit under dir="rtl". This is a genuinely inline element (unlike .num's
       table cells), so direction alone has no bidi effect without unicode-bidi:embed - CSS
       only lets `direction` reorder inline content when paired with embed/isolate/bidi-override. */
    direction: ltr;
    unicode-bidi: embed;
    font-size: 0.8rem;
    margin-inline-start: 0.35rem;
}

.bracket-delta-up {
    color: var(--win);
}

.bracket-delta-down {
    color: var(--loss);
}

.bracket-card-form {
    margin-top: 0.5rem;
    display: flex;
    flex-direction: column;
    gap: 0.4rem;
}

.bracket-card-actions {
    display: flex;
    gap: 0.5rem;
    margin-top: 0.4rem;
}

.bracket-side-picker {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.4rem;
}

.bracket-side-picker-selects {
    display: flex;
    flex-wrap: wrap;
    gap: 0.3rem;
}

.bracket-side-picker-extra {
    display: inline-flex;
    align-items: center;
    gap: 0.2rem;
}

.bracket-add-teammate,
.bracket-remove-teammate {
    font-size: 0.75rem;
    padding: 0.1rem 0.4rem;
}

.bracket-generate-row {
    display: flex;
    align-items: center;
    gap: 0.4rem;
    padding: 0.15rem 0;
}

/* Named achievement list on a player profile (player.html). The leaderboard shows a capped run of
   bare icons next to a nickname; this is the page that has room to say what each one is. */
.profile-achievements {
    margin: 1.2rem 0;
}

/* The owner-only switches under the awards, spaced like the section above them. The pills and the
   button are the site's existing filter/button styles, so this only has to place the block. */
.profile-badge-visibility {
    margin: 1.2rem 0;
}

.profile-badge-visibility h2 {
    margin-bottom: 0.6rem;
}

/* An unticked pill is the whole point of this block, so it has to read as "off" at a glance rather
   than just slightly greyer than its neighbour. */
.profile-badge-visibility .filter-pill:not(:has(input:checked)) {
    opacity: 0.5;
    text-decoration: line-through;
}

.profile-badge-visibility .filters {
    margin: 0.6rem 0;
}

.profile-achievements h2 {
    margin-bottom: 0.6rem;
}

.achievement-list {
    display: flex;
    flex-wrap: wrap;
    gap: 0.5rem;
    padding: 0;
    margin: 0;
    list-style: none;
}

.achievement-item {
    display: inline-flex;
    align-items: center;
    gap: 0.4rem;
    padding: 0.3rem 0.7rem;
    border: 1px solid var(--border);
    border-radius: 999px;
    background: var(--surface);
    color: var(--text);
    font-size: 0.85rem;
}

.achievement-icon {
    font-size: 1.05rem;
    line-height: 1;
}

/* Applying a filter or switching sort re-fetches just the results section over the network, and on
   a large replay set that is not instant. Without a busy state the page sits there looking exactly
   as it did before, so the reader assumes the click missed and presses Apply again. Dim the old
   results rather than blanking them - they are stale, not wrong, and staying readable is better
   than an empty box - and spin above them.

   Both the positioning context and the overlay exist only while .is-refreshing is on, so nothing
   here changes the section's stacking or hit-testing in its normal state. */
#replayResultsSection.is-refreshing {
    position: relative;
    min-height: 6rem;
}

#replayResultsSection.is-refreshing > * {
    opacity: 0.3;
    pointer-events: none;
}

/* Sits on its own filled surface rather than as a bare ring: wherever the section's content
   happens to line up underneath, the spinner reads as a puck floating above it instead of as a
   stray circle colliding with the stat bar. */
#replayResultsSection.is-refreshing::after {
    content: '';
    position: absolute;
    top: 2.5rem;
    left: 50%;
    width: 2.2rem;
    height: 2.2rem;
    margin-left: -1.1rem;
    border: 3px solid var(--border);
    border-top-color: var(--accent);
    border-radius: 50%;
    background: var(--surface);
    box-shadow: 0 2px 10px rgba(0, 0, 0, 0.45);
    animation: replay-refresh-spin 0.7s linear infinite;
}

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

/* The dimmed results still read as "busy" without it, so a static ring loses nothing here. */
@media (prefers-reduced-motion: reduce) {
    #replayResultsSection.is-refreshing::after {
        animation: none;
    }
}

.replay-refresh-error {
    color: var(--loss);
}

/* ---- Subscribe page: the Pro pitch -------------------------------------------------------
 * The gold is #c9a227, the same fixed colour .tier-pro already uses, and it deliberately does
 * not follow the selected theme - see the palette note near the top of this file: tier and brand
 * colours stay put while general page chrome follows the theme. */
.pro-hero {
    margin: 1.5rem 0 2rem;
    padding: 1.6rem 1.8rem;
    border: 1px solid #c9a227;
    border-radius: 10px;
    background: linear-gradient(135deg, rgba(201, 162, 39, 0.16), rgba(201, 162, 39, 0.04));
}

.pro-hero-eyebrow {
    margin: 0 0 0.4rem;
    color: #c9a227;
    font-weight: 700;
    font-size: 0.8rem;
    letter-spacing: 0.08em;
    text-transform: uppercase;
}

.pro-hero h1 {
    margin: 0 0 0.6rem;
    font-size: 1.7rem;
}

.pro-hero-desc {
    margin: 0 0 1.2rem;
    max-width: 62ch;
}

.pro-price {
    display: flex;
    align-items: baseline;
    gap: 0.5rem;
    flex-wrap: wrap;
}

.pro-price-amount {
    font-size: 2.1rem;
    font-weight: 800;
    color: var(--text-strong);
    line-height: 1;
}

.pro-price-period {
    color: var(--muted);
}

.pro-price-note {
    margin: 0.5rem 0 1.2rem;
    color: var(--muted);
    font-size: 0.88rem;
    max-width: 62ch;
}

.pro-cta-row {
    display: flex;
    gap: 0.7rem;
    flex-wrap: wrap;
    align-items: center;
}

/* This page exists for one action, and .favorite-btn is the quiet outline used all over the
   site - too light a weight to be the thing a visitor's eye lands on. */
.btn-pro,
.btn-pro-ghost {
    display: inline-block;
    padding: 0.55rem 1.3rem;
    border: 1px solid #c9a227;
    border-radius: 6px;
    font-weight: 700;
    text-decoration: none;
    cursor: pointer;
    font-size: 1rem;
}

.btn-pro {
    background: #c9a227;
    color: #1a1405;
}

.btn-pro:hover {
    filter: brightness(1.1);
    color: #1a1405;
}

.btn-pro-ghost {
    background: transparent;
    color: #e8c964;
    font-weight: 600;
}

.btn-pro-ghost:hover {
    background: rgba(201, 162, 39, 0.15);
    color: #f0d883;
}

/* The paid blocks reuse .feature-block so the Pro and free sections read as siblings of one
   layout; the gold edge is what separates them at a glance. */
.feature-block-pro {
    border-inline-start: 3px solid #c9a227;
    background: rgba(201, 162, 39, 0.05);
}

.compare-table {
    width: 100%;
    border-collapse: collapse;
}

.compare-table th,
.compare-table td {
    padding: 0.55rem 0.9rem;
    border-bottom: 1px solid var(--border);
    text-align: start;
}

/* Cells wrap rather than nowrap: this table is the clearest answer the page gives to "what do I
   actually get", and on a phone nowrap pushed the Pro column - the whole point of the comparison -
   off the edge of its scroll container where nobody would find it. Wrapping costs some row height
   on mobile and changes nothing on desktop, where the columns never run out of room. */
.compare-table td:first-child {
    min-width: 11rem;
}

/* On a phone the three columns have to share ~340px. The desktop min-width and padding eat enough
   of that to push the Pro column past the edge of its scroll container - the one column a reader
   opened this table for. Tighter here so all three fit without scrolling at 390px. */
@media (max-width: 700px) {
    .compare-table th,
    .compare-table td {
        padding: 0.5rem 0.45rem;
        font-size: 0.85rem;
    }

    .compare-table td:first-child {
        min-width: 0;
    }
}

.compare-table thead th {
    color: var(--muted);
    font-weight: 600;
    font-size: 0.85rem;
}

.compare-table tbody tr:last-child td {
    border-bottom: none;
}

.compare-col-pro {
    background: rgba(201, 162, 39, 0.07);
}

.compare-table thead th.compare-col-pro {
    color: #c9a227;
}

.compare-table td.compare-col-pro {
    color: var(--text-strong);
}

.compare-yes {
    color: var(--win);
    font-weight: 700;
}

.compare-no {
    color: var(--muted);
}

.pro-cta-note {
    margin: 0;
    color: var(--win);
    font-weight: 600;
}

/* A green tick means "you have this". On the My Subscription page the same list is also shown to
   someone who does NOT have Pro, under a heading saying so - there the ticks are gold, reading as
   "these are Pro features" instead of contradicting the heading right above them. */
.feature-checklist-locked li::before {
    color: #c9a227;
}

/* Jump back to the top of a long page. The profile is the longest page on the site - standings,
   ten-and-counting match cards, then four tabs of tables - so scrolling back by hand is a real
   journey. Fixed to the inline start, which is the left on thirteen of the fourteen locales and
   mirrors itself on Arabic without a second rule.
   z-index 50: above the page and the search dropdown (40), below the nav menus (999) so it can
   never cover an open menu. */
.back-to-top {
    position: fixed;
    inset-inline-start: 1rem;
    bottom: 1.25rem;
    z-index: 50;
    width: 2.75rem;
    height: 2.75rem;
    border-radius: 50%;
    border: 1px solid var(--border);
    background: var(--surface);
    color: var(--text-strong);
    font-size: 1.15rem;
    line-height: 1;
    cursor: pointer;
    box-shadow: 0 4px 14px rgba(0, 0, 0, 0.4);
    /* 2.75rem is 44px: the smallest square a thumb hits reliably. */
    display: none;
    align-items: center;
    justify-content: center;
    padding: 0;
}

.back-to-top.is-visible {
    display: flex;
}

.back-to-top:hover,
.back-to-top:focus-visible {
    border-color: var(--accent);
    color: var(--accent);
}

@media (max-width: 700px) {
    /* The matchup grid is nine columns by definition, so on a phone it will always scroll
       sideways - these rules are about making that scroll short instead of endless. Measured on a
       390px viewport: the table was 1205px wide, of which a 169px row header spelling out
       "Space Marines" and 148px cells.
       The race names go and the icons stay: they are the same nine faction icons the reader already
       picks from in every filter and reads on every match card, and the caption above still says
       which axis is which. */
    .matchup-grid .race-col span:not(:first-child) {
        display: none;
    }

    /* The corner label ("My race \ Opponent") is the widest cell in the first column, and a column
       is as wide as its widest cell - measured, it alone held that column at 154px of a 390px
       screen while the row labels beside it were already hidden. */
    .matchup-grid thead th:first-child span {
        display: none;
    }

    .matchup-grid th,
    .matchup-grid td {
        padding: 0.35rem 0.3rem;
    }

    .matchup-grid .matchup-cell-pct {
        font-size: 0.8rem;
    }

    .matchup-grid .matchup-cell-games {
        font-size: 0.68rem;
    }

    /* Without this the first column keeps the width its (now hidden) label asked for. */
    .matchup-grid .race-col {
        min-width: 0;
        width: 1%;
        white-space: nowrap;
    }
}

/* The provenance note (see fragments/data-source.html). Styled to read as the same muted caption the
   pages already use, so it does not compete with the table it explains - the claim is the summary
   line, the reasoning is behind it. */
.data-source-note {
    color: var(--muted);
    font-size: 0.85rem;
    margin: 0.4rem 0 1rem;
    max-width: 70ch;
}

.data-source-note > summary {
    cursor: pointer;
    /* The default triangle is the only affordance that this opens, so it stays. */
    list-style: revert;
}

.data-source-note > summary:hover,
.data-source-note > summary:focus-visible {
    color: var(--text-strong);
}

.data-source-note > p {
    margin: 0.4rem 0 0;
}

/* Privacy policy: a readable measure of line length for a page that is mostly prose. */
.privacy-page {
    max-width: 720px;
    margin: 0 auto;
    line-height: 1.6;
}

/* Site footer - the only link to the privacy policy, which Play Store requires and nothing on the
   site pointed at until now. Muted and small: it closes a page rather than competing with it. */
.site-footer {
    margin: 3rem auto 1.5rem;
    padding: 1.2rem 1rem 0;
    max-width: 1200px;
    border-top: 1px solid var(--border);
    color: var(--muted);
    font-size: 0.8rem;
    text-align: center;
}

.site-footer-links {
    display: flex;
    flex-wrap: wrap;
    justify-content: center;
    align-items: center;
    gap: 0.5rem 1.2rem;
}

.site-footer-links a {
    color: var(--muted);
    text-decoration: none;
}

.site-footer-links a:hover {
    color: var(--accent);
    text-decoration: underline;
}

.site-footer-note {
    margin: 0.6rem auto 0;
    max-width: 640px;
    font-size: 0.75rem;
    opacity: 0.75;
}
