/*
 * ЕДИНЫЙ ИСТОЧНИК ПРАВДЫ для стекла и цветовой темы 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);
  --inset-border: rgba(255, 255, 255, .09);

  /* капсула подписи под обложкой игры (С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 {
  background: var(--chip); border: 1px solid rgba(255, 255, 255, .08); 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 {
  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 .22s 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; }

/* С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); }
.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 (Михаил, живой тест): было прижато к левому краю, иконка вплотную к тексту - по
   центру и с явным отступом иконки от текста. */
.helmet-menu-btn { width: 100%; justify-content: center; padding: 11px 14px; border-radius: 10px; }
.helmet-menu-icon { margin-right: 8px; }
/* С152: заголовок и статы (батарея/wifi/L/R) увеличены и утяжелены по прямой просьбе
   Михаила — на планшете в клубе мелкий текст плохо читается издалека. */
.helm-name { font-size: 15px; font-weight: 600; }
.helm-status { margin-left: auto; font-size: 10.5px; font-weight: 600; text-transform: uppercase; letter-spacing: .03em; }
/* С157: убран Wi-Fi (неинформативен в пределах одного помещения, только раздувал высоту
   плашки) — остались батарея гарнитуры + оба контроллера одним рядом. Каждый стат теперь
   в своей рамке со скруглением (Михаил, "чтобы было красиво"). Цвет самих иконок/цифр
   следует статусу связи: белый пока шлем онлайн, серый когда оффлайн (см. .helm-stats-offline
   ниже) - раньше был всегда приглушённо-серым независимо от связи, выглядело как "неактивно"
   даже у реально включённого шлема. */
.helm-stats { display: flex; gap: 8px; margin-top: 8px; font-size: 12px; font-weight: 600; color: var(--fg); }
.helm-stats-offline { color: var(--fg-mute); }
.helm-stats span {
  display: inline-flex; align-items: center; gap: 4px; padding: 4px 8px; border-radius: 10px;
  border: 1px solid var(--chip); background: var(--chip);
}
.helm-stats .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-game { 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); }

.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; }
.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; }
.timer .sp { flex: 1; }
.timer .acts { display: flex; gap: 8px; }
/* С157: название запущенной игры переехало из меты таймера в отдельную плашку —
   видна только когда игра реально идёт, исчезает вместе с ней (Михаил, по референсу). */
.running-game-badge {
  display: flex; align-items: center; gap: 7px; padding: 10px 16px; border-radius: 12px;
  background: var(--chip); color: var(--fg-dim); font-size: 12.5px; flex-shrink: 0;
}
.running-game-badge b { color: var(--ok); font-weight: 600; }
.running-game-play { color: var(--ok); font-size: 11px; }
.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: left; }
.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: left; 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; gap: 8px;
}
.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); }
.gcover { position: relative; aspect-ratio: 1 / 1; overflow: hidden; border-radius: 12px 12px 0 0; 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; 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); }
/* С152 (3-й проход): горизонтальный градиент тёмный→светлый→тёмный (блик по центру,
   как на референсе — не плоский цвет), без border. Низ скруглён сильнее верха. */
/* С152: название всегда по центру плашки, независимо от длины (было — короткие названия
   визуально "прижаты" влево из-за flex:1 на .gname рядом с точкой-индикатором). */
.gfoot { padding: 7px 10px; display: flex; align-items: center; justify-content: center; gap: 6px; border-radius: 0 0 12px 12px; background: var(--gfoot-gradient); }
.gname { font-size: 11px; 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: left;
}
.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; }

/* С154: лонг-тап на карточке игры -> "Редактировать игру" (переименовать/заменить обложку,
   см. ТЗ про пиратские копии с битыми иконками). Превью-квадрат кликабелен целиком. */
.edit-game-cover {
  position: relative; width: 100%; aspect-ratio: 1 / 1; border-radius: 14px; overflow: hidden;
  background: var(--inset-bg); border: 1px solid var(--inset-border); cursor: pointer;
  display: flex; align-items: center; justify-content: center; margin-bottom: 14px;
}
.edit-game-cover img { width: 100%; height: 100%; object-fit: cover; }
.edit-game-cover-empty { font-size: 12px; color: var(--fg-dim); }
.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, .65));
}
/* С158: описание игры (модуль «Каталог игр») — многострочное поле поверх общего .login-input. */
.edit-game-textarea { resize: vertical; min-height: 64px; font-family: inherit; margin-top: 8px; }
.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;
}

/* С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 .22s 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, рядом в правом углу шапки: "Свернуть"
   (серая скруглённая окантовка) и "Закрыть" (красная). Текстовые пилюли, не иконки. */
.cast-btn { padding: 6px 16px; border-radius: 999px; background: transparent; font-size: 12.5px; font-weight: 500; cursor: pointer; transition: background .15s; }
.cast-btn-min { margin-left: auto; border: 1.5px solid rgba(255, 255, 255, .32); color: var(--fg-dim); }
.cast-btn-min:active { background: rgba(255, 255, 255, .08); }
.cast-btn-close { margin-left: 8px; 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; }
.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 контейнер, что
   у трансляции (см. выше), но со скроллящейся сеткой карточек вместо видео-элемента. */
.catalog-close { margin-left: auto; }
.catalog-body { position: relative; flex: 1; min-height: 0; overflow-y: auto; padding: 20px; }
.catalog-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(160px, 1fr)); gap: 16px; }
.catalog-tile {
  display: flex; flex-direction: column; border: none; padding: 0; cursor: pointer;
  background: var(--inset-bg); border-radius: 14px; overflow: hidden; text-align: left;
  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);
}
.catalog-tile-title {
  position: absolute; left: 0; right: 0; bottom: 0; padding: 20px 10px 8px; font-size: 12.5px;
  color: #fff; background: linear-gradient(transparent, rgba(0, 0, 0, .8));
}
.catalog-tile-genre {
  position: absolute; top: 8px; left: 8px; font-size: 10px; padding: 3px 8px; border-radius: 999px;
  background: rgba(0, 0, 0, .55); color: #fff;
}
.catalog-detail { max-width: 720px; margin: 0 auto; }
.catalog-detail-back { margin-bottom: 14px; }
.catalog-detail-media { width: 100%; aspect-ratio: 16 / 9; border-radius: 14px; overflow: hidden; background: #000; margin-bottom: 16px; 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-media-dot.is-video { border-radius: 2px; }
.catalog-detail-info h2 { font-size: 18px; font-weight: 500; margin: 0 0 8px; }
.catalog-detail-genre {
  display: inline-block; font-size: 11px; padding: 3px 10px; border-radius: 999px;
  background: var(--chip); color: var(--fg-dim); margin-bottom: 12px;
}
.catalog-detail-desc { font-size: 13.5px; line-height: 1.5; 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; }
}
.mode-switcher { display: flex; gap: 6px; margin-right: 4px; }
