/* Google Fonts - Inter для массивных заголовков */
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800;900&display=swap');

/* ========================================
   CSS VARIABLES - KSLT Design System
   Electric Lime on Deep Black
   ======================================== */
/* Блок :root перенесён в css/tokens.css — единый источник токенов
   для сайта и мобильного приложения. tokens.css подключается ПЕРВЫМ,
   до этого файла. Старые имена переменных продолжают работать через
   слой обратной совместимости в конце tokens.css.
   Восстановить прежний блок: git show main:css/style.css */

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

body {
    font-family: var(--font-sans);
    line-height: 1.5;
    color: var(--text-primary);
    background: var(--bg-primary);
    min-height: 100vh;
    -webkit-font-smoothing: antialiased;
    -moz-osx-font-smoothing: grayscale;
}

/* Touch optimization — remove 200ms tap delay */
a, button, input, select, textarea, [role="button"] {
    touch-action: manipulation;
    -webkit-tap-highlight-color: transparent;
}

/* iOS form reset */
input, button, select, textarea {
    -webkit-appearance: none;
    -moz-appearance: none;
    appearance: none;
}

/* ШРИФТ УПРАВЛЕНИЙ ПЕРЕЕХАЛ В `css/tokens.css` — 13.10.
   Правило стояло здесь и не доезжало до судейского окна и табло: они
   `style.css` не грузят вовсе. В `css/umpire.css` из-за этого жила
   ЕДИНСТВЕННАЯ не мёртвая копия `font-family: inherit` на весь продукт —
   проверено пробниками на 31 странице. Дом правила — файл, который грузят
   ВСЕ 90 страниц. */

/* ========================================
   HEADER VARIANT 2: HYBRID (Комбинация)
   На всю ширину + blur + тень + градиентная линия
   ======================================== */

.floating-header.header-hybrid {
    position: fixed;
    top: 0;
    left: 0;
    right: 0;
    width: 100%;
    z-index: 1000;

    background: rgba(10, 10, 10, 0.4);
    backdrop-filter: blur(20px);
    -webkit-backdrop-filter: blur(20px);

    padding: 0 var(--pad-x);
    transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1);
}

/* Градиентная линия снизу */
.floating-header.header-hybrid::after {
    content: '';
    position: absolute;
    bottom: 0;
    left: 0;
    right: 0;
    height: 1px;
    background: linear-gradient(90deg,
        transparent,
        var(--alpha-lime-20) 20%,
        var(--accent-glow) 50%,
        var(--alpha-lime-20) 80%,
        transparent
    );
    opacity: 0.5;
    transition: all var(--transition-base);
}

/* На телефоне шапка не полагается на размытие.
   Она держалась на `backdrop-filter`, а фон был прозрачным на 60%. В Safari
   размытие срывается, когда за элементом идёт анимированная картинка —
   и шапка просто исчезала на фоне тёмного первого экрана. */
/* ПЛАНШЕТ И УЖЕ — 16.10, кусок 75 */
@media (max-width: 992px) {
    .floating-header.header-hybrid {
        background: rgba(5, 5, 5, 0.92);
        /* Линия видимая и нужная, но нарисована тенью внутрь, а не рамкой:
           рамка входит в высоту и делала шапку 57 вместо 56 и 65 вместо 64. */
        box-shadow: inset 0 -1px 0 var(--alpha-lime-20);
    }
}

.floating-header.header-hybrid.scrolled {
    background: rgba(5, 5, 5, 0.85);
    backdrop-filter: blur(24px);
    -webkit-backdrop-filter: blur(24px);
    box-shadow:
        0 4px 24px var(--alpha-black-30),
        0 0 40px var(--alpha-lime-06);
}

.floating-header.header-hybrid.scrolled::after {
    opacity: 1;
    background: linear-gradient(90deg,
        transparent,
        var(--accent-glow) 15%,
        var(--alpha-lime-50) 50%,
        var(--accent-glow) 85%,
        transparent
    );
}

/* ========================================
   HEADER DEFAULT (Fallback)
   ======================================== */

.floating-header {
    position: fixed;
    top: 0;
    left: 0;
    right: 0;
    width: 100%;
    z-index: 1000;

    background: transparent;
    backdrop-filter: blur(0px);
    -webkit-backdrop-filter: blur(0px);
    /* Рамки здесь нет намеренно. Раньше стояло
     border-bottom: 1px solid transparent — невидимая, но входящая в высоту:
     шапка 64 отрисовывалась как 65, и ВСЕ липкие панели на top: 64 уходили
     под неё на пиксель (h80, замерено 21.09). Линию под шапкой рисует
     ::after — он не влияет на высоту. */
    transition: all var(--transition-base);

    padding: 0 var(--pad-x);
}

.floating-header.scrolled {
    background: var(--alpha-black-85);
    backdrop-filter: blur(20px);
    -webkit-backdrop-filter: blur(20px);
    /* Рамки нет по той же причине, что выше: она добавляла пиксель к высоте
       при прокрутке, и шапка прыгала 64 -> 65. Линию рисует ::after, при
       прокрутке он же становится ярче. */
}

/* ОСТОРОЖНО, ЗДЕСЬ УЖЕ ОБЖИГАЛИСЬ ДВАЖДЫ.
   До 21.09.2026 селектор был голый — `nav` — и ловил КАЖДЫЙ <nav> на сайте.
   Написан он для шапки, а доставалось всем: навигация подвала выросла с 44
   до 64 и утянула плашку со 118 до 139, а полоса вкладок турнира до сих пор
   носит в себе строку «Override global nav styles». Оба места пришлось
   оборонять вручную — .footer-legal-nav с явным height: auto и .td-tabs-bar
   с тремя сбросами.
   Проверено перед сужением: голых <nav> в проекте 80, и ВСЕ 80 лежат прямо
   внутри .floating-header. То есть правило и так работало только по шапке —
   просто заодно било по чужим.
   Не расширять обратно: следующий <nav> на сайте не должен ни от чего
   обороняться. */
.floating-header nav {
    display: flex;
    justify-content: space-between;
    align-items: center;
    max-width: var(--container-max);
    margin: 0 auto;
    height: var(--header-h);
}

/* Равномерное распределение: лого | меню | правая часть */
.nav-links {
    flex: 2;
    justify-content: center;
}

.nav-right {
    flex: 1;
    justify-content: flex-end;
}

/* ========================================
   LOGO
   ======================================== */

.logo {
    display: flex;
    align-items: center;
    text-decoration: none;
    transition: transform var(--transition-base);
}

.logo:hover {
    transform: scale(1.08);
}

/* Настоящий знак КСЛТ вместо текстовой надписи. Родной зелёный рассчитан на
   светлый фон tennis.kg, поэтому перекрашен в лайм. Оттенок плотнее нашего
   акцентного: в акцентном фигурка теннисиста по светлоте совпадала с белыми
   буквами и сливалась с ними. Свечение при этом акцентное — оно держит знак
   в одном ряду с остальной неоновой графикой сайта. */
.logo-img {
    height: 34px;
    width: auto;
    display: block;
    filter: drop-shadow(0 0 6px var(--alpha-lime-30));
}

.footer-logo .logo-img {
    height: 30px;
}

/* Nav Left - Logo + Sponsor */
.nav-left {
    display: flex;
    align-items: center;
    gap: var(--space-md);
    flex: 1;
}

.sponsor-badge {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 2px;
    padding-left: var(--space-sm);
    border-left: var(--bw-hair) solid var(--border-subtle);
}

.sponsor-label {
    font-size: var(--fs-2xs);
    color: var(--text-muted);
    text-transform: uppercase;
    letter-spacing: 0.05em;
}

.sponsor-logo-img {
    height: 24px;
    width: auto;
    max-width: 120px;
    object-fit: contain;
    opacity: 1;
    transition: all var(--transition-fast);
}

/* НАДПИСЬ ВМЕСТО ЛОГОТИПА — СВОЙ КЛАСС, А НЕ КЛАСС КАРТИНКИ (01.11).
   ЧТО БЫЛО: у спонсора без логотипа `js/sponsors-loader.js` ставил на
   SPAN класс `.sponsor-logo-img`. Для картинки `height: 24` и
   `max-width: 120` — рамка, в которую она впишется сама. Для текста это
   ловушка: замер на живой странице дал `clientHeight 24` при
   `scrollHeight` 48 — «Ала-Тоо Спорт» встал ДВУМЯ строками, и вторая при
   `overflow: visible` отрисовалась ниже бокса, до 85.5. Она невидимо
   накрывала ссылку «назад» (её слой цели 70.5 → 114.5) и забирала
   нажатия: профиль попадания показал, что до 84 всё достаётся логотипу, а
   собственная цель ссылки начинается только с 88.

   ВЫСОТА ТА ЖЕ СТУПЕНЬ, что у картинки: ряд шапки не должен поехать от
   того, есть у спонсора логотип или нет. ЧЕМ ПЛАТИМ, НАЗВАНО: длинное
   название обрезается многоточием — полное остаётся в `title` ссылки.
   КЛАСС, СДЕЛАННЫЙ ДЛЯ КАРТИНКИ, НА ТЕКСТЕ СТАНОВИТСЯ ЛОВУШКОЙ. */
.sponsor-logo-text {
    display: block;
    height: 24px;
    line-height: 24px;
    max-width: 120px;
    overflow: hidden;
    white-space: nowrap;
    text-overflow: ellipsis;
    font-weight: 600;
    color: var(--accent);
}

.sponsor-badge:hover .sponsor-logo-img {
    transform: scale(1.1);
}

.sponsor-logo-link {
    display: inline-flex;
    align-items: center;
    text-decoration: none;
    color: inherit;
}

.sponsor-logo-link:hover .sponsor-logo-img {
    transform: scale(1.1);
}

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

@keyframes ballSpin {
    0% {
        transform: rotate(0deg);
    }
    100% {
        transform: rotate(360deg);
    }
}

/* ========================================
   NAVIGATION LINKS
   ======================================== */

.nav-links {
    display: flex;
    list-style: none;
    gap: var(--space-xs);
    align-items: center;
}

/* ОСТОРОЖНО: ВЫСОТУ ЗАДАЁТ min-height, А НЕ ВЕРТИКАЛЬНЫЙ padding.
   Было padding: 10px 16px — и высота выходила побочно: 10 + строка 21 + 10 = 41.
   Такая высота уезжает от любой правки кегля или межстрочного, то есть «41»
   держалось на честном слове трёх чисел сразу. Решение Кости 20.09 по макету
   211:145: одна высота 36 на весь ряд — пункт, «Войти», язык.
   Под пальцем это правило НЕ действует: ниже в файле блок (pointer: coarse)
   поднимает цель до 44. Мышь точная, палец нет — разница намеренная. */
.nav-item {
    display: flex;
    align-items: center;
    gap: 0.4rem;
    color: var(--text-secondary);
    text-decoration: none;
    min-height: 36px;
    padding: 0 16px;
    border-radius: var(--radius-full);
    /* Кегль 16, а не 14. Решение Кости 20.09 по макету 214:145.
       Довод не «крупнее красивее»: в девятиступенчатой шкале 14 — ступень
       вторичного текста и кнопок, 16 — ступень основного текста. Главная
       навигация сайта не вторичный текст, и глаз ловит это несоответствие
       роли раньше, чем разум.
       ОСТОРОЖНО: тот же ряд отдаётся на 844 (телефон боком) и 1024. Замер
       20.09 показал, что на 844 у блока меню запас нулевой ещё при 14.
       Если 16 туда не влезает — ступенька от 993, как у высоты шапки, и
       величина живёт в токене, а не числом в медиазапросе. */
    font-size: var(--fs-base);
    font-weight: 500;
    /* Название раздела НЕ переносится внутри своей пилюли.
       Кыргызский «Түз эфир» — два слова, и он разваливался на две строки:
       пункт становился вдвое выше соседей, ряд выглядел сломанным. Русский
       и английский этого не показывали, потому что там одно слово «Live».
       ОСТОРОЖНО: запрет переноса не создаёт места. Если ряд не влезает —
       он теперь полезет за край вместо того, чтобы сложиться, и это видно
       прибором 23-header-row-fit (поле «налезает»). Для кыргызского на 844
       места не хватает даже при кегле 14 — это решается длиной названия
       или более ранним бургером, а не белым пробелом. */
    white-space: nowrap;
    transition: all var(--transition-fast);
    position: relative;
}

.nav-item:hover {
    color: var(--text-primary);
    background: var(--surface-glass-hover);
}

/* =========================================================================
   ВИДИМЫЙ ФОКУС С КЛАВИАТУРЫ
   =========================================================================
   В шапке не было НИ ОДНОГО правила фокуса: ни у пункта, ни у «Войти», ни
   у языка. При ходьбе клавишей Tab не было видно, где стоишь. WCAG 2.4.7,
   уровень AA. В мобильном меню фокус описан — в десктопной шапке не был.

   ОСТОРОЖНО: :focus-visible, а НЕ :focus. Второй срабатывает и на мышь —
   обводка вспыхивает при каждом щелчке, это раздражает, и её обычно гасят
   вовсе, заодно отрезая клавиатуру. :focus-visible показывает обводку
   только тогда, когда браузер видит навигацию с клавиатуры.

   outline, а не border и не box-shadow: outline не входит в поток и не
   двигает соседей — иначе весь ряд дёргался бы при переходе по Tab.
   ========================================================================= */
/* =========================================================================
   ОТКРЫТЫЙ РАЗДЕЛ ОТМЕЧАЕТ ЧЕРТА, А НЕ ПОДЛОЖКА
   =========================================================================
   Подложка-пилюля кончается на 50, панель начинается на 64 — между ними
   14 пикселей шапки. Именно этот зазор Костя 20.09 назвал «как будто
   отвалилось». Убрав подложку у открытого раздела, мы убираем и зазор:
   висеть становится нечему, панель идёт прямо от края шапки.

   Роль подложки — показать, какой раздел раскрыт — берёт на себя лаймовая
   черта под подписью. Приём в проекте уже есть: такой же чертой отмечается
   текущий раздел в других местах, так что новый язык мы не вводим.

   Черта идёт от 16 до 16, то есть под подписью И под стрелкой: стрелка —
   часть этого же элемента, и подчёркивать половину элемента было бы странно. */
.nav-dropdown:hover > .nav-item,
.nav-dropdown:focus-within > .nav-item,
.nav-dropdown.open > .nav-item {
    background: transparent;
}

.nav-dropdown > .nav-item::after {
    content: '';
    position: absolute;
    left: 16px;
    right: 16px;
    bottom: 4px;
    height: 2px;
    border-radius: 2px;
    background: var(--accent);
    opacity: 0;
    transition: opacity var(--transition-fast);
}

.nav-dropdown:hover > .nav-item::after,
.nav-dropdown:focus-within > .nav-item::after,
.nav-dropdown.open > .nav-item::after {
    opacity: 1;
}

.nav-item:focus-visible,
.btn-auth:focus-visible,
.lang-toggle:focus-visible,
.logo:focus-visible,
.burger-menu:focus-visible,
.nav-dropdown-item:focus-visible {
    outline: var(--bw-thick) solid var(--accent);
    outline-offset: 2px;
}

/* ОСТОРОЖНО, Я ЗДЕСЬ УЖЕ ОШИБСЯ 20.09, И ОШИБКА БЫЛА ТОНКАЯ.
   В правило выше я дописал border-radius: var(--radius-full) — «чтобы
   обводка была скруглённой». Но outline и так повторяет собственное
   скругление элемента; а border-radius в правиле :focus-visible меняет
   форму САМОГО ЭЛЕМЕНТА в момент фокуса. У пунктов меню и кнопок радиус
   и так полный, там разницы не видно. А у логотипа своего скругления нет,
   и он на время фокуса превращался в таблетку: обводка шла овалом и
   срезала знак. Костя это и увидел.
   Вывод: в правиле фокуса задаём ТОЛЬКО обводку. Форму элемент приносит
   свою — иначе фокус начинает перекраивать вёрстку.

   Логотипу нужен собственный радиус и больший отступ: знак — вектор с
   выносными элементами (бегущая фигура выходит за прямоугольник текста),
   и обводка вплотную режет его визуально. */
.logo:focus-visible {
    border-radius: 10px;
    outline-offset: 6px;
}

.nav-item.active,
.nav-item.is-active {
    color: var(--accent);
    font-weight: 600;
}

.nav-item.active:hover,
.nav-item.is-active:hover {
    color: var(--accent);
}

/* Пульсирующая Electric Lime точка — Active page indicator */
.indicator-dot {
    width: 6px;
    height: 6px;
    flex-shrink: 0;
    background: var(--accent);
    border-radius: 50%;
    animation: livePulse 2s ease-in-out infinite;
    box-shadow: 0 0 10px var(--accent-glow);
    display: none;
}

.nav-item.active .indicator-dot,
.nav-item.is-active .indicator-dot {
    display: inline-block;
}

.nav-item:hover .indicator-dot {
    display: inline-block;
}

/* Live dot - красный пульсирующий индикатор */
.live-dot {
    width: 8px;
    height: 8px;
    background: var(--danger);
    border-radius: 50%;
    box-shadow: 0 0 10px rgba(255, 71, 87, 0.5);
    display: none;
}

.nav-item-live.is-live .live-dot {
    display: inline-block;
    animation: livePulse 1.5s ease-in-out infinite;
}

.nav-item-live {
    color: var(--text-secondary) !important;
    font-weight: 500;
    transition: color var(--transition-fast);
}

.nav-item-live:hover {
    color: var(--text-primary) !important;
}

.nav-item-live.is-live {
    color: var(--danger) !important;
    font-weight: 600;
}

.nav-item-live.is-live:hover {
    color: var(--danger-hover) !important;
}

@keyframes livePulse {
    0%, 100% {
        opacity: 1;
        transform: scale(1);
    }
    50% {
        opacity: 0.6;
        transform: scale(1.1);
    }
}

/* ========================================
   NAV DROPDOWN (Rankings)
   ======================================== */

.nav-dropdown {
    position: relative;
}

.nav-item-dropdown {
    cursor: pointer;
}

.nav-arrow {
    transition: transform var(--transition-fast);
    opacity: 0.5;
    margin-left: 2px;
}

.nav-dropdown:focus-within .nav-arrow,
.nav-dropdown:hover .nav-arrow {
    transform: rotate(180deg);
    opacity: 1;
}

.nav-dropdown-menu {
    position: absolute;
    top: 100%;
    /* ВЫРАВНИВАНИЕ ПОД ЗАГОЛОВОК, А НЕ ЦЕНТРОВКА ПО НЕМУ.
       Было left: 50% + translateX(-50%). Панель шире кнопки, значит её левый
       край торчал левее кнопки, а текст внутри прижат влево — и колонка
       текста вставала на 59…75 пикселей левее текста своего же заголовка
       (замер 20.09, прибор 22-header-dropdowns). Курсор шёл к каждому пункту
       по диагонали: человек целится в буквы, а не в строку.
       Откуда −8: у заголовка отступ 16, у панели 8, у пункта 16.
       16 − (8 + 16) = −8. При этом числе буквы пункта стоят ровно под
       буквами заголовка, и курсор идёт вниз по прямой. */
    /* ВЫРАВНИВАНИЕ ОДНИМ ЧИСЛОМ.
       Сначала я ставил -8, чтобы БУКВЫ пункта встали под буквы заголовка.
       Текст выровнялся, а коробка — нет: панель торчала на 8 левее кнопки
       и уходила далеко правее. Костя 20.09: «выбивается».
       При отступе панели 8 и отступе пункта 8 текст пункта и так встаёт
       в 16 от левого края панели — ровно как у кнопки. Значит достаточно
       поставить коробку по кнопке, и текст выровняется сам. */
    left: 0;
    transform: translateY(10px);
    /* ШИРИНА ОТ СОДЕРЖИМОГО, А НЕ ОБЩАЯ.
       Было min-width: 260px одной строкой на все пять панелей. 260 нужны
       «Политике конфиденциальности» в «Инфо»; остальным четырём это давало
       94 / 130 / 90 / 97 пикселей пустоты справа. Теперь ширину задаёт самый
       длинный пункт, а нижняя граница — собственная кнопка: панель никогда
       не уже своего заголовка. */
    width: max-content;
    min-width: 100%;
    white-space: nowrap;
    /* НАСТОЯЩЕЕ СТЕКЛО, А НЕ ПОЛОВИНА ЕГО.
       Было rgba(20,20,20,0.95) с размытием 20. При 95% сквозь панель почти
       ничего не видно — значит размытие не делало ничего; но те 5%, что
       проходили, — это тёплое фото корта на главной, и панель уходила в
       коричневый. Костя 20.09: «ни туда ни сюда», и это буквально так:
       половина стекла и половина плоскости дают худшее от обоих.
       СТЕКЛО ПОПРОБОВАЛИ И ОТКАЗАЛИСЬ, 20.09, на живой странице.
       Прозрачность 0.62 с размытием 32 выглядела хорошо на макете и
       оказалась нечитаемой над героем: сквозь панель проступали «Сезон
       2026» и огромное лаймовое «КСЛТ», и пункты меню накладывались на
       них. Костя: «вживую текст нечитабельный, не пойдёт стекло».
       Урок шире этого места: прозрачную поверхность нельзя принимать по
       макету, где под ней нарисован спокойный фон. Под ней бывает
       заголовок высотой в сто пикселей.
       ВТОРАЯ ПОПЫТКА ТОЖЕ БЫЛА НЕВЕРНОЙ, И ПО ДРУГОЙ ПРИЧИНЕ.
       Я взял --bg-elevated (#1E1E1E) — токен, заведённый под «модалка,
       дропдаун, поповер». По названию подходит, по делу нет: #1E1E1E это
       СЕРЫЙ, и он задуман для модалки, которая лежит поверх затемнённой
       страницы. Там светлая поверхность читается как приподнятая. У нас
       затемнения нет: панель висит прямо над тёплой фотографией корта, и
       серый прямоугольник выбивается из палитры страницы. Костя 20.09:
       «фон не попадает под обложки страниц, выбивается по цветовой
       гармонии».

       ПОЧЕМУ ИМЕННО --bg-primary. Шапка над фото имеет оттенок rgba(10,10,10),
       то есть ту же основу, что и страница. Панель — продолжение шапки, и
       её цвет обязан быть из того же семейства. --bg-primary (#0A0A0A) —
       базовый цвет всего сайта: он не может «не попасть в палитру», он и
       ЕСТЬ палитра. Любое другое число здесь будет чужим оттенком, каким бы
       уместным ни казалось его имя. */
    background: var(--bg-primary);
    /* Размытие включаем только у открытого меню. У закрытого оно всё равно
       обрабатывалось браузером и приглушало то, что попадало под его полосу,
       — из-за этого первые строки статьи выглядели тусклыми, хотя текст
       белый и ничего поверх него нет. */
    backdrop-filter: none;
    border: var(--bw-hair) solid var(--border-subtle);
    /* ВЕРХНИЕ УГЛЫ ПРЯМЫЕ. Панель продолжает шапку вниз, а не лежит под
       ней отдельной карточкой. Два отвергнутых варианта (Костя, 20.09):
       с зазором — «как будто отвалилась»; с язычком до низа шапки —
       «сапог», потому что узкий язычок с широкой панелью даёт букву Г
       при любой длине меню.
       Скруглишь верх обратно — вернётся и отрыв от края, и щели, сквозь
       которые видно лаймовую линию под шапкой. */
    border-radius: 0 0 var(--radius-lg) var(--radius-lg);
    border-top: none;
    padding: 8px;
    opacity: 0;
    visibility: hidden;
    transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
    z-index: 1000;
    box-shadow: 0 20px 40px var(--alpha-black-50);
}

/* Между кнопкой и меню десять пикселей пустоты: курсор шёл к пункту, по
   дороге терял наведение, и меню закрывалось прямо под пальцем — щелчок
   попадал уже по странице. Мостик закрывает разрыв, а задержка даёт время
   вернуться, если рука дрогнула */
.nav-dropdown-menu::before {
    content: '';
    position: absolute;
    top: -14px;
    left: 0;
    right: 0;
    height: 14px;
}

.nav-dropdown-menu {
    transition-delay: 0.18s;
}

/* КЛАВИАТУРА: панель открыта, пока фокус внутри неё.
   :focus-within закрывает сама себя — фокус ушёл, панель свернулась. Ни
   таймеров, ни обработчиков закрытия, ни класса, который кто-то забудет
   снять. Именно на забытом снятии класса и сломался список языков: его
   закрытие было описано только для щелчка мимо, и при ходьбе по Tab он
   оставался висеть поверх страницы (найдено Костей 20.09).
   Без этого правила вложенные ссылки разделов недостижимы с клавиатуры:
   :hover у клавиатуры нет, а .open вешает только палец. */
.nav-dropdown:focus-within .nav-dropdown-menu,
.nav-dropdown:hover .nav-dropdown-menu {
    opacity: 1;
    visibility: visible;
    /* Размытия нет: панель сплошная, размывать под ней нечего. Раньше
       blur стоял здесь ради полупрозрачного фона; с непрозрачным он
       только заставляет браузер обсчитывать слой впустую. */
    transform: translateY(0);
    transition-delay: 0s;
}

/* =========================================================================
   ПАНЕЛЬ ПОД ПАЛЬЦЕМ — РАСКРЫВАЕТСЯ НАЖАТИЕМ
   =========================================================================
   На 1024 (планшет боком) отдаётся ДЕСКТОПНАЯ шапка, а у пальца наведения
   не существует. Все пять панелей висели только на :hover — значит пять
   вложенных ссылок в каждом разделе были недостижимы вовсе: тап по
   «Рейтингу» уводил на страницу раздела, а Pro-Masters, Masters, Tour,
   Challengers и Futures не открывались ничем.

   Третий случай одной и той же ошибки: h270 и h274 в подвале, теперь здесь.
   Правило судило по наведению там, где ввода наведением нет.

   ОСТОРОЖНО, ПРИБОР ЭТОГО НЕ ЛОВИТ. Playwright hover шлёт события мыши, и
   панель открывается даже при hasTouch: 22-header-dropdowns докладывал
   «панель работает на 1024», и это было неправдой про живой палец. Проверять
   здесь надо поведением, а не наведением.

   Решение Кости 20.09: первый тап раскрывает, второй уводит на страницу.
   Класс .open вешает js/script.js.
   ========================================================================= */
@media (any-pointer: coarse) {
    .nav-dropdown.open .nav-dropdown-menu {
        opacity: 1;
        visibility: visible;
        backdrop-filter: blur(20px);
        transform: translateY(0);
        transition-delay: 0s;
    }

    .nav-dropdown.open .nav-dropdown-item {
        opacity: 1;
        transform: translateX(0);
    }

    .nav-dropdown.open .nav-arrow {
        transform: rotate(180deg);
        opacity: 1;
    }
}

.nav-dropdown-item {
    display: flex;
    align-items: center;
    gap: 12px;
    /* Пункты стояли слишком просторно: в «Инфо» их восемь, и меню
       растягивалось почти на пол-экрана. Список должен читаться как список.
       ОТСТУП 8, А НЕ 16, И ЭТО НЕ ВКУС: 8 у панели + 8 у пункта = 16, ровно
       отступ кнопки в шапке. При этих числах выравнивание коробок само
       выравнивает и текст. Поменяешь на 16 — буквы уедут на 8 вправо. */
    padding: 6px 8px;
    color: var(--text-secondary);
    text-decoration: none;
    border-radius: var(--radius-md);
    /* Было 0.9rem = 14.4 при заголовке 14 — ребёнок крупнее родителя, и
       заголовок из-за этого читался мельче, чем есть. Плюс 14.4 мимо
       девятиступенчатой шкалы. Теперь 14 у пункта против 16 у заголовка. */
    font-size: var(--fs-sm);
    transition: all var(--transition-fast);
    opacity: 0;
    transform: translateX(-10px);
}

.nav-dropdown:focus-within .nav-dropdown-item,
.nav-dropdown:hover .nav-dropdown-item {
    opacity: 1;
    transform: translateX(0);
}

.nav-dropdown:hover .nav-dropdown-item:nth-child(1) { transition-delay: 0.05s; }
.nav-dropdown:hover .nav-dropdown-item:nth-child(2) { transition-delay: 0.1s; }
.nav-dropdown:hover .nav-dropdown-item:nth-child(3) { transition-delay: 0.15s; }
.nav-dropdown:hover .nav-dropdown-item:nth-child(4) { transition-delay: 0.2s; }
.nav-dropdown:hover .nav-dropdown-item:nth-child(5) { transition-delay: 0.25s; }

.nav-dropdown-item.active {
    color: var(--accent);
    font-weight: 600;
}

/* =========================================================================
   ТРИ СОСТОЯНИЯ ПУНКТА — ТРИ РАЗНЫХ СИГНАЛА, ОДНА ПОВЕРХНОСТЬ
   =========================================================================
   Костя 20.09: «наложений много — чёрная подложка, потом лаймовый, потом
   ховер; утяжеляет». Он прав, и причина хуже, чем просто лишний слой:
   ховер и активный пункт красились ОДНИМ И ТЕМ ЖЕ лаймом. То есть «здесь
   мой курсор» и «я сейчас в этом разделе» выглядели одинаково, и подложка
   была единственным, что их различало. Отсюда и ощущение нагромождения:
   два смысла в одном цвете плюс поверхность, которая их разводит.

   Теперь:
       покой    — серый (--text-secondary)
       ховер    — белый (--text-primary), БЕЗ подложки
       активный — лайм + вес 600
   Три состояния, три отличия, ни одной лишней поверхности.

   ПОЧЕМУ ХОВЕР НЕ УБРАН СОВСЕМ, хотя Костя предлагал оставить только Tab:
   в списке из девяти коротких строк без ховера не видно, по какой именно
   строке придётся щелчок. Это не украшение, это обратная связь. Убрать её
   у мыши — значит отнять у основного способа ввода то, что оставили
   клавиатуре. Вес снимает не сам ховер, а его подложка. */
.nav-dropdown-item:hover {
    color: var(--text-primary);
}

/* ========================================
   DROPDOWN MENUS
   ======================================== */

.dropdown {
    position: relative;
}

.dropdown-toggle {
    cursor: pointer;
}

.dropdown-arrow {
    transition: transform var(--transition-fast);
    opacity: 0.5;
}

.dropdown:hover .dropdown-arrow {
    transform: rotate(180deg);
}

.dropdown-menu {
    position: absolute;
    top: calc(100% + 8px);
    left: 50%;
    transform: translateX(-50%) translateY(8px);
    min-width: 180px;

    background: var(--bg-card);
    border: var(--bw-hair) solid var(--border-subtle);
    border-radius: var(--radius-md);
    box-shadow: var(--shadow-lg);

    padding: 8px;
    list-style: none;
    opacity: 0;
    visibility: hidden;
    transition: all var(--transition-base);
}

.dropdown:hover .dropdown-menu {
    opacity: 1;
    visibility: visible;
    transform: translateX(-50%) translateY(0);
}

.dropdown-menu li a {
    display: block;
    padding: 10px 14px;
    color: var(--text-secondary);
    text-decoration: none;
    border-radius: var(--radius-sm);
    font-size: var(--fs-sm);
    transition: all var(--transition-fast);
}

.dropdown-menu li a:hover {
    background: var(--surface-glass-hover);
    color: var(--text-primary);
}

/* ========================================
   RIGHT SECTION - City & Auth
   ======================================== */

.nav-right {
    display: flex;
    align-items: center;
    gap: var(--space-sm);
}

/* Контурная кнопка Войти (не отвлекает от CTA) */
/* Высота из min-height, а не из padding — см. комментарий у .nav-item.
   Замерено 39: padding 8 сверху и снизу, строка 21 и рамка по пикселю
   с каждой стороны. Рамка входит в высоту, поэтому 8+21+8 давало 37, а
   на экране стояло 39. */
/* =========================================================================
   «ВОЙТИ» — ЕДИНСТВЕННАЯ ЗАЛИВКА В РЯДУ
   =========================================================================
   Решение Кости 20.09, и довод продуктовый, а не про красоту: гость ходит
   по сайту, решает завести аккаунт — и «Войти» должно само броситься в
   глаза, потому что за ним стоит и регистрация.

   До этого кнопка носила рамку. Рамка делала её тяжелее соседей («кажется
   больше»), не давая взамен внятного призыва: серая рамка на тёмном — это
   не «нажми сюда», это «я тоже что-то». Рамок в ряду теперь нет ни у кого,
   ряд однороден, и единственное выделение — заливка.

   ОСТОРОЖНО: текст на лайме ТОЛЬКО --accent-on (#0A0A0A). Белый на лайме
   даёт контраст 1.17:1 и не читается вовсе. Это решение принято в проекте
   давно, и нарушать его нельзя нигде.
   ========================================================================= */
.btn-auth {
    display: inline-flex;
    align-items: center;
    background: var(--accent);
    color: var(--accent-on);
    text-decoration: none;
    min-height: 36px;
    /* Отступ 16, как у пунктов меню. Было 20 — при сорока пикселях текста
       это сорок пикселей воздуха, ровно поровну. Вместе с рамкой кнопка
       читалась крупнее соседей, хотя кегль был тот же. */
    padding: 0 16px;
    border-radius: var(--radius-full);
    font-size: var(--fs-base);
    font-weight: 600;
    border: none;
    cursor: pointer;
    transition: all var(--transition-fast);
}

/* Костя 20.09: «в спокойном состоянии названия мельче, чем вход и языки».
   ЗАМЕР ГОВОРИТ ДРУГОЕ: кегль у всех троих одинаковый — 16. Разница не в
   размере, а в яркости. Пункт меню в покое — белый на 72% (--text-secondary),
   «Войти» был белый на 100% (--text-primary). Более яркий текст того же
   кегля читается крупнее: глаз меряет контраст, а не высоту буквы. Ровно
   поэтому же «Инфо» под курсором «становится больше» — он просто доходит
   до ста процентов.
   Уравниваем покой: все трое на 72, все трое до 100 под курсором. Кнопку
   по-прежнему видно — её отличает рамка, а не яркость. */
/* Приглушение до 72% снято: оно было нужно, пока кнопка стояла в одном
   ряду с названиями как равная. Теперь она призыв — и обязана быть ярче,
   иначе заливка теряет смысл. */

.btn-auth:hover {
    background: var(--accent-hover);
    color: var(--accent-on);
}

/* ========================================
   USER DROPDOWN (logged-in nav)
   ======================================== */

.user-dropdown {
    position: relative;
}

.user-dropdown-toggle {
    display: flex;
    align-items: center;
    gap: 8px;
    background: var(--bg-elevated);
    border: var(--bw-hair) solid var(--border-subtle);
    color: var(--text-primary);
    padding: 5px 12px 5px 5px;
    border-radius: var(--radius-full);
    font-size: var(--fs-sm);
    font-weight: 500;
    cursor: pointer;
    transition: all var(--transition-fast);
}

.user-dropdown-toggle:hover {
    border-color: var(--border-light);
    background: var(--bg-card);
}

.user-dropdown.open .user-dropdown-toggle {
    border-color: var(--accent);
    background: var(--bg-card);
}

.user-dropdown-avatar {
    width: 28px;
    height: 28px;
    border-radius: 50%;
    object-fit: cover;
}

.user-dropdown-avatar-ph {
    width: 28px;
    height: 28px;
    border-radius: 50%;
    background: linear-gradient(135deg, var(--alpha-lime-20), var(--alpha-lime-06));
    color: var(--accent);
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: var(--fs-2xs);
    font-weight: 700;
}

.user-dropdown-name {
    max-width: 120px;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.user-dropdown-arrow {
    opacity: 0.5;
    transition: transform var(--transition-base);
}

.user-dropdown.open .user-dropdown-arrow {
    transform: rotate(180deg);
}

/* Dropdown menu */
.user-dropdown-menu {
    position: absolute;
    top: calc(100% + 8px);
    right: 0;
    min-width: 200px;
    background: var(--bg-card);
    border: var(--bw-hair) solid var(--border-subtle);
    border-radius: var(--radius-lg);
    box-shadow: var(--shadow-lg);
    padding: 6px;
    opacity: 0;
    visibility: hidden;
    transform: translateY(-10px) scale(0.95);
    transform-origin: top right;
    transition: all 0.25s cubic-bezier(0.4, 0, 0.2, 1);
    z-index: 1000;
}

.user-dropdown.open .user-dropdown-menu {
    opacity: 1;
    visibility: visible;
    transform: translateY(0) scale(1);
}

.user-dropdown-header {
    padding: 8px 12px 6px;
    font-size: var(--fs-xs);
    font-weight: 600;
    color: var(--text-muted);
    border-bottom: var(--bw-hair) solid var(--border-subtle);
    margin-bottom: 4px;
}

.user-dropdown-item {
    display: flex;
    align-items: center;
    gap: 10px;
    width: 100%;
    padding: 9px 12px;
    background: transparent;
    border: none;
    border-radius: var(--radius-md);
    color: var(--text-secondary);
    font-size: var(--fs-sm);
    cursor: pointer;
    transition: all var(--transition-fast);
    text-align: left;
    text-decoration: none;
}

.user-dropdown-item:hover {
    background: var(--surface-glass-hover);
    color: var(--text-primary);
}

.user-dropdown-item svg {
    flex-shrink: 0;
    opacity: 0.6;
}

.user-dropdown-item:hover svg {
    opacity: 1;
}

.user-dropdown-divider {
    height: 1px;
    background: var(--border-subtle);
    margin: 4px 0;
}

.user-dropdown-logout {
    color: var(--danger);
}

.user-dropdown-logout:hover {
    background: rgba(255, 71, 87, 0.1);
    color: var(--danger);
}

.user-dropdown-logout svg {
    opacity: 0.8;
}

/* Notification Bell (header) */
.site-notif-wrap {
    position: relative;
    display: inline-flex;
    align-items: center;
    margin-right: 8px;
}
.site-notif-btn {
    background: var(--alpha-white-08);
    border: var(--bw-hair) solid var(--alpha-white-12);
    border-radius: 50%;
    width: 36px;
    height: 36px;
    display: flex;
    align-items: center;
    justify-content: center;
    cursor: pointer;
    color: var(--text-secondary, #999);
    transition: all 0.2s;
    padding: 0;
    position: relative;
}
.site-notif-btn:hover {
    color: var(--accent);
    border-color: var(--accent-glow);
    background: var(--alpha-lime-06);
}
.site-notif-dot {
    position: absolute;
    top: 0;
    right: 0;
    min-width: 16px;
    height: 16px;
    background: var(--danger);
    border-radius: 8px;
    font-size: var(--fs-2xs);
    font-weight: 700;
    color: var(--neutral-white);
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 0 3px;
    line-height: 1;
}
/* Уведомления показываются окном, а не выпадающим списком: в колонке
   шириной триста пикселей длинное сообщение обрезалось на второй строке */
.site-notif-overlay {
    position: fixed;
    inset: 0;
    background: var(--alpha-black-60);
    backdrop-filter: blur(4px);
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 20px;
    z-index: 4000;
    opacity: 0;
    transition: opacity 0.2s;
}

.site-notif-overlay.active { opacity: 1; }

.site-notif-modal {
    width: 100%;
    max-width: 520px;
    max-height: 80vh;
    display: flex;
    flex-direction: column;
    /* Тот же вид, что у остальных окон сайта: приподнятая подложка,
       крупное скругление и рамка цветом клуба — приглушённым, чтобы
       обводка не спорила с содержимым */
    background: var(--bg-elevated);
    border: var(--bw-hair) solid var(--alpha-lime-30);
    border-radius: var(--radius-lg);
    box-shadow: 0 20px 60px var(--alpha-black-50);
    overflow: hidden;
}

.site-notif-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
    padding: 16px 20px;
    border-bottom: var(--bw-hair) solid var(--border-subtle, var(--alpha-white-08));
}

.site-notif-title {
    font-size: var(--fs-base);
    font-weight: 700;
}

.site-notif-close {
    background: none;
    border: none;
    color: var(--text-muted, #888);
    font-size: var(--fs-xl);
    line-height: 1;
    cursor: pointer;
    padding: 0 4px;
}

.site-notif-close:hover { color: var(--text-primary, var(--neutral-white)); }

.site-notif-body {
    overflow-y: auto;
    flex: 1;
}

.site-notif-foot {
    padding: 12px 20px;
    border-top: var(--bw-hair) solid var(--border-subtle, var(--alpha-white-08));
    text-align: center;
}

.site-notif-foot a {
    color: var(--accent);
    font-size: var(--fs-sm);
    font-weight: 600;
    text-decoration: none;
}

.site-notif-foot a:hover { text-decoration: underline; }

.site-notif-dropdown {
    position: absolute;
    top: 44px;
    right: 0;
    width: 300px;
    max-height: 400px;
    overflow-y: auto;
    background: var(--bg-card);
    border: var(--bw-hair) solid var(--border-subtle, var(--alpha-white-12));
    border-radius: 12px;
    box-shadow: 0 8px 32px var(--alpha-black-50);
    z-index: 1000;
}
.site-notif-item {
    padding: 12px 16px;
    border-bottom: var(--bw-hair) solid var(--border-subtle, var(--alpha-white-08));
    transition: background 0.15s;
}
.site-notif-item:last-child { border-bottom: none; }
.site-notif-item:hover { background: var(--alpha-white-04); }
.site-notif-item.unread {
    background: var(--alpha-lime-06);
    border-left: var(--bw-heavy) solid var(--accent);
}
.site-notif-item-title {
    font-weight: 600;
    font-size: var(--fs-sm);
    color: var(--text-primary, var(--neutral-white));
    margin-bottom: 2px;
}
.site-notif-item-msg {
    font-size: var(--fs-xs);
    color: var(--text-secondary, #999);
    line-height: 1.3;
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
    overflow: hidden;
}

/* Строка списка — кнопка: по ней открывается само уведомление */
.site-notif-dropdown .site-notif-item,
.site-notif-modal .site-notif-item {
    display: block;
    width: 100%;
    text-align: left;
    background: none;
    border: none;
    border-bottom: var(--bw-hair) solid var(--border-subtle, var(--alpha-white-08));
    color: inherit;
    font: inherit;
    cursor: pointer;
}

.site-notif-dropdown .site-notif-item:hover,
.site-notif-modal .site-notif-item:hover {
    background: var(--alpha-white-04);
}

/* Непрочитанное в выпадающем списке заметно полосой слева */
.site-notif-dropdown .site-notif-item.unread {
    border-left: var(--bw-heavy) solid var(--accent);
    background: var(--alpha-lime-06);
}

.site-notif-dropdown .site-notif-foot {
    position: sticky;
    bottom: 0;
    background: var(--bg-card);
    padding: 10px 16px;
}

/* Текст одного уведомления — целиком, без обрезки */
.site-notif-full {
    padding: 18px 20px 8px;
    font-size: var(--fs-sm);
    line-height: 1.5;
    color: var(--text-secondary, #bbb);
    white-space: pre-wrap;
}

.site-notif-modal .site-notif-item {
    padding: 14px 20px;
}
.site-notif-item-time {
    font-size: var(--fs-2xs);
    color: var(--text-muted);
    margin-top: 4px;
}
@media (max-width: 600px) {
    .site-notif-dropdown { width: 260px; right: -40px; }
}

/* Admin Nav Dropdown (hover-based, like nav-dropdown) */
.admin-nav-dropdown {
    position: relative;
}

/* АДМИНСКАЯ КНОПКА — НЕ «ВОЙТИ», И ЗАЛИВКА ЕЙ НЕ ПОЛОЖЕНА.
   Решение Кости 28.09: «ты перекрасил кнопку в лаймовый цвет, а раньше была
   только обводка».

   ЧТО ПРОИЗОШЛО НА САМОМ ДЕЛЕ — одно понятие, два определения, и беда
   родилась ровно на шве. Кнопка админа носит класс .btn-auth и поверх него
   красит себе текст лаймом. Пока `.btn-auth` была рамкой, лаймовый текст на
   тёмном читался. 20.09 «Войти» стала лаймовой ЗАЛИВКОЙ (решение Кости, и
   оно остаётся) — а про её админского двойника никто не вспомнил: лаймовый
   текст лёг на лаймовую заливку, контраст 1 : 1, и в спокойном состоянии
   кнопка была пустой таблеткой без единой буквы.

   Заливку у «Войти» не трогаем: за ней стоит призыв для гостя. Админская
   кнопка призывом не является — это служебная ссылка, и у неё своя одежда:
   прозрачный фон, лаймовая обводка, лаймовый текст; под курсором обводка
   ярче и лёгкая лаймовая подложка. `!important` убран: правило и так стоит
   ниже `.btn-auth` по файлу и побеждает при равной силе. */
.admin-nav-toggle {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    background: transparent;
    border: var(--bw-hair) solid var(--accent-glow);
    color: var(--accent);
    text-decoration: none;
}

.admin-nav-toggle:hover {
    background: var(--alpha-lime-06);
    border-color: var(--alpha-lime-50);
    color: var(--accent);
}

.admin-nav-toggle .nav-arrow {
    opacity: 0.5;
    transition: transform 0.3s ease;
}

.admin-nav-dropdown:hover .nav-arrow {
    opacity: 1;
    transform: rotate(180deg);
}

.admin-nav-menu {
    left: 0 !important;
    right: auto !important;
    transform: translateY(10px) !important;
    min-width: 220px;
}

.admin-nav-dropdown:hover .admin-nav-menu {
    opacity: 1;
    visibility: visible;
    transform: translateY(0) !important;
}

.admin-nav-menu .nav-dropdown-item {
    opacity: 1;
    transform: none;
}

/* ========================================
   LANGUAGE DROPDOWN
   ======================================== */

.lang-dropdown {
    position: relative;
}

/* Высота из min-height — см. комментарий у .nav-item. Замерено 33: это был
   самый низкий элемент ряда, ниже «Войти» на шесть пикселей и ниже пункта
   меню на восемь.
   ОТДЕЛЬНО, НЕ ТРОГАЮ БЕЗ РЕШЕНИЯ: кегль здесь 0.8rem = 12.8, а это мимо
   девятиступенчатой шкалы (11/12/14/16/18/21/26/32/40). Костя про кегль не
   решал — правится отдельно, вместе с остальными выпадающими из шкалы. */
.lang-toggle {
    display: flex;
    align-items: center;
    gap: 6px;
    /* Рамка — это сигнал «сюда нажимают». Когда её носят двое, она
       перестаёт что-либо значить. В ряду остаётся одна рамка — у «Войти»,
       единственного действия, которого мы ждём от гостя. Выбор языка не
       прячется: он на месте и в том же кегле, просто перестаёт спорить
       с кнопкой. Решение Кости 20.09, вариант 3 на доске 213:145.
       Кегль был 0.8rem = 12.8 — мимо девятиступенчатой шкалы. */
    background: none;
    border: var(--bw-hair) solid transparent;
    color: var(--text-secondary);
    min-height: 36px;
    padding: 0 16px;
    border-radius: var(--radius-full);
    font-size: var(--fs-base);
    font-weight: 500;
    cursor: pointer;
    transition: all var(--transition-fast);
}

.lang-toggle:hover {
    border-color: var(--border-light);
    background: var(--bg-card);
}

.lang-dropdown.active .lang-toggle {
    border-color: var(--accent);
    background: var(--bg-card);
}

.lang-current {
    text-transform: uppercase;
}

.lang-arrow {
    transition: transform var(--transition-base);
    opacity: 0.6;
}

.lang-dropdown.active .lang-arrow {
    transform: rotate(180deg);
}

/* Выпадающее меню */
.lang-menu {
    position: absolute;
    top: calc(100% + 8px);
    right: 0;
    min-width: 160px;

    background: var(--bg-card);
    border: var(--bw-hair) solid var(--border-subtle);
    border-radius: var(--radius-lg);
    box-shadow: var(--shadow-lg);

    padding: 6px;

    /* Скрыто по умолчанию */
    opacity: 0;
    visibility: hidden;
    transform: translateY(-10px) scale(0.95);
    transform-origin: top right;
    transition: all 0.25s cubic-bezier(0.4, 0, 0.2, 1);
}

/* Открываем и по наведению тоже: соседние меню разделов открываются так,
   и человек ждёт от языка того же. Клик оставлен для тач-экранов, где
   наведения не существует */
.lang-dropdown.active .lang-menu,
.lang-dropdown:hover .lang-menu {
    opacity: 1;
    visibility: visible;
    transform: translateY(0) scale(1);
}

.lang-dropdown:hover .lang-toggle {
    border-color: var(--accent);
    background: var(--bg-card);
}

.lang-dropdown:hover .lang-arrow {
    transform: rotate(180deg);
}

/* Опции языка */
.lang-option {
    display: flex;
    align-items: center;
    gap: 10px;
    width: 100%;
    padding: 10px 12px;
    background: transparent;
    border: none;
    border-radius: var(--radius-md);
    color: var(--text-secondary);
    font-size: var(--fs-sm);
    cursor: pointer;
    transition: all var(--transition-fast);
    text-align: left;
    text-decoration: none;
}

.lang-option:hover {
    background: var(--surface-glass-hover);
    color: var(--text-primary);
}

.lang-option.active {
    background: var(--alpha-lime-12);
    color: var(--accent);
}

/* ФЛАГ ЯЗЫКА. 24.09 я заменил его кодом языка буквами — флаг обозначает
   страну, а не язык, — но Костя посмотрел и вернул флаги: «мне надо флаги,
   так лучше было». Решение его, записано на доске. */
.lang-flag {
    font-size: var(--fs-md);
}

.nav-ico {
    width: 18px;
    height: 18px;
    flex: 0 0 auto;
    vertical-align: -3px;
}

.lang-ico {
    width: 14px;
    height: 14px;
    display: block;
}


.lang-name {
    flex: 1;
}

.lang-check {
    opacity: 0;
    color: var(--accent);
    font-weight: 700;
    transform: scale(0);
    transition: all var(--transition-fast);
}

.lang-option.active .lang-check {
    opacity: 1;
    transform: scale(1);
}

/* Анимация появления каждой опции */
.lang-dropdown.active .lang-option {
    animation: langOptionSlide 0.3s ease forwards;
}

.lang-dropdown.active .lang-option:nth-child(1) { animation-delay: 0.05s; }
.lang-dropdown.active .lang-option:nth-child(2) { animation-delay: 0.1s; }
.lang-dropdown.active .lang-option:nth-child(3) { animation-delay: 0.15s; }

@keyframes langOptionSlide {
    from {
        opacity: 0;
        transform: translateX(10px);
    }
    to {
        opacity: 1;
        transform: translateX(0);
    }
}

/* ========================================
   MOBILE LANGUAGE LIST
   ======================================== */

.mobile-lang-list {
    display: flex;
    flex-direction: column;
    gap: 4px;
}

/* Подпись «ЯЗЫК»: было 0.7rem = 11.2, мимо шкалы. Ступень 12. */
.mobile-lang-title {
    font-size: var(--fs-xs);
    text-transform: uppercase;
    letter-spacing: 0.1em;
    color: var(--text-muted);
    padding: 8px 12px 4px;
}

.mobile-lang-option {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 12px 16px;
    background: transparent;
    border: none;
    border-radius: var(--radius-md);
    color: var(--text-secondary);
    font-size: var(--fs-sm);
    cursor: pointer;
    transition: all var(--transition-fast);
    text-align: left;
    text-decoration: none;
}

.mobile-lang-option:hover {
    background: var(--surface-glass-hover);
}

.mobile-lang-option.active {
    background: var(--alpha-lime-12);
    color: var(--accent);
}

/* Скрыто на десктопе, показывается в мобильном меню */
.mobile-lang-list {
    display: none;
}

main {
    padding-top: var(--header-h);
}

/* Секция во всю ширину: фон от края до края, содержимое — по общей линии.
   Отступ --pad-x сам центрует содержимое и не даёт ему разъехаться шире контейнера,
   поэтому обёртка внутри не нужна. */
section {
    width: 100%;
    max-width: none;
    margin-inline: 0;
    /* ВОЗДУХ СЕКЦИИ — КРУТИЛКА, А НЕ ВТОРОЕ ОБЪЯВЛЕНИЕ padding.
       Слои ниже меняют ЗНАЧЕНИЕ, объявление padding остаётся одно на весь файл —
       тот же приём, что у .hero, и те же три условия, чтобы граница героя и первой
       секции не была швом между двумя разными системами.

       Было: var(--section-y-sm) = clamp(1rem, 1.6vw, 1.5rem) — доля ШИРИНЫ ОКНА.
       Замерено 22.09: 40 на 768, 16.384 на 769, 24 на 1512. Втрое поперёк одного
       пикселя, и посередине число, которого нет ни в одной шкале. */
    --section-vozduh: var(--space-10);   /* 40 */
    padding: var(--section-vozduh) var(--pad-x);
}

section:not(.hero) {
    background: var(--bg-secondary);
    border-top: var(--bw-hair) solid var(--border-subtle);
}

h2 {
    font-size: var(--fs-2xl);
    font-weight: 800;
    margin-bottom: 1.25rem;
    color: var(--text-primary);
    letter-spacing: -0.03em;
    text-transform: uppercase;
}

/* Здесь было второе объявление .section-header с margin-bottom: var(--space-lg).
   Оно НИКОГДА не работало: ниже по файлу стоит полное объявление .section-header
   с margin-bottom: var(--space-md), и побеждает оно. Мёртвая строка, которая
   читалась как правило. Отбивку шапки раздела задаёт ОДНО объявление. */


/* ========================================
   HERO SECTION - Apple-style
   ======================================== */

.hero {
    --header-height: 2px;
    /* Занимал экран целиком, и до первого блока приходилось скроллить.
       Теперь высота от содержимого, с потолком — чтобы на больших мониторах
       не растягивался, а на ноутбуке было видно, что страница продолжается */
    min-height: min(80vh, 740px);
    display: flex;
    flex-direction: column;
    align-items: center;
    /* СВЕРХУ ВНИЗ, А НЕ ПО ЦЕНТРУ.
       Было justify-content: center при min-height. Центрирование съедало
       padding-top: объявлено 80, а до бейджа доходило 16 — остаток уходил
       в центровку, и зазор под шапкой не был управляем ни на одной полосе.
       Теперь колонка течёт сверху, а полосу спонсоров прижимает к низу
       flex: 1 у .hero-content (1718) — это и даёт «шапка сверху,
       спонсоры у сгиба» без борьбы двух механизмов. */
    justify-content: flex-start;
    /* ОТБИВКА ШАПКИ — ОДНО ОБЪЯВЛЕНИЕ НА ВСЕ ШИРИНЫ.
       Было пять .hero с разным padding-top (992, 768, 768 второй раз, 993+),
       и в полосе 769–992 отбивки не было вовсе: на 844x390 бейдж стоял под
       шапкой. Шапка меняется ступенькой (--header-h: 64, ниже 600 — 56),
       поэтому calc честно следует за ней сам. */
    /* ДВЕ КРУТИЛКИ ВМЕСТО ВТОРОГО ОБЪЯВЛЕНИЯ.
       Горизонтальному телефону (844x390) нужен воздух меньше: там на всё
       содержимое остаётся 326 точек, и кнопка «Найти игрока» резалась сгибом
       пополам. Написать второй .hero с другим padding значило бы вернуть ровно
       ту болезнь, которую лечили утром, — пять объявлений в пяти полосах.
       Поэтому значение вынесено в имя: медиазапрос крутит КРУТИЛКУ, а
       объявление padding остаётся одно на весь файл. */
    --hero-vozduh: var(--space-10);   /* 40 — воздух под шапкой */
    --hero-shag:   var(--space-6);    /* 24 — шаг между смысловыми группами */
    padding: calc(var(--header-h) + var(--hero-vozduh)) var(--pad-x) var(--space-8);
    position: relative;
    overflow: hidden;
    box-sizing: border-box;
}

.hero-sponsors-section {
    width: 95%;
    max-width: var(--container-narrow);
    margin-top: auto;
    /* 32 сверху — шаг между смысловыми группами. Снизу 0: под лентой
       отбивает сам .hero своим padding-bottom, иначе зазор складывается
       из трёх слагаемых и его нельзя прочитать. */
    padding: var(--space-8) 0 0;
    position: relative;
    z-index: 2;
}

/* ОДНА ПОДПИСЬ НА ВСЕ БЛОКИ СПОНСОРОВ. Решение Кости 25.09: «надо будет
   сделать лайм „Наши спонсоры" и полосы справа и слева с градиентом по
   концам». Он показал первый экран лендинга — там это уже было, — а на
   странице матча у блока спонсоров лиги стояла тихая серая подпись без
   полос. Два блока, два вида одного и того же.
   Правило не копируется, а РАСШИРЯЕТСЯ именем: одно определение на одно
   понятие. Появится третий блок — допишется третьим селектором, а не
   третьей копией.
   НАЙДЕНО ПО ДОРОГЕ, НЕ ЧИНЮ СЕЙЧАС: кегль здесь задан
   clamp(0.7rem, 1.5vw, 0.85rem) — это 11.2 → 13.6, и ни один конец не
   ступень шкалы, а между ними размер течёт непрерывно. Лендинг принят и
   закрыт, трогать его без спроса нельзя. Записано в трекер. */
.hero-sponsors-title,
.lm-sponsors-title {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 16px;
    font-size: clamp(0.7rem, 1.5vw, 0.85rem);
    font-weight: 500;
    text-transform: uppercase;
    letter-spacing: 3px;
    color: var(--accent);
    margin-bottom: var(--space-2);
}

.hero-sponsors-title::before,
.hero-sponsors-title::after,
.lm-sponsors-title::before,
.lm-sponsors-title::after {
    content: '';
    height: 1px;
    flex: 1;
    opacity: 0.4;
}

.hero-sponsors-title::before,
.lm-sponsors-title::before {
    background: linear-gradient(90deg, transparent, var(--accent));
}

.hero-sponsors-title::after,
.lm-sponsors-title::after {
    background: linear-gradient(90deg, var(--accent), transparent);
}

/* НИЗ ПЕРВОГО ЭКРАНА МЕРИТСЯ ПО ЛОГОТИПУ, А НЕ ПО КОРОБКЕ ЛЕНТЫ.
   Моя ошибка 22.09, поймана Костей глазами: я замерил «под лентой 32» от
   ГРАНИЦЫ .sponsors-carousel-infinite — а у неё своих 40 пустого padding
   снизу, внутри коробки. Глаз видел 40 + 32 = 72 воздуха под логотипами,
   и вдобавок герой выходил за сгиб: 896.6 при окне 857. Получалось, что
   фон-фотография тянется ниже карусели, и лента не читалась как конец
   первого экрана.
   Лечим оба слагаемых: у ленты В ГЕРОЕ нет ни margin, ни padding снизу,
   отбивку целиком задаёт .hero своим padding-bottom: 32. Замерено после
   правки в живом браузере Кости: логотипы → низ героя 32, высота героя
   856.6 при окне 857 — экран садится ровно на сгиб.
   Верхний padding 40 у ленты ОСТАВЛЕН: он отделяет логотипы от разделителя
   «НАШИ СПОНСОРЫ», и в сумме с margin-bottom заголовка (8) даёт 48 — шаг
   из шкалы. У ленты на странице спонсоров всё как было (4076). */
.hero-sponsors-section .sponsors-carousel-infinite {
    margin-bottom: 0;
    padding-bottom: 0;
}

/* Мягкий градиент фона — без резких акцентов */
.hero::before {
    content: '';
    position: absolute;
    inset: 0;
    background:
        radial-gradient(ellipse 70% 50% at 70% 30%, var(--alpha-white-04) 0%, transparent 50%),
        var(--bg-primary);
    z-index: -1;
}

.hero-content {
    z-index: 2;
    position: relative;
    width: 95%;
    max-width: var(--container-narrow);
    text-align: center;
    display: flex;
    flex-direction: column;
    align-items: center;
    /* GAP УБРАН НАМЕРЕННО.
       Один gap на колонку не выражает нужный ритм: внутри смысловой группы
       шаг 12, между группами 24. Раньше поверх общего gap на слоган вешали
       margin-top: 26 — 26 + 12.87 давало 39, число, которого нет в шкале.
       Теперь шаг задаёт сам блок, и каждое число читается глазами. */
    gap: 0;
    flex: 1;
    justify-content: flex-start;
    min-height: 0;
}

/* Badge — с акцентной точкой */
.hero-badge {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    background: var(--alpha-lime-06);
    border: var(--bw-hair) solid var(--alpha-lime-20);
    padding: clamp(6px, 1vh, 10px) clamp(12px, 2vw, 18px);
    border-radius: var(--radius-full);
    font-size: clamp(0.7rem, 1.5vw, 0.85rem);
    color: var(--accent);
    font-weight: 500;
    animation: fadeInUp 0.6s ease;
    box-shadow: 0 0 20px var(--alpha-lime-12);
}

.badge-dot {
    /* ОПТИЧЕСКИЙ ЦЕНТР, А НЕ ГЕОМЕТРИЧЕСКИЙ.

       Замер 21.09 на пяти видах и трёх языках: геометрически точка стоит
       ровно посередине пилюли — смещение 0. Но зрительно она задрана, и
       Костя это увидел. Причина: центр СТРОКИ и центр БУКВ — разные вещи.
       В «Сезон 2026» масса букв строчная, её середина ниже середины
       строки на 0.5-1 пиксель в зависимости от кегля.

       Сдвиг задан в em, а не в пикселях: кегль пилюли текучий,
       clamp(0.7rem, 1.5vw, 0.85rem), и фиксированный сдвиг разъехался бы
       между видами. 0.06em даёт 0.8 при кегле 13.6 и 0.67 при 11.2 —
       ровно измеренную разницу.

       ДВИГАЕМ ЧЕРЕЗ top, А НЕ transform: на точке крутится анимация
       livePulse, и она задаёт свой transform: scale(). translateY был бы
       стёрт на первом же кадре. */
    position: relative;
    top: 0.06em;
    width: 6px;
    height: 6px;
    background: var(--accent);
    border-radius: 50%;
    animation: livePulse 2s ease-in-out infinite;
    box-shadow: 0 0 10px var(--accent-glow);
}

/* Заголовок — массивный, уверенный */
.hero-title {
    margin-top: var(--space-3);
    font-size: clamp(2rem, 4.8vw, 3.7rem);
    font-weight: 900;
    line-height: 1;
    letter-spacing: -0.04em;
    color: var(--text-primary);
    animation: fadeInUp 0.6s ease 0.1s both;
    text-transform: uppercase;
}

.hero-title .highlight {
    color: var(--accent);
    text-shadow: 0 0 40px var(--accent-glow);
}

/* Расшифровка под аббревиатурой — тем же приёмом, что на «Инфо» */
.hero-full {
    /* Была почти одного роста со слоганом и того же цвета — две белые строки
       подряд читались как одна. Уменьшаем и приглушаем: это пояснение к
       аббревиатуре, оно принадлежит заголовку, а не стоит рядом с ним */
    font-size: clamp(1rem, 1.5vw, 1.12rem);
    font-weight: 500;
    font-style: italic;
    color: var(--alpha-white-72);
    text-transform: none;
    letter-spacing: 0;
    margin-top: var(--space-3);
    animation: fadeInUp 0.6s ease 0.12s both;
}

/* Слоган */
.hero-slogan {
    /* Тот же размер, что у заголовков разделов (h2, 2rem): слоган и
       «Ближайшие турниры» — соседи по значимости */
    font-size: clamp(1.3rem, 3vw, 2rem);
    font-weight: 800;
    /* Лайм остаётся только у аббревиатуры. Когда слоган тоже был лаймовым
       и светился, два пятна тянули взгляд каждое к себе, и заголовок
       казался крупнее, чем есть */
    color: var(--text-primary);
    text-transform: uppercase;
    letter-spacing: 0.1em;
    /* Отступ больше остальных: здесь кончается «кто мы» и начинается
       «зачем». Одинаковые промежутки между всеми строками и делали из них
       сплошную стену.
       Было 26 поверх общего gap колонки — в сумме 39, числа из шкалы нет.
       Теперь весь шаг здесь и целиком: 24. */
    margin-top: var(--hero-shag);
    animation: fadeInUp 0.6s ease 0.15s both;
}

/* Subtitle — больше воздуха */
.hero-subtitle {
    font-size: clamp(0.95rem, 1.7vw, 1.12rem);
    color: var(--text-secondary);
    max-width: 580px;
    margin-top: var(--space-3);
    line-height: 1.5;
    /* Иначе последнее слово строки уезжает вниз в одиночку и отрывается
       от того, к чему относится */
    text-wrap: balance;
    font-weight: 400;
    animation: fadeInUp 0.6s ease 0.2s both;
}

/* CTA Buttons */
.hero-cta {
    margin-top: var(--hero-shag);
    display: flex;
    gap: var(--space-sm);
    flex-wrap: wrap;
    justify-content: center;
    animation: fadeInUp 0.6s ease 0.3s both;
}

/* Кнопка «Найти партнёра» — Electric Lime с черным текстом */
.btn-primary {
    display: inline-flex;
    align-items: center;
    gap: 0.6rem;
    background: var(--accent);
    color: var(--neutral-black);
    text-decoration: none;
    padding: 18px 36px;
    border-radius: var(--radius-full);
    font-size: var(--fs-base);
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.05em;
    transition: all var(--transition-base);
    box-shadow: 0 0 30px var(--accent-glow);
}

.btn-primary:hover {
    background: var(--accent-hover);
    transform: translateY(-2px);
    box-shadow: 0 0 50px var(--accent-glow);
}

.btn-primary svg {
    stroke: var(--bg-primary);
    width: 18px;
    height: 18px;
}

/* Кнопка «Забронировать корт» — прозрачная с белой обводкой */
.btn-secondary {
    display: inline-flex;
    align-items: center;
    background: transparent;
    color: var(--text-primary);
    text-decoration: none;
    padding: 18px 36px;
    border-radius: var(--radius-full);
    font-size: var(--fs-base);
    font-weight: 500;
    border: var(--bw-thick) solid var(--border-light);
    transition: all var(--transition-base);
}

.btn-secondary:hover {
    border-color: var(--text-secondary);
    background: var(--surface-glass-hover);
}

/* Stats — с анимацией счётчика */
.hero-stats {
    margin-top: var(--hero-shag);
    display: flex;
    align-items: center;
    justify-content: center;
    flex-wrap: wrap;
    row-gap: clamp(12px, 2vh, 20px);
    gap: clamp(16px, 4vw, 48px);
    margin-bottom: 0;
    animation: fadeInUp 0.6s ease 0.4s both;
}

.stat {
    display: flex;
    flex-direction: column;
    gap: 4px;
}

.stat-value {
    font-size: clamp(1.5rem, 4vw, 2.25rem);
    font-weight: 700;
    color: var(--text-primary);
    letter-spacing: -0.02em;
    font-variant-numeric: tabular-nums;
    /* БЫЛО ДРУГОЕ СЕМЕЙСТВО НА ТОМ ЖЕ ЭКРАНЕ: здесь стояло 'SF Pro
       Display' без Inter — на Маке цифры статистики рисовались не тем
       шрифтом, что всё вокруг, а на других системах падали на system-ui.
       Одна запись семейства на продукт; ширину цифр держит
       `tabular-nums` строкой выше, а не выбор шрифта. */
    font-family: var(--font-sans);
}

/* Класс для анимации набега цифр */
.stat-value.animate {
    animation: countUp 0.8s ease-out forwards;
}

@keyframes countUp {
    from {
        opacity: 0;
        transform: translateY(10px);
    }
    to {
        opacity: 1;
        transform: translateY(0);
    }
}

.stat-label {
    font-size: var(--fs-sm);
    color: var(--text-muted);
    font-weight: 450;
    text-transform: uppercase;
    letter-spacing: 0.05em;
}

/* ========================================
   HERO VISUAL (Right side)
   ======================================== */

.hero-visual {
    display: none;
}

.hero-image-container {
    position: relative;
    width: 100%;
    max-width: 440px;
}

/* Floating Cards — чистый стиль без glow */
.floating-card {
    position: absolute;
    display: flex;
    align-items: center;
    gap: 14px;

    background: var(--bg-card);
    border: var(--bw-hair) solid var(--border-subtle);

    padding: 16px 20px;
    border-radius: var(--radius-lg);
    box-shadow: var(--shadow-md);
    transition: all var(--transition-base);
}

.floating-card:hover {
    border-color: var(--border-light);
    transform: translateY(-2px);
}

.floating-card .card-icon {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 44px;
    height: 44px;
    background: var(--bg-elevated);
    border-radius: var(--radius-md);
}

.floating-card .card-icon svg {
    stroke: var(--accent);
}

.floating-card .card-info {
    display: flex;
    flex-direction: column;
    gap: 2px;
}

.floating-card .card-title {
    font-size: var(--fs-2xs);
    color: var(--text-muted);
    font-weight: 500;
    text-transform: uppercase;
    letter-spacing: 0.05em;
}

.floating-card .card-value {
    font-size: var(--fs-md);
    font-weight: 600;
    color: var(--text-primary);
}

.floating-card .card-value small {
    font-size: var(--fs-xs);
    color: var(--accent);
    font-weight: 500;
}

.card-rating {
    top: 15%;
    right: -10px;
    animation: floatSoft 5s ease-in-out infinite, fadeInUp 0.8s ease 0.4s both;
}

.card-match {
    bottom: 20%;
    left: -20px;
    animation: floatSoft 5s ease-in-out 1.5s infinite, fadeInUp 0.8s ease 0.5s both;
}

@keyframes floatSoft {
    0%, 100% {
        transform: translateY(0);
    }
    50% {
        transform: translateY(-8px);
    }
}

/* Animations */
@keyframes fadeInUp {
    from {
        opacity: 0;
        transform: translateY(30px);
    }
    to {
        opacity: 1;
        transform: translateY(0);
    }
}


/* ========================================
   HERO VIDEO BACKGROUND
   ======================================== */

.hero-bg {
    position: absolute;
    inset: 0;
    z-index: 0;
    overflow: hidden;
}

.hero-bg-image {
    width: 100%;
    height: 100%;
    object-fit: cover;
    opacity: 0.4;
    animation: heroZoom 18s ease-in-out infinite alternate;
    transform-origin: center center;
    object-position: 50% 30%;
}

@keyframes heroZoom {
    0% {
        transform: scale(1);
    }
    100% {
        transform: scale(1.08);
    }
}

.hero-bg-overlay {
    position: absolute;
    inset: 0;
    /* Затемнение было 85%: тёмный снимок под ним превращался в чёрное
       пятно. Текст читается и при 62% — проверено на этой фотографии */
    background: linear-gradient(
        135deg,
        var(--alpha-black-60) 0%,
        var(--alpha-black-50) 50%,
        var(--alpha-black-50) 100%
    );
}

/* ВИДЕО-ФОН КЫРГЫЗСКОЙ ГЛАВНОЙ УДАЛЁН 22.09.
   index-kg.html:283 держал <div class="hero-video-bg"> с роликом, который
   тянулся с чужого CDN assets.mixkit.co (сток «tennis player serving»).
   У классов .hero-video-bg и .hero-video-overlay НЕ БЫЛО НИ ОДНОГО правила
   в css: div стоял в обычном потоке, толкал герой вниз на 157px, и зазор
   под шапкой выходил 197 вместо 40. Ни ru, ни en этого не показывали.
   Решение Кости: на ru и en видео нет, значит и на kg оно не нужно.
   Разметка kg заменена на тот же .hero-bg со снимком images/hero-courts.jpg,
   что на двух других языках, — один фон на три языка, без чужого сервера. */

.hero-image {
    width: 100%;
    height: 100%;
    object-fit: cover;
    border-radius: var(--radius-xl);
    aspect-ratio: 4/5;
}

/* ========================================
   SECTION COMMON STYLES
   ======================================== */

/* Шапка раздела: заголовок и ссылка «все».

   Отступ был 5rem — восемьдесят пикселей пустоты между названием блока и
   первой карточкой. На каждом разделе это лишний экран прокрутки, а связь
   заголовка с содержимым читалась хуже: они выглядели двумя разными вещами.

   Заголовок тоже был крупноват: 2rem почти вровень с заголовком страницы,
   хотя это подпись к ряду карточек, а не название документа. */
.section-header {
    display: flex;
    justify-content: space-between;
    align-items: center;
    /* ОДНА ВЫСОТА ВО ВСЕХ РАЗДЕЛАХ. Заголовок турниров стоял на 3px ниже, чем
       у live, и это оказалось не ошибкой вёрстки: у «Все турниры» цель нажатия
       44, у live ссылки нет вовсе — шапка выходила 44 против 39, и заголовок
       по центру съезжал. Цель нажатия трогать нельзя, поэтому высоту получают
       ВСЕ шапки: с ссылкой и без неё заголовок теперь стоит одинаково. */
    min-height: 44px;
    margin-bottom: var(--space-md);
    flex-wrap: wrap;
    gap: var(--space-sm);
}

/* Свой нижний отступ заголовку здесь не нужен: расстояние до карточек
   держит сама шапка, иначе они складываются и получается двойной провал */
.section-header h2 {
    font-size: var(--fs-xl);
    margin-bottom: 0;
}

.link-all {
    /* ЦЕЛЬ НАЖАТИЯ 44, ВИД НЕ МЕНЯЕТСЯ.

       Замер 21.09 на пяти видах и трёх языках: 119x21 на десктопе, 102x18
       на телефоне — при пороге 44. Правило не новое: доска
       «Header & navigation — APPROVED SPEC» уже говорит «Tap targets 44
       minimum under (pointer: coarse)», здесь оно просто применяется.

       inline-flex, а не блок: ширина остаётся по тексту, растёт только
       высота области нажатия. Кегль, цвет и начертание те же — человек
       не увидит разницы, палец увидит. */
    display: inline-flex;
    align-items: center;
    min-height: 44px;
    color: var(--accent);
    text-decoration: none;
    font-weight: 600;
    font-size: var(--fs-sm);
    transition: all var(--transition-fast);
}

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

/* ========================================
   TOURNAMENTS SECTION
   ======================================== */

.section-tournaments {
    background: var(--bg-primary);
}

/* ОПРЕДЕЛЕНИЕ СЕТКИ ТУРНИРОВ СНЕСЕНО ОТСЮДА 16.10. Оно давало две
   колонки, но ниже по этому же файлу стоит второе — двенадцать колонок,
   — и при равной силе выигрывало оно, всегда. Понятие одно, определение
   теперь тоже одно: `.tournaments-grid` живёт в разделе карточки турнира.
   Сторож: tools/check-turniry-vidy.js. */

.tournament-card {
    background: var(--bg-card);
    border: var(--bw-hair) solid var(--border-subtle);
    border-radius: var(--radius-xl);
    overflow: hidden;
    transition: all var(--transition-base);
    cursor: pointer;
    text-decoration: none;
    color: inherit;
}

.tournament-card:hover {
    border-color: var(--border-light);
    transform: translateY(-4px);
}

.tournament-card-featured {
    grid-column: 1 / -1;
    display: grid;
    grid-template-columns: 1.2fr 1fr;
    max-height: 400px;
    overflow: hidden;
}

.tournament-image {
    position: relative;
    overflow: hidden;
}

.tournament-card-featured .tournament-image {
    height: 100%;
    max-height: 400px;
}

.tournament-card-featured .tournament-info {
    overflow-y: auto;
}

.tournament-image img {
    width: 100%;
    height: 100%;
    object-fit: cover;
    min-height: 200px;
}

.tournament-badge {
    position: absolute;
    top: var(--space-sm);
    left: var(--space-sm);
    background: var(--accent);
    color: var(--neutral-black);
    padding: 6px 14px;
    border-radius: var(--radius-full);
    font-size: var(--fs-xs);
    font-weight: 700;
    text-transform: uppercase;
}

.tournament-info {
    padding: var(--space-lg);
    display: flex;
    flex-direction: column;
    gap: var(--space-sm);
}

.tournament-date {
    color: var(--accent);
    font-size: var(--fs-sm);
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.05em;
}

.tournament-info h3 {
    font-size: var(--fs-xl);
    font-weight: 700;
    color: var(--text-primary);
}

.tournament-info p {
    color: var(--text-secondary);
    font-size: var(--fs-sm);
}

.tournament-meta {
    display: flex;
    gap: var(--space-md);
    color: var(--text-muted);
    font-size: var(--fs-sm);
}

.tournament-meta strong {
    color: var(--text-primary);
}

/* ========================================
   LIVE SECTION
   ======================================== */

.section-live {
    background: var(--bg-secondary);
}

.live-indicator {
    display: inline-block;
    width: 10px;
    height: 10px;
    background: var(--danger);
    border-radius: 50%;
    margin-right: 8px;
    animation: livePulse 1.5s ease-in-out infinite;
    box-shadow: 0 0 15px rgba(255, 71, 87, 0.5);
}

/* Карточка живого матча компактнее: в ней всего два имени и счёт, а
   занимала она столько же места, сколько карточка турнира с афишей */
.live-matches {
    display: grid;
    /* Ширину карточки ограничиваем: при одном матче она растягивалась на всю
       страницу, и два имени со счётом тонули в пустоте. Теперь карточки
       выстраиваются слева направо и держат свой размер */
    grid-template-columns: repeat(auto-fill, minmax(340px, 440px));
    /* РЯД ИДЁТ СЛЕВА НАПРАВО. Решение Кости 22.09, глазами, после замера.

       ЧТО ПРОБОВАЛИ И ОТВЕРГЛИ: центрирование ряда. Числа были в его пользу —
       при одном матче справа оставалось 960 из 1400 на десктопе и 539 из 979
       на планшете боком. Сделали, замерили: одна карточка 480/480, две 248/248,
       ширины и высота не поплыли — технически безупречно. И отвергли по виду:
       «по центру как бедный родственник». Заголовок раздела стоит у левого края,
       и одинокая карточка посреди пустоты читается не как «вот что сейчас
       происходит», а как «тут больше ничего нет».

       Слева направо: первая у левого края под заголовком, вторая правее, третья
       ещё правее — ряд наполняется, а не расползается из середины.

       НЕ МЕНЯТЬ на center, не показав Косте глазами. Числа этот спор уже
       проиграли один раз. */
    justify-content: start;
    gap: var(--space-md);
}

.live-match {
    background: var(--bg-card);
    border: var(--bw-hair) solid var(--border-subtle);
    border-radius: var(--radius-lg);
    /* Card 29:65, Padding=Compact — 16. Было 16/18: восемнадцати нет ни в шкале,
       ни в компоненте. Радиус и рамка с компонентом сходились и раньше. */
    padding: var(--space-4);
    display: flex;
    flex-direction: column;
    gap: 10px;
    /* ФОТО ЗА СОДЕРЖИМЫМ — решение Кости 24.09, вариант Б, по снимкам.
       Было: та же заливка bg/card, что у новости, корта и значка, — живой
       матч ничем не отличался от архивной записи, и единственное, что кричало
       «идёт прямо сейчас», был бейдж высотой 24.
       Слои собственные, а не фон карточки: заливка bg/card остаётся снизу и
       работает, пока снимок не загрузился и если его вовсе нет. */
    position: relative;
    overflow: hidden;
    isolation: isolate;
}

/* Содержимое поднимается над слоями. Без этого текст лежал бы ПОД скримом. */
.live-match > * {
    position: relative;
    z-index: 1;
}

/* СНИМОК И СКРИМ — ОДНИМ СЛОЕМ, ДВУМЯ ЗАЛИВКАМИ.
   Вместе, а не двумя псевдоэлементами, СПЕЦИАЛЬНО: если файла снимка нет,
   вся строка background-image становится недействительной и НЕ РИСУЕТСЯ
   НИЧЕГО — ни фотографии, ни затемнения. Разведи их по ::before и ::after —
   и при пропавшем файле останется голый градиент, который зальёт левую
   половину плоской карточки почти чёрным. Умолчание — свойство данных.

   Снимок сдвинут вправо и увеличен: корт встаёт за колонку счёта, где пусто,
   а имена читаются по тёмному. Корт по центру пробовали — линии разметки
   проходят ровно под именами (вариант А, отвергнут Костей по виду).
   132% и 82% — НАМЕРЕНО: это кадрирование фотографии, а шкала меряет размеры
   элементов, а не кадр.

   СКРИМ той же семьи, что у героя лендинга (.hero-bg-overlay выше): наклонный
   градиент, плотный там, где текст, прозрачный там, где фотография дышит.
   У героя 135deg 0.62 → 0.40, здесь 95deg и четыре стопа, потому что текст
   собран в левой половине, а не по диагонали.

   ЗАМЕРЕНО по четырём снимкам, худшее из четырёх (порог 4.5 для мелкого,
   3 для графики):
     серый 14 .match-set, левый край      4.70
     белый 16 имена, середина            11.62
     белый 16/800 счёт, правый край       8.72
     лайм, точка подачи, правый край      7.42
   ЛЕВЫЙ СТОП 0.92 — НЕСУЩИЙ. На нём стоят три полупрозрачные подложки:
   --warning-subtle у разминки, --surface-glass-hover у паузы и у заглушки
   аватара. Badge 27:53 запрещает тинты ПОВЕРХ ЧУЖОГО СНИМКА: их контраст
   зависит от того, что загрузил организатор. Здесь снимки свои, их четыре и
   они затемнены замером, и под стопом 0.92 все три тинта становятся ЛУЧШЕ,
   чем на плоской карточке: разминка 7.02 → 7.33, аватар 14.74 → 15.34,
   пауза 8.28 → 8.52. Опустишь левый стоп — уронишь этот пол. Следит правило. */
.live-match::before {
    content: '';
    position: absolute;
    inset: 0;
    z-index: 0;
    background-image:
        linear-gradient(
            95deg,
            rgba(10, 10, 10, 0.92) 0%,
            rgba(10, 10, 10, 0.68) 38%,
            rgba(10, 10, 10, 0.20) 72%,
            rgba(10, 10, 10, 0.06) 100%
        ),
        var(--lm-photo);
    background-size: auto, 132%;
    background-position: 0 0, 82% 50%;
    background-repeat: no-repeat;
}

/* ЧЕРЕДОВАНИЕ — ПО МЕСТУ В РЯДУ, А НЕ ПО МАТЧУ. Решение Кости 24.09:
   «если прямых два — лучше разные ставить».
   Считаем номер карточки, а не хэш идентификатора: соседние никогда не
   совпадут, и у одного матча фон не прыгнет, когда счёт обновится по сокету.
   Тот же приём, что у витрины турниров (.tournaments-grid > .tc:nth-child).
   Снимков четыре, пятая карточка начинает круг заново.
   Мяч в набор НЕ ВЗЯТ: он лаймовый и спорит и с акцентом, и с точкой подачи.
   Файл images/live-fony/kort-ball.webp лежит рядом неиспользованным.
   ВЕС: четыре снимка вместе 28.9 КБ. Самая лёгкая обложка героя — 86 КБ. */
.live-matches > .live-match:nth-child(4n+1) { --lm-photo: url('../images/live-fony/kort-grass.webp'); }
.live-matches > .live-match:nth-child(4n+2) { --lm-photo: url('../images/live-fony/kort-hard.webp'); }
.live-matches > .live-match:nth-child(4n+3) { --lm-photo: url('../images/live-fony/kort-clay.webp'); }
.live-matches > .live-match:nth-child(4n+4) { --lm-photo: url('../images/live-fony/kort-night.webp'); }

.match-status {
    display: flex;
    align-items: center;
    gap: var(--space-sm);
}

/* БЕЙДЖ СТАТУСА — ПО КОМПОНЕНТУ Badge 27:53.
   Было: сплошной красный #FF3B30 и пульсация У ВСЕХ статусов, включая разминку
   и паузу. Разминка — это ещё не игра, а выглядела как «идёт прямо сейчас»,
   и красный переставал что-либо значить.
   Компонент: высота 24, padding 8, radius/full, 11 Medium uppercase, ls 0.5.
   Solid — только для ОДНОГО бейджа, который должен доминировать. Здесь это LIVE.
   То же решение уже принято на странице матча (css/live-match.css:82-92) —
   два независимых источника сошлись. */
.live-badge {
    display: inline-flex;
    align-items: center;
    height: 24px;
    padding: 0 var(--space-2);
    border-radius: var(--radius-full);
    font-size: var(--fs-2xs);
    font-weight: 500;
    text-transform: uppercase;
    letter-spacing: 0.5px;
    background: var(--danger-subtle);
    color: var(--danger);
}

/* Danger / Solid — единственный, кто доминирует, и единственный, кто пульсирует */
.live-badge.is-live {
    background: var(--danger);
    /* Белый на #FF4757 — 3.34, порог для мелкого текста 4.5.
       Почти-чёрный на той же заливке — 5.93. Яркость заливки не трогаем:
       этот бейдж обязан доминировать. Та же пара, что у лайма. */
    color: var(--danger-on);
    animation: livePulse 1.5s ease-in-out infinite;
}

/* Warning / Subtle */
.live-badge.is-warmup {
    background: var(--warning-subtle);
    color: var(--warning);
}

/* Neutral / Subtle */
.live-badge.is-paused {
    background: var(--surface-glass-hover);
    color: var(--text-secondary);
}

/* Neutral / Subtle — ЗАВЕРШЁН. Тон тот же, что у паузы: по доске
   328:9 завершённый матч и пауза оба нейтральны. Повтор намеренный
   и стережётся правилом: два состояния — два имени, один тон. */
.live-badge.is-completed {
    background: var(--surface-glass-hover);
    color: var(--text-secondary);
}

.match-set {
    color: var(--text-muted);
    font-size: var(--fs-sm);
}

.match-players {
    display: flex;
    flex-direction: column;
    gap: 6px;
}

.match-players .player {
    display: flex;
    align-items: center;
    gap: 10px;
}

.match-players .player img {
    width: 32px;
    height: 32px;
    border-radius: 50%;
    object-fit: cover;
    flex-shrink: 0;
}

/* ЗАГЛУШКА АВАТАРА — ИНИЦИАЛЫ, А НЕ КАРТИНКА С ЧУЖОГО CDN.
   Было: два запроса на placehold.co на каждую карточку, и в кружке стоял «?».
   Avatar 29:82 говорит прямо: Size=sm это 32, а когда фото нет — инициалы на
   accent/subtle с текстом accent/default. «Фото нет у большинства» — это
   умолчание компонента, а не исключение. Ни одного запроса наружу. */
.match-players .player .live-avatar {
    width: 32px;
    height: 32px;
    border-radius: 50%;
    flex-shrink: 0;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    /* Одна заглушка на весь сайт, нейтральная. Была лаймовой — см. .avatar-initials. */
    background: var(--surface-glass-hover);
    color: var(--text-primary);
    font-size: var(--fs-xs);
    font-weight: 700;
    letter-spacing: 0.02em;
    text-transform: uppercase;
}

.player-name {
    flex: 1;
    font-weight: 500;
    min-width: 0;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.player-score {
    font-size: var(--fs-md);
    font-weight: 700;
    font-variant-numeric: tabular-nums;
    letter-spacing: 1px;
    min-width: 54px;
    text-align: right;
}

.player-score.serving::before {
    content: "•";
    color: var(--accent);
    margin-right: 6px;
}

/* ========================================
   BADGES CTA SECTION
   ======================================== */

.section-badges-cta {
    /* Это перебивка между разделами, а не раздел: даёт передышку и зовёт
       зарегистрироваться. Поэтому ниже соседей, а не вровень с ними */
    padding: var(--section-y-sm) var(--pad-x);
    background: var(--bg-secondary);
    position: relative;
    overflow: hidden;
}

.badges-cta-inner {
    max-width: var(--container-narrow);
    margin: 0 auto;
    text-align: center;
}


/* Перебивке не нужен голос раздела: заголовок мельче, чем у «Ближайших
   турниров», иначе она спорит с ними за внимание */
/* ПЕРЕБИВКА ГОВОРИТ ТИШЕ РАЗДЕЛА. Решение Кости 24.09, доска 358:9, блоки 13-14,
   смотрено 1:1. Было clamp(1.05rem, 1.8vw, 1.25rem) = 16.8 … 20 при весе 800:
   четыре кегля, из них на шкале только один, и вес раздела на всех видах.
   Замер показал, что на 768 и на 390 перебивка была НЕОТЛИЧИМА от заголовка
   раздела — 21/800 против 21/800. Теперь ступень h3: 21 / Bold, а с 768 — 16.
   Заморозка проверяет ОТНОШЕНИЕ к заголовку раздела, а не это число. */
.badges-cta-text h2 {
    font-size: var(--fs-lg);
    font-weight: var(--fw-bold);
    line-height: 1.3;
    letter-spacing: 0;
    color: var(--text-primary);
    margin-bottom: 6px;
}

.badges-cta-text p {
    font-size: var(--fs-base);
    color: var(--text-secondary);
    margin-bottom: var(--space-lg);
}

.badges-cta-wrap {
    position: relative;
    margin: 20px 0 0;
}

.badges-cta-cards {
    display: grid;
    grid-template-columns: repeat(6, 1fr);
    gap: 12px;
}

/* Второй ряд уходит в туман: видно, что достижений больше, но чтобы их
   открыть — надо играть. Поверх ложится кнопка */
.badges-cta-locked {
    margin-top: 12px;
    filter: blur(2.5px);
    -webkit-mask-image: linear-gradient(to bottom, rgba(0,0,0,0.55) 0%, transparent 62%);
    mask-image: linear-gradient(to bottom, rgba(0,0,0,0.55) 0%, transparent 62%);
    pointer-events: none;
}

/* ПЛИТКА — КОМПОНЕНТ Card 29:65, Padding=Dense 12. Решение Кости 24.09.
   Радиус, фон и край у неё и раньше совпадали с компонентом; разъехались
   только поля: было 12/10 на широком и 9/5 на узком — ни то, ни другое не
   значение компонента. Третья плотность заведена В КОМПОНЕНТЕ, а не здесь:
   поле 12 встречается в продукте 64 раза, чаще шестнадцати, и стоит на шкале
   8 · 12 · 16 · 24 · 32 · 40, тогда как 10 — нет. */
.badge-cta-card {
    background: var(--bg-card);
    border: var(--bw-hair) solid var(--border-subtle);
    border-radius: var(--radius-lg);
    padding: var(--space-3);          /* 12 — Padding=Dense */
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: var(--space-2);              /* 8 */
    transition: transform 0.2s ease, border-color 0.2s ease;
}

/* ЗДЕСЬ ЛЕЖАЛО ВТОРОЕ ПРАВИЛО .badge-cta-card:hover, убрано 24.09: оно просило
   подъём на 4 и полный акцент в рамке, а побеждало то, что ниже по файлу —
   подъём на 3 и лайм в 35%. Два правила на один селектор в одном файле: правка
   первого молча не меняла ничего. */

.badge-cta-emoji {
    font-size: var(--fs-lg);
    line-height: 1;
    transition: transform 0.25s ease, filter 0.25s ease;
}

/* Оживляем без сторонних библиотек: значок подрастает под курсором и получает
   лаймовый ореол. Настоящий праздник уместен в момент получения достижения,
   а не по кругу на главной */
.badge-cta-card:hover {
    transform: translateY(-3px);
    border-color: var(--alpha-lime-30);
}

.badge-cta-card:hover .badge-cta-emoji {
    transform: scale(1.15);
    filter: drop-shadow(0 0 10px var(--alpha-lime-50));
}

/* ПОДПИСЬ ЗНАЧКА НЕЙТРАЛЬНА. Решение Кости 24.09. Была лаймовой на каждой из
   двенадцати плиток — шесть открытых и шесть под туманом, — и всё это прямо
   над лаймовой кнопкой, единственным действием секции. Правило 109:370: лайм
   принадлежит одному главному действию в поле зрения. Та же логика сделала
   нейтральными заглушку аватара и плашку категории новости. */
.badge-cta-name {
    font-size: var(--fs-base);
    font-weight: var(--fw-bold);
    color: var(--text-primary);
}

.badge-cta-desc {
    font-size: var(--fs-xs);
    color: var(--text-muted);
    line-height: 1.3;
}

.badges-cta-btn {
    position: relative;
    /* Внутри коробки кнопка обычная, заливная — ровно как в карточке входа */
    /* ВЫСОТА ЗАДАЁТСЯ СВОЙСТВОМ, А НЕ ПОЛЯМИ — правило компонента Button 17:82.
       Было padding 13/36, из чего выходило 47 на широком и 43 на узком. 43 ниже
       порога цели нажатия 44, и это единственная кнопка всей секции.
       Ступень md = 44 — умолчание компонента и минимум по Apple HIG.
       Горизонтальное поле 36 → 32: 36 не ступень шкалы отступов.
       РАДИУС ОСТАЁТСЯ 12 — решение Кости 24.09, вариант A доски 358:9:
       двенадцать носят все восемь кнопок внутри коробок-гейтов, и радиус для
       всех восьми решается на шаге компонентов вместе с самой коробкой. */
    display: inline-flex;
    align-items: center;
    justify-content: center;
    height: var(--btn-h-md);          /* 44 */
    padding: 0 var(--space-8);        /* 32; было 36 — не ступень */
    background: var(--accent);
    color: var(--neutral-black);
    font-size: var(--fs-sm);
    font-weight: 800;
    border-radius: 12px;
    text-decoration: none;
    transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
    box-shadow: 0 0 24px var(--alpha-lime-20), 0 0 60px var(--alpha-lime-06);
}

/* Коробка вокруг кнопки — тот же вид, что у карточки «Весь рейтинг виден
   после входа»: тёмный фон, скруглённые углы и бегущий по контуру огонёк */
.badges-cta-box {
    position: relative;
    display: inline-block;
    margin-top: -46px;
    /* ПОЛЯ НА ШКАЛЕ. Было 22/34 — ни одно не ступень. Эта коробка с 24.09
       назначена ЭТАЛОННОЙ: три её копии (.guest-cta-card, .pl-guest-cta,
       .pt-guest-cta) подключаются к ней на шаге компонентов. */
    padding: var(--space-6) var(--space-8);   /* 24 / 32 */
    z-index: 2;
    background: rgba(18, 18, 18, 0.85);
    border-radius: 24px;
    backdrop-filter: blur(20px);
    -webkit-backdrop-filter: blur(20px);
    overflow: hidden;
    isolation: isolate;
}

.badges-cta-box::before {
    content: '';
    position: absolute;
    inset: 0;
    border-radius: 24px;
    padding: 1px;
    background: conic-gradient(
        from var(--guest-angle, 0deg),
        transparent 0%,
        var(--accent) 10%,
        transparent 20%,
        transparent 50%,
        var(--accent-glow) 60%,
        transparent 70%
    );
    -webkit-mask: linear-gradient(var(--neutral-white) 0 0) content-box, linear-gradient(var(--neutral-white) 0 0);
    mask: linear-gradient(var(--neutral-white) 0 0) content-box, linear-gradient(var(--neutral-white) 0 0);
    -webkit-mask-composite: xor;
    mask-composite: exclude;
    animation: guest-border-spin 4s linear infinite;
    z-index: -1;
}

.badges-cta-btn:hover {
    background: var(--accent-hover);
    transform: translateY(-2px);
    box-shadow: 0 8px 24px var(--alpha-lime-20);
}

/* ЗДЕСЬ ЛЕЖАЛИ ТРИ МЁРТВЫХ СЛОЯ, убраны 24.09.
   Блок max-width: 768 дублировал селектор .badges-cta-cards со вторым таким же
   блоком ниже по файлу, и побеждал тот: зазор, поля плитки и кегль эмодзи
   отсюда не применялись никогда.
   Блоки max-width: 540 и max-width: 480 просили по две колонки — а на 390 было
   намерено ТРИ. Их перекрывал тот же блок 768, лежащий ниже.
   Доказательство числом: плитка на 390 равнялась 114 = (358 − 2 × 8) / 3;
   две колонки дали бы 175. Правило может быть зелёным, читаемым и мёртвым. */

/* Compact grid for all badges (authorized users) */
/* ЛЕСТНИЦА ГРАНИЦ. Решение 23.09: по ширине два числа, 640 и 992, остальное
   делится высотой и поворотом. Эта секция — первая, построенная на ней.
   Шесть колонок от 993, три ниже: на 768 секция перестаёт быть телефоном. */
@media (max-width: 992px) {
    .badges-cta-cards {
        grid-template-columns: repeat(3, 1fr);
        gap: var(--space-3);
    }

    /* ПОЯСНЕНИЕ К ЗНАЧКУ ОСТАЁТСЯ ТОЛЬКО НА ШИРОКОМ. Решение Кости 24.09.
       Это перебивка-баннер между разделами, а не раздел с содержимым: её работа
       — показать, что достижения есть, и довести до кнопки. С пояснениями на
       768 секция выходила 518 против 444, плюс 37% ради второй строки.
       Смысл несёт название; как получить значок, объясняет страница достижений
       за кнопкой. Тот же приём, что у карточек турниров и новостей в лентах:
       где тесно, карточка теряет вторичную строку, а не ширину. */
    .badge-cta-desc { display: none; }
}

/* ЗАГОЛОВОК ПЕРЕБИВКИ ИДЁТ ЗА ЗАГОЛОВКОМ РАЗДЕЛА, А НЕ ЗА ШИРИНОЙ.
   Заголовки разделов падают на мобильную ступень с 768 — и перебивка обязана
   падать вместе с ними, иначе в полосе 641–768 они читаются одинаково.
   Это осознанное 768 в секции, которая в остальном живёт на 640 и 992.
   Заморозка проверяет ОТНОШЕНИЕ: перебивка строго мельче заголовка раздела на
   каждом из пяти видов. Когда заголовок раздела переедет на 640 — перебивка
   поедет за ним, или правило упадёт и скажет. */
@media (max-width: 768px) {
    .badges-cta-text h2 { font-size: var(--fs-base); }

    /* ПЕРЕНЕСЕНО СЮДА 24.09 с границы 640, поймал настоящий Playwright:
       3 падения из 150, все на планшете, на трёх языках. Заголовок падал на
       768, подзаголовок — только на 640, и в полосе 641-768 оба сидели на 16:
       пара схлопывалась в один вес, отличаясь лишь жирностью. Правило было
       написано верно и поставлено не на ту границу. Теперь пара едет ОДНОЙ
       границей, и заморозка сторожит именно это, а не сами числа. */
    .badges-cta-text p { font-size: var(--fs-sm); }
}

/* ТЕЛЕФОН. Подпись значка встаёт на телефонную ступень лестницы card title,
   а пояснение к значку на плитке в 111 пикселей не читается. Подзаголовок
   отсюда УЕХАЛ 24.09 на границу 768, к своему заголовку. */
@media (max-width: 640px) {
    .badge-cta-name {
        font-size: var(--fs-sm);
        line-height: 1.1;
        text-align: center;
    }
}

/* ТЕСНО ПО ВЫСОТЕ — телефон боком. Два ряда, туман и наезд коробки остаются:
   это форма секции, решение Кости 24.09. Место находим в плитке — она теряет
   пояснение, ровно как карточки турниров и новостей в своих лентах.
   КОЛОНКИ ВОЗВРАЩАЕМ НА ШЕСТЬ: 844 попадает в max-width: 992, и без этой
   строки лента получила бы три колонки — замерено 559 при экране 390, хуже
   исходных 469. Блок, который не перебить снизу, обязан ограничить сам себя. */
@media (max-width: 992px) and (orientation: landscape) and (max-height: 500px) {
    .badges-cta-cards { grid-template-columns: repeat(6, 1fr); }
}

.badges-cta-cards.badges-all {
    grid-template-columns: repeat(auto-fill, minmax(100px, 1fr));
    gap: var(--space-sm);
}
.badges-cta-cards.badges-all .badge-cta-card { padding: var(--space-sm); }
.badges-cta-cards.badges-all .badge-cta-emoji { font-size: var(--fs-xl); }
.badges-cta-cards.badges-all .badge-cta-name { font-size: var(--fs-xs); }
.badges-cta-cards.badges-all .badge-cta-desc { display: none; }

/* Locked (unearned) badge state */
.badge-cta-card.locked { opacity: 0.4; filter: grayscale(1); }
.badge-cta-card.locked:hover { opacity: 0.7; filter: grayscale(0.5); }

/* ========================================
   RANKINGS SECTION
   ======================================== */

.section-rankings {
    background: var(--bg-primary);
}

.section-rankings .section-header {
    margin-bottom: var(--space-md);
}

/* ЗАГЛУШКА АВАТАРА — ОДНА НА ВЕСЬ САЙТ. Размер не задаёт: его даёт место,
   где она стоит, тем же объявлением, что и у фотографии. Avatar 29:82:
   нет фото -> инициалы на accent/subtle с текстом accent/default. */
.avatar-initials {
    flex-shrink: 0;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    border-radius: 50%;
    /* НЕЙТРАЛЬНАЯ, А НЕ ЛАЙМОВАЯ. Решение Кости 23.09 «хочется одинаково»,
       обосновано разбором FAANG: цвет на этом сайте уже занят. Красный,
       зелёный, синий и серый держат СОСТОЯНИЕ турнира; лайм по правилу
       109:370 принадлежит ОДНОМУ главному действию в поле зрения. Аватар —
       это личность, а не состояние и не действие, поэтому он не занимает
       ни то, ни другое. Так же у Apple в Контактах и у Linear: серый круг,
       белые буквы. Палитра «цвет по имени» (Google, Slack) здесь не годится
       именно потому, что цвет у нас уже что-то значит.
       Белый на подложке 8% даёт 14.1 — мерено. */
    background: var(--surface-glass-hover);
    color: var(--text-primary);
    font-weight: 700;
    letter-spacing: 0.02em;
    text-transform: uppercase;
    overflow: hidden;
    /* Кегль по умолчанию — для мест, где размер кружка обычный. Крупные
       кружки (подиум игроков) поднимают его у себя. */
    font-size: var(--fs-xs);
}

.points {
    font-weight: 600;
    color: var(--text-primary);
    text-align: right;
}

.change {
    text-align: center;
    font-weight: 600;
    font-size: var(--fs-sm);
}

.change.negative {
    color: var(--danger);
}

/* Rankings: columns, tabs, panels */
/* Переключатель пола — только на телефоне: на широком экране обе таблицы
   стоят рядом, и выбирать нечего */
/* ПЕРЕКЛЮЧАТЕЛЬ ПОЛА — ЭТО Segmented control 49:70. В описании компонента
   пол назван прямым текстом как случай применения: «a choice that changes
   the form below it, where every option must stay visible». Дорожка 44 —
   порог цели нажатия; было 36. */
.rk-gender-switch {
    display: none;
    gap: var(--space-2);                  /* 8 */
    margin: 0 0 var(--space-4);           /* 16; было 14 — не ступень */
}

.rk-gender {
    flex: 1;
    min-height: var(--btn-h-md);          /* 44 */
    padding: 0 var(--space-4);            /* 16; было 9/14 — не ступень */
    border: var(--bw-hair) solid var(--border-subtle);
    border-radius: var(--radius-full);
    background: transparent;
    color: var(--text-secondary);
    font: inherit;
    font-size: var(--fs-sm);
    font-weight: 600;
    cursor: pointer;
}

.rk-gender.active {
    background: var(--accent);
    border-color: var(--accent);
    color: var(--neutral-black);
}

.rankings-columns {
    display: grid;
    grid-template-columns: repeat(2, 1fr);
    gap: var(--space-lg);
}

.rankings-col {
    min-width: 0;
    display: flex;
    flex-direction: column;
}

.rankings-col-title {
    text-align: center;
    font-size: var(--fs-base);
    font-weight: 600;
    /* РАЗРЯДКА В EM, А НЕ ЧИСЛОМ. 1.5px не тянулась за кеглем; в файле 28
       разрядок заданы в em и 14 числом — беда родится ровно на шве. 24.09 */
    letter-spacing: 0.08em;
    text-transform: uppercase;
    /* ЛАЙМ УБРАН 24.09. Правило 109:370: акцент принадлежит одному главному
       действию в поле зрения, а здесь это «Полный рейтинг →». Название
       колонки — подпись, не действие и не состояние. Тот же разбор, что увёл
       лайм с даты новости, с аватара и с подписей значков. */
    color: var(--text-secondary);
    margin-bottom: var(--space-6);        /* 24 */
    padding: var(--space-3) 0;            /* 12; было 10 — не ступень */
    border-bottom: none;
}

/* ЛЕНТА ЧИПОВ ЛИСТАЕТСЯ НА ВСЕХ ВИДАХ, И ЭТО ОДНО ОПРЕДЕЛЕНИЕ.
   Раньше лента переносилась на широком и листалась на узком — два правила
   на одно понятие, и беда родилась ровно на шве: у мужчин пять разрядов, у
   женщин три, и на планшете боком (колонка 469) мужская лента уезжала на
   вторую строку, а женская оставалась на одной. Таблицы начинались на
   разной высоте. Теперь лента не переносится нигде: помещается — стоит по
   центру (width: fit-content + auto), не помещается — листается пальцем.
   Высоту чипа не трогаем, она из Filter chip 49:77. 24.09 */
.rankings-tabs {
    display: flex;
    gap: var(--space-2);                  /* 8; было 4 — не ступень шкалы */
    margin-bottom: var(--space-md);
    flex-wrap: nowrap;
    justify-content: flex-start;
    width: fit-content;
    max-width: 100%;
    margin-inline: auto;
    overflow-x: auto;
    -webkit-overflow-scrolling: touch;
    scrollbar-width: none;
    padding-bottom: var(--space-2);       /* 8 */
}

.rankings-tab { flex: 0 0 auto; }

/* ВКЛАДКА РАЗРЯДА — ЭТО Filter chip 49:77, А НЕ Tab 28:45.
   Описание компонента решает спор само: «a chip filters a list in place,
   a Tab switches the whole view. They look similar and must not be swapped».
   Наши вкладки фильтруют один список на месте. Высота 36 — ступень sm
   компонента Button; было 32, ниже любой ступени шкалы. */
.rankings-tab {
    min-height: var(--btn-h-sm);          /* 36 */
    display: inline-flex;
    align-items: center;
    padding: 0 var(--space-4);            /* 16 */
    font-size: var(--fs-sm);              /* 14; было 12 */
    /* 1.3. Был normal — единственный из девятнадцати текстовых уровней
       раздела без замеренного межстрочного: замораживать было нечего. */
    line-height: var(--lh-snug);
    border: var(--bw-hair) solid var(--border-light);
    border-radius: var(--radius-full);
    background: transparent;
    color: var(--text-secondary);
    font-weight: 500;
    cursor: pointer;
    /* transition: all — 24.09 из-за него замер через сжатие iframe возвращал
       старую отбивку, и я полдня искал несуществующее противоречие в каскаде.
       Правило замера: вид меряется ЗАГРУЗКОЙ на нужной ширине. */
    /* ПЕРЕХОД ТОЛЬКО ПО ЦВЕТАМ: `all` тянул и font-weight, поэтому
       наведение дёргало ширину всей полосы вкладок. 24.09 */
    transition: background-color 0.2s ease, border-color 0.2s ease,
                color 0.2s ease;
    white-space: nowrap;
}

/* ВЫБРАННАЯ — ОДНА, И ЭТО ВИДНО. Наведение было один в один как выбранная:
   тот же лайм заливкой, тот же вес 600. Пока указатель на соседней вкладке,
   какая выбрана — не видно. Теперь наведение подсвечивает рамку и текст и
   веса не трогает. 24.09 */
.rankings-tab:hover:not(.active) {
    color: var(--text-primary);
    border-color: var(--accent);
}

.rankings-tab.active {
    background: var(--accent);
    color: var(--neutral-black);
    border-color: var(--accent);
    font-weight: 600;
}

.rankings-panel {
    display: none;
    background: var(--bg-card);
    border: var(--bw-hair) solid var(--border-subtle);
    border-radius: var(--radius-xl);
    overflow: hidden;
}

.rankings-panel.active {
    display: block;
    flex: 1;
}

/* ========================================
   GUEST RESTRICTION CTA (Main Page)
   ======================================== */

.guest-section-cta {
    position: absolute;
    bottom: 0;
    left: 0;
    right: 0;
    z-index: 5;
    padding: 0 var(--pad-x) var(--section-y-sm);
    background: linear-gradient(to bottom,
        transparent 0%,
        rgba(10, 10, 10, 0.7) 30%,
        var(--bg-primary) 70%
    );
    display: flex;
    justify-content: center;
}

.guest-section-cta-players {
    background: linear-gradient(to bottom,
        transparent 0%,
        rgba(14, 14, 14, 0.7) 30%,
        var(--bg-secondary) 70%
    );
}

/* Игроки уместились в один ряд: накладка накрыла бы его целиком, и в блоке
   осталась бы одна кнопка. Опускаем её на середину карточек — верхняя
   половина игроков видна, нижняя уходит в туман. Точную высоту ставит
   home-partners.js: только он знает, где кончается ряд */
.guest-section-cta.guest-cta-half {
    max-height: none;
}

.guest-cta-card {
    position: relative;
    text-align: center;
    /* ПОДКЛЮЧЕНА К ЭТАЛОНУ 24.09 — первая из трёх копий коробки-гейта.
       Числа больше не свои: они взяты у .badges-cta-box, как и обещано в
       её комментарии и в tools/check-korobka.js.
       До этого размеры плашки жили в ЧЕТЫРЁХ местах: базовое правило
       (40 / 40 / 36), два блока 768 (28 / 24 / 24 и 18 / 16 / 16) и пара
       наборов #players / #rankings байт в байт одинаковых (26 / 28 / 24).
       Осталось два объявления — это и телефонная ступень ниже, обе с
       числами эталона. */
    padding: var(--space-6) var(--space-8);   /* 24 / 32 — как у эталона */
    max-width: 340px;                    /* было 420 в базе и 340 в обеих секциях */
    width: 100%;
    background: rgba(18, 18, 18, 0.85);
    border-radius: 24px;
    backdrop-filter: blur(20px);
    -webkit-backdrop-filter: blur(20px);
    overflow: hidden;
    isolation: isolate;
}

/* Rotating gradient border */
.guest-cta-card::before {
    content: '';
    position: absolute;
    inset: 0;
    border-radius: 24px;
    padding: 1px;
    background: conic-gradient(
        from var(--guest-angle, 0deg),
        transparent 0%,
        var(--accent) 10%,
        transparent 20%,
        transparent 50%,
        var(--accent-glow) 60%,
        transparent 70%
    );
    -webkit-mask: linear-gradient(var(--neutral-white) 0 0) content-box, linear-gradient(var(--neutral-white) 0 0);
    mask: linear-gradient(var(--neutral-white) 0 0) content-box, linear-gradient(var(--neutral-white) 0 0);
    -webkit-mask-composite: xor;
    mask-composite: exclude;
    animation: guest-border-spin 4s linear infinite;
    z-index: -1;
}

@property --guest-angle {
    syntax: '<angle>';
    initial-value: 0deg;
    inherits: false;
}

@keyframes guest-border-spin {
    to { --guest-angle: 360deg; }
}


/* Ambient glow */
.guest-cta-card::after {
    content: '';
    position: absolute;
    top: -40%;
    left: 50%;
    transform: translateX(-50%);
    width: 180px;
    height: 180px;
    background: radial-gradient(circle, var(--alpha-lime-12) 0%, transparent 70%);
    border-radius: 50%;
    z-index: -1;
    animation: guest-glow-pulse 3s ease-in-out infinite;
}

@keyframes guest-glow-pulse {
    0%, 100% { opacity: 0.5; transform: translateX(-50%) scale(1); }
    50% { opacity: 1; transform: translateX(-50%) scale(1.2); }
}

.guest-cta-icon {
    width: 40px;
    height: 40px;
    margin: 0 auto var(--space-3);       /* 12; было 20 в базе и 14 в секциях */
    display: flex;
    align-items: center;
    justify-content: center;
    border-radius: 50%;
    background: var(--alpha-lime-06);
    border: var(--bw-hair) solid var(--alpha-lime-12);
    color: var(--accent);
}

.guest-cta-title {
    font-size: var(--fs-base);           /* 16; было 18 в базе и 16 в секциях */
    font-weight: 800;
    color: var(--neutral-white);
    margin-bottom: var(--space-4);       /* 16; было 22 */
    letter-spacing: -0.02em;
}

/* Button with shimmer */
.guest-cta-btn {
    position: relative;
    /* КНОПКА ЭТАЛОНА, СЛОВО В СЛОВО. Замер 24.09 дал три высоты на пяти
       видах — 43, 48 и 48, и ни одна не ступень Button 17:82
       (28 · 36 · 44 · 52). Взята запись .badges-cta-btn: высота свойством,
       ступень md 44, поля 0 / 32. Это кнопка той же роли в такой же
       коробке-гейте, и разойтись они больше не должны. */
    display: inline-flex;
    align-items: center;
    justify-content: center;
    height: var(--btn-h-md);             /* 44 */
    padding: 0 var(--space-8);           /* 32; было 13×36 — как у эталона */
    background: var(--accent);
    color: var(--neutral-black);
    font-weight: 800;
    font-size: var(--fs-sm);
    border-radius: 12px;
    text-decoration: none;
    transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
    box-shadow: 0 0 24px var(--alpha-lime-20), 0 0 60px var(--alpha-lime-06);
    overflow: hidden;
}

.guest-cta-btn::after {
    content: '';
    position: absolute;
    top: -50%;
    left: -60%;
    width: 40%;
    height: 200%;
    background: linear-gradient(90deg, transparent, var(--alpha-white-12), transparent);
    transform: skewX(-20deg);
    animation: guest-shimmer 3s ease-in-out infinite;
}

@keyframes guest-shimmer {
    0%, 100% { left: -60%; }
    50% { left: 120%; }
}

.guest-cta-btn:hover {
    transform: translateY(-3px) scale(1.02);
    box-shadow: 0 0 32px var(--alpha-lime-30), 0 8px 40px var(--alpha-lime-12);
}

/* ТЕЛЕФОН — единственная вторая ступень плашки, и тоже с числами эталона.
   Границы 768 больше нет: лестница разрешает 640 и 992. */
@media (max-width: 640px) {
    .guest-cta-card { padding: var(--space-4) var(--space-6); }  /* 16 / 24 — как у эталона */
    .guest-cta-icon { width: 32px; height: 32px; margin-bottom: var(--space-2); }  /* 8; было 10 */
    .guest-cta-title { font-size: var(--fs-sm); margin-bottom: var(--space-3); }   /* 14 / 12 */
    .guest-cta-btn  { padding: 0 var(--space-6); }            /* 24; высота та же, 44 */
}

/* ========================================
   PLAYERS SECTION
   ======================================== */

.section-players {
    background: var(--bg-secondary);
}

.section-badge {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    background: var(--alpha-lime-06);
    border: var(--bw-hair) solid var(--alpha-lime-20);
    padding: 6px 16px;
    border-radius: var(--radius-full);
    font-size: var(--fs-xs);
    color: var(--accent);
    font-weight: 500;
    box-shadow: 0 0 20px var(--alpha-lime-12);
}

.player-card {
    background: var(--bg-card);
    border: var(--bw-hair) solid var(--border-subtle);
    border-radius: var(--radius-xl);
    padding: var(--space-lg);
    display: flex;
    flex-direction: column;
    align-items: center;
    text-align: center;
    gap: var(--space-md);
    transition: all var(--transition-base);
}

.player-card:hover {
    border-color: var(--accent);
    transform: translateY(-4px);
}

.player-photo {
    width: 100px;
    height: 100px;
    border-radius: 50%;
    object-fit: cover;
    border: var(--bw-heavy) solid var(--border-subtle);
}

.player-card:hover .player-photo {
    border-color: var(--accent);
}

.player-info {
    display: flex;
    flex-direction: column;
    gap: 4px;
}

.player-info h4 {
    font-size: var(--fs-md);
    font-weight: 700;
    color: var(--text-primary);
}

.player-level {
    color: var(--accent);
    font-weight: 600;
    font-size: var(--fs-sm);
}

/* ============================================
   ГЛАВНАЯ — «Где играть и у кого учиться»
   ============================================
   Один блок вместо двух разделов: корты и тренеры читаются как одна мысль —
   вот площадки, вот люди. Четыре карточки в ряд вместо трёх: партнёров
   больше трёх, и каждому надо дать место. */
.section-venues {
    padding: var(--section-y-sm) var(--pad-x);
}

/* Подзаголовок ряда стоит ближе к своим карточкам, чем к чужим: сверху
   отступ больше, снизу меньше — так видно, к чему он относится */
.vn-row-head {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 16px;
    margin: 20px 0 10px;
}

.vn-row-head h3 {
    font-size: var(--fs-lg);
    font-weight: 700;
}

/* ВИТРИНА «ГДЕ ИГРАТЬ И У КОГО УЧИТЬСЯ» — переписана 24.09.2026.
   Доска Figma: Venues — seven layers, six breakpoints and the ladder inside
   the card (419:9).

   БЫЛО: семь слоёв на шести границах — 1024 · 1100 · 768 · 600 · 560 · 480,
   и ни одной из 640 и 992. «Две колонки» объявлены четыре раза. Высота
   фотографии объявлена числом трижды (200 · 168 · 92) при текучей ширине,
   поэтому отношение сторон гуляло от 1.79 до 3.91 — на 768 полоска.

   СТАЛО: базовый слой, ≤992, ≤640 и телефон боком.
   ОТНОШЕНИЕ СТОРОН — СВОЙСТВО КАРТОЧКИ, ЧИСЛО КОЛОНОК — СВОЙСТВО ЭКРАНА. */
.courts-grid.vn-four,
.coaches-grid.vn-four {
    grid-template-columns: repeat(4, 1fr);
    gap: var(--space-6);
}

/* Карточка — ссылка целиком. Ссылка по умолчанию строчная, поэтому раскладку
   задаём явно, иначе фотография схлопывается */
.vn-four .court-card,
.vn-four .coach-card {
    display: flex;
    flex-direction: column;
    text-decoration: none;
    color: inherit;
}

/* Карточка тренера получает ту же форму, что карточка корта: фотография 4:3
   сверху, текст слева. Круг 100 был шестой ступенью мимо Avatar 29:82
   (24 · 32 · 40 · 56 · 80), а портрет по центру не оставлял свободного угла */
.vn-four .coach-card {
    padding: 0;
    gap: 0;
    align-items: stretch;
    text-align: left;
}

.vn-four .court-card img,
.vn-four .coach-photo {
    width: 100%;
    height: auto;
    aspect-ratio: 4 / 3;
    object-fit: cover;
    border: 0;
    border-radius: 0;
}

/* Портрет в рамке 4:3 режется по голове: снимки у тренеров вертикальные и
   лицо стоит в верхней трети. Кадрируем от верха, а не от центра */
.vn-four .coach-photo { object-position: 50% top; }

.vn-four .coach-card:hover .coach-photo { border-color: transparent; }

/* Плотность Dense 12 по описанию Card 29:65, между строками 8 — ступень шкалы,
   а не 2, 4 и 6, которые стояли здесь в трёх слоях сразу */
.vn-four .court-info,
.vn-four .coach-info {
    flex: 1;
    display: flex;
    flex-direction: column;
    padding: var(--space-3);
    gap: var(--space-2);
}

/* Имя — две строки с многоточием. Рисовалось столько строк, сколько придёт из
   базы, и ряд карточек разной высоты читался как сбой */
.vn-four .court-info h4,
.vn-four .coach-info h4 {
    font-size: var(--fs-base);
    line-height: 1.3;
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
    overflow: hidden;
}

/* Мета была 12 на широком и 11 на узком — 11 самый мелкий уровень на сайте.
   Один уровень — один кегль и один межстрочный на все виды */
.vn-four .court-info span,
.vn-four .coach-info span {
    font-size: var(--fs-xs);
    line-height: 1.3;
    color: var(--text-muted);
}

/* ВНУТРИ ВИТРИНЫ ЛАЙМА НЕТ: он принадлежит одному главному действию в поле
   зрения, а это «Все корты →» (правило 109:370). Покрытие, цена и скидка —
   значимая мета, 72 % белого; город и специальность — 50 %.
   Город и опыт давали 3.21 : 1 при пороге WCAG 1.4.3 = 4.5, стало 5.33 */
.vn-four .court-surface,
.vn-four .coach-price,
.vn-four .vn-discount {
    color: var(--text-secondary) !important;
    font-weight: 600;
}

.vn-four .coach-speciality {
    color: var(--text-muted);
    font-weight: 500;
}

/* Нижняя строка: мета слева, стрелка справа, прижата к низу карточки */
.vn-four .vn-foot {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-2);
    margin-top: auto;
}

/* Тремя классами: у .vn-four .court-info span два класса и элемент, и
   двухклассовое правило оно перебивает — метка садилась на 12 вместо 18 */
.vn-four .vn-foot .vn-go {
    font-size: var(--fs-md);
    line-height: 1;
    color: var(--text-muted);
    transition: color var(--transition-fast);
}

.vn-four .court-card:hover .vn-foot .vn-go,
.vn-four .coach-card:hover .vn-foot .vn-go,
.vn-four .court-card:focus-visible .vn-foot .vn-go,
.vn-four .coach-card:focus-visible .vn-foot .vn-go {
    color: var(--accent);
}

/* Кольцо фокуса обязательно по WCAG 2.4.7 — описание Button 17:82.
   До 24.09 во всей секции не было ни одного :focus-visible */
.vn-four .court-card:focus-visible,
.vn-four .coach-card:focus-visible {
    outline: var(--bw-heavy) solid var(--accent);
    outline-offset: 2px;
}

/* Карточка двигается при наведении — значит, спрашиваем разрешения */


/* Метка партнёра ушла из угла фотографии в строку: Badge 27:53 Subtle —
   умолчание для карточек, а лаймовая заливка — снова акцент не на действии.
   Плюс правило: в углу карточки с портретом свободного угла нет */
/* Тоже тремя классами, и по той же причине */
.vn-four .court-info .vn-partner,
.vn-four .coach-info .vn-partner {
    position: static;
    align-self: flex-start;
    background: var(--alpha-white-08);
    color: var(--text-secondary);
    font-size: var(--fs-2xs);
    font-weight: 500;
    letter-spacing: 0.05em;
    text-transform: uppercase;
    padding: 0 var(--space-2);
    line-height: 24px;
    border-radius: var(--radius-full);
}

/* Сбой запроса и пустой список — разные вещи, и текст у них разный: описание
   Empty state 37:53 требует сказать, что здесь появится и как */
.vn-notice {
    grid-column: 1 / -1;
    padding: var(--space-6) var(--space-4);
    text-align: center;
    color: var(--text-muted);
    font-size: var(--fs-sm);
}

/* ТОЛЬКО ЦЕЛЫЕ РЯДЫ. Потолок вида кратен числу его колонок, хвост считается
   ОТ КОНЦА: css не знает, сколько записей придёт из базы */
.vn-four > .court-card:nth-child(n+5),
.vn-four > .coach-card:nth-child(n+5) { display: none; }

@media (max-width: 992px) {
    .courts-grid.vn-four,
    .coaches-grid.vn-four { grid-template-columns: repeat(3, 1fr); }

    .vn-row-head h3 { font-size: var(--fs-md); }

    .vn-four > .court-card:nth-child(n+4),
    .vn-four > .coach-card:nth-child(n+4) { display: none; }
}

@media (max-width: 640px) {
    .courts-grid.vn-four,
    .coaches-grid.vn-four {
        grid-template-columns: repeat(2, minmax(0, 1fr));
        gap: var(--space-3);
    }

    .vn-four > .court-card:nth-child(n+4),
    .vn-four > .coach-card:nth-child(n+4) { display: flex; }

    .vn-four > .court-card:nth-child(n+5),
    .vn-four > .coach-card:nth-child(n+5) { display: none; }

    /* :not(:first-child) — единственная запись не прячется. Тренер в базе
       пока один: без этой оговорки правило хвоста гасило весь ряд, и вместо
       карточки оставалась пустота. ХВОСТ ПРЯЧЕТСЯ, ПОКА ЕСТЬ ЧТО ОСТАВИТЬ */
    .vn-four > .court-card:nth-child(odd):nth-last-child(1):not(:first-child),
    .vn-four > .coach-card:nth-child(odd):nth-last-child(1):not(:first-child) { display: none; }

    /* 16 в колонке 166 даёт 12 знаков в строке, 14 даёт 14.
       Межстрочный НЕ меняется: один уровень — один межстрочный на все виды.
       Едет ступень кегля, плотность не едет */
    .vn-four .court-info h4,
    .vn-four .coach-info h4 { font-size: var(--fs-sm); }
}

/* ТЕЛЕФОН БОКОМ — четыре в ряд, один ряд. Слой пишется целиком: у соседа
   ≤992 те же два класса, и «вернуть в поток» из чужого слоя проигрывает */
@media (orientation: landscape) and (max-height: 500px) {
    .courts-grid.vn-four,
    .coaches-grid.vn-four { grid-template-columns: repeat(4, 1fr); }

    .vn-four > .court-card:nth-child(n+4),
    .vn-four > .coach-card:nth-child(n+4) { display: flex; }

    .vn-four > .court-card:nth-child(n+5),
    .vn-four > .coach-card:nth-child(n+5) { display: none; }
}

.courts-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(350px, 1fr));
    gap: var(--space-lg);
}

.court-card {
    background: var(--bg-card);
    border: var(--bw-hair) solid var(--border-subtle);
    border-radius: var(--radius-xl);
    overflow: hidden;
    transition: all var(--transition-base);
    cursor: pointer;
    text-decoration: none;
    color: inherit;
    display: flex;
    flex-direction: column;
}

.court-card:hover {
    border-color: var(--border-light);
    transform: translateY(-4px);
}

.court-card img {
    width: 100%;
    height: 200px;
    object-fit: cover;
}

.court-info {
    padding: var(--space-lg);
    display: flex;
    flex-direction: column;
    gap: 6px;
    flex-grow: 1;
}

.court-info h4 {
    font-size: var(--fs-md);
    font-weight: 700;
    color: var(--text-primary);
}

.court-info span {
    font-size: var(--fs-sm);
    color: var(--text-muted);
}

.court-surface {
    color: var(--accent) !important;
    font-weight: 500;
}

.coaches-grid {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    gap: var(--space-lg);
}

.coach-card {
    background: var(--bg-card);
    border: var(--bw-hair) solid var(--border-subtle);
    border-radius: var(--radius-xl);
    overflow: hidden;
    transition: all var(--transition-base);
    display: flex;
    flex-direction: column;
    align-items: center;
    text-align: center;
    padding: var(--space-lg);
    gap: var(--space-md);
    cursor: pointer;
    text-decoration: none;
    color: inherit;
}

.coach-card:hover {
    border-color: var(--border-light);
    transform: translateY(-4px);
}

.coach-photo {
    width: 100px;
    height: 100px;
    border-radius: 50%;
    object-fit: cover;
    border: var(--bw-thick) solid var(--border-subtle);
}

.coach-card:hover .coach-photo {
    border-color: var(--accent);
}

.coach-info {
    display: flex;
    flex-direction: column;
    gap: 4px;
    flex-grow: 1;
}

.coach-info h4 {
    font-size: var(--fs-md);
    font-weight: 700;
    color: var(--text-primary);
}

.coach-speciality {
    font-size: var(--fs-sm);
    color: var(--accent);
    font-weight: 500;
}

.coach-experience{
    font-size: var(--fs-sm);
    color: var(--text-muted);
}

.coach-price {
    font-size: var(--fs-sm);
    color: var(--text-primary);
    font-weight: 600;
}

/* ========================================
   SPONSORS SECTION
   ======================================== */

.section-sponsors {
    background: var(--bg-secondary);
    text-align: center;
}

.section-sponsors-compact {
    background: var(--bg-secondary);
    /* Было 5 — логотипы упирались в нижний край полосы */
    padding-bottom: 32px;
}

.section-sponsors-compact .section-header {
    margin-bottom: var(--space-lg);
}

/* ── ГЕНЕРАЛЬНЫЙ СПОНСОР ──────────────────────────────────────────────
   Плитка у него ТА ЖЕ, что у остальных: спонсоры равны, а статус называет
   подпись и своя строка. Решение Кости 24.09, доска 73:9.
   Лайма здесь нет: он принадлежит кнопке «Подробнее о спонсорстве», которая
   стоит прямо над этой секцией — правило 109:370, одно действие в поле
   зрения. До 24.09 подпись была лаймовой. */
.sponsor-hero {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    gap: var(--space-2);
    margin-bottom: var(--space-6);
}

.sponsor-hero-label {
    font-size: var(--fs-2xs);
    font-weight: var(--fw-semibold);
    text-transform: uppercase;
    letter-spacing: 0.08em;
    color: var(--text-muted);
}

/* ── СТЕНА ЛОГОТИПОВ ──────────────────────────────────────────────────
   Плитки одного размера. Логотипы приходят из базы разной ширины при одной
   высоте — замерено на 1280: от 39 до 140, — и без общей коробки вес на
   экране у них выходил втрое разный. Не потому, что кто-то больше заплатил,
   а потому, что логотип вытянутый.
   Ряд заполняется целиком, последний неполный встаёт по центру: когда число
   спонсоров не делится на число колонок, нижний ряд сам оказывается со
   смещением. Кирпичная кладка получается из ЧИСЛА карточек, а не рисуется. */
.sponsors-cloud {
    display: flex;
    flex-wrap: wrap;
    justify-content: center;
    gap: var(--space-4);
}

.sponsor-hero-logo,
.sponsors-cloud .sponsor-logo-link,
.sponsors-cloud .spon-static {
    display: flex;
    align-items: center;
    justify-content: center;
    /* ПЯТЬ В РЯД — не трогаем: спонсоров десять, и пять даёт два целых
       ряда. Витрина показывает только целые ряды.
       ВЫСОТА 88 → 64 (Костя, 28.09): «подвал же идёт как второстепенное,
       зачем их увеличивать». Замерено до правки: плитка 267 × 88, а
       логотип внутри 36 в высоту — коробка была втрое выше содержимого.
       64 — ступень восьмёрки, потолок логотипа 58 % от неё это те же 37,
       то есть логотип не мельчает. Цель нажатия 64, порог 44 держится. */
    /* ЯЧЕЙКА — ОДНОГО РАЗМЕРА И МАЛЕНЬКАЯ. Костя 28.09: «я имел в виду не
       сами логотипы, а карточки-ячейки, они большие — сделай один размер
       ячейки и помести туда логотипы».
       Было: ширина делилась на пять долей экрана — 267 при логотипе 22 в
       высоту и 19–85 в ширину, то есть коробка в три раза шире рисунка.
       Стало: 120 — одно число на все ячейки, ряд встаёт по центру. Десять
       спонсоров умещаются в ОДИН ряд: 10 × 120 + 9 × 16 = 1344 при поле
       1400. Целый ряд — по-прежнему целый. */
    flex: 0 0 120px;
    height: 64px;
    /* ПОЛЕ ВОКРУГ ЛОГОТИПА — МИНИМАЛЬНОЕ, КОНТУР НЕ ВИДЕН. Костя 28.09:
       «мне не нравится, что вокруг логотипа есть воздух и рамка — на многих
       сайтах таких рамок нет».
       Плитку при этом не убираем: без неё логотипы снова встанут в ряд
       шириной от 39 до 140 при одной высоте, и вес на экране будет втрое
       разный — это замерено 24.09 и стало причиной плитки. Заливка остаётся
       и держит равенство, а контур гасится до прозрачного: коробки не
       видно, равенство на месте. Поле 8 вместо 12. */
    /* КОРОБКИ НЕ ВИДНО ВОВСЕ — Костя 28.09: «по контуру там ещё есть
       подложка другого цвета, тоже не нравится».
       Равенство спонсоров держит РАЗМЕР ячейки, а не её заливка: ячейка
       остаётся 120 x 64 у всех, логотип центрируется внутри. Замеренная
       24.09 беда — логотипы шириной от 39 до 140 в общем ряду — от этого
       не возвращается: тогда места были разной ширины, теперь одинаковой.
       Поверхность появляется только под курсором: наведение рисует рамку
       акцентом, и коробка объявляет себя ровно там, где на неё смотрят. */
    padding: var(--space-2);
    background: transparent;
    border: var(--bw-hair) solid transparent;
    border-radius: var(--radius-lg);
    text-decoration: none;
    color: inherit;
    transition: transform var(--transition-fast), border-color var(--transition-fast);
}

/* Вся плитка — цель нажатия, и она заведомо выше порога 44. До 24.09 целью
   был сам логотип: «STRETCH» давал 39 в ширину, то есть мимо порога. */
/* НАВЕДЕНИЕ ПОДСВЕЧИВАЕТ ПЛИТКУ АКЦЕНТОМ — Костя 28.09: «почему логотипы
   не подсвечиваются лаймовым при наведении, они только увеличиваются».
   Правилу «лайма в секции нет» это не противоречит: там запрещён лаймовый
   ТЕКСТ, потому что лайм принадлежит одному действию в поле зрения. Под
   курсором действие ровно одно — эта плитка, и подсветка держится ровно
   пока на ней курсор. */
.sponsors-cloud .sponsor-logo-link:hover,
.sponsor-hero-logo:hover {
    transform: translateY(-2px);
    border-color: var(--accent);
    box-shadow: 0 0 0 1px var(--accent);
}

.sponsors-cloud .sponsor-logo-link:focus-visible,
.sponsor-hero-logo:focus-visible {
    outline: var(--bw-heavy) solid var(--accent);
    outline-offset: 3px;
}

/* Логотип показывается как есть, в своих цветах: спонсор платит за
   узнаваемый бренд. Потолок внутри плитки один на всех — иначе равные
   коробки снова дадут неравный вес. */
.sponsor-hero-logo img,
.sponsors-cloud .sponsor-logo-link img,
.sponsors-cloud .spon-static img {
    /* ЛОГОТИП КРУПНЕЕ ВНУТРИ ЯЧЕЙКИ — Костя 28.09. Было 70 % и 58 %: в
       ячейке 120 x 64 это давало рисунок 84 x 37, и ячейка весила больше
       того, что в ней. Стало 84 % и 72 % — 100 x 46. Поля остаются, иначе
       логотип упирается в контур. */
    max-width: 94%;
    max-height: 88%;
    width: auto;
    height: auto;
    object-fit: contain;
    display: block;
}


/* Колонок меньше — плитка та же. Число колонок это свойство экрана,
   а размер плитки — свойство карточки. */
@media (max-width: 992px) {
    .sponsor-hero-logo,
    .sponsors-cloud .sponsor-logo-link,
    .sponsors-cloud .spon-static {
        flex-basis: calc((100% - 3 * var(--space-4)) / 4);
        height: 56px;
    }
}

/* Телефон: четыре в ряд, как и было до 24.09. Три колонки дали бы десяти
   спонсорам четыре ряда вместо трёх — секция вырастала на 90 пикселей
   ни за что. Число колонок — свойство экрана, размер плитки — её собственное. */
@media (max-width: 640px) {
    .sponsors-cloud { gap: var(--space-3); }

    .sponsor-hero-logo,
    .sponsors-cloud .sponsor-logo-link,
    .sponsors-cloud .spon-static {
        flex-basis: calc((100% - 3 * var(--space-3)) / 4);
        height: 56px;
        padding: var(--space-2);
    }

    .sponsor-hero-logo img,
    .sponsors-cloud .sponsor-logo-link img,
    .sponsors-cloud .spon-static img {
        max-width: 80%;
        max-height: 64%;
    }
}


/* ========================================
   SPONSOR DETAIL MODAL
   ======================================== */

.spon-modal-overlay {
    position: fixed;
    top: 0; left: 0; right: 0; bottom: 0;
    background: var(--modal-bg-heavy);
    backdrop-filter: blur(4px);
    z-index: 9999;
    display: flex;
    align-items: center;
    justify-content: center;
    opacity: 0;
    transition: opacity 0.25s ease;
    padding: 20px;
}

.spon-modal-overlay.visible {
    opacity: 1;
}

.spon-modal {
    background: var(--bg-card);
    border: var(--bw-hair) solid var(--border);
    border-radius: var(--radius-xl);
    padding: 32px 28px 24px;
    max-width: 400px;
    width: 100%;
    position: relative;
    text-align: center;
    transform: scale(0.95);
    transition: transform 0.25s ease;
}

.spon-modal-overlay.visible .spon-modal {
    transform: scale(1);
}

/* Цель нажатия 44 — Icon button 40:93, ступень lg: «Only lg (44) meets the
   44pt minimum touch target, so lg is the required size anywhere a finger
   uses it». Было 22 x 32 при padding 4: и ниже порога 44, и уже минимума 24
   из WCAG 2.5.8. Крестик остаётся того же кегля, растёт коробка. */
.spon-modal-close {
    position: absolute;
    top: var(--space-2); right: var(--space-2);
    display: flex;
    align-items: center;
    justify-content: center;
    width: var(--btn-h-md);
    height: var(--btn-h-md);
    background: none;
    border: none;
    border-radius: var(--radius-full);
    color: var(--text-muted);
    font-size: var(--fs-xl);
    cursor: pointer;
    line-height: 1;
    padding: 0;
}

.spon-modal-close:focus-visible,
.spon-modal-close-btn:focus-visible,
.spon-modal-action:focus-visible {
    outline: var(--bw-heavy) solid var(--accent);
    outline-offset: 3px;
}

.spon-modal-close:hover {
    color: var(--text-primary);
}

.spon-modal-logo {
    margin-bottom: 16px;
}

.spon-modal-logo img {
    max-height: 80px;
    max-width: 200px;
    object-fit: contain;
}

.spon-modal-name {
    font-size: var(--fs-lg);
    font-weight: 700;
    color: var(--text-primary);
    margin-bottom: 8px;
}

.spon-modal-desc {
    font-size: var(--fs-sm);
    color: var(--text-secondary);
    line-height: 1.5;
    margin-bottom: 16px;
    /* Описание вводят в админке в несколько строк, а браузер по умолчанию
       склеивает их в одну. Сохраняем переносы как набраны */
    white-space: pre-line;
}

.spon-modal-address {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 8px;
    font-size: var(--fs-xs);
    color: var(--text-muted);
    margin-bottom: 16px;
}

.spon-modal-address-icon { display: flex; flex-shrink: 0; }
.spon-modal-address-icon svg { width: 14px; height: 14px; }

.spon-modal-actions {
    display: flex;
    flex-direction: column;
    gap: 8px;
    margin-bottom: 16px;
}

.spon-modal-action {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 12px 16px;
    background: var(--alpha-white-04);
    border: var(--bw-hair) solid var(--border);
    border-radius: var(--radius-md);
    color: var(--text-primary);
    text-decoration: none;
    font-size: var(--fs-sm);
    font-weight: 500;
    transition: all 0.2s ease;
}

.spon-modal-action:hover {
    background: var(--alpha-lime-06);
    border-color: var(--accent);
    color: var(--accent);
}

.spon-modal-action-icon {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 24px;
    flex-shrink: 0;
    color: var(--accent);
}

.spon-modal-action-icon svg { width: 20px; height: 20px; }

/* Подпись и сам адрес: видно не только «Instagram», но и какой именно */
.spon-modal-action-text {
    display: flex;
    flex-direction: column;
    gap: 2px;
    min-width: 0;
    text-align: left;
}

.spon-modal-action-label { font-weight: 600; line-height: 1.3; }

.spon-modal-action-value {
    font-size: var(--fs-xs);
    font-weight: 400;
    color: var(--text-secondary);
    line-height: 1.3;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.spon-modal-action:hover .spon-modal-action-value { color: inherit; opacity: 0.75; }

/* Высота свойством, а не полями — Button 17:82. Было 99 x 36 из padding
   10/24: ниже порога нажатия на кнопке, которой закрывают окно. */
.spon-modal-close-btn {
    height: var(--btn-h-md);
    padding: 0 var(--space-6);
    background: var(--alpha-white-08);
    border: var(--bw-hair) solid var(--border);
    border-radius: var(--radius-md);
    color: var(--text-secondary);
    font-size: var(--fs-sm);
    cursor: pointer;
    transition: all 0.2s ease;
}

.spon-modal-close-btn:hover {
    background: var(--alpha-white-12);
    color: var(--text-primary);
}

@media (max-width: 640px) {
    .spon-modal {
        padding: 24px 20px 20px;
    }
    .spon-modal-logo img {
        max-height: 60px;
    }
}

/* ========================================
   INFINITE SPONSORS CAROUSEL
   Бесконечная автоматическая прокрутка
   ======================================== */

.sponsors-carousel-infinite {
    position: relative;
    overflow: hidden;
    margin-bottom: var(--space-xl);
    padding: var(--space-lg) 0;

    /* Градиентные края для плавного исчезновения */
    mask-image: linear-gradient(
        to right,
        transparent,
        black 10%,
        black 90%,
        transparent
    );
    -webkit-mask-image: linear-gradient(
        to right,
        transparent,
        black 10%,
        black 90%,
        transparent
    );
}

/* Пауза по касанию: на телефоне навести курсор нечем, и дорожка не
   останавливалась никогда — в плитку-ссылку попасть было невозможно */
.carousel-track-infinite.is-paused {
    animation-play-state: paused;
}

.carousel-track-infinite {
    display: flex;
    width: max-content;
    animation: scrollCarousel 90s linear infinite;
}

/* ДВИЖЕНИЕ, КОТОРОЕ ВЕЗЁТ СОДЕРЖИМОЕ, НЕ ПРОСТО ОСТАНАВЛИВАЮТ. Лента едет
   90 с, и родитель держит `overflow: hidden`: останови её — и логотипы за
   краем экрана станут недоступны НИКАК. До 15.10 здесь стояло
   `animation: none`, и у того, кто выключил анимации, лента замирала на
   первом кадре вместе с половиной спонсоров.
   Поэтому движение снимается, а взамен даётся прокрутка пальцем. Маска
   краёв тоже снимается: за ней прячется начало содержимого, которое
   теперь листают руками. WCAG 2.3.3 и 1.4.2. */
@media (prefers-reduced-motion: reduce) {
    .carousel-track-infinite {
        animation: none !important;
        transform: none !important;
    }
    .sponsors-carousel-infinite {
        overflow-x: auto;
        mask-image: none;
        -webkit-mask-image: none;
    }
}

.sponsors-carousel-infinite:hover .carousel-track-infinite {
    animation-play-state: paused;
}


@keyframes scrollCarousel {
    0% {
        transform: translateX(0);
    }
    100% {
        transform: translateX(-50%);
    }
}

.carousel-slide-infinite {
    flex: 0 0 auto;
    width: 200px;
    margin: 0 var(--space-md);
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: var(--space-sm);
    transition: transform var(--transition-fast);
    /* Слайд — ссылка, и браузер красил подпись по-своему: синим и с
       подчёркиванием, поверх тёмной ленты. Сбрасываем, цвет задан ниже */
    text-decoration: none;
    color: inherit;
}
.carousel-slide-infinite:hover,
.carousel-slide-infinite:focus,
.carousel-slide-infinite:visited { text-decoration: none; color: inherit; }

.carousel-slide-infinite:hover {
    transform: scale(1.05);
}

.carousel-slide-infinite img {
    width: 100%;
    height: 100px;
    object-fit: contain;
    border-radius: var(--radius-lg);
    border: var(--bw-hair) solid var(--border-subtle);
    background: var(--bg-card);
    padding: 12px;
    box-sizing: border-box;
    transition: all var(--transition-fast);
}

.carousel-slide-infinite:hover img {
    border-color: var(--accent);
    box-shadow: 0 0 20px var(--alpha-lime-20);
}

.carousel-slide-infinite span {
    font-size: var(--fs-sm);
    font-weight: 600;
    color: var(--text-secondary);
    transition: color var(--transition-fast);
}

.carousel-slide-infinite:hover span {
    color: var(--accent);
}

/* Мобильные стили */
/* ПЛАНШЕТ И УЖЕ: плитка ленты логотипов мельче уже на планшете. */
@media (max-width: 992px) {
    .carousel-slide-infinite {
        width: 160px;
        margin: 0 var(--space-sm);
    }

    .carousel-slide-infinite img {
        height: 80px;
    }

    .carousel-track-infinite {
        /* На телефоне дорожка короче, и при том же времени логотипы
           пролетали за четыре секунды вместо минуты. Замедляем, а не
           ускоряем: читать надо успевать. */
        animation-duration: 110s;
    }
}

@media (max-width: 640px) {
    .carousel-slide-infinite {
        width: clamp(110px, 29.3vw, 140px);
    }

    .carousel-track-infinite {
        animation-duration: 110s;
    }
}

/* ========================================
   РАЗДЕЛ «О ПРОЕКТЕ КСЛТ» — переписан 24.09.2026.
   Доска Figma: About — six tiles that name six sections and lead nowhere
   (427:9). Компонент иконки: Feature icon (429:191).

   БЫЛО: три слоя на границах 992 и 768, и в блоке 768 одно понятие
   определено дважды — строка 7734 ставила плиткам одну колонку и не
   работала никогда, потому что ниже, в ТОМ ЖЕ условии, стояли две.

   ГЛАВНОЕ: сетка возможностей — это навигация, а не витрина текста.
   Шесть плиток называли шесть разделов сайта и никуда не вели.
   ======================================== */

.section-about {
    background: var(--bg-primary);
}

/* Заголовок раздела и тело — разные смысловые группы: 24 (правило 323:2) */
.about-head {
    margin-bottom: var(--space-6);
}

.about-head .section-header {
    margin-bottom: 0;
}

/* ДВЕ КОЛОНКИ: слева вступление и плитки под ним, справа фотография во всю
   высоту левой части. РЕШЕНИЕ КОСТИ 24.09, вечер — оно отменило моё
   «сетка во всю ширину».

   Почему моё возражение снято числами: сжималась плитка не от колонки, а
   от ТРЁХ колонок внутри неё — 177 на 1280 и 133 на 1024. В двух колонках
   внутри той же левой части плитка получает вдвое больше.

   Зазор 32 — вокруг блока, который несёт свою поверхность (фотография). */
.about-body {
    display: grid;
    grid-template-columns: 1.15fr 1fr;
    grid-template-rows: auto 1fr;
    column-gap: var(--space-8);
    row-gap: var(--space-6);
    align-items: stretch;
}

.about-lead {
    grid-column: 1;
    grid-row: 1;
    font-size: var(--fs-lg);
    line-height: 1.65;
    color: var(--text-secondary);
    /* Ширину ограничиваем не из вредности: строка длиннее ~90 знаков
       читается тяжело — глаз не находит начало следующей. Замер: 70 */
    max-width: 68ch;
    margin: 0;
}

.about-image {
    grid-column: 2;
    grid-row: 1 / span 2;
}

/* НА ШИРОКОМ высоту снимка задаёт ЛЕВАЯ ЧАСТЬ: он тянется на обе строки —
   вступление и плитки. Отношение сторон файла здесь не действует, кадрирует
   object-fit. На узком колонка одна, и тогда снимок возвращается к отношению
   своего файла 1400 × 691 — там он не режется вовсе.
   width и height в разметке остаются в обоих случаях: они резервируют место
   до загрузки. */
.about-image img {
    width: 100%;
    height: 100%;
    object-fit: cover;
    display: block;
    border-radius: var(--radius-xl);
}

.about-features {
    grid-column: 1;
    grid-row: 2;
    display: grid;
    /* Две колонки внутри левой части: плитка ≈308 на 1280 против 177,
       когда их было три */
    grid-template-columns: repeat(2, 1fr);
    /* 24 — между смысловыми группами: каждая плитка отдельный смысл */
    gap: var(--space-6);
    list-style: none;
    margin: 0;
    padding: 0;
}

/* Пункт списка — только носитель ссылки: тянется сам и тянет ссылку,
   иначе плитки в ряду разной высоты, а нажимается только текст */
.about-features > li {
    display: flex;
}

/* Плитка — ссылка целиком. Адрес есть у каждой из шести: раньше человек
   читал «полный список кортов Кыргызстана», нажимал и не получал ничего.

   ЗНАЧОК СЛЕВА ОТ ТЕКСТА, а не над ним. Решение Кости 24.09: «много воздуха
   где иконки». Числами: плашка над текстом давала плитку 126 в высоту при
   содержимом в 97, и три ряда таких плиток растягивали левую часть, а с ней
   и снимок. Значок сбоку — плитка 64, ряд короче на 62. */
.feature {
    flex: 1;
    display: flex;
    flex-direction: row;
    align-items: flex-start;
    /* 12 — внутри одной смысловой группы: плашка и текст */
    gap: var(--space-3);
    text-decoration: none;
    color: inherit;
}

.feature .feature-icon {
    flex: 0 0 auto;
}

.feature-text {
    flex: 1;
    min-width: 0;
}

.feature-text {
    display: flex;
    flex-direction: column;
    /* 8 — подпись к тому, что подписывает */
    gap: var(--space-2);
}

/* Кольцо фокуса обязательно по WCAG 2.4.7 — описание Button 17:82.
   До 24.09 во всей секции не было ни одного :focus-visible */
.feature:focus-visible {
    outline: var(--bw-heavy) solid var(--accent);
    outline-offset: 4px;
    border-radius: var(--radius-md);
}

/* Плашка иконки — компонент Feature icon 429:191.
   ЭТО НЕ Icon button 40:93: у той радиус full, потому что на неё нажимают
   пальцем. Здесь нажимается вся плитка, а плашка только показывает.
   Ступень 44 взята у lg того компонента, форма — нет. */
.feature-icon {
    width: 44px;
    height: 44px;
    border-radius: var(--radius-lg);
    display: inline-flex;
    align-items: center;
    justify-content: center;
    color: var(--accent);
    background: linear-gradient(145deg,
        var(--alpha-lime-20),
        var(--alpha-lime-06) 60%,
        var(--alpha-white-04));
    border: var(--bw-hair) solid var(--alpha-lime-20);
    box-shadow:
        inset 0 1px 0 var(--alpha-white-28),
        inset 0 -10px 16px var(--alpha-black-50),
        0 8px 20px var(--alpha-black-50);
    transition: transform var(--transition-fast),
                box-shadow var(--transition-fast),
                border-color var(--transition-fast);
}

.feature-icon svg {
    width: 24px;
    height: 24px;
    display: block;
}

.feature:hover .feature-icon,
.feature:focus-visible .feature-icon {
    transform: translateY(-2px) rotate(-3deg);
    border-color: var(--alpha-lime-50);
    box-shadow:
        inset 0 1px 0 var(--alpha-white-28),
        inset 0 -10px 16px var(--alpha-black-50),
        0 10px 26px var(--alpha-black-50),
        0 0 22px var(--alpha-lime-20);
}

/* Плашка двигается — значит, спрашиваем разрешения */


/* Ступень card title по лестнице 86:10: заголовок КАРТОЧКИ, не раздела.
   Было 16 — ровно кегль строки под ним, разница только в весе */
.feature h3 {
    font-size: var(--fs-base);
    font-weight: 700;
    line-height: 1.3;
    color: var(--text-primary);
    margin: 0;
}

/* Ступень small. Было 16 цветом --text-dim: 3.14 : 1 при пороге 4.5 —
   седьмое место с этим дефектом по сайту, и текст здесь не мелкий */
.feature p {
    font-size: var(--fs-sm);
    line-height: 1.5;
    color: var(--text-muted);
    margin: 0;
}

@media (max-width: 992px) {
    .about-body {
        grid-template-columns: 1fr;
        row-gap: var(--space-6);
    }

    /* Порядок по смыслу: сначала о чём это, потом куда пойти, и только
       потом снимок. Было наоборот — фотография стояла первой (order: -1) */
    .about-lead     { grid-column: 1; grid-row: auto; order: 1; }
    .about-features { grid-column: 1; grid-row: auto; order: 2; grid-template-columns: repeat(2, 1fr); }
    .about-image    { grid-column: 1; grid-row: auto; order: 3; }

    /* Колонка одна — снимок возвращается к отношению своего файла */
    .about-image img {
        height: auto;
        aspect-ratio: 1400 / 691;
        max-height: 240px;
    }

    /* Ступень lead на узком — 16. Было 14, то есть мельче, чем строка
       плитки на широком */
    .about-lead {
        font-size: var(--fs-base);
        line-height: 1.5;
    }
}

@media (max-width: 640px) {
    /* Колонка 150: плашка 44 и поля оставили бы тексту 94 — значок
       возвращается наверх */
    .feature {
        flex-direction: column;
        align-items: stretch;
    }

    .feature h3 { font-size: var(--fs-sm); }

    /* Ступень caption — 12 и Medium, а не Regular */
    .feature p {
        font-size: var(--fs-xs);
        font-weight: 500;
    }
}

/* Телефон боком: секция была 998 при экране 390, и 340 из них занимала
   одна фотография. Вид делится поворотом и высотой, а не шириной */
@media (orientation: landscape) and (max-height: 500px) {
    .about-image img {
        height: auto;
        aspect-ratio: 1400 / 691;
        max-height: 160px;
    }
}
/* ========================================
   ПОЛОСА ПОИСКА И ФИЛЬТРОВ — одна на весь сайт
   Используется на страницах категорий турниров и на баттлах;
   остальные разделы переводим на неё по очереди
   ======================================== */

/* Фильтры — одной полосой
   ------------------------------------------------------------------
   Было: залитые чипы и лаймовые подписи «СТАТУС» / «ПОЛ» — на фоне
   остального сайта чужеродно. Стало: прозрачные чипы с тонкой рамкой,
   как в «Новостях» и «Рейтинге», активный — лаймовый. */
.trn-filters {
    position: sticky;
    top: var(--header-h);
    z-index: 30;
    background: var(--bg-primary);
    padding: 14px 0;
    border-bottom: var(--bw-hair) solid var(--border-subtle);
    transition: box-shadow 0.2s;
}

/* Фон полосы — во всю ширину, содержимое — по сетке страницы.

   ОДНО ОПРЕДЕЛЕНИЕ ЛЕВОГО КРАЯ. 05.10, слово Кости «выровнять с началом
   заголовка и полоска». Замер нашёл, что полоса считала свой отступ
   --gutter, а содержимое страницы — --pad-x. На 1280, 768 и 375 они дают
   одно и то же, и расхождения не видно; на 1512 полоса начиналась на 88,
   а лаймовая полоска заголовка — на 56. Край разъезжался ровно там, где
   экран шире контейнера.

   Теперь обе стороны считают одной формулой: --pad-x сам выбирает между
   полем и половиной остатка от 1400. max-width и margin: auto больше не
   нужны — их работу делает та же формула. */
.trn-filters-inner {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 12px;
    padding: 0 var(--pad-x);
}

.trn-filters.stuck {
    box-shadow: 0 4px 20px var(--alpha-black-50);
}

/* НА НИЗКОМ ГОРИЗОНТАЛЬНОМ ПОЛОСА НЕ ЛИПНЕТ.
   Замер 05.10: при экране 844×390 липкая полоса фильтров занимала 156 —
   СОРОК ПРОЦЕНТОВ высоты окна, и столько же отнимала у списка, ради
   которого человек сюда пришёл. Фильтры там прокручиваются вместе со
   страницей: найти их — одно движение, а место возвращается всё. */
@media (max-height: 500px) and (orientation: landscape) {
    .trn-filters {
        position: static;
        padding: 8px 0;
    }
}

/* «Все турниры» живёт внутри полосы: над ней ссылка уезжала вверх при
   первой же прокрутке — ровно тогда, когда она и нужна */
.trn-back {
    flex: 0 0 auto;
    white-space: nowrap;
    font-size: var(--fs-sm);
}

.trn-search-wrap {
    position: relative;
    flex: 0 1 320px;
    min-width: 220px;
}

.trn-search-icon {
    position: absolute;
    left: 15px;
    top: 50%;
    transform: translateY(-50%);
    color: var(--text-muted);
    pointer-events: none;
}

.trn-search-input {
    /* КЕГЛЬ ПОЛЯ — 16, А НЕ 14. Компонент `Search field 50:67`: «Same 44px
       height, radius/sm and 16px text as Input». У `Input 25:5` причина
       названа прямо: всё, что меньше 16, заставляет Safari на iOS
       ПРИБЛИЖАТЬ СТРАНИЦУ при фокусе в поле. Стояло `--fs-sm` 14 — на всех
       пяти разделах с поиском сразу. Высота 44 держится `min-height` и от
       кегля не зависит: померено. */
    font-size: var(--fs-base);
    /* НАСТОЯЩАЯ ВЫСОТА, А НЕ СЛОЙ ЦЕЛИ: в поле надо попадать курсором, а не
       слоем над ним. 44 — высота компонента `Search field 50:67` и ступень
       шкалы кнопок. */
    min-height: var(--btn-h-md);
    width: 100%;
    padding: 10px 16px 10px 42px;
    background: var(--alpha-white-08);
    border: var(--bw-hair) solid var(--border-subtle);
    /* РАДИУС ИЗ КОМПОНЕНТА, А НЕ ТАБЛЕТКА. `Search field 50:67`: radius 8
       («radius/sm»), как у `Input 25:5`. В коде стояла таблетка — поле
       поиска читалось как чип, а не как поле ввода, и расходилось с полями
       всех форм продукта. Одно объявление на пять разделов с поиском. */
    border-radius: var(--radius-sm);
    color: var(--text-primary);
    font-family: var(--font-sans);
    outline: none;
    /* не «all»: см. чип выше — outline-width под переход не попадает */
    transition: background-color 0.25s, border-color 0.25s, box-shadow 0.25s;
}

.trn-search-input::placeholder {
    color: var(--text-muted);
}

.trn-search-input:focus {
    border-color: var(--accent);
    background: var(--alpha-white-12);
    box-shadow: 0 0 0 3px var(--alpha-lime-12);
}

.trn-chips {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 8px;
    /* Без прижатия вправо: на узком экране чипы переносятся строкой ниже,
       и прижатые они повисали бы у правого края, оторванные от поиска */
}

/* ОБОЙМА ГРУППЫ. Чипы одного смысла стоят вместе и переносятся вместе:
   раньше их держала одна лента и чёрточка-разделитель, и на узком экране
   «Все» от пола оказывалось между «Завершённые» и «Мужские». Теперь
   группа — целый элемент: ломается полоса между группами, а не внутри. */
.trn-chip-group {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: inherit;
}

.trn-chip {
    /* Единая высота у всех чипов в ряду: знаки ♂ и ♀ тянут строку вверх,
       и чипы пола выходили на три пикселя выше соседних */
    display: inline-flex;
    align-items: center;
    justify-content: center;
    position: relative;
    /* СТУПЕНЬ, А НЕ ЧИСЛО. Было 1.15 — 13.8 при кегле 12 и 16.1 при 14;
       такой ступени на лестнице нет вовсе. Чип однострочный, высоту ему
       задают поля, поэтому --lh-none. */
    line-height: var(--lh-none);
    min-height: var(--btn-h-md);
    padding: 8px 18px;
    background: transparent;
    border: var(--bw-hair) solid var(--border-subtle);
    border-radius: var(--radius-full);
    color: var(--text-secondary);
    /* ЦЕЛЬ НАЖАТИЯ — НАСТОЯЩЕЙ ВЫСОТОЙ, А НЕ СЛОЕМ.
       Слой 44 я сначала повесил на ::after и замер поймал: он срабатывал
       у первого и последнего чипа в группе и НЕ срабатывал у средних —
       elementFromPoint в пяти пикселях над «Рег. открыта» возвращал
       .trn-filters-inner. Доказать слой на одном чипе нельзя, а разбирать
       порядок отрисовки ради цели нажатия дороже, чем взять высоту:
       НАСТОЯЩУЮ ВЫСОТУ ПЕРЕКРАСИТЬ НЕЧЕМ. Полоса от этого не раздулась:
       она и так упала с 114 до 73 на десктопе за счёт кегля и ступени
       межстрочного.
       ОДИН КЕГЛЬ НА ВСЕ ВИДЫ. Замер 05.10: один и тот же чип выходил 12 на
       375 и 768 и 14 на 844, 1024 и 1280 — при одной и той же роли.
       Взята младшая ступень: ряд чипов липкий, и каждый лишний пиксель его
       высоты съедает экран. */
    font-size: var(--fs-xs);
    font-family: var(--font-sans);
    font-weight: 500;
    cursor: pointer;
    white-space: nowrap;
    /* ПЕРЕЧИСЛЯЕМ, А НЕ «ALL» — правило куплено 09.10 на кнопке H2H и
       применяется здесь без нового разбора. «all» анимирует и
       outline-width: контур у клавиатурного пользователя наезжает от нуля,
       и замер сразу после Tab видит `0px solid` при горящем
       :focus-visible. Прогон 12.10 уронил на этом пять щупов подряд. */
    transition: background-color var(--transition-fast),
                color var(--transition-fast),
                border-color var(--transition-fast),
                box-shadow var(--transition-fast),
                transform var(--transition-fast);
}

/* ЦЕЛЬ НАЖАТИЯ — СТУПЕНЬ 44, А ВЫСОТА ОСТАЁТСЯ СВОЕЙ.
   Замер 05.10: чипы были 32 и 34, поиск 39, «Все турниры» 21 — девять
   целей в полосе, и ни одной 44. Ряд из 44-высоких пилюль поднял бы
   липкую полосу ещё на десяток пикселей, а она и так съедает две пятых
   низкого горизонтального экрана. Поэтому прозрачный слой, приём принят
   Костей 04.10. У .trn-chips нет overflow — проверено чтением, слой не
   обрезается. */
.trn-back::after {
    content: '';
    position: absolute;
    left: 0;
    right: 0;
    top: 50%;
    transform: translateY(-50%);
    height: var(--btn-h-md);
}

.trn-back {
    position: relative;
}

/* Фильтр, который ничего не найдёт, не предлагается: ноль на экране
   сообщает меньше, чем погасшая кнопка */
.trn-chip:disabled {
    opacity: 0.4;
    cursor: default;
    pointer-events: none;
}

/* Подсветка по общему правилу сайта: лаймовая, а не белая */
.trn-chip:hover {
    background: var(--alpha-lime-06);
    border-color: var(--accent);
    color: var(--accent);
}

/* Системную синюю рамку после нажатия убираем, но с клавиатуры
   фокус должен быть виден — лаймовой обводкой */
.trn-chip:focus {
    outline: none;
}

.trn-chip:focus-visible {
    outline: var(--bw-thick) solid var(--accent);
    outline-offset: -2px;
}

.trn-chip.active {
    background: var(--accent);
    border-color: var(--accent);
    color: var(--neutral-black);
    font-weight: 600;
}

/* Короткая подпись чипа — только для телефона: на широком экране
   показываем полное слово */
.trn-chip-short {
    display: none;
}

/* Сколько турниров попадёт под фильтр — видно до нажатия */
.trn-chip-count {
    margin-left: 6px;
    opacity: 0.55;
    font-variant-numeric: tabular-nums;
}

.trn-chip.active .trn-chip-count {
    opacity: 0.6;
}

/* Выпадающий список внутри полосы фильтров — например NTRP на поиске
   игрока: выглядит как чип, а не как поле из другой оперы */
.trn-chips select {
    padding: 8px 18px;
    background: transparent;
    border: var(--bw-hair) solid var(--border-subtle);
    border-radius: var(--radius-full);
    color: var(--text-secondary);
    font-size: var(--fs-sm);
    font-family: var(--font-sans);
    font-weight: 500;
    cursor: pointer;
}

/* Название категории в полосе — справа от поиска, тем же ростом, что чипы */
.trn-cat-name {
    margin-left: auto;
    font-size: var(--fs-base);
    font-weight: 700;
    color: var(--text-primary);
    white-space: nowrap;
}

.trn-chip-div {
    width: 1px;
    align-self: stretch;
    background: var(--border-subtle);
    margin: 2px 6px;
}

/* ПЛАНШЕТ И УЖЕ — 16.10, кусок 75 */
@media (max-width: 992px) {
    .trn-filters {
        padding: 12px 0;
    }

    /* Влево, как всё остальное содержимое страницы: по центру «Все турниры»
       висела посреди полосы и читалась как заголовок, а не как ссылка назад */
    .trn-filters-inner {
        justify-content: flex-start;
        padding: 0 var(--pad-x);
    }

    .trn-back {
        flex: 1 1 100%;
    }

    /* На странице рейтинга у этого же блока есть правило с родителем, оно
       весит больше и оставляло поиск шириной 320 даже там, где места хватает */
    .trn-search-wrap,
    .pl-category-row .trn-search-wrap {
        flex: 1 1 100%;
        max-width: 480px;
    }

    .trn-chips {
        justify-content: flex-start;
    }

    /* Разделитель групп на телефоне — не чёрточка, а перенос строки: фильтры
       состояния идут своей строкой, пол — своей. Вперемешку «Все» от пола
       оказывалось между «Завершённые» и «Мужские», и было не понять,
       к чему оно относится */
    .trn-chip-div {
        flex: 1 1 100%;
        width: auto;
        height: 0;
        margin: 0;
        background: none;
    }

    /* НИЗКИЙ ГОРИЗОНТАЛЬНЫЙ: «НАЗАД» НЕ ЗАНИМАЕТ ЦЕЛУЮ СТРОКУ — 31.10,
       слово Кости «да».

       ЗАМЕРЕНО ДО ПРАВКИ, В БРАУЗЕРЕ, ЗАГРУЗКОЙ НА 844×390: полоса
       фильтров 177 точек = 45 % экрана при пределе 34. Складывается из
       ТРЁХ рядов по 44 (цель нажатия) плюс два зазора 12 и поля 8/12.
       Три ряда потому, что `.trn-back` выше стоит `flex: 1 1 100%` и
       растягивается до 807 при своей нужде 144.
       ПОСЛЕ ПРАВКИ, ТЕМ ЖЕ ПРИБОРОМ: 121 = 31 %, два ряда — «назад»
       144 и поиск 480 в одном, чипсы 643 во втором.

       ЦЕЛЬ НАЖАТИЯ НЕ ТРОНУТА. Опустить 44 до 36 было бы короче, но это
       уже решено на живом матче: WCAG 2.5.5 не отменяется поворотом
       телефона (`css/live-match.css:1037`). Экран выигрывается рядом, а
       не пальцем.

       УСЛОВИЕ — ПОВОРОТ И ВЫСОТА, А НЕ ТРЕТЬЯ ШИРИНА: ширины в медиа
       только 640 и 992, остальное делится высотой и поворотом. Тот же
       порог 500, что у полноэкранного живого матча. */
    @media (orientation: landscape) and (max-height: 500px) {
        .trn-back {
            flex: 0 0 auto;
        }
    }

    .trn-chip {
        padding: 8px 16px;
        /* Кегль один на все виды — задан в общем правиле */
    }

    .trn-chips {
        gap: 6px;
    }

    /* Короткие подписи: полными четыре фильтра состояния занимали 448
       пикселей при полосе в 351, и «Завершённые» уходили на вторую строку.
       Ужимать шрифт бесполезно — чипы стали бы мелкими для пальца */
    .trn-chip-full {
        display: none;
    }

    .trn-chip-short {
        display: inline;
    }

}

@media (max-width: 640px) {
    /* Поля потеснее и счётчик вплотную: четыре фильтра состояния должны
       встать одной строкой. С прежними 12 они занимали 359 пикселей при
       полосе в 351 — «Прошедшие» срывались вниз. Теперь 326 */
    .trn-chip {
        padding: 7px 10px;
        /* Было min-height: 34 — ниже ступени кнопок и ниже цели нажатия.
           Ступень берётся из общего правила: 44 на всех видах */
    }

    .trn-chip-count {
        margin-left: 3px;
    }

}

/* ========================================
   ОБЛОЖКА РАЗДЕЛА НА ТЕЛЕФОНЕ — один размер на весь сайт
   ======================================== */

/* ПЛАНШЕТ И УЖЕ — 16.10, кусок 75 */
@media (max-width: 992px) {
    /* Все шапки разделов одной высоты: переход между разделами не должен
       прыгать, а длина названия — менять размер обложки. Было от 265 до 460
       в зависимости от раздела.

       Название стоит вверху, под шапкой сайта, цифры (если они есть) — у
       нижнего края: между ними видна фотография. Раскладку внутри задаёт
       файл раздела, здесь только размеры.

       Страницы отдельных карточек — турнира, корта, тренера, новости —
       живут по своим правилам: там афиша объекта, а не шапка раздела. */
    /* Класс написан дважды не по ошибке: у каждого раздела свой файл, и он
       подключается после style.css — при равном весе побеждала бы его
       собственная высота для широкого экрана */
    .pl-hero.pl-hero,
    .ct-hero.ct-hero,
    .co-hero.co-hero,
    .ip-hero.ip-hero,
    .io-hero.io-hero,
    .sv-hero.sv-hero,
    /* 07.10: `pt-hero` был НАЗВАН в комментарии к этому правилу
       (css/style.css:11863), но в самом списке его не было — и страница
       «Поиск игрока» оставалась единственной со своей высотой обложки.
       Замер 07.10: 358.4 и 253.2 против 275 у шести соседей, причём
       ДОЛЯМИ ВЫСОТЫ ОКНА (35vh и 30vh). */
    .pt-hero.pt-hero,
    /* 08.10: страница новостей входит в тот же список. У неё обложка
       списка — .news-hero-list; обложка ОТДЕЛЬНОЙ новости сюда не входит,
       её высоту задаёт содержимое, и она своя. */
    .news-hero-list.news-hero-list,
    .pr-hero.pr-hero {
        /* ПОЛ СНЯТ ОТСЮДА 12.10 — ОН БЫЛ ВТОРЫМ ОПРЕДЕЛЕНИЕМ.
           Здесь стояло `min-height: 275px` для девяти обложек разом, а в
           `css/tokens.css` живёт крутилка `--oblozhka-pol`: 360 базово и
           275 на ≤640. Границы у них РАЗНЫЕ — тут 768, у крутилки 640, — и
           на планшете 768 выигрывало это правило: замер давал пол 275 при
           крутилке 360. Шов записан ещё 07.10 и четыре дня ждал.

           Теперь пол называет только крутилка; все девять обложек уже
           держат его через `min-height: var(--oblozhka-pol)` в своих
           файлах. Воздух остаётся здесь — он от пола не зависит.

           ЦЕНА, НАЗВАННАЯ ВСЛУХ: на планшете 768 обложки стали выше —
           360 вместо 275. Это ВИДНО, и Костя смотрел снимки до коммита. */
        padding: var(--section-y) var(--pad-x) var(--section-y-sm);
    }
}

@media (max-width: 640px) {
    /* На узком телефоне полоса всего 324 пикселя, и со счётчиками строка
       не сходится. Цифры не теряются: то же число стоит в подзаголовке
       блока — «8 турниров в архиве» */
    .trn-chip[data-filter="status"] .trn-chip-count {
        display: none;
    }
}


/* ========================================
   ПОЛОСА СТРАНИЦ — ОДНА НА ВЕСЬ САЙТ
   Переехала из css/players.css 07.10: её попросили корты, а определение
   должно остаться одним. Собрана по `Pagination row 115:171` и
   `Pagination item 31:43`; все причины — в комментариях ниже, как были.
   ======================================== */

.pl-pagination-section {
    padding: 0 var(--pad-x) var(--section-y);
}

.pl-pagination-section:empty {
    padding: 0;
}

/* ПОЛОСА СТРАНИЦ: СЧЁТЧИК СЛЕВА, СТРАНИЦЫ СПРАВА — как в компоненте
   `Pagination row 115:171` и как требует отраслевое правило: «always present
   a clear indicator of how many results have been returned» (Elastic EUI).
   Счётчика здесь не было вовсе.
   Ширина — та же колонка, что у таблицы: полоса принадлежит таблице, а не
   странице, и её края обязаны совпадать с краями таблицы. */
.pl-pagination {
    display: flex;
    justify-content: space-between;
    align-items: center;
    gap: var(--space-3);
    width: 100%;
    /* ШИРИНА ПОЛОСЫ — ШИРИНА ЕЁ ВИТРИНЫ, А НЕ ЧИСЛО. У рейтинга витрина
       это таблица в 1100, у кортов — сетка карточек по широкой коробке.
       Замер 07.10 после переезда: витрина 1337, полоса 1100 — счётчик
       слева и кнопки справа висели уже списка, который считают.
       Число остаётся умолчанием, раздел называет своё. */
    max-width: var(--polosa-shirina, 1100px);
    margin-inline: auto;
    /* МЕСТО ПОД ВЫСТУП СЛОЯ ЦЕЛИ. Кнопка страницы 36 (`Pagination item
       31:43`), а цель 44 — слой `::after` центрирован на кнопке и торчит
       по 4 с каждой стороны. У КРАЙНЕЙ кнопки этот выступ выходил за ряд,
       и полоса становилась шире себя на 4: проба 61 ловила это как
       перелив. Поле 4 — ступень шкалы отступов, слой помещается внутрь.
       Решение «кнопка 36 плюс слой 44» принято Костей 04.10 и не трогается. */
    padding-inline: var(--space-1);
}

.pl-pagination-pages {
    display: flex;
    align-items: center;
    /* Шаг 6 на шкале отсутствует (8 · 12 · 16 · 24 · 32 · 40), и ровно 8
       назван в описании компонента `Pagination row 115:171`. */
    gap: var(--space-2);
}

/* Счётчик и «страница N из M» — один уровень текста: оба говорят, где ты в
   списке, и оба тихие рядом с кнопками */
.pl-page-count,
.pl-page-now {
    color: var(--text-muted);
    font-size: var(--fs-sm);
    line-height: var(--lh-none);
    white-space: nowrap;
}

.pl-page-now {
    display: inline-flex;
    align-items: center;
    min-height: var(--btn-h-sm);
    padding: 0 var(--space-2);
    color: var(--text-secondary);
    font-weight: var(--fw-medium);
}

@media (max-width: 640px) {

    /* НА ТЕЛЕФОНЕ ИМЯ МЕЛЬЧЕ, И ПОЛЕ 10 УЖЕ НЕ ДОТЯГИВАЕТ. Замер: строка
       имени 21 вместо 24, 21 + 20 = 41 при норме 44. Поле 12 даёт 45.
       Число считается от строки, а не берётся на глаз. */
    .pl-player-name {
        padding-block: 12px;
        margin-block: -12px;
    }


    /* На телефоне счётчик уходит под кнопки: ряд из счётчика, двух стрелок и
       «20 из 40» просит 343 ровно под обрез, и длинное число его ломает */
    .pl-pagination {
        flex-direction: column-reverse;
        gap: var(--space-2);
    }
}

/* КНОПКА СТРАНИЦЫ — СТУПЕНЬ ШКАЛЫ И ЦЕЛЬ НАЖАТИЯ.
   Было 40 × 40 — такой ступени на шкале кнопок нет (28 · 36 · 44 · 52), и
   это меньше цели нажатия 44. Ниже 480 стояло второе определение на 36 —
   ещё и по запрещённой ширине. Теперь одно число на все виды: 44. */
.pl-page-btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    position: relative;
    /* РАЗМЕР И РАДИУС ИЗ КОМПОНЕНТА `Pagination item 31:43`: «36x36
       (control/btn-h-sm), radius/sm. Active fills with accent/default».
       Здесь стояли 44 и радиус 12 — моё число, поставленное ради цели
       нажатия. Цель остаётся: КНОПКА 36 ЗАКОННА, ЕСЛИ У НЕЁ ЕСТЬ ПРОЗРАЧНЫЙ
       СЛОЙ ЦЕЛИ 44 (принято Костей 04.10) — слой ниже. Тем же приёмом живут
       пилюли разрядов и «Показать всех» на этой же странице. */
    min-width: var(--btn-h-sm);
    height: var(--btn-h-sm);
    padding: 0 8px;
    background: var(--bg-card);
    border: var(--bw-hair) solid var(--border-subtle);
    border-radius: var(--radius-sm);
    color: var(--text-secondary);
    font-family: var(--font-sans);
    font-size: var(--fs-sm);
    font-weight: var(--fw-medium);
    line-height: var(--lh-none);
    cursor: pointer;
    /* не «all»: оно анимирует outline-width, и контур фокуса наезжает
       от нуля — та же беда, что у чипа (09.10, 12.10) */
    transition: background-color 0.2s, color 0.2s, border-color 0.2s;
}

/* Слой цели нажатия: коробка 36, попадание 44. Слой прозрачный и ничего не
   рисует; у `.pl-pagination` нет `overflow`, так что он не обрезается.

   12.10: ПО ШИРИНЕ СЛОЙ ТОЖЕ ДОВЕДЁН ДО 44. Было 36 — высота 44, ширина по
   коробке, и палец мимо попадал по горизонтали. Посчитано прежде правки:
   зазор между кнопками 8, коробка 36, значит 36 + 8 = 44 — слои соседей
   встают ВПРИТЫК и не перекрываются, нажатие чужой кнопки не съедается.
   Найдено замером кабинета, но правится здесь: полоса одна на весь сайт. */
.pl-page-btn::after {
    content: '';
    position: absolute;
    left: 50%;
    top: 50%;
    transform: translate(-50%, -50%);
    min-width: var(--btn-h-md);
    width: 100%;
    height: var(--btn-h-md);
}

/* Многоточие между далёкими страницами: не кнопка и нажатия не просит */
.pl-page-gap {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 24px;
    height: var(--btn-h-sm);
    color: var(--text-muted);
    font-size: var(--fs-sm);
    line-height: var(--lh-none);
    user-select: none;
}

.pl-page-btn:hover:not(:disabled) {
    border-color: var(--accent);
    color: var(--accent);
}

.pl-page-btn.active {
    background: var(--accent);
    border-color: var(--accent);
    color: var(--neutral-black);
    font-weight: var(--fw-bold);
}

.pl-page-btn:disabled {
    opacity: 0.3;
    cursor: not-allowed;
}

.pl-page-prev,
.pl-page-next {
    background: transparent;
}


/* ========================================
   ВЫПАДАЮЩИЙ ФИЛЬТР В ПОЛОСЕ — один на весь сайт
   Кнопка выглядит как чип, по нажатию — список значений со счётчиками.
   Пользуются корты (город, покрытие), тренеры (возраст, занятия,
   уровень), поиск игрока и новости.

   КОРТЫ С 02.11 ЗДЕСЬ НЕ ОДЕВАЮТСЯ: они перешли на общий `.kslt-dd` в
   `tokens.css` — дом `js/kslt-vypadashka.js`, одежда по компонентам
   `Dropdown 34:55` и `Menu item 32:44`. Сюда остались тренеры, партнёры
   и новости, и это ДОЛГ, а не решение: три копии одного понятия ждут
   того же переезда.

   Написано mobile-first: база описывает телефон, широкий экран
   добавляется через min-width. На телефоне список выравнивается по краям
   полосы фильтров, а не по краям своей кнопки — иначе у правой кнопки он
   вылезал за экран
   ======================================== */

/* Ряд чипов — точка отсчёта для списка. Ширина во всю полосу: на поиске
   игрока содержимое центрируется, и ряд получался по ширине текста */
.trn-chips {
    position: relative;
    width: 100%;
}

/* Влево раскрывается только список с соседями слева: когда он в полосе
   один, кнопка стоит слева, и меню должно держаться её */
/* ГАЛОЧКА У ВЫБРАННОГО ПУНКТА — `Menu item 32:44` ТРЕБУЕТ ЕЁ ПРЯМО:
   «Selected carries a tick and goes accent/default — never rely on the
   tint alone, it is invisible to anyone who cannot separate the two
   greys». До 12.10 выбранный пункт отличался ТОЛЬКО краской, и человек,
   не различающий два серых, не видел своего выбора вовсе.
   Галочка встаёт перед счётчиком, а не вместо него. */

                                                        
                                                                        
                                                                           
                                                                     
                       
                                                                       
                                                 
                                                                        
.ct-card:focus-visible,
.co-card:focus-visible,
.pl-page-btn:focus-visible{
    outline: var(--bw-thick) solid var(--accent);
    outline-offset: 2px;
}

/* Шире телефона полоса фильтров не тянется во всю ширину: место есть.
   Правила про `.f-dd` отсюда ушли 02.11 вместе с самим `.f-dd` — все
   четыре раздела перешли на общий `.kslt-dd`, и он держится своей
   кнопки сам, без оглядки на ширину экрана. */
@media (min-width: 641px) {
    .trn-chips {
        width: auto;
    }
}

/* ========================================
   ОКНО ПРИГЛАШЕНИЯ — одно на весь сайт
   Зовёт гостя зарегистрироваться, а зарегистрированного — оформить
   членство. Показывается на поиске игрока, на главной и на странице
   игрока, поэтому живёт здесь, а не в файле раздела
   ======================================== */

.pt-modal-overlay {
    position: fixed;
    inset: 0;
    z-index: 9999;
    background: var(--modal-bg-heavy);
    backdrop-filter: blur(6px);
    -webkit-backdrop-filter: blur(6px);
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 20px;
    opacity: 0;
    transition: opacity 0.2s ease;
}

.pt-modal-overlay.visible {
    opacity: 1;
}

.pt-modal {
    position: relative;
    /* Как у остальных окон сайта: плотная подложка и приглушённая рамка
       цветом клуба. Прозрачный фон просвечивал страницу насквозь */
    background: var(--bg-elevated);
    border: var(--bw-hair) solid var(--alpha-lime-30);
    border-radius: 16px;
    padding: 22px 18px 20px;
    max-width: 320px;
    width: 100%;
    text-align: center;
    backdrop-filter: blur(20px);
    -webkit-backdrop-filter: blur(20px);
    transform: translateY(20px);
    transition: transform 0.25s ease;
}

.pt-modal-overlay.visible .pt-modal {
    transform: translateY(0);
}

.pt-modal-close {
    position: absolute;
    top: 10px;
    right: 10px;
    width: 28px;
    height: 28px;
    background: var(--alpha-white-08);
    border: none;
    border-radius: 50%;
    color: var(--text-secondary);
    font-size: var(--fs-md);
    cursor: pointer;
    display: flex;
    align-items: center;
    justify-content: center;
    transition: background 0.2s;
}

.pt-modal-close:hover {
    background: var(--alpha-white-12);
}

.pt-modal-icon {
    font-size: var(--fs-xl);
    margin-bottom: 10px;
}

.pt-modal-title {
    font-size: clamp(1.05rem, 4.6vw, 1.2rem);
    line-height: 1.3;
    font-weight: 800;
    color: var(--neutral-white);
    margin-bottom: 10px;
}

.pt-modal-text {
    font-size: var(--fs-sm);
    color: var(--text-secondary);
    line-height: 1.3;
    margin-bottom: 18px;
}

.pt-modal-btn {
    display: inline-block;
    padding: 10px 22px;
    background: var(--accent);
    color: var(--neutral-black);
    font-size: var(--fs-sm);
    font-weight: 700;
    border: none;
    border-radius: var(--radius-md);
    text-decoration: none;
    cursor: pointer;
    transition: background 0.2s, transform 0.15s;
}

.pt-modal-btn:hover {
    background: var(--accent-hover);
    transform: translateY(-1px);
}

.pt-modal-disclaimer {
    margin-top: 12px;
    font-size: var(--fs-2xs);
    color: var(--text-muted);
    line-height: 1.5;
}

.pt-modal-disclaimer a {
    color: var(--accent);
    text-decoration: underline;
    text-underline-offset: 2px;
}

/* Payment modal — wider */
.pt-modal-pay {
    max-width: 480px;
    padding: 32px 28px 28px;
    text-align: left;
    max-height: 90vh;
    overflow-y: auto;
}

.pt-modal-pay .pt-modal-title {
    text-align: center;
}

.pt-modal-pay .pt-modal-text {
    text-align: center;
}

.pt-pay-label {
    font-size: var(--fs-2xs);
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 1px;
    color: var(--text-muted);
    margin-top: 14px;
    margin-bottom: 8px;
}

.pt-pay-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(100px, 1fr));
    gap: 8px;
}

.pt-pay-method {
    position: relative;
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 4px;
    padding: 12px 8px;
    background: var(--alpha-white-04);
    border: var(--bw-hair) solid var(--border-subtle);
    border-radius: var(--radius-md);
    cursor: default;
    opacity: 0.5;
    transition: opacity 0.2s;
}

.pt-pay-icon {
    font-size: var(--fs-lg);
}

.pt-pay-name {
    font-size: var(--fs-xs);
    font-weight: 600;
    color: var(--text-secondary);
    text-align: center;
}

.pt-pay-soon {
    position: absolute;
    top: 4px;
    right: 4px;
    font-size: var(--fs-2xs);
    font-weight: 700;
    text-transform: uppercase;
    color: var(--accent);
    background: var(--alpha-lime-12);
    padding: 1px 5px;
    border-radius: 4px;
    letter-spacing: 0.5px;
}

.pt-pay-divider {
    display: flex;
    align-items: center;
    gap: 12px;
    margin: 16px 0;
    font-size: var(--fs-xs);
    color: var(--text-muted);
}

.pt-pay-divider::before,
.pt-pay-divider::after {
    content: '';
    flex: 1;
    height: 1px;
    background: var(--border-subtle);
}

.pt-pay-tg-btn {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 8px;
    width: 100%;
    padding: 12px;
    background: var(--accent);
    color: var(--neutral-black);
    font-size: var(--fs-sm);
    font-weight: 700;
    border: none;
    border-radius: var(--radius-md);
    text-decoration: none;
    cursor: pointer;
    transition: background 0.2s, transform 0.15s;
}

.pt-pay-tg-btn:hover {
    background: var(--accent-hover);
    transform: translateY(-1px);
}

.pt-modal-note {
    margin-top: 12px;
    font-size: var(--fs-xs);
    color: var(--text-muted);
    text-align: center;
    line-height: 1.3;
}

.pt-modal-pay .pt-modal-disclaimer {
    text-align: center;
    margin-top: 10px;
}


/* Шире телефона: места больше, окно дышит свободнее */
@media (min-width: 769px) {
    .pt-modal {
        padding: 40px 32px 32px;
        border-radius: var(--radius-lg);
        max-width: 400px;
    }

    .pt-modal-icon {
        font-size: var(--fs-3xl);
        margin-bottom: 16px;
    }

    .pt-modal-title {
        font-size: var(--fs-lg);
    }

    .pt-modal-text {
        font-size: var(--fs-sm);
        line-height: 1.5;
        margin-bottom: 24px;
    }

    .pt-modal-btn {
        padding: 11px 32px;
        font-size: var(--fs-sm);
    }

    .pt-modal-close {
        top: 14px;
        right: 14px;
        width: 32px;
        height: 32px;
    }

    .pt-modal-disclaimer {
        margin-top: 16px;
        font-size: var(--fs-2xs);
    }
}

/* ========================================
   БЛОКИ РАЗДЕЛА — одни на весь сайт
   Заголовок с лаймовой чертой, поясняющая строка под ним и пустое
   состояние. Используются на страницах категорий турниров и на баттлах
   ======================================== */

/* Блоки турниров
   ------------------------------------------------------------------
   «Идут сейчас» и «Предстоящие» скрываются целиком, когда пусто: это
   состояние, а не раздел, и пустая заглушка первым экраном читается как
   поломка. «Завершённые» остаются всегда — архив есть у каждой категории. */
.trn-block {
    padding: var(--section-y-sm) var(--pad-x);
    background: var(--bg-primary);
}

.trn-block:nth-of-type(even) {
    background: var(--bg-secondary);
}

.trn-block[hidden] {
    display: none;
}

.trn-block-head {
    border-left: var(--bw-heavy) solid var(--accent);
    padding-left: 14px;
    margin-bottom: var(--space-md);
}

.trn-block-title {
    font-size: var(--fs-xl);
    font-weight: 700;
    color: var(--text-primary);
}

.trn-block-sub {
    font-size: var(--fs-sm);
    color: var(--text-muted);
    margin-top: 2px;
}

.trn-live-dot {
    display: inline-block;
    width: 8px;
    height: 8px;
    border-radius: 50%;
    background: var(--danger);
    margin-right: 8px;
    vertical-align: middle;
    animation: trnLivePulse 1.6s infinite;
}

@keyframes trnLivePulse {
    0%, 100% { opacity: 1; }
    50% { opacity: 0.3; }
}

/* ========================================
   КАРТОЧКА ТУРНИРА — одна на весь сайт
   Разметку рисует js/tournament-card.js
   ======================================== */

.tournaments-grid {
    display: grid;
    /* Двенадцать колонок, чтобы делить ряд и пополам, и на трети, и на
       четверти, оставаясь в одном ритме */
    grid-template-columns: repeat(12, 1fr);
    gap: 24px;
}

/* Первая — главная: половина ряда, фотография слева, текст справа.
   Правила привязаны к .tc: тот же класс сетки носят страницы категорий,
   где пока живёт старая карточка, и там «span 6» растягивало её на всю
   ширину — карточка на три колонки занимала ряд целиком */
.tournaments-grid > .tc:nth-child(1) { grid-column: span 6; }
.tournaments-grid > .tc:nth-child(2),
.tournaments-grid > .tc:nth-child(3) { grid-column: span 3; }
.tournaments-grid > .tc:nth-child(n+4) { grid-column: span 4; }

.tc {
    display: flex;
    flex-direction: column;
    background: var(--surface-glass);
    border: var(--bw-hair) solid var(--border-subtle);
    border-radius: var(--radius-lg);
    overflow: hidden;
    text-decoration: none;
    color: inherit;
    transition: border-color var(--transition-base), transform var(--transition-base);
}

.tc:hover {
    border-color: var(--alpha-lime-20);
    transform: translateY(-3px);
}

.tc-featured {
    display: grid;
    grid-template-columns: 1.1fr 1fr;
}

/* Текстовый блок тянется на всё, что осталось от афиши: иначе он держал свою
   высоту, и «по центру» с «прижать вниз» ничего не меняли */
.tournaments-grid > .tc:nth-child(-n+3):not(.tc-featured) .tc-body {
    flex: 1 1 auto;
}

/* У завершённых в плитке кнопки нет, и снизу оставалась дыра в треть карточки.
   Ставим текст по центру свободного места — так пустота делится поровну */
.tournaments-grid > .tc:nth-child(-n+3):not(.tc-featured) .tc-body {
    justify-content: center;
}

.tournaments-grid > .tc:nth-child(-n+3):not(.tc-featured):has(.tc-btn) .tc-body {
    justify-content: flex-start;
}

/* В плитке места мало. Отсчёт важнее адреса — его оставляем, а строку с
   кортом прячем: она есть на странице турнира */
.tc:not(.tc-featured) .tc-meta span:not(.tc-slots) {
    display: none;
}

.tc:not(.tc-featured) .tc-meta span {
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.tc:not(.tc-featured) .tc-btn {
    margin-top: auto;
}

/* СНЯТО 05.10 вместе с остальными тремя мёртвыми состояниями заявки
   (is-registered, is-pending-guest, is-refused). Ни одного вхождения вне
   css во всём дереве и в истории js — ставить их некому. На вид лендинга
   снятие повлиять не может: класса, по которому правило срабатывало, не
   существует нигде. */

/* Первый ряд — одной высоты. Главная карточка была ниже соседних, и ряд
   выглядел ступенькой; а если её не ограничить, длинное описание растягивает
   весь ряд втрое. Поэтому высота задана всем троим, а описание обрезается.

   320 вместо 380 — решение Кости 23.09, доска 345:9. Замер при 380: у боковых
   тело 228 при содержимом 128, полях 24 и промежутках 18 — настоящая дыра 58;
   у главной дыра 174. Сжать боковые ОТДЕЛЬНО нельзя: они стоят с главной в
   одном ряду сетки, и высоту ряда задаёт самый высокий. Поэтому одно число:
   при 320 тело боковых становится 170 и занято ровно, в ноль, а у главной
   тело 318 — и туда же дотягивается её содержимое.
   (380 в своё время пришло взамен 332, когда в плитке появилась кнопка.) */
/* 07.10 · ЧИСЛО ПЕРЕСТАЛО ПОМЕЩАТЬ СОДЕРЖИМОЕ, И ЭТО БЫЛО ВОПРОСОМ ВРЕМЕНИ.
   Выше по комментарию сказано прямо: «при 320 тело боковых становится 170 и
   занято ровно, в ноль». Ноль запаса — значит первая же новая строка ломает
   карточку. Она и появилась: как только в турнире завелись заявки, в плитке
   встала строка «осталось N мест». Замер 07.10: тело выросло 170 -> 193,
   карточке нужно 343 при коробке 320, и `overflow: hidden` срезал кнопку
   «Регистрация» на 12 пикселей.
   ВЫСОТА КАРТОЧКИ — СЛЕДСТВИЕ СОДЕРЖИМОГО, А НЕ ЧИСЛО. Число остаётся, но
   как МИНИМУМ: ряд сетки по-прежнему ровный — его высоту задаёт самая
   высокая карточка, — а содержимое больше никогда не срезается. */
.tournaments-grid > .tc:nth-child(-n+3) {
    height: auto;
    min-height: 320px;
}

.tc-featured .tc-desc {
    display: -webkit-box;
    -webkit-line-clamp: 3;
    -webkit-box-orient: vertical;
    overflow: hidden;
}

/* Текстовая половина главной карточки не должна вылезать за её край:
   иначе кнопка «Подробнее» срезается нижней границей */
.tc-featured .tc-body {
    overflow: hidden;
}

/* Вертикальные афиши в плитке высотой 132 превращались в узкую полоску:
   вписанная целиком, картинка занимала треть ширины. Даём блоку больше
   высоты — афиша читается, а горизонтальные ничего не теряют */
.tc-image {
    position: relative;
    /* Высота ряда фиксированная: под афишу отдаём столько, чтобы ниже
       поместились дата, отсчёт, название и кнопка записи */
    height: 150px;
    flex: 0 0 150px;
    overflow: hidden;
    background: #101010;
}

.tc-featured .tc-image { height: 100%; flex: 1 1 auto; }

/* Афиша вписывается целиком: вертикальную обрезать нельзя, на ней текст.
   Поля достраиваем размытой копией её же — получается фон в тех же цветах */
.tc-image::before {
    content: '';
    position: absolute;
    inset: 0;
    background-image: var(--tc-poster);
    background-size: cover;
    background-position: center;
    filter: blur(24px) brightness(0.5);
    transform: scale(1.2);
}

.tc-image img {
    position: relative;
    width: 100%;
    height: 100%;
    object-fit: contain;
    display: block;
}

.tc-noimage {
    position: relative;
    display: flex;
    align-items: center;
    justify-content: center;
    height: 100%;
    font-size: var(--fs-2xl);
    opacity: 0.35;
}

.tc-badge {
    /* Веса во всей карточке идут токенами --fw-*, а не числами. 23.09:
       числа рисовали то же самое, но система про них не знала — поменяем
       ступень веса в токенах, и карточка не сдвинется. */
    /* БЕЙДЖ СТАТУСА — ПО КОМПОНЕНТУ Badge 27:53. Костя посмотрел оба варианта
       рядом и принял 23.09.

       Компонент: высота 24, поля 8, radius/full, 11 Medium ЗАГЛАВНЫМИ,
       разрядка 0.5. Было: высота 30 (5+12 полей), кегль 12, вес 700, строчными.
       Цвет и радиус не менялись — они и раньше совпадали с компонентом.

       inline-flex с фиксированной высотой, а не padding сверху-снизу: высоту
       бейджа задаёт компонент одним числом, и она не должна плавать от кегля. */
    position: absolute;
    top: 12px;
    left: 12px;
    z-index: 2;
    display: inline-flex;
    align-items: center;
    height: 24px;
    padding: 0 var(--space-2);
    border-radius: var(--radius-full);
    font-size: var(--fs-2xs);
    font-weight: var(--fw-medium);
    text-transform: uppercase;
    letter-spacing: 0.5px;
}

/* ТОНА СТАТУСА. Костя принял 22.09, тон подобран замером.

   СВЕРКА ФИГМЫ: изобретать не пришлось — в компоненте Badge 27:53 уже
   лежат шесть тонов на два стиля. Берём готовые: идёт = Danger/Solid,
   открыта = Success/Subtle, скоро = Info/Subtle, закрыта и завершён =
   Neutral/Subtle, отменён = Danger/Subtle.

   ПОЧЕМУ НЕ ЛАЙМ. Правило 109:370, записано с твоих слов «так много
   лаймового, что тошнить начинает»: лайм принадлежит ОДНОМУ главному
   действию в поле зрения. На карточке это кнопка записи. Бейдж и дата
   забирали его себе — в секции насчитали 13 лаймовых пятен, осталось 7.

   ПОЧЕМУ ПОДЛОЖКИ НЕПРОЗРАЧНЫЕ. Бейдж лежит НА АФИШЕ, а не на карточке.
   Сквозь подложку в 15% проступает фотография, и контраст перестаёт быть
   числом: он зависит от того, какую картинку загрузил организатор.
   Поэтому здесь не -subtle, а их непрозрачные двойники из tokens.css.

   ЗАМЕРЫ (WCAG 1.4.3; подпись 11px — мелкий текст, порог 4.5):
     идёт     #FF4757 + почти-чёрный   5.93   было белым — 3.34, не проходило
     открыта  #183122 + --success      7.25
     скоро    #1F2A37 + --info         5.72   на старом #3B82F6 было 4.19
     закрыта  #303030 + 72% белого     7.7
     отменён  #331B1D + --danger       5.6

   ВОЛОСЯНОЙ КРАЙ. Тёмная подложка на тёмной афише сливается с ней
   (1.44 к чёрному), и бейдж читается как текст, висящий в воздухе.
   Даём край. Не рамкой, а внутренней тенью: рамка добавила бы 2px
   к высоте, а высоту 24 задаёт компонент одним числом. */
.tc-badge-live {
    background: var(--danger);
    color: var(--danger-on);
}
.tc-badge-open {
    background: var(--success-surface);
    color: var(--success);
    box-shadow: inset 0 0 0 1px var(--border-light);
}
.tc-badge-soon {
    background: var(--info-surface);
    color: var(--info);
    box-shadow: inset 0 0 0 1px var(--border-light);
}
.tc-badge-closed,
.tc-badge-done {
    background: var(--neutral-surface);
    color: var(--text-secondary);
    box-shadow: inset 0 0 0 1px var(--border-light);
}
/* Отменённый турнир рисовался БЕЗ подложки: класса .tc-badge-cancelled
   не было вовсе, хотя js/tournament-card.js его выдаёт (статус cancelled
   есть во всех трёх языках). Найдено 22.09. */
.tc-badge-cancelled {
    background: var(--danger-surface);
    color: var(--danger);
    box-shadow: inset 0 0 0 1px var(--border-light);
}

.tc-body {
    display: flex;
    flex-direction: column;
    gap: 6px;
    padding: 12px 14px;
}

.tc-featured .tc-body { padding: 16px 24px; justify-content: center; }

.tc-date {
    font-size: var(--fs-xs);
    font-weight: var(--fw-semibold);
    letter-spacing: 0.04em;
    text-transform: uppercase;
    /* ЛЕСТНИЦА ЦВЕТА В КАРТОЧКЕ — ТРИ СТУПЕНИ, доска 345:9, решение Кости 23.09.
       Дата была лаймовой; лайм ушёл кнопке по правилу 109:370, и я поставил
       белый — получил вторую стопроцентную ступень рядом с названием.
       Костя: «дата название описание все одного белого цвета, всё сливается».
       Теперь: 100% — что это (название), 72% — про что это (дата и описание),
       50% — подробности (места). Дату и описание разводит не цвет, а форма:
       12 капсом с разрядкой и весом 600 против обычных 14. Контраст 9.88. */
    color: var(--text-secondary);
}

.tc-title {
    font-size: var(--fs-base);
    font-weight: var(--fw-bold);
    margin: 0;
    color: var(--text-primary);
}

.tc-featured .tc-title { font-size: var(--fs-lg); }

/* Названия турниров бывают длинными: «ДРУЖЕСКИЙ ПАРНЫЙ ТУРНИР — BRK GROUP
   — женский» занимало четыре строки и выталкивало кнопку за нижний край
   карточки. Высота у карточки фиксированная, поэтому ограничиваем заголовок */
.tc-featured .tc-title {
    display: -webkit-box;
    -webkit-line-clamp: 3;
    -webkit-box-orient: vertical;
    overflow: hidden;
    line-height: 1.3;
}

.tc-desc {
    font-size: var(--fs-sm);
    color: var(--text-secondary);
    margin: 0;
}

.tc-meta {
    display: flex;
    flex-wrap: wrap;
    gap: 14px;
    font-size: var(--fs-sm);
    /* Было --text-dim, 35% белого: контраст 3.22 при кегле 14. Порог 4.5
       (WCAG 1.4.3) — не проходило. 50% даёт 5.31. Найдено замером 23.09,
       глазами такое не ловится: текст «читается», просто плохо. */
    color: var(--text-muted);
}

.tc-featured .tc-meta { font-size: var(--fs-sm); }

/* Свободные места. Пока их много — обычная строка вместе с местом
   проведения; когда осталась треть, подсвечиваем: ради этого счётчик
   и завели, иначе цифру не замечают */
.tc-slots-tight {
    color: var(--warning);
    font-weight: var(--fw-semibold);
}

/* 07.10 · ВЫСОТУ КНОПКИ ДЕРЖИТ СТУПЕНЬ, А НЕ ПОЛЯ. Прогон 07.10 поймал
   `кнопка записи 41` у карточки в блоке «Предстоящие»: высоты у кнопки не
   было вовсе — её давали поля, а в колонке с нехваткой места флекс имеет
   право сжать такой элемент. 41 — не ступень шкалы кнопок и меньше цели
   нажатия 44. Ступень ставится явно, и сжатие запрещено.
   Та же беда уже записана правилом: «`flex-basis: 0%` ПЕРЕБИВАЕТ `height`». */
.tc-btn {
    align-self: flex-start;
    flex-shrink: 0;
    min-height: var(--btn-h-md);
    display: inline-flex;
    align-items: center;
    justify-content: center;
    margin-top: 6px;
    padding: 10px 22px;
    border-radius: var(--radius-full);
    background: var(--accent);
    color: var(--bg-primary);
    font-size: var(--fs-sm);
    font-weight: var(--fw-bold);
}

/* Счётчик появляется за двое суток до старта и тикает каждую секунду */
/* Завершённый турнир тусклее живого. Не прячем: под наведением возвращаем
   полную яркость — в архиве смотрят итоги, сетку и фотографии */
.tc-past {
    opacity: 0.7;
}

.tc-past:hover {
    opacity: 1;
}

/* Отсчёт красной плашкой — как на страницах турниров и категорий. Белым он
   терялся среди прочего текста, хотя это самое срочное на карточке */
.tc-countdown {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    align-self: flex-start;
    padding: 5px 10px;
    border-radius: 8px;
    /* Прежний комментарий говорил: «красной плашкой — белым он терялся
       среди прочего текста». Довод верный, вывод оказался неверен:
       нейтральная плашка с акцентными цифрами даёт 14.19 против 5.69 у
       белого на красном, то есть теряется ЕЩЁ меньше. А красный тут стоял
       в 1.70 от эфира и мешал «идёт сейчас» отличаться от «до начала». */
    background: var(--alpha-white-08);
    border: var(--bw-hair) solid var(--alpha-white-12);
    font-size: var(--fs-xs);
    font-weight: var(--fw-bold);
    font-variant-numeric: tabular-nums;
    line-height: 1;
    color: var(--accent);
}

.tc-cd-label {
    font-weight: var(--fw-semibold);
    letter-spacing: 0.04em;
    text-transform: uppercase;
    color: var(--text-secondary);
}

.tc-countdown small { font-weight: var(--fw-medium); opacity: 0.6; }
/* СРОЧНО — единственная тревога на карточке. */
.tc-cd-urgent {
    background: var(--danger-subtle);
    border-color: var(--danger);
    color: var(--danger);
    box-shadow: 0 0 0 2px rgba(255, 71, 87, 0.25);
}

/* Правило по номеру ребёнка сильнее правила по классу — иначе сообщение
   встаёт в колонку главной карточки и занимает половину ряда */
.tournaments-grid > .tc-notice {
    grid-column: 1 / -1;
    padding: 40px 20px;
    text-align: center;
    color: var(--text-muted);
    border: var(--bw-hair) dashed var(--border-subtle);
    border-radius: var(--radius-lg);
}

@media (max-width: 992px) {
    .tournaments-grid > .tc:nth-child(1) { grid-column: span 12; }
    .tournaments-grid > .tc:nth-child(2),
    .tournaments-grid > .tc:nth-child(3),
    .tournaments-grid > .tc:nth-child(n+4) { grid-column: span 6; }
    .tc-featured { max-height: none; }

    /* ПЯТЬ КАРТОЧЕК, 1 + 2 + 2. Решение Кости 23.09, доска 341:52.
       Дефект был не в количестве, а в ШЕСТОЙ карточке: она висела одна в
       последнем ряду, и секция читалась как обрезанный список, а не как
       витрина. Правило одно на все узкие виды — показываем столько, сколько
       заполняет ряды ЦЕЛИКОМ. Телефон 390: 1 + 2. Планшет 768: 1 + 2 + 2. */
    .tournaments-grid > .tc:nth-child(n+6) { display: none; }
}

/* Высота 380 у первых трёх задумана для ДЕСКТОПНОГО ряда из трёх. На планшете
   главная идёт на всю ширину, и от этой высоты второй ряд выходил на 58 выше
   третьего — ступенька на ровном месте. Все обычные карточки одной высоты,
   как на доске 341:52.

   Условие сужено с двух сторон НАРОЧНО. Сначала это правило стояло просто в
   слое 992 — и протекло в два других вида: на телефоне вертикально карточки
   стали 322 вместо 273, в ленте боком 322 вместо 270. Причина в цепкости:
   (0,4,1) перебивает и «height: auto» телефона, и высоту ленты. Правило,
   которое нельзя перебить снизу, обязано само знать свои границы. */
@media (min-width: 641px) and (max-width: 992px) and (orientation: portrait) {
    .tournaments-grid > .tc:nth-child(-n+3):not(:nth-child(1)) { height: auto; min-height: 322px; }
}

/* На телефоне карточки шли по одной на всю ширину — ряд из четырёх штук
   растягивался на четыре экрана. Ставим по две: афиша сверху, короткая
   подпись снизу, как на витринах магазинов. */
@media (max-width: 640px) {
    /* ОДНА ШИРОКАЯ СВЕРХУ, ДВЕ ПОД НЕЙ. Решение Кости 23.09, доска 341:9.
       Было четыре одинаковых плитки 2 × 2, и главную карточку нельзя было
       отличить от остальных — витрина без витрины.
       Обе строки одной цепкости (0,2,1): раньше здесь стоял !important,
       чтобы перебить слой 992, теперь перебивать нечем — правила равные,
       а этот блок стоит ниже. */
    .tournaments-grid > .tc:nth-child(n+2) { grid-column: span 6; }
    .tournaments-grid > .tc:nth-child(1)   { grid-column: span 12; }
    .tc-featured { grid-template-columns: 1fr; }
    .tc-featured .tc-image { height: 130px; }
    .tc-image { height: 130px; }

    /* Три карточки: ряды заполнены целиком, четвёртая ушла бы одна в ряд */
    .tournaments-grid > .tc:nth-child(n+4) { display: none; }

    /* Название — две строки. Селектор с родителем: у карточки есть своё
       правило, и одиночный класс ему проигрывал — заголовок оставался
       нулевой высоты и пропадал совсем */
    .tournaments-grid .tc .tc-title,
    .tournaments-grid .tc-featured .tc-title {
        font-size: var(--fs-sm);
        line-height: 1.3;
        display: -webkit-box;
        -webkit-line-clamp: 2;
        -webkit-box-orient: vertical;
        overflow: hidden;
        margin: 4px 0 6px;
        max-height: none;
    }

                                                                            
                                                       
    .tournaments-grid .tc .tc-desc,
    .tournaments-grid .tc-featured .tc-desc{ display: none; }

    /* Место проведения на карточке не показываем: оно приходит подписью без
       класса и занимало три строки из четырёх. Всё это есть на странице
       турнира, а в плитке важнее название и свободные места */
    .tournaments-grid .tc .tc-meta > span:not([class]) { display: none; }

    /* Дата и отсчёт в одну строку и мельче: вдвоём они занимали три */
    .tc-date { font-size: var(--fs-2xs); }
    .tc-countdown { font-size: var(--fs-2xs); padding: 3px 6px; }
    /* Мета оставалась 14 — того же кегля, что заголовок: одиночный .tc-meta
       (0,1,0) проигрывал правилу .tc-featured .tc-meta (0,2,0) из базы.
       Замер 23.09 показал 14/400 при заголовке 14/700. Подпись не может быть
       равна заголовку, поэтому селектор с родителем — как у названия ниже. */
    .tc-meta { margin-top: auto; }
    .tournaments-grid .tc .tc-meta,
    .tournaments-grid .tc-featured .tc-meta { font-size: var(--fs-2xs); }

    /* Значок состояния вылезал за карточку: он ставится от левого края и
       растёт по содержимому, а в колонке 170 пикселей «Регистрация открыта»
       длиннее самой карточки. Ужимаем и не даём выйти за границы */
    /* На телефоне у бейджа остаётся ТОЛЬКО защита от выхода за карточку.
       Размеры отсюда убраны 23.09: кегль и поля задаёт компонент, один на все
       ширины. Раньше здесь стояли font-size и padding: 3px 7px — они ломали
       высоту 24 и делали бейдж на телефоне другим компонентом. */
    /* `.tc-status` снят 14.10 (B2): на сайте его не ставит никто —
       имя живёт в мобильном приложении, и у того СВОЙ app.css. */
    .tc-badge {
        top: 6px;
        left: 6px;
        right: 6px;
        width: fit-content;
        max-width: calc(100% - 12px);
        white-space: nowrap;
        overflow: hidden;
        text-overflow: ellipsis;
    }

    /* Кнопка была шире карточки: ширина считалась без внутренних отступов */
    .tournaments-grid .tc .tc-btn,
    .tournaments-grid .tc .btn-register {
        box-sizing: border-box;
        /* Кнопка растянута на всю карточку, а надпись у неё — обычный span:
           без выравнивания «Смотреть сетку» прижималось к левому краю */
        text-align: center;
        width: 100%;
        max-width: 100%;
        margin-left: 0;
        margin-right: 0;
    }

    /* Афиши одной высоты: у крупной карточки своё правило, и ряд выходил
       ступенькой */
    .tournaments-grid .tc .tc-image,
    .tournaments-grid .tc-featured .tc-image {
        height: 120px;
        flex: 0 0 120px;
    }

    /* ГЛАВНАЯ КАРТОЧКА крупнее остальных и по афише, и по названию:
       она на всю ширину, ей есть чем распорядиться. Ступень card title из
       лестницы 86:10 — 21 главная / 16 обычная / 14 телефон; на телефоне
       главная берёт ступень выше обычной. */
    .tournaments-grid > .tc:nth-child(1) .tc-image {
        height: 150px;
        flex: 0 0 150px;
    }
    .tournaments-grid > .tc:nth-child(1) .tc-title { font-size: var(--fs-base); }

    /* Карточки были вытянутыми: между строчками копился воздух, и четыре
       плитки занимали два экрана. Ужимаем отступы и промежутки — содержимое
       то же, но ряд помещается целиком */
    .tournaments-grid .tc .tc-body {
        padding: 8px 9px 10px;
        gap: 0;
    }

    .tournaments-grid .tc .tc-date { margin: 0 0 4px; }
    .tournaments-grid .tc .tc-countdown { margin: 0 0 5px; }
    .tournaments-grid .tc .tc-title { margin: 0 0 5px; }
    .tournaments-grid .tc .tc-slots { margin: 0 0 6px; font-size: var(--fs-2xs); }
    .tournaments-grid .tc .tc-meta { margin: 0 0 6px; }

    .tournaments-grid .tc .tc-btn,
    .tournaments-grid .tc .btn-register {
        margin-top: auto;
        padding: 7px 8px;
    }

    /* На широком экране первым трём карточкам задана высота 380px, чтобы ряд
       не шёл ступенькой. На телефоне эта высота — чистый воздух: содержимого
       на 170px, остальное пустота. Пусть карточка будет по содержимому */
    .tournaments-grid > .tc:nth-child(-n+3) { height: auto; }

    /* Отсчёт в одну строку: «Старт через» и цифры переносились по слогам */
    .tournaments-grid .tc .tc-cd-label { display: none; }
    .tournaments-grid .tc .tc-countdown {
        display: inline-flex;
        gap: 4px;
        padding: 3px 7px;
        white-space: nowrap;
    }

    /* Карточки в ряду одной высоты: содержимое тянется, кнопка внизу */
    .tournaments-grid > .tc { align-self: stretch; }
    .tc-body { display: flex; flex-direction: column; flex: 1; padding: 10px 10px 12px; }
    .tc .btn-primary, .tc-btn {
        width: 100%;
        padding: 8px;
        font-size: var(--fs-xs);
        margin-top: 8px;
        /* Цель нажатия 44 — меньше палец не берёт уверенно. С padding 8 и
           кеглем 12 выходило около 33. Высоту задаём прямо, а надпись
           центруем: растянутая кнопка иначе прижимает текст влево. */
        min-height: 44px;
        display: inline-flex;
        align-items: center;
        justify-content: center;
    }
}

/* ==========================================================================
   ТУРНИРЫ НА ТЕЛЕФОНЕ БОКОМ — ЛЕНТА. Решение Кости 23.09, доска 341:105.
   Экран 844 × 390. Замер до правки: главная карточка 807 × 380 при высоте
   экрана 390 — одна карточка И ЕСТЬ экран, ниже сгиба не видно ничего.
   Дефицит здесь — высота, поэтому ряды заменяем полосой: вниз она не растёт
   совсем.

   ДВЕ ЦЕЛЫЕ КАРТОЧКИ В КАДРЕ И 88 ОТ ТРЕТЬЕЙ: 340 + 12 + 340 + 12 + 88 = 792.
   Выглядывающий кусок — не украшение. Полоса, обрезанная ровно по краю
   карточки, читается как законченный ряд, и её никто не листает. App Store,
   Netflix и Google Play держат полоску следующей карточки по той же причине.

   Условие сужено по ширине нарочно: без max-width под правило попало бы и
   низкое окно браузера на ноутбуке, где лента не нужна. */
@media (max-width: 992px) and (orientation: landscape) and (max-height: 500px) {
    .tournaments-grid {
        display: flex;
        grid-template-columns: none;
        gap: 12px;
        overflow-x: auto;
        scroll-snap-type: x mandatory;
        -webkit-overflow-scrolling: touch;
        scrollbar-width: none;
        padding-bottom: 4px;
    }
    .tournaments-grid::-webkit-scrollbar { display: none; }

    /* В ленте карточка не участвует в колонках: ширина и высота заданы прямо.
       Цепкость (0,2,1) — той же, что у правил колонок выше, и блок ниже. */
    .tournaments-grid > .tc:nth-child(n) {
        grid-column: auto;
        flex: 0 0 340px;
        width: 340px;
        height: 270px;
        scroll-snap-align: start;
        display: flex;
    }
    /* Лента показывает ВСЕ карточки: прятать нечего, вниз она не растёт */
    .tournaments-grid > .tc:nth-child(n+4) { display: flex; }

    /* Главная теряет разметку «афиша слева»: при 340 в ширину делить нечего.
       Роль ей несёт ПОРЯДОК — она первая в полосе. */
    .tc-featured { grid-template-columns: 1fr; }
    .tournaments-grid .tc .tc-image,
    .tournaments-grid .tc-featured .tc-image {
        height: 150px;
        flex: 0 0 150px;
    }
    /* РАЗМЕР КАРТОЧКИ — ПО ЕЁ СОДЕРЖИМОМУ, А НЕ ПО НОМЕРУ В РЯДУ (31.10).
       Было: `:nth-child(-n+3) { height: auto; min-height: 270px }` — то
       есть первым трём разрешалось расти, остальным оставалось жёсткое
       270 из правила выше. Порядковый номер не говорит о содержимом
       ничего, и ряд выходил разнобоем: 309 · 309 · 309 · 270 · 270 · 270
       (замер 31.10 на 844×390), а у трёх задних карточек текст был
       СРЕЗАН — им нужно 301 при выданных 270, суммарно 93 точки.
       Теперь правило одно на всю ленту: растут все, пол прежний 270.
       ЗАМЕР ПОСЛЕ: шесть по 309, разброс 0, срезано 0, высота самой
       ленты не изменилась — 313, потому что первые три и так были 309. */
    .tournaments-grid > .tc:nth-child(n) { height: auto; min-height: 270px; }

    /* Кегли берём телефонные: карточка 340 в ширину, ей ступени широкого
       экрана велики. Ступень card title из лестницы 86:10 — 16 у главной,
       14 у обычной. Селекторы с родителем, иначе .tc-featured .tc-title
       (0,2,0) перебьёт одиночный класс, как это уже было с метой. */
    .tournaments-grid .tc .tc-date,
    .tournaments-grid .tc-featured .tc-date { font-size: var(--fs-2xs); }
    /* Заголовку мало задать кегль. У главной карточки в базе стоит
       -webkit-box с зажимом в три строки и фиксированной высотой карточки;
       в полосе высотой 270 он схлопывался в НОЛЬ — заголовок исчезал совсем,
       и карточка показывала дату, места и кнопку без названия. Ровно та же
       беда уже случалась на телефоне, там это лечено полным блоком —
       повторяем его, а не половину. */
    .tournaments-grid .tc .tc-title,
    .tournaments-grid .tc-featured .tc-title {
        font-size: var(--fs-sm);
        line-height: 1.3;
        display: -webkit-box;
        -webkit-line-clamp: 2;
        -webkit-box-orient: vertical;
        overflow: hidden;
        margin: 4px 0 6px;
        max-height: none;
    }
    .tournaments-grid > .tc:nth-child(1) .tc-title { font-size: var(--fs-base); }
    .tournaments-grid .tc .tc-meta,
    .tournaments-grid .tc-featured .tc-meta { font-size: var(--fs-2xs); }
    .tournaments-grid .tc .tc-desc,
    .tournaments-grid .tc-featured .tc-desc { display: none; }

    /* ПОЧЕМУ ЗАГОЛОВОК ИСЧЕЗАЛ. У него из базы стоит overflow: hidden ради
       зажима строк, а это снимает у флекс-элемента защиту min-height: auto —
       ему становится можно сжаться в ноль. Тело карточки в полосе не
       помещалось на 10 пикселей, браузер отнял их у единственного, кто умел
       сжиматься, и название пропадало совсем: оставались дата, места и кнопка.
       Лечим с двух сторон — запрещаем сжимать заголовок и убираем те самые
       10 пикселей из отступов, как это уже сделано на телефоне. */
    .tournaments-grid .tc .tc-title { flex-shrink: 0; }
    .tournaments-grid .tc .tc-body { padding: 8px 10px 10px; gap: 0; }
    .tournaments-grid .tc .tc-date { margin: 0 0 4px; }
    .tournaments-grid .tc .tc-meta { margin: 0 0 6px; }

    /* Цель нажатия 44 — та же, что на телефоне вертикально: палец один и
       тот же, экран просто повёрнут. */
    .tournaments-grid .tc .tc-btn,
    .tournaments-grid .tc .btn-register {
        width: 100%;
        min-height: 44px;
        padding: 8px;
        font-size: var(--fs-xs);
        display: inline-flex;
        align-items: center;
        justify-content: center;
        margin-top: auto;
    }

    /* Последней плиткой — «Все турниры». У полосы должен быть конец,
       иначе непонятно, докуда листать. */
    .tournaments-grid > .tc-more { display: flex; }
}

/* Плитка «Все турниры» живёт только в ленте: в сетке её роль исполняет
   ссылка в шапке раздела, и второй такой же на экране не нужно. */
.tc-more {
    display: none;
    flex: 0 0 200px;
    width: 200px;
    height: 270px;
    align-items: center;
    justify-content: center;
    text-align: center;
    gap: 6px;
    flex-direction: column;
    border: var(--bw-hair) dashed var(--border-light);
    border-radius: var(--radius-lg);
    background: var(--surface-glass);
    color: var(--text-primary);
    font-size: var(--fs-sm);
    font-weight: var(--fw-bold);
    text-decoration: none;
    scroll-snap-align: start;
}
.tc-more:hover { border-color: var(--accent); color: var(--accent); }

/* ========================================
   ПОЗДРАВЛЕНИЕ С ДОСТИЖЕНИЕМ
   Разметку создаёт js/badge-celebration.js
   ======================================== */

.ach-overlay {
    position: fixed;
    inset: 0;
    z-index: 9999;
    display: none;
    align-items: center;
    justify-content: center;
    background: var(--alpha-black-60);
    backdrop-filter: blur(6px);
    animation: achFade 0.25s ease both;
}

.ach-overlay.show { display: flex; }

@keyframes achFade { from { opacity: 0 } to { opacity: 1 } }

.ach-card {
    position: relative;
    width: min(92vw, 360px);
    padding: 34px 28px 26px;
    text-align: center;
    background: #121212;
    border: var(--bw-hair) solid var(--alpha-lime-30);
    border-radius: 24px;
    box-shadow: 0 0 60px var(--alpha-lime-20);
    animation: achPop 0.5s cubic-bezier(0.2, 1.4, 0.4, 1) both;
}

@keyframes achPop {
    from { opacity: 0; transform: scale(0.75) translateY(20px) }
    to   { opacity: 1; transform: scale(1) translateY(0) }
}

.ach-kicker {
    font-size: var(--fs-xs);
    font-weight: 700;
    letter-spacing: 0.16em;
    text-transform: uppercase;
    color: var(--accent);
    animation: achUp 0.4s ease 0.15s both;
}

.ach-emoji {
    display: inline-block;
    font-size: var(--fs-4xl);
    line-height: 1;
    margin: 14px 0 10px;
    animation: achBadge 0.8s cubic-bezier(0.2, 1.5, 0.4, 1) 0.1s both;
}

@keyframes achBadge {
    0%   { opacity: 0; transform: scale(0) rotate(-25deg) }
    60%  { opacity: 1; transform: scale(1.25) rotate(8deg) }
    100% { opacity: 1; transform: scale(1) rotate(0) }
}

/* Волна расходится один раз — ничего не крутится по кругу */
.ach-halo {
    position: absolute;
    left: 50%;
    top: 96px;
    width: 120px;
    height: 120px;
    margin: -60px 0 0 -60px;
    border-radius: 50%;
    border: var(--bw-thick) solid var(--accent);
    pointer-events: none;
    animation: achHalo 1s ease-out 0.25s both;
}

@keyframes achHalo {
    from { opacity: 0.8; transform: scale(0.4) }
    to   { opacity: 0;   transform: scale(2.2) }
}

.ach-name {
    font-size: var(--fs-lg);
    font-weight: 800;
    animation: achUp 0.4s ease 0.3s both;
}

.ach-desc {
    margin-top: 6px;
    font-size: var(--fs-base);
    color: var(--text-secondary);
    animation: achUp 0.4s ease 0.38s both;
}

@keyframes achUp {
    from { opacity: 0; transform: translateY(10px) }
    to   { opacity: 1; transform: none }
}

.ach-btn {
    margin-top: 22px;
    padding: 12px 34px;
    border: none;
    border-radius: var(--radius-full);
    background: var(--accent);
    color: var(--bg-primary);
    font-size: var(--fs-base);
    font-weight: 700;
    cursor: pointer;
    animation: achUp 0.4s ease 0.46s both;
}

.ach-count {
    margin-top: 12px;
    font-size: var(--fs-xs);
    color: var(--text-muted);
    animation: achUp 0.4s ease 0.5s both;
}

/* Теннисные мячи вместо конфетти — свои, узнаваемые */
.ach-fx {
    position: absolute;
    inset: -40px;
    overflow: hidden;
    pointer-events: none;
}

.ach-fx.balls i {
    position: absolute;
    top: -16px;
    width: 14px;
    height: 14px;
    border-radius: 50%;
    background: radial-gradient(circle at 34% 30%, #e8ff7a, #b9e400 60%, #8fb300);
    box-shadow: inset -2px -3px 4px var(--alpha-black-30);
    animation: achFall linear forwards;
}

@keyframes achFall {
    from { transform: translateY(0) rotate(0); opacity: 1 }
    to   { transform: translateY(420px) rotate(540deg); opacity: 0 }
}

/* ========================================
   ТОП РЕЙТИНГА НА ГЛАВНОЙ
   Строки рисует js/home-rankings.js,
   данные — js/rankings-data.js (общие со страницей рейтинга)
   ======================================== */

.rk-row {
    display: grid;
    /* NTRP теперь двойной — «3.5 / 3.5». В прежние 42px он не влезал, падал
       на вторую строку и делал строки разной высоты, а таблицы мужчин и
       женщин — разного роста */
    grid-template-columns: 26px 1fr 30px 66px 46px 58px 36px;
    align-items: center;
    gap: 8px;
    padding: var(--space-2) var(--space-3);   /* 8 12; было 8 10 */
    min-height: 44px;
    border-radius: var(--radius-md);          /* 12; было 10 */
    font-size: var(--fs-sm);
}

/* 8, а не 4: ряд с фоном и скруглением — карточка, а четвёрки в лестнице
   отступов нет. Раздел вырос, и это плата. 24.09 */
.rk-row + .rk-row { margin-top: var(--space-2); }
.rk-row:hover { background: var(--alpha-white-04); }

.rk-head {
    font-size: var(--fs-2xs);
    text-transform: uppercase;
    letter-spacing: 0.06em;
    /* БЫЛ --text-dim, 35 % белого: контраст 3.21 при пороге 4.5. Замерено
       24.09. Третий раз одна и та же беда — новости, турниры, теперь голова
       таблицы рейтинга. --text-secondary даёт 9.98. */
    color: var(--text-secondary);
    padding-bottom: var(--space-2);       /* 8; было 2 — не ступень */
}

.rk-head:hover { background: none; }

.rk-rank {
    font-weight: 700;
    /* Тот же контраст 3.21 и в каждой строке, а не только в голове. */
    color: var(--text-secondary);
    text-align: center;
}

/* .rk-rank.top УБРАН 24.09 вместе с приходом пьедестала. Правило 109:370:
   акцент один на поле зрения, а здесь это «Полный рейтинг →». Тройку
   выделяет сам пьедестал — второе выделение лишнее. Класс `top` больше не
   ставит и js. */

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

/* Фото ИЛИ инициалы — размер объявлен один раз на оба. */
/* Avatar 29:82, размер xs 24 — «xs 24 for table rows». Было 26, мимо. */
.rk-player img,
.rk-player .avatar-initials {
    width: 24px;
    height: 24px;
    border-radius: 50%;
    object-fit: cover;
    flex-shrink: 0;
    font-size: var(--fs-2xs);
}

.rk-player a,
.rk-player span {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    color: inherit;
    text-decoration: none;
}

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

.rk-country { text-align: center; }
.rk-ntrp { text-align: center; color: var(--text-secondary); white-space: nowrap; }

/* Подпись под названием столбца NTRP: какое число одиночное, какое парное */
.pl-col-sub {
    display: block;
    font-size: var(--fs-2xs);
    font-weight: 400;
    letter-spacing: 0;
    text-transform: none;
    opacity: 0.55;
}
.rk-wl { text-align: center; color: var(--text-secondary); font-variant-numeric: tabular-nums; }
.rk-points { text-align: right; font-weight: 700; font-variant-numeric: tabular-nums; }
/* 14, как и остальные шесть ячеек ряда. Была 12 — единственная мельче
   соседей без причины. 24.09 */
.rk-change { text-align: center; font-size: var(--fs-sm); }

/* ПЬЕДЕСТАЛ ЖИВЁТ В css/podium.css, А НЕ ЗДЕСЬ.
   24.09 я написал для главной свой `.rk-podium` — а пьедестал уже был в
   коде, на странице рейтинга, по компоненту Ranking podium 51:80. Свой
   выброшен целиком; стили вынесены из css/players.css в общий файл, и обе
   стороны берут один и тот же. Размер на главной считается от колонки. */

.rk-up { color: var(--green-500); }
.rk-down { color: var(--red-500); }
.rk-same { color: var(--text-muted); }

/* ТУМАН ГОСТЯ — ПРАВИЛО ВИДА, А НЕ РАЗМЕТКИ, И ОДНО НА ВСЕ ВИДЫ.
   До 24.09 классы .rk-blur-1/2 навешивал js по индексу строки, потом их
   сменили три копии правил: база, слой 640 и слой телефона боком — и каждая
   считала строки от начала («с седьмой», «с пятой»). С приходом пьедестала
   тройка ушла наверх, таблица начинается с четвёртого места, и счёт от
   начала развалился бы весь сразу.
   Считаем ОТ КОНЦА: туманятся две последние строки, сколько бы их ни было.
   Отношение, а не число, — и копия ровно одна.
   Голова таблицы тоже .rk-row, поэтому :not(.rk-head): в короткой категории
   иначе размылась бы она. */
.is-guest .rankings-panel .rk-row:not(.rk-head):nth-last-child(-n+2) {
    pointer-events: none;
    user-select: none;
}
.is-guest .rankings-panel .rk-row:not(.rk-head):nth-last-child(2) {
    filter: blur(2px);
    opacity: 0.55;
}
.is-guest .rankings-panel .rk-row:not(.rk-head):nth-last-child(1) {
    filter: blur(3.5px);
    opacity: 0.35;
}

/* Empty state 37:53. Поля 48/20 компонента, кегль 14, цвет читаемый.
   Текст обязан говорить, ЧТО здесь появится, а не «данных нет» — копия
   правится в js/home-rankings.js, здесь только вид. */
.rk-empty {
    padding: var(--space-10) var(--space-5);   /* 40 / 20; было 30/12 */
    text-align: center;
    color: var(--text-secondary);
    font-size: var(--fs-sm);
}

/* Карточка входа тут перекрывает сразу две колонки, поэтому чуть компактнее
   обычной */
/* Затемнение не поднимается выше половины раздела: иначе, когда строк мало
   или база молчит, оно накрывает заголовки колонок и вкладки категорий */
#rankings .guest-section-cta {
    max-height: 45%;
    align-items: flex-end;
}

/* Набор размеров плашки отсюда убран 24.09: он был копией базового
   правила .guest-cta-card и такого же набора в #players. */

/* ========================================
   НАЙДИ ПАРТНЁРА ПО ИГРЕ — блок на главной
   Карточку рисует js/home-partners.js, стили самой карточки живут
   в css/partners.css: она общая со страницей поиска
   ======================================== */

/* ПОДЗАГОЛОВОК РАЗДЕЛА, А НЕ МЕЛКАЯ РЕМАРКА.
   Костя 24.09: «мне кажется, она не читаема». Замер подтвердил числом:
   было 14 / Regular цветом --text-dim, то есть 35 % белого, и это
   КОНТРАСТ 3.14 : 1 при пороге WCAG 1.4.3 = 4.5. Четвёртое место с этим
   же дефектом: правило «--text-dim на мелком тексте не проходит» выведено
   ещё на новостях.

   Ступень 18 — «лид» по токенам, на две ниже заголовка раздела 26.
   ПАРА ЕДЕТ ОДНОЙ ГРАНИЦЕЙ: строка про 16 лежит в том же медиаслое, что и
   кегль h2 (768), иначе в полосе между границами они схлопнутся.

   Отрицательный отступ -14 убран: он гасил нижнее поле шапки раздела.
   Теперь шапка сама отдаёт меньше, когда за ней идёт подзаголовок, и оба
   числа стоят на шкале. */
#players .section-header:has(+ .pg-sub) {
    margin-bottom: var(--space-2);        /* 8 */
}

.pg-sub {
    margin: 0 0 var(--space-6);           /* 24; было -14 / 20 */
    color: var(--text-secondary);         /* было --text-dim, 3.14 : 1 */
    font-size: var(--fs-md);              /* 18; было 14 */
}

/* На главной их десять, а не сорок, — поэтому карточка крупнее и вытянутее */
/* ПЯТЬ КАРТОЧЕК НА ДЕСКТОПЕ — решение Кости 24.09. Корты и тренеры идут по
   четыре, сетку целиком не сводим; сводим РИТМ: зазор один на все витрины,
   24 на широком и 12 на телефоне. Было три разных — 20 у партнёров, 40 у
   кортов и тренеров, 10 у всех на узком. */
#players .pt-grid {
    display: grid;
    grid-template-columns: repeat(5, 1fr);
    gap: var(--space-6);                  /* 24; было 20 */
}

/* ПОЛЯ ИЗ КОМПОНЕНТА Card 29:65: «Compact 16 для плотных списков, Regular 24
   для отдельных». Витрина из пяти — плотный список. Было 24 / 16 / 20:
   три разных числа на одну карточку, два из них мимо шкалы. */
#players .pt-card {
    padding: var(--space-4);              /* 16; было 24 16 20 */
}

/* АВАТАР 92 → 80. У компонента Avatar 29:82 четыре ступени: xs 24 · sm 32 ·
   md 40 · lg 56, и 92 нет ни одной. Правило Stat tile 39:57: «Do not invent
   intermediate sizes per section» — если нужен новый размер, он заводится в
   КОМПОНЕНТЕ. 80 заводится пятой ступенью xl.
   КЕГЛЬ ИНИЦИАЛОВ ИДЁТ ЗА ДИАМЕТРОМ — правило пьедестала: доля 0.36,
   округлённая до ступени. 80 → 26. */
#players .pt-avatar,
#players .pt-avatar-placeholder {
    width: 80px;
    height: 80px;
    font-size: var(--fs-xl);              /* 26; доля 0.33 */
}

#players .pt-avatar-wrap { margin-bottom: var(--space-3); }   /* 12; было 14 */

/* ИМЯ И КАТЕГОРИЯ РАЗВЕДЕНЫ НА СТУПЕНЬ. Были обе 16 и отличались только
   весом; на 768 они сходились в 12 и 11 и почти слипались. */
#players .pt-name { font-size: var(--fs-base); }              /* 16 */
#players .pt-category { font-size: var(--fs-sm); }            /* 14; было 16 */
/* Кнопка прижата к низу, чтобы ряд не шёл лесенкой: у кого строк меньше,
   у того она уезжала вверх. Селектор с идентификатором — он же перебивал
   правило из partners.css, где это уже было исправлено */
#players .pt-invite-btn { margin-top: auto; }

/* Форма — последние пять матчей. Цвета те же, что в рейтинге.
   Зазор 4 — это сетка ИКОНОК, а не шкала отступов: точки по 8 пикселей, и
   восьмёрка между ними развалила бы полоску на пять отдельных пятен. Тот же
   довод, что у медали пьедестала: у иконок своя размерная сетка. */
.pg-form {
    display: flex;
    gap: var(--space-1);                  /* 4; было 5 */
    justify-content: center;
    margin-top: var(--space-2);           /* 8; было 6 */
    min-height: 8px;
}

.pg-form i {
    width: 8px;
    height: 8px;
    border-radius: 50%;
    display: inline-block;
}

.pg-form .w { background: var(--success); }
.pg-form .l { background: var(--danger); }

/* Приглушённая строка. --text-dim здесь давал те же 3.14 : 1, что и подпись
   раздела: один и тот же провал дважды в одной секции. */
.pg-record {
    margin-top: var(--space-2);           /* 8; было 6 */
    font-size: var(--fs-xs);
    color: var(--text-secondary);         /* было --text-dim */
}

/* Парные игры второй строкой. Цвета те же, что у меток в истории матчей, —
   третий язык для одного и того же заводить незачем */
.pg-pairs {
    display: flex;
    justify-content: center;
    gap: var(--space-2);                  /* 8; было 6 */
    margin-top: var(--space-2);           /* 8; было 5 */
}

/* Метки парных — это Badge 27:53, стиль Subtle: 11 Medium прописными,
   поля 8, радиус full. Было 11 Bold с полями 1 / 7. */
.pg-pairs span {
    font-size: var(--fs-2xs);             /* 11 */
    font-weight: 500;
    text-transform: uppercase;
    letter-spacing: 0.5px;
    padding: 0 var(--space-2);            /* 8; было 1 / 7 */
    line-height: 24px;
    border-radius: var(--radius-full);
    white-space: nowrap;
}
.pg-pairs .pp-dbl { background: var(--info-subtle); color: var(--info); }
.pg-pairs .pp-mix { background: rgba(168,85,247,0.18); color: var(--role-manager); }

/* Empty state 37:53. Поля те же, что у .rk-empty: одно пустое состояние на
   весь сайт, а не восьмое написание той же идеи. */
.pg-empty {
    grid-column: 1 / -1;
    padding: var(--space-10) var(--space-5);   /* 40 / 20 */
    text-align: center;
    color: var(--text-secondary);              /* было --text-dim */
    border: var(--bw-hair) dashed var(--border-subtle);
    border-radius: var(--radius-lg);
}

/* Карточка входа — того же размера, что в рейтинге */
#players { position: relative; }
#players .guest-section-cta { max-height: 55%; align-items: flex-end; }

/* ТОЛЬКО ЦЕЛЫЕ РЯДЫ. Решение Кости 24.09 по трём снимкам: ряд с дырой на
   месте недостающей карточки читается как поломка, а не как «игроков пока
   мало». Поэтому у каждого вида свой потолок, кратный числу колонок, и
   хвост, не добравший ряд, прячется.
     широкий  — 5 в ряд, потолок 5   (один ряд)
     планшет  — 3 в ряд, потолок 6   (два ряда)
     боком    — 4 в ряд, потолок 4   (один ряд)
     телефон  — 2 в ряд, потолок 4   (два ряда)
   ПОСЧИТАНО на сегодняшней базе: игроков пять, поэтому планшет показывает
   три, а не шесть — шестая карточка появится с шестым игроком. Остальные
   три вида на пяти игроках заполняются целиком. */
#players .pt-grid > .pt-card:nth-child(n+6) { display: none; }

/* ГРАНИЦЫ 1200 И 900 УБРАНЫ 24.09. Лестница разрешает две ширины — 640 и
   992; всё остальное делится высотой и поворотом. Полоса 993–1200 теперь
   считается десктопом: пять карточек, ПОСЧИТАНО ≈177 при окне 1024. */
@media (max-width: 992px) {
    #players .pt-grid { grid-template-columns: repeat(3, 1fr); }
}

/* ПЛАНШЕТ ВЕРТИКАЛЬНО — единственный вид с тремя колонками, и потому
   единственный, где считается хвост из трёх.
   ГРАНИЦЫ НЕ НОВЫЕ: 641 и 501 — это те же 640 и 500 с другой стороны.
   Условие выписано целиком нарочно: правила хвоста весят на два
   псевдокласса больше обычного селектора, и «вернуть в поток» их в
   соседнем слое уже не получится — они перебьют возврат. Значит слой
   обязан не пересекаться с соседями, а не чиниться после них. */
@media (min-width: 641px) and (max-width: 992px) and (min-height: 501px) {
    #players .pt-grid > .pt-card:nth-child(n+6) { display: flex; }
    #players .pt-grid > .pt-card:nth-child(n+7) { display: none; }

    /* ХВОСТ, НЕ ДОБРАВШИЙ РЯД ИЗ ТРЁХ. Считается ОТ КОНЦА, потому что
       начало может уехать: правило не знает, сколько игроков в базе.
       Остался один лишний — прячем один, осталось два — прячем оба. */
    #players .pt-grid > .pt-card:nth-child(3n+1):nth-last-child(1),
    #players .pt-grid > .pt-card:nth-child(3n+1):nth-last-child(2),
    #players .pt-grid > .pt-card:nth-child(3n+2):nth-last-child(1) { display: none; }
}

/* ПАРА ЕДЕТ ОДНОЙ ГРАНИЦЕЙ. Заголовок раздела падает 26 → 21 на 768
   (общее правило .section-header h2 ниже по файлу), а подзаголовок стоял
   18 на всех видах: на широком пара шла через две ступени, на планшете —
   через одну, и в полосе между ними схлопывалась. Отношение заморожено,
   а не число: 26/18 и 21/16 — обе пары через две ступени.
   Граница 768 здесь ЗАИМСТВОВАНА у заголовка и уедет на 640 вместе с ним,
   когда лестница заголовков сайта встанет на шкалу. */
@media (max-width: 768px) {
    .pg-sub { font-size: var(--fs-base); }   /* 16 */
}

/* ТЕЛЕФОН БОКОМ — четыре в ряд и ровно один ряд: по высоте видно всё
   равно только его. Хвост здесь не нужен: потолок равен числу колонок. */
@media (orientation: landscape) and (max-height: 500px) {
    #players .pt-grid { grid-template-columns: repeat(4, 1fr); }
    #players .pt-grid > .pt-card:nth-child(n+5) { display: none; }
}

/* ТЕЛЕФОН. 24.09 сюда перенесены правила «Давай сыграем», стоявшие в
   блоке 768: граница 768 не входит в лестницу, и из-за неё планшет получал
   телефонные размеры — две карточки вместо трёх, кружок 52 вместо 80. */
@media (max-width: 640px) {
    /* «Давай сыграем»: на телефоне карточки шли по одной в столбик, десять
       штук растягивались на три экрана. Теперь две в ряд — как в поиске
       игрока, на кортах и у тренеров.

       Три в ряд помещались только за счёт сжатия: на карточку оставалось
       107 пикселей, «Пригласить» обрывалось на «ПРИГЛАСИТ», а метка NTRP
       налезала на фото. Селектор с идентификатором, иначе его перебьёт
       правило блока на широком экране */
    #players .pt-grid {
        grid-template-columns: repeat(2, minmax(0, 1fr));
        gap: var(--space-3);              /* 12; было 10 */
    }

    #players .pt-card { min-width: 0; }

    #players .pt-name,
    #players .pt-category {
        overflow: hidden;
        text-overflow: ellipsis;
        white-space: nowrap;
        max-width: 100%;
    }

    /* Два ряда по две. Потолок четыре, и одинокая пятая в третьем ряду
       прячется: нечётная последняя начинает ряд, который некем закрыть. */
    #players .pt-grid > .pt-card:nth-child(n+5) { display: none; }
    #players .pt-grid > .pt-card:nth-child(odd):nth-last-child(1) { display: none; }

    /* Dense 12 — третья плотность компонента Card 29:65, заведённая 24.09
       под плитку значка. Было 10 / 6 / 12: три числа, два мимо шкалы. */
    #players .pt-card {
        padding: var(--space-3);          /* 12 */
    }

    /* Кружок 52, кегль за диаметром: 52 × 0.36 = 18.7 → ступень 18.
       Было 16 — доля 0.31. */
    #players .pt-avatar,
    #players .pt-avatar-placeholder {
        width: 52px;
        height: 52px;
        font-size: var(--fs-md);          /* 18; было 16 */
    }

    #players .pt-avatar-wrap { margin-bottom: var(--space-2); }   /* 8 */

    /* ПЛАНШЕТ БОЛЬШЕ НЕ МЕЛЬЧЕ ТЕЛЕФОНА. Было: имя 16 → 12 → 14,
       категория 16 → 11 → 11, то есть на 768 текст оказывался самым мелким
       на сайте. Теперь оба уровня едут вниз ровно на одну ступень и
       сохраняют между собой ступень разницы. */
    #players .pt-name { font-size: var(--fs-sm); }                /* 14; было 12 */
    #players .pt-category { font-size: var(--fs-xs); }            /* 12; было 11 */

    /* Счёт побед и поражений словами на три колонки не помещается —
       остаётся полоска формы */
    #players .pg-record { display: none; }


    /* Размер кнопки задаёт общая вторая ступень ниже в этом же файле.
       Здесь остаётся то, что относится к тесной карточке на главной:
       во всю ширину и без разрядки. Раньше тут стояли поля 7×4 и кегль
       0.62rem — в три раза мельче, чем у соседней «Найти игрока» */
    #players .pt-invite-btn {
        letter-spacing: 0;
        width: 100%;
        min-width: 0;
    }
}

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

.site-footer {
    background: var(--bg-primary);
    border-top: var(--bw-hair) solid var(--border-subtle);
    padding-top: var(--space-lg);
}

/* КОЛОНКИ СКЛАДЫВАЮТСЯ САМИ, БЕЗ ГРАНИЦЫ 1200. Пять блоков в ряд требуют
   места, и раньше это решала своя точка останова: ниже 1200 колонок
   становилось четыре, а контакты уезжали в отдельную строку. Точка была
   чужая — у нас разрешены 640 и 992, а между 992 и бесконечностью границы
   нет вовсе. Теперь ряд набирается из равных дорожек шириной от 170:
   браузер сам кладёт столько, сколько влезло, и переносит лишнее. Бренду
   отданы две дорожки — он шире остальных по смыслу, а не по числу.

   ПОЛ ДОРОЖКИ 170 — ЗАМЕРЕН, А НЕ ВЫБРАН. Это не ступень шкалы и не может
   ею быть: шкала задаёт отступы и кегли, а здесь порог, на котором ряд
   решает переноситься. Мерено на 1024 и 1512. При 140 всё встаёт в один
   ряд, но колонка контактов сужается до 143, и почтовый адрес ломается
   посреди слова — адрес, разорванный на «...@gm / ail.com», читается как
   поломка. При 170 подвал на 1024 выглядит ровно как раньше: четыре блока
   в ряду и контакты строкой ниже, высота 350 против прежних 374. На 1512
   — один ряд 451/213/213/213/213, высота 248, как и было. Перелива нет
   нигде. ЧТО ОСТАЛОСЬ ОТКРЫТЫМ: один ряд на 1024 возможен, если дать
   адресу точку переноса по «@», но это правка разметки в 83 файлах —
   записано в находки, в этот заход не бралось. */
.footer-content {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(170px, 1fr));
    gap: var(--space-md);
    padding: 0 var(--pad-x);
    padding-bottom: var(--space-lg);
}

/* Footer Brand Block */
.footer-brand {
    grid-column: span 2;
    padding-right: var(--space-lg);
}

.footer-logo {
    display: flex;
    align-items: center;
    margin-bottom: var(--space-sm);
}

.footer-desc {
    color: var(--text-secondary);
    font-size: var(--fs-xs);
    line-height: 1.5;
    margin-bottom: var(--space-md);
}

/* Social Links */
.footer-socials {
    display: flex;
    gap: 12px;
}

.social-link {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 32px;
    height: 32px;
    background: var(--bg-elevated);
    border: var(--bw-hair) solid var(--border-subtle);
    border-radius: var(--radius-sm);
    color: var(--text-secondary);
    transition: all var(--transition-fast);
    position: relative;
}

/* Область нажатия 44x44 при коробке 32x32 — минимум касания.
   Псевдоэлементом, а не padding: padding увеличил бы саму коробку и
   сдвинул соседей. Отсчёт идёт от внутренней коробки 30x30 (рамка 1px),
   поэтому -7, а не -6: 30 + 7 + 7 = 44. Промежуток 12 не даёт
   областям соседних иконок пересечься. */
.social-link::after {
    content: "";
    position: absolute;
    inset: -7px;
}

.social-link svg {
    width: 16px;
    height: 16px;
}

.social-link:hover {
    background: var(--accent);
    border-color: var(--accent);
    color: var(--neutral-black);
    transform: translateY(-2px);
}

/* Footer Block */
.footer-block h4 {
    font-size: var(--fs-xs);
    font-weight: 700;
    color: var(--text-primary);
    text-transform: uppercase;
    letter-spacing: 0.05em;
    margin-bottom: var(--space-sm);
}

.footer-links {
    list-style: none;
    display: flex;
    flex-direction: column;
    gap: 6px;
}

.footer-links a {
    color: var(--text-secondary);
    text-decoration: none;
    font-size: var(--fs-xs);
    transition: color var(--transition-fast);
}

.footer-links a:hover {
    color: var(--accent);
}

/* Footer Contacts */
.footer-contacts {
    list-style: none;
    display: flex;
    flex-direction: column;
    gap: 8px;
}

.footer-contacts li {
    display: flex;
    align-items: center;
    gap: 8px;
    color: var(--text-secondary);
    font-size: var(--fs-xs);
}

.footer-contacts svg {
    flex-shrink: 0;
    opacity: 0.6;
    width: 14px;
    height: 14px;
}

.footer-contacts a {
    color: var(--text-secondary);
    text-decoration: none;
    transition: color var(--transition-fast);
}

.footer-contacts a:hover {
    color: var(--accent);
}

/* Footer Bottom */
.footer-bottom {
    display: flex;
    justify-content: space-between;
    align-items: center;
    flex-wrap: wrap;
    gap: var(--space-sm);
    padding: var(--space-md) var(--gutter);
    border-top: var(--bw-hair) solid var(--border-subtle);
    padding-inline: var(--pad-x);
}

.footer-legal {
    display: flex;
    gap: var(--space-md);
}

.footer-legal a {
    color: var(--text-muted);
    text-decoration: none;
    font-size: var(--fs-xs);
    transition: color var(--transition-fast);
}

.footer-legal a:hover {
    color: var(--accent);
}

.footer-made {
    color: var(--text-muted);
    font-size: var(--fs-xs);
}

/* Тот же перенос, h274. Здесь лежит центрирование блока бренда и
   соцсетей. Оно относится к мобильной раскладке подвала, а стояло на
   одной ширине — поэтому на iPad горизонтально (1024) текст вставал по
   центру, а соцсети оставались у левого края. Замерено: знак и иконки
   на x 22.5 при выравнивании center у всего остального.
   Условие ТО ЖЕ, что у .footer-acc и у складывать() в js/script.js. */
@media (any-pointer: coarse), (max-width: 992px) {
    .footer-content {
        grid-template-columns: 1fr 1fr;
        gap: var(--space-lg);
    }

    .footer-brand {
        grid-column: 1 / -1;
        padding-right: 0;
        text-align: center;
    }

    .footer-logo {
        justify-content: center;
    }

    .footer-socials {
        justify-content: center;
    }

    .footer-block:last-child {
        grid-column: 1 / -1;
    }
}

/* Тот же перенос, h274: шесть правил выравнивания подвала.
   Условие ТО ЖЕ, что у блока .footer-acc и у складывать() в
   js/script.js. Меняешь здесь — меняй во всех трёх местах. */
@media (any-pointer: coarse), (max-width: 768px) {
    .footer-content {
        grid-template-columns: 1fr;
        text-align: center;
    }

    .footer-block h4 {
        margin-bottom: var(--space-sm);
    }

    .footer-links,
    .footer-contacts {
        align-items: center;
    }

    .footer-contacts li {
        justify-content: center;
    }

    .footer-bottom {
        flex-direction: column;
        text-align: center;
    }

    .footer-legal {
        flex-direction: column;
        gap: var(--space-sm);
    }
}

/* ========================================
   BURGER MENU & MOBILE NAVIGATION
   ======================================== */

.burger-menu {
    display: none;
    flex-direction: column;
    justify-content: center;
    gap: 6px;
    width: 44px;
    height: 44px;
    background: transparent;
    border: none;
    cursor: pointer;
    padding: 10px;
    z-index: 1001;
    -webkit-tap-highlight-color: transparent;
}

.burger-line {
    display: block;
    width: 100%;
    height: 1.5px;
    background: var(--text-primary);
    border-radius: 2px;
    transition: all var(--transition-base);
}

.burger-menu.active .burger-line:nth-child(1) {
    transform: rotate(45deg) translate(6px, 6px);
}

.burger-menu.active .burger-line:nth-child(2) {
    opacity: 0;
}

.burger-menu.active .burger-line:nth-child(3) {
    transform: rotate(-45deg) translate(6px, -6px);
}

/* =========================================================================
   БОКОВОЙ ЛИСТ НАВИГАЦИИ — вариант B, решение Кости 20.09 (доска 243:145)
   =========================================================================
   БЫЛО: штора во всю ширину, выезжающая вниз из-под шапки.
   ЗАМЕР 20.09 (прибор 24-burger-inside): на 844x390 панель 827 высотой —
   501 пиксель за сгибом, человек их просто не видит. При этом 844 пикселя
   ширины простаивали. Спор шёл о том, как утрамбовать высоту, а незанятой
   была ширина. Вопрос был поставлен неверно.

   СТАЛО: лист прижат к правому краю ПОД шапкой. Шапка остаётся живой —
   логотип ведёт на главную, «Войти» нажимается одним действием, бургер на
   своём месте становится крестиком. Затемнение гасит только страницу.

   ОТВЕРГНУТ ВАРИАНТ A (лист поверх шапки). Цена по макету на 844x390:
   своя верхняя строка 56 плюс своя копия «Войти» внизу 56 — 112 из 390,
   29% экрана под служебное, видно 5 строк меню вместо 7, и призыв к
   действию раздваивается на две кнопки.

   ПОЧЕМУ height, А НЕ bottom: 0. Лист лежит внутри .floating-header, а у
   шапки есть backdrop-filter. Элемент с фильтром создаёт систему координат
   для position: fixed внутри себя — значит bottom: 0 отсчитывался бы от
   НИЗА ШАПКИ, и лист получил бы нулевую высоту. Высота задаётся явно.

   Стережёт tools/check-header.js, девять правил, каждое доказано откатом.
   ========================================================================= */
.mobile-nav {
    --sheet-w: 100%;
    display: none;
    position: fixed;
    top: var(--header-h);
    right: 0;
    width: var(--sheet-w);
    max-width: 100%;
    height: calc(100dvh - var(--header-h));
    background: var(--bg-primary);
    border-left: var(--bw-hair) solid var(--border-subtle);
    padding: var(--space-md) var(--space-lg);
    padding-right: calc(var(--space-lg) + env(safe-area-inset-right));
    padding-bottom: calc(var(--space-md) + env(safe-area-inset-bottom));
    overflow-y: auto;
    overscroll-behavior: contain;
    opacity: 0;
    visibility: hidden;
    transform: translateX(12px);
    transition: opacity var(--transition-base), transform var(--transition-base), visibility var(--transition-base);
    z-index: 1002;
}

/* Ступени ширины растут вместе с экраном. Базовая 100%: на 375 лист занимает
   всю ширину — при листе 360 странице осталось бы 15 пикселей, это не
   «страница видна», это полоска. На доске 243:145 у меня 844 сначала получал
   лист уже, чем 768: экран шире, лист уже. Ошибка макета, здесь исправлена. */
@media (min-width: 480px) {
    .mobile-nav { --sheet-w: 360px; }
}

@media (min-width: 768px) {
    .mobile-nav { --sheet-w: 400px; }
}

.mobile-nav.active {
    opacity: 1;
    visibility: visible;
    transform: translateX(0);
}

/* Затемнение. top берётся из --header-h, а не равен нулю: гасится страница,
   шапка остаётся живой. top: 0 — это отвергнутый вариант A, где «Войти»
   выглядит выключенным, хотя нажимается. z-index ниже шапки (1000) и выше
   страницы — поэтому шапка не уходит под затемнение. */
.mobile-nav-scrim {
    position: fixed;
    top: var(--header-h);
    left: 0;
    right: 0;
    bottom: 0;
    background: var(--alpha-black-60);
    opacity: 0;
    pointer-events: none;
    transition: opacity var(--transition-base);
    z-index: 999;
}

.mobile-nav-scrim.active {
    opacity: 1;
    pointer-events: auto;
}

body.nav-open {
    overflow: hidden;
}

/* =========================================================================
   ГРАНИЦА МЕЖДУ ШАПКОЙ И БУРГЕРОМ — 992
   =========================================================================
   Решение Кости 20.09 по макету 215:145. Было 768.

   ЗАМЕР, НА КОТОРОМ ЭТО СТОИТ (прибор 23-header-row-fit, три языка, 844):
       русская     просит 559.3, получает 595.1 — остаток 35.8
       английская           564.4 / 615.1       — остаток 50.7
       кыргызская           689.5 / 616.3       — НЕ ХВАТАЕТ 73.2
   И это уже с поджатым рядом: ниже 992 отступы были 12 вместо 16, зазор 0
   вместо 8. Запас не «почти кончился» — он кончился. Кыргызское «Маалымат»
   наезжало на «Войти», то есть две кликабельные цели лежали друг на друге.

   ПОЧЕМУ ГРАНИЦА, А НЕ СОКРАЩЕНИЕ СЛОВ. «Түз эфир» → Live дало 40.3 и
   закрыло 1024, где не хватало 29.6. На 844 не хватало 113.6 — трёх таких
   слов. Кыргызские названия длиннее по природе языка: «Жаңылыктар» 102,
   «Мелдештер» 91 против русских аналогов на четверть короче. Граница не
   зависит от того, какие слова появятся в меню завтра, — сокращения зависят.

   ЧТО ЗДЕСЬ ЛЕГКО СЛОМАТЬ: развести показ бургера и показ его панели по
   разным границам. Тогда найдётся ширина, где кнопка есть, а открывать ей
   нечего. Оба правила обязаны жить в этом блоке. Стережёт tools/check-header.js.
   ПОРЯДОК ВАЖНЕЕ САМОЙ ГРАНИЦЫ, И ЗДЕСЬ Я УЖЕ ОШИБСЯ 20.09.
   Сначала я поставил этот блок ВЫШЕ базовых правил .burger-menu { display:
   none } и .mobile-nav { display: none }. Вес селекторов одинаковый, значит
   побеждает тот, что ниже, — и бургер пропал на ВСЕХ ширинах, включая
   телефон. Заморозка при этом показывала 13 из 13: она проверяла, что блок
   есть и что граница 992, но не то, где он стоит. Проверку дополнил.
   Блок обязан стоять НИЖЕ базовых правил.
   ========================================================================= */
@media (max-width: 992px) {
    .nav-links {
        display: none;
    }

    .burger-menu {
        display: flex;
    }

    .burger-line {
        background: var(--text-primary);
    }

    .mobile-nav {
        display: block;
    }
}

/* =========================================================================
   ЯЗЫК ПЕРЕЕЗЖАЕТ ТАМ ЖЕ, ГДЕ ПОЯВЛЯЕТСЯ БУРГЕР — 992
   =========================================================================
   Решение Кости 21.09, вариант A. Было 768, и это давало полосу 769–992,
   где бургер уже есть, а язык ещё в шапке. Телефон боком (844) и планшет
   стоймя (768) — два сенсорных устройства одного класса — получали
   переключатель в РАЗНЫХ местах.

   Замер 21.09 (прибор 25-nav-sheet): на 844 в листе три цели с нулевым
   размером — «Русский», «Кыргызча», «English». Они лежали в разметке, но
   не показывались, потому что .mobile-lang-list включался только до 768.
   Сорок одна цель в DOM, тридцать восемь видимых.

   Побочная выгода: ряд шапки на 844 становится короче на весь блок языка.
   Кыргызской версии там было теснее всех (замер 23-header-row-fit: 689.5
   при 616.3 доступных).

   Граница ОДНА на всё: пункты меню, бургер, язык. Стережёт check-header.js.
   ========================================================================= */
@media (max-width: 992px) {
    .lang-dropdown {
        display: none;
    }

    .mobile-lang-list {
        display: flex;
        flex-direction: row;
        align-items: center;
        gap: 6px;
        padding: 4px 14px 0;
    }

    .mobile-lang-title {
        padding: 0 4px 0 0;
    }

    /* ЧИП ЯЗЫКА: font-size: 0 — это НЕ размер текста, а способ спрятать
       полную подпись («Русский», «Кыргызча», «English») так, чтобы
       осталось только сокращение из ::after. Три слова в столбик занимали
       192 пикселя — больше всего раздела «Инфо». Настоящий видимый кегль
       живёт в ::after, и он на шкале: 12.
       Прибор 24-burger-inside 20.09 вернул по чипу кегль 0 — и был прав.
       Это я прочёл его как поломку прибора. Мерить надо ::after. */
    .mobile-lang-option {
        gap: 5px;
        padding: 7px 10px;
        font-size: 0;
        border: var(--bw-hair) solid var(--border-subtle);
    }

    .mobile-lang-option .lang-flag {
        font-size: var(--fs-sm);
    }

    .mobile-lang-option::after {
        content: attr(data-lang);
        font-size: var(--fs-xs);
        font-weight: 600;
        letter-spacing: 0.02em;
    }

    .mobile-lang-option.active {
        border-color: var(--accent);
    }
}


.mobile-nav-links {
    list-style: none;
    display: flex;
    flex-direction: column;
    gap: 4px;
}

.mobile-nav-links a {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 12px 16px;
    color: var(--text-secondary);
    text-decoration: none;
    border-radius: var(--radius-md);
    font-size: var(--fs-base);
    font-weight: 500;
    transition: all var(--transition-fast);
}

/* Подсветка пункта меню — одна на все строки: и обычные ссылки, и
   раскрывающиеся разделы, и вложенные категории. Раньше раскрывающиеся
   светились лаймовым, а обычные серым — в одном списке два разных вида */
.mobile-nav-links a:hover,
.mobile-nav-links a:focus-visible,
.mobile-dropdown-menu li a:hover {
    background: var(--alpha-lime-06);
    color: var(--accent);
}

.mobile-nav-links a:focus {
    outline: none;
}

.mobile-nav-links a:focus-visible {
    outline: var(--bw-thick) solid var(--accent);
    outline-offset: -2px;
}

.mobile-nav-links .indicator-dot {
    width: 6px;
    height: 6px;
    display: none;
}

.mobile-nav-links a.is-active {
    color: var(--accent);
    font-weight: 600;
}

.mobile-nav-links a.is-active .indicator-dot {
    display: inline-block;
}

.mobile-nav-links .live-dot {
    width: 8px;
    height: 8px;
    display: none;
}

.mobile-nav-links a.is-live .live-dot {
    display: inline-block;
    animation: livePulse 1.5s ease-in-out infinite;
}

.mobile-nav-links a.is-live {
    color: var(--danger) !important;
    font-weight: 600;
}

/* Mobile Rankings Dropdown */
.mobile-nav-dropdown {
    position: relative;
}

.mobile-dropdown-toggle {
    display: flex;
    align-items: center;
    justify-content: space-between;
    width: 100%;
    padding: 12px 16px;
    color: var(--text-secondary);
    text-decoration: none;
    border-radius: var(--radius-md);
    font-size: var(--fs-base);
    font-weight: 500;
    transition: all var(--transition-fast);
    cursor: pointer;
}

/* Раскрывающийся пункт меню: своя подсветка вместо системной обводки
   ----------------------------------------------------------------
   После нажатия браузер рисовал вокруг пункта синюю рамку — чужую для
   тёмной лаймовой темы. Убираем её и даём свой вид: при наведении и
   в раскрытом состоянии пункт подсвечивается лаймовым, как везде.
   Для клавиатуры обводка остаётся — иначе не видно, где находишься. */
.mobile-dropdown-toggle:focus {
    outline: none;
}

.mobile-dropdown-toggle:focus-visible {
    outline: var(--bw-thick) solid var(--accent);
    outline-offset: -2px;
}

.mobile-dropdown-toggle:hover,
.mobile-nav-dropdown.active > .mobile-dropdown-toggle {
    background: var(--alpha-lime-06);
    color: var(--accent);
}

/* Точка «идёт сейчас» стоит вплотную к названию, а не посреди строки:
   она относится к разделу, а не к пустому месту рядом */
.mobile-dropdown-toggle {
    gap: 8px;
}

.mobile-dropdown-toggle .indicator-dot {
    margin-right: auto;
}

.mobile-nav-arrow {
    transition: transform 0.3s ease;
    opacity: 0.5;
}

.mobile-nav-dropdown.active .mobile-nav-arrow {
    transform: rotate(180deg);
    opacity: 1;
}

.mobile-dropdown-menu {
    list-style: none;
    max-height: 0;
    overflow: hidden;
    transition: max-height 0.4s cubic-bezier(0.4, 0, 0.2, 1);

    /* ШАГ УРОВНЯ ОБЪЯВЛЕН, А НЕ СЛОЖЕН.
       Замер 20.09 (прибор 24-burger-inside): левые края вложенных пунктов
       58 / 57.8 / 57.1 на трёх ширинах. Расхождение в доли пикселя значит,
       что отступ НИКТО не задавал — он складывался из полей панели, полей
       ссылки и этого числа, и слагаемые округлялись каждое по-своему.
       20 к тому же мимо сетки 8. Шаг уровня — 16, одним числом. */
    padding-left: 16px;
}

.mobile-nav-dropdown.active .mobile-dropdown-menu {
    max-height: 500px;
    overflow-y: auto;
}

/* КЕГЛЬ ВЛОЖЕННОГО ПУНКТА — ОДИН НА ВСЕ ШИРИНЫ, ИЗ ШКАЛЫ.
   Было три размера у одного и того же пункта меню:
       0.95rem = 15.2  от 769 и выше
       0.88rem = 14.08 в блоке до 768
       0.88rem = 14.08 копией в блоке до 375 (мёртвый дубль)
   Ни 15.2, ни 14.08 в девятиступенчатой шкале 11/12/14/16/18/21/26/32/40
   не существует. Пункт менял размер от ширины окна — то, что Костя
   называет «сайт плывёт», в одном правиле. Теперь --fs-sm, то есть 14,
   и на уровень ниже родителя (16), а не вровень с ним. */
.mobile-dropdown-menu li a {
    padding: 12px 16px;
    font-size: var(--fs-sm);
    color: var(--text-muted);
    opacity: 0;
    transform: translateX(-10px);
    transition: all 0.3s ease;
}

.mobile-nav-dropdown.active .mobile-dropdown-menu li a {
    opacity: 1;
    transform: translateX(0);
}

/* Первый пункт ведёт на сам раздел, а не в его часть: нажатие на название
   выше только раскрывает список, и попасть на страницу «Турниры» из меню
   было нельзя. Выделяем цветом — это не ещё одна категория */
.mobile-dropdown-menu li a.mobile-dropdown-self {
    color: var(--accent);
    font-weight: 600;
}

.mobile-nav-dropdown.active .mobile-dropdown-menu li:nth-child(1) a { transition-delay: 0.05s; }
.mobile-nav-dropdown.active .mobile-dropdown-menu li:nth-child(2) a { transition-delay: 0.1s; }
.mobile-nav-dropdown.active .mobile-dropdown-menu li:nth-child(3) a { transition-delay: 0.15s; }
.mobile-nav-dropdown.active .mobile-dropdown-menu li:nth-child(4) a { transition-delay: 0.2s; }
.mobile-nav-dropdown.active .mobile-dropdown-menu li:nth-child(5) a { transition-delay: 0.25s; }

.mobile-dropdown-menu li a.active {
    color: var(--accent);
    font-weight: 600;
}

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

.mobile-nav-bottom {
    display: flex;
    flex-direction: column;
    gap: var(--space-md);
    padding-top: var(--space-md);
    margin-top: var(--space-md);
    border-top: var(--bw-hair) solid var(--border-subtle);
}

/* ========================================
   STICKY BACK BAR — Fixed back button on scroll
   ======================================== */

.sticky-back-bar {
    position: fixed;
    top: var(--header-h);
    left: 0;
    right: 0;
    z-index: 999;
    background: rgba(10, 10, 10, 0.95);
    backdrop-filter: blur(16px);
    -webkit-backdrop-filter: blur(16px);
    border-bottom: var(--bw-hair) solid var(--border-subtle);
    padding: 10px 24px;
    transform: translateY(-100%);
    opacity: 0;
    transition: transform 0.3s ease, opacity 0.3s ease;
    pointer-events: none;
}

.sticky-back-bar.visible {
    transform: translateY(0);
    opacity: 1;
    pointer-events: auto;
}

.sticky-back-bar a {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    color: var(--text-secondary);
    text-decoration: none;
    font-size: var(--fs-sm);
    font-weight: 600;
    transition: color 0.2s;
    max-width: var(--container-max);
    margin: 0 auto;
}

.sticky-back-bar a:hover {
    color: var(--accent);
}

.sticky-back-bar a svg {
    flex-shrink: 0;
}

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

/* ГРАНИЦА 1200 У ШАПКИ СНЕСЕНА 16.10. Она повторяла базовое правило слово
   в слово: `.floating-header.header-hybrid { padding: 0 var(--pad-x) }` стоит
   выше по файлу тем же селектором из двух классов. Беда, ради которой блок
   заводился, — проигрыш правила на ОДИН класс — вылечена в самом базовом
   правиле, и копия с тех пор не делала ничего. Проверено чтением, не видом. */

@media (max-width: 992px) {
    /* СНЯТО: .nav-links { gap: 0 } и .nav-item { padding: 0 12px }.
       Это было поджатие ряда, чтобы он влез на узких ширинах. С границей
       бургера на 992 горизонтальное меню здесь больше не показывается —
       правила стали мёртвыми. Не возвращать «на всякий случай»: они будут
       молча бороться с базовыми отступами, если границу когда-нибудь
       подвинут обратно. */

    /* Только горизонтальный: высоту держит min-height из базового правила.
       Если вернуть сюда вертикальный padding, на 769–992 ряд снова станет
       другой высоты, чем на широком экране. */
    .sponsor-badge {
        display: none;
    }

    .hero-content {
        width: 95%;
    }

    .hero-sponsors-section {
        width: 95%;
    }

    .hero-visual {
        display: none;
    }

    .card-rating {
        right: 5%;
    }

    .card-match {
        left: 5%;
    }
}

/* ПЛАНШЕТ И УЖЕ — 16.10, кусок 75 */
@media (max-width: 992px) {
    .floating-header.header-hybrid {
        padding: 0 var(--pad-x);
    }

    /* Тот же вес, что у базового правила: иначе фон при прокрутке на
       телефоне не применялся */
    .floating-header.header-hybrid.scrolled {
        background: var(--alpha-black-85);
    }

    .sticky-back-bar {
        /* ОСТОРОЖНО, ЗДЕСЬ УЖЕ ОШИБАЛИСЬ. Тут стояло top: 56px — это была
           единственная во всём проекте попытка учесть вторую высоту шапки,
           и она была неверной: шапка становилась 56 только ниже 375, а это
           правило действовало до 768. Замерено 21.09: на 768 панель липла
           на 56 при шапке 65. Теперь высоту даёт токен из базового правила,
           и она верна на любой ширине — отдельная строка тут не нужна. */
        padding: 8px 16px;
    }

    .sticky-back-bar a {
        font-size: var(--fs-sm);
    }

    .btn-auth {
        padding: 8px 16px;
        font-size: var(--fs-xs);
    }

    .user-dropdown-name {
        display: none;
    }

    .user-dropdown-toggle {
        padding: 4px;
    }

    .user-dropdown-arrow {
        display: none;
    }

    /* main { padding-top: 0 } убран 21.09. Отбивка от шапки теперь одна на
       все ширины — сам main, через var(--header-h), — а зазор до первой
       строки задаёт --header-gap у первой секции. Раньше на телефоне main
       не отбивался вовсе, и всю работу делало отдельное правило первой
       секции: два разных механизма на одну задачу. */

    /* Первый экран на телефоне не запираем в высоту окна.
       Заголовок, кнопка, пять цифр в три ряда и полоса спонсоров в один
       экран не помещаются, а `overflow: hidden` не давал им выйти за край —
       содержимое наезжало само на себя и страница не прокручивалась. */
    .hero {
        /* Осталось только overflow. Остальные четыре строки были мертвы —
           доказано замером 22.09: height:auto и max-height:none повторяли
           значения по умолчанию, а обе min-height перебивались правилом
           .hero { min-height: 0 } ниже по файлу (9870), в том же условии
           max-width: 768. Вычислённое значение на 390x844 и 768x1024 было
           minH=0px ещё ДО удаления — то есть строки ни на что не влияли.
           Замысел «не запирать первый экран в высоту окна» при этом цел:
           min-height: 0 делает его ровно этим, и даже честнее, чем 100dvh. */
        overflow: visible;
    }

    .hero-content {
        width: 100%;
    }

    .hero-sponsors-section {
        width: 100%;
    }

    .hero-cta {
        flex-direction: column;
        align-items: center;
        gap: 10px;
    }

    .btn-primary,
    .btn-secondary {
        width: 100%;
        max-width: 300px;
        justify-content: center;
        padding: 14px 28px;
    }

    .hero-stats {
        flex-wrap: wrap;
    }

    .floating-card {
        padding: 12px 16px;
    }

    .floating-card .card-icon {
        width: 36px;
        height: 36px;
    }

    .floating-card .card-value {
        font-size: var(--fs-base);
    }

    .card-rating {
        top: 5%;
        right: 5%;
    }

    .card-match {
        bottom: 10%;
        left: 5%;
    }
}

/* Mobile touch support for dropdowns */
@media (hover: none) {
    .dropdown.active .dropdown-menu {
        opacity: 1;
        visibility: visible;
        transform: translateX(-50%) translateY(5px);
    }

    .dropdown.active .dropdown-arrow {
        transform: rotate(180deg);
    }
}

/* ========================================
   RESPONSIVE - TABLETS
   ======================================== */

@media (max-width: 992px) {
    /* Двенадцать колонок оставляем и на телефоне: карточки делят ряд
       пополам (span 6), то есть по две в ряд. Одна колонка здесь ломала
       расчёт — карточка на «span 6» растягивалась на всю ширину */
    .tournaments-grid {
        grid-template-columns: repeat(12, 1fr);
        gap: 12px;
    }

    .tournament-card-featured {
        grid-template-columns: 1fr 1fr;
        max-height: 320px;
    }

}

/* ========================================
   RESPONSIVE - MOBILE
   ======================================== */

@media (max-width: 768px) {
    section {
        /* ТОЛЬКО БОКА. Вертикаль отсюда убрана 23.09: здесь стояло
           padding: 40px 26px — сокращённая запись, которая перебивала крутилку
           --section-vozduh целиком, вместе со всеми слоями. Правило заморозки
           этого не увидело: шаблон искал section БЕЗ отступа, а это объявление
           внутри медиазапроса. Замер тоже не увидел: 40 на телефоне лежит на
           шкале и выглядело правильным числом.
           Поймал Playwright — на 375x812 ждали 24 по сенсорному слою, получили 40.
           Величина воздуха на телефоне теперь задана ниже отдельным слоем, чтобы
           её было видно как решение, а не как побочный эффект записи в одну строку.

           Было --space-xl (80px): между разделами копилось 160 пикселей пустоты —
           пол-экрана телефона уходило в никуда. 40 хватает, чтобы разделы
           читались раздельно.

           ПОЛЕ ПО БОКАМ — 24.09. Здесь стояло 26px числом, и это был ВТОРОЙ
           левый край страницы. Померено на 768: семь секций стояли на 26, а
           #venues и блок спонсорам — на системном --pad-x, 16.89, потому что
           их слои переписаны и берут формулу. Тест 40-sponsor нашёл это,
           требуя одного края, и был прав про страницу, а не про блок.
           Одно понятие — одно определение: поле от края экрана задаёт --pad-x
           и только оно. 26 не ступень шкалы и ниоткуда не выведено. */
        padding-inline: var(--pad-x);
    }

    /* Здесь было .section-header { flex-direction: column }. Оно тоже НИКОГДА
       не работало: ниже в файле есть ВТОРОЙ блок @media (max-width: 768px),
       который ставит flex-direction: row. Два блока с одним условием спорили,
       побеждал второй. Шапку раздела на телефоне задаёт он один. */

    .tournament-card-featured {
        grid-template-columns: 1fr;
        max-height: none;
    }

    .tournament-card-featured .tournament-image {
        max-height: 220px;
    }

    .live-matches {
        grid-template-columns: 1fr;
    }

    .courts-grid {
        grid-template-columns: 1fr;
    }

    .coaches-grid {
        grid-template-columns: 1fr;
    }

    .hero-visual {
        display: none;
    }
}

/* ========================================
   AUTH PAGE
   ======================================== */
.auth-page {
    min-height: 100vh;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    position: relative;
    overflow: hidden;
    padding: 20px;
}

.auth-bg {
    position: absolute;
    inset: 0;
    z-index: 0;
}

.auth-bg-image {
    width: 100%;
    height: 100%;
    object-fit: cover;
    animation: heroZoom 18s ease-in-out infinite alternate;
    transform-origin: center center;
}

.auth-bg-overlay {
    position: absolute;
    inset: 0;
    background: var(--alpha-black-85);
}

.auth-header {
    position: absolute;
    top: 0;
    left: 0;
    width: 100%;
    padding: 20px 32px;
    z-index: 2;
}

.auth-header .logo {
    text-decoration: none;
    transition: filter var(--transition-base);
}

.auth-header .logo:hover {
    transform: none;
    filter: drop-shadow(0 0 20px var(--alpha-lime-70)) brightness(1.2);
}

.auth-card {
    width: 100%;
    max-width: 500px;
    background: rgba(20, 20, 20, 0.85);
    backdrop-filter: blur(24px);
    border: var(--bw-hair) solid var(--border-subtle);
    border-radius: var(--radius-xl);
    padding: 40px 36px;
    z-index: 1;
}

/* Паддинг 0, а не 4: высоту дорожки задаёт сама вкладка — 44, как требует
   палец. Кольцо вокруг пилюли рисует не паддинг дорожки, а внутренний
   отступ самой вкладки (см. .auth-tab). */
.auth-tabs {
    display: flex;
    background: var(--alpha-white-04);
    border-radius: var(--radius-full);
    padding: 0;
    margin-bottom: 32px;
}

/* ОБЛАСТЬ НАЖАТИЯ И РИСУНОК — РАЗНЫЕ ВЕЩИ.

   Было 36 — ниже 44, то есть нарушение WCAG 2.5.5 на всех видах и языках.
   Растянуть пилюлю до 44 нельзя: по макету она 38, и Apple с Material обе
   рисуют сегментированный контрол низким, а палец ловят шире собственных
   границ.

   Поэтому: min-height 44 — это ЦЕЛЬ. background-clip: content-box —
   лаймовый фон красится только по внутренней части, то есть 44 минус
   3 сверху и 3 снизу = 38 нарисованной пилюли.

   Тронешь padding — поедет пилюля; тронешь min-height — поедет палец.
   Это два разных числа, и они намеренно не равны.
   Макет: Figma «Auth card — sizes to the scale» (258:145). */
.auth-tab {
    flex: 1;
    min-height: 44px;
    /* Прозрачная рамка 3 + background-clip: padding-box — фон красится
       ВНУТРИ рамки, то есть ровно на 3 внутрь со ВСЕХ сторон.
       content-box так не умеет: он режет по паддингу, а паддинг здесь
       разный по осям (16 по горизонтали нужен тексту), и пилюля ужималась
       по бокам на 16 вместо 3. */
    border: var(--bw-heavy) solid transparent;
    background-clip: padding-box;
    padding: 0 13px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    background-color: transparent;
    color: var(--text-muted);
    font-size: var(--fs-sm);
    font-weight: 600;
    border-radius: var(--radius-full);
    cursor: pointer;
    transition: all var(--transition-fast);
}

.auth-tab.active {
    background-color: var(--accent);
    color: var(--neutral-black);
}

.auth-form {
    display: none;
    flex-direction: column;
    gap: 16px;
    animation: fadeIn 0.3s ease;
}

.auth-form.active {
    display: flex;
}

@keyframes fadeIn {
    from { opacity: 0; transform: translateY(6px); }
    to { opacity: 1; transform: translateY(0); }
}

.auth-required {
    color: var(--danger);
    font-weight: 600;
}

.auth-pw-rules {
    display: flex;
    flex-wrap: nowrap;
    gap: 0;
    font-size: var(--fs-2xs);
}

.pw-rule,
.pw-rule-reset {
    color: var(--text-muted);
    transition: color var(--transition-fast);
    white-space: nowrap;
}

.pw-rule::before,
.pw-rule-reset::before {
    content: '○ ';
}

.pw-rule.valid::before,
.pw-rule-reset.valid::before {
    content: '● ';
}

.pw-rule.valid,
.pw-rule-reset.valid {
    color: var(--accent);
}

.pw-rule.pw-rule-error,
.pw-rule-reset.pw-rule-error {
    color: var(--danger);
    animation: pw-rule-shake 0.5s ease;
}

.pw-rule.pw-rule-error::before,
.pw-rule-reset.pw-rule-error::before {
    content: '✕ ';
}

@keyframes pw-rule-shake {
    0%, 100% { transform: translateX(0); }
    20%, 60% { transform: translateX(-3px); }
    40%, 80% { transform: translateX(3px); }
}

.auth-pw-detail {
    text-align: left;
}

.auth-pw-detail strong {
    display: block;
    margin-bottom: 6px;
}

.auth-pw-detail ul {
    list-style: none;
    padding: 0;
    margin: 0;
}

.auth-pw-detail li {
    padding: 2px 0;
    font-size: var(--fs-sm);
}

.auth-pw-detail li::before {
    content: '✕ ';
    color: var(--danger);
    font-weight: 700;
}

/* Выполненное требование отмечается галочкой: раньше крестик стоял всегда,
   и человек с подходящим паролем видел четыре красных строки */
.auth-pw-detail li.valid {
    color: var(--accent);
}

.auth-pw-detail li.valid::before {
    content: '✓ ';
    color: var(--accent);
}

.pw-rule + .pw-rule,
.pw-rule-reset + .pw-rule-reset {
    margin-left: 6px;
    padding-left: 8px;
    border-left: var(--bw-hair) solid var(--border-subtle);
}

.auth-screen-header {
    text-align: center;
    margin-bottom: 8px;
}

.auth-screen-title {
    font-size: var(--fs-lg);
    font-weight: 700;
    color: var(--text-primary);
    margin-bottom: 6px;
}

.auth-screen-subtitle {
    font-size: var(--fs-sm);
    color: var(--text-muted);
    line-height: 1.3;
}

.auth-screen-subtitle strong {
    color: var(--accent);
    word-break: break-all;
}

.auth-icon-wrap {
    display: flex;
    justify-content: center;
    margin-bottom: 12px;
    color: var(--accent);
}

.auth-back-link {
    /* ЦЕЛЬ НАЖАТИЯ, А НЕ ПОЛОСА ВО ВСЮ КАРТОЧКУ.

       Замер 21.09: 426x20 на десктопе, 308x20 на телефоне. Высота 20 — это
       нарушение 2.5.5: ссылка БЛОЧНАЯ, а не внутри предложения, и
       исключение для встроенных ссылок на неё не распространяется.
       Ширина важна не меньше: невидимая полоса во всю карточку ловит
       палец, который целился в соседнее.

       fit-content сажает цель по тексту, min-height даёт 44, поля по 12
       отделяют от края, автоматические внешние поля держат по центру —
       раньше центрировал text-align, но центрировать теперь нечего. */
    display: flex;
    align-items: center;
    justify-content: center;
    width: fit-content;
    min-height: 44px;
    padding: 0 12px;
    margin-inline: auto;
    text-align: center;
    font-size: var(--fs-sm);
    color: var(--text-muted);
    text-decoration: none;
    transition: color var(--transition-fast);
}

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

/* ---- GLOBAL BACK LINK (detail pages) ---- */
/* Хлебные крошки живут по той же сетке, что и содержимое страницы:
   у них был свой контейнер в 1100, и ссылка «назад» стояла правее всего,
   что под ней */
/* Хлебные крошки живут по той же сетке, что и содержимое страницы:
   у них был свой контейнер в 1100, и ссылка «назад» стояла правее всего,
   что под ней. Фон при этом должен идти во всю ширину, поэтому его
   растягивает подложка */
.kslt-back-wrap {
    position: sticky;
    top: var(--header-h);
    z-index: 10;
    max-width: var(--container-max);
    margin: 0 auto;
    padding: 16px var(--gutter);
    box-sizing: border-box;
}

.kslt-back-wrap::before {
    content: '';
    position: absolute;
    top: 0;
    bottom: 0;
    /* Ровно ширина экрана, от его левого края. Раньше подложку растягивали
       на 100vw в каждую сторону, и документ становился вдвое шире окна:
       на телефоне страница уезжала вбок */
    left: 50%;
    width: 100vw;
    margin-left: -50vw;
    background: var(--bg-primary);
    border-bottom: var(--bw-hair) solid var(--border-subtle);
    z-index: -1;
}

.kslt-back-wrap .kslt-back-sep {
    margin: 0 6px;
}

.kslt-back {
    /* ЦЕЛЬ НАЖАТИЯ — ПРОЗРАЧНЫМ СЛОЕМ (приём принят 04.10). ЗАМЕР 08.10 на
       странице новости: 142 x 21 при норме 44. Высоту самой ссылке давать
       нельзя — она стоит в одном ряду с разделителями и крошками, и полоса
       поехала бы на всех страницах, где эта ссылка есть. Слой цели не знает
       высоты элемента и ничего не сдвигает. */
    position: relative;
    display: inline-flex;
    align-items: center;
    gap: 8px;
    color: var(--text-secondary);
    text-decoration: none;
    font-size: var(--fs-sm);
    font-weight: 600;
    transition: color var(--transition-fast);
}

.kslt-back::after {
    content: '';
    position: absolute;
    left: 0;
    right: 0;
    top: 50%;
    height: 44px;
    transform: translateY(-50%);
}

.kslt-back:hover {
    color: var(--accent);
}

.kslt-back-sep {
    color: var(--text-muted);
    font-size: var(--fs-sm);
}

/* ---- PROMOTED / RECOMMENDED BADGE ---- */

/* Badge on card photo (overlay, bottom-left) */
.kslt-recommended-badge {
    position: absolute;
    bottom: 12px;
    left: 12px;
    background: var(--modal-bg-heavy);
    backdrop-filter: blur(10px);
    -webkit-backdrop-filter: blur(10px);
    border: var(--bw-hair) solid var(--accent);
    color: var(--accent);
    font-size: var(--fs-2xs);
    font-weight: 700;
    letter-spacing: 0.5px;
    text-transform: uppercase;
    padding: 4px 10px;
    border-radius: 6px;
    line-height: 1.1;
    z-index: 2;
    pointer-events: none;
}

/* Card with promoted highlight */
.kslt-promoted-card {
    border: var(--bw-hair) solid var(--alpha-lime-20);
}

/* Image wrapper needs position:relative for badge overlay */
.co-card-img-wrap {
    position: relative;
    display: flex;
    flex-direction: column;
    align-items: center;
}

/* Badge on circular coach photo — centered below circle */
.co-card-img-wrap .kslt-recommended-badge {
    position: relative;
    bottom: auto;
    left: auto;
    margin-top: -14px;
    margin-bottom: 4px;
}

/* Badge next to h1 on detail page */
.kslt-recommended-detail {
    display: inline-flex;
    align-items: center;
    background: var(--alpha-lime-12);
    border: var(--bw-hair) solid var(--accent);
    color: var(--accent);
    font-size: var(--fs-xs);
    font-weight: 700;
    letter-spacing: 0.5px;
    text-transform: uppercase;
    padding: 4px 12px;
    border-radius: 6px;
    line-height: 1.1;
    white-space: nowrap;
    flex-shrink: 0;
}

/* Title row with badge alignment */
.ct-detail-title-row,
.co-detail-title-row {
    display: flex;
    align-items: center;
    gap: 12px;
    flex-wrap: wrap;
}

.ct-detail-name,
.co-detail-name {
    margin: 0;
}

@media (max-width: 640px) {
    .kslt-recommended-badge {
        font-size: var(--fs-2xs);
        padding: 3px 8px;
        bottom: 8px;
        left: 8px;
    }
    .kslt-recommended-detail {
        font-size: var(--fs-2xs);
        padding: 3px 10px;
    }
}

/* ПЛАНШЕТ И УЖЕ: поля ссылки «назад». */
@media (max-width: 992px) {
    .kslt-back-wrap {
        padding: 12px 16px;
    }
}

@media (max-width: 640px) {
    .kslt-back-wrap {
        padding: 10px 12px;
    }
}

.auth-resend {
    text-align: center;
    font-size: var(--fs-xs);
    color: var(--text-muted);
}

.auth-resend a {
    color: var(--accent);
    text-decoration: none;
}

.auth-resend a:hover {
    text-decoration: underline;
}

/* Auth Modal */
.auth-modal-overlay {
    display: none;
    position: fixed;
    top: 0; left: 0; right: 0; bottom: 0;
    background: var(--modal-bg-heavy);
    backdrop-filter: blur(4px);
    z-index: 9999;
    align-items: center;
    justify-content: center;
}

.auth-modal-overlay.active {
    display: flex;
}

.auth-modal {
    position: relative;
    background: var(--glass-bg, rgba(30, 30, 30, 0.95));
    border: var(--bw-hair) solid var(--border-subtle);
    border-radius: 16px;
    padding: 40px 32px 32px;
    max-width: 400px;
    width: 90%;
    text-align: center;
    animation: authModalIn 0.25s ease-out;
}

@keyframes authModalIn {
    from { opacity: 0; transform: scale(0.9) translateY(10px); }
    to { opacity: 1; transform: scale(1) translateY(0); }
}

.auth-modal-icon {
    margin-bottom: 16px;
}

/* ЗАГОЛОВОК ОКНА САЙТА — ОДНО ОПРЕДЕЛЕНИЕ НА ПОНЯТИЕ (кусок 174).
   Переезд двух окон из строк js дал бы четвёртое и пятое написание
   заголовка; чужой сторож `check-lestnica-okna` поймал четвёртое и был
   прав. Селекторы присоединены здесь. ПОСЧИТАНО: заголовок окна записи
   был 21/600 — стал 21/700; заголовок парного окна был 18.4/700 (вес от
   h3) — стал 21/700. */
.auth-modal-title,
.trn-register-modal-title,
.de-title {
    font-size: var(--fs-lg);
    font-weight: 700;
    color: var(--text-primary);
    margin-bottom: 8px;
}

/* Отступы у заголовков двух переехавших окон свои: окно записи центровано
   и дышит, парное — плотное. Один шаг лестницы вниз, а не своё число. */
.trn-register-modal-title {
    margin: 0 0 var(--space-3);
}

.de-title {
    margin: 0 0 var(--space-1);
}

.auth-modal-text {
    font-size: var(--fs-base);
    color: var(--alpha-white-72);
    line-height: 1.65;
    margin-bottom: 24px;
}

.auth-modal-actions {
    display: flex;
    flex-direction: column;
    gap: 10px;
}

.auth-modal-btn {
    padding: 12px 24px;
    border-radius: 10px;
    font-size: var(--fs-base);
    font-weight: 600;
    cursor: pointer;
    border: none;
    transition: all var(--transition-fast);
}

.auth-modal-btn-primary {
    background: var(--accent);
    color: var(--neutral-black);
}

.auth-modal-btn-primary:hover {
    background: var(--accent-hover);
}

.auth-modal-btn-secondary {
    background: transparent;
    color: var(--text-muted);
    border: var(--bw-hair) solid var(--border-subtle);
}

.auth-modal-btn-secondary:hover {
    color: var(--text-primary);
    border-color: var(--text-muted);
}

.auth-modal-close {
    position: absolute;
    top: 12px; right: 16px;
    background: none;
    border: none;
    color: var(--text-muted);
    font-size: var(--fs-xl);
    cursor: pointer;
    line-height: 1;
    padding: 4px;
}

.auth-modal-close:hover {
    color: var(--text-primary);
}

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

.auth-label {
    font-size: var(--fs-xs);
    font-weight: 500;
    color: var(--text-secondary);
}

.auth-input {
    padding: 12px 16px;
    background: var(--alpha-white-04);
    border: var(--bw-hair) solid var(--border-light);
    border-radius: var(--radius-md);
    color: var(--text-primary);
    /* 16, а не 0.95rem = 15.2. Ниже 16 Safari на iOS зумит страницу при
       касании поля, и человек после первого же тапа видит четверть формы.
       Токен --input-fs держит это значение для всех полей продукта. */
    font-size: var(--input-fs);
    transition: border-color var(--transition-fast), box-shadow var(--transition-fast);
    outline: none;
}

.auth-input::placeholder {
    color: var(--text-muted);
}

.auth-input:focus {
    border-color: var(--accent);
    box-shadow: 0 0 0 3px var(--alpha-lime-12);
}

.auth-input:-webkit-autofill,
.auth-input:-webkit-autofill:hover,
.auth-input:-webkit-autofill:focus,
.auth-input:-webkit-autofill:active {
    -webkit-box-shadow: 0 0 0 1000px var(--neutral-850) inset !important;
    -webkit-text-fill-color: var(--neutral-white) !important;
    transition: background-color 5000s ease-in-out 0s;
    caret-color: var(--neutral-white);
}

.auth-password-wrap {
    position: relative;
}

.auth-password-wrap .auth-input {
    width: 100%;
    padding-right: 48px;
}

/* Цель нажатия 44x44 (WCAG 2.5.5). Было 28x28: значок 20 плюс паддинг 4.
   right: 4 вместо 12 — чтобы значок остался ровно там, где стоял:
   его центр был на 26 от края поля (12 + 14), и при цели 44 это 4 + 22. */
.auth-eye {
    position: absolute;
    right: 4px;
    top: 50%;
    transform: translateY(-50%);
    width: 44px;
    height: 44px;
    background: none;
    border: none;
    color: var(--text-muted);
    cursor: pointer;
    padding: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    transition: color var(--transition-fast);
}

.auth-eye:hover {
    color: var(--text-secondary);
}

/* Цель нажатия 44 по высоте (WCAG 2.5.5). Была 19 — высота строки текста.
   Отрицательное поле справа держит текст прижатым к краю колонки,
   а не сдвинутым внутрь на паддинг цели. */
.auth-forgot {
    font-size: var(--fs-xs);
    color: var(--accent);
    text-decoration: none;
    align-self: flex-end;
    margin-top: -8px;
    display: inline-flex;
    align-items: center;
    min-height: 44px;
    padding: 0 8px;
    margin-right: -8px;
}

.auth-forgot:hover {
    text-decoration: underline;
}

.auth-btn {
    padding: 14px;
    background: var(--accent);
    color: var(--neutral-black);
    border: none;
    border-radius: var(--radius-md);
    font-size: var(--fs-base);
    font-weight: 700;
    cursor: pointer;
    transition: background var(--transition-fast), transform var(--transition-fast);
    margin-top: 4px;
}

.auth-btn:hover {
    background: var(--accent-hover);
    transform: translateY(-1px);
}

.auth-btn:disabled {
    opacity: 0.6;
    cursor: not-allowed;
    transform: none;
}

.auth-message {
    padding: 12px 16px;
    border-radius: 8px;
    font-size: var(--fs-sm);
    margin-bottom: 12px;
    text-align: center;
}

.auth-message-error {
    background: var(--danger-subtle);
    border: var(--bw-hair) solid var(--alpha-red-30);
    color: var(--danger);
}

.auth-message-success {
    background: var(--alpha-lime-12);
    border: var(--bw-hair) solid var(--accent-glow);
    color: var(--accent);
}

.auth-divider {
    display: flex;
    align-items: center;
    gap: 16px;
    color: var(--text-muted);
    font-size: var(--fs-xs);
}

.auth-divider::before,
.auth-divider::after {
    content: '';
    flex: 1;
    height: 1px;
    background: var(--border-subtle);
}

.auth-social {
    display: flex;
    gap: 12px;
}

.auth-social-btn {
    flex: 1;
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 8px;
    padding: 12px;
    background: transparent;
    border: var(--bw-hair) solid var(--border-light);
    border-radius: var(--radius-md);
    color: var(--text-secondary);
    font-size: var(--fs-sm);
    font-weight: 500;
    cursor: pointer;
    transition: all var(--transition-fast);
}

.auth-social-btn .google-icon,
.auth-tg-app-btn .tg-icon {
    filter: grayscale(1) brightness(0.8);
    transition: filter var(--transition-fast);
}
.auth-social-btn:hover .google-icon,
.auth-tg-app-btn:hover .tg-icon {
    filter: none;
}
.auth-social-btn:hover {
    background: var(--surface-glass-hover);
    border-color: var(--border-light);
}

/* Signup option buttons (Google / Telegram / Form) */
.auth-signup-options {
    display: flex;
    flex-direction: column;
    gap: 12px;
    margin-bottom: 4px;
}

/* СПОСОБЫ РЕГИСТРАЦИИ — ТА ЖЕ СЕМЬЯ, ЧТО НА ВХОДЕ.

   Было третье семейство кнопок на одной карточке: высота ~48 (паддинг
   14/20), радиус 8 числом, кегль 0.95rem = 15.2 вне шкалы, фон сырыми
   цветами #fff и #0088cc.

   Почему увели цвет из фона в значок. Брендбуки Google и Telegram белую и
   синюю кнопки разрешают — дело не в запрете. Дело в том, что на карточке,
   где главное действие лаймовое, самым громким элементом экрана
   оказывался синий Telegram, который главным действием не является. И по
   правилу App Store 4.8 кнопка Apple обязана быть не менее заметной, чем
   остальные: рядом с белой и синей чёрная Apple читалась бы как
   второсортная. Фирменный цвет остаётся в значке — узнаваемость держит он.

   Размеры те же, что у способов входа: 52 · радиус --radius-md · --fs-sm.
   Макет: Figma «Auth card — sizes to the scale» (258:145). */
.auth-option-btn {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 10px;
    width: 100%;
    min-height: 52px;
    padding: 0 16px;
    border-radius: var(--radius-md);
    font-size: var(--fs-sm);
    font-weight: 500;
    cursor: pointer;
    transition: all var(--transition-fast);
    border: var(--bw-hair) solid var(--border-light);
    background: transparent;
    color: var(--text-secondary);
}

/* Фон у всех способов одинаковый — отличает их значок, а не заливка.
   Наведение тоже одно на всех: разный hover у соседних кнопок читается
   как разные виды элементов. */
.auth-option-google,
.auth-option-telegram,
.auth-option-form {
    background: transparent;
    color: var(--text-secondary);
}

/* Фирменный цвет ушёл из заливки в значок — он и держит узнаваемость */
.auth-option-telegram svg {
    color: var(--brand-telegram);
}

.auth-option-btn:hover {
    background: var(--surface-glass);
    border-color: var(--border-light);
}

.auth-tg-app-btn {
    flex: 1;
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 8px;
    padding: 10px 16px;
    background: var(--surface-glass);
    border: var(--bw-hair) solid var(--border-subtle);
    border-radius: 8px;
    color: var(--text-primary);
    font-size: var(--fs-sm);
    cursor: pointer;
    transition: all var(--transition-fast);
}
.auth-tg-app-btn:hover {
    background: var(--surface-glass-hover);
    border-color: var(--border-light);
}

/* Кнопка Apple. Логотип НЕ нарисован: это их фирменный знак, берётся
   официальным файлом из ресурсов Apple и кладётся в images/apple-logo.svg.
   Пока файла нет — место под него зарезервировано, кнопка спрятана
   флагом window.KSLT_APPLE. */
/* АТРИБУТ HIDDEN ПРОТИВ DISPLAY В КЛАССЕ — правило уехало 02.11 в общий
   `css/tokens.css`. Оно стояло здесь с 21.09 и работало ТОЛЬКО на сайте;
   приложение его не получило и наступило на те же грабли: пустая рамка
   «Нет трансляции» со своим `display: flex` занимала 358 x 201, хотя код
   ставил ей `hidden`. ПРАВИЛО, ВЕРНОЕ ДЛЯ ОБОИХ ПРОДУКТОВ, ЖИВЁТ ТАМ,
   ОТКУДА ЕГО ВИДНО ОБОИМ. */

.auth-apple-btn,
.auth-option-apple {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 8px;
    width: 100%;
    min-height: 52px;
    padding: 0 16px;
    border: var(--bw-hair) solid var(--border-light);
    border-radius: var(--radius-md);
    background: transparent;
    color: var(--text-secondary);
    font-size: var(--fs-sm);
    font-weight: 500;
    cursor: pointer;
    transition: all var(--transition-fast);
}

.auth-apple-btn:hover,
.auth-option-apple:hover {
    border-color: var(--border-light);
    background: var(--surface-glass);
}

/* Слот под официальный знак: держит место, чтобы подпись не прыгнула,
   когда файл положат */
.apple-logo {
    width: 16px;
    height: 19px;
    flex: none;
}

/* Способы входа — ОДНА семья кнопок.

   До 21.09 их было две: .auth-social-btn (паддинг 12, радиус --radius-md,
   текст --text-secondary, фон прозрачный, кегль 0.85rem) и .auth-tg-app-btn
   (паддинг 10/16, радиус 8 числом, текст --text-primary, фон --surface-glass,
   кегль 0.9rem). Замер дал 44 и 40 — соседние кнопки в одном столбце
   разной высоты, и обе ниже 44 у Telegram, то есть WCAG 2.5.5 на всех
   пяти видах и трёх языках.

   Правило действует только на экране входа — через обёртку .auth-col-methods.
   Экран выбора способа в регистрации живёт на .auth-option-btn и не задет.

   Макет: Figma «Auth card — sizes to the scale» (258:145) — три способа по 52.
*/
.auth-col-methods .auth-social-btn,
.auth-col-methods .auth-tg-app-btn,
.auth-col-methods .auth-apple-btn {
    min-height: 52px;
    padding: 0 16px;
    border-radius: var(--radius-md);
    border: var(--bw-hair) solid var(--border-light);
    background: transparent;
    color: var(--text-secondary);
    font-size: var(--fs-sm);
    font-weight: 500;
}

/* Обёртка полей регистрации — флекс, а не блок.

   #signupFields показывается из JS (style.display = ''), и без правила
   она становилась обычным блоком. У блока дети не растягиваются, и
   кнопка «Создать аккаунт» сжималась по содержимому: замер 21.09 дал
   175 при карточке 500 — и на узких экранах это не видно, там карточка
   335 и кнопка выглядит почти во всю ширину.

   Дефект был и до наших правок, на исходной форме из девяти полей.
   Нашёлся только когда посмотрели на широком экране — ровно то, ради
   чего правило «3 вида» и существует.

   Шаг между полями тот же, что у формы: 16. */
#signupFields {
    display: flex;
    flex-direction: column;
    gap: 16px;
}

/* Колонки карточки входа. На обычных экранах это просто два блока друг
   под другом — вид и порядок те же, что были до обёрток: у формы gap 16,
   внутри колонок тоже 16, у второй кнопки способа свой margin-top 12. */
.auth-col-fields,
.auth-col-methods {
    display: flex;
    flex-direction: column;
    gap: 16px;
}

.auth-row {
    display: flex;
    gap: 12px;
    flex-wrap: wrap;
}

.auth-row .auth-field {
    flex: 1;
    min-width: 140px;
}

.auth-row-birthday {
    gap: 8px;
}

.auth-row-birthday .auth-input {
    flex: 1;
    min-width: 0;
}

.auth-gender {
    display: flex;
    gap: 12px;
}

.auth-gender-option {
    flex: 1;
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 8px;
    padding: 10px 16px;
    background: var(--alpha-white-04);
    border: var(--bw-hair) solid var(--border-light);
    border-radius: var(--radius-md);
    color: var(--text-secondary);
    font-size: var(--fs-sm);
    cursor: pointer;
    transition: all var(--transition-fast);
}

.auth-gender-option:has(input:checked) {
    border-color: var(--accent);
    background: var(--alpha-lime-06);
    color: var(--accent);
}

.auth-gender-option input[type="radio"] {
    display: none;
}

/* Цель нажатия у согласия — ВЕСЬ ярлык, а не квадратик.
   Квадратик браузера ~13px, но клик по ярлыку переключает его, значит
   целью считается ярлык. min-height 44 приводит его к правилу.

   Две ссылки внутри предложения (условия и политика) НЕ раздуваем:
   у WCAG 2.5.5 есть прямое исключение для ссылок внутри строки текста —
   их размер задан межстрочным интервалом соседних слов. Растянуть их до
   44 значит разорвать предложение. Прибор замера это исключение знает. */
.auth-checkbox {
    display: flex;
    align-items: flex-start;
    gap: 10px;
    min-height: 44px;
    padding-top: 2px;
    font-size: var(--fs-xs);
    color: var(--text-muted);
    cursor: pointer;
}

/* ══════════════ ЧЕКБОКС — ОДНО ОПРЕДЕЛЕНИЕ НА ПРОДУКТ ══════════════
   Был нарисован трижды: здесь (вход), в `admin.css:4037` (заявки) и в
   `dashboard.css:576` (панель). Третья копия оказалась мёртвой — имени
   `.db-checkbox` нет ни в разметке, ни в скриптах, — и снесена. Две
   живые стоят здесь, потому что `style.css` подключён и на входе, и в
   админке, а `admin.css` грузится ПОСЛЕ него: своя копия там перекрыла
   бы общую молча.
   Числа вынесены в токены глифа (`tokens.css`): коробка была 16 · 14 ·
   13, радиус 3 · 3 · 2, штрих 2 · 1.5 · 1.5 — теперь по одной ступени
   на каждое.
   ГАЛОЧКА ЦЕНТРИРУЕТСЯ ТРАНСФОРМОМ, А НЕ ПОДОБРАННЫМИ ПИКСЕЛАМИ: было
   `left: 3.5px; top: 0.5px` и `left: 4px; top: 1px` — два набора
   подгонок под две коробки. Теперь смещение одно и не зависит от
   размера коробки; -60% по вертикали, а не -50%, потому что у
   повёрнутой галочки зрительный центр выше геометрического.
   Цвет `#000` оставлен как был — голый цвет мимо системы разбирается
   своим заходом (цвет), а не этим. */
.auth-checkbox input[type="checkbox"],
.ad-reg-check,
.ad-reg-check-all {
    -webkit-appearance: none;
    appearance: none;
    width: var(--check-box);
    height: var(--check-box);
    min-width: var(--check-box);
    border: var(--bw-thick) solid var(--text-muted);
    border-radius: var(--radius-xs);
    background: transparent;
    cursor: pointer;
    flex-shrink: 0;
    position: relative;
    transition: var(--transition-fast);
}

/* выравнивание с первой строкой текста — своё у каждого потребителя */
.auth-checkbox input[type="checkbox"] { margin-top: 2px; }

.auth-checkbox input[type="checkbox"]:checked,
.ad-reg-check:checked,
.ad-reg-check-all:checked {
    background: var(--accent);
    border-color: var(--accent);
}

.auth-checkbox input[type="checkbox"]:checked::after,
.ad-reg-check:checked::after,
.ad-reg-check-all:checked::after {
    content: '';
    position: absolute;
    left: 50%;
    top: 50%;
    width: var(--check-mark);
    height: var(--check-mark-long);
    border: solid var(--neutral-black);
    border-width: 0 var(--bw-thick) var(--bw-thick) 0;
    transform: translate(-50%, -60%) rotate(45deg);
}

.auth-checkbox a {
    color: var(--accent);
    text-decoration: none;
}

.auth-checkbox a:hover {
    text-decoration: underline;
}

@media (max-width: 640px) {
    .auth-card {
        padding: clamp(24px, 6.4vw, 28px) clamp(16px, 4.3vw, 20px);
        border-radius: var(--radius-lg);
    }
}

/* ==============================================================
   ВХОД: ДВЕ КОЛОНКИ НА НИЗКОМ И ШИРОКОМ ЭКРАНЕ

   Условие написано по ВЫСОТЕ, а не по ширине и не по устройству.
   Телефон боком 844x390 и неразвёрнутое окно ноутбука — одна и та
   же задача: высоты мало, ширины много. Замер 21.09: на 844x390
   способы входа не видны вовсе без прокрутки.

   Правило держит ТОЛЬКО экран входа: :has(#signinForm.active).

   ПОЧЕМУ ДВЕ ОБЁРТКИ, А НЕ ОДНА ОБЩАЯ СЕТКА. Первый заход был без
   обёрток: дети формы раскладывались по общей сетке карточки. Тогда
   строки оказались ОБЩИМИ для обеих колонок, и ритм одной протекал
   в другую — замер дал зазоры 12, 16, 33 и 37 на одном экране, и ни
   одного из шкалы. Две колонки должны быть двумя независимыми
   стопками: делят только верх карточки, внутри у каждой свой шаг.

   Макет: Figma «Auth card — sizes to the scale» (258:145).
   ============================================================== */
@media (max-height: 680px) and (min-width: 600px) {

    /* шапка страницы входа худеет с ~62 до 48: на 390 каждый пиксель счётный */
    .auth-header {
        padding: 10px 24px;
    }

    /* 100dvh, а не 100vh: 100vh считает высоту БЕЗ адресной строки,
       и карточка уезжает под неё */
    .auth-page:has(#signinForm.active) {
        min-height: 100dvh;
        padding: 48px 20px 12px;
    }

    .auth-card:has(#signinForm.active) {
        max-width: 700px;
        padding: 24px;
        display: grid;
        grid-template-columns: 1fr 1fr;
        /* вторая строка 1fr — она забирает весь излишек высоты, который
           приносит колонка полей. Если оставить auto, излишек растечётся
           по обеим строкам и вкладки уедут от способов входа */
        grid-template-rows: auto 1fr;
        column-gap: 40px;
        row-gap: 16px;
        /* без start ячейки тянутся на высоту строки: замер дал вкладки 69
           и Google 69 при Telegram 40 */
        align-items: start;
    }

    /* display: contents — обёртки колонок становятся ячейками сетки карточки */
    #signinForm.active {
        display: contents;
    }

    .auth-card:has(#signinForm.active) .auth-tabs {
        grid-column: 1;
        grid-row: 1;
        margin-bottom: 0;
    }

    #signinForm > .auth-col-methods {
        grid-column: 1;
        grid-row: 2;
        /* 12 даёт инлайновый margin-top у второй кнопки; свой зазор гасим,
           иначе складывается 16 + 12 */
        gap: 0;
    }

    #signinForm > .auth-col-fields {
        grid-column: 2;
        grid-row: 1 / -1;
    }

    /* «или» разделяет две колонки, которых в одну колонку больше нет */
    #signinForm > .auth-divider {
        display: none;
    }

    /* ЭКРАН КОДА — ТЕ ЖЕ ДВЕ КОЛОНКИ.

       Правило писалось под вход и держало только :has(#signinForm.active).
       Экран кода под него не попадал, и на 844x390 замер 21.09 живьём дал:
       карточка 500x577, низ на 205 ниже сгиба, повтор на 50, ссылка
       возврата на 88, а СООБЩЕНИЕ ОБ ОШИБКЕ — на 152. Человек вводил
       неверный код, экран не менялся, попытка из трёх сгорала молча.

       Контейнер взят тот же, что принят для входа: карточка 700, две
       колонки, независимые стопки через display: contents. Слева контекст
       (заголовок, таймер, «Изменить адрес»), справа действие (ошибка,
       клетки, повтор) — так низ карточки укладывается в 371 при экране
       390, и за сгиб не уходит ничего.

       Макет: Figma «Auth card — sizes to the scale», раздел Code screen. */
    .auth-page:has(#otpCodeForm.active) {
        min-height: 100dvh;
        padding: 48px 20px 12px;
    }

    .auth-card:has(#otpCodeForm.active) {
        max-width: 700px;
        padding: 24px;
        display: grid;
        grid-template-columns: 1fr 1fr;
        /* auto auto, а не auto 1fr. У входа вторая строка растягивается,
           потому что колонка полей длиннее колонки способов. Здесь обе
           колонки короткие, растягиваться нечему — и карточка вырастала
           выше содержимого, оставляя пустую полосу в треть высоты.
           Увидел Костя на снимке, а не я. */
        grid-template-rows: auto auto;
        column-gap: 40px;
        row-gap: 16px;
        align-items: start;
    }

    #otpCodeForm.active {
        display: contents;
    }

    #otpCodeForm > .otp-col-head   { grid-column: 1; grid-row: 1; }
    #otpCodeForm > .otp-col-aside  { grid-column: 1; grid-row: 2; }
    #otpCodeForm > .otp-col-action { grid-column: 2; grid-row: 1 / -1; }

    /* в одну колонку у ряда клеток свои внешние поля; в две колонки они
       складываются с зазором сетки и рвут ритм */
    #otpCodeForm > .otp-col-action > .otp-inputs {
        margin-top: 12px;
    }

    /* ВОССТАНОВЛЕНИЕ ПАРОЛЯ — ТЕ ЖЕ ДВЕ КОЛОНКИ.

       Замер 21.09 живьём на 844x390: карточка 500x537, низ на 167 ниже
       сгиба, а «Отправить код» — на 66. Единственное действие, ради
       которого экран существует, лежало за сгибом: чтобы отправить код,
       надо было сначала прокрутить. Нашёл Костя.

       Слева — ЧТО вводим: заголовок, вкладки способа, поле. Справа — ЧЕМ
       подтверждаем и ЧЕМ отправляем: капча, кнопка, возврат. Капча стоит
       перед кнопкой, которую она разблокирует: если она всё-таки
       потребует нажатия, препятствие должно встретиться ДО действия, а не
       после него.

       Правая колонка выравнивается по центру: она короче левой, и прижатая
       к верху выглядела бы оторванной.

       Макет: Figma «Auth card — sizes to the scale», раздел Recovery screen. */
    .auth-page:has(#forgotStep1.active) {
        min-height: 100dvh;
        padding: 48px 20px 12px;
    }

    .auth-card:has(#forgotStep1.active) {
        max-width: 700px;
        padding: 24px;
        display: grid;
        grid-template-columns: 1fr 1fr;
        column-gap: 40px;
        align-items: center;
    }

    #forgotStep1.active {
        display: contents;
    }

    #forgotStep1 > .forgot-col-enter { grid-column: 1; align-self: start; }

    /* ритм колонки задаёт промежуток, а собственные внешние поля детей
       гасятся: иначе они складываются с ним — ровно так плыла форма
       регистрации 21.09 */
    #forgotStep1 > .forgot-col-send {
        grid-column: 2;
        display: flex;
        flex-direction: column;
        gap: 16px;
    }

    #forgotStep1 > .forgot-col-send > * {
        margin: 0;
    }
}


/* ОБЁРТКИ КОЛОНОК ВОССТАНОВЛЕНИЯ — ТАКИЕ ЖЕ СТОПКИ, ЧТО И САМА ФОРМА.

   .auth-form — это flex-колонка с промежутком 16, и её дети растягивались
   по ширине. Когда я завернул их в две обёртки, флекс-элементами стали
   обёртки, а кнопка внутри обычного блока — <button> по умолчанию
   строчно-блочный — села по своему тексту: замер дал 158 вместо 426.
   Это ТОТ ЖЕ дефект, что чинили утром 21.09 на форме регистрации, и я
   наступил на него второй раз за день. Обёртка обязана быть такой же
   колонкой с тем же промежутком, иначе ритм и ширина живут порознь. */
.forgot-col-enter,
.forgot-col-send {
    display: flex;
    flex-direction: column;
    gap: 16px;
}

/* OTP Verification */
.otp-method-tabs {
    display: flex;
    gap: 0;
    margin-bottom: 20px;
    border-radius: var(--radius-md);
    overflow: hidden;
    border: var(--bw-hair) solid var(--border-light);
}

.otp-method-tab {
    /* Замер 21.09 живьём: 212 x 38 — две цели ниже 44 на экране, куда
       человек приходит пальцем. Высота задаётся минимумом, а не отступами:
       отступы зависят от кегля и уезжают вместе с ним. */
    min-height: 44px;
    flex: 1;
    padding: 10px 16px;
    background: var(--alpha-white-04);
    border: none;
    color: var(--text-secondary);
    font-size: var(--fs-sm);
    cursor: pointer;
    transition: all var(--transition-fast);
}

.otp-method-tab.active {
    background: var(--alpha-lime-12);
    color: var(--accent);
    font-weight: 600;
}

.otp-method-tab:hover:not(.active) {
    background: var(--alpha-white-08);
}

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

.otp-country-select {
    width: 110px;
    min-width: 110px;
    padding: 12px 10px;
    background: var(--alpha-white-08);
    border: var(--bw-hair) solid var(--border-light);
    border-radius: var(--radius-md);
    color: var(--text-primary);
    font-size: var(--fs-base);
    cursor: pointer;
    -webkit-appearance: none;
    appearance: none;
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' fill='%23888' viewBox='0 0 16 16'%3E%3Cpath d='M8 11L3 6h10z'/%3E%3C/svg%3E");
    background-repeat: no-repeat;
    background-position: right 10px center;
}

.otp-country-select:focus {
    border-color: var(--accent);
    outline: none;
}

.otp-phone-row .auth-input {
    flex: 1;
}

.otp-inputs {
    display: flex;
    gap: 8px;
    justify-content: center;
    margin: 24px 0;
}

.otp-digit {
    width: 48px;
    height: 56px;
    text-align: center;
    font-size: var(--fs-xl);
    font-weight: 700;
    /* ШИРИНУ ЦИФР ДЕРЖИТ СВОЙСТВО, А НЕ ВТОРОЕ СЕМЕЙСТВО.
       Было `'Inter', monospace`: моно включалось только там, где
       Inter не загрузился, — то есть счёт был либо пропорциональным,
       либо моно, по удаче. `tabular-nums` даёт одну ширину цифры в
       самом Inter, и счёт не дёргается при смене цифр. */
    font-family: var(--font-sans);
    font-variant-numeric: tabular-nums;
    background: var(--alpha-white-08);
    border: var(--bw-thick) solid var(--border-light);
    border-radius: var(--radius-md);
    color: var(--text-primary);
    transition: all var(--transition-fast);
    -webkit-appearance: none;
    -moz-appearance: textfield;
}

.otp-digit::-webkit-inner-spin-button,
.otp-digit::-webkit-outer-spin-button {
    -webkit-appearance: none;
    margin: 0;
}

.otp-digit:focus {
    border-color: var(--accent);
    outline: none;
    box-shadow: 0 0 0 3px var(--accent-subtle);
}

.otp-digit.filled {
    border-color: var(--accent);
    background: var(--alpha-lime-06);
}

.otp-timer {
    text-align: center;
    font-size: var(--fs-sm);
    color: var(--text-muted);
    margin-bottom: 16px;
}

.otp-timer strong {
    color: var(--accent);
    font-variant-numeric: tabular-nums;
}

.otp-channel-hint {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 6px;
    font-size: var(--fs-sm);
    color: var(--text-secondary);
    margin-bottom: 16px;
}

.otp-channel-hint svg {
    width: 16px;
    height: 16px;
}

@keyframes otp-shake {
    0%, 100% { transform: translateX(0); }
    10%, 30%, 50%, 70%, 90% { transform: translateX(-4px); }
    20%, 40%, 60%, 80% { transform: translateX(4px); }
}

.otp-inputs.shake {
    animation: otp-shake 0.4s ease-in-out;
}

.otp-inputs.shake .otp-digit {
    border-color: var(--danger);
}

@media (max-width: 640px) {
    /* КЛЕТКИ КОДА ТЯНУТСЯ ПО РЯДУ, А НЕ СТОЯТ ФИКСИРОВАННЫМИ 42.

       Замер 21.09 живьём: на телефоне клетка 42x50 — шесть целей ниже 44
       ровно там, где палец, а не мышь. На десктопе те же клетки 48x56, и
       глазами дефект не виден вовсе.

       Свободная ширина ВНУТРИ карточки, замерено: 360 -> 288, 375 -> 303,
       390 -> 310, 430 -> 350. Шесть целей по 44 с зазором 6 требуют 294 —
       на 375 и шире помещаются, на 360 нет. Поэтому не фиксированная
       ширина, а flex: основа 44 держит порог, рост забирает остаток (на
       390 выходит 46, на 430 — 53), а сжатие включается только ниже 360,
       где шесть целей по 44 не помещаются ни при каком зазоре. */
    .otp-inputs {
        gap: 6px;
    }
    .otp-digit {
        flex: 1 1 44px;
        width: auto;
        min-width: 0;
        max-width: 56px;
        height: 52px;
        font-size: var(--fs-lg);
    }
}

/* ОДНА КОЛОНКА И ПЕРЕКЛЮЧАТЕЛЬ ПОЛА — ОДНА ГРАНИЦА, 992.
   Раньше колонки схлопывались на 992, а переключатель появлялся только на
   768: в полосе 769-992 две таблицы вставали друг под друга, и раздел
   вырастал до 950 при экране 390. Решение Кости 24.09: две таблицы остаются,
   но там, где рядом они не помещаются, показывается одна, а пол выбирается
   кнопкой. Значит и то и другое включается в одном месте.
   Правило пары: то, что читается как одно целое, едет одной границей. */
@media (max-width: 992px) {
    .rankings-columns {
        grid-template-columns: 1fr;
        gap: var(--space-10);             /* 40 */
    }

    .rk-gender-switch {
        display: flex;
    }

    /* Показываем только выбранный пол */
    .rankings-columns[data-пол="men"] .rankings-col:nth-child(2),
    .rankings-columns[data-пол="women"] .rankings-col:nth-child(1) {
        display: none;
    }

    /* Заголовок колонки не нужен: пол и так выбран кнопкой выше */
    .rankings-col-title {
        display: none;
    }
}

/* ========================================
   375px — Ultra-small phones (iPhone SE)
   ======================================== */
@media (max-width: 375px) {
    .floating-header.header-hybrid {
        padding: 0 var(--pad-x);
    }

    .btn-auth {
        padding: 6px 12px;
        font-size: var(--fs-xs);
    }

    .hero-title {
        font-size: clamp(1.6rem, 8vw, 2.2rem);
    }

    .hero-subtitle {
        font-size: var(--fs-sm);
    }

    .btn-primary, .btn-secondary {
        width: 100%;
        padding: 12px 20px;
        font-size: var(--fs-sm);
    }

    .section-title {
        font-size: clamp(1.4rem, 6vw, 1.8rem);
    }

    .mobile-nav-links a {
        padding: 12px 16px;
    }

    .mobile-dropdown-menu li a {
        padding: 10px 14px;
    }

    .scroll-to-top {
        bottom: 20px;
        right: 16px;
        width: 40px;
        height: 40px;
    }
}

/* ==============================
   SCROLL TO TOP BUTTON
   ============================== */
.scroll-to-top {
    position: fixed;
    bottom: 32px;
    right: 32px;
    width: 44px;
    height: 44px;
    border-radius: 50%;
    background: var(--accent);
    color: var(--bg-primary);
    border: none;
    cursor: pointer;
    display: flex;
    align-items: center;
    justify-content: center;
    z-index: 100;
    opacity: 0;
    transform: translateY(16px);
    pointer-events: none;
    transition: opacity 0.3s ease, transform 0.3s ease;
    box-shadow: 0 4px 16px var(--alpha-black-30);
}

.scroll-to-top.visible {
    opacity: 1;
    transform: translateY(0);
    pointer-events: auto;
}

.scroll-to-top:hover {
    background: var(--lime-400);
}

.scroll-to-top svg {
    width: 20px;
    height: 20px;
}

/* ========================================
   OVERFLOW FIXES (E2E BUG-002)
   ======================================== */

/* Prevent sections from causing horizontal scroll */
section {
    overflow-x: hidden;
}

/* Полоса фильтров — исключение: из неё выпадают списки. Правило выше
   задаёт overflow только по горизонтали, а браузер делает вторую ось
   прокручиваемой — и список обрезался краем полосы, выглядело так,
   будто он прячется за карточками */
section.trn-filters,
section.pt-filters {
    overflow: visible;
}

/* Fix players-grid cascade: 480px rule was overridden by later 768px rule */
@media (max-width: 480px) {

    .live-matches {
        grid-template-columns: 1fr;
    }

    .courts-grid {
        grid-template-columns: 1fr;
    }
}

/* Global image constraint */
img {
    max-width: 100%;
    height: auto;
}


/* ---- Одно измерение вместо графика ----
   График из единственной точки в углу пустого поля не сообщает ничего —
   на его месте показываем само число. Стиль общий: график рисуется и в
   кабинете, и на странице игрока */
.rc-single {
    display: flex;
    align-items: baseline;
    gap: 10px;
    flex-wrap: wrap;
}

.rc-single-value {
    font-size: var(--fs-xl);
    font-weight: 700;
    color: var(--accent);
    line-height: 1;
}

.rc-single-note {
    font-size: var(--fs-sm);
    color: var(--text-muted);
}

/* ---- Ответ на вызов прямо в уведомлении ----
   Раньше кнопки «Принять» и «Отклонить» жили только в личке Telegram-бота,
   и на сайте ответить на вызов было нечем. */
.site-notif-actions {
    display: flex;
    gap: 8px;
    padding: 0 20px 18px;
}

.site-notif-act {
    flex: 1;
    padding: 10px 14px;
    font-size: var(--fs-sm);
    font-weight: 500;
    /* Скругление как у кнопок сайта, а не восемь пикселей из ниоткуда */
    border-radius: var(--radius-full);
    cursor: pointer;
    border: var(--bw-hair) solid var(--border-light);
    background: none;
    color: var(--text-muted);
    transition: color 0.15s, background 0.15s, border-color 0.15s;
}

/* Согласие — цветом клуба сразу, а не только при наведении: выбор здесь
   неравный, и главное действие должно читаться без наведения мыши */
.site-notif-yes {
    background: var(--accent);
    border-color: var(--accent);
    color: var(--neutral-black);
    font-weight: 600;
}

.site-notif-act:disabled { opacity: 0.5; cursor: default; }

.site-notif-yes:hover:not(:disabled) {
    color: var(--neutral-950);
    background: var(--accent);
    border-color: var(--accent);
}

.site-notif-no:hover:not(:disabled) {
    color: var(--danger);
    border-color: rgba(255, 71, 87, 0.5);
}

.site-notif-answered {
    padding: 0 20px 18px;
    font-size: var(--fs-sm);
    color: var(--text-muted, #8A8A8F);
}

/* ---- Автоподстановка Chrome ----
   Браузер узнаёт сохранённое значение и красит поле своим белым фоном
   поверх наших стилей — на тёмной теме поле вспыхивало белым прямо при
   вводе. Отменить фон напрямую нельзя, поэтому подменяем его внутренней
   тенью, а цвет текста задаём через -webkit-text-fill-color. */
input:-webkit-autofill,
input:-webkit-autofill:hover,
input:-webkit-autofill:focus,
input:-webkit-autofill:active,
textarea:-webkit-autofill,
select:-webkit-autofill {
    -webkit-text-fill-color: var(--text-primary, var(--neutral-white));
    caret-color: var(--text-primary, var(--neutral-white));
    -webkit-box-shadow: 0 0 0 1000px var(--bg-elevated, var(--neutral-850)) inset;
    box-shadow: 0 0 0 1000px var(--bg-elevated, var(--neutral-850)) inset;
    /* Смена фона у автоподстановки не отменяется — только откладывается */
    transition: background-color 100000s ease-in-out 0s;
}

/* Пока открыт список ранее введённых значений, Chrome красит поле не как
   заполненное, а как «предпросмотр» — у него другие внутренние состояния,
   и правило выше их не ловит. Они нестандартные, поэтому вынесены в
   отдельный набор: неизвестный селектор рядом со стандартным обнулил бы
   всё правило целиком. */
input:-internal-autofill-selected,
input:-internal-autofill-previewed {
    -webkit-text-fill-color: var(--text-primary, var(--neutral-white)) !important;
    -webkit-box-shadow: 0 0 0 1000px var(--bg-elevated, var(--neutral-850)) inset !important;
    box-shadow: 0 0 0 1000px var(--bg-elevated, var(--neutral-850)) inset !important;
    background-color: transparent !important;
}


/* ---- Плавающий возврат на страницах-карточках ----
   Игрок, тренер, корт: ссылка закреплена у левого края под шапкой. В потоке
   она стояла под баннером и уезжала при первой прокрутке, а прилипание не
   спасает — оно действует в пределах родителя. Рамки нет: с ней ссылка
   выглядит кнопкой и спорит с шапкой, которая рядом и тоже обведена. */
.kslt-back-wrap.kslt-back-float {
    position: fixed;
    top: 76px;
    left: 24px;
    z-index: 900;
    display: flex;
    align-items: center;
    gap: 6px;
    padding: 8px 14px;
    border: none;
    border-radius: var(--radius-full);
    background: rgba(10, 10, 10, 0.6);
    backdrop-filter: blur(14px);
    -webkit-backdrop-filter: blur(14px);
}

.kslt-back-wrap.kslt-back-float .kslt-back {
    font-size: var(--fs-sm);
}

/* ГРАНИЦА ДОМАШНЯЯ, А НЕ 1300. Было `max-width: 1300px` — число мимо пары
   640 / 992, на которой стоит весь продукт. Ниже 993 по бокам действительно
   нет места, и ссылка возвращается в поток строкой над карточкой. */
@media (max-width: 992px) {
    .kslt-back-wrap.kslt-back-float {
        position: static;
        display: block;
        padding: 12px var(--pad-x);
        border-radius: 0;
        background: var(--bg-primary);
        backdrop-filter: none;
        -webkit-backdrop-filter: none;
    }
}

/* НА ШИРОКИХ ВИДАХ ВОЗВРАТ СТОИТ В КОЛОНКЕ, А НЕ ПЛАВАЕТ ПИЛЮЛЕЙ.
   Глаза Кости 13.10: «надо подложки на кнопке убрать, там цвет отличается».
   Замер нашёл две подложки разом: сама пилюля — `rgba(10,10,10,0.6)` с
   размытием 14 поверх чёрного фона, и `::before` базового правила — полоса
   `--bg-primary` ВО ВСЮ ШИРИНУ ОКНА с границей снизу. Поверх обложки первая
   читалась серым пятном, ниже — другим чёрным.
   Ссылка встаёт по той же ширине, что и содержимое страницы: один левый
   край у возврата, карточки, таблицы и текста.
   КОЛОНКА 840 ОТМЕНЕНА 13.10 СЛОВОМ КОСТИ — «саму страницу кортов и тренера
   не нравится, верни как было». Числа были в порядке, вид — нет; глаза
   решают, и решение не переоткрывается. Подложки при этом остаются
   снятыми: их он просил убрать отдельно. */
@media (min-width: 993px) {
    .kslt-back-wrap.kslt-back-float {
        position: static;
        max-width: var(--container-max);
        margin-inline: auto;
        /* ПОЛЕ ПОД ВЫСТУП СЛОЯ ЦЕЛИ — то же, что у полосы страниц 12.10.
           У пилюли стояло 8, и ей хватало: она плавала поверх всего. В
           потоке слой 44 торчит за обёртку высотой 37, и выступ перехватывают
           соседи — обложка сверху, контейнер снизу. Прогон 13.10 поймал это
           ровно на двух широких видах: цель 39 при коробке 21. 12 — ступень
           шкалы отступов, 12 + 21 + 12 = 45. */
        padding-block: 12px;
        padding-inline: var(--pad-x);
        border-radius: 0;
        background: transparent;
        backdrop-filter: none;
        -webkit-backdrop-filter: none;
    }

    .kslt-back-wrap.kslt-back-float::before {
        display: none;
    }
}

/* Нижняя строка футера: копирайт, документы и подпись — одним ростом.
   Раньше у ссылок не было ни одного правила, они наследовали цвет и размер
   обычной ссылки и выбивались из строки. */
/* Ссылки правовых документов обёрнуты в <nav> ради ориентира для читалки.
   Ловушка: в проекте есть голый селектор `nav` — он написан для шапки и
   задаёт height 64px, display flex и gap. Новая навигация подвала под него
   попала и выросла с 44 до 64, а плашка с 118 до 139. Поэтому свой класс и
   явные свойства, а не надежда на то, что чужое правило не помешает.
   Имя .footer-legal уже занято другим блоком, отсюда -nav. */
.footer-legal-nav {
    display: flex;
    justify-content: center;
    align-items: center;
    height: auto;
    gap: 0;
}

.footer-bottom-links {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 6px 0;
    list-style: none;
}

.footer-bottom-links li { display: flex; }

/* ОСОЗНАННЫЙ ДУБЛЬ, не чинить и не схлопывать.
   У двух русских ссылок подпись стоит дважды — полная и короткая. Это
   решение Кости от 19.09, а не забытая копипаста.
   Замер: на 390 внутри плашки доступно 358, полные русские названия
   занимают 457 — не влезают, переносятся во второй ряд, и цель нажатия
   падает до 17px вместо 44. Короткие дают 333 в один ряд.
   На десктопе места хватает (110 + 164 + 209 в строку), и терять точное
   имя документа там незачем.
   Кыргызский и английский дубля НЕ имеют: их полные названия влезают и
   на 390 — 324 и 241. Если появится четвёртый язык, проверить его
   замером, а не на глаз.

   ГРАНИЦА ПЕРЕКЛЮЧЕНИЯ — 600, а не 768. Сначала она была привязана к 768
   заодно с раскладкой, и на планшете показывались короткие названия при
   736 доступных — места хватало с запасом 279. Причина сокращать другая:
   полные занимают 457, и не влезают они только на узком экране. Считано:
   600 даёт 568 доступных и запас 111; 500 даёт запас 11 — по нашему же
   правилу это не запас; 480 не влезает вовсе, а на 481 появилась бы
   сломанная полоса до ~500. */
.footer-legal-short { display: none; }

.footer-bottom-links a {
    color: var(--text-secondary);
    font-size: var(--fs-xs);
    line-height: 1.3;
    transition: color var(--transition-base);
}

.footer-bottom-links a:hover { color: var(--accent); }

/* Свой стиль фокуса, как у .mobile-nav-links и .trn-chip: до этого у ссылок
   подвала работало только умолчание браузера, и они выпадали из системы */
.footer-bottom-links a:focus { outline: none; }
.footer-bottom-links a:focus-visible {
    outline: var(--bw-thick) solid var(--accent);
    outline-offset: 2px;
    border-radius: 4px;
}

/* Разделяем точкой, а не пробелом: так строка читается как одна */
.footer-bottom-links li + li a::before {
    content: '·';
    margin: 0 10px;
    color: var(--border-light);
}

.footer-bottom p { color: var(--text-secondary); font-size: var(--fs-xs); }

/* ========================================
   ПРИГЛАШЕНИЕ СПОНСОРАМ НА ГЛАВНОЙ
   ========================================
   Под логотипами спонсоров: показали, кто с нами, и тут же позвали встать
   рядом. Цифры — потому что спонсору важна аудитория, а не слова. Подробное
   предложение живёт на отдельной странице, сюда влезает только главное.

   ЭТО РАЗДЕЛ, А НЕ ПЕРЕБИВКА ВНУТРИ РАЗДЕЛА. У него своя секция, свои
   отступы сверху и снизу и своя поверхность — поэтому заголовок говорит
   голосом раздела (26 / Extra Bold), а числа стоят ступенью НИЖЕ него.
   До 24.09 было наоборот: заголовок 21, числа 26 — и первым в блоке про
   спонсорство читалась цифра 350. Замерено на пяти видах, доска 437:9.

   КОРОБКА-ПРЕДЛОЖЕНИЕ СУЩЕСТВУЕТ В ПРОЕКТЕ ДВАЖДЫ: здесь и .sn-final
   на странице спонсоров (sponsors.css). Заливка, радиус и свечение у них
   общие — эталон здесь, за расхождением следит правило-сторож.
   ======================================== */

/* Отступ сверху такой же, как у заголовков разделов от разделительной
   линии — иначе карточка прилипает к полосе */
.sp-offer-wrap { padding: var(--section-y-sm) var(--pad-x) var(--section-y); }

/* Ширины своей у коробки нет: её задаёт --pad-x обёртки, как и всем
   остальным секциям страницы. Было max-width: var(--container-narrow) —
   1100 при контейнере 1400, и на 1280 содержимое блока начиналось на
   125 пикселе против 28 у соседей. Третий левый край на одной странице. */
.sp-offer {
    padding: var(--space-6) var(--space-8);
    background: var(--surface-glass);
    border: var(--bw-hair) solid var(--alpha-lime-20);
    border-radius: var(--radius-xl);
    position: relative;
    overflow: hidden;
}

/* Мягкое свечение из угла — чтобы блок не выглядел плоской плашкой */
.sp-offer::after {
    content: '';
    position: absolute;
    top: -70px;
    right: -70px;
    width: 250px;
    height: 250px;
    border-radius: 50%;
    background: radial-gradient(circle, var(--alpha-lime-12), transparent 70%);
    pointer-events: none;
}
.sp-offer > * { position: relative; z-index: 1; }

/* Голос раздела. Мобильная ступень — в общем правиле заголовков разделов,
   там же, где она у всех остальных: пара «этот заголовок и заголовок
   соседнего раздела» обязана ехать одной границей. */
.sp-offer h2 {
    font-size: var(--fs-xl);
    font-weight: var(--fw-extrabold);
    letter-spacing: -0.03em;
    line-height: var(--lh-tight);
    color: var(--text-primary);
    margin: 0 0 var(--space-2);
}

/* Вступление — обычный текст, а не лид. Ступень lead (21 / 16) заведена
   для абзаца ПОД заголовком раздела; внутри коробки это body. */
.sp-offer-lead {
    color: var(--text-secondary);
    font-size: var(--fs-base);
    line-height: var(--lh-relaxed);
    max-width: 640px;
    margin: 0 0 var(--space-6);
}

.sp-nums {
    display: grid;
    grid-template-columns: repeat(4, 1fr);
    gap: var(--space-6);
    margin-bottom: var(--space-6);
}

/* Ступень НИЖЕ заголовка блока. Stat tile 39:57 сюда не подходит — у него
   своя поверхность, а наши числа лежат внутри уже готовой; но его правило
   «подпись говорит, что именно посчитано» взято дословно. */
.sp-num-val {
    font-size: var(--fs-lg);
    font-weight: var(--fw-extrabold);
    color: var(--accent);
    line-height: var(--lh-tight);
    letter-spacing: -0.02em;
}
.sp-num-cap {
    display: block;
    margin-top: var(--space-2);
    color: var(--text-secondary);
    font-size: var(--fs-sm);
    line-height: 1.3;
}

.sp-gives {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: var(--space-3) var(--space-8);
    margin: 0 0 var(--space-6);
    list-style: none;
    padding: 0;
}
.sp-gives li {
    position: relative;
    padding-left: var(--space-6);
    color: var(--text-secondary);
    font-size: var(--fs-sm);
    line-height: 1.5;
}
/* Галочка нарисована рамками: иконочного набора на сайте ещё нет, и таких
   галочек в коде тринадцать штук. Сведение их в одну — отдельный кусок. */
/* ════════ ГАЛОЧКА-МАРКЕР СПИСКА — ОДНО ОПРЕДЕЛЕНИЕ НА ПРОДУКТ ════════
   Нарисована трижды: здесь (что даём спонсору), `pricing.css:277` и
   `sponsors.css:118`. Две были 14×8, эта — 13×7: уехала на пиксель по
   обеим осям, и на экране галочка в третьем списке мельче остальных.
   Размер в токенах (`--tick-w` · `--tick-h`), ступень взята по
   большинству — 14×8.
   ВЫРАВНИВАНИЕ В ЕМ, А НЕ В ПИКСЕЛАХ: было `top: 6px` у двух и
   `0.35em` у этой. Шесть пиксел верны только при одном кегле строки;
   доля кегля едет вместе с ним и потому одна на все три списка.
   Правило стоит здесь, потому что `style.css` подключён на всех трёх
   страницах и грузится ПЕРВЫМ — своя копия в `pricing.css` или
   `sponsors.css` перекрыла бы его молча. */
.sp-gives li::before,
.pm-feats li::before,
.sn-tier li::before {
    content: '';
    position: absolute;
    left: 0;
    top: 0.35em;
    width: var(--tick-w);
    height: var(--tick-h);
    border-left: var(--bw-thick) solid var(--accent);
    border-bottom: var(--bw-thick) solid var(--accent);
    transform: rotate(-45deg);
}

/* Button 17:82: высота свойством, а не полями; md 44 — минимальная цель
   нажатия; кольцо фокуса обязательно (WCAG 2.4.7). Было 48/40 из паддинга,
   и 40 не проходило порог. */
.sp-cta-btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: var(--space-2);
    height: var(--btn-h-md);
    padding: 0 var(--space-6);
    border-radius: var(--radius-full);
    border: none;
    background: var(--accent);
    color: var(--accent-on);
    font-size: var(--fs-sm);
    font-weight: var(--fw-bold);
    cursor: pointer;
    text-decoration: none;
    transition: transform 0.2s, box-shadow 0.2s;
}
.sp-cta-btn:hover { transform: translateY(-2px); box-shadow: 0 8px 24px var(--alpha-lime-20); }
.sp-cta-btn:focus-visible {
    outline: var(--bw-heavy) solid var(--accent);
    outline-offset: 3px;
}


/* Телефон. Граница та же, на которой переключается заголовок раздела:
   заголовок и число читаются парой и обязаны ехать одной границей.
   Когда общий долг по ломкам будет закрыт и заголовки разделов уедут
   на 640, этот слой уедет вместе с ними — правило заморозки проверяет
   ОТНОШЕНИЕ, а не число. */
@media (max-width: 768px) {
    .sp-offer { padding: var(--space-4); }

    .sp-offer-lead {
        font-size: var(--fs-sm);
        line-height: var(--lh-normal);
        margin-bottom: var(--space-4);
    }

    .sp-nums {
        grid-template-columns: 1fr 1fr;
        gap: var(--space-4);
        margin-bottom: var(--space-4);
    }
    .sp-num-val { font-size: var(--fs-md); }
    .sp-num-cap { font-size: var(--fs-xs); }

    .sp-gives {
        gap: var(--space-3);
        margin-bottom: var(--space-4);
    }
}

/* Телефон по ширине. Список встаёт в одну колонку: на двух колонка выходила
   в 156 пикселей, строка — в 19 знаков, и каждый пункт ложился в три строки.
   Замерено на 390. На 768 колонка 342 — там две колонки читаются, и одна
   колонка стоила бы разделу 64 пикселя высоты. */
@media (max-width: 640px) {
    .sp-gives { grid-template-columns: 1fr; }
}

/* Телефон боком: тесно по высоте — режем воздух, а не кегль */
@media (orientation: landscape) and (max-height: 500px) {
    .sp-offer { padding: var(--space-4) var(--space-6); }
    .sp-offer-lead { margin-bottom: var(--space-4); }
    .sp-nums { margin-bottom: var(--space-4); }
    .sp-gives { margin-bottom: var(--space-4); }
}

/* Логотип спонсора, у которого нет ни ссылок, ни описания: показываем, но
   не притворяемся, что на него можно нажать */
.spon-static {
    cursor: default;
    text-decoration: none;
}
.sponsors-cloud .spon-static:hover { transform: none; }

/* ============================================
   Окно возврата удалённой учётной записи
   ============================================
   Показывается поверх любой страницы сразу после входа, поэтому стили
   держим здесь, а не в кабинете: человек может войти откуда угодно. */

.ar-overlay {
    position: fixed;
    inset: 0;
    z-index: 3000;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: var(--space-sm);
    background: var(--alpha-black-60);
    backdrop-filter: blur(6px);
}

.ar-box {
    width: 100%;
    max-width: 460px;
    padding: var(--space-md);
    border: var(--bw-hair) solid var(--alpha-lime-30);
    border-radius: var(--radius-lg);
    background: var(--bg-elevated);
    box-shadow: var(--shadow-lg);
}

.ar-title {
    margin: 0 0 10px;
    font-size: var(--fs-md);
    font-weight: 600;
    color: var(--text-primary);
}

.ar-text {
    margin: 0 0 var(--space-md);
    color: var(--text-secondary);
    font-size: var(--fs-sm);
    line-height: 1.65;
}

.ar-actions {
    display: flex;
    gap: 10px;
    flex-wrap: wrap;
    justify-content: flex-end;
}

.ar-btn {
    padding: 10px 18px;
    border-radius: var(--radius-full);
    font-size: var(--fs-sm);
    font-weight: 600;
    cursor: pointer;
    border: var(--bw-hair) solid transparent;
    transition: opacity 0.2s ease;
}

.ar-btn:disabled { opacity: 0.5; cursor: default; }

.ar-btn-primary {
    background: var(--accent);
    color: var(--neutral-black);
}

.ar-btn-ghost {
    background: transparent;
    border-color: var(--border-light);
    color: var(--text-muted);
}

.ar-note {
    margin: 12px 0 0;
    min-height: 1.2em;
    font-size: var(--fs-sm);
    color: var(--text-muted);
    text-align: right;
}

@media (max-width: 640px) {
    .ar-actions { flex-direction: column-reverse; }
    .ar-btn { width: 100%; }
    .ar-note { text-align: center; }
}

/* Контакты прямо в уведомлении: принял приглашение — и они тут же,
   без похода в кабинет */
.site-notif-contacts {
    display: flex;
    flex-direction: column;
    gap: 8px;
    padding: 0 20px 16px;
}

.site-notif-contact {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 10px 14px;
    border: var(--bw-hair) solid var(--border-subtle);
    border-radius: var(--radius-md);
    background: var(--bg-card);
    color: var(--text-primary);
    text-decoration: none;
    font-size: var(--fs-sm);
    transition: border-color 0.2s, color 0.2s;
}

.site-notif-contact-icon {
    display: inline-flex;
    align-items: center;
    flex-shrink: 0;
    color: var(--text-muted);
    transition: color 0.2s;
}

.site-notif-contact-icon svg { width: 18px; height: 18px; }

.site-notif-contact-val { flex: 1; }

/* Каждая сеть подсвечивается своим цветом — плашка узнаётся быстрее,
   чем по надписи */
.site-notif-contact:hover { border-color: var(--accent); }
.site-notif-contact:hover .site-notif-contact-icon { color: var(--accent); }
.site-notif-contact--wa:hover { border-color: #25D366; }
.site-notif-contact--wa:hover .site-notif-contact-icon { color: #25D366; }
.site-notif-contact--tg:hover { border-color: var(--brand-telegram); }
.site-notif-contact--tg:hover .site-notif-contact-icon { color: var(--brand-telegram); }
.site-notif-contact--ig:hover { border-color: #E1306C; }
.site-notif-contact--ig:hover .site-notif-contact-icon { color: #E1306C; }

/* ========================================
   НОВОСТИ НА ГЛАВНОЙ
   Три последние статьи. Данные — js/home-news.js,
   обложки общие с разделом новостей
   ======================================== */

/* Отступы у раздела общие, как у остальных: своя ширина ломала ряд — карточки
   уходили в край, а соседние разделы держали поля */


.hn-grid {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    gap: var(--space-lg);
}

.hn-card {
    display: flex;
    flex-direction: column;
    background: var(--bg-card);
    border: var(--bw-hair) solid var(--border-subtle);
    border-radius: var(--radius-xl);
    overflow: hidden;
    text-decoration: none;
    color: inherit;
    transition: transform 0.2s ease, border-color 0.2s ease;
}

.hn-card:hover {
    transform: translateY(-3px);
    border-color: var(--accent);
}

.hn-img {
    position: relative;
    aspect-ratio: 16 / 9;
    background: var(--bg-elevated);
    overflow: hidden;
}

.hn-img img {
    width: 100%;
    height: 100%;
    object-fit: cover;
}

/* Наша обложка, а не присланная афиша: приглушаем, чтобы она читалась как
   фон карточки, а не как случайная картинка */
.hn-own-cover img {
    opacity: 0.85;
}

.hn-cat {
    /* ПЛАШКА КАТЕГОРИИ — ПО КОМПОНЕНТУ Badge 27:53, вариант Neutral Subtle.
       Решение Кости 23.09, вариант A.

       Было: своя таблетка — высота 23, вес 700, разрядка 0.44, поля 3/10,
       подложка rgba(10,10,10,.75), текст ЛАЙМОМ.

       Дело было НЕ в контрасте: подложка 75% — полноценный скрим, и даже на
       белом пятне афиши лайм давал 7.91 при пороге 4.5. Замерено 23.09.
       Дело в лайме. По правилу 109:370 лайм принадлежит одному главному
       действию в поле зрения, а лаймовая плашка висела на КАЖДОЙ обложке:
       три на десктопе, четыре на планшете и телефоне, и прямо над ними —
       лаймовая кнопка записи в карточке турнира. Категория новости не
       действие и не состояние, это таксономия, — значит берёт нейтральный
       тон, ровно по той же логике, по какой нейтральной стала заглушка
       аватара.

       Подложка непрозрачная: Subtle поверх изображения — не плёнка, а
       непрозрачный двойник (Badge 27:53, раздел CONTRAST). Теперь контраст
       не зависит от афиши вовсе — 7.65 при любой обложке.

       inline-flex с фиксированной высотой, а не поля сверху-снизу: высоту
       бейджа задаёт компонент одним числом, и она не плавает от кегля. */
    position: absolute;
    left: 10px;
    bottom: 10px;
    z-index: 2;
    display: inline-flex;
    align-items: center;
    height: 24px;
    padding: 0 var(--space-2);
    border-radius: var(--radius-full);
    background: var(--neutral-surface);
    color: var(--text-secondary);
    box-shadow: inset 0 0 0 1px var(--border-light);
    font-size: var(--fs-2xs);
    font-weight: var(--fw-medium);
    text-transform: uppercase;
    letter-spacing: 0.5px;
}

.hn-body {
    padding: var(--space-md) var(--space-lg) var(--space-lg);
    display: flex;
    flex-direction: column;
    gap: 6px;
    flex: 1;
}

.hn-body h3 {
    font-size: var(--fs-base);
    /* Ступень card title лестницы 86:10 — Bold, а не Semi Bold. Было 600,
       и новости расходились с турнирами на один вес. Решение Кости 23.09. */
    font-weight: var(--fw-bold);
    line-height: 1.3;
    color: var(--text-primary);
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
    overflow: hidden;
}

.hn-body p {
    font-size: var(--fs-sm);
    color: var(--text-secondary);
    line-height: 1.5;
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
    overflow: hidden;
}

.hn-date {
    margin-top: auto;
    font-size: var(--fs-xs);
    /* Было --text-dim, 35% белого: 3.22 при кегле 12, порог 4.5 не проходит.
       Тот же случай, что с «местами» в карточке турнира. 50% даёт 5.31.
       Лестница цвета одна на обе секции: 100 заголовок · 72 анонс · 50 дата. */
    color: var(--text-muted);
}

/* ДВЕ КОЛОНКИ, А НЕ ОДНА. Решение Кости 23.09, доска 347:9.
   Полоса 769–900 никем не рисовалась: 768 попадал в двухколоночный блок ниже,
   а 844 оставался в одной колонке с обложкой 453 — секция выходила 1993 при
   экране 390. Теперь две колонки на всю полосу, а телефон боком забирает
   лента ниже по файлу. */
@media (max-width: 900px) {
    .hn-grid {
        grid-template-columns: 1fr 1fr;
    }
}

/* ЧЕТВЁРТУЮ ПРЯЧЕМ ТОЛЬКО ТАМ, ГДЕ СЕТКА В ТРИ КОЛОНКИ. Раньше условие было
   min-width: 641 — оно описывало не то: на планшете 768 сетка двухколоночная,
   и третья карточка висела одна в ряду. Правило витрины то же, что у турниров:
   показываем столько, сколько заполняет ряды ЦЕЛИКОМ.
     три колонки (901 и шире) — три карточки
     две колонки (900 и уже)  — четыре, два полных ряда
     лента (телефон боком)    — все, рядов там нет */
@media (min-width: 901px) {
    .hn-grid > .hn-card:nth-child(n+4) {
        display: none;
    }
}

/* ==========================================================================
   НОВОСТИ НА ТЕЛЕФОНЕ БОКОМ — ЛЕНТА. Решение Кости 23.09, доска 347:9.
   Та же механика, что у турниров, и условие то же из трёх частей: дефицит
   здесь высота, а не ширина. Замер до правки: одна колонка, обложка 453,
   секция 1993 при экране 390 — пять экранов на три новости.
   Две целые карточки в кадре и кусок третьей: 340 + 12 + 340 + 12 + остаток.
   Выглядывающая часть — единственное, что говорит «листай». */
@media (max-width: 992px) and (orientation: landscape) and (max-height: 500px) {
    .hn-grid {
        display: flex;
        grid-template-columns: none;
        gap: 12px;
        overflow-x: auto;
        scroll-snap-type: x mandatory;
        -webkit-overflow-scrolling: touch;
        scrollbar-width: none;
        padding-bottom: 4px;
    }
    .hn-grid::-webkit-scrollbar { display: none; }

    .hn-grid > .hn-card:nth-child(n) {
        flex: 0 0 340px;
        width: 340px;
        height: 270px;
        scroll-snap-align: start;
        display: flex;
    }
    /* В ленте показываем ВСЕ: прятать нечего, вниз она не растёт */
    .hn-grid > .hn-card:nth-child(n+4) { display: flex; }

    /* Обложка 16:9 при 340 даёт 191 — для карточки в 270 это две трети
       высоты, и на текст не остаётся. Берём 150, как у турниров. */
    .hn-grid .hn-img {
        aspect-ratio: auto;
        height: 150px;
        flex: 0 0 150px;
    }
    .hn-grid .hn-body { padding: 8px 10px 10px; gap: 4px; }
    .hn-grid .hn-card h3 { flex-shrink: 0; }
    .hn-grid .hn-card p { display: none; }

    /* ТИП В ЛЕНТЕ — ТЕЛЕФОННАЯ СТУПЕНЬ, 14 / 11. Обход через Chrome 23.09
       положил рядом две карточки ОДНОЙ ширины 340: у турнира заголовок 14 и
       дата 11, у новости оставались 16 и 12. Никто этого не решал — просто у
       новостей «телефон» отмерен по ширине (≤768), а лента живёт на 844.
       Телефон боком — всё ещё телефон, значит ступень card title телефонная.
       Цепкость поднята :nth-child(n) НАРОЧНО: правило на 16 из блока
       641–768 имеет ту же цепкость и лежит ниже по файлу, а на узком и
       низком экране оба блока могут совпасть. */
    .hn-grid > .hn-card:nth-child(n) h3 { font-size: var(--fs-sm); }
    .hn-grid > .hn-card:nth-child(n) .hn-date { font-size: var(--fs-2xs); }

    /* Последней плиткой — «Все новости». У полосы должен быть конец. */
    .hn-grid > .hn-more { display: flex; }
}

/* Плитка «Все новости» живёт только в ленте: в сетке её роль исполняет
   ссылка в шапке раздела. Устроена как .tc-more у турниров. */
.hn-more {
    display: none;
    flex: 0 0 200px;
    width: 200px;
    height: 270px;
    align-items: center;
    justify-content: center;
    flex-direction: column;
    gap: 6px;
    text-align: center;
    border: var(--bw-hair) dashed var(--border-light);
    border-radius: var(--radius-lg);
    background: var(--surface-glass);
    color: var(--text-primary);
    font-size: var(--fs-sm);
    font-weight: var(--fw-bold);
    text-decoration: none;
    scroll-snap-align: start;
}
.hn-more:hover { border-color: var(--accent); color: var(--accent); }

/* Заголовок новости на ПЛАНШЕТЕ остаётся 16, как у карточки турнира той же
   ширины. Правило мельче лежит в блоке max-width: 768 — а он у новостей
   считается «телефоном», хотя у турниров телефон начинается с 640. Из-за
   этого на 768 две карточки одинаковой ширины 352 имели разные заголовки:
   у турнира 16, у новости 14. Ступень card title одна на обе секции. */
@media (min-width: 641px) and (max-width: 768px) {
    /* Селектор с родителем — НАРОЧНО. Правило мельче лежит ниже по файлу, в
       блоке max-width: 768, и при равной цепкости побеждало бы оно. Поднимать
       цепкость приходится потому, что у новостей «телефон» начинается с 768,
       а у турниров с 640: пока эти границы не сведены, планшет забирает своё
       здесь. Свести границы — отдельная задача, она записана в трекер. */
    .hn-grid .hn-card h3 {
        font-size: var(--fs-base);
    }
}

/* МЕТА В ТЕСНОЙ КАРТОЧКЕ — 11, а не 12. На телефоне новости и турниры стоят
   по две в ряд карточками одной ширины, и дата обязана быть одного кегля:
   у турнира она 11 с 640 (.tc-date в блоке max-width: 640), у новости
   оставалась 12. Правило, которое должно было её мельчить, целилось в <time>,
   а разметка отдаёт <span class="hn-date"> — оно не срабатывало вовсе.
   Граница ровно 640, как у турниров: на 768 обе секции держат 12, и сводить
   их к одному числу здесь нельзя, не тронув турниры. */
@media (max-width: 640px) {
    .hn-card .hn-date {
        font-size: var(--fs-2xs);
    }
}

/* ========================================
   СОСТОЯНИЕ ЗАЯВКИ — ОДИН ВИД НА ВЕСЬ САЙТ
   Карточка на главной, карточка в списках, страница турнира.
   Правится здесь, отображается везде
   ======================================== */

.kslt-status {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    padding: 9px 16px;
    border-radius: 10px;
    border: var(--bw-hair) solid transparent;
    font-size: var(--fs-sm);
    font-weight: 700;
    line-height: 1.1;
    white-space: nowrap;
    cursor: default;
}

.kslt-status::before {
    content: '';
    width: 8px;
    height: 8px;
    border-radius: 50%;
    background: currentColor;
    flex-shrink: 0;
}

/* В сетке — свой лайм: человек играет */
.kslt-status-draw {
    background: var(--alpha-lime-12);
    border-color: var(--alpha-lime-50);
    color: var(--accent);
}

/* Очередь и рассмотрение — янтарный: место есть, но решение ещё не принято */
.kslt-status-wait {
    background: rgba(255, 179, 0, 0.14);
    border-color: rgba(255, 179, 0, 0.45);
    color: #ffb300;
}

/* Отказ — красный, молчать о нём нельзя */
.kslt-status-refused {
    background: rgba(255, 71, 87, 0.14);
    border-color: rgba(255, 71, 87, 0.45);
    color: var(--danger);
}

/* На карточках состояние стоит вместо кнопки записи */
.tc .kslt-status,
.to-compact .kslt-status,
.to-featured .kslt-status {
    align-self: center;
    text-align: center;
}


/* ============================================
   ТЕЛЕФОН: последние правила
   ============================================
   Стоят в конце файла намеренно. Базовые правила этих же блоков
   объявлены ниже по файлу, и при равном весе побеждало последнее —
   мобильные объявления молча не применялись. */

/* ---- ТЕЛЕФОН: ВЕРТИКАЛЬНО И БОКОМ, ОДНИМ БЛОКОМ ----
   Столбцы отваливаются ПО ПОРЯДКУ, а не по вкусу: место, имя и очки держатся
   всегда, дальше по мере сужения уходят форма, В/П, страна. NTRP держим до
   последнего: рейтинг одиночный, и парный разряд виден ТОЛЬКО вторым числом
   в этой колонке — уберём её, и парная игра исчезнет с главной вовсе.
   Граница 640, а не 768: iPad mini вертикально 744, и на 768 он считался бы
   телефоном.

   ТЕЛЕФОН БОКОМ — ТОТ ЖЕ ТЕЛЕФОН, и до 24.09 он лежал отдельным блоком с
   ДОСЛОВНО тем же содержимым. Две копии одного правила — беда на шве; теперь
   оба случая перечислены через запятую в одном условии. По ширине 844 это
   «планшет», а по сути рука держит тот же аппарат, и до сгиба 390 пикселей:
   разделяем не шириной, а высотой и поворотом. Ширины у нас только две,
   640 и 992. */
@media (max-width: 640px),
       (orientation: landscape) and (max-height: 500px) {
    .rk-country,
    .rk-wl,
    .rk-change {
        display: none;
    }

    .rk-row {
        grid-template-columns: 34px 1fr 54px 58px;
    }

}

/* ---- Телефон: карточки турниров на главной ----
   На главной живёт другая карточка, `.tournament-card`, и крупная первая
   занимала ряд целиком: внутри неё два столбца, фотография и текст, и на
   390 пикселях от текста оставалась полоска. Здесь все карточки одинаковые,
   по две в ряд, фотография сверху. */
@media (max-width: 768px) {
    .tournaments-grid .tournament-card-featured {
        grid-column: span 1;
        display: flex;
        flex-direction: column;
        max-height: none;
    }

    .tournaments-grid .tournament-card-featured .tournament-image {
        height: 110px;
    }

    /* Больше четырёх на главной не показываем: дальше — ссылка в раздел */
    .tournaments-grid > .tournament-card:nth-child(n+5) {
        display: none;
    }

    .tournament-card .tournament-info {
        padding: 10px 12px 12px;
    }


    /* ОДНО правило на заголовок новости, а не два. До 23.09 их было два, и оба
       ставили один и тот же кегль: правка одного молча не меняла второй.
       Ступень card title лестницы 86:10, телефон — 14 / Bold. Три строки, а не
       две: подпись новости длиннее названия турнира. */
    .hn-card h3 {
        font-size: var(--fs-sm);
        line-height: 1.3;
        display: -webkit-box;
        -webkit-line-clamp: 3;
        -webkit-box-orient: vertical;
        overflow: hidden;
        /* ОТСТУПЫ ДЕРЖИТ ТЕЛО КАРТОЧКИ, А НЕ ЗАГОЛОВОК. Свой padding: 0 9px
           отбивал название на 18 от края, а дату — на 9: левый край внутри
           карточки шёл лесенкой. Воздух сверху (margin: 8px) переехал в
           padding тела, поэтому по высоте не сдвинулось ничего. */
        margin: 0;
    }

    .hn-card p {
        display: none;
    }

    /* Новости на телефоне шли колонкой по одной: четыре карточки занимали
       полтора экрана. Ставим две в ряд, как турниры */
    .hn-grid {
        grid-template-columns: 1fr 1fr;
        gap: 12px;
    }

    /* Селектор .hn-card time убран: разметка отдаёт <span class="hn-date">
       (js/home-news.js:83), <time> в новостях нет нигде. Правило не срабатывало
       вовсе. Заодно убран .hn-meta — такого класса в разметке тоже нет. */
    .hn-card .hn-body {
        font-size: var(--fs-2xs);
    }

    /* 16 сверху = прежние 8 тела + 8 марджина заголовка, снятого выше */
    .hn-card .hn-body { padding: 16px 9px 10px; }


    /* ЗНАЧКИ. Поля плитки и её зазор отсюда УБРАНЫ: плитка теперь несёт одну
       плотность на все виды — Padding=Dense 12 компонента Card 29:65.
       Осталось только то, что действительно зависит от ширины. */
    .badge-cta-emoji { font-size: var(--fs-xl); }

    /* Из закрытых показываем один ряд: он и так под туманом, а лишние ряды
       только растягивали блок */
    .badges-cta-locked .badge-cta-card:nth-child(n+4) { display: none; }

    /* ПОПРАВКА К СОБСТВЕННОЙ ЗАПИСИ 24.09: широкая ступень эталона была
       поставлена на шкалу, а телефонная осталась 16 / 20 — и 20 не ступень
       (8 · 12 · 16 · 24 · 32 · 40). К эталону подключаются копии, значит
       неверным числом он делится дальше. Горизонталь идёт на следующую
       ступень, 24, и форма «по бокам шире, чем сверху» сохраняется. */
    .badges-cta-box {
        margin-top: -38px;
        padding: var(--space-4) var(--space-6);   /* 16 / 24; было 16 / 20 */
    }

    /* ЛЕНТА ЧИПОВ ОТСЮДА УБРАНА 24.09: она ровно та же, что на широком, и
       теперь объявлена один раз в базовом правиле .rankings-tabs. Скрытая
       полоса прокрутки тоже описана один раз — в общем правиле ниже по
       файлу, вместе с .td-tabs и .hn-scroll. */

    /* Кнопка «наверх» стояла в 32 пикселях от края и налезала на содержимое —
       на телефоне край дороже */
    .scroll-to-top {
        right: 16px;
        bottom: 20px;
    }

    /* Приглашение войти занимало полэкрана: значок 40 пикселей, крупный
       заголовок и кнопка в 28 пикселей отступов по бокам. На телефоне
       это карточка-подсказка, а не главный герой блока */
    /* ПЛАШКА-ГЕЙТ ОТСЮДА УБРАНА 24.09 — вся, вместе с набором #rankings.
       Радиус 18 и 10 мимо шкалы (8 · 12 · 16 · 24), граница 768 мимо
       лестницы. Телефонная ступень теперь одна, в блоке 640 у базового
       правила. */


    /* ─────────────────────────────────────────────────────────────
       Стандарт заголовка раздела на телефоне
       ─────────────────────────────────────────────────────────────
       Разделы главной шли по 25.6 пикселя весом 800, а категории на
       странице турниров — по 20.8 весом 700. Рядом это читалось как два
       разных сайта: «СПОНСОРЫ КСЛТ» выглядели важнее, чем «PRO-MASTERS».

       За образец взят вид со страницы турниров. Правило общее: заголовок
       любого раздела на телефоне — 1.3rem, вес 700. */
    /* СТУПЕНЬ ШКАЛЫ, А НЕ ПЛАВАЮЩЕЕ ЧИСЛО.
       Лестница заголовков в Figma (86:10) — «одна на все 16 страниц» — 22.09
       дополнена мобильной ступенью: заголовок раздела = 21 / Extra Bold.
       Было clamp(1.15rem, 5vw, 1.32rem) при весе 700, то есть 18.4–21.1 и Bold:
       и кегль мимо шкалы, и вес не тот. На десктопе заголовок раздела уже
       26 / 800 и с лестницей сходится — замерено 22.09 на #live. */
    /* .badges-cta-text h2 ОТСЮДА УБРАН 24.09: перебивка — не заголовок раздела,
       и попав в это правило она получала ровно его голос, 21 / Extra Bold.
       Её ступень живёт в её собственном блоке. */
    .section-header h2,
    /* Заголовок блока «Станьте спонсором» — это заголовок раздела, а не
       перебивка внутри него: у блока своя секция, свои отступы и своя
       поверхность. Ступень у него общая, и живёт она здесь, в одном месте. */
    .sp-offer h2,
    section > h2 {
        font-size: var(--fs-lg);   /* 21 */
        font-weight: 800;          /* Extra Bold */
        letter-spacing: -0.03em;
        line-height: 1.1;
    }

    /* Шапка раздела: заголовок слева, ссылка «Все турниры» справа —
       как на широком экране. В столбик она уезжала под заголовок и
       прижималась к левому краю, будто это ещё один подзаголовок.
       Строка с переносом: если заголовок длинный, ссылка опускается
       под него, но остаётся у правого края */
    .section-header {
        flex-direction: row;
        flex-wrap: wrap;
        align-items: center;
        justify-content: space-between;
        gap: 6px 10px;
    }

    .section-header .link-all,
    .section-header > a {
        margin-left: auto;
        font-size: var(--fs-xs);
        white-space: nowrap;
    }

    /* ─────────────────────────────────────────────────────────────
       Стандарт статистики на обложках страниц
       ─────────────────────────────────────────────────────────────
       Цифры стояли по 40 пикселей с промежутком в 80: четыре показателя
       занимали 248 пикселей — больше половины обложки.

       Размеры заданы через clamp, а не числом: между 320 и 640 пикселями
       ширины они меняются плавно, а не прыгают на границе медиазапроса.
       Средняя величина — доля ширины экрана (vw), крайние — пределы.

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

       Селекторы с родителем не для красоты: те же размеры заданы в
       tournaments.css, tournaments-overview.css и battles-overview.css,
       а эти файлы подключаются после style.css и при равном весе
       побеждают. */
    .hero-stats,
    .tournament-hero .tournament-hero-stats,
    .to-hero .tournament-hero-stats,
    .td-hero .td-hero-stats,
    .bo-hero .bo-hero-stats {
        display: grid;
        grid-template-columns: 1fr 1fr;
        gap: clamp(8px, 2.5vw, 14px);
        justify-items: center;
    }

    /* Колонок столько, сколько показателей, но не больше трёх. В жёсткой
       сетке 2×2 три цифры вставали криво: две сверху, третья одна посреди
       пустой строки. Четыре по-прежнему идут 2×2 */
    .hero-stats:has(> :nth-child(3):last-child),
    .tournament-hero .tournament-hero-stats:has(> :nth-child(3):last-child),
    .to-hero .tournament-hero-stats:has(> :nth-child(3):last-child),
    .td-hero .td-hero-stats:has(> :nth-child(3):last-child),
    .bo-hero .bo-hero-stats:has(> :nth-child(3):last-child) {
        grid-template-columns: repeat(3, 1fr);
    }

    /* Четыре показателя тоже идут строкой: сеткой 2×2 они занимали половину
       обложки, а в ряд читаются одним взглядом */
    .hero-stats:has(> :nth-child(4):last-child),
    .tournament-hero .tournament-hero-stats:has(> :nth-child(4):last-child),
    .to-hero .tournament-hero-stats:has(> :nth-child(4):last-child),
    .td-hero .td-hero-stats:has(> :nth-child(4):last-child),
    .bo-hero .bo-hero-stats:has(> :nth-child(4):last-child) {
        grid-template-columns: repeat(4, 1fr);
    }

    /* ДВА ПОКАЗАТЕЛЯ — ПАРОЙ ПО ЦЕНТРУ, А НЕ ПО ПОЛОВИНАМ ОБЛОЖКИ.
       Равные дорожки `1fr 1fr` разводили два числа к краям: на 667
       пикселях между ними стояло 212, а оптический центр пары промахивался
       мимо центра обложки на 12 — числа разной ширины, дорожки одинаковой.
       Дорожки по содержимому плюс `justify-content: center` сажают пару
       ровно в центр на любой ширине, а зазор берётся со шкалы.
       Пять показателей лендинга под это правило не попадают — у них
       второй ребёнок не последний, и база `1fr 1fr` остаётся их. */
    .hero-stats:has(> :nth-child(2):last-child),
    .tournament-hero .tournament-hero-stats:has(> :nth-child(2):last-child),
    .to-hero .tournament-hero-stats:has(> :nth-child(2):last-child),
    .td-hero .td-hero-stats:has(> :nth-child(2):last-child),
    .bo-hero .bo-hero-stats:has(> :nth-child(2):last-child) {
        grid-template-columns: repeat(2, auto);
        justify-content: center;
        column-gap: var(--space-lg);
    }

    .hero-stats:has(> :only-child),
    .tournament-hero .tournament-hero-stats:has(> :only-child),
    .to-hero .tournament-hero-stats:has(> :only-child),
    .td-hero .td-hero-stats:has(> :only-child),
    .bo-hero .bo-hero-stats:has(> :only-child) {
        grid-template-columns: 1fr;
    }

    .hero-stat {
        display: flex;
        flex-direction: column;
        align-items: center;
        gap: 2px;
    }

    .hero-stats .hero-stat-value,
    .tournament-hero-stats .hero-stat-value,
    .td-hero-stats .hero-stat-value,
    .bo-hero-stats .bo-hero-stat-value,
    .bo-hero-stats .hero-stat-value {
        font-size: clamp(1.35rem, 6.4vw, 1.9rem);
        line-height: 1.1;
    }

    .hero-stats .hero-stat-label,
    .tournament-hero-stats .hero-stat-label,
    .td-hero-stats .hero-stat-label,
    .bo-hero-stats .bo-hero-stat-label,
    .bo-hero-stats .hero-stat-label {
        font-size: clamp(0.6rem, 2.7vw, 0.72rem);
        line-height: 1.3;
        letter-spacing: 0.04em;
        text-align: center;
    }


    /* Шапка на телефоне висит поверх страницы, и название «КСЛТ» уезжало
       под неё: первый экран начинался с половины буквы. Отводим под шапку
       её высоту с запасом, а низ ужимаем — содержимое и так длиннее экрана */
    .hero {
        min-height: 0;
    }
}

/* Оформление подвала под палец — вынесено сюда из блока на 768.
   Находка h274. Гармошка переехала на (pointer: coarse) 20.09, а её
   оформление осталось на ширине — и на планшете выходил гибрид:
   структура мобильная, вид десктопный.
   Замерено под пальцем ДО правки, на 1024 и на 844: выравнивание было
   start вместо center, перед «Контактами» висели лишние 24px
   (блоки шли 45 · 45 · 45 · 69), нижняя плашка стояла в строку
   с полями 24/22.5 вместо 12/0/14.
   Условие ТО ЖЕ, что у блока .footer-acc и у складывать() в
   js/script.js. Меняешь здесь — меняй во всех трёх местах. */
@media (any-pointer: coarse), (max-width: 768px) {

    /* Футер шёл сплошной колонкой на 1116 пикселей: пять блоков подряд с
       промежутком в 40. Ставим разделы по два в ряд — «Навигация» рядом с
       «Игрокам», «Спонсорам» рядом с «Контактами», — а шапку с логотипом
       оставляем во всю ширину */
    .footer-content {
        grid-template-columns: 1fr 1fr;
        gap: 18px 14px;
    }

    .footer-brand {
        grid-column: 1 / -1;
    }

    /* «Контакты» — последний блок, и с широкого экрана ему досталось
       растягивание во всю ширину: он вставал один под «Спонсорами»
       вместо того, чтобы встать рядом */
    .footer-block:last-child {
        grid-column: auto;
        margin-top: 0;
    }

    .footer-block h4 {
        font-size: var(--fs-xs);
        margin-bottom: 6px;
    }
    .footer-links { gap: 2px; }

    .footer-links a,
    .footer-contacts a,
    .footer-contacts span {
        line-height: 1.5;
    }

    .footer-desc { line-height: 1.3; }

    /* Нижняя полоса: три строки по центру превращались в 157 пикселей */
    .footer-bottom {
        gap: 6px;
        padding: 12px 0 14px;
    }

    /* Оферта, условия и политика шли от левого края и упирались в него:
       строка не была выровнена и не знала про поля экрана */
    .footer-bottom-links {
        display: flex;
        flex-wrap: wrap;
        justify-content: center;
        align-items: center;
        gap: 4px 10px;
        padding: 0 16px;
        text-align: center;
    }

    /* Цель нажатия 44px. Ссылка была высотой 17 — зона попадания 8px вверх
       и 8 вниз, промах уводил либо в пустоту, либо в соседний документ.
       Псевдоэлементом расширить было нельзя: два ряда стояли в 4px друг от
       друга и области наложились бы на 23px. Поэтому строка, а не подкладка.
       Поля внутри ссылки 0 — иначе ряд не влезает в 358: русские названия
       сокращены до «Оферта · Условия использования · Конфиденциальность»
       = 45 + 141 + 127 + два зазора по 10 = 333. Запас 25 держим запасом.
       flex-wrap оставлен: если перевод вырастет, ряд свалится во второй
       ряд высотой 44, а не обратно в нынешнюю поломку. */
    .footer-bottom-links a {
        display: flex;
        align-items: center;
        justify-content: center;
        min-height: 44px;
    }

    /* Точки между ссылками ставятся через ::before. При переносе строки они
       оставались висеть у края — на трёх ссылках подряд это выглядело как
       мусор. На телефоне строки и так разделены промежутком */
    .footer-bottom-links li + li a::before {
        content: none;
    }
}


/* ГАРМОШКА ПОДВАЛА — там, где палец, и там, где узко.
   =====================================================
   Условие двойное и читается как «или»: сенсорный экран ЛЮБОЙ ширины
   либо узкое окно на компьютере.
   Раньше эти правила лежали внутри @media (max-width: 768px), и это
   оставляло дыру: телефон боком (844) и iPad Pro (1024) шире 768, но
   тыкают в них пальцем. Там подвал разворачивался в колонки со всеми
   списками — на 844 это 988 пикселей высоты при экране 390, почти два с
   половиной экрана.
   Скрипт js/script.js ставит класс footer-acc по такому же условию —
   если поменять одно, надо менять и другое. */
@media (any-pointer: coarse), (max-width: 768px) {
    /* Гармошка: видны только заголовки, списки раскрываются по нажатию.
       Класс footer-acc ставит script.js — если он не отработал, подвал
       остаётся обычным, со всеми ссылками на виду */
    .footer-acc {
        grid-template-columns: 1fr;
        gap: 0;
    }

    .footer-acc .footer-block:not(.footer-brand) {
        border-top: var(--bw-hair) solid var(--alpha-white-08);
        padding: 0;
    }

    /* Значки соцсетей упирались в первую линию гармошки — она шла прямо
       по их нижнему краю */
    .footer-acc .footer-brand {
        padding-bottom: 18px;
    }

    .footer-acc .footer-block h4 {
        position: relative;
        margin: 0;
        /* Строка была 45 пикселей — под палец хватает и 40 */
        padding: 13px 26px 13px 0;
        cursor: pointer;
        user-select: none;
        text-align: left;
        /* Размер был как у ссылок, но жирность 700, заглавные и разрядка
           делали заголовки тяжелее всего подвала */
        font-size: var(--fs-xs);
        font-weight: 600;
        letter-spacing: 0.03em;
        color: var(--text-secondary);
    }

    /* Стрелка вниз, а у раскрытого раздела — вверх */
    .footer-acc .footer-block h4::after {
        content: '';
        position: absolute;
        top: 50%;
        right: 6px;
        /* ШЕВРОН НА ШКАЛЕ. Было 7×7 со штрихом 1.5: половину пиксела
           браузер округляет сам — на обычном экране в 1 или 2 по своему
           усмотрению, на плотном в три физических. Сторона встаёт на
           токен, штрих — на толстую ступень рамки; коробка выросла на
           пиксел, чтобы штрих 2 не съел просвет внутри угла. */
        width: var(--chevron);
        height: var(--chevron);
        margin-top: -5px;
        border-right: var(--bw-thick) solid currentColor;
        border-bottom: var(--bw-thick) solid currentColor;
        transform: rotate(45deg);
        transition: transform 0.2s ease;
        opacity: 0.6;
    }

    .footer-acc .footer-block.открыт h4::after {
        margin-top: -2px;
        transform: rotate(-135deg);
    }

    .footer-acc .footer-block:not(.footer-brand) .footer-links,
    .footer-acc .footer-block:not(.footer-brand) .footer-contacts {
        display: none;
    }

    .footer-acc .footer-block.открыт .footer-links,
    .footer-acc .footer-block.открыт .footer-contacts {
        display: flex;
        flex-direction: column;
        align-items: flex-start;
        padding: 0 0 12px;
        /* Выравнивание влево: по центру ссылки выглядели случайной кучкой,
           а в подвале весь текст по центру — наследство широкого экрана */
        text-align: left;
    }

    .footer-acc .footer-block.открыт .footer-links a,
    .footer-acc .footer-block.открыт .footer-contacts a,
    .footer-acc .footer-block.открыт .footer-contacts span {
        text-align: left;
    }

    /* Цель нажатия 44 и ВНУТРИ раскрытого раздела.
       Гармошка давала 44 заголовку, а ссылки внутри оставались 15 при шаге
       26 и просвете 12 — расширить псевдоэлементом нельзя, области
       наложились бы. Поэтому строка 44, а собственный зазор списка убран:
       иначе шаг стал бы 50 и раскрытый раздел раздулся бы без нужды.
       Раскрыт всегда один раздел, так что подвал целиком не растёт.
       Адрес в контактах — не ссылка и 44 не получает: нажимать там нечего. */
    .footer-acc .footer-block.открыт .footer-links,
    .footer-acc .footer-block.открыт .footer-contacts {
        gap: 0;
    }

    .footer-acc .footer-block.открыт .footer-links a,
    .footer-acc .footer-block.открыт .footer-contacts a {
        display: flex;
        align-items: center;
        min-height: 44px;
    }
}

/* ЦЕЛИ НАЖАТИЯ ТАМ, ГДЕ ПАЛЕЦ, А НЕ ТАМ, ГДЕ УЗКО.
   ================================================
   Правила на 44 сначала жили только в @media (max-width: 768px), и это
   была ошибка рассуждения: ширина окна ничего не говорит о том, чем по
   экрану тыкают. Замерено на iPad Pro 1024×1366 — браузер отвечает
   (pointer: coarse) ДА и (hover: none) ДА, то есть прямо сообщает, что
   указатель грубый. А граница 768 записывала это устройство в компьютеры,
   и подвал отдавал ему 14 ссылок высотой 15 и 18 при шаге 30, плюс три
   ссылки в плашке по 17. Семнадцать целей размером в два миллиметра под
   подушечку пальца в восемь.
   Та же дыра у телефона в горизонтальном положении: iPhone 12 Pro боком —
   844 пикселя, тоже шире 768.
   Поэтому спрашиваем устройство, а не считаем пиксели. На компьютере с
   мышью (pointer: fine) не меняется ничего.
   Правила на 768 ОСТАВЛЕНЫ намеренно: узкое окно на компьютере даёт
   pointer: fine, а автотесты гоняют именно такое окно — убрав ширину, мы
   потеряли бы проверку. */
@media (any-pointer: coarse) {
    .footer-bottom-links a {
        display: flex;
        align-items: center;
        justify-content: center;
        min-height: 44px;
    }

    /* Списки колонок: строка 44 и без собственного зазора, иначе шаг стал
       бы 50 и подвал раздулся бы сверх нужного. Адрес в контактах — не
       ссылка и 44 не получает, нажимать там нечего. */
    .footer-links,
    .footer-contacts {
        gap: 0;
    }

    .footer-links a,
    .footer-contacts a {
        display: flex;
        align-items: center;
        min-height: 44px;
    }
}

/* Короткие подписи — только на узком экране. Полные 457 не влезают в
   доступные (ширина − 32) примерно до 489, поэтому граница 600 с запасом
   111. Подробности и расчёт — у .footer-legal-short выше. */
@media (max-width: 600px) {
    .footer-legal-full { display: none; }
    .footer-legal-short { display: inline; }
}


/* ============================================================
   Полосы прокрутки — один вид на всём сайте
   ============================================================
   Где бы ни появилась прокрутка — сетка турнира, таблица группы,
   расписание, список участников, лента фильтров, — полоса выглядит
   одинаково: тонкая, дорожка чуть светлее фона, ползунок лаймовый.

   Раньше каждый блок стилизовал её по-своему: у сетки была одна полоса,
   у таблицы группы — другая, где-то оставалась системная светлая. Новый
   прокручиваемый блок ничего объявлять не должен — он получает этот вид
   сам. */

* {
    scrollbar-width: thin;
    scrollbar-color: var(--alpha-lime-30) var(--alpha-white-04);
}

*::-webkit-scrollbar {
    width: 6px;
    height: 6px;
}

*::-webkit-scrollbar-track {
    background: var(--alpha-white-04);
    border-radius: 3px;
}

*::-webkit-scrollbar-thumb {
    background: var(--alpha-lime-30);
    border-radius: 3px;
}

*::-webkit-scrollbar-thumb:hover {
    background: var(--alpha-lime-50);
}

                                                                    
                                       
.rankings-tabs::-webkit-scrollbar,
.td-tabs::-webkit-scrollbar{
    display: none;
}

/* ============================================
   ОБВОДКА ФОКУСА — ВСЕГДА НАША
   ============================================ */

/* Браузер сам рисует вокруг нажатой кнопки синюю рамку — свою для Chrome,
   свою для Safari. На тёмной странице с лаймовым акцентом она выглядит
   чужой: на плитках кабинета это было особенно заметно.
   Одно правило на весь сайт вместо десятка точечных.

   Только :focus-visible — обводка показывается при ходьбе с клавиатуры,
   а от нажатия пальцем или мышью не появляется. */

:focus-visible {
    outline: var(--bw-thick) solid var(--accent);
    outline-offset: 2px;
}

/* ============================================
   БОКОВОЕ МЕНЮ НА ТЕЛЕФОНЕ
   ============================================ */

/* Меню занимало 464 пикселя: пункты по 52 при кегле 16 и поля по 40 с
   боков — текст начинался на 56-м пикселе от края экрана, хотя везде на
   сайте отступ 12–16.

   Пункты остаются крупными: в меню палец должен попадать без прицела,
   от 44 пикселей. Но кегль и боковые поля к общей шкале приводим.

   Раньше такая правка лежала в блоке для экранов до 375 — то есть на
   обычном телефоне не работала вовсе. */

/* ПЛАНШЕТ И УЖЕ — 16.10: мобильное меню открывается и на планшете —
   его порог 992 задан отдельно, здесь только размеры пунктов. */
@media (max-width: 992px) {
    .mobile-nav {
        padding: 20px 24px;
    }

    .mobile-nav-links a {
        padding: 12px 16px;
    }

    .mobile-dropdown-menu li a {
        padding: 10px 14px;
    }
}

/* ============================================
   ВЫБОР ЯЗЫКА В МЕНЮ — ОДНОЙ СТРОКОЙ
   ============================================ */

/* Три языка в столбик занимали 192 пикселя — больше, чем весь раздел
   «Инфо» с подпунктами. В строку с флагом и сокращением достаточно:
   «Русский», «Кыргызча», «English» никто не читает, выбирают по флагу.

   Подпись берём из самой ссылки (data-lang="RU"), текст при этом остаётся
   в разметке — его читают программы для незрячих. */

/* ============================================
   ИМЯ В МЕНЮ ПОД АВАТАРОМ
   ============================================ */

/* Имя владельца кабинета стояло приглушённой подписью в 0.8rem — мельче
   пунктов меню под ним. Это заголовок меню: по нему человек понимает, под
   какой учётной записью сидит */
.user-dropdown-header {
    padding: 10px 12px 8px;
    font-size: var(--fs-base);
    color: var(--text-primary);
}

/* ============================================
   ЗАЗОР МЕЖДУ АВАТАРОМ И БУРГЕРОМ
   ============================================ */

/* Кнопка меню стояла вплотную к аватару — ноль пикселей между ними.
   Промахнуться легко: вместо своего профиля открывается боковое меню.
   Разводим на 10; справа у бургера остаётся 12 до края экрана */

/* Граница та же, что у самого бургера (992). Раньше здесь стояло 768, и в
   полосе 769–992 бургер оставался без этой отбивки — но там его и не было.
   Теперь есть, и отбивка нужна там же. */
@media (max-width: 992px) {
    .burger-menu {
        margin-left: 10px;
        margin-right: -8px;
    }
}

/* ============================================
   СПИСОК УВЕДОМЛЕНИЙ НА ТЕЛЕФОНЕ
   ============================================ */

/* Кегли по общей шкале: заголовок был крупнее, чем у разделов кабинета,
   а список висел на 400 пикселей независимо от высоты экрана */

/* ПЛАНШЕТ И УЖЕ: окно уведомлений во всю ширину там, где узко. */
@media (max-width: 992px) {
    .site-notif-dropdown {
        max-height: 60vh;
    }

    .site-notif-head {
        padding: 12px 14px;
    }

    .site-notif-title {
        font-size: var(--fs-sm);
    }

    .site-notif-dropdown .site-notif-item {
        padding: 10px 14px;
    }

    .site-notif-item-title {
        font-size: var(--fs-sm);
    }

    .site-notif-item-msg {
        font-size: var(--fs-xs);
    }

    .site-notif-item-time {
        font-size: var(--fs-2xs);
    }

    .site-notif-dropdown .site-notif-foot {
        padding: 10px 14px;
    }

    .site-notif-foot a {
        font-size: var(--fs-xs);
    }
}

/* ============================================
   ЗАГОЛОВОК РАЗДЕЛА НА ТЕЛЕФОНЕ — ОДИН НА ВЕСЬ САЙТ
   ============================================ */

/* Одно и то же делали пять классов, и каждый со своим размером: страница
   игрока и корты 1.3rem, кабинет и новости 1.2rem, общий section-title
   вовсе clamp(1.4–1.8rem). Отсюда и разнобой между разделами.

   Здесь единственное место, где размер задаётся. Классы перечислены
   дважды намеренно: style.css подключается раньше файлов страниц, и при
   равном весе побеждало бы правило из них — двойной класс поднимает вес.

   Админку не трогаем: ей телефон не нужен, там свои правила. */

@media (max-width: 768px) {
    .section-title.section-title,
    .co-section-title.co-section-title,
    .ct-section-title.ct-section-title,
    .db-section-title.db-section-title,
    .ip-about-section-title.ip-about-section-title,
    .ip-rules-section-title.ip-rules-section-title,
    .news-section-title.news-section-title,
    .pp-section-title.pp-section-title,
    .td-fic-section-title.td-fic-section-title,
    .trn-block-title.trn-block-title {
        font-size: var(--fs-lg);
        font-weight: 700;
    }
}

/* ============================================
   КНОПКИ НА ТЕЛЕФОНЕ — ТРИ СТУПЕНИ
   ============================================ */

/* Кнопки жили каждая со своим размером: «Найти игрока» 54 пикселя при
   кегле 17, «Пригласить» — 44 при кегле 11.5 (крупная кнопка с мелкой
   подписью), вкладки рейтинга 36. Сводим к трём ступеням — по роли, а не
   по странице:

   1. Главная — призыв, ради которого сделан экран.
   2. Обычная — действие в карточке или в форме.
   3. Мелкая — переключатель, вкладка, действие внутри строки.

   Дальше размер правится здесь, в одном месте. Классы перечислены
   дважды: style.css подключается раньше файлов страниц, и при равном весе
   побеждало бы правило оттуда */

/* ПЛАНШЕТ И УЖЕ — 16.10: кнопки по шкале узкого экрана. */
@media (max-width: 992px) {
                           
                                                                 
                                                                         
                                                                    
                                              
    .btn-primary.btn-primary{
        min-height: 48px;
        padding: 14px 24px;
        font-size: var(--fs-base);
    }

                         
                                                                              
                                                                             
                                                                            
                                   
                                                                             
                                                                             
                                                                             
                                                                                
                                                                               
                                                                            
                                                                         
                                                                           
                                                                     
                                                                              
                                                                           
                                                                        
                                                                       
                                                                       
                                                                    
                                                                         
                                                                       
                                                                             
    .btn-secondary.btn-secondary{
        min-height: 40px;
        padding: 11px 16px;
        font-size: var(--fs-sm);
    }

    /* 3. Мелкая — переключатели и вкладки.

       .rankings-tab И .rk-gender ОТСЮДА УБРАНЫ 24.09, вторым случаем после
       .badges-cta-btn. Ступень «мелкая» даёт 32, а у обоих есть свой
       компонент со своей ступенью: вкладка разряда — Filter chip 49:77,
       высота 36; переключатель пола — Segmented control 49:70, дорожка 44,
       это ещё и порог цели нажатия. Высоту теперь задаёт собственное правило.

       В ДОЛГ ПО КНОПКАМ, третья запись: ступени этого блока 48 / 40 / 32 не
       совпадают со ступенями компонента Button 17:82 (28 / 36 / 44 / 52).
       Каждый раз, когда кусок доходит до своей очереди, из блока вынимается
       ещё один селектор. Разбирать надо блок целиком, а не по одному. */
    /* .trn-chip ВЫНУТ 05.10 — четвёртой записью после .badges-cta-btn,
       .rankings-tab и .rk-gender, по заведённому здесь же порядку: кусок
       дошёл до своей очереди — селектор уходит из блока.
       Ступень «мелкая» даёт 32, а у чипа есть своя: он переключатель
       фильтра, и 32 — это ниже цели нажатия 44 по WCAG 2.5.5. Замер
       05.10: на 375 и 768 чипы выходили 32, на 844, 1024 и 1280 — 44, при
       одной и той же роли. Высоту задаёт собственное правило. */
    /* .pl-category-pill ВЫНУТ 06.10 — пятой записью, по заведённому здесь
       порядку: кусок дошёл до своей очереди, селектор уходит из блока.
       Причина та же, что у .trn-chip: ступень «мелкая» даёт 32, а пилюля —
       переключатель разряда, и 32 ниже цели нажатия 44 по WCAG 2.5.5.
       Замер 06.10: на 768 и 375 пилюля выходила 32, на 1280 — 36, при одной
       и той же роли. Высоту задаёт её собственное правило в players.css:
       ступень 36 плюс прозрачный слой цели 44. */
    /* .db-chip ВЫНУТ 12.10 — шестой записью, по заведённому здесь порядку:
       кусок дошёл до своей очереди, селектор уходит из блока. Причина та
       же, что у .trn-chip (05.10) и .pl-category-pill (06.10): ступень
       «мелкая» даёт 32, а чип — переключатель фильтра, и 32 ниже цели
       нажатия 44 по WCAG 2.5.5. Замер 12.10: на 768 и 375 чип выходил 32,
       хотя своё правило в dashboard.css велит 36 — удвоенный класс бил его
       по весу. Высоту задаёт собственное правило: ступень 36 по
       Filter chip 49:77 плюс прозрачный слой цели 44. */
}

/* ============================================================================
   ГЕРОЙ ПОД ШАПКОЙ — держим в самом конце файла

   Правило должно стоять после всех медиазапросов: у них та же специфичность,
   и при равной специфичности побеждает то, что ниже. Иначе на узких экранах
   мобильный @media перебивал отбивку, и «Сезон 2026» уезжал под шапку.
   ========================================================================= */

/* Страница с героем не отбивается сверху — фото само уходит под шапку,
   а отбивку берёт на себя padding героя. Отрицательные margin не используем:
   на мобильных main обнуляет padding, и герой уезжал за верх экрана. */
/* Только десктоп: на узких экранах у main и так нет верхней отбивки,
   а у героя своя мобильная раскладка — туда не лезем. */
/* Главная: фото героя уходит ПОД шапку, поэтому main здесь не отбивается.
   Правило действует на всех ширинах: до 21.09 его хватало только выше 993,
   потому что ниже main и так имел нулевую отбивку. Отбивку убрали — значит
   исключение для героя должно работать везде, иначе герой главной уехал бы
   вниз на высоту шапки. */
/* УСЛОВИЕ СВЁРНУТО 22.09 — ровно так, как разрешала прежняя записка.
   Она гласила: «исключение действует везде, КРОМЕ 769-992; не упрощать,
   не свернув сначала полосу 769-992 у самого героя». Полоса свёрнута:
   у .hero теперь ОДНА отбивка calc(var(--header-h) + var(--space-10)) на
   все ширины, значит герой отбивает шапку сам везде, и main под ним не
   должен отбивать её второй раз ни в одной полосе.
   Замерено на 844x390 ДО свёртки: main padding-top 64 + hero padding-top
   104 = бейдж на 168, то есть 104 под шапкой вместо 40. ПОСЛЕ — 40.
   Меняешь отбивку героя — проверь это правило: они парные. */
main:has(.hero) {
    padding-top: 0;
}

/* Мобильный герой оставлен как был — разбираемся отдельно. */

/* ============================================================================
   ПЕРВЫЙ БЛОК СТРАНИЦЫ НА УЗКИХ ЭКРАНАХ

   На мобильных main не отбивается сверху, а шапка фиксированная и
   полупрозрачная. У героев на всех внутренних страницах — tournament-hero,
   to-hero, io-hero, ip-hero, sv-hero, pt-hero, ct-hero, pr-hero — всего
   24px собственного отступа, и заголовок уезжал под шапку.

   Одно правило вместо восьми: первый блок страницы получает высоту шапки.
   Главная исключена — у её героя своя раскладка.
   ========================================================================= */

/* Одно правило на все ширины. Шапку отбивает main, а это — ЗАЗОР до первой
   строки страницы, и он теперь намеренный везде.

   ОСТОРОЖНО: до 21.09 правило жило только до 768, а выше зазор получался
   побочно — из собственного вертикального отступа секции (--section-y),
   который плавает с шириной. Поэтому зазор и разъезжался с 23 до 45.
   Не возвращать сюда --section-y: это отступ МЕЖДУ секциями, другая работа. */
main > section:first-child:not(.hero) {
    padding-top: var(--header-gap);
}

/* ============================================================================
   ЦЕЛИ НАЖАТИЯ В ШАПКЕ — 44 ТАМ, ГДЕ ПАЛЕЦ

   Правило одно: 44x44 на сенсорном вводе. Apple HIG и WCAG 2.5.5. Минимум
   WCAG 2.5.8 (AA) — 24x24, его мы и так проходили, но 24 это «хоть как-то
   попадает», а не норма.

   ПРИЁМ ТОТ ЖЕ, ЧТО В ПОДВАЛЕ: подпись не трогаем, растёт только область,
   которая ловит палец. В подвале так ссылка высотой 17 стала целью 44, и вид
   не изменился ни на пиксель.

   ОСТОРОЖНО, ЗДЕСЬ ЛЕГКО ОШИБИТЬСЯ — И ИМЕННО ЗДЕСЬ УЖЕ ОШИБАЛИСЬ ДВАЖДЫ.
   Условие — (pointer: coarse), а НЕ ширина окна. Ширина про палец ничего не
   говорит: iPhone 12 Pro боком это 844, iPad Mini боком 1024 — обе шире 768,
   обе сенсорные. Замерено 21.09: на 844 и 1024 бургер не показывается вовсе,
   страница отдаёт ДЕСКТОПНУЮ шапку сенсорному устройству, и все девять её
   элементов были ниже 44 (пункты меню 37 и 41, язык 33, «Войти» 39).
   Ровно эта ошибка стоила нам h270 и h274 в подвале. Не переписывать на
   max-width.

   ЧЕГО ЗДЕСЬ НЕТ НАМЕРЕННО: мыши. На десктопе цель 41 — это нормально,
   указатель точный. Менять высоту пункта меню на мыши — отдельное решение
   (h276, предложение 4), Костей не принятое.
   ========================================================================= */
@media (any-pointer: coarse) {

    /* Знак остаётся 34 в высоту — растёт только область вокруг него. */
    .floating-header .logo {
        min-height: 44px;
    }

    .floating-header .nav-item,
    .floating-header .btn-auth,
    .floating-header .lang-toggle {
        min-height: 44px;
    }

    /* Вложенные ссылки раскрывающихся разделов бургера — тридцать две штуки,
       все были 41.1. display: flex здесь обязателен: у строчного <a>
       min-height не работает вовсе. */
    .mobile-dropdown-menu li a {
        display: flex;
        align-items: center;
        min-height: 44px;
    }

    /* Чипы выбора языка в бургере — были 38.8. */
    .mobile-lang-option {
        min-height: 44px;
    }

    /* Здесь я сначала поднял потолок с 500 до 560 «на всякий случай», считая
       по памяти: восемь ссылок в самом длинном разделе. ЗАМЕР 20.09 показал
       другое — длиннее всех «Инфо», в нём девять ссылок, и раскрытый список
       занимает ровно 396 (9 x 44). До 500 запас 104 пикселя.
       Плюс разделы работают ГАРМОШКОЙ: раскрытие одного закрывает соседний,
       поэтому больше одного списка одновременно не бывает никогда.
       Потолок возвращён к 500 — это измеренная величина, а не запас от
       испуга. Поднимать придётся, если в разделе станет больше одиннадцати
       пунктов (11 x 44 = 484). */
}


/* ============================================================================
   ПЕРВЫЙ ЭКРАН НА ТЕЛЕФОНЕ — ВАРИАНТ «C», ПРИНЯТ КОСТЕЙ 22.09

   Доска: «Landing — first screen on a phone» (315:9, страница Patterns).
   Замерено живьём на iPhone 12 Pro, 390x844, с боевыми данными: герой 991.2
   при окне 844 — за сгибом 147.

   ПОЧЕМУ ЭТО НЕ ПРО ОТСТУПЫ. Все зазоры первого экрана уже на шкале и вместе
   дают 260. Даже обнулив их все, снимаем 260 из 991 — экран не сойдётся.
   Режут два блока: цифры 223.5 и полоса спонсоров 235.

   ЧТО ДЕЛАЕТ ПЕРВЫЙ ЭКРАН: заявление и одно действие. Цифры и логотипы —
   доказательство и обязательство перед спонсорами, а не то, ради чего человек
   открыл сайт. На десктопе они стоят в строку и почти ничего не стоят; на
   телефоне занимали половину.

   Считано по варианту C: герой 991 -> 612, запас до сгиба 232, и видно начало
   следующей секции — экран сам зовёт прокрутить.

   ГРАНИЦА НАМЕРЕННО ПО ШИРИНЕ, А НЕ ПО ПАЛЬЦУ. Обычно условие сенсорного ввода
   у нас (any-pointer: coarse) — но телефон боком 844x390 и планшет боком 1024
   с данными ещё НЕ мерены, и распространять на них решение, принятое по замеру
   390x844, значило бы выдумывать. Померим — расширим условие осознанно.
   ========================================================================= */
@media (max-width: 768px) {

    /* B · ПЯТЬ ЦИФР — ТРИ И ДВА, ВСЕ ВИДНЫ СРАЗУ.
       Было: сетка 1fr 1fr (правило выше, общее для всех обложек) — пять цифр
       в три ряда, 223.5, четверть экрана.

       СНАЧАЛА Я СДЕЛАЛ НЕ ТАК. Первым решением был один ряд с горизонтальной
       прокруткой: 223.5 -> 61, экран сошёлся. Костя посмотрел и сказал то, чего
       я не взвесил: две цифры из пяти прятались за жестом, который большинство
       не сделает, а цифры на первом экране показывают, что сайт живой и им
       пользуются. Прокрутка убрана, все пять видны без жеста.

       ПОЧЕМУ РАЗМЕРЫ ЗАДАНЫ ЗДЕСЬ ЯВНО. Мобильные размеры цифр на обложках
       заданы правилом .hero-stats .hero-stat-value (выше по файлу), а на
       главной классы другие — .stat-value и .stat-label. То есть главная
       НИКОГДА не получала мобильных размеров и рисовала цифры десктопными.
       Найдено замером 22.09. Правило выше не трогаем: его классы живут на
       других обложках. */
    .hero-stats {
        /* РАСТЯНУТЬСЯ ПРИХОДИТСЯ ЯВНО. И .hero, и .hero-content стоят
           align-items: center — значит их дети НЕ растягиваются, а меряются по
           содержимому. Без этого ряд вырастал шире экрана, страница получала
           горизонтальный перелив, и браузер масштабировал её целиком:
           innerWidth становился 549 вместо 390. Поймано замером на переливы,
           глазами такое не видно — страница просто «чуть мельче». */
        align-self: stretch;
        display: flex;
        flex-wrap: wrap;
        justify-content: center;
        align-items: flex-start;
        /* 12 между рядами, 8 между колонками: по вертикали шаг крупнее, иначе
           два ряда читаются как один комок */
        gap: var(--space-3) var(--space-2);
    }

    /* Три в ряд: пятая и четвёртая уходят во второй ряд и центруются сами.
       Ровно треть минус половина промежутка — иначе третья карточка не влезает
       и ряд распадается на 2 + 2 + 1. */
    .hero-stats > .stat {
        flex: 0 0 calc(33.333% - 6px);
        min-width: 0;
        align-items: center;
        text-align: center;
    }

    .hero-stats .stat-value {
        font-size: clamp(1.35rem, 6.4vw, 1.9rem);
        line-height: 1.1;
    }

    .hero-stats .stat-label {
        font-size: clamp(0.6rem, 2.7vw, 0.72rem);
        line-height: 1.3;
        letter-spacing: 0.04em;
    }

    /* Логотипы спонсоров мельче: место, освободившееся под цифры, взято здесь.
       Замерено — карточка 138 -> 120, и нижний край логотипов остаётся над
       сгибом. Ниже сгиба уходит только нижняя отбивка полосы, пустая. */
    .hero-sponsors-carousel .carousel-slide-infinite {
        gap: var(--space-2);
    }

    .hero-sponsors-carousel .carousel-slide-infinite img {
        height: 48px;
    }

    .hero-sponsors-carousel .carousel-slide-infinite > span {
        font-size: var(--fs-xs);
    }

    /* A · ПОЛОСА СПОНСОРОВ — СВОЯ ПОВЕРХНОСТЬ, А НЕ ФОТОГРАФИЯ.
       В разметке она остаётся внутри .hero — и это правильно: на десктопе она
       часть первого экрана и лежит на фото. На телефоне ей даётся собственный
       непрозрачный фон во всю ширину, и фотография героя кончается там, где
       кончается содержимое. Глаз читает её как отдельную секцию под героем.
       Никакого переноса в разметке: одна разметка, разная раскладка. */
    /* НИЖНЮЮ ОТБИВКУ ГЕРОЯ ПОЛОСА СЪЕДАЕТ САМА.
       Первой мыслью я написал здесь .hero { padding-bottom: 0 } — и заморозка
       тут же уронила два правила: «у .hero ровно три объявления» и «ни одно
       другое объявление .hero не трогает padding». Правило сработало ровно
       так, как задумано, поймав меня на том же, что чинили утром: четвёртое
       объявление .hero в четвёртой полосе ширины.
       Поэтому механизм местный: полоса вытягивает себя на 32 вниз
       отрицательным полем и отдаёт те же 32 обратно собственным padding —
       уже своей поверхностью, а не фотографией. .hero не трогаем вовсе. */
    .hero-sponsors-section {
        /* stretch по той же причине, что и у ряда цифр: родитель .hero стоит
           align-items: center, и без этого полоса меряется по содержимому —
           лента спонсоров растягивала её шире экрана. */
        align-self: stretch;
        width: auto;
        max-width: none;
        margin-inline: calc(var(--pad-x) * -1);
        margin-bottom: calc(var(--space-8) * -1);
        /* ВОЗДУХ НАД ШВОМ. Поймал Костя глазами 22.09: подписи цифр выглядели
           обрезанными. Замер показал почему — низ ряда цифр 552.5 и верх тёмной
           поверхности 552.5, зазор РОВНО НОЛЬ. Тридцать два, которые я считал
           отбивкой, лежали ВНУТРИ тёмной полосы, над заголовком «Спонсоры», а
           между фотографией и текстом не было ничего: буквы упирались в шов.
           На десктопе этого не видно — там обе стороны шва лежат на фото.
           base-правило ставит margin-top: auto (прижать к низу, когда есть
           запас); на телефоне запаса нет, и шаг задаётся числом из шкалы. */
        margin-top: var(--space-8);
        padding: var(--space-8) var(--pad-x);
        background: var(--bg);
    }

    /* Те 32 воздуха над швом надо откуда-то взять, иначе экран перестаёт
       помещаться. Берём у самой ленты: у .sponsors-carousel-infinite 40
       собственного padding сверху и снизу — внутри коробки, вокруг логотипов.
       На телефоне полоса уже отбита своими 32, и сорок сверх них — это воздух
       на воздухе.
       Сколько именно: с 24 герой вышел 844.7 при окне 844 — на 0.7 за сгибом,
       то есть последняя строка пикселей полосы всё-таки срезалась. Замерено,
       не прикинуто. Ставим 16: герой уходит под сгиб с запасом, а зазор от
       заголовка «Спонсоры» до логотипов становится 8 + 16 = 24 — шаг из
       шкалы. На десктопе там 48, и это намеренно: там места вдоволь.
       На странице спонсоров лента не трогается. */
    .hero-sponsors-section .sponsors-carousel-infinite {
        padding-top: var(--space-4);
    }
}

/* ============================================================================
   ШИРОКИЙ, НО НЕВЫСОКИЙ ЭКРАН — НОУТБУК. Принято Костей 22.09.

   ЧТО БЫЛО. На ширине 1512 герой имеет высоту 864.8, и она НЕ зависит от высоты
   окна: содержимое всегда длиннее min-height и диктует высоту само. Значит
   первый экран помещается только в окно выше 865.

   Замерено 22.09, что срезается при разной высоте окна:
     900 — помещается целиком
     861 — режется 3.8 ПУСТОЙ фотографии (нижняя отбивка героя 32). Чисто.
     800 — РЕЖУТСЯ ЛОГОТИПЫ спонсоров на 32.8
     760 — режутся логотипы на 72.8
   Правило «сгиб может резать пустое, но не строку и не цифры» на 800 и ниже
   нарушалось. 800 — это обычный ноутбук поменьше.

   ЧТО ДЕЛАЕМ. Трогаем ТОЛЬКО ВОЗДУХ, содержимое не прячем: на десктопе
   строка-расшифровка читается, и убирать её тут незачем. Воздух под шапкой
   40 -> 32, шаг между смысловыми группами 24 -> 16, верхний padding ленты
   40 -> 24. Итого 48: герой 864.8 -> 816.8, низ логотипов 784.8.
   Теперь чисто для любого окна выше 785.

   ПОРОГ 880, А НЕ 900. Сжимать надо только когда надо: окно 881 и выше вмещает
   полный герой (864.8) без правок, и незачем отнимать у него воздух.

   ПОЧЕМУ БЛОК СТОИТ ВЫШЕ СЕНСОРНОГО. На сенсорном устройстве оба условия могут
   совпасть, и обе крутилки будут заданы дважды. Сенсорный блок идёт ПОСЛЕ и
   поэтому побеждает — у пальца свои значения, более сжатые. Меняешь порядок —
   ломаешь планшет боком.
   ========================================================================= */
@media (min-width: 993px) and (max-height: 880px) {
    /* Ноутбучный слой. Секция дышит на ступень тише — ровно как герой над ней. */
    section {
        --section-vozduh: var(--space-8);   /* 32 */
    }

    .hero {
        --hero-vozduh: var(--space-8);   /* 40 -> 32 */
        --hero-shag:   var(--space-4);   /* 24 -> 16 */
    }

    .hero-sponsors-section .sponsors-carousel-infinite {
        padding-top: var(--space-6);     /* 40 -> 24 */
    }
}

/* ============================================================================
   ТЕСНЫЕ ПО ВЫСОТЕ ЭКРАНЫ — ВАРИАНТ «B», ПРИНЯТ КОСТЕЙ 22.09

   Два вида: телефон боком 844x390 и планшет боком 1024x768. Порог 820 выбран
   так, чтобы накрыть оба и не задеть ни телефон вертикально (844 высоты), ни
   планшет вертикально (1024), ни десктоп. Палец в условии обязателен: без него
   обычное окно на десктопе высотой 800 вдруг потеряло бы строку-расшифровку.

   Доска: «Landing — first screen, phone landscape» (318:9, страница Patterns).
   Замерено живьём на 844x390 с боевыми данными: герой 815.7 при окне 390, и
   кнопка «Найти игрока» РАЗРЕЗАНА сгибом пополам — её низ на 427.1.

   ПОЧЕМУ ЭТО НЕ ПРО ОТСТУПЫ. Все зазоры первого экрана вместе дают 124. Окно
   390, шапка 64 — на содержимое остаётся 326, а одни текстовые блоки занимают
   179 плюс кнопка 60. Обнулив весь воздух, кнопку не вытащить.

   ЧТО УБРАЛИ И ПОЧЕМУ ИМЕННО ЭТО. Строку-расшифровку «Кыргызстанское Сообщество
   Любителей Тенниса»: кто мы — уже сказали логотип и «КСЛТ», а полное имя есть
   в подвале и в «О нас». Подзаголовок ОСТАВЛЕН намеренно — это единственная
   строка, которая объясняет, зачем сюда идти.

   Запас после правки — 31. Меньше брать нельзя: английские и кыргызские строки
   длиннее русских, слоган переносится, и запас в семь точек съедается языком.

   УСЛОВИЕ — ВЫСОТА И ПАЛЕЦ. Не ширина: 844 бывает и у окна на десктопе, где
   места по вертикали вдоволь. Режет здесь именно нехватка высоты.
   ========================================================================= */
@media (any-pointer: coarse) and (max-height: 820px) {

    /* Сенсорный тесный. Стоит НИЖЕ ноутбучного намеренно: на планшете боком
       совпадают оба условия, и побеждать должен палец — у него значения сжатее. */
    section {
        --section-vozduh: var(--space-6);   /* 24 */
    }

    /* Крутилки, а не второе объявление padding. Оба значения из шкалы. */
    .hero {
        --hero-vozduh: var(--space-6);   /* 40 -> 24 */
        --hero-shag:   var(--space-4);   /* 24 -> 16 */
    }

    /* Расшифровка аббревиатуры: 24 своей высоты плюс 12 зазора = 36 точек,
       которых и не хватало кнопке. */
    .hero-full {
        display: none;
    }

    /* Верхний padding ленты 40 -> 16. На планшете боком именно этих 24 не
       хватало, чтобы карточки логотипов уместились целиком: замерено 22.09 —
       герой 848.1 при окне 768, логотипы срезаны на 48.1. На телефоне
       вертикально то же значение уже стоит в блоке max-width: 768px; здесь оно
       для тесных по высоте ШИРОКИХ экранов, куда тот блок не доезжает. */
    .hero-sponsors-section .sponsors-carousel-infinite {
        padding-top: var(--space-4);
    }
}

/* ============================================================================
   ВОЗДУХ СЕКЦИИ НА ТЕЛЕФОНЕ И ПЛАНШЕТЕ ВЕРТИКАЛЬНО.
   Стоит НИЖЕ сенсорного слоя намеренно и побеждает его.

   ПОЧЕМУ ОТДЕЛЬНЫМ СЛОЕМ. Сенсорный слой ловит «тесные по высоте» условием
   max-height: 820 — оно придумано для ПЕРВОГО экрана, чтобы герой влез до
   сгиба. Обычный телефон 375x812 попадает под него случайно: он не тесный,
   он просто телефон. А воздух МЕЖДУ секциями от высоты экрана не зависит
   вовсе — там нет сгиба, за который идёт борьба.

   Значение 40 — ровно то, что было на телефоне и раньше. Поведение не
   менялось, менялся способ: раньше его давала сокращённая запись
   padding: 40px 26px, которая молча перебивала все слои сразу.
   ============================================================================ */
@media (max-width: 768px) {
    section {
        --section-vozduh: var(--space-10);   /* 40 */
    }
}

/* ============================================================================
   САМЫЕ КОРОТКИЕ ЭКРАНЫ — СГИБ НЕ РЕЖЕТ ЦИФРЫ. Принято Костей 22.09.

   Отдельный, более узкий слой поверх блока «тесных по высоте»: там мы СЖИМАЕМ,
   здесь — следим, ЧТО именно оказывается на линии сгиба.

   ЧТО БЫЛО. На телефоне боком (844x390) низ кнопки 359.1, верх блока цифр
   375.1, сгиб 390 — из-за сгиба торчали 14.9, то есть ВЕРХУШКИ ЦИФР,
   срезанные пополам. Глаз читает это не как «там есть ещё», а как поломку.
   Ровно то же Костя до этого поймал на логотипах спонсоров.

   ПРАВИЛО, КОТОРОГО НЕ ХВАТАЛО: сгиб может резать пустое место или блок,
   который явно продолжается, но НЕ строку текста и не цифры.

   Шаг «кнопка -> цифры» 16 -> 32: цифры уезжают на 1.1 ниже сгиба, под кнопкой
   остаётся 30.9 чистой фотографии, и первый экран кончается кнопкой.

   ПОЧЕМУ ПОРОГ 440, А НЕ 820. На планшете боком (768 высоты) цифры и так видны
   целиком, а лишние 16 съели бы запас с 19.9 до 3.9 — при том что английские и
   кыргызские строки длиннее русских. Замерено, не прикинуто.
   ========================================================================= */
@media (any-pointer: coarse) and (max-height: 440px) {
    .hero-stats {
        margin-top: var(--space-8);
    }
}

/* ============================================================================
   БЕЙДЖ РОЛИ ПОЛЬЗОВАТЕЛЯ — ОДНО ОПРЕДЕЛЕНИЕ НА ОДНО ПОНЯТИЕ (96в, 19.10)
   Стоял четырьмя формами: класс в admin.css (выс 19), инлайн в users.js
   (12/600/радиус 4, выс 18), .ad-sidebar-role (межбуквенный 1px) и
   .db-sidebar-role (отступы 3·10, выс 21). Ни одна не совпадала с эталоном —
   компонентом Badge 27:53 в Фигме, который 18.09 уже заменил 22 комбинации.
   Живёт ЗДЕСЬ, а не в admin.css, потому что роль рисуется и в админке, и в
   кабинете, а эти два файла друг друга не видят: style.css грузят оба.
   Числа все именами: высота --chip-h 24, кегль --fs-2xs 11, вес --fw-medium.
   ОБВОДКА ВНУТРЕННЕЙ ТЕНЬЮ, А НЕ border: эталон прямо предупреждает — border
   добавляет 2px к фиксированным 24, и замер это подтвердил (вышло 26).
   Межбуквенный 0.5px — НАМЕРЕНО число: лестницы межбуквенных в системе нет,
   а эталон называет именно 0.5 (боковые панели держали 1px сами по себе).
   Контраст, мерено на стенде maket/rol-badzh-zamer.html 19.10 на подложках
   продукта (админка #0f1219, кабинет --bg-base): админ 11.99, менеджер 5.52
   (в кабинете 6.00), игрок 5.84, пользователь 5.18 — порог AA 4.5 для 11px.
   Краска админа ЛАЙМОМ — слово Кости 19.10. Красный не взят намеренно:
   --danger занят ролью ошибки и отказа (круг 21), одно значение на краску.
   ============================================================================ */
.kslt-role {
    display: inline-flex;
    align-items: center;
    min-height: var(--chip-h);
    padding: 0 var(--space-2);
    font-size: var(--fs-2xs);
    font-weight: var(--fw-medium);
    letter-spacing: 0.5px;
    text-transform: uppercase;
    border-radius: var(--radius-full);
    white-space: nowrap;
}

.kslt-role-admin {
    background: var(--alpha-lime-12);
    color: var(--accent);
    box-shadow: inset 0 0 0 var(--bw-hair) var(--alpha-lime-20);
}

.kslt-role-manager {
    background: var(--role-manager-subtle);
    color: var(--role-manager);
    box-shadow: inset 0 0 0 var(--bw-hair) var(--role-manager-border);
}

.kslt-role-player {
    background: var(--info-subtle);
    color: var(--info);
    box-shadow: inset 0 0 0 var(--bw-hair) var(--info-border);
}

.kslt-role-user {
    background: var(--alpha-white-04);
    color: var(--text-muted);
    box-shadow: inset 0 0 0 var(--bw-hair) var(--border-subtle);
}

/* ══════════════════════════════════════════════════════════════════════
   ДВА ПУБЛИЧНЫХ ОКНА, ПЕРЕЕХАВШИЕ ИЗ СТРОК JS (кусок 174, 26.10)

   До 26.10 вид этих окон жил СТРОКАМИ внутри `js/tournament-register.js`
   (17 правил) и `js/doubles-entry.js` (33 правила) и вписывался в <head>
   при первом открытии. Причина, названная автором в коде, честная:
   «держим здесь же — модуль подключается на разные страницы». Цена
   оказалась выше: правило в строке js не читает НИ ОДИН сторож — сторож
   инлайнов смотрит style="…" и cssText, сторожа красок, прозрачностей и
   шкал читают css. Поэтому в этих пятидесяти правилах жили 33 краски мимо
   системы, 16 кеглей мимо шкалы (в rem!), 19 отступов и 9 радиусов числом.

   Оба окна подключены на 16 страницах сайта, и `style.css` есть на всех
   шестнадцати — поэтому правила здесь, а не в новом файле, который пришлось
   бы подключать в шестнадцати местах и забыть в семнадцатом.

   ЧТО ИЗМЕНИЛОСЬ В ВИДЕ, ПОСЧИТАНО: кегли встали на ступени (20.8→21,
   15.2→16, 13.6→14, 13.12→12, 11.52→11, 18.4→18, не заданные 13.33→14,
   иконка 38.4→40); поля ввода 38→44 (--input-h, и кегль 16: ниже 16 Safari
   на iOS зумит страницу при фокусе); кнопки 40→44; крестики 32×32 и 16×24
   → 44×44, потому что цель нажатия, МЕРЕНАЯ ПОПАДАНИЕМ, давала 2 из 6 и
   0 из 6 точек; радиус переключателя 10→12. Краски — те же токены, что
   были, но без запасных чисел вида `var(--text-dim,#888)`: запасное число
   жило своей жизнью и в системе не стояло. Подложка обоих окон встала на
   понятие `--overlay-modal` (0.60): было 0.70 у окна записи и 0.72 у
   парного — ОДНО ПОНЯТИЕ ДЕРЖАЛОСЬ ТРЁХ ЗНАЧЕНИЙ, считая админское 0.60.
   ═══════════════════════════════════════════════════════════════════════ */

/* ── ОКНО «ВОЙДИТЕ, ЧТОБЫ ЗАПИСАТЬСЯ» и его братья (js/tournament-register.js)
   В Фигме это Gate modal (51:60): иконка, заголовок, одна строка, одно
   действие и крестик — других выходов нет. */
.trn-register-modal-overlay {
    position: fixed;
    inset: 0;
    background: var(--overlay-modal);
    backdrop-filter: blur(4px);
    z-index: 10000;
    display: flex;
    align-items: center;
    justify-content: center;
    opacity: 0;
    transition: opacity .2s ease;
}

.trn-register-modal-overlay.active {
    opacity: 1;
}

.trn-register-modal {
    position: relative;
    width: 90%;
    max-width: 420px;
    padding: var(--space-10) var(--space-8);
    text-align: center;
    background: var(--bg-card);
    border: var(--bw-hair) solid var(--border);
    border-radius: var(--radius-lg);
    transform: scale(.95);
    transition: transform .2s ease;
}

.trn-register-modal-overlay.active .trn-register-modal {
    transform: scale(1);
}

/* Крестик — ступень 44: единственный выход, кроме действия. */
.trn-register-modal-close {
    position: absolute;
    top: var(--space-2);
    right: var(--space-2);
    display: flex;
    align-items: center;
    justify-content: center;
    width: var(--btn-h-md);
    height: var(--btn-h-md);
    padding: 0;
    background: none;
    border: none;
    border-radius: var(--radius-full);
    color: var(--text-muted);
    font-size: var(--fs-xl);
    line-height: 1;
    cursor: pointer;
}

.trn-register-modal-close:hover {
    color: var(--text-primary);
}

.trn-register-modal-close:focus-visible {
    outline: var(--bw-thick) solid var(--accent);
    outline-offset: 2px;
}

.trn-register-modal-icon {
    margin-bottom: var(--space-4);
    font-size: var(--fs-3xl);
    line-height: 1;
}

.trn-register-modal-text {
    max-width: 340px;
    margin: 0 auto;
    font-size: var(--fs-base);
    line-height: 1.5;
    color: var(--text-muted);
}

.trn-register-modal-note {
    max-width: 340px;
    margin: var(--space-4) auto 0;
    padding: var(--space-3) var(--space-4);
    border: var(--bw-hair) solid var(--alpha-lime-20);
    border-radius: var(--radius-sm);
    background: var(--alpha-lime-06);
    color: var(--text-secondary);
    font-size: var(--fs-sm);
    line-height: 1.5;
}

.trn-register-modal-btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-height: var(--btn-h-md);
    margin-top: var(--space-6);
    padding: 0 var(--space-8);
    border: none;
    border-radius: var(--radius-sm);
    background: var(--accent);
    color: var(--accent-on);
    font-size: var(--btn-fs-md);
    font-weight: var(--fw-semibold);
    cursor: pointer;
    transition: opacity .2s;
}

.trn-register-modal-btn:hover {
    opacity: .9;
}

.trn-register-modal-btn:focus-visible {
    outline: var(--bw-thick) solid var(--accent);
    outline-offset: 2px;
}

.trn-register-modal-btn.is-error {
    background: var(--danger);
    color: var(--danger-on);
}

.trn-register-modal-actions {
    display: flex;
    gap: var(--space-3);
    justify-content: center;
    flex-wrap: wrap;
}

.trn-register-modal-actions .trn-register-modal-btn {
    margin-top: var(--space-6);
}

.trn-register-modal-btn.is-ghost {
    background: transparent;
    border: var(--bw-hair) solid var(--border);
    color: var(--text-muted);
}

.trn-register-modal-btn.is-ghost:hover {
    border-color: var(--text-muted);
    color: var(--text-primary);
    opacity: 1;
}

/* ── ОКНО ЗАЯВКИ В ПАРНЫЙ ТУРНИР (js/doubles-entry.js) ──────────────── */
.de-overlay {
    position: fixed;
    inset: 0;
    background: var(--overlay-modal);
    z-index: 2000;
    display: flex;
    align-items: center;
    justify-content: center;
    opacity: 0;
    transition: opacity .18s;
}

.de-overlay.active {
    opacity: 1;
}

.de-modal {
    position: relative;
    width: min(420px, 92vw);
    max-height: 88vh;
    overflow-y: auto;
    padding: var(--space-6);
    background: var(--bg-card);
    border: var(--bw-hair) solid var(--border-subtle);
    border-radius: var(--radius-lg);
}

.de-close {
    position: absolute;
    top: var(--space-1);
    right: var(--space-1);
    display: flex;
    align-items: center;
    justify-content: center;
    width: var(--btn-h-md);
    height: var(--btn-h-md);
    padding: 0;
    background: none;
    border: none;
    border-radius: var(--radius-full);
    color: var(--text-muted);
    font-size: var(--fs-xl);
    line-height: 1;
    cursor: pointer;
}

.de-close:hover {
    color: var(--text-primary);
}

.de-close:focus-visible {
    outline: var(--bw-thick) solid var(--accent);
    outline-offset: 2px;
}

.de-trn {
    margin: 0 0 var(--space-4);
    font-size: var(--fs-sm);
    color: var(--text-secondary);
}

.de-row {
    display: flex;
    align-items: center;
    gap: var(--space-3);
    margin-bottom: var(--space-3);
}

.de-label {
    font-size: var(--fs-2xs);
    text-transform: uppercase;
    letter-spacing: .05em;
    color: var(--text-muted);
}

.de-label-block {
    display: block;
    margin: var(--space-4) 0 var(--space-2);
}

.de-me {
    font-weight: var(--fw-semibold);
    color: var(--text-primary);
}

.de-limit {
    padding: var(--space-2) var(--space-3);
    border: var(--bw-hair) solid var(--alpha-lime-20);
    border-radius: var(--radius-sm);
    background: var(--alpha-lime-06);
    color: var(--accent);
    font-size: var(--fs-xs);
}

/* Переключатель «из базы · гость · пока без» — выбор, меняющий форму ниже. */
.de-switch {
    display: flex;
    margin-bottom: var(--space-3);
    border: var(--bw-hair) solid var(--border-subtle);
    border-radius: var(--radius-md);
    overflow: hidden;
}

.de-switch button {
    flex: 1;
    min-height: var(--btn-h-sm);
    padding: var(--space-2) var(--space-1);
    background: transparent;
    border: none;
    color: var(--text-secondary);
    font-size: var(--fs-xs);
    font-weight: var(--fw-semibold);
    white-space: nowrap;
    cursor: pointer;
}

.de-switch button + button {
    border-left: var(--bw-hair) solid var(--border-subtle);
}

.de-switch button.on {
    background: var(--accent);
    color: var(--accent-on);
}

.de-switch button:focus-visible {
    outline: var(--bw-thick) solid var(--accent);
    outline-offset: -2px;
}

/* Поле — ступень 44 и кегль 16: ниже 16 Safari на iOS зумит страницу. */
.de-input {
    width: 100%;
    min-height: var(--input-h);
    padding: 0 var(--space-3);
    box-sizing: border-box;
    background: var(--bg-elevated);
    border: var(--bw-hair) solid var(--border-subtle);
    border-radius: var(--radius-sm);
    color: var(--text-primary);
    font-size: var(--input-fs);
}

.de-input + .de-input {
    margin-top: var(--space-2);
}

.de-input:focus {
    outline: none;
    border-color: var(--accent);
    box-shadow: 0 0 0 var(--bw-thick) var(--accent-subtle);
}

.de-two {
    display: flex;
    gap: var(--space-2);
    margin-top: var(--space-2);
}

.de-two .de-input {
    margin-top: 0;
}

.de-results {
    max-height: 190px;
    overflow-y: auto;
    margin-top: var(--space-2);
}

.de-item {
    display: flex;
    justify-content: space-between;
    align-items: center;
    gap: var(--space-2);
    min-height: var(--btn-h-md);
    padding: var(--space-2) var(--space-3);
    border-radius: var(--radius-sm);
    color: var(--text-primary);
    font-size: var(--fs-sm);
    cursor: pointer;
}

.de-item:hover {
    background: var(--surface-glass-hover);
}

.de-item:focus-visible {
    outline: var(--bw-thick) solid var(--accent);
    outline-offset: -2px;
}

.de-ntrp {
    color: var(--text-muted);
    font-size: var(--fs-xs);
}

.de-empty {
    padding: var(--space-3);
    color: var(--text-muted);
    font-size: var(--fs-sm);
}

.de-note {
    margin: var(--space-3) 0 0;
    font-size: var(--fs-xs);
    line-height: 1.5;
    color: var(--text-secondary);
}

.de-actions {
    display: flex;
    gap: var(--space-3);
    margin-top: var(--space-6);
}

.de-btn {
    flex: 1;
    min-height: var(--btn-h-md);
    padding: 0 var(--space-3);
    border: none;
    border-radius: var(--radius-md);
    font-size: var(--btn-fs-md);
    font-weight: var(--fw-semibold);
    cursor: pointer;
}

.de-btn:focus-visible {
    outline: var(--bw-thick) solid var(--accent);
    outline-offset: 2px;
}

.de-cancel {
    background: transparent;
    border: var(--bw-hair) solid var(--border-subtle);
    color: var(--text-secondary);
}

.de-send {
    background: var(--accent);
    color: var(--accent-on);
}

.de-send:disabled {
    opacity: .6;
    cursor: default;
}

.de-input.de-bad {
    border-color: var(--danger);
    box-shadow: 0 0 0 var(--bw-thick) var(--danger-subtle);
}

.de-bad-note {
    margin: var(--space-2) 0 0;
    font-size: var(--fs-xs);
    color: var(--danger);
}

/* Цель нажатия на узком виде: переключатель становится ступенью 44.
   МЕРЕНО ПОПАДАНИЕМ: на 36 из шести точек вокруг центра ловились четыре. */
@media (max-width: 640px) {
    .de-switch button {
        min-height: var(--btn-h-md);
    }
}
