/*
 * ЕДИНЫЙ ИСТОЧНИК ПРАВДЫ для стекла и цветовой темы 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), выглядело как случайно разбросанный градиент, а не рамка. */
  --scene:
    radial-gradient(circle at 78% -8%, rgba(90, 140, 255, .26), transparent 46%),
    radial-gradient(circle at 15% 108%, rgba(90, 140, 255, .26), transparent 46%),
    radial-gradient(circle at 15% -8%, rgba(90, 140, 255, .26), transparent 46%),
    radial-gradient(circle at 78% 108%, rgba(90, 140, 255, .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(38, 32, 26, .62);
  /* С152: контур 1.5px (было 1px — слишком тонко, читалось как "срезанное" рядом с
     несимметричной внешней тенью 0 5px). Внешняя тень заменена на симметричную
     0 0 14px, чтобы не создавать ложное ощущение перекоса выделенной карточки. */
  --glass-helm-active-shadow: inset 0 1px 0 rgba(255, 255, 255, .12), 0 0 0 1px rgba(255, 154, 77, .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);
  --accent: #ff9a4d;
  --accent2: #e8834a;
  --accent-fg: #1a1410;
  --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: #ff9a4d;
  --accent2: #e8834a;
  --accent-fg: #1a1410;
  --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; margin-bottom: 22px; }
.login-title {
  font-size: 30px; font-weight: 500; letter-spacing: -.01em;
  background: linear-gradient(90deg, #ff9a4d 0%, #e8834a 25%, #5a8cff 50%, #ff9a4d 75%, #e8834a 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(255, 154, 77, .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(255, 154, 77, .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;
  background: linear-gradient(90deg, #ff9a4d 0%, #e8834a 25%, #5a8cff 50%, #ff9a4d 75%, #e8834a 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 .mode-switcher { display: flex; flex-direction: column; gap: 6px; }
.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 (см. выше): не
   цвет сам по себе, а ЗАЛИВКА + рамка + свечение — тот же приём, которым там же решалась
   ровно эта проблема. Плюс текст словом: значок без подписи всё равно требует догадки.

   Цвет ОРАНЖЕВЫЙ (акцентный), а НЕ красный: красный занят аварийным режимом (.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(255, 154, 77, .18);
  color: var(--accent);
  border: 1px solid var(--accent);
  box-shadow: 0 0 8px rgba(255, 154, 77, .55);
  animation: helm-upd-pulse 2s ease-in-out infinite;
}
@keyframes helm-upd-pulse {
  0%, 100% { box-shadow: 0 0 6px rgba(255, 154, 77, .40); }
  50%      { box-shadow: 0 0 14px rgba(255, 154, 77, .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); }
.topacts { display: flex; gap: 8px; flex-shrink: 0; margin-left: auto; }

.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;
}

.timer.inset-panel { padding: 16px 20px; display: flex; align-items: center; gap: 18px; flex-shrink: 0; }
/* С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 {
  font-size: 11px; padding: 9px 15px; border-radius: 100px; 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); }
.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; }

/* ================= MODALS ================= */
/* С152 (2-й проход): раньше blur на .ov убирали из-за конфликта вложенных backdrop-filter —
   тогда причина оказалась в БАГЕ СБОРКИ (Vite/Lightning CSS резал backdrop-filter, см.
   project-unitvr-vite-backdropfilter-bug-s152), а не в самом наложении фильтров. После
   фикса сборки пробуем снова — Михаил прямо просит фон ЗА попапом заметно размывать, чтобы
   попап визуально отделялся от библиотеки игр позади. */
.ov { position: fixed; inset: 0; background: var(--overlay-bg); backdrop-filter: blur(10px); -webkit-backdrop-filter: blur(10px); display: none; align-items: center; justify-content: center; z-index: 50; padding: 20px; }
.ov.open { display: flex; }
/* С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; }
.tariff-list { display: flex; gap: 8px; }
.tariff-btn { flex: 1; 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; }
.settings-melody-btn { justify-content: center; font-size: 10.5px; padding: 8px 6px; white-space: nowrap; }

.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); }
.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(255,154,77,.5); box-shadow: inset 0 1px 3px rgba(0,0,0,.15), 0 0 10px rgba(255,154,77,.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(255, 154, 77, .35); }
.add-helmet-fields .login-input:focus { border-color: rgba(255, 154, 77, .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.
   Задаём кнопке ровно её, чтобы рамки совпали пиксель в пиксель, а не приблизительно. */
.edit-game-search-btn {
  margin-top: auto; border-radius: 10px; height: 43px; justify-content: center;
  font-size: 12px; padding: 0 10px; text-align: center; line-height: 1.2;
}
.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; }
.cast { width: 100vw; height: 100vh; 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: 100px; 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;
  background: linear-gradient(90deg, #ff9a4d 0%, #e8834a 25%, #5a8cff 50%, #ff9a4d 75%, #e8834a 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). "← К списку" слева убран,
   крестик в правом верхнем углу на маленькой круглой стеклянной подложке вместо текстовой кнопки. */
.catalog-detail-wrap { max-width: 880px; margin: 0 auto; position: relative; }
.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; }
.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; margin-top: 20px; 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;
}
.catalog-detail-desc { font-size: 15px; line-height: 1.6; color: var(--fg-dim); }

/* С152: попап поддержки по образцу референса Stardog — плотная стеклянная окантовка
   снаружи (шапка+низ на стекле), белая панель в центре под сам чат. Окантовка = padding
   стеклянного контейнера, белая панель — отдельный внутренний блок со своим скруглением. */
/* С157 (Михаил): побольше по обеим осям - обращение в поддержку часто длинное, нужно
   больше места и под текст сообщений, и под историю переписки. */
.sup { width: min(460px, 100%); height: min(560px, 85vh); 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); }

/* Аварийный доступ (см. ТЗ) - подозрительная активность у скрытой точки на шлеме, требует
   явного подтверждения владельцем, поэтому НЕ самоисчезает как обычный 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: раньше "Свернуть" жила в шапке левой панели (место освобождено под шестерёнку
   настроек) и разворачивалась ОТДЕЛЬНОЙ плавающей кнопкой в углу экрана. Теперь одна
   кнопка на границе колонок, по центру высоты — крупная, но приглушённая (не должна
   бросаться в глаза среди рабочих кнопок), с одинаковым положением что свёрнуто, что нет. */
.col-collapse-btn {
  position: fixed; top: 50%; left: 292px; transform: translate(-50%, -50%); width: 26px; height: 64px; border-radius: 13px;
  border: none; background: var(--chip); color: var(--fg-mute); font-size: 13px; z-index: 20; opacity: .55;
  display: flex; align-items: center; justify-content: center; cursor: pointer; transition: left .3s cubic-bezier(.4, 0, .2, 1), opacity .2s;
}
.col-collapse-btn:hover, .col-collapse-btn:active { opacity: 1; }
#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; }
  #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; }

.instr-shot-slot {
  margin-top: 8px; border: 1.5px dashed rgba(255,255,255,.16); border-radius: 10px;
  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 { margin-top: 8px; border-radius: 10px; overflow: hidden; border: 1px solid rgba(255,255,255,.08); }
.instr-shot-ready img { display: block; width: 100%; height: auto; }

/* ⭐ С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; }
.stats-session-head { display: flex; align-items: baseline; gap: 10px; font-size: 12.5px; }
.stats-session-time { font-weight: 600; }
.stats-session-dur { margin-left: auto; font-variant-numeric: tabular-nums; }
.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:last-child { margin-left: auto; }
.stats-dim { color: var(--fg-mute); font-weight: 400; }
.mode-switcher { display: flex; gap: 6px; margin-right: 4px; }
