/* ===== ТЕМЫ =====
   Весь цвет интерфейса собран здесь. Ниже по файлу прямых цветов почти нет:
   вместо них переменные, поэтому вторая тема — это один блок значений, а не
   вторая вёрстка. Тему ставит applyTheme() в app.js атрибутом data-theme на
   <html>; выбор хранится на устройстве (localStorage bp_theme), а не в
   профиле: планшет в клубе общий, и чужой вход не должен его перекрашивать.

   Три канала вместо одного «чёрно-белого»:
     --lift   — чем поверхность ПОДСВЕЧИВАЮТ сверху (стеклянный блик, рамка);
     --sink   — чем её УТОПЛЯЮТ снизу внутри самой поверхности;
     --shadow — чем она отбрасывает тень НАРУЖУ.
   На тёмной теме первые два — белый и чёрный, и разница незаметна. Но в
   светлой теме тень остаётся тёмной, а «утопленный» край становится светлым:
   если бы канал был один, все карточки на светлой теме почернели бы.

   Каналы записаны через пробел (255 255 255), чтобы подставляться в
   rgb(var(--lift) / .08) — прозрачность у каждого места своя.

   Цвета, которые НЕ здесь и это осознанно:
   — чёрный фон под видео и полотном обрезки (леттербокс чёрный в любой теме);
   — всё, что лежит ПОВЕРХ видео (заставка, ролики, сетка кадра, органы плеера):
     там белый читается по кадру, а не по теме;
   — тона плашек в «Активность логи» (зелёный старт, красный стоп): они
     смысловые, а не оформительские. */
:root{
  --radius:calc(16px * var(--r-k));
  /* высота основных кнопок пульта (пауза/запись) — общая точка отсчёта для
     кнопок верхнего ряда эфира ниже (см. .rec-toprow-left/-panel) */
  --ctrl-btn-h:clamp(50px,4.8vw,64px);

  /* ФОРМА И ЭФФЕКТЫ — вторая половина темы, помимо цвета. Сделано
     множителями, а не набором готовых значений: при единице вид ровно тот,
     что был, поэтому нынешняя тема не двигается ни на пиксель, а новой хватит
     одного числа, чтобы стать острой, плоской или, наоборот, мягче.

     --r-k     скругления (0.3 — почти прямые углы, 1.4 — мягче)
     --r-pill  «таблетка» отдельным токеном: при острых углах она может как
               остаться таблеткой, так и распрямиться — это решает тема
     --bw      толщина рамки, от неё же считаются двойные и тройные
     --sh-k    тени: 0 гасит их все разом и делает интерфейс плоским
     --blur-k  размытие стекла позади панелей; 0 — матовые панели
     --glow    свечение вокруг главных кнопок, --glow-soft — вокруг карточек
               и панелей. Это отдельный СЛОЙ в списке теней, поэтому
               «выключено» тут не none (в списке слоёв он недопустим), а
               прозрачная тень нулевого размера. Слоты уже расставлены.

     Чего темой быть НЕ может: размеры и расположение блоков. Раскладку
     экрана записи считает JS по замерам (см. fitVideoBox), и сдвиг габаритов
     ломает подгонку видео. Скругления, тени и свечение габаритов не трогают. */
  --r-k:1;
  --r-pill:999px;
  --bw:1px;
  --sh-k:1;
  --blur-k:1;
  --glow:0 0 0 rgba(0,0,0,0);
  --glow-soft:0 0 0 rgba(0,0,0,0);
}

/* Тёмная зелёная — та, что была всегда. Значения продублированы в :root без
   селектора темы: если атрибут data-theme почему-то не проставился (старый
   кэш, отключённый JS), приложение выглядит как раньше, а не голым HTML.

   Третий селектор — просто [data-theme="…"], без :root — нужен не приложению,
   а витринам: в Claude Design карточки компонентов оборачивают в узел с этим
   атрибутом, и без него тема на них не попадала бы. В самом приложении
   атрибут стоит только на <html>, так что на вид это не влияет. */
:root,
:root[data-theme="green"],
[data-theme="green"]{
  /* явный color-scheme — иначе некоторые WebView (напр. Fully Kiosk Browser на
     части Android-сборок) включают алгоритмический "force dark" и перекрашивают
     полупрозрачные белые элементы (см. .live-timeline/.live-scrub-fill ниже) в
     чёрный, хотя весь интерфейс и так тёмный */
  color-scheme:dark;
  --bg:#0f1714;
  --bg2:#16211c;
  --card:#1b2924;
  --line:#2c3d35;
  --txt:#eaf2ee;
  --mut:#9bb0a6;
  --acc:#1f9d5f;
  --acc-d:#14773f;
  --acc-l:#3fe08f;
  --goal:#2ec27a;
  --dispute:#e6b53b;
  --custom:#4ea3e0;
  --danger:#e0584e;
  --tag:#a86ee0;
  --ball-lit:#f4d03f;

  --lift:255 255 255;
  --sink:0 0 0;
  --shadow:0 0 0;
  --mix-dark:#000;   /* чем гасят акцент в градиентах кнопок */
  --mix-light:#fff;
  --scrim:5 10 8;    /* затемнение под всплывающими окнами */
  --bloom:31 157 95; /* два пятна на фоне страницы */
  --bloom2:20 119 63;
  --sheet-hi:rgba(30,42,36,.98);  /* лист окна «О системе» */
  --sheet-lo:rgba(12,18,15,.98);

  --on-acc:#fff;          /* текст на акцентной заливке */
  --on-acc-soft:#eafff2;  /* второстепенный текст там же */
  --on-acc-dim:#d8f5e6;   /* подпись под ним — глуше на тон */
  --on-glass:#fff;        /* текст на стеклянной панели (пульт, плеер) */
  --ink:#06140d;          /* самый глухой тон: фон тоста, текст на светлом */
  --ok-t:#a9f0c9;         /* текст статусных плашек */
  --warn-t:#f2d27a;
  --danger-t:#ff9891;

  /* экран просмотра записи живёт по своему макету (см. theme-v02.css) и оттенки
     у него чуть свои — но переключаться он обязан вместе со всеми */
  --player-panel:#0d1311;
  --player-txt:#e8f0ec;
  --player-mut:#8fa39b;
  --player-acc:#1d9e75;
}

/* Вторая тема — тёмно-бордовая. Пока это образец: на нём видно, что
   переключение работает и что цвет нигде не остался прибитым гвоздями.
   Когда из Claude Design придёт настоящий редизайн, он ляжет сюда же —
   отдельным блоком с тем же набором имён. */
:root[data-theme="wine"],
[data-theme="wine"]{
  color-scheme:dark;
  --bg:#180f12;
  --bg2:#22161a;
  --card:#2b1c21;
  --line:#432c33;
  --txt:#f3e9ea;
  --mut:#b39ba1;
  --acc:#a63a4e;
  --acc-d:#7c2739;
  --acc-l:#e06880;
  --goal:#c4a24a;
  --dispute:#e6b53b;
  --custom:#4ea3e0;
  --danger:#e0584e;
  --tag:#a86ee0;
  --ball-lit:#f4d03f;

  --lift:255 246 248;
  --sink:0 0 0;
  --shadow:0 0 0;
  --mix-dark:#12080b;
  --mix-light:#fff;
  --scrim:12 6 8;
  --bloom:166 58 78;
  --bloom2:124 39 57;
  --sheet-hi:rgba(48,30,36,.98);
  --sheet-lo:rgba(22,13,16,.98);

  --on-acc:#fff;
  --on-acc-soft:#ffeaef;
  --on-acc-dim:#f6dbe1;
  --on-glass:#fff5f7;
  --ink:#150a0d;
  --ok-t:#f0d9a9;
  --warn-t:#f2d27a;
  --danger-t:#ff9891;

  --player-panel:#140c0f;
  --player-txt:#f3e9ea;
  --player-mut:#a89197;
  --player-acc:#a63a4e;
}

/* «Изумруд» — тема из Claude Design (10.08.2026). Первая, пришедшая не от
   меня: значения целиком авторские, я только разложил их по нашим именам.
   Фон глубже нынешнего, акцент ярче и холоднее, вокруг главных кнопок
   свечение. */
:root[data-theme="emerald"],
[data-theme="emerald"]{
  color-scheme:dark;
  --bg:#070a09;
  --bg2:#0c1110;
  --card:#111917;
  --line:#22302c;
  --txt:#eef5f2;
  --mut:#8fa69f;
  --acc:#25c274;
  --acc-d:#0f8a51;
  --acc-l:#5ff0a8;
  --goal:#2ec27a;
  --dispute:#e6b53b;
  --custom:#4ea3e0;
  --danger:#e0584e;
  --tag:#a86ee0;
  --ball-lit:#f4d03f;

  --lift:235 255 246;
  --sink:0 0 0;
  --shadow:0 0 0;
  --mix-dark:#000;
  --mix-light:#fff;
  --scrim:3 6 5;
  --bloom:37 194 116;
  --bloom2:48 120 190;
  --sheet-hi:rgba(22,31,28,.92);
  --sheet-lo:rgba(8,12,11,.94);

  --on-acc:#ffffff;
  --on-acc-soft:#eafff3;
  --on-acc-dim:#cdf3e0;
  --on-glass:#fff;
  --ink:#04100b;
  --ok-t:#a9f0c9;
  --warn-t:#f2d27a;
  --danger-t:#ff9891;

  --player-panel:#0a100e;
  --player-txt:#eef5f2;
  --player-mut:#8fa69f;
  --player-acc:#25c274;

  --r-k:1.3;
  --r-pill:999px;
  --bw:1px;
  --sh-k:1.25;
  --blur-k:1.15;
  --glow:0 0 26px rgba(95,240,168,.34);
  --glow-soft:0 0 22px rgba(37,194,116,.10);
}

/* Объёмные шары — правило из редизайна, дословно. Оно НЕ токенное, поэтому
   заскоплено на темы, которым принадлежит: в зелёной и в моих образцах шар
   остаётся плоским. В прототипе подсвеченный шар помечался инлайновым style,
   у нас это класс .lit — единственное, что я перевёл.

   ОСТОРОЖНО: градиент на шаре мы однажды уже убирали. На WebView клубного
   планшета (Fully Kiosk) radial-gradient на диаметре 22–32 точки рисовался
   тёмным пятном вместо блика, и тени там же вели себя так же; причина в
   драйвере, а не в разметке, так что смена темы её не отменяет. Прежде чем
   ставить любую из этих тем на планшет у стола — посмотреть на шары вживую.
   См. комментарий у .pp-ball выше. */
[data-theme="emerald"] .pp-ball,
[data-theme="felt"] .pp-ball,
[data-theme="aurora"] .pp-ball{
  background:radial-gradient(circle at 34% 26%, rgb(var(--lift) / .18), rgb(var(--sink) / .5) 62%);
  border:var(--bw) solid rgb(var(--lift) / .3);
  box-shadow:inset 0 calc(-2px * var(--sh-k)) calc(4px * var(--sh-k)) rgb(var(--sink) / .55),
             inset 0 calc(1px * var(--sh-k)) calc(1px * var(--sh-k)) rgb(var(--lift) / .18);
}
[data-theme="emerald"] .pp-ball.lit,
[data-theme="felt"] .pp-ball.lit,
[data-theme="aurora"] .pp-ball.lit{
  background:radial-gradient(circle at 34% 26%, #fff6d2 0%, var(--ball-lit) 44%, #b07f1c 100%);
  border-color:color-mix(in srgb, var(--ball-lit) 40%, transparent);
  box-shadow:0 calc(3px * var(--sh-k)) calc(7px * var(--sh-k)) rgb(var(--shadow) / .5),
             inset 0 calc(-2px * var(--sh-k)) calc(4px * var(--sh-k)) rgba(90,55,0,.4);
}

/* «Сукно» — из того же редизайна: теплее, мягче углы, стекло почти без
   размытия, свечение приглушённое. */
:root[data-theme="felt"],
[data-theme="felt"]{
  color-scheme:dark;
  --bg:#0b1512; --bg2:#10201a; --card:#15271f; --line:#26402f;
  --txt:#f1f7f2; --mut:#9db9a8;
  --acc:#1fa968; --acc-d:#136e43; --acc-l:#4ede9a;
  --goal:#2ec27a; --dispute:#e6b53b; --custom:#4ea3e0; --danger:#e0584e; --tag:#a86ee0; --ball-lit:#f4d03f;
  --lift:255 250 240; --sink:0 0 0; --shadow:0 0 0;
  --mix-dark:#04100a; --mix-light:#fff;
  --scrim:5 12 9; --bloom:31 169 104; --bloom2:150 108 46;
  --sheet-hi:rgba(28,45,36,.98); --sheet-lo:rgba(11,21,18,.98);
  --on-acc:#fff; --on-acc-soft:#eafff2; --on-acc-dim:#d8f5e6;
  --on-glass:#fff; --ink:#06140d;
  --ok-t:#a9f0c9; --warn-t:#f2d27a; --danger-t:#ff9891;
  --player-panel:#0d1815; --player-txt:#f1f7f2; --player-mut:#9db9a8; --player-acc:#1fa968;
  --r-k:1.55; --r-pill:999px; --bw:1px; --sh-k:.85; --blur-k:.5;
  --glow:0 0 16px rgba(78,222,154,.22); --glow-soft:0 0 0 rgba(0,0,0,0);
}

/* «Аврора» — самая контрастная: холодный синий фон, сиреневое пятно на
   заднике, сильное свечение и самое заметное размытие. */
:root[data-theme="aurora"],
[data-theme="aurora"]{
  color-scheme:dark;
  --bg:#07090f; --bg2:#0c1018; --card:#121722; --line:#252c3d;
  --txt:#eef1f8; --mut:#94a0ba;
  --acc:#2fd08a; --acc-d:#179a63; --acc-l:#74f7ba;
  --goal:#2ec27a; --dispute:#e6b53b; --custom:#6aa8ff; --danger:#e0584e; --tag:#a86ee0; --ball-lit:#f4d03f;
  --lift:236 240 255; --sink:0 0 0; --shadow:2 4 16;
  --mix-dark:#05060b; --mix-light:#fff;
  --scrim:4 5 10; --bloom:126 74 224; --bloom2:47 208 138;
  --sheet-hi:rgba(24,29,42,.92); --sheet-lo:rgba(8,10,16,.94);
  --on-acc:#ffffff; --on-acc-soft:#e9fff5; --on-acc-dim:#c9f4e1;
  --on-glass:#fff; --ink:#05070d;
  --ok-t:#a9f0c9; --warn-t:#f2d27a; --danger-t:#ff9891;
  --player-panel:#0a0d15; --player-txt:#eef1f8; --player-mut:#94a0ba; --player-acc:#2fd08a;
  --r-k:1.45; --r-pill:999px; --bw:1px; --sh-k:1.4; --blur-k:1.35;
  --glow:0 0 30px rgba(47,208,138,.42); --glow-soft:0 0 28px rgba(126,74,224,.18);
}

/* «Неон» — тема, которая отличается НЕ цветом, а оформлением: вокруг главных
   кнопок и панелей стоит свечение, стекло размыто сильнее, углы мягче. Она и
   заведена ради проверки, что через тему проходит не только палитра. */
:root[data-theme="neon"],
[data-theme="neon"]{
  color-scheme:dark;
  --bg:#080c14;
  --bg2:#0d1420;
  --card:#121c2b;
  --line:#1e3350;
  --txt:#e6f3ff;
  --mut:#8fa8c4;
  --acc:#1f8fd4;
  --acc-d:#12608f;
  --acc-l:#5ad2ff;
  --goal:#2ec27a;
  --dispute:#e6b53b;
  --custom:#4ea3e0;
  --danger:#e0584e;
  --tag:#a86ee0;
  --ball-lit:#f4d03f;

  --lift:214 238 255;
  --sink:0 0 0;
  --shadow:0 6 20;
  --mix-dark:#05080f;
  --mix-light:#fff;
  --scrim:3 7 14;
  --bloom:31 143 212;
  --bloom2:18 96 143;
  --sheet-hi:rgba(22,36,54,.98);
  --sheet-lo:rgba(8,13,20,.98);

  --on-acc:#fff;
  --on-acc-soft:#dff1ff;
  --on-acc-dim:#c3e4fb;
  --on-glass:#f2fbff;
  --ink:#050b14;
  --ok-t:#a9f0c9;
  --warn-t:#f2d27a;
  --danger-t:#ff9891;

  --player-panel:#0a121c;
  --player-txt:#e6f3ff;
  --player-mut:#8fa8c4;
  --player-acc:#1f8fd4;

  --r-k:1.35;
  --bw:1px;
  --sh-k:1.2;
  --blur-k:1.6;
  --glow:0 0 22px rgba(90,210,255,.55);
  --glow-soft:0 0 16px rgba(90,210,255,.18);
}

/* «Плоская» — обратный полюс: теней нет вовсе, стекло не размывается, углы
   почти прямые. Цвет при этом близок к нынешнему — чтобы на ней было видно
   именно разницу в оформлении, а не в палитре. */
:root[data-theme="flat"],
[data-theme="flat"]{
  color-scheme:dark;
  --bg:#101815;
  --bg2:#18211d;
  --card:#1d2723;
  --line:#33423b;
  --txt:#e8f0ec;
  --mut:#93a89e;
  --acc:#1f9d5f;
  --acc-d:#14773f;
  --acc-l:#3fe08f;
  --goal:#2ec27a;
  --dispute:#e6b53b;
  --custom:#4ea3e0;
  --danger:#e0584e;
  --tag:#a86ee0;
  --ball-lit:#f4d03f;

  --lift:255 255 255;
  --sink:0 0 0;
  --shadow:0 0 0;
  --mix-dark:#000;
  --mix-light:#fff;
  --scrim:5 10 8;
  --bloom:31 157 95;
  --bloom2:20 119 63;
  --sheet-hi:#1d2723;
  --sheet-lo:#1d2723;

  --on-acc:#fff;
  --on-acc-soft:#eafff2;
  --on-acc-dim:#d8f5e6;
  --on-glass:#fff;
  --ink:#0b120f;
  --ok-t:#a9f0c9;
  --warn-t:#f2d27a;
  --danger-t:#ff9891;

  --player-panel:#141c19;
  --player-txt:#e8f0ec;
  --player-mut:#93a89e;
  --player-acc:#1f9d5f;

  --r-k:.3;
  --r-pill:6px;
  --bw:1px;
  --sh-k:0;
  --blur-k:0;
  --glow:0 0 0 rgba(0,0,0,0);
  --glow-soft:0 0 0 rgba(0,0,0,0);
}
*{box-sizing:border-box;-webkit-tap-highlight-color:transparent}
html,body{margin:0;height:100%}
body{
  /* no-repeat — без него background (repeat по умолчанию) тайлит оба radial-gradient
     вниз по странице на длинном контенте (напр. #view-sysadmin в 2 колонки при
     узком/высоком окне) — виден жёсткий шов и повтор пятна. attachment:fixed —
     держит оба пятна привязанными к вьюпорту, а не к верху/низу документа, так
     что при любой высоте страницы и любом скролле фон выглядит одинаково цельным */
  background:
    radial-gradient(1200px 800px at 10% -10%,rgb(var(--bloom) / .16),transparent 60%),
    radial-gradient(900px 700px at 110% 110%,rgb(var(--bloom2) / .16),transparent 55%),
    var(--bg);
  background-repeat:no-repeat;
  background-attachment:fixed;
  color:var(--txt);
  font-family:-apple-system,Segoe UI,Roboto,Inter,system-ui,sans-serif;
  overscroll-behavior:none;
}
/* height (не min-height) — иначе flex:1 у #main/.view считается от
   неопределённой (auto) высоты, и min-height:0 на record-view/rec-main ниже
   ничего не решает (сжимать нечего, если у контейнера нет жёсткого потолка
   для расчёта). Другим экранам (архив, история баланса и т.п.) это не
   мешает: у #app нет overflow:hidden, длинный контент по-прежнему просто
   выйдет за рамки и страница проскроллится как обычно. */
#app{display:flex;flex-direction:column;height:100dvh}
.hidden{display:none !important}

/* topbar */
#topbar{
  display:flex;align-items:center;gap:8px;
  padding:env(safe-area-inset-top) 12px 0;
  height:calc(54px + env(safe-area-inset-top));
  background:linear-gradient(180deg,rgb(var(--lift) / .06),rgb(var(--sink) / .25)),var(--bg2);
  border-bottom:var(--bw) solid rgb(var(--lift) / .12);position:sticky;top:0;z-index:20;
  backdrop-filter:blur(calc(10px * var(--blur-k)));-webkit-backdrop-filter:blur(calc(10px * var(--blur-k)));
}
#title{flex:1;text-align:center;font-weight:700;letter-spacing:.3px}
.icon-btn{
  display:flex;align-items:center;justify-content:center;gap:8px;
  background:rgb(var(--lift) / .08);border:var(--bw) solid rgb(var(--lift) / .16);color:var(--txt);
  font-size:22px;height:42px;padding:0 12px;border-radius:var(--r-pill);cursor:pointer;
  backdrop-filter:blur(calc(8px * var(--blur-k)));-webkit-backdrop-filter:blur(calc(8px * var(--blur-k)));
  transition:transform .12s,filter .12s,background .12s;
}
.icon-btn:active{transform:scale(.95);filter:brightness(.9)}
@media (hover:hover){
  .icon-btn:hover{background:rgb(var(--lift) / .16);transform:translateY(-1px)}
}
.icon-btn.ghost{font-size:20px}
#backBtn{width:42px;padding:0}
.logout-btn{font-size:18px}
.logout-btn .icon-btn-lbl{font-size:13px;font-weight:700;white-space:nowrap}

#main{flex:1;display:flex;flex-direction:column;min-height:0}
.view{display:none;flex:1;padding:18px;max-width:680px;width:100%;margin:0 auto}
.view.active{display:flex;flex-direction:column}

/* auth */
.brand{text-align:center;margin:34px 0 22px}
.brand-logo{height:72px;width:auto;display:inline-block}
.brand-sub{color:var(--mut);margin:6px 0 0}
.card{
  background:linear-gradient(165deg,rgb(var(--lift) / .08),rgb(var(--sink) / .35));
  border:var(--bw) solid rgb(var(--lift) / .14);
  border-radius:var(--radius);padding:18px;
  box-shadow:0 calc(8px * var(--sh-k)) calc(22px * var(--sh-k)) rgb(var(--shadow) / .35),inset 0 calc(1.5px * var(--sh-k)) calc(2px * var(--sh-k)) rgb(var(--lift) / .08),var(--glow-soft);
  backdrop-filter:blur(calc(10px * var(--blur-k)));-webkit-backdrop-filter:blur(calc(10px * var(--blur-k)));
}
.auth-form{display:flex;flex-direction:column;gap:14px}
label{display:flex;flex-direction:column;gap:6px;font-size:13px;color:var(--mut)}
input,select{
  background:rgb(var(--lift) / .06);border:var(--bw) solid rgb(var(--lift) / .18);color:var(--txt);
  padding:13px;border-radius:calc(11px * var(--r-k));font-size:16px;outline:none;
  backdrop-filter:blur(calc(6px * var(--blur-k)));-webkit-backdrop-filter:blur(calc(6px * var(--blur-k)));
  transition:border-color .12s,background .12s;
}
input:focus,select:focus{border-color:var(--acc);background:rgb(var(--lift) / .1)}
input[readonly]{opacity:.65;cursor:default}
#playersBlock{display:flex;flex-direction:column;gap:14px}
.btn{
  border:none;border-radius:calc(12px * var(--r-k));padding:14px;font-size:16px;font-weight:700;
  cursor:pointer;color:var(--on-acc);transition:transform .12s,filter .12s,box-shadow .12s;
}
.btn:active{transform:scale(.97);filter:brightness(.9)}
@media (hover:hover){
  .btn:not(:disabled):hover{transform:translateY(-2px);filter:brightness(1.12)}
}
/* до сих пор ни одна .btn не бывала disabled, поэтому вида у неё не было —
   выключенная кнопка выглядела рабочей (см. «Отправить вызов» в разработке) */
.btn:disabled{opacity:.45;cursor:not-allowed}
.btn:disabled:active{transform:none;filter:none}
.btn-primary{
  background:linear-gradient(160deg,color-mix(in srgb,var(--acc) 60%,var(--mix-dark)),color-mix(in srgb,var(--acc-d) 55%,var(--mix-dark)));
  border:var(--bw) solid rgb(var(--lift) / .22);
  box-shadow:0 calc(8px * var(--sh-k)) calc(20px * var(--sh-k)) rgb(var(--shadow) / .4),inset 0 calc(1.5px * var(--sh-k)) calc(2px * var(--sh-k)) rgb(var(--lift) / .15),var(--glow);
}
.btn-danger{
  background:rgba(224,88,78,.1);border:var(--bw) solid rgba(224,88,78,.55);color:var(--danger-t);
  backdrop-filter:blur(calc(6px * var(--blur-k)));-webkit-backdrop-filter:blur(calc(6px * var(--blur-k)));
}
@media (hover:hover){ .btn-danger:hover{background:rgba(224,88,78,.22)} }
.btn-secondary{
  background:rgb(var(--lift) / .06);border:var(--bw) solid rgb(var(--lift) / .22);color:var(--txt);
  backdrop-filter:blur(calc(6px * var(--blur-k)));-webkit-backdrop-filter:blur(calc(6px * var(--blur-k)));
}
@media (hover:hover){ .btn-secondary:hover{background:rgb(var(--lift) / .12)} }
.form-msg{margin:0;font-size:13px;min-height:16px;color:var(--dispute)}
.form-msg.ok{color:var(--goal)}
.hint{color:var(--mut);font-size:12px;text-align:center;margin-top:16px;line-height:1.5}
.btn-link{background:none;border:none;color:var(--acc);font-size:13px;cursor:pointer;padding:2px;text-align:left;transition:color .12s}
@media (hover:hover){ .btn-link:hover{color:var(--acc-l);text-decoration:underline} }

/* ввод телефона + стрелка "отправить код" рядом с полем (см. #phoneAuthForm) */
.phone-input-row{display:flex;gap:8px}
/* min-width:0 — иначе поле не сжимается уже собственной «естественной»
   ширины (у input это ~20 знаков, у select — самый длинный вариант), и ряд
   из трёх полей с кнопкой выходил за правый край экрана телефона */
.phone-input-row input,.phone-input-row select{flex:1;min-width:0}
/* флаг определённой по номеру страны слева от поля (см. #phoneFlag) */
.phone-flag{flex-shrink:0;display:flex;align-items:center;justify-content:center;width:44px;font-size:22px;line-height:1;border:var(--bw) solid rgb(var(--lift) / .14);border-radius:calc(11px * var(--r-k));background:rgb(var(--lift) / .06)}
/* список кода страны слева от поля (см. #phoneDial): флаг + код, код
   подставляется автоматически, но список можно раскрыть и выбрать другой */
/* Код страны не растягиваем. Правило «.phone-input-row input, select{flex:1}»
   выше нужно селектам в РЯДАХ ПРИВЯЗКИ (клуб/камера/планшет) — там их и правда
   тянут на всю строку. Здесь же содержимое короткое, «🇷🇺 +7», и flex-grow
   раздувал его на половину ряда: на телефоне это пряталось за переносом строки,
   а на десктопе рядом с номером зияло пустое поле. Сжиматься ему по-прежнему
   можно — иначе на узком экране ряд снова полезет за край.
   Селектор из двух классов, потому что «.phone-input-row select» специфичнее
   одного «.phone-dial» и порядком его не перебить. */
.phone-input-row .phone-dial{flex:0 1 auto}
.phone-dial{flex-shrink:0;width:auto;padding:0 8px;font-size:16px;line-height:1;border:var(--bw) solid rgb(var(--lift) / .14);border-radius:calc(11px * var(--r-k));background:rgb(var(--lift) / .06);cursor:pointer}
/* смена валюты на экране баланса (см. #currencySwitchBlock) */
.currency-switch{display:flex;align-items:center;gap:10px;flex-wrap:wrap;margin-top:16px;padding-top:14px;border-top:var(--bw) solid rgb(var(--lift) / .1)}
.currency-switch-label{font-size:13px;color:var(--mut)}
.currency-switch select{flex:1;min-width:120px}
.currency-switch select:disabled{opacity:.55;cursor:not-allowed}
.currency-switch .form-msg{flex-basis:100%;margin:0}
.phone-send-btn{
  flex-shrink:0;width:48px;border:var(--bw) solid rgb(var(--lift) / .22);border-radius:calc(11px * var(--r-k));
  background:linear-gradient(160deg,color-mix(in srgb,var(--acc) 60%,var(--mix-dark)),color-mix(in srgb,var(--acc-d) 55%,var(--mix-dark)));
  color:var(--on-acc);font-size:20px;font-weight:700;cursor:pointer;
  box-shadow:0 calc(4px * var(--sh-k)) calc(12px * var(--sh-k)) rgb(var(--shadow) / .35);
  transition:transform .12s,filter .12s;
}
.phone-send-btn:active{transform:scale(.95);filter:brightness(.9)}
@media (hover:hover){ .phone-send-btn:hover{transform:translateY(-1px);filter:brightness(1.15)} }

/* "Вход по QR" для Игрока-2 (см. "Новая запись") — тот же приём, что
   .phone-send-btn (иконка рядом с полем в той же строке, чтобы было видно,
   к какому именно полю относится), но auto-width: после успешного входа
   меняется на текстовую красную "Выйти" (.is-authenticated), а не остаётся
   квадратом под одну иконку */
.player2-qr-btn{
  flex-shrink:0;min-width:48px;padding:0 14px;border:var(--bw) solid rgb(var(--lift) / .22);border-radius:calc(11px * var(--r-k));
  background:linear-gradient(160deg,color-mix(in srgb,var(--acc) 60%,var(--mix-dark)),color-mix(in srgb,var(--acc-d) 55%,var(--mix-dark)));
  color:var(--on-acc);font-size:20px;font-weight:700;cursor:pointer;
  display:flex;align-items:center;justify-content:center;
  box-shadow:0 calc(4px * var(--sh-k)) calc(12px * var(--sh-k)) rgb(var(--shadow) / .35);
  transition:transform .12s,filter .12s,background .12s,border-color .12s,box-shadow .12s;
}
.player2-qr-btn:active{transform:scale(.95);filter:brightness(.9)}
@media (hover:hover){ .player2-qr-btn:hover{transform:translateY(-1px);filter:brightness(1.15)} }
.player2-qr-btn.is-authenticated{
  background:rgba(224,88,78,.15);border-color:rgba(224,88,78,.55);color:var(--danger-t);
  font-size:14px;box-shadow:none;
}
@media (hover:hover){ .player2-qr-btn.is-authenticated:hover{background:rgba(224,88,78,.28)} }

/* номер для "подтверждения по звонку" (см. #phoneCallBlock) — крупно, чтобы
   легко было набрать/скопировать на телефоне-клиенте */
.call-phone-number{display:block;text-align:center;font-size:26px;font-weight:700;letter-spacing:.02em;margin:4px 0;color:var(--acc);text-decoration:none}
@media (hover:hover){ .call-phone-number:hover{text-decoration:underline} }

/* menu */
.menu-topbar{display:flex;justify-content:flex-end;align-items:center;gap:10px;margin-bottom:16px}
.wallet-chip{
  display:flex;align-items:center;gap:7px;
  background:linear-gradient(165deg,rgb(var(--lift) / .1),rgb(var(--sink) / .4));
  border:var(--bw) solid rgb(var(--lift) / .18);
  border-radius:calc(14px * var(--r-k));padding:9px 14px;color:var(--txt);cursor:pointer;font-weight:700;font-size:15px;
  backdrop-filter:blur(calc(8px * var(--blur-k)));-webkit-backdrop-filter:blur(calc(8px * var(--blur-k)));
  box-shadow:0 calc(4px * var(--sh-k)) calc(14px * var(--sh-k)) rgb(var(--shadow) / .3),var(--glow-soft);
  transition:transform .12s,filter .12s,background .12s;
}
.wallet-chip:active{transform:scale(.97)}
@media (hover:hover){
  .wallet-chip:hover{transform:translateY(-1px);background:linear-gradient(165deg,rgb(var(--lift) / .16),rgb(var(--sink) / .45))}
}
/* display:flex, а не просто align-items у чипа: <svg> внутри span — инлайновый
   элемент и стоит на базовой линии текста, из-за чего под ним оставался запас
   под нижние выносные элементы и иконка визуально уезжала выше цифр баланса.
   Флекс-контейнер убирает базовую линию и центрирует иконку по высоте */
.wallet-chip .wallet-ico{font-size:18px;display:flex;align-items:center;line-height:0}
.wallet-chip .wallet-amt{font-variant-numeric:tabular-nums}
.btn-sm{padding:9px 16px;font-size:14px}

/* два ряда: [начать запись][архив] сверху, [админ-панель] снизу на всю ширину */
.menu-grid{display:grid;grid-template-columns:1fr 1fr;gap:16px}
.big-card{
  display:flex;flex-direction:column;align-items:flex-start;gap:8px;
  background:linear-gradient(165deg,rgb(var(--lift) / .09),rgb(var(--sink) / .38));
  border:var(--bw) solid rgb(var(--lift) / .16);border-radius:calc(20px * var(--r-k));
  padding:26px 22px;color:var(--txt);cursor:pointer;text-align:left;
  box-shadow:0 calc(8px * var(--sh-k)) calc(22px * var(--sh-k)) rgb(var(--shadow) / .35),inset 0 calc(1.5px * var(--sh-k)) calc(2px * var(--sh-k)) rgb(var(--lift) / .08),var(--glow-soft);
  backdrop-filter:blur(calc(10px * var(--blur-k)));-webkit-backdrop-filter:blur(calc(10px * var(--blur-k)));
  transition:transform .12s,filter .12s,box-shadow .12s;
}
.big-card.accent{
  background:linear-gradient(160deg,color-mix(in srgb,var(--acc) 55%,var(--mix-dark)),color-mix(in srgb,var(--acc-d) 45%,var(--mix-dark)));
  border:var(--bw) solid rgb(var(--lift) / .25);
  box-shadow:0 calc(8px * var(--sh-k)) calc(22px * var(--sh-k)) rgb(var(--shadow) / .4),inset 0 calc(1.5px * var(--sh-k)) calc(2px * var(--sh-k)) rgb(var(--lift) / .15),var(--glow);
}
.big-card:active{transform:scale(.98);filter:brightness(.95)}
@media (hover:hover){
  .big-card:hover{transform:translateY(-2px);box-shadow:0 calc(12px * var(--sh-k)) calc(28px * var(--sh-k)) rgb(var(--shadow) / .45);filter:brightness(1.08)}
}
.big-card.span-2{grid-column:1 / -1}
.big-ico{font-size:34px}
.big-t{font-size:20px;font-weight:700}
.big-s{font-size:13px;color:var(--mut)}
.big-card.accent .big-s{color:var(--on-acc-dim)}
.big-card.disabled{background:linear-gradient(165deg,rgb(var(--lift) / .05),rgb(var(--sink) / .3));opacity:.5;cursor:not-allowed}
.big-card.disabled:hover{transform:none;filter:none;box-shadow:0 calc(8px * var(--sh-k)) calc(22px * var(--sh-k)) rgb(var(--shadow) / .35)}
.big-card.disabled .big-s{color:var(--mut)}

/* archive */
.screen-h{font-size:22px;margin:6px 0 16px}
/* Заголовок записи прижимаем к полосе меню: на экране просмотра каждый
   лишний десяток пикселей — это отъеденная высота у самого видео. */
#view-player{padding-top:8px}
.list{display:flex;flex-direction:column;gap:10px}
.rec-item{
  display:flex;align-items:center;gap:12px;
  background:linear-gradient(165deg,rgb(var(--lift) / .08),rgb(var(--sink) / .35));
  border:var(--bw) solid rgb(var(--lift) / .14);border-radius:calc(14px * var(--r-k));padding:14px;cursor:pointer;
  backdrop-filter:blur(calc(8px * var(--blur-k)));-webkit-backdrop-filter:blur(calc(8px * var(--blur-k)));
  box-shadow:0 calc(4px * var(--sh-k)) calc(14px * var(--sh-k)) rgb(var(--shadow) / .3),var(--glow-soft);
  transition:transform .12s,filter .12s,background .12s;
}
.rec-item:active{transform:scale(.98);filter:brightness(.95)}
@media (hover:hover){
  .rec-item:hover{transform:translateY(-1px);background:linear-gradient(165deg,rgb(var(--lift) / .13),rgb(var(--sink) / .4))}
}
.rec-thumb{
  width:46px;height:46px;border-radius:calc(11px * var(--r-k));flex-shrink:0;
  background:linear-gradient(160deg,color-mix(in srgb,var(--acc) 55%,var(--mix-dark)),color-mix(in srgb,var(--acc-d) 40%,var(--mix-dark)));
  border:var(--bw) solid rgb(var(--lift) / .2);
  display:flex;align-items:center;justify-content:center;font-size:22px;
}
.rec-info{flex:1;min-width:0}
.rec-name{font-weight:600;font-size:15px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
/* Стена камер (см. renderCamWall): все ракурсы клуба сразу, плиткой. Плитка
   крупная — 420 px минимум: экран нужен, чтобы РАЗГЛЯДЕТЬ, что в зале, а на
   мелких превью стол превращается в зелёное пятно. min(100%,420px) — чтобы на
   телефоне это не превратилось в горизонтальную прокрутку: там одна колонка
   во всю ширину. */
.cam-wall{
  display:grid;gap:12px;margin-top:12px;
  grid-template-columns:repeat(auto-fill,minmax(min(100%,420px),1fr));
}
.cam-tile{
  position:relative;aspect-ratio:16/9;border-radius:calc(14px * var(--r-k));overflow:hidden;
  background:#000;border:var(--bw) solid var(--line);
}
/* contain, а не cover: экран ровно про то, чтобы видеть ВЕСЬ кадр камеры */
.cam-tile video{width:100%;height:100%;object-fit:contain;display:block}
.cam-tile-cap{
  position:absolute;left:0;right:0;bottom:0;padding:18px 12px 8px;
  font-size:13px;font-weight:700;color:#fff;pointer-events:none;
  background:linear-gradient(transparent,rgba(0,0,0,.75));
}
.cam-tile-msg{
  position:absolute;inset:0;display:flex;align-items:center;justify-content:center;
  font-size:12px;color:var(--mut);
}
.cam-tile-msg-err{color:var(--danger)}
/* значок «доступны траектории» в строке архива (см. buildGameRow) — того же
   цвета, что и «забитый шар» в легенде: зелёный тут значит «готово» */
.rec-traj{display:inline-flex;vertical-align:-2px;color:var(--goal)}
.rec-meta{color:var(--mut);font-size:12px;margin-top:3px}
/* остаток срока хранения в строке архива: последнюю неделю — янтарным, чтобы
   «скачайте, если нужно» относилось к конкретной записи, а не ко всем сразу */
.rec-expire.soon{color:var(--warn-t)}
/* строка про срок хранения над списком архива */
.archive-retention{margin:0 0 12px;line-height:1.4}
.rec-arrow{color:var(--mut);font-size:20px}
.admin-item{cursor:default}
@media (hover:hover){ .rec-item.admin-item:hover{transform:none} }
.admin-stop-btn{
  border:var(--bw) solid rgba(224,88,78,.55);border-radius:calc(12px * var(--r-k));padding:10px 16px;font-size:13px;font-weight:700;cursor:pointer;
  background:rgba(224,88,78,.1);color:var(--danger-t);flex-shrink:0;
  backdrop-filter:blur(calc(6px * var(--blur-k)));-webkit-backdrop-filter:blur(calc(6px * var(--blur-k)));
  transition:transform .12s,filter .12s,background .12s;
}
.admin-stop-btn:active{transform:scale(.97);filter:brightness(.9)}
@media (hover:hover){ .admin-stop-btn:hover{background:rgba(224,88,78,.25);transform:translateY(-1px)} }
.empty{color:var(--mut);text-align:center;margin-top:40px}

/* Активность логи (сисадмин) — фильтры сверху + двухстрочные карточки событий */
.log-filters{display:flex;gap:10px;flex-wrap:wrap;margin-bottom:14px}
.log-filters select{flex:1;min-width:150px}

/* Компактная строка фильтров (логи, статистика). Раньше select'ы «Все
   действия»/«Все клубы» стояли на flex:1 и растягивались на всю оставшуюся
   ширину — рядом с узкими полями дат это выглядело несоразмерно. Здесь всё
   по содержимому и в одну строку: сначала период, потом выпадающие списки. */
.filters-row{display:flex;gap:8px;flex-wrap:wrap;align-items:center;margin-bottom:14px}
.card > .filters-row{margin-bottom:0}
.filters-row .btn{padding:9px 16px;font-size:14px}
/* label глобально стоит колонкой — здесь подпись слева от поля, в строку */
.filter-date{flex-direction:row;align-items:center;gap:6px;font-size:12px;color:var(--mut)}
/* 16px у самого поля — иначе iOS зумит страницу при фокусе на date-инпуте */
.filter-date input{width:auto;padding:8px 10px;font-size:16px}
.filter-select{width:auto;max-width:100%;padding:8px 10px;font-size:14px}
/* ===== «Баланс заканчивается» ===== */
/* Узкая янтарная полоска: заметная, но игру не перекрывает — на экране записи
   она стоит над пультом, а не поверх него. */
.low-balance-bar{
  display:flex;align-items:center;gap:10px;flex-wrap:wrap;
  margin:0 0 10px;padding:8px 12px;border-radius:calc(12px * var(--r-k));
  background:rgba(215,170,40,.14);border:var(--bw) solid rgba(215,170,40,.4);
}
.low-balance-text{flex:1;min-width:0;color:var(--warn-t);font-size:13px;font-weight:600}
/* Кнопка «Пополнить» переливается: это единственное место, где интерфейс
   действительно должен поймать взгляд — иначе запись остановится посреди
   партии. Блик гоняем через background-position, а не через box-shadow: тень
   пришлось бы перерисовывать каждым кадром, а сдвиг фона композитор тянет
   сам, не трогая раскладку. */
.low-balance-btn{
  flex-shrink:0;border:none;color:#1a1206;font-weight:700;
  background:linear-gradient(100deg,#e8c25a 0%,#f7e3a6 42%,#fffdf5 50%,#f7e3a6 58%,#e8c25a 100%);
  background-size:280% 100%;
  animation:lowBalanceShine 2.4s linear infinite;
}
@keyframes lowBalanceShine{
  from{background-position:140% 0}
  to{background-position:-140% 0}
}
/* уважаем системную настройку «меньше анимации» — блик там просто гаснет,
   кнопка остаётся золотой и заметной */
@media (prefers-reduced-motion:reduce){
  .low-balance-btn{animation:none;background:linear-gradient(100deg,#e8c25a,#f7e3a6)}
}

/* Пополнение поверх записи (см. openTopupOverlay) */
.topup-overlay{
  position:fixed;inset:0;z-index:60;display:flex;align-items:center;justify-content:center;
  padding:16px;background:rgb(var(--scrim) / .78);backdrop-filter:blur(calc(4px * var(--blur-k)));-webkit-backdrop-filter:blur(calc(4px * var(--blur-k)));
}
.topup-overlay-box{
  width:100%;max-width:420px;max-height:90vh;overflow-y:auto;
  background:var(--card);border:var(--bw) solid var(--line);border-radius:var(--radius);padding:16px;
}
.topup-overlay-head{display:flex;align-items:center;justify-content:space-between;gap:10px;font-weight:700;margin-bottom:6px}
/* пресеты минут на экране баланса и в оверлее */
.minute-presets{display:flex;gap:8px;flex-wrap:wrap}

/* ===== Колокольчик уведомлений в шапке ===== */
.bell-btn{position:relative;flex-shrink:0}
.bell-dot{
  position:absolute;top:6px;right:6px;width:9px;height:9px;border-radius:50%;
  background:var(--danger);border:calc(var(--bw) * 2) solid var(--bg);
}
/* строки уведомлений: непрочитанное — акцентной полосой слева, как у алертов */
.ntf-row{align-items:flex-start;cursor:default}
.ntf-row.unread{border-left:calc(var(--bw) * 3) solid var(--acc)}
.ntf-body{flex:1;min-width:0}
.ntf-title{font-weight:600;font-size:15px}
/* перевод строки в шаблоне сообщения — значимый, текст свёрстан абзацами */
.ntf-text{color:var(--txt);font-size:14px;margin-top:6px;white-space:pre-line}
.ntf-fine{color:var(--mut);font-size:11px;margin-top:8px;line-height:1.35}
.ntf-meta{color:var(--mut);font-size:12px;margin-top:8px}

/* кто играл — сисадмин видит, кому возвращать минуты, не открывая игру */
.alert-player{color:var(--txt);font-size:12px;margin-top:6px}
/* форма компенсации внутри строки алерта */
.alert-actions{display:flex;gap:8px;flex-wrap:wrap;margin-top:10px}
.alert-actions .btn{background:rgb(var(--lift) / .1);border:var(--bw) solid rgb(var(--lift) / .18)}
@media(hover:hover){ .alert-actions .btn:hover{background:rgb(var(--lift) / .18)} }
.compensate-box{margin-top:10px}
.compensate-form{display:flex;flex-direction:column;gap:8px;
  background:var(--bg2);border:var(--bw) solid var(--line);border-radius:calc(12px * var(--r-k));padding:12px}
.compensate-form label{font-size:12px}
.compensate-form input,.compensate-form textarea{width:100%;min-width:0;padding:9px;font-size:14px;
  background:rgb(var(--lift) / .06);border:var(--bw) solid rgb(var(--lift) / .18);color:var(--txt);border-radius:calc(10px * var(--r-k))}
.compensate-form textarea{resize:vertical;font-family:inherit;line-height:1.4}

/* история движений баланса */
.wtx-row{align-items:center}
.wtx-amount{text-align:right;font-weight:700;font-size:15px;white-space:nowrap;display:flex;flex-direction:column;gap:2px}
.wtx-amount.plus{color:var(--goal)}
.wtx-amount.minus{color:var(--mut)}
.wtx-after{font-weight:400;font-size:11px;color:var(--mut)}

/* кнопка «Видео» в таблице «Активность логи» — компактнее обычной btn-sm */
.log-video-btn{padding:4px 12px;font-size:12px;background:rgb(var(--lift) / .1);border:var(--bw) solid rgb(var(--lift) / .18)}
@media(hover:hover){ .log-video-btn:hover{background:rgb(var(--lift) / .18)} }

/* Полоска активного клуба над клубозависимыми экранами сисадмина (см.
   mountClubBar в app.js). Чипы — те же .role-chip, что у фильтра ролей:
   отдельный визуальный язык для «того же действия» тут ни к чему. */
.club-bar{
  display:flex;align-items:center;gap:10px;flex-wrap:wrap;min-width:0;
  margin:0 0 16px;padding:10px 12px;
  background:var(--bg2);border:var(--bw) solid var(--line);border-radius:calc(14px * var(--r-k));
}
.club-bar-label{font-size:11px;color:var(--mut);text-transform:uppercase;letter-spacing:.05em}
.club-bar-chips{display:flex;gap:8px;flex-wrap:wrap;min-width:0}
.club-bar .role-chip{font-size:13px;padding:6px 12px}

/* готовые интервалы под полями дат («Сегодня», «Неделя», «30 дней» …) —
   те же пилюли, что у фильтра ролей, только мельче: это не самостоятельный
   фильтр, а быстрый способ подставить период в поля выше */
.range-presets{display:flex;gap:6px;flex-wrap:wrap;margin:0 0 14px}
.card .range-presets{margin:10px 0 0}
.preset-chip{
  padding:5px 11px;border-radius:var(--r-pill);cursor:pointer;font-size:12px;font-weight:600;
  border:var(--bw) solid var(--line);background:var(--card);color:var(--mut);
  transition:background .12s,color .12s,border-color .12s;
}
.preset-chip:hover{color:var(--txt);border-color:var(--acc)}
.preset-chip.on{background:var(--acc);border-color:var(--acc);color:var(--on-acc)}

/* выбор темы в «Моих данных» (см. renderThemePick в app.js). Кружок слева —
   не украшение: он показывает фон и акцент темы, поэтому выбирают по нему, а
   не по названию */
.theme-pick{
  display:flex;flex-direction:column;gap:8px;
  margin:14px 0 0;padding-top:14px;border-top:var(--bw) solid var(--line);
}
.theme-pick-label{font-size:11px;color:var(--mut);text-transform:uppercase;letter-spacing:.05em}
.theme-pick-row{display:flex;gap:8px;flex-wrap:wrap}
.theme-opt{
  display:inline-flex;align-items:center;gap:8px;
  padding:5px 14px 5px 5px;border-radius:var(--r-pill);cursor:pointer;
  font-size:13px;font-weight:600;
  border:var(--bw) solid var(--line);background:var(--card);color:var(--mut);
  transition:color .12s,border-color .12s;
}
.theme-opt:hover{color:var(--txt);border-color:var(--acc)}
.theme-opt.on{color:var(--txt);border-color:var(--acc);box-shadow:0 0 0 1px var(--acc)}
.theme-swatch{
  width:24px;height:24px;border-radius:50%;flex:none;
  border:var(--bw) solid rgb(var(--lift) / .2);
}

/* Телефон: две строки — сверху период, снизу выпадающие списки.
   Раньше поля дат стояли на flex:1 1 0 (базис — ноль), а списки шли по
   содержимому, и вчетвером всё оставалось в ОДНОЙ строке: списки забирали
   ~330px, датам доставались считанные пиксели, и «29 Jul» лез из-под
   «Все действия». Теперь базис у каждого — половина строки, поэтому пара дат
   заполняет первую, пара списков переносится на вторую.
   Шрифт полей остаётся 16px (меньше — и iOS зумит страницу при фокусе),
   горизонтальные отступы режем. Кнопка «Показать» — своей строкой. */
@media(max-width:560px){
  .filters-row .filter-date{flex:1 1 calc(50% - 8px);min-width:0;gap:4px}
  .filters-row .filter-date input{width:100%;min-width:0;padding:8px 4px}
  .filters-row .filter-select{flex:1 1 calc(50% - 8px);min-width:0;width:auto}
  #view-stats .filters-row .btn{flex:1 1 100%;margin-top:2px}
}
.log-item{align-items:flex-start}
/* верхняя строка: дата/время + чип характера действия */
.log-top{display:flex;align-items:center;gap:10px;flex-wrap:wrap}
.log-when{font-weight:600;font-size:15px}
.log-action{font-size:11px;font-weight:700;padding:2px 9px;border-radius:var(--r-pill);white-space:nowrap;
  background:rgb(var(--lift) / .1);border:var(--bw) solid rgb(var(--lift) / .16);color:#dfe7ff}
.log-action--record_start{background:rgba(20,119,63,.28);border-color:rgba(46,194,122,.4);color:var(--ok-t)}
.log-action--record_stop{background:rgba(224,88,78,.16);border-color:rgba(224,88,78,.4);color:var(--danger-t)}
.log-action--topup{background:rgba(215,170,40,.18);border-color:rgba(215,170,40,.4);color:var(--warn-t)}
/* попытка — приглушённо, отказ — тревожно: в логе они должны читаться врозь,
   иначе три статуса одного действия сливаются в одно жёлтое пятно */
.log-action--topup_try{background:rgba(215,170,40,.10);border-color:rgba(215,170,40,.28);color:#cdbc8c}
.log-action--topup_failed{background:rgba(224,88,78,.20);border-color:rgba(224,88,78,.5);color:var(--danger-t)}
/* просмотр и скачивание — синим: это не про деньги и не про запись, а про то,
   что человек делает со ГОТОВЫМ архивом */
/* уточнение под чипом действия — у скачивания это ракурс («Сверху»/«Сбоку»).
   Отдельной колонки не заводим: заполнено оно у одного вида событий из шести,
   а таблица и без того на восемь столбцов */
.log-note{font-size:11px;color:var(--mut);margin-top:3px;white-space:nowrap}
.log-action--watch{background:rgba(78,163,224,.16);border-color:rgba(78,163,224,.4);color:#9ecbff}
.log-action--download{background:rgba(78,163,224,.24);border-color:rgba(78,163,224,.5);color:#bfe0ff}
/* нижняя строка — нежирным, приглушённым: имя, телефон, клуб, место */
.log-sub{color:var(--mut);font-size:12px;font-weight:400;margin-top:4px}
/* фильтр по дате в «Активность логи» */
.log-date{display:flex;align-items:center;gap:6px;font-size:12px;color:var(--mut)}
.log-date input{width:auto}
/* сисадмин: база пользователей */
.user-row{cursor:pointer}
.user-edit-box{max-width:440px;width:92vw}
.user-edit-games{max-height:220px;overflow-y:auto;margin-top:12px;border-top:var(--bw) solid rgb(var(--lift) / .1)}

/* сисадмин: привязка стола — сложнее одной строки текста (3 select'а),
   поэтому не горизонтальный .rec-item, а карточка с полями в столбик */
.sysadmin-row{flex-direction:column;align-items:stretch;cursor:default;gap:12px;min-width:0}
.sysadmin-fields{display:flex;gap:10px;flex-wrap:wrap;min-width:0}
.sysadmin-fields label{flex:1;min-width:130px;font-size:12px}
/* min-width:0 обязателен: у <select> своя минимальная ширина — по самому
   длинному варианту («Стол 3-рус сверху»), и без неё он не сжимался, а
   распирал карточку за правый край экрана телефона */
.sysadmin-fields select,.sysadmin-fields input{width:100%;min-width:0;max-width:100%;padding:10px;font-size:14px}
/* Ровно две колонки, парами по смыслу (см. карточку пользователя): при
   flex-wrap строки «съезжают» от длины подписей, а тут пары держатся жёстко. */
.sysadmin-fields.fields-2col{display:grid;grid-template-columns:1fr 1fr}
.sysadmin-fields.fields-2col label{min-width:0}
/* высота у поля и у выпадающего списка своя — «Роль» рядом с «Балансом»
   выглядела ниже соседа; задаём общую */
.sysadmin-fields.fields-2col input,
.sysadmin-fields.fields-2col select{height:40px;box-sizing:border-box;padding:8px 10px}
.sysadmin-save{align-self:flex-end}
/* планшеты и столы — по 2 карточки в ряд (класс специфичнее .list, переопределяет
   его flex-column); на узких экранах одна колонка, иначе карточки слишком жмутся */
/* min-width:0 у ячеек: по умолчанию грид-элемент не сжимается уже своего
   min-content, и одно длинное поле внутри растягивало бы всю колонку */
.sysadmin-grid{display:grid;grid-template-columns:1fr 1fr;gap:10px}
.sysadmin-grid > *{min-width:0}
@media(max-width:560px){ .sysadmin-grid{grid-template-columns:1fr} }
.sysadmin-btnrow{display:flex;gap:8px;justify-content:flex-end;flex-shrink:0}
/* активный клуб (экран «Клубы») — чьё оборудование показывают разделы ниже */
.club-row-active{border:var(--bw) solid var(--acc);box-shadow:0 0 0 1px var(--acc) inset}

/* экран "Оборудование клуба" — для администрирования с десктопа/ноутбука
   (не для планшета за столом, в отличие от остальных .view), поэтому ломает
   общее ограничение .view{max-width:680px} тем же приёмом, что и .record-view:
   на MacBook Pro 14" (и шире) 680px-колонка посередине оставляла бы полэкрана
   пустыми по бокам просто зря */
.sysadmin-view{max-width:1100px;padding:24px 18px}
.sysadmin-columns{display:grid;grid-template-columns:1fr 1fr;gap:24px;align-items:start}
.sysadmin-col{min-width:0}
@media(max-width:760px){ .sysadmin-columns{grid-template-columns:1fr} }

/* Телефон (iPhone 15 Pro Max — 430pt). Экран рассчитан на десктоп, но зайти
   с телефона и что-то поправить нужно уметь: строки полей, которые на широком
   экране стоят в ряд, здесь переносятся, а не сжимаются до нечитаемых 90px. */
@media(max-width:560px){
  .sysadmin-view{padding:16px 12px}
  .phone-input-row{flex-wrap:wrap}
  /* 160px — базис, а не жёсткая ширина: пара «поле + кнопка» остаётся в одну
     строку, а ряд из трёх полей клуба переносится, вместо того чтобы жаться */
  .phone-input-row input{flex:1 1 160px;min-width:0}
  .phone-input-row .btn{flex:1 1 auto}
  /* привязка камер/планшетов: каждое поле на свою строку — в две колонки
     названия камер обрезаются до «Камера сверх…» и выбрать нельзя вслепую.
     Ширину задаём базисом (flex-basis:100%), а не min-width: минимальная
     ширина у флекс-элемента и так «по содержимому», и вместе с длинным
     названием камеры в <select> она распирала карточку за край экрана */
  .sysadmin-fields label{flex:1 1 100%;min-width:0}
  .sysadmin-btnrow{flex-wrap:wrap;justify-content:flex-start}
  .sysadmin-btnrow .btn{flex:1 1 auto}
  /* токен клуба и примеры команд в подсказках — длинные и без пробелов */
  .hint-inline code{word-break:break-all}
}

/* просмотр камер — кнопка на каждую камеру стола, тот же акцентный градиент,
   что и .btn-primary, просто компактнее (может быть 1-2 в ряд на карточку) */
.sysadmin-cam-btns{display:flex;gap:8px;flex-wrap:wrap}
.cam-preview-btn{
  border:var(--bw) solid rgb(var(--lift) / .22);border-radius:calc(12px * var(--r-k));padding:10px 16px;
  font-size:13px;font-weight:700;cursor:pointer;color:var(--on-acc-soft);
  background:linear-gradient(160deg,color-mix(in srgb,var(--acc) 45%,var(--mix-dark)),color-mix(in srgb,var(--acc-d) 35%,var(--mix-dark)));
  transition:transform .12s,filter .12s;
}
.cam-preview-btn:active{transform:scale(.96);filter:brightness(.9)}
@media(hover:hover){ .cam-preview-btn:hover{filter:brightness(1.15);transform:translateY(-1px)} }

/* Камеры клуба — одна камера в одну строку со всеми кнопками. Раньше это были
   карточки .sysadmin-row с полями в столбик: полей всего четыре, а высоты одна
   камера занимала как треть экрана, и пять камер клуба не помещались вместе.
   Подписи полей ушли в placeholder — в строке позиция говорит не хуже. */
.cam-row{
  display:flex;align-items:center;gap:8px;flex-wrap:wrap;
  padding:10px 12px;border-radius:calc(12px * var(--r-k));
  background:linear-gradient(165deg,rgb(var(--lift) / .06),rgb(var(--sink) / .28));
  border:var(--bw) solid rgb(var(--lift) / .1);
}
.cam-row input{padding:8px 10px;font-size:13px;min-width:0}
.cam-row .cam-name{flex:1 1 150px}
.cam-row .cam-host{flex:0 1 140px}
.cam-row .cam-login{flex:0 1 110px}
.cam-row .cam-pass{flex:0 1 120px}
.cam-row .cam-url-ro{flex:2 1 240px;opacity:.65}
/* привязка к столу и состояние камеры — справочно, поэтому мелким и серым;
   flex:1 забирает остаток строки, прижимая кнопки к правому краю */
.cam-row-note{flex:1 1 90px;min-width:0;color:var(--mut);font-size:11px;line-height:1.3}
.cam-row-btns{display:flex;align-items:center;gap:6px;flex-shrink:0}
.cam-row-btns .cam-preview-btn{padding:8px 12px;font-size:12px}
.cam-row-btns .btn{padding:8px 14px;font-size:12px}
/* На телефоне строка честно переносится: поля становятся во всю ширину, а
   кнопки — своим рядом; сжимать их до 40px было бы хуже, чем перенести */
@media(max-width:560px){
  .cam-row input{flex:1 1 100%}
  .cam-row-note{flex:1 1 100%}
  .cam-row-btns{width:100%;justify-content:flex-end}
}

/* модалка живого превью камеры — поверх любого экрана (см. #idleOverlay рядом
   в index.html, тот же приём: вне #app, фиксированная позиция) */
.cam-preview-modal{
  position:fixed;inset:0;z-index:60;background:rgb(var(--scrim) / .92);
  display:flex;flex-direction:column;padding:18px;
  backdrop-filter:blur(calc(4px * var(--blur-k)));-webkit-backdrop-filter:blur(calc(4px * var(--blur-k)));
}
.cam-preview-head{display:flex;justify-content:space-between;align-items:center;gap:12px;margin-bottom:14px;color:#fff;font-weight:700;font-size:16px}
.cam-preview-close{
  flex-shrink:0;width:36px;height:36px;border-radius:calc(10px * var(--r-k));font-size:15px;cursor:pointer;
  background:rgb(var(--lift) / .12);border:var(--bw) solid rgb(var(--lift) / .25);color:#fff;
  transition:transform .12s,filter .12s,background .12s;
}
.cam-preview-close:active{transform:scale(.94)}
@media(hover:hover){ .cam-preview-close:hover{background:rgb(var(--lift) / .22)} }
#camPreviewVideo,#camTuneVideo{flex:1;width:100%;min-height:0;object-fit:contain;background:#000;border-radius:calc(12px * var(--r-k))}
/* подгонка кадра: один ряд под видео — восемь коэффициентов и две кнопки.
   Именно один: подбор идёт итерациями «поправил число — посмотрел», и лишняя
   строка полей отъедала бы высоту у самой картинки, по которой и судят.
   На узком экране ряд переносится, а не сжимает поля до нечитаемых. */
.cam-tune-bar{
  display:flex;align-items:flex-end;gap:10px;flex-wrap:wrap;margin-top:12px;color:#eafff2;
}
.cam-tune-bar label{display:flex;flex-direction:column;gap:3px;font-size:11px;color:rgba(234,255,242,.7)}
.cam-tune-bar input{
  /* 78, а не 66: автоподбор отдаёт три знака после запятой («-0,271»), и в
     прежнюю ширину последняя цифра не влезала — поле показывало «-0,27» */
  width:78px;padding:7px 8px;font-size:13px;font-variant-numeric:tabular-nums;
  background:rgb(var(--lift) / .08);border:var(--bw) solid rgb(var(--lift) / .2);border-radius:calc(9px * var(--r-k));color:#fff;
}
/* сообщение о перезапуске потока — между полями и кнопками, забирает остаток
   строки, чтобы кнопки прижались к правому краю */
/* flex-basis 220px, а не 0: короткое «Перезапускаю поток…» так и остаётся между
   полями и кнопками, а длинный ответ автоподбора («стол упирается в край
   кадра…») переносится на свою строку целиком, вместо того чтобы сжиматься в
   узкую колонку из одного слова */
.cam-tune-msg{flex:1 1 220px;min-width:0;font-size:12px;color:rgba(234,255,242,.75);align-self:center}
.cam-tune-bar .btn{flex-shrink:0}
/* включённая «Сетка» — тот же приём, что у кнопок плеера: залитая, а не
   контурная, чтобы состояние читалось без подписи */
.cam-preview-btn.is-on{background:var(--acc);color:var(--ink);border-color:var(--acc)}

/* сетка поверх кадра при подгонке. Позицию и размер ставит JS по вписанному
   прямоугольнику картинки (см. layoutCamTuneGrid) — сюда только вид линий.
   pointer-events:none обязателен: под сеткой лежит <video>, и без этого она
   перехватывала бы клики по нему */
/* форма обращения (кнопка «?» в шапке) — та же модалка-коробка, что у кропа
   аватара, только с полем текста вместо холста */
.feedback-box{max-width:480px;width:92vw;text-align:left}
.feedback-box textarea{
  width:100%;min-height:120px;resize:vertical;padding:12px;font:inherit;font-size:14px;
  background:var(--card);border:var(--bw) solid var(--line);border-radius:calc(12px * var(--r-k));color:var(--txt);
}

.cam-tune-grid{position:absolute;pointer-events:none;z-index:1;box-shadow:inset 0 0 0 1px rgba(255,255,255,.85)}
.cam-tune-grid i{position:absolute;background:rgba(255,255,255,.85)}
.cam-grid-h{left:0;right:0;height:1px}
.cam-grid-v{top:0;bottom:0;width:1px}
/* cover (не contain) — камера-сканер должна заполнить весь видоискатель, а не
   вписаться с полями, как превью стола выше */
#qrScanVideo{flex:1;width:100%;min-height:0;object-fit:cover;background:#000;border-radius:calc(12px * var(--r-k))}

/* newgame */
.field-row{display:flex;gap:12px;margin-bottom:18px}
.field-row label{flex:1}
.field-row label.narrow{flex:0 0 90px}
.lbl{color:var(--mut);font-size:13px;margin:4px 0 10px}
.hint-inline{color:var(--mut);font-size:12px;line-height:1.5;margin:-4px 0 14px}
/* после строки поле+кнопка (см. "Столов в клубе"/"Рекламный ролик" на сисадмин-
   экране) отрицательный margin-top выше прижимает подсказку впритык к кнопке —
   тут вместо этого нужен нормальный отступ */
.phone-input-row + .hint-inline{margin-top:10px}
.wallet-balance-big{font-size:38px;font-weight:800;margin:4px 0 2px;font-variant-numeric:tabular-nums}
.topup-qr{margin-top:18px;text-align:center}
.topup-qr-img{width:200px;height:200px;background:#fff;border-radius:calc(12px * var(--r-k));padding:8px;margin:8px 0}
#topupLink{display:inline-block;margin:10px 0;text-decoration:none}
/* #qrPairBlock — одновременно .auth-form (display:flex) и .topup-qr: text-align
   на flex-контейнер не действует на позицию самого flex-элемента (картинки),
   только align-items — без этого QR прижимался к левому краю */
#qrPairBlock{align-items:center}
.choice-grid{display:flex;flex-direction:column;gap:12px}
.choice{
  text-align:left;
  background:linear-gradient(165deg,rgb(var(--lift) / .07),rgb(var(--sink) / .32));
  border:var(--bw) solid rgb(var(--lift) / .14);
  border-radius:calc(14px * var(--r-k));padding:18px;cursor:pointer;color:var(--txt);
  display:flex;flex-direction:column;gap:6px;
  backdrop-filter:blur(calc(8px * var(--blur-k)));-webkit-backdrop-filter:blur(calc(8px * var(--blur-k)));
  transition:transform .12s,filter .12s,border-color .12s,background .12s;
}
.choice:active{transform:scale(.98)}
.choice:active,.choice.sel{border-color:var(--acc);background:linear-gradient(165deg,rgba(46,194,122,.18),rgb(var(--sink) / .35))}
@media (hover:hover){
  .choice:hover{transform:translateY(-1px);border-color:rgb(var(--lift) / .3)}
}
.choice-t{font-size:17px;font-weight:700}
.choice-s{font-size:13px;color:var(--mut)}

/* record — этот экран специально ломает общее ограничение .view (max-width:680px,
   padding:18px) и занимает всю ширину окна: это не форма для чтения, а полноэкранная
   консоль записи (видео + HUD), на большом экране планшета вокруг иначе оставались
   бы пустые поля с обеих сторон. */
/* min-height:0 — .view уже flex:1, но без этого экран не сжимается меньше
   суммарной "естественной" высоты своих детей (тот же флекс-капкан, что и
   у rec-main/rec-side-panel ниже) — теперь, когда у панелей появились
   отступы/обводки, без этого низ пульта на невысоких экранах опять уезжает
   за видимую область */
.record-view{gap:14px;max-width:none;padding:10px;min-height:0}
/* «ЭКРАН» — видео действительно во весь экран (см. #hudToggle в app.js).
   Панели игроков и колонка меток уходят вместе с нижним рядом: на планшете
   ширину съедают именно они, и без этого видео прибавляло от силы десятую
   часть. Отступы экрана тоже снимаем — иначе по краям остаётся рамка,
   которой в полноэкранном режиме взяться неоткуда.
   Метки в этом режиме не поставить — это осознанный размен: сюда переходят,
   чтобы СМОТРЕТЬ на стол, а «Показать меню» возвращает всё одним касанием. */
.record-view.rec-fs{padding:0;gap:0}
.record-view.rec-fs .pp-player,
.record-view.rec-fs .rec-side-panel{display:none}
/* видео + правая колонка кнопок — ряд. Видео держит строгие 16:9 (aspect-ratio),
   но размер считается ОТ ВЫСОТЫ (height:100%), а не от ширины: rec-main теперь
   гарантированно ограничен по высоте (см. min-height:0 в цепочке ниже и
   #app{height:100dvh}), поэтому видео просто сжимается по высоте, оставаясь
   ровно 16:9, вместо того чтобы раздувать всю страницу за пределы экрана. */
/* без flex:1 — раньше блок принудительно растягивался на всё оставшееся
   место, видео получало высоту меньше этого блока (см. fitVideoBox в app.js),
   и низ пульта (#recHud) уезжал к нижнему краю экрана с пустым зазором
   между видео и собой; теперь высота блока = высоте самого видео, и #recHud
   идёт сразу следом (см. .record-view{gap:14px}) */
.rec-main{display:flex;gap:10px;min-height:0;justify-content:center}
.rec-video-wrap{position:relative;box-sizing:border-box;height:100%;max-width:100%;aspect-ratio:16/9;border-radius:var(--radius);overflow:hidden;background:#000;border:var(--bw) solid rgb(var(--lift) / .22);box-shadow:var(--glow-soft);order:2}
#livePreview{position:absolute;inset:0;width:100%;height:100%;object-fit:cover;background:#000}
.live-placeholder{position:absolute;inset:0;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:10px;padding:16px;text-align:center;color:var(--mut);background:#0a0f0d}
.live-placeholder .live-ico{font-size:32px}
.live-placeholder .live-msg{max-width:300px;font-size:13px;line-height:1.4}

/* Панели игроков — боковые колонки рядом с видео, не поверх него (см.
   .rec-main{order:...} ниже). .pp-panels сам не участвует в раскладке
   (display:contents) — его два .pp-player становятся прямыми флекс-детьми
   .rec-main наравне с видео и панелью маркеров; toggle('hidden', ...) на
   #partiiPanels/#ballcountPanels по-прежнему прячет разом обе карточки
   режима (.hidden{display:none!important} побеждает display:contents). */
.pp-panels{display:contents}
/* карточка-подложка под именем/кнопками/шарами каждого игрока — стеклянная
   плашка сбоку от видео, той же высоты, что и само видео (см. fitVideoBox
   в app.js — выставляет высоту инлайн-стилем, как и .mk-col) */
.pp-player{
  display:flex;flex-direction:column;gap:10px;align-items:stretch;
  /* группа «игрок(и) → кнопки → шары» держится вместе по центру колонки.
     Было space-between — имя липло к верхней границе, а шары к нижней, и на
     высокой панели между ними зияла дыра. safe center — на случай, когда
     содержимое всё-таки выше панели (2×2 на невысоком экране): обычный center
     обрезал бы верх без возможности доскроллить (панель с overflow-y:auto).
     Две строки подряд — прогрессивное улучшение: браузер без поддержки
     ключевого слова safe возьмёт первую. */
  justify-content:center;
  justify-content:safe center;
  flex-shrink:0;box-sizing:border-box;overflow-y:auto;
  padding:14px 16px;border-radius:calc(18px * var(--r-k));
  background:linear-gradient(165deg,rgb(var(--lift) / .14),rgb(var(--sink) / .6));
  border:var(--bw) solid rgb(var(--lift) / .22);box-shadow:0 calc(8px * var(--sh-k)) calc(22px * var(--sh-k)) rgb(var(--shadow) / .45),var(--glow-soft);
  backdrop-filter:blur(calc(12px * var(--blur-k)));-webkit-backdrop-filter:blur(calc(12px * var(--blur-k)));
}
.pp-player.pp-left{order:1}
.pp-player.pp-right{order:3}
/* аватар+имя вместо прежней статичной подписи "ИГРОК-1"/"ИГРОК-2" —
   заполняется реальным именем/фото игрока в startRecordScreen() (app.js) */
.pp-player-id{display:flex;flex-direction:column;align-items:center;gap:6px;text-align:center}
.pp-player-avatar,.pp-player-avatar-placeholder{
  width:clamp(40px,4vw,56px);height:clamp(40px,4vw,56px);border-radius:50%;flex-shrink:0;object-fit:cover;
  background:linear-gradient(160deg,color-mix(in srgb,var(--acc) 55%,var(--mix-dark)),color-mix(in srgb,var(--acc-d) 40%,var(--mix-dark)));
  border:var(--bw) solid rgb(var(--lift) / .25);
}
/* line-height:1 обязателен: у эмодзи собственная метрика, и при обычной
   межстрочной глиф съезжал с центра кружка вверх */
.pp-player-avatar-placeholder{display:flex;align-items:center;justify-content:center;line-height:1;font-size:clamp(18px,2vw,26px)}
/* max-width в ch — если имя игрока «Имя Фамилия» (два слова), оно само
   перенесётся на вторую строку (обычный wrap по пробелу) без отдельного
   JS для подсчёта строк */
.pp-player-name{font-size:clamp(13px,1.5vw,18px);font-weight:800;line-height:1.2;color:var(--on-glass);max-width:10ch;word-break:break-word}
/* кнопки и стойка шаров растягиваются на одинаковую ширину карточки, и
   space-between внутри каждой раскладывает их край-в-край: "+" у левого
   края, "−" у правого, а шары — ровно между теми же границами */
/* Кнопки и шары ЦЕНТРИРУЕМ, а не расталкиваем по краям. Карточка теперь
   забирает весь остаток ширины экрана (см. fitVideoBox), и прежний
   space-between растягивал ряд вслед за ней: «−» и «+» разъезжались на треть
   метра, шары — в редкую цепочку. Промежутки заданы числом и от ширины
   карточки больше не зависят вовсе. */
.pp-btns{display:flex;gap:10px;justify-content:center}
/* прямоугольные (не круглые) кнопки — тёмные глянцевые градиенты вместо ярких */
.pp-round{
  width:clamp(52px,4.6vw,72px);height:clamp(44px,4vw,60px);flex-shrink:0;border-radius:calc(14px * var(--r-k));
  border:var(--bw) solid rgb(var(--lift) / .25);font-size:clamp(22px,2vw,30px);font-weight:800;
  cursor:pointer;color:var(--on-acc);text-shadow:0 1px 2px rgb(var(--shadow) / .4);
  box-shadow:0 calc(4px * var(--sh-k)) calc(12px * var(--sh-k)) rgb(var(--shadow) / .5),inset 0 calc(1.5px * var(--sh-k)) calc(2px * var(--sh-k)) rgb(var(--lift) / .2),inset 0 calc(-3px * var(--sh-k)) calc(5px * var(--sh-k)) rgb(var(--shadow) / .35),var(--glow);
  transition:transform .12s,filter .12s,box-shadow .12s;
}
.pp-plus{background:linear-gradient(160deg,color-mix(in srgb,var(--goal) 55%,var(--mix-dark)) 0%,color-mix(in srgb,var(--goal) 30%,var(--mix-dark)) 100%)}
.pp-minus{background:linear-gradient(160deg,color-mix(in srgb,var(--danger) 55%,var(--mix-dark)) 0%,color-mix(in srgb,var(--danger) 28%,var(--mix-dark)) 100%)}
.pp-round:active{transform:scale(.95);filter:brightness(.92)}
@media (hover:hover){
  .pp-round:hover{transform:scale(1.05);filter:brightness(1.2);box-shadow:0 calc(4px * var(--sh-k)) calc(14px * var(--sh-k)) rgb(var(--shadow) / .55),inset 0 calc(1px * var(--sh-k)) calc(1px * var(--sh-k)) rgb(var(--lift) / .3)}
}
.pp-rack{display:flex;flex-direction:column;gap:8px;width:100%}
.pp-row{display:flex;justify-content:center;gap:8px}
/* незабитый шар — просто белый контур без заливки; забитый (.lit) — сплошной
   с бликом сверху для объёма (radial-gradient имитирует блик глянцевого шара) */
.pp-ball{
  width:clamp(22px,2.1vw,32px);height:clamp(22px,2.1vw,32px);border-radius:50%;
  background:transparent;border:calc(var(--bw) * 2) solid rgb(var(--lift) / .85);box-sizing:border-box;
}
/* сплошная заливка без градиента — на этом WebView (Fully Kiosk Browser)
   radial-gradient(white→жёлтый) на маленьком диаметре шара (22-32px)
   рендерится с тёмным/чёрным пятном вместо белого блика (похоже на баг
   интерполяции градиента конкретного GPU-драйвера); box-shadow до этого
   уже убирали по той же причине (см. историю) — решили не гнаться за
   глянцевым эффектом ценой надёжности рендера на реальном железе */
.pp-ball.lit{
  background:var(--ball-lit);
  border-color:transparent;
}
/* цифра под +/- в режиме "по количеству шаров" скрыта — счёт только внизу,
   в общей таблетке (#ballcountScoreLine); элемент остаётся в DOM и
   продолжает обновляться (renderBallcountScore в app.js), просто не виден */
.pp-score{display:none}

/* правая колонка (маркеры + вид/настройки) — обычный флекс-элемент рядом с
   видео, не абсолютное позиционирование поверх него. Единая стеклянная
   панель-подложка под всеми кнопками меню, тот же стиль, что и у карточек
   игроков — по референсу это одно меню, а не набор кнопок россыпью. */
/* min-height:0 — без этого флекс-элемент по умолчанию не сжимается меньше
   контента (min-height:auto), и на невысоких экранах панель со своими
   отступами/обводкой раздвигает весь rec-main шире доступного места (тот же
   класс проблемы, что раньше был у rec-video-wrap с aspect-ratio); overflow-y
   — на случай, если 6 кнопок с подложкой всё равно не влезают, лучше скролл
   внутри панели, чем обрезанная кнопка «Начать запись» внизу экрана */
/* justify-content:flex-start (не center) — панель должна начинаться от той же
   верхней границы, что и окно видео (см. fitVideoBox в app.js, которая
   выставляет .mk-col высоту строго равной высоте видео инлайн-стилем) */
.rec-side-panel{display:flex;flex-direction:column;justify-content:flex-start;flex-shrink:0;min-height:0;order:4}
/* --mk-size — фиксированный размер кнопок панели (НЕ vw-clamp): по умолчанию
   не меняется при сужении окна, кнопки живут своим размером, пока не начнёт
   действовать fitVideoBox() в app.js — та уменьшает именно эту переменную
   инлайн-стилем, и только когда видео+панель вместе уже не помещаются в
   экран (см. MIN_VIDEO_W там же), а не превентивно от одной лишь ширины vw */
.mk-col{
  --mk-size:64px;
  display:flex;flex-direction:column;justify-content:space-between;gap:8px;padding:12px 8px;border-radius:calc(22px * var(--r-k));
  background:linear-gradient(165deg,rgb(var(--lift) / .12),rgb(var(--sink) / .58));
  border:var(--bw) solid rgb(var(--lift) / .2);box-shadow:0 calc(8px * var(--sh-k)) calc(22px * var(--sh-k)) rgb(var(--shadow) / .4),var(--glow-soft);
  backdrop-filter:blur(calc(12px * var(--blur-k)));-webkit-backdrop-filter:blur(calc(12px * var(--blur-k)));
  box-sizing:border-box;max-height:100%;overflow-y:auto;
}
/* Вид/Настройки остаются нейтральными (прозрачная стеклянная заливка); четыре
   маркера ниже перекрашены в тон соответствующего действия — тот же градиент
   (160deg, color-mix 55%/30% к чёрному), что у pp-plus, только по своей
   переменной цвета, чтобы уровень оттенка совпадал с кнопкой "+". */
/* Скруглённый квадрат, а не круг. Подпись стоит НИЖЕ значка, а у круга ширина
   на этой высоте заметно меньше диаметра: «МЕТКА» и «ЭКРАН» формально влезали,
   но упирались в скругление и читались как обрезанные. Квадрат даёт те же
   габариты и всю ширину под текст — заодно кнопки счёта рядом (.pp-round) той
   же формы, экран перестал состоять из двух разных геометрий. */
.mk-round{
  display:flex;flex-direction:column;align-items:center;justify-content:center;gap:3px;
  width:var(--mk-size);height:var(--mk-size);flex-shrink:0;border-radius:calc(16px * var(--r-k));
  box-sizing:border-box;padding:0 3px;
  border:var(--bw) solid rgb(var(--lift) / .3);font-size:calc(var(--mk-size) * .19);font-weight:700;
  letter-spacing:.2px;cursor:pointer;color:var(--on-glass);
  background:linear-gradient(160deg,rgb(var(--lift) / .16),rgb(var(--lift) / .04));
  box-shadow:0 calc(3px * var(--sh-k)) calc(10px * var(--sh-k)) rgb(var(--shadow) / .4),inset 0 calc(1px * var(--sh-k)) calc(1px * var(--sh-k)) rgb(var(--lift) / .25),var(--glow);
  backdrop-filter:blur(calc(6px * var(--blur-k)));-webkit-backdrop-filter:blur(calc(6px * var(--blur-k)));
  transition:transform .12s,filter .12s,box-shadow .12s,width .15s,height .15s;
}
#mkGoal{color:var(--on-acc);background:linear-gradient(160deg,color-mix(in srgb,var(--goal) 55%,var(--mix-dark)) 0%,color-mix(in srgb,var(--goal) 30%,var(--mix-dark)) 100%)}
#mkPenalty{color:var(--on-acc);background:linear-gradient(160deg,color-mix(in srgb,var(--danger) 55%,var(--mix-dark)) 0%,color-mix(in srgb,var(--danger) 30%,var(--mix-dark)) 100%)}
#mkHit{color:var(--on-acc);background:linear-gradient(160deg,color-mix(in srgb,var(--custom) 55%,var(--mix-dark)) 0%,color-mix(in srgb,var(--custom) 30%,var(--mix-dark)) 100%)}
#mkTag{color:var(--on-acc);background:linear-gradient(160deg,color-mix(in srgb,var(--tag) 55%,var(--mix-dark)) 0%,color-mix(in srgb,var(--tag) 30%,var(--mix-dark)) 100%)}
.mk-ico{font-size:calc(var(--mk-size) * .3);line-height:1}
.mk-lbl{line-height:1;white-space:nowrap}
/* «ВИД СВЕРХУ» в две строки: подпись у этой кнопки длиннее всех остальных
   («ШТР», «УДАР»), поэтому ей — свой размер и плотная строка. Кнопку это не
   растит: высота у всех фиксированная, см. --mk-size выше. */
#viewToggleBtn .mk-lbl{font-size:.82em;line-height:1.05;text-align:center}
.mk-round:active{transform:scale(.95);filter:brightness(.85)}
.mk-round:disabled{opacity:.4;cursor:not-allowed;filter:none;transform:none}
@media (hover:hover){
  .mk-round:not(:disabled):hover{transform:scale(1.08);filter:brightness(1.25);box-shadow:0 calc(5px * var(--sh-k)) calc(16px * var(--sh-k)) rgb(var(--shadow) / .5),inset 0 calc(1px * var(--sh-k)) calc(1px * var(--sh-k)) rgb(var(--lift) / .35)}
}

/* ширину выставляет fitVideoBox() в app.js (видео + gap + панель кнопок, а не
   вся ширина экрана) — margin:0 auto тут же центрирует блок по той же оси,
   по которой центрируется .rec-main выше */
.rec-hud{display:flex;flex-direction:column;gap:14px;margin:0 auto;width:100%}
.rec-top{display:flex;align-items:center;gap:10px;flex-wrap:wrap}
/* мигание — им пользуется индикатор эфира на кнопке «В эфире» (.golive-dot) */
@keyframes blink{50%{opacity:.25}}
/* «стеклянные» бейджи верхней строки — размытие + тонкая обводка, как и
   остальные плавающие элементы поверх видео */
.rec-typebadge{
  font-size:12px;color:var(--on-acc-dim);padding:5px 11px;border-radius:calc(20px * var(--r-k));
  background:linear-gradient(150deg,color-mix(in srgb,var(--acc) 55%,transparent),color-mix(in srgb,var(--acc-d) 70%,transparent));
  border:var(--bw) solid rgb(var(--lift) / .25);backdrop-filter:blur(calc(6px * var(--blur-k)));-webkit-backdrop-filter:blur(calc(6px * var(--blur-k)));
}
.rec-typebadge.warn{color:#2a1f04;background:linear-gradient(150deg,color-mix(in srgb,var(--dispute) 90%,var(--mix-light)),var(--dispute))}
.rec-typebadge.low{color:#ffe2df;background:linear-gradient(150deg,color-mix(in srgb,var(--danger) 90%,var(--mix-light)),var(--danger))}
/* «идёт запись MM:SS» — справа в верхней строке. Отдельный элемент, а не число
   под таймлинией: там оно стоит в паре «позиция / длительность» и читается как
   перемотка, а не как «пишем сорок одну минуту». Кружок мигает тем же
   keyframes, что индикатор эфира на кнопке «В эфире». */
.rec-elapsed{
  display:inline-flex;align-items:center;gap:7px;font-size:12px;color:var(--danger-t);
  padding:5px 12px;border-radius:calc(20px * var(--r-k));font-variant-numeric:tabular-nums;
  background:rgb(var(--sink) / .45);border:var(--bw) solid color-mix(in srgb,var(--danger) 45%,transparent);
  backdrop-filter:blur(calc(6px * var(--blur-k)));-webkit-backdrop-filter:blur(calc(6px * var(--blur-k)));
}
.rec-elapsed b{font-weight:700}
.rec-elapsed-dot{width:8px;height:8px;border-radius:50%;background:var(--danger);animation:blink 1s infinite}
.wall-clock{
  margin-left:auto;font-variant-numeric:tabular-nums;font-weight:700;font-size:16px;padding:5px 12px;border-radius:calc(20px * var(--r-k));
  background:rgb(var(--sink) / .45);border:var(--bw) solid rgb(var(--lift) / .2);backdrop-filter:blur(calc(6px * var(--blur-k)));-webkit-backdrop-filter:blur(calc(6px * var(--blur-k)));
}
/* один ряд над таймлинией во всю ширину: [1×][последний забитый шар] — счёт —
   [вернуться в эфир] (первый блок в #recHud, не оверлей на видео).
   Грид 1fr auto 1fr: средняя колонка со счётом центрирована в ряду независимо
   от ширины боковых групп, поэтому подгонять их ширины из JS больше не нужно —
   и «вернуться в эфир» может занимать столько, сколько требует текст. */
.rec-toprow{display:grid;grid-template-columns:1fr auto 1fr;align-items:center;gap:10px;margin-bottom:10px}
/* min-width:0 — без этого грид-трек 1fr не сжимается меньше "естественной"
   ширины содержимого (двух кнопок) и перекашивает ряд */
.rec-toprow-left{justify-self:start;display:flex;gap:8px;min-width:0}
.rec-toprow .scoreboard{justify-self:center}
.rec-toprow-panel{justify-self:end;min-width:0;display:flex;justify-content:flex-end;align-items:center}
.rec-toprow-left.controls-disabled,.rec-toprow-panel.controls-disabled{
  opacity:.4;filter:grayscale(.7);pointer-events:none;
  transition:opacity .2s,filter .2s;
}
.scoreboard{display:flex;justify-content:center;align-items:center;gap:10px}
/* единая тёмная «таблетка» — счёт партии и таймер записи под ним внутри
   одного блока (не два раздельных элемента, как было, когда таймер сидел
   в rec-top). Размытие+обводка — тот же стеклянный стиль, что у бейджей и
   круглых кнопок, а не плоская заливка. */
.scoreboard-pill{
  display:flex;flex-direction:column;align-items:center;gap:4px;border-radius:calc(18px * var(--r-k));padding:10px 26px;
  background:linear-gradient(165deg,rgb(var(--lift) / .1),rgb(var(--sink) / .55));
  border:var(--bw) solid rgb(var(--lift) / .22);box-shadow:0 calc(6px * var(--sh-k)) calc(20px * var(--sh-k)) rgb(var(--shadow) / .4),var(--glow-soft);
  backdrop-filter:blur(calc(10px * var(--blur-k)));-webkit-backdrop-filter:blur(calc(10px * var(--blur-k)));
  pointer-events:auto;
}
/* line-height:1 + добивка снизу — оптическое центрирование счёта в плашке.
   У шрифта над цифрами заметно больше воздуха, чем под ними (ascent считает
   место под диакритику, а у цифр её нет), поэтому при симметричных отступах
   счёт визуально сидел ниже центра — замер показывал 21px сверху и 0 снизу.
   Добивка задана в em, поэтому переживает clamp() у font-size. */
.score-line{
  font-size:clamp(28px,3.2vw,46px);font-weight:800;color:var(--on-glass);font-variant-numeric:tabular-nums;
  line-height:1;padding-bottom:.27em;
}
.live-scrub{margin-top:10px}
/* до старта записи таймлиния видна, но неактивна — мотать пока нечего
   (кнопка «Пауза» до старта вообще скрыта, см. startRecordScreen в app.js) */
.live-scrub.controls-disabled{
  opacity:.4;filter:grayscale(.7);pointer-events:none;
  transition:opacity .2s,filter .2s;
}
/* тонкая полупрозрачная нить, а не толстая "таблетка" — по референсу это
   почти незаметный трек, вся выразительность в белом свечении у плейхеда,
   а не в цветной заливке всей пройденной части */
/* .timeline.live-timeline (два класса) — не просто .live-timeline: у общего
   .timeline{height:34px} (используется на экране плеера) та же специфичность,
   и он идёт ниже по файлу, поэтому побеждал бы просто по порядку объявления */
.timeline.live-timeline{
  position:relative;height:5px;border-radius:var(--r-pill);cursor:pointer;overflow:visible;
  background:rgba(255,255,255,.18);
}
.live-timeline::before{
  content:'';position:absolute;inset:-8px 0;/* увеличенная область клика/тапа — трек визуально тонкий, но попасть по нему пальцем должно быть легко */
}
.live-scrub-fill{
  position:absolute;inset:0 auto 0 0;width:0;border-radius:var(--r-pill);
  background:linear-gradient(90deg,rgba(255,255,255,.35),rgba(255,255,255,1));
  transition:width .2s linear; /* сглаживает шаг между 250мс-тиками таймера записи */
}
/* во время протаскивания (см. #liveTimeline pointermove в app.js) заливка должна
   идти строго за пальцем без задержки — transition тут дал бы ощущение "залипания" */
.live-scrub.dragging .live-scrub-fill{transition:none}
.live-marks{position:absolute;inset:-6px 0}
.live-scrub-row{display:flex;align-items:center;justify-content:center;margin-top:8px}
.live-scrub-time{font-size:12px;color:var(--mut);font-variant-numeric:tabular-nums}
/* маленькие пилюли (1×, последний забитый шар, вернуться в эфир) — заметно
   прозрачнее основных кнопок пульта, почти невидимы, пока не нажаты */
.live-golive,.live-pause{
  display:inline-flex;align-items:center;gap:6px;
  font-size:12px;font-weight:700;border-radius:calc(14px * var(--r-k));padding:7px 14px;cursor:pointer;
  background:rgb(var(--lift) / .08);border:var(--bw) solid rgb(var(--lift) / .16);color:var(--on-glass);
  backdrop-filter:blur(calc(8px * var(--blur-k)));-webkit-backdrop-filter:blur(calc(8px * var(--blur-k)));
  transition:transform .12s,filter .12s,background .12s;
}
.live-golive.is-live{background:rgba(20,119,63,.35);border-color:rgba(46,194,122,.5);color:var(--ok-t)}
/* «Траектории» включены — та же подсветка нажатого состояния, что у «в эфире» */
.live-pause.is-on{background:rgba(20,119,63,.35);border-color:rgba(46,194,122,.5);color:var(--ok-t)}
/* регулятор длины хвоста — рядом с кнопкой, в той же пилюле. Ползунок крупный
   намеренно: им пользуются пальцем на телефоне, часто на весу */
.traj-len{
  display:inline-flex;flex-direction:row;align-items:center;gap:10px;white-space:nowrap;
  font-size:12px;font-weight:700;border-radius:calc(14px * var(--r-k));padding:0 12px;height:34px;color:var(--on-glass);
  background:rgb(var(--lift) / .08);border:none;   /* без обводки: в ряду это плашка, а не поле ввода */
  flex:0 1 auto;min-width:0;   /* в тесном ряду пусть жмётся сам, а не выталкивает время за экран */
}
/* Пульт с траекториями и скоростью в одну строку на телефоне в ландшафте уже
   не помещается — в этом ряду перенос разрешён (тема v02 его запрещает ради
   планшета). Полоса от этого становится выше, а видео над ней — ниже:
   syncFsBar() пересчитает (см. app.js). */
body.theme-v02 #view-player .player-scrub-row{flex-wrap:wrap}
/* Где именно рвать ряд, решаем сами, а не «как ляжет по ширине»: транспорт и
   «Траектории» — первой строкой, «Скорость» с ползунком — второй. Пустой
   элемент во всю ширину и есть перенос (обычным флексом иначе не задать). */
.ctrl-break{display:none}
/* Время. В книжной ориентации экран узкий, и в ряду кнопок ему места нет —
   поднимаем его первой строкой этого ряда, то есть прямо под таймлинию, по
   центру. В альбомной ширины хватает, и оно остаётся там, где было: справа в
   одном ряду с кнопками. order:-1, а не отдельный блок, — тогда высоту ряда
   меряет один и тот же syncFsBar(), а раскладка остаётся флексом. */
@media (orientation:portrait){
  body.theme-v02 #view-player .player-scrub-row .v2-time{
    order:-1;width:100%;margin:0 0 4px;text-align:center;
  }
}
@media (max-width:520px){
  #view-player .ctrl-break{display:block;flex-basis:100%;height:0;margin:0}
  /* чтобы первая строка сошлась на 375 px, у «Экрана» и «Вида» остаются
     значки: смысл кнопки они передают, а подписи тут съедают 140 px */
  body.theme-v02 #view-player #playerFullscreenBtn,
  body.theme-v02 #view-player #playerViewToggle{font-size:0;gap:0;padding:0 10px}
  body.theme-v02 #view-player #playerFullscreenBtn svg,
  body.theme-v02 #view-player #playerViewToggle svg{width:16px;height:16px}
}
.traj-len span{min-width:46px;text-align:right;color:var(--mut)}
/* свои дорожка и бегунок: нативные слишком мелкие для пальца, а accent-color
   размер не меняет */
.traj-len input[type=range]{
  -webkit-appearance:none;appearance:none;background:transparent;cursor:pointer;
  /* 17vw, а не больше: в альбомной ориентации телефона в ряду с ним стоят
     шесть кнопок и время, и на 844 px всё вместе должно уложиться в строку */
  width:clamp(100px,17vw,200px);min-width:0;height:26px;margin:0;
}
.traj-len input[type=range]::-webkit-slider-runnable-track{height:6px;border-radius:calc(3px * var(--r-k));background:rgba(255,255,255,.25)}
.traj-len input[type=range]::-webkit-slider-thumb{
  -webkit-appearance:none;width:24px;height:24px;margin-top:-9px;border-radius:50%;
  background:var(--goal);border:calc(var(--bw) * 2) solid rgba(0,0,0,.35);
}
.traj-len input[type=range]::-moz-range-track{height:6px;border-radius:calc(3px * var(--r-k));background:rgba(255,255,255,.25)}
.traj-len input[type=range]::-moz-range-thumb{width:24px;height:24px;border:calc(var(--bw) * 2) solid rgba(0,0,0,.35);border-radius:50%;background:var(--goal)}
@media (hover:hover){ .live-golive:hover,.live-pause:hover{transform:translateY(-1px);background:rgb(var(--lift) / .16)} }
/* кнопки верхнего ряда — минимум --ctrl-btn-h (в 2 раза крупнее, чем раньше,
   когда было /2); шрифт растёт пропорционально высоте через calc от той же
   переменной, а не фиксированным px. min-height (не height) — если текст на
   кнопке не влезает в одну строку (узкий экран), кнопка растёт вниз вместо
   того, чтобы текст вылезал за пределы плашки. Размер — по содержимому (не
   flex:1): кнопки идут "друг за другом" слева, а не растягиваются на всю
   ширину группы. */
.rec-toprow-left .live-pause,.rec-toprow-panel .live-golive{
  box-sizing:border-box;min-height:var(--ctrl-btn-h);
  justify-content:center;padding:0 14px;font-size:calc(var(--ctrl-btn-h) * .22);
  text-align:center;
}
/* «Вернуться в эфир» — одной строкой: раньше кнопка была зажата шириной
   колонки маркеров и текст переносился. Теперь ряд — грид 1fr auto 1fr, и
   крайним колонкам ширину никто не диктует, так что достаточно запретить
   перенос и дать кнопке вырасти по содержимому. */
.rec-toprow-panel .live-golive{white-space:nowrap;padding:0 18px}
/* кружок-индикатор слева от подписи: горит и мигает, пока идёт эфир; после
   перемотки или паузы — гаснет (тёмный, с тонкой обводкой, чтобы оставался
   различим на полупрозрачной кнопке) */
.golive-dot{width:9px;height:9px;border-radius:50%;flex-shrink:0;box-sizing:border-box}
.live-golive.is-live .golive-dot{background:#ff3b30;animation:blink 1s infinite}
.live-golive:not(.is-live) .golive-dot{background:#0b0f0d;border:var(--bw) solid rgb(var(--lift) / .4)}
/* подписи взаимоисключающие — показываем ту, что соответствует состоянию */
.live-golive:not(.is-live) .golive-live,
.live-golive.is-live .golive-back{display:none}
.rec-controls{display:flex;gap:10px;align-items:center;margin-top:12px}
/* значок-кружок слева + подпись — форма "таблетки" (border-radius:999px,
   почти капсула), как на референсе, а не прямоугольник со скруглением.
   height:var(--ctrl-btn-h) — явная высота (не просто "по контенту"), общая
   точка отсчёта с кнопками верхнего ряда эфира выше */
.rec-controls .btn{
  display:flex;align-items:center;justify-content:center;gap:12px;box-sizing:border-box;height:var(--ctrl-btn-h);
  font-size:clamp(15px,1.6vw,19px);font-weight:800;letter-spacing:.3px;
  padding:6px clamp(18px,2vw,26px);border-radius:var(--r-pill);
  transition:transform .12s,filter .12s,box-shadow .12s;
}
.btn-badge{
  flex-shrink:0;width:clamp(38px,3.6vw,52px);height:clamp(38px,3.6vw,52px);border-radius:50%;
  display:flex;align-items:center;justify-content:center;font-size:clamp(16px,1.6vw,22px);
  background:rgba(255,255,255,.25);box-shadow:inset 0 calc(1px * var(--sh-k)) calc(1px * var(--sh-k)) rgba(255,255,255,.4);
}
/* ▶ рисуем border-треугольником, а не текстовым глифом (см. setBtnBadge в
   app.js, textContent пустой при этом классе) — центр буквенного бокса глифа
   не совпадает с его оптическим центром и съезжает по-разному в зависимости
   от шрифта/движка (на части WebView, напр. Fully Kiosk Browser, заметно
   сильнее, чем в десктопных браузерах); border-треугольник центруется
   предсказуемо везде, т.к. не зависит от метрик шрифта вообще */
.btn-badge.ico-play::before{
  content:'';width:0;height:0;
  border-style:solid;border-width:.32em 0 .32em .5em;
  border-color:transparent transparent transparent currentColor;
  margin-left:.14em; /* остриё треугольника "тяжелее" справа — сдвигаем на глаз в оптический центр */
}
/* ■ той же природы, что и ▶ выше — текстовый глиф на части WebView визуально
   съезжает от центра кружка (метрики строки/baseline у моноширинных фигурных
   символов не гарантируют точный геометрический центр); простой div-квадрат
   центруется flex-контейнером .btn-badge без всяких компенсаций */
.btn-badge.ico-stop::before{content:'';width:.42em;height:.42em;background:currentColor;border-radius:calc(2px * var(--r-k))}
/* ⏸ той же природы, что ▶/■ выше: вместо текстового глифа (съезжает в WebView)
   две вертикальные полоски CSS-фигурой, центруются flex-контейнером .btn-badge */
.btn-badge.ico-pause{gap:.14em}
.btn-badge.ico-pause::before,.btn-badge.ico-pause::after{content:'';width:.16em;height:.5em;background:currentColor;border-radius:calc(2px * var(--r-k))}
.btn-label{text-align:center}
/* «Начать запись» — тёмно-зелёный градиент (действие, к которому ведут),
   «Конец записи» — тёмный с красным контуром (осторожное/завершающее). */
.btn-rec{
  flex:1;color:var(--on-acc-soft);border:var(--bw) solid rgb(var(--lift) / .25);
  background:linear-gradient(160deg,color-mix(in srgb,var(--goal) 55%,var(--mix-dark)),color-mix(in srgb,var(--goal) 25%,var(--mix-dark)));
  box-shadow:0 calc(8px * var(--sh-k)) calc(22px * var(--sh-k)) rgb(var(--shadow) / .4),inset 0 calc(1.5px * var(--sh-k)) calc(2px * var(--sh-k)) rgb(var(--lift) / .12),var(--glow);
}
.btn-stop{
  flex:1;color:var(--danger-t);background:linear-gradient(160deg,#333,#131313);
  border:var(--bw) solid color-mix(in srgb,var(--danger) 65%,transparent);
  box-shadow:0 calc(8px * var(--sh-k)) calc(22px * var(--sh-k)) rgb(var(--shadow) / .45),inset 0 calc(1.5px * var(--sh-k)) calc(2px * var(--sh-k)) rgb(var(--lift) / .1);
}
.btn-pause{
  flex:1;color:var(--on-glass);border:var(--bw) solid rgb(var(--lift) / .25);
  background:linear-gradient(160deg,rgb(var(--lift) / .16),rgb(var(--lift) / .04));
  backdrop-filter:blur(calc(8px * var(--blur-k)));-webkit-backdrop-filter:blur(calc(8px * var(--blur-k)));
  box-shadow:0 calc(6px * var(--sh-k)) calc(18px * var(--sh-k)) rgb(var(--shadow) / .35),inset 0 calc(1.5px * var(--sh-k)) calc(2px * var(--sh-k)) rgb(var(--lift) / .15);
}
@media (hover:hover){
  .rec-controls .btn:hover{transform:translateY(-2px);filter:brightness(1.15)}
}
.rec-controls .btn:active{transform:scale(.97);filter:brightness(.9)}
.hud-show{position:fixed;bottom:calc(16px + env(safe-area-inset-bottom));left:50%;transform:translateX(-50%);background:rgb(var(--sink) / .6);color:#fff;border:var(--bw) solid var(--line);padding:10px 18px;border-radius:calc(20px * var(--r-k));font-size:14px;cursor:pointer;z-index:10}
.marker-flash{position:absolute;top:46%;left:50%;transform:translate(-50%,-50%);font-size:20px;font-weight:800;opacity:0;padding:10px 20px;border-radius:calc(14px * var(--r-k));pointer-events:none;transition:opacity .15s}
.marker-flash.show{opacity:1}

/* player */
.player-wrap{
  background:linear-gradient(165deg,rgb(var(--lift) / .06),rgb(var(--sink) / .35));
  border:var(--bw) solid rgb(var(--lift) / .14);border-radius:calc(16px * var(--r-k));overflow:hidden;
  backdrop-filter:blur(calc(10px * var(--blur-k)));-webkit-backdrop-filter:blur(calc(10px * var(--blur-k)));
  box-shadow:0 calc(8px * var(--sh-k)) calc(22px * var(--sh-k)) rgb(var(--shadow) / .35),var(--glow-soft);
}
.player-video-box{position:relative}
/* Полный экран разворачивает КОРОБКУ, а не само видео (см. #playerFullscreenBtn
   в app.js): иначе холст с траекториями остался бы под ним. Раскладка коробки
   на телефоне и на планшете разная, поэтому в полноэкранном режиме задаём её
   явно — видео по центру чёрного поля, во всю доступную высоту. */
.player-video-box:fullscreen{display:flex;align-items:center;justify-content:center;background:#000}
.player-video-box:fullscreen #playerVideo{width:100%;height:100%;max-height:100%;object-fit:contain}
/* Свой полноэкранный режим — для iPhone, где штатный есть только у самого
   <video> и закрывает собой холст с траекториями (см. setPlayerFs в app.js).
   Растягиваем коробку сами, а пульт и таймлинию прижимаем к нижнему краю,
   иначе они остались бы под ней и до них было бы не дотянуться.
   Селекторы с #view-player — чтобы перебить раскладку планшета из theme-v02
   (тот файл подключается позже и при равной силе выигрывал бы).
   z-index ниже заставки бездействия (1000): она должна закрывать и это. */
/* Видео занимает всё, ЧТО ОСТАЁТСЯ над пультом, а не весь экран: пульт и
   таймлиния поверх кадра перекрывали нижний борт стола — как раз там, где чаще
   всего и стоит разбираемый шар. Высоты обеих полос меряет syncFsBar() в
   app.js: на телефоне кнопки переносятся на две строки, в ландшафте влезают в
   одну, фиксированное число тут не годится.

   Высота через 100dvh, а не bottom:0 — на телефоне панель браузера то
   появляется, то уходит, и фиксированный низ уезжал под неё вместе с куском
   кадра. Отступы по краям — вырез и «домашняя полоска». */
body.player-fs #view-player .player-video-box{
  position:fixed;top:0;left:0;right:0;z-index:80;background:#000;
  height:calc(100dvh - var(--fs-bar-h,96px));
  padding:env(safe-area-inset-top) env(safe-area-inset-right) 0 env(safe-area-inset-left);
  box-sizing:border-box;
  display:flex;align-items:center;justify-content:center;
}
body.player-fs #view-player #playerVideo{width:100%;height:100%;max-height:100%;object-fit:contain}
/* таймлиния — своей строкой прямо над пультом, время справа от неё (в самом
   пульте ему тесно, а на узком экране оно уезжало на отдельную строку) */
/* Таймлиния — своей полосой прямо над пультом. Фон обязателен: она лежит
   поверх страницы, а под ней в этот момент легенда маркеров из правой
   колонки — без заливки просвечивала. */
body.player-fs #view-player #playerScrub{
  position:fixed;left:0;right:0;bottom:var(--fs-row-h,56px);z-index:81;margin:0;
  padding:6px 16px 8px;background:#000;
}
body.player-fs #view-player .player-scrub-row{
  position:fixed;left:0;right:0;bottom:0;z-index:81;margin:0;gap:8px;
  padding:8px 16px calc(10px + env(safe-area-inset-bottom));background:#000;
}
#playerVideo{width:100%;display:block;background:#000;max-height:52vh;cursor:pointer}
/* вспышка ±15с по двойному тапу (см. app.js seekPlayerBy) — прямоугольник на
   свою половину видео, гаснет сама; pointer-events:none, чтобы не мешать
   кликам/тапам ПО видео под ней (обработчик висит на самом #playerVideo) */
.seek-flash{
  position:absolute;top:0;bottom:0;width:50%;
  display:flex;align-items:center;justify-content:center;
  font-size:16px;font-weight:800;color:#fff;letter-spacing:.3px;
  background:radial-gradient(circle,rgba(255,255,255,.16),transparent 70%);
  opacity:0;pointer-events:none;transition:opacity .15s;
}
/* холст траекторий: ровно поверх видео, кликам не мешает. Растягивается на всю
   коробку, а поля letterbox'а (object-fit:contain на планшете) вычитает сам
   рисовальщик — см. trajFit в app.js */
.traj-canvas{position:absolute;inset:0;width:100%;height:100%;pointer-events:none}
.seek-flash-left{left:0}
.seek-flash-right{right:0}
.seek-flash.show{opacity:1}
/* #liveScrub на экране трансляции сидит в HUD-оверлее без своих отступов —
   тут же таймлиния внутри обычной карточки, рядом с #playerMeta (padding:14px),
   поэтому нужны свои горизонтальные отступы, иначе бар прилипает к краям карточки */
#playerScrub{margin:12px 14px 0}
.timeline{position:relative;height:34px;background:var(--bg2);margin:0;cursor:pointer}
.tl-mark{position:absolute;top:0;width:4px;height:100%;transform:translateX(-2px)}
.tl-mark.goal{background:var(--goal)}
.tl-mark.penalty{background:var(--danger)}
.tl-mark.hit{background:var(--custom)}
.tl-mark.tag{background:var(--tag)}
.tl-mark.dispute{background:var(--dispute)}
.tl-mark.custom{background:var(--custom)}
.player-meta{padding:14px;color:var(--mut);font-size:13px;line-height:1.6}
/* список маркеров под таймлайном — свой скролл внутри фиксированной высоты,
   а не растягивание всей страницы: при 30-40 маркерах в партии список на
   всю длину сделал бы страницу неудобной для навигации */
.marker-list{max-height:220px;overflow-y:auto;border-top:var(--bw) solid rgb(var(--lift) / .1)}
.marker-row{
  display:flex;align-items:center;gap:10px;padding:9px 14px;cursor:pointer;
  border-bottom:var(--bw) solid rgb(var(--lift) / .06);transition:background .12s;
}
.marker-row:last-child{border-bottom:none}
.marker-row:active{background:rgb(var(--lift) / .1)}
@media (hover:hover){ .marker-row:hover{background:rgb(var(--lift) / .07)} }
.marker-row .dot{flex:none}
.marker-row-label{flex:1;min-width:0;font-size:14px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.marker-row-score{color:var(--mut);font-variant-numeric:tabular-nums}
.marker-row-time{color:var(--mut);font-size:13px;font-variant-numeric:tabular-nums}
.marker-row.sel{background:rgba(46,194,122,.16)}
/* Ролик вертикальный (поворот вида сверху на 90°, см. server/clips.js), поэтому
   ограничиваем именно ВЫСОТУ: иначе он растянется на всю правую колонку и
   выдавит список за экран. */
.player-scrub-row{display:flex;gap:8px;margin:12px 14px 0;justify-content:center;flex-wrap:wrap}
/* правая колонка плеера (участники+маркеры+легенда+скачать). На телефоне —
   обычной стопкой внутри карточки; на планшете уезжает вправо (грид ниже) */
.player-aside{display:flex;flex-direction:column}
.player-actions{display:flex;gap:12px;margin:16px 14px 14px}
.player-actions .btn{flex:1}
.legend{display:flex;gap:16px;flex-wrap:wrap;margin:16px 14px 0;color:var(--mut);font-size:13px}
.legend span{display:flex;align-items:center;gap:7px}
/* удаление записи — кнопка на карточке в архиве (см. buildGameRow); на экране
   просмотра кнопки больше нет */
.rec-del-btn{
  flex-shrink:0;width:38px;height:38px;border-radius:calc(10px * var(--r-k));display:flex;align-items:center;justify-content:center;
  background:rgba(224,88,78,.1);border:var(--bw) solid rgba(224,88,78,.42);color:var(--danger-t);cursor:pointer;
  transition:transform .12s,background .12s;
}
.rec-del-btn:active{transform:scale(.94);filter:brightness(.9)}
/* «Ролики» у строки архива — отдельный вход, минуя часовой повтор */
.rec-clips-btn{
  flex-shrink:0;width:38px;height:38px;border-radius:calc(10px * var(--r-k));display:flex;align-items:center;justify-content:center;
  background:rgba(46,194,122,.1);border:var(--bw) solid rgba(46,194,122,.42);color:var(--acc);
  cursor:pointer;font-size:15px;transition:transform .12s,background .12s;
}
.rec-clips-btn:active{transform:scale(.94)}
.rec-clips-btn:hover{background:rgba(46,194,122,.2)}
@media (hover:hover){ .rec-del-btn:hover{background:rgba(224,88,78,.22)} }

/* Планшет в ландшафте (напр. 1280×800): просмотр записи на весь экран —
   видео слева, справа колонка «участники + маркеры + скачать», под видео
   таймлиния и пульт. Телефон и портрет остаются вертикальной стопкой выше. */
@media (min-width:900px) and (orientation:landscape){
  #view-player{max-width:none}
  .player-wrap{
    display:grid;
    grid-template-columns:minmax(0,1fr) clamp(280px,30%,380px);
    grid-template-rows:minmax(0,1fr) auto auto;
    grid-template-areas:"video aside" "scrub aside" "controls aside";
    height:calc(100dvh - 150px);
  }
  /* С новой шапкой записи над панелью зашитые 150 px перестали сходиться:
     панель уезжала вниз вместе с таймлинией и пультом, а на широком экране
     видео вытягивалось на 1700 точек. Отдать высоту флексу нельзя — строка
     грида minmax(0,1fr) растёт по содержимому, пока контейнер не ограничен, —
     поэтому вычитаем ещё и шапку. Её высота приходит из JS (см.
     applyNextPlayer): в один ряд кнопки помещаются не всегда. */
  [data-ui="next"] #view-player .player-wrap{height:calc(100dvh - 82px - var(--ph-h,79px))}
  .player-video-box{grid-area:video;min-height:0;height:100%;display:flex;align-items:center;justify-content:center;background:#000;border-right:var(--bw) solid rgb(var(--lift) / .1)}
  #playerVideo{width:100%;height:100%;max-height:100%;object-fit:contain}
  #playerScrub{grid-area:scrub;border-right:var(--bw) solid rgb(var(--lift) / .1);padding-right:14px}
  .player-scrub-row{grid-area:controls;border-right:var(--bw) solid rgb(var(--lift) / .1);margin-bottom:12px;padding-bottom:2px}
  /* Колонка ПРОКРУЧИВАЕТСЯ ЦЕЛИКОМ, а списки внутри имеют каждый свой потолок.
     Так было не всегда: раньше единственный список маркеров растягивался на
     всю высоту (flex:1), и это работало ровно до появления второго списка —
     ролики носят тот же класс .marker-list, забирали всю колонку, и маркеры
     схлопывались в НОЛЬ (оставались одна легенда и ролики). Делить фиксированную
     высоту между двумя списками флексом можно, но каждая новая мелочь — включили
     видео ролика, добавили кнопку — снова кого-нибудь схлопывает. Прокрутка
     колонки таких сюрпризов не даёт вовсе. */
  .player-aside{grid-area:aside;min-height:0;overflow-y:auto}
  /* flex-shrink:0 обязателен: колонка — флекс с заданной высотой, и без этого
     она не прокручивается, а ужимает детей. Первым в ноль уходил список
     маркеров — ровно та пропажа, с которой всё началось. */
  .player-aside > *{flex-shrink:0}
  .player-aside #markerList{max-height:none;min-height:0}
}
.dot{width:11px;height:11px;border-radius:calc(3px * var(--r-k));display:inline-block}
.dot.goal{background:var(--goal)}.dot.penalty{background:var(--danger)}.dot.hit{background:var(--custom)}.dot.tag{background:var(--tag)}
.dot.dispute{background:var(--dispute)}.dot.custom{background:var(--custom)}

/* личный кабинет — пилл в шапке (см. #profilePill в index.html) — тот же
   приём, что .wallet-chip, только круглое фото вместо иконки-эмодзи */
.profile-pill{
  display:flex;align-items:center;gap:8px;
  background:linear-gradient(165deg,rgb(var(--lift) / .1),rgb(var(--sink) / .4));
  border:var(--bw) solid rgb(var(--lift) / .18);
  border-radius:calc(14px * var(--r-k));padding:6px 14px 6px 6px;color:var(--txt);cursor:pointer;font-weight:700;font-size:14px;
  backdrop-filter:blur(calc(8px * var(--blur-k)));-webkit-backdrop-filter:blur(calc(8px * var(--blur-k)));
  box-shadow:0 calc(4px * var(--sh-k)) calc(14px * var(--sh-k)) rgb(var(--shadow) / .3),var(--glow-soft);
  transition:transform .12s,filter .12s,background .12s;
}
.profile-pill:active{transform:scale(.97)}
@media (hover:hover){
  .profile-pill:hover{transform:translateY(-1px);background:linear-gradient(165deg,rgb(var(--lift) / .16),rgb(var(--sink) / .45))}
}
.pill-avatar,.pill-avatar-placeholder{
  width:28px;height:28px;border-radius:50%;flex-shrink:0;object-fit:cover;
  background:linear-gradient(160deg,color-mix(in srgb,var(--acc) 55%,var(--mix-dark)),color-mix(in srgb,var(--acc-d) 40%,var(--mix-dark)));
  border:var(--bw) solid rgb(var(--lift) / .25);
}
.pill-avatar-placeholder{display:flex;align-items:center;justify-content:center;font-size:15px}
.pill-name{max-width:120px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.pill-name:empty::before{content:'Личный кабинет'}
/* На телефоне в шапке остаётся только фото. Имя с фамилией занимало там до
   половины строки, а рядом ещё колокольчик и «?». На планшете и десктопе места
   хватает, и подпись оставляем: планшет у стола общий, и по ней сразу видно,
   чей аккаунт сейчас открыт. Заодно круглим пилл — без текста ассиметричный
   отступ справа смотрелся бы обрезанным. */
@media(max-width:560px){
  .pill-name{display:none}
  .profile-pill{padding:6px;border-radius:50%}
}

/* крупная круглая фото-кнопка — онбординг и #view-profile (см. .avatar-picker) */
/* align-items:center и gap — из-за карточки пользователя у сисадмина: там
   рядом с кругом стоит ещё «Убрать фото», и без этого кнопка растягивалась
   на всю высоту круга */
.avatar-picker{display:flex;justify-content:center;align-items:center;gap:12px;margin-bottom:6px}
.avatar-circle{
  width:96px;height:96px;border-radius:50%;padding:0;cursor:pointer;overflow:hidden;
  background:linear-gradient(160deg,color-mix(in srgb,var(--acc) 55%,var(--mix-dark)),color-mix(in srgb,var(--acc-d) 40%,var(--mix-dark)));
  border:calc(var(--bw) * 2) solid rgb(var(--lift) / .25);
  color:var(--on-acc);
  display:flex;align-items:center;justify-content:center;
  transition:transform .12s,filter .12s;
}
.avatar-circle:active{transform:scale(.96);filter:brightness(.9)}
@media (hover:hover){ .avatar-circle:hover{filter:brightness(1.1)} }
.avatar-img{width:100%;height:100%;object-fit:cover}
.avatar-placeholder{font-size:34px}

.profile-card{display:flex;flex-direction:column;align-items:center;gap:16px}
.profile-card .auth-form{width:100%}
.profile-card .btn-danger{width:100%;margin-top:4px}

/* «Мои данные» на телефоне должны помещаться на экран целиком, без прокрутки
   (iPhone 15 Pro Max — 932pt минус шапка и вкладки). Поэтому здесь: поля
   парами в две колонки, фото поменьше и прижато к ним, поджатые отступы.
   Клубы, кнопки и сообщение формы — во всю ширину, их пополам не делят. */
/* align-items:stretch (а не start) + растяжка поля внутри подписи: у
   input[type=date] есть встроенная кнопка календаря, и он на пару пикселей
   выше обычного текстового поля — в паре «Никнейм / Дата рождения» это было
   видно. Так соседи по строке всегда одной высоты, без подгонки числом */
/* minmax(0,1fr), а не 1fr: у обычного 1fr нижняя граница колонки — min-content
   её содержимого, поэтому поле, которое отказывается сжиматься (нативный
   input[type=date] на iOS), раздвигает саму колонку, и сетка вылезает за
   карточку. minmax(0,...) снимает эту нижнюю границу на уровне сетки —
   независимо от того, что о своей ширине думает поле внутри. */
.form-2col{display:grid;grid-template-columns:minmax(0,1fr) minmax(0,1fr);gap:10px;align-items:stretch}
.form-2col > label{height:100%}
.form-2col > label > input{flex:1 1 auto;min-height:0}
/* без min-width:0 колонка растягивается под собственную ширину поля (у
   input[type=date] она немаленькая) и форма вылезает за край карточки */
.form-2col > *{min-width:0}
.form-2col input{width:100%;min-width:0}
/* ...но одного min-width:0 не хватает: iOS рисует input[type=date] нативным
   контролом со своей собственной минимальной шириной («20 Jan 1993» плюс
   поля), и width:100% её не пересиливает — поле уезжало за правый край
   карточки, утаскивая за собой всю сетку. appearance:none снимает нативную
   отрисовку, после чего поле ведёт себя как обычная коробка. Значение внутри
   при этом центрируется — прижимаем влево, чтобы совпадало с соседями. */
.form-2col input[type=date]{-webkit-appearance:none;appearance:none}
.form-2col input[type=date]::-webkit-date-and-time-value{text-align:left;margin:0;min-width:0}
.form-2col > .club-picker-field,
.form-2col > .btn,
.form-2col > .form-msg{grid-column:1 / -1}
.profile-card-tight{gap:10px;padding:14px}
.profile-card-tight .avatar-picker{margin-bottom:0}
.profile-card-tight .avatar-circle{width:76px;height:76px}
.profile-card-tight .avatar-placeholder{font-size:28px}
/* значки клубов пока пустые — на телефоне их резерв высоты только мешает */
.profile-card-tight .profile-badges{min-height:0}
.profile-card-tight .profile-stat{padding:8px 6px}
.profile-card-tight .profile-stat-n{font-size:20px}
.profile-card-tight .profile-stat-l{font-size:11px}
.profile-card-tight .btn{padding:11px}
/* пустая пока (значки клубов появятся позже) — фиксированная высота, чтобы
   верстка не прыгала, когда их станет видно */
.profile-badges{display:flex;gap:8px;min-height:24px}
/* «клубы, в которых играю» — чекбоксы-пилюли внутри формы профиля; погашенные
   (.disabled) — те, что не влезают в лимит, см. applyProfileClubsLimit */
.club-picker-field{display:flex;flex-direction:column;gap:6px;text-align:left}
.club-picker-label{font-size:13px;color:var(--mut)}
.club-picker{display:flex;flex-wrap:wrap;gap:8px}
.club-picker-item{
  display:flex;align-items:center;gap:6px;cursor:pointer;font-size:14px;
  padding:8px 12px;border-radius:calc(14px * var(--r-k));
  background:rgb(var(--lift) / .05);border:var(--bw) solid rgb(var(--lift) / .12);
}
/* Клубы игрока — строка целиком, без нативной галочки. Чекбокс остаётся в
   разметке (на нём состояние и лимит в три клуба), но не показывается: он
   подхватывал общий стиль полей ввода и разъезжался в «синяя галка, пустая
   полоса на всю ширину и подпись, уехавшая на вторую строку». Выбранный клуб
   виден по заливке строки — тем же .active, что и плитки в мастере брони. */
#profileClubsList{flex-direction:column}
/* flex-direction задаём явно: это <label> внутри формы профиля, а там у
   подписей стоит колонка («Имя» над полем) — и наша строка от неё же
   получала колонку с align-items:center, из-за чего название клуба вставало
   по центру. Список читается сверху вниз по названиям, значит строка и текст
   слева. */
#profileClubsList .club-picker-item{
  width:100%;padding:11px 14px;flex-direction:row;justify-content:flex-start;
}
#profileClubsList .club-pick{
  position:absolute;width:1px;height:1px;opacity:0;pointer-events:none;margin:0;
}
.club-picker-item.disabled{opacity:.45;cursor:not-allowed}
.yc-staff-pick{display:flex;align-items:center;gap:8px;cursor:pointer}
/* «вам брошен вызов» — над сеткой карточек главного меню */
.match-notice{
  display:flex;align-items:center;justify-content:space-between;gap:10px;flex-wrap:wrap;
  padding:12px 14px;margin-bottom:12px;border-radius:calc(14px * var(--r-k));
  background:linear-gradient(160deg,color-mix(in srgb,var(--acc) 28%,var(--mix-dark)),color-mix(in srgb,var(--acc-d) 18%,var(--mix-dark)));
  border:var(--bw) solid rgb(var(--lift) / .22);
}
.match-row .rec-info{cursor:pointer}
.match-row .rec-actions{display:flex;gap:6px;flex-wrap:wrap}
.rec-meta.warn{color:var(--danger)}
/* сетка «плиток» выбора (даты, время) — те же пилюли, что в пикере клубов,
   но выложенные плотной сеткой: слотов бывает под три десятка */
.slot-grid{display:flex;flex-wrap:wrap;gap:8px}
.slot-grid .club-picker-item{justify-content:center;min-width:76px}
/* выбранная плитка/строка мастера */
.club-picker-item.active{
  border-color:color-mix(in srgb,var(--acc) 60%,transparent);
  background:color-mix(in srgb,var(--acc) 22%,transparent);
}
#oppClubSelect{width:100%;margin-bottom:8px}
/* имя соперника на экране вызова ведёт в его профиль — но это внутренняя
   навигация, а не внешняя ссылка: дефолтный синий подчёркнутый вид браузера
   тут выбивается из оформления */
#mdOpponentLink{color:var(--acc);text-decoration:none}
/* «Сохраняем запись…» — модалка без кнопки закрытия: она уходит сама, когда
   агент дописал файл и открывается просмотр (см. stopRecording в app.js).
   В отличие от остальных окон на этом каркасе (у них шапка с крестиком и
   контент во всю высоту) тут маленький блок по центру экрана */
#savingModal{justify-content:center;align-items:center}
.saving-body{
  display:flex;flex-direction:column;align-items:center;gap:12px;text-align:center;
  padding:26px 30px;border-radius:calc(18px * var(--r-k));max-width:340px;
  background:linear-gradient(165deg,rgb(var(--lift) / .1),rgb(var(--sink) / .5));
  border:var(--bw) solid rgb(var(--lift) / .2);box-shadow:0 calc(10px * var(--sh-k)) calc(30px * var(--sh-k)) rgb(var(--shadow) / .5);
}
.saving-title{margin:0;font-weight:700;font-size:16px;color:var(--on-glass)}
.spinner{
  width:34px;height:34px;border-radius:50%;flex-shrink:0;
  border:calc(var(--bw) * 3) solid rgb(var(--lift) / .18);border-top-color:var(--acc);
  animation:spin .8s linear infinite;
}
@keyframes spin{to{transform:rotate(360deg)}}
/* уважаем системную настройку «меньше движения» — крутилка замирает, но
   остаётся видимой как индикатор */
@media (prefers-reduced-motion:reduce){ .spinner{animation:none} }
/* слоты состава на «Новой записи» — те же поля, что были у Игрока-2, просто
   их теперь до четырёх (игра 2×2), см. renderPlayerSlots в app.js */
.player-slot{display:block;margin-bottom:10px}
.player-slot .lbl{display:block;margin-bottom:4px}
/* блок игрока, имя которого можно вписать по тапу (пустой слот или вписанный
   руками); у подтверждённого по QR имя из учётки и тапать нечего */
.pp-player-id.pp-renamable{cursor:pointer}
@media (hover:hover){
  .pp-player-id.pp-renamable:hover .pp-player-name{text-decoration:underline}
}
.profile-stats{display:grid;grid-template-columns:repeat(3,1fr);gap:10px;width:100%}
.profile-stat{
  display:flex;flex-direction:column;align-items:center;gap:2px;padding:12px 8px;
  background:rgb(var(--lift) / .05);border:var(--bw) solid rgb(var(--lift) / .12);border-radius:calc(12px * var(--r-k));
}
.profile-stat-n{font-size:24px;font-weight:800;font-variant-numeric:tabular-nums}
.profile-stat-l{font-size:12px;color:var(--mut);text-align:center}

/* вкладки личного кабинета (Мои данные/История игр/Рейтинг/Друзья) — тот же
   вложенный паттерн переиспользуется для под-вкладок "Друзья" (Мои друзья/
   Заявки), просто на уровень глубже */
.profile-tabs{display:flex;gap:8px;overflow-x:auto;margin-bottom:16px;scrollbar-width:none}
.profile-tabs::-webkit-scrollbar{display:none}
.profile-tab{
  flex-shrink:0;background:rgb(var(--lift) / .06);border:var(--bw) solid rgb(var(--lift) / .18);color:var(--mut);
  padding:9px 16px;border-radius:var(--r-pill);font-size:13px;font-weight:700;cursor:pointer;
  transition:background .12s,color .12s,border-color .12s;
}
.profile-tab.active{
  background:linear-gradient(160deg,color-mix(in srgb,var(--acc) 60%,var(--mix-dark)),color-mix(in srgb,var(--acc-d) 55%,var(--mix-dark)));
  border-color:rgb(var(--lift) / .22);color:var(--on-acc);
}
.profile-tab-pane{display:none}
.profile-tab-pane.active{display:block}
.profile-tabs-nested{margin-bottom:14px}

/* строки рейтинга/друзей/поиска — переиспользуют .rec-item/.rec-thumb (см.
   выше, тот же паттерн, что строки архива), только с фото вместо эмодзи и
   кнопками действия вместо стрелки */
.rec-thumb img{width:100%;height:100%;border-radius:inherit;object-fit:cover}
.rec-actions{display:flex;gap:8px;flex-shrink:0}
.rec-actions .btn{padding:8px 14px;font-size:13px;border-radius:calc(10px * var(--r-k))}
.rec-item.me{box-shadow:0 0 0 1.5px var(--acc) inset,0 calc(4px * var(--sh-k)) calc(14px * var(--sh-k)) rgb(var(--shadow) / .3)}

.friend-search-body{flex:1;min-height:0;display:flex;flex-direction:column;gap:12px;overflow-y:auto}

/* кадрирование фото в квадрат — общая модалка (см. openCropper в app.js),
   тот же приём поверх всего, что .cam-preview-modal */
.crop-modal{
  position:fixed;inset:0;z-index:70;background:rgb(var(--scrim) / .92);
  display:flex;align-items:center;justify-content:center;padding:18px;
  backdrop-filter:blur(calc(4px * var(--blur-k)));-webkit-backdrop-filter:blur(calc(4px * var(--blur-k)));
}
.crop-box{display:flex;flex-direction:column;align-items:center;gap:14px;width:100%;max-width:420px}
#cropCanvas{width:100%;max-width:340px;aspect-ratio:1/1;border-radius:50%;background:#000;touch-action:none;cursor:grab}
#cropCanvas:active{cursor:grabbing}
.crop-zoom{width:100%;max-width:340px}
.crop-actions{display:flex;gap:12px;width:100%;max-width:340px}
.crop-actions .btn{flex:1}

/* заставка бездействия (рекламный ролик) — на весь экран, поверх вообще всего
   (см. #idleOverlay в index.html, showIdleOverlay/hideIdleOverlay в app.js) */
.idle-overlay{
  position:fixed;inset:0;z-index:1000;background:#000;
  display:flex;align-items:center;justify-content:center;
}
.idle-overlay.hidden{display:none}
.idle-overlay video{width:100%;height:100%;object-fit:cover}
/* Зов к экрану — полосой понизу поверх ролика: середину кадра не закрываем,
   ролик клуба показывают ради него самого. Затемнение градиентом, иначе текст
   пропадает на светлом кадре.
   pointer-events:none обязателен: тап должен ГАСИТЬ заставку (его ловит общий
   слушатель, см. resetIdleActivity), а не упираться в подпись. */
.idle-cta{
  position:absolute;left:0;right:0;bottom:0;padding:26px 24px 30px;text-align:center;
  background:linear-gradient(to top,rgba(0,0,0,.85),rgba(0,0,0,.55) 55%,transparent);
  pointer-events:none;
}
/* Зов сделан КНОПКОЙ, хотя нажимать её отдельно не нужно: экран гасится тапом
   в любое место. Форма кнопки здесь — не обман, а единственный понятный с
   полутора метров знак, что прибор живой и его трогают. Отсюда и три слоя
   движения: кнопка «дышит», вокруг расходится ореол, по ней проходит перелив.
   Каждый по отдельности почти незаметен, вместе — ловят взгляд боковым
   зрением, ради чего всё и затевалось. */
.idle-cta-tap{
  display:inline-block;position:relative;overflow:hidden;
  padding:15px 40px;border-radius:var(--r-pill);
  font-size:30px;font-weight:800;color:#fff;
  background:linear-gradient(135deg,var(--acc-d),var(--acc));
  border:var(--bw) solid rgba(255,255,255,.38);
  animation:idleBreathe 2.8s ease-in-out infinite;
}
/* перелив — светлая полоса, проходящая по кнопке и потом ждущая большую часть
   цикла: непрерывное мерцание на стене раздражает, редкий проблеск — зовёт */
.idle-cta-tap::after{
  content:'';position:absolute;inset:0;
  background:linear-gradient(115deg,transparent 36%,rgba(255,255,255,.5) 50%,transparent 64%);
  transform:translateX(-130%);
  animation:idleSheen 3.4s ease-in-out infinite;
}
@keyframes idleBreathe{
  0%,100%{transform:scale(1);box-shadow:0 calc(10px * var(--sh-k)) calc(28px * var(--sh-k)) rgba(0,0,0,.45),0 0 0 0 rgba(46,194,122,.55)}
  50%{transform:scale(1.035);box-shadow:0 calc(14px * var(--sh-k)) calc(34px * var(--sh-k)) rgba(0,0,0,.5),0 0 0 16px rgba(46,194,122,0)}
}
@keyframes idleSheen{ 0%,58%{transform:translateX(-130%)} 100%{transform:translateX(130%)} }
.idle-cta-sub{margin-top:14px;font-size:17px;line-height:1.35;color:#d7ece0}
.idle-cta-price{
  margin-top:12px;display:inline-block;padding:7px 16px;border-radius:var(--r-pill);
  font-size:16px;font-weight:700;color:#eafff4;
  background:rgba(29,122,76,.55);border:var(--bw) solid rgba(255,255,255,.25);
}
/* Человек, которому движение мешает (настройка системы), получает ту же кнопку
   без всякой анимации — форма и цвет от этого не страдают */
@media (prefers-reduced-motion:reduce){
  .idle-cta-tap{animation:none}
  .idle-cta-tap::after{display:none}
}
@media (max-width:560px){
  .idle-cta{padding:18px 16px 22px}
  .idle-cta-tap{font-size:23px;padding:12px 28px}
  .idle-cta-sub{font-size:15px;margin-top:12px}
  .idle-cta-price{font-size:15px}
}

/* install-banner — нижний баннер «установить приложение» вне клуба (см.
   showInstallBannerIfNeeded в app.js) + модалка с ручной инструкцией для
   браузеров без beforeinstallprompt */
.install-banner{
  position:fixed;left:12px;right:12px;bottom:16px;z-index:55;
  display:flex;align-items:center;gap:10px;flex-wrap:wrap;
  background:var(--ink);border:var(--bw) solid var(--acc);border-radius:calc(14px * var(--r-k));
  padding:12px 14px;box-shadow:0 calc(8px * var(--sh-k)) calc(24px * var(--sh-k)) rgb(var(--shadow) / .35);
}
/* пока в фокусе поле ввода — баннер убран с дороги (см. focusin в app.js):
   на Android он всплывает над клавиатурой ровно туда, куда метит палец */
.install-banner.typing{display:none}
.install-banner-text{flex:1;min-width:180px;font-size:13px;color:var(--txt)}
.install-banner-close{
  flex-shrink:0;width:28px;height:28px;border-radius:calc(8px * var(--r-k));font-size:13px;cursor:pointer;
  background:rgb(var(--lift) / .08);border:var(--bw) solid rgb(var(--lift) / .2);color:var(--txt);
}
.install-instructions{color:var(--txt);font-size:15px;line-height:1.6;overflow-y:auto}
.install-instructions ol{margin:0;padding-left:20px}
.install-instructions li{margin-bottom:10px}

/* toast */
.toast{
  position:fixed;left:50%;bottom:30px;transform:translateX(-50%);
  background:var(--ink);border:var(--bw) solid var(--acc);color:var(--txt);
  padding:12px 20px;border-radius:calc(14px * var(--r-k));font-size:14px;z-index:50;max-width:90%;
}

@media(min-width:760px){
  .choice-grid{flex-direction:row}
}
@media(max-width:520px){
  .menu-grid{grid-template-columns:1fr}
  .big-card.span-2{grid-column:auto}
}
/* Панель сисадмина на десктопе: пункты по три в ряд. Все шесть карточек стоят
   span-2 (на всю ширину) — на 1100px это давало ультрадлинные полосы с морем
   пустоты справа от заголовка. Здесь span-2 отменяется. */
.menu-grid--rows .big-card.span-2{grid-column:auto}
.menu-grid--rows .big-card{padding:20px 18px;gap:6px}
.menu-grid--rows .big-ico{font-size:28px}
.menu-grid--rows .big-t{font-size:17px}
@media(min-width:900px){ .menu-grid--rows{grid-template-columns:repeat(3,1fr);gap:12px} }

/* Панель сисадмина на телефоне: карточка становится строкой «иконка слева —
   заголовок и описание справа». Вертикальная вёрстка с иконкой над текстом
   давала ~120px на пункт, и шесть пунктов панели не помещались на экран —
   приходилось мотать. Здесь пункт ~64px, вся панель видна сразу. */
@media(max-width:560px){
  .menu-grid--rows{grid-template-columns:1fr;gap:10px}
  .menu-grid--rows .big-card{
    display:grid;grid-template-columns:auto 1fr;column-gap:14px;align-items:center;
    padding:12px 14px;border-radius:calc(14px * var(--r-k));gap:2px 14px;
  }
  .menu-grid--rows .big-ico{grid-row:1 / 3;display:flex;align-items:center;font-size:26px}
  .menu-grid--rows .big-t{font-size:16px;align-self:end}
  .menu-grid--rows .big-s{font-size:12px;line-height:1.3;align-self:start}
}

/* Роли в списке пользователей (сисадмин): чип в цвете самой роли. Цвет
   задаётся в окне «Роли» и приезжает с сервера переменной --role-color, потому
   что список ролей теперь не зашит в код и класса на каждую не напишешь.
   color-mix даёт из одного цвета и заливку, и рамку — так чип остаётся
   читаемым на тёмном фоне, каким бы яркий цвет ни выбрали. */
.log-action.role-pill{
  color:var(--role-color,#8fa3b0);
  background:color-mix(in srgb, var(--role-color,#8fa3b0) 18%, transparent);
  border-color:color-mix(in srgb, var(--role-color,#8fa3b0) 45%, transparent);
}
/* строка игры в архиве пользователя (сисадмин) — открывает плеер */
.user-game-row{cursor:pointer}

/* ===== Алерты (только главный сисадмин, см. #view-alerts) ===== */
/* Бейдж на карточке хаба: красный кружок с числом непросмотренных. inline-flex
   с min-width=высоте, чтобы однозначная цифра давала ровный круг, а двузначная
   растягивала его в «пилюлю», а не ломала вёрстку заголовка карточки. */
.alert-badge{
  display:inline-flex;align-items:center;justify-content:center;
  min-width:22px;height:22px;padding:0 6px;margin-left:8px;
  border-radius:calc(11px * var(--r-k));background:var(--danger);color:var(--on-acc);
  font-size:13px;font-weight:700;line-height:1;vertical-align:middle;
}
/* Вкладки и проверка доставки в одном ряду: вкладки прокручиваются сами
   (у .profile-tabs свой overflow-x), кнопка приколота справа и не сжимается —
   иначе на телефоне её текст ужимался в две строки. */
.alerts-head{display:flex;align-items:flex-start;gap:10px}
.alerts-head .profile-tabs{flex:1;min-width:0}
.alerts-pushtest{flex:none}
.alerts-toolbar{display:flex;align-items:center;gap:10px;flex-wrap:wrap;margin-bottom:14px}
.alerts-count{color:var(--mut);font-size:14px;margin-right:auto}
/* Кружок выбора: тот же элемент и в шапке («выбрать все»), и в каждой строке.
   Заполненное состояние — .on, промежуточное («выбрано не всё») — .some. */
.alert-check{
  flex:none;width:22px;height:22px;border-radius:50%;
  border:calc(var(--bw) * 2) solid var(--line);background:transparent;cursor:pointer;padding:0;
  position:relative;transition:background .12s,border-color .12s;
}
.alert-check:hover{border-color:var(--acc)}
.alert-check.on{background:var(--acc);border-color:var(--acc)}
.alert-check.on::after{
  content:"";position:absolute;left:6px;top:2px;width:5px;height:10px;
  border:solid var(--on-acc);border-width:0 2px 2px 0;transform:rotate(45deg);
}
.alert-check.some{border-color:var(--acc)}
.alert-check.some::after{
  content:"";position:absolute;left:4px;top:8px;width:10px;height:2px;background:var(--acc);
}
/* Алерт свёрнут в одну строку, подробности разворачиваются по клику: разбирают
   их всё равно по одному, а видеть разом хочется весь список. */
.alert-row{display:flex;flex-direction:column;align-items:stretch;gap:0;padding:0}
/* непросмотренный выделяем полосой слева и более плотным фоном — чтобы
   в общем списке сразу было видно, что новое, а что уже разобрано */
.alert-row.unread{border-left:calc(var(--bw) * 3) solid var(--danger)}
.alert-row.read{opacity:.55}
.alert-head{display:flex;align-items:center;gap:10px;min-width:0;padding:10px 14px;cursor:pointer}
@media(hover:hover){ .alert-head:hover{background:rgb(var(--lift) / .04)} }
/* заголовок съедает всю свободную ширину и обрезается многоточием — строка
   обязана оставаться одной строкой, иначе плотность теряется */
.alert-title{flex:1;min-width:0;font-weight:600;font-size:14px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.alert-head-player,.alert-head-when{color:var(--mut);font-size:12px;white-space:nowrap;flex-shrink:0}
.alert-caret{color:var(--mut);font-size:12px;flex-shrink:0;transition:transform .12s}
.alert-row.open .alert-caret{transform:rotate(180deg)}
.alert-sev{
  display:inline-block;width:9px;height:9px;border-radius:50%;flex-shrink:0;
  background:var(--dispute);
}
.alert-sev.critical{background:var(--danger)}
/* info — не поломка, а факт из жизни клуба (открылся/закрылся). Приглушённый
   серый, чтобы в списке такие строки не притягивали взгляд наравне с авариями */
.alert-sev.info{background:var(--mut)}
/* подробности живут в потоке, а не в наложении: под ними разворачивается ещё и
   форма компенсации, и высота у неё какая угодно */
.alert-body{display:none;padding:0 14px 12px 36px;min-width:0}
.alert-row.open .alert-body{display:block}
.alert-text{color:var(--mut);font-size:14px;word-break:break-word}
.alert-meta{color:var(--mut);font-size:13px;margin-top:4px;opacity:.8}
/* на телефоне в строке остаётся только суть — имя и время не влезают рядом
   с заголовком, а время всё равно продублировано в развороте */
@media(max-width:560px){
  .alert-head-player,.alert-head-when{display:none}
  .alert-body{padding-left:14px}
}
.alert-repeats{
  display:inline-block;margin-left:6px;padding:1px 7px;border-radius:calc(9px * var(--r-k));
  background:var(--bg2);border:var(--bw) solid var(--line);font-size:12px;font-weight:600;
}
/* на телефоне кнопки шапки иначе встают в три строки и отжимают список за
   нижний край экрана — счётчик уводим на свою строку, кнопки делим пополам */
@media (max-width:520px){
  .alerts-count{flex-basis:100%;margin-right:0;order:-1}
  .alerts-toolbar .btn{flex:1;min-width:0;padding-left:8px;padding-right:8px}
}

/* ===== Таблица пользователей (см. #view-users) =====
   Таблица шире экрана телефона; крутим её внутри обёртки, а не всю страницу —
   иначе горизонтально уезжает шапка и заголовок экрана. */
.table-scroll{overflow-x:auto;-webkit-overflow-scrolling:touch;border-radius:var(--radius)}
/* Строки поджаты: это таблица, в которую СМОТРЯТ списком — чем больше людей
   видно разом, тем меньше прокрутки. Читаемость держит не кегль, а колонки. */
.data-table{width:100%;border-collapse:collapse;font-size:13px;background:var(--card)}
.data-table th,.data-table td{padding:6px 10px;text-align:left;white-space:normal}
.data-table thead th{
  position:sticky;top:0;z-index:1;background:var(--bg2);color:var(--mut);
  font-weight:600;font-size:13px;cursor:pointer;user-select:none;
  border-bottom:var(--bw) solid var(--line);white-space:nowrap;
}
.data-table thead th:hover{color:var(--txt)}
.data-table thead th.sorted{color:var(--txt)}
/* стрелка направления: рисуем только у активной колонки, чтобы шапка не рябила */
.data-table thead th.sorted::after{content:"↓";margin-left:6px;font-size:11px}
.data-table thead th.sorted[data-dir="asc"]::after{content:"↑"}
.data-table tbody tr{border-top:var(--bw) solid var(--line);cursor:pointer}
.data-table tbody tr:hover{background:var(--bg2)}
.data-table .num{text-align:right}
.data-table .cell-nowrap{white-space:nowrap}
.data-table .cell-name{font-weight:600;color:var(--txt)}
/* Имя в «Активность логи» ведёт в профиль игрока. Пунктирное подчёркивание,
   а не сплошное: строк в таблице сотни, и сплошная линия под каждым именем
   превратила бы колонку в частокол. */
.log-name-btn{
  padding:0;border:0;background:none;font:inherit;color:inherit;cursor:pointer;
  text-align:left;text-decoration:underline dotted;text-underline-offset:3px;
  text-decoration-color:rgb(var(--lift) / .35);
}
@media(hover:hover){ .log-name-btn:hover{color:var(--acc-l);text-decoration-color:currentColor} }
/* время рядом с датой — приглушённое: дата отвечает на «когда», время нужно
   реже и не должно спорить с ней за внимание */
.data-table .cell-time{color:var(--mut)}
/* На телефоне таблицы (пользователи, активность логи) ужимаем: восемь колонок
   всё равно не влезают в 430pt, зато в горизонтальную прокрутку помещается
   заметно больше строк и колонок сразу. */
@media(max-width:560px){
  .data-table{font-size:12px}
  .data-table th,.data-table td{padding:6px 8px}
  .data-table thead th{font-size:11px}
  .data-table .log-action{font-size:10px;padding:1px 7px}
  .data-table .cell-name{font-size:12px}
}
.pwa-flag{font-weight:600}
.pwa-flag.yes{color:var(--goal)}
.pwa-flag.no{color:var(--mut)}

/* фильтр ролей кнопками-переключателями (несколько сразу) */
/* Строка поиска на экране пользователей: поле пониже обычного (это не форма
   ввода данных, а фильтр — его перечитывают глазами, а не заполняют) и кнопка
   «Добавить» рядом, а не отдельным этажом. */
.users-search-row{display:flex;gap:8px;align-items:center;margin-bottom:12px}
.users-search-row input{flex:1;min-width:0;padding:7px 11px;font-size:14px}
.users-search-row .btn{flex:0 0 auto;white-space:nowrap}

.role-filters{display:flex;gap:6px;flex-wrap:wrap;margin-bottom:12px}
/* Чипы клубов на экране статистики отделяем от карточки с периодом сверху:
   без отступа они прилипали к её нижней кромке и читались как часть блока с
   календарём, хотя относятся к тому, что НИЖЕ — к итогам и графикам.
   Правило точечное, а не в .role-filters: те же чипы стоят на других экранах,
   где соседи другие и лишний отступ им не нужен. */
#statsClubFilter{margin-top:18px}
/* Цвет чипа — цвет самой роли (тот же, что у плашки в таблице), а не общий
   акцент: так фильтр и колонка «Тип» читаются как одно и то же. Приём тот же,
   что у фильтра действий в логах: выключённый приглушён, включённый в полную
   силу и с внутренней обводкой. */
.role-chip{
  padding:4px 11px;border-radius:var(--r-pill);cursor:pointer;font-size:12px;
  border:var(--bw) solid currentColor;background:transparent;
  color:var(--role-color,var(--mut));opacity:.45;
  transition:opacity .12s,box-shadow .12s;
}
.role-chip:hover{opacity:.8}
.role-chip.on{opacity:1;box-shadow:0 0 0 1px currentColor inset}

/* Фильтр действий в «Активность логи». Цвет берётся от .log-action--* — тех же
   классов, которыми покрашены плашки в таблице ниже, так что кнопка выглядит
   ровно как то, что она отбирает. Размер меньше обычных чипов: действий восемь,
   в две строки они занимали пол-экрана, а тап по 26 точкам пальцем берётся
   уверенно. Невыбранное приглушено, выбранное — в полную силу и с обводкой:
   иначе восемь цветных кнопок читаются как «всё уже включено». */
.action-chip{
  font-size:12px;padding:5px 11px;cursor:pointer;opacity:.45;
  transition:opacity .12s,box-shadow .12s;
}
.action-chip:hover{opacity:.8}
.action-chip.on{opacity:1;box-shadow:0 0 0 1px currentColor inset}

/* ===== Фон экрана входа на планшете (см. #view-auth, resetAuthScreen) =====
   Тот же ролик и те же параметры, что на главной os.bilpro.ru: object-fit:cover
   на весь вьюпорт, блюр и тёмный градиент сверху вниз для читаемости карточки
   с QR. Показывается только с классом .with-bg — на телефоне гостя фон не нужен.

   position:fixed, а не absolute: .view — обычный блок в потоке, и абсолютный
   фон растянулся бы по высоте КАРТОЧКИ, а не экрана. z-index:-1 держит ролик
   над фоном страницы (он рисуется на канве и всегда ниже), но под контентом. */
.auth-bg-video,
.auth-bg-overlay{ display:none }
#view-auth.with-bg .auth-bg-video{
  display:block;position:fixed;inset:0;width:100%;height:100%;
  object-fit:cover;z-index:-1;
  /* scale(1.05) — компенсация блюра: blur() размывает и края самого элемента,
     без запаса по периметру экрана виден светлый ободок. Ролик снят вертикальным
     (720×1080), на планшете 1280×800 он сильно кадрируется и растягивается —
     под блюром это незаметно, а резкости тут и не требуется. */
  filter:blur(calc(5px * var(--blur-k)));
  transform:scale(1.05);
}
#view-auth.with-bg .auth-bg-overlay{
  display:block;position:fixed;inset:0;z-index:-1;
  background:linear-gradient(180deg,rgba(15,23,20,.35) 0%,rgba(15,23,20,.8) 100%);
}
/* Планшет у стола висит на стене, и его видно из зала: логотип с карточкой
   QR должны стоять по центру экрана, а не подпирать верхнюю кромку. Без
   этого на 1280×800 под карточкой оставалась пустой почти треть высоты
   (.view — flex-колонка на всю высоту #main, но с выравниванием по верху).
   Центрируем ТОЛЬКО .with-bg: на телефоне гостя, который открыл ссылку из
   QR, снизу вылезает клавиатура, вьюпорт скачком укорачивается — и
   центрированный блок прыгал бы вместе с ней прямо под пальцами. */
#view-auth.with-bg{justify-content:center}
#view-auth.with-bg .brand{margin-top:0}
/* Экран с кодом остаётся лаконичным: карточка по центру, код в середине, под
   ним одна строка и кнопка. Читают его стоя, с полутора метров, поэтому кегль
   крупнее обычного интерфейсного. */
.qr-intro-btn{margin-top:14px;padding:9px 18px;font-size:15px;align-self:center}
/* Рассказ о системе — ОКНО поверх кода, а не продолжение карточки: разворот
   внутри неё не помещался в 800 точек планшета, и за это приходилось платить
   то прокруткой стенда, то спрятанной шапкой. У листа своя высота с прокруткой
   ВНУТРИ: на планшете текст влезает целиком, а на телефоне в ландшафте
   прокрутится сам лист, не утягивая за собой страницу. */
.qr-intro-modal{
  position:fixed;inset:0;z-index:70;background:rgb(var(--scrim) / .86);
  display:flex;align-items:center;justify-content:center;padding:20px;
  backdrop-filter:blur(calc(4px * var(--blur-k)));-webkit-backdrop-filter:blur(calc(4px * var(--blur-k)));
}
.qr-intro-sheet{
  position:relative;width:min(560px,100%);max-height:100%;overflow-y:auto;
  box-sizing:border-box;padding:26px 26px 22px;border-radius:var(--radius);
  background:linear-gradient(165deg,var(--sheet-hi),var(--sheet-lo));
  border:var(--bw) solid rgb(var(--lift) / .16);box-shadow:0 calc(24px * var(--sh-k)) calc(60px * var(--sh-k)) rgb(var(--shadow) / .6);
}
.qr-intro-close{
  position:absolute;top:12px;right:12px;width:36px;height:36px;border-radius:calc(10px * var(--r-k));
  font-size:15px;cursor:pointer;background:rgb(var(--lift) / .12);
  border:var(--bw) solid rgb(var(--lift) / .25);color:var(--on-glass);transition:transform .12s,background .12s;
}
.qr-intro-close:active{transform:scale(.94)}
@media(hover:hover){ .qr-intro-close:hover{background:rgb(var(--lift) / .22)} }
.qr-intro-more{text-align:left}
.qr-intro-more p{margin:0 0 10px;font-size:15px;line-height:1.45}
/* отступ справа у заголовка — чтобы он не заезжал под крестик */
.qr-intro-title{font-size:20px;line-height:1.25;margin:0 0 14px !important;padding-right:40px;text-align:center;font-weight:700}
.qr-intro-fine{font-size:13px;color:var(--mut);margin-top:12px !important}
@media (max-width:560px){
  .qr-intro-sheet{padding:22px 18px 18px}
  .qr-intro-title{font-size:17px}
  .qr-intro-more p{font-size:14px}
}

/* ===== Ролик поверх записи (см. openClipOverlay) =====
   Размеры внутренней обёртки ставит layoutClipOverlay: вертикальный файл
   разворачивается в горизонт, и ширина с высотой при этом меняются местами. */
/* Во весь экран везде. Внутри коробки видео наложение жить не может по
   простой причине: ролики открываются и с отдельного экрана «Ролики» (из
   архива), где никакой записи на экране нет вовсе. Заодно на компьютере
   ролик получается шире, чем был бы в области записи. */
.clip-overlay{position:fixed;inset:0;z-index:60;background:rgba(4,8,7,.96);overflow:hidden}
.clip-overlay-inner{position:absolute;left:50%;top:50%;transform-origin:center center}
.clip-overlay-inner video{display:block;width:100%;height:100%;background:#000}
.clip-overlay-close{
  position:absolute;top:10px;right:10px;z-index:2;width:36px;height:36px;border-radius:50%;
  background:rgba(0,0,0,.55);border:var(--bw) solid rgba(255,255,255,.25);color:#fff;
  font-size:16px;line-height:1;cursor:pointer;
}
.clip-overlay-close:hover{background:rgba(0,0,0,.8)}
.clip-overlay-bar{
  position:absolute;left:0;right:0;bottom:0;z-index:2;
  display:flex;align-items:center;gap:12px;padding:10px 14px;
  background:linear-gradient(rgba(0,0,0,0),rgba(0,0,0,.65));
}
.clip-overlay-bar .live-scrub{flex:1;margin:0}
.clip-overlay-bar .live-pause{flex:0 0 auto;min-width:46px}
.clip-overlay-bar .live-scrub-time{flex:0 0 auto}

/* Кнопки ролика прямо в строке маркера (см. renderMarkerList): плёнка —
   смотреть, стрелка — скачать, карандаш — править. Отдельного списка роликов
   нет: он дублировал те же метки с тем же временем в соседнем блоке. */
.clip-btn{
  flex:0 0 auto;background:transparent;border:var(--bw) solid rgba(255,255,255,.18);
  color:var(--mut);border-radius:calc(8px * var(--r-k));padding:1px 6px;font-size:13px;line-height:1.5;cursor:pointer;
}
.clip-btn:hover{color:var(--txt);border-color:rgba(255,255,255,.36)}
/* строка с роликом чуть заметнее — видно, у каких меток он есть */
.marker-row.has-clip .marker-row-label{color:var(--txt)}

/* ===== Редактор ролика (см. openClipEdit в app.js) =====
   Коробка держит пропорции БУДУЩЕГО ролика, а не исходного кадра: человек
   правит вертикальный клип, и показывать ему широкий кадр — врать про
   результат. Пропорции и размеры внутренней обёртки ставит layoutClipEdit,
   потому что при повороте на 90° ширина и высота меняются местами. */
/* размеры коробки ставит layoutClipEdit — они зависят от поворота */
.clipedit-box{
  position:relative;margin:0 auto 12px;
  background:#000;border-radius:calc(12px * var(--r-k));overflow:hidden;
}
.clipedit-inner{
  position:absolute;left:50%;top:50%;transform:translate(-50%,-50%);
  transform-origin:center center;
}
.clipedit-inner video{width:100%;height:100%;object-fit:contain;display:block}
.clipedit-inner .traj-canvas{position:absolute;inset:0;width:100%;height:100%;pointer-events:none}

/* Полоса выбора. Ручки крупные (44px по вертикали) — их таскают пальцем на
   телефоне, а промах стоит пересборки ролика. */
.cliprange{
  position:relative;height:44px;margin:0 6px;
  background:linear-gradient(rgba(255,255,255,.10),rgba(255,255,255,.10)) center/100% 10px no-repeat;
  border-radius:calc(8px * var(--r-k));touch-action:none;cursor:pointer;
}
.cliprange-sel{
  position:absolute;top:50%;height:10px;transform:translateY(-50%);
  background:var(--acc);opacity:.55;border-radius:calc(3px * var(--r-k));pointer-events:none;
}
.cliprange-pos{
  position:absolute;top:6px;bottom:6px;width:2px;background:#fff;
  border-radius:calc(2px * var(--r-k));pointer-events:none;opacity:.9;
}
.cliprange-h{
  position:absolute;top:0;bottom:0;width:22px;margin-left:-11px;
  display:flex;align-items:center;justify-content:center;
}
.cliprange-h::after{
  content:'';width:10px;height:30px;border-radius:calc(5px * var(--r-k));
  background:var(--acc);box-shadow:0 calc(1px * var(--sh-k)) calc(6px * var(--sh-k)) rgba(0,0,0,.5);
}
.cliprange-info{
  display:flex;justify-content:space-between;align-items:baseline;
  margin:6px 8px 12px;color:var(--mut);font-size:13px;font-variant-numeric:tabular-nums;
}
.cliprange-info b{color:var(--txt);font-size:15px}
.clipedit-btns{display:flex;flex-wrap:wrap;gap:8px;margin-bottom:10px}

/* ===== Графики на экране статистики (см. renderStatsChartsAll) ===== */
.chart{
  background:var(--card);border:var(--bw) solid var(--line);border-radius:var(--radius);
  padding:14px;margin-bottom:14px;
}
.chart-head{display:flex;align-items:baseline;justify-content:space-between;gap:10px;flex-wrap:wrap;margin-bottom:10px}
.chart-title{font-weight:600;font-size:14px}
.chart-max{color:var(--mut);font-size:12px}
/* показание под курсором/пальцем; в покое здесь максимум за день */
.chart-readout{color:var(--mut);font-size:12px;white-space:nowrap}
.chart-readout.on{color:var(--txt);font-weight:600}
.chart-body{display:flex;gap:8px;align-items:flex-start}
/* Вертикальная ось. Подписи — обычный HTML поверх плоскости, а не <text> в
   SVG: preserveAspectRatio="none" растягивает содержимое по горизонтали, и
   любые цифры внутри SVG размазало бы вместе со столбцами. */
.chart-yaxis{position:relative;width:42px;height:150px;flex-shrink:0}
.chart-yaxis span{position:absolute;right:0;transform:translateY(-50%);color:var(--mut);font-size:10px;white-space:nowrap}
.chart-plot{position:relative;flex:1;min-width:0;touch-action:pan-y}
/* height фиксированный, ширина резиновая: preserveAspectRatio="none" тянет
   столбцы по горизонтали, не искажая их высоту относительно шкалы */
.chart-svg{display:block;width:100%;height:150px}
/* подсветка выбранного дня — накладка на всю высоту, а не подкраска столбца:
   в нулевой день красить было бы нечего, а показание всё равно нужно */
.chart-cursor{position:absolute;top:0;bottom:0;background:rgb(var(--lift) / .14);
  border-left:var(--bw) solid rgb(var(--lift) / .3);border-right:var(--bw) solid rgb(var(--lift) / .3);
  pointer-events:none;border-radius:calc(2px * var(--r-k))}
.chart-cursor.hidden{display:none}
.chart-axis{display:flex;justify-content:space-between;color:var(--mut);font-size:11px;margin-top:6px}
/* подписи дат должны стоять под плоскостью, а не под всей карточкой —
   иначе ось смещена на ширину вертикальной шкалы */
.chart-body + .chart-axis{margin-left:50px}

/* Плитки итогов на экране статистики: всегда по две в ряд. Общий
   .sysadmin-grid схлопывается в одну колонку под 560px, и четыре плитки
   занимали на телефоне целый экран, оттесняя графики вниз. */
.sysadmin-grid.stats-tiles{grid-template-columns:1fr 1fr}
@media(max-width:560px){
  .sysadmin-grid.stats-tiles{gap:8px}
  .stats-tiles .rec-item{padding:10px 12px;gap:4px}
  .stats-tiles .rec-name{font-size:12px;font-weight:500;color:var(--mut);white-space:normal}
  .stats-tiles .rec-meta{font-size:15px;font-weight:700;color:var(--txt);margin-top:0}
}
/* ===== «Место»: хранилище в облаке и диски клубов (см. renderStorage) ===== */
.store-head{display:flex;align-items:center;justify-content:space-between;gap:10px;flex-wrap:wrap;margin-bottom:10px}
.store-sub-h{font-size:15px;font-weight:600;margin:18px 0 8px;color:var(--mut)}
/* строка-вывод над цифрами: главное здесь не число, а «удаляется или нет» */
.store-note{font-size:13px;line-height:1.4;padding:9px 12px;border-radius:calc(12px * var(--r-k));margin-bottom:8px;
  background:var(--bg2);border:var(--bw) solid var(--line);color:var(--mut)}
.store-note.ok{background:rgba(46,194,122,.12);border-color:rgba(46,194,122,.35);color:var(--ok-t)}
.store-note.warn{background:rgba(215,170,40,.14);border-color:rgba(215,170,40,.4);color:var(--warn-t)}
/* Пояснение мелким шрифтом — и под цифрой плитки, и под полем ввода объёма.
   Второй селектор не случаен: плитки статистики поднимают всем .rec-meta вес
   и размер (это же там сама цифра), и одиночный .store-sub проигрывал бы им
   по специфичности — пояснение выходило таким же жирным, как число. */
.store-sub{font-size:11px;line-height:1.35;color:var(--mut);font-weight:400;margin:2px 0 0}
.rec-meta.store-sub,
.stats-tiles .rec-meta.store-sub{font-size:11px;line-height:1.35;color:var(--mut);font-weight:400;margin-top:2px}
/* ввод оплаченного объёма: поле и кнопка в строку, пояснение под ними */
.store-plan{
  display:flex;align-items:flex-end;gap:10px;flex-wrap:wrap;
  background:var(--bg2);border:var(--bw) solid var(--line);border-radius:calc(12px * var(--r-k));padding:12px;margin-bottom:10px;
}
.store-plan label{flex:1 1 160px;min-width:0;font-size:12px;color:var(--mut);gap:4px}
.store-plan input{width:100%;min-width:0;padding:9px 10px;font-size:16px}
.store-plan .store-sub,.store-plan .form-msg{flex:1 1 100%;margin:0}
/* полоска занятости диска: краснеет заранее, а не когда уже поздно */
.store-bar{height:6px;border-radius:var(--r-pill);background:rgb(var(--lift) / .1);overflow:hidden;margin:6px 0 2px}
.store-bar-fill{display:block;height:100%;border-radius:var(--r-pill);background:var(--goal)}
.store-bar-fill.warn{background:#d7aa28}
.store-bar-fill.crit{background:var(--danger)}

/* ===== Пользователи: заведение аккаунта и окно ролей (сисадмин) ===== */
.users-actions{display:flex;gap:8px;flex-wrap:wrap;margin-bottom:12px}
/* Выбранный чип роли — в её собственном цвете, а не в общем акцентном: в
   фильтре и в списке ролей это должен быть один и тот же цвет, иначе выбор
   роли выглядит как выбор чего-то другого. */
.role-chip[style*="--role-color"].on{
  background:var(--role-color);
  border-color:var(--role-color);
  color:var(--ink);
}
/* Роли — вкладка экрана пользователей, не всплывающее окно: на телефоне
   прокрутка внутри модала уводила страницу под ним. Прокрутка тут общая
   с экраном, ограничивать высоту нечем и незачем. */
.roles-actions{display:flex;gap:10px;flex-wrap:wrap;justify-content:flex-end;margin-top:14px}
.roles-actions .btn{flex:1 1 auto;min-width:140px}
/* .crop-box центрирует детей по горизонтали — он рассчитан на круглый кроппер,
   которому ширина не нужна. Строкам полей, списку прав и шапке со списком
   ролей ширину задаём явно, иначе они схлопываются по содержимому и съезжают
   к середине окна. */
.user-edit-box .sysadmin-fields,.roles-head,.perm-list{width:100%}
/* Кнопок в карточке пользователя стало пять (Профиль, Архив игр, Удалить,
   Отмена, Сохранить) — в одну строку на телефоне они не влезали и уезжали за
   правый край. Здесь ряд переносится; у кроппера с его двумя кнопками
   ограничение по ширине остаётся прежним. */
.user-edit-box .crop-actions{max-width:none;flex-wrap:wrap;justify-content:center}
.user-edit-box .crop-actions .btn{flex:1 1 auto;min-width:104px}
.roles-head{display:flex;align-items:flex-start;justify-content:space-between;gap:10px;flex-wrap:wrap;margin-bottom:12px}
.roles-head .role-filters{margin-bottom:0;flex:1;min-width:0}
/* поле цвета — квадратик рядом с названием, а не поле во всю ширину */
.role-color-label{flex:0 0 auto}
.role-color-label input[type=color]{width:52px;height:40px;padding:2px;background:transparent;border:var(--bw) solid var(--line);border-radius:calc(10px * var(--r-k));cursor:pointer}
/* права: флажок слева, название и пояснение в две строки справа */
.perm-list{display:flex;flex-direction:column;gap:2px;margin-top:12px}
.perm-row{display:flex;align-items:flex-start;gap:10px;padding:8px 10px;border-radius:calc(10px * var(--r-k));cursor:pointer;flex-direction:row}
.perm-row:hover{background:rgb(var(--lift) / .05)}
.perm-row input{width:18px;height:18px;margin-top:2px;flex-shrink:0;accent-color:var(--acc)}
.perm-text{display:flex;flex-direction:column;gap:2px;min-width:0}
.perm-text b{font-size:14px;font-weight:600}
.perm-text small{font-size:12px;color:var(--mut);line-height:1.35}

/* ===== Пульт счёта на телефоне (см. #view-remote) =====
   Тот же рэк и те же круглые «+/−», что у стола, но без видео и в один
   столбец: экран телефона узкий, а нажимать надо не глядя, одной рукой. */
.remote-score{
  text-align:center;font-size:34px;font-weight:800;letter-spacing:1px;
  padding:10px 0;color:var(--on-glass);
}
.remote-panels{display:flex;gap:10px}
.remote-panels .pp-player{flex:1;min-width:0}
.remote-panels .pp-btns{justify-content:center}
/* кнопки крупнее, чем у стола: телефон в руке, попадать надо большим пальцем */
.remote-panels .pp-round{width:64px;height:64px;font-size:30px}
.remote-marks{display:flex;gap:8px;margin-top:14px}
.remote-marks .btn{flex:1;padding:14px 6px}

/* ===================== НОВАЯ РАСКЛАДКА (data-ui="next") =====================
   Из редизайна Claude Design (см. .design-sync/handoff/redesign-2026-08-21.css).
   Живёт ОТДЕЛЬНЫМ блоком в конце файла и целиком под флагом: без него ни одно
   правило не действует, поэтому недоделанную раскладку можно спокойно катить в
   прод. Флаг ставится атрибутом на <html> — тем же приёмом, что и темы (см.
   ранний скрипт в index.html и раздел «Интерфейс» в панели сисадмина).

   Здесь только ЭКРАН ЗАПИСИ. Остальные экраны добавятся такими же блоками.

   Габариты, которые считает JS (ширина видео из остатка), правилами не
   трогаем: размер кнопок панели меток задаётся из app.js через --mk-size,
   иначе подгонка перебила бы его инлайном (см. mkSizeDefault). */

/* Пальцевые размеры. Значения дизайн-системы рассчитаны на мышь, а у стола по
   этим кнопкам попадают пальцем, в полутьме и не глядя. */
[data-ui="next"]{--ctrl-btn-h:80px}
[data-ui="next"] .mk-col{--mk-size:80px}
[data-ui="next"] .preset-chip{font-size:16px;font-weight:700;padding:12px 20px}
[data-ui="next"] .live-golive,
[data-ui="next"] .live-pause{font-size:16px;padding:13px 20px;border-radius:calc(18px * var(--r-k))}
[data-ui="next"] .live-golive .golive-dot{width:10px;height:10px}
[data-ui="next"] .rec-controls .btn{font-size:22px}
/* подпись в одну строку: в тесном ряду две широкие кнопки переносили текст, и
   значок с подписью разъезжались по высоте */
[data-ui="next"] .rec-controls .btn-label{white-space:nowrap}

/* Счёт под кадром — крупно. Он главный на экране: его читают через стол, а не
   с расстояния вытянутой руки. */
[data-ui="next"] .score-line{font-size:52px}
[data-ui="next"] .scoreboard-pill{padding:12px 34px}

/* Имя и фамилия в две строки. word-spacing разводит их по строкам даже у
   короткой пары вроде «Пётр Ким», а min-height держит высоту панели
   одинаковой независимо от того, одно слово в имени или два — иначе панели
   игроков разъезжаются по высоте между собой. */
[data-ui="next"] .pp-player-name{
  white-space:normal;word-spacing:100vw;min-height:2.6em;line-height:1.3;
  /* Длинную фамилию НЕ переносим по слогам, а обрезаем многоточием на второй
     строке. Перенос внутри слова давал «Володимир/ович-/Заковырис/тый» — четыре
     строки в 117 px, панель раздувалась по высоте, а прочитать это всё равно
     нельзя. Такие фамилии редки, и «Константин Володимирович-…» опознаётся не
     хуже. Две строки — ровно имя и фамилия, ни больше.
     overflow-wrap:anywhere оставлен для случая, когда НЕРАЗРЫВНОЕ слово шире
     панели: без него оно вылезло бы за карточку прежде, чем сработает обрезка. */
  display:-webkit-box;-webkit-box-orient:vertical;-webkit-line-clamp:2;
  overflow:hidden;overflow-wrap:anywhere;max-width:100%;
}
/* Ширина панели задана явно, и это не косметика. Подгонка видео меряет
   ЕСТЕСТВЕННУЮ ширину панелей и вычитает её из строки; при двухстрочных именах
   панель разъедалась до 206 px, видео ужималось, и кнопкам меток оставалось 56
   вместо задуманных 80 — проверено на стенде при 1280×800. Со 168 px в ряд
   помещаются и пальцевые кнопки, и видео шире прежнего. Пустые поля по краям
   панелям всё равно достанутся: JS раздаёт им остаток ПОСЛЕ расчёта видео.
   Содержимое центрируем по высоте — панель тянется по кадру, и прижатый к
   верху столбик выглядел бы обрезанным. */
[data-ui="next"] .pp-player{
  width:168px;justify-content:center;
  /* min-width:0 обязателен. У флекс-элемента минимальная ширина по умолчанию
     равна ширине самого длинного НЕРАЗРЫВНОГО слова, поэтому фамилия
     «Володимирович-Заковыристый» распирала панель до 206 px, несмотря на
     width:168 — поймано на стенде, состояние «длинные имена». */
  min-width:0;
}


/* ===== Главный экран, новая раскладка (макет 01, «две колонки по центру») =====

   Вне флага обёртки прозрачны, а новые блоки не существуют для раскладки —
   старый главный остаётся ровно таким, каким был. */
.home-cols,.home-left{display:contents}
#homeStats,#homeTable,#homeRecent{display:none}

/* Две колонки. Пропорции макета (1.3fr/1fr) сохранены, а вот max-width 1560
   пришлось снять: на реальном планшете окно 1280 CSS-точек (макет рисовался
   на 1920 физических), ограничивать там нечего. */
/* Главный экран перестаёт быть колонкой в 680 точек: две колонки макета в неё
   не помещаются — правая обрезала строки записей. Ширину берём всю, как в
   макете, отступ по краям тот же, что у .view. Остальные экраны остаются
   узкими: текст, набранный во всю ширину планшета, читать невозможно. */
[data-ui="next"] #view-menu{max-width:none;padding:18px 24px}
[data-ui="next"] .home-cols{
  display:grid;grid-template-columns:1.3fr 1fr;gap:22px;align-items:start;
}
[data-ui="next"] .home-left{display:grid;align-content:start;gap:22px;min-width:0}
[data-ui="next"] .home-right{min-width:0}
[data-ui="next"] #homeStats,[data-ui="next"] #homeRecent{display:block}
[data-ui="next"] #homeTable:not(.hidden){display:flex}

/* Плитки — квадратной сеткой 2×2 вместо прежней ленты во всю ширину.
   Высота 150, а не 190 из макета: на 800 точках высоты два ряда по 190 вместе
   со статистикой не помещаются, а прокрутка на планшете у стола — худшее из
   зол (руки заняты кием). */
[data-ui="next"] .menu-grid{gap:14px}
[data-ui="next"] .menu-grid .big-card{min-height:150px;justify-content:center}
/* span-2 отменяем: в макете все четыре плитки одного размера. Панели админа и
   сисадмина — исключение, они остаются полосой во всю ширину. */
[data-ui="next"] .menu-grid .big-card.span-2:not(#adminCard):not(#sysadminHubCard){grid-column:auto}

/* Карточка-подложка — тот же стеклянный материал, что у панелей игрока на
   экране записи, чтобы главный и запись не выглядели из разных приложений. */
.home-card{
  box-sizing:border-box;padding:18px 20px;border-radius:calc(18px * var(--r-k));
  background:linear-gradient(165deg,rgb(var(--lift) / .10),rgb(var(--sink) / .45));
  border:var(--bw) solid rgb(var(--lift) / .16);
}
.home-card-head{display:flex;align-items:center;justify-content:space-between;gap:12px;margin-bottom:14px}
.home-card-title{font-size:13px;letter-spacing:.06em;text-transform:uppercase;color:var(--mut)}

.home-stat-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:10px}
.home-stat{
  display:flex;flex-direction:column;align-items:center;gap:4px;padding:12px 6px;
  border-radius:calc(14px * var(--r-k));background:rgb(var(--lift) / .06);
  border:var(--bw) solid rgb(var(--lift) / .10);
}
.home-stat-v{font-size:26px;font-weight:800;line-height:1;font-variant-numeric:tabular-nums}
.home-stat-l{font-size:12px;color:var(--mut);text-align:center}

/* Полоса оплаты стола (BillTime, пока скрыта) */
.home-table{align-items:center;gap:14px}
.home-table-ico{
  width:42px;height:42px;border-radius:calc(14px * var(--r-k));flex-shrink:0;
  display:flex;align-items:center;justify-content:center;font-size:20px;
  background:rgb(var(--lift) / .08);border:var(--bw) solid rgb(var(--lift) / .14);
}
.home-table-txt{flex:1;min-width:0}
.home-table-t{font-weight:600;font-size:16px}
.home-table-s{font-size:14px;color:var(--mut);margin-top:3px}

/* Список последних записей. Прокрутка ВНУТРИ карточки, а не всей страницей:
   колонка ровно по высоте экрана, и плитки слева от неё не уезжают. */
[data-ui="next"] #homeRecent{display:flex;flex-direction:column;max-height:100%}
.home-recent-list{display:grid;align-content:start;gap:10px;overflow-y:auto;min-height:0}
.home-recent-empty{color:var(--mut);font-size:14px;padding:6px 2px}
.home-rec{
  display:flex;align-items:center;gap:12px;padding:10px 12px;cursor:pointer;
  border-radius:calc(14px * var(--r-k));background:rgb(var(--lift) / .05);
  border:var(--bw) solid rgb(var(--lift) / .10);
}
@media (hover:hover){ .home-rec:hover{background:rgb(var(--lift) / .09)} }
.home-rec:active{transform:scale(.99)}
.home-rec-thumb{
  width:44px;height:44px;border-radius:calc(12px * var(--r-k));flex-shrink:0;
  display:flex;align-items:center;justify-content:center;font-size:18px;color:var(--mut);
  background:rgb(var(--sink) / .5);border:var(--bw) solid rgb(var(--lift) / .12);
}
.home-rec-txt{flex:1;min-width:0}
.home-rec-t{font-weight:700;font-size:15px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.home-rec-m{font-size:12px;color:var(--mut);margin-top:2px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}

/* На телефоне колонок нет — всё в одну, как и остальные экраны. */
@media (max-width:820px){
  [data-ui="next"] .home-cols{grid-template-columns:1fr}
  [data-ui="next"] .menu-grid .big-card{min-height:120px}
}

/* Знак приложения в шапке главного (макет 01): квадрат 30×30 со скруглением
   10 и градиентом акцента, дальше имя, палочка и место — клуб со столом. */
.home-brand{display:none;align-items:center;gap:10px;min-width:0}
[data-ui="next"] .home-brand:not(.hidden){display:flex}
.home-brand-sq{
  width:30px;height:30px;border-radius:10px;flex-shrink:0;
  background:linear-gradient(150deg,var(--acc-l),var(--acc-d));box-shadow:var(--glow);
}
.home-brand-name{font-weight:700;font-size:17px;letter-spacing:.2px}
.home-brand-sep{width:1px;height:22px;background:rgb(var(--lift) / .14);flex-shrink:0}
.home-brand-place{font-size:14px;color:var(--mut);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
/* Заголовок «Главное меню» при этом убираем: в макете его нет, а рядом со
   знаком и названием клуба он бы только повторял и без того очевидное.
   Заодно знак распирает полосу вправо: без этого колокольчик, «?» и профиль
   липли к слову bilproOS, вместо того чтобы стоять у правого края. */
[data-ui="next"] #topbar:has(.home-brand:not(.hidden)) #title{display:none}
[data-ui="next"] .home-brand:not(.hidden){margin-right:auto}

/* Чип баланса переехал в верхнюю полосу (см. applyNextTopbar в app.js) —
   подгоняем его под соседей: высота как у круглых кнопок, скругление тоже. */
[data-ui="next"] #topbar .wallet-chip{height:42px;padding:0 14px;border-radius:var(--r-pill)}
/* Прежняя строка над карточками осталась бы пустой полосой с одной кнопкой
   «Пополнить» — убираем её целиком. */
[data-ui="next"] .menu-topbar{display:none}
@media (max-width:600px){
  .home-brand-sep,.home-brand-place{display:none}
}


/* ===== Экран просмотра, новая раскладка (макет 03) ===== */

/* Шапка записи над панелью: слева состав со счётом и мелкая мета, справа —
   действия. Сама .player-head создаётся в applyNextPlayer (app.js) и вне
   флага не существует, поэтому правила ниже писать под флагом не нужно. */
.player-head{
  display:flex;align-items:flex-start;justify-content:space-between;gap:16px;
  margin:0 2px 14px;flex-wrap:wrap;
}
.player-head-txt{min-width:0;flex:1}
.player-head #playerTitle{
  font-size:20px;font-weight:700;margin:0;line-height:1.25;
}
/* дата, клуб и стол приходят внутри заголовка отдельным span-ом (см.
   openPlayer) — в макете это мелкая серая строка под именами */
.player-head #playerTitle .v2-sub{
  display:block;font-size:13px;font-weight:400;color:var(--mut);margin-top:4px;
}
.player-head #playerMeta{padding:4px 0 0;font-size:13px;line-height:1.5}
/* Кнопки действий — в ряд и по содержимому. Селектор с флагом обязателен:
   в теме планшета у .player-actions .btn стоит flex:1 1 100%, и без перевеса
   по силе кнопки вытягивались в три полосы во всю ширину друг под другом. */
[data-ui="next"] .player-head .player-actions{margin:0;flex:0 0 auto;gap:10px;flex-wrap:wrap;justify-content:flex-end}
[data-ui="next"] .player-head .player-actions .btn{flex:0 0 auto;width:auto;min-height:38px;padding:9px 16px;font-size:14px}

/* Правая колонка — две карточки со своими подписями, как в макете. Отдельная
   рамка вокруг каждой отделяет участников от маркеров: раньше это был один
   поток, и на длинном списке было не понять, где кончается одно и начинается
   другое. */
/* Поля колонки. Двенадцати точек по краям не хватало: строки маркеров и
   участников с их собственными рамками почти касались границ панели, а
   полоса прокрутки списка съедала и то, что оставалось справа.
   scrollbar-gutter держит это место постоянно — иначе список дёргается по
   горизонтали, стоит ему стать прокручиваемым. */
[data-ui="next"] .player-aside{gap:14px;padding:16px 18px}
[data-ui="next"] #markerList{scrollbar-gutter:stable;padding:0}
[data-ui="next"] .player-aside .v2-aside-label{
  display:flex;align-items:center;justify-content:space-between;gap:8px;
  font-size:12px;letter-spacing:.06em;text-transform:uppercase;color:var(--mut);
  padding:0 0 8px;
}
/* margin слева — для СТАРОГО вида: там подпись и число стоят в обычном потоке
   и без отступа склеивались в «Маркеры42». В новой раскладке их и так разводит
   space-between. */
.aside-count{font-size:13px;color:var(--mut);font-variant-numeric:tabular-nums;margin-left:6px}
/* Легенда цветов не нужна: в макете цвет стоит точкой в самой строке рядом с
   названием, и отдельная таблица внизу только занимала место. */
[data-ui="next"] #markerLegend{display:none}
/* Прокручивается ТОЛЬКО список маркеров, а не колонка целиком: иначе при
   сорока метках уезжали и подпись «Маркеры», и участники, и человек терял
   опору. flex:1 + min-height:0 обязательны — без них флекс-элемент вырастает
   по содержимому и никакой overflow не срабатывает. */
[data-ui="next"] .player-aside{overflow:hidden;min-height:0}
[data-ui="next"] #markerList{
  max-height:none;border-top:none;overflow-y:auto;flex:1 1 auto;min-height:0;
}
/* `html[...] body` — не украшение: тема планшета (theme-v02.css) подключается
   ПОСЛЕ этого файла и задаёт свои значения селектором с body, так что при
   равной силе выигрывала она. Из-за этого строка маркера оставалась с
   padding:7px 0, и значок с временем липли к самым краям карточки. */
html[data-ui="next"] body .marker-row{
  border-bottom:none;border-radius:calc(12px * var(--r-k));margin-bottom:6px;
  padding:11px 14px;gap:10px;
  background:rgb(var(--lift) / .05);border:var(--bw) solid rgb(var(--lift) / .10);
}
/* Название события в строке, счёт — приглушённо следом (макет 03). Пока
   названия не было, единственным признаком типа оставался цвет точки, а
   расшифровку приходилось держать отдельной таблицей под списком. */
html[data-ui="next"] body .marker-row-label{display:flex;align-items:baseline;gap:8px;font-size:13px}
html[data-ui="next"] body .marker-row-name{font-weight:600;color:var(--txt)}
html[data-ui="next"] body .marker-row-score{font-size:12px;color:var(--mut)}
html[data-ui="next"] body .marker-row-time{font-size:12px}
/* Селектор с .player-aside — чтобы перевесить тему планшета: там у ростера
   свои боковые 14 px, и участники стояли на 14 точек уже маркеров. */
[data-ui="next"] .player-aside #playerRoster{padding:0}
html[data-ui="next"] body .v2-player-row{
  display:flex;width:100%;align-items:center;gap:10px;padding:11px 14px;margin-bottom:8px;
  border-radius:calc(12px * var(--r-k));
  background:rgb(var(--lift) / .05);border:var(--bw) solid rgb(var(--lift) / .10);
}
/* «+» и «—» между участниками рассчитаны на строку; в столбик они не нужны. */
[data-ui="next"] .v2-vs{display:block}
[data-ui="next"] .v2-vs-dash,[data-ui="next"] .v2-vs-plus{display:none}

/* Команды в 2×2: свои карточки стоят плотнее друг к другу, между командами —
   «против» с линиями по бокам. Без этого четыре имени шли подряд, и счёт
   «(3)4-2(1)» не с чем было сопоставить. */
html[data-ui="next"] body .v2-team{display:flex;flex-direction:column;gap:6px}
html[data-ui="next"] body .v2-team .v2-player-row{margin-bottom:0}
html[data-ui="next"] body .v2-team-vs{
  display:flex;align-items:center;gap:10px;margin:10px 0;
  font-size:11px;letter-spacing:.06em;text-transform:uppercase;color:var(--mut);
}
html[data-ui="next"] body .v2-team-vs::before,
html[data-ui="next"] body .v2-team-vs::after{
  content:"";flex:1;height:1px;background:rgb(var(--lift) / .14);
}


/* ---- Пульт под видео (макет 03) ----

   Ряд: транспорт и режимы кучкой по центру, время прижато вправо. Ползунок
   длины хвоста уходит ОТДЕЛЬНОЙ строкой во всю ширину — в макете он подписан
   словами и тянется через весь пульт, а не жмётся плашкой между кнопками. */
[data-ui="next"] #view-player .player-scrub-row{
  justify-content:center;align-items:center;gap:10px;row-gap:12px;flex-wrap:wrap;
}
/* Длина хвоста — в ТОЙ ЖЕ строке, справа от кнопок; время уходит строкой ниже,
   тоже справа. Перенос перед временем задаём сами пустым элементом во всю
   ширину (.ctrl-break), а не «как ляжет»: без него время вставало бы в ряд
   кнопок, откуда его и просили убрать. Порядок: кнопки · хвост · перенос ·
   время. */
[data-ui="next"] #view-player .ctrl-break{display:block;order:9;flex-basis:100%;height:0;margin:0}
[data-ui="next"] #view-player .player-scrub-row .v2-time{order:11;margin-left:auto;font-size:13px}
[data-ui="next"] #view-player #playerTrajLenWrap{
  order:8;margin-left:auto;flex:0 1 auto;height:38px;padding:0 14px;
  border-radius:calc(12px * var(--r-k));background:rgb(var(--lift) / .06);
}
[data-ui="next"] #view-player #playerTrajLenWrap input[type=range]{flex:0 1 160px;width:auto;max-width:none}
[data-ui="next"] .traj-len-label{font-weight:600;color:var(--mut);flex:0 0 auto}
.traj-len-label{display:none}
[data-ui="next"] .traj-len-label{display:inline}

/* Кнопки пульта: тёмные чипы со значком и подписью, включённые — заливкой
   акцента (макет 03). Прежние пилюли были мельче и прозрачнее — они делались
   так, чтобы не спорить с эфиром на экране записи, а здесь это основной пульт
   и по ним попадают пальцем. */
[data-ui="next"] #view-player .player-scrub-row .live-pause{
  height:38px;padding:0 14px;font-size:13px;font-weight:600;
  border-radius:calc(12px * var(--r-k));
  background:rgb(var(--lift) / .07);border:var(--bw) solid rgb(var(--lift) / .12);
}
[data-ui="next"] #view-player .player-scrub-row .live-pause.is-on{
  background:var(--acc);border-color:transparent;color:var(--on-acc);
}

/* Кнопка воспроизведения — кружок со значком, без слова: в макете это
   транспорт, а не подписанная кнопка. Значок рисуем фигурой, а не глифом «▶»:
   у глифа своя метрика внутри строки, и он встаёт левее центра (тем же
   приёмом сделана кнопка в теме планшета). */
/* Транспорт — скруглённый квадрат с приглушённой заливкой акцента, как в
   макете (кружок был моей отсебятиной). */
[data-ui="next"] #view-player #playerPlayToggle{
  position:relative;width:44px;height:38px;padding:0;flex:none;
  border-radius:calc(12px * var(--r-k));
  background:color-mix(in srgb, var(--acc) 30%, transparent);
  border:var(--bw) solid color-mix(in srgb, var(--acc) 45%, transparent);
  font-size:0;color:transparent;overflow:hidden;
}
[data-ui="next"] #view-player #playerPlayToggle::before{
  content:"";position:absolute;top:50%;left:50%;width:0;height:0;
  border-style:solid;border-width:8px 0 8px 13px;
  border-color:transparent transparent transparent var(--txt);
  transform:translate(-50%,-50%) translateX(2px);
}
[data-ui="next"] #view-player #playerPlayToggle.is-playing::before{
  width:11px;height:14px;border:none;transform:translate(-50%,-50%);
  border-left:4px solid var(--txt);border-right:4px solid var(--txt);
}
