/*
 * ЕДИНЫЙ ИСТОЧНИК ПРАВДЫ для стекла и цветовой темы UnitVR.
 * Все компоненты используют ТОЛЬКО эти CSS-переменные — никогда не хардкодят свои
 * rgba/blur/box-shadow значения локально. Правка стиля стекла = правка ЭТОГО файла,
 * изменение расходится по всему приложению автоматически (прямое требование Михаила, С151).
 *
 * История: рецепт менялся дважды за ночь С151 (см. reference-glass-canon-2026 в памяти
 * агента) — сначала окантовка/просвечивание были неверными, затем плотность 0.42 давала
 * либо тусклость, либо жёсткую окантовку при попытке компенсировать. Текущие значения —
 * финальные, подтверждённые Михаилом визуально на попапе поддержки.
 */

:root[data-theme="dark"] {
  --fg: #eceef2;
  --fg-dim: #a8abbc;
  --fg-mute: #7c8096;

  /* С157 (Михаил, живой тест в SceneTunerModal): 4 угла должны быть ОДИНАКОВО светлыми
     выбранным цветом, затемнение только к центру - раньше углы имели разную прозрачность
     (.32/.24/.20/.20), выглядело как случайно разбросанный градиент, а не рамка.
     С188: цвет подобран Михаилом живьём через SceneTunerModal (🎨 Настройка фона) и снят из
     scene-color-log.jsonl на сервере (club_id 2, saved_at 2026-08-19T14:49:44.910Z) - взят
     как дефолт для ВСЕХ клубов. Сама кнопка настройки теперь скрыта (см. App.jsx,
     SCENE_TUNER_VISIBLE), но код тюнера не удалён - если понадобится подобрать другой цвет,
     достаточно вернуть флаг в true. */
  --scene:
    radial-gradient(circle at 78% -8%, rgba(139, 81, 48, .26), transparent 46%),
    radial-gradient(circle at 15% 108%, rgba(139, 81, 48, .26), transparent 46%),
    radial-gradient(circle at 15% -8%, rgba(139, 81, 48, .26), transparent 46%),
    radial-gradient(circle at 78% 108%, rgba(139, 81, 48, .26), transparent 46%),
    radial-gradient(circle at 50% 50%, rgba(0, 0, 0, .22), transparent 60%),
    linear-gradient(160deg, #1c1408 0%, #0d0e14 55%, #08090e 100%);

  /* ===== ГЛАВНЫЕ ТОКЕНЫ СТЕКЛА (менять ТОЛЬКО здесь) ===== */
  --glass-bg: rgba(18, 18, 21, .72);
  --glass-img: linear-gradient(160deg, rgba(255, 255, 255, .05) 0%, rgba(255, 255, 255, .015) 40%, rgba(255, 255, 255, 0) 100%);
  --glass-blur: blur(30px) saturate(140%) brightness(1.0);
  --glass-shadow: inset 0 1px 0 rgba(255, 255, 255, .08), 0 8px 24px rgba(0, 0, 0, .32);
  --glass-radius: 18px;

  /* плотнее вариант для мелких элементов (тайлы игр, карточки шлемов) */
  --glass-tile-bg: rgba(20, 20, 24, .5);
  --glass-tile-blur: blur(18px) saturate(150%) brightness(1.02);
  /* С152 (3-й проход): раньше только верхний блик 1px — окантовка "исчезала" книзу
     карточки. inset 0 0 0 1px даёт РАВНОМЕРНЫЙ тонкий контур по всему периметру. */
  --glass-tile-shadow: inset 0 0 0 1px rgba(255, 255, 255, .1), 0 5px 14px rgba(0, 0, 0, .24);
  --gfoot-gradient: linear-gradient(90deg, rgba(255,255,255,.14) 0%, rgba(255,255,255,.26) 50%, rgba(255,255,255,.14) 100%);

  --glass-helm-bg: rgba(20, 20, 24, .48);
  --glass-helm-active-bg: rgba(26, 24, 38, .62);
  /* С152: контур 1.5px (было 1px — слишком тонко, читалось как "срезанное" рядом с
     несимметричной внешней тенью 0 5px). Внешняя тень заменена на симметричную
     0 0 14px, чтобы не создавать ложное ощущение перекоса выделенной карточки.
     Цвет окантовки переведён с оранжевого на акцент кабинета (rgba от #8ab4ff), по просьбе
     Михаила - планшет должен визуально совпадать со стилем личного кабинета. */
  --glass-helm-active-shadow: inset 0 1px 0 rgba(255, 255, 255, .12), 0 0 0 1px rgba(138, 180, 255, .55), 0 0 14px rgba(0, 0, 0, .22);

  /* окантовка попап-окон (поддержка/тариф/трансляция), С152 (3-й проход). История:
     0.86+blur(6px на оверлее) убило матовость (двойной конфликт backdrop-filter);
     откат на 0.45 оказался СЛИШКОМ слабым — "плохо видно саму рамку стекла и текст на
     ней" (Михаил). Средний баланс: плотность 0.62 (между крайностями), blur чуть выше,
     чтобы текст лучше читался поверх стекла, окантовка (нижняя тень) усилена — теперь
     рамка визуально отделяется от фона сама по себе, не полагаясь только на оверлей. */
  --glass-sup-bg: rgba(16, 16, 20, .85);
  --glass-sup-blur: blur(22px) saturate(160%) brightness(1.05);
  --glass-sup-shadow: inset 0 1px 0 rgba(255, 255, 255, .14), 0 12px 32px rgba(0, 0, 0, .5);
  --sup-panel-bg: rgba(255, 255, 255, .96);
  --sup-panel-fg: #16171c;
  --sup-panel-fg-dim: #6b6d78;

  /* контейнер-обёртка правой колонки (KPI+таймер+библиотека) — один большой кусок
     тёмного стекла, внутри которого лежат светло-серые вставки (см. --inset-*).
     С157 (Михаил): та же плотность/подсветка/тень, что у попап-окон (--glass-sup-*) —
     "легкое свечение и рамка выглядят красиво", раньше .main-panel была бледнее и слабее
     отделялась от фона сцены за ней. */
  --main-panel-bg: rgba(0, 0, 0, .85);
  --main-panel-blur: blur(28px) saturate(150%) brightness(1.0);
  --main-panel-shadow: inset 0 1px 0 rgba(255, 255, 255, .14), 0 12px 32px rgba(0, 0, 0, .5);
  --inset-bg: rgba(255, 255, 255, .045);
  /* С161 (Михаил, живой тест на планшете): окантовка вставок (карточки шлемов,
     таймер, библиотека игр) была слишком бледной, блоки плохо отделялись друг
     от друга — подняли яркость с .09 до .18, сам фон/стекло не трогали */
  --inset-border: rgba(255, 255, 255, .18);

  /* капсула подписи под обложкой игры (С152) — заметно светлее .inset-bg, ближе к
     референсу Stardog: явная светлая плашка, а не тонкая подложка того же тона */
  --gfoot-bg: rgba(255, 255, 255, .12);
  --gfoot-border: rgba(255, 255, 255, .16);

  /* С152: оверлей под модалками слегка размывает сцену за окном (Михаил — «попап теряется
     на фоне большого числа обложек игр»). ⚠️ Первая попытка (0.32 + blur 6px) в СУММЕ с
     поднятой плотностью самой панели убила матовость целиком — стекло перестало
     просвечивать. Плотность панели вернул к 0.72 (см. --glass-sup-bg), поэтому здесь
     оставляю МЯГКИЙ вариант: blur всего 3px, затемнение чуть выше исходного 0.18 — этого
     достаточно, чтобы окно отделялось от фона, но сцена всё ещё видна сквозь стекло. */
  --overlay-bg: rgba(8, 9, 16, .5);
  --overlay-blur: blur(3px);

  --chip: rgba(10, 10, 14, .45);
  /* ⭐ С188 (Михаил): «мы перевели все кнопки на сиреневый цвет» — акцент планшета отставал от
     кабинета/сайта (те перешли на сине-фиолетовый ещё в С180-С181). Значения взяты 1-в-1 из
     account/src/styles/theme.css, чтобы вход/кнопки на планшете совпадали с остальным UnitVR.
     Оранжевый НЕ убран целиком - там, где он значит что-то своё (сигнал "идёт трансляция в
     зал" у .cast-btn-active/.helm-cast-tv, тёплый угол фона экрана входа), он намеренно
     оставлен - см. комментарии на местах в layout.css. */
  --accent: #8ab4ff;
  --accent2: #b89bff;
  --accent-fg: #0d1017;
  --btn-radius: 13px;
  --ok: #7ee6a0;
  --warn: #ffd180;
  --bad: #ff9fac;
}

:root[data-theme="light"] {
  --fg: #eef0f4;
  --fg-dim: #c2c7d2;
  --fg-mute: #9aa0ad;

  --scene:
    radial-gradient(circle at 72% 18%, rgba(210, 160, 110, .16), transparent 34%),
    radial-gradient(circle at 24% 30%, rgba(150, 175, 205, .14), transparent 40%),
    radial-gradient(circle at 88% 78%, rgba(120, 140, 170, .12), transparent 42%),
    radial-gradient(circle at 40% 95%, rgba(190, 150, 110, .10), transparent 38%),
    linear-gradient(155deg, #2a2e36 0%, #1c1f26 45%, #14161c 100%);

  --glass-bg: rgba(50, 54, 62, .58);
  --glass-img: linear-gradient(150deg, rgba(255, 255, 255, .09) 0%, rgba(255, 255, 255, .02) 42%, rgba(0, 0, 0, .03) 100%);
  --glass-blur: blur(26px) saturate(110%) brightness(1.0);
  --glass-shadow: inset 0 1px 0 rgba(255, 255, 255, .1), 0 10px 28px rgba(0, 0, 0, .22);
  --glass-radius: 18px;

  --glass-tile-bg: rgba(46, 50, 58, .5);
  --glass-tile-blur: blur(18px) saturate(105%) brightness(1.0);
  --glass-tile-shadow: inset 0 0 0 1px rgba(255, 255, 255, .12), 0 6px 18px rgba(0, 0, 0, .18);
  --gfoot-gradient: linear-gradient(90deg, rgba(255,255,255,.18) 0%, rgba(255,255,255,.3) 50%, rgba(255,255,255,.18) 100%);

  --glass-helm-bg: rgba(46, 50, 58, .46);
  --glass-helm-active-bg: rgba(58, 62, 72, .6);
  --glass-helm-active-shadow: inset 0 1px 0 rgba(255, 255, 255, .16), 0 0 0 1px rgba(255, 255, 255, .2), 0 0 16px rgba(0, 0, 0, .16);

  --glass-sup-bg: rgba(50, 54, 62, .85);
  --glass-sup-blur: blur(20px) saturate(130%) brightness(1.05);
  --glass-sup-shadow: inset 0 1px 0 rgba(255, 255, 255, .12), 0 10px 28px rgba(0, 0, 0, .3);
  --sup-panel-bg: rgba(255, 255, 255, .97);
  --sup-panel-fg: #16171c;
  --sup-panel-fg-dim: #6b6d78;

  --main-panel-bg: rgba(0, 0, 0, .85);
  --main-panel-blur: blur(26px) saturate(120%) brightness(1.0);
  --main-panel-shadow: inset 0 1px 0 rgba(255, 255, 255, .12), 0 10px 28px rgba(0, 0, 0, .3);
  --inset-bg: rgba(255, 255, 255, .06);
  --inset-border: rgba(255, 255, 255, .12);
  --gfoot-bg: rgba(255, 255, 255, .16);
  --gfoot-border: rgba(255, 255, 255, .2);

  --overlay-bg: rgba(8, 9, 16, .5);
  --overlay-blur: blur(3px);

  --chip: rgba(10, 10, 14, .35);
  --accent: #8ab4ff;
  --accent2: #b89bff;
  --accent-fg: #0d1017;
  --btn-radius: 13px;
  --ok: #7ee6a0;
  --warn: #ffd180;
  --bad: #ff9fac;
}

/* ===== БАЗОВЫЙ КЛАСС СТЕКЛА (используется всеми .glass-панелями) ===== */
.glass {
  position: relative;
  border-radius: var(--glass-radius);
  background-color: var(--glass-bg);
  background-image: var(--glass-img);
  backdrop-filter: var(--glass-blur);
  -webkit-backdrop-filter: var(--glass-blur);
  border: 1px solid transparent;
  background-clip: padding-box;
  box-shadow: var(--glass-shadow);
}

.glass-tile {
  background-color: var(--glass-tile-bg);
  backdrop-filter: var(--glass-tile-blur);
  -webkit-backdrop-filter: var(--glass-tile-blur);
  box-shadow: var(--glass-tile-shadow);
}

/* стекло попапа техподдержки — плотнее .glass (см. --glass-sup-* выше) */
.glass-sup {
  position: relative;
  border-radius: var(--glass-radius);
  background-color: var(--glass-sup-bg);
  backdrop-filter: var(--glass-sup-blur);
  -webkit-backdrop-filter: var(--glass-sup-blur);
  border: 1px solid transparent;
  background-clip: padding-box;
  box-shadow: var(--glass-sup-shadow);
}

/* контейнер-обёртка правой колонки — один большой кусок тёмного стекла (С152) */
.main-panel {
  position: relative;
  border-radius: var(--glass-radius);
  background-color: var(--main-panel-bg);
  backdrop-filter: var(--main-panel-blur);
  -webkit-backdrop-filter: var(--main-panel-blur);
  box-shadow: var(--main-panel-shadow);
}

/* С152: светло-серые вставки внутри тёмного стекла (по образцу референса Stardog —
   большая тёмная стеклянная область, внутри которой лежат светлые под-панели с мягкой
   серой окантовкой). Используется для таймера/библиотеки игр внутри .main-panel. */
.inset-panel {
  border-radius: 16px;
  background: var(--inset-bg);
  border: 1px solid var(--inset-border);
}
/* компактные чипы-теги (по образцу «Seekingalpha/CNBC» на референсе) для KPI */
.chip-tag {
  border-radius: 100px;
  background: var(--inset-bg);
  border: 1px solid var(--inset-border);
}

/* grain — то, что отличает матовое стекло от чистого (см. reference-glass-canon-2026) */
.grain::after {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: inherit;
  pointer-events: none;
  opacity: .025;
  mix-blend-mode: overlay;
  z-index: 3;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='140' height='140'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.75' numOctaves='3' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
}
* { box-sizing: border-box; -webkit-tap-highlight-color: transparent; }
html, body, #root { height: 100%; margin: 0; overflow: hidden; }
body {
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
  font-weight: 300;
  color: var(--fg);
  background: var(--scene);
  background-attachment: fixed;
  -webkit-font-smoothing: antialiased;
}
.hidden { display: none !important; }

/* ================= LOGIN ================= */
/* С152: медленный "дышащий" перелив фоновой сцены на экране логина (Михаил — "чтобы
   градиент медленно перетекал, не так быстро, как надпись"). Только .login-screen — общий
   .scene в приложении не трогаем, чтобы не анимировать фон везде. Два радиальных пятна
   плавно меняют позицию через отдельный слой поверх статичного --scene. */
.login-screen { position: fixed; inset: 0; display: flex; align-items: center; justify-content: center; background: var(--scene); overflow: hidden; }
.login-screen::before {
  content: ""; position: absolute; inset: -35%; z-index: 0; pointer-events: none;
  background:
    radial-gradient(circle at 78% -8%, rgba(255, 154, 77, .38), transparent 40%),
    radial-gradient(circle at 15% 108%, rgba(90, 140, 255, .3), transparent 40%);
  animation: login-scene-drift 14s ease-in-out infinite alternate;
}
.login-screen .login-card { position: relative; z-index: 1; }
@keyframes login-scene-drift {
  0% { transform: translate(-10%, -8%) scale(1); }
  50% { transform: translate(8%, 10%) scale(1.15); }
  100% { transform: translate(-6%, 12%) scale(1.08); }
}
/* Карточка логина НЕ использует общий .glass — тот честно просвечивает сцену, а на экране
   входа сцена анимированная и асимметричная (тёплое пятно сверху-справа, холодное снизу-слева,
   плюс тёмный линейный градиент к низу), из-за чего полупрозрачная карточка получала бы
   разную яркость по углам и визуально "таяла" книзу. Михаил явно попросил вместо этого ровную
   матовую поверхность без просвечивания — плотность почти непрозрачная (.97), матовость только
   через зерно и лёгкий верхний блик, не через backdrop-filter по сцене. */
.login-card {
  width: min(320px, 90vw); padding: 28px 26px; display: flex; flex-direction: column; gap: 12px;
  position: relative;
  border-radius: var(--glass-radius);
  background-color: rgba(20, 20, 24, .97);
  background-image: linear-gradient(160deg, rgba(255, 255, 255, .05) 0%, rgba(255, 255, 255, .015) 40%, rgba(255, 255, 255, 0) 100%);
  border: 1px solid transparent;
  background-clip: padding-box;
  box-shadow: inset 0 0 0 1px rgba(255, 255, 255, .06), inset 0 1px 0 rgba(255, 255, 255, .08), 0 20px 50px rgba(0, 0, 0, .45);
}

/* С152 (3-й проход): фоновая петля убрана совсем — на карточке такого размера крупная
   петля не помещалась и наезжала на поля логина/пароля (Михаил проверил вживую). Осталась
   только надпись UnitVR по центру, "VR" жирнее "Unit". Направление перелива слева→направо
   (было наоборот — background-position рос в ту же сторону, что и сам градиент, из-за
   чего визуально цвет "приезжал" с противоположной стороны). */
.login-brand { display: flex; align-items: center; justify-content: center; gap: 10px; margin-bottom: 22px; }
/* ⭐ С187 (Михаил): знак-«восьмёрка» рядом с надписью — как в кабинете владельца.
   Размер привязан к строке (1em ≈ высота надписи), а не задан в пикселях: при любой правке
   размера заголовка знак останется соразмерным, без ручной подгонки. flex-shrink: 0 — знак
   не должен сплющиваться, если надписи вдруг станет тесно (см. С187, разбор про широкий
   системный шрифт macOS). */
/* ⭐ С188 (Михаил): «восьмёрку побольше, надпись поменьше, как на сайте». Прошлый расчёт
   (1.35em) считался от НЕ того шрифта - .login-mark не вложен в .login-title, а em берёт
   размер шрифта родителя (.login-brand его не задаёт → падал на body 16px = 21.6px), тогда
   как заголовок рядом - 30px. Знак получался МЕЛЬЧЕ надписи, а нужно наоборот. Высота задана
   явным px, привязанным к заголовку "на глаз" по образцу сайта (Header.jsx: знак 40px против
   текста 22px, но там viewBox с запасом по краям; здесь viewBox обрезан точно под фигуру,
   поэтому для того же зрительного баланса берём меньшее число). */
.login-mark { height: 46px; width: auto; flex-shrink: 0; display: block; }
.login-title {
  font-size: 26px; font-weight: 500; letter-spacing: -.01em;
  /* С188: тот же переливающийся градиент бренда, что в кабинете/на сайте (.logo-text) -
     сине-фиолетовый доминирует, оранжевый - только короткий блик в середине. Раньше было
     наоборот (оранжевый доминировал) - остаток доребрендинговой палитры. */
  background: linear-gradient(90deg, #8ab4ff 0%, #b89bff 25%, #ffb066 50%, #b89bff 75%, #8ab4ff 100%);
  background-size: 300% 100%;
  -webkit-background-clip: text; background-clip: text; color: transparent;
  animation: login-shine 5s linear infinite;
}
.login-title b { font-weight: 800; }
@keyframes login-shine {
  0% { background-position: 300% 50%; }
  100% { background-position: 0% 50%; }
}
.login-input {
  /* С182 (Михаил): окантовка полей была почти невидимой (.08) - поля сливались с фоном
     окна, границу приходилось угадывать. Подняли до .18: контур читается, но не спорит
     с содержимым и не выглядит как рамка-обводка. */
  background: var(--chip); border: 1px solid rgba(255, 255, 255, .18); border-radius: 10px; padding: 12px 14px; font-size: 14px;
  color: var(--fg); outline: none; font-family: inherit; font-weight: 300; width: 100%;
  transition: border-color .2s;
}
.login-input:focus { border-color: rgba(138, 180, 255, .4); }
.login-error { color: var(--bad); font-size: 12px; min-height: 16px; }

.login-password-field { position: relative; }
.login-password-field .login-input { padding-right: 40px; }
.login-password-toggle {
  position: absolute; right: 4px; top: 50%; transform: translateY(-50%);
  width: 32px; height: 32px; display: flex; align-items: center; justify-content: center;
  background: none; border: none; border-radius: 8px; font-size: 15px; cursor: pointer;
  opacity: .65; transition: opacity .15s, background .15s;
}
.login-password-toggle:hover { opacity: 1; background: rgba(255, 255, 255, .06); }

/* ================= LAYOUT ================= */
#app {
  position: fixed; inset: 0; display: grid; grid-template-columns: 292px 1fr; gap: 14px; padding: 14px;
  padding-top: max(14px, env(safe-area-inset-top)); padding-bottom: max(14px, env(safe-area-inset-bottom));
  padding-left: max(14px, env(safe-area-inset-left)); padding-right: max(14px, env(safe-area-inset-right));
  transition: grid-template-columns .3s cubic-bezier(.4, 0, .2, 1);
  /* С159 (Михаил, живой тест x5): "открытие ровное, закрытие тормозит в конце" — при закрытии
     #app ВОЗВРАЩАЕТСЯ в видимую область экрана, браузер вынужден заново composite/paint весь
     тяжёлый контент внутри (карточки с backdrop-filter/blur/тени) именно в момент, когда область
     становится видимой - при открытии эта же панель просто уезжает ЗА пределы экрана, перерисовывать
     нечего, GPU только двигает уже готовый слой. will-change:transform держит #app на отдельном
     GPU-слое ПОСТОЯННО (не только во время анимации grid-columns) - слой уже составлен заранее,
     возврату в видимую область не нужен дорогой re-paint. */
  will-change: grid-template-columns, transform;
}
#app.collapsed { grid-template-columns: 0px 1fr; }
/* С159 (Михаил, живой эксперимент с двумя каталогами слева/справа подтвердил): замедление при
   ВОЗВРАТЕ #app в кадр — не про направление жеста, а про сам главный экран. Настоящая причина:
   каждая .gtile/.helm (карточка игры/шлема) имеет backdrop-filter (см. .gtile/.helm ниже) —
   пока #app движется через transform, GPU не может закешировать blur между кадрами (родительский
   слой трансформируется), пересчитывает его КАЖДЫЙ кадр для ВСЕХ карточек разом. При открытии
   #app уезжает ЗА экран — как только уходит за viewport, браузер перестаёт его рендерить,
   тормозить нечему. При закрытии он всё время (частично) виден, и ближе к концу пути видна
   максимальная площадь карточек одновременно — самый дорогой момент анимации. catalog-moving
   ставится, пока catalogProgress>0 (см. App.jsx) - отключает backdrop-filter на время движения,
   карточки на статичном экране (progress===0, анимация уже закончилась) сохраняют блюр как обычно. */
#app.catalog-moving .gtile,
#app.catalog-moving .ggenre,
#app.catalog-moving .gbadge,
#app.catalog-moving .helm,
/* С159 (Михаил, живой тест x11, найдено программистом-экспертом): "мелкие рывки, как видео без
   кадров" именно на главном экране - правило выше отключало blur ТОЛЬКО у мелких карточек, но
   .main-panel/.main-panel-l (blur 28px saturate 150%, см. theme.css) - это ДВА САМЫХ ДОРОГИХ
   blur-слоя, покрывающих практически весь #app целиком (левая и правая колонки). Именно их
   пересчёт каждый кадр драга и давал дёрганость - у каталога (.cast/.glass-sup) нет ничего
   настолько крупного, отсюда и разница "главный экран рябит, каталог гладкий". */
#app.catalog-moving .main-panel,
#app.catalog-moving .main-panel-l {
  backdrop-filter: none !important;
  -webkit-backdrop-filter: none !important;
}
/* С159 (Михаил, живой тест): свайп двумя пальцами открывает/закрывает каталог игр -
   основной экран должен визуально "уезжать" влево синхронно с пальцем (drag 1:1, БЕЗ
   transition, см. useSwipeCatalog.js), а при отпускании - плавно доводиться до открытого
   или закрытого состояния (.catalog-anim добавляется только на settle, не во время драга). */
#app.catalog-anim { transition: grid-template-columns .3s cubic-bezier(.4, 0, .2, 1), transform .3s linear; }
/* С157 (Михаил, живой тест: "дёрганая анимация сворачивания"): анимация grid-template-columns
   пересчитывает layout КАЖДЫЙ кадр, и это особенно дорого вместе с несколькими вложенными
   backdrop-filter (главная панель + карточки шлемов) — каждый кадр браузер заново применяет
   blur ко всем слоям под новой шириной колонки. contain:layout на .col-l не даёт изменению её
   размеров запускать пересчёт остального дерева вне неё, ощутимо снижает нагрузку на кадр. */
.col-l { display: flex; flex-direction: column; gap: 10px; min-height: 0; overflow: hidden; contain: layout; }
.col-r { display: flex; flex-direction: column; gap: 12px; min-height: 0; min-width: 0; }
/* С152: левая колонка — тот же приём, что .main-panel справа: ОДИН большой кусок тёмного
   стекла (Михаил — "справа явно единый крупный блок с текстурой, слева карточки как будто
   сливаются со сценой без разделения"), внутри него карточки шлемов теперь светлые вставки. */
.main-panel-l { flex: 1; min-height: 0; padding: 8px; display: flex; flex-direction: column; gap: 8px; }
/* С152: кнопка добавления шлема — ВСЕГДА закреплена сверху, вне скролла карточек
   (Михаил — "чтобы не искать её среди уже добавленных шлемов"), заметная пунктирная
   рамка отличает её от обычных карточек/кнопок. С157: темнее серым - используется редко,
   не должна бросаться в глаза среди рабочих элементов панели. */
.add-helmet-btn {
  flex-shrink: 0; padding: 11px; border-radius: 12px; border: 1.5px dashed var(--inset-border);
  background: transparent; color: var(--fg-mute); font-size: 13px; font-weight: 400;
  cursor: pointer; transition: border-color .2s, color .2s, background .2s;
}
.add-helmet-btn:hover, .add-helmet-btn:active { border-color: var(--accent); color: var(--accent); background: rgba(138, 180, 255, .06); }

/* С156: живой подбор фонового градиента приложения — открывает SceneTunerModal. */
.scene-tuner-btn { flex-shrink: 0; justify-content: center; }
/* С170: версия приложения в самом низу левой колонки (кнопка настройки фона чуть приподнята
   ею; саму кнопку позже уберём совсем - решение Михаила). Мелко и ненавязчиво, как в шлеме. */
/* ⭐ С180 (Михаил): в подвале левой колонки два значения - слева срок подписки, справа версия.
   Версия ушла из центра вправо, чтобы освободить место под срок и не делать из подвала кашу. */
.app-footer-line {
  flex-shrink: 0;
  display: flex; align-items: baseline; justify-content: space-between; gap: 8px;
  padding: 6px 6px 0;
}
.app-version-label {
  font-size: 11px;
  font-family: ui-monospace, monospace;
  color: var(--fg-mute);
}
.app-days-left {
  font-size: 11px;
  color: var(--fg-mute);
  white-space: nowrap;
}
/* Последние дни - тем же красным, что и заканчивающееся время сессии: язык сигналов в
   интерфейсе один, оператору не надо запоминать разные цвета для разных мест. */
.app-days-left.low { color: #ff5a5a; }

/* С152 (2-й проход): реальный отступ до ТЕКСТА внутри карточки шлема — это СУММА всех
   вложенных padding: .main-panel-l (8px) + #helmScroll (2px) + .helm (13px) = 23px,
   а не только последний. Первая попытка (11px) выравнивала по краю .helm, а не по тексту
   внутри неё — логотип оказался левее, чем нужно.
   С157: Михаил сверяет по левому краю КНОПКИ "Добавить шлем" (.add-helmet-btn) - её реальный
   отступ до текста = .main-panel-l (8px, родитель обеих) + свой padding (11px) = 19px.
   .brand-mark - прямой потомок .brand внутри .brandbar, оба без padding - значит margin-left
   должен точно повторить эти же 19px (не 19+что-то ещё, предыдущая правка на 8px ошиблась в
   расчёте и оставляла восьмёрку правее кнопки). */
.brandbar { display: flex; align-items: center; gap: 9px; padding: 2px 4px 2px 0; flex-shrink: 0; }
.brand { font-size: 15px; font-weight: 400; display: flex; align-items: center; gap: 8px; }
.brand-mark { width: 30px; height: 30px; flex-shrink: 0; margin-left: 11px; }
/* С152 (2-й проход): индикатор связи с сервером переехал в правую панель, рядом с
   Обновить/Поддержка/Запчасти (Михаил — "не загромождать левую панель"), явным текстовым
   бейджем вместо голой точки. */
.conn-badge {
  display: inline-flex; align-items: center; gap: 6px; padding: 9px 14px; border-radius: 100px;
  font-size: 11px; font-weight: 500; white-space: nowrap;
}
.conn-dot { width: 7px; height: 7px; border-radius: 50%; flex-shrink: 0; }
.conn-online { background: rgba(126, 230, 160, .12); border: 1px solid rgba(126, 230, 160, .3); color: var(--ok); }
.conn-online .conn-dot { background: var(--ok); box-shadow: 0 0 6px var(--ok); }
.conn-offline { background: rgba(255, 159, 172, .12); border: 1px solid rgba(255, 159, 172, .3); color: var(--bad); }
.conn-offline .conn-dot { background: var(--bad); box-shadow: 0 0 6px var(--bad); }
/* С152: фирменный лого в шапке приложения — тот же градиентный перелив, что на экране
   логина, но МЯГЧЕ и МЕДЛЕННЕЕ (Михаил: "не такой быстрый, пусть потихонечку переливается"). */
.brand-title {
  font-size: 23px; font-weight: 500; letter-spacing: -.005em;
  /* С188: тот же переливающийся градиент бренда, что в кабинете/на сайте (.logo-text) -
     сине-фиолетовый доминирует, оранжевый - только короткий блик в середине. Раньше было
     наоборот (оранжевый доминировал) - остаток доребрендинговой палитры. */
  background: linear-gradient(90deg, #8ab4ff 0%, #b89bff 25%, #ffb066 50%, #b89bff 75%, #8ab4ff 100%);
  background-size: 300% 100%;
  -webkit-background-clip: text; background-clip: text; color: transparent;
  animation: login-shine 14s linear infinite;
}
.brand-title b { font-weight: 800; }
.icon-btn {
  width: 30px; height: 30px; border-radius: 10px; border: none; background: var(--chip); color: var(--fg-dim);
  font-size: 13px; display: flex; align-items: center; justify-content: center; cursor: pointer; flex-shrink: 0;
  transition: background .2s, color .2s;
}
.icon-btn:active { transform: scale(.94); }
.instructions-btn { font-size: 16px; }
.settings-btn { font-size: 17px; }

/* С152: меню режима (Клуб/Выезды) переехало из топбара в шестерёнку слева — временный
   переключатель на период разработки, не должен быть виден в "финальном" виде клубной
   версии, пока Михаил смотрит именно её дизайн. */
.dev-menu { position: absolute; top: 38px; right: 0; z-index: 30; padding: 10px; width: 180px; display: flex; flex-direction: column; gap: 8px; }
.dev-menu-label { font-size: 9.5px; text-transform: uppercase; letter-spacing: .04em; color: var(--fg-dim); margin-top: 4px; }
.dev-menu-theme { width: 100%; height: auto; border-radius: 8px; padding: 7px 10px; font-size: 12px; gap: 6px; justify-content: flex-start; }
.dev-menu-revenue { font-size: 15px; font-weight: 600; color: var(--fg); }

/* кнопка "Обновить" видна ТОЛЬКО когда обновление реально готово — сама её видимость уже
   индикатор, отдельная точка избыточна (Михаил заметил). Красная — чтобы админ сразу её
   заметил и нажал (С157, по просьбе Михаила). Окантовка тонкая (1px, как conn-badge), но
   полностью непрозрачная + свечение вокруг (тот же приём, что glow у .conn-dot), а не
   только цвет рамки — иначе тонкая линия сама по себе не бросается в глаза. */
.pill-update-ready {
  background: rgba(230, 90, 90, .16);
  color: #ff6b6b;
  border: 1px solid #ff6b6b;
  box-shadow: 0 0 8px rgba(255, 107, 107, .55);
}
.bell-shake { display: inline-block; animation: bell-shake 1.6s ease-in-out infinite; transform-origin: 50% 0; }
@keyframes bell-shake {
  0%, 74%, 100% { transform: rotate(0); }
  76% { transform: rotate(-14deg); }
  78% { transform: rotate(11deg); }
  80% { transform: rotate(-8deg); }
  82% { transform: rotate(5deg); }
  84% { transform: rotate(-3deg); }
  86% { transform: rotate(0); }
}
@media (prefers-reduced-motion: reduce) { .bell-shake { animation: none; } }

/* С152: .col-l родитель имеет overflow:hidden (нужен для анимации схлопывания боковой
   панели) — он обрезал 1.5px-контур выделенной карточки слева и сверху у первой карточки
   в списке (некому перекрыть тень изнутри, обрезает именно внешний контейнер). Небольшой
   padding по всем сторонам даёт контуру место, не задевая сам скролл списка. Теперь этот
   запас даёт .main-panel-l (padding 8px) — здесь оставлен небольшой добавочный отступ. */
/* С152: #helmScroll оборачивает карточки в ДОПОЛНИТЕЛЬНЫЙ <div> (нужен Lenis-скроллу) —
   gap на #helmScroll ни на что не действовал, т.к. его единственный flex-ребёнок — этот
   wrapper-div, а не сами карточки. gap перенесён на wrapper, где реальные .helm — прямые
   дети (отсюда карточки визуально были "впритык" друг к другу без зазора). */
#helmScroll {
  flex: 1; min-height: 0; overflow-y: auto; padding: 2px;
  -webkit-overflow-scrolling: touch; scrollbar-width: none;
  touch-action: pan-y; /* С159: см. #gamesScroll выше — тот же принцип, тот же баг */
}
#helmScroll > div { display: flex; flex-direction: column; gap: 8px; }
#helmScroll::-webkit-scrollbar { display: none; }
/* С152: карточка шлема теперь светлая ВСТАВКА внутри общего тёмного .main-panel-l (было —
   собственное тёмное стекло того же тона, что фон сцены за панелью, отсюда "сливаются"). */
/* С157 (Михаил, живой тест): долгий тап на карточке (открывает меню Переименовать/Перенести/
   Удалить) параллельно триггерил системное выделение текста Android поверх карточки - тот же
   приём, что уже решал такую же проблему на .gcover img (см. ниже), нужен и здесь. */
.helm {
  position: relative; border-radius: 14px; padding: 11px 13px; background: var(--inset-bg);
  border: 1px solid var(--inset-border);
  cursor: pointer; flex-shrink: 0; transition: background-color .25s, box-shadow .25s, transform .15s;
  -webkit-touch-callout: none; -webkit-user-select: none; user-select: none;
}
.helm:active { transform: scale(.985); }
/* С152: z-index — иначе следующая карточка в DOM-порядке рисуется ПОВЕРХ предыдущей
   (все .helm имеют position:relative, z-index:auto) и обрезает верхний/нижний край
   контура выделенной карточки на стыке — баг воспроизведён на скриншотах Михаила
   (окантовка "срезалась" то сверху, то снизу в зависимости от того, какая карточка
   выделена относительно соседей). */
.helm.sel { position: relative; z-index: 1; background-color: var(--glass-helm-active-bg); box-shadow: var(--glass-helm-active-shadow); }
.helm.mass-sel { position: relative; z-index: 1; background-color: var(--glass-helm-active-bg); box-shadow: inset 0 1px 0 rgba(255, 255, 255, .1), 0 0 0 1.5px var(--accent), 0 5px 14px rgba(0, 0, 0, .22); }
/* ⭐⭐⭐⭐⭐ С177 (Михаил): АВАРИЙНЫЙ РЕЖИМ НА КОНКРЕТНОМ ШЛЕМЕ.
   Всплывающая плашка о включении режима закрывается и пропадает, а при 20 шлемах в клубе
   администратору потом негде посмотреть, на каком именно он сработал. Поэтому карточка
   этого шлема подсвечивается, пока режим активен, - видно с одного взгляда.

   Цвет и свечение - РОВНО как у таймера на исходе времени (.timer .t.low): тот же красный
   #ff6b6b и то же ВНУТРЕННЕЕ свечение через inset. Прямое замечание Михаила: первая версия
   использовала переменную --bad (#ff9fac) и наружное свечение - получился «противный розовый»,
   выбивавшийся из остального интерфейса. Внутреннее свечение читается как тревога, но не
   размазывается на соседние карточки.

   Свечение медленно пульсирует: неподвижная рамка теряется среди других карточек, а резкое
   мигание раздражало бы (планшет перед глазами администратора весь день). */
.helm.emg {
  border-color: #ff6b6b;
  box-shadow: inset 0 0 22px rgba(255, 107, 107, .75);
  animation: helm-emg-pulse 2.4s ease-in-out infinite;
}
/* выбранный аварийный шлем: свечение чуть сильнее, чтобы не потерялось на светлом фоне выбора */
.helm.emg.sel, .helm.emg.mass-sel {
  border-color: #ff6b6b;
  box-shadow: inset 0 0 26px rgba(255, 107, 107, .85);
}
@keyframes helm-emg-pulse {
  0%, 100% { box-shadow: inset 0 0 14px rgba(255, 107, 107, .60); }
  50%      { box-shadow: inset 0 0 26px rgba(255, 107, 107, .90); }
}
@media (prefers-reduced-motion: reduce) { .helm.emg { animation: none; } }
.helm-top { display: flex; align-items: center; gap: 8px; }
.mass-check { width: 16px; height: 16px; accent-color: var(--accent); flex-shrink: 0; }
/* С157: кнопки ↑/↓ переноса карточки шлема - режим включается через долгий тап -> "Перенести" */
.helm-reorder-btns { display: flex; gap: 4px; flex-shrink: 0; }
.helm-reorder-btn {
  width: 24px; height: 24px; border-radius: 8px; border: none; background: var(--chip); color: var(--fg);
  font-size: 12px; cursor: pointer; display: flex; align-items: center; justify-content: center;
}
.helm-reorder-btn:disabled { color: var(--fg-mute); opacity: .4; cursor: default; }
/* меню карточки шлема (Переименовать/Перенести/Удалить) - кнопки на всю ширину, не капсулой */
/* С157 (Михаил, живой тест): было прижато к левому краю, иконка вплотную к тексту - по
   центру и с явным отступом иконки от текста. */
/* ⭐⭐⭐⭐⭐ С174 (Михаил: «пункты слишком близко друг к другу, между ними надо тоненькие серые
   полосы, чтобы визуально разделялись»): пункты меню шлема - вертикальный список с воздухом
   и разделителями. Пунктов стало много (переименовать / перенести / телевизор / диагностика /
   аварийная разблокировка / удалить), сплошной стопкой они читались как одна масса.
   ⭐ С178 (Михаил, живой промах пальцем): тонкой линии оказалось недостаточно - метил в
   «Привязать телевизор», попал в «Отправить диагностику» под ней. Добавлен реальный зазор
   (gap) между кнопками, а не только визуальная линия - теперь между пунктами есть физическое
   расстояние, а не просто граница впритык.
   ⭐⭐ С178 (Михаил, ПОВТОРНЫЙ живой промах даже с gap:8px): зазора 8px всё ещё не хватило -
   палец на планшете задевает соседнюю кнопку. Увеличено вдвое, до 16px - должно быть видно
   и физически ощутимо как явный промежуток, а не почти сомкнутые кнопки. */
.helmet-menu-list { display: flex; flex-direction: column; gap: 16px; margin-top: 6px; }
.helmet-menu-btn { width: 100%; justify-content: flex-start; padding: 16px 18px; border-radius: 10px; font-size: 13.5px; }
/* Разделитель рисуем ПЕРЕД каждым пунктом кроме первого, по центру нового зазора (-8px =
   половина gap:16px) - так линии не появляются ни сверху списка, ни снизу. */
.helmet-menu-list .helmet-menu-btn + .helmet-menu-btn { position: relative; }
.helmet-menu-list .helmet-menu-btn + .helmet-menu-btn::before {
  content: ""; position: absolute; top: -8px; left: 12px; right: 12px;
  border-top: 1px solid rgba(255, 255, 255, .09);
}
/* «Удалить» - действие другого веса, отделяем заметнее: отступ вместо тонкой линии. */
.helmet-menu-list .helmet-menu-danger { margin-top: 6px; }
.helmet-menu-list .helmet-menu-danger::before { display: none; }
.helmet-menu-icon { margin-right: 12px; width: 20px; text-align: center; flex-shrink: 0; }
/* ⭐ С178 (Михаил): попап меню шлема пошире базовой модалки (340px) - пунктов много, кнопки
   теперь крупнее (padding 16/18), на узкой модалке текст с иконкой смотрелся тесно. */
.helmet-menu-modal { width: min(380px, 100%); }
/* С152: заголовок и статы (батарея/wifi/L/R) увеличены и утяжелены по прямой просьбе
   Михаила — на планшете в клубе мелкий текст плохо читается издалека. */
/* ⭐⭐⭐⭐ С182 (Михаил): справа от имени живут значки трансляции и статус («в игре»/«онлайн»/
   «оффлайн») - всё это обязано умещаться в ОДНУ строку. Ввод ограничен 20 символами
   (HelmetMenuModal/AddHelmetModal), а это - страховка для уже существующих длинных имён и для
   узких экранов: имя ужимается и получает многоточие, но НИКОГДА не выдавливает значки.
   min-width:0 обязателен - без него flex-элемент не даёт себя ужать ниже длины текста. */
.helm-name { font-size: 15px; font-weight: 600; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.helm-status { flex-shrink: 0; white-space: nowrap; }
/* Подсказка со счётчиком символов под полем переименования. */
.helm-name-hint { margin-top: 6px; font-size: 11px; color: var(--fg-mute); text-align: right; }
/* ⭐⭐⭐⭐⭐ С182 (Михаил): отказ запуска - на месте строки «Игра: …», красным. Занимает ровно
   ту же строку, поэтому карточка таймера не прыгает по высоте при появлении сообщения.
   ⚠️ flex: 1 1 0% обязателен и здесь: строка «Игра: …» держит на себе всю свободную ширину
   карточки (см. .timer-running-game ниже). Без растяжки кнопки «Начать сессию»/«Пауза»/
   «Трансляция» подъезжали к надписи вплотную - живой промах С182. */
.timer-launch-issue {
  flex: 1 1 0%; min-width: 0;
  font-size: 12.5px; font-weight: 700; color: #EB5757; letter-spacing: .01em;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* ⭐⭐⭐⭐⭐ С174 (Михаил): значок «на этом шлеме идёт трансляция» прямо на карточке - виден,
   даже когда оператор ушёл работать с другим шлемом. Телевизор подсвечен оранжевым (идёт в
   ЗАЛ, это видят посетители), просмотр у оператора - приглушённый. */
.helm-cast-badge { font-size: 13px; line-height: 1; opacity: .55; flex-shrink: 0; }
/* ⭐⭐⭐⭐⭐ С177 (Михаил): «обновление ждёт подтверждения» на конкретном шлеме.
   На PICO обновление ставится только с подтверждением человека в шлеме, и пока никто не нажал,
   шлем молча стоит на старой версии — администратор должен видеть, к какому шлему подойти.

   Первая версия была голым символом ⬇ — Михаил справедливо забраковал: «маленькая и
   незаметненькая, вообще неприметна». Сделано по образцу .pill-update-ready (см. выше): не
   цвет сам по себе, а ЗАЛИВКА + рамка + свечение — тот же приём, которым там же решалась
   ровно эта проблема. Плюс текст словом: значок без подписи всё равно требует догадки.

   Цвет АКЦЕНТНЫЙ (var(--accent), С188 - сине-фиолетовый), а НЕ красный: красный занят
   аварийным режимом (.helm.emg), путать тревогу и рутину нельзя. Обновления выходят редко
   и не срочны. */
.helm-upd-badge {
  flex-shrink: 0; font-size: 10px; font-weight: 600; letter-spacing: .01em; white-space: nowrap;
  padding: 3px 8px; border-radius: 100px;
  background: rgba(138, 180, 255, .18);
  color: var(--accent);
  border: 1px solid var(--accent);
  box-shadow: 0 0 8px rgba(138, 180, 255, .55);
  animation: helm-upd-pulse 2s ease-in-out infinite;
}
@keyframes helm-upd-pulse {
  0%, 100% { box-shadow: 0 0 6px rgba(138, 180, 255, .40); }
  50%      { box-shadow: 0 0 14px rgba(138, 180, 255, .85); }
}
@media (prefers-reduced-motion: reduce) { .helm-upd-badge { animation: none; } }
.helm-cast-tv { opacity: 1; filter: drop-shadow(0 0 6px rgba(255, 154, 77, .85)); }
.helm-status { margin-left: auto; font-size: 10.5px; font-weight: 600; text-transform: uppercase; letter-spacing: .03em; }
/* С161 (Михаил): три отдельных стата (гарнитура+L+R контроллеров) заменены строкой
   "название запущенной игры — батарея гарнитуры" - заряд контроллеров недоступен без
   SDK производителя шлема, а три текстовых чипа занимали лишнее место на карточке. */
.helm-mid { display: flex; align-items: center; margin-top: 8px; font-size: 12px; font-weight: 600; color: var(--fg); gap: 6px; }
.helm-stats-offline { color: var(--fg-mute); }
.helm-game { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.helm-game-play { color: var(--ok); }
.helm-batt {
  margin-left: auto; flex-shrink: 0; display: inline-flex; align-items: center; gap: 4px;
  padding: 4px 8px; border-radius: 10px; border: 1px solid var(--chip); background: var(--chip);
}
.helm-batt.w { color: var(--bad); border-color: var(--bad); }
/* по образцу референса Fuselab — тонкая нарисованная line-icon вместо эмодзи для
   батареи гарнитуры (см. StatIcons.jsx). currentColor подхватывает и обычный
   приглушённый тон, и предупреждающий красный (.w) при низком заряде. */
.stat-icon { width: 13px; height: 13px; flex-shrink: 0; opacity: .85; }
.stat-icon-lg { width: 15px; height: 15px; }
.helm-bot { display: flex; align-items: center; margin-top: 8px; font-size: 10.5px; color: var(--fg-dim); gap: 6px; }
.helm-session-len { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.helm-timer {
  margin-left: auto; font-family: ui-monospace, monospace; font-size: 12px; color: var(--fg);
  font-variant-numeric: tabular-nums; padding: 3px 8px; border-radius: 8px;
  border: 1px solid var(--inset-border); transition: color .3s, border-color .3s, box-shadow .3s;
}
/* С152: таймер краснеет в последние 5 минут сессии — администратор видит на глаз, что
   время клиента подходит к концу, не открывая карточку/не считая вручную.
   С157: постоянная окантовка + свечение ВНУТРЬ при <5 мин, тот же приём, что у .timer .t.
   Зелёное свечение, пока игра реально идёт (Михаил - "смотря на плашки со шлемами сразу
   видно, где запущена игра") - тот же фирменный зелёный, что --ok. */
.helm-timer.low { color: var(--bad); font-weight: 700; border-color: #ff6b6b; box-shadow: inset 0 0 8px rgba(255, 107, 107, .75); }
.helm-timer.active { color: var(--ok); font-weight: 700; border-color: var(--ok); box-shadow: inset 0 0 8px rgba(126, 230, 160, .75); }
/* С160 (Михаил): код привязки шлема - виден всегда, не только по долгому тапу (нечего
   скрывать), но не должен спорить с главным на карточке (игра/таймер) - 12px (не мельче,
   Михаил попросил не мельчить), тусклый цвет. */
.helm-pairing { margin-top: 5px; font-size: 12px; color: var(--fg-dim); display: flex; justify-content: space-between; align-items: center; gap: 8px; }
/* С163 (Михаил): занято/всего памяти шлема, справа в той же строке что код привязки -
   не переносится (white-space: nowrap), первый span (код) сжимается через text-overflow,
   если места не хватает, а не наезжает на память. */
.helm-pairing > span:first-child { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.helm-storage { flex-shrink: 0; white-space: nowrap; font-family: ui-monospace, monospace; }

.mass-bar { display: flex; align-items: center; gap: 8px; padding: 10px 4px 2px; flex-shrink: 0; }
.mass-bar span { font-size: 11px; color: var(--fg-dim); }

/* С152: правая колонка — один большой кусок тёмного стекла (.main-panel), внутри
   которого лежат светлые вставки (.inset-panel/.chip-tag) — образец референса Stardog */
.main-panel { flex: 1; min-height: 0; display: flex; flex-direction: column; gap: 12px; padding: 16px; }

.topbar { display: flex; align-items: center; gap: 10px; flex-shrink: 0; flex-wrap: wrap; }
.kpis { display: flex; gap: 8px; flex-wrap: wrap; }
.kpi.chip-tag { padding: 8px 16px; display: flex; align-items: baseline; gap: 7px; }
.kpi .l { font-size: 10px; color: var(--fg-dim); text-transform: uppercase; letter-spacing: .04em; }
.kpi .v { font-size: 14px; font-weight: 500; font-variant-numeric: tabular-nums; }
/* ⭐ С186: плитка «Сеансов» стала кнопкой - сбрасываем браузерные дефолты <button>, сама плитка
   уже задана классом .kpi.chip-tag выше. */
.kpi-clickable { font: inherit; color: inherit; cursor: pointer; transition: background .15s; }
.kpi-clickable:hover { background: rgba(255, 255, 255, .06); }
/* ⭐⭐⭐ С187, найдено аудитом: на портретном iPad Pro 11" (834px — шире брейкпойнта 820,
   поэтому раскладка остаётся десктопной) правой колонке достаётся ~468px, а кнопкам шапки
   нужно ~479px даже на узком шрифте. Без переноса бейдж «Связь с сервером» срезался краем.
   flex-wrap решает без нового брейкпойнта: не поместились — перенеслись на вторую строку. */
.topacts { display: flex; gap: 8px; flex-shrink: 0; margin-left: auto; flex-wrap: wrap; justify-content: flex-end; }

.offline-badge {
  font-size: 11px; padding: 8px 14px; border-radius: 12px; background: rgba(255, 209, 128, .12);
  color: var(--warn); border: 1px solid rgba(255, 209, 128, .25); flex-shrink: 0;
}

/* С187: та же причина, что у .topacts выше — на портретном iPad Pro 11" строка таймера
   (само время min-width 130px + кнопки «Закончить сессию»/«Пауза»/«Трансляция», которым
   запрещено сжиматься) требует ~500px на шрифте Safari при доступных ~468px и выпирала за
   панель. Разрешаем перенос — кнопки уходят на вторую строку вместо того, чтобы вылезать. */
.timer.inset-panel { padding: 16px 20px; display: flex; align-items: center; gap: 18px; flex-shrink: 0; flex-wrap: wrap; }
/* С157 (Михаил): постоянная окантовка вокруг таймера, свечение ВНУТРЬ (inset), а не наружу
   как у кнопки "Обновить" - когда до конца сессии меньше 5 минут (см. isTimeLow). Тот же
   принцип повторён у мини-таймера в карточке шлема слева (.helm-timer). */
.timer .t {
  font-family: ui-monospace, monospace; font-size: 36px; font-weight: 300; font-variant-numeric: tabular-nums;
  min-width: 130px; text-align: center; padding: 6px 14px; border-radius: 14px;
  border: 1px solid var(--inset-border); transition: color .3s, border-color .3s, box-shadow .3s;
}
.timer .t.low { color: var(--bad); border-color: #ff6b6b; box-shadow: inset 0 0 14px rgba(255, 107, 107, .75); }
/* С157: зелёное свечение, пока игра/сессия реально идёт - в спокойном состоянии (00:00,
   шлем свободен) таймер остаётся белым/нейтральным без окантовки-акцента. */
.timer .t.active { color: var(--ok); border-color: var(--ok); box-shadow: inset 0 0 14px rgba(126, 230, 160, .75); }
.timer .meta { font-size: 11.5px; color: var(--fg-dim); line-height: 1.5; }
/* .sp — растяжка-спейсер, актуальна только когда игра не запущена (timer-running-game
   не отрендерен) и надо оттолкнуть кнопки вправо от таймера. Когда игра идёт, эту роль
   берёт на себя сам .timer-running-game (тоже flex:1), поэтому .sp там не нужен -
   ДВА элемента с flex-grow:1 в одном ряду ломали расчёт ширины третьего (баг С161:
   название игры схлопывалось в width:0, был виден только текст "Игра:" без названия). */
.timer .sp { flex: 1; }
.timer .acts { display: flex; gap: 8px; flex-shrink: 0; }
.timer-running-game {
  flex: 1 1 0%; min-width: 0; display: flex; align-items: baseline; gap: 4px;
  color: var(--fg-dim); font-size: 12.5px; white-space: nowrap;
}
.timer-running-game-title {
  display: inline-block; min-width: 0; overflow: hidden; text-overflow: ellipsis;
  color: var(--ok); font-weight: 600;
}
.running-game-play { color: var(--ok); font-size: 11px; flex-shrink: 0; }
.pill {
  /* ⭐ С188 (Михаил): «закругление сделали по-другому» - кабинет/сайт (С180) заменили полное
     скругление кнопок на var(--btn-radius); планшет отставал, здесь та же смена. */
  font-size: 11px; padding: 9px 15px; border-radius: var(--btn-radius); display: inline-flex; align-items: center; gap: 5px;
  white-space: nowrap; border: none; cursor: pointer; font-family: inherit; font-weight: 300; transition: transform .12s, filter .2s;
}
.pill:active { transform: scale(.95); }
.pill-dark { background: var(--chip); color: var(--fg); }
/* ⭐ С193: цифра непрочитанных ответов поддержки прямо на кнопке. */
.pill-badge {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 19px;
  height: 19px;
  padding: 0 6px;
  margin-left: 7px;
  border-radius: 10px;
  background: #ff6b81;
  color: #fff;
  font-size: 11.5px;
  font-weight: 700;
  line-height: 1;
}
.pill-accent { background: linear-gradient(135deg, var(--accent), var(--accent2)); color: var(--accent-fg); font-weight: 500; }
/* С152: opacity на градиентной кнопке давал "полупрозрачный оранжевый, будто верстка
   сломалась" (Михаил). Явный серый фон вместо приглушённого акцента — понятнее читается
   как "неактивно", не как визуальный баг. */
.pill:disabled { background: var(--chip); color: var(--fg-mute); opacity: 1; cursor: default; }

/* Пауза/Стоп/Трансляция — прозрачный фон + цветная окантовка вместо заливки/эмодзи (С157).
   Окантовка тоньше (1px, было 1.5px) - Михаил, живой тест на планшете, "потоньше". */
.pill-outline { background: transparent; color: #fff; font-weight: 400; border: 1px solid transparent; }
.pill-outline-orange { border-color: rgba(230, 162, 60, .5); }
.pill-outline-red { border-color: rgba(230, 90, 90, .5); }
.pill-outline-blue { border-color: rgba(78, 163, 230, .5); }
.pill-outline-green { border-color: rgba(126, 230, 160, .5); }
.pill-outline:disabled { background: transparent; border-color: rgba(255, 255, 255, .12); color: var(--fg-mute); }

.games.inset-panel { flex: 1; min-height: 0; padding: 14px 16px; display: flex; flex-direction: column; }
.games-head { display: flex; align-items: center; gap: 9px; margin-bottom: 11px; flex-wrap: wrap; flex-shrink: 0; }
.games-head h4 { font-size: 10.5px; text-transform: uppercase; letter-spacing: .06em; color: var(--fg-dim); margin: 0; white-space: nowrap; font-weight: 300; }
.games-rescan-btn { font-size: 11.5px; padding: 6px 12px; white-space: nowrap; border: 1px solid var(--inset-border); }
/* С157 (Михаил, живой тест): символ ⟳ имеет собственную baseline/высоту глифа, отличную от
   букв рядом в той же строке - визуально "проваливался" ниже текста. inline-block с
   line-height:1 убирает влияние строчной метрики шрифта на положение символа. */
.rescan-icon { display: inline-block; line-height: 1; vertical-align: middle; margin-bottom: 1px; }
/* С152 (2-й проход): нативный <select> заменён на свой Dropdown — на Safari/WebKit
   внутренний текст select добавлял собственный отступ перед стрелкой независимо от CSS,
   зазор оставался неконтролируемо большим для длинных списков (жанры). Полностью свой
   рендер даёт точный контроль над отступами на всех браузерах одинаково. */
.dropdown { position: relative; }
/* С157: .chip перестал контрастировать с фоном панели после того, как .main-panel стал
   плотнее (0.5 → 0.85, тот же приём что у попапов) - без своей окантовки дропдаун/поиск
   визуально "тонули" в потемневшем фоне. Тонкая рамка держит их видимыми независимо от
   плотности фона позади. */
.dropdown-btn {
  display: flex; align-items: center; gap: 8px; background: var(--chip); border: 1px solid var(--inset-border); border-radius: 10px;
  padding: 7px 10px; font-size: 11.5px; color: var(--fg); cursor: pointer; font-family: inherit; font-weight: 300;
}
.dropdown-genre .dropdown-btn { width: 108px; }
.dropdown-label { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; flex: 1; text-align: center; }
.dropdown-arrow { width: 10px; height: 6px; flex-shrink: 0; color: var(--fg-mute); }
.dropdown-menu {
  position: absolute; top: calc(100% + 6px); left: 0; z-index: 40; min-width: 100%;
  padding: 6px; display: flex; flex-direction: column; gap: 2px; max-height: 240px; overflow-y: auto;
}
.dropdown-item {
  background: none; border: none; text-align: center; padding: 7px 10px; border-radius: 7px; font-size: 11.5px;
  color: var(--fg); cursor: pointer; font-family: inherit; font-weight: 300; white-space: nowrap;
}
.dropdown-item:hover { background: rgba(255, 255, 255, .06); }
.dropdown-item.active { color: var(--accent); font-weight: 600; }
.search { position: relative; margin-left: auto; width: 190px; }
.search input { width: 100%; background: var(--chip); border: 1px solid var(--inset-border); border-radius: 10px; padding: 8px 10px 8px 29px; font-size: 11.5px; color: var(--fg); outline: none; font-family: inherit; font-weight: 300; }
.search .si { position: absolute; left: 9px; top: 50%; transform: translateY(-50%); font-size: 11px; opacity: .5; }

/* С159 (Михаил, живой тест x4): touch-action:pan-y СТАТИЧНО (не только динамически из JS на
   флаге двух пальцев) - это CSS-подсказка браузеру ЗАРАНЕЕ, до начала любого жеста, что скролл
   разрешён только одним пальцем по вертикали. Динамического touch-action:none (см. useLenis.js)
   самого по себе было недостаточно - JS-флаг долетает уже ПОСЛЕ touchstart, браузер успевает
   закоммититься к scroll-жесту на первом кадре второго пальца ("список то выше, то ниже дышит"
   - типичный симптом гонки JS-флага против уже начавшегося композитор-скролла). */
#gamesScroll { flex: 1; min-height: 0; overflow-y: auto; -webkit-overflow-scrolling: touch; scrollbar-width: none; padding-right: 2px; touch-action: pan-y; }
#gamesScroll::-webkit-scrollbar { display: none; }
#ggrid { display: grid; grid-template-columns: repeat(auto-fill, minmax(152px, 1fr)); gap: 16px; padding: 2px 2px 6px; }
/* С152: по образцу референса Stardog — карточка ЦЕЛИКОМ матовое стекло (padding даёт
   видимую окантовку вокруг картинки), обложка не размазана в край, подпись — отдельная
   светлая плашка внутри того же padding, не полоска во всю ширину. */
.gtile {
  position: relative; border-radius: 18px; background-color: var(--glass-tile-bg);
  backdrop-filter: var(--glass-tile-blur); -webkit-backdrop-filter: var(--glass-tile-blur); box-shadow: var(--glass-tile-shadow);
  cursor: pointer; transition: transform .15s, box-shadow .25s; padding: 8px; display: flex; flex-direction: column;
}
.gtile:active { transform: scale(.96); }
.gtile.playing { box-shadow: inset 0 1px 0 rgba(255, 255, 255, .15), 0 0 0 1.5px var(--ok), 0 5px 14px rgba(0, 0, 0, .24); }
.gtile.paused { box-shadow: inset 0 1px 0 rgba(255, 255, 255, .15), 0 0 0 1.5px var(--warn), 0 5px 14px rgba(0, 0, 0, .24); }
/* С160 (Михаил): библиотека переведена на вертикальный формат обложек (2:3) вслед за каталогом
   игр - реальные обложки теперь вертикальные постеры (Quest/Steam), квадратный .gcover их
   обрезал. Картинка идёт ДО САМОГО НИЗА карточки (border-radius на все 4 угла, не только
   верхние) - .gfoot больше не отдельный блок снизу, а плавающая стеклянная панель ПОВЕРХ
   картинки (см. .gfoot ниже, рецепт "матовое стекло" из reference-glass-canon-2026). */
.gcover { position: relative; aspect-ratio: 2 / 3; overflow: hidden; border-radius: 12px; background: var(--chip); }
/* Лонг-тап на карточке открывает "Редактировать игру" (см. GameTile.jsx) - без этих свойств
   долгое нажатие на <img> вызывает системное меню браузера "Сохранить изображение"/"Открыть
   картинку" ПОВЕРХ нашей модалки. -webkit-touch-callout убирает именно touch-and-hold меню
   (contextmenu preventDefault один не успевает на мобильном тач-событии). */
.gcover img {
  width: 100%; height: 100%; object-fit: cover; display: block;
  -webkit-touch-callout: none; -webkit-user-select: none; user-select: none;
  -webkit-user-drag: none;
}
/* С157 (Михаил): плашка жанра на плотной подложке, как у попапов (--glass-sup-bg) - была
   почти прозрачной (.55) поверх обложки игры, текст плохо читался на светлых картинках. */
.ggenre {
  position: absolute; top: 0; left: 0; z-index: 2; font-size: 8.5px; font-weight: 600; text-transform: uppercase; letter-spacing: .03em;
  color: #eef1ff; padding: 4px 8px; background: rgba(15, 15, 20, .85); backdrop-filter: blur(8px) saturate(160%); border-radius: 12px 0 10px 0;
}
.gbadge {
  position: absolute; top: 6px; right: 6px; z-index: 2; width: 22px; height: 22px; border-radius: 50%;
  display: flex; align-items: center; justify-content: center; font-size: 9px; background: rgba(15, 15, 20, .6);
  backdrop-filter: blur(8px); border: 1.5px solid transparent;
}
.gtile.playing .gbadge { border-color: var(--ok); color: var(--ok); }
.gtile.paused .gbadge { border-color: var(--warn); color: var(--warn); }
/* С160 (Михаил, живой фидбек): лёгкое чёрное затемнение снизу картинки, под стеклянной
   плашкой - ~20% высоты карточки, растворяется кверху. Само стекло .gfoot недостаточно
   контрастно на светлых обложках, текст плохо читался. */
.gshade {
  position: absolute; left: 0; right: 0; bottom: 0; height: 20%; z-index: 1;
  background: linear-gradient(to top, rgba(0, 0, 0, .55), transparent);
  pointer-events: none;
}
/* С160 (Михаил, референс-скриншот матового стекла): .gfoot переоформлен с плоского светлого
   градиента на "матовое стекло" по проверенному канону reference-glass-canon-2026 (рецепт
   B-dark, ИСПРАВЛЕННАЯ версия - плотность 0.72, НЕ полная окантовка по периметру, только тонкий
   верхний блик) - плавающая панель ПОВЕРХ картинки снизу, с воздухом от краёв карточки
   (position:absolute + inset с отступами, не впритык к краям), не сплошная плашка на всю
   ширину. Название всегда по центру плашки (С152, сохранено). */
/* ⭐⭐⭐⭐⭐ С182 (Михаил): библиотека приведена к тому же приёму, что каталог игр - подпись не
   плавающей плашкой, а полосой во ВСЮ нижнюю часть обложки. Отличие только в цвете: в
   каталоге стекло белое, здесь чёрное, под тёмный интерфейс планшета. Стекло настоящее -
   backdrop-filter размывает обложку под полосой, поэтому картинка сквозь неё просвечивает
   (заливка намеренно полупрозрачная: сделай её плотной, и смысл матового стекла пропадёт).
   Углы скруглены только снизу и повторяют форму самой обложки. */
.gfoot {
  position: absolute; left: 0; right: 0; bottom: 0; z-index: 2;
  padding: 8px 10px; display: flex; align-items: center; justify-content: center; gap: 6px;
  border-radius: 0 0 12px 12px;
  background-color: rgba(12, 12, 16, .58);
  background-image: linear-gradient(160deg, rgba(255, 255, 255, .06) 0%, rgba(255, 255, 255, .02) 45%, rgba(255, 255, 255, 0) 100%);
  backdrop-filter: blur(16px) saturate(150%);
  -webkit-backdrop-filter: blur(16px) saturate(150%);
  border-top: 1px solid rgba(255, 255, 255, .10);
}
/* Запасной вариант для движков без backdrop-filter: там прозрачная подложка дала бы
   нечитаемый текст поверх картинки - заливаем плотнее (как и в каталоге). */
@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  .gfoot { background-color: rgba(12, 12, 16, .88); }
}
/* С160 (Михаил, живой фидбек x2): базовый вес шрифта приложения 300 (тонкий) плохо читался
   поверх стекла даже после затемнения снизу - название игры важнее общего стиля, поднят до 600. */
.gname {
  font-size: 12px; font-weight: 600; color: var(--fg); overflow: hidden; text-overflow: ellipsis;
  white-space: nowrap; text-align: center;
}
.gdot { width: 6px; height: 6px; border-radius: 50%; flex-shrink: 0; background: transparent; }
.gtile.playing .gdot { background: var(--ok); box-shadow: 0 0 6px var(--ok); }
.gtile.paused .gdot { background: var(--warn); box-shadow: 0 0 6px var(--warn); }
.gempty { grid-column: 1 / -1; text-align: center; color: var(--fg-mute); font-size: 12px; padding: 30px 0; }

/* ⭐⭐⭐⭐⭐ С189 (Михаил): шкала загрузки игр под списком. Раньше во время скана (минуты
   на клуб с 40+ играми) планшет молчал, показывая «Ничего не найдено» — человек не понимал,
   ждать или чинить. flex-shrink: 0 — панель библиотеки flex-колонка со скроллом внутри,
   без этого шкалу «съело» бы при длинном списке. */
.games-scan { flex-shrink: 0; margin-top: 10px; padding-top: 10px; border-top: 1px solid var(--inset-border); }
/* flex-wrap + min-width:0 — подпись длинная («обложки, описания, названия»), на узкой панели
   она обязана переноситься, а не выдавливать счётчик за край (С189). */
.games-scan-head { display: flex; align-items: baseline; justify-content: space-between; gap: 4px 10px; margin-bottom: 6px; flex-wrap: wrap; }
.games-scan-label { font-size: 13.5px; color: var(--fg-dim); min-width: 0; }
.games-scan-count { font-size: 13.5px; color: var(--fg-mute); font-variant-numeric: tabular-nums; white-space: nowrap; }
.games-scan-track { height: 6px; border-radius: 999px; background: rgba(255, 255, 255, .1); overflow: hidden; }
.games-scan-fill {
  height: 100%; border-radius: 999px;
  background: linear-gradient(90deg, var(--accent, #8ab4ff), var(--accent2, #b89bff));
  /* Плавный рост: сервер двигает счётчик рывками (по игре за раз), без перехода полоса дёргалась бы. */
  transition: width .45s ease;
}
/* С189, фаза «шлем сканирует свои приложения»: знаменатель ещё неизвестен, поэтому вместо
   выдуманных процентов — бегущий блик. Показывает, что работа идёт, ничего не обещая. */
.games-scan-fill.indeterminate {
  width: 35%;
  transition: none;
  animation: games-scan-run 1.4s ease-in-out infinite;
}
@keyframes games-scan-run {
  0%   { margin-left: -35%; }
  100% { margin-left: 100%; }
}

/* ================= MODALS ================= */
/* С152 (2-й проход): раньше blur на .ov убирали из-за конфликта вложенных backdrop-filter —
   тогда причина оказалась в БАГЕ СБОРКИ (Vite/Lightning CSS резал backdrop-filter, см.
   project-unitvr-vite-backdropfilter-bug-s152), а не в самом наложении фильтров. После
   фикса сборки пробуем снова — Михаил прямо просит фон ЗА попапом заметно размывать, чтобы
   попап визуально отделялся от библиотеки игр позади. */
/* ⭐⭐⭐⭐⭐ С187, НАЙДЕНО АУДИТОМ ВЁРСТКИ — самая опасная находка дня. Ни у подложки, ни у
   самого окна не было ни ограничения высоты, ни прокрутки, а `html, body, #root` жёстко
   заданы overflow: hidden — то есть всё, что не поместилось, обрезалось насмерть и добраться
   до него было НЕЧЕМ. Пока окна маленькие, это незаметно; ломается там, где содержимое
   растёт от реальных данных клуба:
     • «Сеансы сегодня» — сервер отдаёт ВСЕ сеансы за день без лимита (routes-admin.js),
       карточка ≈ 60-90px. Клуб на 10 шлемов за смену даёт 40-80 сеансов → окно высотой в
       несколько тысяч пикселей, отцентрованное по вертикали: виден только СЕРЕДИНА списка,
       начало и конец недостижимы. Это сломалось бы в первый же рабочий день, на любом
       устройстве — не только на Маке.
     • «Редактировать игру» с открытой лентой найденных картинок ≈ 718px — не влезает на
       ноутбук 1366×768 при масштабе 125% (видимая высота ~614px) и на iPad в альбомной.
     • Окно телевизоров растёт от числа подключённых ТВ.
   Лечим в корне и для ВСЕХ окон сразу: подложка сама прокручивается, окно не выше экрана.
   align-items: flex-start вместо center — иначе длинное окно центрируется и уезжает вверх
   за границу экрана, откуда его не достать даже прокруткой; margin: auto возвращает
   вертикальное центрирование коротким окнам, которым места хватает.
   dvh, а не vh — на iPad/iPhone Safari vh считается от экрана БЕЗ учёта своих панелей, и
   нижняя часть окна пряталась бы под тулбаром (см. также замену vh на dvh ниже по файлу). */
.ov {
  position: fixed; inset: 0; background: var(--overlay-bg); backdrop-filter: blur(10px);
  -webkit-backdrop-filter: blur(10px); display: none; align-items: flex-start;
  justify-content: center; z-index: 50; padding: 20px;
  overflow-y: auto; overscroll-behavior: contain;
}
.ov.open { display: flex; }
/* ⚠️ ИСКЛЮЧЕНИЯ, проверены по всем 12 использованиям .ov в коде:
     .ov-cast — трансляция и каталог игр, там ребёнок .cast сам занимает весь экран (100vw/vh)
                и имеет собственную внутреннюю раскладку; ограничивать его высотой нельзя;
     .ov-clear — подборщик фона, намеренно прижат к нижнему краю (свои justify/align инлайном).
   Остальным окнам правило нужно — именно они растут от данных клуба. */
.ov:not(.ov-cast):not(.ov-clear) > * {
  margin: auto; max-height: calc(100vh - 40px); max-height: calc(100dvh - 40px); overflow-y: auto;
}
/* С156: подбор фона — задний план должен оставаться ЧИСТЫМ (без размытия/затемнения),
   чтобы Михаил в реальном времени видел, как меняется фон приложения за окошком. */
.ov-clear { background: transparent; backdrop-filter: none; -webkit-backdrop-filter: none; }
.modal { width: min(340px, 100%); border-radius: 20px; padding: 20px 22px; }
.modal-h { display: flex; align-items: center; gap: 10px; margin-bottom: 14px; position: relative; z-index: 1; }
.modal-h b { font-size: 15px; font-weight: 400; }
.modal-h .x { margin-left: auto; width: 26px; height: 26px; border-radius: 9px; border: none; background: var(--chip); color: var(--fg-dim); cursor: pointer; }
/* ⭐⭐⭐⭐ С187, найдено аудитом — та же мина, что взорвалась на кнопке поиска картинок, но
   ещё не замеченная вживую. Тарифы больше не зашиты по три (С180): владелец заводит их в
   кабинете сам, сколько захочет. При flex: 1 все кнопки насильно равной ширины, а .pill
   запрещает перенос текста — на внутренней ширине окна 296px четыре тарифа дают по 68px на
   кнопку, тогда как «120 минут» требует ~79px. То есть уже на ЧЕТЫРЁХ тарифах текст полезет
   за рамку даже на Android, а на широком шрифте Mac — на трёх.
   flex: 1 1 auto — кнопки могут быть разной ширины по своему тексту; flex-wrap — если в строку
   не помещаются, переносятся вниз, а не сплющиваются. */
.tariff-list { display: flex; gap: 8px; flex-wrap: wrap; }
.tariff-btn { flex: 1 1 auto; justify-content: center; padding: 12px; }

/* С156: панель живого подбора фона — квадрат-радуга (насыщенность/яркость) + hue-полоса,
   как в Photoshop, один выбранный цвет красит все 4 угла сразу через инлайновую --scene
   прямо на странице — код в theme.css трогается только после того, как Михаил назовёт
   итоговое значение из текстового поля (кнопка "Сохранить этот цвет"). */
.scene-tuner { width: min(360px, 100%); touch-action: none; }
.scene-tuner-sb {
  position: relative; width: 100%; height: 160px; border-radius: 12px; margin-bottom: 12px;
  cursor: crosshair; touch-action: none; overflow: hidden;
}
.scene-tuner-sb-cursor {
  position: absolute; width: 18px; height: 18px; border-radius: 50%; border: 2px solid #fff;
  box-shadow: 0 0 0 1px rgba(0,0,0,.4), 0 2px 6px rgba(0,0,0,.4);
  transform: translate(-50%, -50%); pointer-events: none;
}
.scene-tuner-hue {
  position: relative; width: 100%; height: 20px; border-radius: 10px; margin-bottom: 16px;
  cursor: pointer; touch-action: none;
  background: linear-gradient(to right, #f00, #ff0, #0f0, #0ff, #00f, #f0f, #f00);
}
.scene-tuner-hue-cursor {
  position: absolute; top: 50%; width: 22px; height: 22px; border-radius: 50%; border: 2px solid #fff;
  background: transparent; box-shadow: 0 0 0 1px rgba(0,0,0,.4), 0 2px 6px rgba(0,0,0,.4);
  transform: translate(-50%, -50%); pointer-events: none;
}
.scene-tuner-row { display: flex; align-items: center; gap: 10px; margin-bottom: 10px; }
.scene-tuner-row-label { width: 150px; flex-shrink: 0; font-size: 12.5px; color: var(--fg-dim); }
.scene-tuner-row input[type="range"] { flex: 1; min-width: 0; }
.scene-tuner-preview {
  width: 100%; height: 28px; border-radius: 10px; margin-bottom: 14px;
  border: 1px solid var(--inset-border);
}
.scene-tuner-saved { background: var(--ok) !important; color: #0d2b16 !important; }

/* С158: попап "Настройки" по центру экрана (заменил прежнее выпадающее dev-menu) — шире
   стандартного .modal, т.к. внутри список мелодий в 2 колонки. С158 живой тест (Михаил):
   вкладки-аккордеон по образцу AURORA (свёрнуты по умолчанию), плюс попап оказался узковат -
   4 кнопки мелодий вылезали за край, расширил и убавил их шрифт отдельно от общего. */
.settings-modal { width: min(500px, 100%); }
.settings-modal .dev-menu-theme { width: auto; }
.settings-num-input {
  width: 70px; flex-shrink: 0; padding: 6px 10px; border-radius: 8px;
  border: 1px solid var(--inset-border); background: var(--chip); color: var(--fg);
  font-size: 13px; text-align: center;
}
.settings-melody-list { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; flex: 1; min-width: 0; }
/* ⭐⭐⭐ С187: nowrap здесь уже приводил к тому, что названия мелодий вылезали за край (С158) —
   тогда вылечили расширением окна, то есть симптом, а не причину; на широком шрифте Mac
   вернулось бы снова. Разрешаем перенос: длинное название встанет в две строки. */
.settings-melody-btn { justify-content: center; font-size: 10.5px; padding: 8px 6px; white-space: normal; line-height: 1.25; }

.settings-section { border-radius: 12px; overflow: hidden; margin-bottom: 8px; background: var(--chip); }
.settings-section-h {
  width: 100%; display: flex; align-items: center; justify-content: space-between;
  padding: 12px 14px; border: none; background: transparent; color: var(--fg);
  font-size: 13.5px; cursor: pointer; text-align: center;
}
.settings-section-arrow { transition: transform .18s ease; color: var(--fg-dim); font-size: 11px; }
.settings-section-arrow.open { transform: rotate(180deg); }
/* ⭐ С200 (Михаил, по скриншоту настроек): ЕДИНЫЙ вид всех пунктов. Раньше строки верстались
   тремя разными способами, и «Выручка сегодня» выглядела чужеродно рядом с «Темой оформления».
   Правило одно: название слева, управление справа, пояснение — строкой ниже. */
.settings-row-wrap { padding: 10px 0; border-bottom: 1px solid rgba(255,255,255,.06); }
.settings-row-wrap:last-child { border-bottom: none; }
.settings-row { display: flex; align-items: center; justify-content: space-between; gap: 14px; min-height: 38px; }
.settings-row-label { font-size: 15px; color: var(--fg); }
.settings-row-control { flex: 0 0 auto; display: flex; align-items: center; gap: 8px; }
.settings-row-value { font-size: 17px; font-weight: 600; color: var(--fg); }
.settings-hint { margin-top: 6px; font-size: 13px; line-height: 1.45; color: var(--fg-mute); max-width: 46ch; }
.settings-section-body { padding: 0 14px 14px; }

/* С158 (Михаил, живой тест): вкл/выкл звука — стеклянный ползунок вместо плоской кнопки-пилюли,
   тот же матовый язык, что и у остальных "утопленных" элементов UnitVR (.inset-panel/.chip-tag -
   var(--inset-bg)/var(--inset-border)), бегунок светится акцентным градиентом при "включено"
   (как .pill-accent), а не плоским зелёным - чтобы не выбиваться из общей стеклянной эстетики. */
.settings-toggle {
  position: relative; width: 44px; height: 26px; flex-shrink: 0; border-radius: 14px;
  background: var(--inset-bg); border: 1px solid var(--inset-border);
  box-shadow: inset 0 1px 3px rgba(0,0,0,.25); cursor: pointer;
  padding: 0; transition: border-color .18s ease, box-shadow .18s ease;
}
.settings-toggle.on { border-color: rgba(138,180,255,.5); box-shadow: inset 0 1px 3px rgba(0,0,0,.15), 0 0 10px rgba(138,180,255,.25); }
.settings-toggle-knob {
  position: absolute; top: 2px; left: 2px; width: 20px; height: 20px; border-radius: 50%;
  background: rgba(255,255,255,.55); backdrop-filter: blur(6px); -webkit-backdrop-filter: blur(6px);
  box-shadow: 0 1px 3px rgba(0,0,0,.35); transition: transform .18s ease, background .18s ease;
}
.settings-toggle.on .settings-toggle-knob {
  transform: translateX(18px);
  background: linear-gradient(135deg, var(--accent), var(--accent2));
}

/* С152: доработка модалки тарифа по запросу Михаила — крупная плашка с числом минут и
   ручной корректировкой (+/- 5 мин), явная кнопка "Начать" вместо мгновенного запуска
   по клику на пресет (пресет теперь только ВЫДЕЛЯЕТ тариф, не запускает игру). */
.tariff-custom {
  margin-top: 14px; padding: 18px; display: flex; align-items: center; justify-content: space-between; gap: 16px;
}
.tariff-step {
  width: 44px; height: 44px; border-radius: 14px; border: none; background: var(--chip); color: var(--fg);
  font-size: 22px; font-weight: 300; cursor: pointer; flex-shrink: 0; transition: transform .12s, background .2s;
}
.tariff-step:active:not(:disabled) { transform: scale(.92); }
.tariff-step:disabled { opacity: .3; cursor: default; }
.tariff-value { flex: 1; display: flex; flex-direction: column; align-items: center; gap: 2px; }
.tariff-num { font-size: 40px; font-weight: 300; font-variant-numeric: tabular-nums; line-height: 1; color: var(--fg); }
.tariff-unit { font-size: 11px; color: var(--fg-dim); text-transform: uppercase; letter-spacing: .04em; }
.tariff-start { width: 100%; justify-content: center; padding: 14px; margin-top: 16px; font-size: 14px; }

/* С152: экран с кодом привязки нового шлема — крупная моноширинная строка, легко
   считывается с расстояния при вводе через VR-контроллер (медленный процесс). */
.pairing-result { display: flex; flex-direction: column; align-items: center; gap: 14px; padding: 8px 0; }
.pairing-code {
  font-family: ui-monospace, monospace; font-size: 40px; font-weight: 600; letter-spacing: 6px;
  color: var(--accent); background: var(--inset-bg); border: 1px solid var(--inset-border);
  border-radius: 12px; padding: 16px 24px; width: 100%; text-align: center;
}
.pairing-hint { font-size: 12.5px; color: var(--fg-dim); line-height: 1.5; text-align: center; }
/* ⭐ С178 (Михаил): ссылка на сайт для ввода на телевизоре терялась в приглушённом сером
   тексте подсказки - выделена белым и жирным, чтобы бросалась в глаза издалека (оператор
   читает её на планшете, потом набирает на пульте ТВ). */
.pairing-link { color: var(--fg); font-weight: 700; }
/* ⭐ С178 (Михаил): плашка с кодом ТВ вместо простого текста "Код для телевизора: XXXX" -
   крупно, в фирменном градиенте акцента, без подписи (понятно по контексту экрана). */
.tv-code-plate {
  margin: 14px 0; padding: 18px 24px; border-radius: 16px; text-align: center;
  background: linear-gradient(135deg, var(--accent), var(--accent2)); color: var(--accent-fg);
  font-family: ui-monospace, monospace; font-size: 32px; font-weight: 700; letter-spacing: .12em;
}

/* С166 (Михаил, живой фидбек): модалка "Добавить шлем" стала шире (по образцу .settings-modal) -
   раньше "Название" и "Код" были двумя одинаковыми .login-input друг под другом без зазора,
   сливались визуально. Название/код теперь в 2 колонки, каждое поле с подписью сверху. Обе
   окантовки акцентные (одинаковые) - первая версия делала окантовку только у кода, Михаил
   попросил унифицировать, не оставлять код визуально "особенным" относительно названия. */
.add-helmet-modal { width: min(500px, 100%); }
.add-helmet-fields { display: grid; grid-template-columns: 1fr 1.1fr; gap: 12px; margin-top: 18px; margin-bottom: 14px; }
.add-helmet-field { display: flex; flex-direction: column; gap: 6px; min-width: 0; }
.add-helmet-label { font-size: 12px; color: var(--fg); font-weight: 500; text-transform: uppercase; letter-spacing: .04em; }
.add-helmet-fields .login-input { border-color: rgba(138, 180, 255, .35); }
.add-helmet-fields .login-input:focus { border-color: rgba(138, 180, 255, .6); }
.add-helmet-code-input {
  font-family: ui-monospace, monospace; font-size: 16px; letter-spacing: .18em; text-align: center;
  text-transform: uppercase; color: var(--accent);
}
.add-helmet-code-input::placeholder { color: var(--fg-dim); letter-spacing: .18em; }
@media (max-width: 420px) {
  .add-helmet-fields { grid-template-columns: 1fr; }
}
/* С160 (Михаил): дублирует .helm-pairing на самой карточке - код виден и в меню долгого тапа
   (заголовок), не только на карточке в списке. */
.helmet-pairing-hint { font-size: 13px; color: var(--fg-dim); margin-bottom: 14px; }
.helmet-pairing-code { font-family: ui-monospace, monospace; color: var(--fg); font-size: 15px; letter-spacing: .02em; }

/* С160 (Михаил, живой фидбек): попап "Редактировать игру" переоформлен на горизонтальную
   раскладку - слева обложка+поиск картинок фиксированной ширины, справа поля текста на всю
   оставшуюся ширину. Свой класс, не трогает базовый .modal (используется другими попапами,
   которым узкая вертикальная форма подходит - смена ширины у него сломала бы верстку там). */
/* ⭐⭐⭐⭐⭐ С182 (Михаил): окно расширено - «описание сделать более широкое окно, чтобы текст
   больше помещался». 560px хватало на два поля и две строки описания, из-за чего подтянутый
   из магазина текст приходилось растягивать вручную. */
/* С182, вторая правка ширины (Михаил): «текст на кнопке не вмещается - сделай попап пошире,
   тогда и обложка опустится вровень с полем описания». 900px + колонка 220px: подпись
   «Посмотреть доступные картинки» умещается в одну строку, а обложка при большей ширине
   становится выше и её низ приходится примерно на низ поля описания. */
.edit-game-modal { width: min(900px, 100%); }
/* Действия переехали в шапку: главное - «Сохранить», рядом переключатель «Популярная» и
   «Скрыть на шлеме», крестик остаётся крайним справа. */
.edit-game-head-actions { margin-left: auto; display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.edit-game-head-actions .x { margin-left: 2px; }
/* ⭐ С182 (Михаил: «Сохранить и Популярная разной высоты - выровнять»). Причина была в рамке:
   базовая .pill идёт БЕЗ границы, а контурные варианты с границей в 1px - отсюда разница в
   два пикселя. Даём всем кнопкам шапки одинаковую рамку (у акцентной прозрачную) и общую
   высоту, чтобы они стояли ровно независимо от вида. */
.edit-game-head-btn {
  padding: 0 14px; font-size: 12px; height: 34px;
  border: 1px solid transparent; box-sizing: border-box; line-height: 1;
}
/* Отметка «Популярная»: вне отметки - серая и незаметная, при отметке - розовая, тем же
   цветом, что бейдж на плитке каталога (см. .catalog-tile-hot-badge). Оператор видит связь
   между кнопкой здесь и меткой там, объяснять ничего не нужно. */
.edit-game-pop-off { color: var(--fg-mute); border-color: var(--inset-border); }
.edit-game-pop-on {
  background: #e8467c; color: #fff; border: 1px solid #e8467c;
  box-shadow: 0 4px 12px rgba(232, 70, 124, .35);
}
/* ⭐⭐⭐⭐ С182 (Михаил): «пусть ссылка на трейлер будет вровень с кнопкой посмотреть картинки -
   тогда всё симметрично, а превью выстраиваются по низу». Колонки тянутся на одну высоту,
   кнопка поиска прижата к низу левой - обе колонки заканчиваются на одной линии, под ними
   идёт лента найденных картинок во всю ширину окна. */
/* ⛔⭐⭐⭐⭐⭐ С182, ТРЕТЬЯ ПОПЫТКА - и первая надёжная (Михаил: «кнопка всё так же не вровень, а
   на макбуке она ниже, чем на планшете»). Разное поведение на разных экранах и было
   подсказкой: выравнивание через flex зависело от того, какой колонке хватило высоты, а
   высота зависит от ширины экрана, длины описания и переносов текста. Подгонять отступами
   такое бесполезно - что сходится на планшете, разъезжается на ноутбуке.
   Решение: СЕТКА из двух колонок и двух строк. Обложка и поля - верхняя строка, кнопка
   поиска картинок и поле ссылки на трейлер - НИЖНЯЯ, одна на двоих. Лежащие в одной строке
   сетки элементы физически не могут разъехаться по вертикали, какой бы ни была высота
   соседа: align-items: end прижимает оба к нижней границе строки. */
.edit-game-layout {
  display: grid; grid-template-columns: 220px 1fr; gap: 12px 18px; align-items: stretch;
}
.edit-game-left { display: flex; flex-direction: column; }
/* Нижняя строка сетки: слева кнопка, справа ссылка на трейлер - выровнены по нижнему краю. */
.edit-game-bottom-left { grid-column: 1; align-self: end; }
.edit-game-bottom-right { grid-column: 2; align-self: end; }
.edit-game-bottom-right .edit-game-field { margin-bottom: 0; }
/* Кнопка стоит вровень с последним полем правой колонки (ссылка на трейлер): та же высота,
   то же скругление, что у полей ввода - раньше это была капсула, и рядом с прямоугольным
   полем она выбивалась (Михаил, С182). Нижний отступ последнего поля снят, иначе правая
   колонка на 12px длиннее и кнопка уезжает ниже. */
/* ⭐ С182 (Михаил: «окантовка кнопки выше, чем у трейлера»). Высота поля ввода складывается
   из отступов и строки текста: 12px сверху + ~17px строка + 12px снизу + рамка = 43px.
   Задаём кнопке ровно её, чтобы рамки совпали пиксель в пиксель, а не приблизительно. */
/* ⭐⭐⭐⭐⭐ С187 (Михаил, живой случай на iMac): «текст вылезал за границы кнопки».
   Корень: базовый класс .pill задаёт white-space: nowrap, а здесь стояла ЖЁСТКАЯ height: 43px
   при ширине во всю колонку. Пока шрифт узкий (Android-планшет, Windows), длинная надпись
   «Посмотреть доступные картинки» помещалась впритык; на macOS системный шрифт заметно шире —
   строка перестаёт влезать и, раз переносить запрещено и обрезки нет, вылезает наружу.
   Подгонять размер шрифта под каждый экран бессмысленно: на следующем устройстве разъедется
   снова. Разрешаем перенос и делаем высоту минимальной, а не жёсткой — выравнивание по низу
   колонки (align-self: end у .edit-game-bottom-left) сохраняется, а кнопка при нехватке места
   становится двустрочной вместо того, чтобы ломать вёрстку.
   ⚠️ Ровно та же связка «nowrap + фиксированная height + длинный русский текст» опасна везде,
   где .pill растягивают на всю ширину контейнера — при правках держать это в голове. */
.edit-game-search-btn {
  margin-top: auto; border-radius: 10px; min-height: 43px; height: auto; justify-content: center;
  font-size: 12px; padding: 6px 10px; text-align: center; line-height: 1.2;
  white-space: normal;
}
.edit-game-right .edit-game-field:last-of-type { margin-bottom: 0; }
/* ⭐⭐⭐⭐ С182 (Михаил: «поле описания сузилось, сделай его вровень с картинкой по нижней
   части»). Правая ячейка верхней строки тянется на высоту обложки, а описание занимает в ней
   всё, что осталось после названия и жанра - низ поля приходится ровно на низ обложки при
   любой высоте картинки. Раньше поле было фиксированной высоты, и справа зияла пустота. */
.edit-game-right { min-width: 0; display: flex; flex-direction: column; }
/* ⚠️ Специфичность та же, что у .edit-game-field ниже, поэтому там побеждало БОЛЕЕ ПОЗДНЕЕ
   правило с display:block - и поле описания не растягивалось, сколько ему ни задавай flex.
   Тот же класс ошибки, что был с кнопкой поиска картинок. Поднимаем вес селектором
   родителя: теперь правило выигрывает независимо от порядка в файле. */
.edit-game-right .edit-game-field-desc {
  flex: 1; display: flex; flex-direction: column; min-height: 0; margin-bottom: 0;
}
.edit-game-right .edit-game-field-desc .edit-game-textarea { flex: 1; min-height: 120px; }

/* Лейбл названия поля НАД самим полем - раньше единственной подсказкой был placeholder внутри
   поля, который исчезает после заполнения (значение уже не видно, что это за поле). */
.edit-game-field { display: block; margin-bottom: 12px; }
/* С182: пара полей в одну строку (название | жанр). На узком экране переносятся сами -
   flex-wrap оставляет форму рабочей и на телефоне владельца, не только на планшете. */
.edit-game-row { display: flex; gap: 12px; flex-wrap: wrap; }
.edit-game-field-half { flex: 1 1 220px; min-width: 0; }
.edit-game-label {
  display: block; font-size: 11px; font-weight: 600; text-transform: uppercase;
  letter-spacing: .04em; color: var(--fg-dim); margin-bottom: 5px;
}

/* С154: лонг-тап на карточке игры -> "Редактировать игру" (переименовать/заменить обложку,
   см. ТЗ про пиратские копии с битыми иконками). Превью кликабельно целиком.
   С160 (Михаил): квадрат -> вертикальный формат 2:3, вслед за реальными обложками (Quest/Steam
   постеры теперь вертикальные, не квадратные) - предпросмотр в форме редактирования должен
   показывать ту же пропорцию, что реально будет в библиотеке/каталоге. */
/* ⛔ С182 (Михаил: «при нажатии посмотреть картинки обложка сначала уменьшается, потом снова
   становится большой»). Левая колонка - flex-контейнер, а в нём элементам по умолчанию
   разрешено ужиматься: пока грузились картинки, колонка становилась выше, и обложка честно
   сжималась, а потом возвращалась. flex-shrink: 0 запрещает ей меняться в размере. */
.edit-game-cover {
  position: relative; width: 100%; aspect-ratio: 2 / 3; border-radius: 14px; overflow: hidden;
  flex-shrink: 0;
  background: var(--inset-bg); border: 1px solid var(--inset-border); cursor: pointer;
  display: flex; align-items: center; justify-content: center;
  /* С182: нижний отступ убран - он был нужен, когда под обложкой стояла кнопка поиска
     картинок. Кнопка переехала в нижнюю строку сетки, и этот запас мешал низу обложки
     совпасть с низом поля описания. */
  margin-bottom: 0;
}
.edit-game-cover img { width: 100%; height: 100%; object-fit: cover; }
.edit-game-cover-empty { font-size: 12px; color: var(--fg-dim); }
/* ⭐ С182 (Михаил): «белый шрифт теряется на светлых обложках - добавь тени». Одного
   градиента снизу мало: у ярких картинок низ бывает почти белым. Тень под самими буквами
   отделяет текст от любого фона, а градиент сделан плотнее. */
.edit-game-cover-hint {
  position: absolute; left: 0; right: 0; bottom: 0; padding: 8px; text-align: center;
  font-size: 11px; color: #fff; background: linear-gradient(transparent, rgba(0, 0, 0, .82));
  text-shadow: 0 1px 3px rgba(0, 0, 0, .95), 0 0 8px rgba(0, 0, 0, .7);
}
/* С158: описание игры (модуль «Каталог игр») — многострочное поле поверх общего .login-input. */
/* С182: описание - главное поле этого окна, ему и место. Подтянутый из магазина Meta текст
   обычно 400-700 знаков: при прежних 64px он показывался двумя строками с прокруткой. */
.edit-game-textarea { resize: vertical; min-height: 190px; font-family: inherit; width: 100%; line-height: 1.5; }
/* Заголовок поля с кнопкой «Подтянуть» в одной строке - действие рядом с тем, на что влияет. */
.edit-game-label-row { display: flex; align-items: center; justify-content: space-between; gap: 10px; }
.edit-game-inline-btn {
  border: 1px solid var(--inset-border); background: var(--chip); color: var(--fg-dim);
  border-radius: 8px; padding: 3px 10px; font-size: 11px; font-weight: 600; cursor: pointer;
  font-family: inherit;
}
.edit-game-inline-btn:disabled { opacity: .6; cursor: default; }
.edit-game-warn {
  font-size: 11.5px; line-height: 1.4; color: var(--warn); background: rgba(255, 209, 128, .1);
  border: 1px solid rgba(255, 209, 128, .25); border-radius: 8px; padding: 8px 10px; margin-top: 8px;
}
/* С160 (Михаил): "посмотреть доступные картинки" — автопоиск обложки в интернете (SearXNG)
   вместо ручной загрузки файла, для игр без автообложки со Steam/Quest. */
/* ⚠️ С182: правило кнопки поиска картинок теперь ОДНО (см. .edit-game-search-btn выше).
   Здесь оставлена только ширина: прежний margin-bottom перебивал новое выравнивание по низу
   колонки - кнопка не вставала вровень с полем ссылки на трейлер, сколько её ни правь. */
.edit-game-search-btn { width: 100%; }
.edit-game-actions { display: flex; gap: 10px; flex-wrap: wrap; }
/* ⭐⭐⭐⭐ С182 (Михаил): «раз кнопки уехали наверх, картинки пусть выстраиваются в ряд по
   горизонтали - так и красивее». Раньше это была сетка 3 в ряд, которая росла вниз и тянула
   окно: при десятке вариантов приходилось прокручивать всю форму. Теперь одна лента с
   горизонтальной прокруткой - высота окна не меняется, сколько бы картинок ни нашлось.
   Полосу прокрутки прячем: на планшете листают пальцем, полоса только съедает высоту. */
.edit-game-image-grid {
  display: flex; gap: 8px; margin-bottom: 14px; overflow-x: auto; padding-bottom: 2px;
  scrollbar-width: none;
}
.edit-game-image-grid::-webkit-scrollbar { display: none; }
.edit-game-image-option {
  position: relative; aspect-ratio: 2 / 3; width: 104px; flex: 0 0 auto;
  border-radius: 10px; overflow: hidden; padding: 0;
  border: 1px solid var(--inset-border); cursor: pointer; background: var(--inset-bg);
}
.edit-game-image-option img { width: 100%; height: 100%; object-fit: cover; }
.edit-game-image-option:active { transform: scale(.96); }
.edit-game-image-applying {
  position: absolute; inset: 0; display: flex; align-items: center; justify-content: center;
  background: rgba(0, 0, 0, .6); color: #fff; font-size: 10.5px; text-align: center; padding: 6px;
}

/* С156 (Михаил): окно трансляции — во весь экран планшета БЕЗ зазоров, шапка
   (название/статус/свернуть/крестик) остаётся, видео занимает весь остаток. */
.ov-cast { padding: 0; }
/* С187: dvh вместо vh — в Safari на iPad/iPhone vh считается от экрана БЕЗ учёта собственных
   панелей браузера, поэтому низ полноэкранного окна (трансляция, каталог игр) уходил под
   тулбар. Строка с vh оставлена выше как запасная для браузеров, не знающих dvh. */
.cast { width: 100vw; height: 100vh; height: 100dvh; border-radius: 0; overflow: hidden; display: flex; flex-direction: column; will-change: transform; }
/* С159 (Михаил, живой тест x3): каталог игр — панель едет за пальцем 1:1 во время драга
   (transform инлайном, БЕЗ transition), доводится плавно только на отпускание (.catalog-anim
   на ov). Убрано затемнение фона (opacity на .ov) — Михаил попросил "ровно два экрана выезжают",
   без фейда поверх. И ease-out, и предыдущая cubic-bezier(.4,0,.2,1) имеют выраженный пик
   ускорения/торможения У ОДНОГО из концов кривой — на короткой дистанции (палец отпустил
   близко к границе) это читалось как "тормозит, потом рывок" в ОДНОМ направлении, но не в
   другом (потому что открытие и закрытие стартуют с разных точек кривой). linear убирает
   любую асимметрию между направлениями - скорость постоянна на всей дистанции, доводка
   выглядит ОДИНАКОВО что влево, что вправо, независимо от того, где палец её отпустил. */
.ov-cast.catalog-anim .cast { transition: transform .3s linear; }

/* С156: свёрнутый режим — живое мини-превью в правом нижнем углу, соединение НЕ рвётся,
   администратор управляет играми и разворачивает обратно кликом по мини-окну. */
.ov-cast-min { background: transparent; backdrop-filter: none; -webkit-backdrop-filter: none; pointer-events: none; align-items: flex-end; justify-content: flex-end; padding: 14px; }
.ov-cast-min .cast { width: 300px; height: 190px; border-radius: 14px; pointer-events: auto; cursor: pointer; box-shadow: 0 10px 30px rgba(0,0,0,.5); }
.ov-cast-min .cast-h { padding: 6px 10px; }
.ov-cast-min .cast-h b { font-size: 11px; }
.ov-cast-min .cast-status { display: none; }
.ov-cast-min .cast-min-btn { display: none; }
/* С156 (Михаил): кнопки окна — как в Windows, рядом в правом углу шапки: "Свернуть"
   (серая скруглённая окантовка) и "Закрыть" (красная). Текстовые пилюли, не иконки. */
/* ⭐⭐⭐⭐⭐ С174 (решение Михаила): выбор цели трансляции - планшет или телевизор. Стекло с
   размытием, как остальные всплывающие панели планшета (та же линия, что .cast-tv-menu). */
.cast-pick-wrap { position: relative; display: inline-block; }
/* Прозрачная подложка на весь экран: тап мимо меню закрывает его, как привычно на планшете. */
.cast-pick-backdrop { position: fixed; inset: 0; z-index: 20; }
/* Раскрывается ВНИЗ (С174, живой тест Михаила: вверх панель обрезалась краем экрана -
   кнопка стоит в верхней части карточки, места над ней нет).
   ⭐ С174: ширина по содержимому и прижата к ПРАВОМУ краю кнопки - при min-width панель
   уходила за пределы экрана (надписи стали короткими: «Планшет» / «Телевизор»).
   Фон СВЕТЛЕЕ окружения: на тёмной теме чёрная панель на чёрном фоне почти не читалась. */
.cast-pick {
  position: absolute; top: calc(100% + 10px); right: 0;
  width: max-content; max-width: 92vw; padding: 12px; border-radius: 18px; z-index: 21;
  display: flex; flex-direction: column; gap: 12px;
  /* ⭐ С174 (Михаил): тёмная панель + СВЕТЛАЯ ОКАНТОВКА по контуру - светлый фон не подошёл,
     выбивался из стиля. Форма читается за счёт рамки, а не заливки. */
  background: rgba(10, 12, 18, .97);
  border: 1.5px solid rgba(255, 255, 255, .28);
  box-shadow: 0 18px 44px rgba(0, 0, 0, .55);
}
/* ⭐ С174 (Михаил): каждая кнопка в СВОЕЙ белой окантовке - как кнопка «Трансляция». Идёт
   показ на эту цель - окантовка и текст становятся оранжевыми. Разделители не нужны:
   кнопки и так разделены рамками. */
/* Толщина окантовки 1px - ровно как у индикатора «Связь с сервером» (.conn-badge, С174:
   прямое указание Михаила взять его за образец). */
/* Размер как у обычных кнопок планшета (.pill: шрифт 11, отступы 9/15) - С174, Михаил:
   «сделай по высоте пониже, примерно как кнопка Начать сессию, а то крупные выделяются». */
.cast-pick-item {
  display: flex; align-items: center; gap: 8px;
  padding: 9px 15px; border-radius: var(--btn-radius); background: transparent;
  border: 1px solid rgba(255, 255, 255, .5); color: #fff;
  font-size: 11px; font-weight: 300; text-align: left; cursor: pointer;
  white-space: nowrap; transition: background .15s, border-color .15s, color .15s, box-shadow .15s;
}
.cast-pick-item:active { background: rgba(255, 255, 255, .1); }
/* ⭐ С174, ВТОРОЙ заход (флекс-центрирование не помогло, живой тест: «иконка всё так же
   опущена ниже надписи»). У эмодзи собственная метрика внутри глифа - шрифт рисует его со
   смещением вниз, и выравнивание контейнера этого не лечит. Сдвигаем сам знак вверх. */
.cast-pick-ic {
  font-size: 14px; width: 18px; flex-shrink: 0; line-height: 1;
  display: inline-block; text-align: center;
  position: relative; top: -1px;
}
/* Тот же значок внутри самой кнопки «Трансляция» - там он тоже садился ниже надписи. */
.cast-pick-ic-inline { width: auto; margin-left: 4px; }
/* Активная цель - тот же приём, что у зелёного индикатора связи: полупрозрачная заливка
   цветом + рамка в цвет + мягкое свечение внутрь и наружу. Только оранжевое. */
.cast-pick-item.on {
  border-color: rgba(255, 154, 77, .75);
  color: #ff9a4d;
  background: rgba(255, 154, 77, .12);
  box-shadow: inset 0 0 12px rgba(255, 154, 77, .28), 0 0 10px rgba(255, 154, 77, .3);
}
.pill-cast-active { border-color: rgba(255, 154, 77, .9) !important; color: #ff9a4d !important; background: rgba(255, 154, 77, .12) !important; }

.cast-btn { padding: 6px 16px; border-radius: 999px; background: transparent; font-size: 12.5px; font-weight: 500; cursor: pointer; transition: background .15s; }
/* С173: контейнер кнопки «На телевизор» + выпадающий список ТВ. margin-left:auto здесь
   (не на cast-btn-min, как раньше) - эта кнопка в разметке теперь идёт первой из правого
   блока, .cast-btn-min остаётся сразу после неё без своего отступа. */
.cast-tv-wrap { position: relative; margin-left: auto; }
.cast-tv-wrap .cast-btn { border: 1.5px solid rgba(125, 200, 255, .5); color: #7dc8ff; }
.cast-tv-wrap .cast-btn:active { background: rgba(125, 200, 255, .12); }
/* ⭐⭐⭐⭐⭐ С174 (Михаил): «если трансляция запущена - горит оранжевым, не запущена - не горит».
   Оранжевый = «сейчас идёт наружу, в зал», видно боковым зрением, не спутать с обычной кнопкой. */
.cast-tv-wrap .cast-btn.cast-btn-active { border-color: rgba(255, 154, 77, .9); color: #ff9a4d; background: rgba(255, 154, 77, .12); }
.cast-tv-wrap .cast-btn.cast-btn-active:active { background: rgba(255, 154, 77, .22); }
.cast-tv-menu { position: absolute; top: calc(100% + 8px); right: 0; min-width: 220px; max-width: 280px; padding: 8px; border-radius: 14px; z-index: 5; display: flex; flex-direction: column; gap: 4px; }
.cast-tv-item { padding: 10px 14px; border-radius: 10px; border: none; background: transparent; color: var(--fg); font-size: 13px; text-align: left; cursor: pointer; }
.cast-tv-item:active { background: rgba(255, 255, 255, .08); }
.cast-tv-empty { padding: 10px 12px; font-size: 12px; color: var(--fg-dim); line-height: 1.5; }
.ov-cast-min .cast-tv-wrap { display: none; }
.cast-btn-min { border: 1.5px solid rgba(255, 255, 255, .32); color: var(--fg-dim); }
.cast-btn-min:active { background: rgba(255, 255, 255, .08); }
/* ⭐⭐⭐⭐⭐ С174 (Михаил: «три кнопки с разными расстояниями - нужно их всех выровнять»):
   свой margin-left был ТОЛЬКО у этой кнопки, из-за чего зазор «Свернуть → Закрыть» отличался
   от «Телевизор → Свернуть». Расстояние теперь задаёт единый gap контейнера .cast-h. */
.cast-btn-close { border: 1.5px solid rgba(255, 82, 95, .75); color: #ff8d99; }
.cast-btn-close:active { background: rgba(255, 82, 95, .12); }
.ov-cast-min .cast-btn { display: none; }
.cast-h { display: flex; align-items: center; gap: 10px; padding: 14px 16px; border-bottom: 1px solid rgba(255, 255, 255, .06); position: relative; z-index: 1; }
.cast-h b { font-size: 14px; font-weight: 400; }
.cast-status { font-size: 11px; color: var(--fg-dim); margin-left: 6px; }
/* ⭐⭐⭐⭐⭐ С180 (Михаил): таймер сессии по центру верхней полосы окна трансляции. Центр берём
   не флексом (слева заголовок, справа кнопки - их ширина плавает), а абсолютным позиционированием
   от середины: тогда цифры стоят ровно посередине независимо от длины имени шлема. Цвета и
   моноширинные цифры - как у главного таймера, чтобы читалось одинаково. */
.cast-timer {
  position: absolute; left: 50%; transform: translateX(-50%);
  font-size: 17px; font-weight: 700; letter-spacing: .02em;
  font-variant-numeric: tabular-nums; color: #45e08a; pointer-events: none;
}
.cast-timer.low { color: #ff5a5a; }
.ov-cast-min .cast-timer { display: none; }   /* в свёрнутом окошке места нет */
.cast-h .x { margin-left: auto; width: 26px; height: 26px; border-radius: 9px; border: none; background: var(--chip); color: var(--fg-dim); cursor: pointer; }
.cast-body { position: relative; flex: 1; min-height: 0; background: #000; display: flex; align-items: center; justify-content: center; }
.cast-body video { width: 100%; height: 100%; object-fit: contain; background: #000; }
.cast-hint { position: absolute; inset: 0; display: flex; align-items: center; justify-content: center; text-align: center; padding: 24px; font-size: 12.5px; color: var(--fg-dim); pointer-events: none; }
/* С158: попап «Каталог игр» — тот же полноэкранный .cast/.cast-h/.cast-hint контейнер, что
   у трансляции (см. выше), но со скроллящейся сеткой карточек вместо видео-элемента.
   С160 (Михаил): переоформлен как отдельная витрина, не техническое меню - свой заголовок
   (не общий .cast-h - тот делят другие попапы вроде трансляции, которым кнопка "Закрыть"
   нужна), фирменный радиальный фон вместо плоского чёрного (тот же приём, что в остальном
   UnitVR - см. --bg-glow в App.jsx/main-panel), просвечивает в зазорах между карточками. */
.catalog-cast { position: relative; }
.catalog-cast::before {
  content: ""; position: absolute; inset: 0; z-index: 0; pointer-events: none;
  background:
    radial-gradient(ellipse 60% 40% at 15% 0%, rgba(255, 154, 77, .16), transparent 60%),
    radial-gradient(ellipse 60% 45% at 100% 30%, rgba(90, 140, 255, .16), transparent 60%),
    radial-gradient(ellipse 70% 50% at 30% 100%, rgba(232, 131, 74, .1), transparent 65%);
}
.catalog-title-h {
  /* С163 (Михаил, живой фидбек): z-index 1 (тот же, что .catalog-body) создавал СВОЙ stacking
     context у заголовка - выпадающее меню жанра/сортировки (z-index:40 ВНУТРИ этого контекста)
     сравнивалось только само с собой, .catalog-body (идёт позже в DOM, тот же уровень 1)
     перекрывал открытое меню поверх. z-index поднят выше .catalog-body, чтобы дочерний
     .dropdown-menu гарантированно оказывался над сеткой игр независимо от порядка в DOM. */
  position: relative; z-index: 2; display: flex; justify-content: center;
  padding: 22px 16px 10px; border-bottom: 1px solid rgba(255, 255, 255, .06);
}
.catalog-title-brand {
  font-size: 22px; font-weight: 700; letter-spacing: .08em; text-transform: uppercase;
  /* С188: тот же переливающийся градиент бренда, что в кабинете/на сайте (.logo-text) -
     сине-фиолетовый доминирует, оранжевый - только короткий блик в середине. Раньше было
     наоборот (оранжевый доминировал) - остаток доребрендинговой палитры. */
  background: linear-gradient(90deg, #8ab4ff 0%, #b89bff 25%, #ffb066 50%, #b89bff 75%, #8ab4ff 100%);
  background-size: 300% 100%;
  -webkit-background-clip: text; background-clip: text; color: transparent;
  animation: login-shine 6s linear infinite;
}
.catalog-body { position: relative; z-index: 1; flex: 1; min-height: 0; overflow-y: auto; padding: 20px; }
/* С160 (Михаил): обложки теперь честные вертикальные постеры (Quest/Steam, не мыло) —
   крупнее и красивее смотрятся при более крупной плитке, меньше карточек на экран разом.
   160px -> 240px (x1.5, первая проба). Второй заход: карточки сливались друг с другом
   (зазор 16px мал) - зазор x2 (16->32px), плитка чуть уменьшена (240->210px), чтобы на
   планшете осталось ~4 в ряд, а не расползлось в 2-3 от выросшего gap. */
.catalog-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(210px, 1fr)); gap: 32px; }
.catalog-tile {
  display: flex; flex-direction: column; border: none; padding: 0; cursor: pointer;
  background: var(--inset-bg); border-radius: 14px; overflow: hidden; text-align: center;
  aspect-ratio: 2 / 3; position: relative;
}
.catalog-tile img { width: 100%; height: 100%; object-fit: cover; position: absolute; inset: 0; }
.catalog-tile-empty {
  width: 100%; height: 100%; display: flex; align-items: center; justify-content: center;
  padding: 12px; text-align: center; font-size: 12px; color: var(--fg-dim);
}
/* С160 (Михаил): плоский нижний градиент + овальная плашка жанра сменены на "плавающие
   pill-подложки" (стиль App Store, выбран из 5 макетов) - обложка почти не затемняется,
   она и есть главная реклама игры; название/жанр вынесены в компактную "таблетку" снизу.
   Живой тест: отдельная бирка жанра сверху смотрелась не нарядно - убрана, жанр теперь
   второй строкой серым внутри той же pill ("Жанр: Шутер"), одна общая табличка вместо двух. */
.catalog-tile-shade {
  position: absolute; left: 0; right: 0; bottom: 0; height: 40%; z-index: 1;
  background: linear-gradient(to top, rgba(0, 0, 0, .5), transparent);
  pointer-events: none;
}
/* ⭐⭐⭐⭐⭐ С182 (Михаил): «плашка как-то непонятно смотрится - пусть вся нижняя часть картинки
   будет залита, и лучше белым матовым стеклом, чтобы за ним слегка просвечивала размытая
   обложка». Плавающая таблетка (С160) уступила место полосе во всю ширину плитки: она
   прижата к трём краям, углы скруглены только сверху и продолжают форму самой плитки.
   Стекло настоящее: backdrop-filter размывает обложку под полосой, поэтому у каждой игры
   свой оттенок подложки - холодный у тёмных обложек, тёплый у ярких. Заливка полупрозрачная
   (не сплошной белый) - иначе размытие под ней не видно и смысл теряется.
   saturate поднимает цвет просвечивающей картинки: одно blur делает её сероватой. */
.catalog-tile-pill {
  position: absolute; left: 0; right: 0; bottom: 0; z-index: 2;
  background: rgba(255, 255, 255, .72);
  -webkit-backdrop-filter: blur(14px) saturate(150%);
  backdrop-filter: blur(14px) saturate(150%);
  border-top: 1px solid rgba(255, 255, 255, .5);
  border-radius: 0 0 14px 14px; padding: 9px 12px;
}
/* Запасной вариант для движков без backdrop-filter (старые планшеты клубов): там прозрачная
   подложка превратилась бы в нечитаемый текст поверх картинки - заливаем плотнее. */
@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  .catalog-tile-pill { background: rgba(255, 255, 255, .93); }
}
/* ⭐⭐⭐⭐ С182 (Михаил, живой взгляд на матовые плашки): «шрифт заголовка крупнее, а жанр и
   шлем серые - теряются, их плохо видно, слишком мелко». Подняты все три размера, а серые
   строки заметно затемнены: на матовом стекле светло-серый по светлому фону читался хуже,
   чем на прежней плотной белой плашке - прозрачность съедает контраст. */
.catalog-tile-title {
  display: block; color: #17151c; font-size: 15px; font-weight: 700; letter-spacing: -.01em;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.catalog-tile-genre-line {
  display: block; color: #55505d; font-size: 12.5px; font-weight: 600; margin-top: 3px;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* С163 (Михаил): третья строка плашки - модель(и) шлема, где стоит игра ("Шлем: Pico 4 /
   Meta Quest 2"). Тот же серый тон что жанр, но чуть тусклее - вспомогательная информация,
   не должна спорить с названием/жанром за внимание. */
.catalog-tile-helmet-line {
  display: block; color: #6b6674; font-size: 11.5px; font-weight: 600; margin-top: 2px;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* С163 (Михаил): счётчик популярности - тем же тоном что модель шлема, самая младшая по
   значимости строка в плашке (после названия/жанра/шлема). */
.catalog-tile-popular-line {
  display: block; color: #6b6674; font-size: 11.5px; font-weight: 600; margin-top: 2px;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* С163 (Михаил): бейдж "Новинка" - верхний левый угол обложки, фирменный акцентный цвет
   (тот же --accent, что активный чипс фильтра), не спорит с pill-плашкой снизу (z-index 2,
   та же плоскость, что shade-градиент). */
.catalog-tile-new-badge {
  position: absolute; top: 10px; left: 10px; z-index: 2;
  background: var(--accent); color: #17151c; font-size: 10px; font-weight: 800;
  padding: 4px 9px; border-radius: 999px; letter-spacing: .02em;
  box-shadow: 0 4px 10px rgba(0, 0, 0, .3);
}
/* ⭐⭐⭐⭐ С182 (Михаил): «Популярная» вместо строки «сыграно N раз за месяц» в плашке.
   Правый верхний угол - парой к «Новинке» слева: обе метки видны сразу, между собой не
   спорят и не сдвигают текст под обложкой. Цвет намеренно ДРУГОЙ (оранжевый занят новинкой):
   тёплый малиновый читается как «горячая», не путается с акцентом интерфейса. */
.catalog-tile-hot-badge {
  position: absolute; top: 10px; right: 10px; z-index: 2;
  background: #e8467c; color: #fff; font-size: 10px; font-weight: 800;
  padding: 4px 9px; border-radius: 999px; letter-spacing: .02em;
  box-shadow: 0 4px 10px rgba(0, 0, 0, .3);
}

/* С163 (Михаил, живой фидбек x1): первая версия ("поиск на всю ширину + строка фильтров")
   утягивала сетку игр вниз - переделано на компактную шапку. Поиск/сортировка - маленькие
   элементы СПРАВА/СЛЕВА от заголовка "Каталог игр" в ТОЙ ЖЕ строке (высота панели не растёт),
   фильтр по шлему - тонкая строка чипсов под заголовком, показывается только если у клуба
   реально больше одной модели шлема (частый случай - вообще не занимает места). */
/* С163 (Михаил, живой фидбек x2): системный <select> показывал ГОЛОЕ ANDROID-меню поверх
   фирменного стеклянного интерфейса - заменено на уже существующий компонент Dropdown.jsx
   (тот же .dropdown-menu.glass-sup, что использует, например, .dropdown-genre в других местах
   приложения) - переиспользован, не написан с нуля. Обе боковые группы (поиск+жанр слева,
   сортировка справа) равной ширины через flex:1, чтобы заголовок "Каталог игр" оставался
   визуально по центру независимо от того, сколько элементов показано в каждой группе. */
.catalog-title-tools { justify-content: space-between; align-items: center; gap: 10px; }
.catalog-title-tools-side { display: flex; align-items: center; gap: 8px; flex: 1; min-width: 0; }
.catalog-title-tools-side.left { justify-content: flex-start; }
.catalog-title-tools-side.right { justify-content: flex-end; }
.catalog-tool-btn {
  background: var(--chip); border: 1px solid var(--inset-border); border-radius: 10px;
  width: 32px; height: 32px; font-size: 13px; cursor: pointer; flex-shrink: 0;
  display: flex; align-items: center; justify-content: center; transition: background .15s;
}
.catalog-tool-btn.active { background: var(--accent); border-color: var(--accent); }
.catalog-tool-dropdown .dropdown-btn { width: 118px; }
.catalog-search-row { padding: 0 20px 14px; position: relative; z-index: 1; }
.catalog-search {
  width: 100%; background: var(--inset-bg); border: 1px solid var(--inset-border);
  border-radius: 12px; padding: 10px 14px; font-size: 13.5px; color: var(--fg);
}
.catalog-search::placeholder { color: var(--fg-dim); }
/* С182: отдельный ряд чипсов упразднён - фильтр моделей переехал в левую часть строки
   заголовка (см. GameCatalogModal). Правило оставлено пустым намеренно: класс мог
   остаться в чужой разметке, и пусть он ничего не ломает. */
.catalog-chips-row { display: none; }
/* Чипсы теперь живут в шапке: не растягиваются на всю ширину, а при нехватке места
   прокручиваются по горизонтали внутри своей половины строки - заголовок и правые
   инструменты остаются на местах даже с длинными именами моделей. */
.catalog-chips {
  display: flex; gap: 8px; min-width: 0; overflow-x: auto; scrollbar-width: none;
}
.catalog-chips::-webkit-scrollbar { display: none; }
.catalog-chips .catalog-chip { flex-shrink: 0; }
.catalog-chip {
  background: var(--inset-bg); border: 1px solid var(--inset-border); border-radius: 999px;
  padding: 6px 14px; font-size: 12px; font-weight: 600; color: var(--fg-dim); cursor: pointer;
  white-space: nowrap; transition: background .15s, color .15s, border-color .15s;
}
.catalog-chip.active { background: var(--accent); border-color: var(--accent); color: #17151c; }
/* С160 (Михаил, живой фидбек): раскрытая карточка переоформлена - матовая стеклянная подложка
   (.glass-sup, тот же приём, что у попапов) вместо голого фона, шире (880px, было 720px),
   обложка+описание сразу видны бок о бок сверху, медиа (видео/скриншоты) ниже при скролле -
   пока читаешь описание, HLS успевает прогрузиться (см. HlsVideo). "← К списку" слева убран,
   крестик в правом верхнем углу на маленькой круглой стеклянной подложке вместо текстовой кнопки. */
/* ⭐⭐⭐⭐⭐ С190 (Михаил): карточка игры перестроена под сценарий «нажал - посмотрел трейлер -
   закрыл - следующая». Ширина поднята с 880px, потому что теперь на экране ДВЕ колонки и
   видео должно быть крупным, а не втиснутым в остаток строки. */
.catalog-detail-wrap { max-width: 1180px; margin: 0 auto; position: relative; min-height: 100%; }
.catalog-detail-x {
  position: absolute; top: 14px; right: 14px; z-index: 3; width: 34px; height: 34px;
  border-radius: 50%; border: none; cursor: pointer; font-size: 15px; color: #fff;
  display: flex; align-items: center; justify-content: center;
  background: rgba(255, 255, 255, .1); backdrop-filter: blur(12px) saturate(160%);
  -webkit-backdrop-filter: blur(12px) saturate(160%);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, .16), 0 4px 12px rgba(0, 0, 0, .3);
}
.catalog-detail-x:active { background: rgba(255, 255, 255, .18); }
.catalog-detail { border-radius: 20px; padding: 20px; }

/* Две колонки: слева обложка с описанием, справа трейлер. Пропорция 1fr/1.25fr - видео
   заметно крупнее, оно здесь главное (прямое наблюдение Михаила: описание почти не читают).
   Прокрутки быть не должно: карточка целиком помещается в экран планшета. */
/* ⭐⭐⭐⭐⭐ С190, ФИНАЛЬНАЯ РАСКЛАДКА (по разбору Михаила на живом виде): сверху слева крупная
   обложка с названием и жанром, справа от неё - трейлер; описание ЦЕЛИКОМ ВНИЗУ, во всю ширину
   карточки. Так правая половина не пустует у игр без видео, а текст читается широкой колонкой,
   а не колодцем. Карточка занимает экран по высоте, описание забирает остаток и прокручивается. */
.catalog-detail.has-media {
  display: grid;
  /* ⛔С190 (Михаил: «где-то видеоплеер больше, где-то меньше»). Левая колонка была auto -
     подстраивалась под ДЛИНУ НАЗВАНИЯ игры, и трейлеру каждый раз доставалось разное место.
     Фиксированная ширина: плеер теперь одинаковый в любой карточке. */
  grid-template-columns: 300px 1fr;
  grid-template-rows: auto 1fr;
  grid-template-areas:
    "head media"
    "desc desc";
  /* stretch, а не start: левый блок тянется на высоту трейлера, чтобы обложку можно было
     поставить ровно по центру относительно него (просьба Михаила про симметрию). */
  /* ⭐С190 (Михаил): min-height вместо height. Пока описание помещается - карточка ровно в
     экран, как и задумано (видео и обложка видны сразу). Если текст длинный - карточка растёт,
     и прокручивается ЦЕЛИКОМ, как у карточек без видео: так на экран попадает больше текста,
     чем в узкой полосе с внутренней прокруткой. */
  gap: 18px 20px; align-items: stretch; min-height: 100%;
}
.catalog-detail.has-media .catalog-detail-left { display: contents; }
.catalog-detail.has-media .catalog-detail-head { grid-area: head; }
.catalog-detail.has-media .catalog-detail-right { grid-area: media; }
.catalog-detail.has-media .catalog-detail-desc { grid-area: desc; }
/* Слева колонка: строка «обложка + название/жанр», под ней описание во всю ширину. */
.catalog-detail-left { display: flex; flex-direction: column; gap: 14px; min-width: 0; min-height: 0; }
.catalog-detail-head { display: flex; gap: 16px; align-items: flex-start; }
.catalog-detail-titles { min-width: 0; padding-top: 2px; }

/* ⭐С190 (Михаил): в карточке с трейлером обложка стоит ПО ЦЕНТРУ левой половины, название и
   жанр - под ней, тоже по центру. Высота этого блока равна высоте плеера, поэтому две половины
   выглядят симметрично. */
.catalog-detail.has-media .catalog-detail-head {
  flex-direction: column; align-items: center; justify-content: center;
  gap: 14px; text-align: center; min-height: 0;
}
.catalog-detail.has-media .catalog-detail-titles { text-align: center; padding-top: 0; }
.catalog-detail.has-media .catalog-detail-titles h2 { margin-bottom: 6px; }
/* ⛔С190: обложка выглядела квадратной и обрезалась - моя ширина конфликтовала со старым
   правилом flex: 0 0 200px. Задаём размер целиком здесь: пропорция 2/3 как у настоящего
   постера, высота небольшая - обложка тут опознавательный знак, а не украшение. */
/* Обложка крупная - такая же, как у карточек без трейлера (Михаил: «так даже становится
   хорошо и понятно»). Пропорция 2/3, как у настоящего постера. */
/* Обложка держит пропорцию постера (2/3) и не вылезает за высоту левого блока: если трейлер
   низкий, она сама ужимается, а не ломает раскладку. */
.catalog-detail.has-media .catalog-detail-cover {
  flex: 0 1 auto; width: auto; height: 100%; max-height: 300px; max-width: 100%;
  aspect-ratio: 2 / 3; min-height: 0;
}
/* Описание забирает всю оставшуюся высоту и прокручивается внутри себя - карточку не
   растягивает, видео на месте (ровно та проблема, ради которой всё и переделывалось). */
/* Внутренней прокрутки у описания больше нет - прокручивается вся карточка (см. выше). */
.catalog-detail.has-media .catalog-detail-desc { margin: 0; }
/* Видео по центру по вертикали - иначе оно прижато к верху, а под ним пустота (Михаил). */
.catalog-detail-right { min-width: 0; display: flex; align-items: center; }

/* Узкий экран (планшет вертикально, телефон): колонки складываются, и ВИДЕО ИДЁТ ПЕРВЫМ -
   ради него карточку и открывают. */
/* Узкий экран: всё в одну колонку, видео первым - ради него карточку и открывают. */
@media (max-width: 900px) {
  .catalog-detail.has-media {
    grid-template-columns: 1fr; grid-template-rows: auto auto 1fr;
    grid-template-areas: "media" "head" "desc";
  }
  /* В одну колонку обложка с подписями встают строкой, как в карточке без видео. */
  .catalog-detail.has-media .catalog-detail-head { flex-direction: row; text-align: left; }
  .catalog-detail.has-media .catalog-detail-titles { text-align: left; }
  .catalog-detail.has-media .catalog-detail-cover { flex: 0 0 120px; width: 120px; height: auto; }
}

.catalog-detail-top { display: flex; gap: 20px; }
.catalog-detail-cover {
  flex: 0 0 200px; aspect-ratio: 2 / 3; border-radius: 16px; overflow: hidden;
  background: var(--inset-bg); position: relative;
}
.catalog-detail-cover img { width: 100%; height: 100%; object-fit: cover; }
.catalog-detail-info { flex: 1; min-width: 0; padding-top: 4px; }
.catalog-detail-media { width: 100%; aspect-ratio: 16 / 9; border-radius: 14px; overflow: hidden; background: #000; position: relative; }
.catalog-detail-media video, .catalog-detail-media img { width: 100%; height: 100%; object-fit: contain; }
/* С158: листание скриншотов/трейлера как в Steam/App Store — стрелки по бокам + точки снизу. */
.catalog-media-nav {
  position: absolute; top: 50%; transform: translateY(-50%); width: 40px; height: 40px; border-radius: 50%;
  border: none; background: rgba(0,0,0,.45); color: #fff; font-size: 22px; cursor: pointer;
  display: flex; align-items: center; justify-content: center; z-index: 2;
}
.catalog-media-nav.prev { left: 10px; }
.catalog-media-nav.next { right: 10px; }
.catalog-media-nav:active { background: rgba(0,0,0,.65); }
.catalog-media-dots { position: absolute; left: 0; right: 0; bottom: 10px; display: flex; justify-content: center; gap: 6px; z-index: 2; }
.catalog-media-dot { width: 7px; height: 7px; border-radius: 50%; background: rgba(255,255,255,.4); cursor: pointer; }
.catalog-media-dot.active { background: #fff; }
.catalog-detail-info h2 { font-size: 22px; font-weight: 650; margin: 0 0 8px; letter-spacing: -.01em; }
.catalog-detail-genre {
  display: block; font-size: 12.5px; font-weight: 600; color: var(--accent, #8b6ff0); margin-bottom: 12px;
}
/* ⭐С190 (живой случай Михаила: «в каталоге текст лежит одним куском, без переносов»).
   Описания из магазина Meta размечены пустыми строками на абзацы, но по умолчанию браузер
   схлопывает их в один поток. pre-line сохраняет переносы автора и при этом переносит длинные
   строки по ширине блока (в отличие от pre, который заставил бы текст уезжать вбок). */
.catalog-detail-desc { font-size: 15px; line-height: 1.6; color: var(--fg-dim); white-space: pre-line; }

/* С152: попап поддержки по образцу референса Stardog — плотная стеклянная окантовка
   снаружи (шапка+низ на стекле), белая панель в центре под сам чат. Окантовка = padding
   стеклянного контейнера, белая панель — отдельный внутренний блок со своим скруглением. */
/* С157 (Михаил): побольше по обеим осям - обращение в поддержку часто длинное, нужно
   больше места и под текст сообщений, и под историю переписки. */
.sup { width: min(460px, 100%); height: min(560px, 85vh); height: min(560px, 85dvh); border-radius: 22px; display: flex; flex-direction: column; overflow: hidden; padding: 10px; gap: 8px; }
.sup-h { display: flex; align-items: center; gap: 10px; padding: 10px 12px; position: relative; z-index: 1; flex-shrink: 0; }
.sup-h b, .sup-h div { color: var(--fg) !important; }
/* С157: "● онлайн" должен быть зелёным (--ok) - у .sup-h div выше та же специфичность
   (класс+тег = класс+класс), а объявлен он РАНЬШЕ по файлу, поэтому просто !important на
   .sup-online-badge не побеждал. Явно вложенный селектор .sup-h .sup-online-badge даёт
   более высокую специфичность и гарантированно выигрывает у общего правила выше. */
.sup-h .sup-online-badge { font-size: 10px; color: var(--ok) !important; }
.sup-panel { flex: 1; min-height: 0; display: flex; flex-direction: column; border-radius: 14px; background: var(--sup-panel-bg); overflow: hidden; }
.sup-b { flex: 1; overflow-y: auto; padding: 14px 16px; display: flex; flex-direction: column; gap: 9px; }
.msg { max-width: 82%; padding: 9px 12px; border-radius: 13px; font-size: 13px; line-height: 1.5; }
.msg.bot { background: var(--sup-panel-fg-dim); background: rgba(0, 0, 0, .06); color: var(--sup-panel-fg); align-self: flex-start; border-bottom-left-radius: 4px; }
.msg.usr { background: linear-gradient(135deg, var(--accent), var(--accent2)); color: var(--accent-fg); align-self: flex-end; border-bottom-right-radius: 4px; }
.sup-q { padding: 10px 16px 12px; display: flex; flex-wrap: wrap; gap: 6px; border-top: 1px solid rgba(0, 0, 0, .07); }
.qb { font-size: 10.5px; background: rgba(0, 0, 0, .05); color: var(--sup-panel-fg-dim); border: none; border-radius: 100px; padding: 7px 12px; cursor: pointer; font-family: inherit; font-weight: 300; }
.sup-i { display: flex; gap: 8px; padding: 10px 12px 2px; position: relative; z-index: 1; flex-shrink: 0; }
.sup-i input { flex: 1; background: var(--sup-panel-bg); border: none; border-radius: 10px; padding: 9px 12px; font-size: 12px; color: var(--sup-panel-fg); outline: none; font-family: inherit; font-weight: 300; }
.sup-i button { border: none; border-radius: 10px; background: linear-gradient(135deg, var(--accent), var(--accent2)); color: var(--accent-fg); padding: 9px 14px; font-size: 12px; cursor: pointer; font-family: inherit; font-weight: 500; }

.toast {
  position: fixed; left: 50%; bottom: 24px; transform: translate(-50%, 20px); background: rgba(20, 20, 30, .94);
  backdrop-filter: blur(9px); color: #eaeaf2; padding: 11px 20px; border-radius: 100px; font-size: 12px;
  opacity: 0; transition: all .3s; z-index: 60; pointer-events: none; box-shadow: 0 12px 30px -10px rgba(0, 0, 0, .5);
}
.toast.show { opacity: 1; transform: translate(-50%, 0); }
/* ⭐ С202 (Михаил: тёмное уведомление снизу плохо видно на тёмном фоне киоска) - предупреждение/
   отказ ("Не удалось…", "модуль не подключён") получает красноватую подсветку вместо нейтральной
   тёмной, чтобы отличаться от обычных подтверждений и не терялось на фоне зала. */
.toast.toast-warn {
  background: rgba(64, 18, 22, .96); color: #ffd8d8; border: 1px solid rgba(255, 90, 90, .45);
  box-shadow: 0 12px 30px -10px rgba(0, 0, 0, .5), 0 0 0 1px rgba(255, 90, 90, .12) inset;
}

/* Аварийный доступ (см. ТЗ) - подозрительная активность у скрытой точки на шлеме, требует
   явного подтверждения владельцем, поэтому НЕ самоисчезает как обычный toast */
.emg-alert {
  position: fixed; top: max(16px, env(safe-area-inset-top)); left: 50%; transform: translateX(-50%);
  display: flex; align-items: center; gap: 10px; max-width: min(560px, calc(100vw - 32px));
  background: rgba(120, 20, 20, .96); backdrop-filter: blur(10px); color: #fff; padding: 12px 16px;
  border-radius: 14px; font-size: 13px; z-index: 90; box-shadow: 0 12px 30px -10px rgba(0, 0, 0, .6);
}
.emg-alert-icon { font-size: 16px; flex-shrink: 0; }
.emg-alert-text { flex: 1; line-height: 1.4; }
.emg-alert-close {
  flex-shrink: 0; border: none; border-radius: 100px; background: rgba(255, 255, 255, .18); color: #fff;
  padding: 7px 14px; font-size: 12px; cursor: pointer; font-family: inherit; font-weight: 500;
}

/* С157: раньше "Свернуть" жила в шапке левой панели (место освобождено под шестерёнку
   настроек) и разворачивалась ОТДЕЛЬНОЙ плавающей кнопкой в углу экрана. Теперь одна
   кнопка на границе колонок, по центру высоты, с одинаковым положением что свёрнуто, что нет.
   ⭐ Михаил (живой тест на планшете), 2 прохода:
   1) изначальная версия была приглушена под opacity .55 в расчёте на hover - на тач-экране
      навести курсор нельзя, кнопка читалась как ошибка вёрстки.
   2) первая попытка исправить (крупнее + пульсирующее свечение) стала громоздкой и налезала
      на карточки шлемов рядом. Сузил, свечение статичное и тесно прижатое к контуру.
   3) ⭐ ДАЖЕ суженная версия всё ещё налезала - причина в математике позиционирования: колонка
      шлемов (col-l) шириной 292px начинается ПОСЛЕ отступа #app (padding 14px), значит её
      правая граница в координатах viewport - 14+292=306px, а не 292px, где кнопка стояла
      раньше (значит держалась на 14px ГЛУБЖЕ внутри карточек, чем казалось). Зазор между
      колонками (gap) - 306..320px, гарантированно пустой от карточек. Ширина кнопки теперь
      РОВНО как зазор (14px) и стоит по его центру (306+7=313px) - переползти на карточки
      физически не может. Цвет приглушён (полупрозрачная окантовка вместо сплошной, тусклая
      иконка вместо акцентной) - заметно, но не кричаще. */
.col-collapse-btn {
  position: fixed; top: 50%; left: 313px; transform: translate(-50%, -50%); width: 14px; height: 46px; border-radius: 7px;
  border: 1px solid rgba(138, 180, 255, .45); background: var(--glass-tile-bg);
  color: var(--fg-dim); font-size: 11px; z-index: 20; opacity: 1;
  display: flex; align-items: center; justify-content: center; cursor: pointer;
  transition: left .3s cubic-bezier(.4, 0, .2, 1), background .2s;
  box-shadow: var(--glass-tile-shadow), 0 0 3px rgba(138, 180, 255, .3);
}
.col-collapse-btn:hover, .col-collapse-btn:active { background: var(--glass-helm-active-bg); color: var(--accent); }
#app.collapsed .col-collapse-btn { left: 0px; }

@media (max-width: 820px) {
  #app { grid-template-columns: 1fr; grid-template-rows: auto 1fr; }
  .col-l { max-height: 34vh; max-height: 34dvh; }
  #helmScroll { overflow-x: auto; overflow-y: hidden; }
  #helmScroll > div { flex-direction: row; }
  .helm { min-width: 215px; }
  .search { width: 100%; margin-left: 0; }
  .col-collapse-btn { display: none; }
}

/* ⭐⭐⭐ С172: карточки спрятанных приложений — та же сетка, что у игр, но вместо обложки
   название и кнопка возврата. Обложки у них обычно нет (это не игры), поэтому плитка простая. */
.hidden-tile {
  display: flex; flex-direction: column; justify-content: center; align-items: center; gap: 12px;
  aspect-ratio: 2 / 3; padding: 16px; border-radius: 12px;
  background: var(--chip); border: 1px solid var(--gfoot-border); text-align: center;
}
.hidden-tile-title { font-size: 13px; color: var(--fg); word-break: break-word; }

/* ⭐⭐⭐⭐⭐ С173: инструкция UnitVR на планшете - оглавление-ссылки + места под скриншоты.
   Те же классы, что в кабинете владельца (account/src/styles/layout.css) - разметка идёт
   из общего текста ../data/instructionsContent.js, стили держим в одном духе. */
.instr-toc-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(180px, 1fr)); gap: 4px 10px; margin-bottom: 4px; }
.instr-toc-link {
  display: flex; align-items: center; gap: 6px; font-size: 11.5px; color: var(--fg-dim);
  padding: 5px 7px; border-radius: 7px; text-decoration: none; transition: background .12s, color .12s;
}
.instr-toc-link:hover { background: rgba(255,255,255,.05); color: var(--fg); }
.instr-toc-icon { flex-shrink: 0; width: 16px; text-align: center; }

/* ⭐С194: та же логика, что в кабинете владельца (account/src/styles/layout.css) - карточка
   ФИКСИРОВАННОГО размера (16:9) под любой скриншот, картинка вписана целиком без обрезки. */
.instr-shot-slot {
  margin-top: 8px; border: 1.5px dashed rgba(255,255,255,.16); border-radius: 10px;
  aspect-ratio: 16 / 9; display: flex; flex-direction: column; align-items: center;
  justify-content: center; padding: 14px 12px; text-align: center; background: rgba(255,255,255,.02);
}
.instr-shot-slot-icon { font-size: 18px; opacity: .5; margin-bottom: 5px; }
.instr-shot-slot-desc { font-size: 10.5px; color: var(--fg-mute); line-height: 1.4; }
.instr-shot-ready {
  all: unset; display: block; box-sizing: border-box; width: 100%; margin-top: 8px;
  aspect-ratio: 16 / 9; border-radius: 10px; overflow: hidden; border: 1px solid rgba(255,255,255,.08);
  background: rgba(0,0,0,.18); position: relative; cursor: zoom-in;
}
.instr-shot-ready img { display: block; width: 100%; height: 100%; object-fit: contain; }
.instr-shot-zoom {
  position: absolute; right: 8px; bottom: 8px; width: 26px; height: 26px; border-radius: 50%;
  background: rgba(8,9,16,.65); backdrop-filter: blur(4px); -webkit-backdrop-filter: blur(4px);
  display: flex; align-items: center; justify-content: center; font-size: 12px; pointer-events: none;
}

/* Клик — оригинал крупно по центру ЭКРАНА (не окна инструкции): портал в body, поверх
   самого модального окна .instr-modal (см. компонент Lightbox). */
.instr-lightbox-ov {
  position: fixed; inset: 0; z-index: 400; display: flex; align-items: center; justify-content: center;
  background: rgba(6,7,12,.72); backdrop-filter: blur(6px); -webkit-backdrop-filter: blur(6px);
  padding: 24px; cursor: zoom-out;
}
.instr-lightbox-box {
  position: relative; cursor: default; border-radius: 14px; overflow: hidden;
  border: 1px solid rgba(255,255,255,.12); box-shadow: 0 20px 60px rgba(0,0,0,.5); background: #0a0a0e;
}
.instr-lightbox-box img { display: block; max-width: min(820px, 90vw); max-height: 82vh; width: auto; height: auto; }
.instr-lightbox-close {
  position: absolute; top: 8px; right: 8px; width: 32px; height: 32px; border-radius: 50%;
  border: none; background: rgba(8,9,16,.7); color: #fff; font-size: 14px; cursor: pointer;
  display: flex; align-items: center; justify-content: center; backdrop-filter: blur(4px);
}
.instr-lightbox-close:hover { background: rgba(8,9,16,.92); }

/* ⭐ С186: список сеансов для оператора (SessionsModal) - те же классы, что в кабинете владельца
   (account/src/styles/layout.css), скопированы сюда: планшет и кабинет - разные React-сборки,
   общего CSS между ними нет. */
.stats-empty { font-size: 12.5px; color: var(--fg-mute); padding: 10px 0; }
.stats-list { display: flex; flex-direction: column; gap: 8px; }
.stats-session { background: rgba(255, 255, 255, .035); border-radius: 12px; padding: 10px 12px; }
/* ⭐⭐⭐⭐⭐ С187 (Михаил, живой случай на iMac): «на экране сессий вёрстка сломана».
   Корень — классическая ловушка flex: по умолчанию элемент НЕ сжимается меньше своего
   содержимого (min-width: auto). Слева стоит длинная строка («№5 · <имя шлема> · 14:30 —
   15:00»), справа — длительность, прижатая через margin-left: auto. Пока текст короткий, всё
   сходится; стоит ему стать длиннее (широкий системный шрифт macOS, длинное имя шлема или
   игры) — левая часть распирает строку и выталкивает длительность за край карточки.
   Лечение общее для всех экранов, не подгонка под macOS: левой части разрешаем сжиматься и
   переносить (min-width: 0), правой запрещаем сжиматься и переноситься (flex-shrink: 0 +
   nowrap) — время и длительность обязаны читаться целиком всегда. */
.stats-session-head { display: flex; align-items: baseline; gap: 10px; font-size: 12.5px; }
.stats-session-time { font-weight: 600; min-width: 0; overflow-wrap: anywhere; }
.stats-session-dur { margin-left: auto; font-variant-numeric: tabular-nums; flex-shrink: 0; white-space: nowrap; }
.stats-games { margin-top: 7px; display: flex; flex-direction: column; gap: 3px; font-size: 12px; }
.stats-game { display: flex; align-items: baseline; gap: 10px; }
.stats-game span:first-child { min-width: 0; overflow-wrap: anywhere; }
.stats-game span:last-child { margin-left: auto; flex-shrink: 0; white-space: nowrap; }
.stats-dim { color: var(--fg-mute); font-weight: 400; }

/* ═══════════════════════════════════════════════════════════════════════════════════════
   С187 — БЛОК СОВМЕСТИМОСТИ С APPLE-УСТРОЙСТВАМИ (iPad / MacBook / iMac).
   Найдено аудитом вёрстки после живого случая Михаила на iMac. Все правила ниже — про то,
   что Safari делает ИНАЧЕ, а не про размеры экрана.
   ═══════════════════════════════════════════════════════════════════════════════════════ */

/* ⭐⭐⭐⭐⭐ Автозум iOS/iPadOS при фокусе в поле ввода.
   Safari принудительно приближает страницу, если у поля, в которое ткнули, шрифт МЕЛЬЧЕ 16px.
   У нас такие все поля (поиск 11.5, поддержка 12, настройки 13, каталог 13.5, логин 14).
   Флаг user-scalable=no в index.html от этого НЕ спасает — iOS его игнорирует с 10-й версии.
   А так как #app это position: fixed, после зума интерфейс не прокручивается, а ОБРЕЗАЕТСЯ:
   часть экрана уезжает за границу и вернуть её можно только пинчем. Это и есть заметная доля
   впечатления «на Apple всё разъезжается».
   ⚠️ Правило нарочно спрятано под @supports (-webkit-touch-callout: none) — это признак
   именно движка WebKit на тач-устройствах (iPhone/iPad, любой браузер там — тоже WebKit).
   Android-планшет клуба под него НЕ попадает и остаётся ровно таким, как был: там автозума
   нет, а привычные компактные поля менять без нужды нельзя. */
@supports (-webkit-touch-callout: none) {
  .search input, .sup-i input, .settings-num-input, .catalog-search,
  .login-input, .edit-game-textarea, input, textarea, select {
    font-size: 16px;
  }
}

/* Safari на macOS ВСЕГДА рисует свой степпер внутри числового поля (Chrome — только при
   наведении, мобильные — вообще никогда). В поле шириной 70px степпер съедает часть места, и
   двузначное число подрезается. Убираем системный вид — поле выглядит одинаково везде. */
.settings-num-input { -webkit-appearance: none; appearance: none; margin: 0; }
.settings-num-input::-webkit-outer-spin-button,
.settings-num-input::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; }

/* Safari подставляет системный вид кнопкам и полям, из-за чего фирменные скругления и фоны
   местами «проглядывают» серым. Явно отключаем — вид перестаёт зависеть от браузера. */
button, input, textarea, select { -webkit-appearance: none; appearance: none; }
input[type="range"] { -webkit-appearance: none; appearance: none; background: transparent; }

/* iOS Safari при повороте экрана самовольно увеличивает шрифт («автоинфляция текста»).
   Дешёвая страховка, ничего не меняет на других платформах. */
body { -webkit-text-size-adjust: 100%; text-size-adjust: 100%; }

/* Safari до 18-й версии знает только префиксный backdrop-filter. Сборка ничего не
   префиксует (cssMinify отключён, autoprefixer в проекте нет), поэтому дописываем руками.
   Критично для .catalog-detail-x: это крестик закрытия карточки игры поверх обложки — без
   размытия его фон почти прозрачный, и кнопка становится невидимой. Остальные три — косметика. */
.catalog-detail-x { -webkit-backdrop-filter: blur(10px); }
.ggenre, .gbadge, .toast, .emg-alert { -webkit-backdrop-filter: blur(8px); }

/* ⭐⭐⭐⭐ Класс .list-row используется в окне телевизоров (HelmetMenuModal), но в CSS его
   НЕ БЫЛО вовсе — строка оставалась блочной, и кнопка «Отвязать»/«Привязать» с
   margin-left:auto не уезжала вправо, а липла к названию телевизора. Не зависит от
   устройства — было сломано везде, просто не бросалось в глаза. */
.list-row { display: flex; align-items: center; gap: 10px; min-width: 0; }
.list-row > span { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* ⭐ С187: кнопка «Установить приложение на рабочий стол» (см. InstallAppButton.jsx).
   На экране входа — под формой, отделена тонкой чертой: это не часть входа, а отдельное
   предложение, и оно не должно спорить с кнопкой «Войти» за внимание. */
.install-app { margin-top: 14px; }
.install-app-login { border-top: 1px solid var(--inset-border); padding-top: 14px; }
.install-app-btn {
  width: 100%; justify-content: center; padding: 10px 12px; border-radius: 10px;
  font-size: 11.5px; white-space: normal; line-height: 1.25; text-align: center;
}
.install-app-hint {
  margin-top: 8px; font-size: 11px; line-height: 1.4; color: var(--fg-mute); text-align: center;
}

/* ⭐⭐⭐⭐⭐ С190: свой плеер трейлера (Михаил: «системный плеер некрасивый; три точки со
   скоростью и картинкой-в-картинке не нужны, иконка звука тоже - нужны play/stop, развернуть
   и полоса времени, но красивая»). Панель управления лежит ПОВЕРХ видео, поэтому не занимает
   отдельную высоту и не выталкивает карточку за пределы экрана. */
.trailer-player {
  position: relative; width: 100%; aspect-ratio: 16 / 9;
  border-radius: 14px; overflow: hidden; background: #000;
}
.trailer-player video { width: 100%; height: 100%; object-fit: contain; display: block; cursor: pointer; }
/* ⭐С190: свой полноэкранный режим вместо системного (см. разбор в GameCatalogModal.jsx —
   Android рисовал поверх свою плашку про выход из fullscreen). Плеер накрывает весь экран,
   z-index выше модального окна каталога. */
.trailer-player.is-full {
  position: fixed; inset: 0; z-index: 200;
  aspect-ratio: auto; width: 100%; border-radius: 0; background: #000;
  /* ⛔С190: было 100vh - на Android это высота БЕЗ учёта системных панелей, поэтому низ плеера
     (а с ним полоса времени) оказывался за краем экрана. 100dvh считает реально видимую
     область; для старых браузеров ниже остаётся 100% от inset: 0. */
  height: 100dvh;
}
/* Развёрнутый плеер лежит в <body> (см. портал в GameCatalogModal.jsx) - видео центрируем,
   чтобы при любом соотношении сторон ролика панель управления оставалась внизу экрана. */
.trailer-player.is-full video { height: 100%; width: 100%; object-fit: contain; }

/* Кнопка «развернуть» - в правом верхнем углу, как и просил Михаил. Свернуть появляется на
   её же месте, когда развернули: одна точка касания, не надо искать глазами новую. */
.trailer-full-btn {
  position: absolute; top: 10px; right: 10px; z-index: 2;
  width: 38px; height: 38px; border: 0; border-radius: 12px;
  background: rgba(10, 12, 18, .55); color: #fff; font-size: 17px; line-height: 1;
  backdrop-filter: blur(8px); cursor: pointer;
}
.trailer-full-btn:active { background: rgba(10, 12, 18, .8); }

/* Крупная кнопка по центру на паузе - попасть по ней проще, чем по мелкой внизу. */
.trailer-center-play {
  position: absolute; inset: 0; margin: auto; z-index: 2;
  width: 72px; height: 72px; border: 0; border-radius: 50%;
  background: rgba(10, 12, 18, .55); color: #fff; font-size: 26px; padding-left: 5px;
  backdrop-filter: blur(8px); cursor: pointer;
}

.trailer-bar {
  position: absolute; left: 0; right: 0; bottom: 0; z-index: 2;
  display: flex; align-items: center; gap: 12px; padding: 10px 14px 12px;
  /* Затемнение снизу вверх - полоса читается на любом кадре, хоть на светлом, хоть на тёмном. */
  background: linear-gradient(to top, rgba(0, 0, 0, .75), rgba(0, 0, 0, 0));
}
.trailer-play {
  border: 0; background: none; color: #fff; font-size: 15px; line-height: 1;
  cursor: pointer; padding: 4px 2px; flex-shrink: 0; min-width: 22px;
}
/* Полоса времени: высокая зона касания (14px) при тонкой видимой линии - на планшете пальцем
   надо попадать легко, а выглядеть должно аккуратно. */
.trailer-track {
  position: relative; flex: 1; height: 14px; display: flex; align-items: center;
  cursor: pointer; min-width: 0;
}
.trailer-track::before {
  content: ""; position: absolute; left: 0; right: 0; height: 4px;
  border-radius: 4px; background: rgba(255, 255, 255, .25);
}
.trailer-track-fill {
  position: relative; height: 4px; border-radius: 4px;
  background: linear-gradient(90deg, #8ab4ff, #b89bff);   /* фирменный сине-фиолетовый */
  box-shadow: 0 0 10px rgba(138, 180, 255, .5);
}
.trailer-time { color: rgba(255, 255, 255, .85); font-size: 12px; flex-shrink: 0; font-variant-numeric: tabular-nums; }

/* ⭐С190: «развернуть» переехала в панель плеера (в правом верхнем углу она сталкивалась с
   крестиком закрытия карточки — см. комментарий в GameCatalogModal.jsx). */
/* ⭐С190 (Михаил: «кнопка развернуть маленькая, плохо видно») — крупнее и с подложкой,
   чтобы читалась на любом кадре, а не терялась на светлом видео. */
.trailer-full {
  border: 0; color: #fff; cursor: pointer; padding: 0;
  flex-shrink: 0; width: 42px; height: 42px; border-radius: 12px;
  background: rgba(255, 255, 255, .12);
  display: flex; align-items: center; justify-content: center;   /* иконка строго по центру */
}
.trailer-full:active { background: rgba(255, 255, 255, .24); }

/* ⛔⭐⭐⭐⭐⭐ С190: пока трейлер развёрнут, снимаем матовое стекло с окна каталога и его
   родителей. Причина в спецификации: элемент с backdrop-filter становится точкой отсчёта для
   position: fixed внутри себя, поэтому «на весь экран» плеер растягивался относительно ОКНА,
   а не планшета — вылезал за края, и полосу времени было не видно.
   Почему не порталом в <body>: пробовали, и это ломало воспроизведение — React пересоздавал
   <video> на новом месте, поток к нему уже не подключён, оставалась одна обложка.
   Стекло под развёрнутым видео всё равно не видно, так что визуально мы ничего не теряем. */
body.trailer-expanded .glass-sup,
body.trailer-expanded .glass,
body.trailer-expanded .glass-tile {
  backdrop-filter: none !important;
  -webkit-backdrop-filter: none !important;
}
/* Прокрутка фона под развёрнутым видео не нужна и мешает: палец должен попадать по плееру. */
body.trailer-expanded { overflow: hidden; }

/* ⭐С190: ореол за обложкой в её же тонах (см. комментарий в GameCatalogModal.jsx).
   Размытая копия картинки лежит позади и чуть крупнее — обложка отделяется от тёмного фона
   попапа мягким цветным свечением, а не жёстким краем. */
.catalog-cover-glow-wrap {
  position: relative; display: flex; min-height: 0; max-height: 100%;
  /* повторяет поведение самой обложки в сетке, чтобы выравнивание не поехало */
  flex: 0 1 auto; justify-content: center;
}
.catalog-cover-glow {
  position: absolute; left: 50%; top: 50%; transform: translate(-50%, -50%) scale(1.12);
  width: 100%; height: 100%; object-fit: cover;
  filter: blur(30px) saturate(1.7); opacity: .55;
  border-radius: 30px; pointer-events: none; z-index: 0;
}
.catalog-cover-glow-wrap .catalog-detail-cover { position: relative; z-index: 1; }

/* ═══ ⭐⭐⭐⭐⭐ С194 ЭКРАН СМЕНЫ (кто работает в зале) ═══════════════════════════════════════
   Всё крупное и пальцевое: планшет стоит на стойке, администратор жмёт стоя и на бегу.
   Пин вводится своей клавиатурой, а не системной — она на планшете перекрывает пол-экрана. */
/* ⛔⭐⭐⭐⭐⭐ С200 (АУДИТ): экран прокручиваться НЕ МОГ. У корня приложения стоит
   `overflow: hidden` (самое начало файла), а карточка с пин-падом и списком людей выше экрана
   планшета: при восьми администраторах нижние имена и цифры просто обрезались, и войти в смену
   было нечем. Лечение для окон уже написано выше по файлу («Лечим в корне и для ВСЕХ окон
   сразу») — на этот экран близнеца не перенесли. Заодно 100vh → 100dvh: на планшетном Safari
   vh считается без учёта панелей браузера, и низ уезжает под тулбар — там же об этом сказано. */
/* ⚠️ Высота именно ФИКСИРОВАННАЯ (height, не min-height): у #root стоит overflow:hidden, и
   растущий контейнер он просто обрежет — прокрутка появится только у элемента с заданной
   высотой. Центрирование делаем через `margin:auto` у карточки, а не align-items:center: при
   контенте выше экрана центрирование прячет ВЕРХ карточки за краем, и до него не добраться
   (тот же приём применён к окнам выше по файлу). */
.shift-screen { height: 100vh; height: 100dvh; display: flex; align-items: flex-start;
  justify-content: center; padding: 24px; overflow-y: auto; overscroll-behavior: contain; }
.shift-card { width: min(520px, 100%); border-radius: 26px; padding: 34px 30px; text-align: center;
  margin: auto; }   /* С200: центрирует, но не прячет верх при длинном списке людей */
.shift-title { font-size: 26px; font-weight: 650; }
.shift-sub { font-size: 15px; color: var(--fg-mute); margin-top: 8px; }
.shift-people { display: flex; flex-direction: column; gap: 12px; margin-top: 26px; }
.shift-person { display: flex; align-items: center; gap: 14px; padding: 16px 18px; font-size: 19px;
  border-radius: 18px; border: 1px solid rgba(255,255,255,.12); background: rgba(255,255,255,.05);
  color: var(--fg); cursor: pointer; font-family: inherit; text-align: left; }
.shift-person:active { background: rgba(255,255,255,.1); }
.shift-ava { width: 42px; height: 42px; border-radius: 14px; flex: 0 0 auto; font-weight: 700;
  display: flex; align-items: center; justify-content: center; color: var(--accent-fg);
  background: linear-gradient(135deg, var(--accent), var(--accent2)); }
.shift-dots { display: flex; gap: 12px; justify-content: center; margin: 26px 0 8px; }
.shift-dots i { width: 14px; height: 14px; border-radius: 50%; background: rgba(255,255,255,.14); }
.shift-dots i.on { background: linear-gradient(135deg, var(--accent), var(--accent2)); }
.shift-error { color: var(--bad); font-size: 14.5px; margin-bottom: 6px; }
.shift-pad { display: grid; grid-template-columns: repeat(3, 1fr); gap: 12px; margin-top: 18px; }
.shift-key { height: 64px; font-size: 24px; border-radius: 18px; cursor: pointer; font-family: inherit;
  border: 1px solid rgba(255,255,255,.12); background: rgba(255,255,255,.05); color: var(--fg); }
.shift-key:active { background: rgba(255,255,255,.12); }
.shift-key-soft { font-size: 17px; color: var(--fg-dim); }
/* С200: выход к залу с идущими сеансами — намеренно неяркая, вторичная кнопка: обычный путь
   всё-таки открыть смену, а это исключение для «сеанс идёт, а смена закрылась». */
.shift-view-hall { margin-top: 16px; width: 100%; padding: 13px 16px; border-radius: 16px;
  font-family: inherit; font-size: 15px; cursor: pointer; color: var(--fg-mute);
  background: rgba(255,255,255,.05); border: 1px solid rgba(255,255,255,.12); }
.shift-view-hall:active { background: rgba(255,255,255,.1); }
.shift-start { width: 100%; height: 62px; margin-top: 20px; border: none; border-radius: 18px;
  font-size: 19px; font-weight: 600; cursor: pointer; font-family: inherit;
  background: linear-gradient(135deg, var(--accent), var(--accent2)); color: var(--accent-fg); }
.shift-start:disabled { opacity: .45; }

/* ⭐ С196 (аудит): экран барьера ошибок — см. components/ErrorBoundary.jsx. Показывается
   вместо белого экрана, если что-то упало в отрисовке. Стили намеренно простые и не
   зависят от остального: этот экран должен нарисоваться даже когда сломалось всё прочее. */
.boundary-screen {
  position: fixed; inset: 0; z-index: 9999;
  display: flex; align-items: center; justify-content: center;
  padding: 24px; background: #14161f;
}
.boundary-card {
  max-width: 520px; width: 100%; padding: 28px 26px; border-radius: 20px;
  background: rgba(28, 31, 44, .92); border: 1px solid rgba(138, 180, 255, .35);
  text-align: center; color: #e8ecf6;
}
.boundary-title { font-size: 22px; font-weight: 600; margin-bottom: 12px; }
.boundary-text { font-size: 15.5px; line-height: 1.5; opacity: .85; margin-bottom: 22px; }
.boundary-btn { min-width: 220px; }

/* ⭐ С196: полоса режима техподдержки на планшете — та же роль, что и в кабинете: не дать
   перепутать чужой зал со своим. Поверх всего, но не мешает управлению (содержимое сдвигается). */
.support-banner-tablet {
  position: fixed; top: 0; left: 0; right: 0; z-index: 1000;
  display: flex; align-items: center; gap: 12px;
  padding: 8px 16px;
  background: linear-gradient(90deg, #c2410c, #ea580c);
  color: #fff; font-size: 13.5px; line-height: 1.35;
  box-shadow: 0 2px 14px rgba(0,0,0,.35);
}
.support-banner-tablet .support-banner-dot {
  width: 9px; height: 9px; border-radius: 50%; background: #fff; flex: 0 0 auto;
  animation: support-pulse 1.6s ease-in-out infinite;
}
@keyframes support-pulse { 0%,100% { opacity: 1 } 50% { opacity: .35 } }
.support-banner-tablet .support-banner-exit {
  margin-left: auto; flex: 0 0 auto;
  padding: 5px 12px; border-radius: 8px; cursor: pointer;
  background: rgba(255,255,255,.16); color: #fff;
  border: 1px solid rgba(255,255,255,.5); font-size: 13px;
}
body:has(.support-banner-tablet) #app { padding-top: 38px; }

/* ⭐ С196: отметка «техподдержка на связи» в шапке зала. Спокойная, в тон остальным значкам —
   администратор должен ЗНАТЬ о подключении, но работа зала при этом не прерывается. */
.support-badge {
  background: rgba(234, 88, 12, .14);
  border-color: rgba(234, 88, 12, .45);
  color: #fdba74;
}
.support-badge .conn-dot {
  background: #f97316;
  animation: support-pulse 1.8s ease-in-out infinite;
}

/* ⭐⭐⭐⭐⭐ С201: полноэкранная блокировка планшета при истёкшей подписке/демо (см.
   SubscriptionLockOverlay.jsx) - z-index выше любого модального окна и тоста в приложении,
   закрыть кнопкой нельзя, уходит сама при возобновлении подписки. */
.sub-lock-ov {
  position: fixed; inset: 0; z-index: 10000;
  display: flex; align-items: center; justify-content: center; padding: 24px;
  background: rgba(8, 9, 16, .92);
  backdrop-filter: blur(14px) saturate(140%); -webkit-backdrop-filter: blur(14px) saturate(140%);
}
.sub-lock-card {
  max-width: 420px; text-align: center;
  padding: 32px 28px; border-radius: 20px;
  background: rgba(255, 255, 255, .06);
  border: 1px solid rgba(255, 255, 255, .12);
  box-shadow: 0 20px 60px rgba(0, 0, 0, .45);
}
.sub-lock-icon { font-size: 40px; margin-bottom: 14px; }
.sub-lock-title { font-size: 21px; font-weight: 700; color: #fff; margin-bottom: 10px; }
.sub-lock-text { font-size: 14.5px; line-height: 1.5; color: rgba(255, 255, 255, .72); }

/* ⭐⭐⭐⭐⭐ С230 (Михаил, по клубу «VR Tandil»): выключенная служба специальных возможностей
   была видна только владельцу в кабинете — администратор в зале не знал о ней вовсе, а шлем
   при этом не обновляется и не закрывает диалог выхода из игры. Плашка на всю ширину карточки
   под остальными строками, ЖЁЛТАЯ: красный в этом интерфейсе занят аварийным режимом
   (.helm.emg), путать тревогу и «надо разово зайти и включить» нельзя. Без анимации: в
   отличие от обновления это состояние может держаться днями, мигать всё это время — пытка. */
.helm-a11y-warn {
  display: block; width: 100%; margin-top: 6px;
  padding: 5px 8px; border-radius: 8px;
  font-size: 10.5px; font-weight: 600; line-height: 1.25; text-align: left;
  background: rgba(255, 193, 77, .14);
  color: #FFC14D;
  border: 1px solid rgba(255, 193, 77, .55);
  cursor: pointer;
}
.helm-a11y-warn:hover { background: rgba(255, 193, 77, .22); }

.a11y-modal { max-width: 560px; }
.a11y-sub { font-size: 13px; line-height: 1.5; opacity: .85; margin-bottom: 12px; }
.a11y-steps { margin: 0 0 14px; padding-left: 20px; font-size: 13.5px; line-height: 1.6; }
.a11y-steps li { margin-bottom: 6px; }
.a11y-why {
  font-size: 12.5px; line-height: 1.5; opacity: .85;
  padding: 9px 11px; border-radius: 8px; margin-bottom: 10px;
  background: rgba(255, 255, 255, .04);
}
.a11y-note {
  font-size: 12.5px; line-height: 1.5; color: #FFC14D;
  padding: 8px 11px; border-radius: 8px; margin-bottom: 8px;
  background: rgba(255, 193, 77, .10);
  border: 1px solid rgba(255, 193, 77, .28);
}
.a11y-actions { display: flex; justify-content: flex-end; margin-top: 14px; }

/* С231: переключатель языка — на экране входа (по центру, под кнопкой установки) и в
   Настройках (справа в строке). Активный язык — акцентом, остальные приглушены. */
.lang-switch { display: flex; gap: 6px; justify-content: center; }
.lang-switch-btn {
  padding: 5px 12px; border-radius: 8px; font-size: 13px; cursor: pointer;
  border: 1px solid var(--inset-border); background: var(--chip); color: var(--fg-dim);
}
.lang-switch-btn.active { border-color: var(--accent); color: var(--fg); }
.lang-switch-compact { justify-content: flex-end; }

/* ⭐⭐⭐⭐⭐ С251: вкладки программ площадки («Клуб | Школа | Видео») на планшете.
   Плавающая полоска сверху по центру — не занимает место в рабочей области и одинаково стоит
   на клубном и школьном экранах. Видна ТОЛЬКО у площадки с двумя и более программами: у
   клиента с одной программой её нет в разметке вовсе (см. ProgramTabs.jsx). */
.program-tabs { position: fixed; top: 6px; left: 50%; transform: translateX(-50%); z-index: 60;
  display: flex; gap: 4px; padding: 4px; border-radius: 12px;
  background: rgba(10, 10, 14, .72); border: 1px solid rgba(255, 255, 255, .16);
  backdrop-filter: blur(8px); }
.program-tab { padding: 6px 16px; border: none; border-radius: 9px; cursor: pointer;
  font-size: 13.5px; font-weight: 600; color: #a8abbc; background: none; }
.program-tab.active { color: #0d1017; background: #8ab4ff; }
/* Школьный экран занимает весь экран: полоска вкладок не должна наезжать на его шапку. */
.school-root:has(.program-tabs) .top { padding-top: 34px; }
/* ⚠️ ФАЙЛ СОБИРАЕТСЯ СКРИПТОМ — правки вносить в education/panel/src/styles.css,
   затем выполнить: python3 tools/school_panel_import.py (см. С251, объединение программ).
   Все селекторы заключены в скоуп .school-root, чтобы школьные классы (.app, .card, .btn)
   не пересекались с панелью зала — в С225 такие коллизии дважды ломали вёрстку. */
/* UnitVR School — панель школы.
   ⭐С247 (Михаил: «панель School в стиле UnitVR Club — те же цвета, единый стиль, логотипы разные»): палитра и
   стекло взяты из планшета Club (src/app/src/styles/theme.css): тёплая тёмная сцена, нейтральное матовое стекло,
   акцент сине-сиреневый (#8ab4ff → #b89bff), тонкий шрифт. Прежний неон (бирюза + розовый) заменён. */
.school-root {
  /* ⛔С247 (Михаил: «сверил с дизайном UnitVR Клубы — много различий по цветам и стилям»): недостающие
     переменные Club — два уровня приглушённого текста, подложка чипов и радиус кнопок. */
  --bg: #08090e; --glass: rgba(0, 0, 0, .85); --line: rgba(255, 255, 255, .18);   /* ⛔С247: у Club блоки чёрные */
  --fg: #eceef2; --fg-dim: #a8abbc; --fg-mute: #7c8096; --mute: #a8abbc;
  --chip: rgba(10, 10, 14, .45); --btn-radius: 13px;
  --cyan: #8ab4ff; --pink: #b89bff; --gold: #ffd180;
  --green: #7ee6a0; --red: #ff9fac; --lilac: #b89bff; --blue: #8ab4ff;
  --accent: #8ab4ff; --accent2: #b89bff; --accent-fg: #0d1017;
  --inset-bg: rgba(255, 255, 255, .045); --inset-border: rgba(255, 255, 255, .18);
  --r: 18px;
  --scene:
    radial-gradient(circle at 78% -8%, rgba(139, 81, 48, .26), transparent 46%),
    radial-gradient(circle at 15% 108%, rgba(139, 81, 48, .26), transparent 46%),
    radial-gradient(circle at 15% -8%, rgba(139, 81, 48, .26), transparent 46%),
    radial-gradient(circle at 78% 108%, rgba(139, 81, 48, .26), transparent 46%),
    radial-gradient(circle at 50% 50%, rgba(0, 0, 0, .22), transparent 60%),
    linear-gradient(160deg, #1c1408 0%, #0d0e14 55%, #08090e 100%);
}
.school-root, .school-root * { box-sizing: border-box; }
.school-root { margin: 0; background: var(--bg); color: var(--fg); font: 15px/1.45 system-ui, -apple-system, "Segoe UI", Roboto, sans-serif; }
.school-root {
  min-height: 100vh; padding: 0 16px 32px;
  background: var(--scene); background-attachment: fixed; font-weight: 300; -webkit-font-smoothing: antialiased;
}
.school-root button, .school-root input { font: inherit; color: inherit; }
.school-root .muted { color: var(--mute); }
.school-root .small { font-size: 13.5px; }
.school-root .center { min-height: 60vh; display: grid; place-items: center; }
.school-root .brand { font-weight: 500; letter-spacing: .01em; font-size: 23px; }
.school-root .brand .product { color: var(--cyan); text-shadow: 0 0 14px rgba(138, 180, 255, .55); }
.school-root .card {
  position: relative; background: var(--glass); border: 1px solid var(--line); border-radius: var(--r); padding: 16px;
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, .08), 0 12px 32px rgba(0, 0, 0, .5);
  backdrop-filter: blur(28px) saturate(150%); -webkit-backdrop-filter: blur(28px) saturate(150%);
  overflow: hidden;
}
/* ⛔С247: у Club поверх стекла лежит еле заметный шум (.grain) — без него поверхности выглядят «пластиковыми» */
.school-root .card::before {
  content: ""; position: absolute; inset: 0; pointer-events: none; border-radius: inherit;
  opacity: .025; mix-blend-mode: overlay;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='120' height='120'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.8' numOctaves='3'/%3E%3C/filter%3E%3Crect width='120' height='120' filter='url(%23n)'/%3E%3C/svg%3E");
}
.school-root .card > * { position: relative; }
.school-root .card-h { display: flex; align-items: center; justify-content: space-between; gap: 10px; margin-bottom: 12px; }
/* ⛔С247: заголовки блоков у Club — мелкая разрядка капителью, а не крупный полужирный текст */
.school-root h2 { margin: 0; font-size: 10.5px; font-weight: 300; letter-spacing: .06em; text-transform: uppercase; color: var(--fg-dim); }
.school-root .tag { font-size: 11px; font-weight: 500; padding: 6px 14px; border-radius: 100px; background: var(--inset-bg); color: var(--fg-dim); border: 1px solid var(--inset-border); white-space: nowrap; }
.school-root .tag.live { color: var(--bg); background: var(--pink); border-color: transparent; box-shadow: 0 0 12px rgba(184, 155, 255, .55); }
/* ⛔С247: у Club кнопка — капсула без рамки на тёмной подложке, тонким шрифтом (см. .pill в layout.css) */
.school-root .btn {
  min-height: 40px; padding: 9px 15px; border-radius: var(--btn-radius); border: 0;
  background: var(--chip); color: var(--fg); cursor: pointer; font-size: 12.5px; font-weight: 500;
  transition: transform .08s, opacity .2s;
}
.school-root .btn:active { transform: scale(.97); }
.school-root .btn:disabled { opacity: .4; cursor: default; }
.school-root .btn.primary { background: linear-gradient(135deg, var(--accent), var(--accent2)); color: var(--accent-fg); }
.school-root .btn.danger { background: transparent; box-shadow: inset 0 0 0 1px var(--red); color: var(--red); }
.school-root .btn.ghost { background: transparent; box-shadow: inset 0 0 0 1px var(--inset-border); color: var(--fg-dim); }
.school-root .btn:disabled { background: var(--chip); color: var(--fg-mute); opacity: 1; }
.school-root .btn.big { min-height: 52px; width: 100%; font-size: 15px; margin-top: 14px; }
.school-root .btn.wide { width: 100%; margin-top: 14px; }
.school-root .btn.small { min-height: 34px; padding: 6px 12px; font-size: 11.5px; }
.school-root input {
  width: 100%; min-height: 44px; border-radius: 10px; border: 1px solid var(--inset-border); background: var(--chip);
  padding: 0 14px; font-size: 14px; font-weight: 300; outline: none;
}
.school-root input:focus { border-color: rgba(138, 180, 255, .6); }
.school-root .err { color: var(--red); }
.school-root .warn { color: var(--gold); background: rgba(255, 209, 128, .08); border: 1px solid rgba(255, 209, 128, .3); border-radius: 12px; padding: 10px 12px; margin: 10px 0; font-size: 12.5px; }
.school-root .hint { color: var(--mute); font-size: 13px; margin: 6px 0 0; }
/* вход */
.school-root .login { min-height: 100vh; display: grid; place-items: center; }
.school-root .login-card { width: min(400px, 100%); display: grid; gap: 12px; text-align: center; }
.school-root .login-card .brand { font-size: 26px; margin-bottom: 2px; }
.school-root .login-card p { margin: 0 0 6px; }
/* каркас */
/* ⛔С247 (Михаил: «в верхней шапке логотип слева и Выйти справа выпадают из чёрной шапки»): шапка была
   полосой-градиентом во всю ширину страницы, а крайние элементы уезжали за её края. Теперь это такой же
   чёрный блок, как остальные, — всё внутри него. */
.school-root .top {
  position: sticky; top: 0; z-index: 5; display: flex; align-items: center; gap: 14px; flex-wrap: wrap;
  max-width: 1600px; margin: 0 auto 14px; padding: 10px 16px; border-radius: var(--r);
  background: var(--glass); border: 1px solid var(--line);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, .08), 0 12px 32px rgba(0, 0, 0, .5);
  backdrop-filter: blur(28px) saturate(150%); -webkit-backdrop-filter: blur(28px) saturate(150%);
}
.school-root .head-gap { flex: 1; }
/* ⛔С247: название школы в шапке убрано — его видно в кабинете */
.school-root .conn { display: flex; align-items: center; gap: 8px; font-size: 13px; color: var(--mute); }
.school-root .conn i { width: 10px; height: 10px; border-radius: 50%; background: #555; }
.school-root .conn.on i { background: var(--green); box-shadow: 0 0 10px var(--green); }
.school-root .conn.off i { background: var(--red); box-shadow: 0 0 10px var(--red); }
.school-root .layout { display: grid; gap: 14px; grid-template-columns: minmax(280px, 292px) 1fr; max-width: 1600px; margin: 0 auto; align-items: start; }
@media (max-width: 900px) {
.school-root .layout { grid-template-columns: 1fr; }
/* ⛔С248 (Михаил: «слева внизу вообще ничего не написано»): на узком экране колонка шлемов идёт
     сверху во всю длину, и строка версии оказывалась в самом низу длинной страницы. Даём списку свою
     прокрутку — версия видна сразу под ним. */
.school-root .hlist { max-height: 46vh; overflow-y: auto; overscroll-behavior: contain; }

}
.school-root .side { display: flex; flex-direction: column; min-width: 0; padding: 12px; }
.school-root .side .card-h { margin-bottom: 8px; }
/* ⭐С248: компактное переключение игры во время урока — плитки слева, класс и кнопка справа */
.school-root .lesson.switch { flex-shrink: 0; }
.school-root .switch-row { display: grid; grid-template-columns: 1fr minmax(220px, 300px); gap: 14px; align-items: start; }
.school-root .games.mini { grid-template-columns: repeat(auto-fill, minmax(112px, 1fr)); }
.school-root .games.mini .cover { height: 62px; }
.school-root .games.mini .g-title { font-size: 12px; }
.school-root .switch-set { display: flex; flex-direction: column; gap: 6px; }
.school-root .switch-set .btn.big { margin-top: 4px; min-height: 44px; }
@media (max-width: 900px) {
.school-root .switch-row { grid-template-columns: 1fr; }

}
/* ⭐С248: кнопка «Обновить» в шапке — как колокольчик у Club: видна только когда вышла новая версия */
.school-root .update-ready { padding: 7px 14px; font-size: 13px; background: linear-gradient(135deg, var(--accent), var(--accent2)); color: var(--accent-fg); border: 0; }
.school-root .update-ready .bell { display: inline-block; animation: sch-bell-shake 1.6s ease-in-out infinite; }
@keyframes sch-bell-shake { 0%, 70%, 100% { transform: rotate(0); } 75% { transform: rotate(12deg); } 85% { transform: rotate(-12deg); } }
/* ⭐С248: номер версии панели — тихая строка внизу колонки шлемов */
.school-root .ver-foot { margin-top: 8px; padding-top: 8px; border-top: 1px solid var(--line); font-size: 11px; color: var(--fg-mute); text-align: center; letter-spacing: .02em; }
.school-root .btn.add { width: 100%; min-height: 40px; border-style: dashed; color: var(--mute); background: transparent; font-weight: 500; margin-bottom: 8px; flex-shrink: 0; }
.school-root .hlist { display: grid; gap: 8px; align-content: start; }
.school-root .work { display: flex; flex-direction: column; gap: 12px; min-width: 0; }
/* ⭐С245 единый экран (как UnitVR Club): на планшете и ноутбуке страница не прокручивается совсем —
   всё на одном экране, у колонки шлемов и у каждого блока пульта своя прокрутка. На телефоне (<900px) — обычная лента. */
@media (min-width: 901px) {
.school-root { height: 100%; overflow: hidden; }
.school-root { padding: 0 12px; }
.school-root { height: 100%; }
.school-root .app { height: 100%; display: flex; flex-direction: column; }
.school-root .top { padding: 8px 16px; margin-bottom: 12px; }
.school-root .app > .layout { flex: 1; min-height: 0; width: 100%; grid-template-rows: minmax(0, 1fr); align-items: stretch; padding-bottom: 12px; }
.school-root .side { height: 100%; min-height: 0; }
.school-root .hlist { flex: 1; min-height: 0; overflow-y: auto; overscroll-behavior: contain; -webkit-overflow-scrolling: touch; }
.school-root .work { height: 100%; min-height: 0; }
/* ⛔С247 (Михаил: «нужно блоки разделить — при поднятии правого блока чтобы игры не поднимались,
     получается должно быть 3 блока раздельно»): прокручивалась вся карточка запуска целиком, поэтому
     список настроек утягивал за собой обложки игр. Теперь карточка не прокручивается, а каждый её
     столбец — игры и настройки — крутится сам по себе, как колонка шлемов слева. */
.school-root .work > .lesson.start { flex: 1; min-height: 0; overflow: hidden; display: flex; flex-direction: column; }
.school-root .work > .lesson.start .start-grid { flex: 1; min-height: 0; align-items: stretch; }
.school-root .start-r { display: flex; flex-direction: column; }
.school-root .start-l, .school-root .start-r { min-height: 0; overflow-y: auto; overscroll-behavior: contain; -webkit-overflow-scrolling: touch; padding-right: 4px; }
.school-root .pult { flex: 1; min-height: 0; display: flex; flex-direction: column; }
.school-root .pult-grid { flex: 1; min-height: 0; grid-template-columns: 1.2fr 1fr 1fr; grid-template-rows: minmax(0, 1.15fr) minmax(0, 1fr); }
.school-root .p-block { min-height: 0; overflow-y: auto; overscroll-behavior: contain; }
.school-root .b-program { grid-column: 1; grid-row: 1; }
.school-root .b-task { grid-column: 1; grid-row: 2; }
.school-root .b-students { grid-column: 2; grid-row: 1 / span 2; display: flex; flex-direction: column; }
.school-root .b-students .plist { flex: 1; min-height: 0; max-height: none; }
.school-root .b-compete { grid-column: 3; grid-row: 1; }
.school-root .b-log { grid-column: 3; grid-row: 2; display: flex; flex-direction: column; }
.school-root .b-log .loglist { flex: 1; min-height: 0; max-height: none; }
.school-root .b-perf { display: none; }
.school-root .hlist::-webkit-scrollbar, .school-root .p-block::-webkit-scrollbar { width: 0; }

}
/* низкие экраны (планшет 1280×800 с панелями браузера) — всё плотнее */
@media (min-width: 901px) and (max-height: 860px) {
.school-root .card { padding: 12px; }
.school-root .p-block { padding: 10px; }
.school-root .card-h { margin-bottom: 8px; }
.school-root .btn { min-height: 38px; padding: 6px 12px; }
.school-root .seg button { min-height: 34px; padding: 4px 8px; }
.school-root .field { margin-bottom: 10px; }
.school-root .question { font-size: 14.5px; margin: 2px 0 8px; }
.school-root .plist li, .school-root .board li { padding: 5px 8px; }
.school-root .apples { padding: 6px 10px; }
.school-root .apples b { font-size: 22px; }
.school-root .topicsel { min-height: 38px; font-size: 15px; }

}
/* урок */
/* ⛔С244 (Михаил: «свайпаю вверх к "Начать урок" — поднимается панель игр и перекрывает кнопку»): карточка
   урока была липкой, на планшете она выше экрана — низ с кнопкой не прокручивался, а следующие карточки
   наезжали поверх. Липкость убрана. */
.school-root .lesson.live { border-color: rgba(184, 155, 255, .45); box-shadow: 0 0 30px rgba(184, 155, 255, .12), inset 0 1px 0 rgba(255, 255, 255, .06); }
.school-root .field { margin-bottom: 14px; }
.school-root .lbl { display: block; color: var(--mute); font-size: 13px; margin-bottom: 6px; }
.school-root .chips { display: flex; flex-wrap: wrap; gap: 8px; }
.school-root .chip { min-height: 40px; padding: 6px 14px; border-radius: 99px; border: 1px solid var(--line); background: rgba(0, 0, 0, .25); cursor: pointer; }
.school-root .chip.on { border-color: rgba(138, 180, 255, .7); background: rgba(138, 180, 255, .16); }
.school-root .seg { display: flex; flex-wrap: wrap; gap: 4px; background: rgba(0, 0, 0, .28); border: 1px solid var(--line); border-radius: 12px; padding: 4px; }
.school-root .seg button { flex: 1 1 auto; min-height: 40px; padding: 6px 10px; border: 0; border-radius: 9px; background: transparent; color: var(--mute); cursor: pointer; white-space: nowrap; }
.school-root .seg button.on { background: rgba(138, 180, 255, .18); color: var(--fg); box-shadow: inset 0 0 0 1px rgba(138, 180, 255, .55); }
.school-root .lesson-title { font-size: 20px; font-weight: 700; }
.school-root .timer { font-size: 52px; font-weight: 800; letter-spacing: .02em; margin: 6px 0; color: var(--fg); text-shadow: 0 0 18px rgba(184, 155, 255, .45); font-variant-numeric: tabular-nums; }
.school-root .row { display: flex; flex-wrap: wrap; gap: 10px; margin-top: 14px; }
/* шлемы */
.school-root .grid { display: grid; gap: 10px; grid-template-columns: repeat(auto-fill, minmax(220px, 1fr)); }
.school-root .helmet { border: 1px solid var(--line); border-radius: 14px; background: rgba(0, 0, 0, .24); padding: 12px; display: grid; gap: 8px; }
.school-root .helmet.teacher { border-color: rgba(255, 194, 74, .55); box-shadow: 0 0 16px rgba(255, 194, 74, .12); }
.school-root .h-top { display: flex; align-items: center; gap: 8px; }
.school-root .num { min-width: 34px; height: 34px; border-radius: 9px; border: 1px solid var(--line); background: rgba(255, 255, 255, .05); cursor: pointer; font-weight: 700; }
.school-root .name { flex: 1; text-align: left; background: none; border: 0; padding: 0; font-weight: 650; cursor: pointer; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.school-root .dot { width: 11px; height: 11px; border-radius: 50%; background: #56606a; flex-shrink: 0; }
.school-root .dot.on { background: var(--green); box-shadow: 0 0 9px var(--green); }
.school-root .dot.play { background: var(--pink); box-shadow: 0 0 9px var(--pink); }
.school-root .dot.sleep { background: var(--gold); }
.school-root .h-mid { display: flex; justify-content: space-between; font-size: 13.5px; }
.school-root .st.off { color: var(--mute); }
.school-root .st.on { color: var(--green); }
.school-root .st.play { color: var(--pink); }
.school-root .st.sleep { color: var(--gold); }
.school-root .bat { color: var(--mute); }
.school-root .member { font-size: 12.5px; color: var(--mute); }
.school-root .member.sent { color: var(--cyan); }
.school-root .member.skipped_offline, .school-root .member.skipped_asleep, .school-root .member.skipped_busy { color: var(--gold); }
.school-root .h-actions { display: flex; gap: 8px; flex-wrap: wrap; }
.school-root .role { min-height: 34px; padding: 5px 10px; border-radius: 9px; border: 1px solid var(--line); background: transparent; color: var(--mute); cursor: pointer; font-size: 13px; }
.school-root .role.on { color: var(--bg); background: var(--gold); border-color: transparent; font-weight: 700; }
/* окно и уведомление */
.school-root .modal { position: fixed; inset: 0; z-index: 20; display: grid; place-items: center; padding: 16px; background: rgba(0, 0, 0, .6); backdrop-filter: blur(4px); }
.school-root .modal-card { width: min(420px, 100%); display: grid; gap: 12px; background: #0b1622; }
.school-root .modal-card p { margin: 0; }
.school-root .toast { position: fixed; left: 50%; bottom: 24px; transform: translateX(-50%); z-index: 30; max-width: calc(100vw - 32px);
  background: #0f2233; border: 1px solid var(--line); border-radius: 14px; padding: 12px 18px; box-shadow: 0 10px 30px rgba(0, 0, 0, .5); }
.school-root .tag.trial { color: var(--gold); border-color: rgba(255, 194, 74, .4); }
.school-root .warn-tag { color: var(--gold); border-color: rgba(255, 194, 74, .5); text-decoration: none; }
/* ⭐С241: игры обложками — только наши (UnitVR Education) */
.school-root .games { display: grid; gap: 10px; grid-template-columns: repeat(auto-fill, minmax(104px, 1fr)); }
.school-root .game { display: grid; gap: 6px; padding: 6px; border-radius: 14px; border: 1px solid var(--line); background: rgba(0, 0, 0, .25); cursor: pointer; text-align: center; }
.school-root .game .cover { display: block; aspect-ratio: 7 / 10; max-width: 100%; border-radius: 10px; overflow: hidden; background: rgba(255, 255, 255, .05); }
.school-root .game img { width: 100%; height: 100%; object-fit: cover; display: block; }
.school-root .game .g-title { font-size: 13.5px; font-weight: 600; line-height: 1.25; }
.school-root .game.on { border-color: rgba(138, 180, 255, .8); background: rgba(138, 180, 255, .14); box-shadow: 0 0 18px rgba(138, 180, 255, .2); }
/* ⭐С241: пульт урока через сервер связи */
.school-root .pult { border-color: rgba(138, 180, 255, .35); }
.school-root .pult-grid { display: grid; gap: 14px; grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); }
.school-root .p-block { background: rgba(0, 0, 0, .2); border: 1px solid var(--line); border-radius: 14px; padding: 14px; min-width: 0; }
.school-root h3 { margin: 0; font-size: 16px; }
.school-root .conn:not(.on):not(.off) i { background: var(--gold); }
.school-root .task { display: flex; gap: 10px; margin-bottom: 8px; }
.school-root .apples { flex: 1; display: flex; align-items: baseline; gap: 8px; padding: 10px 12px; border-radius: 12px; background: rgba(0, 0, 0, .25); }
.school-root .apples b { font-size: 28px; line-height: 1; }
.school-root .apples .red { color: var(--red); }
.school-root .apples .blue { color: var(--blue); }
.school-root .apples span { color: var(--mute); }
.school-root .question { min-height: 26px; margin: 4px 0 12px; font-size: 16px; }
.school-root .question em { font-style: normal; color: var(--gold); font-weight: 700; }
.school-root .btns { display: grid; grid-template-columns: repeat(2, 1fr); gap: 8px; }
.school-root .btn.gold { background: linear-gradient(180deg, rgba(255, 194, 74, .28), rgba(255, 194, 74, .1)); border-color: rgba(255, 194, 74, .55); }
.school-root .btn.blue { background: linear-gradient(180deg, rgba(77, 134, 255, .26), rgba(77, 134, 255, .1)); border-color: rgba(77, 134, 255, .5); }
.school-root .btn.green { background: linear-gradient(180deg, rgba(70, 226, 138, .28), rgba(70, 226, 138, .1)); border-color: rgba(70, 226, 138, .55); }
.school-root .btn.pink { background: linear-gradient(180deg, rgba(184, 155, 255, .32), rgba(184, 155, 255, .12)); border-color: rgba(184, 155, 255, .6); }
.school-root .plist { list-style: none; margin: 0; padding: 0; display: grid; gap: 6px; max-height: 340px; overflow: auto; }
.school-root .plist li { display: grid; grid-template-columns: 32px 1fr auto auto; align-items: center; gap: 8px; padding: 7px 10px; border-radius: 10px; background: rgba(0, 0, 0, .22); }
.school-root .seat { font-size: 12px; color: var(--mute); text-align: center; border: 1px solid var(--line); border-radius: 8px; padding: 2px 0; }
.school-root .nm { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.school-root .nm small { color: var(--mute); margin-left: 6px; }
.school-root .ans { min-width: 44px; text-align: center; font-weight: 700; border-radius: 8px; padding: 2px 8px; background: rgba(255, 255, 255, .05); color: var(--mute); }
.school-root .ans.ok { color: var(--bg); background: var(--green); }
.school-root .ans.bad { color: #fff; background: rgba(255, 90, 90, .75); }
.school-root .stars { color: var(--gold); min-width: 34px; text-align: right; }
.school-root .plist li.teacher .nm { color: var(--gold); }
/* ⭐С246 поднятая рука: золотая строка «?», нажатие даёт слово; говорит — зелёная */
.school-root .plist li.hand { background: rgba(255, 190, 40, .22); box-shadow: inset 0 0 0 1px rgba(255, 200, 60, .7); cursor: pointer; }
.school-root .plist li.floor { background: rgba(60, 230, 120, .2); box-shadow: inset 0 0 0 1px rgba(80, 240, 140, .7); cursor: pointer; }
.school-root .plist .q { display: inline-grid; place-items: center; width: 20px; height: 20px; margin-right: 6px; border-radius: 50%; background: var(--gold); color: #1a1200; font-size: 13px; }
.school-root .plist .q.talk { background: #43e07f; color: #032; }
.school-root .tag.tag-hand { color: #1a1200; background: var(--gold); border-color: transparent; margin-right: 6px; }
.school-root .board { margin: 12px 0 0; padding: 0; list-style: none; display: grid; gap: 6px; counter-reset: pl; }
.school-root .board li { counter-increment: pl; display: grid; grid-template-columns: 28px 1fr auto; gap: 8px; padding: 7px 10px; border-radius: 10px; background: rgba(0, 0, 0, .22); }
.school-root .board li::before { content: counter(pl); color: var(--cyan); font-weight: 700; text-align: center; }
.school-root .board .sc { color: var(--gold); font-weight: 700; }
.school-root .loglist { list-style: none; margin: 0; padding: 0; display: grid; gap: 4px; max-height: 300px; overflow: auto; font-size: 14px; }
.school-root .loglist li { color: var(--mute); }
.school-root .loglist time { color: #56708a; margin-right: 8px; font-variant-numeric: tabular-nums; }
@media (max-width: 420px) {
.school-root .btns { grid-template-columns: 1fr; }

}
/* ⭐С242: регулятор скорости фруктов «Цифруктов» (1…10) */
/* ⛔С247 (Михаил: «когда пальцем поднимаю правый блок, постоянно задеваю скорость Цифруктов и
   увеличиваю её»): ползунок забирал себе ЛЮБОЕ касание, в том числе движение пальцем вверх для
   прокрутки блока. touch-action: pan-y отдаёт вертикальный жест прокрутке, а значение меняется
   только движением вбок — как и задумано у ползунка. */
.school-root input.speed { width: 100%; accent-color: #8ab4ff; height: 28px; touch-action: pan-y; }
.school-root .speed-scale { display: flex; justify-content: space-between; color: var(--mute); font-size: 12px; margin-top: 2px; }
/* ⭐С242: живые замеры нагрузки шлема учителя */
.school-root .perf-grid { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 8px; }
.school-root .perf-grid div { background: rgba(0, 0, 0, .25); border: 1px solid var(--line); border-radius: 10px; padding: 8px; }
.school-root .perf-grid b { display: block; font-size: 20px; color: var(--fg); }
.school-root .perf-grid span { color: var(--mute); font-size: 12px; }
.school-root .topicsel { width: 100%; min-height: 44px; margin: 4px 0 6px; padding: 8px 12px; border-radius: 12px; border: 1px solid var(--line); background: rgba(0, 0, 0, .28); color: var(--fg); font-size: 16px; }
.school-root .topicsel option { background: #07131d; color: #e8f6ff; }
/* ⭐С245 единый экран: полоса идущего урока, форма запуска в две колонки, компактные шлемы, пульт */
.school-root .lesson.bar { display: flex; align-items: center; gap: 16px; padding: 10px 16px; flex-shrink: 0; }
.school-root .bar-main { flex: 1; min-width: 0; display: grid; gap: 2px; justify-items: start; }
.school-root .bar .lesson-title { font-size: 18px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 100%; }
.school-root .bar .timer { font-size: 40px; margin: 0; }
.school-root .bar-actions { display: flex; gap: 8px; }
.school-root .start-grid { display: grid; gap: 18px; grid-template-columns: 1fr 1fr; }
@media (max-width: 900px) {
.school-root .start-grid { grid-template-columns: 1fr; }

}
.school-root .start .btn.big { margin-top: 8px; }
/* ⛔С247 (Михаил: «чтобы в разделе с играми помещалось 2 игры в ряд, обложки были крупнее»):
   плитки подбирались авторазмером и на планшете вставали по три-четыре мелкими. Жёстко две в ряд —
   обложка занимает половину блока игр и читается с расстояния вытянутой руки. */
.school-root .start .games { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 12px; }
.school-root .start .game { padding: 8px; }
.school-root .start .game .g-title { font-size: 15px; }
.school-root .helmet { padding: 8px 10px; gap: 6px; border-radius: 12px; }
.school-root .h-mid { gap: 8px; align-items: baseline; }
.school-root .h-mid .member { flex: 1; text-align: right; font-size: 12px; }
.school-root .num { min-width: 30px; height: 30px; }
.school-root .role { min-height: 30px; padding: 3px 8px; }
.school-root .btn.small { min-height: 30px; padding: 3px 8px; font-size: 12.5px; }
.school-root .pult-h { display: flex; align-items: center; gap: 12px; margin: 0 0 8px; flex-shrink: 0; }
.school-root .pult-h h2 { font-size: 16px; }
.school-root .pult-h .conn { margin-left: auto; }
.school-root .warn-inline { color: var(--gold); font-size: 13px; }
.school-root .pult-grid { gap: 10px; }
.school-root .prog-row { display: grid; gap: 8px; grid-template-columns: auto 1fr; align-items: center; }
.school-root .prog-row .topicsel { margin: 0; }
.school-root .prog-row .seg button { flex: 0 0 auto; }
@media (max-width: 1150px) {
.school-root .prog-row { grid-template-columns: 1fr; }

}
.school-root .btns.three { grid-template-columns: repeat(3, 1fr); }
.school-root .b-task .btns { grid-template-columns: repeat(3, 1fr); }
.school-root .b-compete .seg button { flex: 1 1 0; }
.school-root .b-compete .btn.wide { margin-top: 8px; }
.school-root .b-compete input.speed { height: 22px; }
.school-root .b-compete .lbl { margin-top: 6px !important; margin-bottom: 4px; }
.school-root .prog-row { grid-template-columns: 1fr !important; gap: 6px; }
.school-root .seg.grades { flex-wrap: nowrap; }
.school-root .seg.grades button { flex: 1 1 0; min-width: 0; padding: 4px 0; }
.school-root .b-program .question { margin: 6px 0 8px; font-size: 14px; }
.school-root .b-program .btn, .school-root .b-task .btn { font-size: 13.5px; padding: 4px 6px; min-height: 36px; }
.school-root .task { gap: 6px; margin-bottom: 4px; }
.school-root .apples { align-items: center; justify-content: space-between; padding: 4px 10px; }
.school-root .apples span { font-size: 13px; }
.school-root .b-task .question { font-size: 13.5px; }
.school-root .helmet { grid-template-columns: 1fr; }
.school-root .helmet .h-mid { font-size: 12.5px; }
.school-root .h-actions .role, .school-root .h-actions .btn.small { font-size: 12px; }
.school-root .helmet .h-top { gap: 6px; }
.school-root .helmet { position: relative; }
@media (min-width: 901px) {
.school-root .helmet { display: grid; grid-template-columns: auto 1fr; grid-template-areas: "top top" "mid act"; column-gap: 8px; row-gap: 4px; }
.school-root .helmet .h-top { grid-area: top; }
.school-root .helmet .h-mid { grid-area: mid; flex-direction: column; gap: 0; align-items: flex-start; }
.school-root .helmet .h-actions { grid-area: act; justify-content: flex-end; align-items: center; gap: 6px; }

}
.school-root .btns > .btn { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.school-root .b-log .loglist, .school-root .b-students .plist { align-content: start; }
/* ⭐С245 трансляция экрана шлема: окно поверх панели, можно свернуть в угол, не разрывая поток */
.school-root .btn.cast-b { border-color: rgba(138, 180, 255, .5); color: var(--cyan); }
.school-root .castwin { position: fixed; z-index: 25; inset: 12px; display: flex; flex-direction: column; background: #050d15; border: 1px solid rgba(138, 180, 255, .45);
  border-radius: 16px; box-shadow: 0 20px 60px rgba(0, 0, 0, .7); overflow: hidden; }
.school-root .castwin.mini { inset: auto 16px 16px auto; width: 340px; height: 230px; cursor: pointer; }
.school-root .cast-h { display: flex; align-items: center; gap: 10px; padding: 8px 12px; border-bottom: 1px solid var(--line); flex-shrink: 0; }
.school-root .cast-h b { font-size: 16px; }
.school-root .cast-st { flex: 1; color: var(--gold); font-size: 13px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.school-root .cast-st.live { color: var(--green); }
.school-root .castwin.mini .cast-h b { font-size: 13px; }
.school-root .cast-body { position: relative; flex: 1; min-height: 0; background: #000; display: grid; place-items: center; }
.school-root .cast-body video { width: 100%; height: 100%; object-fit: contain; background: #000; }
.school-root .cast-hint { position: absolute; max-width: 460px; padding: 14px; text-align: center; color: var(--mute); font-size: 14px; }
/* ── ⭐С247 единый стиль с UnitVR Club: стекло, вставки, кнопки, бренд ─────────────────────────────── */
/* ⛔С247 (Михаил: «шапка чёрная, а нижние два блока ты забыл сделать чёрными»): поверх чёрного лежал
   светлый градиент от прежнего стекла — из-за него блоки выглядели серыми рядом с шапкой. */
.school-root .card {
  background-color: var(--glass);
  background-image: none;
  -webkit-backdrop-filter: blur(30px) saturate(140%); backdrop-filter: blur(30px) saturate(140%);
  border: 1px solid rgba(255, 255, 255, .08);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, .1), 0 12px 32px rgba(0, 0, 0, .45);
}
.school-root h2, .school-root h3, .school-root .lesson-title { font-weight: 500; }
.school-root .p-block, .school-root .helmet, .school-root .game, .school-root .plist li, .school-root .board li, .school-root .apples, .school-root .perf-grid div {
  background: var(--inset-bg); border: 1px solid var(--inset-border);
}
.school-root .plist li, .school-root .board li { border-color: rgba(255, 255, 255, .1); }
/* ⛔С247: цветные кнопки пульта — контуром, как .pill-outline у Club (рамки у базовой кнопки больше нет) */
.school-root .btn.pink { background: linear-gradient(135deg, var(--accent), var(--accent2)); color: var(--accent-fg); }
.school-root .btn.gold { background: transparent; box-shadow: inset 0 0 0 1px var(--gold); color: var(--gold); }
.school-root .btn.green { background: transparent; box-shadow: inset 0 0 0 1px var(--green); color: var(--green); }
.school-root .btn.blue { background: transparent; box-shadow: inset 0 0 0 1px var(--accent); color: var(--accent); }
.school-root input, .school-root .topicsel { background: rgba(10, 10, 14, .45); border-color: rgba(255, 255, 255, .18); font-weight: 300; }
.school-root input:focus { border-color: rgba(138, 180, 255, .45); }
.school-root .chip.on, .school-root .seg button.on, .school-root .game.on { background: rgba(138, 180, 255, .16); box-shadow: inset 0 0 0 1px rgba(138, 180, 255, .6); }
.school-root .seg { background: rgba(10, 10, 14, .45); }
.school-root .tag.live { color: var(--accent-fg); background: linear-gradient(135deg, var(--accent), var(--accent2)); box-shadow: 0 0 12px rgba(184, 155, 255, .4); }
.school-root .timer { text-shadow: none; font-weight: 600; }
.school-root .lesson.live { border-color: rgba(184, 155, 255, .4); box-shadow: 0 0 26px rgba(184, 155, 255, .12), inset 0 1px 0 rgba(255, 255, 255, .08); }
.school-root .dot.play { background: var(--accent2); box-shadow: 0 0 9px var(--accent2); }
.school-root .st.play { color: var(--accent2); }
.school-root .modal-card { background: rgba(20, 20, 24, .97); }
.school-root .toast { background: rgba(20, 20, 30, .94); border-color: rgba(255, 255, 255, .12); }
.school-root .castwin { background: #0a0b10; border-color: rgba(138, 180, 255, .4); }
/* бренд: надпись с тем же переливом, что у Club (сине-сиреневый, короткий тёплый блик); слово School — своё */
.school-root .brand { font-weight: 500; letter-spacing: -.01em; }
.school-root .brand .logo, .school-root .brand .product {
  background: linear-gradient(90deg, #8ab4ff 0%, #b89bff 25%, #ffb066 50%, #b89bff 75%, #8ab4ff 100%);
  background-size: 300% 100%; -webkit-background-clip: text; background-clip: text; color: transparent;
  animation: sch-brand-shine 6s linear infinite; text-shadow: none;
}
.school-root .brand .logo { font-weight: 800; }
@keyframes sch-brand-shine { 0% { background-position: 300% 50%; } 100% { background-position: 0% 50%; } }
/* поднятая рука (С246) — золото Club-палитры, говорит — зелёный ok */
.school-root .plist li.hand { background: rgba(255, 209, 128, .18); box-shadow: inset 0 0 0 1px rgba(255, 209, 128, .7); }
.school-root .plist li.floor { background: rgba(126, 230, 160, .16); box-shadow: inset 0 0 0 1px rgba(126, 230, 160, .7); }
/* ⛔С247 (аудит): цели нажатия на планшете были 30–34 px — меньше рекомендованных 44. Поднимаем на сенсорных
   экранах (на мыши плотность оставляем: там попадают точно, а место на едином экране дорого). */
@media (pointer: coarse) {
.school-root .btn.small, .school-root .role, .school-root .num { min-height: 44px; }
.school-root .plist li { padding: 9px 10px; }
.school-root .plist li.hand, .school-root .plist li.floor { cursor: pointer; }

}
/* ⛔С247 (Михаил): карточка шлема — статус и заряд в строке названия, кнопки шире; полоса урока — «Идёт урок» под названием */
.school-root .h-top { flex-wrap: wrap; }
.school-root .h-top .st { margin-left: auto; font-size: 12.5px; }
.school-root .h-top .bat { font-size: 12.5px; color: var(--mute); }
.school-root .h-actions { gap: 6px; }
.school-root .h-actions .role, .school-root .h-actions .btn.small { flex: 1 1 auto; justify-content: center; text-align: center; }
@media (min-width: 901px) {
.school-root .helmet { grid-template-columns: 1fr; grid-template-areas: "top" "mid" "act"; }
.school-root .helmet .h-actions { grid-area: act; justify-content: stretch; }

}
.school-root .bar-sub { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
/* ⭐С247 правка карточки по удержанию: номер и название — просто текст, не кнопки */
.school-root .h-top { cursor: default; -webkit-user-select: none; user-select: none; }
.school-root .h-top .num { display: inline-grid; place-items: center; min-width: 30px; height: 30px; border-radius: 9px;
  border: 1px solid var(--inset-border); background: rgba(255, 255, 255, .05); font-weight: 700; }
.school-root .h-top .name { flex: 1; font-weight: 650; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* ⭐С247 (Михаил: «выбирать режим и класс на планшете перед запуском и тд»): экран запуска стал
   полным — режим, урок, демо-ученики, оформление арены и выбор шлемов. Чтобы он не растянулся на
   два экрана планшета, кнопки режимов двухстрочные, а длинные списки (уроки, шлемы) — столбиком
   с прокруткой. */
/* ⛔С247 (Михаил: «название и пояснение слитно — можно пояснение через тире»): режим одной строкой,
   пояснение тем же шрифтом помельче и приглушённее. */
.school-root .seg.modes button { padding: 8px 10px; line-height: 1.2; }
/* ⛔С247 (Михаил: «пояснение через тире надо шрифт увеличить, а то мелко, плохо видно») */
.school-root .seg.modes button i { font-style: normal; font-size: 14px; opacity: .8; }
/* ⛔С247 (Михаил: «сделай чтобы все 5 классов были в одну строчку») */
.school-root .seg.grades-row { flex-wrap: nowrap; }
.school-root .seg.grades-row button { flex: 1 1 0; min-width: 0; padding-left: 4px; padding-right: 4px; }
.school-root .seg.col { flex-direction: column; align-items: stretch; }
.school-root .seg.col button { text-align: left; }
/* ⛔С247 (Михаил: «в обложке Цифруктов появились цифры 2+3 — это ты добавил?»): пока обложки не было,
   плитку рисовали стилями (неоновый фон и пример «2 + 3» псевдоэлементом). Обложка появилась, а заглушка
   осталась и печаталась ПОВЕРХ картинки. Удалена целиком. */
/* ⛔С247 (Михаил: «кнопку поставить сверху, а если поднимаю правый блок — чтобы она перемещалась в самый
   низ блока для удобства»): кнопка запуска стоит первой в настройках, а как только блок прокручен —
   та же кнопка липнет к нижнему краю блока, поверх содержимого. */
.school-root .launch-bottom .btn.big { margin-top: 4px; }
.school-root .ready { margin: 8px 0 14px; }
/* ⛔С247 (Михаил: «окантовка у кнопки Начать урок пропала»; «расстояние между режимами меньше»;
   «поля выбора урока уже по высоте»; «демо-ученики и время урока в одну строчку списками» — чтобы
   правый блок помещался целиком и не приходилось его прокручивать). */
.school-root .btn.primary { box-shadow: inset 0 0 0 1px rgba(255, 255, 255, .5); }
.school-root .btn.big { min-height: 46px; font-size: 14.5px; }
.school-root .seg.modes { gap: 2px; padding: 3px; }
.school-root .seg.modes button { min-height: 34px; padding: 5px 10px; }
.school-root .seg.modes button i { font-size: 13px; }
.school-root .seg.col button { min-height: 32px; padding: 4px 10px; font-size: 13px; }
.school-root .seg.grades-row button { min-height: 34px; }
.school-root .field { margin-bottom: 10px; }
.school-root .field.two { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; }
/* ⛔С247: свой выпадающий список вместо системного — как .dropdown-btn/.dropdown-menu у Club */
.school-root .pick { position: relative; display: block; }
.school-root .pick-btn {
  width: 100%; min-height: 38px; border-radius: 10px; border: 1px solid var(--inset-border);
  background: var(--chip); color: var(--fg); font-size: 13px; font-weight: 300; padding: 0 10px;
  display: flex; align-items: center; justify-content: space-between; gap: 8px; cursor: pointer;
}
.school-root .pick-btn i { width: 0; height: 0; border: 5px solid transparent; border-top-color: var(--fg-dim); margin-top: 4px; }
.school-root .pick-btn.open i { transform: rotate(180deg); margin-top: -4px; }
.school-root .pick-menu {
  position: absolute; left: 0; right: 0; top: calc(100% + 4px); z-index: 20; display: grid; gap: 2px;
  padding: 4px; border-radius: 12px; background: rgba(0, 0, 0, .95); border: 1px solid var(--inset-border);
  box-shadow: 0 12px 32px rgba(0, 0, 0, .6);
}
.school-root .pick-menu button {
  min-height: 34px; padding: 4px 10px; border: 0; border-radius: 8px; background: transparent;
  color: var(--fg); font-size: 13px; font-weight: 300; text-align: left; cursor: pointer;
}
.school-root .pick-menu button.on { color: var(--accent); background: rgba(138, 180, 255, .12); }
/* ⛔С247: рамка фокуса от системы обрезалась соседним блоком — свой список её не рисует */
.school-root .pick-btn:focus, .school-root .pick-menu button:focus, .school-root .btn:focus { outline: none; }
.school-root .speed-scale { margin: 0 0 2px; }
.school-root .speed-scale { font-size: 11px; }
.school-root input.speed { height: 24px; }
/* ⛔С247 (Михаил: «"на связи" нужно в зелёной окантовке сделать, как у UnitVR Клуба»): капсула с контуром
   цвета состояния — зелёная на связи, красная при обрыве. */
/* ⛔С247, вторая правка (Михаил: «у нас там тонкая окантовка и свечение сделано»): повторяем .conn-badge
   из Club — полупрозрачная заливка цветом состояния, рамка 1px той же гаммы, свечение на точке. */
.school-root .conn {
  padding: 9px 14px; border-radius: 100px; font-size: 11px; font-weight: 500; gap: 6px;
  background: var(--chip); color: var(--fg-dim); border: 1px solid var(--inset-border); box-shadow: none;
}
.school-root .conn.on { color: var(--green); background: rgba(126, 230, 160, .12); border-color: rgba(126, 230, 160, .3); }
.school-root .conn.off { color: var(--red); background: rgba(255, 159, 172, .12); border-color: rgba(255, 159, 172, .3); }
.school-root .conn i { width: 7px; height: 7px; }
.school-root .conn.on i { background: var(--green); box-shadow: 0 0 6px var(--green); }
.school-root .conn.off i { background: var(--red); box-shadow: 0 0 6px var(--red); }
/* «Выйти» — тем же приёмом, что заметная кнопка обновления в Club: тонкая непрозрачная рамка и свечение */
.school-root .top .btn.danger { box-shadow: inset 0 0 0 1px var(--red), 0 0 8px rgba(255, 159, 172, .4); }
/* ⛔С247 (Михаил: «блок поджали, снизу осталось место — добавь отступы между настройками»): поля дышат */
.school-root .start-r .field { margin-bottom: 14px; }
.school-root .start-r .field.two { margin-bottom: 14px; }
/* ⛔С247 (Михаил: «снизу расстояние ещё есть, но блок слегка приподнимается»): последний элемент колонки
   держал нижний отступ, и содержимое оказывалось на десяток пикселей выше контейнера — этого хватало,
   чтобы блок начал прокручиваться при пустом месте внизу. Хвостовые отступы снимаем. */
.school-root .start-r > *:last-child, .school-root .start-l > *:last-child { margin-bottom: 0; }
/* ⛔С249 (Михаил: «кнопка Начать урок уезжает сразу после Обновить и остаётся внизу, пока не перезагружу
   приложение»): кнопка была прижата к низу блока, а высоту блока задаёт экран — после перезагрузки страницы
   внутри приложения планшет отдаёт другую высоту окна, и кнопку уносило вниз. Теперь она идёт сразу под
   скоростью фруктов и от высоты экрана не зависит. */
.school-root .start-r .launch-bottom { margin-top: 18px; }
.school-root .start-r .field:last-of-type { margin-bottom: 0; }
.school-root .start-r, .school-root .start-l { padding-bottom: 0; }
/* ⛔С247 (Михаил: «цифра 5 посередине, а точка на шкале левее — она должна быть ровно под пятёркой»):
   подписи раскладывались поровну по ширине, поэтому «5» вставала в середину поля. Но у ползунка 1…10
   середина — это 5,5, а пятёрка стоит на 4/9 длины. Ставим подписи по фактическим долям шкалы. */
.school-root .speed-scale { position: relative; display: block; height: 14px; }
.school-root .speed-scale span { position: absolute; top: 0; }
.school-root .speed-scale span:nth-child(1) { left: 0; }
.school-root .speed-scale span:nth-child(2) { left: 44.4%; transform: translateX(-50%); }
.school-root .speed-scale span:nth-child(3) { right: 0; }
/* ⛔С247 (Михаил: «кнопка Начать урок не помещается буквально чуть-чуть»): забираем десяток пикселей
   у отступов и высоты кнопки — этого хватает, чтобы блок настроек влез целиком, без прокрутки. */
.school-root .start-r .field, .school-root .start-r .field.two { margin-bottom: 11px; }
.school-root .start-r .card-h { margin-bottom: 9px; }
.school-root .launch-bottom .btn.big { margin-top: 0; min-height: 44px; }
.school-root .start-r .lbl { margin-bottom: 4px; }
/* ⛔С247 (Михаил: «верхнюю шапку на планшете надо на всю ширину сделать, как нижние блоки; лого слева
   и остальное справа»): шапка теперь ровно по ширине сетки блоков, логотип прижат к левому краю,
   подписка, связь и «Выйти» — к правому. */
.school-root .top { width: 100%; }
.school-root .top .brand { margin-right: auto; }
.school-root .head-gap { display: none; }
@media (min-width: 901px) {
.school-root .app { padding: 0; }
.school-root .top { max-width: none; margin-left: 0; margin-right: 0; }
.school-root .app > .layout { max-width: none; }

}
/* ⛔С247 (Михаил: «названия блоков Игры и настройки надо на такой же высоте, как название блока Шлемы»):
   у «Шлемов» строку заголовка растягивала капсула-счётчик, у остальных её нет — и текст вставал выше.
   Задаём строке заголовка одинаковую высоту во всех блоках. */
.school-root .card-h { min-height: 32px; }
.school-root .start-r .card-h, .school-root .start-l .card-h { margin-bottom: 9px; }
/* ⛔С247 (Михаил: «в Цифруктах демо-ученики и время урока тоже нужно убирать» — они оставались на
   экране): строка с двумя списками объявлена как grid, и это правило перебивало служебный атрибут
   hidden, которым скрывается блок. */
.school-root [hidden] { display: none !important; }
/* ⛔С247 (Михаил: «на нижние чёрные блоки надо лёгкие окантовки добавить»): чёрное на чёрном сливалось —
   тонкий светлый контур очерчивает каждый блок, как в клубах. */
.school-root .card, .school-root .top { border-color: rgba(255, 255, 255, .12); }
.school-root .card { box-shadow: inset 0 0 0 1px rgba(255, 255, 255, .05), inset 0 1px 0 rgba(255, 255, 255, .1), 0 12px 32px rgba(0, 0, 0, .5); }
/* ⛔С247 (Михаил: «номер шлема — в тонкой фиолетовой окантовке со свечением внутри, как плашка "на связи"») */
.school-root .num {
  border: 1px solid rgba(184, 155, 255, .55); background: rgba(184, 155, 255, .12);
  color: #d8c7ff; font-weight: 500; box-shadow: inset 0 0 10px rgba(184, 155, 255, .25);
}
/* ⛔С247 (Михаил: «кнопка учитель, залитая жёлтым, некрасиво смотрится»): вместо заливки — тонкий
   золотой контур с мягким свечением, как у остальных состояний. */
.school-root .role.on {
  color: var(--gold); background: rgba(255, 209, 128, .12);
  border-color: rgba(255, 209, 128, .55); font-weight: 500;
  box-shadow: inset 0 0 10px rgba(255, 209, 128, .2);
}
/* ⭐С247: версия игры под названием. Пока на шлемах старая сборка — подпись золотая со стрелкой
   «стоит → доступна», после обновления остаётся один номер приглушённым цветом. */
.school-root .g-ver { display: block; font-size: 11px; color: var(--fg-mute); margin-top: 1px; }
.school-root .g-ver.old { color: var(--gold); }
/* ⛔С249 (Михаил: «блок настроек растягивается — Урок, Живой учитель, Оператор, и блок Урок растягивается»):
   колонка настроек — flex-столбец, а кнопки внутри списков — flex: 1 1 auto. Высоту колонки задаёт окно, и
   после «Обновить» планшет отдаёт другую высоту: блоки сжимались или растягивались, кнопки в списках тянулись
   вместе с ними, а «Начать урок» уезжала вниз. Теперь у каждого блока и каждой кнопки списка своя высота
   по содержимому; не хватит места — колонка прокручивается, но ничего не растягивается. */
.school-root .start-r > *, .school-root .start-l > * { flex: 0 0 auto; }
.school-root .seg.col button, .school-root .seg.modes button { flex: 0 0 auto; }
/* ⛔С249 (Михаил: «правое окно стало высокое и ездит вверх-вниз — высоту окон надо меньше»): три режима
   стояли столбиком (114 px) — теперь в один ряд, пояснение мелко под названием. Колонка настроек
   помещается на планшете без прокрутки. */
.school-root .seg.modes { flex-direction: row; flex-wrap: nowrap; }
.school-root .seg.modes button { flex: 1 1 0; min-width: 0; white-space: normal; text-align: center; min-height: 0; padding: 5px 6px; line-height: 1.15; }
.school-root .seg.modes button b { display: block; font-weight: 600; font-size: 14px; }
.school-root .seg.modes button i { display: block; font-size: 11.5px; margin-top: 1px; }
.school-root .start-r > .fit { display: flex; flex-direction: column; }
.school-root .start-r .fit > * { flex: 0 0 auto; }
.school-root .start-r .fit > *:last-child { margin-bottom: 0; }
