/* ==========================================================================
   Статистика L4D2 — PlayBCM
   Токены и приёмы вёрстки повторяют основной сайт playbcm.net, чтобы раздел
   не выглядел чужим: те же цвета, те же карточки, тот же сайдбар.
   ========================================================================== */

/* --------------------------------------------------------------------------
   1. Токены
   -------------------------------------------------------------------------- */

:root {
    --content-width: 1040px;
    --sidebar-width: 200px;
    --layout-gap: 40px;

    --font-family: Inter, "Segoe UI", "Helvetica Neue", Arial, sans-serif;
    --font-size-logo: 2em;
    --font-size-huge: 1.3em;
    --font-size-large: 1.15em;
    --font-size-default: 14px;
    --font-size-small: 0.9em;
    --font-size-smaller: 0.8em;
    --font-size-tiny: 0.7em;
    /* Подсказки — в пикселях, а не в em. Они всплывают внутри самых мелких
       подписей (шкала графика набрана 0.7em), и относительный размер там
       складывался сам с собой: текст подсказки доходил до восьми пикселей. */
    --font-size-tip: 13px;

    /* Галочка вниз у выпадающих списков. Переменной, а не строкой по месту:
       flatpickr сбрасывает фон списка месяца сокращённой записью background,
       и картинку приходится объявлять заново в каждом состоянии. */
    --select-arrow: url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath fill='%23999' d='M3.2 5.7a.75.75 0 0 1 1.06-.02L8 9.19l3.74-3.51a.75.75 0 1 1 1.02 1.1l-4.25 4a.75.75 0 0 1-1.02 0l-4.25-4a.75.75 0 0 1-.04-1.06'/%3E%3C/svg%3E");

    /* Задержка появления подсказки. Мышь по странице проходит через десятки
       элементов с подсказками, и без паузы они вспыхивали вдоль всего пути
       курсора. Прячется подсказка без задержки: уводя курсор, её уже не ждут. */
    --tip-delay: 300ms;

    --font-weight-regular: 400;
    --font-weight-medium: 500;
    --font-weight-bold: 600;

    --line-height-default: 1.4em;

    --radius-card: 8px;
    --radius-control: 6px;

    --badge-background-red: #b3261e;

    --mobile-display-flex: none;
}

/* Палитра одна — тёмная, переключателя тем нет. */
:root {
    --background-body: rgb(18, 18, 18);
    --background-card: rgb(28, 28, 30);
    --border-card: rgb(36, 36, 38);
    --color-brand: hsl(0, 0%, 90%);

    --background-nav-hover: rgb(255 255 255 / 5%);
    --background-nav-active: rgb(255 255 255 / 8%);
    --background-nav-mobile: #1c1c1e;

    --color-text-important: hsl(209, 5%, 95%);
    --color-text-default: hsl(209, 5%, 85%);
    --color-text-secondary: hsl(209, 5%, 65%);
    --color-text-sidebar: hsl(209, 5%, 80%);

    --color-icon-default: hsl(0, 0%, 80%);
    --color-icon-secondary: hsl(0, 0%, 60%);
    --color-icon-tertiary: hsl(0, 0%, 40%);
    --color-icon-nav: hsl(0, 0%, 60%);

    --color-blue: #71aaeb;
    --color-green: #78b757;
    --color-orange: #e7893a;
    --color-red: #ec5e5d;
    --color-violet: #9a54a3;

    /* Цвета игровых проектов. Взяты в стороне от рабочей палитры нарочно:
       синий, зелёный, оранжевый, красный и фиолетовый уже заняты состояниями
       и типами наказаний, и проект, покрашенный в один из них, читался бы как
       ещё одно состояние. Эти три ничего не значат — просто различают. */
    --color-group-bcm: #4fa79c;
    --color-group-madx: #aba663;
    --color-group-swat: #8b86b8;

    --background-default: rgb(255 255 255 / 5%);
    --background-blue: rgb(109 165 243 / 12%);
    --border-blue: rgb(109 165 243 / 30%);

    --input-background: rgb(36, 36, 38);
    --input-background-focus: rgb(44, 44, 46);
    --input-border: rgb(44, 44, 46);
    --input-border-focus: rgb(72, 72, 74);

    --table-border: hsl(0, 0%, 20%);
    --table-hover: hsl(0, 0%, 13%);
    --table-inner: hsl(240, 2%, 13%);

    --bar-fill: rgb(109 165 243 / 14%);
    --chart-bar: #71aaeb;

    --skeleton-shadow: none;
}

/* --------------------------------------------------------------------------
   2. База
   -------------------------------------------------------------------------- */

*,
*::before,
*::after {
    box-sizing: border-box;
}

/* Место под полосу прокрутки резервируется всегда.
   ---------------------------------------------------------------------------
   Ширина содержимого задана в одном месте (--content-width), но полезная ширина
   окна зависела от того, поместилась страница целиком или нет: на длинном
   банлисте полоса прокрутки съедала 15 px, а на короткой выдаче VIP их не
   съедала — и вся раскладка, выровненная по центру, прыгала вбок при переходе
   между разделами.

   scrollbar-gutter держит эту полосу зарезервированной на любой странице. На
   телефонах, где полоса рисуется поверх содержимого и места не занимает,
   свойство ничего не меняет. */
html {
    -webkit-text-size-adjust: 100%;
    scrollbar-gutter: stable;
}

body {
    margin: 0;
    background-color: var(--background-body);
    color: var(--color-text-default);
    font-family: var(--font-family);
    font-size: var(--font-size-default);
    font-weight: var(--font-weight-regular);
    line-height: var(--line-height-default);
    overflow-x: hidden;
    -webkit-tap-highlight-color: transparent;
    word-break: break-word;
}

input,
select,
button {
    font-family: inherit;
    font-size: inherit;
}

a {
    color: inherit;
    text-decoration: none;
}

a.link {
    color: var(--color-blue);
}

a.link:hover {
    text-decoration: underline;
}

h1,
h2,
h3 {
    color: var(--color-text-important);
    font-weight: var(--font-weight-bold);
    margin: 0;
}

h1 {
    font-size: var(--font-size-huge);
}

img {
    max-width: 100%;
}

/* Базовый размер контурных иконок. SVG задан только viewBox, поэтому без
   явных размеров он растягивается на всю ширину родителя. */
.icon {
    height: 1.15em;
    width: 1.15em;
    flex: none;
    vertical-align: -0.18em;
}

.muted {
    color: var(--color-text-secondary);
}

/* --------------------------------------------------------------------------
   3. Каркас страницы
   -------------------------------------------------------------------------- */

.layout {
    display: flex;
    flex-direction: row;
    justify-content: center;
    gap: var(--layout-gap);
    padding: 0 20px;
}

.sidebar {
    display: flex;
    flex-direction: column;
    position: sticky;
    top: 0;
    align-self: flex-start;
    height: 100vh;
    width: var(--sidebar-width);
    flex: 0 0 var(--sidebar-width);
    user-select: none;
}

.sidebar .brand {
    display: flex;
    align-items: center;
    min-height: 100px;
    padding-left: 20px;
    font-size: var(--font-size-logo);
    font-weight: var(--font-weight-bold);
    color: var(--color-brand);
}

.sidebar .nav {
    display: flex;
    flex-direction: column;
}

/* Геометрия пункта повторяет playbcm.net до пикселя: высота 41 px, шаг 45 px,
   иконка на 20 px от края, подпись на 50 px.

   Высоту задаёт только строка текста. На основном сайте line-height нигде не
   переопределён и равен normal — это 17 px при Inter 14 px, плюс отступы 12+12
   даёт 41. У нас же line-height: 1.4em висит на body, то есть 19.6 px, и пункт
   вырастал до 43.6 px. Поэтому здесь возвращаем normal. */
.sidebar .nav a {
    display: flex;
    align-items: center;
    padding: 12px 20px;
    margin-bottom: 4px;
    border-radius: var(--radius-card);
    color: var(--color-text-sidebar);
    line-height: normal;
    transition: background-color 150ms;
    cursor: pointer;
}

/* Подпись отодвигается фиксированным отступом, а не gap: иконка выведена из
   потока (см. ниже), и раздвигать flex-ом уже нечего. 20 px отступа плюс 30 —
   ровно те же 50 px от края пункта, что и на основном сайте. */
.sidebar .nav a span {
    margin-left: 30px;
}

.sidebar .nav a:hover,
.sidebar .nav a:focus-visible {
    background-color: var(--background-nav-hover);
    outline: none;
}

.sidebar .nav a.active {
    background-color: var(--background-nav-active);
    color: var(--color-text-important);
}

/* Иконка вынута из потока — так же, как на основном сайте. Она выше строки
   текста (19.6 против 17 px), и оставаясь flex-элементом растягивала бы пункт
   по себе. Координаты не заданы: абсолютный элемент без top/left встаёт на своё
   же место в потоке, а во flex-контейнере это место считается по align-items,
   то есть иконка остаётся у левого края и по центру высоты. */
.sidebar .nav svg {
    position: absolute;
    height: 1.4em;
    width: 1.4em;
    color: var(--color-icon-nav);
}

.sidebar .nav a.active svg {
    color: var(--color-icon-default);
}

.content {
    width: 100%;
    max-width: var(--content-width);
    min-width: 0;
    /* Отступ сверху даёт шапка (.topbar): она ровно той же высоты, что и блок
       с логотипом в сайдбаре, поэтому содержимое любой страницы начинается на
       одной линии с первым пунктом меню. */
    padding-top: 0;
    /* Снизу отступ даёт сама карточка (margin-bottom: 20px). Дополнительные
       40 px остались от строки «Загружено за …» и после её удаления читались
       как пустой хвост страницы. */
    padding-bottom: 0;
}

.page-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 16px;
    flex-wrap: wrap;
    padding-bottom: 20px;
}

.page-head .subtitle {
    color: var(--color-text-secondary);
    font-size: var(--font-size-small);
    margin-top: 2px;
}

.page-foot {
    color: var(--color-text-secondary);
    font-size: var(--font-size-smaller);
    text-align: center;
    padding: 8px 0 24px;
}

/* --------------------------------------------------------------------------
   4. Карточки
   -------------------------------------------------------------------------- */

.card {
    background-color: var(--background-card);
    border: 1px solid var(--border-card);
    border-radius: var(--radius-card);
    margin-bottom: 20px;
    box-shadow: var(--skeleton-shadow);
}

.card > .header {
    display: flex;
    align-items: center;
    gap: 10px;
    border-bottom: 1px solid var(--border-card);
    font-size: var(--font-size-large);
    font-weight: var(--font-weight-bold);
    color: var(--color-text-important);
    padding: 14px 20px;
}

.card > .header img {
    height: 20px;
    width: 20px;
    object-fit: contain;
}

/* Иконка раздела в шапке карточки рейтинга. */
.card > .header .board-icon {
    height: 18px;
    width: 18px;
    color: var(--color-icon-secondary);
    vertical-align: 0;
}

/* Заголовок карточки рейтинга ведёт на тот же рейтинг целиком. Стрелка
   приглушена, но видна всегда: только по наведению её не нашли бы с телефона,
   где наведения не бывает. */
.card > .header .board-title {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    color: inherit;
    transition: color 150ms;
}

.card > .header .board-more {
    height: 14px;
    width: 14px;
    opacity: 0.45;
    transition: opacity 150ms;
}

.card > .header .board-title:hover .board-more,
.card > .header .board-title:focus-visible .board-more {
    opacity: 1;
}

.card > .header .note {
    margin-left: auto;
    font-size: var(--font-size-smaller);
    font-weight: var(--font-weight-regular);
    color: var(--color-text-secondary);
}

.card > .body {
    padding: 16px 20px;
}

.card > .body.flush {
    padding: 4px 0;
}

/* Колонки — обычный grid с готовой раскладкой из PHP. CSS-колонки (columns: 2)
   здесь не годятся: карточку выше колонки они разрывают, а break-inside: avoid
   в таком случае не спасает. */
.card-grid {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 0 20px;
    align-items: start;
}

.card-column {
    min-width: 0;
}

/* --------------------------------------------------------------------------
   5. Таблицы
   -------------------------------------------------------------------------- */

.table-scroll {
    overflow-x: auto;
    -webkit-overflow-scrolling: touch;
    /* Полосу прокрутки прячем: она оказывается под таблицей, а у рекордов это
       полторы тысячи строк вниз. Тянуть можно за любое место (см. app.js). */
    scrollbar-width: none;
    -ms-overflow-style: none;
}

.table-scroll::-webkit-scrollbar {
    display: none;
}

/* Курсор-«рука» только когда таблица действительно шире экрана. */
.table-scroll.grabbing {
    cursor: grabbing;
    user-select: none;
}

table.data {
    width: 100%;
    border-collapse: collapse;
    font-variant-numeric: tabular-nums;
}

table.data th,
table.data td {
    padding: 8px 20px;
    text-align: left;
    vertical-align: middle;
}

/* Шапка таблицы ничего не делает по нажатию (сортировки здесь нет), и текстовая
   каретка над ней обещала бы выделение, которое никому не нужно. */
table.data thead th {
    cursor: default;
    color: var(--color-text-secondary);
    font-weight: var(--font-weight-bold);
    font-size: var(--font-size-smaller);
    text-transform: uppercase;
    letter-spacing: 0.03em;
    white-space: nowrap;
    border-bottom: 1px solid var(--table-border);
    padding-top: 10px;
    padding-bottom: 10px;
}

/* Подпись к заголовку: что именно стоит нижним ярусом в этой колонке.
   Без неё соединённые колонки читались бы как случайный набор значений. */
.th-sub {
    color: var(--color-icon-tertiary);
    font-weight: var(--font-weight-regular);
    text-transform: none;
    letter-spacing: 0;
    margin-left: 6px;
}

table.data thead th a {
    display: inline-flex;
    align-items: center;
    gap: 4px;
}

table.data thead th a:hover {
    color: var(--color-text-important);
}

table.data thead th a.sorted {
    color: var(--color-blue);
}

table.data tbody tr {
    border-bottom: 1px solid var(--table-border);
}

table.data tbody tr:last-child {
    border-bottom: 0;
}

table.data tbody tr:hover {
    background-color: var(--table-hover);
}

/* Строка-ссылка: нажимается вся площадь, а не только ник.
   Выделение текста отключено, иначе клик по строке начинал бы его выделять. */
table.data tbody tr[data-href] {
    cursor: pointer;
    user-select: none;
    -webkit-user-select: none;
}

table.data tbody tr[data-href] a {
    cursor: pointer;
}

table.data td.numeric,
table.data th.numeric {
    text-align: right;
    white-space: nowrap;
}

/* Колонка сжимается по содержимому: ширина 1% при table-layout: auto
   заставляет браузер отдать ей минимум, а остаток забирают соседние. */
table.data td.shrink,
table.data th.shrink {
    width: 1%;
    white-space: nowrap;
}

table.data td.position {
    color: var(--color-text-secondary);
    width: 1%;
    white-space: nowrap;
    padding-right: 0;
}

table.data td.position.top-1 { color: #e0b341; font-weight: var(--font-weight-bold); }
table.data td.position.top-2 { color: #b6bec8; font-weight: var(--font-weight-bold); }
table.data td.position.top-3 { color: #c8813f; font-weight: var(--font-weight-bold); }

/* Строка в два яруса.
   ---------------------------------------------------------------------------
   Раньше каждое поле занимало свою колонку, и восьми колонок банлиста не
   хватало ширины страницы — таблицу приходилось прокручивать вбок. Теперь
   строка живёт в двух ярусах: сверху то, за что цепляется глаз при просмотре
   (ник, причина, состояние), снизу — уточнения (SteamID, тип, срок, сервер).
   Колонок стало вдвое меньше, а данные остались все до одного. */
.cell {
    display: flex;
    flex-direction: column;
    gap: 1px;
    min-width: 0;
}

/* Нижний ярус: перечисление через точку-разделитель. flex, а не строка текста,
   потому что внутри попадаются значки — флаг сервера, галочка совпадения. */
.cell-sub {
    display: flex;
    align-items: center;
    gap: 6px;
    min-width: 0;
    color: var(--color-text-secondary);
    font-size: var(--font-size-smaller);
    line-height: 1.35;
    white-space: nowrap;
}

/* Разделитель рисуем сами, а не пишем в разметку: части нижнего яруса часто
   условны (нет сервера, нет срока), и «·» в тексте оставлял бы висячие точки. */
.cell-sub > * + *::before {
    content: '·';
    margin-right: 6px;
    color: var(--color-icon-tertiary);
}

/* Между плашками разделитель не нужен: у каждой свой фон, и он отделяет их
   лучше точки. Точка остаётся там, где ярус — перечисление обычного текста. */
.cell-sub > .badge + .badge::before {
    content: none;
    margin-right: 0;
}

.cell-sub > * {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
}

/* Части, которые не жалко ужать: длинное название сервера или ник админа
   отдают место причине, а не растягивают колонку. */
.cell-sub .clip {
    display: block;
    overflow: hidden;
    text-overflow: ellipsis;
    max-width: 18ch;
}

/* Ярус без разделителей: между фразой и плашкой состояния точка была бы
   лишней — плашку и так отделяет её собственный фон. */
.cell-sub.plain > * + *::before {
    content: none;
    margin-right: 0;
}

/* Плашка внутри нижнего яруса. Свой размер ей задавать нельзя: .cell-sub уже
   набран 0.8em, и заданные .badge те же 0.8em множились бы сами на себя. */
.cell-sub .badge {
    font-size: inherit;
    padding: 1px 8px;
}

.cell-sub a {
    color: inherit;
}

.cell-sub a:hover {
    color: var(--color-text-important);
}

/* Верхний ярус: одна строка с многоточием вместо переноса. Строки списка
   должны быть одной высоты, иначе таблицу тяжело просматривать глазом. */
.cell-main {
    display: flex;
    align-items: center;
    gap: 8px;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.cell-main .grow {
    overflow: hidden;
    text-overflow: ellipsis;
    min-width: 0;
}

/* Значки и плашки в верхнем ярусе не сжимаются: место отдаёт текст рядом
   (.grow), иначе «Навсегда» превращалось бы в «Навсег…». */
.cell-main > .badge,
.cell-main > .tone,
.cell-main > .flag,
.cell-main > svg {
    flex: none;
}

/* Значок человека в составе админов — ссылка в Steam. Красить не нужно: он и
   так значок, а не текст; отзывается подсветкой под курсором. */
.person-link {
    display: inline-flex;
    color: var(--color-icon-secondary);
}

.person-link:hover {
    color: var(--color-blue);
}

/* Строка-карточка на телефоне.
   ---------------------------------------------------------------------------
   Даже четыре колонки в 375 px не помещаются: одни только ник и SteamID
   занимают половину экрана. На телефоне строка перестаёт быть строкой и
   раскладывается сеткой — кто и чем кончилось в одну линию, за что и когда
   под ними. Разметка при этом та же самая: колонки помечены ролями
   (.col-who, .col-what, .col-state, .col-when), а не порядковым номером,
   поэтому таблицы с разным набором полей пользуются одними правилами.

   Порог — 900 px, а не 640: у истории чата пять колонок, и между этими двумя
   значениями она переставала помещаться. Держать разные пороги для разных
   таблиц было бы хуже — на планшете половина разделов ехала бы строками,
   половина карточками. */
@media (max-width: 900px) {
    table.data.cards thead {
        display: none;
    }

    /* Скрытая строка остаётся скрытой: display: grid ниже перебивает
       браузерное правило для атрибута hidden, и раскрывающиеся строки
       предупреждений на телефоне были бы видны всегда. */
    table.data.cards tbody tr[hidden] {
        display: none;
    }

    table.data.cards tbody tr {
        display: grid;
        grid-template-columns: minmax(0, 1fr) auto;
        grid-template-areas:
            'who   who'
            'what  what'
            'when  state';
        gap: 4px 12px;
        padding: 12px 16px;
    }

    table.data.cards tbody td {
        display: block;
        padding: 0;
        /* Ширины из «настольного» режима (width: 1%, width: 55%) в сетке
           только мешают: место распределяет grid-template-columns. */
        width: auto;
        max-width: none;
    }

    table.data.cards td.col-who   { grid-area: who; }
    table.data.cards td.col-what  { grid-area: what; }
    table.data.cards td.col-when  { grid-area: when; }
    table.data.cards td.col-meta  { grid-area: meta; }
    table.data.cards td.col-server { grid-area: server; }

    /* У блокировок полей тоже пять: когда, кто, чем зашёл, по какому бану и
       на каком сервере. Складываются они так же, как у чата. */
    table.data.cards.blocks tbody tr {
        grid-template-areas:
            'who   when'
            'what  what'
            'state server';
    }

    table.data.cards.blocks td.col-server {
        justify-self: end;
    }

    /* У чата полей пять, а не четыре, и карточка складывается по-своему: ник и
       время в одну линию (так читается лог), под ними реплика, а откуда она —
       двумя подписями в подвале. */
    table.data.cards.chat tbody tr {
        grid-template-areas:
            'who   when'
            'what  what'
            'meta  state';
    }

    table.data.cards.chat td.col-when {
        justify-self: end;
    }

    table.data.cards.chat td.col-when .cell {
        align-items: flex-end;
    }

    /* В карточке ширины экрана хватает — предел в 16 ch тут только режет. */
    table.data.cards td.nick .name {
        max-width: none;
    }

    /* Прижимать ник вправо в карточке незачем: сообщение под ним, а не рядом,
       и прижатый ник просто уехал бы от левого края в никуда. */
    table.data.cards td.nick {
        text-align: left;
    }

    /* Отступы в карточке задаёт сетка, а не ячейки. */
    table.data.cards td.nick,
    table.data.cards td.message {
        padding: 0;
    }

    table.data.cards td.nick .player {
        justify-content: flex-start;
    }

    table.data.cards td.col-state {
        grid-area: state;
        justify-self: end;
    }

    table.data.cards td.col-state .cell {
        align-items: flex-end;
    }

    /* Дата и время встают в одну линию: отдельного яруса они не стоят, а
       нижней строкой карточки лишняя высота заметна. */
    table.data.cards td.col-when .cell {
        flex-direction: row;
        align-items: center;
        gap: 6px;
    }

    table.data.cards td.col-when .cell-main {
        color: var(--color-text-secondary);
        font-size: var(--font-size-smaller);
    }

    /* Нижний ярус переносится: на узком экране «Мут · 5 д. · MadX COOP» в одну
       строку не помещается, а обрезать сервер здесь уже нечем оправдать. */
    table.data.cards .cell-sub {
        flex-wrap: wrap;
        white-space: normal;
    }

    table.data.cards .cell-sub .clip {
        max-width: none;
    }

    /* В карточке ник занимает всю ширину экрана, и жёсткие пределы в 13 ch,
       нужные узкой колонке, теперь только режут имя без причины. */
    table.data.cards .player .name {
        max-width: none;
    }

    table.data.cards td.empty,
    table.data.cards td.wide {
        grid-column: 1 / -1;
        padding: 28px 0;
    }

    table.data.cards td.wide {
        padding: 0;
    }

    /* Пустая таблица — одна ячейка на всю строку, сетка карточки ей не нужна. */
    table.data.cards tbody tr:has(td.empty) {
        display: block;
    }
}

/* Таблицы внутри двухколоночной сетки живут в 500 px — им нужны более
   плотные отступы и более короткий предел для ников. */
.card-grid table.data th,
.card-grid table.data td {
    padding-left: 12px;
    padding-right: 12px;
}

.card-grid .player .name {
    max-width: 14ch;
}

table.data .empty {
    text-align: center;
    color: var(--color-text-secondary);
    padding: 28px 20px;
}

/* Пары «показатель — значение». */
.facts {
    width: 100%;
    border-collapse: collapse;
}

.facts th,
.facts td {
    padding: 7px 20px;
    vertical-align: baseline;
}

/* Ширину подписи не задаём числом: она досталась от профиля игрока в
   статистике, где в левой колонке были длинные фразы. Здесь подписи короткие
   («Причина», «Срок», «Steam ID»), и фиксированные 55 % отдавали им больше
   половины карточки, а значение рядом переносилось на вторую строку при
   свободном месте. Пусть колонки считаются по содержимому.

   Но слова в левой колонке не рвутся посередине. У body стоит
   word-break: break-word — он разрешает разрыв в любом месте слова, и для
   браузера наименьшая возможная ширина «Причины» становится равной одной букве.
   Стоило причине бана оказаться в две сотни знаков, как она забирала всю
   строку, а подпись рассыпалась в столбик по слогам. Здесь разрыв возвращается
   к обычному — только по пробелам, — и колонка перестаёт сжиматься уже́ целого
   слова.

   Именно так, а не nowrap: в досье игрока в левой колонке лежат не подписи, а
   данные — адреса со значками, — и запрещать им перенос вовсе было бы неверно.
   Оба свойства названы явно: `break-word` — устаревшая запись, и разные
   браузеры раскладывают её то в word-break, то в overflow-wrap. */
.facts th {
    text-align: left;
    word-break: normal;
    overflow-wrap: normal;
    font-weight: var(--font-weight-regular);
    color: var(--color-text-secondary);
}

.facts td {
    text-align: right;
    color: var(--color-text-important);
    font-variant-numeric: tabular-nums;
}

.facts tr:hover {
    background-color: var(--table-hover);
}

/* Знак вопроса с пояснением: заменил сноски со звёздочкой внизу страницы. */
.hint-mark {
    position: relative;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 14px;
    height: 14px;
    margin-left: 5px;
    border: 1px solid var(--color-icon-tertiary);
    border-radius: 50%;
    color: var(--color-icon-secondary);
    font-size: 10px;
    line-height: 1;
    cursor: help;
    vertical-align: 1px;
    transition: color 150ms, border-color 150ms;
}

.hint-mark::before {
    content: "?";
}

.hint-mark:hover,
.hint-mark:focus-visible {
    color: var(--color-text-important);
    border-color: var(--color-text-secondary);
    outline: none;
}

/* Общий вид всплывающих подсказок: пояснение под знаком вопроса, ячейка
   шкалы активности, точка «в игре», подсказка на графике очков и общая
   подсказка таблиц (.tip). Раньше каждая описывала фон, рамку и кегль сама —
   и расходились они тоже сами. */
.hint-tip,
.tip {
    background-color: var(--background-nav-mobile);
    border: 1px solid var(--border-card);
    border-radius: var(--radius-control);
    color: var(--color-text-important);
    font-size: var(--font-size-tip);
    font-weight: var(--font-weight-regular);
    line-height: 1.35;
    padding: 6px 10px;
    z-index: 3;
}

/* Подсказки, которые появляются по наведению. У графика очков подсказка
   висит постоянно и прячется атрибутом hidden, поэтому её здесь нет. */
.hint-tip{
    position: absolute;
    bottom: calc(100% + 8px);
    left: 50%;
    transform: translateX(-50%);
    width: max-content;
    opacity: 0;
    pointer-events: none;
    transition: opacity 150ms;
}

.hint-tip {
    max-width: 240px;
    color: var(--color-text-default);
    text-align: left;
    white-space: normal;
}

.hint-mark:hover .hint-tip,
.hint-mark:focus-visible .hint-tip {
    opacity: 1;
    transition-delay: var(--tip-delay);
}

/* Общая подсказка для всего, что раньше объяснялось системным title.
   ---------------------------------------------------------------------------
   Системная подсказка появляется через секунду, набрана шрифтом Windows и в
   тёмной теме выглядит белым прямоугольником посреди страницы — то же самое
   уже было замечено про точку «в игре».

   Здесь она не может быть вложенной, как .hint-tip: подсказки висят на
   содержимом ячеек, а те режут текст многоточием (overflow: hidden) и лежат
   внутри .table-scroll с overflow-x. Любой вложенный элемент там обрезался бы
   вместе с текстом. Поэтому подсказка одна на страницу, лежит прямо в body и
   позиционируется fixed — обрезать её нечему. Координаты считает app.js. */
.tip {
    position: fixed;
    top: 0;
    left: 0;
    max-width: min(320px, calc(100vw - 16px));
    white-space: normal;
    opacity: 0;
    pointer-events: none;
    transition: opacity 150ms;
}

.tip.is-visible {
    opacity: 1;
}

.facts .note {
    display: block;
    color: var(--color-text-secondary);
    font-size: var(--font-size-smaller);
    font-variant-numeric: normal;
}

/* --------------------------------------------------------------------------
   6. Игрок в строке таблицы
   -------------------------------------------------------------------------- */

.player {
    display: flex;
    align-items: center;
    gap: 8px;
    min-width: 0;
}

.player .flag {
    height: 12px;
    width: 16px;
    flex: none;
    object-fit: cover;
    border-radius: 2px;
}

.player .name {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    color: var(--color-text-important);
    /* Без ограничения ширины колонка растягивается под самый длинный ник
       в выдаче, и многоточие никогда не срабатывает. Полное имя — в title. */
    max-width: 24ch;
}

.player .alias {
    color: var(--color-text-secondary);
    font-size: var(--font-size-smaller);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

/* Флаг в строке «Откуда». */
.facts .flag {
    height: 12px;
    width: 16px;
    object-fit: cover;
    border-radius: 2px;
    vertical-align: baseline;
}

/* --- Сводка активности ------------------------------------------------ */

.activity {
    padding: 4px 20px 18px;
}

/* --------------------------------------------------------------------------
   12. Поиск, кнопки, фильтры
   -------------------------------------------------------------------------- */

.toolbar {
    display: flex;
    align-items: center;
    gap: 10px;
    flex-wrap: wrap;
}

.search {
    display: flex;
    align-items: center;
    gap: 8px;
    flex-wrap: wrap;
}

.field {
    position: relative;
    display: flex;
    align-items: center;
}

.field svg {
    position: absolute;
    left: 10px;
    height: 16px;
    width: 16px;
    color: var(--color-icon-secondary);
    pointer-events: none;
}

.field.wide {
    flex: 1;
    min-width: 220px;
}

.field.wide input {
    width: 100%;
}

.field input {
    width: 220px;
    max-width: 100%;
    padding: 8px 12px 8px 32px;
    color: var(--color-text-default);
    background-color: var(--input-background);
    border: 1px solid var(--input-border);
    border-radius: var(--radius-control);
    transition: border-color 150ms, background-color 150ms;
}

.field input::placeholder {
    color: var(--color-text-secondary);
}

.field input:focus {
    outline: none;
    background-color: var(--input-background-focus);
    border-color: var(--input-border-focus);
}

.button {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 6px;
    padding: 8px 14px;
    /* Кегль и интерлиньяж свои, а не унаследованные. Кегль — потому что в шапке
       карточки текст крупнее (--font-size-large), и кнопка рядом с заголовком
       вырастала против такой же кнопки в теле. Интерлиньяж — потому что у
       <button> браузер ставит normal, а у ссылки-кнопки наследуется 1.4em от
       body, и две одинаковые с виду кнопки различались на три пикселя в
       высоту, смотря чем они сделаны. */
    font-size: var(--font-size-default);
    line-height: normal;
    border: 1px solid var(--input-border);
    border-radius: var(--radius-control);
    background-color: var(--input-background);
    color: var(--color-text-important);
    cursor: pointer;
    transition: background-color 150ms, border-color 150ms;
}

.button:hover {
    background-color: var(--background-nav-hover);
}

.button.primary {
    background-color: rgb(52, 52, 54);
    border-color: rgb(52, 52, 54);
    color: hsl(0, 0%, 60%);
}

.button.primary:hover {
    filter: brightness(1.15);
}

.button svg {
    height: 16px;
    width: 16px;
}

/* Кнопка из одного значка. Квадратная и без боковых полей: подпись у неё
   снаружи — заголовок карточки слева, — а padding от кнопки с текстом
   растягивал бы квадрат в прямоугольник. */
.button.icon-only {
    /* Высота задана числом, а не отступами: без подписи внутри осталась бы одна
       высота значка, и кнопка вышла бы вдвое ниже соседних. 35 px — ровно
       столько же, сколько у кнопки с текстом. */
    height: 35px;
    width: 35px;
    padding: 0;
}

.chips {
    display: flex;
    gap: 6px;
    flex-wrap: wrap;
}

.chip {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    padding: 5px 12px;
    border-radius: 999px;
    border: 1px solid var(--border-card);
    color: var(--color-text-secondary);
    font-size: var(--font-size-small);
    white-space: nowrap;
    cursor: pointer;
    transition: background-color 150ms, color 150ms;
}

.chip:hover {
    background-color: var(--background-nav-hover);
    color: var(--color-text-important);
}

/* Переключатель, который в текущем сочетании фильтров ничего не даст. Не
   прячем: ряд менял бы длину прямо под курсором. */
.chip.disabled {
    opacity: 0.45;
    cursor: default;
}

.chip.disabled:hover {
    background-color: transparent;
    color: var(--color-text-secondary);
}

.chip.active {
    background-color: var(--background-blue);
    border-color: var(--border-blue);
    color: var(--color-blue);
}

.filter-note {
    display: flex;
    align-items: center;
    gap: 10px;
    flex-wrap: wrap;
    padding: 12px 20px;
    border-bottom: 1px solid var(--border-card);
    color: var(--color-text-secondary);
    font-size: var(--font-size-small);
}

/* --------------------------------------------------------------------------
   13. Пагинация
   -------------------------------------------------------------------------- */

.pagination {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 4px;
    flex-wrap: wrap;
    padding: 14px 20px;
    border-top: 1px solid var(--border-card);
}

.pagination a,
.pagination span {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 34px;
    height: 34px;
    padding: 0 8px;
    border-radius: var(--radius-control);
    color: var(--color-text-secondary);
    font-variant-numeric: tabular-nums;
}

.pagination a:hover {
    background-color: var(--background-nav-hover);
    color: var(--color-text-important);
}

/* Текущая страница — не ссылка: нажимать её незачем, и каретка над числом
   выглядела бы приглашением. */
.pagination .current {
    cursor: default;
    background-color: var(--background-blue);
    color: var(--color-blue);
    font-weight: var(--font-weight-bold);
}

/* Многоточие между номерами страниц — подпись, а не пропущенная кнопка. */
.pagination .gap {
    min-width: 20px;
    cursor: default;
}

/* --------------------------------------------------------------------------
   14. Вкладки
   -------------------------------------------------------------------------- */

.tabs {
    display: flex;
    gap: 4px;
    border-bottom: 1px solid var(--border-card);
    padding: 0 12px;
    overflow-x: auto;
    /* Прокрутка нужна только на узком экране, а полоса под вкладками мешает
       всегда — прячем её, жест и колесо продолжают работать. */
    scrollbar-width: none;
    -ms-overflow-style: none;
}

.tabs::-webkit-scrollbar {
    display: none;
}

.tabs button {
    position: relative;
    padding: 12px 12px 11px;
    border: 0;
    background: none;
    color: var(--color-text-secondary);
    white-space: nowrap;
    cursor: pointer;
    border-bottom: 2px solid transparent;
    margin-bottom: -1px;
    transition: color 150ms;
}

.tabs button:hover {
    color: var(--color-text-important);
}

.tabs button[aria-selected="true"] {
    color: var(--color-text-important);
    border-bottom-color: var(--color-blue);
    font-weight: var(--font-weight-medium);
}

/* --------------------------------------------------------------------------
   15. Мобильная навигация
   -------------------------------------------------------------------------- */

.nav-mobile {
    position: fixed;
    left: 0;
    right: 0;
    bottom: 0;
    height: 56px;
    display: var(--mobile-display-flex);
    justify-content: space-around;
    align-items: stretch;
    /* Первое объявление — запасное для браузеров без color-mix(). */
    background-color: var(--background-nav-mobile);
    background-color: color-mix(in srgb, var(--background-nav-mobile) 82%, transparent);
    backdrop-filter: saturate(180%) blur(20px);
    -webkit-backdrop-filter: saturate(180%) blur(20px);
    border-top: 1px solid var(--border-card);
    padding-bottom: env(safe-area-inset-bottom);
    z-index: 10;
}

.nav-mobile a {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 2px;
    flex: 1;
    font-size: var(--font-size-tiny);
    color: var(--color-text-secondary);
}

.nav-mobile a.active {
    color: var(--color-blue);
}

.nav-mobile svg {
    height: 22px;
    width: 22px;
}

/* Оптическое выравнивание значков навигации.
   ---------------------------------------------------------------------------
   Рамка у всех одна (1.4em), но рисунок внутри неё занимает разную площадь: у
   щита банлиста 10x12 единиц из шестнадцати, у облачка чата и знака запрета —
   все 14x14. В ряду это читается как значки разного кегля.

   Приводим каждый к «Главной»: коэффициент — отношение среднего
   геометрического сторон её рисунка (12,5) к своему. Сплошное и контурное
   начертания одного значка совпадают по габаритам, поэтому коэффициент один
   на оба. Звезду VIP не трогаем: её 0,98 глазом не различить. */
.nav a[data-icon="shield"] svg,
.nav-mobile a[data-icon="shield"] svg {
    transform: scale(1.14);
}

.nav a[data-icon="comment"] svg,
.nav-mobile a[data-icon="comment"] svg,
.nav a[data-icon="prohibited"] svg,
.nav-mobile a[data-icon="prohibited"] svg {
    transform: scale(0.89);
}

.nav a[data-icon="sync"] svg,
.nav-mobile a[data-icon="sync"] svg {
    transform: scale(0.92);
}

.nav a[data-icon="search"] svg,
.nav-mobile a[data-icon="search"] svg,
.nav a[data-icon="team"] svg,
.nav-mobile a[data-icon="team"] svg {
    transform: scale(0.96);
}

/* Шапка страницы: логотип слева, учётная запись справа.
   На большом экране логотип уже стоит в сайдбаре, и здесь остаётся только
   правая половина — но высота держится та же (100 px), иначе содержимое
   разъехалось бы с первым пунктом меню. */
.topbar {
    display: flex;
    align-items: center;
    justify-content: flex-end;
    gap: 12px;
    min-height: 100px;
}

.topbar .brand {
    display: none;
    margin-right: auto;
    font-size: var(--font-size-huge);
    font-weight: var(--font-weight-bold);
    color: var(--color-brand);
}

/* --------------------------------------------------------------------------
   16. Адаптив
   -------------------------------------------------------------------------- */

@media (max-width: 1200px) {
    :root {
        --font-size-default: 16px;
        --mobile-display-flex: flex;
    }

    .layout {
        padding: 0;
        gap: 0;
    }

    .sidebar {
        display: none;
    }

    .topbar {
        min-height: auto;
        padding: 14px 20px;
    }

    .topbar .brand {
        display: block;
    }

    .content {
        max-width: 100%;
        padding: 0 16px 80px;
    }

    .page-head {
        padding: 4px 0 16px;
    }

    .card-grid {
        grid-template-columns: minmax(0, 1fr);
    }

}

/* Порог подобран по фактической ширине таблицы при базовом шрифте 16 px:
   ниже него колонкам нужны более плотные отступы. */
@media (max-width: 900px) {
    /* Подпись к заголовку — не данные, а пояснение, и на узком экране она
       задаёт колонке минимальную ширину больше, чем сами значения. */
    .th-sub {
        display: none;
    }

    table.data th,
    table.data td {
        padding-left: 12px;
        padding-right: 12px;
    }

    table.data td.position {
        padding-right: 0;
    }

    .player .name {
        max-width: 17ch;
    }
}

@media (max-width: 640px) {
    .content {
        padding: 0 0 80px;
    }

    .page-head {
        padding: 4px 16px 12px;
    }

    .card {
        border-radius: 0;
        border-left: 0;
        border-right: 0;
        margin-bottom: 12px;
    }

    .topbar,
    .card > .header,
    .card > .body,
    table.data th,
    table.data td,
    .facts th,
    .facts td,
    .filter-note,
    .pagination {
        padding-left: 16px;
        padding-right: 16px;
    }

    /* На телефоне режем пределы ещё сильнее — полное значение остаётся в title. */
    .player .name {
        max-width: 13ch;
    }

    /* В рекордах в строке соседствуют две длинные текстовые колонки —
       название карты и ник, поэтому обеим нужен более жёсткий предел. */
    table[data-records] .player .name {
        max-width: 10ch;
    }

    .activity {
        padding: 4px 16px 16px;
    }
}

@media (prefers-reduced-motion: reduce) {
    * {
        transition-duration: 0.01ms !important;
        animation-duration: 0.01ms !important;
    }
}

/* --------------------------------------------------------------------------
   17. Банлист
   -------------------------------------------------------------------------- */

.filters {
    display: flex;
    flex-direction: column;
    gap: 12px;
}

/* Отбивка от того, что стоит под формой.
   ---------------------------------------------------------------------------
   В банлисте плашки типов лежат внутри формы, и расстояние между рядами задаёт
   её gap. В истории чата они — соседи формы, а не её содержимое: переключаются
   ссылкой, а не полем. Своего отступа у .chips нет, и без этого правила они
   прилипали к строке поиска вплотную. `:not(:last-child)` нужен, чтобы в
   банлисте, где под формой ничего нет, не появлялся пустой хвост. */
.card > .body > .filters:not(:last-child),
.card > .body > .chips:not(:last-child) {
    margin-bottom: 12px;
}

/* Поиск и кнопка — одно действие, поэтому и строка одна. */
.filter-search {
    display: flex;
    align-items: center;
    gap: 8px;
}

.filter-search .field {
    flex: 1;
    min-width: 0;
}

.filter-search .button {
    flex: none;
}

/* Фильтры.
   ---------------------------------------------------------------------------
   Видна всегда только строка поиска. Всё остальное — тип, сервер, кто выдал —
   разворачивается значком справа от «Найти». Раньше здесь стояли четыре
   выпадающих списка и два ряда плашек в постоянно раскрытом виде, и шапка
   раздела весила больше самой таблицы.

   Открывается это скриптом, а не <details>: значок должен стоять в строке
   поиска, а панель раскрываться под ней во всю ширину, и вложенностью
   <summary> такое не выкладывается. */
.filter-toggle {
    flex: none;
    position: relative;
    padding: 8px 10px;
    color: var(--color-text-secondary);
}

.filter-toggle:hover,
.filter-toggle[aria-expanded="true"] {
    color: var(--color-text-important);
}

.filter-toggle svg {
    height: 17px;
    width: 17px;
}

/* Фильтр применён — видно и с закрытой панелью. */
.filter-toggle.has-filters {
    border-color: var(--border-blue);
    color: var(--color-blue);
}

.filter-count {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 16px;
    height: 16px;
    padding: 0 4px;
    margin-left: 6px;
    border-radius: 999px;
    background-color: var(--background-blue);
    color: var(--color-blue);
    font-size: var(--font-size-tiny);
}

/* Поля идут столбиком, а не в ряд: место экономить больше не на чем — панель
   и так закрыта, пока не понадобится, — а подпись над каждым полем читается
   куда лучше, чем догадка по первому пункту списка. */
.filter-panel {
    display: flex;
    flex-direction: column;
    gap: 14px;
    padding-top: 14px;
    border-top: 1px solid var(--border-card);
}

/* Свой display перебивает браузерное [hidden] { display: none }, и без этого
   правила панель оставалась бы раскрытой всегда. */
.filter-panel[hidden] {
    display: none;
}

.filter-field {
    display: flex;
    flex-direction: column;
    gap: 6px;
}

.filter-label {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    color: var(--color-text-secondary);
    font-size: var(--font-size-small);
}

.filter-label svg {
    height: 15px;
    width: 15px;
    color: var(--color-icon-secondary);
}

/* Список занимает ширину панели, но не растягивается без предела: на широком
   экране поле в тысячу пикселей ради «Любой тип» выглядело бы нелепо, а 300
   хватает самому длинному названию сервера. */
.filter-field .select {
    width: 100%;
    max-width: 300px;
}

.filter-actions {
    display: flex;
    gap: 8px;
}

@media (max-width: 640px) {
    .filter-actions {
        flex-wrap: wrap;
    }

    .filter-actions .button {
        flex: 1;
    }
}

/* Выпадающие списки повторяют начертание полей поиска: стрелку рисуем сами,
   потому что системная в тёмной теме остаётся светлой и выбивается. */
.select {
    appearance: none;
    -webkit-appearance: none;
    padding: 8px 30px 8px 12px;
    color: var(--color-text-default);
    background-color: var(--input-background);
    background-image: var(--select-arrow);
    background-repeat: no-repeat;
    background-position: right 8px center;
    background-size: 16px;
    border: 1px solid var(--input-border);
    border-radius: var(--radius-control);
    max-width: 100%;
    cursor: pointer;
    transition: border-color 150ms, background-color 150ms;
}

.select:hover {
    background-color: var(--input-background-focus);
}

.select:focus {
    outline: none;
    border-color: var(--input-border-focus);
}

.select option {
    background-color: var(--background-card);
    color: var(--color-text-default);
}

.chip svg {
    height: 14px;
    width: 14px;
}

/* Состояние наказания. Цвет несёт смысл, поэтому дублируется словом внутри. */
.badge {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    padding: 3px 9px;
    border-radius: 999px;
    background-color: var(--background-default);
    color: var(--color-text-secondary);
    font-size: var(--font-size-smaller);
    white-space: nowrap;
}

.badge-red {
    background-color: rgb(236 94 93 / 14%);
    color: var(--color-red);
}

.badge-orange {
    background-color: rgb(231 137 58 / 14%);
    color: var(--color-orange);
}

.badge-green {
    background-color: rgb(120 183 87 / 14%);
    color: var(--color-green);
}

/* Голубая плашка — про то, что можно открыть и почитать, а не про состояние
   наказания: этим она и отличается от красной, оранжевой и зелёной. */
.badge-blue {
    background-color: var(--background-blue);
    color: var(--color-blue);
}

/* Плашки проектов в составе админов. Подложка у них общая, как у обычной
   плашки, — меняется только цвет надписи: проект не состояние, красить его
   заливкой наравне с «снят» и «действует» значило бы приравнять одно к
   другому. Класс собирается из названия группы (pages/admins.eta), поэтому у
   нового проекта без своего цвета плашка просто останется серой. */
.group-bcm { color: var(--color-group-bcm); }
.group-madx { color: var(--color-group-madx); }
.group-swat { color: var(--color-group-swat); }

/* Тип наказания: иконка в цвете плюс подпись. */
.tone {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    white-space: nowrap;
}

.tone svg {
    height: 15px;
    width: 15px;
    flex: none;
}
/* Цвет по типу наказания: класс собирается из `tone` в lib/dictionary.js
   (`tone-<%= type.tone %>` в шаблонах), поэтому здесь должны быть все пять —
   тип без своего правила остался бы цвета текста. */
.tone-red { color: var(--color-red); }
.tone-orange { color: var(--color-orange); }
.tone-violet { color: var(--color-violet); }
.tone-blue { color: var(--color-blue); }
.tone-grey { color: var(--color-text-secondary); }

/* Причина — самая длинная колонка таблицы. Обрезаем её, а не переносим:
   иначе строки в списке скачут по высоте и таблицу тяжело просматривать.
   Ширину задаём в процентах: колонка забирает остаток строки и сжимается
   вместе с ней, а внутренние .grow и .clip режут текст многоточием. */
table.data td.reason {
    width: 55%;
    max-width: 0;
}

/* Дата и время друг под другом: одной строкой «15.08.2026 в 14:05» эта
   колонка занимала 176 px — больше, чем ник игрока. */
table.data td.when .cell-main {
    white-space: nowrap;
}

.server {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    white-space: nowrap;
}

.server img {
    height: 16px;
    width: 16px;
    flex: none;
    opacity: 0.85;
}

/* SteamID и IP — моноширинным: так видно, что это идентификатор, а не текст,
   и одинаковые адреса легко сравнить взглядом. */
.mono {
    font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
    font-size: var(--font-size-small);
}

.action-title {
    display: flex;
    align-items: center;
    gap: 10px;
}

.action-title svg {
    height: 24px;
    width: 24px;
}

/* Мелкие значки-действия в карточке наказания: поиск, ссылка на профиль. */
.fact-link {
    color: var(--color-icon-tertiary);
    margin-left: 4px;
    vertical-align: middle;
}

.fact-link:hover {
    color: var(--color-blue);
}

.fact-link svg {
    height: 14px;
    width: 14px;
}

.facts .flag {
    vertical-align: -1px;
}

@media (max-width: 900px) {
    .toolbar .select {
        flex: 1;
        min-width: 140px;
    }

}

/* На телефоне «15.08.2026 в 14:05» — самая широкая ячейка строки. Разрешаем ей
   перенос по слову «в»: две короткие строки дешевле, чем лишние 90 px ширины
   у всей таблицы. */
@media (max-width: 640px) {
    table.data td.date,
    table.data th.date {
        white-space: normal;
        max-width: 11ch;
    }
}

/* Комментарии админов к наказанию.
   ---------------------------------------------------------------------------
   Это не чат: обсуждение идёт днями, читается сверху вниз как переписка в
   деле, и оформлено соответственно — без пузырей, аватарок и разделения на
   «свои-чужие». Вся разница между записями — имя автора и время. */

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

.comment {
    padding: 12px 20px;
    border-bottom: 1px solid var(--table-border);
}

.comment:last-child {
    border-bottom: 0;
}

.comment-head {
    display: flex;
    align-items: baseline;
    gap: 8px;
    flex-wrap: wrap;
    font-size: var(--font-size-smaller);
    color: var(--color-text-secondary);
    margin-bottom: 2px;
}

.comment-author {
    color: var(--color-blue);
    font-weight: var(--font-weight-medium);
    font-size: var(--font-size-default);
}

.comment-author:hover {
    text-decoration: underline;
}

/* Переводы строк держит CSS, а не <br> в разметке: так в выводе нет ни одного
   тега, которого не было в тексте админа. break-word обязателен — в
   комментарии попадаются ссылки длиннее колонки. */
.comment-text {
    white-space: pre-wrap;
    word-break: break-word;
    overflow-wrap: anywhere;
}

.comments-empty {
    padding: 24px 20px;
    text-align: center;
    color: var(--color-text-secondary);
}

.comment-form {
    display: flex;
    flex-direction: column;
    gap: 10px;
    padding: 16px 20px;
    border-top: 1px solid var(--table-border);
}

.comment-form textarea {
    width: 100%;
    resize: none;
    padding: 10px 12px;
    color: var(--color-text-default);
    background-color: var(--input-background);
    border: 1px solid var(--input-border);
    border-radius: var(--radius-control);
    font-family: inherit;
    font-size: inherit;
    line-height: var(--line-height-default);
    transition: border-color 150ms, background-color 150ms;
}

.comment-form textarea::placeholder {
    color: var(--color-text-secondary);
}

.comment-form textarea:focus {
    outline: none;
    background-color: var(--input-background-focus);
    border-color: var(--input-border-focus);
}

.comment-actions {
    display: flex;
    justify-content: flex-end;
}

@media (max-width: 640px) {
    .comment,
    .comments-empty,
    .comment-form {
        padding-left: 16px;
        padding-right: 16px;
    }
}

/* --------------------------------------------------------------------------
   18. Вход и учётная запись
   -------------------------------------------------------------------------- */

.auth {
    display: flex;
    justify-content: center;
    padding: 40px 0;
}

.auth-card {
    width: 100%;
    max-width: 380px;
}

.auth-form {
    display: flex;
    flex-direction: column;
    gap: 14px;
}

.auth-field {
    display: flex;
    flex-direction: column;
    gap: 5px;
}

.auth-field > span {
    color: var(--color-text-secondary);
    font-size: var(--font-size-small);
}

.auth-field input {
    padding: 9px 12px;
    color: var(--color-text-default);
    background-color: var(--input-background);
    border: 1px solid var(--input-border);
    border-radius: var(--radius-control);
    transition: border-color 150ms, background-color 150ms;
}

.auth-field input:focus {
    outline: none;
    background-color: var(--input-background-focus);
    border-color: var(--input-border-focus);
}

.auth-error {
    display: flex;
    align-items: center;
    gap: 8px;
    margin: 0 0 14px;
    padding: 10px 12px;
    border-radius: var(--radius-control);
    background-color: rgb(236 94 93 / 12%);
    color: var(--color-red);
}

.auth-error svg {
    height: 16px;
    width: 16px;
    flex: none;
}

.auth-note {
    margin: 16px 0 0;
    font-size: var(--font-size-small);
}

/* Кто вошёл и кнопка выхода.
   ---------------------------------------------------------------------------
   Блок жил подвалом сайдбара: вход искали внизу левого столбца, хотя на самом
   playbcm.net он всегда справа сверху. Теперь он стоит там же — в шапке, на
   одной линии с логотипом, и разметка у большого экрана и телефона одна.

   Все части — значок, ник и выход — лежат в коробках одной высоты (32 px) и
   центрируются в них. Раньше высоты были разные: строка с ником 19.6 px (это
   line-height от body), кнопка выхода 24 px, — каждая центрировалась сама по
   себе, и три элемента visually расходились. У ника поэтому line-height: 1 —
   его коробка должна быть ростом со шрифт, а не с интерлиньяж абзаца.

   Просветы тоже одинаковые: между значком и ником 8 px задаёт gap, между ником
   и значком выхода — те же 8 px, но их даёт внутренний отступ кнопки. Поэтому
   у самого .account gap нулевой: иначе второй просвет оказался бы вдвое шире
   первого, и блок читался бы как «значок с ником» и отдельно висящая кнопка. */
.account {
    display: flex;
    align-items: center;
    color: var(--color-text-secondary);
    font-size: var(--font-size-small);
    min-width: 0;
}

.account form {
    display: flex;
    margin: 0;
}

.account svg {
    height: 16px;
    width: 16px;
    flex: none;
}

.account-name,
.account-login {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    height: 32px;
    margin-right: 8px;
    line-height: 1;
    min-width: 0;
}

/* Своё имя ничего не делает по нажатию, и текстовая каретка над ним обещала бы
   выделение, за которым сюда не приходят. У ссылки входа рядом курсор свой. */
.account-name {
    cursor: default;
}

.account-name > span,
.account-login > span {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.account-login:hover {
    color: var(--color-text-important);
}

/* Квадрат 32×32 — та же высота, что у ника рядом, и значок ровно посередине. */
.account-exit {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    height: 32px;
    width: 32px;
    padding: 0;
    border: 0;
    border-radius: var(--radius-control);
    background: none;
    color: var(--color-icon-tertiary);
    cursor: pointer;
    transition: color 150ms, background-color 150ms;
}

.account-exit:hover {
    background-color: var(--background-nav-hover);
    color: var(--color-red);
}

/* --------------------------------------------------------------------------
   19. История чата
   -------------------------------------------------------------------------- */

/* Сообщение переносится по словам: в отличие от причины бана его читают
   целиком, и обрезать середину фразы здесь нельзя. */
table.data td.message {
    white-space: normal;
    word-break: break-word;
    min-width: 20ch;
    /* Забирает всю свободную ширину: соседи сжаты по содержимому, и без этого
       остаток растекался бы между ними. */
    width: 100%;
}

/* Ник и сообщение — одна реплика, и читаются они слитно. Поэтому между ними
   отступ вдвое меньше обычного: при стандартных 20 px с каждой стороны
   сообщение отъезжало от ника на добрых сорок.

   Заголовкам те же отступы, что и ячейкам, иначе подписи колонок разъезжаются
   с содержимым — «Сообщение» стояло бы на двенадцать пикселей правее реплик. */
table.data th.nick,
table.data td.nick {
    padding-right: 8px;
}

table.data th.message,
table.data td.message {
    padding-left: 8px;
}

/* Колонка ника сжата по содержимому (.shrink), то есть её ширину задаёт самый
   длинный ник на странице. Предел нужен, иначе одна «Пиииисечка_2007_Красотка»
   отодвигала бы все сообщения страницы. Полный ник — в подсказке. */
table.data td.nick .name {
    max-width: 16ch;
}

/* Адрес и SteamID в истории чата — ссылки, но подчёркивать и красить их
   незачем: колонка целиком из ссылок, и цвет тут был бы шумом. Подсвечиваем
   только под курсором. */
table.data td.col-meta a:hover {
    color: var(--color-text-important);
}

/* Ник прижат вправо, к своей реплике.
   ---------------------------------------------------------------------------
   Слева ники стояли бы столбиком, но короткий «Кэп» отрывался бы от сообщения
   на всю ширину колонки, а длинный подходил вплотную — расстояние гуляло от
   строки к строке. Прижатые вправо, они все заканчиваются на одной линии, и
   до реплики от любого ника одинаковые 16 px. Сообщения при этом по-прежнему
   в одном ряду: колонка-то общая. Так набирают логи IRC — и по той же
   причине. */
table.data th.nick,
table.data td.nick {
    text-align: right;
}

table.data td.nick .player {
    justify-content: flex-end;
}

table.data td.message .tone {
    vertical-align: -3px;
    margin-right: 4px;
}

table.data td.message .muted svg {
    height: 14px;
    width: 14px;
    vertical-align: -2px;
    margin-right: 3px;
}

/* --------------------------------------------------------------------------
   20. Блокировки, состав админов, перенос статистики
   -------------------------------------------------------------------------- */

/* Совпало ли то, чем игрок пришёл, с тем, на что выдан бан. Цвет здесь не
   украшение: расхождение и есть признак обхода наказания. */
.match {
    display: inline-flex;
    align-items: center;
    margin-left: 6px;
    font-size: var(--font-size-smaller);
    vertical-align: 1px;
}

.match-same {
    color: var(--color-green);
}

.match-same svg {
    height: 13px;
    width: 13px;
}

/* Крест — противовес галочке, поэтому и выглядит так же: только значок в
   цвете, без плашки. Раньше здесь стояло слово «другой» в оранжевой пилюле, и
   рядом с галочкой это читалось как два разных вида сведений. */
.match-other {
    color: var(--color-orange);
}

.match-other svg {
    height: 13px;
    width: 13px;
}

/* Строка с обходом: тонкая полоса слева, чтобы такие попытки было видно при
   быстром просмотре списка, не вчитываясь в каждую ячейку. */
table.data tbody tr.row-evasion td:first-child {
    box-shadow: inset 2px 0 0 var(--color-orange);
}

.flags {
    letter-spacing: 0.04em;
}

/* Кнопка действия в строке таблицы. */
.row-action {
    display: inline-flex;
    padding: 4px;
    border: 0;
    border-radius: var(--radius-control);
    background: none;
    color: var(--color-icon-tertiary);
    cursor: pointer;
    transition: color 150ms, background-color 150ms;
}

.row-action:hover {
    background-color: var(--background-nav-hover);
    color: var(--color-red);
}

.row-action svg {
    height: 14px;
    width: 14px;
}

/* Полоса с результатом действия над страницей. */
.notice {
    display: flex;
    align-items: center;
    gap: 8px;
    margin-bottom: 20px;
    padding: 12px 16px;
    border-radius: var(--radius-card);
    border: 1px solid var(--border-card);
}

.notice svg {
    height: 16px;
    width: 16px;
    flex: none;
}

.notice-ok {
    background-color: rgb(120 183 87 / 10%);
    border-color: rgb(120 183 87 / 25%);
    color: var(--color-green);
}

.notice-error {
    background-color: rgb(236 94 93 / 10%);
    border-color: rgb(236 94 93 / 25%);
    color: var(--color-red);
}

/* Карточка с наказанием самого посетителя. Полоса слева вместо рамки вокруг
   всего: она отделяет блок от списка, не превращая его в отдельное окно. */
/* Полоса на всю ширину содержимого — та, что висит над каждой страницей.
   Кнопка в ней прижата вправо: текст объясняет, кнопка уводит читать. */
.notice-page {
    margin-bottom: 20px;
}

.notice-page .button {
    margin-left: auto;
    flex: none;
}

.notice-card {
    border-left: 2px solid var(--color-red);
}

/* Внутри карточки уведомление — её заголовок, а не всплывшее сообщение:
   собственных отступов и рамки ему не нужно. */
.notice-card .notice {
    margin: 0;
    border: 0;
    border-radius: 0;
    border-bottom: 1px solid var(--border-card);
}

/* Две карточки игроков рядом: откуда и куда переносим. */
.transfer-pair {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
    gap: 16px;
    margin-bottom: 16px;
}

.transfer-side {
    border: 1px solid var(--border-card);
    border-radius: var(--radius-card);
    overflow: hidden;
}

.transfer-caption {
    padding: 8px 20px;
    background-color: var(--background-default);
    color: var(--color-text-secondary);
    font-size: var(--font-size-small);
    font-weight: var(--font-weight-medium);
}

/* Разделов стало восемь, и на телефоне они перестают помещаться в нижнюю
   панель. Разрешаем ей прокрутку вместо того, чтобы прятать пункты: иначе
   часть панели была бы недоступна с телефона совсем. */
.nav-mobile {
    overflow-x: auto;
    scrollbar-width: none;
    -ms-overflow-style: none;
}

.nav-mobile::-webkit-scrollbar {
    display: none;
}

.nav-mobile a {
    min-width: 68px;
}

/* Поле выбора дня в истории чата. Значок календаря рисуем свой слева, а
   системный убираем: в тёмной теме он остаётся чёрным и не читается. */
.date-field input {
    /* Выпадающий календарь рисует сам браузер, и стилями его не достать —
       единственный рычаг — color-scheme. С ним Chrome берёт тёмную палитру
       вместо системной светлой, из-за которой календарь и выглядел чужим.
       Полностью свой календарь — отдельная работа, здесь его нет. */
    color-scheme: dark;
    width: 165px;
    padding: 8px 12px 8px 32px;
    color: var(--color-text-default);
    background-color: var(--input-background);
    border: 1px solid var(--input-border);
    border-radius: var(--radius-control);
    transition: border-color 150ms, background-color 150ms;
}

.date-field input:focus {
    outline: none;
    background-color: var(--input-background-focus);
    border-color: var(--input-border-focus);
}

/* Календарь flatpickr — в оформлении панели.
   ---------------------------------------------------------------------------
   Библиотека приносит свою вёрстку и светлую тему. Тёмная тема из её же
   комплекта не подходит: это просто другой набор цветов, а не наш. Здесь
   календарь пересобран под панель — карточка, отступы, скругления и элементы
   управления те же, что у остальных полей раздела. Разметку и поведение
   по-прежнему делает библиотека, мы только переопределяем оформление.

   Главное отличие от прежней правки: сетка дней и строка с днями недели
   собраны заново на grid. У библиотеки это два независимых ряда с зашитыми в
   пиксели ширинами (307.875 px на семь колонок), и стоило задать дню свой
   размер, как подписи «пн вт ср» переставали стоять над своими столбцами, а
   сама сетка вылезала за правый край карточки на два десятка пикселей. Одна
   общая сетка на семь колонок делает это невозможным по устройству. */

/* Ширина не задаётся числом: календарь позиционирован абсолютно и потому
   растягивается по содержимому — семь столбцов по 36 px плюс отступы. */
.flatpickr-calendar {
    width: auto;
    padding: 10px;
    background-color: var(--background-card);
    border: 1px solid var(--border-card);
    border-radius: var(--radius-card);
    box-shadow: 0 10px 30px rgb(0 0 0 / 50%);
    font-family: var(--font-family);
    font-size: var(--font-size-default);
}

/* Уголок-стрелку убираем совсем: у карточек панели её нигде нет. */
.flatpickr-calendar.arrowTop::before,
.flatpickr-calendar.arrowTop::after,
.flatpickr-calendar.arrowBottom::before,
.flatpickr-calendar.arrowBottom::after {
    display: none;
}

/* Обёртки библиотеки со своими ширинами в пикселях: место распределяет сетка
   дней, а не они. */
.flatpickr-calendar .flatpickr-rContainer,
.flatpickr-calendar .flatpickr-days,
.flatpickr-calendar .flatpickr-weekdays {
    width: 100%;
    min-width: 0;
    max-width: none;
}

/* --- шапка: месяц и год списками --- */

/* position: relative — не украшение: стрелки месяца библиотека позиционирует
   абсолютно, и без этого они отсчитывались от самой карточки, то есть вставали
   в её угол поверх внутреннего отступа, на полтора десятка пикселей выше
   названия месяца. */
.flatpickr-months {
    position: relative;
    align-items: center;
    gap: 4px;
    padding-bottom: 10px;
}

/* height: auto здесь мало: у библиотеки .flatpickr-current-month позиционирован
   абсолютно, поэтому родитель без явной высоты схлопывался до нуля и обрезал
   переключатели своим overflow: hidden. Возвращаем блок в поток. */
.flatpickr-months .flatpickr-month {
    height: auto;
    overflow: visible;
    background-color: transparent;
    color: var(--color-text-important);
}

.flatpickr-current-month {
    position: static;
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 6px;
    height: auto;
    width: auto;
    padding: 0;
    font-size: var(--font-size-default);
    font-weight: var(--font-weight-medium);
}

/* Оба списка выглядят как поля панели — тот же фон, рамка и скругление, что у
   выпадающих списков в фильтрах. Год тоже список: своего у библиотеки нет, его
   подставляет app.js, и класс он берёт от списка месяцев. */
.flatpickr-current-month .flatpickr-monthDropdown-months {
    appearance: none;
    -webkit-appearance: none;
    height: 30px;
    /* Библиотека сдвигает список на -1px вверх; со своей рамкой и стрелками
       месяца рядом этот пиксель виден как перекос шапки. */
    margin: 0;
    padding: 0 24px 0 10px;
    background-color: var(--input-background);
    background-image: var(--select-arrow);
    background-repeat: no-repeat;
    background-position: right 7px center;
    background-size: 14px;
    border: 1px solid var(--input-border);
    border-radius: var(--radius-control);
    color: var(--color-text-important);
    font-size: var(--font-size-small);
    font-weight: var(--font-weight-medium);
    cursor: pointer;
    transition: border-color 150ms, background-color 150ms;
}

/* Фон приходится описывать заново и целиком: наведение у библиотеки задано
   сокращённой записью `background`, а она сбрасывает разом все составляющие —
   и картинку, и повтор, и положение, и размер.

   Одной только картинки тут мало: с ней стрелка возвращалась, но повтор
   становился `repeat`, размер `auto`, положение — левый верхний угол, и поле
   под курсором замащивалось десятком стрелок во всю его высоту. */
.flatpickr-current-month .flatpickr-monthDropdown-months:hover,
.flatpickr-current-month .flatpickr-monthDropdown-months:focus,
.flatpickr-current-month .flatpickr-monthDropdown-months:active {
    background-color: var(--input-background-focus);
    background-image: var(--select-arrow);
    background-repeat: no-repeat;
    background-position: right 7px center;
    background-size: 14px;
    border-color: var(--input-border-focus);
}

.flatpickr-current-month .flatpickr-monthDropdown-months:focus-visible {
    outline: 2px solid var(--color-blue);
    outline-offset: 1px;
}

.flatpickr-current-month .flatpickr-monthDropdown-months option {
    background-color: var(--input-background);
    color: var(--color-text-default);
}

/* --- стрелки месяца --- */

.flatpickr-months .flatpickr-prev-month,
.flatpickr-months .flatpickr-next-month {
    top: 0;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    height: 30px;
    width: 30px;
    padding: 0;
    border-radius: var(--radius-control);
    fill: var(--color-icon-secondary);
    transition: background-color 150ms;
}

.flatpickr-months .flatpickr-prev-month:hover,
.flatpickr-months .flatpickr-next-month:hover {
    background-color: var(--background-nav-hover);
}

.flatpickr-months .flatpickr-prev-month svg,
.flatpickr-months .flatpickr-next-month svg {
    height: 12px;
    width: 12px;
}

.flatpickr-months .flatpickr-prev-month:hover svg,
.flatpickr-months .flatpickr-next-month:hover svg {
    fill: var(--color-blue);
}

/* Недоступную стрелку библиотека убирает из потока совсем, и вся шапка на ней
   дёргается: у последнего доступного месяца пропадает целый элемент, а месяц с
   годом уезжают вбок. Место оставляем, стрелку прячем. */
.flatpickr-months .flatpickr-prev-month.flatpickr-disabled,
.flatpickr-months .flatpickr-next-month.flatpickr-disabled {
    display: inline-flex;
    visibility: hidden;
}

/* --- сетка дней --- */

/* Дни недели и сами дни — одна и та же сетка из семи колонок: подпись стоит
   ровно над своим столбцом, и по-другому уже не встанет. */
.flatpickr-weekdaycontainer,
.dayContainer {
    display: grid;
    grid-template-columns: repeat(7, 36px);
    gap: 2px;
    width: 100%;
    min-width: 0;
    max-width: none;
    padding: 0;
}

.flatpickr-weekdays {
    height: auto;
    padding-bottom: 6px;
    background-color: transparent;
}

span.flatpickr-weekday {
    width: auto;
    background-color: transparent;
    color: var(--color-text-secondary);
    font-size: var(--font-size-smaller);
    font-weight: var(--font-weight-regular);
    line-height: 20px;
    text-transform: lowercase;
}

/* Ячейка дня — flex, а не строка с line-height: у библиотеки высота держится
   на line-height, и число в квадрате 36×34 стояло чуть выше середины. */
.flatpickr-day {
    display: flex;
    align-items: center;
    justify-content: center;
    height: 34px;
    width: auto;
    max-width: none;
    line-height: 1;
    border: 1px solid transparent;
    border-radius: var(--radius-control);
    color: var(--color-text-default);
    transition: background-color 150ms, color 150ms;
}

.flatpickr-day:hover,
.flatpickr-day:focus {
    background-color: var(--background-nav-hover);
    border-color: transparent;
    color: var(--color-text-important);
}

/* Сегодня — тонкая серая обводка и более яркий текст. Голубым не метим: этим
   цветом отмечен выбранный день, и два дня выглядели бы выбранными разом. */
.flatpickr-day.today {
    border-color: var(--input-border-focus);
    color: var(--color-text-important);
}

.flatpickr-day.today:hover,
.flatpickr-day.today:focus {
    background-color: var(--background-nav-hover);
    border-color: var(--input-border-focus);
}

.flatpickr-day.selected,
.flatpickr-day.selected:hover,
.flatpickr-day.selected:focus,
.flatpickr-day.selected.today,
.flatpickr-day.selected.today:hover {
    background-color: var(--background-blue);
    border-color: var(--border-blue);
    color: var(--color-blue);
    font-weight: var(--font-weight-medium);
}

/* Дни соседних месяцев приглушены — и остаются такими под курсором.
   Общее правило наведения поднимало их до цвета обычного дня, и хвост чужого
   месяца вспыхивал ярче, чем свой: подсветка обещала, что нажатие даст тот же
   результат, что и в основной сетке, хотя она уводит календарь на месяц. */
.flatpickr-day.prevMonthDay,
.flatpickr-day.nextMonthDay {
    color: var(--color-icon-tertiary);
}

/* border-color здесь обязателен: у библиотеки день соседнего месяца обводится
   при наведении почти белым (rgb(230,230,230)), и в тёмной панели это читалось
   как рамка вокруг случайной клетки. */
.flatpickr-day.prevMonthDay:hover,
.flatpickr-day.nextMonthDay:hover,
.flatpickr-day.prevMonthDay:focus,
.flatpickr-day.nextMonthDay:focus {
    background-color: var(--input-background);
    border-color: transparent;
    color: var(--color-text-secondary);
}

.flatpickr-day.flatpickr-disabled,
.flatpickr-day.flatpickr-disabled:hover {
    background-color: transparent;
    color: var(--color-icon-tertiary);
    opacity: 0.5;
    cursor: default;
}

.date-field input::-webkit-calendar-picker-indicator {
    /* Родной значок оставляем кликабельным, но невидимым: он растянут на всё
       поле, и нажатие в любом месте открывает календарь. */
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    opacity: 0;
    cursor: pointer;
}

/* --------------------------------------------------------------------------
   21. Идентификация
   -------------------------------------------------------------------------- */

.empty-note {
    margin: 0;
    padding: 14px 20px;
}

/* В карточке досье в одну ячейку попадает несколько серверов подряд. */
.facts td .server + .server {
    margin-left: 12px;
}

.facts td .badge,
.facts td .match {
    vertical-align: 1px;
}

/* Заголовок карточки с пометками справа не должен ломаться на узком экране. */
.card > .header .note .badge + .badge {
    margin-left: 4px;
}

/* --------------------------------------------------------------------------
   22. Выбор из нескольких вариантов
   -------------------------------------------------------------------------- */

.vip-form {
    display: flex;
    flex-direction: column;
    gap: 20px;
}

.choice {
    margin: 0;
    padding: 0;
    border: 0;
}

.choice legend {
    padding: 0 0 8px;
    color: var(--color-text-secondary);
    font-size: var(--font-size-small);
}

.choice-row {
    display: flex;
    gap: 8px;
}

.choice-row.wrap {
    flex-wrap: wrap;
}

/* Переключатели-карточки вместо выпадающего списка: все варианты видны сразу,
   вместе с ценами, и выбор — одно нажатие. */
.choice-option {
    position: relative;
    display: flex;
    flex: 1 1 150px;
    cursor: pointer;
}

/* Сам radio не прячем через display: none — так он выпадает из порядка обхода
   с клавиатуры и перестаёт отвечать на стрелки. Делаем его невидимым, оставив
   на месте. */
.choice-option input {
    position: absolute;
    opacity: 0;
    width: 0;
    height: 0;
    margin: 0;
}

.choice-body {
    display: flex;
    flex-direction: column;
    gap: 3px;
    width: 100%;
    padding: 10px 14px;
    border: 1px solid var(--input-border);
    border-radius: var(--radius-control);
    background-color: var(--input-background);
    transition: background-color 150ms, border-color 150ms, color 150ms;
}

.choice-option:hover .choice-body {
    background-color: var(--input-background-focus);
}

.choice-option input:checked + .choice-body {
    background-color: var(--background-blue);
    border-color: var(--border-blue);
    color: var(--color-blue);
}

.choice-option input:focus-visible + .choice-body {
    outline: 2px solid var(--color-blue);
    outline-offset: 2px;
}

.choice-title {
    display: flex;
    align-items: center;
    gap: 6px;
    color: var(--color-text-important);
    font-weight: var(--font-weight-medium);
}

.choice-option input:checked + .choice-body .choice-title {
    color: inherit;
}

.choice-price {
    color: var(--color-text-secondary);
    font-size: var(--font-size-smaller);
}

.choice-option input:checked + .choice-body .choice-price {
    color: inherit;
    opacity: 0.8;
}

.choice-discount {
    padding: 1px 6px;
    border-radius: 999px;
    background-color: rgb(120 183 87 / 14%);
    color: var(--color-green);
    font-size: var(--font-size-smaller);
    font-weight: var(--font-weight-regular);
}

/* --------------------------------------------------------------------------
   23. Выданный доступ
   -------------------------------------------------------------------------- */

/* Форма в столбик — та же раскладка, что у выдачи VIP. */
.form-stack {
    display: flex;
    flex-direction: column;
    gap: 20px;
}

/* Готовое сообщение с логином и паролем.
   Моноширинный шрифт здесь не для красоты: пароль переписывают руками, и в
   пропорциональном шрифте «rn» от «m», а «I» от «l» не отличить. Пробелы и
   переводы строк сохраняются — сообщение копируется целиком, как есть. */
.handout {
    margin: 16px 0 0;
    padding: 14px 16px;
    border: 1px solid var(--input-border);
    border-radius: var(--radius-control);
    background-color: var(--input-background);
    color: var(--color-text-important);
    font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
    font-size: var(--font-size-small);
    line-height: 1.6;
    white-space: pre-wrap;
    word-break: break-word;
    user-select: all;
}

.handout + .toolbar {
    margin-top: 12px;
}

/* --------------------------------------------------------------------------
   24. Карточка наказания на телефоне
   -------------------------------------------------------------------------- */

/* Наказание — единственная таблица, которую на телефоне открывают чаще, чем
   на большом экране: «за что меня забанили» читают с того же телефона, с
   которого не пустили на сервер. Поэтому у неё своя раскладка карточки, а не
   общая.

   Читается она сверху вниз как ответ на четыре вопроса подряд: кто, когда,
   за что, кто выдал. Всё прижато к левому краю — прижатый вправо столбец в
   карточке шириной с экран висит в пустоте и связи с соседней строкой не
   держит. */
@media (max-width: 900px) {
    table.data.cards.actions tbody tr {
        grid-template-columns: minmax(0, 1fr);
        grid-template-areas:
            'who'
            'when'
            'what'
            'state';
    }

    /* Steam ID под ником убран: на телефоне он занимает целую строку, а его
       ни прочесть с ходу, ни скопировать одним движением — за этим всё равно
       открывают саму карточку наказания. Его место занимает дата. */
    table.data.cards.actions td.col-who .cell-sub {
        display: none;
    }

    /* Дата с временем — вторым ярусом сразу под ником, как подпись к нему.
       Собственный ярус ей не нужен: она короткая. */
    table.data.cards.actions td.col-when .cell {
        flex-direction: row;
        align-items: baseline;
        gap: 6px;
    }

    /* Кто выдал и на каком сервере — по левому краю, как и всё остальное. */
    table.data.cards.actions td.col-state {
        justify-self: start;
    }

    table.data.cards.actions td.col-state .cell {
        align-items: flex-start;
    }

    /* «Админ:» — только здесь. В таблице подпись даёт колонка «Выдан», а в
       карточке шапки нет, и ник админа стоял бы отдельной строкой без всякого
       объяснения — рядом с ником игрока сверху их легко перепутать. */
    table.data.cards.actions td.col-state .cell-main::before {
        content: 'Админ: ';
        flex: none;
        color: var(--color-text-secondary);
    }
}

/* Кнопки действий в шапке карточки прижаты вправо, к краю: заголовок слева,
   действия справа — как в шапке страницы. */
.card > .header > .toolbar {
    margin-left: auto;
}

/* Единица и количество — одной строкой: «Дни» и «30» читаются вместе, а по
   отдельности ни то, ни другое ничего не значит. */
.term-row {
    display: flex;
    align-items: center;
    gap: 8px;
}

.term-row .select {
    width: auto;
}

.term-row input[type="number"] {
    width: 8ch;
    padding: 8px 10px;
    border: 1px solid var(--input-border);
    border-radius: var(--radius-control);
    background-color: var(--input-background);
    color: var(--color-text-important);
    font-family: inherit;
    font-size: var(--font-size-default);
}

.term-row input[type="number"]:focus {
    outline: none;
    border-color: var(--input-border-focus);
    background-color: var(--input-background-focus);
}

/* --------------------------------------------------------------------------
   25. Предупреждения админам
   -------------------------------------------------------------------------- */

/* Плашка «1/3», которая нажимается. Выглядит она плашкой, а ведёт себя кнопкой:
   собственных стилей у кнопки нет вовсе, она только снимает браузерные. */
.badge-button {
    border: 0;
    font: inherit;
    font-size: var(--font-size-smaller);
    cursor: pointer;
}

.badge-button:hover {
    filter: brightness(1.25);
}

/* Несколько действий в одной ячейке строки: раскрыть и снять. */
.row-actions {
    display: flex;
    align-items: center;
    gap: 4px;
}

.row-actions form {
    display: flex;
}

/* Раскрытая строка таблицы. Фон чуть темнее строки, чтобы было видно, к кому
   она относится: сама по себе она шире всех колонок и иначе читалась бы как
   отдельный блок посреди списка. */
.warnings-row > td.wide {
    padding: 0;
    background-color: var(--input-background);
    border-top: 0;
}

.warnings {
    display: flex;
    flex-direction: column;
    gap: 16px;
    padding: 16px 20px;
}

.warnings-list {
    display: flex;
    flex-direction: column;
    gap: 12px;
    margin: 0;
    padding: 0;
    list-style: none;
}

.warning {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    gap: 8px;
    padding: 12px 14px;
    border: 1px solid var(--border-card);
    border-radius: var(--radius-card);
    background-color: var(--background-card);
}

.warning-head {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 8px;
    width: 100%;
    color: var(--color-text-secondary);
    font-size: var(--font-size-small);
}

/* Крестик погашения — к правому краю, подальше от даты и пометок слева: это
   единственное здесь действие, и путать его с ними не с чем. */
.warning-head form {
    margin-left: auto;
}

.warning-text {
    color: var(--color-text-important);
    white-space: pre-wrap;
    word-break: break-word;
}

.warning .link {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    font-size: var(--font-size-small);
}

.warning .link svg {
    height: 15px;
    width: 15px;
    color: var(--color-icon-secondary);
}

.warning form {
    margin: 0;
}
