/* ========================================
   TOURNAMENTS OVERVIEW PAGE STYLES
   Prefix: to-
   ======================================== */

/* Hero — with background photo (img approach, like category pages) */
/* Шапка раздела — той же высоты и того же ритма, что на страницах
   категорий турниров: раздел один, и шапки в нём должны читаться одинаково */
.to-hero {
    /* ШАГ МЕЖДУ СМЫСЛОВЫМИ ГРУППАМИ — КРУТИЛКА, А НЕ ВТОРОЕ ОБЪЯВЛЕНИЕ.
       Тот же приём, что у .hero на главной (--hero-shag): значение меняет
       медиазапрос, объявлений остаётся по одному на свойство */
    --to-hero-shag: var(--space-lg);
    /* ВОЗДУХ — КРУТИЛКА, А НЕ ВТОРОЕ ОБЪЯВЛЕНИЕ padding. Прувер 27.09
       насчитал у обложки ПЯТЬ объявлений padding в пяти полосах — ровно та
       болезнь, которую главная уже вылечила (css/style.css:1631) */
    /* ОБЛОЖКА НЕ ЗАБИРАЕТ БОЛЬШЕ ТРЕТИ ПЕРВОГО ЭКРАНА — решение Кости
       28.09, вариант 1. Было: воздух по --section-y (24→40) и потолок
       460, отчего на десктопе обложка выходила ~600 из 1300, почти
       половина экрана, и до сгиба доезжал край одной карточки. Человек
       пришёл смотреть турниры, а видит заголовок и четыре цифры.
       Ступенью ниже: воздух --section-y-sm (16→24), потолок 360. */
    --to-hero-vozduh:     var(--section-y-sm);
    --to-hero-vozduh-niz: var(--space-sm);

    position: relative;
    padding: var(--to-hero-vozduh) var(--pad-x) var(--to-hero-vozduh-niz);
    text-align: center;
    min-height: 360px;
    display: flex;
    align-items: center;
    justify-content: center;
    overflow: hidden;
    margin-bottom: 24px;
}

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

.to-hero-bg img {
    width: 100%;
    height: 100%;
    object-fit: cover;
}

.to-hero-overlay {
    position: absolute;
    inset: 0;
    background: linear-gradient(
        to bottom,
        var(--alpha-black-50) 0%,
        var(--alpha-black-85) 100%
    );
    z-index: 1;
}

.to-hero-content {
    position: relative;
    z-index: 2;
}

.to-hero h1 {
    /* СТУПЕНЬ, А НЕ ДОЛЯ ШИРИНЫ. Было clamp(1.9rem, 4.4vw, 3.1rem) и давало
       34 на планшете — числа, которого нет ни в одной шкале. Замерено 27.09.

       А 31.10 выяснилось, что ступень тут СВОЯ, и это шов: кегли обложки
       раздела были определены ДВАЖДЫ — токенами `--fs-hero*` (их читает
       страница категории) и собственным набором правил здесь. Пока числа
       совпадали, шва не было видно. Я опустил токен на низком
       горизонтальном — и пара разъехалась: у категории 26, у обзорной 32,
       проба 55 это и поймала.
       СВЕДЕНО К ОДНОМУ ОПРЕДЕЛЕНИЮ. Сверено чтением ДО правки, по всем
       трём ступеням и всем четырём уровням: заголовок 40/32/26,
       подзаголовок 21/18/16, цифра 40/32/21, подпись 14/12/11 — токен и
       обзорная совпадали один в один. Значит вид не меняется нигде,
       кроме низкого горизонтального, где и была беда.
       ПАРА, КОТОРАЯ ЧИТАЕТСЯ КАК ПАРА, ЕДЕТ ОДНОЙ ГРАНИЦЕЙ. */
    font-size: var(--fs-hero);
    font-weight: 800;
    line-height: var(--lh-tight);
    color: var(--text-primary);
    margin-bottom: var(--space-sm);
}

/* Подзаголовок отдельной строкой: раньше «Все категории турниров KSLT» и
   «от начального до профессионального уровня» шли одной фразой через тире */
/* Цвет приглушённый и вес меньше, чем у заголовка: белым и жирным
   подзаголовок сливался с названием в одну нечитаемую массу */
.to-hero-sub {
    /* Тоже ступень: было 17 на телефоне и планшете, 19 на телефоне боком.
       Ступень берётся у токена — см. заголовок выше. */
    font-size: var(--fs-hero-sub);
    font-weight: 600;
    color: var(--text-secondary);
    margin-bottom: 8px;
    text-transform: none;
    letter-spacing: 0;
    line-height: var(--lh-normal);
}

.to-hero p {
    font-size: var(--fs-base);
    color: var(--text-secondary);
    max-width: 560px;
    margin: 0 auto;
    line-height: var(--lh-relaxed);
}

.tournament-hero-stats {
    display: flex;
    justify-content: center;
    gap: var(--space-xl);
    flex-wrap: wrap;
    margin-top: var(--to-hero-shag);
}

.hero-stat {
    text-align: center;
}

.hero-stat-value {
    display: block;
    font-size: var(--fs-3xl);
    font-weight: 800;
    color: var(--accent);
}

.hero-stat-label {
    font-size: var(--fs-sm);
    color: var(--text-muted);
    text-transform: uppercase;
    letter-spacing: 0.5px;
}

/* ЛЕСТНИЦА ОБЛОЖКИ — ПО СТУПЕНЯМ НА КАЖДОМ ВИДЕ.
   Цифры и подписи правит ОБЩЕЕ правило css/style.css:10897 долями ширины:
   6.4vw даёт 25 на телефоне и 30 на планшете. Оно накрывает пять семейств
   обложек сразу, включая закрытые страницы, поэтому чиню не его, а свой
   раздел — а общее правило записано находкой и ждёт своей очереди. */
/* СТУПЕНЬ БЕРЁТСЯ У ТОКЕНА — ДОВЕДЕНО 31.10. Восемь перебивок в медиа я
   снял первой правкой, а эти два БАЗОВЫХ правила оставил со своими
   ступенями — и цифра обложки встала в 40 на всех видах вместо
   32 / 21, потому что медиа её больше не поправляли. Поймала та же
   проба 55: «цифра обложки, ожидали 40, намеряли 32».
   СВЕДЕНИЕ, НЕ ДОВЕДЁННОЕ ДО КОНЦА, ХУЖЕ НЕСВЕДЁННОГО: пока копии
   совпадали, вид был верным; половина копий — и он поехал. */
.to-hero .tournament-hero-stats .hero-stat-value {
    font-size: var(--fs-hero-num);
}

.to-hero .tournament-hero-stats .hero-stat-label {
    font-size: var(--fs-hero-cap);
}


/* Search — sticky bar */
.to-search-wrap {
    position: sticky;
    top: var(--header-h);
    z-index: 30;
    display: flex;
    justify-content: center;
    /* ВОЗДУХ СРЕЗАН ДО СТУПЕНИ, А НЕ ДО НУЛЯ — решение Кости 28.09:
       «срежь воздух по максимуму, только не в 0». Было 24 сверху и снизу,
       стало 12 — следующая ступень шкалы вниз. Ноль поставить нельзя:
       строка липкая, и без поля она при прилипании сливается с шапкой.
       Выигрыш 24 пикселя на каждом виде. */
    /* СВЕРХУ ВОЗДУХА МЕНЬШЕ, ЧЕМ СНИЗУ — Костя 28.09: «сверху поисковой
       строки уменьшить надо ещё воздух». Над строкой уже стоит нижнее
       поле обложки, и два поля подряд читаются как провал; под строкой
       поле своё и единственное. Ступени 8 и 12. */
    padding: var(--space-2) var(--pad-x) var(--space-3);
    background: var(--bg-primary);
    border-bottom: var(--bw-hair) solid transparent;
    transition: border-color 0.2s;
}

.to-search-wrap.stuck {
    border-bottom-color: var(--border-subtle);
}

/* ПОЛЕ — СВОЯ КОРОБКА, ЛУПА ВНУТРИ НЕЁ.
   Было: лупа стояла В ПОТОКЕ перед полем и заезжала на него отрицательным
   отступом −38, а вертикаль ловилась отступом сверху 14. На телефоне для
   того же приходилось писать ещё четыре числа: top 24, right 30 и размер
   17×17. Шесть чисел держали одну лупу, и ни одного из них нет на шкале */
.to-search-field {
    position: relative;
    width: 100%;
    max-width: 480px;
}

.to-search-icon {
    position: absolute;
    right: var(--space-sm);
    top: 50%;
    transform: translateY(-50%);
    color: var(--text-muted);
    pointer-events: none;
}

.to-search-input {
    width: 100%;
    max-width: 480px;
    /* ВЫСОТА КНОПКИ — СВОЙСТВОМ, А НЕ ПОЛЯМИ: ступень md, она же цель
       нажатия. Справа место под лупу: 16 поле + 20 лупа + 12 зазор */
    height: var(--btn-h-md);
    /* Справа место под лупу: поле 16 + лупа 20 + зазор 12. Посчитано из
       частей, а не подобрано: поменяется лупа — поменяется и отступ */
    padding: 0 calc(var(--space-sm) + 20px + var(--space-3)) 0 var(--space-sm);
    background: var(--alpha-white-08);
    backdrop-filter: blur(12px);
    -webkit-backdrop-filter: blur(12px);
    /* ГРАНИЦА ПОЛЯ ВВОДА — ЭТО НЕ УКРАШЕНИЕ, А ГРАНИЦА ЭЛЕМЕНТА УПРАВЛЕНИЯ:
       норма 3, а на --border-subtle выходило 1.25 — поле сливалось с фоном.
       Ступень 25 на лестнице прозрачностей не заведена, и заводить её ради
       одного места не стал: ближайшая существующая 35 даёт 3.23 */
    border: var(--bw-hair) solid var(--alpha-white-35);
    border-radius: var(--radius-full);
    color: var(--text-primary);
    font-family: var(--font-sans);
    font-size: var(--fs-base);
    outline: none;
    /* НЕ all: под него попадала и обводка обхода, и она ПРОЯВЛЯЛАСЬ плавно.
       Playwright 27.09 поймал её на первом кадре толщиной 1 вместо 2 —
       человек, идущий клавиатурой, ровно так её и видит: не сразу.
       Рамка фокуса обязана появляться мгновенно, поэтому перечисляем
       свойства поимённо */
    transition: border-color 0.3s, background-color 0.3s, box-shadow 0.3s;
}

/* ПОДСКАЗКА ЧИТАЕТСЯ, А НЕ УГАДЫВАЕТСЯ. Замерено 27.09 с наложением слоёв:
   на --text-dim контраст выходил 3.23 при норме 4.5 — это текст, и правило
   для текста к нему применяется целиком. На --text-muted проходит */
.to-search-input::placeholder {
    color: var(--text-muted);
}

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

/* Category blocks */
.to-categories {
    /* Ступень карточки в ленте живёт на РАЗДЕЛЕ: карточки ленты лежат вне
       .to-card-grid, и внутри него переменная до них не доходит */
    --to-lenta-h: 336px;

    padding: 0 0 var(--space-xl);
}

/* Тот же вертикальный ритм, что у секций главной: было по 80 пикселей
   сверху и снизу — между двумя категориями зияло 160, и страница
   разъезжалась на пустоту */
.to-category-block {
    padding: var(--section-y-sm) var(--pad-x);
}

/* ОТМЕНЕНО 28.09, ЗАМЕРОМ: первому блоку давали меньшее поле сверху ради
   12 пикселей — и он становился на 9-12 ниже соседей, а ритм блоков
   держится правилом «все блоки категорий одного роста». Двенадцать
   пикселей дешевле, чем ровный ряд. Воздух срезан там, где он ничей, —
   вокруг поиска. */

.to-category-block:nth-child(odd) {
    background: var(--bg-primary);
}

.to-category-block:nth-child(even) {
    background: var(--bg-secondary);
}

.to-category-header {
    display: flex;
    justify-content: space-between;
    align-items: center;
    margin-bottom: var(--space-lg);
    flex-wrap: wrap;
    gap: var(--space-sm);
}

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

.to-category-title span {
    color: var(--accent);
}

/* ЦЕЛЬ НАЖАТИЯ РИСУЕТСЯ РАМКОЙ ВОКРУГ ЭЛЕМЕНТА, А НЕ ЕГО РАЗМЕРОМ.
   Замерено 27.09: ссылка была 112 × 21 — почти вдвое ниже нормы 44.
   Кегль и вид не меняются, растёт только область, в которую можно попасть */
.to-view-all {
    display: inline-flex;
    align-items: center;
    min-height: var(--btn-h-md);
    gap: var(--space-2);
    font-size: var(--fs-sm);
    font-weight: 600;
    color: var(--accent);
    text-decoration: none;
    transition: opacity 0.2s;
}

.to-view-all:hover {
    opacity: 0.8;
}

.to-view-all svg {
    transition: transform 0.2s;
}

.to-view-all:hover svg {
    transform: translateX(3px);
}

/* Card grid: 1 big left + 3 small right */
/* СТУПЕНЬ СЛОТА ЖИВЁТ В РАЗДЕЛЕ ТУРНИРОВ, А НЕ В ОБЩЕМ .to-card-grid:
   тот же класс рисует js/battles-overview.js:445, и переменная на общем
   селекторе подняла бы карточки баттлов до 720. Проверено чтением, а не
   на глаз */
.to-categories .to-card-grid {
    /* СТУПЕНЬ ВЫВЕДЕНА ИЗ ТОГО, ЧТО БЫЛО, А НЕ С ПОТОЛКА СТРОК.
       28.09, поправка к себе. Я взял 224 — потолок самой длинной боковой
       строки (217) — и от него посчитал крупную: 224 × 3 + 24 × 2 = 720.
       Столб вырос с 528 до 720, крупная с 420 до 720. Обе стали БОЛЬШЕ,
       а задача была их ВЫРОВНЯТЬ. Костя: «надо было выровнять, а не
       увеличивать в высоту».
       160 — это ровно та ступень, при которой столб остаётся собой:
       160 × 3 + 24 × 2 = 528, то есть его замеренная высота ДО правки.
       Крупная подрастает с 420 до 528 и забирает те самые 108 пустоты,
       ради которых кусок и затевался. Ничего лишнего не прибавляется. */
    --to-slot: 160px;
    /* Ширина миниатюры боковой карточки. Была зашита числом 210 в трёх
       местах — здесь, в ширине и в ограничении чипа поверх неё. 208 —
       это 8 × 26, то есть ступень, а не подобранное число */
    /* Ширина миниатюры и колонки сведений уехали в сам компонент
       .to-compact-thumb: эта карточка стоит не только в сетке категорий,
       а падать они должны одинаково везде (см. ниже) */
    --to-featured-h: calc(var(--to-slot) * 3 + var(--space-md) * 2);
}

.to-card-grid {
    /* СТУПЕНЬ БОКОВОГО СЛОТА — ОДНА НА ВСЮ СТРАНИЦУ.
       Замерено 26.09 на стенде до правки: крупная карточка была 420 в
       Pro-Masters, 185 в Masters, 191 в Challengers и 160 в Futures, а
       столб сбоку оказывался ВЫШЕ неё — 528 против 420. Каждый блок был
       согласен сам с собой, а вместе они шли четырьмя разными ритмами.

       Теперь высота выводится, а не подбирается: три слота и два зазора
       между ними. Меняется ступень — обе колонки едут вместе, и правило
       заморозки стережёт именно ОТНОШЕНИЕ. */
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: var(--space-lg);
    align-items: stretch;
}

/* Боковая стопка бывает выше большой карточки — когда в блоке много
   турниров. Пробовали ровнять: делить высоту поровну — полосы сплющивались
   и обрезали статус с кнопкой; выносить лишние вниз лентой — они
   растягивались на всю ширину и читались хуже. Пусть колонка будет длиннее */
/* СЛОТ — ТОЧКА ОТСЧЁТА ШИРИНЫ. Полоса перестраивается по СВОЕЙ ширине,
   а не по ширине окна: на 1280 карточка 558, на 1024 — 469, на 768 — 313,
   и три разных случая одной точкой останова не разделить. Точек останова
   по ширине на сайте две — 640 и 992, и заводить третью под планшет боком
   нельзя. */
.to-categories .to-side-stack > .to-slot,
.to-card-slot {
    container-type: inline-size;
    min-width: 0;
}

/* Ячейка ничего не рисует — она только точка отсчёта ширины для карточки
   внутри. Ширину и высоту отдаёт ей же, чтобы сетка и столб не поехали. */
.to-card-slot {
    display: flex;
    min-width: 0;
    /* БЕЗ height: 100% — замер поймал: в столбе ячейка брала высоту ВСЕГО
       столба, и карточка раздувалась со 198 до 595. Растяжка по месту и
       так даётся сеткой: лишнее указание её ломает. */
}

.to-card-slot > .to-compact {
    flex: 1 1 auto;
    min-width: 0;
}

.to-categories .to-side-stack {
    /* ТРИ СЛОТА ВСЕГДА. Пустой слот не схлопывается, а держит место:
       иначе категория с одним турниром давала блок вдвое ниже соседнего,
       и полоса между блоками каждый раз была другой */
    display: grid;
    /* 17.10 · СТУПЕНЬ СЛОТА СНОВА ПОТОЛОК, И ЭТО РЕШЕНИЕ КОСТИ («Б»).
       07.10 ступень сделали МИНИМУМОМ, чтобы строка «осталось N мест» не
       срезала кнопку «Регистрация». Обрезку это починило — и сломало
       ритм: слот с кнопкой рос до 166.8, столб рос за ним, крупная
       карточка тянулась за столбом, и блок категории оказывался на 7
       выше соседнего. Поймала проба `44-turniry` — три падения, по
       одному на язык, И ТОЛЬКО НА 1280: на 1512 ряд помещался.
       ЛЕЧИТСЯ НЕ ПРАВИЛО, А КОМПОНЕНТ: карточка ужата (см. ниже), и
       теперь самый полный слот — с кнопкой и с «осталось мест» —
       укладывается в ступень с запасом. Замерено на стенде
       `maket/bokovaya-kartochka-zamer.html`: слоты 160 · 160 · 160,
       столб 528, обрезки нет. */
    grid-template-rows: repeat(3, var(--to-slot));
    gap: var(--space-md);
}

/* В боковой колонке места мало: статус и отсчёт уводим под текст, чтобы
   название занимало всю ширину и не обрывалось на «Дружеский турнир в…» */
.to-side-stack .to-compact-thumb {
    grid-template-columns: auto minmax(0, 1fr);
    /* МЕСТО ПОД АФИШУ — ПО РАЗМЕРУ АФИШИ, А НЕ ПО ШИРОКОМУ ВИДУ (31.10).
       Компонент отводит слева `--to-thumb + зазор`, и на широком виде это
       120 + 24 = 144. В боковом столбе карточка всего 355 — 144 съедали
       сорок процентов её ширины, и колонке с названием оставалось
       СЕМНАДЦАТЬ точек: текст шёл по букве в столбик, вырастал с 64 до
       254, переливал за дно на 39, а кнопка «Регистрация» уезжала на 95
       ниже дна и НЕ БЫЛА ВИДНА ВОВСЕ — с карточки нельзя было записаться
       на турнир. Замер 31.10 на 768×1024: 31 перелив на девять карточек.
       Те же семнадцать точек записаны в этом файле 05.10 (:748) — беда
       возвращается вторым заходом, потому что чинили следствие
       (`minmax(0, 1fr)`), а не место под афишу.
       96 — не новое число: на телефоне афиша уже 96 (:1999). Боковой
       столб узок на всех видах, а не только на телефоне, поэтому ступень
       берётся здесь, у самого столба.
       ЗАМЕР ПОСЛЕ: 768 — ноль переливов, название 209 вместо 17;
       1024 — ноль вместе с зазором строк ниже. Высота карточки не
       меняется ни там, ни там. */
    --to-thumb: 96px;
    padding-left: calc(var(--to-thumb) + var(--space-2));
}

.to-side-stack .to-compact .to-compact-right {
    grid-column: 1 / -1;
    justify-content: flex-start;
    flex-wrap: wrap;
}

/* ДВЕ СТРОКИ, А НЕ ТРИ (решение «Б», 17.10). Боковая карточка — анонс, а
   не полная запись турнира: третья строка названия стоила слоту 27
   пикселей и выталкивала его за ступень. */
.to-side-stack .to-compact-info h4 {
    -webkit-line-clamp: 2;
}

/* Ниже этого карточка перестаёт читаться: название сжимается в строку, а
   адрес расползается. Если столько не помещается — лучше показать меньше
   карточек, чем расплющить все */
/* Полосы держат свою ВЫСОТУ: растянутые они разъезжались — колонки
   сжимались, название обрезалось, а адрес расползался на три строки.

   07.10 · НО ВЫСОТУ В РЯДУ ДЕРЖИТ `align-self`, А НЕ `flex`. Слот
   `.to-card-slot` — флекс-СТРОКА, и `flex: 0 0 auto` запрещал сжатие по
   ГОРИЗОНТАЛИ. На странице категории это и случилось: карточка женского
   MASTERS встала 866 при слоте 680 — вылезла на 186 пикселей, название
   обрезалось краем окна, кнопка ушла за экран. На обзорной беда не видна:
   там слот — ячейка сетки с треком 680, и она сама не пускает.
   Правило `.to-card-slot > .to-compact { flex: 1 1 auto }` стоит ВЫШЕ по
   файлу и при равном весе (0,2,0) проигрывает: ПОРЯДОК ПРАВИЛ РЕШАЕТ. */
.to-side-stack .to-compact {
    flex: 1 1 auto;
    min-width: 0;
    align-self: start;
}

/* Featured card (big) */
/* ═══ СЛОЙ-ССЫЛКА КРУПНОЙ КАРТОЧКИ ═══════════════════════════════════════
   13.10. Полоса архива получила слой 04.10, а крупная карточка осталась
   `div[data-href]` с общим обработчиком `bindLinks`: в новой вкладке не
   открывалась, клавиатурой не бралась. Порядок слоёв — тот же, что у
   полосы: содержимое 1, слой 2, своё действие 3. */
.to-featured > .to-featured-bg,
.to-featured > .to-featured-overlay,
.to-featured > .to-featured-content {
    position: relative;
    z-index: 1;
}

.to-featured > .to-featured-cover {
    position: absolute;
    inset: 0;
    z-index: 2;
    border-radius: inherit;
    text-decoration: none;
}

.to-featured > .to-featured-cover:focus-visible {
    outline: var(--bw-thick) solid var(--accent);
    outline-offset: -4px;
}

/* Запись — своё действие, выше слоя */
.to-featured .to-register,
.to-featured .btn-calendar {
    position: relative;
    z-index: 3;
}

.to-featured {
    position: relative;
    border-radius: var(--radius-xl);
    overflow: hidden;
    min-height: var(--to-featured-h, 380px);
    display: flex;
    flex-direction: column;
    justify-content: flex-end;
    transition: transform 0.3s ease, box-shadow 0.3s ease;
    border: var(--bw-hair) solid var(--border-subtle);
    cursor: pointer;
}

.to-featured:hover {
    transform: translateY(-4px);
    box-shadow: 0 12px 40px var(--alpha-black-30);
}

.to-featured-bg {
    position: absolute;
    inset: 0;
    z-index: 0;
}

.to-featured-bg img {
    width: 100%;
    height: 100%;
    object-fit: cover;
}

.to-featured-overlay {
    position: absolute;
    inset: 0;
    background: linear-gradient(to top, var(--alpha-black-85) 0%, var(--alpha-black-50) 50%, var(--alpha-black-20) 100%);
    z-index: 1;
}

.to-featured-content {
    position: relative;
    z-index: 2;
    padding: var(--space-lg);
}

.to-featured-date {
    display: inline-flex;
    flex-direction: column;
    align-items: center;
    background: var(--accent);
    color: var(--neutral-black);
    padding: var(--space-2) var(--space-3);
    border-radius: var(--radius-md);
    line-height: var(--lh-none);
    margin-bottom: var(--space-sm);
}

.to-featured-date .to-day {
    font-size: var(--fs-xl);
    font-weight: 800;
}

.to-featured-date .to-month {
    font-size: var(--fs-2xs);
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 1px;
}

/* Взнос на карточке: две суммы через дробь, лаймом не выделяем —
   это не достоинство турнира, а справка */
.to-featured-detail .to-fee {
    white-space: nowrap;
}

/* ЧИП — ОДИН РАЗМЕР НА ВСЕ ЧИПЫ РАЗДЕЛА.
   Было шесть разных у одного и того же по смыслу элемента: 4/12, 5/14,
   4/12, 2/7, 5/7 и 6/12. Компонент `Badge 27:53` в фигме говорит одно:
   высота 24, кегль 11 Medium, uppercase. Высота задаётся СВОЙСТВОМ,
   а не полями — правило продукта */
.to-featured-status,
.to-compact-status,
.to-gender-badge,
.to-norating-badge {
    display: inline-flex;
    align-items: center;
    height: var(--chip-h);
    padding: 0 var(--space-3);
    border-radius: var(--radius-full);
    font-size: var(--fs-2xs);
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    white-space: nowrap;
}

.to-gender-badge {
    background: var(--alpha-black-60);
    color: var(--accent);
    border: var(--bw-hair) solid var(--alpha-lime-50);
    margin-bottom: var(--space-2);
    margin-left: var(--space-2);
    vertical-align: middle;
    backdrop-filter: blur(8px);
    -webkit-backdrop-filter: blur(8px);
}

/* Турнир вне рейтинга — например, дружеские выходного дня.
   Метка приглушённая: это пояснение, а не достоинство турнира. */
.to-norating-badge {
    background: var(--alpha-black-50);
    color: var(--text-secondary);
    border: var(--bw-hair) solid var(--border-subtle);
    margin-bottom: var(--space-2);
    margin-left: var(--space-2);
    vertical-align: middle;
    backdrop-filter: blur(8px);
    -webkit-backdrop-filter: blur(8px);
}

.to-featured-content h3 {
    font-size: var(--fs-lg);
    font-weight: 700;
    color: var(--text-primary);
    margin-bottom: var(--space-xs);
}

.to-featured-meta {
    display: flex;
    flex-wrap: wrap;
    gap: 12px;
    font-size: var(--fs-sm);
    color: var(--text-secondary);
    margin-bottom: var(--space-sm);
}

.to-featured-meta span {
    display: flex;
    align-items: center;
    gap: var(--space-2);
}

.to-featured-meta svg {
    flex-shrink: 0;
    color: var(--text-muted);
}

.to-featured-details {
    display: flex;
    gap: var(--space-md);
    flex-wrap: wrap;
    margin-bottom: var(--space-md);
}

.to-featured-detail {
    font-size: var(--fs-xs);
}

.to-featured-detail .to-label {
    color: var(--text-muted);
    text-transform: uppercase;
    font-size: var(--fs-2xs);
    letter-spacing: 0.5px;
    display: block;
    margin-bottom: 2px;
}

.to-featured-detail .to-value {
    color: var(--text-primary);
    font-weight: 600;
}

.to-featured-detail .to-value.prize {
    color: var(--accent);
}

.to-featured-status {
    margin-bottom: var(--space-sm);
}

.to-featured-status.open {
    background: var(--alpha-black-60);
    border: var(--bw-hair) solid var(--alpha-lime-50);
    color: var(--accent);
    backdrop-filter: blur(8px);
    -webkit-backdrop-filter: blur(8px);
}

.to-featured-status.soon {
    background: var(--alpha-black-60);
    border: var(--bw-hair) solid rgba(255, 159, 67, 0.5);
    color: var(--orange-500);
    backdrop-filter: blur(8px);
    -webkit-backdrop-filter: blur(8px);
}

/* «ИДЁТ» — ОДНА РОЛЬ НА ВЕСЬ ПРОДУКТ (круг 16, 18.10). Роль объявлена в
   `tokens.css` как «менеджер / идёт сейчас» ещё 17.10 — ПРИНЯТОЕ НЕ
   ПЕРЕОТКРЫВАЕТСЯ, его просто не донесли сюда. Было три краски на одно
   состояние: фиолетовая в списке админки (верная), лаймовая в её форме
   (ΔE 164 от верной) и голубой `#00A3FF` числом здесь (ΔE 37). Человек
   видел турнир лаймовым в форме и голубым на сайте. */
.to-featured-status.ongoing {
    background: var(--alpha-black-60);
    border: var(--bw-hair) solid var(--role-manager-ring);
    color: var(--role-manager);
    backdrop-filter: blur(8px);
    -webkit-backdrop-filter: blur(8px);
}

.to-featured-status.closed {
    background: var(--alpha-black-60);
    border: var(--bw-hair) solid var(--alpha-white-20);
    color: var(--text-secondary);
    backdrop-filter: blur(8px);
    -webkit-backdrop-filter: blur(8px);
}

.to-featured-status.past {
    background: var(--alpha-black-60);
    border: var(--bw-hair) solid var(--alpha-white-12);
    color: var(--text-muted);
    backdrop-filter: blur(8px);
    -webkit-backdrop-filter: blur(8px);
}

.to-featured-link {
    display: inline-block;
    /* ВЫСОТА КНОПКИ — СВОЙСТВОМ, А НЕ ПОЛЯМИ. 44 — ступень md и цель
       нажатия, одна на весь продукт (`Button 17:82`) */
    height: var(--btn-h-md);
    padding: 0 var(--space-lg);
    background: var(--accent);
    color: var(--neutral-black);
    font-size: var(--fs-sm);
    font-weight: 700;
    border-radius: var(--radius-md);
    text-decoration: none;
    border: none;
    cursor: pointer;
    transition: background 0.2s, transform 0.2s;
}

.to-featured-link:hover {
    background: var(--accent-hover);
    transform: translateY(-1px);
}

/* Compact card (small, horizontal) */
.to-compact {
    /* СВОЯ СТУПЕНЬ, А НЕ СОДЕРЖИМОЕ — тот же приём, что у карточки
       телефона на обзорной («ступень выше максимума, 8 × 37»).
       Замер 05.10 по шести категориям: карточка меняла рост от того, что
       в ней заполнено. Плашка пола поднимала левую колонку с 64 до 92 и
       карточку со 118 до 142; пустой адрес убирал ещё строку. Ступень
       144 — это 8 × 18, первая выше замеренного максимума 142. */
    min-height: 144px;
    display: grid;
    /* minmax(0, 1fr) У СРЕДНЕЙ КОЛОНКИ — приём из этого же файла (:711).
       Прогон 05.10 на ТЕСТОВОЙ базе: там есть турниры с открытой записью,
       колонка действий с кнопкой раздувается, и на название оставалось
       СЕМНАДЦАТЬ пикселей из 341. На боевой таких турниров нет, и мой
       замер этого не показал — БАЗА ЗАМЕРА РЕШАЕТ, ЧТО ВИДНО. */
    grid-template-columns: auto minmax(0, 1fr) auto;
    gap: var(--space-md);
    align-items: center;
    padding: var(--space-md) var(--space-lg);
    background: var(--bg-card);
    border: var(--bw-hair) solid var(--border-subtle);
    border-radius: var(--radius-lg);
    text-decoration: none;
    cursor: pointer;
    transition: transform 0.2s ease, box-shadow 0.2s ease, border-color 0.2s;
    position: relative;
    background-size: cover;
    background-position: center;
}

/* В СЛОТЕ ВЫСОТУ ДИКТУЕТ СЛОТ, И МЕСТО ПОД ПУСТОЕ ПОЛЕ ТАМ НЕ ДЕРЖИТСЯ.
   Прогон 05.10 поймал: мои min-height у названия и меты (они нужны на
   странице категории, где карточка стоит сама по себе) внутри слота
   переливали содержимое вниз на 11 и 17 пикселей. В комментарии ниже это
   и записано числом: при ступени 160 содержимому остаётся 112. Карточка в
   слоте ужимается под слот — это и есть принятое отношение. */
.to-side-stack .to-compact-info h4,
.to-side-stack .to-compact-sub {
    min-height: 0;
}

/* Высота боковой строки — свойство СЛОТА, а не содержимого: до правки одна
   и та же карточка была 185 в одной категории и 147 в другой */
.to-categories .to-side-stack .to-compact {
    height: 100%;
    /* ДОЖИМАЕМ СВОЁ СОДЕРЖИМОЕ, А НЕ ПОДНИМАЕМ СТУПЕНЬ.
       При ступени 160 строке остаётся 160 − 48 полей = 112, а содержимому
       нужно 125: сведения 80, зазор 24, ряд статуса с кнопкой 44 —
       переливало на 13. Кнопку трогать нельзя, её цель нажатия 44 по
       WCAG 2.5.5. Поэтому поле по вертикали уходит со ступени 24 на
       ступень 16 — и помещается всё, с запасом в 3.
       Правило сужено до столба турниров: тот же .to-compact рисует
       страница баттлов. */
    /* 17.10 · ещё ступень вниз, 16 -> 8: вместе с остальным это и есть
       решение «Б» — ужать карточку, а не поднимать ступень. */
    padding-top: var(--space-2);
    padding-bottom: var(--space-2);
    /* Ряд статуса стоит ПОД сведениями, и зазор между ними тоже
       вертикальный: со ступени 24 на ступень 12. Вместе с полями это
       снимает перелив целиком и оставляет 7 в запасе.
       31.10 · ещё ступень вниз, 12 -> 8. Замер на 1024×768: после
       сужения афиши оставалось 3 точки перелива у ряда записи. Зазор
       встаёт на ТУ ЖЕ ступень, что и поля этой карточки (`--space-2`),
       то есть число не новое. После: ноль переливов, высота 160 та же. */
    row-gap: var(--space-2);
}

/* СРОК РЕГИСТРАЦИИ УХОДИТ ИЗ БОКОВОЙ КАРТОЧКИ (решение «Б», 17.10).
   Три строки сведений — место, срок регистрации, число мест — занимали 80
   пикселей из 112 доступных, и слот перерастал ступень, как только
   появлялась кнопка «Регистрация». Лишняя здесь именно средняя: статус
   «Рег. открыта» стоит слева от неё, а дата турнира — крупно под ним,
   то есть срок дублирует то, что уже сказано дважды. Число мест и адрес
   остаются: их больше нигде на карточке нет.

   ПЕРВАЯ РЕДАКЦИЯ ЭТОЙ ПРАВКИ БЫЛА ХУЖЕ, И ЭТО ПОКАЗАЛ ЗАМЕР: я свёл все
   три пункта в одну строку с многоточием — ритм выровнялся, но на 1280
   адрес «Ала-Тоо», Бишкек сжался до 31 пикселя, то есть перестал
   читаться совсем. Ровный ритм ценой нечитаемых сведений — не починка.
   Замер после: разброс 0, обрезки нет, НИ ОДНА строка не режется. */
.to-side-stack .to-compact-reg {
    display: none;
}

/* ПУСТОЙ СЛОТ ГОВОРИТ, А НЕ МОЛЧИТ. Пунктир отличает «здесь ещё ничего
   нет» от карточки; текст объясняет, что тут появится */
.to-slot-empty {
    /* МЕСТО ДЕРЖИМ, НО НИЧЕГО НЕ РИСУЕМ. Решение Кости 28.09: «только не
       рисуй там ничего, а то то одна большая, другая маленькая — все
       одного размера и стандарта». Пунктир и подпись «здесь появится
       следующий турнир» были моим предположением и отменены: слот
       остаётся пустой клеткой сетки, блоки всех категорий — одного
       роста, а обещаний на экране нет. */
    visibility: hidden;
}

/* Архивные карточки тусклее живых, при наведении — полная яркость */
.to-compact-past,
.to-featured[data-status="past"] {
    opacity: 0.7;
}

.to-compact-past:hover,
.to-featured[data-status="past"]:hover {
    opacity: 1;
}

/* Завершённые: афиша остаётся фоном, но глушим её почти полностью — иначе
   крупные надписи с самой афиши просвечивают ровно под названием турнира */
.to-compact[style*="background-image"]::before {
    content: '';
    position: absolute;
    inset: 0;
    background: rgba(10, 10, 10, 0.955);
    border-radius: inherit;
    z-index: 0;
}

/* Живые турниры и баттлы: афиша миниатюрой слева, текст на чистом фоне.
   Так вид не зависит от того, какую картинку загрузили */
.to-compact-thumb {
    grid-template-columns: auto minmax(0, 1fr) auto;
    gap: var(--space-md);
    align-items: center;
    /* Слева место под афишу: она лежит отдельным слоем и тянется во всю высоту */
    padding-left: calc(var(--to-thumb, 120px) + var(--space-md));
    overflow: hidden;
}

/* Афиша во всю высоту строки. Маленький квадрат читался хуже: на полосе видно
   и цвет, и часть надписи с самой афиши */
/* Афиша прижата к левому краю и тянется во всю высоту карточки. В сетке это
   не выходило: строки у карточки неявные, и «от первой до последней» для них
   не работает — картинка обрывалась на середине */
.to-compact-thumb::before {
    content: '';
    position: absolute;
    left: 0;
    top: 0;
    bottom: 0;
    width: var(--to-thumb, 120px);
    background-image: var(--poster);
    /* Афиши у клуба вертикальные и с текстом. Обрезанные по ширине, они
       резали надписи посередине — «жеский парный тур MASTERS». Вписываем
       целиком: пусть по краям останется поле, зато афиша читается */
    background-size: contain;
    background-repeat: no-repeat;
    background-position: center;
    background-color: var(--alpha-white-04);
}

/* Афиши нет — миниатюру не рисуем и место под неё не держим */
.to-compact-thumb:not([style*="--poster"]) {
    grid-template-columns: auto 1fr auto;
}

.to-compact-thumb:not([style*="--poster"])::before {
    display: none;
}

.to-compact > * {
    position: relative;
    z-index: 1;
}

/* ВХОД НА ТУРНИР — НАСТОЯЩАЯ ССЫЛКА, РАСТЯНУТАЯ НА ВСЮ ПОЛОСУ.
   Вес 0,2,0 — выше общего `.to-compact > *` (0,1,0): иначе слой получил бы
   от него `position: relative` и встал бы в поток.

   ПОРЯДОК СЛОЁВ ВЫВЕДЕН ЗАМЕРОМ, А НЕ ЗДРАВЫМ СМЫСЛОМ. Сначала я положил
   слой НИЖЕ содержимого (`z-index: 0`), чтобы не перекрыть кнопку записи,
   — и замер 04.10 показал, что в середине полосы `elementFromPoint`
   возвращает `SPAN`, а не ссылку: нажатие на название и мету перестало
   вести куда-либо вовсе. До правки туда жал обработчик всей полосы.
   Вчерашнее правило «СЛОЙ ЦЕЛИ МОЖЕТ СЪЕСТЬ САМУ ЦЕЛЬ» работает и в
   обратную сторону: СОДЕРЖИМОЕ СЪЕДАЕТ СЛОЙ, если слой под ним.

   Поэтому слой идёт ВЫШЕ неинтерактивного содержимого (2), а то, что
   жмётся само, поднимается НАД слоем (3). Поднимается ровно то, у чего
   своё действие, — иначе слой снова съел бы цель. */
.to-compact > .to-compact-cover {
    position: absolute;
    inset: 0;
    z-index: 2;
    border-radius: inherit;
}

/* Колонка со своими действиями: кнопка записи и счётчик. Выше слоя */
.to-compact > .to-compact-right {
    z-index: 3;
}

/* ФОКУС ВИДЕН НА ВСЕЙ ПОЛОСЕ: слой прозрачный, и обводка по его границам —
   это рамка вокруг карточки, то есть именно то, что человек выбрал. До
   04.10 полоса не бралась клавиатурой вовсе, и выделять было нечего */
.to-compact > .to-compact-cover:focus-visible {
    outline: var(--bw-thick) solid var(--accent);
    outline-offset: 2px;
}

.to-compact-left {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: var(--space-2);
}

.to-compact-gender-badge {
    display: inline-block;
    font-size: var(--fs-2xs);
    font-weight: 700;
    padding: 2px 8px;
    border-radius: var(--radius-full);
    background: var(--alpha-black-60);
    color: var(--accent);
    border: var(--bw-hair) solid var(--alpha-lime-50);
    white-space: nowrap;
    line-height: var(--lh-snug);
}

.to-compact-norating-badge {
    display: inline-block;
    font-size: var(--fs-2xs);
    font-weight: 700;
    padding: 2px 8px;
    border-radius: var(--radius-full);
    background: var(--alpha-black-50);
    color: var(--text-secondary);
    border: var(--bw-hair) solid var(--border-subtle);
    white-space: nowrap;
    line-height: var(--lh-snug);
    margin-top: var(--space-2);
}

.to-compact:hover {
    transform: translateY(-2px);
    box-shadow: 0 8px 24px var(--alpha-black-30);
    border-color: var(--border-light);
}

.to-compact-date {
    display: flex;
    flex-direction: column;
    align-items: center;
    line-height: var(--lh-none);
    min-width: 44px;
}

.to-compact-date .to-day {
    font-size: var(--fs-lg);
    font-weight: 800;
    color: var(--accent);
}

.to-compact-date .to-month {
    font-size: var(--fs-2xs);
    font-weight: 600;
    text-transform: uppercase;
    color: var(--text-muted);
    letter-spacing: 1px;
}

.to-compact-info {
    min-width: 0;
}

/* Название — до двух строк. Обрезка в одну строку съедала половину имени
   турнира: «Дружеский т…» не говорит ни о чём */
.to-compact-info h4 {
    font-size: var(--fs-base);
    font-weight: 600;
    /* ОДНА СТУПЕНЬ НА ВСЕ ВИДЫ. Замер 04.10: при одном кегле 16
       межстрочный выходил 24 на 1024 и 1280 и 20 на 768 — пара
       «название + мета» ехала двумя разными границами */
    line-height: var(--lh-snug);
    /* НАЗВАНИЕ ДЕРЖИТ ДВЕ СТРОКИ, ДАЖЕ ЕСЛИ ЗАНЯЛО ОДНУ.
       Оно и так обрезано двумя (-webkit-line-clamp ниже), то есть две —
       его настоящий предел. Без этого высота карточки оставалась
       функцией от длины имени: замер 05.10 дал 142 против 160 в одной
       категории на телефоне и 118 против 142 у дружеских на десктопе.
       Мера в em, а не в пикселях: кегль названия на телефоне другой, и
       две строки должны считаться от СВОЕГО кегля. */
    min-height: calc(2 * var(--lh-snug) * 1em);
    color: var(--text-primary);
    margin-bottom: var(--space-2);
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
    overflow: hidden;
}

/* СТРОКА МЕТЫ ДЕРЖИТ СВОЁ МЕСТО, КОГДА АДРЕСА НЕТ.
   УМОЛЧАНИЕ — СВОЙСТВО ДАННЫХ, А НЕ РАЗМЕТКИ: место под строку есть
   всегда, занята она или нет. Правило в общем блоке, а не в телефонном:
   замер 05.10 показал, что у дружеских мета пуста и на 768, и на 1280. */
.to-compact-sub {
    min-height: calc(var(--fs-xs) * 1.5);
    display: flex;
    align-items: center;
    gap: var(--space-3);
    font-size: var(--fs-xs);
    color: var(--text-muted);
    flex-wrap: wrap;
}

.to-compact-sub svg {
    flex-shrink: 0;
}

.to-compact-sub span {
    display: inline-flex;
    align-items: center;
    gap: var(--space-2);
}

.to-compact-reg {
    color: var(--accent);
    font-size: var(--fs-xs);
}

.to-compact-slots {
    font-size: var(--fs-xs);
    color: var(--text-muted);
}

/* Осталась треть мест или запись закрыта — цифру видно сразу */
/* Этот показатель ЖИВОЙ: класс ставит js/tournaments-overview.js:906.
   !important снят — правило и так стоит ПОСЛЕ .to-compact-slots с тем же
   весом и выигрывает порядком. Краска #FFB020 осталась числом намеренно:
   ближайший токен --warning это #FF9F43, другой оттенок, и менять его без
   твоих глаз нельзя. Записано открытым. */
.to-slots-tight {
    color: var(--warning);
    font-weight: 600;
}

.to-compact-right {
    display: flex;
    flex-direction: column;
    align-items: flex-end;
    gap: var(--space-2);
    flex-shrink: 0;
}

/* ПУСТОЙ БЛОК НЕ ЗАНИМАЕТ ЯЧЕЙКУ (26.10, кусок 182). В правом блоке полосы
   живут две вещи: кнопка записи — только при `registration_open` — и отсчёт.
   У прошедшего турнира нет ни одной, и блок уезжает в разметку пустым: замер
   на обзоре турниров — 13 полос из 13, у каждой он занимал ячейку грида во
   всю ширину (494px при нулевой высоте). Запись 07.10 говорила «у шести» —
   их тринадцать.
   ПОЧЕМУ CSS, А НЕ JS: условие в сборщике разметки добавляло продукту ещё
   одну склейку с разметкой и ещё одну голую переменную в ней — чужие
   храповики «данных в разметке» на это и упали, и были правы: их число
   должно убывать, а не расти ради опрятности. Здесь же вид решается домом
   вида, одной строкой и без новых чисел.
   ВИД НЕ МЕНЯЕТСЯ, И ЭТО ЗАМЕРЕНО ДО ПРАВКИ: блок снимался прямо в DOM у
   всех тринадцати полос, с возвратом на место, и высота полосы осталась
   160px — вторую строку грида съедает минимальная высота полосы. */
.to-compact-right:empty { display: none; }


.to-compact-status.open {
    background: var(--alpha-black-60);
    border: var(--bw-hair) solid var(--alpha-lime-50);
    color: var(--accent);
}

.to-compact-status.soon {
    background: var(--alpha-black-60);
    border: var(--bw-hair) solid rgba(255, 159, 67, 0.4);
    color: var(--orange-500);
}

.to-compact-status.ongoing {
    background: var(--alpha-black-60);
    border: var(--bw-hair) solid var(--role-manager-ring-sm);
    color: var(--role-manager);
}

.to-compact-status.closed {
    background: var(--alpha-black-60);
    border: var(--bw-hair) solid var(--alpha-white-12);
    color: var(--text-secondary);
}

.to-compact-status.past {
    background: var(--alpha-black-60);
    border: var(--bw-hair) solid var(--alpha-white-12);
    color: var(--text-muted);
}

/* (to-compact-gender removed — replaced by to-compact-gender-badge) */

/* Empty state */
.to-empty {
    grid-column: 1 / -1;
    text-align: center;
    padding: var(--space-xl) var(--space-lg);
    color: var(--text-muted);
    font-size: var(--fs-base);
}

.to-empty svg {
    display: block;
    margin: 0 auto var(--space-sm);
    opacity: 0.3;
}

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


/* === COMPACT COUNTDOWN (cards) === */
.to-cd {
    display: inline-flex;
    align-items: center;
    gap: var(--space-2);
    /* ОТСЧЁТ ДО НАЧАЛА — НЕ ТРЕВОГА, А ОЖИДАНИЕ, и красная плашка здесь
       была двумя бедами сразу. Первая: «срочно» (`-urgent`) отличалось от
       обычного на 1.39 — градация не работала, а белый на ярком красном
       давал 4.09 при норме 4.5, то есть САМОЕ СРОЧНОЕ читалось хуже
       обычного. Вторая: красный отсчёт стоял в 1.70 от эфира и схлопывался
       с ним — ровно то, что Костя отверг по снимку в 2б.
       Приём взят у страницы турнира (`.td-cd-box` + `.td-cd-num`), где он
       был с самого начала: нейтральная плашка, акцентные цифры, красный
       оставлен ТОЛЬКО для срочного. Померено: 14.19 против 5.69 у
       обычного, разница с срочным 2.84 против 1.39. */
    background: var(--alpha-white-08);
    border: var(--bw-hair) solid var(--alpha-white-12);
    border-radius: var(--radius-sm);
    height: var(--chip-h);
    padding: 0 var(--space-3);
    font-size: var(--fs-sm);
    font-weight: 700;
    font-variant-numeric: tabular-nums;
    color: var(--accent);
    line-height: var(--lh-none);
    position: relative;
    overflow: hidden;
    animation: toCdAppear 0.4s ease-out;
    box-shadow: none;
}

/* Shimmer glow effect */
.to-cd::after {
    content: '';
    position: absolute;
    top: 0; left: -100%;
    width: 60%;
    height: 100%;
    background: linear-gradient(90deg, transparent, var(--alpha-white-12), transparent);
    animation: toCdShimmer 3s ease-in-out infinite;
}

@keyframes toCdShimmer {
    0%, 100% { left: -100%; }
    50% { left: 150%; }
}

@keyframes toCdAppear {
    from { opacity: 0; transform: scale(0.9); }
    to { opacity: 1; transform: scale(1); }
}

.to-cd-unit {
    display: inline-flex;
    align-items: baseline;
    gap: 1px;
    position: relative;
    z-index: 1;
}

.to-cd-unit small {
    font-size: var(--fs-2xs);
    font-weight: 600;
    opacity: 0.75;
    margin-right: 3px;
}

.to-cd-unit:last-child small {
    margin-right: 0;
}

/* Urgent: < 1 hour — intense pulse */
.to-cd-urgent {
    /* СРОЧНО — вот теперь это тревога, и она одна такая на карточке. */
    background: var(--danger-subtle);
    border-color: var(--danger);
    color: var(--danger);
    box-shadow: 0 2px 16px rgba(255,71,87,0.25);
    animation: toCdAppear 0.4s ease-out, toCdUrgentPulse 2s ease-in-out infinite;
}

@keyframes toCdUrgentPulse {
    0%, 100% { box-shadow: 0 2px 12px var(--alpha-red-30); }
    50% { box-shadow: 0 2px 24px rgba(255,71,87,0.6); }
}

/* Live indicator — green with sonar */
.to-cd-live {
    /* ПЛОТНАЯ ЗАЛИВКА, А НЕ ГРАДИЕНТ — и это решение ГЛАЗ, а не чисел.
       Градиентный вариант проходил AA (4.97), но вставал рядом с
       отсчётом `.to-cd`, который тоже красный и тоже градиентный: два
       значка схлопывались в один вид, и «идёт сейчас» переставало
       отличаться от «до начала» иначе как текстом. Плоский против
       градиентного разводит их формой, а не только краской.
       Пара взята у эталона `.live-badge.is-live`: белый на #FF4757 даёт
       3.34 при норме 4.5, почти-чёрный на той же заливке — 5.93. */
    background: var(--danger);
    border-color: transparent;
    color: var(--danger-on);
    gap: 8px;
    font-size: var(--fs-xs);
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 1.5px;
    box-shadow: 0 2px 12px var(--alpha-red-30);
}

.to-cd-live::after {
    display: none;
}

.to-cd-dot {
    position: relative;
    width: 8px;
    height: 8px;
    border-radius: 50%;
    background: var(--danger-on);
    z-index: 1;
}

/* Sonar ring effect */
.to-cd-dot::before {
    content: '';
    position: absolute;
    inset: -3px;
    border-radius: 50%;
    border: var(--bw-thick) solid rgba(255,71,87,0.6);
    animation: toCdSonar 2s ease-out infinite;
}

@keyframes toCdSonar {
    0% { transform: scale(1); opacity: 0.8; }
    100% { transform: scale(2.5); opacity: 0; }
}

/* Countdown prefix label */
.to-cd-label {
    font-size: var(--fs-2xs);
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 1.5px;
    opacity: 0.85;
    position: relative;
    z-index: 1;
    margin-right: var(--space-2);
}

/* Positioning in featured card */
.to-featured-content > .to-cd {
    margin: var(--space-2) 0 0;
}

/* Positioning in compact card */
.to-compact-right .to-cd {
    margin-top: var(--space-2);
}

/* Positioning in tournament-card (category pages) */
.tournament-card > .to-cd {
    margin: 0 var(--space-md);
}

/* ---- Запись на турнир прямо из карточки ---- */
.to-featured-actions {
    display: flex;
    align-items: center;
    gap: var(--space-sm);
    flex-wrap: wrap;
}

.to-featured-actions .to-featured-link {
    margin: 0;
}

.btn-register.to-register {
    height: var(--btn-h-md);
    padding: 0 var(--space-lg);
    border: none;
    border-radius: var(--radius-full, 999px);
    background: var(--accent);
    color: var(--neutral-black);
    font-size: var(--fs-sm);
    font-weight: 600;
    cursor: pointer;
    white-space: nowrap;
    transition: opacity 0.2s;
}

.btn-register.to-register:hover {
    opacity: 0.9;
}

/* `is-registered` снят из группы 05.10: класса не ставит никто.
   `:disabled` — живой, его ставит браузер. */
.btn-register.to-register:disabled {
    background: transparent;
    border: var(--bw-hair) solid var(--alpha-white-20);
    color: var(--alpha-white-50);
    cursor: default;
    pointer-events: none;
}

/* На телефоне и планшете однострочная подпись обрезалась почти вдвое: от
   «ТЕСТ второй категории — Tour мужской одиночный» оставалось меньше
   половины. Даём две строки, многоточие остаётся */


/* Живой турнир — карточка с афишей сбоку, как на главной. Обёртка нужна,
   чтобы карточка занимала ту же ячейку сетки, что и прежняя большая.
   Высоту ограничиваем: вертикальная афиша тянула карточку почти на экран */
.to-featured-side {
    display: flex;
    /* ТОТ ЖЕ РОСТ, ЧТО У СТОЛБА — И ТЕПЕРЬ ЭТО ОТНОШЕНИЕ, А НЕ ЧИСЛО.
       Было `height: calc(ступень * 3 + зазор * 2)`: пока слот ровно 160,
       счёт сходился, но стоило слоту вырасти под содержимое — крупная
       осталась 528 при столбе 556. Обе ячейки стоят в ОДНОМ ряду сетки,
       поэтому высоту им выравнивает сама сетка, а прежнее число остаётся
       минимумом. ЗАМОРАЖИВАЕТСЯ ОТНОШЕНИЕ, А НЕ ЧИСЛО. */
    height: 100%;
    min-height: var(--to-featured-h, 420px);
}

.to-featured-side .tc {
    width: 100%;
}

/* Афиша лежит отдельным слоем внутри карточки — ей нужна точка отсчёта */
.to-compact-thumb {
    position: relative;
    /* АФИША 120, А НЕ 208 — 28.09. Плашка статуса ушла с картинки в колонку
       сведений, и колонке нужна ширина: 88 пикселей оплачены афишей.
       Заодно короб миниатюры стал 120x160, то есть 3:4 — афиши у клуба
       вертикальные, и в прежнем коробе 208x160 они стояли с полями по
       бокам, отчего три афиши подряд выглядели разного размера.

       ЧИСЛО ЖИВЁТ В КОМПОНЕНТЕ, А НЕ В СЕТКЕ КАТЕГОРИЙ. Пока оно стояло на
       .to-categories .to-card-grid, та же карточка на pages/tournaments.html
       брала запасное 208 — одна карточка на двух страницах сайта показывала
       афишу разной ширины. Запасное значение, не равное рабочему, — это
       второе определение одного понятия. */
    --to-thumb: 120px;
    /* Колонка «статус над датой»: 13 ступеней по 8 */
    --to-meta: 104px;
}

/* СТАТУС НАД ДАТОЙ, ЗА ГРАНИЦЕЙ АФИШИ — решение Кости 28.09: «на афишу не
   залезай, до границы афиш; не надо так сильно прижимать, нужен немного
   воздуха». Плашка ушла из положения absolute поверх картинки в колонку
   сведений и стала её первой строкой. Воздух между афишей и колонкой —
   тот же зазор сетки (24), отдельного числа под плашку нет.
   ЦЕНА НАЗВАНА: в потоке плашка забирает ширину, поэтому афиша сужена
   (см. --to-thumb). */
.to-compact-thumb .to-compact-left {
    align-items: flex-start;
    /* Колонка одной ширины во всех строках: иначе дата и метки стояли бы
       на разной вертикали — у «Скоро» колонка 72, у «Регистрация открыта» 191 */
    width: var(--to-meta, 104px);
}

/* В узкой колонке плашка переносится: высота становится НЕ МЕНЬШЕ ступени
   чипа, а не равна ей. Это единственное место, где чип переносится, —
   ступень 24 остаётся его нижней границей */
.to-compact-thumb .to-compact-status {
    max-width: 100%;
    height: auto;
    min-height: var(--chip-h);
    padding: 2px var(--space-2);
    line-height: var(--lh-snug);
    white-space: normal;
    text-align: left;
}

/* Отсчёт в боковой карточке — сверху, над кнопкой: он важнее, чем действие,
   и внизу под кнопкой читался как приписка */
.to-compact-thumb .to-compact-right {
    flex-direction: column-reverse;
    justify-content: flex-end;
}

/* Кнопка записи в боковой карточке: раньше там висел только статус
   «регистрация открыта», а записаться было негде */
.to-compact-regbtn {
    height: var(--btn-h-sm);
    padding: 0 var(--space-sm);
    border-radius: 999px;
    border: none;
    background: var(--accent);
    color: var(--neutral-950);
    font-size: var(--fs-xs);
    font-weight: 700;
    cursor: pointer;
    white-space: nowrap;
}

.to-compact-regbtn:hover:not(:disabled) {
    filter: brightness(1.08);
}

.to-compact-regbtn:disabled {
    opacity: 0.6;
    cursor: default;
}

/* СНЯТО 05.10 — МЁРТВЫЙ КОД, ПРОВЕРЕННЫЙ, А НЕ УВИДЕННЫЙ.
   Здесь жили три состояния кнопки записи: is-registered, is-pending-guest,
   is-refused — восемь объявлений, шесть из них с !important и красками
   #f44336 / #ffb300 мимо токенов.
   Проверка: ни одного вхождения вне css — ни в js, ни в разметке, ни в
   mobile/www; `git log -S` по js/ не нашёл их и в истории. Класс не
   собирался и строкой: конструкций вида 'is-' + состояние нет.
   Ставить эти классы некому, значит краска никогда не показывалась.
   Правило заморозки стережёт, чтобы они не вернулись краской раньше, чем
   кодом, который их ставит. */

/* ============================================================
   Телефон
   ============================================================
   Правила живут здесь, а не в style.css: этот файл подключается после
   него, и мобильная правка выигрывает по порядку — без подпорок вида
   main .to-featured и без !important. */


/* ОБЛОЖКА НА НИЗКОМ ГОРИЗОНТАЛЬНОМ ЭКРАНЕ — ТАК ЖЕ, КАК НА ГЛАВНОЙ.
   Не доля экрана и не новое число: первый экран там вообще не запирается в
   высоту окна — min-height уходит в 0, а рост даёт содержимое плюс воздух.
   Причина записана в css/style.css:7906 дословно: «min-height: 0 делает его
   ровно этим, и даже честнее, чем 100dvh», и там же про наш случай — на
   844x390 «на всё содержимое остаётся 326 точек».

   Воздух при этом не второе объявление padding, а КРУТИЛКА: значение меняет
   медиазапрос, объявление остаётся одно. Тот же приём, что у .hero. */

/* ФОКУС — ОДИН ВИД НА ВЕСЬ ПРОДУКТ.
   Разбор 27.09: на этой странице не было НИ ОДНОГО правила фокуса, а общее
   правило css/style.css:432 покрывает только шапку. Клавиатурой по карточкам,
   ссылкам и кнопке регистрации человек шёл вслепую.

   :focus-visible, а НЕ :focus — второй срабатывает и на мышь, и тогда рамка
   вспыхивает при каждом щелчке. Вид взят из шапки, чтобы не завести в
   продукте второй стиль фокуса: обводка 2 акцентом, отступ 2. */
.to-search-input:focus-visible,
.to-view-all:focus-visible,
.to-featured:focus-visible,
.to-featured-side:focus-visible,
.to-featured-link:focus-visible,
.to-compact:focus-visible,
.to-compact-regbtn:focus-visible,
.to-categories .btn-register:focus-visible,
.to-phone-cards .tc:focus-visible {
    outline: var(--bw-thick) solid var(--accent);
    outline-offset: 2px;
}

/* У поля поиска своя рамка и своё свечение на :focus — обводка поверх них
   дала бы два кольца сразу. Оставляем одно, но именно то, которое видно
   при ходьбе клавиатурой */
.to-search-input:focus-visible {
    outline-offset: 0;
}

/* МЕНЬШЕ ДВИЖЕНИЯ — ЭТО НАСТРОЙКА ЧЕЛОВЕКА, А НЕ ПОЖЕЛАНИЕ.
   Разбор 27.09: на странице 29 элементов с переходами и анимацией и ни
   одного правила на этот случай. Движение убираем целиком, а не замедляем:
   тем, кому оно мешает, мешает и медленное */

/* ЛЕНТА НА НИЗКОМ ГОРИЗОНТАЛЬНОМ: СОДЕРЖИМОЕ ДОЖИМАЕТСЯ ПОД ЧУЖУЮ СТУПЕНЬ.
   Замерено Playwright 27.09: на 844×390 четырнадцать карточек переполнены —
   в коробку 270 не влезало 329. Ступень 270 задаёт общее правило ленты
   (css/style.css:6364), и оно уже прячет описание и зажимает заголовок в
   две строки. Лишнее у нас своё: кнопка записи, которой в ленте главной
   нет. Поэтому дожимаем СВОЁ, а не переписываем общее:
   заголовок в одну строку и мета убирается — дата, статус и кнопка
   остаются, их и нажимают. */

/* ══ УЗКИЕ ВИДЫ ═══════════════════════════════════════════════════════
   ПЕРЕНЕСЕНЫ В КОНЕЦ ФАЙЛА 28.09. Раньше оба медиаблока стояли В НАЧАЛЕ,
   сразу после обложки, а базовые правила категорий шли ПОСЛЕ них. При
   равной силе побеждает то, что ниже, — и 31 объявление внутри медиа
   не действовало вовсе: кегль заголовка категории, рост крупной карточки
   на планшете, отступы поиска, обрезка строк в карточке и прочее.
   Поймано замером: лестница говорила 18 на телефоне, браузер показывал 26.
   Правило: УЗКИЕ ВИДЫ ЖИВУТ В КОНЦЕ ФАЙЛА, А НЕ В СЕРЕДИНЕ. */

@media (max-width: 992px) {

    /* КОЛОНКА ДЕЙСТВИЙ УЕЗЖАЕТ НА СВОЮ СТРОКУ. Третья колонка `auto` с
       кнопкой записи отнимала у названия всё: 17 пикселей из 341 на 768 и
       844. Ниже 640 это уже было сделано — здесь та же граница поднята до
       992, где ряд и перестаёт помещаться. */
    .to-compact-right {
        grid-column: 1 / -1;
    }

    /* ЧЕТЫРЕ ПЕРЕБИВКИ СНЯТЫ 31.10: они повторяли ступени токенов
       `--fs-hero*` слово в слово (32 / 18 / 32 / 12), то есть были вторым
       определением одного понятия. Теперь ступень одна и живёт в
       `css/tokens.css`, раздел «ОБЛОЖКА РАЗДЕЛА». */
    .to-card-grid {
        grid-template-columns: 1fr;
    }

    .to-side-stack {
        display: grid;
        grid-template-columns: 1fr 1fr;
        gap: var(--space-md);
    }

    /* НА ПЛАНШЕТЕ ПУСТЫЕ МЕСТА НЕ ДЕРЖИМ — решение Кости 28.09.
       Наверху столб стоит РЯДОМ с крупной карточкой, и пустая клетка там
       нужна: она держит края колонок на одной линии. Здесь столб ушёл ВНИЗ,
       ровнять нечего — и пустая клетка превращается в дыру на пол-экрана.
       Довод действует, пока действует его условие. */
    .to-categories .to-slot-empty {
        display: none;
    }

    /* ПЛАНШЕТ: столб уходит ПОД карточку в две колонки, значит слотов
       ЧЕТЫРЕ — два целых ряда. Три дали бы ряд из двух и ряд из одного,
       а витрина показывает только целые ряды. Решение Кости 27.09 */
    /* СРОК РЕГИСТРАЦИИ ВОЗВРАЩАЕТСЯ: здесь слот 216 и две колонки, места
       хватает, а довод «строка лишняя» был про тесноту бокового столба.
       ДОВОД ДЕЙСТВУЕТ, ПОКА ДЕЙСТВУЕТ ЕГО УСЛОВИЕ. */
    .to-side-stack .to-compact-reg {
        display: inline;
    }

    .to-categories .to-side-stack {
        /* СТУПЕНЬ — ПОТОЛОК И ЗДЕСЬ, ПРОСТО ОНА ВЫШЕ. Замер 17.10 на 992:
           при ровной ступени 216 содержимое переливало на 2, при минимуме
           — ритм гулял на 3.6 (слоты 220 и 216). 224 вмещает самый полный
           слот и держит ряды ровными: перелива нет, разброса нет. */
        grid-template-rows: repeat(2, var(--to-slot));
        /* СТУПЕНЬ СЛОТА НА ПЛАНШЕТЕ ВЫШЕ: 160 -> 216. Плитке нужно уложить
           строку «статус · дата · метки» (у «Регистрация открыта» она
           переносится и занимает 53), название в две строки, адрес и
           кнопку записи 44. Кнопку уменьшать нельзя: 44 — цель нажатия по
           WCAG 2.5.5. Померено 28.09 на 768: при 160 перелив 71, при 200 —
           4, при 216 — 0. ПЕРЕМЕРЕНО 17.10 после того, как ряды стали
           ровной ступенью: нужно 220, ближайшая ступень кратная 8 — 224. */
        --to-slot: 224px;
    }

    /* Отношение «крупная равна столбу» действует, ПОКА столб стоит РЯДОМ.
       Здесь он под карточкой, условие кончилось — и число возвращается
       к прежнему росту обложки раздела, а не тянется за тремя слотами */
    .to-categories .to-card-grid {
        --to-featured-h: 320px;
    }

    .to-featured {
        min-height: 320px;
    }
    .to-compact-info h4 {
        white-space: normal;
        display: -webkit-box;
        -webkit-line-clamp: 2;
        -webkit-box-orient: vertical;
        line-height: var(--lh-snug);
    }


}


    /* МЕТА — РЯДОМ, А НЕ СТОЛБЦОМ: вторая половина того же решения (31.10).
       Сужения афиши до 96 на планшете вертикально не хватило: колонка
       меты держит свои 104, и названию из 355 оставалось 57 — замер
       31.10 дал 22 перелива, худший +37, кнопка «Регистрация» всё ещё
       за дном. Столбец меты — раскладка ШИРОКОГО вида; в узком столбе
       мета читается рядом, как уже сделано на телефоне (:2016).
       ЗАМЕР ПОСЛЕ: ноль переливов, название 209 вместо 57. */
    .to-side-stack .to-compact-thumb {
        grid-template-columns: minmax(0, 1fr);
    }

    .to-side-stack .to-compact-left {
        flex-direction: row;
        align-items: center;
    }

@media (max-width: 640px) {
    /* И здесь те же четыре перебивки сняты 31.10 — они повторяли ступени
       токенов (26 / 16 / 21 / 11). */
    .to-search-wrap {
        padding: var(--space-2) var(--pad-x) var(--space-3);
    }

    .to-hero {
        --to-hero-vozduh-niz: var(--section-y-sm);
        min-height: 320px;
    }

    .to-hero h1 {
        font-size: var(--fs-xl);
    }

    /* Между категориями было по 40 пикселей сверху и снизу — на телефоне
       это 80 пустоты на каждый стык */
    .to-category-block {
        padding: 24px var(--pad-x);
    }

    /* Под последней категорией держались ещё 80 пикселей, и до «Спонсоров»
       оставалась пустая треть экрана */
    .to-categories {
        padding-bottom: 0;
    }

    .to-side-stack {
        grid-template-columns: 1fr;
    }

    .to-featured {
        min-height: 280px;
    }

    .to-featured-content h3 {
        font-size: var(--fs-md);
    }

    /* НА ТЕЛЕФОНЕ КАРТОЧКА — В ОДНУ КОЛОНКУ, А НЕ В ДВЕ.
       Замер 05.10: левый блок тут разворачивается В СТРОКУ (статус, дата,
       бейдж пола рядом), и его колонка `auto` забирала 238 пикселей из
       341 — названию оставалось 39. В колонке шириной 39 название встаёт
       вертикальной лапшой, и высота карточки делается функцией от длины
       имени и наличия адреса: 202 против 94 у соседей в одной категории.
       Теперь ряд плашек — своей строкой, название и адрес — своей, во всю
       ширину. Тот же приём, что у карточки телефона на обзорной. */
    .to-compact {
        /* Ступень телефона своя: раскладка в одну колонку выше, замеренный
           максимум 160 — это ровно 8 × 20 */
        min-height: 160px;
        grid-template-columns: minmax(0, 1fr);
        gap: var(--space-sm);
    }

    .to-compact-left {
        flex-direction: row;
        align-items: center;
        gap: 8px;
    }

    .to-compact-right {
        grid-column: 1 / -1;
        flex-direction: row;
        align-items: center;
        justify-content: flex-start;
    }


 /* было 480 */
    .to-hero {
        --to-hero-vozduh-niz: var(--section-y-sm);
        min-height: 280px;
    }

    .to-hero h1 {
        font-size: var(--fs-xl);
    }

    .to-hero p {
        font-size: var(--fs-sm);
    }

    .to-category-title {
        font-size: var(--fs-lg);
    }

    .to-featured {
        min-height: 260px;
    }

    .to-featured-content {
        padding: var(--space-md);
    }

    .to-featured-content h3 {
        font-size: var(--fs-base);
    }

    .to-featured-details {
        gap: var(--space-sm);
    }

    .to-compact {
        padding: var(--space-sm) var(--space-md);
    }

    .to-compact-info h4 {
        font-size: var(--fs-sm);
    }
    .to-category-title {
        font-size: var(--fs-md);
    }

    .to-featured-link {
        padding: 0 var(--space-md);
        font-size: var(--fs-xs);
    }
    /* Обложка той же высоты, что у страниц категорий: раздел один, и
       переход с обзора в категорию не должен прыгать. Содержимое стоит
       у нижнего края с отступом в 30 — так цифры не жмутся ни к заголовку,
       ни к самому низу, а сверху остаётся видна фотография */
    .to-hero {
        --to-hero-vozduh-niz: var(--section-y-sm);
        min-height: 275px;
        align-items: stretch;
        margin-bottom: 0;
    }

    /* Подпись идёт вплотную к названию: со своим отступом обложка обзора
       вырастала на 7 пикселей выше остальных в разделе */
    .to-hero-sub {
        margin-bottom: 0;
    }

    /* ЗАГОЛОВОК, ПОДПИСЬ И ЦИФРЫ — ОДНА ГРУППА ПО ЦЕНТРУ, так же, как на
       страницах категорий (css/tournaments.css, медиа 768). Было
       `space-between`: на телефоне 390 между названием и цифрами стояло
       167 пикселей пустоты при 59 на телефоне лёжа, где пол обложки снят.
       Правило выведено на странице категории по слову Кости 07.10 и
       применено сюда без нового разбора. */
    .to-hero-content {
        width: 100%;
        display: flex;
        flex-direction: column;
        justify-content: center;
    }

    /* Поиск шёл через 25 пикселей от обложки и добавлял свои 24 сверху */
    .to-search-wrap {
        padding: var(--space-2) var(--pad-x) var(--space-3);
    }

    /* Шрифт остаётся 16: при меньшем Safari на iPhone сам увеличивает
       страницу, как только ставишь курсор в поле, и обратно уже не выйти.
       Высоту и отступы поле несёт одни на все виды — ступень md */
    .to-search-input {
        font-size: var(--fs-base);
    }

    /* Заголовок категории нёс снизу отступ в 20 пикселей: из-за него
       ссылка «Все турниры», выровненная по центру строки, вставала ниже
       заголовка, а под шапкой зияла пустота. Отступ отдаём самой шапке */
    .to-category-title {
        margin: 0;
    }

    /* ПАРА, КОТОРАЯ ЧИТАЕТСЯ КАК ПАРА, ЕДЕТ ОДНОЙ ГРАНИЦЕЙ.
       Шапка стояла в строку с переносом, и перенос решала ДЛИНА НАЗВАНИЯ:
       «Masters» и «Tour» помещались рядом с «Все турниры», а «Pro-Masters»
       и «Challengers» — нет. Блок вырастал на 55, и ритм страницы зависел
       от того, сколько букв в слове. Замерено на 375: шапка 99 против 44,
       блоки 515 · 460 · 515 · 460 · 460, и одинаково на всех трёх языках.

       На узком экране пара встаёт СТОЛБИКОМ ВСЕГДА — решает точка
       останова, а не содержимое. Платим 55 пикселями на каждой категории;
       взамен все блоки одного роста при любых названиях, включая
       кыргызские, которые длиннее всех. */
    .to-category-header {
        margin-bottom: var(--space-3);
        flex-direction: column;
        align-items: flex-start;
        gap: var(--space-3);
    }

    /* ЛЕНТА, А НЕ СЕТКА. Решение Кости 27.09: на телефоне категория
       листается вбок — две карточки видны, до шести доступно.

       Приём не выдуман: ровно так уже сделан телефон боком
       (css/style.css:6330), и там же записана причина — «держим полоску
       следующей карточки», как App Store, Netflix и Google Play: ряд,
       обрезанный ровно по краю, читается как законченный, и его никто
       не листает.

       Ширина карточки в долях экрана, а не в пикселях: две помещаются, а
       третья выглядывает краем и зовёт листать дальше. */
    .to-phone-cards {
        display: flex;
        gap: var(--space-xs);
        overflow-x: auto;
        scroll-snap-type: x mandatory;
        -webkit-overflow-scrolling: touch;
        scrollbar-width: none;
        padding-bottom: var(--space-xs);
        /* Лента идёт от края до края экрана, а поля отдаёт себе сама:
           обрезанная карточка у самого края и есть признак ленты */
        margin-inline: calc(-1 * var(--pad-x));
        padding-inline: var(--pad-x);
    }

    .to-phone-cards::-webkit-scrollbar { display: none; }

    .to-categories .to-phone-cards > .tc {
        /* ВЫСОТА КАРТОЧКИ В ЛЕНТЕ — СТУПЕНЬ, А НЕ СОДЕРЖИМОЕ. Playwright
           27.09: блоки категорий выходили 449 · 453 · 432 · 412 · 449 —
           разброс 41, потому что высоту задавала самая высокая карточка
           своего блока. Естественные высоты 288 · 308 · 325 · 329, ступень
           выше максимума — 336 (8 × 42), запас семь пикселей на длинные
           названия по-английски и по-кыргызски */
        flex: 0 0 44%;
        height: var(--to-lenta-h);
        scroll-snap-align: start;
    }

    /* ─── Категория читается как две карточки в ряд, два ряда ───
       Карточка занимала весь экран: 380 пикселей минимальной высоты,
       заголовок налезал на описание, и одна категория шла четырьмя
       экранами */
    .to-card-grid {
        grid-template-columns: 1fr 1fr;
        gap: var(--space-3);
    }

    /* Высота задана прямо: ряд сетки растягивался до 632 пикселей —
       фоновая картинка тянула его за собой, хотя текста в карточке
       на 190. Плитка одного размера, фото внутри обрезается по cover */
    .to-featured,
    .to-featured-side {
        min-height: 0;
        height: 200px;
    }

    .to-featured-content {
        padding: var(--space-3);
    }

    .to-featured-content h3 {
        font-size: var(--fs-sm);
        line-height: var(--lh-snug);
        margin-bottom: var(--space-2);
        display: -webkit-box;
        -webkit-line-clamp: 2;
        -webkit-box-orient: vertical;
        overflow: hidden;
    }
    .to-featured-details,
    .to-featured-meta {
        display: none;
    }

    .to-featured-status {
        font-size: var(--fs-2xs);
        padding: 0 var(--space-2);
    }

    .to-featured-date {
        font-size: var(--fs-2xs);
        padding: var(--space-1) var(--space-2);
    }

    .to-featured-date .to-day { font-size: var(--fs-base); }
    .to-featured-date .to-month { font-size: var(--fs-2xs); }

    .to-featured-actions a,
    .to-featured-actions button {
        height: var(--btn-h-sm);
        padding: 0 var(--space-3);
        font-size: var(--fs-2xs);
        width: 100%;
        text-align: center;
    }

    /* Рядом с главной карточкой стояла стопка из трёх компактных строк —
       она держала ряд в 632 пикселя, а её карточки на узкой колонке
       разъезжались. display: contents растворяет стопку: её карточки
       становятся плитками той же сетки, и категория встаёт как 2×2 */
    .to-side-stack {
        display: contents;
    }

    .to-side-stack .to-compact {
        display: flex;
        flex-direction: column;
        justify-content: flex-end;
        height: 200px;
        padding: var(--space-3);
        gap: var(--space-2);
        overflow: hidden;
    }

    /* Строчная раскладка «картинка — текст — метки» на плитке не нужна:
       фон карточки уже несёт картинку */
    .to-side-stack .to-compact-thumb { display: none; }

    .to-side-stack .to-compact-info h4 {
        font-size: var(--fs-xs);
        line-height: var(--lh-snug);
        -webkit-line-clamp: 2;
    }

    .to-side-stack .to-compact-info span,
    .to-side-stack .to-compact-right {
        font-size: var(--fs-2xs);
    }

    .to-side-stack .to-compact .to-compact-right {
        gap: var(--space-2);
    }
}

/* ══ НИЗКИЙ ГОРИЗОНТАЛЬНЫЙ И «МЕНЬШЕ ДВИЖЕНИЯ» ════════════════════════
   СТОЯТ ПОСЛЕ ШИРИН НАРОЧНО. Телефон боком — это 844 в ширину: под него
   подходит и медиа 992, и медиа высоты. Побеждает то, что ниже, поэтому
   высота должна идти ПОСЛЕ ширины — иначе планшетная раскладка перебивает
   ленту. 28.09 при переносе медиаблоков в конец файла это едва не
   перевернулось. */

/* ─────────────────────────────────────────────────────────────────────
   СТРАНИЦА РАЗРЯДА НА ТЕЛЕФОНЕ — КАРТОЧКА, А НЕ ПЛИТКА.
   Слово Кости 07.10: «на стр категории турниров не отображается кнопка
   регистрация и нет второго турнира, а на обзорной карточка отображается
   очень хорошо».
   Замер 07.10 на 375: блок «Предстоящие» — это `.trn-block > .to-card-grid`,
   и до него доставали правила витрины категорий из медиа ≤640
   (`.to-card-grid { 1fr 1fr }` и `.to-featured-side { height: 200px }`).
   Сетка выходила «165.5px 165.5px», крупная карточка 166 × 200, а столб
   со вторым турниром — 0 × 0. Кнопка записи и второй турнир просто не
   помещались.
   Те правила писались для ВИТРИНЫ: на обзорной телефон строит ленту
   `.to-phone-cards` (js/tournaments-overview.js:607), а `.to-card-grid`
   у неё на телефоне остаётся только в заглушке «ничего не найдено»;
   плитками по две в ряд ими пользуется страница баттлов
   (js/battles-overview.js:445) — её не трогаем. Поэтому исключение
   называется адресно: блок раздела, а не всё подряд.
   ───────────────────────────────────────────────────────────────────── */
@media (max-width: 640px) {

    .trn-block .to-card-grid {
        grid-template-columns: 1fr;
        gap: var(--space-md);
    }

    /* Высоту даёт содержимое: внутри `.to-featured-side` стоит та же
       карточка `.tc`, что в ленте обзорной, и ступень 200 обрезала ей
       низ вместе с кнопкой записи */
    .trn-block .to-featured,
    .trn-block .to-featured-side {
        height: auto;
        min-height: 0;
    }

    /* ВТОРОЙ ТУРНИР ВОЗВРАЩАЕТСЯ. Столб остаётся `display: contents` —
       его полосы становятся рядами той же одной колонки. Но правила
       плитки делали полосу коробкой 200 с прижатым к низу текстом и
       снимали с неё всю строчную раскладку
       (`.to-side-stack .to-compact-thumb { display: none }`), а на
       странице разряда полоса — ЕДИНСТВЕННЫЙ вид второго турнира: он
       пропадал с экрана целиком. Возвращается компонент, как он
       объявлен: сетка, своя ступень 144, видимая афиша. */
    .trn-block .to-side-stack .to-compact {
        display: grid;
        height: auto;
        min-height: 144px;
        overflow: visible;
    }

    .trn-block .to-side-stack .to-compact-thumb {
        display: grid;
    }

    /* АФИША ЛЕЖИТ СЛОЕМ, И МЕСТО ПОД НЕЁ ДЕРЖИТ ПОЛЕ СЛЕВА.
       Замер 07.10 на 375: правило телефона переобъявило `padding` карточки
       целиком (`.to-compact` в медиа ≤640), и поле в `calc(120 + 24)`,
       которым компонент отводит место афише, пропало — а слой `::before`
       остался. Колонка статуса и даты встала поверх афиши: «РЕГ. ОТКРЫТА»
       и «10 ОКТ» легли на картинку, а бейдж пола — на адрес.
       Поле возвращается, афиша сужается до 96 (8 × 12): при 120 строке
       оставалось 187, кнопка записи 170 вставала впритык. Колонка меты
       перестаёт быть 104: на телефоне она ряд, а не столбец, и ширину ей
       даёт содержимое. */
    .trn-block .to-side-stack .to-compact-thumb {
        --to-thumb: 96px;
        padding-left: calc(var(--to-thumb) + var(--space-sm));
        /* ОДНА КОЛОНКА, А НЕ ДВЕ. Столб объявляет `auto minmax(0, 1fr)`
           (css/tournaments-overview.css:393) — это раскладка широкого вида,
           где слева столбец меты, справа название. На телефоне мета — РЯД,
           и колонка `auto` забирала всю строку: название получало ширину
           НОЛЬ и пропадало совсем. Здесь ряд меты, название и строка
           записи идут друг под другом, как у соседней крупной карточки. */
        grid-template-columns: minmax(0, 1fr);
    }

    .trn-block .to-side-stack .to-compact-thumb .to-compact-left {
        width: auto;
        flex-wrap: wrap;
    }
}


@media (max-height: 500px) and (orientation: landscape) {
    .to-hero {
        min-height: 0;
        --to-hero-vozduh:     var(--section-y-sm);
        --to-hero-vozduh-niz: var(--section-y-sm);
        /* Вторая ручка, как на главной: 40 -> 16. Кегли не трогаем —
           лестница одна на все виды, меняется только воздух между группами */
        --to-hero-shag: var(--space-4);
    }

    .to-categories .to-phone-cards > .tc {
        /* Своя ступень, а не содержимое: с height: auto один блок выходил
           424 против 400 у соседей. Естественные высоты после дожима —
           265 и 289, ступень выше максимума 296 (8 × 37) */
        height: 296px;
    }

    .to-categories .to-phone-cards .tc-title {
        -webkit-line-clamp: 1;
    }

    .to-categories .to-phone-cards .tc-meta {
        display: none;
    }
}

/* ══ УЗКАЯ КАРТОЧКА: ПОЛОСА СТАНОВИТСЯ ПЛИТКОЙ ════════════════════════
   Спрашиваем ширину СЛОТА, а не окна. Порог ПОСЧИТАН из того, что стоит
   в строке: афиша 120 + зазор 24 + колонка «статус над датой» 104 +
   зазор 24 + название, которому нужно хотя бы 200 = 472, на восьмёрке 480.
   Уже этого строка не держится: померено 28.09 — на 1024 названию
   доставалось 131, и содержимое переливало из карточки на 31 элемент; на
   768 — 121 и перелив на 40. В плитке те же сведения идут столбиком:
   строка «статус · дата · метки», название во всю ширину (387 на 1024,
   273 на 768), адрес, кнопка. Афиши в такой карточке нет: картинка в
   полосе шириной 313 дороже названия турнира.
   СТОИТ ПОСЛЕ МЕДИА НАРОЧНО: на 768 действуют оба блока, и побеждает
   тот, что ниже. */
/* 07.10 · ПОРОГ ОПУЩЕН 480 -> 400. Правило писалось под полосу 313 — там
   афиша действительно дороже названия. Но на планшете слот страницы
   категории равен 469, и порог 480 гасил афишу там же: замерено 07.10 —
   `::before` уходил в `display: none` при живом `--poster`. Костя увидел
   глазами: «на стр категории турниров нет афиши, хотя на телефоне есть».
   340 стоит между двумя замеренными ширинами: 313 (афиша мешает и правило
   писалось ровно под неё) и 355 — слот страницы категории на планшете 768,
   где Костя и увидел пропажу. На 1024 слот 469, на телефоне 358 — афиша
   есть везде, кроме той самой узкой полосы. */
@container (max-width: 340px) {
    .to-compact-thumb {
        padding-left: var(--space-lg);
        grid-template-columns: minmax(0, 1fr);
        align-content: start;
    }

    .to-compact-thumb::before {
        display: none;
    }

    .to-compact-thumb .to-compact-left {
        flex-direction: row;
        align-items: center;
        width: auto;
        gap: var(--space-2);
        flex-wrap: wrap;
    }

    /* Дата в строке читается как «12 сен», а не столбиком в две строки */
    .to-compact-thumb .to-compact-date {
        flex-direction: row;
        align-items: baseline;
        gap: var(--space-2);
        min-width: 0;
    }

    .to-compact-thumb .to-compact-info,
    .to-compact-thumb .to-compact-right {
        grid-column: 1 / -1;
    }
}

/* ЗДЕСЬ ЖИВЁТ ТОЛЬКО ТО, ЧЕГО ОБЩИЙ СБРОС НЕ ДЕЛАЕТ. Гашение анимаций и
   переходов стояло и тут — тем же приёмом 0.01ms, но лишь для трёх
   секций. 15.10 оно переехало в общий блок (css/tokens.css, заход B4) и
   накрывает весь продукт звёздочкой; здесь это была бы копия, которая
   стареет вместе с файлом. Прилипание прокрутки общий сброс не трогает:
   это не движение, а поведение ленты. */
@media (prefers-reduced-motion: reduce) {
    /* Лента перестаёт «доезжать» рывком: палец ведёт её сам */
    .to-phone-cards {
        scroll-snap-type: none;
    }
}
