/* Нагульщик кук — оформление личного кабинета.
   Язык интерфейса: Apple. Матовые слои поверх контента, системный шрифт,
   пружинные кривые вместо линейных, тёмная тема наравне со светлой.
   Всё, что задаёт вид и движение, живёт здесь — в разметке ни одного стиля. */

/* ------------------------------------------------------------ токены */
:root {
  color-scheme: light dark;

  --bg:            #f2f2f7;
  --bg-deep:       #e8e8ed;
  --surface:       rgba(255, 255, 255, 0.72);
  --surface-solid: #ffffff;
  --surface-2:     rgba(255, 255, 255, 0.55);
  --hairline:      rgba(0, 0, 0, 0.10);
  --hairline-soft: rgba(0, 0, 0, 0.06);

  --text:      #1d1d1f;
  /* 🔴 Вторичный и третичный серый ПОДНЯТЫ по замеру контраста:
     было #6e6e73 (4.54:1, впритык) и #8e8e93 (2.92:1, вдвое ниже нормы).
     Третичным набраны подписи под числами на плитках и в карточках
     профилей — кегль 11–12px, и именно они читались плохо. */
  --text-2:    #5f5f64;
  --text-3:    #67676c;

  /* На фоне страницы #f2f2f7 прежний #0071e3 давал 4.21:1 — ниже нормы
     для пунктов меню (14px). #0062cc даёт 5.20:1 и остаётся синим Apple. */
  --accent:      #0062cc;
  --accent-soft: rgba(0, 98, 204, 0.12);
  --green:       #157a33;
  --green-soft:  rgba(28, 140, 60, 0.12);
  --amber:       #9a6400;
  --amber-soft:  rgba(180, 120, 0, 0.14);
  --red:         #c92a2a;
  --red-soft:    rgba(201, 42, 42, 0.12);

  --r-sm: 8px;
  --r-md: 12px;
  --r-lg: 18px;
  --r-xl: 24px;

  --shadow-1: 0 1px 2px rgba(0, 0, 0, 0.05), 0 4px 14px rgba(0, 0, 0, 0.06);
  --shadow-2: 0 8px 40px rgba(0, 0, 0, 0.14);
  --switch-off:  rgba(120, 120, 128, 0.24);
  /* Заливка плашек (чипы, бейджи, жёлоба) — системный fill Apple. Отдельно от
     --surface-2: тот белый и на белой карточке пропадает, он только для hover. */
  --fill: rgba(120, 120, 128, 0.12);

  /* Пружина без перелёта — критически задемпфированная, отклик ~0.4 с.
     Перелёт оставлен только там, где движению предшествует «бросок». */
  --spring:      cubic-bezier(0.32, 0.72, 0, 1);
  --spring-soft: cubic-bezier(0.4, 0, 0.2, 1);

  --sidebar-w: 232px;
  --log-w: 340px;
}

@media (prefers-color-scheme: dark) {
  :root {
    --bg:            #000000;
    --bg-deep:       #0a0a0c;
    --surface:       rgba(28, 28, 30, 0.72);
    --surface-solid: #1c1c1e;
    --surface-2:     rgba(44, 44, 46, 0.55);
    --hairline:      rgba(255, 255, 255, 0.12);
    --hairline-soft: rgba(255, 255, 255, 0.07);

    --text:   #f5f5f7;
    --text-2: #a1a1a6;
    --text-3: #8a8a8e;

    --accent:      #0a84ff;
    --accent-soft: rgba(10, 132, 255, 0.20);
    --green:       #30d158;
    --green-soft:  rgba(48, 209, 88, 0.18);
    --amber:       #ffd60a;
    --amber-soft:  rgba(255, 214, 10, 0.16);
    --red:         #ff453a;
    --red-soft:    rgba(255, 69, 58, 0.18);

    --shadow-1: 0 1px 2px rgba(0, 0, 0, 0.5), 0 8px 28px rgba(0, 0, 0, 0.45);
    --shadow-2: 0 12px 48px rgba(0, 0, 0, 0.6);
    --switch-off: rgba(120, 120, 128, 0.42);
    --fill: rgba(120, 120, 128, 0.26);
  }
}

/* Ручной переключатель темы должен побеждать системный в обе стороны. */
:root[data-theme="light"] {
  color-scheme: light;
  --bg: #f2f2f7; --bg-deep: #e8e8ed;
  --surface: rgba(255,255,255,.72); --surface-solid: #fff; --surface-2: rgba(255,255,255,.55);
  --hairline: rgba(0,0,0,.10); --hairline-soft: rgba(0,0,0,.06);
  --text: #1d1d1f; --text-2: #5f5f64; --text-3: #67676c;
  --accent: #0062cc; --accent-soft: rgba(0,98,204,.12);
  --green: #157a33; --green-soft: rgba(28,140,60,.12);
  --amber: #9a6400; --amber-soft: rgba(180,120,0,.14);
  --red: #c92a2a; --red-soft: rgba(201,42,42,.12);
  --shadow-1: 0 1px 2px rgba(0,0,0,.05), 0 4px 14px rgba(0,0,0,.06);
  --shadow-2: 0 8px 40px rgba(0,0,0,.14); --switch-off: rgba(120,120,128,.24); --fill: rgba(120,120,128,.12);
}
:root[data-theme="dark"] {
  color-scheme: dark;
  --bg: #000; --bg-deep: #0a0a0c;
  --surface: rgba(28,28,30,.72); --surface-solid: #1c1c1e; --surface-2: rgba(44,44,46,.55);
  --hairline: rgba(255,255,255,.12); --hairline-soft: rgba(255,255,255,.07);
  --text: #f5f5f7; --text-2: #a1a1a6; --text-3: #8a8a8e;
  --accent: #0a84ff; --accent-soft: rgba(10,132,255,.20);
  --green: #30d158; --green-soft: rgba(48,209,88,.18);
  --amber: #ffd60a; --amber-soft: rgba(255,214,10,.16);
  --red: #ff453a; --red-soft: rgba(255,69,58,.18);
  --shadow-1: 0 1px 2px rgba(0,0,0,.5), 0 8px 28px rgba(0,0,0,.45);
  --shadow-2: 0 12px 48px rgba(0,0,0,.6); --switch-off: rgba(120,120,128,.42); --fill: rgba(120,120,128,.26);
}

/* --------------------------------------------------------------- основа */
* { box-sizing: border-box; }

/* Любой класс с собственным `display` (а у .btn это inline-flex) перебивает
   браузерное правило для `hidden` — и «спрятанная» кнопка остаётся на виду. */
[hidden] { display: none !important; }

html, body { height: 100%; }

body {
  margin: 0;
  background: var(--bg);
  color: var(--text);
  font: 15px/1.5 -apple-system, BlinkMacSystemFont, "SF Pro Text", "Segoe UI",
        system-ui, "Helvetica Neue", Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
  font-optical-sizing: auto;
}

h1, h2, h3 { margin: 0; font-weight: 600; }
/* Крупный кегль требует отрицательного трекинга — иначе буквы «разъезжаются». */
h1 { font-size: clamp(1.5rem, 1.2rem + 1vw, 2rem); line-height: 1.12; letter-spacing: -0.021em; }
h2 { font-size: 1.0625rem; line-height: 1.25; letter-spacing: -0.012em; }
h3 { font-size: 0.9375rem; line-height: 1.3; letter-spacing: -0.008em; }
p  { margin: 0; }

a { color: var(--accent); text-decoration: none; }
a:hover { text-decoration: underline; }

.muted { color: var(--text-2); font-size: 0.8125rem; }
.mono { font-variant-numeric: tabular-nums; font-feature-settings: "tnum" 1; }

:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
  border-radius: 6px;
}

.skip-link {
  position: fixed; top: 8px; left: 8px; z-index: 100;
  padding: 10px 16px; border-radius: var(--r-md);
  background: var(--surface-solid); color: var(--text);
  box-shadow: var(--shadow-2);
  transform: translateY(-160%);
  transition: transform .28s var(--spring);
}
.skip-link:focus-visible { transform: translateY(0); }

/* ------------------------------------------------------------- каркас */
.shell {
  display: grid;
  grid-template-columns: var(--sidebar-w) minmax(0, 1fr) var(--log-w);
  min-height: 100vh;
}

/* ------------------------------------------------------------ сайдбар */
.sidebar {
  position: sticky; top: 0; align-self: start;
  height: 100vh;
  display: flex; flex-direction: column; gap: 4px;
  padding: 18px 12px 14px;
  background: var(--surface);
  backdrop-filter: blur(28px) saturate(180%);
  -webkit-backdrop-filter: blur(28px) saturate(180%);
  border-right: 1px solid var(--hairline);
}

.brand { display: flex; align-items: center; gap: 10px; padding: 4px 8px 18px; }
.brand-mark {
  width: 32px; height: 32px; flex: none;
  display: grid; place-items: center;
  border-radius: 9px;
  background: linear-gradient(160deg, var(--accent), #6c4bff);
  color: #fff; font-size: 12px; font-weight: 700; letter-spacing: .02em;
}
.brand-text { display: flex; flex-direction: column; line-height: 1.15; min-width: 0; }
.brand-text strong { font-size: 0.875rem; letter-spacing: -0.008em; }
.brand-text small { color: var(--text-3); font-size: 0.6875rem; }

.nav-list { list-style: none; margin: 0; padding: 0; display: grid; gap: 2px; }
.nav-list a {
  display: flex; align-items: center; gap: 10px;
  min-height: 38px; padding: 0 10px;
  border-radius: 9px;
  color: var(--text); font-size: 0.875rem; text-decoration: none;
  transition: background-color .18s var(--spring-soft), transform .18s var(--spring);
}
.nav-list a:hover { background: var(--surface-2); text-decoration: none; }
.nav-list a:active { transform: scale(0.98); }
.nav-list a[aria-current="page"] { background: var(--accent-soft); color: var(--accent); font-weight: 600; }
/* Иконки меню. Штрих и его атрибуты заданы один раз здесь, а не в каждом svg:
   один вес линии на все семь — иначе набор выглядит собранным с миру по нитке.
   Скруглённые концы и стыки — это язык SF Symbols, к нему всё и приводится. */
.nav-ico { width: 20px; height: 20px; flex: none; display: grid; place-items: center; }
.nav-ico svg {
  width: 20px; height: 20px; display: block;
  fill: none; stroke: currentColor; stroke-width: 1.7;
  stroke-linecap: round; stroke-linejoin: round;
  /* На ступень светлее подписи, но не блёклые: знак и слово читаются парой. */
  color: var(--text-2);
  transition: color .18s var(--spring-soft);
}
.nav-list a:hover .nav-ico svg { color: var(--text); }
/* У активного пункта иконка берёт цвет ссылки — плашка, текст и знак заодно. */
.nav-list a[aria-current="page"] .nav-ico svg { color: currentColor; }
.nav-badge {
  margin-left: auto; padding: 1px 7px; border-radius: 20px;
  background: var(--fill); color: var(--text-2);
  font-size: 0.6875rem; font-variant-numeric: tabular-nums;
}

.sidebar-foot { margin-top: auto; display: grid; gap: 8px; padding: 12px 6px 0; border-top: 1px solid var(--hairline-soft); }
.threads { display: flex; align-items: baseline; justify-content: space-between; font-size: 0.75rem; color: var(--text-2); }
.threads strong { font-size: 0.8125rem; color: var(--text); font-variant-numeric: tabular-nums; }
.conn { display: flex; align-items: center; gap: 7px; font-size: 0.75rem; color: var(--text-2); }
.conn .dot { width: 8px; height: 8px; border-radius: 50%; background: var(--text-3); flex: none; }
.conn[data-state="ok"] .dot { background: var(--green); }
.conn[data-state="warn"] .dot { background: var(--amber); }
.conn[data-state="bad"] .dot { background: var(--red); }

/* ------------------------------------------------------------ контент */
.content { min-width: 0; padding: 0 26px 60px; }
.content:focus { outline: none; }

.topbar {
  position: sticky; top: 0; z-index: 20;
  display: flex; align-items: flex-end; justify-content: space-between; gap: 20px;
  padding: 22px 0 14px;
  /* 🔴 Настоящее стекло: под эту полосу контент реально уезжает, и без
     размытия шапка таблицы накладывалась на подзаголовок страницы. */
  /* Плотность подобрана замером, а не на глаз: при 80% и маске с 76% из-под
     полосы читались призраки строки «НАЗВАНИЕ ДОМЕН КАТЕГОРИЯ» — маска
     обнуляла материал раньше, чем текст успевал уйти. Держим материал почти
     до самого низа и растворяем в последних 8%. */
  background: color-mix(in srgb, var(--bg) 94%, transparent);
  backdrop-filter: blur(24px) saturate(180%);
  -webkit-backdrop-filter: blur(24px) saturate(180%);
  mask-image: linear-gradient(to bottom, #000 92%, transparent);
  -webkit-mask-image: linear-gradient(to bottom, #000 92%, transparent);
  /* Кромка вместо жёсткой линейки: контент растворяется под панелью. */
}
.topbar-title { min-width: 0; }
.topbar-actions { display: flex; gap: 8px; flex-wrap: wrap; justify-content: flex-end; }
/* Поля в формах тянутся на всю ширину — в панели действий это распирало её и
   сбрасывало кнопку на второй этаж при полностью свободном месте справа. */
.topbar-actions input { width: 236px; }

/* ------------------------------------------- меню «ещё» в панели действий
   Пять равнозначных кнопок в ряд — не панель, а список всего, что умеет
   раздел. Наружу выходит главный путь, остальное живёт на слой глубже. */
.menu-wrap { position: relative; display: inline-flex; }
.menu-wrap > .btn[aria-expanded="true"] { background: var(--surface-2); }
.menu {
  position: absolute; top: calc(100% + 6px); left: 0; z-index: 60;
  /* Ширины хватает самому длинному пункту: перенос в две строки превращает
     список действий в абзац. */
  min-width: 262px; padding: 5px;
  display: grid; gap: 2px;
  border: 1px solid var(--hairline); border-radius: 14px;
  /* Плотнее обычной карточки: сквозь меню не должно читаться содержимое под
     ним. Размытие остаётся — это материал, а не глухая плита. */
  background: color-mix(in srgb, var(--surface-solid) 90%, transparent);
  backdrop-filter: blur(28px) saturate(180%);
  -webkit-backdrop-filter: blur(28px) saturate(180%);
  box-shadow: var(--shadow-2);
  transform-origin: top left;  /* растёт из своей кнопки, а не из ниоткуда */
  animation: menu-in .28s var(--spring) both;
}
.menu[hidden] { display: none; }
.menu .btn {
  width: 100%; justify-content: flex-start; text-align: left;
  min-height: 33px; padding: 0 10px;
  white-space: nowrap;
  border-color: transparent; background: transparent;
  font-weight: 400;
}
.menu .btn:hover { --btn-bg: var(--surface-2); }
.menu .btn:active { transform: scale(.985); }
@keyframes menu-in {
  from { opacity: 0; transform: scale(.94) translateY(-5px); }
  to   { opacity: 1; transform: none; }
}

/* 🔴 `minmax(0, 1fr)`, а не просто `grid`. У элемента сетки размер по
   умолчанию `min-width: auto`, то есть он НЕ сжимается ниже своего
   содержимого. Из-за этого широкая таблица распирала карточку, карточка —
   всю страницу, и на узком экране кабинет ехал вбок: 922px при окне 430.
   Причём у тела карточки своя прокрутка (`.scroll-x`) и она обязана была
   спасти — но не спасала, потому что распирало снаружи, а не внутри. */
.view { display: grid; grid-template-columns: minmax(0, 1fr); gap: 18px; }
.view > * { min-width: 0; }
.card, .card-head, .card-body { min-width: 0; }

/* ------------------------------------------------------------- кнопки */
.btn {
  --btn-bg: var(--surface-solid);
  --btn-fg: var(--text);
  display: inline-flex; align-items: center; justify-content: center; gap: 7px;
  min-height: 34px; padding: 0 14px;
  border: 1px solid var(--hairline);
  border-radius: 9px;
  background: var(--btn-bg); color: var(--btn-fg);
  font: inherit; font-size: 0.8125rem; font-weight: 600;
  cursor: pointer;
  transition: transform .12s var(--spring), background-color .18s var(--spring-soft),
              opacity .18s var(--spring-soft);
}
/* 🔴 Меняем ПЕРЕМЕННУЮ, а не само свойство. Было `background: var(--surface-2)`
   — прямое объявление перебивало `--btn-bg`, который выставляют `.btn-primary`
   и `.btn-danger`, и при наведении СИНЯЯ кнопка становилась белой, сохраняя
   белый текст. То есть «Запустить нагул», «Сохранить», «Создать», «Войти»
   пропадали под курсором во всём кабинете.
   Поймано снимком страницы входа: кнопки на нём просто не было — курсор после
   клика остался на ней. Ни линтер, ни замер геометрии такого не видят:
   элемент на месте, размер верный, «просто» невидимый. */
.btn:hover { --btn-bg: var(--surface-2); }
.btn:active { transform: scale(0.97); }          /* отклик на нажатие, не на отпускание */
.btn[disabled] { opacity: .45; cursor: not-allowed; transform: none; }
.btn-sm { min-height: 28px; padding: 0 10px; font-size: 0.75rem; }

.btn-primary { --btn-bg: var(--accent); --btn-fg: #fff; border-color: transparent; }
.btn-primary:hover { --btn-bg: color-mix(in oklab, var(--accent) 88%, #000); }
.btn-ghost { --btn-bg: transparent; border-color: transparent; color: var(--text-2); }
.btn-ghost:hover { background: var(--surface-2); color: var(--text); }
.btn-danger { --btn-bg: var(--red); --btn-fg: #fff; border-color: transparent; }
.btn-danger-ghost { --btn-bg: transparent; color: var(--red); border-color: transparent; }
.btn-danger-ghost:hover { background: var(--red-soft); }

/* Иконочные кнопки: видимая часть маленькая, зона попадания — нет. */
.icon-btn {
  position: relative; width: 30px; height: 30px; padding: 0;
  display: grid; place-items: center;
  border: 1px solid var(--hairline); border-radius: 8px;
  background: var(--surface-solid); color: var(--text-2);
  cursor: pointer;
  transition: transform .12s var(--spring), background-color .18s var(--spring-soft);
}
.icon-btn::after { content: ""; position: absolute; inset: -7px; }
/* Кнопка во flex-строке сжимается по умолчанию: в строке прокси «✕» ужимался
   до 13 px и переставал попадаться под курсор. Ширина кнопки не обсуждается. */
.btn, .icon-btn { flex: none; }
.icon-btn:hover { background: var(--surface-2); color: var(--text); }
.icon-btn:active { transform: scale(0.94); }

/* ------------------------------------------------------------- карточки */
.card {
  /* Стекла тут нет намеренно: карточка — базовый слой, под ней не проходит
     ничего, и размытие было бы платой процессору за невидимое. */
  background: var(--surface-solid);
  border: 1px solid var(--hairline);
  border-radius: var(--r-lg);
  box-shadow: var(--shadow-1);
}
.card-head {
  display: flex; align-items: center; justify-content: space-between; gap: 12px;
  padding: 16px 18px 12px;
}
.card-body { padding: 0 18px 18px; }
.card-body.tight { padding: 0; }

.grid-tiles {
  display: grid; gap: 12px;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 148px), 1fr));
}
.tile {
  padding: 15px 16px;
  background: var(--surface-solid);
  border: 1px solid var(--hairline);
  border-radius: var(--r-md);
  box-shadow: var(--shadow-1);
}
.tile-label { color: var(--text-2); font-size: 0.75rem; letter-spacing: .003em; }
.tile-value {
  margin-top: 5px;
  font-size: 1.625rem; font-weight: 600; line-height: 1.05;
  letter-spacing: -0.019em;
  font-variant-numeric: tabular-nums;
}
.tile-sub { margin-top: 3px; color: var(--text-3); font-size: 0.75rem; }

/* --------------------------------------------------------------- списки */
.rows { display: grid; }
.row {
  display: grid; align-items: center; gap: 12px;
  padding: 11px 18px;
  border-top: 1px solid var(--hairline-soft);
  font-size: 0.8125rem;
}
.rows .row:first-child { border-top: none; }
.row-head {
  /* Без капса и разрядки: в списках Apple (Finder, Mail, Numbers) шапка —
     обычный регистр вторичным цветом. Капс вразрядку — Bootstrap. */
  color: var(--text-3); font-size: 0.75rem; font-weight: 600;
  letter-spacing: .006em;
  padding-top: 8px; padding-bottom: 8px;
  border-top: none;
}
.row-link { cursor: pointer; transition: background-color .16s var(--spring-soft); }
.row-link:hover { background: var(--surface-2); }
.row .ell { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* Табличные цифры работают только с правым краем: без него 4 и 13 в одном
   столбце не сопоставляются взглядом. */
.row .num { font-variant-numeric: tabular-nums; color: var(--text-2);
            text-align: right; }
.row-head .num { text-align: right; }

/* 🔴 Сетка списка задаётся ОДИН раз на `.rows`, а строки её наследуют.
   Раньше её писали инлайном дважды — отдельно шапке, отдельно строке, — и
   копии разошлись: у шапки последний столбец стоял `auto` и схлопывался в
   ноль (ячейка-то пустая), у строки — жёсткие 96px. Итог на «Прокси»:
   подписи разъехались с колонками, а трём кнопкам не хватило 96px, и при
   `justify-content:flex-end` переполнение полезло ВЛЕВО — «Сессии» закрыла
   собой «83 мс» от значения задержки. На снимке это выглядело как обрезанное
   число, хотя обрезки не было вовсе.

   🔴 И второе, менее очевидное: КАЖДАЯ СТРОКА — ОТДЕЛЬНАЯ СЕТКА. Общий
   `--cols` этого не меняет: `fr` и `max-content` считаются внутри каждой
   строки по её собственному содержимому. У шапки ячейка действий пуста,
   `max-content` там ноль — и освободившееся место уходит соседним `fr`,
   отчего подписи снова разъезжаются со значениями. Одного общего объявления
   мало.
   Значит все негибкие столбцы задаём ТОЧНЫМИ пикселями: тогда `fr` делят
   одинаковый остаток и строки выстраиваются. Ширина колонки действий
   (`--act`) не угадана, а замерена по трём кнопкам — 188px, взято 196
   с запасом на «Проверить» в состоянии проверки. */
.rows[style*="--cols"] > .row { grid-template-columns: var(--cols); }
/* Ячейка действий: кнопки не жмутся и не наезжают на соседей. */
.row .row-actions {
  display: flex; gap: 6px; justify-content: flex-end; align-items: center;
}
.row .row-actions > * { flex: none; }

/* Диапазон «от — до»: два поля в одной строке под общей подписью.
   Раньше это были два самостоятельных поля во всю ширину друг под другом,
   и связь между ними держалась только на словах «, от» и «до» в подписях. */
.range-row { display: flex; align-items: center; gap: 8px; }
.range-row input { flex: 1 1 0; min-width: 0; }
.range-dash { color: var(--text-2); flex: none; }

/* «Тема» и «Выйти» в подвале — в одну строку и одной ширины: две кнопки
   разного размера друг под другом читались бы как разные по важности. */
/* Стадия профиля: слово + пояснение + полоса до порога.
   Цвет несёт смысл, но НЕ ЕДИНОЛИЧНО — рядом всегда слово. Иначе для
   человека с дальтонизмом «нагулян» и «копит» неразличимы. */
.pstage {
  display: flex; align-items: baseline; gap: 8px; flex-wrap: wrap;
  margin: 0; font-size: 0.8125rem;
}
.pstage-word { font-weight: 600; letter-spacing: -0.005em; }
.pstage[data-tone="ready"]   .pstage-word { color: var(--green); }
.pstage[data-tone="rest"]    .pstage-word { color: var(--amber); }
.pstage[data-tone="growing"] .pstage-word { color: var(--text); }
.pstage[data-tone="phone"]   .pstage-word { color: var(--accent); }

/* Жёлоб — системный fill, а не полупрозрачный белый: на белой карточке
   тот пропадает совсем (это уже ловили на полосе прогресса и тумблерах). */
.pgrow { height: 4px; border-radius: 2px; background: var(--fill); overflow: hidden; }
.pgrow i { display: block; height: 100%; border-radius: 2px; background: var(--accent);
           transition: width .5s var(--spring); }
.pgrow i[data-tone="ready"] { background: var(--green); }
.pgrow i[data-tone="rest"]  { background: var(--amber); }
.pgrow i[data-tone="phone"] { background: var(--accent); }

.foot-row { display: flex; gap: 8px; }
.foot-row > *, .foot-row form { flex: 1 1 0; }
.foot-row .btn { width: 100%; }

.scroll-x { overflow-x: auto; }

/* ------------------------------------------------------------- формы */
/* 🔴 `align-content: start` — иначе грид растягивает поле по высоте самого
   высокого соседа в ряду. Замер на «Прокси»: два поля 54px против 34px у
   третьего, ярлыки на разной высоте — ряд выглядел проваленным. */
.field { display: grid; gap: 6px; align-content: start; }
.field > label, .field-legend { font-size: 0.8125rem; font-weight: 600; color: var(--text); }
/* Подсказки и ошибки — не только внутри .field: в шторке они лежат сами по себе. */
.hint { color: var(--text-2); font-size: 0.75rem; line-height: 1.42; }
.err  { color: var(--red);    font-size: 0.75rem; line-height: 1.42; }

input[type="text"], input[type="number"], input[type="password"],
input[type="search"], select, textarea {
  width: 100%;
  min-height: 34px; padding: 6px 10px;
  border: 1px solid var(--hairline);
  border-radius: 9px;
  background: var(--surface-solid);
  color: var(--text);
  font: inherit; font-size: 0.8125rem;
  transition: border-color .18s var(--spring-soft), box-shadow .18s var(--spring-soft);
}
input:hover, select:hover, textarea:hover { border-color: var(--text-3); }
input[aria-invalid="true"] { border-color: var(--red); }
textarea { min-height: 70px; resize: vertical; }

.field-row { display: grid; gap: 12px; grid-template-columns: repeat(auto-fit, minmax(140px, 1fr)); }
.settings-grid { display: grid; gap: 16px; grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); }
.settings-group { display: grid; gap: 13px; align-content: start; }

/* Переключатель: подпись и сам тумблер — одна зона нажатия, без мёртвых зон.
   Выключенная дорожка красится отдельным токеном: полупрозрачная светлая
   подложка на белой карточке не читалась бы вовсе. */
.switch {
  display: flex; align-items: center; gap: 10px;
  min-height: 34px; cursor: pointer;
  font-size: 0.8125rem;
}
.switch input, .cat input {
  appearance: none; -webkit-appearance: none;
  width: 42px; height: 25px; flex: none; margin: 0; padding: 0;
  border-radius: 20px; border: none;
  background: var(--switch-off);
  position: relative; cursor: pointer;
  transition: background-color .24s var(--spring);
}
.switch input::after, .cat input::after {
  content: ""; position: absolute; top: 2px; left: 2px;
  width: 21px; height: 21px; border-radius: 50%;
  background: #fff; box-shadow: 0 1px 3px rgba(0,0,0,.3);
  transition: transform .28s var(--spring);
}
.switch input:checked, .cat input:checked { background: var(--green); }
.switch input:checked::after, .cat input:checked::after { transform: translateX(17px); }
.switch input:hover, .cat input:hover { border: none; }
.switch .sw-text { display: grid; gap: 1px; }
.switch .sw-text small { color: var(--text-2); font-size: 0.75rem; }

.chips { display: flex; flex-wrap: wrap; gap: 6px; }
.chips.nowrap { flex-wrap: nowrap; overflow: hidden; }
.chips.nowrap .chip { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.chip {
  padding: 3px 10px; border-radius: 20px;
  background: var(--fill); color: var(--text-2);
  font-size: 0.75rem; border: 1px solid transparent;
}
.chip[data-tone="accent"] { background: var(--accent-soft); color: var(--accent); }
.chip[data-tone="green"]  { background: var(--green-soft);  color: var(--green); }
.chip[data-tone="amber"]  { background: var(--amber-soft);  color: var(--amber); }
.chip[data-tone="red"]    { background: var(--red-soft);    color: var(--red); }

/* --------------------------------------------------------- живой лог */
.logpanel {
  position: sticky; top: 0; align-self: start;
  height: 100vh; display: flex; flex-direction: column;
  background: var(--surface);
  backdrop-filter: blur(28px) saturate(180%);
  -webkit-backdrop-filter: blur(28px) saturate(180%);
  border-left: 1px solid var(--hairline);
}
.logpanel-head {
  display: flex; align-items: center; gap: 10px;
  padding: 18px 16px 10px;
}
/* Заголовок занимает свободное место, лампа и кнопки прижаты вправо. */
.logpanel-head h2 { flex: 1; min-width: 0; }
.log { list-style: none; margin: 0; padding: 0 12px 20px; overflow-y: auto; flex: 1; display: grid; gap: 3px; align-content: start; }
.log li {
  padding: 7px 10px; border-radius: 9px;
  font-size: 0.75rem; line-height: 1.4; color: var(--text-2);
  background: transparent;
  animation: logIn .32s var(--spring);
}
.log li b { color: var(--text); font-weight: 600; }
.log li time { display: block; color: var(--text-3); font-size: 0.6875rem; font-variant-numeric: tabular-nums; }
/* Уровень отмечаем полоской у края, а не заливкой всей строки: залитые плашки
   тяжелее собственного текста и семь предупреждений подряд забивали панель. */
.log li[data-l="ok"], .log li[data-l="warn"], .log li[data-l="error"] {
  padding-left: 10px;
  /* Именно граница, а не inset-тень: тень шла по скруглению и полоска
     получалась скобкой с пустой серединой. Левый угол выпрямлен под неё. */
  border-left: 2px solid currentColor;
  border-radius: 2px 9px 9px 2px;
}
.log li[data-l="ok"]    { color: var(--green); }
.log li[data-l="warn"]  { color: var(--amber); }
.log li[data-l="error"] { color: var(--red); background: var(--red-soft); }
.log li[data-l="visit"] { background: var(--fill); color: var(--text); }

/* Счётчик схлопнутых повторов. */
.log-x {
  display: inline-block; margin-left: 7px; padding: 0 6px;
  border: 1px solid currentColor; border-radius: 20px;
  font-size: 0.625rem; font-weight: 600; font-variant-numeric: tabular-nums;
  white-space: nowrap; opacity: .72; vertical-align: 1px;
}

@keyframes logIn { from { opacity: 0; transform: translateY(-6px); } to { opacity: 1; transform: none; } }

/* -------------------------------------------------------------- шторка */
.sheet {
  width: min(460px, calc(100vw - 32px));
  padding: 0; border: 1px solid var(--hairline);
  border-radius: var(--r-xl);
  background: var(--surface-solid); color: var(--text);
  box-shadow: var(--shadow-2);
}
.sheet::backdrop { background: rgba(0, 0, 0, 0.32); backdrop-filter: blur(3px); }
.sheet[open] { animation: sheetIn .34s var(--spring); }
/* Материал прибывает: масштаб и размытие вместе, а не голая прозрачность. */
@keyframes sheetIn {
  from { opacity: 0; transform: scale(0.94) translateY(12px); filter: blur(6px); }
  to   { opacity: 1; transform: none; filter: none; }
}
.sheet-body { display: grid; gap: 14px; padding: 22px; max-height: min(86vh, 900px); overflow-y: auto; }
.sheet-actions {
  display: flex; gap: 8px; justify-content: flex-end; flex-wrap: wrap;
  position: sticky; bottom: -22px; margin: 4px -22px -22px; padding: 14px 22px;
  background: var(--surface-solid);
  border-top: 1px solid var(--hairline-soft);
}
.sep { border: none; border-top: 1px solid var(--hairline-soft); margin: 2px 0; }

.qstats { display: grid; gap: 8px; padding: 12px; border-radius: var(--r-md); background: var(--fill); }
.qstats-head { display: flex; align-items: center; justify-content: space-between; gap: 10px; }
.qlist { list-style: none; margin: 0; padding: 0; display: grid; gap: 4px; }
.qlist li { display: flex; align-items: baseline; gap: 10px; font-size: 0.75rem; }
.qlist .q { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.qlist .n { font-variant-numeric: tabular-nums; color: var(--text-2); }
.qlist .when { color: var(--text-3); font-size: 0.6875rem; }

/* -------------------------------------------------------------- тосты */
.toast-wrap {
  position: fixed; right: 18px; bottom: 18px; z-index: 90;
  display: grid; gap: 8px; justify-items: end;
  pointer-events: none;
}
.toast {
  max-width: 380px; padding: 11px 15px;
  border-radius: var(--r-md);
  background: var(--surface-solid); color: var(--text);
  border: 1px solid var(--hairline);
  box-shadow: var(--shadow-2);
  font-size: 0.8125rem;
  pointer-events: auto;
  animation: toastIn .38s var(--spring);
}
.toast[data-tone="error"] { border-color: var(--red); }
.toast[data-tone="ok"] { border-color: var(--green); }
@keyframes toastIn { from { opacity: 0; transform: translateY(14px) scale(.96); } to { opacity: 1; transform: none; } }

/* -------------------------------------------------------------- пустое */
.empty { display: grid; gap: 8px; justify-items: center; text-align: center; padding: 44px 24px; color: var(--text-2); }
.empty strong { color: var(--text); font-size: 0.9375rem; }
.empty .btn { margin-top: 6px; }

.spinner {
  width: 15px; height: 15px; border-radius: 50%;
  border: 2px solid var(--hairline); border-top-color: var(--accent);
  animation: spin .7s linear infinite;
}
@keyframes spin { to { transform: rotate(360deg); } }

/* ----------------------------------------------------------- адаптив */
@media (max-width: 1240px) {
  .shell { grid-template-columns: var(--sidebar-w) minmax(0, 1fr); }
  .logpanel {
    grid-column: 1 / -1; position: static; height: auto; max-height: 320px;
    border-left: none; border-top: 1px solid var(--hairline);
  }
}

@media (max-width: 860px) {
  .shell { grid-template-columns: minmax(0, 1fr); }
  /* 🔴 ПЕРЕНОС, а не горизонтальная прокрутка. Замер на 430px: полоса меню
     имела scrollWidth 1184 при ширине окна 430 — 754px спрятанного вбок, и
     ни одного признака, что там что-то есть. Кнопки «Тема» и «Выйти» стояли
     на x=976, то есть за экраном совсем: выйти из кабинета с телефона было
     нельзя. Скрытая прокрутка без кромки и стрелки — это не компактность,
     это потерянные пункты. */
  .sidebar {
    position: static; height: auto; flex-direction: row; align-items: center;
    flex-wrap: wrap; row-gap: 8px;
    gap: 12px; padding: 10px 14px; overflow-x: visible;
    border-right: none; border-bottom: 1px solid var(--hairline);
  }
  .brand { padding: 0; }
  .brand-text { display: none; }
  .nav-list { display: flex; gap: 4px; flex-wrap: wrap; }
  .nav-list a { white-space: nowrap; }
  /* Подвал — своей строкой во всю ширину, а не «прижат вправо» за краем. */
  .sidebar-foot {
    margin: 0; padding: 0; border: none; order: 99; width: 100%;
    display: flex; align-items: center; gap: 10px;
  }
  .threads, .conn { white-space: nowrap; }
  /* В строке подвала подпись и значение слипались: «Потоки0 / 2». */
  .threads { display: flex; align-items: baseline; gap: 6px; }
  .content { padding: 0 14px 40px; }
  .pcard-stats { grid-template-columns: repeat(2, 1fr); }

  /* В ряд заголовку и кнопкам тесно: заголовок сжимался в колонку по слову,
     а кнопки вставали лесенкой. На узком они живут этажами. */
  .topbar { flex-direction: column; align-items: stretch; gap: 12px; padding-bottom: 12px; }
  .topbar-actions { justify-content: flex-start; }
  .topbar-actions .btn { flex: 1 1 auto; }
  .topbar-actions input { width: 100%; }
  .menu-wrap { flex: 0 0 auto; }
  /* На узком экране меню шире панели быть не может. */
  .menu { min-width: min(262px, calc(100vw - 40px)); }
}

/* ------------------------------------------------- предпочтения системы */
/* 🔴 Спиннеру вращение ВОЗВРАЩАЕМ. Общий сброс ниже гасит любую анимацию,
   и единственный индикатор неопределённой загрузки замирал — «мягче»
   превращалось в «без обратной связи». Равномерное вращение мелкого
   элемента вестибулярно безопасно, оно и остаётся. */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: .01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: .12s !important;
  }
  .btn:active, .icon-btn:active, .nav-list a:active { transform: none; }
  .sheet[open] { animation: none; }
}

@media (prefers-reduced-transparency: reduce) {
  .sidebar, .logpanel, .card, .tile {
    background: var(--surface-solid);
    backdrop-filter: none; -webkit-backdrop-filter: none;
  }
  .topbar { background: var(--bg); }
}

/* 🔴 РАЗБОР ПО ТЕМАМ ОБЯЗАТЕЛЕН. Прежний блок объявлял тёмно-серый
   `--text-2: #4a4a4f` без разбора и стоял в файле ПОСЛЕ блока тёмной темы —
   то есть перебивал её. Замер: при системной тёмной теме и включённом
   «повысить контраст» подпись плитки давала 1.93:1 вместо 7:1, а рамки
   становились чёрными на чёрном и исчезали совсем.
   Человек, включивший режим ради читаемости, получал кабинет ХУЖЕ обычного —
   то есть настройка доступности работала против себя. */
@media (prefers-contrast: more) {
  :root {
    --hairline: rgba(0,0,0,.4); --hairline-soft: rgba(0,0,0,.25);
    --text-2: #3a3a3f; --text-3: #45454a;
  }
  @media (prefers-color-scheme: dark) {
    :root {
      --hairline: rgba(255,255,255,.5); --hairline-soft: rgba(255,255,255,.3);
      --text-2: #d8d8dc; --text-3: #c4c4c8;
    }
  }
  /* Ручной выбор темы бьёт системный и здесь — иначе режим контраста
     «чинил» бы не ту тему, которую человек видит. */
  :root[data-theme="light"] {
    --hairline: rgba(0,0,0,.4); --text-2: #3a3a3f; --text-3: #45454a;
  }
  :root[data-theme="dark"] {
    --hairline: rgba(255,255,255,.5); --hairline-soft: rgba(255,255,255,.3);
    --text-2: #d8d8dc; --text-3: #c4c4c8;
  }
  .card, .tile, .btn { border-width: 1px; }
}

@media (forced-colors: active) {
  .card, .tile, .btn, .icon-btn { border: 1px solid CanvasText; }
  .switch input { border: 1px solid CanvasText; }
  .switch input:checked::after { background: Highlight; }
}

/* 🔴 Отклик на НАЖАТИЕ, а не только на наведение. Он был всего у четырёх
   селекторов из десяти нажимаемых: кликабельные строки таблиц (их сотни),
   карточки категорий, тумблеры и карточки профилей отвечали только на hover
   — то есть на сенсорном экране не отвечали вовсе. Палец жмёт, ничего не
   происходит, пока не отработает переход. */
.row-link:active, .cat:active, .switch:active { background: var(--fill); }
.pcard { transition: transform .12s var(--spring); }
.pcard:active { transform: scale(.995); }

/* Спиннер под уменьшенным движением — см. комментарий выше. */
@media (prefers-reduced-motion: reduce) {
  .spinner { animation-duration: 1.1s !important;
             animation-iteration-count: infinite !important; }
}

/* 🔴 Поля на узком экране — 16px. Safari на iPhone увеличивает страницу при
   фокусе в поле мельче 16px, и кабинет прыгает на каждом касании. На
   странице входа это уже учтено, в самом кабинете было 13px. */
@media (max-width: 860px) {
  /* 🔴 ПЕРЕЧЕНЬ ТИПОВ ЯВНО. Голый `input` ловил и чекбоксы тумблеров: замер
     на 430px дал `.switch input` 42×40 вместо 25 — тумблеры превращались в
     зелёные кляксы, а кругляш оставался прибит к верхнему краю. И для самих
     полей правило не срабатывало вовсе: базовое `input[type=...]` имеет
     специфичность 0-1-1, а голый `input` — 0-0-1, медиазапрос её не
     добавляет. То есть правка «поля 16px» не работала ни на одном поле и
     ломала все тумблеры разом. */
  input[type="text"], input[type="number"], input[type="password"],
  input[type="search"], input[type="email"], input[type="url"],
  select, textarea { font-size: 1rem; min-height: 40px; }
}

/* Безопасные зоны: `viewport-fit=cover` объявлен в разметке, а отступов под
   вырез и индикатор не было ни одного — на iPhone первый пункт меню и тосты
   уезжали под скругление. */
@media (max-width: 860px) {
  .sidebar { padding-left: max(12px, env(safe-area-inset-left));
             padding-right: max(12px, env(safe-area-inset-right)); }
  .content { padding-left: max(14px, env(safe-area-inset-left));
             padding-right: max(14px, env(safe-area-inset-right)); }
  .toast-wrap { bottom: max(18px, env(safe-area-inset-bottom)); }
}

/* ------------------------------------------- сворачивание боковых панелей */
/* Экран 1440px делят три колонки: 232 меню + контент + 340 лог. На таблице с
   семью столбцами контенту остаётся меньше половины, а лог нужен не всегда.
   Свёрнутое состояние помнится между заходами — иначе его пришлось бы
   складывать заново на каждой странице. */
.nav-fold { margin-left: auto; flex: none; }
.logpanel-head .icon-btn { flex: none; }

.shell[data-nav="folded"] { --sidebar-w: 62px; }
.shell[data-log="folded"] { --log-w: 44px; }

/* Меню в свёрнутом виде: только иконки.
   🔴 Подпись раздела — ГОЛЫЙ ТЕКСТОВЫЙ УЗЕЛ («…</span>Обзор</a>»), элемента
   вокруг неё нет, и селектором её не спрятать: правило
   `a span:not(.nav-ico)` не срабатывало вовсе — панель сжималась до 62px, а
   надписи торчали поверх контента. Гасим кеглем у ссылки и возвращаем его
   иконке и счётчику. */
.shell[data-nav="folded"] .nav-list a { font-size: 0; }
.shell[data-nav="folded"] .nav-list a .nav-ico { font-size: 1rem; }
.shell[data-nav="folded"] .brand-text,
.shell[data-nav="folded"] .threads { display: none; }
/* 🔴 «Тему» и «Выйти» НЕ прячем. Состояние сворачивания помнится между
   заходами, поэтому спрятанный «Выйти» означал бы, что выйти из кабинета
   нельзя вовсе, пока не догадаешься развернуть панель. */
.shell[data-nav="folded"] .foot-row { flex-direction: column; gap: 6px; }
.shell[data-nav="folded"] .foot-row .btn,
.shell[data-nav="folded"] .foot-row form { width: 34px; }
.shell[data-nav="folded"] .foot-row .btn {
  min-height: 30px; padding: 0; font-size: 0;
}
.shell[data-nav="folded"] .foot-row .btn::after {
  font-size: 13px; line-height: 1;
}
.shell[data-nav="folded"] #themeToggle::after { content: "◐"; }
.shell[data-nav="folded"] #logoutForm .btn::after { content: "⏻"; }
.shell[data-nav="folded"] .sidebar { padding: 18px 8px 14px; align-items: center; }
/* В строку знак 32px + отступ + кнопка 30px не помещаются в 62px панели:
   замер дал знак на x = −5.5, а кнопку с правым краем 66.5. Ставим столбиком. */
.shell[data-nav="folded"] .brand {
  flex-direction: column; gap: 8px; padding: 4px 0 14px;
}
.shell[data-nav="folded"] .nav-list a { justify-content: center; padding: 9px 0; }
.shell[data-nav="folded"] .nav-badge { display: none; }
.shell[data-nav="folded"] .nav-fold { margin: 0; transform: rotate(180deg); }

/* Лог свёрнут — остаётся вертикальный корешок с подписью, чтобы было
   очевидно, что панель не пропала, а сложена. */
.shell[data-log="folded"] .logpanel { padding: 10px 6px; overflow: hidden; }
.shell[data-log="folded"] .logpanel-head h2 {
  writing-mode: vertical-rl; font-size: 0.75rem; color: var(--text-2);
  margin-top: 10px;
}
.shell[data-log="folded"] .logpanel-head { flex-direction: column; gap: 6px; }
.shell[data-log="folded"] #logClear { display: none; }
.shell[data-log="folded"] .log { display: none; }
.shell[data-log="folded"] #logFold { transform: rotate(180deg); }

/* Ширины колонок меняются пружиной, а не рывком: панель «складывается»,
   а не исчезает кадром. */
.shell { transition: grid-template-columns .32s var(--spring); }
@media (prefers-reduced-motion: reduce) { .shell { transition: none; } }

/* На узком экране колонок нет вовсе — сворачивать нечего. */
@media (max-width: 860px) {
  .nav-fold, #logFold { display: none; }
  .shell[data-nav="folded"] .nav-list a { font-size: revert; }
  .shell[data-nav="folded"] .brand-text,
  .shell[data-nav="folded"] .threads,
  .shell[data-nav="folded"] .foot-row .btn { display: revert; }
}

/* Шапка карточки с подзаголовком — в колонку: в узкой колонке настроек
   заголовок «Жизненный цикл профиля» сжимался до 117px и рвался на три
   строки, а схема цикла рядом — ещё на три, разрываясь посреди стрелки. */
.card-head:has(> .muted) { flex-direction: column; align-items: flex-start; gap: 4px; }

/* Родные стрелки у числовых полей — чужие всей остальной форме. */
input[type="number"]::-webkit-inner-spin-button,
input[type="number"]::-webkit-outer-spin-button { appearance: none; margin: 0; }
input[type="number"] { -moz-appearance: textfield; }

/* Панель действий шторки обрывала текст глухой плитой — растворяем кромкой,
   как уже сделано у липкой шапки. */
.sheet-actions {
  background: linear-gradient(to bottom, transparent, var(--surface-solid) 14px);
  border-top: none;
}

/* Деструктивное действие — к левому краю, подальше от «Сохранить»: на 430px
   между «Убрать правило» и «Сохранить» оставалось 10px при пальце в 44. */
.sheet-actions .btn-danger-ghost { margin-right: auto; }

/* Сценарий второго этапа: нумерованные шаги читаются как последовательность,
   а не как список настроек — потому и цифры, и отступ. */
.scenario { margin: 10px 0 14px; padding-left: 20px; display: grid; gap: 8px;
            font-size: 0.8125rem; color: var(--text-2); line-height: 1.42; }
.scenario b { color: var(--text); font-weight: 600; }

/* Панель фильтров профилей: поиск и стадии в одну строку. */
.filters { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; }
.filters input[type="search"] { flex: 1 1 260px; min-width: 0; }
.chip.filter {
  cursor: pointer; border: 1px solid var(--hairline);
  background: var(--surface-solid); color: var(--text);
  display: inline-flex; gap: 6px; align-items: center;
  transition: background-color .16s var(--spring), border-color .16s var(--spring);
}
.chip.filter b { color: var(--text-2); font-weight: 600; }
.chip.filter:hover { background: var(--fill); }
.chip.filter:active { transform: scale(.98); }
/* Выбранный фильтр — заливкой И рамкой, не только цветом: иначе на
   монохромном экране и при дальтонизме он неотличим от остальных. */
.chip.filter[data-on="true"] {
  background: var(--accent-soft); border-color: var(--accent); color: var(--accent);
}
.chip.filter[data-on="true"] b { color: var(--accent); }

/* «В работе» — ДРУГОЕ измерение, а не шестая стадия: оно накладывается поверх
   выбранной. Стоящая в общем ряду кнопка читалась бы как продолжение списка,
   и «Все» переставало быть суммой остальных. Отделяем волосяной чертой — на
   узком экране она не нужна, там группы и так на разных строках. */
.filters .chips + .chips { position: relative; padding-left: 13px; }
.filters .chips + .chips::before {
  content: ""; position: absolute; left: 0; top: 50%; translate: 0 -50%;
  width: 1px; height: 20px; background: var(--hairline);
}
@media (max-width: 720px) {
  .filters .chips + .chips { padding-left: 0; }
  .filters .chips + .chips::before { display: none; }
}

/* --------------------------------------------------------------- аналитика */

/* 🔴 Класс называется .trend, а НЕ .chart: `.chart` занят столбиками на
   «Обзоре», и его правило `.chart div { background: var(--accent) }` залило
   подложку графика синим на всю площадь. Линия при этом рисовалась верно —
   в DOM всё было правильно, а на экране сплошной прямоугольник. Поймал это
   только скриншот; линтер и разбор DOM показывали чистое.  */

/* Подпись только для читалки экрана: у селекторов в шапке видимая метка
   съела бы строку, но без метки поле остаётся безымянным. */
.vh {
  position: absolute; width: 1px; height: 1px; overflow: hidden;
  clip-path: inset(50%); white-space: nowrap;
}
.pick { display: inline-flex; align-items: center; }
.pick select { min-height: 36px; max-width: 320px; }

/* Толщина линий в SVG со скошенным viewBox поехала бы вместе с масштабом —
   отсюда non-scaling-stroke: линия остаётся волосяной при любой ширине. */
/* Ось подписываем поверх: цифры внутри viewBox растянулись бы вместе с ним. */

/* Предупреждение — не ошибка: жёлтым, а не красным. Красное «трафик
   заканчивается» рядом с настоящими сбоями обесценивает и то, и другое. */
.warn { color: var(--amber); font-size: 0.8125rem; line-height: 1.45;
        margin-top: 12px; padding: 10px 12px; border-radius: var(--r-md);
        background: var(--amber-soft); }
.card-body > .err { margin-top: 12px; padding: 10px 12px; border-radius: var(--r-md);
                    background: var(--red-soft); color: var(--red); font-size: 0.8125rem; }

/* В колонке «Изменение» фраза «без изменений» ломалась на две строки и
   раздувала каждую строку таблицы вдвое. Внутри ряда чип не переносим. */
.row .chip { white-space: nowrap; }

/* Заголовок с объяснением: без курсора-подсказки владелец не догадается
   навести мышь, а само объяснение продублировано текстом над таблицей. */
.help { cursor: help; }

/* 🔴 Скрытая половина таблицы. На 390 px «Фразы» прячут 404 px из 764 (53%),
   а скроллбар в macOS/Windows оверлейный — то есть невидимый. У правого края
   висели половинки чипов, и это читалось как дефект отрисовки, а не как
   «здесь есть ещё». Растушёвка края говорит правду без единого слова. */
.scroll-x {
  -webkit-mask-image: linear-gradient(90deg, #000 calc(100% - 28px), transparent);
          mask-image: linear-gradient(90deg, #000 calc(100% - 28px), transparent);
}
.scroll-x[data-end="1"] { -webkit-mask-image: none; mask-image: none; }

/* Точки графика рисуем НЕ внутри svg: при preserveAspectRatio="none" единица
   по X в семь раз крупнее, чем по Y, круг r=1.2 выходил овалом 18×4.8 px, и
   форма менялась вместе с шириной окна. Слой в процентах даёт круг всегда. */
/* Палец в 10 px не попадает — расширяем хит-зону прозрачным полем,
   не трогая видимый размер точки. */

/* Чипы фильтра высотой 22 px не попадались пальцем — ниже собственного
   порога проекта. На узком экране, где по ним и целятся, даём полные 44. */
.chip.filter { min-height: 32px; }
@media (max-width: 720px) {
  .chip.filter { min-height: 44px; padding-inline: 14px; }
}

/* Даты в шапке карточки не переносим: «2026-» / «08-04» посреди токена. */
.card-head .muted { white-space: nowrap; }
@media (max-width: 720px) { .card-head .muted { white-space: normal; } }

.stack-gap { margin-top: 12px; }

/* Кнопка в ряду полей не должна растягиваться на всю колонку грида:
   «Сохранить» шириной 250 px читается как основное действие страницы,
   хотя это подтверждение двух соседних полей. */
.plan-row { align-items: end; }
.plan-row .field:last-child { justify-self: start; }
.plan-row .btn { width: auto; }

/* Откуда заходили: имя, полоса доли, число, процент. Полоса важнее числа —
   по ней видно соотношение, не считая в уме. */
.src-list { display: grid; gap: 8px; }
.src-row {
  display: grid; grid-template-columns: 132px minmax(0, 1fr) 64px 48px;
  align-items: center; gap: 10px; font-size: 0.8125rem;
}
.src-name { color: var(--text); }
.src-bar { height: 8px; border-radius: 4px; background: var(--fill); overflow: hidden; }
.src-bar i { display: block; height: 100%; border-radius: 4px; background: var(--accent);
             transition: width .4s var(--spring); }
.src-bar i[data-tone="green"] { background: var(--green); }
.src-bar i[data-tone="amber"] { background: var(--amber); }
/* Прямой заход — серым: это не источник, а его отсутствие, и подсвечивать
   его цветом наравне с поисковиками значило бы врать глазу. */
.src-bar i[data-tone="grey"]  { background: var(--text-3); }
.src-n   { color: var(--text); font-weight: 600; }
.src-pct { color: var(--text-2); }
@media (max-width: 620px) {
  .src-row { grid-template-columns: 96px minmax(0, 1fr) 52px 40px; gap: 8px; }
}

/* Лампа связи в шапке живого лога. Зелёная точка — события идут; янтарная и
   слово «связи нет» — лента замерла не потому, что нагул стоит. */
.link-dot {
  display: inline-flex; align-items: center; gap: 6px;
  font-size: 0.6875rem; color: var(--text-3); white-space: nowrap;
}
.link-dot i {
  width: 7px; height: 7px; border-radius: 50%; background: var(--green);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--green) 22%, transparent);
}
.link-dot[data-state="lost"] { color: var(--amber); }
.link-dot[data-state="lost"] i {
  background: var(--amber);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--amber) 25%, transparent);
  animation: пульс 1.4s ease-in-out infinite;
}
@keyframes пульс { 50% { opacity: .35 } }
@media (prefers-reduced-motion: reduce) { .link-dot i { animation: none } }
/* Кнопка «Очистить» больше не растягивается: место занял индикатор. */
.logpanel-head .btn-ghost { margin-left: 0; }

/* Парк остановлен предохранителем — карточка должна бросаться в глаза
   первой, до всех плиток: это не деталь, это причина, по которой ничего
   не происходит. */
.card.halt {
  border-color: color-mix(in srgb, var(--red) 45%, var(--hairline));
  background: color-mix(in srgb, var(--red) 6%, var(--surface-solid));
}

/* ------------------------------------------------- пульт: свои компоненты
   Имена проверены `grep` по этому файлу до того, как заведены: файл без
   сборки, все имена в одном пространстве, и `.chart` тут уже однажды залил
   график сплошным синим. */

/* Лампа состояния. Точка, а не слово: слово уже стоит рядом чипом, а глазу
   нужен якорь, который читается периферийным зрением. */
.lamp { width: 9px; height: 9px; border-radius: 50%; flex: none;
        background: var(--text-3); box-shadow: 0 0 0 3px var(--fill); }
.lamp[data-tone="хорошо"]  { background: var(--green); box-shadow: 0 0 0 3px var(--green-soft); }
.lamp[data-tone="плохо"]   { background: var(--red);   box-shadow: 0 0 0 3px var(--red-soft); }
.lamp[data-tone="внимание"]{ background: var(--amber); box-shadow: 0 0 0 3px var(--amber-soft); }

/* Сетка карточек машин. `minmax(0, …)` обязателен: без него широкая строка
   с адресом распирает карточку, карточка — страницу, и своя прокрутка не
   спасает, потому что распирает снаружи. */
.vm-grid { display: grid; gap: 14px;
           grid-template-columns: repeat(auto-fill, minmax(min(340px, 100%), 1fr)); }
.vm-grid > * { min-width: 0; }
.vmcard { display: grid; align-content: start; }
.vmcard-head { display: flex; align-items: center; justify-content: space-between; gap: 10px; }
.vmcard-head h3 { display: flex; align-items: center; gap: 9px; min-width: 0;
                  font-size: 1rem; letter-spacing: -0.011em; }
.vmcard-head h3 .ell { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.vmcard-actions { display: flex; flex-wrap: wrap; gap: 8px; padding-top: 4px; }

/* Факты машины: подпись слева, значение справа. Сетка, а не flex — иначе
   значения не встают в колонку и глаз не может их сравнить между карточками. */
.facts { display: grid; gap: 7px; }
.fact { display: grid; grid-template-columns: 84px minmax(0, 1fr); gap: 10px;
        align-items: baseline; font-size: 0.8125rem; line-height: 1.45; }
.fact-k { color: var(--text-2); }
.fact-v { min-width: 0; overflow-wrap: anywhere; }
.fact-v[data-tone="хорошо"] { color: var(--green); }
.fact-v[data-tone="плохо"]  { color: var(--red); }
.fact-v[data-tone="тихо"]   { color: var(--text-2); }
[data-tone="плохо"] { color: var(--red); }

/* Полоса ресурса под плиткой. Жёлоб — `--fill`, а не `--surface-2`: тот
   полупрозрачный белый и на белой карточке пропадает целиком. */
.res-bar { height: 6px; border-radius: 3px; background: var(--fill);
           overflow: hidden; margin: 6px 0 4px; }
.res-bar i { display: block; height: 100%; border-radius: 3px; background: var(--accent); }
.res-bar i[data-tone="green"] { background: var(--green); }
.res-bar i[data-tone="amber"] { background: var(--amber); }
.res-bar i[data-tone="red"]   { background: var(--red); }

/* Строка идущей задачи. */
.tline { display: grid; gap: 5px; padding: 11px 0; border-top: 1px solid var(--hairline-soft); }
.tline:first-child { border-top: none; padding-top: 0; }
.tline-top { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.tline-top b { font-weight: 600; letter-spacing: -0.006em; }
.tline-last { color: var(--text-2); font-size: 0.75rem; line-height: 1.45;
              overflow-wrap: anywhere; }
/* Строки задачи целиком: моноширинный лог, своя прокрутка, страницу не рвёт. */
.tlog { margin: 0; padding: 12px; border-radius: var(--r-md); background: var(--fill);
        font-size: 0.75rem; line-height: 1.55; max-height: 420px; overflow: auto;
        white-space: pre-wrap; overflow-wrap: anywhere; }

/* Предупреждение о ресурсах. Пороги названы прямо в тексте: «мало памяти»
   без числа не даёт решить, что делать. */
.warnbox { display: block; padding: 11px 13px; border-radius: var(--r-md);
           font-size: 0.8125rem; line-height: 1.5; margin-bottom: 8px;
           background: var(--amber-soft); color: var(--text); }
.warnbox:last-child { margin-bottom: 0; }
.warnbox[data-tone="плохо"] { background: var(--red-soft); }

/* Чип состояния: тон задаём атрибутом, а не отдельными классами. */
.chip[data-tone="red"]    { background: var(--red-soft); color: var(--red); }
.chip[data-tone="green"]  { background: var(--green-soft); color: var(--green); }
.chip[data-tone="amber"]  { background: var(--amber-soft); color: var(--amber); }
.tile[data-tone="плохо"] .tile-value { color: var(--red); }
.tile[data-tone="внимание"] .tile-value { color: var(--amber); }
.tile-value small { font-size: 0.5em; font-weight: 500; color: var(--text-2);
                    letter-spacing: 0; margin-left: 2px; }

/* Уровень строки живого лога: свой цвет у работы задачи. */
.log li[data-l="work"] { color: var(--text-2); }

/* Действия внутри карточки. НЕ `.sheet-actions`: у того отрицательные поля
   под padding шторки (22px), и в карточке (18px) он вылезал за край на 3px —
   поймано замером сразу на трёх ширинах. */
.card-actions { display: flex; gap: 8px; justify-content: flex-end;
                flex-wrap: wrap; margin-top: 16px; }
/* Таблица внутри прокрутки не сжимается до нечитаемого: полоса прокрутки для
   того и есть. Без этого сетка с точными колонками распирала карточку, та —
   страницу, и на 430px весь пульт ехал вбок (699px при окне 430). */
.scroll-x > .rows { min-width: max-content; }

/* ------------------------------------------------- пульт: профили и нагул */
.prof-grid { display: grid; gap: 12px;
             grid-template-columns: repeat(auto-fill, minmax(min(320px, 100%), 1fr)); }
.prof-grid > * { min-width: 0; }
.prof { display: grid; gap: 10px; padding: 14px 16px; }
.prof-head { display: flex; align-items: center; justify-content: space-between; gap: 10px; }
.prof-head b { font-size: 0.9375rem; letter-spacing: -0.008em; }
.prof-nums { display: grid; grid-template-columns: repeat(4, 1fr); gap: 8px; }
.prof-num { display: grid; gap: 1px; }
.prof-num i { font-style: normal; font-size: 0.9375rem; font-weight: 600;
              font-variant-numeric: tabular-nums; }
.prof-num small { color: var(--text-2); font-size: 0.6875rem; }
.prof-act { display: flex; gap: 8px; flex-wrap: wrap; }
/* Лампа агента: та же логика, что у лампы связи — состояние читается цветом. */
.agent-dot { display: inline-flex; align-items: center; gap: 7px;
             font-size: 0.75rem; color: var(--text-2); }
.agent-dot i { width: 8px; height: 8px; border-radius: 50%; background: var(--text-3); }
.agent-dot[data-on="1"] i { background: var(--green); }
