/* ==========================================================================
   Марго Квиз — Design System v2 «Неон-сцена»
   components.css — предметы домена, из которых складываются экраны.

   Стадия 03 по docs/design/prompts/03-components.md. Порядок покрытия из
   UX_PLAN.md: ЗАЛ → КАПИТАН → ОПЕРАТОР. Закрыты все три: зал 1–12,
   капитан 13–23, оператор 24–32. Группы разделены баннером-полосой,
   поверхности не перемешиваются: у зала нет ввода, у капитана нет дистанции,
   у оператора нет права на промах.

   ПРЕДМЕТЫ 33–39 ДОБАВЛЕНЫ СТАДИЕЙ 04 (2026-08-23) вместе с двумя последними
   поверхностями — мини-игрой и лендингом. Они не образуют своей группы
   намеренно: 33 (живой фон) и 34 (кнопка действия) нужны сразу двум
   поверхностям, 35–38 — мини-игре, 39 — лендингу. Правило прежнее: предмет,
   понадобившийся второму экрану, живёт здесь, а не в файле первого.

   ТРИ ПРАВИЛА, КОТОРЫЕ ДОРОЖЕ ВКУСА
   1. Компонент называется ПО ДОМЕНУ. Не «карточка», не «панель» — в этом
      продукте панель бывает панелью вердикта, очередью буззера и подготовкой
      сессии, и это три разных предмета с разной судьбой.
   2. Один акцент в кадре — цвет текущей эмоции. Статусы (верно, неверно,
      медали) исключение: они сообщают ФАКТ, а не настроение.
   3. Цвет, кегль, отступ, радиус и тень — только из tokens.css. Ни одного
      литерального hex.

   ПРО ПИКСЕЛИ. Внутри сцены размеры только в px — сцена спроектирована в
   координатах 1920×1080 и масштабируется целиком. Отступы и радиусы берутся
   из шкал; собственные габариты компонента (диаметр кольца, высота полосы)
   объявлены ИМЕНОВАННЫМИ переменными в начале своего блока — чтобы их можно
   было найти поиском и поправить в одном месте, а не россыпью литералов.

   ПРО ЧТЕНИЕ С ДИСТАНЦИИ. Зал смотрит с расстояния до 4 метров
   (PRODUCT.md:24). Пол чтения — --fs-h2; всё, что ниже, вспомогательное.
   Поэтому команда, очки и вердикт не бывают мельче --fs-h2, а реплика Марго
   и тикер сознательно ниже: их не обязаны прочитать.
   ========================================================================== */

/* ==========================================================================
   АТРИБУТ hidden ГАСИТ ЛЮБОЙ ПРЕДМЕТ СИСТЕМЫ

   Три раза подряд одна и та же мина: плашка «команд пока нет» в лобби зала,
   плашка отказа на входе капитана, окно бонуса за скорость на буззере.
   Каждый прячут атрибутом hidden — и каждый оставался на экране, потому что
   hidden ставит display: none правилом БРАУЗЕРА, а display: flex у предмета
   объявлен АВТОРОМ и потому сильнее.

   Лечится один раз и для всех: селектор из двух простых частей
   (класс-атрибут) весит больше одиночного класса, поэтому побеждает
   независимо от порядка объявления, и !important не нужен.

   Правило системное: узел, который прячут, обычно нельзя просто удалить —
   он держит место в раскладке, чтобы появление отказа не сдвигало кнопку
   под пальцем в момент нажатия.
   ========================================================================== */
[class^="mk-"][hidden],
[class*=" mk-"][hidden] { display: none; }

/* ==========================================================================
   ОХРАННОЕ ПОЛЕ КАДРА — ОДНО ПРАВИЛО НА ВСЕ ЭКРАНЫ ЗАЛА

   Слот — это правая часть сцены, 1424 × 984, которую узел пересоздаёт на
   каждой смене экрана (screens/lobby.js, viewer.js). Экран приходит и
   уходит, слот остаётся — поэтому поле кадра принадлежит слоту, а не
   экрану.

   Раньше поля были перечислены поимённо, экран за экраном, и разъехались:
   экраны стадии 04 стояли на 48, сцены (screens/scenes.css) на 32, интро
   тура вообще без полей, а лобби держало своё третье число. Владелец:
   «Нет охранной зоны для деталей ТВ. Всё впритык». Теперь число одно —
   --mk-guard из tokens.css, — и объявлено оно один раз здесь.

   ПОЧЕМУ В components.css, А НЕ В screens/hall.css. Правило действует
   только там, где файл подключён, а файл экранов зала подключают не все
   поверхности с этими экранами: дизайн-стенд грузит tokens + motion +
   components + lobby + splash, и hall.css среди них нет. Пока правило
   стояло там, стенд — поверхность приёмки по docs/design/sync.md — остался
   без охранного поля вовсе. Здесь тот же случай, что у .mk-qr, .mk-ticker
   и .mk-team-chip: предмет, понадобившийся второй поверхности, живёт в
   общем файле, а не в файле первой.

   СЛЕВА ПОЛЯ НЕТ НАМЕРЕННО: зазор до колонки ведущей даёт её правое поле,
   и оно равно тому же --mk-guard. Иначе между Марго и краем экрана
   набегало бы двойное поле, а между Марго и слотом — одинарное.
   ========================================================================== */
.mk-screen-slot {
    position: relative;
    z-index: 2;
    flex: 1 1 auto;
    min-width: 0;
    padding: var(--mk-guard) var(--mk-guard) var(--mk-guard) 0;
}

/* ЛОББИ — ЕДИНСТВЕННОЕ ИСКЛЮЧЕНИЕ, И ОНО НЕ ПРО ВКУС. По нижней кромке
   кадра лежит тикер фактов (72 px, --mk-ticker-h), прибитый к ней абсолютно;
   охранное поле снизу отсчитывается от ТИКЕРА, а не от кромки. Двойное
   --mk-guard даёт 96 — ровно то число, что стояло здесь до охранного поля
   (lobby.css, .mk-lobby): тикер плюс воздух над ним. Два класса в селекторе —
   чтобы правило пережило любой порядок подключения файлов. */
.mk-screen-slot.mk-lobby { padding-bottom: calc(var(--mk-guard) * 2); }

/* ВТОРАЯ ДЕТАЛЬ КАДРА — КОЛОНКА ВЕДУЩЕЙ, и поле у неё то же самое. Было 32
   (--sp-6) против 48 у экранов: ведущая стояла к кромке ближе, чем всё
   остальное, а её ПРАВОЕ поле — единственный зазор до слота (слева слот
   поля не имеет), и плашка тура вставала к Марго вплотную. Оба поля равны
   --mk-guard, поэтому клип-бокс колонки совпадает с содержимым слота по всем
   четырём кромкам: обрезается одновременно, а не «раньше блока с вопросом».

   Правило стоит здесь, а не в screens/hall.css, ровно по той же причине,
   что и поле слота: половина охранного поля, доехавшая до поверхности без
   второй половины, — это не охранное поле, а новый разнобой. Ширина колонки
   (520 в зале) осталась в hall.css: это решение зала, а не кадра, и на
   стендах колонка по-прежнему 400. */
.mk-stage > .mk-margo { padding: var(--mk-guard); }

/* ==========================================================================
   1 · Панель вопроса
   Состояния: без вариантов · с четырьмя · с медиа · медиа не загрузилось
   ========================================================================== */

/* Панель ограничена высотой слота. Без этого предела медиа и длинный вопрос
   вместе перерастают кадр, и обнаруживается это уже в зале, где отката нет.

   ЧИСЛА ЗДЕСЬ НЕТ НАМЕРЕННО. Стояло `var(--mk-slot-h, 984px)` — то есть 984
   жило в файле вторым экземпляром выведенного значения: `--mk-slot-h` и есть
   «кадр минус охранное поле сверху и снизу» (tokens.css), и подвинуть поле,
   не подвинув запасной вариант, значило бы получить два разных потолка на
   одну панель. Запасной вариант к тому же вступал в силу ровно там, где он
   бессмысленен: вне `.mk-stage` (галерея компонентов, стенды) кадра 1920×1080
   нет вовсе, и потолок в 984 px — просто чужое число. Токен объявлен на
   `.mk-stage`; нет сцены — нет и предела. */
.mk-question {
    display: flex;
    flex-direction: column;
    gap: var(--sp-6);
    padding: var(--sp-7);
    max-height: var(--mk-slot-h);
}

/* Текст вопроса — --fs-h1: он обязателен к прочтению всем залом.
   Гарнитура текстовая, а не дисплейная: дисплейная на 140 знаках
   превращается в стену. */
.mk-question__text {
    margin: 0;
    font-family: var(--font-body);
    font-size: var(--fs-h1);
    line-height: 1.24;
    color: var(--text-hi);
    text-wrap: balance;
}

/* Пропорция блока ЗАДАЁТСЯ, а не получается случайно из ширины панели.
   16:9 — то, в чём снимают и что отдают камеры; кадр другой пропорции ляжет
   внутрь с полями, но сам блок останется предсказуемым, и вопрос под ним
   не будет прыгать от картинки к картинке.

   Высота ограничена сверху: сцена 1080, и вопросу под медиа нужно место
   на две-три строки в --fs-h1. 520 px по высоте дают 924 px по ширине —
   блок уже панели и потому центрируется. Если панель окажется уже 924,
   ширину подрежет max-width, а высота пересчитается сама. */
.mk-question__media {
    --mk-media-h: 520px;              /* потолок по высоте в координатах сцены */
    align-self: center;
    /* Медиа отдаёт место тексту, а не наоборот: flex-shrink забирает у него
       высоту, когда вопрос переносится в лишнюю строку, а aspect-ratio держит
       16:9 и пересчитывает ширину. Приоритет верный — вопрос обязателен
       к прочтению, снимок сопровождает. */
    flex: 0 1 auto;
    min-height: 0;
    height: var(--mk-media-h);
    max-width: 100%;
    aspect-ratio: 16 / 9;
    width: auto;
    display: flex;
    align-items: center;
    justify-content: center;
    border-radius: var(--r-md);
    background: var(--bg-0);
    border: 1px solid var(--glass-border);
    overflow: hidden;
}

/* contain, а не cover: обрезать чужой кадр нельзя — на снимке может быть
   ровно та деталь, о которой спрашивают. */
.mk-question__media img,
.mk-question__media video,
.mk-question__media > svg { width: 100%; height: 100%; object-fit: contain; }

/* Медиа не загрузилось. Экран обязан выглядеть осмысленно в деградации:
   зал не должен понять, что что-то сломалось, — поэтому не «ошибка», а
   спокойная заглушка, и вопрос остаётся читаемым сам по себе. */
.mk-question__media--broken {
    flex-direction: column;
    gap: var(--sp-4);
    color: var(--text-dim);
    font-family: var(--font-body);
    font-size: var(--fs-h3);
}

.mk-question__media--broken svg {
    --mk-broken-icon: 64px;
    width: var(--mk-broken-icon);
    height: var(--mk-broken-icon);
    opacity: 0.5;
}

.mk-question__options {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: var(--sp-5);
}

/* ==========================================================================
   2 · Вариант ответа
   Состояния: покой · выбран большинством · верный · пустой слот
   ========================================================================== */

.mk-option {
    --mk-option-key: 72px;            /* сторона квадрата с буквой */
    position: relative;
    display: flex;
    align-items: center;
    gap: var(--sp-5);
    padding: var(--sp-5);
    border-radius: var(--r-lg);
    background: var(--glass-bg);
    border: 1px solid var(--glass-border);
    overflow: hidden;
}

/* Дорожка доли голосов — под содержимым, по --mk-share.
   ЗАПОЛНЯЕТСЯ scaleX, А НЕ ШИРИНОЙ (стадия 05). Ширина — свойство раскладки:
   каждый её кадр заставляет браузер пересчитывать геометрию, а дорожек
   в кадре четыре и заполняются они одновременно, на глазах у зала.
   Контракт переменной поэтому изменился: --mk-share теперь ЧИСЛО 0…1,
   а не проценты, — процент в calc не поделить, чтобы получить множитель. */
.mk-option::before {
    content: "";
    position: absolute;
    inset: 0;
    background: var(--emotion-faint);
    transform: scaleX(var(--mk-share, 0));
    transform-origin: left center;
    transition: transform var(--dur-md) var(--ease-out);
}

.mk-option__key,
.mk-option__text { position: relative; }

.mk-option__key {
    display: flex;
    align-items: center;
    justify-content: center;
    flex: 0 0 var(--mk-option-key);
    height: var(--mk-option-key);
    border-radius: var(--r-md);
    background: var(--bg-2);
    border: 1px solid var(--glass-border);
    font-family: var(--font-display);
    font-weight: 700;
    font-size: var(--fs-h2);
    color: var(--text-mid);
}

.mk-option__text {
    font-family: var(--font-body);
    font-size: var(--fs-h2);
    color: var(--text-hi);
}

.mk-option__share {
    position: relative;
    margin-left: auto;
    font-family: var(--font-num);
    font-variant-numeric: tabular-nums;
    font-size: var(--fs-h3);
    color: var(--text-mid);
}

/* Выбран большинством — эмоция, не статус: факта здесь ещё нет. */
.mk-option--picked {
    border-color: var(--emotion-border);
    box-shadow: var(--glow-emotion-soft);
}

.mk-option--picked .mk-option__key {
    background: var(--emotion-soft);
    border-color: transparent;
    color: var(--emotion-ink);
}

/* Верный — статус. Показывается ТОЛЬКО когда каскад окончен: пока право
   ходит по очереди, правильный ответ на экране не появляется. */
.mk-option--correct {
    border-color: var(--ok);
    box-shadow: var(--glow-ok);
}

.mk-option--correct::before { background: var(--ok-glow); width: 100%; }

.mk-option--correct .mk-option__key {
    background: var(--ok);
    border-color: transparent;
    color: var(--bg-0);
}

/* Пустой слот — вариантов меньше четырёх. Не пропуск в сетке, а погашенное
   место: иначе оставшиеся три расползаются и ломают ритм кадра. */
.mk-option--empty {
    background: transparent;
    border-style: dashed;
    opacity: 0.35;
}

.mk-option--empty .mk-option__key { visibility: hidden; }

/* ==========================================================================
   3 · Кольцо таймера
   Состояния: идёт · десять секунд · последние пять · время вышло · пауза
   Пульсация только scale и opacity — толщина кольца не анимируется никогда.
   ========================================================================== */

.mk-timer {
    --mk-timer-size: 240px;
    --mk-timer-track: 14px;           /* толщина кольца, величина постоянная */
    position: relative;
    width: var(--mk-timer-size);
    height: var(--mk-timer-size);
    display: grid;
    place-items: center;
}

.mk-timer svg { position: absolute; inset: 0; transform: rotate(-90deg); }

.mk-timer circle {
    fill: none;
    stroke-width: var(--mk-timer-track);
    stroke-linecap: round;
}

.mk-timer__track { stroke: var(--glass-border); }

.mk-timer__run {
    stroke: var(--emotion);
    transition: stroke-dashoffset 1s linear, stroke var(--dur-md) var(--ease-inout);
}

.mk-timer__value {
    font-family: var(--font-num);
    font-variant-numeric: tabular-nums;
    font-size: var(--fs-hero);
    color: var(--text-hi);
    line-height: 1;
}

/* Десять секунд — цвет меняется, размер нет: скачок кегля на табло читается
   как сбой, а не как предупреждение. */
.mk-timer--warn .mk-timer__run { stroke: var(--warn); }
.mk-timer--warn .mk-timer__value { color: var(--warn); }

/* Последние пять — срочный вид: тик КАЖДУЮ СЕКУНДУ, и тик этот вешает
   событие, а не бесконечный цикл (choreo.js, tick). Цикл длиной в секунду
   не синхронизирован с самой секундой: за пять тактов он расходится
   с цифрой на пол-такта, и зал видит, что кольцо пульсирует отдельно
   от числа. Толщина кольца не анимируется никогда — только scale. */
.mk-timer--urgent .mk-timer__run { stroke: var(--err); }
.mk-timer--urgent .mk-timer__value { color: var(--err); }

.mk-timer--done .mk-timer__run { stroke: var(--err); }
.mk-timer--done .mk-timer__value { color: var(--err); opacity: 0.6; }

.mk-timer--paused .mk-timer__run { stroke: var(--text-dim); }
.mk-timer--paused .mk-timer__value { color: var(--text-dim); }

/* ==========================================================================
   4 · Объявление буззера
   Кто нажал: команда, обращение к капитану, время реакции крупно.
   ========================================================================== */

.mk-buzzer-call {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: var(--sp-4);
    padding: var(--sp-7);
    text-align: center;
}

.mk-buzzer-call__team {
    font-family: var(--font-display);
    font-weight: 700;
    font-size: var(--fs-h1);
    color: var(--text-hi);
}

.mk-buzzer-call__captain {
    font-family: var(--font-body);
    font-size: var(--fs-h2);
    color: var(--emotion-soft);
}

/* Время реакции — герой кадра: это единственное число, которое зал
   обсуждает вслух. Моноширинное, иначе прыгает при смене разряда. */
.mk-buzzer-call__ms {
    font-family: var(--font-num);
    font-variant-numeric: tabular-nums;
    font-size: var(--fs-hero);
    color: var(--emotion);
    line-height: 1;
}

.mk-buzzer-call__ms sub {
    font-size: var(--fs-h3);
    color: var(--text-mid);
    vertical-align: baseline;
    margin-left: var(--sp-2);
}

/* Очередь: кто следующий, если ответ не засчитают. */
.mk-buzzer-queue {
    display: flex;
    gap: var(--sp-3);
    margin-top: var(--sp-4);
}

.mk-buzzer-queue__item {
    display: flex;
    align-items: center;
    gap: var(--sp-2);
    padding: var(--sp-2) var(--sp-4);
    border-radius: var(--r-full);
    background: var(--glass-bg);
    border: 1px solid var(--glass-border);
    font-family: var(--font-body);
    font-size: var(--fs-h3);
    color: var(--text-mid);
}

/* Исключена — «эта команда на этом вопросе больше не играет». */
.mk-buzzer-queue__item--out {
    opacity: 0.4;
    text-decoration: line-through;
}

/* ПРОБЕЛ В ТОКЕНАХ ЗАКРЫТ 22.08.2026. Было: у эмоции свечение оформлено
   токенами, у статусов — только цвет с альфой, поэтому радиус в четырёх местах
   стоял литералом. Владелец принял --glow-ok / --glow-err; в tokens.css они
   объявлены тремя ступенями (soft 16 / базовый 42 / strong 60), потому что
   статус светится и на точке 8 px, и на плашке во весь слот. Все четыре
   литерала заменены токенами, новых литералов не заводить. */

/* ==========================================================================
   5 · Плашка вердикта
   Состояния: верно · неверно · правильный ответ раскрыт
   ========================================================================== */

.mk-verdict {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: var(--sp-5);
    padding: var(--sp-7);
    border-radius: var(--r-lg);
    text-align: center;
}

.mk-verdict__mark {
    --mk-verdict-mark: 120px;
    display: grid;
    place-items: center;
    width: var(--mk-verdict-mark);
    height: var(--mk-verdict-mark);
    border-radius: var(--r-full);
}

.mk-verdict__mark svg {
    --mk-verdict-icon: 56px;
    width: var(--mk-verdict-icon);
    height: var(--mk-verdict-icon);
}

.mk-verdict__team {
    font-family: var(--font-display);
    font-weight: 700;
    font-size: var(--fs-h2);
    color: var(--text-hi);
}

.mk-verdict__points {
    font-family: var(--font-num);
    font-variant-numeric: tabular-nums;
    font-size: var(--fs-hero);
    line-height: 1;
}

.mk-verdict--ok {
    background: var(--bg-1);
    border: 1px solid var(--ok);
    box-shadow: var(--glow-ok-strong);
}

.mk-verdict--ok .mk-verdict__mark { background: var(--ok); color: var(--bg-0); }
.mk-verdict--ok .mk-verdict__points { color: var(--ok); }

.mk-verdict--err {
    background: var(--bg-1);
    border: 1px solid var(--err);
    box-shadow: var(--glow-err-strong);
}

.mk-verdict--err .mk-verdict__mark { background: var(--err); color: var(--bg-0); }
.mk-verdict--err .mk-verdict__points { color: var(--err); }

/* Раскрытие правильного ответа. Отдельное состояние, а не часть вердикта:
   пока каскад жив, право уходит следующей команде, и ответ показывать нельзя. */
.mk-verdict--reveal {
    background: var(--bg-1);
    border: 1px solid var(--glass-border);
}

.mk-verdict--reveal .mk-verdict__answer {
    font-family: var(--font-display);
    font-weight: 700;
    font-size: var(--fs-h1);
    color: var(--ok);
}

/* ==========================================================================
   6 · Строка табло
   Состояния: покой · лидер · сменивший позицию · всплывающее «+N»
   ========================================================================== */

.mk-scoreboard-row {
    --mk-row-place: 64px;
    position: relative;
    display: grid;
    grid-template-columns: var(--mk-row-place) 1fr auto;
    align-items: center;
    gap: var(--sp-5);
    padding: var(--sp-4) var(--sp-5);
    border-radius: var(--r-md);
    background: var(--glass-bg);
    border: 1px solid transparent;
    transition: transform var(--dur-md) var(--ease-spring),
                border-color var(--dur-md) var(--ease-inout);
}

.mk-scoreboard-row__place {
    font-family: var(--font-num);
    font-variant-numeric: tabular-nums;
    font-size: var(--fs-h2);
    color: var(--text-dim);
    text-align: center;
}

/* Имя команды НЕ ПЕРЕНОСИТСЯ: перенос ломает сетку строк — соседние строки
   разъезжаются по высоте, и табло перестаёт читаться как таблица. Длинное имя
   ужимает кеглем автоподгон (data-fit="floor", fit-text.js), но не ниже
   --fs-h2: пол чтения зала. */
.mk-scoreboard-row__team {
    min-width: 0;
    white-space: nowrap;
    font-family: var(--font-body);
    font-size: var(--fs-h2);
    color: var(--text-hi);
}

/* Полоса относительно лидера — не декор: она показывает разрыв, который
   иначе надо считать в уме. Ширину гонит скрипт переменной. */
.mk-scoreboard-row__bar {
    --mk-bar-h: 8px;
    grid-column: 2;
    height: var(--mk-bar-h);
    margin-top: var(--sp-2);
    border-radius: var(--r-full);
    background: var(--emotion-faint);
    overflow: hidden;
}

/* Заполнение — scaleX по --mk-gap (число 0…1), см. предмет 2. Скругление
   снято с заливки и оставлено дорожке: масштабированный радиус превращается
   в эллипс, и на разрыве 0,2 круглый торец становится сплюснутой каплей.
   Дорожка клипует заливку своим радиусом — левый торец остаётся круглым,
   правый режется ровно, как у любой шкалы прогресса. */
.mk-scoreboard-row__bar i {
    display: block;
    height: 100%;
    width: 100%;
    background: var(--emotion);
    transform: scaleX(var(--mk-gap, 0));
    transform-origin: left center;
    transition: transform var(--dur-lg) var(--ease-out);
}

.mk-scoreboard-row__score {
    grid-row: 1 / 3;
    grid-column: 3;
    font-family: var(--font-num);
    font-variant-numeric: tabular-nums;
    font-size: var(--fs-h1);
    color: var(--text-hi);
}

.mk-scoreboard-row--leader {
    border-color: var(--gold);
    background: var(--glass-bg-heavy);
}

.mk-scoreboard-row--leader .mk-scoreboard-row__place,
.mk-scoreboard-row--leader .mk-scoreboard-row__score { color: var(--gold); }
.mk-scoreboard-row--leader .mk-scoreboard-row__bar i { background: var(--gold); }

/* ПЛОТНОСТЬ СТРОКИ — ФУНКЦИЯ ЧИСЛА КОМАНД, а не вкуса. Считается так:
   слот 984, заголовок табло ~100, остаётся 860 на все строки.
     обычная строка  161 px → влезает 5;
     --dense         108 px → влезает 8 (это и есть типовая игра, 5–8 команд);
     --flat           94 px, без полосы, в две колонки → влезает 15 (максимум,
                      на который рассчитан интерфейс лобби).
   Кегль имени команды ни в одной не опускается ниже --fs-h2: это пол чтения
   зала. Ужимается всё остальное — счёт, полоса, поля. */
.mk-scoreboard-row--dense { padding: var(--sp-3) var(--sp-5); }
.mk-scoreboard-row--dense .mk-scoreboard-row__score { font-size: var(--fs-h2); }
.mk-scoreboard-row--dense .mk-scoreboard-row__bar {
    --mk-bar-h: 6px;
    margin-top: var(--sp-1);
}

/* Плоская: полоса разрыва снимается — при 9+ командах она перестаёт читаться
   как разрыв и превращается в узор. Разрыв в этом случае показывают числа. */
.mk-scoreboard-row--flat { padding: var(--sp-3) var(--sp-4); }
.mk-scoreboard-row--flat .mk-scoreboard-row__bar { display: none; }
.mk-scoreboard-row--flat .mk-scoreboard-row__score {
    grid-row: 1;
    font-size: var(--fs-h2);
}

/* Сменивший позицию — подсветка на один такт пересборки, не постоянная. */
.mk-scoreboard-row--moved {
    border-color: var(--emotion-border);
    box-shadow: var(--glow-emotion-soft);
}

/* Всплывающее «+N». НАД СТРОКОЙ, А НЕ ПОВЕРХ СЧЁТА: при top: 0 прибавка
   ложилась ровно на число очков — «+33» поверх «143», и обе цифры переставали
   читаться именно в ту секунду, ради которой прибавку и показывают. Теперь
   она выходит из верхней кромки своей строки в зазор между строками.
   Кегль на ступень ниже счёта — это подпись к числу, а не второе число. */
.mk-scoreboard-row__delta {
    position: absolute;
    right: var(--sp-5);
    bottom: 100%;
    font-family: var(--font-num);
    font-size: var(--fs-h3);
    line-height: 1;
    color: var(--ok);
    text-shadow: 0 2px 12px var(--bg-0);   /* читается поверх соседней строки */
    animation: mk-float-up var(--dur-xl) var(--ease-out) both;
    pointer-events: none;
}

.mk-scoreboard-row__delta--minus { color: var(--err); }

/* Выходит ИЗ строки вверх: старт под её кромкой, финиш в зазоре. */
@keyframes mk-float-up {
    0%   { opacity: 0; transform: translateY(14px) scale(0.8); }
    18%  { opacity: 1; transform: translateY(-4px) scale(1); }
    100% { opacity: 0; transform: translateY(-64px) scale(1); }
}

/* ==========================================================================
   7 · Чип команды в лобби
   Состояния: влетел · онлайн · ждём
   ========================================================================== */

.mk-team-chip {
    display: inline-flex;
    align-items: center;
    gap: var(--sp-3);
    padding: var(--sp-3) var(--sp-5);
    border-radius: var(--r-full);
    background: var(--glass-bg);
    border: 1px solid var(--glass-border);
    font-family: var(--font-body);
    font-size: var(--fs-h3);
    color: var(--text-hi);
}

/* ТОЧКА СЪЕДАЕТ 26 px (14 сама плюс отступ 12), и порог «длинного имени»
   для чипа с точкой ниже: не 24 знака, а 21. Проверено на приёмке — чип
   «Метанол · ждём капитана» при 23 знаках не влезал в колонку ровно на
   3 px и терял хвост под многоточие. Считать надо всю надпись вместе
   с точкой, а не одно имя команды. */
.mk-team-chip__dot {
    --mk-dot: 14px;
    width: var(--mk-dot);
    height: var(--mk-dot);
    border-radius: var(--r-full);
    background: var(--text-dim);
    flex: none;
}

.mk-team-chip--online { border-color: var(--emotion-border); }
.mk-team-chip--online .mk-team-chip__dot {
    background: var(--ok);
    box-shadow: var(--glow-ok-soft);
}

.mk-team-chip--waiting { color: var(--text-mid); }
.mk-team-chip--waiting .mk-team-chip__dot {
    background: var(--warn);
    animation: mk-pulse-dot 1.6s var(--ease-inout) infinite;
}

@keyframes mk-pulse-dot {
    0%, 100% { opacity: 1; transform: scale(1); }
    50%      { opacity: 0.45; transform: scale(0.8); }
}

/* Влёт чипа — первое, что зал видит за вечер, и происходит это на глазах
   у самих команд. Пружина с овершутом. */
.mk-team-chip--enter { animation: mk-pop var(--dur-sm) var(--ease-spring) both; }

/* Пусто: ноль команд в лобби. Состояние живёт дольше, чем кажется, —
   поэтому оно проектируется, а не остаётся дырой в разметке. */
.mk-team-empty {
    display: flex;
    flex-direction: column;
    gap: var(--sp-3);
    padding: var(--sp-6);
    border-radius: var(--r-lg);
    border: 1px dashed var(--glass-border);
    text-align: center;
    font-family: var(--font-body);
    font-size: var(--fs-h3);
    color: var(--text-dim);
}


/* ==========================================================================
   8 · Панель QR-приглашения
   ========================================================================== */

.mk-qr {
    display: flex;
    align-items: center;
    gap: var(--sp-6);
    padding: var(--sp-6);
}

.mk-qr__code {
    --mk-qr-size: 260px;
    display: grid;
    place-items: center;
    width: var(--mk-qr-size);
    height: var(--mk-qr-size);
    padding: var(--sp-4);
    border-radius: var(--r-md);
    background: var(--text-hi);   /* QR читается только на светлом */
    flex: none;
}

.mk-qr__code img, .mk-qr__code svg { width: 100%; height: 100%; }

.mk-qr__text { display: flex; flex-direction: column; gap: var(--sp-3); }

.mk-qr__url {
    font-family: var(--font-num);
    font-size: var(--fs-h2);
    color: var(--emotion-soft);
}

.mk-qr__hint {
    font-family: var(--font-body);
    font-size: var(--fs-h3);
    color: var(--text-mid);
}

/* ==========================================================================
   9 · Плитка подиума
   Состояния: первое · второе · третье · речь по призёру
   ========================================================================== */

.mk-podium {
    display: flex;
    align-items: flex-end;
    justify-content: center;
    gap: var(--sp-6);
}

.mk-podium-place {
    --mk-podium-w: 320px;
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: var(--sp-4);
    width: var(--mk-podium-w);
    padding: var(--sp-6) var(--sp-5);
    border-radius: var(--r-lg);
    background: var(--glass-bg);
    border: 1px solid var(--glass-border);
    text-align: center;
}

.mk-podium-place__crown { color: var(--gold); }
.mk-podium-place__crown svg {
    --mk-crown: 72px;
    width: var(--mk-crown);
    height: var(--mk-crown);
}

.mk-podium-place__num {
    font-family: var(--font-display);
    font-weight: 700;
    font-size: var(--fs-hero);
    line-height: 1;
}

.mk-podium-place__team {
    font-family: var(--font-body);
    font-size: var(--fs-h2);
    color: var(--text-hi);
}

.mk-podium-place__score {
    font-family: var(--font-num);
    font-variant-numeric: tabular-nums;
    font-size: var(--fs-h1);
    color: var(--text-mid);
}

/* Реплика по призёру — у каждого своя, это часть финала, а не подпись. */
.mk-podium-place__line {
    font-family: var(--font-body);
    font-size: var(--fs-body);
    line-height: 1.4;
    color: var(--text-mid);
}

.mk-podium-place--1 { --mk-podium-w: 380px; border-color: var(--gold); }
.mk-podium-place--1 .mk-podium-place__num { color: var(--gold); }
.mk-podium-place--2 { border-color: var(--silver); }
.mk-podium-place--2 .mk-podium-place__num { color: var(--silver); }
.mk-podium-place--3 { border-color: var(--bronze); }
.mk-podium-place--3 .mk-podium-place__num { color: var(--bronze); }

/* ==========================================================================
   10 · Тикер фактов
   ========================================================================== */

.mk-ticker {
    --mk-ticker-h: 72px;
    display: flex;
    align-items: center;
    height: var(--mk-ticker-h);
    padding: 0 var(--sp-6);
    border-top: 1px solid var(--glass-border);
    background: var(--glass-bg);
    overflow: hidden;
}

.mk-ticker__track {
    display: flex;
    gap: var(--sp-8);
    white-space: nowrap;
    animation: mk-ticker 40s linear infinite;
}

.mk-ticker__item {
    display: inline-flex;
    align-items: center;
    gap: var(--sp-3);
    font-family: var(--font-body);
    font-size: var(--fs-body);
    color: var(--text-mid);
}

/* Ромб-разделитель между фактами: строка бежит, и без метки начала факты
   слипаются в одно длинное предложение. */
.mk-ticker__item::before {
    content: "◆";
    font-size: var(--fs-small);
    color: var(--emotion-soft);
}

/* ==========================================================================
   11 · Панель реплики Марго
   Состояния: говорит · думает · говорит без аудио (только субтитр)
   ========================================================================== */

.mk-margo-line {
    display: flex;
    flex-direction: column;
    gap: var(--sp-3);
    padding: var(--sp-5);
    border-radius: var(--r-lg);
}

.mk-margo-line__text {
    font-family: var(--font-body);
    font-size: var(--fs-body);
    line-height: 1.45;
    color: var(--text-mid);
}

.mk-margo-line--speaking { border-color: var(--emotion-border); }
.mk-margo-line--speaking .mk-margo-line__text { color: var(--text-hi); }

/* «Марго думает…» — пауза между «оператор нажал» и «Марго заговорила»
   реальная: реплика генерируется и озвучивается. Движение должно честно
   говорить «идёт работа» и не выглядеть зависанием. */
.mk-margo-line--thinking .mk-margo-line__text { color: var(--text-dim); }

.mk-think {
    display: inline-flex;
    gap: var(--sp-2);
    align-items: center;
    margin-left: var(--sp-3);
}

.mk-think i {
    --mk-think-dot: 12px;
    width: var(--mk-think-dot);
    height: var(--mk-think-dot);
    border-radius: var(--r-full);
    background: var(--emotion);
    animation: mk-pulse-dot 1.2s var(--ease-inout) infinite;
    animation-delay: calc(var(--i, 0) * 160ms);
}

/* Озвучка не сработала — остаётся субтитр. Зал не должен понять, что это
   отказ: помечаем спокойно и не на весь кадр. */
.mk-margo-line--silent .mk-margo-line__text { color: var(--text-hi); }

.mk-margo-line__badge {
    align-self: flex-start;
    padding: var(--sp-1) var(--sp-3);
    border-radius: var(--r-sm);
    background: var(--bg-2);
    font-family: var(--font-body);
    font-size: var(--fs-small);
    letter-spacing: var(--ls-label);
    text-transform: uppercase;
    color: var(--text-dim);
}

/* ==========================================================================
   12 · Бейдж типа тура и прогресс
   ========================================================================== */

.mk-tour-badge {
    display: inline-flex;
    align-items: center;
    gap: var(--sp-3);
    padding: var(--sp-3) var(--sp-5);
    border-radius: var(--r-full);
    background: var(--emotion-faint);
    border: 1px solid var(--emotion-border);
    font-family: var(--font-body);
    font-size: var(--fs-h3);
    color: var(--emotion-soft);
}

.mk-progress {
    display: flex;
    align-items: center;
    gap: var(--sp-4);
}

.mk-progress__text {
    font-family: var(--font-num);
    font-variant-numeric: tabular-nums;
    font-size: var(--fs-h3);
    color: var(--text-mid);
}

.mk-progress__dots { display: flex; gap: var(--sp-2); }

.mk-progress__dots i {
    --mk-pdot: 16px;
    width: var(--mk-pdot);
    height: var(--mk-pdot);
    border-radius: var(--r-full);
    background: var(--glass-border);
}

.mk-progress__dots i.done { background: var(--emotion-deep); }
.mk-progress__dots i.now  { background: var(--emotion); box-shadow: var(--glow-emotion-soft); }

/* ══════════════════════════════════════════════════════════════════════════
   К А П И Т А Н   ·   группа captain
   Телефон в руке, полутьма, действия секундами. Стадия 03, вторая поверхность
   по порядку UX_PLAN.md: зал → КАПИТАН → оператор.

   ЧЕМ ЭТА СЦЕНА ОТЛИЧАЕТСЯ ОТ ЗАЛА — четыре правила, из которых выводится
   всё остальное:

   1. ЗДЕСЬ ЕСТЬ ВВОД. В зале палец ничего не трогает; здесь каждый компонент
      либо принимает нажатие, либо объясняет, почему не принимает. Отсюда
      --tap 56px: меньше нельзя ни у чего, во что целятся пальцем.
   2. ЭКРАН ОДИН И МАЛЕНЬКИЙ. В кадре живёт ровно одно действие; всё, что не
      действие, — строка контекста сверху и строка состояния снизу.
   3. КАПИТАН 90 % ВРЕМЕНИ НИЧЕГО НЕ ДЕЛАЕТ. Значит покой — не «пустой экран
      до следующего события», а полноценный компонент (.mk-standby): фаза,
      тур, своё место, что будет дальше.
   4. ОТКАЗ — ТОЖЕ ЭКРАН. Сеть падает, PIN не подходит, отправка не уходит.
      Системный alert() — не показ состояния, а признание, что его не
      нарисовали (.mk-fault).

   ПРО РАЗМЕРЫ. Внутри .mk-captain — только px: базовая шкала :root построена
   на clamp(...vw...) под ТВ, на телефоне она схлопывается в нижнюю границу,
   а в окне ноутбука, где идёт приёмка, наоборот раздувается. Шкала капитана
   объявлена блоком .mk-captain в tokens.css, там же --tap.
   ══════════════════════════════════════════════════════════════════════════ */

/* ==========================================================================
   13 · Поле ввода PIN
   Состояния: пусто · четыре цифры · отвергнут сервером · сети нет
   PIN — не пароль, а пропуск: четыре цифры, которые оператор диктует вслух.
   Поэтому они показываются открыто и крупно — ошибиться в наборе куда
   дороже, чем быть подсмотренным.
   ========================================================================== */

.mk-pin {
    display: flex;
    flex-direction: column;
    gap: var(--sp-4);
}

.mk-pin__cells { display: flex; gap: var(--sp-3); }

.mk-pin__cell {
    --mk-pin-cell: 68px;              /* сторона ячейки: 4 × 68 + 3 × 12 = 308 */
    flex: 1 1 var(--mk-pin-cell);
    height: var(--mk-pin-cell);
    display: grid;
    place-items: center;
    border-radius: var(--r-md);
    background: var(--glass-bg);
    border: 1px solid var(--glass-border);
    font-family: var(--font-num);
    font-variant-numeric: tabular-nums;
    font-size: var(--fs-h1);
    color: var(--text-hi);
    transition: border-color var(--dur-xs) var(--ease-out),
                box-shadow var(--dur-xs) var(--ease-out);
}

/* Место, куда встанет следующая цифра. Не мигающая палочка, а подсвеченный
   борт: палочка на телефоне теряется, борт видно боковым зрением. */
.mk-pin__cell--now {
    border-color: var(--emotion-border);
    box-shadow: var(--glow-emotion-soft);
}

.mk-pin__cell--filled { border-color: var(--emotion-border); }

.mk-pin__msg {
    min-height: var(--sp-5);          /* место под строку держится всегда: иначе
                                         появление ошибки дёргает кнопку под
                                         пальцем в момент нажатия */
    font-family: var(--font-body);
    font-size: var(--fs-small);
    color: var(--text-dim);
}

/* Сервер отверг PIN. Тряска — единственное «нет» в системе, которое не нужно
   читать, чтобы понять: короткая, не блокирует повтор, цифры не стирает. */
.mk-pin--rejected .mk-pin__cell {
    border-color: var(--err);
    box-shadow: var(--glow-err-soft);
}
.mk-pin--rejected .mk-pin__msg { color: var(--err); }
.mk-pin--rejected .mk-pin__cells { animation: mk-shake var(--dur-md) var(--ease-out) 1; }

@keyframes mk-shake {
    0%, 100% { transform: translateX(0); }
    20%      { transform: translateX(-8px); }
    45%      { transform: translateX(7px); }
    70%      { transform: translateX(-4px); }
    88%      { transform: translateX(2px); }
}

/* Сети нет. Главное, что обязан сообщить экран: набранное не потеряно,
   заново вводить не надо, повтор произойдёт сам. */
.mk-pin--offline .mk-pin__cell { border-color: var(--warn); opacity: 0.75; }
.mk-pin--offline .mk-pin__msg { color: var(--warn); }

/* ==========================================================================
   14 · Кнопка отправки
   Состояния: готова · выключена · отправляется · отправлено
   Одна кнопка на три действия — войти, подтвердить выбор, отправить ответ:
   все три отправляют на сервер и все три одинаково рискуют не дойти.
   Необратимого действия у капитана нет ни одного, поэтому отдельного
   опасного вида здесь нет — он появится у оператора.
   ========================================================================== */

.mk-send {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: var(--sp-3);
    width: 100%;
    min-height: var(--tap);
    padding: var(--sp-4) var(--sp-5);
    border: 1px solid transparent;
    border-radius: var(--r-md);
    /* ЗАЛИВКА АКЦЕНТОМ ПОД ТЕКСТОМ — ТОЛЬКО --emotion-soft. Проверено в
       браузере на всех семи эмоциях: тушь поверх --emotion даёт от 4,17
       (нейтраль) до 10,84, то есть худший случай не дотягивает до AA 4,5;
       поверх --emotion-soft — от 7,09. Правило записано в tokens.css рядом
       с --emotion-ink, здесь оно просто соблюдается. */
    background: var(--emotion-soft);
    color: var(--emotion-ink);
    font-family: var(--font-body);
    font-size: var(--fs-h3);
    font-weight: 600;
    cursor: pointer;
    transition: transform var(--dur-xs) var(--ease-out),
                background var(--dur-sm) var(--ease-out);
}

/* Отклик на палец обязателен: курсора на телефоне нет, и без вдавливания
   капитан не знает, попал он или промахнулся. */
.mk-send:active { transform: scale(0.97); }

.mk-send--off,
.mk-send:disabled {
    background: var(--glass-bg);
    border-color: var(--glass-border);
    color: var(--text-dim);
    cursor: not-allowed;
}
.mk-send--off:active { transform: none; }

/* Отправляется. Кнопка не гаснет и не исчезает: гаснущая кнопка читается как
   «не нажалось», и капитан жмёт второй раз. */
.mk-send--sending { background: var(--emotion-deep); color: var(--text-hi); }

.mk-send__spin {
    --mk-spin: 18px;
    width: var(--mk-spin);
    height: var(--mk-spin);
    border-radius: var(--r-full);
    border: 2px solid var(--text-hi);
    border-top-color: transparent;
    animation: mk-spin 0.8s linear infinite;
}

@keyframes mk-spin { to { transform: rotate(360deg); } }

.mk-send--sent {
    background: transparent;
    border-color: var(--ok);
    color: var(--ok);
    cursor: default;
}
.mk-send--sent:active { transform: none; }

/* ==========================================================================
   15 · Кнопка буззера
   Состояния: открыт · окно бонуса с отсчётом · нажал и ждёт вердикта ·
              опередили · закрыт
   Самый дорогой объект продукта: промах или задержка стоят вопроса. Круг,
   а не прямоугольник, — палец бьёт в центр, а центр у круга один.
   Цвет — эмоция, а не сигнальный красный: красным в этой системе говорят
   «неверно», а неверный ответ на буззере стоит −7, и путать эти два смысла
   на одной кнопке нельзя.
   ========================================================================== */

.mk-buzz {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: var(--sp-5);
    text-align: center;
}

/* ДЛИНА СЛОВА НА КРУГЛОЙ КНОПКЕ ОГРАНИЧЕНА ГЕОМЕТРИЕЙ, а не вкусом: строка
   вписывается не в диаметр, а в квадрат, вписанный в круг, — это d / √2, для
   264 px всего 187. «ОТВЕТИТЬ» дисплейной гарнитурой в --fs-h1 занимает 302 px
   и вылезает за круг с обеих сторон. Отсюда два правила: на живой кнопке стоит
   слово из трёх-четырёх букв («ЖМИ»), а мёртвая кнопка объясняется словом
   подлиннее, но на ступень мельче. Всё остальное говорит строка состояния
   под кнопкой — там места сколько угодно. */
.mk-buzz__btn {
    --mk-buzz-size: 264px;            /* круг: 264 + поля 32 влезают в 390 */
    position: relative;
    width: var(--mk-buzz-size);
    height: var(--mk-buzz-size);
    display: grid;
    place-items: center;
    border: 2px solid var(--emotion);
    border-radius: var(--r-full);
    /* Купол светится изнутри: центр --emotion-soft, край --emotion. Обратный
       порядок (emotion → deep) давал под тушью 2,35 на нейтрали — ниже даже
       крупного порога 3,0. */
    background: radial-gradient(circle at 50% 38%,
                var(--emotion-soft) 0%, var(--emotion) 78%);
    color: var(--emotion-ink);
    font-family: var(--font-display);
    font-weight: 700;
    font-size: var(--fs-h1);
    cursor: pointer;
    box-shadow: var(--glow-emotion);
    animation: mk-breathe 2.4s var(--ease-inout) infinite;
    transition: transform var(--dur-xs) var(--ease-out);
}

.mk-buzz__btn:active { transform: scale(0.94); }

/* Окно бонуса за скорость: +3, если нажать в первые три секунды
   (core/scoring.py, buzzer.speed_bonus). Отсчёт идёт вниз, цвет --warn —
   это факт про время, а не второй акцент рядом с эмоцией. */
.mk-buzz__bonus {
    display: inline-flex;
    align-items: baseline;
    gap: var(--sp-3);
    padding: var(--sp-2) var(--sp-5);
    border-radius: var(--r-full);
    background: var(--glass-bg);
    border: 1px solid var(--warn);
    color: var(--warn);
    font-family: var(--font-body);
    font-size: var(--fs-small);
    font-weight: 600;
    letter-spacing: var(--ls-label);
    text-transform: uppercase;
}

.mk-buzz__count {
    font-family: var(--font-num);
    font-variant-numeric: tabular-nums;
    font-size: var(--fs-h2);
    letter-spacing: 0;
}

.mk-buzz__status {
    max-width: 34ch;
    font-family: var(--font-body);
    font-size: var(--fs-body);
    color: var(--text-mid);
}

/* Нажал и ждёт вердикта. Кнопка остаётся на месте и остаётся видимой —
   исчезнувшая читается как «не засчиталось». Дыхание снимается: ждать под
   пульсирующей кнопкой невозможно. */
.mk-buzz--sent .mk-buzz__btn {
    animation: none;
    background: var(--glass-bg);
    border-color: var(--ok);
    color: var(--ok);
    box-shadow: var(--glow-ok);
    cursor: default;
}
.mk-buzz--sent .mk-buzz__btn:active { transform: none; }
.mk-buzz--sent .mk-buzz__status { color: var(--ok); }

/* Опередили и закрыт. Это не ошибка капитана — не красим ошибкой, гасим
   и объясняем. Кто именно опередил и какой ты в очереди, говорит
   .mk-queue-place ниже. */
.mk-buzz--lost .mk-buzz__btn,
.mk-buzz--closed .mk-buzz__btn {
    animation: none;
    background: var(--glass-bg);
    border-color: var(--glass-border);
    color: var(--text-dim);
    box-shadow: none;
    cursor: not-allowed;
}
.mk-buzz--lost .mk-buzz__btn:active,
.mk-buzz--closed .mk-buzz__btn:active { transform: none; }

.mk-buzz--closed .mk-buzz__btn { opacity: 0.55; }

/* Кнопка перестала быть действием — слово на ней стало объяснением и потому
   уходит на ступень вниз: «ЗАНЯТО» и «ЗАКРЫТ» в --fs-h1 не помещаются в круг. */
.mk-buzz--lost .mk-buzz__btn,
.mk-buzz--closed .mk-buzz__btn { font-size: var(--fs-h2); }

/* ==========================================================================
   16 · Кнопка варианта голосования
   Состояния: не выбран · выбран · подтверждён и заблокирован · остальные
   после подтверждения (состояние вынужденное: без него заблокированная
   сетка выглядит такой же живой, как до подтверждения)
   ========================================================================== */

.mk-vote-grid {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: var(--sp-3);
}

.mk-vote {
    display: flex;
    align-items: center;
    gap: var(--sp-3);
    min-height: var(--tap);
    padding: var(--sp-4);
    border-radius: var(--r-md);
    background: var(--glass-bg);
    border: 1px solid var(--glass-border);
    color: var(--text-hi);
    font-family: var(--font-body);
    font-size: var(--fs-h3);
    text-align: left;
    cursor: pointer;
    transition: transform var(--dur-xs) var(--ease-out),
                border-color var(--dur-xs) var(--ease-out);
}

.mk-vote:active { transform: scale(0.97); }

.mk-vote__key {
    --mk-vote-key: 36px;
    flex: 0 0 var(--mk-vote-key);
    height: var(--mk-vote-key);
    display: grid;
    place-items: center;
    border-radius: var(--r-sm);
    background: var(--bg-2);
    border: 1px solid var(--glass-border);
    font-family: var(--font-display);
    font-weight: 700;
    font-size: var(--fs-body);
    color: var(--text-mid);
}

.mk-vote--picked {
    border-color: var(--emotion);
    box-shadow: var(--glow-emotion-soft);
}
.mk-vote--picked .mk-vote__key {
    background: var(--emotion-soft);
    border-color: transparent;
    color: var(--emotion-ink);
}

/* Подтверждён. Выбор ушёл на сервер, обратной дороги нет — поэтому заливка,
   а не борт: борт читается как «наведён», заливка как «сделано». */
.mk-vote--locked {
    background: var(--emotion-faint);
    border-color: var(--emotion);
    cursor: default;
}
.mk-vote--locked .mk-vote__key {
    background: var(--emotion-soft);
    border-color: transparent;
    color: var(--emotion-ink);
}
.mk-vote--locked:active { transform: none; }

/* Остальные варианты после подтверждения. */
.mk-vote--out { opacity: 0.4; cursor: not-allowed; }
.mk-vote--out:active { transform: none; }

/* ==========================================================================
   17 · Поле текстового ответа
   Состояния: пусто · набирается со счётчиком знаков · отправлено ·
              отправка не удалась
   Потолок 500 знаков — из продукта (static/pwa/index.html, maxlength=500).
   ========================================================================== */

.mk-answer {
    display: flex;
    flex-direction: column;
    gap: var(--sp-3);
}

.mk-answer__field {
    --mk-answer-h: 132px;             /* ~4 строки: команда пишет фразу, не эссе */
    width: 100%;
    min-height: var(--mk-answer-h);
    padding: var(--sp-4);
    border-radius: var(--r-md);
    background: var(--glass-bg);
    border: 1px solid var(--glass-border);
    color: var(--text-hi);
    font-family: var(--font-body);
    font-size: var(--fs-body);
    line-height: 1.45;
    resize: none;
}

.mk-answer__field::placeholder { color: var(--text-dim); }

.mk-answer__field:focus {
    outline: none;
    border-color: var(--emotion-border);
    box-shadow: var(--glow-emotion-soft);
}

.mk-answer__foot {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--sp-3);
    font-family: var(--font-body);
    font-size: var(--fs-small);
    color: var(--text-dim);
}

.mk-answer__count { font-family: var(--font-num); font-variant-numeric: tabular-nums; }

/* Ответ ушёл. Текст остаётся на экране: капитан должен видеть, ЧТО именно
   отправил, — Марго будет обсуждать это вслух. */
.mk-answer--sent .mk-answer__field { border-color: var(--ok); color: var(--text-mid); }
.mk-answer--sent .mk-answer__foot { color: var(--ok); }

/* Отправка не удалась. В продукте здесь пустой catch (static/pwa/app.js:796),
   и капитан уверен, что ответил. Состояние обязано быть громким и обязано
   предлагать повтор. */
.mk-answer--failed .mk-answer__field {
    border-color: var(--err);
    box-shadow: var(--glow-err-soft);
}
.mk-answer--failed .mk-answer__foot { color: var(--err); }

/* ==========================================================================
   18 · Бейдж таймера
   Состояния: идёт · последние секунды
   В зале таймер — кольцо во весь слот; здесь строка сверху: капитан смотрит
   на кнопку, а не на часы, и таймер не имеет права занимать место действия.
   ========================================================================== */

.mk-timer-badge {
    display: inline-flex;
    align-items: baseline;
    gap: var(--sp-2);
    white-space: nowrap;      /* «на ответ 0:04» не переносится никогда */
    padding: var(--sp-2) var(--sp-4);
    border-radius: var(--r-full);
    background: var(--glass-bg);
    border: 1px solid var(--glass-border);
    font-family: var(--font-body);
    font-size: var(--fs-label);
    letter-spacing: var(--ls-label);
    text-transform: uppercase;
    color: var(--text-dim);
}

.mk-timer-badge__value {
    font-family: var(--font-num);
    font-variant-numeric: tabular-nums;
    font-size: var(--fs-h3);
    letter-spacing: 0;
    color: var(--text-hi);
}

/* Последние секунды. В продукте стиль .pwa-timer.urgent написан, а класс не
   выставляется никогда (static/pwa/style.css:137 против app.js:39):
   состояние есть на бумаге и нет на экране. */
.mk-timer-badge--urgent {
    border-color: var(--err);
    color: var(--err);
    box-shadow: var(--glow-err-soft);
    /* Пульс — такт по секунде из choreo.js, тот же, что у кольца в зале:
       две поверхности показывают одно и то же время одним движением. */
}
.mk-timer-badge--urgent .mk-timer-badge__value { color: var(--err); }

/* ==========================================================================
   19 · Шапка капитана
   Состояния: онлайн · оффлайн · счёт с анимацией изменения
   Единственная постоянная строка экрана: кто я и есть ли связь.
   ========================================================================== */

.mk-captain-bar {
    display: flex;
    align-items: center;
    gap: var(--sp-3);
    min-height: var(--tap);
    padding: var(--sp-2) var(--sp-4);
    border-radius: var(--r-full);
    background: var(--glass-bg);
    border: 1px solid var(--glass-border);
}

.mk-captain-bar__dot {
    --mk-cdot: 10px;
    flex: none;
    width: var(--mk-cdot);
    height: var(--mk-cdot);
    border-radius: var(--r-full);
    background: var(--ok);
    box-shadow: var(--glow-ok-soft);
}

/* ИМЯ КОМАНДЫ ПЕРЕНОСИТСЯ, А НЕ ОБРЕЗАЕТСЯ. Долг приёмки, записанный 24.08:
   шапка резала строку многоточием. Проверено на переносе капитана 28.08 —
   режется не экзотика, а типовое имя: «ФУМ-лента Мёбиуса · Марио» это
   24 знака, и они не влезают даже после автоподгона до пола (16 px).
   Из двух записанных вариантов взят второй — тот же, что уже принят
   для строки лобби у оператора: строка растёт в высоту.
   Шапка при этом остаётся пилюлей, просто в две строки, и это дешевле,
   чем «Ночная смена промысла ГК…» на телефоне капитана. */
.mk-captain-bar__team {
    flex: 1 1 auto;
    min-width: 0;
    overflow-wrap: break-word;
    font-family: var(--font-body);
    font-size: var(--fs-body);
    color: var(--text-hi);
}

.mk-captain-bar__score {
    display: inline-flex;
    align-items: baseline;
    gap: var(--sp-2);
    font-family: var(--font-body);
    font-size: var(--fs-label);
    letter-spacing: var(--ls-label);
    text-transform: uppercase;
    color: var(--text-dim);
}

.mk-captain-bar__score b {
    font-family: var(--font-num);
    font-variant-numeric: tabular-nums;
    font-size: var(--fs-h3);
    letter-spacing: 0;
    color: var(--emotion-soft);
}

/* Счёт изменился — короткий толчок. Без него прибавка проходит незамеченной:
   капитан в этот момент смотрит на кнопку, а не на шапку. */
.mk-captain-bar__score--bump b { animation: mk-pop var(--dur-sm) var(--ease-spring) 1; }

/* Оффлайн. Реконнект идёт сам каждые три секунды, поэтому вид тревожный,
   но не аварийный: делать капитану нечего. */
.mk-captain-bar--offline { border-color: var(--warn); }
.mk-captain-bar--offline .mk-captain-bar__dot {
    background: var(--warn);
    box-shadow: none;
    animation: mk-pulse-dot 1.6s var(--ease-inout) infinite;
}
.mk-captain-bar--offline .mk-captain-bar__team { color: var(--text-mid); }

/* ==========================================================================
   20 · Экран ожидания
   Состояния: до старта · зал слушает Марго · чужой ход · сессия завершена
   ЭТО 90 % ВРЕМЕНИ ИГРЫ. В продукте здесь одна строка текста — капитан не
   знает ни фазы, ни тура, ни своего места и потому поднимает голову в зал.
   Компонент обязан отвечать на четыре вопроса: что сейчас, где мы, как я,
   что дальше.
   ========================================================================== */

.mk-standby {
    display: flex;
    flex-direction: column;
    gap: var(--sp-5);
    padding: var(--sp-5);
    text-align: center;
}

.mk-standby__what {
    font-family: var(--font-display);
    font-weight: 700;
    font-size: var(--fs-h2);
    line-height: 1.2;
    color: var(--text-hi);
}

.mk-standby__where {
    font-family: var(--font-body);
    font-size: var(--fs-body);
    color: var(--text-mid);
}

/* Своё место и свои очки — две цифры рядом: место без очков не показывает
   разрыв, очки без места не показывают, кого догонять. */
.mk-standby__me { display: flex; gap: var(--sp-3); }

.mk-standby__stat {
    flex: 1 1 0;
    padding: var(--sp-4) var(--sp-3);
    border-radius: var(--r-md);
    background: var(--glass-bg);
    border: 1px solid var(--glass-border);
}

.mk-standby__stat b {
    display: block;
    font-family: var(--font-num);
    font-variant-numeric: tabular-nums;
    font-size: var(--fs-h1);
    line-height: 1.05;
    color: var(--emotion-soft);
}

.mk-standby__stat span {
    font-family: var(--font-body);
    font-size: var(--fs-label);
    letter-spacing: var(--ls-label);
    text-transform: uppercase;
    color: var(--text-dim);
}

/* Что дальше. Одна строка, всегда о будущем, — она и есть причина, по
   которой капитан не лезет смотреть в зал. */
.mk-standby__next {
    padding: var(--sp-3) var(--sp-4);
    border-radius: var(--r-md);
    background: var(--emotion-faint);
    border: 1px solid var(--emotion-border);
    font-family: var(--font-body);
    font-size: var(--fs-small);
    color: var(--emotion-soft);
}

/* Сессия завершена: игра кончилась, экран больше ничего не ждёт. */
.mk-standby--over .mk-standby__what { color: var(--text-mid); }
.mk-standby--over .mk-standby__next {
    background: var(--glass-bg);
    border-color: var(--glass-border);
    color: var(--text-dim);
}

/* ==========================================================================
   21 · Вердикт по своему ответу
   Состояния: верно · неверно
   В турах голосования и текста этого нет вообще: капитан узнаёт свой
   результат из зала, если успел поднять голову (docs/design/screens.md,
   «Пустые состояния и отказы капитана»). Накрывает экран целиком и уходит
   сам — подтверждать его капитан не должен, у него руки заняты игрой.
   ========================================================================== */

.mk-own-verdict {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: var(--sp-4);
    padding: var(--sp-7) var(--sp-5);
    border-radius: var(--r-lg);
    text-align: center;
}

.mk-own-verdict__mark {
    --mk-mark: 88px;
    width: var(--mk-mark);
    height: var(--mk-mark);
    display: grid;
    place-items: center;
    border-radius: var(--r-full);
}

.mk-own-verdict__mark svg { width: 50%; height: 50%; }

.mk-own-verdict__points {
    font-family: var(--font-num);
    font-variant-numeric: tabular-nums;
    font-size: var(--fs-hero);
    line-height: 1;
}

.mk-own-verdict__why {
    max-width: 30ch;
    font-family: var(--font-body);
    font-size: var(--fs-body);
    color: var(--text-mid);
}

.mk-own-verdict--ok {
    background: var(--bg-1);
    border: 1px solid var(--ok);
    box-shadow: var(--glow-ok-strong);
}
.mk-own-verdict--ok .mk-own-verdict__mark { background: var(--ok); color: var(--bg-0); }
.mk-own-verdict--ok .mk-own-verdict__points { color: var(--ok); }

.mk-own-verdict--err {
    background: var(--bg-1);
    border: 1px solid var(--err);
    box-shadow: var(--glow-err-strong);
}
.mk-own-verdict--err .mk-own-verdict__mark { background: var(--err); color: var(--bg-0); }
.mk-own-verdict--err .mk-own-verdict__points { color: var(--err); }

/* ==========================================================================
   22 · Позиция в очереди буззера
   Состояния: ждёшь своей очереди · право у тебя · выбыл на этом вопросе
   Очередь сервер капитанам не рассылает вовсе — компонент нарисован под
   событие, которого пока нет (docs/design/screens.md; UX_PLAN.md:122).
   Без него «опередили» и «выбыл» на экране неотличимы, а это разные вещи:
   в первом случае ход ещё может вернуться, во втором — уже нет.
   ========================================================================== */

.mk-queue-place {
    display: flex;
    align-items: center;
    gap: var(--sp-3);
    padding: var(--sp-3) var(--sp-4);
    border-radius: var(--r-full);
    background: var(--glass-bg);
    border: 1px solid var(--glass-border);
    font-family: var(--font-body);
    font-size: var(--fs-body);
    color: var(--text-mid);
}

.mk-queue-place__num {
    --mk-qnum: 34px;
    flex: none;
    width: var(--mk-qnum);
    height: var(--mk-qnum);
    display: grid;
    place-items: center;
    border-radius: var(--r-full);
    background: var(--bg-2);
    font-family: var(--font-num);
    font-variant-numeric: tabular-nums;
    font-size: var(--fs-body);
    color: var(--text-hi);
}

/* Право у тебя — единственное состояние, где капитан обязан говорить вслух. */
.mk-queue-place--now {
    border-color: var(--emotion);
    color: var(--text-hi);
    box-shadow: var(--glow-emotion-soft);
}
.mk-queue-place--now .mk-queue-place__num {
    background: var(--emotion-soft);
    color: var(--emotion-ink);
}

/* Исключена на этом вопросе: каскад ушёл дальше, ход кончился. */
.mk-queue-place--out { opacity: 0.5; }
.mk-queue-place--out .mk-queue-place__num { background: var(--glass-bg); color: var(--text-dim); }

/* ==========================================================================
   23 · Плашка отказа
   Состояния: сеть пропала (чинится сама) · сервер отказал (нужно действие) ·
              игра ушла вперёд (действие потеряно)
   Заменяет три системных alert() (static/pwa/app.js:287, :296, :307).
   Правило: плашка называет, что случилось И что делать. «Ошибка сети» —
   не сообщение, а извинение.
   ========================================================================== */

.mk-fault {
    display: flex;
    gap: var(--sp-4);
    padding: var(--sp-4);
    border-radius: var(--r-md);
    background: var(--glass-bg);
    border: 1px solid var(--warn);
    text-align: left;
}


.mk-fault__icon {
    --mk-fault-icon: 28px;
    flex: none;
    width: var(--mk-fault-icon);
    height: var(--mk-fault-icon);
    color: var(--warn);
}

.mk-fault__icon svg { width: 100%; height: 100%; }

.mk-fault__body { display: flex; flex-direction: column; gap: var(--sp-2); }

.mk-fault__title {
    font-family: var(--font-body);
    font-size: var(--fs-h3);
    font-weight: 600;
    color: var(--text-hi);
}

.mk-fault__text {
    font-family: var(--font-body);
    font-size: var(--fs-small);
    color: var(--text-mid);
}

/* Сервер отказал: причина названа, повтор в руках капитана. */
.mk-fault--err { border-color: var(--err); }
.mk-fault--err .mk-fault__icon { color: var(--err); }

/* Игра ушла вперёд — не отказ техники, а факт: вопрос закрыт, ответ не нужен.
   Спокойный вид, никакого повтора. */
.mk-fault--stale { border-color: var(--glass-border); }
.mk-fault--stale .mk-fault__icon { color: var(--text-dim); }
.mk-fault--stale .mk-fault__title { color: var(--text-mid); }

/* ══════════════════════════════════════════════════════════════════════════
   О П Е Р А Т О Р   ·   группа operator
   Ноутбук, горячие клавиши, живая игра идёт один раз. Стадия 03, третья и
   последняя поверхность: зал → капитан → ОПЕРАТОР.

   ЧЕМ ЭТА СЦЕНА ОТЛИЧАЕТСЯ ОТ ДВУХ ПРЕДЫДУЩИХ:

   1. ОТКАТА НЕТ. Механизма отмены хода в игре не существует вовсе. Значит
      цена промаха здесь выше, чем цена лишнего клика: необратимое действие
      отделено от рутинного и просит подтверждения (.mk-cmd--danger).
   2. ОПЕРАТОР СМОТРИТ НЕ В ЭКРАН, А В ЗАЛ. Экран он видит боковым зрением
      и в паузах. Отсюда: одно главное действие в кадре, крупно, с буквой
      клавиши; всё остальное мельче и тише.
   3. ГЛАВНОЕ ДЕЙСТВИЕ ЖИВЁТ В ЗАКРЕПЛЁННОЙ ЗОНЕ, А НЕ В ЛЕНТЕ. Кнопки
      вердикта, уехавшие вверх со скроллом чата, — сегодняшний способ
      потерять вопрос.
   4. ЭКРАН ОБЯЗАН ГОВОРИТЬ ДАЖЕ ТОГДА, КОГДА НЕ ПОНИМАЕТ, ЧТО ПРОИСХОДИТ.
      Неопознанная фаза сейчас даёт пустую зону и оператора без подсказки —
      это отдельное состояние, а не «такого не бывает».

   ПРО РАЗМЕРЫ. Внутри .mk-operator — только px, шкала объявлена блоком
   .mk-operator в tokens.css. Она МЕЛЬЧЕ базовой: зал читают с четырёх метров,
   оператор — с шестидесяти сантиметров, и ему нужна плотность, а не кегль.
   Потолок ширины — 1280; нижняя граница по высоте — 768, поэтому вертикаль
   дороже горизонтали, и ни один компонент не растёт вверх без нужды.
   ══════════════════════════════════════════════════════════════════════════ */

/* ==========================================================================
   24 · Бейдж фазы
   Состояния: ждём людей · ход за оператором · идёт речь Марго ·
              фаза не опознана
   Фаза пишется ПО-РУССКИ. Сейчас на экране стоит сырой enum (voting_open),
   и стоит он дважды. Оператор читает его в третий раз за игру уже вслепую,
   а на четвёртый — не читает вовсе.
   ========================================================================== */

.mk-phase {
    display: inline-flex;
    align-items: center;
    gap: var(--sp-2);
    padding: var(--sp-2) var(--sp-4);
    border-radius: var(--r-full);
    background: var(--glass-bg);
    border: 1px solid var(--glass-border);
    font-family: var(--font-body);
    font-size: var(--fs-label);
    font-weight: 600;
    letter-spacing: var(--ls-label);
    text-transform: uppercase;
    color: var(--text-mid);
}

.mk-phase__dot {
    --mk-phase-dot: 8px;
    width: var(--mk-phase-dot);
    height: var(--mk-phase-dot);
    border-radius: var(--r-full);
    background: var(--text-dim);
}

/* Ждём людей: капитаны думают, отвечают, жмут. Трогать нельзя — и это
   такое же состояние, как любое другое, а не отсутствие состояния. */
.mk-phase--wait .mk-phase__dot {
    background: var(--warn);
    animation: mk-pulse-dot 1.6s var(--ease-inout) infinite;
}
.mk-phase--wait { border-color: var(--warn); color: var(--warn); }

/* Ход за оператором — единственное состояние, где от него чего-то ждут. */
.mk-phase--act {
    border-color: var(--emotion);
    color: var(--emotion-soft);
    box-shadow: var(--glow-emotion-soft);
}
.mk-phase--act .mk-phase__dot { background: var(--emotion); }

/* Говорит Марго: жать нельзя, пока не договорит, — иначе буззер откроется
   поверх её реплики. */
.mk-phase--live { border-color: var(--emotion-border); color: var(--text-hi); }
.mk-phase--live .mk-phase__dot {
    background: var(--emotion);
    animation: mk-breathe 1.4s var(--ease-inout) infinite;
}

/* Фаза не опознана. Красным — потому что это не «нормально и редко»,
   а сигнал, что экран и движок разошлись. */
.mk-phase--unknown { border-color: var(--err); color: var(--err); }
.mk-phase--unknown .mk-phase__dot { background: var(--err); }

/* Сырое значение фазы рядом с русским именем — мелко, для отладки и разговора
   с разработчиком. Убирать нельзя: без него оператор не сможет назвать то,
   что видит. */
.mk-phase__raw {
    font-family: var(--font-num);
    font-size: var(--fs-label);
    letter-spacing: 0;
    text-transform: none;
    color: var(--text-dim);
}

/* ==========================================================================
   25 · Команда оператора
   Состояния: рутинная · главная · необратимая · взведённая · выключенная
   Три веса, а не два. Разница между «Показать вопрос» и «Завершить игру» не
   в цвете, а в последствиях: первое повторяется, второе не отменяется.
   ========================================================================== */

.mk-cmd {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: var(--sp-3);
    min-height: var(--tap);
    padding: var(--sp-3) var(--sp-5);
    border-radius: var(--r-sm);
    background: var(--glass-bg);
    border: 1px solid var(--glass-border);
    color: var(--text-hi);
    font-family: var(--font-body);
    font-size: var(--fs-h3);
    font-weight: 600;
    cursor: pointer;
    transition: border-color var(--dur-xs) var(--ease-out),
                background var(--dur-xs) var(--ease-out);
}

.mk-cmd:hover { border-color: var(--emotion-border); }

/* Буква клавиши прямо на кнопке: оператор ведёт игру с клавиатуры, а мышью
   только страхуется. Клавиша, написанная в справке сбоку, не выучивается. */
.mk-cmd__key {
    padding: 0 var(--sp-2);
    border-radius: var(--r-sm);
    background: var(--bg-0);
    border: 1px solid var(--glass-border);
    font-family: var(--font-num);
    font-size: var(--fs-small);
    color: var(--text-mid);
}

/* Главная — та, которую жмут по Space. Одна в кадре, всегда одна. */
.mk-cmd--main {
    background: var(--emotion-soft);
    border-color: transparent;
    color: var(--emotion-ink);
    font-size: var(--fs-h2);
    padding: var(--sp-4) var(--sp-6);
}
.mk-cmd--main .mk-cmd__key {
    background: transparent;
    border-color: var(--emotion-ink);
    color: var(--emotion-ink);
}
.mk-cmd--main:hover { background: var(--emotion); }

/* Необратимая. Не красная копия обычной, а другой предмет: обведена,
   не залита — заливка приглашает нажать, обводка предупреждает. */
.mk-cmd--danger {
    background: transparent;
    border-color: var(--err);
    color: var(--err);
}
.mk-cmd--danger:hover { border-color: var(--err); background: var(--err-glow); }

/* Взведена: первый клик не выполняет, а спрашивает. Отката в игре нет,
   поэтому подтверждение — единственное, что стоит между промахом и концом
   игры. Само поведение (сброс через пару секунд) — стадия 05. */
.mk-cmd--armed {
    background: var(--err);
    border-color: var(--err);
    color: var(--bg-0);
    box-shadow: var(--glow-err);
    animation: mk-breathe 1s var(--ease-inout) infinite;
}

.mk-cmd--off {
    opacity: 0.4;
    cursor: not-allowed;
}
.mk-cmd--off:hover { border-color: var(--glass-border); }

/* ==========================================================================
   26 · Зона следующего действия
   Состояния: ход за оператором · ждём игроков · говорит Марго ·
              фаза не опознана
   Самая крупная зона экрана и единственная, куда оператор смотрит в спешке.
   Обязана отвечать на один вопрос: что делать прямо сейчас. Когда делать
   нечего — так и говорить, а не показывать пустоту.
   ========================================================================== */

.mk-next {
    display: flex;
    align-items: center;
    gap: var(--sp-5);
    padding: var(--sp-4) var(--sp-5);
    border-radius: var(--r-md);
    background: var(--glass-bg);
    border: 1px solid var(--glass-border);
}

.mk-next__body { flex: 1 1 auto; min-width: 0; }

.mk-next__what {
    font-family: var(--font-body);
    font-size: var(--fs-h2);
    font-weight: 600;
    color: var(--text-hi);
}

.mk-next__why {
    margin-top: var(--sp-1);
    font-family: var(--font-body);
    font-size: var(--fs-small);
    color: var(--text-mid);
}

/* Второстепенные действия — справа от главного и заметно тише: «Пропустить
   вопрос» рядом с «Показать вопрос» не должны читаться как пара равных. */
.mk-next__alt { display: flex; gap: var(--sp-3); }

/* Ждём игроков. Кнопки нет вовсе — не выключенная, а именно нет: выключенная
   кнопка приглашает попробовать, а здесь пробовать нечего. */
.mk-next--wait { border-color: var(--warn); }
.mk-next--wait .mk-next__what { color: var(--warn); }

.mk-next--live { border-color: var(--emotion-border); }

/* ФАЗА НЕ ОПОЗНАНА. Сегодня это пустая зона и оператор без подсказки посреди
   живой игры. Экран обязан предложить хоть что-то: назвать сырую фазу и дать
   безопасный выход. */
.mk-next--unknown { border-color: var(--err); }
.mk-next--unknown .mk-next__what { color: var(--err); }

/* ==========================================================================
   27 · Степпер туров
   Состояния шага: пройден · идёт · впереди · финал
   Оператор должен видеть, где он в игре, не считая в уме. Это же место
   показывает, что туров осталось меньше, чем времени, — и наоборот.
   ========================================================================== */

/* Переносится: в кадре 1280 полный степпер занимает 1230 px, и это без
   боковой панели. Первым переносом уходит финал — он и должен уходить
   последним по важности. */
.mk-tour-steps {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--sp-2);
}

.mk-tour-steps__step {
    display: flex;
    align-items: center;
    gap: var(--sp-2);
    padding: var(--sp-2) var(--sp-4);
    border-radius: var(--r-full);
    background: var(--glass-bg);
    border: 1px solid var(--glass-border);
    font-family: var(--font-body);
    font-size: var(--fs-small);
    color: var(--text-dim);
    white-space: nowrap;
}

.mk-tour-steps__n {
    font-family: var(--font-num);
    font-variant-numeric: tabular-nums;
}

.mk-tour-steps__sep {
    --mk-step-sep: 18px;
    width: var(--mk-step-sep);
    height: 1px;
    background: var(--glass-border);
    flex: none;
}

.mk-tour-steps__step--done { color: var(--text-mid); border-color: var(--glass-border); }
.mk-tour-steps__step--done .mk-tour-steps__n { color: var(--ok); }

.mk-tour-steps__step--now {
    border-color: var(--emotion);
    color: var(--text-hi);
    box-shadow: var(--glow-emotion-soft);
}
.mk-tour-steps__step--now .mk-tour-steps__n { color: var(--emotion-soft); }

.mk-tour-steps__step--next { opacity: 0.55; }

.mk-tour-steps__step--final { border-color: var(--gold); color: var(--gold); }

/* ==========================================================================
   28 · Панель вердикта
   Состояния: оценивать нечего · ждём устный ответ · вердикт уходит ·
              вердикт принят (заблокирована)
   ЗАКРЕПЛЕНА, А НЕ В ЛЕНТЕ. Сегодня кнопки вердикта живут строкой чата и
   уезжают вверх при каждом новом сообщении.
   БЛОКИРУЕТСЯ ПОСЛЕ НАЖАТИЯ. Двойной клик по вердикту начисляет очки дважды
   (эндпоинт не идемпотентен, воспроизведено прогоном). Пока это чинят в
   бэкенде, интерфейс обязан не давать нажать второй раз — и говорить,
   почему не даёт.
   ========================================================================== */

.mk-verdict-panel {
    display: flex;
    align-items: center;
    gap: var(--sp-5);
    padding: var(--sp-4) var(--sp-5);
    border-radius: var(--r-md);
    background: var(--glass-bg-heavy);
    border: 1px solid var(--glass-border);
}

.mk-verdict-panel__who { flex: 1 1 auto; min-width: 0; }

.mk-verdict-panel__team {
    font-family: var(--font-display);
    font-weight: 700;
    font-size: var(--fs-h2);
    color: var(--text-hi);
}

.mk-verdict-panel__meta {
    margin-top: var(--sp-1);
    font-family: var(--font-body);
    font-size: var(--fs-small);
    color: var(--text-mid);
}

/* Верный ответ виден ТОЛЬКО оператору. Пунктир — метка «это не показывают
   залу»: тот же приём, что у подсказки в ленте. */
.mk-verdict-panel__key {
    display: inline-flex;
    align-items: center;
    gap: var(--sp-2);
    padding: var(--sp-2) var(--sp-3);
    border-radius: var(--r-sm);
    border: 1px dashed var(--warn);
    font-family: var(--font-body);
    font-size: var(--fs-small);
    color: var(--warn);
}

.mk-verdict-panel__acts { display: flex; gap: var(--sp-3); flex: none; }

/* Две кнопки вердикта — статусные, а не акцентные: они сообщают факт. */
.mk-verdict-panel__ok,
.mk-verdict-panel__err {
    display: inline-flex;
    align-items: center;
    gap: var(--sp-2);
    min-height: var(--tap);
    padding: var(--sp-3) var(--sp-5);
    border-radius: var(--r-sm);
    background: transparent;
    font-family: var(--font-body);
    font-size: var(--fs-h3);
    font-weight: 600;
    cursor: pointer;
}

.mk-verdict-panel__ok { border: 1px solid var(--ok); color: var(--ok); }
.mk-verdict-panel__ok:hover { background: var(--ok-glow); }
.mk-verdict-panel__err { border: 1px solid var(--err); color: var(--err); }
.mk-verdict-panel__err:hover { background: var(--err-glow); }

/* Оценивать нечего — панель на месте, но пустая и тихая. Место под неё
   держится всегда: панель, появляющаяся и исчезающая, смещает всё под собой
   ровно в тот момент, когда оператор целится мышью. */
.mk-verdict-panel--idle {
    background: var(--glass-bg);
    border-style: dashed;
    color: var(--text-dim);
}

.mk-verdict-panel--wait { border-color: var(--warn); }

/* Вердикт уходит и вердикт принят — обе кнопки мертвы. */
.mk-verdict-panel--sending .mk-verdict-panel__ok,
.mk-verdict-panel--sending .mk-verdict-panel__err,
.mk-verdict-panel--done .mk-verdict-panel__ok,
.mk-verdict-panel--done .mk-verdict-panel__err {
    opacity: 0.35;
    cursor: not-allowed;
}

.mk-verdict-panel--done { border-color: var(--ok); }

/* ==========================================================================
   29 · Кнопки тона
   Состояния: покой · выбран · подсказанный вопросом · заблокирован
   ШЕСТЬ РАЗНЫХ ЦВЕТОВ В ОДНОМ РЯДУ — ЕДИНСТВЕННОЕ МЕСТО В СИСТЕМЕ, ГДЕ ЭТО
   РАЗРЕШЕНО. Правило «один акцент в кадре» здесь не нарушается, а исполняется
   буквально: ряд тонов и есть переключатель этого акцента, и каждая кнопка
   обязана показывать, во что перекрасится сцена. Цвета не заведены заново —
   каждая кнопка несёт data-emotion и берёт свой хью из той же карты
   tokens.css, что орб и зал.
   ========================================================================== */

.mk-tone-row { display: flex; gap: var(--sp-2); }

.mk-tone {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: var(--sp-1);
    min-width: var(--tap);
    padding: var(--sp-2) var(--sp-3);
    border-radius: var(--r-sm);
    background: var(--glass-bg);
    border: 1px solid var(--glass-border);
    color: var(--text-mid);
    font-family: var(--font-body);
    font-size: var(--fs-label);
    cursor: pointer;
    transition: border-color var(--dur-xs) var(--ease-out);
}

.mk-tone__key {
    font-family: var(--font-num);
    font-size: var(--fs-h3);
    font-weight: 700;
    color: var(--emotion);
}

.mk-tone:hover { border-color: var(--emotion-border); }

.mk-tone--on {
    background: var(--emotion-faint);
    border-color: var(--emotion);
    color: var(--text-hi);
    box-shadow: var(--glow-emotion-soft);
}

/* Подсказанный вопросом: в самом вопросе записан suggested_tone. Не выбран
   за оператора — предложен, и видно, что предложен. */
.mk-tone--hint { border-style: dashed; border-color: var(--emotion); }

.mk-tone--off { opacity: 0.35; cursor: not-allowed; }

/* ==========================================================================
   30 · Строка ленты игры
   Состояния: системная · вопрос · реплика Марго · Марго думает · вердикт
              верно · вердикт неверно · ответ команды · нажатие буззера
              с очередью · подсказка с верным ответом (только оператору)
   Лента — не чат, а журнал: по ней после игры разбирают, что произошло.
   Поэтому у каждой строки есть время и тип, а не только текст.
   ========================================================================== */

.mk-feed { display: flex; flex-direction: column; gap: var(--sp-2); }

.mk-feed__line {
    display: flex;
    gap: var(--sp-4);
    padding: var(--sp-3) var(--sp-4);
    border-radius: var(--r-sm);
    background: var(--glass-bg);
    border-left: 2px solid var(--glass-border);
    font-family: var(--font-body);
    font-size: var(--fs-body);
    color: var(--text-mid);
}

.mk-feed__time {
    flex: none;
    font-family: var(--font-num);
    font-variant-numeric: tabular-nums;
    font-size: var(--fs-small);
    color: var(--text-dim);
}

.mk-feed__text { flex: 1 1 auto; min-width: 0; }

.mk-feed__text b { color: var(--text-hi); font-weight: 600; }

/* Системная — самая тихая: её читают только когда что-то пошло не так. */
.mk-feed__line--sys { background: transparent; color: var(--text-dim); }

.mk-feed__line--question {
    border-left-color: var(--text-mid);
    color: var(--text-hi);
}

/* Реплика Марго — голос ведущей, единственная строка в цвете эмоции. */
.mk-feed__line--margo {
    border-left-color: var(--emotion);
    background: var(--emotion-faint);
    color: var(--text-hi);
}

/* Думает — та же строка, но без текста: место занято, чтобы приход реплики
   не сдвинул ленту. */
.mk-feed__line--thinking {
    border-left-color: var(--emotion-border);
    color: var(--text-dim);
    font-style: italic;
}

.mk-feed__line--verdict-ok { border-left-color: var(--ok); }
.mk-feed__line--verdict-ok .mk-feed__points { color: var(--ok); }
.mk-feed__line--verdict-err { border-left-color: var(--err); }
.mk-feed__line--verdict-err .mk-feed__points { color: var(--err); }

.mk-feed__points {
    flex: none;
    font-family: var(--font-num);
    font-variant-numeric: tabular-nums;
    font-weight: 700;
}

.mk-feed__line--answer { border-left-color: var(--text-dim); }

.mk-feed__line--buzz {
    border-left-color: var(--warn);
    color: var(--text-hi);
}

/* Очередь нажатий прямо в строке: кто следующий, если ответ не засчитают. */
.mk-feed__queue {
    display: flex;
    flex-wrap: wrap;
    gap: var(--sp-2);
    margin-top: var(--sp-2);
    font-size: var(--fs-small);
    color: var(--text-mid);
}

.mk-feed__queue i {
    padding: 0 var(--sp-2);
    border-radius: var(--r-sm);
    background: var(--bg-0);
    font-style: normal;
}

.mk-feed__queue i.out { opacity: 0.45; text-decoration: line-through; }

/* ПОДСКАЗКА С ВЕРНЫМ ОТВЕТОМ — ВИДНА ТОЛЬКО ОПЕРАТОРУ. Пунктир по всему
   периметру — единственная в системе метка «этого нет на экране зала».
   Один и тот же приём здесь и в панели вердикта: увидев пунктир, оператор
   знает, что читает не то, что видит зал. */
.mk-feed__line--secret {
    background: transparent;
    border: 1px dashed var(--warn);
    border-left-width: 2px;
    color: var(--warn);
}

/* ==========================================================================
   31 · Строка команды в лобби
   Состояния: подключена · ждём · тест идёт (четыре шага) · тест пройден
   До старта это единственный экран, по которому видно, поедет игра или нет.
   PIN здесь же: его диктуют вслух и переспрашивают чаще всего.
   ========================================================================== */

.mk-lobby-row {
    display: flex;
    align-items: center;
    gap: var(--sp-4);
    padding: var(--sp-3) var(--sp-4);
    border-radius: var(--r-sm);
    background: var(--glass-bg);
    border: 1px solid var(--glass-border);
}

.mk-lobby-row__dot {
    --mk-lobby-dot: 8px;
    flex: none;
    width: var(--mk-lobby-dot);
    height: var(--mk-lobby-dot);
    border-radius: var(--r-full);
    background: var(--text-dim);
}

/* ИМЯ КОМАНДЫ ПЕРЕНОСИТСЯ, А НЕ ОБРЕЗАЕТСЯ. Первая версия строки резала его
   многоточием — и в сайдбаре оператора, где на имя остаётся 77 px после
   PIN и пяти точек прогресса, «Сепараторы» превращались в «Сепарато…»,
   а «Ночная смена» в «Ночная с…». Это нарушение правила системы: многоточие
   здесь не аккуратная вёрстка, а потерянная команда — оператор называет
   её вслух. Строка растёт в высоту; в сайдбаре это дешевле, чем догадки. */
.mk-lobby-row__name {
    flex: 1 1 auto;
    min-width: 0;
    overflow-wrap: break-word;
    hyphens: auto;
    font-family: var(--font-body);
    font-size: var(--fs-h3);
    color: var(--text-hi);
}

/* PIN моноширинный и крупнее окружения: его читают вслух через зал. */
.mk-lobby-row__pin {
    flex: none;
    padding: var(--sp-1) var(--sp-3);
    border-radius: var(--r-sm);
    background: var(--bg-0);
    border: 1px solid var(--glass-border);
    font-family: var(--font-num);
    font-variant-numeric: tabular-nums;
    font-size: var(--fs-h3);
    letter-spacing: 0.08em;
    color: var(--text-hi);
}

/* Четыре шага теста связи — буззер, голосование, текст, таймер и счёт. */
.mk-lobby-row__steps { display: flex; gap: var(--sp-1); flex: none; }

.mk-lobby-row__steps i {
    --mk-step-dot: 10px;
    width: var(--mk-step-dot);
    height: var(--mk-step-dot);
    border-radius: var(--r-full);
    background: var(--glass-border);
}

.mk-lobby-row__steps i.done { background: var(--ok); }
.mk-lobby-row__steps i.now {
    background: var(--emotion);
    box-shadow: var(--glow-emotion-soft);
}

.mk-lobby-row--online .mk-lobby-row__dot {
    background: var(--ok);
    box-shadow: var(--glow-ok-soft);
}

.mk-lobby-row--waiting { opacity: 0.7; }
.mk-lobby-row--waiting .mk-lobby-row__dot {
    background: var(--warn);
    animation: mk-pulse-dot 1.6s var(--ease-inout) infinite;
}

.mk-lobby-row--passed { border-color: var(--ok); }

/* ==========================================================================
   32 · Поле анкеты
   Состояния: покой · в фокусе · заполнено · обязательное не заполнено ·
              счётчик знаков
   Добавлено стадией 04: экраны регистрации и загадок капитана состоят
   из полей, а в инвентаре стадии 03 поля не было — там ввод исчерпывался
   PIN и текстовым ответом. Нумерация сквозная, блок стоит после оператора,
   но живёт по правилам капитанской поверхности (--tap, крупный ввод).
   ========================================================================== */

.mk-field {
    display: flex;
    flex-direction: column;
    gap: var(--sp-2);
}

.mk-field__label {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: var(--sp-3);
    font-family: var(--font-body);
    font-size: var(--fs-label);
    font-weight: 600;
    letter-spacing: var(--ls-label);
    text-transform: uppercase;
    color: var(--text-dim);
}

/* Обязательность помечается словом, а не звёздочкой: звёздочку на телефоне
   в полутьме не видно, а «обязательно» читается сразу. */
.mk-field__req { color: var(--emotion-soft); letter-spacing: 0; text-transform: none; }

.mk-field__input {
    width: 100%;
    min-height: var(--tap);
    padding: var(--sp-3) var(--sp-4);
    border-radius: var(--r-md);
    background: var(--glass-bg);
    border: 1px solid var(--glass-border);
    color: var(--text-hi);
    font-family: var(--font-body);
    font-size: var(--fs-h3);
}

.mk-field__input::placeholder { color: var(--text-dim); }

.mk-field__input:focus {
    outline: none;
    border-color: var(--emotion-border);
    box-shadow: var(--glow-emotion-soft);
}

/* Цифровое поле — моноширинное и крупное: PIN, номер, количество бойцов.
   Тут важна не длина строки, а безошибочность набора. */
.mk-field__input--num {
    font-family: var(--font-num);
    font-variant-numeric: tabular-nums;
    font-size: var(--fs-h1);
    text-align: center;
    letter-spacing: 0.12em;
}

/* Многострочное поле — рассказ о команде в анкете. Высота задана строками,
   а не пикселями: три строки текущего кегля остаются тремя строками и после
   правки шкалы. Тянуть вниз игрок может, вбок — нет: горизонтальное
   растягивание ломает поля экрана в руке. */
.mk-field__input--multi {
    min-height: 0;
    padding-top: var(--sp-3);
    resize: vertical;
    line-height: 1.5;
}

.mk-field--filled .mk-field__input { border-color: var(--emotion-border); }

/* Не заполнено обязательное. Подсветка появляется ТОЛЬКО после попытки
   отправить: краснеть на глазах у того, кто ещё печатает, — это упрёк
   за незаконченное действие. */
.mk-field--empty .mk-field__input { border-color: var(--err); }
.mk-field--empty .mk-field__hint { color: var(--err); }

.mk-field__hint {
    font-family: var(--font-body);
    font-size: var(--fs-small);
    color: var(--text-dim);
}


/* ==========================================================================
   33 · Живой фон сцены
   Состояния: покой · цвет эмоции · perf-режим (фон выключен)

   ПЕРЕЕХАЛ СЮДА ИЗ screens/lobby.css 2026-08-23, значения не менялись.
   Причина переезда — та же, по которой сюда уехали .mk-qr, .mk-ticker
   и .mk-team-chip: фон понадобился ВТОРОЙ поверхности (витрине лендинга),
   а класс, объявленный в файле одного экрана и применяемый на другом, —
   это второй источник правды.

   Что это такое: три размытых пятна в цвете текущей эмоции, медленно
   плывущие по своим орбитам, плюс виньетка и пылинки. Фон — единственное
   место системы, где цвет эмоции виден всегда, даже когда в кадре ничего
   не происходит: именно он делает «эмоция Марго = глобальный акцент»
   не метафорой, а видимым фактом.
   ========================================================================== */

/* КЛИПУЕТ СЕБЯ САМА. Пятна вылезают за кадр по построению — на 200–460 px, —
   и в зале их обрезает сцена (.mk-stage, overflow: hidden). На витрине
   обрезать некому: страница прокручивается, и абсолютно позиционированные
   пятна дают ей фантомную ширину 846 px при экране 390. Клип по своему
   же прямоугольнику: он совпадает с тем, чем их обрезала сцена, — в зале
   не меняется ни один пиксель. */
.mk-aurora { position: absolute; inset: 0; overflow: hidden; pointer-events: none; }

/* ВАЖНО: сцена зала спроектирована в координатах 1920×1080 и масштабируется
   transform'ом — поэтому внутри неё только px, НИКАКИХ vw/vh (они меряют
   окно браузера, а не сцену). Blur умеренный: дорог на слабых GPU.
   Размер вынесен в --mk-aurora-size: у зала пятно 850 px на кадр 1920,
   у витрины кадр меняется вместе с окном, и пятно задаётся её файлом. */
.mk-aurora i {
    position: absolute;
    width: var(--mk-aurora-size, 850px);
    height: var(--mk-aurora-size, 850px);
    border-radius: 50%;
    filter: blur(48px);
    opacity: 0.55;
    will-change: transform;
    transform: translateZ(0);
}

/* Perf-режим для слабого железа (стенд: ?perf=low; реальный ТВ — конфиг):
   без авроры и backdrop-blur, стекло становится плотным. */
[data-perf="low"] .mk-aurora { display: none; }
[data-perf="low"] .mk-panel {
    backdrop-filter: none;
    -webkit-backdrop-filter: none;
    background: var(--glass-bg-heavy);
}
.mk-aurora i:nth-child(1) {
    left: -12%; top: -20%;
    background: radial-gradient(circle,
        hsl(var(--emotion-h) var(--emotion-s) 34% / 0.55), transparent 65%);
    animation: mk-aurora-a 26s var(--ease-inout) infinite;
}
.mk-aurora i:nth-child(2) {
    right: -18%; top: 10%;
    background: radial-gradient(circle,
        hsl(calc(var(--emotion-h) + 40) 70% 30% / 0.42), transparent 65%);
    animation: mk-aurora-b 34s var(--ease-inout) infinite;
}
.mk-aurora i:nth-child(3) {
    left: 22%; bottom: -30%;
    background: radial-gradient(circle,
        hsl(calc(var(--emotion-h) - 30) 60% 26% / 0.38), transparent 65%);
    animation: mk-aurora-c 30s var(--ease-inout) infinite;
}

.mk-vignette {
    position: absolute; inset: 0; pointer-events: none;
    background: radial-gradient(ellipse at center,
        transparent 55%, rgba(4, 6, 14, 0.55) 100%);
}

/* Пылинки: медленно плывущие светлячки в цвете эмоции */
.mk-dust { position: absolute; inset: 0; pointer-events: none; }
.mk-dust i {
    position: absolute;
    border-radius: 50%;
    background: var(--emotion-soft);
    box-shadow: 0 0 8px var(--emotion-glow);
    animation: mk-float 8s var(--ease-inout) infinite;
    will-change: transform;
}
[data-perf="low"] .mk-dust { display: none; }

/* ==========================================================================
   34 · Кнопка действия
   Состояния: главная · обычная · призрак · выключена · занята

   ЗАЧЕМ ОБЩАЯ КНОПКА, КОГДА В СИСТЕМЕ УЖЕ ЕСТЬ ТРИ. Те три — не кнопки
   вообще, а предметы своего домена: .mk-cmd носит клавишу оператора,
   .mk-send знает, что ответ отправляется, .mk-vote — что вариант выбран.
   Мини-игре и витрине нужна кнопка без домена: «НАЧАТЬ», «Войти»,
   «Отправить ещё раз», «Обсудить игру». Заводить её дважды в двух файлах
   экранов — тот самый второй источник правды, из-за которого сюда переехал
   фон, поэтому предмет один и живёт здесь.

   Габарит берётся из --tap своей поверхности: 52 у мини-игры, 48 у витрины.
   Цвет — эмоция; сигнальные цвета кнопке действия не полагаются: красным
   в этой системе говорят «неверно», а не «нажми сюда».
   ========================================================================== */

.mk-act {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: var(--sp-3);
    min-height: var(--tap);
    padding: 0 var(--sp-6);
    border: 1px solid var(--emotion-border);
    border-radius: var(--r-full);
    background: var(--glass-bg);
    color: var(--text-hi);
    font-family: var(--font-body);
    font-size: var(--fs-body);
    font-weight: 600;
    letter-spacing: 0.02em;
    text-decoration: none;
    cursor: pointer;
    transition: transform var(--dur-xs) var(--ease-out),
                border-color var(--dur-xs) var(--ease-out),
                box-shadow var(--dur-sm) var(--ease-out);
}

.mk-act:hover { border-color: var(--emotion); box-shadow: var(--glow-emotion-soft); }
.mk-act:active { transform: scale(0.98); }

/* Главная: одна на кадр. Заливка — --emotion-soft, потому что поверх неё
   лежит текст: правило контраста из tokens.css рядом с --emotion-ink. */
.mk-act--main {
    background: var(--emotion-soft);
    border-color: transparent;
    color: var(--emotion-ink);
    font-weight: 700;
    box-shadow: var(--glow-emotion);
}

.mk-act--main:hover { box-shadow: var(--glow-emotion), 0 0 0 2px var(--emotion-faint); }

/* Призрак: второе действие рядом с главным. Обводка приглашает меньше. */
.mk-act--ghost {
    background: transparent;
    border-color: var(--glass-border);
    color: var(--text-mid);
    font-weight: 500;
}

.mk-act--off {
    opacity: 0.4;
    cursor: not-allowed;
    box-shadow: none;
    pointer-events: none;
}

/* Занята: нажали, ответа ждём. Кнопка обязана перестать быть нажимаемой
   сама — иначе игрок нажмёт её трижды, и очки уйдут трижды. */
.mk-act--busy { pointer-events: none; color: var(--text-mid); }

.mk-act__spin {
    width: 1em;
    height: 1em;
    border: 2px solid var(--glass-border);
    border-top-color: var(--emotion);
    border-radius: var(--r-full);
    /* mk-spin объявлен выше, у предмета 14 (кнопка отправки капитана):
       второй раз те же keyframes не заводим. */
    animation: mk-spin 800ms linear infinite;
}

/* Крупная — для витрины: там кнопка стоит одна в пустом кадре и обязана
   держать вес заголовка над собой. */
.mk-act--lg { min-height: calc(var(--tap) * 1.2); padding: 0 var(--sp-7); font-size: var(--fs-h3); }

/* ==========================================================================
   35 · Узел промысла
   Типы: штатный · газ · конденсат · уникальный приток
   Состояния: спит · давление растёт · критическое · авария · сброшен

   ЭТО СПЕЦИФИКАЦИЯ ДЛЯ CANVAS, А НЕ ТОЛЬКО DOM-ПРЕДМЕТ. Поле мини-игры
   рисуется канвой (static/minigame/game-v2.js), и цвета там сегодня зашиты
   числами. Предмет задаёт, ЧТО именно канва обязана нарисовать: диаметр,
   кольцо давления, отметки оставшихся кликов, четыре цвета типов. Канва
   читает их из вычисленного стиля поверхности — третьей карты цветов
   в проекте не заводится, как её нет у орба и сцены.

   ЦВЕТ ЗДЕСЬ — КАТЕГОРИЯ, А НЕ АКЦЕНТ. Тип узла игрок различает боковым
   зрением за доли секунды, поэтому четыре цвета в одном кадре разрешены
   ровно так же, как шесть цветов в ряду тонов у оператора: это не украшение,
   это данные. Сами значения — в tokens.css, блок .mk-minigame.

   ДИАМЕТР СЧИТАЕТСЯ ОТ ЗОНЫ НАЖАТИЯ, А НЕ ОТ СЕТКИ. Узел — цель под
   быстрый клик, и он не имеет права стать меньше --tap только потому,
   что в четвёртой волне сетка выросла до 5×4.
   ========================================================================== */

.mk-node {
    --node: var(--node-normal);
    --p: 0;                              /* давление 0…1, ставит игра */
    position: relative;
    width: calc(var(--tap) * 1.7);
    height: calc(var(--tap) * 1.7);
    border-radius: var(--r-full);
    display: grid;
    place-items: center;
    background: var(--bg-2);
    border: 1px solid var(--glass-border);
    color: var(--node);
}

.mk-node--normal { --node: var(--node-normal); }
.mk-node--fast   { --node: var(--node-fast); }
.mk-node--heavy  { --node: var(--node-heavy); }
.mk-node--unique { --node: var(--node-unique); }

/* Кольцо давления: заполняется по часовой. Дуга, а не столбик, потому что
   узел круглый и целятся в центр — край кольца не крадёт площадь. */
.mk-node__ring {
    position: absolute;
    inset: -1px;
    border-radius: var(--r-full);
    background: conic-gradient(var(--node) calc(var(--p) * 360deg),
                               var(--glass-border) 0);
    /* #000 в маске — не цвет, а непрозрачность: маска читает альфу, и любой
       токен палитры здесь означал бы ровно то же самое. */
    -webkit-mask: radial-gradient(farthest-side, transparent calc(100% - 6px), #000 calc(100% - 5px));
    mask: radial-gradient(farthest-side, transparent calc(100% - 6px), #000 calc(100% - 5px));
}

/* Сердцевина: сама задвижка. В покое почти не светится — светится то,
   что требует действия, иначе поле превращается в гирлянду. */
.mk-node__core {
    width: 46%;
    height: 46%;
    border-radius: var(--r-sm);
    background: var(--bg-1);
    border: 2px solid var(--node);
    opacity: 0.45;
    /* Переход ТОЛЬКО по прозрачности. Свечение включается мгновенно — и это
       не экономия, а сигнал: узел проснулся, и игрок должен узнать об этом
       в тот же кадр, а не через четверть секунды. Плюс цена: в четвёртой
       волне на поле двадцать узлов, и плавный box-shadow на каждом — это
       двадцать перерисовок размытой тени в кадре. Наведение и фокус —
       другое дело: там элемент один и событие однократное. */
    transition: opacity var(--dur-sm) var(--ease-out);
}

/* Сколько кликов ещё нужно: конденсат — два, уникальный приток — три.
   Число кликов невозможно угадать по цвету, а ошибиться в нём — потерять
   секунду на волне, где секунда стоит узла. */
.mk-node__hits {
    position: absolute;
    bottom: -14px;
    display: flex;
    gap: var(--sp-1);
}

.mk-node__hits i {
    width: 6px;
    height: 6px;
    border-radius: var(--r-full);
    background: var(--node);
    opacity: 0.35;
}

.mk-node__hits i.on { opacity: 1; }

/* Давление растёт: узел просыпается. */
.mk-node--rise .mk-node__core { opacity: 1; box-shadow: 0 0 18px var(--node); }

/* Критическое (>95 %): пульс РАЗМЕРОМ. Был mk-pulse-glow, то есть анимация
   тени, — а узлов в четвёртой волне на экране двадцать, и половина из них
   в этом состоянии. Размер к тому же заметнее боковым зрением, чем ореол:
   игрок в этот момент смотрит в другой угол поля. */
.mk-node--crit { animation: mk-breathe var(--dur-lg) var(--ease-inout) infinite; }
.mk-node--crit .mk-node__core { opacity: 1; box-shadow: var(--glow-err); }

/* Авария: узел сорвало. Красный тут не «ошибка интерфейса», а событие игры,
   и он гаснет сам — застывший красный узел читается как поломка экрана. */
.mk-node--blown {
    border-color: var(--err);
    box-shadow: var(--glow-err-soft);
}
.mk-node--blown .mk-node__core {
    opacity: 1;
    border-color: var(--err);
    background: var(--err-glow);
    box-shadow: none;
}

/* Сброшен: короткая зелёная отбивка и обратно в покой. */
.mk-node--saved { border-color: var(--ok); box-shadow: var(--glow-ok-soft); }
.mk-node--saved .mk-node__core { border-color: var(--ok); opacity: 0.9; }

/* Спит: место в сетке занято, но узел не активен. Не «пустая клетка» —
   игрок должен видеть всю сетку целиком, иначе следующий узел появляется
   в пустоте и глаз ищет его заново. */
.mk-node--calm { opacity: 0.5; }

/* ---------- Легенда типов (экран правил) ---------- */

.mk-node-legend { display: flex; flex-direction: column; gap: var(--sp-2); }

.mk-node-legend__item {
    display: flex;
    align-items: center;
    gap: var(--sp-3);
    font-family: var(--font-body);
    font-size: var(--fs-small);
    color: var(--text-mid);
}

.mk-node-legend__item b { color: var(--text-hi); font-weight: 600; }

.mk-node-legend__dot {
    flex: none;
    width: 14px;
    height: 14px;
    border-radius: var(--r-full);
    background: var(--node, var(--node-normal));
    box-shadow: 0 0 10px var(--node, var(--node-normal));
}

/* ==========================================================================
   36 · Полоса HUD
   Состояния: игра идёт · последние десять секунд · последние пять ·
              передышка между волнами · комбо ниже трёх (скрыто) ·
              комбо x1.5 / x2 / x3

   ЧЕТЫРЕ ЦИФРЫ И ОДНО СЛОВО, И ЧИТАЮТСЯ ОНИ БОКОВЫМ ЗРЕНИЕМ. Игрок смотрит
   в поле, а не в HUD: за 60 секунд у него нет ни одной свободной десятой.
   Отсюда моноширинный --font-num с табличными цифрами — иначе счёт прыгает
   шириной на каждом начислении и глаз к нему возвращается — и порядок ячеек
   по важности: время слева, очки справа, между ними то, что объясняет
   происходящее.

   СКОБОК И COURIER БОЛЬШЕ НЕТ. В продукте HUD набран Courier New и обёрнут
   в квадратные скобки — это язык терминала, а не этой системы. Цифры здесь
   живут в JetBrains Mono, подписи — капс-лейблом.

   ПОРОГИ ВРЕМЕНИ — ТЕ ЖЕ, ЧТО У КОЛЬЦА ТАЙМЕРА В ЗАЛЕ: десять секунд
   и последние пять. В продукте их четыре (20 / 10 / 5 и базовый), и это
   лишняя ступень: игрок не различает «тревожно» и «очень тревожно»,
   он различает «ещё есть время» и «уже нет».
   ========================================================================== */

.mk-hud {
    display: flex;
    align-items: stretch;
    gap: var(--sp-2);
    padding: var(--sp-2);
    border-radius: var(--r-md);
    background: var(--glass-bg-heavy);
    border: 1px solid var(--glass-border);
    font-family: var(--font-body);
}

.mk-hud__cell {
    display: flex;
    flex-direction: column;
    justify-content: center;
    gap: 2px;
    padding: var(--sp-2) var(--sp-4);
    border-radius: var(--r-sm);
    min-width: 0;
}

.mk-hud__cell--grow { flex: 1; }

.mk-hud__label {
    font-size: var(--fs-label);
    font-weight: 600;
    letter-spacing: var(--ls-label);
    text-transform: uppercase;
    color: var(--text-dim);
    white-space: nowrap;
}

.mk-hud__value {
    font-family: var(--font-num);
    font-variant-numeric: tabular-nums;
    font-size: var(--fs-h2);
    font-weight: 700;
    line-height: 1.1;
    color: var(--text-hi);
    white-space: nowrap;
}

/* Волна и статус — словами, и слова длинные: «АВАРИЙНЫЙ СБРОС»,
   «НАЧАЛЬНИК ПРОМЫСЛА». Им кегль подписи, а не счёта. Переносятся, а не
   режутся: на ноутбуке они помещаются в строку, а на телефоне HUD станет
   на строку выше — это дешевле, чем «АВАРИЙНЫЙ СБ…» в момент, когда
   надпись и объясняет происходящее. */
.mk-hud__word {
    font-family: var(--font-display);
    font-size: var(--fs-h3);
    font-weight: 700;
    line-height: 1.15;
    color: var(--emotion-soft);
    overflow-wrap: break-word;
}

.mk-hud__word--status { color: var(--text-mid); }

/* Время: два порога, оба заметны боковым зрением. */
.mk-hud__value--warn { color: var(--warn); }
/* Последние пять секунд. ПУЛЬС СТАВИТ СОБЫТИЕ СЕКУНДЫ, А НЕ БЕСКОНЕЧНЫЙ
   ЦИКЛ: свободный цикл в 600 мс за пять секунд успевает разойтись с цифрой
   на пол-такта, и получается, что число меняется отдельно, а пульс отдельно.
   Такт вешает choreo.js классом .mk-tick-now на каждый тик. */
.mk-hud__value--urgent {
    color: var(--err);
    text-shadow: var(--glow-err-soft);
}

/* Передышка: HUD зеленеет целиком — это единственные три секунды за игру,
   когда можно выдохнуть, и они обязаны читаться, не отрывая глаз от поля. */
.mk-hud--pause {
    border-color: var(--ok);
    box-shadow: var(--glow-ok-soft);
}
.mk-hud--pause .mk-hud__word { color: var(--ok); }

/* ---------- Комбо ----------
   Ниже трёх не показывается вовсе: множителя ещё нет, а мигающая ячейка
   на каждом втором клике — шум. Три ступени цветом, и цвета не новые:
   зелень, золото и красный — те же, что у статусов и мест подиума. */

.mk-combo {
    display: flex;
    align-items: center;
    gap: var(--sp-2);
    padding: var(--sp-2) var(--sp-4);
    border-radius: var(--r-full);
    background: var(--glass-bg);
    border: 1px solid var(--glass-border);
    font-family: var(--font-num);
    font-size: var(--fs-h3);
    font-weight: 700;
    color: var(--ok);
}

.mk-combo__mult { font-size: var(--fs-small); color: var(--text-mid); }
.mk-combo--x2 { color: var(--gold); border-color: var(--gold); }
.mk-combo--x3 {
    color: var(--err);
    border-color: var(--err);
    box-shadow: var(--glow-err-soft);
    animation: mk-breathe 1.6s var(--ease-inout) infinite;
}
/* Модификатора «комбо выключено» нет намеренно: ниже трёх ячейка
   не прячется стилем, а не создаётся вовсе — прятать её значит держать
   в HUD пустое место под то, чего сейчас нет. */

/* ---------- Полоса волны ----------
   В продукте её рисует сама канва внизу экрана. Здесь она остаётся на своём
   месте — под полем, тонкой линией: это не показатель, за которым следят,
   а фон уверенности «волна кончается». */

.mk-wave-bar {
    height: 4px;
    border-radius: var(--r-full);
    background: var(--glass-border);
    overflow: hidden;
}

.mk-wave-bar__fill {
    height: 100%;
    width: 100%;
    background: var(--emotion);
    box-shadow: var(--glow-emotion-soft);
    transform: scaleX(var(--mk-fill, 0));
    transform-origin: left center;
    transition: transform var(--dur-sm) linear;
}

/* ==========================================================================
   37 · Ачивка
   Состояния: закрыта (в правилах) · открыта (в итогах) · только что взята

   ЭМОДЗИ В ГРАФИКЕ ЗАПРЕЩЕНЫ БРЕНДОМ (BRAND.md §6, пункт 6), а девять
   ачивок продукта построены ровно на них: 🧊 🔥 💎 🌊 ⚡ 🛡 👑 🔧 ⏰
   (static/minigame/game-v2.js:53). Замена — не «другая картинка», а другой
   носитель: знак рисуется inline-SVG в одну линию, тем же штрихом, что знак
   продукта. Побочная выгода честная: эмодзи на Windows, Android и iOS
   выглядят тремя разными наборами, а у игры один экран на всех.

   ПЛИТКА ЧИТАЕТСЯ И БЕЗ ЗНАКА. Смысловой якорь — не иконка, а число бонуса
   и название; знак работает меткой для беглого взгляда, а не единственным
   опознавательным признаком.

   ЗАКРЫТАЯ АЧИВКА ПОКАЗЫВАЕТ УСЛОВИЕ. В этом весь смысл списка на экране
   правил: игрок за 15 секунд до старта решает, за чем он охотится.
   ========================================================================== */

.mk-ach {
    display: flex;
    align-items: center;
    gap: var(--sp-3);
    padding: var(--sp-3) var(--sp-4);
    border-radius: var(--r-md);
    background: var(--glass-bg);
    border: 1px solid var(--glass-border);
    font-family: var(--font-body);
}

.mk-ach__mark {
    flex: none;
    width: calc(var(--tap) * 0.62);
    height: calc(var(--tap) * 0.62);
    display: grid;
    place-items: center;
    border-radius: var(--r-sm);
    background: var(--bg-1);
    color: var(--text-dim);
}

.mk-ach__mark svg { width: 62%; height: 62%; }

.mk-ach__body { flex: 1; min-width: 0; }

.mk-ach__name {
    display: block;
    font-size: var(--fs-small);
    font-weight: 600;
    color: var(--text-hi);
}

.mk-ach__desc {
    display: block;
    font-size: var(--fs-label);
    color: var(--text-mid);
    overflow-wrap: break-word;
}

.mk-ach__bonus {
    flex: none;
    font-family: var(--font-num);
    font-variant-numeric: tabular-nums;
    font-size: var(--fs-h3);
    font-weight: 700;
    color: var(--text-dim);
}

/* Взята: золото — та же валюта, что у первого места на подиуме. */
.mk-ach--got {
    border-color: var(--gold);
    background: var(--glass-bg-heavy);
}
.mk-ach--got .mk-ach__mark { color: var(--gold); box-shadow: 0 0 16px var(--gold); }
.mk-ach--got .mk-ach__bonus { color: var(--gold); }

/* Закрыта: НЕ ПРИГЛУШЕНА ПРОЗРАЧНОСТЬЮ. Первая версия гасила плитку целиком
   через opacity: 0.55 — и условие ачивки, ради которого список и показывают
   перед стартом, падало по контрасту с 7,55 до 3,08 : 1, то есть ниже AA.
   Разницу между взятой и невзятой несут цвет знака, цвет бонуса и знак «+»
   перед числом, а текст остаётся читаемым в обоих состояниях. */
.mk-ach--locked .mk-ach__name { color: var(--text-mid); font-weight: 500; }
.mk-ach--locked .mk-ach__mark { color: var(--text-dim); }
.mk-ach--locked .mk-ach__bonus { color: var(--text-dim); font-weight: 500; }

/* Только что взята: всплывает поверх поля на секунду. Держать её дольше
   нельзя — она закрывает узлы, а узлы в этот момент растут. */
.mk-ach--pop {
    border-color: var(--gold);
    box-shadow: var(--shadow-pop), 0 0 32px var(--gold);
    animation: mk-pop var(--dur-sm) var(--ease-spring) both;
}

/* ==========================================================================
   38 · Разбор очков
   Состояния: без аварий · с авариями · с ачивками · итог · в зачёт

   ПОЧЕМУ РАЗБОР ВООБЩЕ НУЖЕН. Игра считает в своей валюте (до 1000 сырых
   очков), а команде уходит не больше 100 — деление на десять с потолком
   (static/minigame/game-v2.js:1559). Игрок, набравший 640, видит на табло
   64 и имеет право не понять, куда делись остальные. Строка «в зачёт»
   показывает пересчёт прямо: сырые очки, дробь, результат.

   ЭТО ЖЕ ЕДИНСТВЕННОЕ МЕСТО, ГДЕ ОБЪЯВЛЯЮТ ЦЕНУ АВАРИИ. По ходу игры
   вычет виден только вспышкой; здесь он назван числом и множителем.
   ========================================================================== */

.mk-tally {
    display: flex;
    flex-direction: column;
    gap: var(--sp-1);
    font-family: var(--font-body);
}

.mk-tally__row {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: var(--sp-4);
    padding: var(--sp-2) 0;
    font-size: var(--fs-small);
    color: var(--text-mid);
}

.mk-tally__row b {
    font-family: var(--font-num);
    font-variant-numeric: tabular-nums;
    font-weight: 600;
    color: var(--text-hi);
}

.mk-tally__row--minus b { color: var(--err); }
.mk-tally__row--bonus b { color: var(--gold); }

.mk-tally__row--sum {
    margin-top: var(--sp-2);
    padding-top: var(--sp-3);
    border-top: 1px solid var(--glass-border);
    font-size: var(--fs-body);
    color: var(--text-hi);
}

/* «В зачёт» — то, что увидит зал на табло. Единственная строка разбора,
   набранная цветом эмоции: с неё игрок уходит к своей команде. */
.mk-tally__row--final b {
    font-size: var(--fs-h2);
    color: var(--emotion-soft);
}

.mk-tally__note {
    font-size: var(--fs-label);
    color: var(--text-dim);
}

/* ==========================================================================
   39 · Дверь
   Состояния: обычная · главная · закрыта (игра не идёт)

   ХАБ — ЭТО НЕ ВИТРИНА, А СВЯЗКА КЛЮЧЕЙ. Десять карточек внутренней
   навигации существуют затем, чтобы человек за минуту до игры нашёл
   свою дверь: зал, капитан, оператор. Поэтому у двери три части — РОЛЬ
   капс-лейблом, название и одна строка «что за ней», — и роль стоит первой:
   ищут не «редактор игр», ищут «где моё».

   ЗАКРЫТАЯ ДВЕРЬ ОСТАЁТСЯ ВИДИМОЙ. Когда игра не идёт, экран зала и вход
   капитана ведут в пустоту; прятать их нельзя — человек будет искать
   пропавшую ссылку. Дверь говорит, что закрыта, и почему.
   ========================================================================== */

.mk-door {
    position: relative;
    display: flex;
    flex-direction: column;
    gap: var(--sp-2);
    min-height: calc(var(--tap) * 3);
    padding: var(--sp-5);
    border-radius: var(--r-lg);
    background: var(--glass-bg);
    border: 1px solid var(--glass-border);
    color: var(--text-mid);
    font-family: var(--font-body);
    text-decoration: none;
    transition: transform var(--dur-sm) var(--ease-out),
                border-color var(--dur-sm) var(--ease-out),
                box-shadow var(--dur-sm) var(--ease-out);
}

.mk-door:hover {
    transform: translateY(-2px);
    border-color: var(--emotion-border);
    box-shadow: var(--glow-emotion-soft);
}

.mk-door__role {
    font-size: var(--fs-label);
    font-weight: 600;
    letter-spacing: var(--ls-label);
    text-transform: uppercase;
    color: var(--text-dim);
}

.mk-door__title {
    font-family: var(--font-display);
    font-weight: 700;
    font-size: var(--fs-h3);
    color: var(--text-hi);
    overflow-wrap: break-word;
}

.mk-door__desc { font-size: var(--fs-small); }

/* Знак двери: тот же штрих, что у ачивок и у знака продукта. */
.mk-door__mark {
    position: absolute;
    top: var(--sp-5);
    right: var(--sp-5);
    width: calc(var(--tap) * 0.6);
    height: calc(var(--tap) * 0.6);
    color: var(--emotion);
    opacity: 0.7;
}

.mk-door__mark svg { width: 100%; height: 100%; }

/* Главная дверь раздела: одна на группу. */
.mk-door--main {
    border-color: var(--emotion-border);
    background: var(--glass-bg-heavy);
    box-shadow: var(--glow-emotion-soft);
}

/* Закрыта: и здесь ПРОЗРАЧНОСТЬЮ НЕ ГАСИМ — по той же причине, что
   у ачивки 37. Строка «откроется, когда ведущий начнёт вечер» — это
   единственное, ради чего закрытую дверь вообще показывают, а opacity: 0.5
   уронила её контраст с 8,8 до 3,50 : 1. Закрытость несут плоский фон,
   приглушённый титул и отсутствие подъёма при наведении. */
.mk-door--off {
    background: var(--bg-1);
    pointer-events: none;
}

.mk-door--off .mk-door__title { color: var(--text-mid); }
.mk-door--off .mk-door__mark { color: var(--text-dim); opacity: 0.5; }
.mk-door--off .mk-door__desc { color: var(--text-dim); }

.mk-door__state {
    margin-top: auto;
    font-size: var(--fs-label);
    color: var(--warn);
}
