/* КРУСТ ПУЛЬТ — компоненты: кнопки, поля, карточки, метрики (metric / kpi / stat), таблицы, пилюли, пагинация,
   диалоги, тосты, баннеры, полосы, чипы, строки-события, пары ключ–значение, графики. Все цвета — через токены admin.css. */

/* ── Кнопки ────────────────────────────────────────────────────── */

.button {
  display: inline-flex; align-items: center; justify-content: center; gap: 8px;
  min-height: var(--control-h); padding: 0 16px;
  border: 1px solid var(--accent); border-radius: var(--radius-sm);
  background: var(--accent); color: var(--on-accent);
  font: 500 14px/1.2 var(--sans); text-decoration: none;
  cursor: pointer; white-space: nowrap;
  transition: background-color var(--dur-fast) var(--ease), border-color var(--dur-fast) var(--ease), color var(--dur-fast) var(--ease), transform var(--dur-fast) var(--ease), box-shadow var(--dur-fast) var(--ease);
}
.button:hover { background: var(--accent-hover); border-color: var(--accent-hover); }
.button:active { background: var(--accent-2); border-color: var(--accent-2); transform: translateY(1px) scale(0.985); transition-duration: 60ms; }
.button .icon-16 { width: 16px; height: 16px; }
.button:disabled { background: var(--surface-3); border-color: var(--line); color: var(--disabled); cursor: not-allowed; transform: none; }

.button--quiet { background: var(--surface); border-color: var(--line-strong); color: var(--ink-2); }
.button--quiet:hover { background: var(--surface-hover); border-color: var(--accent-line); color: var(--ink); }
.button--quiet:active { background: var(--surface-3); }
.button--quiet:disabled { background: transparent; }

.button--danger { background: transparent; border-color: var(--danger-line); color: var(--danger); }
.button--danger:hover { background: var(--danger-soft); border-color: var(--danger); }
.button--danger:active { background: var(--danger-soft); }
.button--danger:disabled { background: transparent; }
/* Выключенная кнопка на наведении не «оживает»: :hover вариантов стоял после :disabled той же специфичности */
.button:disabled:hover { background: var(--surface-3); border-color: var(--line); color: var(--disabled); }
.button--quiet:disabled:hover, .button--danger:disabled:hover { background: transparent; border-color: var(--line); color: var(--disabled); }

/* Кнопка в работе (busy): содержимое подменяется спиннером, вид остаётся «живым» — иначе на медленном
   запросе (письмо-приглашение, разбан) кнопка просто гасла и было непонятно, идёт ли что-то. */
.button.is-busy { position: relative; }
.button.is-busy > * { opacity: 0; }
.button.is-busy::after {
  content: ''; position: absolute; top: 50%; left: 50%; width: 16px; height: 16px; margin: -8px 0 0 -8px;
  border: 2px solid currentColor; border-top-color: transparent; border-radius: 50%; opacity: 0.85;
  animation: spin 700ms linear infinite;
}
.button.is-busy:disabled { background: var(--accent); border-color: var(--accent); color: var(--on-accent); }
.button--quiet.is-busy:disabled { background: var(--surface); border-color: var(--line-strong); color: var(--ink-2); }
.button--danger.is-busy:disabled { background: var(--danger-soft); border-color: var(--danger-line); color: var(--danger); }
.button--icon.is-busy .icon, .button--icon.is-busy:disabled .icon { opacity: 0; }

.button--accent { background: var(--accent-soft); border-color: var(--accent-line); color: var(--accent-text); }
.button--accent:hover { background: var(--accent); color: var(--on-accent); border-color: var(--accent); }

.button--row { min-height: var(--row-control-h); padding: 0 8px; font-size: 13px; }
.button--wide { width: 100%; margin-top: 4px; }
/* gap: 0 — у кнопки-иконки первым ребёнком остаётся пустой .button__label от setButtonLabel(), и общий
   gap: 8px сдвигал глиф на 4 px вправо от центра бокса (весь ряд действий выглядел «поехавшим»).
   line-height: 0 и display: block у глифа — чтобы базовая линия текста ячейки не роняла иконку на 1–2 px вниз */
.button--icon {
  line-height: 0; width: var(--row-control-h); min-width: var(--row-control-h); min-height: var(--row-control-h); padding: 0; gap: 0; flex: none; }
.button--icon .button__label:empty { display: none; }
.button--icon .icon { width: 20px; height: 20px; display: block; }
/* Глиф в строчной кнопке — 18 px: в боксе 34 px даёт ровные 8 px поля со всех сторон и выравнивает
   оптическую плотность разных по насыщенности глифов (карандаш ↔ люди ↔ корзина) */
.button--row.button--icon .icon { width: 18px; height: 18px; }
.button--icon.screen__action, .card__head .button--icon, .toolbar .button--icon { width: var(--control-h); min-width: var(--control-h); min-height: var(--control-h); }
.button--icon:disabled .icon { opacity: 0.6; }
.button--ghost { background: transparent; border-color: transparent; color: var(--muted); }
.button--ghost:hover { background: var(--surface-hover); border-color: transparent; color: var(--ink); }
.button--danger.button--icon:hover .icon { color: var(--danger); }
/* Канон ряда действий: в покое все иконки нейтральны — красная рамка у «заблокировать» и «удалить» делала
   ряд из семи кнопок полосой тревоги и ломала равновесие весов. Опасность показываем в момент наведения
   и фокуса (а само действие и без того закрыто диалогом подтверждения) — предупреждение приходит тогда,
   когда оно нужно, а не постоянно. */
.cell-actions .button--danger.button--row.button--icon,
.channel__actions .button--danger.button--row.button--icon,
.line .button--danger.button--row.button--icon {
  background: var(--surface); border-color: var(--line-strong); color: var(--ink-2);
}
.cell-actions .button--danger.button--row.button--icon:hover,
.cell-actions .button--danger.button--row.button--icon:focus-visible,
.channel__actions .button--danger.button--row.button--icon:hover,
.channel__actions .button--danger.button--row.button--icon:focus-visible,
.line .button--danger.button--row.button--icon:hover,
.line .button--danger.button--row.button--icon:focus-visible {
  background: var(--danger-soft); border-color: var(--danger); color: var(--danger);
}
/* Сенсорный ввод: контролы 44 px, строчные — 40, вкладки/чипы/раскрывашки — не ниже 36 */
@media (pointer: coarse) {
  /* Пальцем: и обычные, и строчные контролы — 44 (владелец: элемент управления не меньше 44×44) */
  :root { --control-h: 44px; --row-control-h: 44px; }
  .button--icon { width: var(--control-h); min-width: var(--control-h); }
  .tab { min-height: var(--touch); }
  /* Строки-ссылки контура и списков («Воркер», «PostgreSQL») — во всю высоту зоны нажатия */
  a.line { min-height: var(--touch); padding-top: 12px; padding-bottom: 12px; }
  /* Строки «Инфраструктуры» на «Сводке» (a.infra-item) на планшете — одной строкой ~36 px: под палец 44 */
  a.infra-item { min-height: var(--touch); }
  .link-button { min-height: var(--touch); }
  .details summary, .props summary, .alert-details summary { min-height: 36px; }
  .details summary, .props summary, .alert-details summary { display: flex; align-items: center; }
  /* Ссылка в баннере («Показать» у молчащих устройств) — зона нажатия 44 без сдвига макета */
  .banner--row > .link { display: inline-flex; align-items: center; min-height: var(--touch); margin: -10px 0; }
  /* Адрес-ссылка в таблице ссылок и прошивок: строка 17 px, зона нажатия дотягивается до 37 без сдвига макета */
  table.data td.cell-break > a { display: inline-block; padding: 10px 0; margin: -10px 0; }
}

.link-button { margin-top: 12px; padding: 8px 0; border: 0; background: none; color: var(--muted); font: 400 13px var(--sans); cursor: pointer; text-decoration: underline; text-underline-offset: 3px; transition: color var(--dur-fast) var(--ease); }
.link-button:hover { color: var(--accent-text); }

.actions { display: flex; flex-wrap: wrap; gap: 6px; align-items: center; }

/* ── Поля и формы ─────────────────────────────────────────────── */

.field {
  display: block; width: 100%; min-height: var(--control-h); padding: 8px 12px;
  background: var(--field-bg); border: 1px solid var(--line-strong); border-radius: var(--radius-sm);
  color: var(--ink); font: 400 14px/1.35 var(--sans);
  transition: border-color var(--dur-fast) var(--ease), box-shadow var(--dur-fast) var(--ease), background-color var(--dur-fast) var(--ease);
}
.field::placeholder { color: var(--muted); }
.field:hover { border-color: var(--accent-line); }
.field:focus { border-color: var(--accent); box-shadow: var(--accent-glow); outline: none; }
.field:disabled { color: var(--disabled); background: var(--surface-3); cursor: not-allowed; }
.field:disabled:hover { border-color: var(--line-strong); }
.field[aria-invalid='true'] { border-color: var(--danger); }
select.field { padding-right: 32px; appearance: none; -webkit-appearance: none;
  background-image: linear-gradient(45deg, transparent 50%, var(--muted) 50%), linear-gradient(135deg, var(--muted) 50%, transparent 50%);
  background-position: calc(100% - 18px) 50%, calc(100% - 13px) 50%; background-size: 5px 5px; background-repeat: no-repeat; }
textarea.field { resize: vertical; min-height: 96px; font-family: var(--mono); font-size: 13px; }
input[type='file'].field { padding: 8px 12px; font-size: 13px; }
input[type='file'].field::file-selector-button {
  margin-right: 12px; padding: 6px 12px; border: 1px solid var(--line-strong); border-radius: var(--radius-xs);
  background: var(--surface-2); color: var(--accent-text); font: 500 13px var(--sans); cursor: pointer;
}
.field--search { flex: 1 1 280px; max-width: 420px; }
.field--select { flex: 0 1 200px; }
.field--select-narrow { flex: 0 1 170px; }
.field--file { font-family: var(--sans); }
.search { position: relative; flex: 1 1 260px; max-width: 320px; }
.search .icon { position: absolute; left: 12px; top: 50%; transform: translateY(-50%); width: 16px; height: 16px; color: var(--muted); pointer-events: none; }
.search .field { padding-left: 36px; max-width: none; }

.field-group { display: flex; flex-direction: column; gap: 6px; min-width: 0; }
.field-group--full { grid-column: 1 / -1; }
.field-label { font-size: 13px; font-weight: 500; color: var(--ink-2); }
.field-label--inline { font-weight: 400; color: var(--muted); }
.field-required { color: var(--danger); }
.field-hint { margin: 0; font-size: 12px; color: var(--muted); }
/* Короткая подсказка с развёрнутым текстом в title («Как получить токен») */
.field-hint--help { cursor: help; text-decoration: underline dotted; text-underline-offset: 3px; width: max-content; max-width: 100%; }
/* Ошибка поля — под самим полем, а не внизу карточки: человек смотрит туда, где ошибся.
   Живёт последним элементом .field-group, отступ берёт из её gap. */
.field-error { margin: 0; font-size: 13px; line-height: 1.35; color: var(--danger); overflow-wrap: anywhere; }

.form-grid { display: grid; gap: 12px 16px; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); align-items: start; }
.form-grid--single { grid-template-columns: minmax(0, 1fr); }
.form-actions { display: flex; align-items: center; flex-wrap: wrap; gap: 12px; }
.form-grid .status-line { margin: 0; }
/* Поле + кнопка в один ряд: подпись над полем, кнопка на одной нижней линии с полем, оба по 40 px (--control-h) */
.field-row { display: flex; align-items: flex-end; flex-wrap: wrap; gap: 8px 12px; }
/* Отступы ряда задаёт только gap. Внешние правила экрана (например «.login .field-group { margin-bottom }») обнуляем:
   при align-items: flex-end выравниваются внешние края с полями, и лишний margin-bottom поднимает поле над кнопкой. */
.field-row .field-group { flex: 1 1 200px; min-width: 0; margin: 0; }
.field-row .field-group .field, .field-row > .button { min-height: var(--control-h); height: var(--control-h); flex: none; }
.field-row .field-group .field { flex: none; }
.field-row > .button { flex: none; }
.field-row .field-hint, .field-row .status-line { flex-basis: 100%; }

.check { display: inline-flex; align-items: center; gap: 8px; min-height: var(--control-h); cursor: pointer; font-size: 14px; color: var(--ink-2); }
.check input { width: 18px; height: 18px; margin: 0; accent-color: var(--accent); flex: none; }
.check--inline { min-height: var(--control-h); padding: 0 6px; white-space: nowrap; }
.check-group { margin: 0; padding: 0; border: 0; min-width: 0; }
.check-group__list {
  display: grid; gap: 2px 16px; grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
  max-height: 220px; overflow: auto; padding: 8px 12px;
  border: 1px solid var(--line); border-radius: var(--radius-sm); background: var(--field-bg);
}
.check-group .check { min-height: 36px; }
/* В диалоге список галочек не прокручивается сам: вложенная прокрутка внутри прокручиваемого окна на сенсоре ловила палец */
.dialog .check-group__list { max-height: none; overflow: visible; }

/* Область проектов пользователя: «Все проекты / Выбранные» + список чекбоксов (ключ рядом с названием) */
.scope { margin: 0; padding: 0; border: 0; min-width: 0; display: flex; flex-direction: column; gap: 6px; }
.scope .segment--row { margin: 0; }
.scope__list { margin-top: 2px; transition: opacity var(--dur-fast) var(--ease); }
.scope__list--off { opacity: 0.7; pointer-events: none; }
/* Роль всего контура (владелец, администратор): fieldset disabled — выбора нет ни мышью, ни с клавиатуры; переключатель
   и список скрыты (screens-ops.js, setScopeByRole), под заголовком — одна строка-пояснение (этап 20) */
.scope:disabled .segment__item, .scope:disabled .check { cursor: not-allowed; color: var(--disabled); }
.scope__role-hint { margin-top: 2px; }
.scope__list--off input { pointer-events: none; }
.scope__key { margin-left: 6px; font-family: var(--mono); font-size: 12px; color: var(--muted); }
.scope__empty { margin: 0; padding: 8px 12px; font-size: 13px; color: var(--muted); border: 1px dashed var(--line-strong); border-radius: var(--radius-sm); }
/* Матрица членства (этап 24, срез 3; screens-ops.js membersField): строка на проект — название и ключ, доступ, чипы
   разделов. «Все проекты (базовая роль)» — матрицы нет вовсе (не приглушена: строк много). Строки въезжают как строки
   таблиц (row-in, --dur); prefers-reduced-motion гасит общим правилом admin.css. Снятый чип — зачёркнут и тише,
   выключенное — цветом токена, а не прозрачностью (как .scope:disabled) */
.members .scope__list--off { display: none; }
.members__list { display: flex; flex-direction: column; gap: 8px; }
.members__row {
  display: flex; flex-direction: column; gap: 8px; padding: 10px 12px; min-width: 0;
  border: 1px solid var(--line); border-radius: var(--radius-sm); background: var(--surface-2);
  animation: row-in var(--dur) var(--ease);
}
.members__head { display: flex; align-items: center; justify-content: space-between; gap: 8px 12px; flex-wrap: wrap; }
.members__title { min-width: 0; font-weight: 500; color: var(--ink); overflow-wrap: anywhere; }
.members__role { flex: 0 1 220px; min-width: 180px; }
.members__chip[aria-pressed='true'] { color: var(--accent-text); border-color: var(--accent-line); background: var(--accent-soft); }
.members__chip[aria-pressed='false'] { text-decoration: line-through; }
/* Снятый чип — ещё и замком (не только зачёркиванием и цветом); у открытого замка нет */
.members__chip { gap: 4px; }
.members__lock { width: 12px; height: 12px; }
.members__chip[aria-pressed='true'] .members__lock { display: none; }
/* Роль шире базовой — рамкой предупреждения и подсказкой в строке (syncMemberRow) */
.members__row--wide { border-color: var(--warn-line); }
.members__wide-note { margin: 0; color: var(--warn); }
.members__base { margin: 0; }
.members__chip:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.members__chip:disabled, .members__chip:disabled:hover { cursor: not-allowed; color: var(--disabled); border-color: var(--line); background: var(--surface-2); }
.members__row--off .members__title { color: var(--ink-2); }
@media (max-width: 640px) { .members__role { flex: 1 1 100%; } }
/* Пальцем: чип раздела — полная зона касания 44 (общее .chip ниже даёт 36 — отсюда .members в селекторе) и поля по бокам */
@media (pointer: coarse) { .members .members__chip { min-height: var(--touch); padding-inline: 12px; } }

.segment { margin: 0 0 12px; padding: 0; border: 0; display: grid; grid-template-columns: repeat(3, 1fr); gap: 6px; }
.segment legend { margin-bottom: 8px; }
.segment__item {
  display: flex; align-items: center; justify-content: center; text-align: center;
  min-height: var(--control-h); padding: 6px; border: 1px solid var(--line); border-radius: var(--radius-sm);
  background: var(--surface-2); color: var(--ink-2); font-size: 13px; cursor: pointer;
  transition: background-color var(--dur-fast) var(--ease), border-color var(--dur-fast) var(--ease), color var(--dur-fast) var(--ease);
}
.segment__item input { position: absolute; opacity: 0; width: 1px; height: 1px; }
.segment__item:has(input:checked) { background: var(--accent-soft); border-color: var(--accent-line); color: var(--accent-text); }
.segment__item:has(input:focus-visible) { outline: 2px solid var(--accent); outline-offset: 2px; }
.segment--row { display: inline-flex; margin: 0; gap: 6px; }
.segment--row .segment__item { padding: 0 12px; }
.progress { width: 100%; max-width: 320px; height: 12px; accent-color: var(--accent); }

/* ── Панель фильтров, вкладки ──────────────────────────────────── */

.toolbar { display: flex; align-items: center; flex-wrap: wrap; gap: 8px 12px; margin-top: 12px; }
.toolbar--card { margin-top: 0; margin-bottom: 12px; }
.toolbar__count { margin-left: auto; font-size: 13px; color: var(--muted); white-space: nowrap; }
.toolbar .button { margin-left: 0; }
.toolbar__filters { display: flex; flex-wrap: wrap; gap: 8px; }
.toolbar__filters .field { flex: 0 1 180px; }
.toolbar__filters:empty { display: none; }
.period { display: flex; flex-wrap: nowrap; align-items: center; gap: 8px; }
.period .field { width: auto; flex: 0 1 150px; }

.tabs { display: flex; flex-wrap: wrap; gap: 6px; margin: 12px 0 0; padding: 4px; border: 1px solid var(--line); border-radius: var(--radius); background: var(--surface); width: max-content; max-width: 100%; }
.tab {
  display: inline-flex; align-items: center; gap: 8px;
  min-height: 34px; padding: 0 12px;
  border: 1px solid transparent; border-radius: var(--radius-xs);
  background: transparent; color: var(--ink-2); cursor: pointer; font: 500 13px/1.2 var(--sans); position: relative;
  transition: background-color var(--dur-fast) var(--ease), border-color var(--dur-fast) var(--ease), color var(--dur-fast) var(--ease);
}
.tab:hover { color: var(--ink); background: var(--surface-hover); }
.tab input { position: absolute; opacity: 0; width: 1px; height: 1px; }
.tab:has(input:checked), .tab--active { background: var(--accent-soft); color: var(--accent-text); }
.tab:has(input:focus-visible) { outline: 2px solid var(--accent); outline-offset: 2px; }
.tab__label { font-size: 13px; }
.tab__count { font-size: 11px; color: var(--muted); }
.tab--button { font: inherit; }
.tab--button .icon { flex: none; }
.tab--add { border-style: dashed; border-color: var(--line-strong); color: var(--muted); }
.tab--add:hover { color: var(--accent-text); border-color: var(--accent-line); }
.tabs--servers { margin-top: 12px; }

/* ── Карточки ─────────────────────────────────────────────────── */

.card, .table-card {
  margin-top: 12px; padding: 16px 20px;
  background: var(--surface); border: 1px solid var(--line); border-radius: var(--radius-lg);
  box-shadow: var(--shadow-card); min-width: 0;
  transition: border-color var(--dur-fast) var(--ease);
}
.card__head { display: flex; align-items: center; flex-wrap: wrap; gap: 8px 12px; margin-bottom: 12px; }
.card__head .card__title { margin: 0; }
.card__title { margin: 0 0 12px; font-size: 16px; font-weight: 600; color: var(--ink); letter-spacing: -0.01em; }
.card__hint { font-size: 13px; color: var(--muted); }
.card__state { margin-left: auto; }
.card__link { margin-left: auto; font-size: 14px; }
.card__head .card__action { margin-left: auto; }
.card--warn { border-color: var(--warn-line); }
.card--danger { border-color: var(--danger-line); }
.card--flush { padding: 0; overflow: hidden; }
.card--flush .card__head { padding: 16px 20px 0; }
.card--flush .card__foot { padding: 8px 20px; border-top: 1px solid var(--line); }
.card__foot:has(> .pager:empty), .card__foot:empty { display: none; }
/* Подпись-выход из карточки-выборки на полный экран раздела */
.card__foot-note { margin: 10px 0 0; font-size: 13px; color: var(--muted); }
.card--flush .table-scroll { padding: 0; }
.card--flush table.data th:first-child, .card--flush table.data td:first-child { padding-left: 20px; }
.card--flush table.data th:last-child, .card--flush table.data td:last-child { padding-right: 20px; }
.card--flush table.data tbody tr:last-child td { border-bottom: 0; }

/* Сетки: карточки одного ряда растягиваются до равной высоты; колонки-стопки (.stack-cards) заполняются сверху,
   .stack-cards--fill — растягивают свои карточки на высоту соседней колонки.
   .grid--start — ряд из карточек заведомо разной высоты (пустота внутри низкой карточки хуже неровного низа) */
.grid { display: grid; gap: 12px; margin-top: 12px; align-items: stretch; }
.grid--start { align-items: start; }
/* min(340px, 100%) — иначе на 320 колонка шире экрана и появляется горизонтальная прокрутка */
.grid--split { grid-template-columns: repeat(auto-fit, minmax(min(340px, 100%), 1fr)); }
.grid--two { grid-template-columns: repeat(2, minmax(0, 1fr)); }
.grid--wide { grid-template-columns: minmax(0, 7fr) minmax(270px, 3fr); }
.grid--main { grid-template-columns: minmax(0, 1.47fr) minmax(300px, 1fr); }
.grid--cards { grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); }
.grid--cards:empty { display: none; }
.grid--tight { gap: 12px 24px; margin-top: 0; }
.grid > .card, .grid > .table-card { margin-top: 0; }
.stack-cards { display: grid; gap: 12px; min-width: 0; align-content: start; }
.stack-cards--fill { align-content: stretch; }
.stack-cards > .card, .stack-cards > .table-card { margin-top: 0; }

/* Метрики старых экранов: подпись, число, подсказка */
.metrics { display: grid; gap: 12px; margin-top: 12px; grid-template-columns: repeat(auto-fit, minmax(170px, 1fr)); }
.metrics--4 { grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); }
.metrics--5 { grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); }
.metrics--6 { grid-template-columns: repeat(auto-fit, minmax(160px, 1fr)); }
.metric {
  padding: 12px 16px; border: 1px solid var(--line); border-radius: var(--radius-lg);
  background: var(--surface); box-shadow: var(--shadow-card);
  transition: border-color var(--dur-fast) var(--ease);
}
.metric:hover { border-color: var(--line-strong); }
.metric__label { margin: 0; font-size: 13px; color: var(--muted); }
.metric__value { margin: 4px 0 0; font-size: 24px; line-height: 1.1; font-weight: 700; color: var(--ink); letter-spacing: -0.01em; }
.metric--ok .metric__value { color: var(--ok); }
.metric--danger .metric__value { color: var(--danger); }
.metric--warn .metric__value { color: var(--warn); }
.metric__hint { margin: 4px 0 0; font-size: 12px; color: var(--muted); }

/* Карточка «Проектов» (тёмный макет): иконка + подпись / число + спарклайн / подпись + доля */
.kpis { display: grid; gap: 12px; margin-top: 12px; grid-template-columns: minmax(150px, 0.75fr) repeat(4, minmax(0, 1fr)); }
.kpis--4 { grid-template-columns: repeat(4, minmax(0, 1fr)); }
.kpi { display: flex; flex-direction: column; gap: 6px; padding: 12px 16px 12px; border: 1px solid var(--line); border-radius: var(--radius-lg); background: var(--surface); box-shadow: var(--shadow-card); min-width: 0; transition: border-color var(--dur-fast) var(--ease); }
.kpi:hover { border-color: var(--line-strong); }
.kpi__head { display: flex; align-items: center; gap: 12px; }
.kpi__icon { width: 20px; height: 20px; }
.kpi__icon--accent { color: var(--info); }
.kpi__icon--info { color: var(--info); }
.kpi__icon--purple { color: var(--purple); }
.kpi__icon--warn { color: var(--warn); }
.kpi__icon--ok { color: var(--ok); }
.kpi__label { font-size: 13px; color: var(--muted); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.kpi__body { display: flex; align-items: center; justify-content: space-between; gap: 12px; min-height: 32px; min-width: 0; }
/* Значение переносится по любому символу: неразрывное «yoda@0.1.0+53c1fff» в карточке проекта уводило документ вбок на 49–76 px (375–430) */
.kpi__value { margin: 0; padding-left: 32px; font-size: 24px; line-height: 1.1; font-weight: 700; color: var(--ink); letter-spacing: -0.01em; overflow-wrap: anywhere; min-width: 0; }
.kpi__foot { display: flex; align-items: baseline; justify-content: space-between; gap: 8px; margin-top: 0; font-size: 13px; }
.kpi__hint { color: var(--muted); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.kpi__hint b { font-weight: 600; color: var(--ink); }
.kpi__aside { font-weight: 500; white-space: nowrap; }
.kpi__aside--ok { color: var(--ok); }
.kpi__aside--danger { color: var(--danger); }
.kpi__aside--warn { color: var(--warn); }
.kpi__aside--muted { color: var(--muted); }

/* Карточка «Сводки»/«Инфраструктуры» (светлый макет): цветной квадрат с иконкой, подпись, число, дельта */
.stats { display: grid; gap: 12px; margin-top: 12px; grid-template-columns: repeat(4, minmax(0, 1fr)); }
.stat { position: relative; display: grid; grid-template-columns: auto minmax(0, 1fr); gap: 2px 12px; align-items: center; padding: 12px 16px; border: 1px solid var(--line); border-radius: var(--radius-lg); background: var(--surface); box-shadow: var(--shadow-card); min-width: 0; transition: border-color var(--dur-fast) var(--ease); }
.stat:hover { border-color: var(--line-strong); }
.stat__icon { display: inline-flex; align-items: center; justify-content: center; width: 44px; height: 44px; border-radius: 10px; }
.stat__icon .icon { width: 22px; height: 22px; }
.stat__icon--round { border-radius: 50%; width: 48px; height: 48px; }
.stat__icon--accent { background: var(--purple-soft); color: var(--accent-text); }
.stat__icon--ok { background: var(--ok-soft); color: var(--ok); }
.stat__icon--info { background: var(--info-soft); color: var(--info); }
.stat__icon--warn { background: var(--warn-soft); color: var(--warn); }
.stat__icon--danger { background: var(--danger-soft); color: var(--danger); }
.stat__text { min-width: 0; }
/* «С предупреждениями» в двухколоночной сетке на 360–412 — колонка текста ~97 px: слово переносится, а не уходит под соседнюю карточку */
.stat__label { margin: 0; font-size: 13px; color: var(--ink-2); overflow-wrap: anywhere; hyphens: auto; }
.stat__value { margin: 0; font-size: 28px; line-height: 1.1; font-weight: 700; color: var(--ink); letter-spacing: -0.01em; white-space: nowrap; }
.stat__delta { grid-column: 1 / -1; margin: 6px 0 0; font-size: 13px; font-weight: 500; }
.stat__delta--ok { color: var(--ok); }
.stat__delta--info { color: var(--info); }
.stat__delta--accent { color: var(--accent-text); }
.stat__delta--warn { color: var(--warn); }
.stat__delta--danger { color: var(--danger); }
.stat__delta--muted { color: var(--muted); }

/* ── Таблицы ───────────────────────────────────────────────────── */

.table-scroll { overflow-x: auto; -webkit-overflow-scrolling: touch; }
table.data { width: 100%; border-collapse: collapse; }
table.data th {
  padding: 8px 12px 8px 0; border-bottom: 1px solid var(--line); background: var(--thead-bg);
  font-size: 13px; font-weight: 500; color: var(--muted);
  text-align: left; white-space: nowrap;
}
table.data td { padding: 12px 12px 12px 0; border-bottom: 1px solid var(--line); font-size: 14px; color: var(--ink-2); vertical-align: top; white-space: nowrap; transition: background-color var(--dur-fast) var(--ease); }
table.data tbody tr:last-child td { border-bottom: 0; }
table.data tbody tr:hover td { background: var(--row-hover); }
table.data tr.row-highlight td { background: var(--accent-soft); }
/* Строка появилась (добавили запись) или уходит (удалили): те же 150–200 мс и та же кривая, что у остальной оболочки */
table.data tr.row--enter { animation: row-in var(--dur) var(--ease); }
table.data tr.row--leave { animation: row-out var(--dur-fast) var(--ease) forwards; }
@keyframes row-in { from { opacity: 0; transform: translateY(-4px); } to { opacity: 1; transform: none; } }
@keyframes row-out { from { opacity: 1; } to { opacity: 0; transform: translateX(8px); } }
table.data tfoot td { padding: 8px 12px 4px 0; border-top: 1px solid var(--line-strong); font-weight: 600; color: var(--ink); font-size: 13px; }
table.data th:last-child, table.data td:last-child { padding-right: 0; }
/* Ряд действий стоит на одной оптической линии с пилюлями и текстом соседних колонок.
   Раньше здесь было vertical-align: middle при vertical-align: top у всех остальных ячеек: ряд кнопок
   центрировался по ВСЕЙ высоте строки и уезжал вниз тем сильнее, чем выше самая длинная ячейка строки.
   Замер: «Пользователи» +8,6…+33 px, «Мониторинг» +19,4, «Релизы» +18,3, «Справочники» +17,5 — и плавало
   от строки к строке, отчего ряд иконок читался съехавшим.
   Ячейка выровнена по верху, как и все прочие, а ряд кнопок поднят на половину разницы между боксом кнопки
   (--row-control-h) и пилюлей (24 px), чтобы их центры совпали на первой строке ячейки.
   vertical-align: top у самой кнопки нужен потому, что часть таблиц («Алерты», «Мониторинг», «Ссылки»,
   «Защита», крэши, логи, сессии) кладёт кнопки прямо в ячейку, без обёртки .actions: там кнопка — строчный
   бокс и без этого села бы на базовую линию строки, ниже пилюли. */
table.data { --actions-lift: calc((var(--row-control-h) - 24px) / 2); }
table.data .cell-actions {
  text-align: right; white-space: nowrap; width: 1%;
  vertical-align: top; padding-top: calc(12px - var(--actions-lift));
}
table.data .cell-actions > .actions { min-height: var(--row-control-h); }
/* Подпись вместо кнопок («это вы» в «Пользователях»): собственный верхний отступ .cell-sub сбивал бы её
   с центра полосы действий на 1 px */
table.data .cell-actions .cell-sub { margin-top: 0; }
table.data .cell-actions > .button, table.data .cell-actions > .button--icon { vertical-align: top; }
table.data td:not(.cell-actions) { max-width: 360px; overflow: hidden; text-overflow: ellipsis; }
table.data td.cell-wrap { white-space: normal; overflow: visible; text-overflow: clip; max-width: 420px; }
/* Адресные колонки (IPv6, CIDR) длиннее ячейки: переносим по любому символу. Отдельный класс, а не .cell-mono —
   иначе ломается .cell-nowrap у моноширинных колонок со временем и версиями */
table.data td.cell-ip { overflow-wrap: anywhere; word-break: break-all; white-space: normal; }
/* Адреса (URL потоков, лент, прошивок) переносятся по любому символу, а не режутся многоточием без подсказки:
   .cell-break проигрывал по весу правилу «table.data td { white-space: nowrap }» и не действовал вовсе.
   Changelog по той же причине терял переносы строк (pre-line) */
table.data td.cell-break { white-space: normal; overflow: visible; text-overflow: clip; }
table.data td.cell-changelog { white-space: pre-line; }
table.data td a { overflow-wrap: anywhere; }
table.data .cell-actions .actions { flex-wrap: nowrap; justify-content: flex-end; }
table.data th.cell-narrow { white-space: normal; line-height: 1.25; }
table.data .row-state td { padding: 20px 0; white-space: normal; text-align: center; max-width: none; overflow: visible; text-overflow: clip; overflow-wrap: anywhere; }
table.data .cell-right, table.data th.cell-right { text-align: right; }
.stat-table td, .stat-table th { text-align: right; }
.stat-table td:first-child, .stat-table th:first-child { text-align: left; }

.cell-title { display: block; font-size: 14px; font-weight: 500; color: var(--ink); }
.cell-sub { display: block; margin-top: 2px; font-size: 12px; color: var(--muted); }
.cell-mono { font-family: var(--mono); font-variant-numeric: tabular-nums; font-size: 13px; }
.cell-small { font-size: 12px; }
.cell-muted { color: var(--muted); }
/* Длинное ФИО/название не обрывается на полсимвола: многоточие, полный текст — в title */
.cell-title { display: block; overflow: hidden; text-overflow: ellipsis; }
/* Метка внутри ячейки (например «заблокирован» у ФИО) — отдельной строкой под подписями */
.cell-flag { margin-top: 4px; align-self: flex-start; }
table.data tr.row-muted td { color: var(--muted); }
.cell-error { color: var(--danger); }
.cell-danger { color: var(--danger); font-weight: 600; }
.cell-warn { color: var(--warn); }
.cell-accent { color: var(--accent-text); }
.cell-loading { color: var(--muted); }
.cell-loading::after { content: ''; display: inline-block; width: 12px; height: 12px; margin-left: 8px; vertical-align: -2px; border: 2px solid var(--line-strong); border-top-color: var(--accent); border-radius: 50%; animation: spin 700ms linear infinite; }
@keyframes spin { to { transform: rotate(360deg); } }
.cell-nowrap { white-space: nowrap; }
.cell-wrap { white-space: normal; overflow-wrap: anywhere; min-width: 120px; max-width: 420px; }
.cell-break { white-space: normal; overflow-wrap: anywhere; word-break: break-all; min-width: 200px; max-width: 420px; }
.cell-changelog { max-width: 280px; white-space: pre-line; font-size: 12px; }
table.data td.cell-details { max-width: 260px; white-space: normal; overflow-wrap: anywhere; font-family: var(--mono); font-size: 11px; line-height: 1.35; color: var(--muted); }
.cell-sha { white-space: nowrap; }
.cell-sha .cell-mono { margin-right: 8px; font-size: 12px; }
.cell-id { min-width: 120px; max-width: 240px; white-space: normal; overflow-wrap: anywhere; }
.cell-check { width: 1%; padding-right: 6px !important; }
.cell-check input { width: 18px; height: 18px; margin: 2px 0 0; accent-color: var(--accent); cursor: pointer; display: block; }
.cell-action--danger { color: var(--danger); font-weight: 600; }
.cell-action--accent { color: var(--accent-text); }
.object-link { padding: 0; border: 0; background: none; text-align: left; cursor: pointer; color: inherit; font: inherit; }
.object-link:hover .cell-title { color: var(--accent-text); text-decoration: underline; text-underline-offset: 3px; }
a.object-link { text-decoration: none; }
a.object-link:hover { color: var(--accent-text); text-decoration: underline; text-underline-offset: 3px; }

.pager { display: flex; align-items: center; justify-content: space-between; flex-wrap: wrap; gap: 12px; margin-top: 12px; min-height: var(--row-control-h); }
.pager:empty { display: none; }
.pager__text { font-size: 13px; color: var(--muted); }
/* Номера страниц переносятся: на 320 ряд из 5 кнопок + стрелки не помещался и уводил документ вбок */
.pager__pages { display: inline-flex; align-items: center; flex-wrap: wrap; justify-content: center; gap: 6px; margin-left: auto; max-width: 100%; }
.pager__page { width: var(--row-control-h); min-width: var(--row-control-h); padding: 0; font-size: 13px; font-weight: 500; color: var(--accent-text); border-color: var(--line); background: var(--surface); }
.pager__page--current { background: var(--accent); border-color: var(--accent); color: var(--on-accent); }
.pager__page--current:hover { background: var(--accent); border-color: var(--accent); color: var(--on-accent); }
.pager__gap { color: var(--muted); padding: 0 2px; }

/* ── Пилюли, точки статуса ─────────────────────────────────────── */

.pill {
  display: inline-flex; align-items: center; gap: 6px; height: 24px; padding: 0 8px;
  border-radius: var(--radius-xs); border: 1px solid transparent;
  font-size: 12px; font-weight: 500; white-space: nowrap;
}
.pill--ok { background: var(--ok-soft); color: var(--ok); border-color: var(--ok-line); }
.pill--accent { background: var(--accent); color: var(--on-accent); }
.pill--danger { background: var(--danger-soft); color: var(--danger); border-color: var(--danger-line); }
.pill--warn { background: var(--warn-soft); color: var(--warn); border-color: var(--warn-line); }
.pill--info { background: var(--info-soft); color: var(--info); border-color: var(--info-line); }
.pill--muted { background: var(--surface-3); color: var(--muted); border-color: var(--line); }
.pill--status { font-size: 13px; padding: 0 8px; }
/* Статусные значки (OK, WARNING, CRITICAL…): одинаковая ширина и вид во всех таблицах — метка читается как иконка */
.pill--badge { min-width: 88px; justify-content: center; font-size: 11px; font-weight: 600; letter-spacing: 0.06em; }
.pill__dot { width: 8px; height: 8px; border-radius: 50%; background: currentColor; flex: none; }
.pills { display: flex; flex-wrap: wrap; gap: 4px; margin-top: 4px; }
.status-dot { display: inline-flex; align-items: center; gap: 8px; white-space: nowrap; }
.status-dot__mark { width: 8px; height: 8px; border-radius: 50%; background: var(--muted); flex: none; }
.status-dot--ok .status-dot__mark { background: var(--ok); }
.status-dot--warn .status-dot__mark { background: var(--warn); }
.status-dot--danger .status-dot__mark { background: var(--danger); }
.status-dot--info .status-dot__mark { background: var(--info); }
.status-dot__text { color: var(--ink-2); }

/* ── Пары ключ–значение, стеки, коды ───────────────────────────── */

.kv { margin: 0; }
.kv__row { display: grid; grid-template-columns: minmax(120px, 180px) minmax(0, 1fr); gap: 8px 20px; padding: 8px 0; border-top: 1px solid var(--line); font-size: 14px; }
.kv__row:first-child { border-top: 0; padding-top: 0; }
.kv__row dt { color: var(--muted); font-size: 13px; }
.kv__row dd { margin: 0; color: var(--ink); overflow-wrap: anywhere; }
.kv--tight .kv__row { grid-template-columns: minmax(90px, 130px) minmax(0, 1fr); padding: 6px 0; font-size: 13px; }
.kv--tight .kv__row dt { font-size: 12px; }
.kv__row--warn dt { color: var(--warn); }
.stack {
  margin: 0; padding: 12px 16px; max-height: 60vh; overflow: auto;
  background: var(--bg); border: 1px solid var(--line); border-radius: var(--radius-sm);
  font: 12px/1.5 var(--mono); color: var(--ink-2);
  white-space: pre-wrap; overflow-wrap: anywhere; tab-size: 4;
}
.details summary { cursor: pointer; color: var(--muted); font-size: 13px; padding: 6px 0; }
.secret { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; margin: 6px 0 8px; font-size: 15px; color: var(--ink); }
.secret-text { font-family: var(--mono); font-size: 12px; color: var(--ink-2); }
.codes { display: grid; grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); gap: 6px 20px; margin: 0 0 12px; padding-left: 24px; font-size: 15px; color: var(--ink); }
.crumbs { margin: 8px 0 0; font-size: 13px; color: var(--muted); }
.crumbs__sep { margin: 0 8px; color: var(--muted); }
.crumbs .link { text-decoration: none; }
.crumbs .link:hover { text-decoration: underline; }
.empty-state { margin-top: 12px; padding: 20px 24px; text-align: center; color: var(--muted); border: 1px dashed var(--line-strong); border-radius: var(--radius-lg); font-size: 14px; }
.empty-state--ok { display: flex; align-items: center; justify-content: center; gap: 8px; color: var(--ok); border-style: solid; border-color: var(--ok-line); }

/* ── Баннеры, статусы, тосты ───────────────────────────────────── */

.banner { margin-top: 12px; padding: 12px 16px; background: var(--warn-soft); border: 1px solid var(--warn-line); border-radius: var(--radius); }
.banner__title { margin: 0; font-size: 14px; font-weight: 600; color: var(--warn); }
.banner__hint { margin: 4px 0 8px; font-size: 13px; color: var(--ink-2); }
.banner__hint:last-child { margin-bottom: 0; }
.banner--danger { background: var(--danger-soft); border-color: var(--danger-line); }
.banner--danger .banner__title { color: var(--danger); }
.banner--info { background: var(--info-soft); border-color: var(--info-line); }
.banner--info .banner__title { color: var(--info); }
.banner--row { display: flex; align-items: center; flex-wrap: wrap; gap: 8px 12px; padding: 12px 16px; animation: screen-enter var(--dur) var(--ease); }
.banner--warn { background: var(--warn-soft); border-color: var(--warn-line); }
.banner__icon { flex: none; color: var(--warn); }
.banner--danger .banner__icon { color: var(--danger); }
.banner--info .banner__icon { color: var(--info); }
.banner__text { flex: 1 1 260px; min-width: 0; }
.banner--row .banner__title { margin: 0; }
.banner--row .banner__hint { margin: 2px 0 0; font-size: 12px; }
.banner__actions { margin-left: auto; }
.banner--row .icon-button--small { margin-left: auto; }
.banner--row .banner__text + .link { margin-left: 0; }
/* Плашка канала (#connector-banner) — постоянный статус, а не событие: она пересобирается при каждом переходе (admin.js),
   и анимация входа заставляла её «прилетать» заново на каждом экране вместе с проявлением секции */
#connector-banner .banner--row { animation: none; }

.status-line { margin: 12px 0 0; min-height: 1.4em; font-size: 13px; color: var(--ok); overflow-wrap: anywhere; }
.status-line:empty { margin-top: 0; min-height: 0; }
.status-line--error { color: var(--danger); }

.toast {
  /* top/left, width/height: auto и overflow: visible сбрасывают стили верхнего слоя popover (inset: 0; width/height: fit-content;
     overflow: auto) — иначе тост встал бы в угол, а на телефоне (left и right заданы) прижимался к левому краю */
  position: fixed; top: auto; left: auto; right: 20px; bottom: 20px; z-index: 40; overflow: visible; width: auto; height: auto;
  display: flex; align-items: flex-start; gap: 8px;
  max-width: min(420px, calc(100vw - 40px)); margin: 0; padding: 10px 8px 10px 16px;
  background: var(--surface); border: 1px solid var(--line-strong); border-left: 3px solid var(--accent); border-radius: var(--radius-sm);
  box-shadow: var(--shadow); color: var(--ink); font-size: 13px; line-height: 1.45;
}
/* Уход тоста — 150 мс с разгоном; allow-discrete держит display и верхний слой на время ухода. Браузер без allow-discrete
   гасит тост сразу, как раньше. Вход — анимацией toast-in ниже */
.toast { transition: opacity var(--dur-fast) cubic-bezier(0.4, 0, 1, 1), transform var(--dur-fast) cubic-bezier(0.4, 0, 1, 1), display var(--dur-fast) allow-discrete, overlay var(--dur-fast) allow-discrete; }
.toast:not(.toast--visible) { display: none; opacity: 0; transform: translateY(4px); }
.toast--visible { animation: toast-in var(--dur) var(--ease); }
@keyframes toast-in { from { opacity: 0; transform: translateY(8px); } to { opacity: 1; transform: none; } }
.toast--error { border-left-color: var(--danger); color: var(--danger); }
.toast__text { flex: 1; min-width: 0; padding-top: 3px; overflow-wrap: anywhere; }
/* Закрыть — своя кнопка: ошибка не гаснет сама, и её нельзя было убрать с экрана иначе, чем сменой экрана */
.toast__close {
  flex: none; display: inline-flex; align-items: center; justify-content: center; width: 28px; height: 28px; margin: -2px 0;
  border: 0; border-radius: var(--radius-xs); background: transparent; color: var(--muted); cursor: pointer;
  transition: background-color var(--dur-fast) var(--ease), color var(--dur-fast) var(--ease);
}
.toast__close:hover { background: var(--surface-hover); color: var(--ink); }
.toast__close .icon { width: 16px; height: 16px; }
@media (pointer: coarse) { .toast__close { width: var(--touch); height: var(--touch); margin: -8px -4px -8px 0; } }

/* ── Диалоги ───────────────────────────────────────────────────── */

.dialog {
  width: min(560px, calc(100vw - 32px)); max-height: calc(100vh - 32px); padding: 24px; overscroll-behavior: contain;
  background: var(--surface); color: var(--ink-2);
  border: 1px solid var(--line-strong); border-radius: var(--radius-lg);
  box-shadow: var(--shadow-strong);
}
.dialog[open] { animation: dialog-in var(--dur) var(--ease); }
.dialog::backdrop { background: var(--backdrop); animation: backdrop-in var(--dur) var(--ease); }
/* Уход окна и затемнения — 150 мс с разгоном: раньше диалог исчезал одним кадром. allow-discrete держит окно в верхнем слое
   на время ухода; без поддержки — мгновенно, как раньше */
.dialog { transition: opacity var(--dur-fast) cubic-bezier(0.4, 0, 1, 1), transform var(--dur-fast) cubic-bezier(0.4, 0, 1, 1), overlay var(--dur-fast) allow-discrete, display var(--dur-fast) allow-discrete; }
.dialog:not([open]) { opacity: 0; transform: translateY(4px) scale(0.985); }
.dialog::backdrop { transition: opacity var(--dur-fast) cubic-bezier(0.4, 0, 1, 1), overlay var(--dur-fast) allow-discrete, display var(--dur-fast) allow-discrete; }
.dialog:not([open])::backdrop { opacity: 0; }
@keyframes dialog-in { from { opacity: 0; transform: translateY(8px) scale(0.985); } to { opacity: 1; transform: none; } }
@keyframes backdrop-in { from { opacity: 0; } to { opacity: 1; } }
.dialog--form { width: min(680px, calc(100vw - 32px)); }
.dialog__pre {
  margin: 0 0 12px; padding: 12px; background: var(--bg); border: 1px solid var(--line-strong); border-radius: var(--radius-sm);
  font: 13px/1.5 var(--mono); color: var(--ink); white-space: pre-wrap; overflow-wrap: anywhere; user-select: all;
}
.dialog__list { margin: 0 0 12px; padding-left: 20px; font-size: 13px; color: var(--ink-2); max-height: 40vh; overflow: auto; overscroll-behavior: contain; }
.dialog__list li { margin: 2px 0; overflow-wrap: anywhere; }
/* Заголовки с подставленным именем («Проект «…»», «Heartbeat сервера «…»») — слово без пробелов не распирает диалог на 320 */
.dialog__title { margin: 0 0 8px; font-size: 18px; color: var(--ink); overflow-wrap: anywhere; text-wrap: balance; }
.dialog__text { margin: 0 0 16px; font-size: 14px; color: var(--ink-2); overflow-wrap: anywhere; text-wrap: pretty; }
.dialog__actions { display: flex; justify-content: flex-end; flex-wrap: wrap; gap: 8px; margin-top: 20px; }
/* Переносы без «сирот» на 320–430: заголовки — ровными строками, подсказки — без одинокого слова в конце */
.screen__title, .login__title, .card__title, .banner__title { text-wrap: balance; }
.login__hint, .field-hint, .banner__hint, .empty-state { text-wrap: pretty; }
/* Длинная форма (справочники, пользователи) прокручивается внутри окна — кнопки и строка ошибки закреплены внизу: на 390 после
   ошибки проверки ни ошибки, ни кнопок в кадре не было. Отрицательные поля выводят фон на края окна (отступ .dialog — 24 px);
   прилипание отсчитывается от внутреннего края отступа окна — отсюда bottom: −24 px, иначе под кнопками просвечивал текст */
.dialog--form .dialog__actions { position: sticky; bottom: -24px; z-index: 1; margin: 16px -24px -24px; padding: 12px 24px 24px; background: var(--surface); border-top: 1px solid var(--line); }
.dialog__error { flex: 1 1 100%; margin: 0; }
.dialog__error:empty { display: none; }

/* ── Полосы, мини-полоски, чипы, строки-события ─────────────────── */

.bars { display: grid; gap: 8px; }
.bars--compact { gap: 6px; }
.bars--grid { grid-template-columns: repeat(auto-fill, minmax(220px, 1fr)); gap: 8px 24px; align-content: start; }
.bar { display: block; min-width: 0; text-decoration: none; color: inherit; }
.bar__head { display: flex; align-items: baseline; justify-content: space-between; gap: 12px; font-size: 13px; color: var(--ink-2); }
.bar__label { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.bar__value { font-size: 12px; color: var(--ink); white-space: nowrap; }
.bar__track { margin-top: 5px; height: 6px; border-radius: 999px; background: var(--surface-3); overflow: hidden; }
/* Заливка — во всю дорожку и сдвинута влево на недостающую долю через transform (widgets.js:bar() пишет style.transform):
   сдвиг идёт на композиторе, а прежний transition: width считал бы layout каждый кадр — и к тому же никогда не срабатывал,
   ширина ставилась до вставки узла в документ. Правый край заливки остаётся скруглённым, левый прячет скруглённая дорожка
   (overflow: hidden). Полоса въезжает только при входе на экран, вместе с каскадом карточек (.screen--enter, admin.js),
   а не при автообновлении. Паттерн — пример Motion «Loading progress bar» (MotionScore S). */
.bar__fill { width: 100%; height: 100%; border-radius: 999px; background: var(--accent); transform: translateX(-100%); }
/* Полоса едет после проявления секции (задержка 150 мс): в те же 200 мс, что секция выходит из прозрачности, сдвиг был не виден */
.screen--enter .bar__fill { animation: bar-in var(--dur) var(--ease) var(--dur-fast) both; }
@keyframes bar-in { from { transform: translateX(-100%); } }
.bar--ok .bar__fill { background: var(--ok); }
.bar--warn .bar__fill { background: var(--warn); }
.bar--danger .bar__fill { background: var(--danger); }
.bar--danger .bar__value { color: var(--danger); }
.bar--info .bar__fill { background: var(--info); }
.bar--muted .bar__fill { background: var(--line-strong); }
.bar--muted .bar__value { color: var(--muted); }
.bar__hint { display: block; margin-top: 3px; font-size: 12px; color: var(--muted); }
.bars--compact .bar__track { height: 4px; margin-top: 3px; }
.bars--compact .bar__head { font-size: 12px; }
.resources { display: grid; gap: 12px; margin-top: 4px; }
.minibar { display: inline-flex; align-items: center; gap: 8px; }
.minibar__track { display: inline-block; width: 72px; height: 6px; border-radius: 999px; background: var(--surface-3); overflow: hidden; }
.minibar__fill { display: block; height: 100%; border-radius: 999px; background: var(--accent); }
.minibar__track--muted .minibar__fill { background: var(--line-strong); }
.minibar__text { font-size: 12px; color: var(--muted); }
/* Длинный моно-текст в ячейке — не больше трёх строк (полный текст в подсказке) */
.cell-clamp { display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 3; line-clamp: 3; overflow: hidden; }
.chips { display: flex; flex-wrap: wrap; gap: 6px; }
.chips:empty { display: none; }
.chip {
  display: inline-flex; align-items: center; min-height: 28px; padding: 0 8px; border-radius: 999px; border: 1px solid var(--line);
  background: var(--surface-2); color: var(--muted); font: 500 12px/1 var(--sans); cursor: default;
  transition: background-color var(--dur-fast) var(--ease), border-color var(--dur-fast) var(--ease), color var(--dur-fast) var(--ease);
}
button.chip { cursor: pointer; }
button.chip:hover { border-color: var(--accent-line); color: var(--accent-text); }
.chip--danger { color: var(--danger); border-color: var(--danger-line); background: var(--danger-soft); }
.chip--warn { color: var(--warn); border-color: var(--warn-line); background: var(--warn-soft); }
/* Включённый вид «Журнала устройства» (tone 'ok') — класса не было, и включённый отличался от выключенного только прозрачностью */
.chip--ok { color: var(--ok); border-color: var(--ok-line); background: var(--ok-soft); }
.chip--active { box-shadow: var(--accent-glow); border-color: var(--accent); }
/* Нулевой счётчик и выключенный фильтр тише, но читаемы: 0.7 давало 4,1:1 в тёмной и 3,0:1 в светлой теме */
.chips .chip--muted { opacity: 0.9; }
/* Сенсор: чип — 36 px (решение гейта 2). Правило стоит после базового .chip: в общем coarse-блоке выше оно перебивалось
   базовым min-height 28 px той же специфичности, и на телефоне чипы оставались 28 px (замер гейта 3) */
@media (pointer: coarse) { .chip { min-height: 36px; } }
.bulk-bar {
  display: flex; flex-wrap: wrap; align-items: center; gap: 8px; margin: 0 0 12px;
  padding: 8px 12px; background: var(--accent-soft); border: 1px solid var(--accent-line); border-radius: var(--radius-sm);
}
.card__head .bulk-bar { margin: 0; flex: 1 1 100%; }
.bulk-bar__count { font-size: 13px; font-weight: 600; color: var(--accent-text); margin-right: 6px; }
.bulk-bar .link-button { margin: 0 0 0 auto; padding: 6px 0; }

.lines { margin-top: 8px; }
.line { display: flex; align-items: flex-start; gap: 8px; padding: 8px 0; border-top: 1px solid var(--line); font-size: 13px; color: var(--ink-2); text-decoration: none; }
.line:first-child { border-top: 0; padding-top: 0; }
a.line:hover .line__text { color: var(--accent-text); text-decoration: underline; text-underline-offset: 3px; }
.line .icon-16 { margin-top: 1px; }
/* Кнопка-иконка внутри строки-события (например «исследовать» в Watchdog) — без рамки в покое: колонка
   таких рамок весила больше самих строк и читалась отдельным столбцом, а не действием строки */
.line .button--icon { background: transparent; border-color: transparent; color: var(--muted); }
.line .button--icon:hover { background: var(--surface-hover); border-color: var(--line-strong); color: var(--ink); }
.line .button--icon:focus-visible { border-color: var(--accent-line); color: var(--ink); }
.line__icon { flex: none; }
.line__icon--ok { color: var(--ok); }
.line__icon--warn { color: var(--warn); }
.line__icon--err { color: var(--danger); }
.line__icon--info { color: var(--info); }
.line__icon--muted { color: var(--disabled); }
.line__icon--kind { color: var(--muted); }
.line__text { flex: 1; min-width: 0; overflow-wrap: anywhere; }
.line__aside { font-size: 12px; line-height: 17px; color: var(--muted); text-align: right; white-space: nowrap; }
.line__time { flex: none; width: 74px; font-size: 12px; color: var(--muted); }
.line--empty { color: var(--muted); }
.line--fail { background: var(--danger-soft); margin: 0 -8px; padding-left: 8px; padding-right: 8px; border-radius: var(--radius-sm); border-top-color: transparent; }
.line--fail .line__text { color: var(--ink); font-weight: 600; }
.line--fail + .line { border-top-color: transparent; }
/* Предупреждение (мало места на диске и т. п.) — жёлтой строкой, а не красной строкой сбоя */
.line--warn { background: var(--warn-soft); margin: 0 -8px; padding-left: 8px; padding-right: 8px; border-radius: var(--radius-sm); border-top-color: transparent; }
.line--warn .line__text { color: var(--ink); font-weight: 600; }
.line--warn + .line { border-top-color: transparent; }

/* Строка внешнего источника: иконка состояния по центру первой строки текста, пилюля состояния рядом с названием,
   действия — отдельным рядом, чтобы на 320 px они не сжимались в нечитаемые огрызки */
.service-line { padding: 10px 0; align-items: flex-start; }
.service-line .line__icon { margin-top: 3px; }
.service-line__head { display: flex; align-items: center; flex-wrap: wrap; gap: 6px; }
.service-line__head .cell-title { display: inline; }
.service-line__actions { margin-top: 8px; gap: 8px; flex-wrap: wrap; }
.service-line__actions .button { min-height: 32px; }
@media (max-width: 480px) {
  .service-line__actions { width: 100%; }
  .service-line__actions .button { flex: 1 1 auto; min-height: 36px; justify-content: center; }
}

/* Экран «Источники»: одна карточка, группы — подзаголовками. Пять отдельных карточек в резиновой сетке рвали
   нижнюю кромку (3+2 на 1280, 4+1 на 1440) и оставляли пустоты на широком экране. Колонки раскладывает CSS,
   группа целиком не разрывается между колонками. */
.sources { column-gap: 40px; }
.sources__group { break-inside: avoid; margin-top: 20px; }
.sources__group:first-child { margin-top: 0; }
.sources__title {
  margin: 0 0 4px; font-size: 12px; font-weight: 700; letter-spacing: 0.06em; text-transform: uppercase; color: var(--muted);
}
@media (min-width: 1000px) { .sources { columns: 2; } }
@media (min-width: 1500px) { .sources { columns: 3; } }

/* ── Графики: спарклайны, области, линии, кольцо ───────────────── */

.spark { display: block; overflow: visible; flex: none; }
.spark__line { fill: none; stroke: var(--ok); stroke-width: 1.5; stroke-linejoin: round; stroke-linecap: round; }
.spark__area { fill: var(--ok); opacity: 0.16; }
.spark__flat { stroke: var(--line-strong); stroke-width: 1.5; stroke-dasharray: 2 3; }
.spark--warn .spark__line { stroke: var(--warn); }
.spark--warn .spark__area { fill: var(--warn); }
.spark--danger .spark__line { stroke: var(--danger); }
.spark--danger .spark__area { fill: var(--danger); }
.spark--info .spark__line { stroke: var(--info); }
.spark--info .spark__area { fill: var(--info); }
.spark--purple .spark__line { stroke: var(--purple); }
.spark--purple .spark__area { fill: var(--purple); }
.spark--accent .spark__line { stroke: var(--accent-text); }
.spark--accent .spark__area { fill: var(--accent-text); }
/* Спокойный ряд (ноль тревог за период) — нейтральной линией, а не красной */
.spark--muted .spark__line { stroke: var(--line-strong); }
.spark--muted .spark__area { fill: var(--line-strong); opacity: 0.1; }
.spark__fail { fill: var(--danger); }
.spark__empty { font-size: 12px; color: var(--muted); }

.area { display: flex; align-items: stretch; gap: 8px; width: 100%; max-width: 240px; }
.area__axis { display: flex; flex-direction: column; justify-content: space-between; padding-bottom: 16px; font-size: 10px; line-height: 1; color: var(--muted); text-align: right; }
.area__body { flex: 1; min-width: 0; display: flex; flex-direction: column; }
.area__svg { display: block; width: 100%; height: 56px; overflow: visible; }
.area__grid { stroke: var(--line); stroke-width: 1; vector-effect: non-scaling-stroke; }
.area__line { fill: none; stroke: var(--ok); stroke-width: 1.5; stroke-linejoin: round; }
.area__fill { fill: var(--ok); opacity: 0.18; }
.area--warn .area__line { stroke: var(--warn); }
.area--warn .area__fill { fill: var(--warn); }
.area--danger .area__line { stroke: var(--danger); }
.area--danger .area__fill { fill: var(--danger); }
.area__dates { display: flex; justify-content: space-between; margin-top: 5px; font-size: 10px; line-height: 1; color: var(--muted); }

.chart { margin-top: 4px; }
.chart__svg { display: block; width: 100%; height: auto; overflow: visible; }
.chart__grid { stroke: var(--line); stroke-width: 1; }
.chart__label { fill: var(--muted); font-family: var(--sans); font-size: 12px; }
.chart__line { fill: none; stroke-width: 2; stroke-linejoin: round; stroke-linecap: round; }
.chart__line--dau, .chart__line--total, .chart__line--accent { stroke: var(--accent-text); }
.chart__line--wau { stroke: var(--info); stroke-dasharray: 5 4; stroke-width: 1.5; }
.chart__line--errors { stroke: var(--danger); stroke-width: 1.5; }
.chart__line--ok { stroke: var(--ok); }
.chart__line--warn { stroke: var(--warn); }
.chart__line--danger { stroke: var(--danger); }
.chart__dot { fill: var(--accent-text); stroke: var(--surface); stroke-width: 1; }
.chart__dot--errors, .chart__dot--danger { fill: var(--danger); }
.chart__dot--ok { fill: var(--ok); }
.chart__dot--warn { fill: var(--warn); }
.chart-legend { display: inline-flex; flex-wrap: wrap; gap: 12px 20px; font-size: 13px; color: var(--ink-2); }
.chart-legend__item { display: inline-flex; align-items: center; gap: 8px; }
.chart-legend__item::before { content: ''; display: inline-block; width: 8px; height: 8px; border-radius: 50%; background: var(--accent-text); }
/* DAU и WAU — два близких синих: в легенде образец линии (сплошная и пунктир 5–4, как на графике), а не точка */
.chart-legend__item--dau::before, .chart-legend__item--wau::before { width: 14px; height: 2px; border-radius: 1px; }
.chart-legend__item--wau::before { background: repeating-linear-gradient(90deg, var(--info) 0 5px, transparent 5px 9px); }
.chart-legend__item--total::before { background: var(--accent-text); }
.chart-legend__item--errors::before { background: var(--danger); }
.chart-legend__item--ok::before { background: var(--ok); }
.chart-legend__item--warn::before { background: var(--warn); }
.chart-legend__item--danger::before { background: var(--danger); }
.donut { display: block; width: 168px; height: 168px; flex: none; }
.donut__slice { fill: none; }
.donut__slice--ok { stroke: var(--ok); }
.donut__slice--warn { stroke: var(--warn); }
.donut__slice--danger { stroke: var(--danger); }
.donut__empty { fill: none; stroke: var(--surface-3); }
/* Доли на ярких срезах тёмной темы белым читались 1,9–3,1:1 — подпись цветом поверхности (тёмная в тёмной теме, белая в светлой) */
.donut__label { fill: var(--surface); font-family: var(--sans); font-size: 12px; font-weight: 600; }

/* ── Адаптив компонентов ───────────────────────────────────────── */

/* Широкие экраны: карточкам и колонкам таблиц можно дать больше места — обрезать текст незачем */
@media (min-width: 1600px) {
  .grid--cards { grid-template-columns: repeat(auto-fill, minmax(340px, 1fr)); }
  .metrics { grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); }
  table.data td:not(.cell-actions) { max-width: 520px; }
  table.data td.cell-wrap, .cell-wrap, .cell-break { max-width: 560px; }
  .cell-changelog { max-width: 400px; }
  table.data td.cell-details { max-width: 360px; }
  .sources { columns: 3; }
}
@media (min-width: 2000px) {
  .grid--cards { grid-template-columns: repeat(auto-fill, minmax(380px, 1fr)); }
  table.data td:not(.cell-actions) { max-width: 640px; }
  table.data td.cell-wrap, .cell-wrap, .cell-break { max-width: 720px; }
  .sources { columns: 4; }
}

@media (max-width: 1100px) {
  .grid--wide, .grid--main { grid-template-columns: minmax(0, 1fr); }
  .kpis { grid-template-columns: repeat(3, minmax(0, 1fr)); }
  .stats { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

@media (max-width: 900px) {
  .card, .table-card { padding: 12px; }
  .grid--cards { grid-template-columns: minmax(0, 1fr); }
  .card--flush .card__head { padding: 12px 12px 0; }
  .card--flush table.data th:first-child, .card--flush table.data td:first-child { padding-left: 12px; }
  .card--flush table.data th:last-child, .card--flush table.data td:last-child { padding-right: 12px; }
  .card--flush .card__foot { padding: 12px; }
  .toolbar__count { margin-left: 0; width: 100%; }
  .search { flex-basis: 100%; max-width: none; }
  .kv__row { grid-template-columns: minmax(0, 1fr); gap: 2px; }
  .cell-wrap, .cell-changelog, .cell-details { max-width: 260px; }
  .toast { right: 12px; left: 12px; bottom: 12px; max-width: none; }
  .dialog { padding: 16px; }
  /* Закреплённая зона кнопок формы выводит фон на края окна — поля под отступ 16 px (иначе −24 px давали горизонтальную прокрутку) */
  .dialog--form .dialog__actions { bottom: -16px; margin: 16px -16px -16px; padding: 12px 16px 16px; }
  .segment { grid-template-columns: 1fr; }
  .segment--row { width: 100%; display: grid; grid-template-columns: repeat(3, 1fr); }
  .scope .segment--row { grid-template-columns: repeat(2, 1fr); }
  .period { flex-wrap: wrap; }
  .period .field { flex-basis: 100%; }
  .bulk-bar .link-button { margin-left: 0; }
  .banner__actions { margin-left: 0; width: 100%; }
  .check--inline { width: 100%; }
  .grid--two { grid-template-columns: minmax(0, 1fr); }
  .metrics, .metrics--4, .metrics--5, .metrics--6, .kpis, .stats { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 8px; }
  /* Нечётная последняя карточка (пятая у «Устройств» и «Проектов») — на всю строку, без пустой клетки справа */
  :is(.metrics, .metrics--5, .kpis) > :last-child:nth-child(odd) { grid-column: 1 / -1; }
  .metric__value { font-size: 22px; }
  .kpi { padding: 12px; }
  .kpi__value { padding-left: 0; font-size: 22px; }
  .kpi__body .spark { display: none; }
  .stat { padding: 16px 12px; gap: 2px 12px; }
  .stat__icon { width: 44px; height: 44px; }
  .stat__icon .icon { width: 22px; height: 22px; }
  .stat__value { font-size: 24px; }
  .stat__delta { margin-top: 8px; }
  .pager { justify-content: center; }
  .pager__pages { margin-left: 0; }
  .tabs { width: 100%; }
  .tab { flex: 1; justify-content: center; }
  .tabs--servers { flex-wrap: nowrap; overflow-x: auto; }
  .tabs--servers .tab { flex: none; }
  .stat__label { font-size: 13px; }
}
/* Фильтры во всю ширину по одному в ряд — только на телефоне: на планшете (641–900) у «Событий» было 7 рядов фильтров,
   теперь они встают по два-три в ряд по своей базовой ширине */
@media (max-width: 640px) {
  .field--search, .field--select, .field--select-narrow { flex-basis: 100%; max-width: none; }
  .toolbar__filters { width: 100%; }
  .toolbar__filters .field { flex-basis: 100%; }
}

/* На узких экранах строки таблиц — карточками: подпись столбца из data-label */
@media (max-width: 640px) {
  /* Шапка убрана с глаз, но остаётся в дереве доступности: связь th[id] ↔ td[headers] должна разрешаться */
  table.data thead { display: block; width: 0; height: 0; overflow: hidden; }
  table.data thead tr, table.data thead th { display: block; width: 0; height: 0; padding: 0; border: 0; overflow: hidden; white-space: nowrap; }
  table.data, table.data tbody, table.data tr, table.data td { display: block; width: 100%; }
  table.data tbody tr { margin-top: 8px; padding: 8px 12px; border: 1px solid var(--line); border-radius: var(--radius); background: var(--surface-2); }
  table.data tbody tr:first-child { margin-top: 0; }
  table.data td, table.data td:not(.cell-actions) { display: flex; align-items: flex-start; justify-content: space-between; gap: 12px; padding: 8px 0; max-width: none; white-space: normal; overflow: visible; text-overflow: clip; border-bottom: 1px solid var(--line); text-align: left; }
  table.data tr td:last-child { border-bottom: 0; }
  table.data td[data-label]::before { content: attr(data-label); flex: 0 0 38%; font-size: 12px; color: var(--muted); }
  table.data td > * { min-width: 0; }
  /* Подпись столбца слева, значение справа; вторая строка ячейки (почта, ник, время «с …») — на новой строке, не внахлёст */
  table.data td { flex-wrap: wrap; }
  table.data td > .cell-sub { flex-basis: 100%; text-align: right; }
  /* Многострочный текст (начало стека, причина отказа, текст ошибки) читается слева направо, а не «флагом» справа */
  table.data td > .cell-sub.cell-wrap, table.data td > .cell-sub.cell-break { text-align: left; }
  table.data td > .cell-flag { margin-left: auto; }
  /* Заголовок ячейки (название алерта, правила, канала) переносится, а не режется многоточием без подсказки */
  table.data td > .cell-title { flex: 0 1 auto; min-width: 0; white-space: normal; overflow: visible; text-overflow: clip; overflow-wrap: anywhere; text-align: right; }
  /* Действия: подпись столбца отдельной строкой, иконки под ней во всю ширину — иначе последняя кнопка
     срывается на вторую строку сиротой, а без подписи ряд иконок в карточке ничем не объяснён */
  table.data .cell-actions { justify-content: flex-end; width: auto; min-width: 0; }
  /* В карточке ячейка — обычный flex-ряд со своими отступами: подъём ряда действий из табличного
     режима здесь не нужен и сбил бы верхний отступ строки-карточки на 4 px */
  table.data td.cell-actions { padding-top: 8px; }
  table.data td.cell-actions::before { flex-basis: 100%; }
  /* Бокс 40 px — минимальная зона касания, она важнее просторного интервала. Ширина ячейки на 390 —
     314 px, семь действий «Пользователей» с gap 4 px занимают 7×40 + 6×4 = 304 px и держатся в одну
     строку с запасом; при 6 px не хватало двух пикселей и последняя кнопка срывалась вниз сиротой.
     На 320 ячейка узкая для семи в ряд, но перенос выходит 5 + 2, а не «шесть и одна». */
  /* Кнопки делят ширину карточки поровну (40…64 px) и не ниже 44: семь действий «Пользователей» на 390 —
     в один ряд по 43 px, на 320 — 5 + 2, каждая под палец */
  table.data .cell-actions .actions { flex: 1 1 100%; flex-wrap: wrap; justify-content: flex-end; gap: 4px; }
  table.data .cell-actions .button--icon { flex: 1 1 44px; width: auto; min-width: 44px; max-width: 64px; min-height: var(--touch); }
  table.data .row-state td { display: block; text-align: center; }
  /* Итог таблицы («Итого (уникальных)» в «Активности по дням») не пропадает, а идёт последней карточкой;
     подписи столбцов у его ячеек ставит экран (data-label) */
  table.data tfoot { display: block; margin-top: 8px; }
  table.data tfoot tr { padding: 8px 12px; border: 1px solid var(--line-strong); border-radius: var(--radius); background: var(--surface-2); }
  table.data tfoot td { border-top: 0; }
  table.data tfoot td[data-label]::before { font-weight: 400; }
  /* tr > — чтобы перебить «table.data td:not(.cell-actions) { display: flex }» выше (0,2,2): чекбоксы в карточках снова скрыты,
     как решено в гейте 2 (массовые действия — с широкого экрана) */
  table.data tr > .cell-check { display: none; }
  .cell-wrap, .cell-break, .cell-changelog, .cell-details, .cell-id { max-width: none; min-width: 0; }
}

/* «Пользователи» и «Устройства» уходят в карточки раньше остальных таблиц (до 1199 px): у «Пользователей» семь действий,
   «Устройствам» нужно не меньше 900 px (замер гейта 3: на 1024 не хватало 180 px, на 1101 — 111, на 1200 — 12) —
   во внутренней прокрутке колонка действий просто не видна */
@media (min-width: 641px) and (max-width: 1199px) {
  /* Шапка убрана с глаз, но остаётся в дереве доступности: связь th[id] ↔ td[headers] должна разрешаться */
  :is(table.data--users, table.data--devices) thead { display: block; width: 0; height: 0; overflow: hidden; }
  :is(table.data--users, table.data--devices) thead tr, :is(table.data--users, table.data--devices) thead th { display: block; width: 0; height: 0; padding: 0; border: 0; overflow: hidden; white-space: nowrap; }
  :is(table.data--users, table.data--devices), :is(table.data--users, table.data--devices) tbody, :is(table.data--users, table.data--devices) tr, :is(table.data--users, table.data--devices) td { display: block; width: 100%; }
  :is(table.data--users, table.data--devices) tbody tr { margin-top: 8px; padding: 8px 12px; border: 1px solid var(--line); border-radius: var(--radius); background: var(--surface-2); }
  :is(table.data--users, table.data--devices) tbody tr:first-child { margin-top: 0; }
  :is(table.data--users, table.data--devices) td, :is(table.data--users, table.data--devices) td:not(.cell-actions) { display: flex; align-items: flex-start; justify-content: space-between; gap: 12px; padding: 8px 0; max-width: none; white-space: normal; overflow: visible; text-overflow: clip; border-bottom: 1px solid var(--line); text-align: left; }
  :is(table.data--users, table.data--devices) tr td:last-child { border-bottom: 0; }
  :is(table.data--users, table.data--devices) td[data-label]::before { content: attr(data-label); flex: 0 0 38%; font-size: 12px; color: var(--muted); }
  :is(table.data--users, table.data--devices) td > * { min-width: 0; }
  /* Подпись столбца слева, значение справа; вторая строка ячейки (почта, ник, время «с …») — на новой строке, не внахлёст */
  :is(table.data--users, table.data--devices) td { flex-wrap: wrap; }
  :is(table.data--users, table.data--devices) td > .cell-sub { flex-basis: 100%; text-align: right; }
  :is(table.data--users, table.data--devices) td > .cell-flag { margin-left: auto; }
  :is(table.data--users, table.data--devices) td > .cell-title { flex: 0 1 auto; min-width: 0; white-space: normal; overflow: visible; text-overflow: clip; overflow-wrap: anywhere; text-align: right; }
  /* Действия: подпись столбца отдельной строкой, иконки под ней во всю ширину — иначе последняя кнопка
     срывается на вторую строку сиротой, а без подписи ряд иконок в карточке ничем не объяснён */
  :is(table.data--users, table.data--devices) .cell-actions { justify-content: flex-end; width: auto; min-width: 0; }
  /* То же, что и в карточном режиме ≤640: подъём ряда действий отменяем */
  :is(table.data--users, table.data--devices) td.cell-actions { padding-top: 8px; }
  :is(table.data--users, table.data--devices) td.cell-actions::before { flex-basis: 100%; }
  :is(table.data--users, table.data--devices) .cell-actions .actions { flex: 1 1 100%; flex-wrap: wrap; justify-content: flex-end; gap: 4px; }
  :is(table.data--users, table.data--devices) .cell-actions .button--icon { flex: 1 1 40px; width: auto; min-width: 40px; max-width: 64px; min-height: var(--touch); }
  :is(table.data--users, table.data--devices) .row-state td { display: block; text-align: center; }
  :is(table.data--users, table.data--devices) tfoot { display: none; }
  :is(table.data--users, table.data--devices) .cell-check { display: none; }
  :is(.data--users, .data--devices) .cell-wrap, :is(.data--users, .data--devices) .cell-break, :is(.data--users, .data--devices) .cell-changelog, :is(.data--users, .data--devices) .cell-details, :is(.data--users, .data--devices) .cell-id { max-width: none; min-width: 0; }
}

/* На самых узких экранах (320–360) две колонки счётчиков уже не помещаются — документ уезжал вбок на ~12 px */
@media (max-width: 360px) {
  .metrics, .metrics--4, .metrics--5, .metrics--6, .kpis, .stats { grid-template-columns: minmax(0, 1fr); }
}

/* 1000–1200: колонка ряда сужается сайдбаром, и таблица банов уходит во внутреннюю прокрутку —
   на этой ширине карточка занимает весь ряд */
@media (min-width: 901px) and (max-width: 1200px) {
  .grid--two > .card--wide-row { grid-column: 1 / -1; }
}

/* Сенсор, добивка: правила ниже перекрывают одноимённые десктопные (вкладки 34, раскрывашки 36, ссылки-действия
   экрана 20 px), которые в файле стоят позже общего блока (pointer: coarse) и потому выигрывали у него */
@media (pointer: coarse) {
  .tab { min-height: var(--touch); }
  .details summary, .props summary, .alert-details summary { min-height: var(--touch); }
  a.link.screen__action { display: inline-flex; align-items: center; min-height: var(--touch); }
  /* Чекбоксы выбора строк: 18 px пальцем не попасть (правило .cell-check input стоит выше и иначе выигрывало бы) */
  .cell-check input, table.data .cell-check input { width: 24px; height: 24px; }
  /* Кнопки в строке источника («Категории видео», «ТВ-каналы», «Настройки»: 32 и 36 px на ≤ 480) и строки списка жанров в диалогах (36) — 44 */
  .service-line__actions .button, .check-group .check { min-height: var(--touch); }
}
