/* Cuttlefish Panel — «Стенд».
   Приборная панель испытательного стенда: тёмная, плотная, без украшений.

   Правило типографики, на котором держится весь интерфейс:
   ЧТО НАПИСАЛ ЧЕЛОВЕК — гротеском (заголовки, подписи, кнопки, пояснения),
   ЧТО СООБЩИЛА МАШИНА — моноширинным (серийники, порты, пути, размеры,
   идентификаторы, время). Смешение этих ролей делало прежнюю панель
   нечитаемой: имя устройства было моно, а его модель — гротеском. */

:root {
    color-scheme: dark;

    /* Поверхности. Прежняя база была холодным сине-чёрным (#0a0c10); теперь —
       тёплый нейтральный уголь с ровной ступенью глубины. Плоскость сменилась
       мягкой глубиной: у поверхностей появились тени (--shadow*), но не тяжёлые —
       ровно чтобы карточка «лежала» на фоне, а не сливалась с ним. */
    --bg: #0e0f13;
    --panel: #16181f;
    --panel-hi: #1e2129;
    --line: #262a34;
    --line-hi: #363c48;

    /* Текст: три уровня, дальше — цвет состояния. */
    --ink: #e9edf4;
    --ink-dim: #9aa4b6;
    --ink-mute: #667082;

    /* Сигнальные цвета. Голубой — действие и живая телеметрия (марка стенда);
       остальные три читаются как светофор состояния устройства. У каждого —
       свой «-hi» для наведения и мягкая подложка «-bed». */
    --signal: #38ccdf;
    --signal-hi: #5ddaeb;
    --signal-ink: #052027;
    --signal-bed: #0e2c33;
    --live: #53d09b;
    --live-bed: #102c23;
    --wait: #e6ac52;
    --wait-bed: #2e2415;
    --fault: #e86b79;
    --fault-bed: #301619;

    /* Мягкая глубина. Тени тёплые-нейтральные, не чисто-чёрные, чтобы не
       «прожигать» тёплый фон. */
    --shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.35);
    --shadow: 0 8px 24px -10px rgba(0, 0, 0, 0.55), 0 2px 6px -2px rgba(0, 0, 0, 0.3);
    --shadow-lg: 0 20px 48px -16px rgba(0, 0, 0, 0.62), 0 4px 12px -4px rgba(0, 0, 0, 0.4);

    /* Роли поверхностей, у которых нет прямого токена, — чтобы светлая тема
       переключалась целиком, а не половиной экрана. */
    --btn-hover-bg: #242833;   /* фон кнопки при наведении */
    --bar-bg: rgba(14, 15, 19, 0.82);  /* липкая шапка (полупрозрачная) */
    --shot-bg: #05070a;        /* поле снимка экрана устройства */
    --accent-line: #244f5b;    /* рамка запущенной карточки (сигнальный оттенок) */

    --ui: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", sans-serif;
    --data: ui-monospace, "SF Mono", "Cascadia Mono", "JetBrains Mono", Menlo, Consolas, monospace;

    --r: 7px;
    --r-lg: 12px;
    --side: 216px;
    --bar: 52px;
}

/* Светлая тема. Тот же каркас и те же роли токенов — только значения. Włącza
   её переключатель в подвале сайдбара (data-theme="light" на <html>, выбор
   хранится в localStorage). Экран устройства (--shot-bg) остаётся тёмным: у
   выключенного аппарата поле снимка — «погашенный» экран, а не белый лист. */
:root[data-theme="light"] {
    color-scheme: light;

    --bg: #f3f5f8;
    --panel: #ffffff;
    --panel-hi: #eef1f5;
    --line: #e1e6ec;
    --line-hi: #ccd3dd;

    --ink: #1a1f28;
    --ink-dim: #53606f;
    --ink-mute: #7b8697;

    --signal: #0d90a5;
    --signal-hi: #0a7789;
    --signal-ink: #ffffff;
    --signal-bed: #d7eff3;
    --live: #1a9a67;
    --live-bed: #dcf2e8;
    --wait: #a9731a;
    --wait-bed: #f5ecd7;
    --fault: #c8384a;
    --fault-bed: #f8dee1;

    --shadow-sm: 0 1px 2px rgba(16, 24, 40, 0.06);
    --shadow: 0 8px 24px -10px rgba(16, 24, 40, 0.14), 0 2px 6px -2px rgba(16, 24, 40, 0.08);
    --shadow-lg: 0 20px 48px -16px rgba(16, 24, 40, 0.2), 0 4px 12px -4px rgba(16, 24, 40, 0.12);

    --btn-hover-bg: #e7ebf1;
    --bar-bg: rgba(243, 245, 248, 0.82);
    --shot-bg: #dfe3ea;
    --accent-line: #7ac8d6;
}

* { box-sizing: border-box; }

/* Атрибут `hidden` должен побеждать любой наш `display`. Без этого правила
   кнопки, снимок-заглушка и iframe scrcpy оставались на экране: у них
   `display` задан классом, а браузерное `[hidden] { display: none }` слабее. */
[hidden] { display: none !important; }

html, body { height: 100%; }

body {
    margin: 0;
    /* Плоский фон сменён очень мягким свечением: тёплое пятно сверху и холодное
       (сигнальное) у левого края дают глубину без «украшательства» — при
       прокрутке фон закреплён, чтобы контент по нему скользил. */
    background:
        radial-gradient(120% 80% at 78% -10%, rgba(230, 172, 82, 0.05), transparent 60%),
        radial-gradient(90% 70% at -5% 0%, rgba(56, 204, 223, 0.045), transparent 55%),
        var(--bg);
    background-attachment: fixed;
    color: var(--ink);
    font: 13px/1.5 var(--ui);
    -webkit-font-smoothing: antialiased;
}

a { color: var(--signal); text-decoration: none; }
a:hover { color: var(--signal-hi); }

h1, h2, h3, h4 { margin: 0; font-weight: 600; }

:focus-visible {
    outline: 2px solid var(--signal);
    outline-offset: 2px;
    border-radius: 3px;
}

/* Плавная прокрутка для якорных переходов (в т.ч. «к содержимому»). */
html { scroll-behavior: smooth; }

/* Утилиты интервалов и раскладки. Небольшой набор — заводится НЕ как фреймворк,
   а чтобы убрать разбросанные по шаблонам инлайновые `style=` (интервалы,
   строки-обёртки, стопки, рамки-заметки): значения в одном месте, шаблон читается
   классами. Динамические (data-driven) величины — ширина метра, aspect-ratio —
   остаются инлайном: там значение считает Jinja на каждый элемент.
   `.m-0` объявлен ДО направленных, чтобы `.m-0 .mt-12` давал «сброс + верх». */
.m-0 { margin: 0; }
.mt-0 { margin-top: 0; }
.mt-2 { margin-top: 2px; }
.mt-4 { margin-top: 4px; }
.mt-6 { margin-top: 6px; }
.mt-8 { margin-top: 8px; }
.mt-10 { margin-top: 10px; }
.mt-12 { margin-top: 12px; }
.mb-6 { margin-bottom: 6px; }
.mb-8 { margin-bottom: 8px; }
.mb-10 { margin-bottom: 10px; }
.my-8 { margin: 8px 0; }
.p-12 { padding: 12px; }
.minw-0 { min-width: 0; }
.mw-640 { max-width: 640px; }
.mw-720 { max-width: 720px; }
.break-all { word-break: break-all; }

/* Строка-обёртка: горизонтальный ряд кнопок/полей, переносится на узком. Имя
   «hrow», а не «row», намеренно: `.row` уже занят колоночной раскладкой внутри
   `.ctl`/`.dock` — глобальный `.row` их бы задел. */
.hrow { display: flex; flex-wrap: wrap; gap: .5rem; align-items: center; }
.hrow-8 { display: flex; flex-wrap: wrap; gap: 8px; }

/* Вертикальная стопка полей формы (замена инлайнового display:grid;gap). */
.stack { display: grid; gap: 14px; }
.stack-10 { display: grid; gap: 10px; }

/* Рамка-заметка: короткий блок в тонкой рамке (снапшот токена, ответ проверки). */
.note-box { padding: .5rem; border: 1px solid var(--line-hi); border-radius: 6px; }

/* Заголовок без верхней отбивки/рамки (первый в своём доке). */
.flush-top { margin-top: 0; padding-top: 0; border-top: 0; }

/* Ссылка «к содержимому»: спрятана за краем экрана, всплывает только при
   фокусе с клавиатуры (Tab на первом же элементе страницы). */
.skip-link {
    position: fixed;
    top: 8px;
    left: 8px;
    z-index: 1000;
    padding: 8px 14px;
    border-radius: var(--r);
    background: var(--signal);
    color: var(--signal-ink);
    font: 600 13px/1 var(--ui);
    text-decoration: none;
    transform: translateY(-160%);
    transition: transform 0.12s ease;
}
.skip-link:focus { transform: translateY(0); outline: none; }
/* Область содержимого фокусируется программно (после перехода по ссылке) —
   рамку показывать не нужно, она бы обвела всю страницу. */
.main:focus { outline: none; }

@media (prefers-reduced-motion: reduce) {
    * { transition: none !important; animation: none !important; }
    html { scroll-behavior: auto; }
}

/* Моноширинный — роль, а не украшение: всё, что сказала машина.
   Табличные цифры, иначе меняющиеся значения ёрзают. */
.data, .mono {
    font-family: var(--data);
    font-variant-numeric: tabular-nums;
    font-size: 0.925em;
    letter-spacing: -0.01em;
}

/* Надзаголовок: единственное место, где допущен капс. Им размечены разделы
   внутри карточек и шапки таблиц — чтобы не плодить размеры текста. */
.eyebrow {
    font-size: 10px;
    font-weight: 600;
    letter-spacing: 0.09em;
    text-transform: uppercase;
    color: var(--ink-mute);
}

.dim { color: var(--ink-dim); }
.mute, .muted { color: var(--ink-mute); font-size: 12px; }

/* Сигнальные состояния как самостоятельные утилиты (а не только внутри
   `.unit-facts`): их носят и абзацы-предупреждения, и ячейки таблиц, и метки
   в списках. Раньше `.fact-fault`/`.fact-mute` были определены ТОЛЬКО в
   контексте `.unit-facts`, поэтому «⚠ течь» на «Диагностике» рисовалась
   обычным цветом — стены не было видно. */
.fact-fault { color: var(--fault); }
.fact-mute { color: var(--ink-mute); }
.fact-live { color: var(--live); }
.hint, .note {
    margin: 6px 0 0;
    color: var(--ink-mute);
    font-size: 12px;
    line-height: 1.5;
}
.empty { margin: 0; padding: 18px 0; color: var(--ink-mute); }
.spread { margin-left: auto; }

/* ========================================================================
   Каркас: сайдбар слева, липкая шапка раздела, поле контента.
   ===================================================================== */

.app {
    display: grid;
    grid-template-columns: var(--side) minmax(0, 1fr);
    min-height: 100dvh;
}

.side {
    position: sticky;
    top: 0;
    display: flex;
    flex-direction: column;
    height: 100dvh;
    background: var(--panel);
    border-right: 1px solid var(--line);
}

.side-brand {
    display: flex;
    align-items: center;
    gap: 9px;
    height: var(--bar);
    padding: 0 14px;
    border-bottom: 1px solid var(--line);
    color: var(--ink);
    font-size: 13px;
    font-weight: 600;
}

.side-brand:hover { color: var(--ink); }

/* Марка — не «логотип», а торец прибора: квадрат в сигнальной рамке. */
.brand-mark {
    display: grid;
    place-items: center;
    width: 24px;
    height: 24px;
    flex: none;
    border: 1px solid var(--signal);
    border-radius: 5px;
    background: var(--signal-bed);
    color: var(--signal);
    font-family: var(--data);
    font-size: 10px;
    font-weight: 700;
}

.side-nav {
    display: flex;
    flex-direction: column;
    gap: 1px;
    padding: 10px 8px;
}

.snav {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 7px 10px;
    border-radius: var(--r);
    color: var(--ink-dim);
    font-size: 13px;
    transition: background 0.12s, color 0.12s;
}

.snav:hover { background: var(--panel-hi); color: var(--ink); }

/* Активный раздел помечен слева сигнальной чертой — видно боковым зрением. */
.snav.is-on {
    background: var(--panel-hi);
    color: var(--ink);
    box-shadow: inset 2px 0 0 var(--signal);
}

.snav svg {
    width: 20px;
    height: 20px;
    flex: none;
    fill: none;
    stroke: currentColor;
    stroke-width: 1.6;
    stroke-linecap: round;
    stroke-linejoin: round;
}

.snav-count {
    margin-left: auto;
    min-width: 18px;
    padding: 1px 5px;
    border-radius: 9px;
    background: var(--signal);
    color: var(--signal-ink);
    font-family: var(--data);
    font-size: 10px;
    font-weight: 700;
    text-align: center;
}

.side-spacer { flex: 1; }

.side-foot {
    padding: 10px;
    border-top: 1px solid var(--line);
    display: flex;
    gap: 8px;
}
/* form не должна ломать flex-раскладку кнопок — растворяем её. */
.side-foot form { display: contents; }

/* Тема и выход — иконки-кнопки, а не текст: короче, узнаются глазом и одинаково
   работают в развёрнутом и свёрнутом сайдбаре. */
.foot-btn {
    flex: 1;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    height: 34px;
    background: transparent;
    border: 0;
    border-radius: var(--r);
    color: var(--ink-dim);
    font-size: 15px;
    line-height: 1;
    cursor: pointer;
    transition: background 0.12s, color 0.12s;
}
/* Без рамки и заливки, как иконки навигации, — чтобы подвал не выбивался из
   общего стиля рейла (по просьбе). Подсветка только на наведении. */
.foot-btn:hover { background: var(--panel-hi); color: var(--ink); }
.foot-btn svg { width: 20px; height: 20px; fill: none; stroke: currentColor; stroke-width: 1.6; stroke-linecap: round; stroke-linejoin: round; }

/* Переключатель темы держит обе иконки, видимую выбирает тема: так кнопка верна
   ещё до выполнения скрипта, а размер совпадает с соседней иконкой выхода
   (текстовый глиф «☀» рядом с ней выглядел мелким). */
.theme-glyph { display: inline-flex; }
.theme-glyph-moon { display: none; }
:root[data-theme="light"] .theme-glyph-sun { display: none; }
:root[data-theme="light"] .theme-glyph-moon { display: inline-flex; }

/* Датчики нагрузки хоста: полоска на ресурс. Прежде это были три текстовых
   чипа в шапке — цифру приходилось читать, чтобы понять, всё ли хорошо. */
.meters { display: flex; flex-direction: column; gap: 9px; }

.meter-head {
    display: flex;
    justify-content: space-between;
    align-items: baseline;
    gap: 8px;
    font-size: 11px;
    color: var(--ink-mute);
}

.meter-head b {
    font-family: var(--data);
    font-size: 11px;
    font-weight: 600;
    color: var(--ink-dim);
}

.meter-track {
    height: 3px;
    margin-top: 4px;
    border-radius: 2px;
    background: var(--line);
    overflow: hidden;
}

.meter-fill {
    height: 100%;
    background: var(--signal);
    transition: width 0.4s ease;
}

.meter.is-hot .meter-fill { background: var(--fault); }
.meter.is-hot .meter-head b { color: var(--fault); }

/* --- шапка раздела --- */

.main { display: flex; flex-direction: column; min-width: 0; }

.bar {
    position: sticky;
    top: 0;
    z-index: 20;
    display: flex;
    align-items: center;
    gap: 12px;
    height: var(--bar);
    padding: 0 20px;
    background: var(--bar-bg);
    backdrop-filter: blur(10px) saturate(1.1);
    border-bottom: 1px solid var(--line);
}

.bar h1 { font-size: 15px; letter-spacing: -0.01em; }
.bar-sub { color: var(--ink-mute); font-size: 12px; }

.bar-actions {
    margin-left: auto;
    display: flex;
    align-items: center;
    gap: 8px;
}

.page { padding: 24px; }

/* Нагрузка хоста прижата вправо и тянет за собой действия шапки: заголовок
   слева, метрики+кнопки справа — одной строкой. Поэтому у `.bar-actions` больше
   нет своего auto-отступа (иначе свободное место делилось бы на два зазора). */
/* Без max-width: три метрики с 64-пиксельной полоской не влезали в 340px и,
   не сжимаясь (flex:none у полоски), налезали друг на друга. Пусть блок будет
   по содержимому и прижмётся вправо. */
.bar #host-mini { margin-left: auto; flex: none; }
.bar #host-mini + .bar-actions { margin-left: 0; }

/* Тумблер сворачивания меню живёт теперь в самой шапке (слева от заголовка) —
   отдельной верхней полосы под него больше нет. */
.nav-toggle {
    display: inline-grid;
    place-items: center;
    width: 26px;
    height: 22px;
    flex: none;
    padding: 0;
    background: transparent;
    border: 0;
    color: var(--ink-dim);
    cursor: pointer;
}
.nav-toggle svg {
    width: 18px;
    height: 18px;
    fill: none;
    stroke: currentColor;
    stroke-width: 1.8;
    stroke-linecap: round;
}
.nav-toggle:hover { color: var(--ink); }
/* Нагрузка в строку: компактные пары «метка N%» с короткой полоской. */
.meters-h { flex-direction: row; align-items: center; gap: 16px; overflow: hidden; }
.meters-h .meter { display: flex; align-items: center; gap: 8px; min-width: 0; }
/* В строке метка и % центрируем (не по базовой линии) и снимаем line-height,
   иначе текст оказывался чуть выше полоски — «съезжало немного». */
.meters-h .meter-head { gap: 5px; align-items: center; line-height: 1; }
.meters-h .meter-track { width: 64px; margin-top: 0; flex: none; }

/* Свёрнутый сайдбар — узкий рейл, а не пустота: иконки остаются, подписи и
   слово-бренд прячутся. Так видно, где ты, и можно перейти в раздел одним
   кликом даже свёрнутым меню (по просьбе: «не скрывать полностью, а уменьшать»). */
.app.nav-off { grid-template-columns: 56px minmax(0, 1fr); }
.app.nav-off .side-brand > span:last-child,
.app.nav-off .snav > span:first-of-type,
.app.nav-off #task-badge { display: none; }
.app.nav-off .side-brand,
.app.nav-off .snav { justify-content: center; gap: 0; padding-left: 0; padding-right: 0; }
.app.nav-off .side-nav { padding: 10px 6px; }
/* Активный раздел в рейле — рамкой, а не левой чертой (центрированной иконке
   черта у края смотрелась бы оторванной). */
.app.nav-off .snav.is-on { box-shadow: inset 0 0 0 1px var(--signal); }
/* Тема и выход в рейле — столбиком, чтобы влезли в узкую колонку. */
.app.nav-off .side-foot { flex-direction: column; padding: 8px 6px; }

/* Нативный выбор файла причёсан под тему (кнопка + имя). */
input[type="file"] {
    font: 12px/1.4 var(--ui);
    color: var(--ink-dim);
    max-width: 100%;
}
input[type="file"]::file-selector-button {
    margin-right: 10px;
    padding: 6px 11px;
    background: var(--panel-hi);
    border: 1px solid var(--line-hi);
    border-radius: var(--r);
    color: var(--ink);
    font: 500 12px/1.4 var(--ui);
    cursor: pointer;
    transition: background 0.12s, border-color 0.12s;
}
input[type="file"]::file-selector-button:hover { background: var(--btn-hover-bg); border-color: var(--line-hi); }

/* Крышка секции внутри страницы. */
.section {
    display: flex;
    align-items: baseline;
    flex-wrap: wrap;
    gap: 8px 10px;
    margin: 26px 0 12px;
}

.section:first-child { margin-top: 0; }
.section h2 { font-size: 13px; }
.section p { margin: 0; }

/* Тулбар списка (счётчик + фильтр + массовые действия) — тонкая строка над
   контентом, а не отдельная «шапка»: заголовок и нагрузка уже в одной строке
   сверху, а это управление самим списком. Компактно и по центру по вертикали. */
.toolbar {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 8px 10px;
    margin: 0 0 14px;
}
/* Длинный путь-моно в тулбаре/шапке карточки (напр. каталог хранилища файлов)
   обязан ломаться, а не распирать строку за край на узком экране (поймано
   Playwright'ом). min-width:0 — чтобы flex-элемент вообще мог сжаться. */
.toolbar .mono,
.card-head .mono { min-width: 0; overflow-wrap: anywhere; }

/* Сегментный фильтр (пул проксей: Все/Живые/Мёртвые/Не проверены). Тот же язык,
   что у `.subtab`: приглушённый до наведения, активный сегмент — цветом сигнала.
   Счётчик рядом с меткой — чтобы вкладки показывали объём каждой группы. */
/* Связный сегментный фильтр (пул проксей): одна рамка, сегменты через тонкие
   перегородки, активный подсвечен. ИМЯ `segfilter`, а НЕ `seg`: `.seg` уже занят
   переключателем вида экрана (ниже, с `.segbtn`), и та, более поздняя, версия
   перебивала эту — фильтр рисовался чужими пилюлями (баг из живого фидбэка). */
.segfilter {
    display: inline-flex;
    border: 1px solid var(--line);
    border-radius: var(--r);
    overflow: hidden;
    background: var(--panel);
}
.segfilter-btn {
    padding: 5px 14px;
    border: 0;
    border-left: 1px solid var(--line);
    border-radius: 0;
    background: transparent;
    color: var(--ink-mute);
    font: 500 12px/1.4 var(--ui);
    cursor: pointer;
    white-space: nowrap;
}
.segfilter-btn:first-child { border-left: 0; }
.segfilter-btn:hover { background: var(--panel-hi); color: var(--ink); }
.segfilter-btn.is-on { background: var(--panel-hi); color: var(--signal); }
.segfilter-n {
    margin-left: 6px;
    font-variant-numeric: tabular-nums;
    color: var(--ink-mute);
    font-size: 11px;
}
.segfilter-btn.is-on .segfilter-n { color: var(--signal); }

/* Обёртка широкой таблицы: горизонтальная прокрутка живёт ВНУТРИ своего блока,
   а не роняет всю страницу в боковой скролл (требование адаптива). */
.table-wrap { width: 100%; overflow-x: auto; }

/* Тег-метка: и как кнопка-фильтр (панель тегов, карточка), и как индикатор.
   Активный (выбранный фильтр) — цветом сигнала. */
.chip {
    display: inline-flex;
    align-items: center;
    padding: 2px 9px;
    border: 1px solid var(--line);
    border-radius: 999px;
    background: transparent;
    color: var(--ink-dim);
    font: 500 11px/1.5 var(--ui);
    cursor: pointer;
    white-space: nowrap;
}
.chip:hover { background: var(--panel-hi); color: var(--ink); }
.chip.is-on { border-color: var(--signal); color: var(--signal); background: var(--signal-bed); }
.fleet-tags { display: flex; flex-wrap: wrap; gap: 6px; margin: 0 0 14px; }
.unit-tags { display: flex; flex-wrap: wrap; gap: 5px; margin-top: 8px; }

/* Сворачиваемая карточка-справочник (`<details class="card">`): заголовок-строка
   с раскрывающим треугольником; самый длинный блок «Настроек» свёрнут по умолчанию. */
.ref-fold > .ref-fold-head { cursor: pointer; list-style: none; display: flex; align-items: baseline; gap: 10px; flex-wrap: wrap; }
.ref-fold > .ref-fold-head::-webkit-details-marker { display: none; }
.ref-fold > .ref-fold-head h2 { font-size: 13px; margin: 0; }
.ref-fold > .ref-fold-head::before {
    content: "▸"; color: var(--ink-mute); font-size: 11px; margin-right: 2px;
    transition: transform 0.12s;
}
.ref-fold[open] > .ref-fold-head::before { transform: rotate(90deg); }

/* Мастер запуска флота (drawer «Новое устройство»): индикатор шагов + сами шаги
   + строка навигации. Форма показывается по одному экрану, а не простынёй. */
.wizard-steps {
    display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
    margin: 0 0 14px; padding-bottom: 12px; border-bottom: 1px solid var(--line);
}
.wz-dot {
    font: 600 11px/1 var(--ui); color: var(--ink-mute);
    padding: 4px 9px; border: 1px solid var(--line); border-radius: 999px; white-space: nowrap;
}
.wz-dot.is-on { color: var(--signal); border-color: var(--line-hi); background: var(--signal-bed); }
.wz-sep { color: var(--ink-mute); font-size: 11px; }
.wizard-step { display: flex; flex-direction: column; gap: 12px; }
.wizard-nav { display: flex; gap: 8px; align-items: center; margin-top: 14px; }
.wizard-nav #launch-wz-next { margin-left: auto; }
/* «Назад» в липком подвале последнего шага: слева, всё остальное (Запустить/
   Собрать/индикатор) уезжает вправо. */
.create-actions .wz-goback { margin-right: auto; }

/* Сетка экранов флота: плотная плитка живых снимков, без карточек-фактов —
   обзор «что на флоте» одним взглядом. Плитки уже, чем в списке (тут не читают
   факты, а смотрят экраны), поэтому влезает больше. */
.screens-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
    gap: 14px;
}
.screen-tile {
    display: block;
    text-decoration: none;
    color: inherit;
    border: 1px solid var(--line);
    border-radius: var(--r);
    overflow: hidden;
    background: var(--panel);
    transition: border-color 0.12s;
}
.screen-tile:hover { border-color: var(--signal); }
.screen-shot { width: 100%; background: #000; }
.screen-shot img { display: block; width: 100%; height: 100%; object-fit: cover; }
.screen-name {
    display: flex;
    align-items: center;
    gap: 6px;
    padding: 6px 8px;
    font: 500 12px/1.4 var(--data);
    color: var(--ink-dim);
    border-top: 1px solid var(--line);
}

/* ========================================================================
   Атомы
   ===================================================================== */

.card {
    background: var(--panel);
    border: 1px solid var(--line);
    border-radius: var(--r-lg);
    padding: 16px;
    box-shadow: var(--shadow-sm);
}

.card-head {
    display: flex;
    align-items: center;
    gap: 10px;
    margin-bottom: 12px;
}

.card-head h2 { font-size: 13px; }

.btn {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    padding: 6px 11px;
    background: var(--panel-hi);
    border: 1px solid var(--line-hi);
    border-radius: var(--r);
    color: var(--ink);
    font: 500 12px/1.4 var(--ui);
    white-space: nowrap;
    cursor: pointer;
    transition: background 0.12s, border-color 0.12s, color 0.12s, transform 0.06s;
}
/* Физический отклик на нажатие (аудит редизайна: не было `:active`-обратной
   связи). Лёгкий сдвиг вниз — палец чувствует «клик». */
.btn:active, .iconbtn:active, .segfilter-btn:active, .chip:active,
.subtab:active, .seg:active, .foot-btn:active { transform: translateY(1px); }

.btn:hover { background: var(--btn-hover-bg); border-color: var(--line-hi); color: var(--ink); }
.btn:disabled { opacity: 0.4; cursor: not-allowed; }
.btn:disabled:hover { background: var(--panel-hi); border-color: var(--line-hi); }
.btn.htmx-request, .htmx-request .btn { cursor: progress; opacity: 0.6; }

.btn-primary {
    background: var(--signal);
    border-color: var(--signal);
    color: var(--signal-ink);
    font-weight: 600;
    box-shadow: 0 1px 0 rgba(255, 255, 255, 0.12) inset, var(--shadow-sm);
}

.btn-primary:hover {
    background: var(--signal-hi);
    border-color: var(--signal-hi);
    color: var(--signal-ink);
}

.btn-danger { color: var(--fault); }
.btn-danger:hover { background: var(--fault-bed); border-color: var(--fault); color: var(--fault); }

.btn-ghost { background: transparent; border-color: transparent; color: var(--ink-dim); }
.btn-ghost:hover { background: var(--panel-hi); border-color: var(--line-hi); }

.btn-sm, .btn-small { padding: 4px 8px; font-size: 11px; }

/* Метка состояния. Она НЕ кнопка: без фона и рамки — только цветная точка и
   слово. Прежде бейджи выглядели кнопками и стояли с ними в одном ряду. */
.tag {
    display: inline-flex;
    align-items: center;
    gap: 5px;
    font-size: 11px;
    color: var(--ink-mute);
    white-space: nowrap;
}

.tag::before {
    content: "";
    width: 5px;
    height: 5px;
    flex: none;
    border-radius: 50%;
    background: currentColor;
}

.tag-live, .status-running, .status-ok { color: var(--live); }
.tag-wait, .status-starting, .status-stale { color: var(--wait); }
.tag-fault, .status-failed { color: var(--fault); }
.tag-signal { color: var(--signal); }

/* Ярлык, а не состояние («APK», «эталон»): рамка вместо точки. */
.tag-flat::before { display: none; }
.tag-flat {
    padding: 1px 6px;
    border: 1px solid var(--line-hi);
    border-radius: 3px;
    font-family: var(--data);
    font-size: 10px;
}

/* Старая разметка партиалов зовёт метки `.status` — держим их такими же. */
.status {
    display: inline-flex;
    align-items: center;
    gap: 5px;
    font-size: 11px;
    color: var(--ink-mute);
    white-space: nowrap;
}

.status::before {
    content: "";
    width: 5px;
    height: 5px;
    flex: none;
    border-radius: 50%;
    background: currentColor;
}

.alert {
    margin: 0 0 12px;
    padding: 9px 12px;
    border: 1px solid;
    border-radius: var(--r);
    font-size: 12px;
    line-height: 1.5;
}

/* Цвет текста и рамки берём из самого сигнала, а фон — из его подложки: так
   алерт читается и на тёмной, и на светлой теме без отдельных значений. */
.alert-error { background: var(--fault-bed); border-color: var(--fault); color: var(--fault); }
.alert-warn { background: var(--wait-bed); border-color: var(--wait); color: var(--wait); }
.alert-ok { background: var(--live-bed); border-color: var(--live); color: var(--live); }

/* Расхождения региона со страной SIM — прямо в карточке, под значениями.
   Цвет тот же сигнальный, что у `.alert-warn`, но без рамки и подложки: это не
   отдельный алерт, а примечание к соседним строкам. */
.warnlist {
    margin: 8px 0 0;
    padding-left: 16px;
    color: var(--wait);
    font-size: 11px;
    line-height: 1.5;
}

.indicator { display: none; color: var(--ink-mute); font-size: 11px; }
.htmx-request .indicator, .htmx-request.indicator { display: inline; }

/* --- поля --- */

input, select, textarea {
    padding: 6px 9px;
    background: var(--bg);
    border: 1px solid var(--line-hi);
    border-radius: var(--r);
    color: var(--ink);
    font: 12px/1.4 var(--ui);
}

input::placeholder { color: var(--ink-mute); }

/* Компактное поле для строки фильтра списка: в ряду .section рядом с eyebrow
   и групповыми кнопками полноразмерный input перевешивал бы счётчик. */
.input-sm {
    padding: 4px 8px;
    max-width: 260px;
    font-size: 12px;
}
.input-sm[type="search"] { min-width: 180px; }

input:focus, select:focus, textarea:focus {
    outline: none;
    border-color: var(--signal);
    box-shadow: 0 0 0 2px var(--signal-bed);
}

input[type="range"] { padding: 0; border: 0; accent-color: var(--signal); }
input[type="checkbox"] { width: 14px; height: 14px; accent-color: var(--signal); }

/* Выпадающие списки — единый тёмный вид: убираем нативный OS-контрол, рисуем
   свой шеврон. Раскрытый список (option) в разных браузерах стилизуется по-
   разному, но фон/цвет задаём, где движок это уважает. */
select {
    appearance: none;
    -webkit-appearance: none;
    padding-right: 30px;
    background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 24 24' fill='none' stroke='%238b96a8' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'/%3E%3C/svg%3E");
    background-repeat: no-repeat;
    background-position: right 9px center;
    cursor: pointer;
}
select option { background: var(--panel-hi); color: var(--ink); }

/* Единые скроллбары под тёмную тему (webkit + firefox). Тонкие, из линии панели,
   без светлого OS-жёлоба, который выбивался из интерфейса. */
* { scrollbar-width: thin; scrollbar-color: var(--line-hi) transparent; }
::-webkit-scrollbar { width: 10px; height: 10px; }
::-webkit-scrollbar-track { background: transparent; }
::-webkit-scrollbar-thumb {
    background: var(--line-hi);
    border-radius: 6px;
    border: 2px solid var(--bg);
}
::-webkit-scrollbar-thumb:hover { background: var(--ink-mute); }

.field { display: flex; flex-direction: column; gap: 5px; }
.field-label { font-size: 12px; color: var(--ink-dim); }
.field-hint { font-size: 11px; line-height: 1.45; color: var(--ink-mute); }
.field-row { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; }
.field-row-3 { grid-template-columns: repeat(3, 1fr); }

.field-check {
    display: grid;
    grid-template-columns: auto 1fr;
    gap: 4px 9px;
    align-items: center;
}

.field-check input { grid-row: 1; }
.field-check .field-hint { grid-column: 2; }

.form-actions, .create-actions {
    display: flex;
    align-items: center;
    gap: 10px;
    margin-top: 4px;
}

.create-form { display: flex; flex-direction: column; gap: 14px; }
.form-section { margin: 8px 0 2px; }
.form-section h3 { font-size: 12px; color: var(--ink-dim); margin: 0; }
.form-section p { margin: 4px 0 0; color: var(--ink-mute); font-size: 11px; }

/* Редактируемые карточки настроек в две колонки: узкие формы иначе оставляли
   пустую правую половину карточки во всю ширину (замечание пользователя). */
/* Masonry-колонки, а не grid: разной высоты карточки в grid выравнивались по
   строкам и короткая справа оставляла дыру под собой («много пустого места»).
   Колонки пакуют плотно, не разрывая карточку. */
.admin-grid { columns: 1; column-gap: 12px; }
@media (min-width: 1100px) { .admin-grid { columns: 2; } }
.admin-grid > .card { break-inside: avoid; margin: 0 0 12px; }

/* Справочник: карточки разной высоты в grid оставляли вертикальные дыры
   («много пустого места»). CSS-колонки (masonry) пакуют их плотно, не разрывая
   карточку. */
.ref-fold .settings-grid { display: block; columns: 3 320px; column-gap: 12px; }
.ref-fold .settings-grid > .card { break-inside: avoid; margin: 0 0 12px; }

/* --- таблица --- */

/* Ширина ограничена намеренно: на окне 1907 px между колонкой «Загружен» и
   действием строки зияло ~700 px, и строка переставала читаться как одна
   запись. Пустое место справа ОТ таблицы читается как поле, внутри строки —
   как сломанная вёрстка. */
/* tabular-nums: цифры в таблицах выстраиваются в колонку (аудит редизайна:
   пропорциональные цифры в data-интерфейсе «пляшут»). */
.table { width: 100%; max-width: 1100px; border-collapse: collapse; font-variant-numeric: tabular-nums; }

/* Разрушительное действие в строке — приглушённой иконкой до наведения.
   Совсем прятать нельзя: `:focus-within` оставляет её доступной с клавиатуры,
   а `hx-confirm` всё равно переспросит. */
.row-danger { color: var(--ink-mute); opacity: 0.45; transition: opacity 0.12s, color 0.12s; }
tr:hover .row-danger,
tr:focus-within .row-danger,
.row-danger:focus-visible { opacity: 1; color: var(--fault); }
.row-danger:hover { opacity: 1; color: var(--fault); }

.table th {
    padding: 0 12px 8px;
    border-bottom: 1px solid var(--line);
    font-size: 10px;
    font-weight: 600;
    letter-spacing: 0.09em;
    text-transform: uppercase;
    color: var(--ink-mute);
    text-align: left;
}

.table td {
    padding: 9px 12px;
    border-bottom: 1px solid var(--line);
    vertical-align: middle;
}

.table tr:last-child td { border-bottom: 0; }
.table tbody tr:hover { background: var(--panel-hi); }
.table .row-actions { display: flex; gap: 6px; justify-content: flex-end; }
.row-active td { background: var(--signal-bed); }

/* --- меню «⋯» --- */

.menu { position: relative; }
.menu summary { list-style: none; cursor: pointer; }
.menu summary::-webkit-details-marker { display: none; }

.menu-btn {
    display: grid;
    place-items: center;
    width: 24px;
    height: 24px;
    border-radius: var(--r);
    color: var(--ink-mute);
    font-size: 15px;
    line-height: 1;
}

.menu-btn:hover { background: var(--panel-hi); color: var(--ink); }

.menu-items {
    position: absolute;
    right: 0;
    top: calc(100% + 4px);
    z-index: 30;
    min-width: 156px;
    padding: 4px;
    background: var(--panel-hi);
    border: 1px solid var(--line-hi);
    border-radius: var(--r);
    box-shadow: var(--shadow-lg);
    display: flex;
    flex-direction: column;
}

.menu-item {
    padding: 6px 9px;
    border: 0;
    border-radius: 4px;
    background: transparent;
    color: var(--ink);
    font: 12px/1.4 var(--ui);
    text-align: left;
    cursor: pointer;
}

.menu-item:hover { background: var(--line); color: var(--ink); }
.menu-item-danger { color: var(--fault); }

/* Переключатель устройств в шапке: имя-заголовок с каретом открывает меню
   устройств (слева, не справа как «⋯»). */
.dev-menu > summary { display: inline-flex; align-items: center; gap: 4px; }
.dev-menu > summary svg { width: 13px; height: 13px; fill: none; stroke: currentColor; stroke-width: 2; opacity: 0.7; }
.dev-menu .menu-items { left: 0; right: auto; min-width: 140px; }
.dev-menu .menu-item.is-cur { color: var(--signal); font-weight: 600; }
.menu-item-danger:hover { background: var(--fault-bed); }

/* --- выезжающая панель --- */

.drawer { position: fixed; inset: 0; z-index: 60; pointer-events: none; }
.drawer[data-open="false"] { visibility: hidden; }

.drawer-scrim {
    position: absolute;
    inset: 0;
    background: rgba(6, 7, 10, 0.66);
    backdrop-filter: blur(2px);
    opacity: 0;
    transition: opacity 0.18s ease;
    pointer-events: auto;
}

.drawer[data-open="true"] .drawer-scrim { opacity: 1; }

.drawer-panel {
    position: absolute;
    top: 0;
    right: 0;
    display: flex;
    flex-direction: column;
    width: min(620px, 100%);
    height: 100%;
    background: var(--panel);
    border-left: 1px solid var(--line-hi);
    box-shadow: var(--shadow-lg);
    transform: translateX(100%);
    transition: transform 0.2s ease;
    pointer-events: auto;
}

.drawer[data-open="true"] .drawer-panel { transform: none; }

.drawer-head {
    display: flex;
    align-items: center;
    height: var(--bar);
    padding: 0 16px;
    border-bottom: 1px solid var(--line);
}

.drawer-head h2 { font-size: 13px; }

.drawer-close {
    margin-left: auto;
    width: 26px;
    height: 26px;
    border: 0;
    border-radius: var(--r);
    background: transparent;
    color: var(--ink-mute);
    font-size: 18px;
    line-height: 1;
    cursor: pointer;
}

.drawer-close:hover { background: var(--panel-hi); color: var(--ink); }
.drawer-body { flex: 1; overflow-y: auto; padding: 16px; }

/* Действие формы в шторке — закреплённым подвалом, а не последней строкой
   прокручиваемого тела. Замерено 04.09: «Сохранить профиль» имел `bottom: 930`
   при окне 917 — на 13 px за краем, и тело прятало ещё 29 px. Это ЕДИНСТВЕННОЕ
   действие формы: до него надо было доскроллить, а выглядело оно обрезанным
   пополам. Форма растёт (16 полей у профиля маскировки), поэтому убавить
   отступы — не лечение: на окне пониже уедет снова. */
.drawer-body .create-actions {
    position: sticky;
    bottom: 0;
    z-index: 1;
    /* Отрицательные поля — чтобы подвал шёл во всю ширину тела и перекрывал
       его собственные отступы, а не висел прямоугольником посреди них. */
    margin: 14px -16px -16px;
    padding: 12px 16px;
    background: var(--panel);
    border-top: 1px solid var(--line);
}
.drawer-lead { margin: 0; color: var(--ink-dim); font-size: 12px; line-height: 1.55; }

/* ========================================================================
   Устройства: стена живых экранов.
   ===================================================================== */

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

/* Карточка целиком ведёт на устройство: отдельная кнопка «Открыть экран»
   занимала строку ради того, на что и так хочется нажать. Ссылка настоящая —
   её растягивает псевдоэлемент имени, поэтому работают и клавиатура, и
   «открыть в новой вкладке». Меню и кнопки подняты над этим слоем. */
/* Компактнее: снимок уже, отступы и зазор меньше. Карточка — витрина инстанса,
   а не страница о нём: чем больше их влезает в экран без прокрутки, тем быстрее
   находится нужный. Снимок при этом остаётся узнаваемым. */
.unit {
    position: relative;
    display: grid;
    grid-template-columns: 76px minmax(0, 1fr);
    gap: 11px;
    padding: 10px;
    background: var(--panel);
    border: 1px solid var(--line);
    border-radius: var(--r-lg);
    box-shadow: var(--shadow-sm);
    transition: border-color 0.15s, background 0.15s, box-shadow 0.15s, transform 0.15s;
}

/* Наведение приподнимает карточку — тактильный отклик на то, что она целиком
   ведёт на устройство. Мягкая глубина, не «объём ради объёма». */
.unit:hover {
    border-color: var(--line-hi);
    background: var(--panel-hi);
    box-shadow: var(--shadow);
    transform: translateY(-1px);
}
.unit.is-on { border-color: var(--accent-line); }
.unit:has(.unit-name:focus-visible) { border-color: var(--signal); }

.unit-name::after {
    content: "";
    position: absolute;
    inset: 0;
    border-radius: var(--r-lg);
}

.unit .menu,
.unit-foot { position: relative; z-index: 1; }

/* Живой снимок экрана — то, ради чего сюда заходят. Пропорции берём из
   профиля аппарата, поэтому плитка не «прыгает», пока снимок не пришёл. */
.unit-shot {
    position: relative;
    align-self: start;
    border: 1px solid var(--line-hi);
    border-radius: 5px;
    background: var(--shot-bg);
    overflow: hidden;
}

/* Высоту карточки задаёт снимок (портретный аппарат — высокий), поэтому не
   режем его, а ЗАПОЛНЯЕМ фактами рядом: пустота справа от экрана ничего не
   сообщала, а место занимала. */
.unit-shot { max-height: 178px; }
.unit-shot img { display: block; width: 100%; height: 100%; object-fit: cover; object-position: top; }

.unit-shot-off {
    display: grid;
    place-items: center;
    height: 100%;
    color: var(--line-hi);
}

.unit-shot-off svg { width: 24px; height: 24px; fill: none; stroke: currentColor; stroke-width: 1.4; }

.unit-body { display: flex; flex-direction: column; min-width: 0; }
.unit-top { display: flex; align-items: center; gap: 8px; }

/* Личность: сверху та, которой устройство притворяется (гротеск, крупно),
   снизу — та, что есть на самом деле (моно, приглушённо). Ради этой пары
   панель и существует, поэтому она — главный элемент карточки. */
.unit-name {
    color: var(--ink);
    font-size: 14px;
    font-weight: 600;
    letter-spacing: -0.01em;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.unit-name:hover { color: var(--signal); }

.unit-real {
    margin-top: 1px;
    color: var(--ink-mute);
    font-family: var(--data);
    font-size: 11px;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* Две колонки: фактов стало вдвое больше, и в строчку они превращались в
   ленту, где не найти нужный. */
/* Факты — ровными колонками: метка (узкая, приглушённая) слева, значение
   (моно, ярче) справа, и так двумя парами в ряд. Прежде каждая ячейка
   right-align'ила своё значение независимо — середина шла «косо». */
.unit-facts {
    display: grid;
    /* Метка · значение по одной паре в ряд: значения показываем ПОЛНОСТЬЮ (в две
       пары они обрезались). Узкая колонка меток фиксирована — значения выстроены
       ровным столбцом (ровная спецификация, а не «косо» и не «обрезано»). */
    grid-template-columns: 58px minmax(0, 1fr);
    gap: 2px 8px;
    margin: 7px 0 0;
    align-items: baseline;
}

.unit-facts dt {
    color: var(--ink-mute);
    font-size: 11px;
    white-space: nowrap;
}

.unit-facts dd {
    margin: 0;
    color: var(--ink-dim);
    font-family: var(--data);
    font-size: 11px;
    font-weight: 500;
    /* Идентификаторы (серийник 14 знаков, IMEI 15 цифр) НЕ рвём посреди слова:
       номер, разорванный на две строки, перестаёт читаться как номер и его
       нельзя выделить одним движением. Ширину под них даёт колонка сетки. */
    overflow-wrap: normal;
    word-break: normal;
}

/* Значение-состояние, а не данные: «выключен», «замеряется» читаются тише
   серийника и IP, а «не работает» обязано бросаться в глаза — инстанс с таким
   прокси либо без интернета, либо светит IP хоста. */
.unit-facts .fact-mute { color: var(--ink-mute); font-family: var(--ui); }
.unit-facts .fact-fault { color: var(--fault); font-family: var(--ui); }

.unit-blank {
    margin: 9px 0 0;
    color: var(--ink-mute);
    font-size: 11px;
    line-height: 1.45;
}

.unit-foot {
    display: flex;
    align-items: center;
    gap: 8px;
    margin-top: auto;
    padding-top: 8px;
}

/* Лента состояния: загрузка · маскировка · сеть одной строкой, а не тремя
   разнородными плашками вперемешку с кнопками. */
.strip { display: flex; align-items: center; flex-wrap: wrap; gap: 4px 12px; }
.unit .strip { margin-top: 9px; }
.unit .strip:empty { display: none; }

.dot {
    width: 6px;
    height: 6px;
    flex: none;
    border-radius: 50%;
    background: var(--ink-mute);
}

.dot-on { background: var(--live); box-shadow: 0 0 0 3px var(--live-bed); }

.blank {
    display: grid;
    place-items: center;
    gap: 6px;
    padding: 56px 20px;
    text-align: center;
}

.blank-title { margin: 0; color: var(--ink); font-size: 14px; font-weight: 600; }
.blank p { margin: 0; max-width: 430px; color: var(--ink-mute); font-size: 12px; }

/* ========================================================================
   Страница устройства
   ===================================================================== */

/* Экран слева фиксированной группой, всё остальное — во всю оставшуюся ширину.
   Прежде страница центрировала группу `[рейлы+экран | 420px]`, и на широком
   мониторе по краям висели пустые поля, а вся личность и управление ютились
   в узкой колонке 420px. Теперь правая зона тянется (`minmax(0,1fr)`). */
.devpage {
    display: grid;
    /* Правый блок (панель разделов) ограничен по ширине, а не тянется на весь
       остаток: раньше он растекался на всю ширину и карточки стояли с большими
       пустотами. Освободившееся место уходит экрану — он становится крупнее. */
    grid-template-columns: minmax(0, 1fr) minmax(340px, 600px);
    gap: 16px;
    padding: 16px 20px;
    align-items: start;
    /* Ровно под вьюпорт и без собственного переполнения: страница НЕ прокручивается
       (был лишний «мини-скролл» всей страницы поверх скролла панели). Прокручивается
       только панель справа. */
    height: calc(100dvh - var(--bar));
    overflow: hidden;
}

/* Экран и оба рейла — одна группа по центру. Отдельными колонками сетки
   левый рейл прижимался к краю страницы и оказывался в полуметре от
   экрана, к которому относится. */
.deck {
    display: flex;
    align-items: flex-start;
    justify-content: center;
    gap: 14px;
    /* Экран остаётся на месте, пока правый блок прокручивается со всей страницей
       (нет вложенного скролла у панели). */
    position: sticky;
    top: calc(var(--bar) + 16px);
    align-self: start;
}

/* Шапка устройства: машинные факты разделены хайрлайнами, а не точками —
   прежняя строка читалась как одна длинная каша. */
.devbar { display: flex; align-items: center; gap: 10px; min-width: 0; }

/* Бургер и «назад» слева — одной сегментной группой (общий контур, разделители),
   как группа иконок действий справа: единый стиль шапки, а не три коробочки. */
.devbar .navgroup { flex: none; }
.devbar .navgroup .back,
.devbar .navgroup .nav-toggle {
    width: 34px;
    height: 32px;
    background: transparent;
    border: 0;
    border-left: 1px solid var(--line);
    border-radius: 0;
    color: var(--ink-dim);
    padding: 0;
}
.devbar .navgroup > :first-child { border-left: 0; }
.devbar .navgroup .back { display: grid; place-items: center; }
.devbar .navgroup .back:hover,
.devbar .navgroup .nav-toggle:hover { background: var(--btn-hover-bg); color: var(--ink); }
.devbar .back svg { width: 15px; height: 15px; fill: none; stroke: currentColor; stroke-width: 1.7; }
/* В сегментной группе шапки устройства бургер чуть мельче — вровень со стрелкой «назад». */
.devbar .navgroup .nav-toggle svg { width: 16px; height: 16px; }

/* Имя инстанса — в рамке (пилюле): отделяет его от кнопок и читается как объект;
   зелёная рамка = работает (см. .dev-name.dev-on). */
.dev-head {
    margin: 0;
    font-size: 14px;
    font-weight: 600;
    padding: 4px 10px;
    border: 1px solid var(--line-hi);
    border-radius: var(--r);
    background: var(--panel-hi);
}

.facts { display: flex; align-items: center; min-width: 0; overflow: hidden; }

.fact {
    display: flex;
    flex-direction: column;
    gap: 1px;
    padding: 0 12px;
    border-left: 1px solid var(--line);
    white-space: nowrap;
}

.fact:first-child { border-left: 0; padding-left: 0; }
.fact .k { color: var(--ink-mute); font-size: 10px; letter-spacing: 0.07em; text-transform: uppercase; }
.fact .v { color: var(--ink-dim); font-family: var(--data); font-size: 11px; }

/* --- сцена с экраном --- */

.stage { display: grid; place-items: start center; }

/* Экран — «герой» страницы: высоту даём во весь экран (минус шапка), ширина
   следует за aspect-ratio модели. Без явной высоты aspect-ratio-бокс с
   absolute-видео схлопывался и держался лишь на пропорции контейнера. */
.phone-screen {
    position: relative;
    height: calc(100dvh - var(--bar) - 40px);
    width: auto;
    background: #000;
    border: 1px solid var(--line-hi);
    border-radius: 14px;
    overflow: hidden;
    touch-action: none;
    cursor: crosshair;
}

.phone-screen:focus-visible { outline: 2px solid var(--signal); outline-offset: 3px; }

/* `canvas` здесь наравне с видео: аппаратный путь рисует кадры в него.
   Без этого правила холст оставался без `position/top/left`, а `transform:
   translate(-50%,-50%)` из раскладки сдвигал его на четверть — на странице
   была видна четверть картинки в углу пустого поля. */
.phone-screen video,
.phone-screen canvas,
.phone-screen img,
.phone-screen iframe {
    display: block;
    position: absolute;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
    border: 0;
}

.phone-screen iframe { width: 100%; height: 100%; }

/* Снимок вписываем в рамку целиком. Без этого он рендерился в натуральную
   величину, и когда его пропорции расходились с видео (стоковый портретный
   кадр при повёрнутом экране), он торчал из-под живого видео и читался как
   второй, маленький экран рядом.

   Размеры задаёт скрипт (`разложить`): при повёрнутом экране стороны меняются
   местами и элемент разворачивается — эмулятор в этом случае кладёт картинку в
   портретный кадр НА БОК. Поэтому здесь только вписывание, без 100%×100%:
   иначе правило перебивало бы расчёт и картинка растягивалась. */
.phone-screen img { object-fit: contain; }

.phone-overlay {
    position: absolute;
    inset: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    background: rgba(4, 6, 9, 0.72);
    color: var(--ink-dim);
    font-size: 12px;
}

/* --- рейлы кнопок --- */

.railwrap { position: relative; }

.rail {
    display: flex;
    flex-direction: column;
    gap: 2px;
    padding: 6px;
    /* Светлее фона (был почти как страница — иконки «терялись на фоне»): рейл
       читается как отдельная панель инструментов. */
    background: var(--panel-hi);
    border: 1px solid var(--line-hi);
    border-radius: var(--r-lg);
    box-shadow: var(--shadow-sm);
}

.rail-sep { height: 1px; margin: 4px 6px; background: var(--line); }

.iconbtn {
    display: grid;
    place-items: center;
    width: 36px;
    height: 36px;
    border: 0;
    border-radius: var(--r);
    background: transparent;
    color: var(--ink-dim);
    cursor: pointer;
    transition: background 0.12s, color 0.12s;
}

.iconbtn:hover { background: var(--panel-hi); color: var(--ink); }
.iconbtn[aria-pressed="true"], .iconbtn.is-on { background: var(--signal-bed); color: var(--signal); }
/* Пуск и удаление среди иконок шапки — цветом при наведении, не заливкой. */
.iconbtn-primary { color: var(--signal); }
.iconbtn-primary:hover { background: var(--signal-bed); color: var(--signal); }
.iconbtn-danger:hover { background: var(--fault-bed); color: var(--fault); }

/* Действия в шапке устройства — одной сегментной группой: общий контур и фон,
   иконки внутри разделены тонкими линиями (как панель инструментов), а не
   висят отдельными глифами. */
.icon-actions {
    display: flex;
    align-items: center;
    gap: 0;
    border: 1px solid var(--line);
    border-radius: var(--r);
    overflow: hidden;
    background: var(--panel-hi);
}
.icon-actions .iconbtn {
    width: 34px;
    height: 32px;
    border-radius: 0;
    border-left: 1px solid var(--line);
}
.icon-actions .iconbtn:first-child { border-left: 0; }
.icon-actions .iconbtn:hover { background: var(--btn-hover-bg); }
.icon-actions .iconbtn-primary { color: var(--signal); }
.icon-actions .iconbtn-danger:hover { background: var(--fault-bed); color: var(--fault); }

/* Имя ИНСТАНСА (fleet1) кодирует состояние цветом (см. шаблон шапки): зелёное —
   работает, приглушённое — остановлено. Так статус не занимает отдельный текст.
   Титул (что устройство изображает) остаётся нейтральным. */
.dev-name.dev-on { color: var(--live); border-color: var(--live); }
.dev-name.dev-off { color: var(--ink-mute); }

/* Блок кода с копированием значком в правом верхнем углу (Appium capabilities и
   т.п.) — открыто, без сворачивания. */
.codeblock { margin-top: 6px; }
.codeblock-head { display: flex; align-items: center; justify-content: space-between; margin-bottom: 4px; }
.code-cap { font-size: 10px; letter-spacing: 0.08em; text-transform: uppercase; color: var(--ink-mute); }
.code-copy { width: 28px; height: 28px; }
.codeblock pre { margin: 0; }
/* Тот же заголовок, но выпадающим списком (переключатель устройств): без рамки
   и заливки, с каретом-стрелкой — читается как заголовок, а работает как select. */
select.dev-pick {
    padding: 2px 20px 2px 4px;
    border: 0;
    background: transparent
        url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='10' height='10' viewBox='0 0 24 24' fill='none' stroke='%23667082' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'><path d='M6 9l6 6 6-6'/></svg>")
        no-repeat right 2px center;
    font-family: var(--ui);
    cursor: pointer;
    appearance: none;
    -webkit-appearance: none;
}
select.dev-pick:hover { color: var(--ink); }
/* span-вариант (когда нет соседей-переключателя) — тот же пилюльный вид. */
span.dev-name {
    margin-left: 4px;
    padding: 3px 8px;
    background: var(--panel-hi);
    border: 1px solid var(--line-hi);
    border-radius: var(--r);
    font: 500 12px/1.4 var(--data);
}

.iconbtn svg {
    width: 20px;
    height: 20px;
    fill: none;
    stroke: currentColor;
    stroke-width: 1.6;
    stroke-linecap: round;
    stroke-linejoin: round;
}

/* Карточка управления встаёт вровень со своей кнопкой — так видно, к чему
   она относится, и панель не растёт вниз на всю высоту форм. */
.pop {
    position: absolute;
    right: calc(100% + 8px);
    z-index: 40;
    width: 224px;
    padding: 10px;
    background: var(--panel-hi);
    border: 1px solid var(--line-hi);
    border-radius: var(--r-lg);
    box-shadow: 0 14px 34px rgba(0, 0, 0, 0.55);
}

.pop h4 { margin: 0 0 7px; font-size: 12px; }
.ctl { display: flex; flex-direction: column; gap: 7px; }
.ctl .row { display: flex; flex-direction: column; gap: 3px; font-size: 11px; color: var(--ink-mute); }
.ctl .row.check { flex-direction: row; align-items: center; gap: 7px; }
.ctl .row-inline { display: flex; align-items: center; gap: 9px; }
.ctl .row-inline input[type="range"] { flex: 1; min-width: 0; }
.ctl .row output { font-family: var(--data); font-size: 11px; color: var(--ink-dim); }
.ctl .row-actions { display: flex; gap: 5px; flex-wrap: wrap; }
/* Кнопки во всплывашках компактнее — набор действий (звонок и т.п.) влезал в
   три ряда и карточка выходила громоздкой. */
.pop .btn-small { padding: 4px 8px; font-size: 11px; }
.pop input[type="text"], .pop input[type="number"], .pop textarea, .pop select { padding: 5px 7px; }

.pop-state {
    min-height: 16px;
    margin: 8px 0 0;
    color: var(--signal);
    font-family: var(--data);
    font-size: 11px;
    text-align: center;
}

/* --- боковая панель сведений --- */

.aside {
    background: var(--panel);
    border: 1px solid var(--line);
    border-radius: var(--r-lg);
    /* Панель — колонка: закреплённая строка вкладок сверху + прокручиваемое тело
       (.aside-scroll). Скролл живёт у тела, а не у всей панели, поэтому полоса
       начинается ПОД «шапкой» вкладок, а не идёт вдоль неё. Сама страница не
       прокручивается (родитель .devpage фиксирован под вьюпорт). */
    align-self: stretch;
    display: flex;
    flex-direction: column;
    max-height: calc(100dvh - var(--bar) - 32px);
    overflow: hidden;
}
.aside-scroll { flex: 1; min-height: 0; overflow-y: auto; }

/* Навигация по разделам единого скролла (не вкладки): липкая строка-якоря
   наверху панели. Клик — плавный скролл к разделу; ничего не прячется. */
.tabs, .secnav {
    display: flex;
    gap: 2px;
    padding: 6px;
    border-bottom: 1px solid var(--line);
    overflow-x: auto;
    scrollbar-width: none;
    /* Фиксированная «шапка» панели-колонки (тело прокручивается ниже, см.
       .aside-scroll) — сама не входит в скролл, поэтому полоса прокрутки её не
       задевает. */
    flex: none;
    background: var(--panel);
    border-top-left-radius: var(--r-lg);
    border-top-right-radius: var(--r-lg);
}
.secnav {
    position: sticky;
    top: 0;
    z-index: 10;
    background: var(--bg);
}

.tabs::-webkit-scrollbar, .secnav::-webkit-scrollbar { display: none; }

/* Заголовок раздела в едином скролле. */
.sechead {
    margin: 0 0 12px;
    font-size: 14px;
    font-weight: 600;
    color: var(--ink);
}
.tab-body + .tab-body { margin-top: 22px; padding-top: 20px; border-top: 1px solid var(--line); }

.tab {
    padding: 5px 10px;
    border: 0;
    border-radius: var(--r);
    background: transparent;
    color: var(--ink-mute);
    font: 500 12px/1.4 var(--ui);
    white-space: nowrap;
    cursor: pointer;
}

.tab:hover { background: var(--panel-hi); color: var(--ink); }
.tab-active { background: var(--panel-hi); color: var(--signal); }

.tab-body { padding: 14px; }

/* Переключатель вида экрана: две половинки одного тумблера, а не две кнопки
   рядом — так видно, что это выбор одного из двух, а не два действия. */
.seg {
    display: inline-flex;
    padding: 2px;
    background: var(--bg);
    border: 1px solid var(--line-hi);
    border-radius: var(--r);
}

.segbtn {
    padding: 3px 9px;
    border: 0;
    border-radius: 4px;
    background: transparent;
    color: var(--ink-mute);
    font: 500 11px/1.5 var(--ui);
    white-space: nowrap;
    cursor: pointer;
}

.segbtn:hover { color: var(--ink); }
.segbtn.is-on { background: var(--signal-bed); color: var(--signal); }

/* Настройки трансляции: шестерёнка в рейле инструментов экрана + панель,
   раскрывающаяся ВЛЕВО (к экрану), т.к. рейл узкий и стоит справа от экрана. */
.stream-cfg { position: relative; }
.stream-cfg-pop {
    position: absolute;
    right: calc(100% + 8px);
    top: 0;
    z-index: 40;
    width: 210px;
    padding: 12px;
    display: flex;
    flex-direction: column;
    gap: 10px;
    background: var(--panel-hi);
    border: 1px solid var(--line-hi);
    border-radius: var(--r);
    box-shadow: var(--shadow-lg);
}
.stream-cfg-pop label {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 10px;
    font-size: 12px;
    color: var(--ink-dim);
}
.stream-cfg-pop select { flex: none; width: 120px; }

/* Мгновенная понятная подпись для кнопок-иконок вместо медленного нативного
   tooltip: рейлы устройства/экрана и меню — сплошь иконки, и что делает каждая,
   иначе понятно только по наведению спустя секунду. Подпись строится из
   `data-tip` (JS переносит туда `title`, чтобы не двоить с нативным). */
.cf-tip {
    position: fixed;
    z-index: 200;
    padding: 4px 8px;
    background: var(--panel-hi);
    color: var(--ink);
    border: 1px solid var(--line-hi);
    border-radius: 6px;
    box-shadow: var(--shadow);
    font-size: 11px;
    line-height: 1.3;
    max-width: 240px;
    white-space: nowrap;
    pointer-events: none;
    opacity: 0;
    transform: translateY(2px);
    transition: opacity 0.1s ease, transform 0.1s ease;
}
.cf-tip.is-in { opacity: 1; transform: none; }

/* Идентификаторы группами: имя слева, машинное значение справа. */
.idgroup + .idgroup { margin-top: 14px; }

.idgroup h4 {
    margin: 0 0 6px;
    font-size: 10px;
    font-weight: 600;
    letter-spacing: 0.09em;
    text-transform: uppercase;
    color: var(--ink-mute);
}

.idlist { margin: 0; display: flex; flex-direction: column; }

/* Строка просторнее прежних 3 px: «Обзор» кончался на 300 px выше дна панели,
   и эта высота ничем не занята — а значения (серийник, IMEI, ICCID) сверяют
   посимвольно, и им теснота мешает. Плотность здесь не самоцель: карточки
   по-прежнему обязаны помещаться в экран без прокрутки. */
/* Строка без собственной линейки: линию несёт заголовок группы, а тридцать
   три волосяные черты подряд снова расчерчивали панель на прямоугольники —
   ровно то, от чего избавились, убрав рамки карточек. Читаемость держит
   выравнивание колонок и просвет. */
.idlist > div {
    display: grid;
    grid-template-columns: 42% 1fr;
    gap: 10px;
    padding: 6px 0;
}
/* Длинная подпись — над значением (одна колонка): в узкой панели она не
   переносится по слогам и не наезжает на моно-значение. Ставит макрос `facts`
   строкам с подписью 11+ символов. */
.idlist > div.idrow-stack {
    grid-template-columns: 1fr;
    gap: 2px;
}
/* Крупнее прежних 11px: «Обзор» — то, ради чего на страницу и заходят, а
   значения (серийник, IMEI, ICCID) читают глазами и сверяют посимвольно.
   Место под это освободилось, когда вкладки стали настоящими и разделы
   перестали лежать одной простынёй. */
.idlist dt { color: var(--ink-mute); font-size: 12px; }
/* Подсказка у названия строки — пунктиром. Раньше о ней знал только тот, кто
   случайно навёл мышь: так молчали и «загрузчик до устройства не доезжает», и
   свёрнутые синонимы («то же значение: имя продукта, плата»). */
.idlist dt.has-hint {
    text-decoration: underline dotted var(--line-hi);
    text-underline-offset: 3px;
    cursor: help;
}
/* Перенос — по дефисам, и только потом «где придётся». `break-all` рвал
   значение в произвольном месте (`cloudripper-16.0-13` / `913425`), а его
   сверяют посимвольно; `anywhere` оставляет естественный разрыв по дефису и
   рвёт насильно лишь то, где разрыва нет вовсе (ICCID, IMEI). */
.idlist dd {
    margin: 0;
    color: var(--ink);
    font-family: var(--data);
    font-size: 12px;
    word-break: normal;
    overflow-wrap: anywhere;
}

.kv { margin: 0; display: grid; grid-template-columns: auto 1fr; gap: 4px 12px; }
.kv dt { color: var(--ink-mute); font-size: 11px; }
/* Как и в `.idlist`: `break-all` рвал путь в произвольном месте
   (`…/deploy/avd_p` / `laystore_root/…`), а пути на «Настройках» копируют и
   сверяют глазами. `anywhere` переносит по `/` и `-`, насильно — только то,
   где точки переноса нет вовсе. */
.kv dd {
    margin: 0;
    color: var(--ink-dim);
    font-family: var(--data);
    font-size: 11px;
    word-break: normal;
    overflow-wrap: anywhere;
}

details.more { margin-top: 12px; padding-top: 10px; border-top: 1px solid var(--line); }

details.more > summary {
    list-style: none;
    cursor: pointer;
    color: var(--ink-dim);
    font-size: 12px;
}

details.more > summary::-webkit-details-marker { display: none; }
details.more > summary::before { content: "▸ "; color: var(--ink-mute); }
details.more[open] > summary::before { content: "▾ "; }
details.more > *:not(summary) { margin-top: 8px; }

/* Поле ввода ограничено по ширине. На «Настройках» секции идут во всю
   страницу, и «URL валидного keybox» растягивался на 1780 px: строка ввода
   шириной с экран не помогает её читать, а глазу приходится ехать через
   полстраницы от подписи к значению. */
.pf { display: flex; flex-direction: column; gap: 5px; font-size: 12px; color: var(--ink-dim); }
.pf > input, .pf > select, .pf > textarea { max-width: 560px; }
.pf-hint { max-width: 560px; }
.pf-hint { font-size: 11px; line-height: 1.45; color: var(--ink-mute); }
/* Строка-подпись поля с бейджем состояния справа (например «переопределено»). */
.pf-row { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }

/* --- консоль, логкат, вывод --- */

.shell-form { display: flex; gap: 6px; }
.shell-form input { flex: 1; min-width: 0; font-family: var(--data); }
.shell-presets { display: flex; flex-wrap: wrap; gap: 5px; margin-top: 8px; }
.shell-presets .btn { font-family: var(--data); }

.shell-output, pre.code {
    margin: 10px 0 0;
    padding: 10px;
    max-height: 340px;
    overflow: auto;
    background: var(--bg);
    border: 1px solid var(--line);
    border-radius: var(--r);
    color: var(--ink-dim);
    font-family: var(--data);
    font-size: 11px;
    line-height: 1.55;
    white-space: pre-wrap;
    word-break: break-word;
}

/* --- файлы на устройстве --- */

/* Строки, а не коробки: пять файлов в рамках со скруглением читались как пять
   плашек, хотя это один список одинаковых вещей. Граница между ними нужна
   ровно одна. */
.filelist { display: flex; flex-direction: column; }

.fileitem {
    display: grid;
    grid-template-columns: auto minmax(0, 1fr) auto;
    align-items: center;
    gap: 10px;
    padding: 9px 4px;
    border-bottom: 1px solid var(--line);
}

.filelist .fileitem:last-child { border-bottom: 0; }

.fileicon { display: grid; color: var(--ink-mute); }
.fileitem.apk .fileicon { color: var(--live); }
.fileicon svg { width: 17px; height: 17px; fill: none; stroke: currentColor; stroke-width: 1.5; }
.filemeta { display: flex; flex-direction: column; min-width: 0; }
.filename { font-family: var(--data); font-size: 11px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.filesize { color: var(--ink-mute); font-family: var(--data); font-size: 10px; }
.fileitem .row-actions { display: flex; gap: 5px; }

/* ========================================================================
   Образы: активная сборка, каталог моделей, версии
   ===================================================================== */

.build-now {
    display: grid;
    grid-template-columns: minmax(0, 1fr) auto;
    gap: 16px;
    align-items: center;
    padding: 16px;
    background: var(--panel);
    border: 1px solid var(--line);
    border-left: 2px solid var(--signal);
    border-radius: var(--r-lg);
}

.build-now h2 { font-size: 18px; letter-spacing: -0.01em; }
.build-now .specs { display: flex; flex-wrap: wrap; gap: 3px 16px; margin-top: 9px; }
.launch-inline { display: flex; align-items: center; gap: 6px; }
.launch-inline input { width: 52px; text-align: center; font-family: var(--data); }

.brand-chips { display: flex; flex-wrap: wrap; gap: 5px; margin-bottom: 10px; }

.brand-chip {
    padding: 4px 10px;
    background: transparent;
    border: 1px solid var(--line-hi);
    border-radius: 12px;
    color: var(--ink-dim);
    font: 500 11px/1.4 var(--ui);
    cursor: pointer;
}

.brand-chip:hover { background: var(--panel-hi); color: var(--ink); }
.brand-chip.is-active { background: var(--signal-bed); border-color: var(--signal); color: var(--signal); }

.filter-input { width: 100%; margin-bottom: 12px; }

/* auto-fit (не auto-fill): карточки растягиваются на всю ширину без пустых
   колонок-«хвостов» справа. Меньше min — плотнее каталог из полусотни моделей. */
.profile-cards {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(258px, 1fr));
    gap: 8px;
}

.drawer-cards { grid-template-columns: 1fr; }

.profile-card {
    display: flex;
    flex-direction: column;
    gap: 7px;
    padding: 10px 11px;
    background: var(--panel);
    border: 1px solid var(--line);
    border-radius: var(--r-lg);
}

.drawer-cards .profile-card { background: var(--bg); }
.profile-card.is-active { border-color: var(--signal); }
.pc-head { display: flex; align-items: center; gap: 8px; }
.pc-title { font-size: 13px; font-weight: 600; }
.pc-badges { margin-left: auto; display: flex; align-items: center; gap: 8px; }

.pc-specs { display: flex; flex-wrap: wrap; gap: 3px 14px; }
.pc-specs span { color: var(--ink-dim); font-family: var(--data); font-size: 11px; }

.pc-specs i {
    margin-right: 5px;
    color: var(--ink-mute);
    font-family: var(--ui);
    font-size: 10px;
    font-style: normal;
    letter-spacing: 0.07em;
    text-transform: uppercase;
}

/* Подвал карточки — колонкой: статус строкой, действия ниже с переносом. В одну
   строку (статус + «Собрать» + счётчик + «Собрать и запустить») кнопки не влезали
   в узкую карточку и обрезались за краем. */
.pc-foot {
    display: flex;
    flex-direction: column;
    align-items: stretch;
    gap: 8px;
    margin-top: auto;
    padding-top: 9px;
    border-top: 1px solid var(--line);
}
.pc-actions { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; }
.pc-actions form { display: contents; }
.spawn-form { display: flex; align-items: center; gap: 6px; }
.spawn-form input { width: 44px; text-align: center; font-family: var(--data); }

/* --- пресеты аппаратов --- */

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

.preset {
    padding: 12px;
    background: var(--panel);
    border: 1px solid var(--line);
    border-radius: var(--r-lg);
}

.preset-top { display: flex; align-items: center; gap: 8px; margin-bottom: 8px; }
.preset-title { font-size: 13px; font-weight: 600; }
.preset-top .menu, .preset-top .linkcopy { margin-left: auto; }
.linkcopy { font-size: 11px; }

.preset-facts { margin: 0; display: flex; flex-direction: column; gap: 3px; }
.preset-facts > div { display: grid; grid-template-columns: 40% 1fr; gap: 10px; }
.preset-facts dt { color: var(--ink-mute); font-size: 11px; }
.preset-facts dd { margin: 0; color: var(--ink-dim); font-family: var(--data); font-size: 11px; }

/* ========================================================================
   Задачи, настройки, вход
   ===================================================================== */

.task-filter { display: flex; flex-wrap: wrap; gap: 6px; margin-bottom: 12px; }
.tfchip {
    padding: 4px 12px;
    background: var(--panel);
    border: 1px solid var(--line-hi);
    border-radius: 20px;
    color: var(--ink-dim);
    font: 500 12px/1.4 var(--ui);
    cursor: pointer;
}
.tfchip:hover { color: var(--ink); }
.tfchip.on { background: var(--signal-bed); border-color: var(--signal); color: var(--signal); }
.task-log summary { cursor: pointer; color: var(--ink-mute); font-size: 11px; }
.task-log[open] summary { color: var(--ink-dim); margin-bottom: 6px; }

/* Список, а не стопка коробок. Одиннадцать задач подряд, каждая в рамке с
   тенью и скруглением, читались как одиннадцать одинаковых плашек: состояние
   («готово») терялось, потому что кричала рамка, а не оно.

   Ширина ограничена намеренно. Мета («запущено 16:31:59 · заняло 1 мин 37 с»)
   стоит у правого края, чтобы времена шли колонкой и сравнивались взглядом, —
   но на окне 1907 px между названием и временем зияло ~1200 px, и строка
   переставала читаться как одно целое. Пустое место СНАРУЖИ списка читается
   как поле, внутри строки — как сломанная вёрстка. */
.tasklist { display: flex; flex-direction: column; max-width: 1000px; }

.task {
    padding: 10px 12px;
    /* Полоска слева несёт состояние — это единственное, что от «коробки»
       осталось, и единственное, что здесь действительно цвет. */
    border-left: 2px solid var(--line-hi);
    border-bottom: 1px solid var(--line);
    transition: background 0.15s, border-color 0.15s;
}

.tasklist .task:last-child { border-bottom: 0; }
.task:hover { background: var(--panel-hi); }
.task-running { border-left-color: var(--signal); }
.task-done { border-left-color: var(--live); }
.task-failed { border-left-color: var(--fault); }
.task-head { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.task-title { font-size: 13px; font-weight: 600; }
/* Мета задачи: «запущено 14:23:01 · заняло 43 с». Слова — гротеском (что сказал
   человек), время и длительность — моно (что измерила машина). Держим единым
   блоком у правого края, чтобы список читался колонкой. */
.task-meta {
    display: inline-flex;
    align-items: baseline;
    gap: 6px;
    font-size: 12px;
    color: var(--ink-mute);
    white-space: nowrap;
}
.task-meta .mono { color: var(--ink-dim); }
.task-sep { color: var(--line-hi); }
.task details { margin-top: 8px; }
.task summary { cursor: pointer; color: var(--ink-mute); font-family: var(--data); font-size: 11px; }

/* Сетка, а не `columns`. Масонри резало поток по высоте: карточка попадала в
   колонку по остатку места, а не по порядку, и четыре блока кончались на
   748/940/968/745 px. Обычная сетка держит порядок чтения слева направо и
   выравнивает верх каждого ряда — разная длина справочных блоков после этого
   читается как разная длина, а не как сбой вёрстки. */
.settings-grid {
    display: grid;
    /* 440, а не 340: при 340 на окне 1907 px влезали ЧЕТЫРЕ колонки, а
       справочных блоков пять — пятый оставался один под первой колонкой, и
       рядом с ним зияли три пустые ячейки. При 440 колонок три, хвост сетки
       ровный (3+2), а главное содержимое этих блоков — длинные пути — влезает
       без переносов через строку. */
    /* `min(440px, 100%)`: на узком экране колонка НЕ распирается до 440px
       (иначе карточка вылезала за 400px — поймано Playwright'ом), а сжимается
       до ширины экрана. На широком поведение прежнее (три колонки по 440). */
    grid-template-columns: repeat(auto-fill, minmax(min(440px, 100%), 1fr));
    gap: 12px;
    align-items: start;
}

.settings-list { margin: 0; display: flex; flex-direction: column; }

.settings-list > div {
    display: grid;
    grid-template-columns: 38% 1fr;
    gap: 12px;
    padding: 6px 0;
    border-bottom: 1px solid var(--line);
}

.settings-list > div:last-child { border-bottom: 0; }
.settings-list dt { color: var(--ink-mute); font-size: 11px; line-height: 1.4; }
.settings-list dd { margin: 0; display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.settings-list dd .mono, .settings-list dd .data {
    color: var(--ink-dim);
    word-break: normal;
    overflow-wrap: anywhere;
}

.service-row { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; }
.service-id { display: flex; flex-direction: column; }
.service-name { font-size: 13px; font-weight: 600; }
.service-actions { margin-left: auto; display: flex; gap: 6px; }

.upload { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }

.enter { display: grid; place-items: center; min-height: 100dvh; padding: 24px; }

.enter form {
    display: flex;
    flex-direction: column;
    gap: 12px;
    width: min(320px, 100%);
    padding: 24px;
    background: var(--panel);
    border: 1px solid var(--line);
    border-radius: var(--r-lg);
}

.enter .side-brand { height: auto; padding: 0 0 6px; border: 0; justify-content: center; }
.enter label { display: flex; flex-direction: column; gap: 5px; font-size: 12px; color: var(--ink-dim); }

/* ========================================================================
   Узкие экраны
   ===================================================================== */

@media (max-width: 1320px) {
    .devpage { grid-template-columns: minmax(0, 1fr) 330px; }
    /* 330 px на панель — это 160 px на колонку: значение переносится посимвольно
       и читать его нечем. Ниже этой ширины карточки идут одной колонкой. */
    .idcards { grid-template-columns: minmax(0, 1fr); }
}

/* Узкий десктоп/планшет (≤1000px): меню — компактный 64px-рейл, а на странице
   устройства панель сведений уходит ПОД экран отдельной строкой, но рейлы
   кнопок ОСТАЮТСЯ по бокам экрана (как на десктопе) — это опрятнее «телефонной»
   раскладки с рейлами внизу. Раскладка одноколоночная, поэтому фиксацию страницы
   под вьюпорт снимаем: прокручивается вся страница целиком, иначе панель сведений
   во второй строке сетки обрезалась бы `overflow:hidden` фиксированной высоты. */
@media (max-width: 1000px) {
    .app { grid-template-columns: 64px minmax(0, 1fr); }
    .side-brand span:last-child, .snav span, .side-foot { display: none; }
    .side-brand, .snav { justify-content: center; }
    .devpage {
        grid-template-columns: 1fr;
        height: auto;
        overflow: visible;
    }
    /* Экран больше не липкий — течёт со страницей, панель сведений идёт следом. */
    .deck { position: static; gap: 8px; }
    /* Панель сведений отдаёт свой внутренний скролл странице: во всю высоту без
       собственной прокрутки, чтобы не появлялось двух вложенных скроллов. */
    .aside { max-height: none; overflow: visible; }
    .aside-scroll { overflow-y: visible; }
    .secnav { position: static; }
    .tab-body { max-height: none; }

    /* Рейлы кнопок остаются вертикальными по бокам экрана, но КОМПАКТНЕЕ: на
       узкой ширине экрану нужно место, поэтому кнопки и отступы рейла меньше
       (`layout()` вычитает фактическую ширину рейлов, так что экран занимает
       ровно остаток). Тап-цель 34px — на грани, но пользователь предпочёл
       боковые рейлы «крупным кнопкам снизу». */
    .rail { padding: 3px; gap: 1px; }
    .rail .iconbtn { width: 34px; height: 34px; }
    .rail .iconbtn svg { width: 18px; height: 18px; }
    .rail-sep { margin: 3px 5px; }

    /* Всплывашки кнопок — по центру экрана карточкой, а не сбоку от рейла: у
       левого рейла всплывашка (`right: 100%`) ушла бы за левый край окна, у
       правого перекрыла бы узкий экран. По центру она видна при любом рейле и не
       вылезает за вьюпорт. */
    .pop {
        position: fixed;
        left: 50%;
        top: 50%;
        right: auto;
        transform: translate(-50%, -50%);
        width: min(320px, calc(100vw - 24px));
        max-height: 80vh;
        overflow-y: auto;
        z-index: 60;
    }
}

@media (max-width: 680px) {
    .page { padding: 14px; }
    .wall { grid-template-columns: 1fr; }
    .field-row, .field-row-3 { grid-template-columns: 1fr; }
    .bar { position: static; height: auto; padding: 10px 14px; flex-wrap: wrap; }
    /* Поясняющий подзаголовок шапки на узком экране прячем: он важен на десктопе,
       но на телефоне отбирал высоту у самой шапки и заворачивался в столбик. */
    .bar-sub { display: none; }
}

/* Проверка маскировки (страница устройства): строки признаков с вердиктом. */
.spoof-checks { margin-top: 8px; display: flex; flex-direction: column; gap: 1px; }
.spoof-row {
    display: grid;
    grid-template-columns: 1.4em 10em 1fr;
    align-items: baseline;
    gap: 8px;
    padding: 2px 0;
    font-family: ui-monospace, "SF Mono", Menlo, monospace;
    font-size: 12px;
}
.spoof-mark { text-align: center; font-weight: 700; }
.spoof-label { color: var(--ink-dim); }
.spoof-val { color: var(--ink); word-break: break-word; }
.spoof-ok   .spoof-mark { color: var(--live); }
.spoof-tell .spoof-mark { color: var(--fault); }
.spoof-tell .spoof-val  { color: var(--fault); }
.spoof-wall .spoof-mark { color: var(--wait); }
.spoof-info .spoof-mark { color: var(--ink-mute); }

/* Расширенное управление устройством: компактная сетка мини-форм. */
.ext-ctls { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; margin-top: 8px; }
.ext-ctls .ctl { border: 1px solid var(--line); border-radius: var(--r); padding: 8px 10px; }
.ext-ctls .ctl h4 { font-size: 12px; margin: 0 0 6px; color: var(--ink-dim); }
.ext-ctls .ctl .row-actions { display: flex; flex-wrap: wrap; gap: 4px; }
.ext-ctls .ctl input[type=range] { width: 100%; }
@media (max-width: 720px) { .ext-ctls { grid-template-columns: 1fr; } }

/* ========================================================================
   Редизайн карточки устройства: личность — карточками, управление — единым
   доком. Правая зона теперь во всю ширину (см. `.devpage`), поэтому и то, и
   другое раскладывается адаптивной сеткой, а не одной узкой колонкой.
   ===================================================================== */

/* Личность: группы (Аппарат/Сборка/SIM/Регион/Экран) — отдельными карточками
   в сетке, а не плоским скролл-списком на всю высоту. */
/* Masonry-раскладка (CSS-колонки), а не grid: группы неравной длины (Аппарат —
   9 строк, Идентификаторы — 1) в grid оставляли под короткими карточками пустые
   провалы. В колонках короткие карточки подтекают друг под друга и заполняют
   высоту, без дыр. */
/* Две ЯВНЫЕ колонки, а не `columns` (masonry). Раскладка по колонкам сама
   решала, где разрезать поток, и делала это по высоте: карточки уезжали не
   туда, где им место по смыслу, колонки кончались на разной высоте (124 px
   разницы на живом профиле), а читались они сверху вниз и только потом
   вправо — порядок, которого никто не ждёт. Теперь состав колонок задан в
   шаблоне: слева — ЧТО это за аппарат, справа — как он выглядит СНАРУЖИ.
   «Обзор» при этом по-прежнему должен помещаться в экран без прокрутки: за
   фактами устройства заходят чаще, чем за чем-либо ещё. */
.idcards {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 22px 20px;
    align-items: start;
}

.idcol { display: flex; flex-direction: column; gap: 22px; min-width: 0; }

/* Набор, которому две колонки не идут: карточек нечётное число, и
   последняя вставала в ряд одна. */
.idcards-single { grid-template-columns: minmax(0, 1fr); }

/* Группа фактов без коробки: рамка, фон и тень вокруг КАЖДОЙ из шести групп
   давали шесть вложенных прямоугольников внутри седьмого (сама панель) — рамка
   рисовала границу там, где её и так видно по пробелу. Осталось то, что
   границу действительно несёт: заголовок с волосяной линией под ним и воздух
   между группами. Плотность строк при этом не тронута — «Обзор» обязан
   помещаться в экран. */
.idcard {
    margin: 0;
    border: 0;
    border-radius: 0;
    background: none;
    box-shadow: none;
    padding: 0;
    min-width: 0;
}

/* Обычный регистр вместо капса: шесть капслоковых плашек подряд читались как
   шесть одинаковых ярлыков, а не как названия разных групп, и разбирать их
   приходилось по буквам. Название набрано тем же кеглем, что и строки, но
   плотнее по цвету — иерархию держит цвет и линия, а не крик. */
.idcard > h4,
.idcard-head > h4 {
    margin: 0;
    font-size: 12px;
    font-weight: 600;
    letter-spacing: 0;
    text-transform: none;
    color: var(--ink-dim);
}

/* Линия под заголовком — единственная граница группы. Тянется на всю ширину
   группы (поэтому на обёртке, а не на самом h4): иначе она обрывалась бы перед
   карандашом правки. */
.idcard > h4,
.idcard-head {
    padding-bottom: 5px;
    margin-bottom: 7px;
    border-bottom: 1px solid var(--line);
}

/* Шапка карточки: название слева, карандаш правки — в правом верхнем углу.
   Правка живёт ЗДЕСЬ же, у своего значения: пока она стояла отдельной вкладкой,
   один и тот же параметр показывался дважды и расходился между копиями. */
.idcard-head {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: 8px;
}
.idcard-head > h4 { flex: 1; min-width: 0; }

.card-edit {
    /* Карандаш заметен только на своей карточке: постоянная яркая иконка в
       каждом углу превратила бы сетку в поле кнопок. */
    flex: none;
    margin: -3px -4px 0 0;
    color: var(--ink-mute);
    opacity: 0.7;
    transition: opacity 0.12s, color 0.12s;
}
.idcard:hover .card-edit, .card-edit:focus-visible { opacity: 1; }
.card-edit:hover { color: var(--signal); opacity: 1; }
.card-edit[aria-pressed="true"] { color: var(--signal); opacity: 1; }

/* Замер С УСТРОЙСТВА в строке факта. Совпало — тихая галочка; разошлось —
   красным и с настоящим значением: расхождение «панель положила одно, устройство
   отдаёт другое» и есть та беда, ради которой замер вообще делается. */
.on-device { font-family: var(--ui); }
.on-device-same { color: var(--live); }
.on-device-diff { display: block; margin-top: 2px; color: var(--fault); }

/* Замер, у которого нет своего поля профиля (сеть, GPU, cpuinfo, native.bridge):
   идёт строкой в ту карточку, где ему место по смыслу. Цвет несёт вердикт —
   отдельной карточки «маскировка», где он читался бы, больше нет. */
.idlist .chk-ok { color: var(--live); }
.idlist .chk-tell { color: var(--fault); }
.idlist .chk-wall { color: var(--wait); }
.idlist .chk-info { color: var(--ink-dim); }

/* Пояснение под фактами карточки: почему значение такое и где оно меняется.
   `text-wrap: pretty` — против висячего слова: в колонке 280 px пояснение
   регулярно оставляло на последней строке одно «в «Образах»». */
.idcard-note {
    margin: 8px 0 0;
    font-size: 11px;
    line-height: 1.45;
    color: var(--ink-mute);
    text-wrap: pretty;
}

/* Форма внутри карточки набрана мельче окружения — она вложена в узкую колонку. */
.idcard [data-card-form] { margin-top: 2px; }
.idcard [data-card-form] .field-hint,
.idcard [data-card-form] .hint { font-size: 11px; }
/* В колонке карточки два поля в ряд не помещаются — ставим их друг под друга.
   Карточка становится выше, но masonry высоту переживает, а обрезанное поле
   ввода — нет. */
.idcard [data-card-form] .field-row { grid-template-columns: 1fr; gap: 10px; }

/* Управление: все команды эмулятора — одним доком карточек, а не вразброс
   (левый рейл + рейл-иконки popup + «Расширенное управление» в табе). */
/* Masonry, как у карточек личности: карточки управления разной высоты (Датчик
   gRPC высокий, Микрофон низкий) в grid оставляли рваные провалы — «разбросано».
   В колонках короткие подтекают под длинные и заполняют высоту. */
.dock { columns: 250px; column-gap: 14px; }

/* Операции над виртуальной машиной — одной строкой под консолью: их шесть,
   нажимают редко, и карточка в колонке дока ставила их столбиком во всю ширину.
   Подпись слева, опасные две отделены разделителем. */
.vmbar {
    display: flex;
    /* Направление задаём ЯВНО: у `.ctl` выше стоит `column`, и без этой строки
       оно доставалось по наследству — кнопки вставали столбиком, хотя правило
       ниже и «сильнее» по порядку: оно просто не объявляло направление. */
    flex-direction: row;
    align-items: center;
    flex-wrap: wrap;
    gap: 6px;
    margin: 12px 0 0;
    padding-top: 10px;
    border-top: 1px solid var(--line);
}
.vmbar-label { color: var(--ink-mute); font-size: 11px; margin-right: 2px; }
/* Черта в ряду кнопок: слева обратимое, справа — то, что не отменить.
   Одна на всю панель: и у ВМ-операций устройства, и у массовых действий
   флота, где «Удалить остановленные» стояло вторым в ряду из пяти. */
.act-sep, .vmbar-sep {
    width: 1px;
    align-self: stretch;
    margin: 2px 4px;
    background: var(--line-hi);
}

.dock .ctl {
    break-inside: avoid;
    margin: 0 0 14px;
    border: 1px solid var(--line);
    border-radius: var(--r-lg);
    background: var(--panel);
    box-shadow: var(--shadow-sm);
    padding: 14px 15px;
}

.dock .ctl h4 {
    margin: 0 0 8px;
    font-size: 12px;
    color: var(--ink);
}

.dock .ctl .row { display: flex; flex-direction: column; gap: 4px; margin-bottom: 8px; }
.dock .ctl .row > span:first-child { color: var(--ink-mute); font-size: 11px; }
.dock .ctl .row.check { flex-direction: row; align-items: center; gap: 8px; }
.dock .ctl .row-inline { display: flex; align-items: center; gap: 8px; }
.dock .ctl .row-actions { display: flex; flex-wrap: wrap; gap: 4px; }
.dock .ctl input[type=range] { width: 100%; }
.dock .ctl input[type=text],
.dock .ctl input[type=number],
.dock .ctl select { width: 100%; }

/* Служебный блок дока: строка статуса + разовые кнопки чтения (статус/камеры),
   на всю ширину сетки под карточками. */
.dock-foot { margin-top: 12px; display: flex; flex-direction: column; gap: 8px; }

/* Вложенный переключатель внутри вкладки (Консоль/Логкат в «Терминале»). */
.subtabs { display: flex; gap: 2px; margin-bottom: 10px; }
.subtab {
    padding: 4px 12px;
    border: 1px solid var(--line);
    border-radius: var(--r);
    background: transparent;
    color: var(--ink-mute);
    font: 500 12px/1.4 var(--ui);
    cursor: pointer;
}
.subtab:hover { background: var(--panel-hi); color: var(--ink); }
.subtab-active { background: var(--panel-hi); color: var(--signal); border-color: var(--line-hi); }

/* `.idcard-edit` и `.overview-foot` убраны вместе со вкладкой «Настройки»:
   формы правки теперь живут внутри своих карточек (см. `.card-edit` выше). */

/* Подзаголовок секции внутри вкладки «Управление» (напр. «Консоль и логкат»). */
.dock-sub {
    margin: 18px 0 10px;
    padding-top: 14px;
    border-top: 1px solid var(--line);
    font-size: 12px;
    color: var(--ink-dim);
}

/* Пошаговая инструкция (Appium «как настроить» и т.п.). */
.howto { margin: 8px 0 0; padding-left: 20px; color: var(--ink-dim); font-size: 12px; line-height: 1.6; }
.howto li { margin-bottom: 6px; }

/* Версии собранных образов — карточки-строки вместо таблицы (в духе остальных
   карточек). Каждая строка: id+заметка · модель/дата/размер · статус · действия. */
.version-cards { display: flex; flex-direction: column; gap: 8px; }
.version-row {
    display: grid;
    grid-template-columns: minmax(0, 2fr) minmax(0, 2fr) auto auto;
    align-items: center;
    gap: 10px 16px;
    padding: 11px 13px;
    background: var(--panel);
    border: 1px solid var(--line);
    border-radius: var(--r-lg);
}
.version-row.is-active { border-color: var(--signal); border-left-width: 2px; }
.vr-id { font-size: 12px; }
.vr-note { display: block; font-size: 11px; margin-top: 2px; }
.vr-meta { display: flex; flex-wrap: wrap; gap: 2px 14px; font-size: 11px; }
.vr-meta .vr-model { color: var(--ink-dim); }
.vr-actions { display: flex; gap: 6px; justify-content: flex-end; }
@media (max-width: 720px) {
    .version-row { grid-template-columns: 1fr; }
    .vr-actions { justify-content: flex-start; }
}

/* Снимок экрана в карточке устройства — кликабелен целиком (открывает
   устройство): крупная цель удобнее маленькой ссылки-имени. */
.unit-shot-link { display: block; width: 100%; height: 100%; }
.unit-shot-link img { transition: opacity 0.12s; }
.unit-shot-link:hover img { opacity: 0.85; }

/* --- единые тосты (правый нижний угол) --- */
.toast-wrap {
    position: fixed;
    right: 16px;
    bottom: 16px;
    z-index: 200;
    display: flex;
    flex-direction: column;
    gap: 8px;
    pointer-events: none;
}
.toast {
    min-width: 220px;
    max-width: 380px;
    padding: 10px 14px;
    border-radius: var(--r-lg);
    background: var(--panel-hi);
    border: 1px solid var(--line-hi);
    border-left: 2px solid var(--signal);
    color: var(--ink);
    font-size: 13px;
    box-shadow: 0 12px 32px rgba(0, 0, 0, 0.55);
    opacity: 0;
    transform: translateY(8px);
    transition: opacity 0.2s ease, transform 0.2s ease;
}
.toast.is-in { opacity: 1; transform: none; }
.toast-error { border-left-color: var(--fault); }
.toast-info { border-left-color: var(--ink-dim); }

/* Липкий фильтр каталога «Образов»: чипы брендов и поиск не уезжают вверх при
   скролле полусотни моделей — фильтровать удобно на любой высоте. Липнет под
   шапкой раздела. */
.catalog-filter {
    position: sticky;
    top: var(--bar);
    z-index: 10;
    padding: 10px 0 8px;
    background: var(--bg);
}
.catalog-filter .filter-input { margin-bottom: 0; }
.catalog-filter .brand-chips { margin-bottom: 8px; }

/* Свитчер устройств в шапке страницы устройства — компактный select. */
.dev-switch {
    margin-left: 4px;
    padding: 3px 8px;
    background: var(--panel-hi);
    border: 1px solid var(--line-hi);
    border-radius: var(--r);
    color: var(--ink-dim);
    font: 500 12px/1.4 var(--data);
    cursor: pointer;
}
.dev-switch:hover { color: var(--ink); }

/* ========================================================================
   Телефон: сайдбар переезжает в нижнюю панель, страница устройства перестаёт
   быть залоченной под вьюпорт и прокручивается целиком, тап-цели крупнее.

   Блок идёт ПОСЛЕДНИМ и адресует те же селекторы, что и рейл на 1000px и
   состояние `nav-off`, — при равной специфичности побеждает порядок, поэтому
   мобильные правила берут верх над обоими состояниями меню. Десктоп (шире
   768px) сюда не попадает и остаётся как был.
   ===================================================================== */
@media (max-width: 768px) {
    /* Сетка каркаса — в одну колонку в ЛЮБОМ состоянии меню (по умолчанию
       навешен `nav-off`, поэтому перечисляем оба селектора). */
    .app,
    .app.nav-off { grid-template-columns: 1fr; }

    /* Сайдбар — нижняя панель на всю ширину: горизонтальный ряд иконок.
       Так на узком экране меню не съедает боковую полосу контента. */
    .side {
        position: fixed;
        top: auto;
        bottom: 0;
        left: 0;
        right: 0;
        z-index: 50;
        flex-direction: row;
        align-items: stretch;
        height: 56px;
        border-right: 0;
        border-top: 1px solid var(--line);
    }
    .side-brand { display: none; }
    .side-spacer { display: none; }

    .side-nav {
        flex: 1;
        flex-direction: row;
        align-items: stretch;
        justify-content: space-around;
        gap: 0;
        padding: 0;
    }
    /* Каждая иконка тянется на равную долю и на всю высоту панели — крупная
       тап-цель (56px). Подписи и счётчик прячем: в ряду они не помещаются. */
    .side .snav {
        flex: 1;
        flex-direction: column;
        justify-content: center;
        gap: 0;
        padding: 0;
        border-radius: 0;
    }
    .side .snav > span:first-of-type,
    .side #task-badge { display: none; }
    /* Активный раздел — верхней чертой (нижняя панель), а не левой. */
    .side .snav.is-on { box-shadow: inset 0 2px 0 var(--signal); }

    /* Тема и выход снова видимы (рейл их прятал) — прижаты к правому краю ряда. */
    .side-foot,
    .app.nav-off .side-foot {
        display: flex;
        flex: 0 0 auto;
        flex-direction: row;
        align-items: stretch;
        gap: 0;
        padding: 0;
        border-top: 0;
        border-left: 1px solid var(--line);
    }
    .side-foot .foot-btn { flex: 0 0 auto; min-width: 48px; border-radius: 0; }

    /* Контент не должен уезжать под нижнюю панель. */
    .main { padding-bottom: 56px; }
    /* Тосты — над нижней панелью, а не под ней. */
    .toast-wrap { bottom: 68px; }

    /* --- страница устройства ---
       Фиксацию под вьюпорт и одноколоночную сетку задаёт блок ≤1000px; на
       телефоне добавляем только свои отступы. Рейлы кнопок ОСТАЮТСЯ по бокам
       экрана (см. компактные рейлы в блоке ≤1000px) — их вниз не переносим. */
    .devpage { padding: 12px; }

    /* Карточки «Обзора»/остановленного устройства — в ОДНУ колонку: на телефоне
       две колонки фактов ужимали значения (IMEI/IMSI/серийник) в переносимую
       посимвольно полосу. (Штатное правило ≤1320px здесь не срабатывает: оно
       стоит в css ВЫШЕ базовой двухколоночной раскладки и перебивается ею по
       порядку; мобильный блок идёт последним и берёт верх.) */
    .idcards { grid-template-columns: 1fr; gap: 16px; }

    /* Шапка устройства — В ОДНУ строку (не два ряда). Раньше `.devbar`
       распирался на 100% и группа действий падала на второй ряд: шапка занимала
       ~110px и «съезжала». Теперь `.devbar` сжимается (имя укорачивается
       многоточием), а компактная группа действий сидит на той же строке.
       Правило СКОУПЛЕНО на бар устройства (`:has(.devbar)`): у остальных
       страниц бар обязан ПЕРЕНОСИТЬ действия на узком экране (иначе кнопки
       «Экраны»/«Новое устройство»/«Проверить алерты» вылезали за 400px —
       поймано Playwright'ом). */
    .bar:has(.devbar) {
        flex-wrap: nowrap;
        height: auto;
        min-height: var(--bar);
        gap: 6px;
        padding: 8px 12px;
    }
    .devbar { flex: 1 1 auto; min-width: 0; gap: 6px; }
    .dev-head,
    .dev-menu > summary {
        min-width: 0;
        overflow: hidden;
        text-overflow: ellipsis;
        white-space: nowrap;
    }
    .bar:has(.devbar) .bar-actions { margin-left: 0; flex: 0 0 auto; }
    /* Кнопки шапки компактнее, чтобы навигация + имя + 5 действий уместились в
       одну строку даже на 360px. */
    .icon-actions .iconbtn,
    .devbar .navgroup .back,
    .devbar .navgroup .nav-toggle { width: 34px; height: 34px; }

    /* Вкладки Обзор/Управление/Файлы — во всю ширину, крупной тап-целью: раньше
       это были мелкие кнопки 12px, жавшиеся слева и неудобные пальцем. */
    .secnav {
        gap: 4px;
        padding: 6px 8px;
        justify-content: stretch;
    }
    .secnav .tab {
        flex: 1;
        text-align: center;
        padding: 10px 6px;
        font-size: 14px;
    }

    /* Кнопки — комфортная тап-цель, но не громоздкие. */
    .btn { min-height: 38px; }
    /* Поля ввода компактные (14px): прежние 16px (ставились против авто-зума iOS)
       на Android-Chrome не нужны и выглядели крупно. Высоту тоже поджимаем. */
    input, select, textarea { font-size: 14px; }
    input[type=text], input[type=search], input[type=number], input[type=password] {
        padding: 7px 10px;
    }
    .input-sm { font-size: 13px; }
}

@media (max-width: 480px) {
    .page { padding: 12px; }
    .devpage { padding: 10px; }
    /* Метры нагрузки хоста в шапке (`.meters-h` ~432px, `flex:none`) на телефоне
       не помещаются рядом с заголовком и распирали бар за край (поймано
       Playwright'ом на живом проде с реальной нагрузкой). Прячем: нагрузка
       остаётся на «Диагностике». На планшете/десктопе — как было. */
    .bar #host-mini { display: none; }
    /* Пары «метка / значение» настроек — в столбик: 38% под метку на узком
       экране оставляли значению нечитаемую полосу. */
    .settings-list > div { grid-template-columns: 1fr; gap: 4px; }
}
