/* Браузерное [hidden] { display: none } — правило без веса, и любой класс с
   display перебивает его молча: подложка остаётся поверх видео, пульт виден
   там, где играть нечего, панель массового действия обещает действие над
   нулём строк. Три раза наступили по отдельности — дальше правило одно на всю
   разметку. */
[hidden] { display: none !important; }

/* Цвета взяты пипеткой из эскизов, а не подобраны на глаз: подложка полотна в
   шапке — #0a1524, фон страницы — #0b1625, фон карточки — #111f36. Отсюда и
   родство: полотно в шапке не выглядит вставленным в чужую страницу. */
:root {
    --bg: #0b1625;
    --bg-soft: #0e1a2d;
    --bg-card: #111f36;
    --line: #1c2d49;
    --fg: #e6edf3;
    --fg-dim: #94a3b8;
    --accent: #2f81f7;
    --accent-soft: #1f6feb33;
    --live: #f85149;
    --ok: #3fb950;
    --warn: #d29922;
    --radius: 10px;
}

* { box-sizing: border-box; }

body {
    margin: 0;
    background: var(--bg);
    color: var(--fg);
    font: 15px/1.5 system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
}

a { color: var(--accent); text-decoration: none; }
a:hover { text-decoration: underline; }

.wrap { max-width: 1180px; margin: 0 auto; padding: 20px 16px 60px; }

header.top {
    display: flex; align-items: center; gap: 16px;
    padding: 14px 16px; border-bottom: 1px solid var(--line);
    background: var(--bg-soft); position: sticky; top: 0; z-index: 20;
}
header.top .brand { font-weight: 700; letter-spacing: .5px; font-size: 17px; }
header.top .brand span { color: var(--accent); }
header.top nav { margin-left: auto; display: flex; gap: 14px; align-items: center; }

h1 { font-size: 22px; margin: 22px 0 6px; }
h2 { font-size: 17px; margin: 26px 0 10px; }
.muted { color: var(--fg-dim); }
.small { font-size: 13px; }

button, .btn {
    font: inherit; cursor: pointer;
    background: var(--accent); color: #fff; border: 0;
    padding: 9px 15px; border-radius: 8px;
}
button:hover, .btn:hover { filter: brightness(1.1); text-decoration: none; }
button:disabled { opacity: .5; cursor: not-allowed; filter: none; }
button.ghost, .btn.ghost { background: transparent; border: 1px solid var(--line); color: var(--fg); }
button.danger { background: var(--live); }
button.small { padding: 5px 10px; font-size: 13px; }

input, select, textarea {
    font: inherit; background: var(--bg); color: var(--fg);
    border: 1px solid var(--line); border-radius: 8px; padding: 9px 11px; width: 100%;
}
label { display: block; margin: 12px 0 5px; font-size: 13px; color: var(--fg-dim); }
.row { display: flex; gap: 12px; flex-wrap: wrap; }
.row > * { flex: 1 1 200px; }
.check { display: flex; align-items: center; gap: 8px; margin: 10px 0; }
.check input { width: auto; }

/* Подпись и поле в одну строку. Обычная разметка ставит подпись отдельной
   строкой сверху — для коротких подписей вроде «Точность» это лишняя строка на
   ровном месте. Поле забирает остаток ширины, min-width: 0 не даёт ему
   распирать карточку собственным содержимым. */
.field-row { display: flex; align-items: center; gap: 10px; margin: 12px 0 0; }
.field-row > label { margin: 0; flex: 0 0 auto; }
.field-row > select, .field-row > input { flex: 1 1 auto; width: auto; min-width: 0; }

.card {
    background: var(--bg-card); border: 1px solid var(--line);
    border-radius: var(--radius); padding: 16px; margin-bottom: 14px;
}

.grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); gap: 14px; }

/* Плитки способов войти. Каждая — отдельный сценарий: начать своё, прийти по
   приглашению, просто посмотреть, поставить хаб. Растягивать их до высоты
   самой длинной незачем — от этого короткие выглядят недописанными. */
.tiles { grid-template-columns: repeat(auto-fit, minmax(320px, 1fr)); align-items: start; }
.tiles .card { margin: 0; }
.tile-note { min-height: 40px; }

.badge {
    display: inline-flex; align-items: center; gap: 5px;
    font-size: 12px; padding: 2px 8px; border-radius: 20px;
    background: var(--line); color: var(--fg-dim);
}
.badge.live { background: var(--live); color: #fff; }
.badge.ok { background: #23863633; color: var(--ok); }
.badge.warn { background: #9e6a0333; color: var(--warn); }
.badge.err { background: #f8514933; color: var(--live); }
.dot { width: 7px; height: 7px; border-radius: 50%; background: currentColor; }

/* ---------- страница трансляции ---------- */

.stage { background: #000; border-radius: var(--radius); overflow: hidden; position: relative; aspect-ratio: 16/9; }
.stage video { width: 100%; height: 100%; object-fit: contain; background: #000; display: block; }
.stage .overlay {
    position: absolute; inset: 0; display: flex; align-items: center; justify-content: center;
    text-align: center; padding: 24px; color: var(--fg-dim);
    background: linear-gradient(#0b0d11cc, #0b0d11ee);
}
.stage .label {
    position: absolute; left: 12px; bottom: 12px;
    background: #000000aa; padding: 4px 10px; border-radius: 6px; font-size: 13px;
}
.stage .live-badge { position: absolute; left: 12px; top: 12px; }

/* Полоса ракурсов: главное окно сверху, все камеры меньшими квадратиками снизу. */
.angles { display: flex; gap: 10px; overflow-x: auto; padding: 12px 2px; }
.angle {
    flex: 0 0 168px; background: #000; border: 2px solid transparent;
    border-radius: 8px; overflow: hidden; cursor: pointer; position: relative;
}
.angle.active { border-color: var(--accent); }
.angle video { width: 100%; aspect-ratio: 16/9; object-fit: cover; display: block; background: #000; }
.angle .cap {
    position: absolute; left: 0; right: 0; bottom: 0;
    background: #000000bb; font-size: 12px; padding: 3px 7px;
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.angle .off {
    position: absolute; inset: 0; display: flex; align-items: center; justify-content: center;
    background: #0b0d11e6; font-size: 12px; color: var(--fg-dim); text-align: center; padding: 6px;
}
.angles-hint { font-size: 13px; color: var(--fg-dim); padding: 0 2px 8px; }

.toolbar { display: flex; gap: 10px; flex-wrap: wrap; align-items: center; margin: 14px 0; }
.toolbar .spacer { flex: 1; }

.notice { padding: 11px 14px; border-radius: 8px; margin: 12px 0; font-size: 14px; }
.notice.err { background: #f8514922; border: 1px solid #f8514955; }
.notice.warn { background: #d2992222; border: 1px solid #d2992255; }
.notice.info { background: var(--accent-soft); border: 1px solid #2f81f755; }

.requests .req {
    display: flex; align-items: center; gap: 10px; padding: 9px 0;
    border-bottom: 1px solid var(--line);
}
.requests .req:last-child { border-bottom: 0; }

table { width: 100%; border-collapse: collapse; font-size: 14px; }
th, td { text-align: left; padding: 9px 8px; border-bottom: 1px solid var(--line); vertical-align: top; }
th { color: var(--fg-dim); font-weight: 500; font-size: 13px; }
td.num { text-align: right; font-variant-numeric: tabular-nums; }

code, .mono { font-family: ui-monospace, "Cascadia Code", Consolas, monospace; font-size: 13px; }
.code-box {
    background: var(--bg); border: 1px solid var(--line); border-radius: 8px;
    padding: 12px; font-family: ui-monospace, Consolas, monospace; font-size: 15px;
    letter-spacing: 2px; text-align: center; user-select: all;
}
.copyrow { display: flex; gap: 8px; align-items: center; }
.copyrow input { font-family: ui-monospace, Consolas, monospace; font-size: 13px; }

/* Кнопка-значок: квадратная, без надписи. Слово «Копировать» рядом с полем
   занимало треть строки, а сам значок читается и без него. Отметка об успехе —
   галочка поверх значка: подменить текст, как у обычной кнопки, здесь нечем. */
button.icon { padding: 8px; line-height: 0; position: relative; flex: 0 0 auto; }
button.icon > svg { display: block; }
button.icon.copied > svg { visibility: hidden; }
button.icon.copied::after {
    content: '✓'; position: absolute; inset: 0;
    display: flex; align-items: center; justify-content: center;
    font-size: 15px; line-height: 1; color: var(--ok);
}

/* Правка на месте. Показ и поле — это одно и то же место страницы в двух
   состояниях: нажали на название — на его месте поле, ушли из поля — снова
   название. Поэтому поля здесь не обведены рамкой заранее: рамка вокруг каждой
   строки превратила бы страницу эфира в анкету, а читают её куда чаще, чем
   правят. Что строка правится, видно по курсору и подсветке под ним. */
.ed { cursor: text; border-radius: 8px; }
.ed:hover { background: var(--accent-soft); box-shadow: 0 0 0 5px var(--accent-soft); }
.ed:focus-visible { outline: 2px solid var(--accent); outline-offset: 4px; }
/* Пустое нажимать тоже надо чем-то: строка «Добавить описание» — это и есть
   то, на что нажимают, когда описания ещё нет. */
.ed-empty { color: var(--fg-dim); }
/* Высоту полю ставит код по его содержимому: одна строка меток — одна строка
   поля, четыре — четыре. Фиксированная высота на телефоне обрезала вторую
   строку, а тянуть поле руками посреди съёмки никто не станет, поэтому и
   resize здесь выключен. Полоса прокрутки появится только у самого длинного
   описания — того, которое переросло потолок в 260 пикселей. */
.ed-in { margin-top: 4px; resize: none; overflow: auto; }
.ed-in.ed-name { font-size: 22px; font-weight: 700; margin-top: 0; }
.ed-in.ed-tags { font-size: 13px; }
/* Подпись под полем — про одно сохранение: «Сохраняем…», «Сохранено», отказ.
   Пустая она не занимает строки: иначе текст под ней прыгал бы туда-сюда при
   каждой правке. */
.ed-note { margin-top: 6px; }
.ed-note:empty { display: none; }
/* Режим — такая же правимая строка, только полем ей служит список. */
.mode-row { display: flex; align-items: center; gap: 8px; margin-top: 12px; font-size: 13.5px; flex-wrap: wrap; }
.mode-row > select { width: auto; max-width: 100%; font-size: 13.5px; padding: 6px 9px; }

/* «Позвать людей»: слева QR, справа обе ссылки друг под другом. */
.share-grid { display: grid; grid-template-columns: auto minmax(240px, 1fr); gap: 18px; align-items: start; }
.share-qr { display: flex; flex-direction: column; gap: 8px; align-items: flex-start; max-width: 220px; }
.share-grid > div > label:first-child { margin-top: 0; }
@media (max-width: 700px) { .share-grid { grid-template-columns: 1fr; } }

.qr { background: #fff; padding: 10px; border-radius: 8px; display: inline-block; line-height: 0; }
.qr svg { display: block; }

.bar { height: 6px; background: var(--line); border-radius: 3px; overflow: hidden; margin-top: 4px; }
.bar > i { display: block; height: 100%; background: var(--accent); }
.bar.warn > i { background: var(--warn); }
.bar.err > i { background: var(--live); }

/* ---------- запись: плеер с перемоткой ---------- */

/* Таймлайн записи — общий для всех ракурсов: время считается от начала
   трансляции, а не от начала конкретного файла. Поэтому и полоса одна, и
   переключение камеры не сбивает позицию. */
.tl { position: relative; height: 28px; margin: 8px 0 0; cursor: pointer; touch-action: none; user-select: none; }
.tl-track {
    position: absolute; left: 0; right: 0; top: 11px; height: 7px;
    border-radius: 4px; background: var(--line); overflow: hidden;
}
.tl-buf { position: absolute; top: 0; height: 100%; background: #ffffff20; }
.tl-play { position: absolute; top: 0; left: 0; height: 100%; background: var(--accent); }
/* Пропуск — не то же самое, что «пока не загрузилось»: камеры там не было. */
.tl-gap {
    position: absolute; top: 0; height: 100%;
    background: repeating-linear-gradient(115deg, #0b1625, #0b1625 4px, #33425a 4px, #33425a 8px);
}
.tl-head {
    position: absolute; top: 6px; width: 16px; height: 16px; margin-left: -8px;
    border-radius: 50%; background: #fff; box-shadow: 0 0 0 3px #2f81f755;
}
.tl-bubble {
    position: absolute; bottom: 30px; transform: translateX(-50%);
    background: #000000dd; border: 1px solid var(--line); padding: 3px 8px;
    border-radius: 6px; font-size: 12px; white-space: nowrap; pointer-events: none;
}
.tl-marks { position: absolute; left: 0; right: 0; top: 0; height: 100%; pointer-events: none; }

.player-bar { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; margin: 10px 0; }
.player-bar .time { font-variant-numeric: tabular-nums; font-size: 13px; }
.icon-btn {
    min-width: 38px; height: 38px; padding: 0 10px;
    display: inline-flex; align-items: center; justify-content: center;
}
.seek-to { display: flex; gap: 6px; align-items: center; }
.seek-to input { width: auto; padding: 6px 9px; font-size: 13px; }

/* Ракурсы записи — строки, а не квадратики: у записи важнее не картинка, а то,
   когда эта камера вообще снимала. */
.takes { display: grid; gap: 8px; }
.take {
    display: grid; grid-template-columns: 1fr auto; gap: 4px 12px; align-items: center;
    background: var(--bg-card); border: 1px solid var(--line); border-left: 3px solid transparent;
    border-radius: 8px; padding: 9px 12px; cursor: pointer; text-align: left; color: inherit; font: inherit;
}
.take:hover { border-color: var(--accent); }
.take.active { border-left-color: var(--accent); background: #1f6feb1a; }
.take .cov { grid-column: 1 / -1; position: relative; height: 7px; border-radius: 4px; background: var(--line); overflow: hidden; }
.take .cov > i { position: absolute; top: 0; height: 100%; background: #3fb95088; }
.take .cov > b { position: absolute; top: 0; width: 2px; height: 100%; background: #fff; }
.take .nm { font-weight: 600; }
.take .when { font-size: 12px; color: var(--fg-dim); font-variant-numeric: tabular-nums; }

/* ---------- архив ---------- */

.filters { display: grid; grid-template-columns: repeat(auto-fit, minmax(160px, 1fr)); gap: 10px 14px; align-items: end; }
.filters label { margin: 0 0 4px; }
.chips { display: flex; gap: 8px; flex-wrap: wrap; margin: 12px 0 0; }
.chip {
    background: transparent; border: 1px solid var(--line); color: var(--fg-dim);
    border-radius: 20px; padding: 4px 12px; font-size: 13px; cursor: pointer;
}
.chip:hover, .chip.on { border-color: var(--accent); color: var(--fg); }
.rec-card .head { display: flex; gap: 8px; align-items: center; margin-bottom: 8px; flex-wrap: wrap; }
.rec-card .angles-line { display: flex; gap: 6px; flex-wrap: wrap; margin-top: 8px; }
.rec-card .angles-line span {
    font-size: 12px; background: var(--bg-soft); border: 1px solid var(--line);
    border-radius: 6px; padding: 2px 7px; color: var(--fg-dim);
}

/* ---------- карта мест съёмки ---------- */

.map-card { padding-bottom: 12px; }
.map-head { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; margin-bottom: 10px; }
.map-head > span.small { margin-left: auto; }

#map { width: 100%; height: auto; display: block; touch-action: pan-y; }
/* Кольца расстояний и север — служебная разметка, она не должна спорить с метками. */
.map-ring { fill: none; stroke: var(--line); stroke-width: 1; }
.map-ring-label { fill: var(--fg-dim); font-size: 11px; }
.map-axis { stroke: var(--line); stroke-width: 1; stroke-dasharray: 3 5; }
.map-me circle { fill: var(--accent); }
.map-me text { fill: var(--fg); font-size: 12px; }

/* Метка съёмки. Погасшая — место, где в это время суток никто не снимал: она
   остаётся на карте, иначе при перетаскивании ползунка точки прыгали бы. */
.pin { cursor: pointer; }
.pin circle { fill: var(--bg-soft); stroke: var(--fg-dim); stroke-width: 1.5; transition: all .12s; }
.pin text { fill: var(--fg-dim); font-size: 11px; opacity: 0; transition: opacity .12s; }
.pin.on circle { fill: var(--live); stroke: #fff; stroke-width: 2; }
.pin.on text { fill: var(--fg); opacity: 1; }
.pin:hover circle { stroke: var(--accent); }
.pin:hover text { opacity: 1; }
.pin .halo { fill: #f8514933; stroke: none; }

.map-time { display: flex; align-items: center; gap: 12px; margin-top: 6px; }
.tod { flex: 1; position: relative; padding-top: 26px; }
/* Столбики — сколько записей шло в эту получасовку. Без них ползунок надо
   возить вслепую: не видно, где вообще что-то снимали. */
.tod-bars { position: absolute; top: 0; left: 0; right: 0; height: 24px; display: flex; gap: 1px; align-items: flex-end; }
.tod-bars > i { flex: 1; background: var(--line); border-radius: 1px 1px 0 0; min-height: 2px; }
.tod-bars > i.hot { background: var(--accent); }
.tod input[type=range] { width: 100%; padding: 0; accent-color: var(--live); }
.tod-ticks { display: flex; justify-content: space-between; font-size: 11px; color: var(--fg-dim); margin-top: 2px; }
.map-time .time { font-variant-numeric: tabular-nums; font-size: 15px; min-width: 52px; text-align: right; }

@media (max-width: 640px) {
    .wrap { padding: 14px 12px 50px; }
    /* На телефоне полоса превью съедает и экран, и трафик — по умолчанию свёрнута. */
    .angles.collapsed { display: none; }
    .angle { flex-basis: 132px; }
    .take { grid-template-columns: 1fr; }
    /* Кнопка и подпись — не одно и то же: подпись уходит на свою строку,
       кнопка остаётся кнопкой, а не растягивается во всю карточку. */
    .map-head > span.small { margin-left: 0; flex-basis: 100%; }
}

/* Сводка админки. Экран парка начинается с чисел: список из тысяч машин не
   читает никто, а вопрос «всё ли в порядке» задаётся каждый раз. Плитки со
   статусом кликабельны — иначе «закрыто снаружи: 12» не отвечает на вопрос,
   какие именно двенадцать. */
.sum { grid-template-columns: repeat(auto-fit, minmax(148px, 1fr)); }
.sum .card { margin: 0; padding: 12px 14px; }
.sum .card.pick { cursor: pointer; }
.sum .card.pick:hover { border-color: var(--accent); }
.sum .card.on { border-color: var(--accent); }
.sum-n { font-size: 26px; line-height: 1.15; font-variant-numeric: tabular-nums; margin-top: 2px; }
.sum-n.ok { color: var(--ok); }
.sum-n.warn { color: var(--warn); }
.sum-n.err { color: var(--live); }

/* Лист поверх страницы — карта выбора места. Отдельного окна здесь не нужно:
   выбор точки это один жест, после которого человек возвращается туда же,
   откуда пришёл. */
.sheet {
    position: fixed; inset: 0; z-index: 50;
    display: flex; align-items: center; justify-content: center;
    background: rgba(0, 0, 0, .55); padding: 16px;
}
.sheet-box {
    background: var(--bg); border: 1px solid var(--line); border-radius: var(--radius);
    padding: 14px; width: min(720px, 100%); max-height: 92vh; overflow: auto;
}
.sheet-map { height: min(58vh, 420px); border-radius: 8px; overflow: hidden; background: var(--line); }

/* ---------- главная: витрина ---------- */

.brand-wrap { display: flex; flex-direction: column; gap: 1px; }
.brand-wrap .brand { display: flex; align-items: center; gap: 2px; font-size: 23px; line-height: 1.1; }
.brand-tag { font-size: 11px; color: var(--fg-dim); letter-spacing: .2px; font-weight: 400; }

header.top nav a.navlink {
    display: inline-flex; align-items: center; gap: 7px;
    color: var(--fg-dim); padding: 5px 1px; border-bottom: 2px solid transparent;
}
header.top nav a.navlink:hover { color: var(--fg); text-decoration: none; }
header.top nav a.navlink.on { color: var(--fg); border-bottom-color: var(--accent); }

/* Короткие названия в меню. Четыре пункта с полными названиями в 375 px не
   помещаются и уезжают за край; на узком остаются «домик», «Мои», «Архив»,
   «Хаб» — по значку и первому слову пункт всё ещё узнаётся. */
.nav-short { display: none; }
@media (max-width: 720px) {
    header.top { gap: 10px; padding: 12px 12px; }
    header.top nav { gap: 12px; }
    .nav-long { display: none; }
    .nav-short { display: inline; }
    header.top nav a.navlink { gap: 5px; }
}

/* Полотно из эскиза «ШапкаСайта»: фотография прижата вправо, слева — заголовок.
   Снимок обрезан по левому краю (в эскизе туда был впечатан тот же текст, что
   стоит в разметке; два раза одно и то же не нужно), поэтому он ставится не
   растяжкой, а по высоте блока — так его нигде не увеличивают и камера остаётся
   резкой. Подложка под ним того же цвета, что левый столбец самой фотографии,
   и стыка не видно. */
.hero {
    position: relative; overflow: hidden;
    border: 1px solid var(--line); border-radius: var(--radius);
    margin: 18px 0 22px; padding: 46px 34px;
    background:
        url('/static/hero.jpg') no-repeat right center / auto 100%,
        radial-gradient(700px 320px at 78% 20%, #1d4ed83b, transparent 62%),
        linear-gradient(150deg, #0d1a2b, #08111c);
}
/* Затемнение поверх фотографии: текст читается на любой картинке, а не только
   на этой. Дальше 80% ширины оно сходит на нет — там уже нечего затемнять. */
.hero::before {
    content: ''; position: absolute; inset: 0; pointer-events: none;
    background: linear-gradient(100deg,
        #0a1524 0%, #0a1524 36%, rgba(10, 21, 36, .78) 54%,
        rgba(10, 21, 36, .18) 80%, rgba(10, 21, 36, 0) 100%);
}
.hero > * { position: relative; }
/* Просвет между строками ровно вдвое меньше прежнего: было 1.14 при кегле в
   единицу, то есть 0.14 воздуха, — стало 0.07. Само число меньше не «в два
   раза»: делится расстояние между строками, а не высота строки с буквами. */
.hero h1 { font-size: clamp(26px, 3.6vw, 40px); line-height: 1.07; margin: 0 0 14px; max-width: 17ch; }
.hero h1 em { color: #52a8ff; font-style: normal; }
.hero p { color: #b9c5d6; max-width: 46ch; margin: 0; }
/* На узком экране фотография шире блока и уезжает под весь текст — здесь она
   уходит в фон целиком, иначе заголовок ложится на сцену и софиты. */
@media (max-width: 760px) {
    .hero { padding: 30px 20px; }
    .hero::before { background: linear-gradient(160deg, #0a1524f2, #0a1524e0); }
}

/* Вкладки внутри карточки. Полоса вкладок — не кнопки действия, поэтому у неё
   нет ни заливки, ни скругления: подчёркивание снизу и всё. */
.tabs { display: flex; gap: 2px; border-bottom: 1px solid var(--line); margin: 0 0 12px; }
.tab {
    font: inherit; background: none; border: 0; border-bottom: 2px solid transparent;
    border-radius: 0; color: var(--fg-dim); padding: 7px 12px; margin-bottom: -1px;
}
.tab:hover { color: var(--fg); filter: none; }
.tab.on { color: var(--fg); border-bottom-color: var(--accent); }

/* Полоса вкладок — обычная горизонтальная прокрутка с привязкой. Свайп на
   телефоне и «двумя пальцами» на ноутбуке она обрабатывает сама, без единого
   обработчика касаний; коду остаётся только подсветить нужную вкладку. */
/* Высоту полосе выставляет код — по той вкладке, которую сейчас видно. Иначе
   она встала бы по самой длинной, и на короткой вкладке карточка кончалась бы
   пустотой в треть экрана. align-items: flex-start нужен, чтобы вкладки не
   растягивались под эту высоту сами. */
.panes {
    display: flex; align-items: flex-start; gap: 16px;
    overflow-x: auto; overflow-y: hidden; scroll-snap-type: x mandatory;
    scrollbar-width: none; overscroll-behavior-x: contain;
    transition: height .18s ease;
}
.panes::-webkit-scrollbar { display: none; }
.pane { flex: 0 0 100%; min-width: 0; scroll-snap-align: start; }
@media (prefers-reduced-motion: reduce) { .panes { transition: none; } }
/* Первое поле на вкладке не должно висеть на отступе метки — иначе вкладки
   разной высоты начинаются по-разному. */
.pane > label:first-child { margin-top: 0; }

/* Ряд «начать трансляцию»: слева настройка, справа три режима доступа. */
.startrow { display: grid; grid-template-columns: minmax(250px, 1.15fr) repeat(3, minmax(190px, 1fr)); gap: 14px; }
@media (max-width: 1040px) { .startrow { grid-template-columns: 1fr 1fr; } }
@media (max-width: 620px) { .startrow { grid-template-columns: 1fr; } }

.mode {
    font: inherit; text-align: left; color: #fff; border: 0; cursor: pointer;
    border-radius: var(--radius); padding: 15px;
    display: flex; flex-direction: column; gap: 8px; min-height: 132px;
}
.mode:hover { filter: brightness(1.07); }
.mode:disabled { opacity: .55; cursor: progress; filter: none; }
.mode .mode-h { display: flex; align-items: center; gap: 10px; font-weight: 600; }
.mode .mode-n { font-size: 13px; opacity: .92; flex: 1; }
/* Нижняя строка кнопки режима: слева значок «Начать трансляцию», справа
   стрелка. Значок нужен потому, что заголовок кнопки называет режим доступа
   («Пускать операторов»), а нажатие сразу заводит эфир — и это единственное
   место, где про это можно сказать до нажатия. */
.mode .mode-go { display: flex; align-items: center; gap: 8px; opacity: .95; }
.mode .mode-badge {
    display: inline-flex; align-items: center; gap: 6px;
    font-size: 12px; font-weight: 600; line-height: 1;
    padding: 5px 9px; border-radius: 999px;
    background: rgba(255, 255, 255, .18);
}
.mode .mode-arr { margin-left: auto; }
.mode.green  { background: linear-gradient(155deg, #1c9a51, #0e6f36); }
.mode.blue   { background: linear-gradient(155deg, #2f6fe4, #1b4fc0); }
.mode.violet { background: linear-gradient(155deg, #7c46e8, #5f2fbe); }

.sect-h { display: flex; align-items: center; flex-wrap: wrap; gap: 10px; margin: 26px 0 4px; }
.sect-h .spacer { flex: 1; }
/* На узком экране переключатель вида и «Все трансляции» уезжают на свою
   строку: втроём с заголовком они ломают его на два слова в столбик. */
@media (max-width: 560px) { .sect-h .spacer { flex-basis: 100%; } }
.sect-h h1, .sect-h h2 { margin: 0; }
.sect-icon { color: var(--accent); display: inline-flex; }

/* ---------- два вида списка «рядом» ---------- */

/* Переключатель вида: две кнопки-значка, слипшиеся в одну плашку. */
.viewsw { display: inline-flex; border: 1px solid var(--line); border-radius: 8px; overflow: hidden; }
.viewsw .vs {
    background: transparent; color: var(--fg-dim); border: 0; border-radius: 0;
    padding: 6px 10px; line-height: 0; cursor: pointer;
}
.viewsw .vs:hover { color: var(--fg); filter: none; background: #ffffff0d; }
.viewsw .vs.on { background: var(--accent); color: #fff; }

/* Обёртка нужна ради кнопок «‹ ›»: они висят поверх краёв полосы, а не
   отнимают у неё ширину. */
.listwrap { position: relative; }

/* Полоса листается прокруткой с привязкой. Свайп на сенсорном экране — это она
   же и есть, поэтому обработчиков касаний в коде нет вовсе: браузер делает это
   лучше и с инерцией. */
.grid.as-tiles, .grid.as-rows { scrollbar-width: none; overscroll-behavior-x: contain; }
.grid.as-tiles::-webkit-scrollbar, .grid.as-rows::-webkit-scrollbar { display: none; }

/* Плитками: два ряда, листается вбок. */
.grid.as-tiles {
    display: grid; grid-auto-flow: column;
    grid-template-rows: auto auto; grid-auto-columns: minmax(258px, 1fr);
    gap: 14px; overflow-x: auto; scroll-snap-type: x mandatory;
    /* start: карточки остаются своей высоты. Ростом их равняет не сетка, а
       код — он ставит «Отбору» высоту живой карточки; сетке доверить это
       нельзя, она бы взяла высоту самого высокого, то есть «Отбора». */
    align-items: start;
}
.grid.as-tiles > * { scroll-snap-align: start; }

/* Списком: одна колонка, листается вверх-вниз. Высота ограничена — иначе
   «листать» нечего, страница просто тянется вниз. */
.grid.as-rows {
    display: grid; grid-auto-flow: row; grid-template-columns: 1fr; gap: 12px;
    max-height: min(70vh, 620px); overflow-y: auto; scroll-snap-type: y mandatory;
    padding-right: 2px;
}
.grid.as-rows > * { scroll-snap-align: start; }
/* Карточка в строку: превью слева небольшим окном, всё остальное справа. */
.grid.as-rows .bcard { flex-direction: row; align-items: stretch; }
.grid.as-rows .bcard .art { flex: 0 0 min(38%, 240px); aspect-ratio: auto; }
.grid.as-rows .bcard .bd { flex: 1; }
.grid.as-rows .bcard .acts { max-width: 340px; }
@media (max-width: 560px) {
    /* На узком экране строка с превью слева оставляет названию полосу в пару
       слов. Там карточка снова обычная, а листается список по-прежнему. */
    .grid.as-rows .bcard { flex-direction: column; }
    .grid.as-rows .bcard .art { flex: none; aspect-ratio: 16 / 9; }
    .grid.as-rows .bcard .acts { max-width: none; }
}

/* Кнопки перелистывания. У плиток — по краям полосы, у списка — по центру
   сверху и снизу: там листают вверх-вниз, и стрелка сбоку сбивала бы. */
.pager {
    background: var(--bg-soft); color: var(--fg); border: 1px solid var(--line);
    border-radius: 999px; width: 36px; height: 36px; padding: 0;
    font-size: 18px; line-height: 1; cursor: pointer;
}
.pager:hover { filter: none; background: var(--bg-card); border-color: var(--accent); }
.pager.left, .pager.right { position: absolute; top: 50%; transform: translateY(-50%); z-index: 2; }
.pager.left { left: -6px; }
.pager.right { right: -6px; }
.pager.up, .pager.down { display: block; margin: 8px auto; }

/* Плитка отбора — первая в сетке «рядом». Выглядит как карточка трансляции,
   но без превью: показывать нечего, а место лучше отдать самому отбору. */
.fcard {
    background: var(--bg-card); border: 1px solid var(--line);
    border-radius: var(--radius); padding: 14px;
    display: flex; flex-direction: column; overflow: hidden;
}
.fcard .fc-h {
    display: flex; align-items: center; gap: 8px;
    font-weight: 600; color: var(--fg);
}
.fcard .fc-h svg { color: var(--accent); }
.fcard label { margin: 12px 0 5px; }
.fcard .field-row { margin-top: 10px; }
.fcard .field-row > label { flex: 0 0 52px; }
.fcard #tags-label { margin: 12px 0 4px; }
.fcard .fc-search { margin-top: 0; }
/* Метки — в своём окошке с прокруткой. Их бывает больше десятка, и без окошка
   плитка вырастала бы вдвое выше соседних, задирая весь ряд. flex: 1 отдаёт ей
   ровно тот остаток высоты, который есть. */
.fcard .fc-tags {
    margin-top: 8px; flex: 1 1 auto; min-height: 34px;
    overflow-y: auto; align-content: flex-start;
}
.fcard #geo-line:empty { display: none; }
.fcard #geo-line { margin-top: 6px; }
.fcard .fc-foot {
    display: flex; align-items: center; gap: 10px;
    margin-top: auto; padding-top: 12px;
}
.fcard .fc-foot .small { flex: 1; }

/* Метка-переключатель. Невыбранная — та же плашка, что и в списке; выбранная
   заливается цветом отбора, чтобы по одному взгляду было видно, чем сузили. */
.tag.pick {
    font: inherit; font-size: 12.5px; cursor: pointer;
    border: 1px solid transparent; padding: 2px 11px;
}
.tag.pick:hover { filter: brightness(1.25); }
.tag.pick.on { background: var(--accent); color: #fff; }

/* Карточка трансляции в списке «рядом». */
.bcard {
    background: var(--bg-card); border: 1px solid var(--line);
    border-radius: var(--radius); overflow: hidden; display: flex; flex-direction: column;
}
.bcard .art { aspect-ratio: 16 / 9; position: relative; display: flex; align-items: center; justify-content: center; }
.bcard .art .glyph { opacity: .28; color: #cfe0f5; }
.bcard .art-l { position: absolute; left: 10px; top: 10px; }
.bcard .art-r { position: absolute; right: 10px; top: 10px; }
/* Метки внизу превью. wrap-reverse прижимает их к нижнему краю: когда меток
   больше, чем влезает в строку, лишние уходят вверх и обрезаются там, а не
   отрывают нижний ряд от края. */
.bcard .art-b {
    position: absolute; left: 10px; right: 10px; bottom: 10px;
    display: flex; flex-wrap: wrap-reverse; gap: 5px; max-height: 48px; overflow: hidden;
}
.bcard .art-b .tag {
    background: #0a1524cc; color: #cfe0f5; font-size: 11px; padding: 2px 8px;
    white-space: nowrap;
}
.bcard .bd { padding: 12px 14px 14px; display: flex; flex-direction: column; gap: 9px; flex: 1; }
.bcard .bt { font-weight: 600; }
.bcard .meta { display: flex; align-items: center; gap: 7px; flex-wrap: wrap; }
.bcard .acts { display: flex; gap: 8px; margin-top: auto; }
.bcard .acts button {
    flex: 1; display: inline-flex; align-items: center; justify-content: center; gap: 7px;
    padding: 8px 10px; font-size: 14px;
}
/* «Записи нет» занимает место кнопки, чтобы карточки в ряду не разъезжались
   по высоте из-за того, что у одной действия нет. */
.bcard .acts .muted { flex: 1; display: flex; align-items: center; justify-content: center; padding: 8px 10px; }

.foot-line { text-align: center; color: var(--fg-dim); font-size: 13px; padding: 26px 0 4px; border-top: 1px solid var(--line); margin-top: 30px; }

/* ---------- страница просмотра ---------- */

/* Четыре части: сцена, карточка трансляции, полоса ракурсов и всё остальное
   (разбор связи, «позвать людей», пульт). На широком экране ракурсы стоят
   колонкой справа во всю высоту, на узком уходят под карточку трансляции —
   не в самый низ страницы: выбор ракурса это то, ради чего сюда и пришли, и
   лежать он должен рядом с видео, а не за тремя экранами прокрутки. */
.watch {
    display: grid;
    grid-template-columns: minmax(0, 1fr) 340px;
    grid-template-areas: "stage cams" "info cams" "rest cams";
    gap: 16px; align-items: start;
}
.watch > .stage-box { grid-area: stage; }
/* Отступы карточке задаёт сетка, свои ей не нужны — иначе они складываются. */
.watch > .w-info { grid-area: info; margin: 0; }
.watch > .cams { grid-area: cams; }
.watch > .w-rest { grid-area: rest; }
.w-rest > :last-child { margin-bottom: 0; }

.stage-box { border: 1px solid var(--line); border-radius: var(--radius); overflow: hidden; background: #000; }
.stage-box .stage { border-radius: 0; }

.pill {
    display: inline-flex; align-items: center; gap: 6px;
    background: #0b0d11cc; border: 1px solid #ffffff22; color: var(--fg);
    padding: 5px 11px; border-radius: 20px; font-size: 13px; white-space: nowrap;
}
.pill.hot { background: var(--live); border-color: transparent; color: #fff; font-weight: 600; }
.stage .top-l { position: absolute; left: 12px; top: 12px; display: flex; gap: 8px; z-index: 2; }
.stage .top-r { position: absolute; right: 12px; top: 12px; z-index: 2; }

/* Полоса управления живым эфиром: кнопки в ряд, а над ними — дорожка
   перемотки. Дорожка своей строкой, потому что ей нужна вся ширина: в одном
   ряду с громкостью и кнопками на телефоне от неё остаётся сантиметр.
   Показывается она только там, где отматывать есть куда (см. таймлайн в
   broadcast.html).
   Имя класса своё, не .player-bar: так называется полоса в записи, а это
   другой плеер с другими кнопками. Пока имя было общим, живая полоса молча
   донашивала от записи выравнивание по центру и внешний отступ. */
.live-bar { display: flex; flex-direction: column; background: #0b0d11; }
.live-bar .pb-ctl { display: flex; align-items: center; gap: 12px; padding: 9px 13px; }
.live-bar .pb {
    background: transparent; border: 0; color: var(--fg); padding: 4px;
    cursor: pointer; display: inline-flex; align-items: center;
}
.live-bar .pb:hover { color: var(--accent); }
.live-bar .pb-ctl input[type=range] { width: 110px; }
.live-bar .grow { flex: 1; }

.pb-line { display: flex; align-items: center; gap: 10px; padding: 9px 13px 0; }
.pb-line input[type=range] { flex: 1; width: auto; min-width: 0; }
.pb-t { font-size: 12px; color: var(--fg-dim); font-variant-numeric: tabular-nums; white-space: nowrap; }
/* Возврат к живому краю — красным: это то же самое «сейчас», что и значок
   LIVE над картинкой, и человек, отмотавший назад, ищет глазами именно его. */
.pb-live {
    font: inherit; font-size: 12px; line-height: 1; cursor: pointer;
    background: var(--live); color: #fff; border: 0; border-radius: 20px; padding: 6px 11px;
    white-space: nowrap;
}

.cams { background: var(--bg-card); border: 1px solid var(--line); border-radius: var(--radius); padding: 12px; }
.cams-h { display: flex; align-items: center; gap: 8px; margin-bottom: 10px; }
.cams-h h2 { margin: 0; font-size: 17px; }
.cams-list { display: flex; flex-direction: column; gap: 10px; max-height: 72vh; overflow: auto; }
.cams-list.collapsed { display: none; }

.camrow {
    position: relative; border: 2px solid transparent; border-radius: 8px;
    overflow: hidden; cursor: pointer; background: #000;
}
.camrow.active { border-color: var(--accent); }
/* Свой ракурс оператор должен находить не читая подписи: он один такой в
   полосе, и именно на него он переключается, чтобы посмотреть на себя. */
.camrow.mine { border-color: var(--ok, #3fb950); }
.camrow.mine.active { border-color: var(--accent); box-shadow: 0 0 0 2px var(--ok, #3fb950); }
.camrow .mine-tag {
    margin-left: auto; font-size: 11px; padding: 1px 6px; border-radius: 999px;
    background: #3fb95033; color: var(--ok, #3fb950); white-space: nowrap;
}
.camrow .now + .mine-tag { margin-left: 6px; }
.camrow video { width: 100%; aspect-ratio: 16 / 9; object-fit: cover; display: block; background: #000; }
.camrow .cap {
    position: absolute; left: 0; right: 0; bottom: 0;
    display: flex; align-items: center; gap: 7px; padding: 7px 9px; font-size: 13px;
    background: linear-gradient(#0000, #000000d0);
}
.camrow .num { color: var(--fg-dim); font-variant-numeric: tabular-nums; }
.camrow .nm { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.camrow .now { margin-left: auto; }
.camrow .off {
    position: absolute; inset: 0; display: flex; align-items: center; justify-content: center;
    background: #0b0d11e6; font-size: 12px; color: var(--fg-dim);
}

@media (max-width: 1040px) {
    .watch { grid-template-columns: 1fr; grid-template-areas: "stage" "info" "cams" "rest"; }
    /* Ракурсы под карточкой — лентой миниатюр, а не столбцом: столбец из шести
       ракурсов на телефоне занимает экран целиком и отодвигает вниз всё
       остальное, а выбирают из них взглядом по картинке, а не чтением
       подписей. Лента листается вбок и встаёт на миниатюру. */
    .cams-list {
        flex-direction: row; max-height: none; overflow-x: auto;
        padding-bottom: 4px; scroll-snap-type: x proximity;
    }
    .cams-list > .camrow { flex: 0 0 46%; max-width: 210px; scroll-snap-align: start; }
    /* Ждущие подтверждения остаются столбцом: там не картинка, по которой
       выбирают, а две кнопки, которым нужна вся ширина. */
    #cams-pending { flex-direction: column; }
    #cams-pending > .camrow { flex: 1 1 auto; max-width: none; }
}

.info-grid { display: grid; grid-template-columns: minmax(0, 1.4fr) minmax(250px, 1fr); gap: 22px; }
@media (max-width: 820px) { .info-grid { grid-template-columns: 1fr; } }
.info-meta {
    display: flex; flex-wrap: wrap; gap: 16px; align-items: center;
    color: var(--fg-dim); font-size: 13.5px; margin: 10px 0 12px;
}
.info-meta > span { display: inline-flex; align-items: center; gap: 7px; }
.tags { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 12px; }
.tag { background: var(--accent-soft); color: #8ec2ff; border-radius: 20px; padding: 3px 11px; font-size: 12.5px; }
.tags .ed-empty { background: transparent; border: 1px dashed var(--line); color: var(--fg-dim); }
/* Метка в пульте владельца — та же плашка, но с крестиком. Крестик держим
   крупнее подписи: в эфире по нему целятся пальцем. */
.tag.editable { display: inline-flex; align-items: center; gap: 6px; padding-right: 5px; }
.tag .tag-x {
    font: inherit; line-height: 1; background: none; border: 0; color: inherit;
    padding: 2px 6px; border-radius: 20px; opacity: .75; cursor: pointer;
}
.tag .tag-x:hover { opacity: 1; background: #ffffff1f; filter: none; }
.tag .tag-x:disabled { opacity: .4; cursor: progress; }

.can-do { display: flex; flex-direction: column; gap: 10px; }
.can-do .act {
    display: flex; align-items: center; gap: 12px; width: 100%; text-align: left;
    font: inherit; cursor: pointer; color: var(--fg);
    background: var(--bg-soft); border: 1px solid var(--line); border-radius: 10px; padding: 11px 14px;
}
.can-do .act:hover:not(:disabled) { border-color: #3c4a5c; filter: none; }
.can-do .act.primary { background: linear-gradient(150deg, #2f6fe4, #1b4fc0); border-color: transparent; color: #fff; }
.can-do .act:disabled { opacity: .45; cursor: not-allowed; }
.can-do .act .t { flex: 1; min-width: 0; }
.can-do .act .t b { display: block; font-weight: 600; }
.can-do .act .t span { font-size: 12.5px; opacity: .82; }

/* Пульт владельца — аккордеоном: он нужен рывками (пришёл запрос, надо сменить
   режим), а место занимает всё время. Свёрнутый заголовок при этом продолжает
   говорить, ждёт ли кто-нибудь ответа. */
details.card > summary {
    cursor: pointer; font-size: 17px; font-weight: 600;
    list-style: none; display: flex; align-items: center; gap: 8px;
}
details.card > summary::-webkit-details-marker { display: none; }
details.card > summary::after { content: '▾'; color: var(--fg-dim); margin-left: auto; font-weight: 400; }
details.card[open] > summary::after { content: '▴'; }
details.card > summary + * { margin-top: 12px; }

/* Ждущая камера — такой же кубик, как у работающих, и стоит он под ними: это
   ещё один ракурс, просто пока не пущенный. В пульте владельца ему было не
   место — там его находили не сразу, а решение нужно быстрое. */
.camrow.pending { border-color: var(--warn); }
.camrow.pending img { width: 100%; height: 100%; object-fit: cover; display: block; aspect-ratio: 16 / 9; }
.camrow .wait {
    position: absolute; inset: 0; display: flex; align-items: center; justify-content: center;
    color: var(--fg-dim); font-size: 12.5px; text-align: center; padding: 10px;
}
.camrow .who {
    position: absolute; left: 0; right: 0; top: 0; padding: 6px 9px; font-size: 13px;
    background: linear-gradient(#000000cc, #0000);
}
.camrow .who .muted { font-size: 11.5px; }
.camrow .go {
    position: absolute; left: 0; right: 0; bottom: 0; padding: 8px 9px;
    background: linear-gradient(#0000, #000000dd);
    display: flex; flex-direction: column; gap: 6px;
}
.camrow .go .st { font-size: 12px; color: var(--warn); }
.camrow .go .pair { display: flex; gap: 8px; }
.camrow .go .pair button { flex: 1; }
