:root{
  --bg:#0d0f12; --panel:rgba(22,25,29,.86); --text:#eceff3; --muted:#9aa3ad;
  --accent:#ffc21c; --ok:#3ec46d; --bad:#e5453a; --line:rgba(255,255,255,.12);
  --display:"Rubik",system-ui,-apple-system,"Segoe UI",Roboto,sans-serif;
}
*{box-sizing:border-box}
html,body{margin:0;height:100%;background:var(--bg);color:var(--text);font-family:var(--display);overflow:hidden}
#app{position:fixed;inset:0;touch-action:none;user-select:none;-webkit-user-select:none}
/* поле имени — единственное место, где нужны и выделение текста, и обычные жесты */
#app input[type=text]{touch-action:auto;user-select:text;-webkit-user-select:text}
canvas#gl{position:absolute;inset:0;width:100%;height:100%;display:block}

/* ---------- HUD ---------- */
#hud{position:absolute;inset:0;pointer-events:none;padding:calc(10px + env(safe-area-inset-top)) 12px 0}
.hud-row{display:flex;gap:8px;justify-content:center;align-items:stretch}
.tile{background:var(--panel);border:1px solid var(--line);border-radius:12px;padding:6px 12px;
  min-width:84px;text-align:center;backdrop-filter:blur(6px)}
/* Поставить паузу без клавиатуры. #hud целиком прозрачен для указателя, поэтому нажатия ловит
   только сама кнопка. Размер — минимальные 44px по короткой стороне: это единственный
   элемент HUD, в который надо попадать пальцем на ходу. Снимает паузу не она, а «Продолжить»
   на экране паузы: тот лежит поверх HUD (.screen, z-index 5) и закрывает кнопку собой. */
.hud-btn{flex:0 0 46px;min-width:0;padding:0;color:var(--text);font-family:var(--display);
  display:flex;align-items:center;justify-content:center;gap:5px;
  pointer-events:auto;touch-action:manipulation;-webkit-tap-highlight-color:transparent}
.hud-btn[hidden]{display:none}
.hud-btn i{display:block;width:5px;height:17px;border-radius:2px;background:currentColor}
.hud-btn:active{background:var(--accent);color:#1a1200;border-color:var(--accent)}
/* На экранах от 320 px четвёртый элемент ряда рядом с полноразмерными плитками уже не
   помещается. Ужимаем плитки, а не кнопку: цифры читают глазами, а в кнопку попадают
   пальцем на ходу, и уменьшать надо не её. */
@media (max-width:359px){
  .hud-row{gap:6px}
  .tile:not(.hud-btn){min-width:62px;padding:6px 8px}
  .tile:not(.hud-btn) b{font-size:20px}
}
.tile b{display:block;font-size:22px;line-height:1.05;font-variant-numeric:tabular-nums}
/* nowrap: с четвёртым элементом в ряду плитка «до лидера» на 360 px стала уже своей подписи,
   и та переносилась на вторую строку — ряд вырастал в высоту на ровном месте. Подпись короче
   не сделать, поэтому ширину плитке задаёт она. */
.tile span{display:block;font-size:10px;color:var(--muted);letter-spacing:.04em;
  text-transform:uppercase;white-space:nowrap}
#hGapTile.warn{border-color:var(--accent);color:var(--accent)}
#hGapTile.bad{border-color:var(--bad);color:var(--bad)}
/* Счётчик до встречной. Появляется в двух случаях: игрок сам на встречной полосе (тогда это
   единственный способ оценить сближение под 60 м/с) и игрок ещё в своей, но впереди помеха для
   обгона — решение выезжать принимается именно там, до нажатия влево. В остальное время это
   шум, и блок скрыт. Что именно показано, решает world.makeHud, а не css. */
#hOnc{position:absolute;left:50%;transform:translateX(-50%);
  top:calc(86px + env(safe-area-inset-top));
  width:max-content;min-width:186px;padding:7px 14px 9px;
  background:rgba(22,25,29,.88);border:1px solid var(--line);border-radius:12px;
  backdrop-filter:blur(6px);text-align:center}
#hOnc[hidden]{display:none}
.onc-top{display:flex;align-items:baseline;justify-content:center;gap:6px}
.onc-top b{font-size:24px;line-height:1;font-variant-numeric:tabular-nums}
.onc-top span{font-size:10px;color:var(--muted);letter-spacing:.04em;text-transform:uppercase}
.onc-top span[hidden]{display:none}
/* «нет в зоне обзора» вместо числа — семнадцать знаков там, где стояло три. Кеглем 24 px они
   не помещаются даже в 320 px экрана, поэтому в этом состоянии надпись ужимается до 13 px и
   занимает строку целиком (подпись «с до встречной» при этом убрана — она про число секунд).
   Цвет остаётся основным: это не второстепенная подпись, а единственное, что тут сказано. */
#hOnc.clear .onc-top b{font-size:13px;font-weight:600;letter-spacing:.02em}
.onc-bar{margin-top:6px;height:4px;border-radius:2px;background:rgba(255,255,255,.12);overflow:hidden}
/* Спокойный цвет полоски — нейтральный серый, а не зелёный. Зелёный читался как разрешение
   обгона, которого счётчик не выдаёт: он меряет время до встречной, а не достаточность этого
   времени на обгон помехи (см. hud.js, oncoming). */
.onc-bar i{display:block;height:100%;width:0;background:var(--muted);transition:width .08s linear,background .15s}
#hOnc.warn{border-color:var(--accent)}
#hOnc.warn .onc-top b{color:var(--accent)}
#hOnc.bad{border-color:var(--bad);background:rgba(70,18,16,.90)}
#hOnc.bad .onc-top b{color:var(--bad)}
/* Счётчик и сообщение стоят на фиксированных местах, а не в потоке. В потоке появление
   счётчика (выезд на встречку) сдвигало сообщение вниз — то есть ровно в тот момент, когда
   читать его надо быстрее всего, оно прыгало. */
/* Предел ширины — поля по 8 px, а не 80%. На широком экране это ничего не меняет (самая
   длинная надпись — 295 px), а на 320 px прежние 80% давали 256 px, и «ТЫ ВПЕРЕДИ СВОЕГО
   МЕСТА» (266 px) переносилось на вторую строку: надпись становилась вдвое выше соседних
   и при смене состояния меняла размер на глазах. Обрезания не было и тогда — width:max-content
   под потолком просто переносит слова, — но одна строка у всех сообщений на всех трёх
   проверяемых ширинах читается ровнее. */
#hMsg{position:absolute;left:50%;transform:translateX(-50%);
  top:calc(150px + env(safe-area-inset-top));
  width:max-content;max-width:calc(100% - 16px);font-size:16px;font-weight:600;
  padding:7px 16px;border-radius:10px;opacity:0;transition:opacity .18s;text-align:center}
#hMsg.show{opacity:1}
/* «ТОРМОЗИ» — самая громкая надпись из всех, и цвет у неё выбран не «поярче», а по контрасту
   в обе стороны. Плашка лежит поверх картинки, которая днём почти белая (небо), а ночью почти
   чёрная, поэтому цвет проверяется на обоих пределах: белый текст на этой плашке даёт 5.5:1
   над белым фоном и 6.3:1 над чёрным — выше требуемых 4.5:1 и там, и там. Непрозрачность
   0.95, а не 0.88 как у соседей, ровно за этим: под ней не должно просвечивать ничего.
   Кегль тот же, что у остальных сообщений: надпись обязана оставаться в одну строку и не
   двигать соседей, а «громкость» набрана цветом и разрядкой. Проверяется всё это на живой
   странице (tests/hud_brake_scenario.js), а не на слово. */
#hMsg.brake{background:rgba(198,32,24,.95);color:#fff;letter-spacing:.10em}
#hMsg.chase{background:rgba(23,25,28,.82);color:#ffb257}
#hMsg.critical{background:rgba(120,20,20,.88);color:#fff}
#hMsg.close{background:rgba(90,60,10,.88);color:#ffe0a3}
#hMsg.gravel{background:rgba(72,60,36,.90);color:#e8d9a8}
/* «ГРАВИЙ ТОРМОЗИТ» — та же обочина, но когда сопротивление уже нарастает. Поэтому плашка та же
   по кеглю и месту, а громче она ровно тем же способом, что и «ТОРМОЗИ»: непрозрачность .95,
   белый текст и разрядка. Цвет считан, а не подобран: над белым фоном (небо днём) контраст
   5.4:1, над чёрным (ночь) — 6.5:1, оба выше требуемых 4.5:1. Оттенок оставлен гравийным
   (охра, а не красный) — красный в HUD занят «ТОРМОЗИ», и две одинаково красных плашки
   отличались бы только текстом. */
#hMsg.gravelhard{background:rgba(146,84,16,.95);color:#fff;letter-spacing:.06em}
/* Полоса отрыва от колонны — та же величина, что и в плитке «до лидера», поэтому стоит прямо
   под ней. Раньше она лежала по низу экрана, где её закрывали пальцы на кнопках газа и руля:
   единственный индикатор приближающегося проигрыша был фактически невидим на телефоне. */
#hBar{margin:8px auto 0;width:min(320px,82%);height:6px;border-radius:3px;
  background:rgba(255,255,255,.12);overflow:hidden}
#hBarFill{display:block;height:100%;width:0;background:var(--ok);transition:width .1s linear,background .2s}

/* ---------- экранные кнопки ---------- */
/* Размер кнопки считается из ширины экрана, а не задан числом. Прежние 78 px были константой,
   и на 320 px четыре кнопки с просветами требовали 348 px: газ уезжал за правый край экрана
   на 28 px и просто не нажимался (сжаться сами они не могли — min-width:auto у флекс-элемента
   не пускает .pad уже своего min-content). Формула ниже раздаёт остаток ширины поровну между
   четырьмя кнопками, вычтя из неё ровно то, что обязано остаться: поля по краям вместе с
   safe-area, просвет между рулём и педалями и просветы внутри пар.
   Нижняя граница clamp — 46 px, чуть больше требуемых 44×44; она срабатывает только когда
   ширины не хватает даже на это (узкий экран вместе с боковым вырезом). Верхняя — прежние
   78 px: на экранах шире ~366 px кнопки перестают расти, и лишняя ширина уходит в просвет
   между рулём и педалями (space-between), а не в разбухание кнопок.

   Просвет между парами задан ФОРМУЛОЙ, а не gap: у контейнера gap стоит маленький (8 px) и
   работает только страховкой «пары никогда не слипнутся». Разница видна ровно в углу, где
   clamp упёрся в 46 px: если бы 24 px были обязательным gap, сумма кнопок и просветов
   перестала бы помещаться в поля, и ряд вылез бы за safe-area вправо (space-between при
   переполнении прижимает содержимое к началу и переполняет конец). Так же, как есть, лишнее
   отдаёт просвет между рулём и педалями — он для этого и годится, в отличие от размера
   кнопки, у которой требование 44×44 жёсткое. */
#ctl{
  --ctl-edge-l:calc(8px + env(safe-area-inset-left));
  --ctl-edge-r:calc(8px + env(safe-area-inset-right));
  --ctl-split:24px;   /* просвет между рулём и педалями, зарезервированный в формуле */
  --ctl-gap:8px;      /* просвет внутри пары, он же страховочный минимум между парами */
  --btn:clamp(46px,
    (100vw - var(--ctl-edge-l) - var(--ctl-edge-r) - var(--ctl-split) - var(--ctl-gap) * 2) / 4,
    78px);
  position:absolute;inset:auto 0 0 0;display:flex;justify-content:space-between;align-items:flex-end;
  padding:0 var(--ctl-edge-r) calc(12px + env(safe-area-inset-bottom)) var(--ctl-edge-l);
  gap:var(--ctl-gap);pointer-events:none}
/* Без этой строки `#ctl.hidden = true` (меню, итог заезда) не прятал ничего: авторский
   display:flex по id перебивает браузерное [hidden]{display:none} — правила разных источников
   сравниваются по источнику, а не по весу селектора. Кнопки газа и руля так и оставались
   висеть под экранами, живые на вид и мёртвые на деле. То же лечение, что у #hOnc. */
#ctl[hidden]{display:none}
.pad{display:flex;gap:var(--ctl-gap);flex:0 0 auto;pointer-events:auto}
.btn{width:var(--btn);height:var(--btn);border-radius:16px;border:2px solid var(--line);
  background:rgba(35,38,43,.72);backdrop-filter:blur(6px);color:var(--text);
  font-family:var(--display);font-weight:600;display:flex;align-items:center;
  justify-content:center;white-space:nowrap;
  /* Подпись тоже привязана к размеру кнопки: «ТОРМОЗ» — 66 px при 15 px кегля и в кнопку
     66×66 (внутри 62 px за вычетом рамок) не помещается. Множитель подобран так, чтобы на
     широком телефоне кегль остался прежними 15 px, а на 320 px ужался до ~12.6 px (≈56 px
     надписи). Переносить нечего — white-space:nowrap выше: подпись либо влезает целиком,
     либо это видно как обрезание, и приёмка на него падает. */
  font-size:clamp(11px,calc(var(--btn) * .192),15px);
  touch-action:none;-webkit-tap-highlight-color:transparent}
/* Стрелки руля — один глиф шириной ~12 px, ему тесно не станет никогда, поэтому он не участвует
   в ужатии подписей: на узком экране руль должен читаться так же, как на широком. */
.btn[data-k="left"],.btn[data-k="right"]{font-size:20px}
.btn.on{background:var(--accent);color:#1a1200;border-color:var(--accent)}
.btn.gas.on{background:var(--ok);color:#03200e}
.btn.brk.on{background:var(--bad);color:#fff}
/* руль упёрся в край дороги — кнопка дрожит */
.btn.bump{animation:bump .26s ease-out}
@keyframes bump{
  0%{transform:translateX(0)}25%{transform:translateX(-5px)}
  55%{transform:translateX(4px)}80%{transform:translateX(-2px)}100%{transform:translateX(0)}}
@media (min-width:900px){#ctl{opacity:.35}}

/* ---------- обучающая остановка ---------- */
/* Плашка урока про первое «ТОРМОЗИ» (js/main.js, teachCheck). Три требования, и все три — здесь:
   — НЕ ловит указатель и не лежит поверх педалей: продолжение урока это сам тормоз, а не кнопка
     «понятно». pointer-events:none плюс место в разметке перед #ctl — нажатие по координатам
     педали достаётся педали;
   — НЕ замазывает то, ради чего заезд остановлен: затемнение лёгкое и без blur. Машину впереди
     игрок обязан увидеть своими глазами, иначе объяснять нечего;
   — стоит НИЖЕ этой машины и ВЫШЕ педалей. Место выбрано замером, а не на глаз: при первом
     «ТОРМОЗИ» помеха проецируется примерно на 52% высоты кадра (и на телефоне, и на десктопе —
     камера смотрит в одну и ту же точку дороги), поэтому плашка прижата к низу и оставляет над
     собой всю дорогу. Читается кадр сверху вниз: «ТОРМОЗИ» — машина — объяснение — педаль.
     Ровно это и проверяется на живой вёрстке (tests/teach_scenario.js): помеха проецируется
     штатной камерой игры и обязана оказаться ВНЕ плашки, на трёх ширинах.
   Отсюда же требование к размеру: плашка обязана оставаться низкой, поэтому в ней две строки
   текста и ни одной необязательной. */
#teach{position:absolute;inset:0;pointer-events:none;display:flex;
  align-items:flex-end;justify-content:center;
  padding:calc(190px + env(safe-area-inset-top)) 16px calc(102px + env(safe-area-inset-bottom));
  background:rgba(8,10,13,.40)}
#teach[hidden]{display:none}
.teach-card{width:min(340px,100%);background:rgba(22,25,29,.94);border:1px solid var(--accent);
  border-radius:14px;padding:11px 14px;text-align:center}
.teach-card b{display:block;font-size:16px;letter-spacing:.05em;color:var(--accent)}
.teach-card p{margin:7px 0 0;font-size:12.5px;line-height:1.40}
.teach-card i{font-style:normal;font-weight:700;color:#ff7a70}
/* Педаль, про которую урок. Пульсация медленная: кнопку надо найти глазами в первый раз в жизни,
   а не заметить вспышку. Рамка и кольцо — цвета тормоза, того же, которым кнопка загорается
   под пальцем (.btn.brk.on), чтобы подсветка и нажатие читались как одно и то же. */
.btn.brk.teach{border-color:var(--bad);animation:teachPulse 1.1s ease-in-out infinite}
@keyframes teachPulse{
  0%,100%{box-shadow:0 0 0 0 rgba(229,69,58,.60)}
  50%{box-shadow:0 0 0 11px rgba(229,69,58,0)}}

/* ---------- экраны ---------- */
.screen{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;
  background:rgba(8,10,13,.72);backdrop-filter:blur(8px);padding:16px;z-index:5}
.screen[hidden]{display:none}
.card{width:min(420px,100%);max-height:92vh;overflow:auto;background:var(--panel);
  border:1px solid var(--line);border-radius:18px;padding:20px}
.card h1{margin:0 0 6px;font-size:26px;letter-spacing:.03em}
.sub{margin:0 0 16px;color:var(--muted);font-size:13px;line-height:1.45}
.hint{color:var(--muted);font-size:11px;line-height:1.5;margin:12px 0 0}
.fld{display:block;font-size:13px;color:var(--muted);margin:0 0 14px}
.fld b{color:var(--text)}
.fld input[type=range]{width:100%;margin-top:6px;accent-color:var(--accent)}
.fld select{width:100%;margin-top:6px;padding:9px;border-radius:10px;background:#1b1f24;
  color:var(--text);border:1px solid var(--line);font-family:var(--display);font-size:13px}
.seg{display:flex;gap:6px;margin-top:6px}
.seg button{flex:1;padding:9px;border-radius:10px;border:1px solid var(--line);background:#1b1f24;
  color:var(--text);font-family:var(--display);font-size:13px}
.seg button.on{background:var(--accent);color:#1a1200;border-color:var(--accent)}
.go{width:100%;margin-top:6px;padding:13px;border-radius:12px;border:0;background:var(--accent);
  color:#1a1200;font-family:var(--display);font-size:16px;font-weight:700;letter-spacing:.04em}
.go.ghost{margin-top:8px;background:transparent;color:var(--text);border:1px solid var(--line)}
/* auto-fit, чтобы новая графа в итогах заезда не ломала сетку на телефоне */
/* Карточка проигрыша плотнее остальных экранов, и это вынужденно: к цифрам на ней добавились
   разбор последней опасности, совет и строка рекорда — три текстовых блока сверх прежнего. На
   320×568 в прежних отступах всё это уезжало под обрез вместе с кнопкой «ЕЩЁ РАЗ», а её жмут
   сразу и не глядя. Ужимаем поля и кнопки, а не кегль текста: читают здесь именно текст.
   Прокрутка у .card остаётся страховкой на случай очень длинного разбора, но в штатных исходах
   до неё доходить не должно — это проверяет tests/over_scenario.js. */
/* max-height:100% вместо общих 92vh — карточка вправе занять всю высоту за вычетом полей
   .screen. Четыре процента высоты экрана, которые общий предел оставляет ради «воздуха» над
   карточкой, здесь стоят как раз последней строки разбора. */
#over .card{padding:16px;max-height:100%}
#over .card h1{font-size:22px}
#over .sub{margin-bottom:10px}
#over .stats{margin:8px 0;gap:6px}
#over .go{padding:10px}
#over .go.ghost{margin-top:6px}
#over .hint{margin-top:8px}
/* Совет на карточке проигрыша. Единственная строка экрана, которая говорит игроку, что делать,
   — поэтому она акцентная и с левой линейкой: отличается от разбора выше не размером шрифта
   (места нет), а тем, что явно выделена в блок. Отступ слева, а не рамка вокруг: рамка на
   320 px съедает ещё две строки высоты. */
.tip{margin:0 0 4px;padding:8px 0 8px 10px;border-left:2px solid var(--accent);
  color:var(--text);font-size:12px;line-height:1.45}
.tip[hidden]{display:none}
/* Ярлык режима в заголовке карточки итога: «зачёт» или «тренировка». Мелкий и в строке с
   заголовком — он уточняет, чем был заезд, а не объявляет новость. Зачёт акцентный (это то,
   ради чего играют), тренировка приглушённая. */
.tag{display:inline-block;vertical-align:middle;margin-left:6px;padding:2px 7px;border-radius:999px;
  font-size:10px;font-style:normal;letter-spacing:.06em;text-transform:uppercase;
  background:var(--accent);color:#1a1200}
.tag:empty{display:none}
.tag.train{background:transparent;color:var(--muted);border:1px solid var(--line)}
/* Выключенные ползунки и кнопки сцены в зачёте: видно, что условия зафиксированы, а не просто
   стоят в этих значениях. Крупность и раскладка не меняются — карточка меню не должна ездить
   при переключении режима. */
/* Подсказка режима относится к переключателю над ней, а не к полю под ним: без нижнего отступа
   она прилипала к «Сцене» и читалась как её подпись. */
#modeHint{margin:8px 0 14px}
.fld input:disabled,.fld select:disabled{opacity:.45}
.seg button:disabled{opacity:.45}
.seg button.on:disabled{opacity:.7}
/* Разница с личным рекордом — подпись прямо под цифрами, к которым она относится. */
.rec{margin:-6px 0 0;color:var(--accent);font-size:12px;line-height:1.45}
.rec[hidden]{display:none}
.stats{display:grid;grid-template-columns:repeat(auto-fit,minmax(62px,1fr));gap:8px;
  margin:14px 0 14px;text-align:center}
.stats b{display:block;font-size:18px;font-variant-numeric:tabular-nums}
.stats span{display:block;font-size:10px;color:var(--muted);text-transform:uppercase}

/* ---------- таблица рекордов ---------- */
.fld input[type=text]{width:100%;margin-top:6px;padding:9px;border-radius:10px;background:#1b1f24;
  color:var(--text);border:1px solid var(--line);font-family:var(--display);font-size:14px}
.fld input[type=text]:focus{outline:0;border-color:var(--accent)}
.card.wide{width:min(560px,100%)}
/* Карточка таблицы — рама с одной прокручиваемой частью посередине. Прокручивается список (и
   справка под ним), а заголовок, переключатель, строка «твой лучший» и обе кнопки стоят на
   месте всегда. Раньше прокручивалась вся карточка целиком, и на 320×568 заполненный топ-10
   выдавливал под обрез и свой результат, и «НАЗАД» с «ОБНОВИТЬ»: игрок видел десять чужих строк
   и ни одной своей кнопки (tests/board_scenario.js, разделы 5 и 6).
   Сжиматься разрешено только списку — остальным частям flex:none, иначе flexbox ужал бы заодно
   и кнопки. max-height:100% вместо общих 92vh: на узком экране эти проценты стоят целой строки.
   Прокрутка самой .card оставлена как была — страховка на случай, когда даже рама не влезает
   (очень низкий экран в горизонтальной ориентации). */
#board .card{display:flex;flex-direction:column;max-height:100%}
#board .card > *{flex:none}
#board .card > .tablewrap{flex:0 1 auto;min-height:0;overflow:auto}
.tablewrap{overflow-x:auto;-webkit-overflow-scrolling:touch}
#bTable{width:100%;border-collapse:collapse;font-size:13px;font-variant-numeric:tabular-nums}
#bTable th{font-size:10px;color:var(--muted);text-transform:uppercase;letter-spacing:.04em;
  font-weight:600;text-align:left;padding:0 6px 6px;border-bottom:1px solid var(--line)}
#bTable td{padding:7px 6px;border-bottom:1px solid rgba(255,255,255,.06);white-space:nowrap}
#bTable .num{text-align:right}
#bTable .nm{max-width:150px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
/* Пометки «матч» у строки больше нет: матчи переехали в свою таблицу (#segBoard), и в одиночном
   зачёте чужих строк не бывает вовсе. Переключатель над таблицей стоит вплотную к заголовку —
   на 320 px каждая строка карточки на счету. */
#segBoard{margin:0 0 10px}
#bTable .strong{font-weight:700}
#bTable .dim{color:var(--muted)}
#bTable tr.me td{background:rgba(255,194,28,.10);color:var(--accent)}
#oSent{min-height:16px}
#oSent.bad{color:var(--bad)}
#oSent.ok{color:var(--ok)}

/* ---------- сетевой режим ---------- */
/* Строка матча стоит НАД сообщением HUD, а не под ним: сообщение живёт по фиксированной
   координате (см. #hMsg), и подпихивать его снизу нельзя — оно поедет ровно тогда, когда его
   надо читать быстрее всего. Здесь же — своя фиксированная строка, которая никого не двигает. */
/* Ширина ограничена, и строка переносится. На 320 px «3 в гонке» плюс «ты выбыл: отстал от
   колонны, 4 место» — это под 370 px, а прежний nowrap без ограничения ширины выпускал хвост
   за ОБА края кадра: единственное объяснение перехода в наблюдение читалось наполовину
   (GAME_REVIEW.md, 3.6 — там это названо риском, здесь оно снято съёмкой на 320×568).
   Наехать переносом на сообщение HUD (#hMsg, top 150px) эта строка не может: вторая строка в
   ней появляется только вместе с причиной выбывания, то есть у наблюдателя, — а у наблюдателя
   сообщений HUD нет вовсе, они собираются из приборов своей машины, которой уже нет
   (js/net/match.js, spectatorHud). У живого игрока #hRole пуст, и строка остаётся одной. */
#hNet{position:absolute;left:50%;transform:translateX(-50%);
  top:calc(122px + env(safe-area-inset-top));
  display:flex;gap:8px;align-items:baseline;flex-wrap:wrap;justify-content:center;
  max-width:calc(100vw - 24px);text-align:center;
  padding:4px 12px;border-radius:9px;background:rgba(23,25,28,.82);font-size:13px}
#hNet[hidden]{display:none}
#hNet b{font-size:15px;font-weight:700}
#hNet span{color:var(--muted);letter-spacing:.04em}
#hNet.out b{color:var(--bad)}

/* ---------- наблюдение после выбывания ---------- */
/* Панель занимает нижнюю полосу — ту самую, где у живого игрока педали, — и по тем же
   отступам от краёв и safe-area. Показываются они по очереди, никогда вместе (js/main.js,
   paintSpect): рулить наблюдателю нечем, и две пары кнопок в одном углу спорили бы за палец.
   Тянется только середина: имя бывает и в 24 знака, и его сжимает многоточие, а число метров
   не сжимается никогда — ради него панель и существует. */
#spect{position:absolute;inset:auto 0 0 0;display:flex;align-items:center;gap:8px;
  padding:0 calc(8px + env(safe-area-inset-right)) calc(12px + env(safe-area-inset-bottom))
    calc(8px + env(safe-area-inset-left));
  pointer-events:none}
#spect[hidden]{display:none}
/* Кнопка переключения уже педали и не считается по формуле --btn: педалей четыре в ряд, а
   этих две, и отдавать им те же 66 px на 320 px значит съесть половину строки с метрами.
   56 — с запасом над обязательными 44×44 для пальца. */
#spect .btn{flex:0 0 auto;width:56px;height:56px;pointer-events:auto}
/* На 320 px «135 м до потери колонны» в остаток строки не влезает и переносится последним
   словом. Ужимаем кнопки до 48 px — всё ещё выше обязательных 44×44, — и середине хватает.
   Ужимается кнопка, а не кегль: число читают глазами на ходу, а в кнопку попадают пальцем. */
@media (max-width:360px){#spect .btn{width:48px;height:48px}}
#spect .btn:disabled{opacity:.35}
.sp-mid{flex:1 1 auto;min-width:0;display:flex;flex-direction:column;align-items:center;gap:1px;
  padding:7px 10px;border-radius:12px;background:rgba(23,25,28,.86);text-align:center;
  border:1px solid var(--line)}
.sp-mid b{max-width:100%;font-size:15px;font-weight:700;
  overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.sp-mid span{font-size:12px;color:var(--muted);line-height:1.25}
.sp-mid u{text-decoration:none}
/* Само число — тем же цветом, что цифры HUD: читается оно первым, а «до потери колонны» —
   подпись к нему. Перенос внутри «128 м» запрещён неразрывным пробелом в самом значении
   (js/main.js, paintSpect): число и единица — одно слово, и разрывать их нельзя. */
.sp-mid i{font-style:normal;font-weight:700;font-size:14px;color:var(--text)}
.sp-mid.warn i{color:var(--accent)}
.sp-mid.bad i{color:var(--bad)}

/* Список игроков в комнате. Строка — имя слева, состояние справа; на 320 px она обязана
   оставаться одной строкой, поэтому имя сжимается, а состояние — нет. */
.plist{list-style:none;margin:10px 0 4px;padding:0;display:flex;flex-direction:column;gap:6px}
.plist li{display:flex;justify-content:space-between;align-items:center;gap:10px;
  padding:8px 12px;border-radius:10px;background:rgba(255,255,255,.05);font-size:15px}
.plist li.me{background:rgba(255,255,255,.12)}
.plist li i{font-style:normal;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.plist li u{text-decoration:none;color:var(--muted);font-size:13px;flex:none}
.plist li.ready u{color:var(--ok)}
.plist li.dead u{color:var(--bad)}
#lCode{letter-spacing:.22em}
