/* Телефон. Слой идёт последним в каскаде и только сужает раскладку — правил
   для широких экранов здесь нет.

   Три решения, из которых следует остальное:
   1. Панель слева на такой ширине не помещается рядом с содержимым, поэтому
      выезжает поверх него по кнопке и закрывается затемнением.
   2. Таблицы разворачиваются в карточки: горизонтальная прокрутка на телефоне
      прячет ровно те колонки, ради которых на список и смотрят. Подписи
      колонок проставляет app.js в data-label — разметка страниц не меняется.
   3. Высоты считаем в dvh: у мобильных браузеров адресная строка съедает часть
      100vh, и низ экрана оказывался за краем. */

@media (max-width: 760px) {
  /* — каркас — */

  /* Прокручивается сама страница, а не внутренний контейнер. Пока скроллером
     был он, Safari на iPhone не сворачивал свои панели — они прокрутку
     документа не видели, и окно оставалось коротким, сколько высоту ни считай.
     Заодно работает привычное «тап по строке состояния — наверх». */
  .app-shell {
    grid-template-columns: 1fr;
    height: auto;
    min-height: 100vh;
    min-height: -webkit-fill-available;
    min-height: 100dvh;
    overflow: visible;
  }

  .main-col {
    grid-column: 1;
    height: auto;
    min-height: 0;
    overflow: visible;
  }

  .page-scroll {
    height: auto;
    overflow: visible;
  }

  /* Шапка прибита к окну, а не к потоку: sticky держится за документ, и на
     iPhone при «резиновой» прокрутке она оттягивалась вместе со страницей.
     Место под неё держит отступ колонки — иначе содержимое уходит под шапку */
  .topbar {
    position: fixed;
    top: 0;
    left: 0;
    right: 0;
    z-index: 30;
  }

  .main-col {
    padding-top: 56px;
  }

  /* Нативная резинка выключена: она тянет всё окно вместе с шапкой. Вместо
     неё тело страницы тянется само (`--pull` ведёт app.js) — шапка при этом
     стоит, а глубокое протягивание запускает обновление.
     Масштаба нет — ни щипком, ни двойным касанием, а прокрутка остаётся;
     щипок в Safari добивает gesturestart в app.js */
  html {
    overscroll-behavior-y: none;
    touch-action: pan-x pan-y;
  }

  /* Сдвиг живёт только на время жеста: любой transform делает блок системой
     отсчёта для position: fixed внутри, а там разворачивается терминал во
     весь экран — вне жеста он должен считать края от окна */
  .page-scroll[data-pulling] {
    transform: translateY(var(--pull, 0px));
  }

  /* Отпустили — тело возвращается плавно; под пальцем переход только мешал бы */
  .page-scroll[data-pulling="release"] {
    transition: transform 0.25s ease;
  }

  /* Указатель обновления: лепестки без подложки — так его рисуют системные
     списки. Выезжает из-под шапки по мере протягивания, смещение ведёт
     app.js через --ptr (0 → 1) */
  .ptr {
    position: fixed;
    top: 56px;
    left: 50%;
    z-index: 25;
    display: block;
    width: 28px;
    height: 28px;
    margin-left: -14px;
    /* До жеста указатель спрятан над экраном и не перехватывает касания */
    transform: translateY(calc(-46px + var(--ptr, 0) * 58px));
    pointer-events: none;
  }

  .ptr-spin {
    display: block;
    width: 100%;
    height: 100%;
  }

  /* Лепестки наливаются по кругу: сколько протянул — столько и видно.
     Ход пальца читается без чисел и подписей */
  .ptr i {
    position: absolute;
    top: 1px;
    left: 50%;
    width: 2px;
    height: 7px;
    margin-left: -1px;
    border-radius: 1px;
    background: var(--muted);
    transform-origin: 50% 13px;
    transform: rotate(calc(var(--i) * 30deg));
    opacity: clamp(0, calc(var(--ptr, 0) * 12 - var(--i)), 1);
  }

  /* Пока идёт обновление, указатель стоит на месте, а круг бежит по лепесткам
     ступенями — плавное вращение полосок читается как размазанное пятно */
  .ptr[data-busy] {
    transform: translateY(12px);
  }

  .ptr[data-busy] .ptr-spin {
    animation: pa-spin 0.9s steps(12) infinite;
  }

  .ptr[data-busy] i {
    opacity: calc(1 - var(--i) / 14);
  }

  /* Без жеста возвращаем указатель плавно; во время протягивания переход
     мешал бы — его снимает app.js атрибутом */
  .ptr:not([data-dragging]) {
    transition: transform 0.2s ease;
  }

  /* Экраны во всю высоту (задачи с логом, консоль) считают её от окна */
  .page-full {
    height: auto;
    min-height: calc(100dvh - 56px);
  }

  .jobs-grid {
    height: auto;
    min-height: calc(100dvh - 56px);
    overflow: visible;
  }

  /* Список задач живёт в общем потоке страницы: своя прокрутка внутри
     прокручиваемой страницы давала «окно в окне» — карточки листались в
     восьмистах пикселях, а страница под ними была вдвое длиннее. Поля — как
     у обычных страниц: тридцать пикселей колонки делали карточки заметно
     уже, чем на остальных экранах */
  .jobs-col {
    padding-left: max(13px, env(safe-area-inset-left));
    padding-right: max(13px, env(safe-area-inset-right));
    overflow: visible;
    /* Разделитель между списком и логом: справа от колонки на телефоне
       ничего нет */
    border-right: 0;
    border-bottom: 1px solid var(--border);
  }

  .jobs-head {
    padding: 18px 0 12px;
  }

  .jobs-scroll {
    padding-bottom: 18px;
    overflow: visible;
  }

  .jobs-panel {
    overflow: visible;
  }

  /* Вырез и домашняя полоса iPhone: содержимое не должно под них уезжать.
     На устройствах без вырезов env(...) равен нулю и ничего не меняет. */
  .topbar {
    padding-left: max(12px, env(safe-area-inset-left));
    padding-right: max(12px, env(safe-area-inset-right));
  }

  .page {
    padding-left: max(13px, env(safe-area-inset-left));
    padding-right: max(13px, env(safe-area-inset-right));
    padding-bottom: calc(44px + env(safe-area-inset-bottom));
  }

  .sidebar {
    padding-bottom: env(safe-area-inset-bottom);
  }

  /* Панель поверх содержимого: в потоке она забирала бы весь экран */
  .sidebar {
    position: fixed;
    top: 0;
    bottom: 0;
    left: 0;
    z-index: 60;
    width: 268px;
    transform: translateX(-100%);
    transition: transform 0.2s ease;
  }

  /* Тень появляется вместе с панелью и только вправо — на затемнение.
     Общая `--shadow-modal` в тёмной теме несёт светлую обводку по контуру: у
     уехавшей панели она ложилась ровно на край экрана и читалась как полоска
     сбоку, а у выехавшей — как лишняя рамка поверх затемнения */
  html[data-nav="open"] .sidebar {
    transform: none;
    box-shadow: 10px 0 40px rgba(0, 0, 0, 0.45);
  }

  /* Затемнение с размытием — как у модалок: панель выезжает поверх страницы
     тем же приёмом, и вести себя должна так же. Оно всегда на месте, но
     прозрачно и пропускает касания: display не анимируется, и при включении
     через none → block размытие появлялось рывком, пока панель плавно
     выезжала. Теперь затемнение и размытие нарастают вместе с панелью, тем же
     временем, и так же уходят при закрытии */
  .nav-scrim {
    position: fixed;
    inset: 0;
    z-index: 55;
    display: block;
    padding: 0;
    border: 0;
    background: rgba(15, 18, 25, 0.5);
    opacity: 0;
    visibility: hidden;
    pointer-events: none;
    /* Префикс нужен Safari на iOS — без него размытия там просто нет */
    -webkit-backdrop-filter: blur(0);
    backdrop-filter: blur(0);
    /* Жест по затемнению не должен прокручивать страницу под ним: прокрутка
       документа на телефоне идёт по <html>, и палец «пробивал» подложку */
    overscroll-behavior: contain;
    touch-action: none;
    /* visibility гаснет после растворения — иначе закрытие обрывалось бы */
    transition:
      opacity 0.2s ease,
      -webkit-backdrop-filter 0.2s ease,
      backdrop-filter 0.2s ease,
      visibility 0s linear 0.2s;
  }

  html[data-nav="open"] .nav-scrim {
    opacity: 1;
    visibility: visible;
    pointer-events: auto;
    -webkit-backdrop-filter: blur(2px);
    backdrop-filter: blur(2px);
    transition:
      opacity 0.2s ease,
      -webkit-backdrop-filter 0.2s ease,
      backdrop-filter 0.2s ease,
      visibility 0s;
  }

  /* Пока панель открыта, страница под ней стоит на месте — как под модалкой.
     Прокрутка живёт на документе (так Safari сворачивает свои панели), и без
     запрета список за затемнением уезжал вместе с пальцем */
  html[data-nav="open"],
  html[data-nav="open"] body {
    overflow: hidden;
  }

  /* Кнопка «свернуть» ни к чему: панель и так уходит целиком */
  .sidebar-foot {
    display: none;
  }

  /* Выехавшая панель всегда полная. Кука могла остаться свёрнутой с широкого
     экрана, и подписи разделов вместе с названием системы пропадали, хотя
     места под них здесь достаточно */
  html[data-sidebar="collapsed"] .sidebar-brand-text {
    display: block;
  }

  html[data-sidebar="collapsed"] .nav-label {
    display: block;
    flex: 1;
  }

  html[data-sidebar="collapsed"] .nav-badge {
    display: inline-block;
  }

  html[data-sidebar="collapsed"] .nav-item {
    justify-content: flex-start;
    padding-inline: 11px;
  }

  .topbar .nav-burger {
    display: inline-flex;
    flex: none;
  }

  /* Экран входа: карточка во всю ширину и поля поменьше — на телефоне
     сорок четыре пикселя внутренних полей съедали половину строки */
  .login-page {
    padding: 16px max(16px, env(safe-area-inset-right)) 16px max(16px, env(safe-area-inset-left));
  }

  .login-card {
    padding: 28px 22px;
  }

  /* — топбар — */

  .topbar {
    gap: 8px;
    padding: 0 12px;
  }

  .topbar-right {
    width: auto;
    gap: 4px;
  }

  .crumbs {
    min-width: 0;
  }

  /* Из цепочки оставляем последнее звено: путь назад есть в самой странице */
  .crumbs > *:not(:last-child) {
    display: none;
  }

  .crumbs > *:last-child {
    overflow: hidden;
    white-space: nowrap;
    text-overflow: ellipsis;
  }

  /* В чипе остаётся только аватар: имя занимало половину шапки. Поля по бокам
     выравниваем — они были разной ширины, потому что правое рассчитано на
     стрелку, а её здесь нет */
  .user-chip > span:not(.avatar),
  .user-chip > svg {
    display: none;
  }

  .user-chip {
    gap: 0;
    padding: 5px 6px;
  }

  /* Меню шапки шире экрана: прижатое к правому краю кнопки, оно уезжало за
     левый край. Разворачиваем его на всю ширину под шапкой */
  .topbar .dd-menu {
    position: fixed;
    top: 56px;
    right: 8px;
    left: 8px;
    width: auto;
    max-height: calc(100dvh - 72px);
    overflow: auto;
  }

  /* — страница — */

  .page {
    padding: 16px 13px 44px;
  }

  .page-head {
    flex-direction: column;
    align-items: stretch;
    gap: 12px;
  }

  .page-title {
    font-size: 19px;
  }

  .page-actions {
    flex-wrap: wrap;
  }

  /* Кнопки шапки делят строку поровну — до края пальца достаёт любая */
  .page-actions > * {
    flex: 1 1 140px;
    justify-content: center;
  }

  /* Меню кнопки в шапке — по её ширине. На десктопе меню привязано к кнопке
     правым краем и растёт влево, а здесь кнопка стоит у левого края экрана,
     и меню «Провайдер» у прокси или «Обновление» на главной уезжало за
     границу окна. Кнопка тянется на полстроки и больше — пунктам места
     хватает */
  .page-actions .dd-menu {
    right: 0;
    left: 0;
    width: auto;
  }

  /* — таблицы карточками — */

  .tbl-wrap {
    overflow: visible;
  }

  .tbl,
  .tbl tbody,
  .tbl tr,
  .tbl td {
    display: block;
    width: auto;
  }

  .tbl thead {
    display: none;
  }

  .tbl tbody tr {
    position: relative;
    margin-bottom: 8px;
    padding: 10px 12px;
    border: 1px solid var(--border);
    border-radius: var(--r-lg, 10px);
    background: var(--surface);
  }

  .tbl tbody tr:hover {
    background: var(--surface);
  }

  .tbl tbody tr:last-child {
    margin-bottom: 0;
  }

  /* Поля крайних колонок задавала таблица — в карточке они превращались в
     ступеньку у последней строки (её значение отходило от края) */
  .tbl td,
  .tbl td:first-child,
  .tbl td:last-child {
    display: flex;
    align-items: baseline;
    /* Значение прижато вправо целиком: при space-between его части —
       флаг и код страны, дата и остаток дней — разъезжались по краям */
    justify-content: flex-end;
    gap: 6px;
    padding: 3px 0;
    border: 0;
    font-size: 12px;
    /* Высота строки задана явно: у моношрифта свои метрики, и пока он
       грузился, карточка успевала подрасти на глазах */
    line-height: 1.25;
    text-align: right;
  }

  /* Подпись колонки живёт слева от значения — иначе карточка нечитаема */
  .tbl td::before {
    content: attr(data-label);
    flex: none;
    /* Остаток строки отдаём значению — оно уходит к правому краю */
    margin-right: auto;
    color: var(--muted);
    font-size: 10.5px;
    font-weight: 600;
    letter-spacing: 0.05em;
    text-transform: uppercase;
    text-align: left;
  }

  /* Место под плашку замера держим заранее, а слот — постоянной высоты:
     иначе карточка подпрыгивала на старте проверки, когда у пустой ячейки
     появлялся спиннер и вместе с ним другая базовая линия */
  .tbl td.probe-cell {
    align-items: center;
    min-height: 24px;
  }

  /* Высота плашки: 16 px строки и по 2 px отступа сверху и снизу */
  .probe-cell .probe-slot {
    height: 20px;
  }

  /* В карточке значение прижато вправо — и давность замера под ним тоже */
  .probe-cell .probe-body {
    align-items: flex-end;
  }

  /* Копирование строки подключения — в правом верхнем углу карточки, на одной
     линии с адресом: у протокола кнопка сбивала колонку значений */
  .tbl td:has(> [data-copy]) {
    position: static;
  }

  /* Действия строки прокси — замер и копирование — последней строкой своей
     части карточки, справа. В углу карточки две кнопки жались к адресу, а у
     части socks5 висели в углу без адреса — к какому протоколу они относятся,
     было не понять. Кнопки — с рамкой и фоном, под палец: голые иконки 22 px
     на телефоне не читались как нажимаемые */
  .tbl tbody td.row-actions {
    display: flex;
    /* По центру, а не по базовой линии, как прочие ячейки карточки: у кнопки
       без текста базовая линия зависит от пробелов вокруг иконки, и соседние
       кнопки вставали лесенкой */
    align-items: center;
    justify-content: flex-end;
    gap: 8px;
    padding: 8px 0 0;
    /* У таблицы с равными колонками ячейки обрезают переполнение — рамку
       кнопки срезало бы при малейшем сдвиге */
    overflow: visible;
  }

  .tbl tbody td.row-actions::before {
    content: none;
  }

  .tbl tbody td.row-actions .btn {
    position: static;
    /* Смещения обнулены явно: на iOS тактильный отклик ставит кнопке в style
       position: relative, и top/right от правила «копирование в угол
       карточки» ниже начинали действовать — кнопка копирования съезжала на
       7 px вниз и влево, ниже соседней и вплотную к ней */
    inset: auto;
    width: 34px;
    height: 34px;
    padding: 0;
    border-color: var(--border);
    background: var(--surface);
    color: var(--ink);
  }

  /* Строка второго протокола — продолжение карточки прокси, а не новая
     карточка: адрес, страна и срок у протоколов общие. Стык — пунктиром, чтобы
     было видно, где кончается одна пара «протокол + порт» и начинается другая */
  .tbl tbody tr:has(+ tr.row-cont) {
    margin-bottom: 0;
    /* Стык рисует пунктир продолжения — своя граница дала бы двойную линию */
    border-bottom: 0;
    border-bottom-right-radius: 0;
    border-bottom-left-radius: 0;
  }

  .tbl tbody tr.row-cont {
    margin-top: 0;
    border-top: 1px dashed var(--border);
    border-top-left-radius: 0;
    border-top-right-radius: 0;
  }

  /* В продолжении первая ячейка — протокол, а не заголовок карточки: она
     остаётся обычной строкой с подписью */
  .tbl tbody tr.row-cont td:first-child {
    display: flex;
    padding: 3px 0;
    font-size: 12px;
  }

  .tbl tbody tr.row-cont td:first-child::before {
    content: attr(data-label);
  }

  .tbl [data-copy] {
    position: absolute;
    /* Кнопка ужата до самой иконки, чтобы её край отступал от края карточки
       ровно так же, как адрес слева, а центр стоял на строке адреса */
    top: 7px;
    right: 7px;
    width: 22px;
    height: 22px;
    padding: 0;
  }

  /* Длинное значение (отпечаток ключа) в карточке не обрезаем: ширину ему
     задавал инлайн-стиль под колонку таблицы, и значение выдавливало подпись
     за край. Здесь оно занимает свою строку целиком */
  .tbl td.truncate {
    display: block;
    max-width: none !important;
    overflow: visible;
    white-space: normal;
    overflow-wrap: anywhere;
    text-align: left;
  }

  .tbl td.truncate::before {
    display: block;
    margin-bottom: 2px;
  }

  /* Первая колонка — заголовок карточки, ему подпись не нужна */
  .tbl td:first-child {
    display: block;
    padding: 0 0 6px;
    font-size: 13.5px;
    text-align: left;
  }

  /* Строка-заглушка на всю ширину и колонка выбора подписей тоже не носят */
  .tbl td:first-child::before,
  .tbl td[colspan]::before,
  .tbl td:not([data-label])::before {
    content: none;
  }

  .tbl td[colspan] {
    display: block;
    text-align: left;
  }

  /* Декоративные ячейки (стрелка «открыть») в карточке лишние. Селектор
     подробный не для красоты: у ячеек выше есть правило с :last-child, и по
     специфичности оно перебивало короткое .tbl .hide-mobile — стрелка
     возвращалась отдельной строкой карточки */
  .hide-mobile,
  .tbl tbody td.hide-mobile {
    display: none;
  }

  .only-mobile {
    display: flex;
  }

  /* Порядок записей — своей строкой под поиском: поле, кнопка «Искать» и
     выбор порядка в 390 px рядом не помещаются, и поле сжималось до трёх
     букв */
  .inline-form:has(.records-sort) {
    flex-wrap: wrap;
  }

  .records-sort {
    flex: 1 0 100%;
    align-items: center;
    gap: 8px;
  }

  /* Значение занимает столько, сколько есть: «Адрес» обрезалось до «Ад…» */
  .records-sort .filter-btn-value {
    max-width: none;
  }

  .records-sort .th-sort-mark {
    opacity: 1;
  }

  /* Действия записи прячутся за правым краем и выезжают свайпом влево — как в
     системных списках. В углу карточки кнопка накрывала название, а места под
     неё в строке заголовка нет: имена ключей и хостов длинные */
  .tbl tbody tr:has(> .cell-actions) {
    position: relative;
    overflow: hidden;
  }

  .tbl tbody td.cell-actions {
    position: absolute;
    top: 0;
    right: 0;
    bottom: 0;
    display: flex;
    align-items: center;
    justify-content: flex-end;
    gap: 8px;
    width: auto;
    min-height: 0;
    padding: 0 14px 0 12px;
    background: var(--surface);
    /* Линия отделяет выехавшую панель от текста записи, который уходит под неё */
    border-left: 1px solid var(--border);
    /* По умолчанию панель за краем; сдвиг обеих частей ведёт app.js — он
       пишет transform прямо ячейкам. Переменной здесь не обойтись: var()
       внутри transform ячейки таблицы не пересчитывается */
    transform: translateX(100%);
  }

  /* Под пальцем переход мешает, после отпускания — возвращает плавно */
  .tbl tbody tr:has(> .cell-actions):not([data-swiping]) > td {
    transition: transform 0.2s ease;
  }

  /* Клавиатура и скринридер жеста не делают: по фокусу панель просто видна */
  .tbl tbody tr:focus-within > td.cell-actions {
    transform: translateX(0);
  }

  /* В выехавшей панели действие названо цветом: красное — то, что уносит
     запись или доступ, синее — обычное. На десктопе те же кнопки остаются
     тихими: там они стоят в ряду и заливка спорила бы с содержимым */
  .cell-actions .btn-danger-text {
    background: var(--red);
    border-color: var(--red);
    color: #fff;
  }

  .cell-actions .btn-accent-text {
    background: var(--accent);
    border-color: var(--accent);
    color: #fff;
  }

  .tbl td.cell-actions::before {
    content: none;
  }

  /* В карточке ширину значения задаёт строка, а не колонка: 220 px не
     помещались рядом с подписью, и отпечаток переносился на вторую строку */
  .fingerprint {
    max-width: 100%;
    min-width: 0;
  }

  /* Ячейка с составным содержимым — барами нагрузки, полосками, кнопками —
     подписью сверху, значением под ней: в строку они не помещаются */
  .tbl td:has(.microbars),
  .tbl td:has(.bar) {
    display: block;
    text-align: left;
  }

  /* Бары во всю ширину карточки: в строке они сжимались до нечитаемых */
  .tbl .microbars {
    display: flex;
    flex-direction: column;
    gap: 3px;
  }

  .tbl td:has(.microbars)::before,
  .tbl td:has(.bar)::before {
    display: block;
    margin-bottom: 4px;
  }

  /* Карточка, в которой лежит список, теряет собственную подложку: строки на
     телефоне и так стали карточками, а рамка вокруг рамки читается как лишний
     слой. Шапка списка (название, баланс, фильтры) при этом остаётся — просто
     живёт прямо на фоне страницы, как заголовок раздела.
     Формы (tbl-form) сюда не входят: их строки карточками не становятся, и
     блок должен выглядеть как остальные блоки настроек — с общей подложкой. */
  .card:has(.tbl-wrap):not(:has(.tbl-form)):not(.card-inset-list) {
    border: 0;
    background: none;
    box-shadow: none;
    /* Скругление и обрезка остаются от card-flush и без подложки уже не
       рисуют край, а режут содержимое: заголовок стоит вплотную к краю, и
       дуга съедала первую букву названия списка */
    border-radius: 0;
    overflow: visible;
  }

  .card:has(.tbl-wrap):not(:has(.tbl-form)):not(.card-inset-list) > .card-head {
    padding: 0 0 10px;
    border-bottom: 0;
  }

  /* Распорка прижимает подвал к низу карточки, когда та тянется по соседу в
     сетке. На телефоне карточки идут в столбец — тянуться не по чему, и
     пустой блок только отодвигал подвал от списка */
  .card:has(.tbl-wrap):not(:has(.tbl-form)):not(.card-inset-list) > .grow:empty {
    display: none;
  }

  /* Подвал списка — строка под ним, а не дно карточки: подложки нет, и линия
     во всю ширину читалась отдельной чертой на фоне страницы. Поля те же, что
     у шапки, — подпись и итог стоят на одной вертикали */
  .card:has(.tbl-wrap):not(:has(.tbl-form)):not(.card-inset-list) > .tbl-foot {
    padding: 10px 0 0;
    border-top: 0;
  }

  /* Список параметров остаётся формой: подпись, под ней поле. Рамка и
     подложка вокруг каждой пары превращали настройку в стопку карточек */
  .tbl-form {
    /* Поля не должны упираться в рамку карточки — поля те же, что у шапки */
    padding: 0 14px 2px;
  }

  .tbl-form tbody tr {
    margin-bottom: 12px;
    padding: 0;
    border: 0;
    border-radius: 0;
    background: none;
  }

  .tbl-form td,
  .tbl-form td:first-child,
  .tbl-form td:last-child {
    display: block;
    width: auto;
    min-height: 0;
    padding: 0;
    text-align: left;
  }

  .tbl-form td:first-child {
    padding-bottom: 4px;
    font-size: 12px;
  }

  .tbl-form td::before {
    content: none;
  }

  /* Заголовок группы — подпись раздела, а не строка списка */
  .tbl-form .tbl-group {
    margin: 10px 0 8px;
  }

  .tbl-form .tbl-group td {
    padding: 0;
    background: none;
  }

  /* Ширину первой колонки задавала таблица — в форме она не нужна */
  .tbl-form td[style] {
    width: auto !important;
  }

  /* На телефоне таблица снова в границах карточки: строки тут не таблица, а
     список с разделителями, и вылет за край смотрелся бы обрывом. Селектор
     повторяет десктопный вместе с `:not` — иначе тот перевешивает по
     специфичности, и записи прижимались к самому краю карточки, мимо полей
     её же заголовка */
  .card-inset-list > .tbl-wrap,
  .card-inset-list:not(.card-flush) > .tbl-wrap {
    margin-inline: 0;
  }

  /* Внутри блока настроек запись — не карточка, а строка списка: своя рамка
     сужала её относительно соседних блоков и ломала общую сетку. Поля те же,
     что у шапки блока, разделитель — линия */
  .card-inset-list tbody tr {
    margin: 0;
    /* По бокам поля уже задала сама карточка: свои добавили бы второй отступ,
       и запись уезжала вправо от заголовка блока */
    padding: 10px 0;
    border: 0;
    border-bottom: 1px solid var(--border);
    border-radius: 0;
    background: none;
  }

  .card-inset-list tbody tr:last-child {
    border-bottom: 0;
  }

  /* У карточки без собственных полей (card-flush) их держит сама запись —
     иначе список упирается в рамку блока */
  .card-inset-list.card-flush tbody tr {
    padding-left: 17px;
    padding-right: 17px;
  }

  .tbl-foot {
    flex-wrap: wrap;
    gap: 6px;
  }

  /* Номера страниц в ряд не помещаются: оставляем стрелки и положение в
     списке, а сами страницы — «2 / 63» */
  .pager {
    padding: 10px 12px;
    flex-wrap: wrap;
  }

  .pager-num,
  .pager-gap {
    display: none;
  }

  /* Диапазон уходит строкой выше: вместе с переключателем и стрелками он в
     360 px не помещается, а перенос по словам рвал бы «из 3 121» */
  .pager-range {
    flex: 1 0 100%;
  }

  .pager-size-opt {
    min-width: 36px;
    padding: 6px 10px;
    font-size: 12px;
  }

  .pager-state {
    display: inline-flex;
    align-items: center;
    padding: 0 8px;
    font-size: 12.5px;
  }

  .pager-nav {
    gap: 6px;
  }

  /* Палец, а не курсор: у стрелок появляется своя рамка и размер под касание */
  .pager-btn {
    min-width: 38px;
    height: 34px;
    border: 1px solid var(--border);
    border-radius: var(--r-md);
    color: var(--ink);
  }

  /* — карточки, сетки, формы — */

  /* Плитки парами: по одной в ряд обзор превращается в длинную ленту, ради
     которой пришлось бы листать до содержательных карточек */
  .grid-stats,
  .grid-metrics {
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 8px;
  }

  .grid-cards {
    grid-template-columns: minmax(0, 1fr);
  }

  /* Переключатель с текстовыми вариантами разворачивается в столбец: при
     переносе внутри рамки разделители рвались посреди строки, и группа
     переставала читаться как один переключатель. Варианты из одних иконок
     узкие — их не трогаем */
  .seg:not(:has(.seg-opt-icon)) {
    flex-direction: column;
    width: 100%;
  }

  .seg:not(:has(.seg-opt-icon)) .seg-opt {
    justify-content: flex-start;
  }

  .seg:not(:has(.seg-opt-icon)) .seg-opt + .seg-opt {
    border-left: 0;
    border-top: 1px solid var(--border);
  }

  /* Разделитель у выбранного варианта сливается с заливкой — как в ряду */
  .seg:not(:has(.seg-opt-icon)) .seg-opt:has(input:checked) + .seg-opt {
    border-top-color: transparent;
  }

  .stat {
    padding: 11px 12px;
  }

  .stat-value {
    font-size: 21px;
  }

  .stat-sub {
    font-size: 10.5px;
  }

  .card-head {
    flex-wrap: wrap;
    gap: 8px;
  }

  /* Подвал карточки ресурса: «Изменить» и «Развернуть» остаются иконками — со
     словами ряд не помещался и разваливался на две строки, а действие
     переставало читаться как продолжение карточки */
  .card-actions .btn-text {
    display: none;
  }

  /* Действия карточки на телефоне остаются кнопками, но без подписи: квадрат
     ростом с соседнюю «Запустить паука». Помечены классом, а не выбраны по
     стилю кнопки: тихими они быть перестали, а сжимать нужно ровно их */
  .card-actions-icons .btn {
    min-height: 30px;
  }

  .card-actions-icons .card-action {
    width: 30px;
    padding: 0;
  }

  /* Ряды кнопок в шапках карточек (фильтры списка, групповые действия)
     переносятся: в одну строку они уезжали за правый край */
  .card-head .row,
  .row {
    flex-wrap: wrap;
  }

  /* Группы переносятся целиком: действия остаются на своей строке, фильтры на
     своей — вперемешку они читались как один длинный ряд кнопок */
  .row-groups > .row-group {
    flex: 1 0 100%;
  }

  .row-groups > .row-sep {
    display: none;
  }

  .field-row > .field {
    flex: 1 1 100%;
  }

  /* Действия формы на телефоне — во всю ширину: кнопка по содержимому
     терялась рядом с длинной подписью, да и попасть по ней большим пальцем
     труднее, чем по строке */
  .form-actions > .btn {
    flex: 1 0 100%;
    justify-content: center;
  }

  .filters-bar {
    flex-wrap: wrap;
    gap: 8px;
  }

  /* Вкладки переносим, а не листаем вбок: скрытый за краем таб на телефоне
     не находят */
  .tabs {
    gap: 14px;
    margin-bottom: 16px;
  }

  .tabs .tab {
    font-size: 13px;
  }

  /* Шесть подписей в строку не помещаются, а перенос и прокрутка одинаково
     плохи: остаются одни иконки, название — в title. Активная видна по цвету
     и подчёркиванию */
  .tabs-icons {
    gap: 0;
    justify-content: space-between;
    flex-wrap: nowrap;
  }

  .tabs-icons .tab {
    justify-content: center;
    /* Площадь под палец: сама иконка 16px, остальное добирает падинг */
    flex: 1;
    padding: 4px 0 10px;
  }

  .tabs-icons .tab .tab-text,
  .tabs-side .tab .tab-text {
    display: none;
  }

  /* Боковой список настроек на телефоне — такой же ряд иконок, как вкладки
     хоста: подписи в него не помещались, и он листался вбок, пряча последнюю
     вкладку за краем. Вид берём оттуда же — линия снизу и подчёркивание у
     выбранного раздела; плашка-заливка рядом с вкладками читалась как другой
     элемент интерфейса */
  .tabs-side {
    gap: 0;
    justify-content: space-between;
    overflow: visible;
    margin-bottom: 16px;
    border-bottom: 1px solid var(--border);
  }

  .tabs-side .tab {
    flex: 1;
    justify-content: center;
    /* Площадь под палец: сама иконка 15px, остальное добирает падинг */
    padding: 4px 0 10px;
    margin-bottom: -1px;
    border-radius: 0;
    border-bottom: 2px solid transparent;
  }

  .tabs-side .tab[aria-current] {
    background: none;
    border-bottom-color: var(--accent);
    color: var(--accent);
  }

  /* — модалки и палитра — */

  dialog.modal,
  dialog.modal-wide,
  dialog.modal-log {
    width: calc(100vw - 20px);
    max-height: 88dvh;
    overflow: auto;
  }

  .modal-foot {
    flex-wrap: wrap;
  }

  .modal-foot > * {
    flex: 1 1 140px;
    justify-content: center;
  }

  .palette {
    width: calc(100vw - 20px);
    max-height: 82dvh;
  }

  /* — график трат — */

  /* Тридцать колонок на ширину телефона: промежутки ужимаем, а столбцу даём
     твёрдую ширину вместо доли от колонки. С долей браузер округлял её
     по-разному, и соседние бары выходили разной толщины */
  .chart {
    gap: 2px;
  }

  .chart-area {
    height: 104px;
  }

  .chart-bar {
    width: 5px;
    margin: 0 auto;
    border-radius: 3px 3px 2px 2px;
  }

  /* Сегменты — той же твёрдой ширины, что и одиночный столбец */
  .chart-seg {
    width: 5px;
    margin: 0 auto;
  }

  /* Шкала слева ужимается: на телефоне 46px забирали пятую часть графика */
  .plot {
    padding-left: 34px;
  }

  .plot-scale {
    height: 104px;
    font-size: 9.5px;
  }

  .plot-scale span {
    width: 30px;
  }

  .plot-scale span::after {
    left: 34px;
  }

  .plot-axis {
    padding-left: 34px;
  }

  .chart-area > .chart-seg:last-child {
    border-radius: 3px 3px 0 0;
  }

  .chart-axis {
    gap: 4px;
    margin-top: 6px;
    font-size: 10px;
  }

  /* Легенда в столбик: три подписи в строку не помещаются и рвутся по словам */
  .chart-legend {
    flex-direction: column;
    gap: 5px;
    margin-top: 10px;
    font-size: 11px;
  }

  .curve {
    height: 104px;
  }

  /* — экран задач: список и лог друг под другом — */

  /* Строки по содержимому: процент от высоты работает только у контейнера с
     заданной высотой, а на телефоне она у сетки авто */
  .jobs-grid {
    grid-template-rows: auto auto;
  }


  /* Пять сегментов («Все», «Активные», «Завершены», «Ошибки», «Отменены») в
     360 px не помещаются: ряд был обрезан наглухо, и до двух последних
     фильтров было не добраться. Теперь листается вбок, полосу прячем — в
     нативных приложениях её у таких рядов нет */
  .segbar {
    max-width: 100%;
    overflow-x: auto;
    scrollbar-width: none;
  }

  .segbar::-webkit-scrollbar {
    display: none;
  }

  /* Пункт не должен сжиматься: иначе ряд «влезает» обрезанными подписями */
  .segbar a {
    flex: none;
  }

  /* Задача не выбрана — панели нет вовсе: «Выберите задачу слева — здесь
     появится её лог» висело в подвале списка, хотя никакого «слева» на
     телефоне нет, а лог открывается окном */
  .jobs-grid:not(.is-open) .jobs-panel {
    display: none;
  }

  .jobs-grid:not(.is-open) .jobs-col {
    border-bottom: 0;
  }

  /* Выбрана задача — лог выезжает поверх списка окном снизу: в потоке он
     занимал вторую половину страницы, и список приходилось листать под ним.
     Высота фиксирована, поэтому внутри снова работает своя прокрутка — лог
     листается, а шапка с кнопками остаются на месте */
  .jobs-grid.is-open .jobs-panel {
    position: fixed;
    left: 0;
    right: 0;
    bottom: 0;
    z-index: 70;
    height: 88dvh;
    overflow: hidden;
    border-radius: var(--r-2xl) var(--r-2xl) 0 0;
    box-shadow: var(--shadow-modal);
    animation: pa-sheet 0.26s cubic-bezier(0.22, 0.8, 0.3, 1);
  }

  .jobs-grid.is-open .jobs-col {
    border-bottom: 0;
  }

  /* Затемнение с размытием — как у модалок и панели разделов. Появляется
     вместе с окном, то есть когда в панели есть подвал с кнопками */
  .jobs-grid.is-open .jobs-scrim {
    position: fixed;
    inset: 0;
    z-index: 69;
    display: block;
    background: rgba(15, 18, 25, 0.5);
    /* Префикс нужен Safari на iOS — без него размытия там просто нет */
    -webkit-backdrop-filter: blur(2px);
    backdrop-filter: blur(2px);
    touch-action: none;
  }

  /* Кнопка закрытия в углу окна: шапка панели занята статусом и метриками */
  .jobs-panel-close {
    position: absolute;
    top: 10px;
    right: 10px;
    z-index: 1;
    display: flex;
    align-items: center;
    justify-content: center;
    width: 30px;
    height: 30px;
    border-radius: 50%;
    background: var(--hover);
    color: var(--muted);
  }

  /* Пока лог открыт, страница под ним стоит на месте — как под модалкой */
  html:has(.jobs-grid.is-open),
  html:has(.jobs-grid.is-open) body {
    overflow: hidden;
  }

  /* Подвал окна не должен уезжать под домашнюю полосу iPhone */
  .jobs-grid.is-open .jobs-panel-foot {
    padding-bottom: calc(12px + env(safe-area-inset-bottom));
  }

  /* — консоль — */

  /* Состояние шелла — точкой, как в списках: словам в одной строке с кнопками
     «Развернуть» и «Подключиться» места нет, а цвет говорит то же самое. Текст
     остаётся в разметке — его читает скринридер */
  .pty-status {
    width: 9px;
    height: 9px;
    flex: none;
    padding: 0;
    font-size: 0;
    background: currentColor;
  }

  /* Ожидание — пульсацией, как у янтарной точки статуса: без подписи иначе не
     отличить «подключаемся» от «не подключено» */
  .pty-wait {
    animation: pa-pulse 1.3s infinite;
  }

  .pty-screen {
    font-size: 11px;
  }

  .console-layout {
    gap: 12px;
  }
}

/* Совсем узкие телефоны: заголовки страниц и плитки ужимаются ещё на шаг */
@media (max-width: 380px) {
  .grid-stats,
  .grid-metrics {
    grid-template-columns: minmax(0, 1fr);
  }

  .page {
    padding: 14px 10px 40px;
  }

  .page-title {
    font-size: 17.5px;
  }

  .stat-value {
    font-size: 20px;
  }
}
