/* Вёрстка рассчитана на зеркалирование: крупные формы, жирный шрифт, высокий
   контраст — Miracast сжимает картинку и съедает тонкие линии.

   Два правила, которые отличают этот проект от обычной страницы:

   1. Верстаем во весь экран телефона (20:9), а не в кадр 16:9. Иначе получим
      поля 16:9 внутри зеркала 20:9 внутри экрана телевизора 16:9 — двойные
      полосы и сильно уменьшенную картинку. Всё важное при этом живёт внутри
      центральной области 16:9 (.safe), чтобы не уехать за край телевизора.
   2. Размер берётся от --vh, который считает js по visualViewport. Полный экран
      здесь косметика, а не несущая конструкция: вылезла строка состояния —
      приложение стало на несколько процентов меньше и продолжает работать. 

   Разбор: docs/prizes-and-cost.md, docs/ui-and-layout.md. */

/* ПАЛИТРА РАЗДЕЛЕНА НАДВОЕ, и это несущее различие, а не аккуратность.

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

   А всё, что лежит ПОВЕРХ живого коридора — HUD, пауза, результат, — светлым
   текстом на тёмном и осталось. Под ним три разные зоны, включая ночную, и
   тёмный текст в ней исчезает. Поэтому у «поверх коридора» свои токены
   (`--on-dark`, `--scrim`, `--track`), и брать для них `--fg` нельзя. */
:root {
  /* ── лист: светлое дневное ── */
  --bg: #c6e2f4;     /* светлое небо; НЕ sky первой зоны — тот темнее */
  --panel: #ffffff;
  --fg: #10243a;
  --dim: #41607a;
  --ink: #0b1a2a;    /* текст на цветной кнопке */
  --accent: #ff9f1c; /* главная кнопка */
  --grass: #3cab4f;  /* вторая кнопка */
  --slate: #2c4a63;  /* служебная кнопка, текст светлый */
  /* Состояния на ЛИСТЕ — тёмные, их читают на светлом фоне. */
  --ok-ink: #1f8f4d;
  --warn-ink: #b06400;
  /* Подложка кнопки: синеватая, а не чёрная — на светлом фоне чёрная тень
     читается грязью. */
  --press: rgba(16, 36, 58, 0.32);
  --me: rgba(255, 159, 28, 0.26); /* своя строка в таблице рекордов */

  /* ── поверх коридора ── */
  --on-dark: #f4f9ff;
  --on-dark-dim: #c3d2e4;
  /* Карточка поверх кадра. Полупрозрачная НАМЕРЕННО: за текстом результата
     продолжает рисоваться коридор, и зона обязана быть видна до конца забега
     (см. docs/ui-and-layout.md). Непрозрачная карточка отменила бы это решение, не сломав
     ни одного теста. */
  --scrim: rgba(10, 22, 38, 0.62);
  /* А те же состояния ПОВЕРХ коридора — яркие, и это не дубль ради симметрии:
     тёмная охра на закатном небе и в ночной зоне не читается вовсе, а
     «камера не работает» и «ты вышел из кадра» обязаны быть видны всегда. */
  --ok: #39d98a;
  --warn: #ffb02e;
  --bad: #ff5a5f;
  --track: #1b2237;  /* дорожка полосы прогресса и полосы поля */
  --zone: #23406b;
  --star: #ffd34d;   /* то же золото, что у колец в игре */
  --stage-run: #0b0d14; /* поля по бокам кадра 16:9 на экране 20:9 */

  --safe: 3.5%;      /* запас на overscan телевизора */
  --vh: 100dvh;      /* переопределяется из js по visualViewport */
  --u: 1vmin;        /* единица масштаба, пересчитывается туда же */
}

* { box-sizing: border-box; }
html, body { margin: 0; height: 100%; overflow: hidden; }
body {
  background: var(--bg);
  color: var(--fg);
  font: 700 calc(var(--u) * 3.4)/1.35 system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
  -webkit-text-size-adjust: 100%;
  -webkit-tap-highlight-color: transparent;
  user-select: none;
  overscroll-behavior: none;
}

[hidden] { display: none !important; }

#stage {
  position: fixed;
  inset: 0;
  height: var(--vh);
  display: grid;
  place-items: center;
  background: var(--bg);
}

/* Экраны телефона: меню, калитка, настройки. Их держат в руке и трогают
   пальцем, прежде чем поставить телефон на штатив, поэтому они вертикальные.
   На телевизор они тоже уходят — с полями по бокам, и это нормально: адресат
   этих экранов стоит рядом с телефоном, а не сидит на диване. */
.sheet {
  width: 100%;
  max-width: calc(var(--u) * 92);
  min-height: var(--vh);
  margin-inline: auto;
  padding: calc(var(--safe) * 1.6) var(--safe);
  display: flex;
  flex-direction: column;
  gap: calc(var(--u) * 3);

  /* Прокрутка — ТОЛЬКО здесь, и это развёрнутое решение.

     Раньше её не было нигде: `html, body` с `overflow: hidden`, чтобы
     случайный свайп не уводил содержимое из-под пальца. Но настроек для
     взрослого стало восемь пунктов плюс пять кнопок, и экран перестал
     влезать в телефон. А `.sheet` центрирует колонку по вертикали, то есть
     лишнее уезжает за ОБА края сразу, унося и заголовок, и кнопку
     «Закрыть», — вернуться после этого нечем вообще. Это хуже случайного
     свайпа, и дальше будет только хуже: пунктов прибавляется.

     Прокручивается сам лист, а не страница: `html, body` по-прежнему
     зафиксированы, и кадр забега (`.safe`) не прокручивается никогда —
     адресат того экрана сидит на диване, двигать там нечего. */
  max-height: var(--vh);
  overflow-y: auto;
  /* Докрутил до конца — тянуть дальше нечего: иначе Chrome отдаёт жест
     странице и показывает «потяни для обновления» посреди настроек. */
  overscroll-behavior: contain;

  /* Две строки подряд, и это не опечатка. Короткий экран обязан стоять по
     центру — так он и выглядел всегда. Но у `center` в прокручиваемом
     контейнере есть известная беда: содержимое, которое не влезло, уезжает за
     ВЕРХ, и доскроллить до него нельзя — браузер не пускает выше нулевой
     прокрутки. `safe center` ровно это и лечит: пока влезает — по центру, не
     влезает — от верха. Браузер, который `safe` не знает, declaration
     отбросит и останется с первой строкой, то есть с сегодняшним поведением. */
  justify-content: center;
  justify-content: safe center;
}
/* В колонке кнопки занимают всю ширину: промахнуться пальцем мимо кнопки во
   весь экран невозможно, а это главное требование к экрану, который держат.
   width и align-self здесь обязательны: на этих же экранах стоит .center,
   который выравнивает содержимое по центру, и без них строка сжимается по
   кнопке, а кнопка остаётся узкой. */
.sheet .row {
  flex-direction: column;
  align-items: stretch;
  align-self: stretch;
  width: 100%;
}
.sheet button { width: 100%; min-width: 0; }

/* Центральная область 16:9 — всё, что обязано быть видно на телевизоре.
   Это кадр забега, а не меню. */
.safe {
  position: relative;
  width: 100%;
  max-width: calc(var(--vh) * 16 / 9);
  aspect-ratio: 16 / 9;
  max-height: var(--vh);
  padding: var(--safe);
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: calc(var(--u) * 2.4);
}

/* ─── типографика ─── */

h1 {
  margin: 0;
  font-size: calc(var(--u) * 9);
  line-height: 1;
  letter-spacing: -0.01em;
  text-shadow: 0 calc(var(--u) * 0.6) 0 var(--press);
}
h2 { margin: 0; font-size: calc(var(--u) * 4.4); }

/* Талисман на калитке и в меню. Размер в `--u`, как и всё остальное: иначе он
   перестанет масштабироваться между телефоном и зеркалом на телевизоре. */
.mascot {
  display: block;
  width: calc(var(--u) * 30);
  height: calc(var(--u) * 30);
  margin-inline: auto;
  flex: none;
}
.mascot.small { width: calc(var(--u) * 18); height: calc(var(--u) * 18); }
p { margin: 0; }
.dim { color: var(--dim); }
.lead { font-size: calc(var(--u) * 3.6); font-weight: 400; color: var(--dim); }

/* ─── кнопки ─── */

.row { display: flex; flex-wrap: wrap; gap: calc(var(--u) * 2.2); align-items: center; }
.row.center { justify-content: center; }

button {
  font: inherit;
  font-size: calc(var(--u) * 4);
  color: var(--ink);
  background: var(--accent);
  border: 0;
  border-radius: calc(var(--u) * 2.6);
  padding: calc(var(--u) * 2.6) calc(var(--u) * 4.4);
  min-width: calc(var(--u) * 34);
  cursor: pointer;
  text-align: center;
  /* Тонкая рамка исчезла бы при сжатии, поэтому различаем кнопки заливкой. */
  box-shadow: 0 calc(var(--u) * 0.8) 0 var(--press);
}
button:active { transform: translateY(calc(var(--u) * 0.4)); box-shadow: none; }
button:disabled { opacity: 0.45; }
/* `ghost` больше НЕ прозрачная, и это исправление по собственному же правилу
   проекта, записанному двумя абзацами ниже: прозрачная кнопка с тонким
   контуром на телевизоре после сжатия выглядит не как кнопка, а как пустое
   место — контур Miracast съедает первым. В главном меню таких было три из
   пяти, то есть большинство кнопок читалось пустотой.

   Имя класса оставлено: оно стоит в разметке десятка экранов, и переименование
   дало бы диff на сотню строк ради слова. Значит `ghost` теперь — белая
   заливка, самая светлая из четырёх. */
button.ghost {
  background: var(--panel);
  color: var(--fg);
  box-shadow: 0 calc(var(--u) * 0.8) 0 var(--press);
}

/* Вторая по важности кнопка: трава. Отличается от главной и тоном, и
   светлотой — цветность режется сильнее светлоты, поэтому одного тона мало. */
button.grass {
  background: var(--grass);
  color: var(--ink);
  box-shadow: 0 calc(var(--u) * 0.8) 0 var(--press);
}

/* Второстепенная кнопка — заливкой, а не рамкой. Прозрачная с тонким
   контуром на телевизоре после сжатия выглядит не как кнопка, а как
   пустое место: контур Miracast съедает первым. Отличается от главной
   светлотой, а не только тоном — цветность режется сильнее светлоты. */
button.secondary {
  background: var(--slate);
  color: var(--on-dark);
  box-shadow: 0 calc(var(--u) * 0.8) 0 var(--press);
}
/* Мелкая кнопка: для того, что нужно взрослому и не должно спорить за
   внимание с «Тренировкой» и «Игрой». */
button.small {
  font-size: calc(var(--u) * 3);
  padding: calc(var(--u) * 1.8) calc(var(--u) * 3);
  min-width: calc(var(--u) * 24);
  opacity: 0.75;
}

button.big {
  font-size: calc(var(--u) * 5.4);
  padding: calc(var(--u) * 4) calc(var(--u) * 5);
  min-width: calc(var(--u) * 46);
  display: flex;
  flex-direction: column;
  gap: calc(var(--u) * 1);
  align-items: center;
}
button.big small {
  font-size: calc(var(--u) * 2.6);
  font-weight: 400;
  opacity: 0.75;
}

/* ─── экраны ─── */

.screen { display: flex; flex-direction: column; gap: calc(var(--u) * 2.6); }
.screen.center { align-items: center; text-align: center; }

/* Карточка. На тёмном фоне она отличалась от него сама; на светлом белое по
   светло-голубому даёт отношение светлот 1.35, и после сжатия Miracast край
   карточки теряется. Поэтому к заливке добавлена подложка — тем же приёмом,
   что у кнопок, и той же переменной: тонкой рамке тут веры нет. */
.card {
  background: var(--panel);
  border-radius: calc(var(--u) * 3);
  padding: calc(var(--u) * 3);
  box-shadow: 0 calc(var(--u) * 0.6) 0 var(--press);
}
.card.warn { box-shadow: inset 0 0 0 calc(var(--u) * 0.5) var(--warn-ink); }

/* Единственное текстовое поле в приложении — имя игрока.

   Образца для него не было: до сих пор весь ввод делался кнопкой, которая
   перебирает конечный список вариантов. Поэтому размеры взяты у кнопки, а не
   у браузерного умолчания: поле на телефоне трогают пальцем, и мелкое
   системное поле на этих экранах выглядело бы случайной деталью с другого
   приложения.

   font-size не ниже 16 px по требованию Safari и Chrome для Android: при
   меньшем размере браузер при фокусе зумит страницу целиком, и верстка
   экрана уезжает вместе с клавиатурой. var(--u) * 4 на телефоне заметно
   больше, так что это запас, а не ограничение. */
.field {
  width: 100%;
  box-sizing: border-box;
  font: inherit;
  font-size: max(16px, calc(var(--u) * 4));
  color: var(--fg);
  background: var(--bg);
  border: 0;
  box-shadow: inset 0 0 0 calc(var(--u) * 0.5) var(--dim);
  border-radius: calc(var(--u) * 2.2);
  padding: calc(var(--u) * 2.4) calc(var(--u) * 3);
  text-align: center;
}
.field:focus { outline: 0; box-shadow: inset 0 0 0 calc(var(--u) * 0.7) var(--accent); }
.field::placeholder { color: var(--dim); }

/* Строка таблицы рекордов. Своей заливкой, а не рамкой и не полосатым фоном:
   рамка после сжатия Miracast исчезает первой. Текущий игрок выделен — это
   единственное, что в таблице надо найти глазами сразу. */
.rec {
  display: flex;
  gap: calc(var(--u) * 2);
  align-items: baseline;
  padding: calc(var(--u) * 1.6) calc(var(--u) * 2);
  border-radius: calc(var(--u) * 1.6);
}
.rec + .rec { margin-top: calc(var(--u) * 1.2); }
.rec.more { color: var(--dim); justify-content: center; padding-block: 0; }

/* Второй рубеж против списка длиннее экрана.

   Первый — в коде: таблица показывает десятку. Этот стоял потому, что
   прокрутки в приложении не было нигде, и длинный список уезжал за ОБА края
   сразу, унося и заголовок, и кнопку «Назад»; вернуться после этого было
   нечем. Теперь лист прокручивается (см. `.sheet`), и смертельным этот случай
   быть перестал — но рубеж остаётся, и уже по другой причине: кнопка «Назад»
   обязана быть видна БЕЗ прокрутки. Иначе выход из экрана надо сначала
   найти, а список игроков кодом не ограничен вовсе — ограничивать его числом
   неправильно, игрока надо выбрать, а не посмотреть.

   Потолок считается от --vh, а НЕ от --u. Это та же ловушка, на которой
   рубеж в первый раз и не сработал: --u привязана к меньшей стороне
   доступной области, и на широком окне «120 единиц» оказываются выше всего
   экрана, то есть ограничением быть перестают. Половина высоты оставляет
   место заголовку, переключателю длины и кнопке «Назад» — тому, ради чего
   рубеж и стоит. */
#recList, #playerList { max-height: calc(var(--vh) * 0.5); overflow-y: auto; }
/* Своя строка подсвечена `--me` — это `--accent` с прозрачностью. Отдельной
   переменной, а не числом внутри правила: подмешать альфу к hex в CSS нечем,
   кроме color-mix, зато рядом с `--accent` её видно и меняют вместе. */
.rec.me { background: var(--me); }
.rec .place { color: var(--dim); min-width: calc(var(--u) * 5); }
.rec .who { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* Число рекорда — тёмной охрой, а не `--accent`. Оранжевая заливка кнопки
   работает как ЗАЛИВКА, но тем же цветом по белой карточке текст даёт
   отношение 2.05 — для цифры, ради которой таблицу и открывают, этого мало. */
.rec .num { color: var(--warn-ink); }
.rec .when { color: var(--dim); font-size: calc(var(--u) * 2.6); }

/* Полоса состояния для взрослого: установлено ли приложение, держится ли
   экран. Ребёнку она не нужна, поэтому мелкая и в углу. */
#status {
  position: absolute;
  left: var(--safe);
  bottom: calc(var(--safe) * 0.4);
  font-size: calc(var(--u) * 2.1);
  font-weight: 400;
  color: var(--dim);
  display: flex;
  gap: calc(var(--u) * 2);
}
#status b { font-weight: 700; }
#status .no { color: var(--warn-ink); }
#status .ver { opacity: 0.65; }
#status .yes { color: var(--ok-ink); }

/* Невидимая зона в углу: долгое удержание открывает родительское меню.
   Ребёнок на неё не наткнётся — там нет ничего, что просит нажатия. */
#parentCorner {
  position: absolute;
  right: 0;
  top: 0;
  width: calc(var(--u) * 14);
  height: calc(var(--u) * 14);
}

/* ─── экран забега ─── */

/* Канвас занимает кадр целиком. Его внутренний буфер фиксирован (1280×720) и
   не зависит от DPR — растягивает его браузер, и это дешевле, чем рисовать в
   высоком разрешении ради картинки, которую Miracast всё равно пережмёт. */
#run { padding: 0; }
#runCanvas {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  display: block;
  background: var(--bg);
}

/* HUD держится внутри запаса на overscan: у телевизора края обрезаются. */
#runHud {
  position: absolute;
  inset: var(--safe);
  /* Полоса прогресса живёт отдельной строкой сверху, поэтому содержимому
     нужен отступ: иначе счёт налезет на неё. */
  padding-top: calc(var(--u) * 2.6);
  display: flex;
  justify-content: space-between;
  align-items: flex-start;
  pointer-events: none;
  font-size: calc(var(--u) * 6);
  /* Свой цвет, а не унаследованный от body: `--fg` на листе теперь тёмный, а
     HUD лежит поверх живого коридора, и в ночной зоне тёмный текст исчезает. */
  color: var(--on-dark);
  text-shadow: 0 calc(var(--u) * 0.5) 0 rgba(0, 0, 0, 0.6);
}
#runScore::before { content: '★ '; color: var(--star); }

/* Полоса до финиша. Ребёнку обещан конец забега, и он должен быть виден:
   «сколько ещё» — первый вопрос, который задают на третьей минуте.

   Но стояла она посреди HUD, между счётом и «вижу тебя», то есть ровно в поле
   зрения — и мешала. Теперь это тонкая линия по верхнему краю кадра: видно,
   когда ищешь глазами, и не лезет, когда смотришь на коридор. */
#runProgress {
  position: absolute;
  left: 0;
  right: 0;
  top: 0;
  height: calc(var(--u) * 0.9);
  border-radius: calc(var(--u) * 0.45);
  background: var(--track);
  overflow: hidden;
}
#runProgressFill {
  height: 100%;
  width: 0%;
  background: var(--star);
  border-radius: inherit;
}
#runSeen { font-size: calc(var(--u) * 2.6); font-weight: 400; color: var(--on-dark-dim); }
#runSeen.lost { color: var(--warn); }

/* Окошко камеры всегда в углу и никогда посередине.

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

   Якорь один на все стадии, меняется только размер: в забеге окошко самое
   маленькое. Слева внизу — справа живёт «Выйти», а сверху счёт и полоса до
   финиша. */
#runPreview {
  position: absolute;
  left: var(--safe);
  bottom: calc(var(--safe) * 4.6);
  width: 26%;
  aspect-ratio: 16 / 9;
  border-radius: calc(var(--u) * 1.6);
  overflow: hidden;
  background: #000;
  /* Контур окошка лежит поверх коридора, поэтому светлый: `--dim` листа
     теперь тёмный и на тёмной зоне растворился бы. */
  box-shadow: 0 0 0 calc(var(--u) * 0.5) var(--on-dark-dim);
}

/* На калибровке окошко НЕ уменьшается, и это решение развёрнуто намеренно.
   Раньше оно уступало место подсказкам — «уже сделало свою работу». Теперь
   подсказка живёт внутри него: зона-цель показывает, куда шагнуть и насколько
   присесть, а словами этого семилетке через комнату не сказать. Значит
   окошко — не зрелище, а орган управления, и на калибровке оно того же
   размера, что на установке штатива. */
/* Зеркалим: камера смотрит на ребёнка спереди, и без зеркала его шаг влево
   уезжал бы на окошке вправо. Окошко, поставленное ради понимания, добавляло
   бы путаницы. Скелет зеркалится в коде, тем же способом. */
#runVideo {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
  transform: scaleX(-1);
}
#runSkeleton { position: absolute; inset: 0; width: 100%; height: 100%; }

/* Во время игры окошко маленькое и в углу: оно подсказка, а не зрелище. */
/* Во время забега — самое маленькое: это подсказка, а не зрелище, и смотреть
   ребёнок должен на коридор. */
#runPreview.watch {
  width: 14%;
  opacity: 0.85;
}

/* ─── полоса игрового поля ─── */

#runField {
  position: absolute;
  left: var(--safe);
  bottom: calc(var(--safe) * 1.4);
  width: 17%;
  height: calc(var(--u) * 3.4);
  border-radius: calc(var(--u) * 1.7);
  background: var(--track);
  box-shadow: inset 0 0 0 calc(var(--u) * 0.35) var(--on-dark-dim);
  overflow: hidden;
}
/* Безопасная зона — та часть кадра, где игра уверенно видит ребёнка. */
#runField .zone {
  position: absolute;
  left: 8%;
  right: 8%;
  top: 0;
  bottom: 0;
  background: var(--zone);
}
#runFieldMark {
  position: absolute;
  top: 50%;
  width: calc(var(--u) * 2.6);
  height: calc(var(--u) * 2.6);
  margin-left: calc(var(--u) * -1.3);
  margin-top: calc(var(--u) * -1.3);
  border-radius: 50%;
  background: var(--ok);
}
#runField.near #runFieldMark { background: var(--warn); }
#runField.lost { box-shadow: inset 0 0 0 calc(var(--u) * 0.5) var(--warn); }
#runField.lost #runFieldMark { background: var(--bad); }

/* Силуэт-цель: куда встать ребёнку. Пунктир пережил бы сжатие хуже сплошной
   рамки, поэтому рамка сплошная и толстая. */
#runSilhouette {
  position: absolute;
  left: 50%;
  top: 8%;
  width: 26%;
  height: 84%;
  transform: translateX(-50%);
  border: calc(var(--u) * 0.7) solid var(--ok);
  border-radius: calc(var(--u) * 1.5);
  opacity: 0.8;
}
#runSilhouette.bad { border-color: var(--warn); }

/* Числа для взрослого при наводке: что дала камера и как стоит ребёнок. */
#runNumbers {
  display: flex;
  gap: calc(var(--u) * 3);
  flex-wrap: wrap;
  justify-content: center;
  font-size: calc(var(--u) * 2.8);
  font-weight: 400;
  color: var(--on-dark-dim);
}
#runNumbers b { display: block; font-size: calc(var(--u) * 4); color: var(--on-dark); font-weight: 700; }
#runNumbers .bad b { color: var(--warn); }
#runNumbers .good b { color: var(--ok); }

/* Подсказка на паузе. Крупно: её читают с дивана, и читает её ребёнок. */
#runOverlay {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  gap: calc(var(--u) * 2);
  align-items: center;
  justify-content: center;
  text-align: center;
  padding: var(--safe);
}
/* На установке штатива середина кадра занята предпросмотром, поэтому
   подсказка уезжает вверх, а числа — вниз. */
#runOverlay.setup { justify-content: space-between; }
/* Числа на установке сдвинуты правее окошка: иначе они оказываются под ним и
   читаются хуже всего именно тогда, когда по ним и наводят штатив. */
#runOverlay.setup #runNumbers { padding-left: 30%; }
#runOverlay.setup #runOverlayTitle { font-size: calc(var(--u) * 6); }

#runOverlayTitle { font-size: calc(var(--u) * 11); line-height: 1; color: var(--on-dark); }
#runOverlayText { font-size: calc(var(--u) * 4); font-weight: 400; color: var(--on-dark-dim); }

/* Подложка под текстом паузы и результата. Стоит на СОДЕРЖИМОМ, а не на всём
   `#runOverlay`: оверлей занимает кадр целиком, и залив его, мы спрятали бы
   коридор — а он обязан быть виден до конца забега, иначе зона на финише
   перестаёт держаться (это уже чинили). Полупрозрачная по той же причине. */
#runOverlayTitle, #runOverlayText, #runResult {
  background: var(--scrim);
  border-radius: calc(var(--u) * 2.4);
  padding: calc(var(--u) * 1.4) calc(var(--u) * 3.4);
}
/* Пустая строка подложки не получает: на калибровке до первой достоверной позы
   подсказка пуста, и под заголовком висела бы тёмная плашка в две строки
   высотой — на пустом месте и без причины. */
#runOverlayTitle:empty, #runOverlayText:empty, #runResult:empty {
  background: none;
  padding: 0;
}

/* Результат забега. Крупно: его читают с дивана, и читает его ребёнок. */
#runResult {
  display: flex;
  gap: calc(var(--u) * 6);
  justify-content: center;
  font-size: calc(var(--u) * 3);
  font-weight: 400;
  color: var(--on-dark-dim);
}
#runResult b { display: block; font-size: calc(var(--u) * 9); color: var(--on-dark); font-weight: 700; }
#runResult .stars b { color: var(--star); }

/* Полоса состояния адресована взрослому при настройке. Во время забега она на
   телевизоре — мусор поверх игры, да ещё и в том углу, где и так есть HUD. */
body:has(#run:not([hidden])) #status { display: none; }

/* Кадр забега — коробка 16:9, а экран телефона 20:9, и на телевизоре по бокам
   остаются поля. В них просвечивал декоративный коридор с экрана меню, и
   выглядело это как остатки прошлого экрана. Во время забега фон гасим до
   цвета неба — того же, которым закрашен кадр, — и поля перестают читаться
   как поля. */
body:has(#run:not([hidden])) .corridor { display: none; }
body:has(#run:not([hidden])) #stage { background: var(--stage-run); }

/* Подсказка о повороте. Показывается только когда экран действительно
   вертикальный, поэтому ей всё равно, сработала блокировка ориентации или
   нет — а она на Android срабатывает не всегда. */
#runRotate { display: none; }
@media (orientation: portrait) {
  body:has(#run:not([hidden])) #runRotate {
    /* fixed, а не absolute: кадр забега — коробка 16:9, и в вертикальном
       экране она занимает лишь полосу посередине. Подсказка, накрывающая
       только её, оставляет коридор торчать сверху и снизу и выглядит
       сломанной ровно там, где должна объяснять. */
    position: fixed;
    inset: 0;
    z-index: 5;
    display: flex;
    flex-direction: column;
    gap: calc(var(--u) * 3);
    align-items: center;
    justify-content: center;
    text-align: center;
    padding: var(--safe);
    background: var(--bg);
  }
}
#runRotateIcon { font-size: calc(var(--u) * 18); line-height: 1; color: var(--fg); }
#runRotateText { font-size: calc(var(--u) * 6); }
#runRotateHint { font-size: calc(var(--u) * 3.4); font-weight: 400; color: var(--dim); }

/* Кнопка паузы — единственная кнопка, которая живёт ПОВЕРХ живого коридора.
   Значит и цвета у неё оттуда же: белая заливка листа была бы в кадре вторым
   по светлоте пятном после проёма и спорила бы с ним за «иди сюда». */
#runExit {
  background: var(--scrim);
  color: var(--on-dark);
  position: absolute;
  right: var(--safe);
  bottom: var(--safe);
  min-width: 0;
  font-size: calc(var(--u) * 2.6);
  padding: calc(var(--u) * 1.4) calc(var(--u) * 2.6);
  opacity: 0.5;
}

/* Фон меню: уходящая вдаль дорожка. Чистый CSS, ничего не грузит.
   Треугольником, а не коническим градиентом: у клипа геометрия задана тремя
   точками и предсказуема, а градиент приходится подбирать углами.

   Был синим пятном на почти чёрном — единственным цветом на экране. Стал
   травяным клином на светлом небе: то же небо и та же трава, что в первой
   зоне забега, поэтому меню и игра читаются как одно место.

   Непрозрачность подбиралась глазами и дважды: 0.18 на светлом фоне не видно
   вовсе, а 0.5 превращает клин в зелёную пирамиду во весь экран, которая
   спорит и с заголовком, и с талисманом. 0.28 — дорожка, а не фигура. */
.corridor {
  position: absolute;
  inset: 0;
  z-index: -1;
  opacity: 0.28;
  background: var(--grass);
  clip-path: polygon(50% 16%, 108% 112%, -8% 112%);
}

@media (prefers-reduced-motion: reduce) {
  button:active { transform: none; }
}

/* Кубок. Высота от --vh, а не от --u: --u привязана к меньшей стороне, и на
   широком экране «сто двадцать единиц» оказываются выше всего окна — ровно
   так уже не сработал потолок у списка рекордов. Кнопка «Назад» обязана быть
   видна БЕЗ прокрутки. */
.cup { width: auto; height: calc(var(--vh) * 0.46); display: block; margin: 0 auto; }
/* Запертая полоса — приглушённой заливкой, а не контуром: контур Miracast
   съедает первым, и полоса читалась бы пустотой.

   Заливку ВЗЯТОЙ полосы ставит код, и ставит её инлайновым стилем: правило
   ниже перебило бы презентационный атрибут SVG, у которого специфичность
   нулевая. Проверено в браузере — полосы оставались серыми при любой копилке,
   а ножка и подставка, которых код не трогает, рисовались верно. */
.cup rect[data-lv] { fill: var(--dim); }

/* Список выбора: четыре мира или пять уровней сложности столбиком, кнопка
   «Назад» под ними и без прокрутки. Потолок от --vh, как у списков рекордов и
   игроков, и по той же причине.

   Класс ОБЩИЙ у обоих списков (`#worldList`, `#levelList`), а не скопированный
   блок правил на каждый: копия этих чисел — потолка, двустрочной кнопки и
   контраста сноски 6.61:1 — разъехалась бы с оригиналом при первой правке.
   Это тот же «самый частый дефект проекта», и заводится он легче всего
   копипастой похожего экрана. */
.picker { display: flex; flex-direction: column; gap: calc(var(--u) * 2.2);
          max-height: calc(var(--vh) * 0.5); overflow-y: auto; }
/* Название и сноска — ДВЕ строки, а не одна. Своего правила у них не было, и
   `<small>` вставал в строку: «Подводная долинаещё 60 колец». Образец
   `button > small` в проекте есть, но живёт он у `.big`, а кнопки списка
   собираются кодом и этого класса не носят. */
.picker button { display: flex; flex-direction: column; align-items: center;
                 gap: calc(var(--u) * 0.8); }
/* Без `opacity`: контраст сноски на запертой кнопке считается тестом точно,
   а прозрачность увела бы его мимо расчёта. Отличается она размером и весом. */
.picker button small { font-size: calc(var(--u) * 2.6); font-weight: 400; }

/* Запертый мир гаснет, но остаётся читаемым: ребёнок должен видеть, за что
   бежит. Заливкой, не контуром — контур Miracast съедает первым.

   У списка сложностей запертых кнопок нет вовсе: все пять открыты сразу.
   Запереть сложность значило бы вернуть машину, решающую за ребёнка, — ровно
   то, что правка 18 октября и убирает, — и вдобавок сделать платным путь
   ВНИЗ, а он обязан быть открыт всегда: «слишком трудно» в этом проекте
   дороже, чем «слишком легко». */
/* Цвет текста — `--panel`, а не `--fg`: на `--dim` первый даёт 6.61:1, а
   второй 2.38:1 при 8.56:1 у соседних кнопок того же списка. «Ещё N колец» —
   единственная строка, объясняющая ребёнку, зачем бежать дальше, и читать её
   он будет с четырёх метров после того, как Miracast съел цветность. */
.picker button[disabled] { background: var(--dim); color: var(--panel); opacity: 1; }
/* Выбранный помечен заливкой, а не галочкой — по той же причине. */
.picker button.chosen { background: var(--ok); }

/* Золотой кубок — приз вершины (1200 колец). Обводка, а не заливка: полосы
   сообщают рост светлотой, и сплошное золото эту лестницу перебило бы.

   Цвет ТЁМНЫЙ, хотя приз называется золотым, и это не ошибка. Полосы идут
   лестницей светлоты с шагом 0.056–0.089, значит цвет, читаемый на ВСЕХ
   двенадцати, обязан лежать вне лестницы: темнее нижней или светлее верхней.
   Светлое золото `#ffd77a` (светлота 0.713) было ровно серединой — на
   полосах 9 и 10 оно давало 0.044 и 0.024 и после Miracast не читалось
   вовсе, то есть приз показывался бы только на нижних полосах, а смотрят на
   верхние: они новые. Поймано ревью, и теперь это сторожит проверка по
   каждой полосе.

   Толщина в единицах viewBox, а не в пикселях: чаша масштабируется под экран.
   На 720p это около 2.6 пикселя — чуть выше того порога в 2.5, который проект
   установил для ободка кольца. */
.cup.gold rect[data-lv] { stroke: #140d02; stroke-width: 1.2; }
