/* ============================================================================
   КСЛТ — ТОКЕНЫ ДИЗАЙН-СИСТЕМЫ
   Единый источник правды для сайта и мобильного приложения.
   Figma: КСЛТ — Дизайн-система (файл iEucRunAjOv2bKUn6ej7XT)

   ПОРЯДОК ПОДКЛЮЧЕНИЯ — tokens.css идёт ПЕРВЫМ, до style.css и остальных:
     <link rel="stylesheet" href="css/tokens.css">
     <link rel="stylesheet" href="css/style.css">

   В mobile/www/index.html — тоже первым, до app.css.

   ПРАВИЛО: новые значения цвета, размера шрифта, отступа и радиуса
   в коде не пишутся. Берётся токен. Если подходящего токена нет —
   это повод обсудить систему, а не вписать число руками.
   ========================================================================= */

:root {

  /* ==========================================================================
     1. ПРИМИТИВЫ — сырые значения. В разметке не использовать напрямую.
     ========================================================================== */

  --neutral-black: #000000;
  --neutral-950:   #0A0A0A;
  --neutral-900:   #111111;
  --neutral-850:   #141414;
  --neutral-800:   #1A1A1A;
  --neutral-750:   #1E1E1E;
  --neutral-white: #FFFFFF;

  --lime-400: #E0FF4D;
  --lime-500: #CCFF00;
  --lime-600: #B8E600;

  --green-500:  #2ED573;
  /* ПОСЧИТАНО 15.10: ступени 400 у красного не было, хотя у синего она
     есть, а система уже берёт hover соседней ступенью
     (`--accent-hover: var(--lime-400)`). Шаг взят от пары
     --blue-400/--blue-500 — 8.0 % светлоты; шаг лаймовой пары (15.1 %)
     НЕ взят: лайм это бренд-акцент, а не информационная краска.
     Проверено в браузере: 7.41 на почти-чёрном против 7.02 у прежнего
     ручного #FF6961, который он заменяет. */
  --red-400:    #FF707C;
  --red-500:    #FF4757;
  /* СТУПЕНИ ДЛЯ СВЕТЛОЙ ТЕМЫ (02.11). Эти четыре числа жили в
     `mobile/www/css/app.css` внутри `[data-theme="light"]` и были там
     ЕДИНСТВЕННЫМ определением: палитра знала только яркие 400/500,
     которые на светлом фоне не читаются. Значения не менялись — они
     переехали туда, где живут остальные краски, чтобы у каждой был
     один источник. */
  --red-600:    #D42E40;
  --green-600:  #1B8F4D;
  --blue-600:   #1D4FCC;
  --orange-600: #C87A25;
  --orange-500: #FF9F43;
  /* Синий 400 добавлен 22.09. Замер: #3B82F6 как текст на своей же
     подложке 15% даёт 4.19 — WCAG 1.4.3 требует 4.5 для мелкого текста,
     а подпись бейджа 11px мелкая. #60A5FA на той же подложке даёт 5.72. */
  --blue-400:   #60A5FA;
  --blue-500:   #3B82F6;
  /* ПОСЧИТАНО 17.10. Роль «менеджер / идёт сейчас» красилась двумя
     написаниями фиолетового (#A78BFA текстом, rgba(168,85,247) подложкой)
     и ступени в палитре не имела. Взята та, что уже стояла в тексте:
     ближайшая занятая краска — --player-2 (D050F7, тон 290), до неё 35°.
     Проверено в браузере: 5.58 на своей подложке 15 % при норме 4.5. */
  --violet-400: #A78BFA;

  /* ПАЛИТРА РАЗЛИЧИТЕЛЕЙ. Круг 15 ветки D, 18.10. «Такой тип» — не «такое
     состояние», и своей палитры у типов не было: шесть красок стояли
     числом, и ПЯТЬ из семи различителей можно было спутать с ролью
     (закрытый корт против «сведений» ΔE 8.8, открытый против «успеха»
     13.1, игрок против «внимания» 16.7 — при пороге заметности 2.3).
     Подобраны по числам: каждая проходит AA на своей подложке и отстоит
     не меньше чем на ΔE 42 от КАЖДОЙ роли состояния и от других
     различителей. ЧЕТВЁРТОЙ ТАКОЙ КРАСКИ В СИСТЕМЕ НЕТ: шесть ролей
     заняли основные тона, а бейдж 12px/600 — мелкий текст, порог 4.5, не
     3.0. Поэтому краской помечается то, что ВЫДЕЛЯЕТ, а обычное
     назначение остаётся нейтральным. */
  --cyan-500:   #00BCD4;
  --sand-300:   #BCAAA4;
  --pink-400:   #F472B6;

  --alpha-white-72: rgba(255, 255, 255, 0.72);
  --alpha-white-50: rgba(255, 255, 255, 0.50);
  --alpha-white-35: rgba(255, 255, 255, 0.35);
  --alpha-white-12: rgba(255, 255, 255, 0.12);
  --alpha-white-08: rgba(255, 255, 255, 0.08);
  --alpha-white-04: rgba(255, 255, 255, 0.04);

  /* ШКАЛА ПРОЗРАЧНОСТИ АКЦЕНТА. Замер 13.10: один цвет был записан числом в
     378 местах, и прозрачностей оказалось ТРИДЦАТЬ ТРИ — 0.02, 0.03, 0.04,
     0.05, 0.06, 0.07, 0.08, 0.09, 0.1, 0.11, 0.12, 0.13, 0.14, 0.16, 0.18 и
     так далее до единицы. Между 0.11 и 0.12 глаз не различает ничего; между
     ступенями — различает. Семь ступеней, как у отступов: ступени должны
     быть ЗАМЕТНО разные, иначе шкала не шкала, а список.
     ЦЕНА НАЗВАНА: 238 мест сдвинулись, наибольший сдвиг 0.1 (0.4 и 0.6 к
     0.5, 0.8 к 0.7); у 207 из них сдвиг 0.05 и меньше. */
  --alpha-lime-06:   rgba(204, 255, 0, 0.06);
  --alpha-lime-12:   rgba(204, 255, 0, 0.12);
  --alpha-lime-20:   rgba(204, 255, 0, 0.20);
  --alpha-lime-30:   rgba(204, 255, 0, 0.30);
  --alpha-lime-50:   rgba(204, 255, 0, 0.50);
  --alpha-lime-70:   rgba(204, 255, 0, 0.70);
  /* Ступень 0.15 осталась от прежнего разнобоя: её место на шкале заняла
     0.12. Имя сохранено, потому что по нему названа подложка. */
  --alpha-lime-15:   var(--alpha-lime-12);
  --alpha-green-15:  rgba(46, 213, 115, 0.15);
  --alpha-red-15:    rgba(255, 71, 87, 0.15);
  --alpha-orange-15: rgba(255, 159, 67, 0.15);
  --alpha-blue-15:   rgba(96, 165, 250, 0.15);   /* из --blue-400 */

  /* ЛЕСТНИЦА РОЛИ, ЗАМЕР 17.10. Подложка у ролей была одна (15 %), а код
     звал ещё 0.04 · 0.05 · 0.06 · 0.08 · 0.1 · 0.12 · 0.16 · 0.2 · 0.3 ·
     0.35 · 0.4 — одиннадцать ступеней на то же самое. Ступени три, по
     РОЛИ В ЭЛЕМЕНТЕ, а не по вкусу места:
       тускло  0.06  полоса строки, подсветка ряда, градиент карточки
       подложка 0.15 заливка бейджа и кнопки
       граница  0.30 рамка и свечение — ВСЕГДА на ступень выше подложки,
                     иначе рамка сливается с заливкой.
     ЦЕНА НАЗВАНА: наибольший сдвиг 0.1 (0.4 и 0.35 к 0.3, 0.2 к 0.3),
     у остальных 0.05 и меньше. Подложка тоста (0.2) и его рамка (0.4)
     сходятся на 0.30 — рамка тоста перестаёт быть видна отдельно. */
  --alpha-green-06:  rgba(46, 213, 115, 0.06);
  --alpha-green-30:  rgba(46, 213, 115, 0.30);
  --alpha-red-06:    rgba(255, 71, 87, 0.06);
  --alpha-red-30:    rgba(255, 71, 87, 0.30);
  --alpha-orange-06: rgba(255, 159, 67, 0.06);
  --alpha-orange-30: rgba(255, 159, 67, 0.30);
  --alpha-blue-06:   rgba(96, 165, 250, 0.06);
  --alpha-blue-30:   rgba(96, 165, 250, 0.30);
  --alpha-violet-15: rgba(167, 139, 250, 0.15);
  --alpha-violet-06: rgba(167, 139, 250, 0.06);
  /* Лестница различителей — та же, что у ролей: 06 тускло, 15 подложка,
     30 граница. */
  --alpha-cyan-06:   rgba(0, 188, 212, 0.06);
  --alpha-cyan-15:   rgba(0, 188, 212, 0.15);
  --alpha-cyan-30:   rgba(0, 188, 212, 0.30);
  --alpha-sand-06:   rgba(188, 170, 164, 0.06);
  --alpha-sand-15:   rgba(188, 170, 164, 0.15);
  --alpha-sand-30:   rgba(188, 170, 164, 0.30);
  --alpha-pink-06:   rgba(244, 114, 182, 0.06);
  --alpha-pink-15:   rgba(244, 114, 182, 0.15);
  --alpha-pink-30:   rgba(244, 114, 182, 0.30);
  --alpha-violet-30: rgba(167, 139, 250, 0.30);
  /* Ступени 40 и 50 завелись 18.10 (круг 16): бейдж статуса на афише
     обведён рамкой роли, и прежний голубой стоял там числом 0.4 и 0.5. */
  --alpha-violet-40: rgba(167, 139, 250, 0.40);
  --alpha-violet-50: rgba(167, 139, 250, 0.50);

  /* ЛЕСТНИЦА ПРОЗРАЧНОСТИ, ступени достроены по ЗАМЕРУ круга B3.
     Белый звали числом 23 разными альфами, чёрный 19 — при шести ступенях
     на обе основы. Ступени ниже закрывают те значения, которые код звал
     чаще всего; всё остальное подтянуто к ближайшей.
     ЛЕСТНИЦА РАЗНАЯ ПО РОЛИ, а не одна на всё:
       белый граница    0.08 · 0.12 · 0.20
       белый подложка   0.04 · 0.08 · 0.12
       белый текст      0.35 · 0.50 · 0.72
       чёрный затемнение 0.50 · 0.65 · 0.85
       чёрный тень      0.30 · 0.50 · 0.90
     Граница ЭЛЕМЕНТА УПРАВЛЕНИЯ в эту лестницу не входит и держит 0.50:
     WCAG 1.4.11 требует от неё 3:1, а белый 0.20 на #0A0A0A даёт 1.7. */
  --alpha-white-20: rgba(255, 255, 255, 0.20);
  --alpha-white-28: rgba(255, 255, 255, 0.28);
  --alpha-black-50: rgba(0, 0, 0, 0.50);
  --alpha-black-85: rgba(0, 0, 0, 0.85);
  --alpha-black-90: rgba(0, 0, 0, 0.90);
  --alpha-black-60: rgba(0, 0, 0, 0.60);
  --alpha-black-30: rgba(0, 0, 0, 0.30);
  --alpha-black-25: rgba(0, 0, 0, 0.25);
  --alpha-black-20: rgba(0, 0, 0, 0.20);


  /* ==========================================================================
     2. ЦВЕТ — семантика. Вот это и используется в разметке.
     ========================================================================== */

  /* Фон */
  --bg-base:     var(--neutral-950);   /* фон страницы */
  --bg-card:     var(--neutral-900);   /* карточка на фоне страницы */
  --bg-surface:  var(--neutral-850);   /* панель, шапка, таб-бар */
  --bg-elevated: var(--neutral-750);   /* модалка, дропдаун, поповер */

  /* Акцент — лайм. ТОЛЬКО светлое на тёмном или заливка с чёрным текстом.
     Белый текст на лайме даёт контраст 1.17:1 — не читается. */
  --accent:        var(--lime-500);
  /* АКЦЕНТНЫЙ ТЕКСТ — СВОЁ ИМЯ, А НЕ ТОТ ЖЕ АКЦЕНТ (переехал сюда из
     `mobile/www/css/app.css` 02.11). По умолчанию равен заливочному:
     лайм на чёрном читается. В светлой теме приложение переопределяет
     его на ступень темнее — там лайм на светлом фоне падает ниже планки
     WCAG. Жил он только в одежде приложения, а зовут его теперь и общие
     компоненты (`.kslt-cc`, `.kslt-dd`), которые есть у обоих
     продуктов: на сайте имя оказалось бы пустым. */
  --accent-text: var(--accent);
  --accent-hover:  var(--lime-400);
  --accent-dim:    var(--lime-600);      /* сплошной, приглушённый */
  --accent-subtle: var(--alpha-lime-15); /* полупрозрачная подложка */
  --accent-glow:   var(--alpha-lime-30); /* только для теней/свечения */
  --accent-on:     var(--neutral-950);   /* текст поверх лаймовой заливки */

  /* Текст */
  --text-primary:   var(--neutral-white);
  --text-secondary: var(--alpha-white-72);
  --text-muted:     var(--alpha-white-50);
  --text-dim:       var(--alpha-white-35);

  /* Границы и стекло */
  --border-subtle:       var(--alpha-white-08);
  /* Фирменный синий Telegram из брендбука. Выбран Костей 19.09.
     В продукте до 21.09 жили ДВА синих: этот и #0088cc (10 вхождений
     в style.css, dashboard.css, admin.css и приложении). Второй —
     устаревший, из старой версии брендбука. */
  --brand-telegram: #2AABEE;
  --border-light:        var(--alpha-white-12);
  --surface-glass:       var(--alpha-white-04);
  --surface-glass-hover: var(--alpha-white-08);

  /* Статусы. По одному цвету на смысл — не четыре зелёных, как было. */
  --success:        var(--green-500);
  --success-subtle: var(--alpha-green-15);
  --danger:         var(--red-500);
  --danger-subtle:  var(--alpha-red-15);
  --danger-hover:   var(--red-400);      /* по образцу --accent-hover */
  --warning:        var(--orange-500);
  --warning-subtle: var(--alpha-orange-15);
  --info:           var(--blue-400);
  --info-subtle:    var(--alpha-blue-15);

  /* РОЛЬ «МЕНЕДЖЕР / ИДЁТ СЕЙЧАС». Своей роли у неё не было, и фиолетовый
     стоял числом в шести местах админки. Это не статус успеха и не
     предупреждение: ни одна занятая роль ей не подходит. */
  --role-manager:        var(--violet-400);
  --role-manager-subtle: var(--alpha-violet-15);

  /* РАЗЛИЧИТЕЛИ: «такой тип», а не «такое состояние». Имена по счёту, а
     не по краске: раздел говорит «различитель 1», и краска у него может
     смениться без правки разделов. */
  --cat-1:        var(--cyan-500);
  --cat-1-subtle: var(--alpha-cyan-15);
  --cat-2:        var(--sand-300);
  --cat-2-subtle: var(--alpha-sand-15);
  --cat-3:        var(--pink-400);
  --cat-3-subtle: var(--alpha-pink-15);

  /* ПОДСКАЗКА ГРАФИКА. Холст не понимает var(), краски ему подаёт дом
     красок (js/kslt-kraska.js) — поэтому имена тут, а не в правилах css.
     До 19.10 фон подсказки был записан ТРЕМЯ разными числами —
     rgba(20,20,24,.95) в графике рейтинга, rgba(20,20,30,.95) в аналитике,
     rgba(20,24,36,.95) в пользователях: одно понятие в трёх видах.
     Теперь одно имя. Фон = --neutral-850 при 95 %, ΔE к прежнему 2.8. */
  --chart-tip-bg:     rgba(20, 20, 20, 0.95);

  /* КАРТОЧКА ВАУЧЕРА — печатная картинка, не экран. Переход фона
     НАМЕРЕННЫЙ и фирменный: верх совпадает с уровнем продукта (ΔE 0.75
     к --neutral-900), низ синеватый и своего уровня в системе не имеет —
     ΔE 14.8 до ближайшего. Поэтому он назван именем и помечен намеренным,
     а не подогнан под ступень: подгонка убила бы фирменный переход.
     Холст var() не понимает — краски отсюда берёт дом. */
  /* ВЕРНУТ 30.10, И ЭТО ПОУЧИТЕЛЬНО. 26.10 кусок 179 снял его как
     мёртвый — «ни один не зовётся нигде, проверено двойным поиском, и вид
     не мог измениться ни на пиксель». Поиск искал `var(--vch-card-top)` в
     css, а дом печатной карточки берёт токен ИЗ JS ПО ИМЕНИ СТРОКОЙ:
     `краска('--vch-card-top')` (`js/kslt-vaucher-kartochka.js:77`).
     Четыре дня верх перехода был пустой строкой, холст бросал
     `addColorStop … ('')`, и карточка ваучера не рисовалась ВОВСЕ —
     поймала это проба 108 полным прогоном 30.10.
     ПРАВИЛО: ТОКЕН, КОТОРЫЙ БЕРУТ ИЗ JS ПО ИМЕНИ, НЕ ВИДЕН ПОИСКОМ ПО
     `var(`. Держит это сторож `tools/check-tokeny-iz-js.js`. */
  --vch-card-top:    var(--neutral-900);
  --vch-card-bottom: #1A1A2E;   /* НАМЕРЕНО: синева карточки */

  /* Тускло и граница — по лестнице роли (см. раздел альф). */
  --success-faint:  var(--alpha-green-06);
  --success-border: var(--alpha-green-30);
  --danger-faint:   var(--alpha-red-06);
  --danger-border:  var(--alpha-red-30);
  --warning-faint:  var(--alpha-orange-06);
  --warning-border: var(--alpha-orange-30);
  --info-faint:     var(--alpha-blue-06);
  --info-border:    var(--alpha-blue-30);
  --role-manager-faint:  var(--alpha-violet-06);
  --role-manager-border: var(--alpha-violet-30);
  /* Обводка бейджа на афише: поверх кадра рамка нужна плотнее обычной —
     крупный бейдж 0.5, боковой 0.4. Числа взяты у прежнего голубого,
     вид обводки не менялся. */
  --role-manager-ring:    var(--alpha-violet-50);
  --role-manager-ring-sm: var(--alpha-violet-40);

  /* Текст поверх сплошной заливки статуса. Та же причина, что у --accent-on:
     белый на #FF4757 даёт 3.34 — не проходит 4.5 для мелкого текста,
     почти-чёрный даёт 5.93. Яркость заливки при этом не трогаем. */
  --danger-on:      var(--neutral-950);
  /* То же и у зелёного: ПОСЧИТАНО 17.10 — белый на #2ED573 даёт 1.93,
     провал даже для графики (там норма 3); на прежнем #4CAF50 он давал
     2.78, то есть клетка «выиграл» не проходила и до сведения.
     Почти-чёрный даёт 10.26. Проверка в браузере — на стенде. */
  --success-on:     var(--neutral-950);

  /* ═══ ДВА ИГРОКА В МАТЧЕ ═══════════════════════════════════════════
     Кто из двоих — это НЕ статус и НЕ действие, поэтому статусные краски
     и лайм сюда не годятся: они уже заняты смыслом. Синий и оранжевый
     взяты потому, что различимы при самой частой слепоте, красно-зелёной,
     и не спорят ни с одним из наших состояний.
     До 24.09 пар было две: судейское окно красило игроков синим и
     оранжевым, а лента на странице матча — зелёным и янтарным, то есть
     теми же красками, что значат «успех» и «внимание». Одно понятие —
     одно определение. */
  --player-1: #4FC3F7;
  /* ПОСЧИТАНО 15.10. Было #FF7043 — тон 14°, вплотную к --danger (355°)
     и --warning (29°): различимость 1.22 и 1.35, то есть «сторона
     игрока» спорила с «опасностью» и «предупреждением». Новый тон —
     середина САМОГО ШИРОКОГО свободного промежутка в системе (138°
     между --blue-500 и --danger), светлота и насыщенность взяты у
     --player-1, чтобы пара читалась парой. До ближайшей занятой краски
     теперь 69° против прежних 15°. Проверено в браузере: 5.82 на
     почти-чёрном, 5.12 на фоне полосы при норме 3 для графики. */
  --player-2: #D050F7;

  /* НЕПРОЗРАЧНЫЕ ДВОЙНИКИ -subtle. Нужны там, где под подложкой не карточка,
     а фотография: бейдж статуса лежит на афише турнира, и сквозь 15% прозрачности
     проступает картинка — контраст перестаёт быть определённым числом.
     Значения = тот же тон 15% (нейтральный 12%), смешанный с карточкой. */
  --success-surface: #183122;
  --danger-surface:  #2A1517;
  --info-surface:    #1F2A37;
  --neutral-surface: #303030;
  /* ПОСЧИТАНО 16.10, ПРОВЕРЕНО В БРАУЗЕРЕ. Ступень у роли «внимание» была
     одна из пяти не заведена: `--danger-surface`, `--success-surface`,
     `--info-surface` и `--neutral-surface` есть, а оранжевой нет — и
     плашки «нужно членство» и «оплатите» красились прозрачным тинтом.
     Тон взят у `--warning` (#FF9F43), насыщенность и светлота — тем же
     приёмом, что у `--danger-surface`: 0.54 и 0.16. Контраст `--warning`
     на ней 7.96 и НЕ ЗАВИСИТ от того, что под ней. */
  --warning-surface: #2A1E13;

  --overlay-modal: var(--alpha-black-60);


  /* ==========================================================================
     3. ТИПОГРАФИКА
     Девять ступеней вместо 109. Между ними ничего нет.
     ========================================================================== */

  --font-sans: 'Inter', -apple-system, BlinkMacSystemFont, system-ui, sans-serif;

  --fw-regular:   400;
  --fw-medium:    500;
  --fw-semibold:  600;
  --fw-bold:      700;
  --fw-extrabold: 800;
  --fw-black:     900;

  --fs-2xs:  0.6875rem; /* 11px — микроподписи, бейджи */
  --fs-xs:   0.75rem;   /* 12px — подписи полей, мета */
  --fs-sm:   0.875rem;  /* 14px — вторичный текст, кнопки */
  --fs-base: 1rem;      /* 16px — основной текст */
  --fs-md:   1.125rem;  /* 18px — лид, крупный пункт */
  --fs-lg:   1.3125rem; /* 21px — H3 */
  --fs-xl:   1.625rem;  /* 26px — H2 */
  --fs-2xl:  2rem;      /* 32px — H1 */
  --fs-3xl:  2.5rem;    /* 40px — крупный заголовок */
  --fs-4xl:  4rem;      /* 64px — витринный размер: пустые состояния, крупные цифры.
                           Добавлено 21.09: семь мест в коде просили 64-70, и держать
                           их сырыми значениями хуже, чем признать ступень. */
  /* ОБЛОЖКА РАЗДЕЛА — СТУПЕНЬ, А НЕ ДОЛЯ ШИРИНЫ.
     До 04.10 здесь стояло clamp(1.9rem, 4.4vw, 3.1rem), и ЭТОТ ТОКЕН НЕ ЧИТАЛ
     НИКТО — грепом по css, js и pages ноль вхождений. А ровно эта строка была
     зашита числом в css/tournaments.css:71: страница категории повторяла собой
     мёртвый токен, который забыли перевести на ступени, когда 27.09 правили
     лестницу обложки на обзорной. Крутилки ниже меняет медиазапрос. */
  --fs-hero:     var(--fs-3xl);  /* 40 — заголовок обложки */
  --fs-hero-sub: var(--fs-lg);   /* 21 — подзаголовок обложки */
  --fs-hero-num: var(--fs-3xl);  /* 40 — цифра статистики */
  --fs-hero-cap: var(--fs-sm);   /* 14 — подпись под цифрой */

  /* ОДНА СТРОКА, ВЫСОТУ ЗАДАЁТ height. Введено 03.10: замер лестницы формы
     турнира нашёл четыре уровня на `normal` — кнопка, вкладка раздела,
     вкладка выключенная, кнопка действия. `normal` считается ОТ ШРИФТА, а
     Inter в админке не подключён (pages/admin.html), значит у разных людей
     число разное. У этих уровней строка одна, а высота уже задана ступенью
     кнопки, поэтому межстрочному нечего делать, кроме как не мешать. */
  --lh-none:    1;    /* однострочные управляющие: кнопки, вкладки, чипы */
  --lh-tight:   1.1;  /* заголовки */
  --lh-snug:    1.3;  /* подзаголовки */
  --lh-normal:  1.5;  /* основной текст */
  --lh-relaxed: 1.65; /* длинные абзацы */

  --ls-tight:  -0.02em; /* крупные заголовки */
  --ls-normal: 0;
  --ls-wide:   0.04em;  /* капслок, бейджи */


  /* ==========================================================================
     4. ОТСТУПЫ — кратно 4. Двенадцать ступеней.
     ========================================================================== */

  /* ПОСЧИТАНО И ПРОВЕРЕНО В БРАУЗЕРЕ 17.10. Шкала начиналась с 4, и
     из-за этого 24 места держали вертикальный отступ 2 числом: у бейджа
     `padding: 2px 8px` высота 20.5, вверх к 4 даёт 24.5, вниз к 0 —
     16.5, то есть бейдж либо толстеет, либо схлопывается. Ступень 2
     восстанавливает начало ритма 2 · 4 · 8 · 12 · 16, а не добавляется
     к списку. Горизонтального собрата у неё нет: по горизонтали меньше
     4 в админке не встречается. */
  --space-05: 0.125rem; /* 2 */
  --space-1:  0.25rem; /* 4  */
  --space-2:  0.5rem;  /* 8  */
  --space-3:  0.75rem; /* 12 */
  --space-4:  1rem;    /* 16 */
  --space-5:  1.25rem; /* 20 */
  --space-6:  1.5rem;  /* 24 */
  --space-8:  2rem;    /* 32 */
  --space-10: 2.5rem;  /* 40 */
  --space-12: 3rem;    /* 48 */
  --space-16: 4rem;    /* 64 */
  --space-20: 5rem;    /* 80 */
  --space-32: 8rem;    /* 128 */


  /* ==========================================================================
     5. РАДИУСЫ
     ========================================================================== */

  /* ПОСЧИТАНО И ПРОВЕРЕНО В БРАУЗЕРЕ 17.10. Шкала радиусов идёт
     удвоением — 4 · 8 · 16 · 32 плюс 12 и 24, — но НАЧИНАЛАСЬ она с 4,
     хотя ритм требует 2. Из-за пропуска микро-элементы держали радиус
     числом: метка 8×8 и коробки галочки 12 и 13 стоят на 2, а ступень 4
     превращает квадратик 8×8 в круг (доля радиуса от высоты 0.5).
     Ступень восстанавливает ритм, а не добавляется к списку. */
  --radius-2xs:  2px;
  --radius-xs:   4px;
  --radius-sm:   8px;
  --radius-md:   12px;
  --radius-lg:   16px;
  --radius-xl:   24px;
  --radius-2xl:  32px;
  --radius-full: 100px;


  /* ==========================================================================
     5б. ТОЛЩИНА РАМКИ

     Замер 14.10 по всем 28 файлам css: толщина написана голым числом
     748 раз, и значений шесть — 1 · 1.5 · 2 · 3 · 4 · 12. Половина
     пиксела (10 мест) браузер округляет сам: на обычном экране она
     станет 1 или 2 по его усмотрению, на плотном даст три физических.
     Это и есть число мимо шкалы — его нельзя ни померить, ни повторить.

     Ступеней три, как у Primer (thin · thick · thicker) и Material
     (1 · 2 · 3): волосок разделяет, толстая отмечает состояние —
     фокус, активную вкладку, выбранную карточку, — тяжёлая обводит
     портрет и подиум. Четвёртого случая замер не нашёл.

     ПОЛОСА-МАРКЕР — НЕ РАМКА. Четыре пиксела слева у строки тревоги
     (admin.css:296, :304) рисуют не границу, а цветную метку рода
     сообщения. У неё своё имя, и к шкале рамки она не относится.
     ========================================================================== */

  --bw-hair:  1px;  /* разделитель, обычная рамка карточки и поля */
  --bw-thick: 2px;  /* состояние: фокус, активная вкладка, выбор */
  --bw-heavy: 3px;  /* обводка портрета, подиум */

  --bar-accent: 4px; /* полоса-метка слева — не рамка */

  /* ГЛИФЫ, НАРИСОВАННЫЕ РАМКОЙ.
     Галочка и шеврон собраны из двух сторон рамки, повёрнутых на 45°.
     Замер 14.10: галочка в квадратике нарисована трижды (админка,
     вход, панель) с коробками 16 · 14 · 13, радиусами 3 · 3 · 2 и
     штрихами 2 · 1.5 · 1.5 — и ни одно число не ступень. Коробка
     встаёт на ступень отступа 16, радиус — на --radius-xs, штрих —
     на толстую ступень рамки.
     Галочка-маркер в списке нарисована трижды же: 14×8 дважды и
     13×7 один раз — третья уехала на пиксель по обеим осям. */

  --check-box:   16px;              /* квадратик чекбокса */
  --check-mark:  4px;               /* короткое плечо галочки */
  --check-mark-long: 8px;           /* длинное плечо */
  --tick-w:      14px;              /* маркер списка: длинное плечо */
  --tick-h:      8px;               /*               короткое плечо */
  --chevron:     8px;               /* шеврон раскрывашки, сторона */


  /* ==========================================================================
     6. ТЕНИ И ПЕРЕХОДЫ
     ========================================================================== */

  --shadow-sm: 0 2px 8px var(--alpha-black-20);
  --shadow-md: 0 4px 16px var(--alpha-black-25);
  --shadow-lg: 0 8px 32px var(--alpha-black-30);
  --glow-accent: 0 0 24px var(--accent-glow);

  /* ГРАДИЕНТ ЛОГОТИПА — ОДНА СУЩНОСТЬ, А НЕ ТРИ ЧИСЛА В ЧУЖОМ ФАЙЛЕ.
     Середина — это акцент продукта; края светлее и темнее его, и своих
     ступеней на шкале лайма у них нет: --lime-400 это #E0FF4D, а здесь
     #F0FF7A, --lime-600 это #B8E600, а здесь #9ED900. Числа оставлены как
     есть — менять вид логотипа никто не решал, — но живут они теперь в
     ОДНОМ месте, и середина берётся токеном. */
  --lime-logo-svetlyy: #F0FF7A;
  --lime-logo-tyomnyy: #9ED900;
  --gradient-logo: linear-gradient(90deg,
                     var(--lime-logo-svetlyy) 0%,
                     var(--accent) 40%,
                     var(--lime-logo-tyomnyy) 100%);

  --transition-fast: 0.2s ease;
  --transition-base: 0.3s ease;
  --transition-slow: 0.5s ease;


  /* ==========================================================================
     7. КНОПКИ
     Было: 151 селектор, 70 разных padding, 22 размера шрифта, 15 радиусов.
     Стало: четыре размера. Высота задаётся высотой, не вертикальным padding —
     тогда кнопка не пляшет от длины текста и от line-height шрифта.
     ========================================================================== */

  /* ЧИП. Высота из компонента `Badge 27:53`: 24, кегль 11 Medium uppercase.
     На шкале отступов такого числа нет и быть не должно — это размер
     компонента, и он заводится здесь, а не зашивается в секцию */
  --chip-h: 24px;

  --btn-h-xs: 28px;
  --btn-h-sm: 36px;
  --btn-h-md: 44px;  /* по умолчанию; 44 — минимальный тап-таргет по iOS HIG */
  --btn-h-lg: 52px;


  --btn-fs-md: var(--fs-sm);


  /* Поля ввода ходят в паре с кнопками — та же высота, иначе формы разъезжаются.
     Высоту задаёт height, а не вертикальный padding: у select line-height
     берётся от шрифта опций, и расчётная высота уезжает на 2-4px
     (замерено 18.09.2026: 44px у 125 полей, 46px у 9, 47.95px у одного). */
  --input-h:       var(--btn-h-md);
  --input-px:      var(--space-4);
  --input-fs:      var(--fs-base); /* 16px. Меньше 16 — Safari на iOS зумит страницу при фокусе */
  --input-radius:  var(--radius-sm); /* 8px — замерено у всех 145 полей админки */

  /* Многострочные поля. Без явного класса textarea берёт высоту из rows
     и размера шрифта — отсюда разнобой 80/120px. Класс обязателен. */
  --textarea-h-sm: 80px;   /* причина, слоган — 2-3 строки */
  --textarea-h:    144px;  /* описание — 6 строк при межстрочном 1.5 (замерено) */
  --textarea-h-lg: 250px;  /* текст статьи */


  /* ==========================================================================
     8. МОДАЛЬНЫЕ ОКНА
     Было: 97 классов, 14 разных max-width — 320, 380, 400, 420, 440, 460,
     480, 500, 520, 550, 640, 720. Стало: четыре.
     ========================================================================== */

  --modal-w-sm: 400px;  /* подтверждение, короткая форма */
  --modal-w-md: 520px;  /* форма средней длины */
  --modal-w-lg: 720px;  /* таблица, длинный контент */
  --modal-w-xl: 960px;  /* сетка, редактор */

  --modal-max-h:      90vh;

  /* Кнопки в подвале модалки.
     Сейчас 12 групп кнопок с gap 8, 10 и 12 и направлением то row,
     то column, то column-reverse — поэтому одна и та же пара кнопок
     выглядит по-разному в каждом окне. Правило одно:

       одна кнопка   — на всю ширину, primary
       две кнопки    — в ряд, вторичная слева, основная справа
       уже 480px     — в колонку, основная сверху
       три и больше  — это не модалка, это форма; выносить на страницу  */

  /* Нижняя шторка (мобильное приложение) */
  --sheet-max-h:   85vh;


  /* ==========================================================================
     9. БРЕЙКПОИНТЫ
     Было 27 значений, включая пары 768/769 и 991/992 — отсюда скачки вёрстки.
     Стало пять. CSS не умеет переменные в @media, поэтому это справочник:
     числа ниже вписываются в @media руками, других быть не должно.

       @media (max-width: 479.98px)  — телефон
       @media (max-width: 767.98px)  — крупный телефон / мелкий планшет
       @media (max-width: 1023.98px) — планшет
       @media (max-width: 1279.98px) — ноутбук
       @media (min-width: 1600px)    — широкий экран

     Дробь .98 вместо -1px: убирает щель, в которую не попадает ни одно
     правило на дробных плотностях экрана.
     ========================================================================== */

  --bp-xs: 480px;
  --bp-sm: 768px;
  --bp-md: 1024px;
  --bp-lg: 1280px;
  --bp-xl: 1600px;


  /* ==========================================================================
     10. МОБИЛЬНОЕ ПРИЛОЖЕНИЕ (Capacitor)
     ========================================================================== */

  --safe-top:    env(safe-area-inset-top, 0px);
  --safe-bottom: env(safe-area-inset-bottom, 0px);
  --tab-height:  64px;


  /* ==========================================================================
     13. РАЗМЕТКА — контейнер и вертикальный ритм

     Было: 105 правил с боковыми отступами в процентах (5% и 7% вперемешку),
     58 разных max-width, отступы между секциями 80/86/96/120px жёстко в px.
     На главной блоки начинались с шести разных левых границ.

     Стало: одна линия для всего содержимого, отступы тянутся за экраном.
     ========================================================================== */

  /* ВЫСОТА ПЛАВАЮЩЕЙ ШАПКИ. Одно число на весь продукт.
     Ею же отбивается контент на внутренних страницах, а на главной герой
     подтягивается под шапку на эту же величину.

     ОСТОРОЖНО, ЗДЕСЬ ЛЕГКО ОШИБИТЬСЯ — ЗДЕСЬ УЖЕ ОШИБАЛИСЬ.
     До 21.09.2026 тут стояло clamp(72px, 6vw, 100px) при настоящей высоте
     шапки 64. Шапка от ширины окна не зависит, а отбивка под ней зависела —
     и зазор до первой строки страницы полз: 23 / 31 / 31 / 33.6 / 45.1 на
     375 / 768 / 844 / 1024 / 1280. Замерено прибором
     tests/e2e/design-system/20-header-offsets.spec.js, 60 снимков.

     Поэтому ЗДЕСЬ ТОЛЬКО ЧИСЛО. Никаких clamp, vw и процентов: высота
     шапки меняется ступенькой, а не плавно. Вторая ступень — ниже. */
  --header-h: 64px;

  /* ЗАЗОР МЕЖДУ НИЗОМ ШАПКИ И ПЕРВОЙ СТРОКОЙ СТРАНИЦЫ.
     Решение Кости 21.09.2026, вариант А: 24 под пальцем, 32 на большом
     экране. Обе величины — ступени шкалы отступов (--space-6 и --space-8),
     а не выдуманные числа: различие по контексту выражено выбором ступени.

     ОСТОРОЖНО, ЗДЕСЬ ЛЕГКО ОШИБИТЬСЯ. До 21.09 этого токена не было, и
     зазор получался двумя разными способами: до 768 его задавало правило
     первой секции, а выше 768 он выходил ПОБОЧНО, из собственного
     вертикального отступа секции, который плавает с шириной. Отсюда и
     разнобой 23 / 31 / 31 / 33.6 / 45.1. Теперь способ один на все ширины.
     Вторая ступень — ниже, рядом с узкой шапкой. */
  --header-gap: 24px;

  --container-max: 1400px;

  /* ВТОРАЯ СТУПЕНЬ ВЫСОТЫ ШАПКИ — см. ниже @media.
     Держим её рядом с первой, чтобы не искать по файлу. */

  /* Узкая колонка для центрированных блоков — герой, оферта, призывы к действию.
     Было три разных ширины: 1000, 1100 и 1200. */
  --container-narrow: 1100px;

  /* Поле от края экрана: 16px на телефоне, до 32px на десктопе */
  --gutter: clamp(1rem, 2.2vw, 2rem);

  /* Главный приём: секция остаётся во всю ширину (фон от края до края),
     а содержимое внутри прижимается к общей линии и не шире контейнера.
     Обёртки в разметке для этого не нужны — отступ считается сам.
     Работает так: берём большее из двух — поле у края или половину
     остатка после контейнера. */
  --pad-x: max(var(--gutter), (100% - var(--container-max)) / 2);

  /* Вертикальный воздух ВНУТРИ секции — тянется за шириной экрана */
  --section-y:    clamp(1.5rem, 2.6vw, 2.5rem); /* 24 -> 40. Было 48 фиксом: между секциями 96px, стало 48-80 */
  --section-y-sm: clamp(1rem, 1.6vw, 1.5rem);  /* 16 -> 24 */

  /* Шов МЕЖДУ секциями. Как у Apple: блоки почти касаются,
     разделяет их смена фона, а не пустая полоса. */
  --section-gap: 1rem; /* 16 */

  /* Длина строки текста. Больше — читать тяжело, глаз теряет начало строки. */
  --measure: 70ch;

  /* ==========================================================================
     11. ОБРАТНАЯ СОВМЕСТИМОСТЬ
     Старые имена продолжают работать — ничего не ломается в день подключения.
     Удалять по мере переписывания кода. Когда блок опустеет, миграция закончена.
     ========================================================================== */

  /* --- сайт (css/style.css) --- */
  --bg-primary:   var(--bg-base);
  --bg-secondary: var(--bg-base);
  --font-primary: var(--font-sans);

  --space-xs:  var(--space-2);  /* 8  */
  --space-sm:  var(--space-4);  /* 16 */
  --space-md:  var(--space-6);  /* 24 */
  --space-lg:  var(--space-10); /* 40 */
  --space-xl:  var(--space-20); /* 80 */
  --space-2xl: var(--space-32); /* 128 */

  /* --- мобильное приложение (mobile/www/css/app.css) --- */
  --bg:            var(--bg-base);
  --bg-card-hover: var(--bg-surface);
  --text:          var(--text-primary);
  --text-sec:      var(--text-secondary);
  --border:        var(--border-subtle);
  --glass:         var(--surface-glass);

  --red:    var(--danger);
  --green:  var(--success);
  --blue:   var(--info);
  --orange: var(--warning);

  --tab-bg:            rgba(10, 10, 10, 0.92);
  --overlay-bg:        rgba(10, 10, 10, 0.95);
  --modal-bg:          var(--alpha-black-60);
  --modal-bg-heavy:    rgba(0, 0, 0, 0.70);
  --onb-bg:            var(--alpha-black-85);
  --shimmer:           var(--alpha-white-04);
  --badge-overlay:     var(--alpha-black-60);
  --serving-row:       rgba(204, 255, 0, 0.04);
  --img-overlay:       linear-gradient(to bottom, transparent 50%, rgba(0, 0, 0, 0.6));
  --img-overlay-top:   linear-gradient(to top, rgba(0, 0, 0, 0.7) 0%, transparent 60%);
  --img-overlay-40:    linear-gradient(to bottom, transparent 40%, rgba(0, 0, 0, 0.6));
  --guest-cta-bg:      rgba(18, 18, 18, 0.85);
  --guest-gradient-mid: rgba(10, 10, 10, 0.6);
  --guest-gradient-end: rgba(10, 10, 10, 0.92);
}

/* ============================================================================
   ВНИМАНИЕ — ОДНО РУЧНОЕ ИСПРАВЛЕНИЕ ПЕРЕД ПОДКЛЮЧЕНИЕМ

   Имя --accent-dim значит разное в двух кодовых базах:
     сайт:     --accent-dim: #B8E600                 (сплошной цвет)
     мобилка:  --accent-dim: rgba(204,255,0,0.15)    (полупрозрачная подложка)

   Автоматически развести нельзя — имя одно. В системе --accent-dim это
   сплошной цвет, как на сайте. Поэтому в mobile/www/css/app.css нужно
   заменить все вхождения var(--accent-dim) на var(--accent-subtle):

     sed -i '' 's/var(--accent-dim)/var(--accent-subtle)/g' mobile/www/css/app.css

   Без этой замены полупрозрачные подложки в приложении станут сплошными.
   ========================================================================= */

/* ============================================================================
   12. НАЙДЕНО ПРИ МИГРАЦИИ — переменные-сироты

   Эти имена используются в коде, но не были объявлены НИГДЕ — ни в старом
   :root сайта, ни в приложении. Часть спасали инлайновые фолбэки
   вида var(--ad-accent, #CCFF00), часть просто не работала: свойство
   с неразрешённой переменной браузер молча выбрасывает.

   Ниже они закрыты токенами. Побочный эффект: места, где правило раньше
   игнорировалось, теперь применится — это и есть починка, но экраны
   стоит глянуть глазами. Список где смотреть:
     --bg-tertiary   → challenge-detail, match-score
     --card-bg       → dashboard (ставится из dashboard.js)
     --border-color  → admin
     --space-xxl     → challenge-detail
   ========================================================================= */

:root {
  --ad-accent:         var(--accent);
  /* ВЕРНУЛИСЬ 26.10 ПО ТОЙ ЖЕ ПРИЧИНЕ, НО ЗОВ ДРУГОЙ: имена подсказки
     графика читает ДОМ ИЗ JS через `getPropertyValue`, и `var(` в коде нет
     вовсе. Сторож «краски холста» прямо говорит: без них подсказка
     нарисуется ПРОЗРАЧНОЙ. */
  --chart-tip-border: var(--border-light);
  --chart-tip-title:  var(--text-primary);
  --chart-tip-body:   var(--text-secondary);
  --glass-bg:          var(--bg-elevated);
  /* ВЕРНУЛИСЬ 26.10: прибор куска 179 назвал их мёртвыми, а чужой сторож
     «каждая переменная в css где-то объявлена» показал, что их ЗОВУТ С
     ЗАПАСНЫМ ЗНАЧЕНИЕМ (`var(--ad-border, …)` в admin.css:1907, 1917, 1921,
     1932) — признак видел только `var(--имя)` с закрывающей скобкой. */
  --ad-border:         var(--border-light);
  --ad-text:           var(--text-primary);
  --ad-text-secondary: var(--text-secondary);

  --bg-dark:      var(--bg-base);
  --bg-sec:       var(--bg-surface);
  --bg-tertiary:  var(--bg-elevated);
  --card-bg:      var(--bg-card);
  --border-color: var(--border-light);

  --font:      var(--font-sans);
}

/* ============================================================================
   14. КЛАССЫ РАЗМЕТКИ

   .container   — содержимое по общей линии, само центруется
   .full-bleed  — растянуть от края до края внутри контейнера
   .measure     — ограничить длину строки текста
   ========================================================================= */

.container {
  width: min(100% - var(--gutter) * 2, var(--container-max));
  margin-inline: auto;
}

.measure { max-width: var(--measure); }


/* ============================================================================
   ОБЛОЖКА РАЗДЕЛА — ОДНО ОПРЕДЕЛЕНИЕ НА ВСЕ РАЗДЕЛЫ

   ПРИЁМ НЕ НОВЫЙ. Он выбран на лендинге (css/style.css:1715 — «медиазапрос
   крутит КРУТИЛКУ, а объявление padding остаётся одно на весь файл») и
   повторён на обзорной турниров (css/tournaments-overview.css:9 — «Тот же
   приём, что у .hero на главной»). Не хватало ОДНОГО: ручки звались по
   странице — --hero-* у лендинга, --to-hero-* у обзорной, — и третья страница
   завела бы свои. Имя стало одно и живёт здесь, рядом с --section-y и --pad-x.

   ВЫСОТА — ПОЛ, А НЕ РАЗМЕР. Рост даёт содержимое; пол держит обложку от
   схлопывания, когда цифр мало. Замер 04.10 на боевой: высота обложки
   категории не менялась НИ от длины цифр (1, 3 и 5 знаков), НИ от длины
   названия (16 и 47 знаков) — её держало число 460, и содержимое до него не
   дорастало: 210 из 460 на десктопе, больше двухсот пикселей пустоты.

   НА НИЗКОМ ГОРИЗОНТАЛЬНОМ ЭКРАНЕ ПОЛА НЕТ ВОВСЕ. Телефон боком: было 460 при
   высоте экрана 390 — фотография съедала первый экран целиком, турниров не
   видно. Решение принято на обзорной 28.09 (доска 482:9, блок M.1, слова
   Кости «обложка 460 → 360») и здесь только доведено до остальных разделов.

   ГРАНИЦЫ ТОЛЬКО 640 И 992 — третьей не заводится.
   ========================================================================= */
:root {
  --oblozhka-pol:        360px;                /* пол высоты */
  --oblozhka-vozduh:     var(--section-y-sm);  /* 16 -> 24 сверху */
  --oblozhka-vozduh-niz: var(--space-2);       /* 8 снизу */
}

@media (max-width: 992px) {
  :root {
    /* ПОЛА ЗДЕСЬ НЕТ НАРОЧНО. Замер обзорной 04.10: 360 и на 1024, и на 768 —
       ступени на 992 у неё не было, и я её сперва выдумал, скопировав число
       из соседнего правила. Пол меняется один раз, на 640. */
    --fs-hero:     var(--fs-2xl);  /* 32 */
    --fs-hero-sub: var(--fs-md);   /* 18 */
    --fs-hero-num: var(--fs-2xl);  /* 32 */
    --fs-hero-cap: var(--fs-xs);   /* 12 */
  }
}

@media (max-width: 640px) {
  :root {
    --oblozhka-pol: 275px;
    --fs-hero:     var(--fs-xl);   /* 26 */
    --fs-hero-sub: var(--fs-base); /* 16 */
    --fs-hero-num: var(--fs-lg);   /* 21 */
    --fs-hero-cap: var(--fs-2xs);  /* 11 */
  }
}

/* ВЕС ПРАВИЛА РЕШАЕТ, А НЕ НАМЕРЕНИЕ.
   Цифры и подписи обложек правит общее правило css/style.css:10958 долями
   ширины — `.tournament-hero-stats .hero-stat-value`, вес (0,2,0). Пока
   крутилка стояла на одноклассовом `.hero-stat-value` (0,1,0), она не
   доезжала: замер 04.10 дал 30.4 вместо 32 и 11.52 вместо 12. Здесь тот же
   приём, которым обзорная обошла это правило 27.09 (tournaments-overview.css
   :129), только список семейств общий, а не одно.
   ОБЩЕЕ ПРАВИЛО НЕ ТРОГАЕМ: оно накрывает лендинг и страницу матча —
   закрытые куски. Находка стоит на доске 482:9, блок L, с 27.09. */
.to-hero .tournament-hero-stats .hero-stat-value,
.tournament-hero .tournament-hero-stats .hero-stat-value,
.td-hero .td-hero-stats .hero-stat-value,
/* Баттлы 13.10: семейства здесь не было, и цифру обложки правило витрины
   считало долями ширины — на 390 выходило 25, числа нет ни в шкале, ни в
   токенах. */
.bo-hero .bo-hero-stats .bo-hero-stat-value {
  font-size: var(--fs-hero-num);
}

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

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

   ЧТО БЫЛО. Пол здесь стоял НУЛЁМ с подписью «его высота и есть вся
   страница», и высоту задавало содержимое. ЗАМЕРЕНО В БРАУЗЕРЕ,
   ЗАГРУЗКОЙ НА 844×390, четырьмя длинами названия:
     16 знаков  — 180 (46 % экрана)
     46 знаков  — 215 (55 %)
     74 знака   — 215 (55 %)
     108 знаков — 251 (64 %), заголовок в ЧЕТЫРЕ строки
   То есть ровно на этом виде высота обложки стала свойством ДАННЫХ. А
   решение по разделу записано иначе и давно (`css/tournaments.css:17`):
   «одна высота у всех категорий… раньше высоту задавало содержимое, и
   шесть страниц одного раздела выглядели по-разному». На низком
   горизонтальном это решение просто потеряли, когда убрали пол.

   ЧТО СТАЛО, ТЕМ ЖЕ ПРИБОРОМ: 180 при всех четырёх длинах. Кегль
   заголовка опущен на ОДНУ СТУПЕНЬ шкалы (32 → 26), и названия до
   полусотни знаков встают в одну строку; пол и потолок равны, заголовок
   не длиннее двух строк. Цифры не срезаются ни в одном из случаев
   (замер: низ блока цифр на 8–11 выше дна обложки).

   ЧЕМ ПЛАТИМ, НАЗВАНО: у названия длиннее ~50 знаков хвост уходит под
   многоточие. Полное имя остаётся в заголовке вкладки и в карточках.

   ГРАНИЦЫ ТОЛЬКО 640 И 992 — здесь не ширина, а поворот и высота, тот
   же порог 500, что у полноэкранного живого матча. */
@media (max-height: 500px) and (orientation: landscape) {
  :root {
    --oblozhka-pol: 180px;
    --oblozhka-potolok: 180px;
    /* ВСЕ ЧЕТЫРЕ УРОВНЯ, А НЕ ОДИН. Первой правкой 31.10 я опустил только
       заголовок — и он стал 26 при цифре 32, то есть МЕЛЬЧЕ ЦИФРЫ:
       лестница перевернулась. Уровни обложки читаются как лестница и
       едут одной границей, поэтому вид берёт набор ступени 640 целиком.
       Высоты это не меняет: пол и потолок равны, обложка ровно 180. */
    --fs-hero:     var(--fs-xl);   /* 26 */
    --fs-hero-sub: var(--fs-base); /* 16 */
    --fs-hero-num: var(--fs-lg);   /* 21 */
    --fs-hero-cap: var(--fs-2xs);  /* 11 */
  }
}


/* ============================================================================
   УЗКАЯ ШАПКА

   Ниже границы шапка ниже. Стандарт Material Design: 56 dp на компактных
   экранах, 64 на средних и выше. Решение Кости от 21.09.2026.

   ГРАНИЦА 600, а не 375. Material Design: 56 dp на компактных экранах,
   64 на средних и выше, и граница между ними — 600. До 21.09.2026 здесь
   стояло 375 — число, доставшееся от старого правила nav { height: 56px },
   написанного под iPhone SE. То есть узкую шапку видел только самый узкий
   телефон, а iPhone 12 Pro (390), 14 Pro (393), Pixel (412) получали
   десктопную высоту 64 на экране высотой 844.

   ОСТОРОЖНО: 600 — НЕ ПРОИЗВОЛЬНОЕ ЧИСЛО и не подгонка под устройство.
   Это граница compact / medium из Material Design. Менять её значит менять
   стандарт, на который мы сослались, — а не «подвинуть на пару пикселей».
   ========================================================================= */
@media (max-width: 599px) {
  :root {
    --header-h: 56px;
  }
}

/* ============================================================================
   БОЛЬШОЙ ЭКРАН — ЗАЗОР ШИРЕ

   Заголовок страницы на десктопе 40px. Двадцать четыре сверху прижимают его
   к шапке, и он читается как строка меню, а не как заголовок. Тридцать два
   дают ему стать заголовком. Сравнивали макетами: Figma 207:207.

   993 — та же граница, на которой герой главной перестаёт уходить под шапку.
   Заводить рядом вторую, свою, значило бы добавить ещё один перелом.
   ========================================================================= */
@media (min-width: 993px) {
  :root {
    --header-gap: 32px;
  }
}

/* ============================================================================
   БАЗОВЫЙ СЛОЙ — ШРИФТ УПРАВЛЕНИЙ

   `button`, `input`, `select` и `textarea` не наследуют семейство от body:
   браузер ставит им свой системный шрифт. В продукте это лечилось поштучно —
   `font-family: inherit` был написан ВОСЕМЬДЕСЯТ раз в двадцати файлах css,
   и там, где забыли, управление выпадало в системный Arial.

   ПОЧЕМУ ЗДЕСЬ, А НЕ В `style.css`. Правило завели 12.10 в `style.css`, и оно
   не доезжало до двух страниц из тридцати одной: судейское окно и табло
   `style.css` не грузят — у них свой css. `tokens.css` грузят ВСЕ 90 страниц
   продукта. ЦЕНА: файл перестал быть «только токенами» и несёт один базовый
   селектор. Заплачено сознательно: одно определение, доезжающее всем, важнее
   чистоты имени файла.

   Селектор из одних типов, вес нулевой — любое правило с классом перебьёт
   его, если шрифт управления намеренно другой (счёт матча `monospace`).
   Поштучные копии сняты все восемьдесят; за их числом следит сторож.
   ========================================================================= */
/* АТРИБУТ HIDDEN ПРОТИВ DISPLAY В КЛАССЕ.

   Браузер прячет `[hidden]` правилом `display: none` из своей таблицы,
   но любое НАШЕ правило с `display` его перебивает — у него выше
   приоритет. Замер 21.09 на сайте: спрятанный слот Apple внутри
   `.auth-social` (display: flex) продолжал занимать 64 пикселя.
   Замер 02.11 в приложении: рамка «Нет трансляции» (display: flex)
   занимала 358 x 201, хотя код ставил ей `hidden`, а подпись к коду
   уверяла, что место снято. ПОДПИСЬ ВРАЛА, ПОТОМУ ЧТО Я ЕЁ НЕ ПОМЕРИЛ.

   Это классические грабли, и правило стоит ОДИН раз на оба продукта:
   до 02.11 оно жило в `css/style.css` и до приложения не доезжало. */
[hidden] {
    display: none !important;
}

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

/* ══════════════════════════════════════════════════════════════════════
   ДВИЖЕНИЕ ВЫКЛЮЧАЕТСЯ ОДНИМ МЕСТОМ, А НЕ ДВАДЦАТЬЮ СЕМЬЮ (заход B4,
   15.10.2026).

   БЫЛО: 27 блоков `prefers-reduced-motion` в 21 файле, каждый перечислял
   один-четыре селектора ПОИМЁННО. Список в каждом отставал от своего
   файла, и 26 бесконечных анимаций из 38 не гасились вовсе: пульс эфира,
   мерцание отсчёта, вращение рамки, спиннер. Человеку, который выключил
   анимации в системе, они продолжали мигать.

   ПРАВИЛО, ПЕРЕЧИСЛЯЮЩЕЕ ИМЕНА, СТАРЕЕТ ВМЕСТЕ С КОДОМ. Поэтому здесь
   звёздочка: новая анимация гасится самим фактом того, что она анимация.

   ПОЧЕМУ 0.01ms, А НЕ `none`. `animation: none` оставляет элемент в
   НАЧАЛЬНОМ кадре: всё, что появляется через `opacity: 0 -> 1`, у
   rm-посетителя осталось бы невидимым навсегда. Длительность 0.01ms
   доматывает анимацию до конечного кадра мгновенно — элемент виден, а
   движения нет. Это приём из практики доступности (Material, Apple HIG,
   GOV.UK), а не выдумка.

   `!important` здесь нужен: правила секций объявлены позже и перебили бы
   этот блок. Это единственное место в продукте, где он оправдан — у
   запроса человека приоритет выше любой секции.

   ЧЕГО ЗДЕСЬ НЕТ. Две ленты везут содержимое, и остановка СПРЯТАЛА БЫ
   часть: лента услуг и лента логотипов. Им движение тоже снимается, но
   взамен даётся прокрутка пальцем — см. `services.css` и `style.css`.
   ══════════════════════════════════════════════════════════════════════ */
@media (prefers-reduced-motion: reduce) {
    *,
    *::before,
    *::after {
        animation-duration: 0.01ms !important;
        animation-iteration-count: 1 !important;
        transition-duration: 0.01ms !important;
        scroll-behavior: auto !important;
    }
}


/* ══════════════════════════════════════════════════════════════════════
   ВЫБОР КОДА СТРАНЫ `.kslt-cc` — ОДИН ОРГАН НА ВЕСЬ ПРОДУКТ (02.11).

   ПЕРЕЕХАЛ СЮДА ИЗ `admin.css`. Выбор страны просят четыре места:
   админка (корты), вход на сайте, вход в приложении и кабинет
   приложения. Пока разметка жила в `admin.css`, остальным троим
   доставался нативный `<select>` — и каждый обзавёлся своим списком.
   `tokens.css` — единственный css, который копируется в приложение
   с проверкой (`tools/check-mobilnye-kopii.js`), поэтому компонент,
   нужный обоим продуктам, живёт здесь.

   ПОЧЕМУ НЕ НАТИВНЫЙ `<select>`. Три причины, и все три померены:
   · КАЗАХСТАН И РОССИЮ НЕ РАЗЛИЧИТЬ: у обоих `+7`, а в закрытом поле
     нативный показывает ту же строку, что в списке. Названия стран
     туда не помещаются — обрезаются на полуслове.
   · СПИСОК РИСУЕТ ANDROID, А НЕ МЫ. В приложении он открывался
     системным окном во весь экран, и в каждой его строке справа от
     кода стоял знак КСЛТ — Костя увидел это глазами 02.11. Что бы его
     туда ни приводило, внутрь системного окна css не достаёт.
   · ВЫСОТА. Нативному нельзя назначить цель нажатия 44 надёжно.

   Стоит на общих токенах, своих чисел не заводит.
   ══════════════════════════════════════════════════════════════════════ */

/* ===== ВЫБОР КОДА СТРАНЫ ===== */
/* Компактная кнопка с флагом и кодом; названия стран видны в раскрытом
   списке. Нативный select показывал бы «Кыргызстан +996» и занимал
   половину строки. */

.kslt-cc {
    position: relative;
    flex-shrink: 0;
}

.kslt-cc-toggle {
    display: flex;
    align-items: center;
    gap: var(--space-1);
    height: 44px;
    padding: 0 var(--space-3);
    background: var(--bg-elevated);
    border: var(--bw-hair) solid var(--border-subtle);
    border-radius: var(--radius-sm);
    color: var(--text-secondary);
    font-size: var(--fs-sm);
    white-space: nowrap;
    cursor: pointer;
    transition: border-color var(--transition-fast);
}

.kslt-cc-toggle:hover {
    border-color: var(--border-light);
}

.kslt-cc-toggle svg {
    opacity: 0.6;
    transition: transform var(--transition-fast);
}

.kslt-cc.open .kslt-cc-toggle svg {
    transform: rotate(180deg);
}

.kslt-cc-menu {
    position: absolute;
    top: calc(100% + 6px);
    left: 0;
    z-index: 80;
    min-width: 230px;
    max-height: 300px;
    overflow-y: auto;
    padding: var(--space-1);
    background: var(--bg-elevated);
    border: var(--bw-hair) solid var(--border-subtle);
    border-radius: var(--radius-sm);
    box-shadow: 0 12px 32px var(--alpha-black-50);
    display: none;
}

.kslt-cc.open .kslt-cc-menu {
    display: block;
}

.kslt-cc-item {
    display: flex;
    align-items: center;
    gap: var(--space-2);
    width: 100%;
    padding: var(--space-2) var(--space-2);
    background: none;
    border: none;
    border-radius: var(--radius-sm);
    color: var(--text-secondary);
    font-size: var(--fs-sm);
    text-align: left;
    cursor: pointer;
    transition: background var(--transition-fast), color var(--transition-fast);
}

.kslt-cc-item:hover {
    background: var(--alpha-white-08);
    color: var(--text-primary);
}

.kslt-cc-item.active {
    color: var(--accent-text);
}

.kslt-cc-name {
    flex: 1;
}

.kslt-cc-code {
    color: var(--text-muted);
}

.kslt-cc-item.active .kslt-cc-code {
    color: var(--accent-text);
    opacity: 0.7;
}

/* ОБВОДКА ФОКУСА — ОТДЕЛЬНАЯ ОТ НАВЕДЕНИЯ: тот, кто ходит клавишами,
   обязан видеть, где он, а `:hover` ему недоступен. */
.kslt-cc-toggle:focus-visible,
.kslt-cc-item:focus-visible {
    outline: var(--bw-thick) solid var(--accent);
    outline-offset: 2px;
}


/* ══════════════════════════════════════════════════════════════════════
   ВЫПАДАЮЩИЙ ФИЛЬТР `.kslt-dd` — ОДИН ОРГАН НА ВЕСЬ ПРОДУКТ (02.11).

   Разметку строит `js/kslt-vypadashka.js`. До него выпадающий фильтр был
   написан ЧЕТЫРЕЖДЫ (корты, тренеры, партнёры, новости), и доступность
   с галочкой выбранного оказались только в одной копии из четырёх.

   ЧИСЛА ВЗЯТЫ С ДОСКИ, А НЕ ПРИДУМАНЫ. `Dropdown 34:55`: панель 240 для
   фильтров, 320 для списка уведомлений, радиус md, рамка 1px
   border/subtle, внутренний отступ 4, тень как у Toast. `Menu item
   32:44`: высота 36 (`--btn-h-sm`), боковые отступы 12, радиус sm,
   подпись 14, у выбранного галочка.

   ДВА РАСХОЖДЕНИЯ С ДОСКОЙ, И ОБА НАЗВАНЫ, А НЕ ЗАМОЛЧАНЫ:
   · ЗАЛИВКА. Компонент велит `bg/surface`; здесь `--bg-elevated`, потому
     что НАШ ЖЕ токен подписан «модалка, дропдаун, поповер» (строка 171),
     и на нём стоят все четыре живые выпадашки. Менять заливку — менять
     вид работающего сайта, а правило 22.10 это запрещает. Расхождение
     идёт в отчёт фигма: решать, кто прав, доске.
   · ТЕНЬ. Компонент велит 45% чёрного, в палитре есть ступень 50%
     (`--alpha-black-50`), на ней стоит весь продукт. Заводить ступень
     ради пяти процентов — это новое число мимо шкалы.
   ══════════════════════════════════════════════════════════════════════ */

.kslt-dd {
    position: relative;
    flex-shrink: 0;
}

.kslt-dd-toggle {
    display: inline-flex;
    align-items: center;
    gap: var(--space-2);
    min-height: var(--btn-h-md, 44px);
    padding: 0 var(--space-4);
    background: var(--glass, var(--bg-surface));
    border: var(--bw-hair) solid var(--border);
    border-radius: var(--radius-full);
    color: var(--text-secondary);
    font-size: var(--fs-xs);
    font-weight: 500;
    white-space: nowrap;
    cursor: pointer;
    transition: border-color var(--transition-fast), color var(--transition-fast);
}

/* ВЫБРАННЫЙ ФИЛЬТР ВИДЕН ПО КНОПКЕ, А НЕ ТОЛЬКО ВНУТРИ СПИСКА:
   закрытая выпадашка — единственное, что человек видит, пока не нажал. */
.kslt-dd-toggle.is-selected {
    background: var(--accent);
    border-color: var(--accent);
    color: var(--neutral-black);
}

.kslt-dd-arrow {
    opacity: 0.7;
    transition: transform var(--transition-fast);
}

.kslt-dd.open .kslt-dd-arrow {
    transform: rotate(180deg);
}

.kslt-dd-menu {
    position: absolute;
    top: calc(100% + var(--space-2));
    left: 0;
    z-index: 60;
    min-width: 240px;
    max-width: calc(100vw - 40px);
    max-height: 320px;
    overflow-y: auto;
    padding: 4px;
    background: var(--bg-elevated);
    border: var(--bw-hair) solid var(--border-subtle);
    border-radius: var(--radius-md);
    box-shadow: 0 12px 32px var(--alpha-black-50);
    display: none;
}

.kslt-dd--wide .kslt-dd-menu {
    min-width: 320px;
}

/* Последняя выпадашка в ряду держится своей кнопки: иначе её список
   уезжает за край экрана, и на телефоне это половина пунктов. */
.kslt-dd:last-child:not(:first-child) .kslt-dd-menu {
    left: auto;
    right: 0;
}

.kslt-dd.open .kslt-dd-menu {
    display: block;
}

.kslt-dd-item {
    display: flex;
    align-items: center;
    gap: var(--space-3);
    width: 100%;
    min-height: var(--btn-h-sm);
    padding: 0 var(--space-3);
    background: none;
    border: none;
    border-radius: var(--radius-sm);
    color: var(--text-secondary);
    font-size: var(--fs-sm);
    text-align: left;
    cursor: pointer;
    transition: background var(--transition-fast), color var(--transition-fast);
}

.kslt-dd-label {
    flex: 1;
}

.kslt-dd-item:hover {
    background: var(--alpha-lime-06);
    color: var(--accent-text);
}

/* ВЫБРАННЫЙ ПУНКТ НЕСЁТ ГАЛОЧКУ, А НЕ ТОЛЬКО КРАСКУ. `Menu item 32:44`
   требует этого прямо: «never rely on the tint alone, it is invisible to
   anyone who cannot separate the two greys». */
.kslt-dd-item.is-selected {
    color: var(--accent-text);
    font-weight: 600;
}

.kslt-dd-tick {
    flex-shrink: 0;
    color: var(--accent-text);
}

.kslt-dd-count {
    flex-shrink: 0;
    font-size: var(--fs-xs);
    color: var(--text-muted);
    font-variant-numeric: tabular-nums;
}

.kslt-dd-item.is-selected .kslt-dd-count {
    color: var(--accent-text);
    opacity: 0.7;
}

.kslt-dd-toggle:focus-visible,
.kslt-dd-item:focus-visible {
    outline: var(--bw-thick) solid var(--accent);
    outline-offset: 2px;
}


/* ══════════════════════════════════════════════════════════════════════
   ЖУРНАЛ ХОДА МАТЧА `.lm-feed` / `.lm-game` / `.lm-cell` (02.11).

   Разметку строит `js/kslt-zhurnal-matcha.js`. Одежда переехала сюда из
   `css/live-match.css` — 45 блоков, 256 строк, — потому что журнал
   теперь показывают ОБА продукта: страница матча на сайте и накладка
   Live в приложении. `tokens.css` — единственный css, который
   копируется в приложение с проверкой.

   ЧИСЛА И КРАСКИ ПЕРЕНЕСЕНЫ БЕЗ ИЗМЕНЕНИЙ: вид на сайте прежний, и это
   условие переноса, а не пожелание — правило 22.10 запрещает менять
   вид работающего экрана заодно с починкой внутренностей.
   ══════════════════════════════════════════════════════════════════════ */

/* СТУПЕНЬ МЕТКИ РОЗЫГРЫША. Девять пикселей — решение 25.09 (почему
   именно столько и почему не жирным, см. у `.lm-cell-mark` ниже). Это
   число МИМО ШКАЛЫ КЕГЛЕЙ, и до переноса его видел `check-shkala`:
   тот ищет сырой `font-size` ВНЕ `tokens.css`. Переезд спрятал бы его
   от сторожа молча — поэтому число получило имя здесь, а присмотр за
   ним вернул `check-zhurnal-matcha`. ЧИСЛО МИМО ШКАЛЫ НЕЛЬЗЯ НИ
   ПОМЕРИТЬ, НИ ПОВТОРИТЬ, пока у него нет имени. */
:root {
    --lm-mark-fs: 9px;

    /* СТУПЕНИ ЖУРНАЛА. Шкала продукта меряет отступы и кегли, а не
       высоту списка, поэтому размеры заводятся в компоненте, как того и
       требует правило «нужен новый размер — заводится в компоненте».
       ПОСЧИТАНО из частей и проверено замером:
       гейм 128 = шапка 21 + две строки по 28 + зазоры;
       обвязка 123 = подпись 17 + чипы сетов 36 + поля карточки.
       ПОЛ ПРИ ТРАНСЛЯЦИИ: идущий гейм помещается целиком. Меньше — и
       списку остаётся 13 пикселей из 131. Костя: «журнал не читается».
       ПОЛ БЕЗ ТРАНСЛЯЦИИ больше на обвязку: там же стоят подпись и чипы
       сетов, и без этой добавки список получал 0 пикселей — померено
       02.11 на накладке Live в приложении.
       До 02.11 ступени стояли на `.lm-container` страницы матча, и
       приложение их не видело. */
    --lm-game-h: 128px;
    --lm-feed-chrome: 123px;
    --lm-feed-min-video: calc(var(--lm-game-h) + var(--space-4) * 2 + var(--space-2));
    --lm-feed-min: calc(var(--lm-feed-chrome) + var(--lm-game-h));
}

/* `.lm-feed { display: flex }` стояла в `css/live-match.css` ДВАЖДЫ —
   на 179 и 451, — и переехала сюда дубликатом вместе с остальным. Первая
   снята 02.11: в каскаде работает последняя (ниже, у `::before`), вид не
   меняется. ОДНО ОПРЕДЕЛЕНИЕ НА ОДНО ПОНЯТИЕ; сторож журнала теперь
   роняет правило, если объявление заведётся второй раз. */
.lm-feed {
    background: var(--bg-card);
    border: var(--bw-hair) solid var(--border-subtle);
    border-radius: var(--radius-lg);
    /* Воздух снизу тот же, что сверху и по бокам. Было 8: последняя
       строка гейма упиралась в край коробки. Решение Кости 25.09:
       «дать воздуха снизу». */
    padding: var(--space-4);
    /* ФОН ПАНЕЛИ — решение Кости 25.09, снимок «арена».
       ПОЧЕМУ СНИЗУ И С РАСТВОРЕНИЕМ, А НЕ ПРОСТО ПОГАШЕННЫЙ.
       Костя спросил, на сколько процентов гасить. Померил: чтобы самая
       тусклая цифра держала 4.5 В ЛЮБОЙ точке панели, гасить надо на 84 %
       — и от снимка остаётся 0.4 % яркости, то есть не видно вовсе. Одна
       яркая точка прожектора тянет вниз весь слой. Числа сказали, что
       фотографию нельзя класть ПОД строки, сколько её ни гаси.
       Поэтому она живёт внизу и растворяется вверх: пока журнал пустой,
       виден корт и пустого окна нет; чем длиннее матч, тем больше строк и
       тем меньше снимка. Под строками его нет, и контраста он не трогает.
       Снимок выбрал Костя — ракетка и мяч. Подробности и поправку к
       моему же счёту см. у .lm-feed::before ниже. */
    position: relative;
    overflow: hidden;
    isolation: isolate;
    /* ВЛИТО ИЗ ТРЕТЬЕГО ОБЪЯВЛЕНИЯ 02.11. `.lm-feed` была объявлена в
       `css/live-match.css` ТРИЖДЫ: 179 и 451 — один и тот же
       `display: flex`, 385 — этот блок. Между ними стоял только
       `.lm-feed > *`, к самой коробке не относящийся, так что каскад и
       вид прежние. Осталось одно объявление. */
    display: flex;
    flex-direction: column;
}
.lm-feed > * { position: relative; z-index: 1; }
.lm-feed::before {
    content: '';
    position: absolute;
    inset: 0;
    z-index: 0;
    background-image: url('../images/live-fony/panel-raketka.webp');
    background-size: cover;
    background-position: 50% 100%;
    -webkit-mask-image: linear-gradient(to top, var(--neutral-black) 0%, rgba(0,0,0,.62) 58%, rgba(0,0,0,.34) 100%);
    mask-image: linear-gradient(to top, var(--neutral-black) 0%, rgba(0,0,0,.62) 58%, rgba(0,0,0,.34) 100%);
}
.lm-feed-title {
    font-size: var(--fs-2xs);
    font-weight: var(--fw-semibold);
    text-transform: uppercase;
    letter-spacing: 0.08em;
    color: var(--text-muted);
    margin: 0 0 var(--space-4);
}
.lm-feed-list { list-style: none; margin: 0; padding: 0; position: relative; }
.lm-feed-item {
    position: relative;
    display: flex; justify-content: space-between; gap: var(--space-4);
    padding: 0 0 var(--space-4) var(--space-6);
    font-size: var(--fs-sm);
    color: var(--text-secondary);
}
.lm-feed-item::before {
    content: '';
    position: absolute; left: 0; top: 4px;
    width: 12px; height: 12px;
    border-radius: var(--radius-full);
    background: var(--surface-glass-hover);
}
.lm-feed-item::after {
    content: '';
    position: absolute; left: 5px; top: 18px; bottom: 0;
    width: 2px;
    background: var(--border-subtle);
}
.lm-feed-item:last-child::after { display: none; }
.lm-feed-item.is-now { color: var(--accent); font-weight: var(--fw-bold); }
.lm-feed-item.is-now::before { background: var(--accent); }
/* `.lm-feed-note` снят 02.11 вместе с переносом журнала: заметку у
   строки запасной ленты не рисовал никто — поле `note` всегда было
   пустым. МЁРТВЫМ КОД НАЗЫВАЕТСЯ ПОСЛЕ ПРОВЕРКИ. */
/* ЧИПЫ СЕТОВ — ОДНА СТРОКА С ПРОКРУТКОЙ, А НЕ ПЕРЕНОС.
   Померено 02.11 на накладке Live, телефон 390: четыре чипа («Все» и
   три сета) вставали в ДВЕ строки и забирали 80 пикселей из 338,
   отведённых журналу, — почти целый гейм на переключатель. В одну
   строку ряд занимает 36, и списку достаётся 241 вместо 164.
   На сайте в колонке 917 они и так стояли в строку: там ничего не
   меняется. МАТЧ ИЗ ПЯТИ СЕТОВ ДАСТ ШЕСТЬ ЧИПОВ — перенос сломался бы
   и там, просто позже. */
.lm-feed-chips {
    display: flex;
    flex-wrap: nowrap;
    overflow-x: auto;
    scrollbar-width: none;
    gap: var(--space-2);
    margin-bottom: var(--space-4);
}
.lm-feed-chips::-webkit-scrollbar { display: none; }
.lm-chip { flex: 0 0 auto; }

/* КТО В КОЛОНКЕ РАСТЁТ, А КТО НЕТ. `.lm-feed` — колонка, и до 02.11 её
   дети этого не объявляли: на сайте места всегда хватало, и сжатия не
   случалось. В накладке приложения, где высота впритык, ряд чипов
   схлопнулся с 36 до ТРЁХ пикселей — кнопки остались 36, а ряд сжался
   под ними. РАСТЁТ СПИСОК ГЕЙМОВ, И ТОЛЬКО ОН. */
.lm-feed-title, .lm-feed-chips { flex: 0 0 auto; }
.lm-feed-games { flex: 1 1 auto; }
.lm-chip {
    height: var(--btn-h-sm);
    padding: 0 var(--space-4);
    border: var(--bw-hair) solid var(--border-subtle);
    border-radius: var(--radius-full);
    background: var(--surface-glass);
    color: var(--text-secondary);
    font-size: var(--fs-sm);
    font-weight: var(--fw-medium);
    cursor: pointer;
    transition: background var(--transition-fast), color var(--transition-fast);
}
.lm-chip.is-active {
    background: var(--accent);
    border-color: var(--accent);
    color: var(--accent-on);
    font-weight: var(--fw-semibold);
}
.lm-chip:focus-visible { outline: var(--bw-heavy) solid var(--accent); outline-offset: 3px; }
.lm-game {
    padding: var(--space-4) 0 var(--space-2);
    border-top: var(--bw-hair) solid var(--border-subtle);
}
.lm-game:first-of-type { border-top: none; padding-top: 0; }
.lm-game-head {
    display: flex;
    align-items: baseline;
    gap: var(--space-3);
    margin-bottom: var(--space-3);
}
.lm-game-no::first-letter { text-transform: uppercase; }
.lm-game-no {
    font-size: var(--fs-sm);
    font-weight: var(--fw-bold);
    color: var(--text-primary);
}
.lm-game-res {
    margin-left: auto;
    font-size: var(--fs-2xs);
    color: var(--text-muted);
    font-variant-numeric: tabular-nums;
}
.lm-game-res.is-now {
    color: var(--danger);
    font-weight: var(--fw-bold);
    text-transform: uppercase;
    letter-spacing: 0.06em;
}
.lm-game-row {
    display: flex;
    align-items: center;
    gap: var(--space-3);
    height: 28px;
}
/* ФАМИЛИЯ ЖМЁТСЯ, ИНИЦИАЛ — НЕТ (03.11).

   `text-overflow: ellipsis` СТОЯЛ ЗДЕСЬ И НЕ ДЕЙСТВОВАЛ: многоточие
   работает только на БЛОЧНОМ контейнере, а `.lm-row-name` —
   `display: flex`. Имя резалось насухо, и отличить обрыв от короткой
   фамилии было нельзя. Померено на телефоне 390, место под текст 74:
   «Абдыкадыров Т.» просит 89, «Сапарбеков А.» 79, двойная фамилия 137 —
   последняя не влезает НИ НА ОДНОМ виде.

   ТЕПЕРЬ УСЕКАЕТСЯ ТОТ УЗЕЛ, КОТОРОМУ ЭТО РАЗРЕШЕНО. Инициал несжимаем:
   он различитель однофамильцев и стоит 11 пикселей из 74. `min-width: 0`
   обязателен — у flex-элемента минимальная ширина равна содержимому, и
   без него фамилия вытолкнула бы инициал за край. */
.lm-row-name {
    flex: 0 0 104px;
    display: flex;
    align-items: center;
    gap: var(--space-2);
    padding-left: 10px;
    font-size: var(--fs-2xs);
    color: var(--text-secondary);
    white-space: nowrap;
    overflow: hidden;
}
.lm-row-fam {
    flex: 0 1 auto;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
}
/* ПРОБЕЛ, А НЕ ОТСТУП — И ПОТОМУ В `em`, А НЕ СТУПЕНЬЮ ШКАЛЫ. Между
   фамилией и инициалом всегда стоял обычный пробел; когда имя стало
   двумя узлами, его место занял `gap` полосы — 8 вместо трёх, и у
   коротких имён («Ким С.») вид поехал там, где он работал. Шкала
   отступов меряет расстояния МЕЖДУ ЭЛЕМЕНТАМИ, а здесь расстояние
   ВНУТРИ слова: оно обязано расти вместе с кеглем. Замер до и после
   совпал до пикселя. */
.lm-row-name { gap: 0; }
.lm-row-serve { margin-right: var(--space-2); }
.lm-row-ini { flex: 0 0 auto; margin-left: 0.27em; }
.lm-row-1 .lm-row-name { box-shadow: inset 3px 0 0 var(--player-1); }
.lm-row-2 .lm-row-name { box-shadow: inset 3px 0 0 var(--player-2); }
.lm-row-serve {
    flex: 0 0 6px;
    width: 6px; height: 6px;
    border-radius: var(--radius-full);
    background: var(--accent);
}
.lm-row-cells {
    display: flex;
    gap: 4px;
    /* flex: 1 1 0 и min-width: 0 ОБЯЗАТЕЛЬНЫ. Без них ряд ячеек в узкой
       колонке схлопывался до 32 — одна ячейка вместо семи: у flex-элемента
       с прокруткой минимальная ширина равна нулю, и он отдавал всё место
       соседу. Померено при переезде журнала в колонку 400. */
    flex: 1 1 0;
    min-width: 0;
    overflow-x: auto;
    scrollbar-width: thin;
    /* ПОЛЕ СВЕРХУ — ПОД МЕТКУ, А НЕ ДЛЯ КРАСОТЫ. Найдено Костей 25.09, когда
       метка впервые доехала до ленты: «эйс не помещается, его не видно».
       Померено: `.lm-cell-mark` висит на top: -7px, то есть СНАРУЖИ своей
       коробки, а этот ряд — горизонтальный скроллер. Всё, что выше края,
       он срезает. `overflow-y: visible` не спасает: при `overflow-x: auto`
       браузер принудительно делает `auto` и по второй оси — в замере так и
       вышло, auto/auto. Поэтому метке отдаётся поле внутри коробки.
       ПЛАТИМ ВЫСОТОЙ: два ряда на гейм, по 8 — ступень --lm-game-h выросла
       со 112 до 128. Число поднято в компоненте, а не зашито в секцию. */
    padding-top: var(--space-2);
}
.lm-cell {
    position: relative;
    flex: 0 0 32px;
    height: 24px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    border-radius: var(--radius-sm);
    font-size: var(--fs-xs);
    font-variant-numeric: tabular-nums;
    /* ПОМЕРЕНО: 38 % белого дало 3.54 при пороге 4.5 — тусклая цифра
       проваливалась. Это не украшение, это СЧЁТ. На --text-muted 5.33. */
    color: var(--text-muted);
}
.lm-cell.is-won {
    background: var(--bg-elevated);
    color: var(--text-primary);
    font-weight: var(--fw-bold);
}
.lm-cell.is-game {
    flex: 0 0 32px;
    font-size: var(--fs-2xs);
    font-weight: var(--fw-bold);
    text-transform: uppercase;
    letter-spacing: normal;
}
.lm-cell.is-game.is-won {
    background: var(--accent);
    color: var(--accent-on);
}
.lm-cell-mark {
    position: absolute;
    top: -7px; right: -2px;
    font-size: var(--lm-mark-fs);
    /* НЕ ЖИРНАЯ И НЕ КУРСИВ. Решение 25.09, вечер, по просьбе Кости
       «сделай не жирным и курсивом может» — беру первое, отказываюсь от
       второго, и вот чем это обосновано.
       НЕ ЖИРНАЯ: у метки было ТРИ признака сразу — лайм, плашка и Bold, —
       и она спорила с цифрой счёта, которую подписывает. У подписи признак
       должен быть один; здесь он цвет. Вес уходит на Medium: ниже 500 на
       девяти пикселях лайм начинает «звенеть» тонкими штрихами.
       НЕ КУРСИВ: девять пикселей прописью по-русски курсивом читаются хуже
       прямого — наклон съедает и без того малые просветы, а «эйс» и
       «двойная» судья и зритель ловят боковым зрением. Курсив здесь дал бы
       четвёртый признак вместо того, чтобы убрать третий.
       ПРОПИСНЫЕ СНЯТЫ по той же причине: это подпись, а не заголовок. */
    font-style: normal;
    font-weight: var(--fw-medium);
    text-transform: lowercase;
    letter-spacing: 0.02em;
    color: var(--accent);
    background: var(--bg-card);
    padding: 0 3px;
    border-radius: var(--radius-full);
}
.lm-break {
    font-size: var(--fs-2xs);
    font-weight: var(--fw-semibold);
    color: var(--accent);
    background: var(--accent-subtle);
    padding: 2px var(--space-2);
    border-radius: var(--radius-full);
}
.lm-empty {
    padding: var(--space-12) var(--space-5);
    text-align: center;
    border: var(--bw-hair) dashed var(--border-subtle);
    border-radius: var(--radius-lg);
}
.lm-empty-title {
    font-size: var(--fs-base);
    font-weight: var(--fw-semibold);
    color: var(--text-secondary);
    margin: 0 0 var(--space-2);
}
.lm-empty-text { font-size: var(--fs-sm); color: var(--text-muted); margin: 0; }
/* ПРОКРУЧИВАЕТСЯ ВНУТРЕННИЙ СПИСОК, А НЕ ВСЯ ПАНЕЛЬ. Прокрутка на самой
   .lm-feed уносит снимок, лежащий на её ::before, вместе со строками.
   Фон обязан стоять; заодно стоят заголовок и чипы сетов. */
.lm-feed-games {
    overflow-y: auto;
    min-height: 0;
    scrollbar-width: thin;
    scrollbar-gutter: stable;
}

/* УЗКИЕ СТУПЕНИ ЖУРНАЛА. Ячейка уже, кегль НЕ мельчает: 14 и 12 уже
   нижние ступени, на которых счёт ещё читается. Граница 640 — одна из
   двух, что есть у продукта.
   ПОРЯДОК НА ТЕЛЕФОНЕ ЗДЕСЬ НЕ ЖИВЁТ: `order` у `.lm-feed` — свойство
   СТРАНИЦЫ, где журнал стоит между кадром и табло, и остался в
   `css/live-match.css`. В приложении журнал стоит один, и порядка ему
   не нужно. ОТСТУП, КОТОРЫЙ ЕДЕТ ВМЕСТЕ С СОДЕРЖИМЫМ, НЕ ОТСТУП. */
@media (max-width: 640px) {
    .lm-row-name { flex: 0 0 84px; }
    .lm-cell { flex: 0 0 28px; }
    .lm-cell.is-game { flex: 0 0 28px; }
}
