/* Панель логистики — общая вёрстка редизайна.
 *
 * Все значения взяты из спеки (spec/Дизайн-спека.dc.html и tokens.css) один
 * в один. Своих чисел здесь нет: спека прямо запрещает промежуточные ступени
 * кеглей, радиусов и отступов.
 *
 * Подключается ПОСЛЕ tokens.css — опирается на его переменные.
 *
 * Порядок разделов: каркас → шапка экрана → KPI → фильтры и поля → кнопки →
 * таблицы → пилюли и теги → модалы → календарь → чек-лист.
 */

/* ─────────────────────────── Основа ─────────────────────────── */

*,
*::before,
*::after { box-sizing: border-box; }

html, body {
  margin: 0;
  background: var(--bg-app);
  color: var(--text-body);
  font-family: var(--font-ui);
  font-size: var(--fs-body);
}

a { color: var(--brand-steel); text-decoration: none; }

/* Единое кольцо фокуса. Отдельный токен, потому что клавиатурная навигация
 * по таблице — обычный способ работы логиста, и терять каретку нельзя. */
:where(a, button, input, select, textarea, [tabindex]):focus-visible {
  outline: var(--focus-ring);
  /* Внутрь, как требует спека: наружное кольцо в плотной таблице
   * налезало бы на соседнюю ячейку. */
  outline-offset: -2px;
}

/* Числа моноширинные — правило 3 спеки. Даты, суммы, номера маршрутов,
 * одометр, часы, IP: разряды выстраиваются по колонке, и расхождение в
 * сумме видно без чтения. */
.num {
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
}

/* ─────────────────────────── Каркас ─────────────────────────── */

.layout {
  display: flex;
  min-height: 100vh;
  align-items: stretch;
}

.sidebar {
  width: var(--sidebar-w);
  flex: none;
  background: linear-gradient(168deg, var(--brand-ink), var(--sidebar-top));
  padding: var(--sp-9) var(--sp-7);
  display: flex;
  flex-direction: column;
  gap: var(--sp-2);
  transition: width .18s ease;

  /* Меню держится на месте, пока листается таблица. Без этого при
   * растягивании по высоте страницы (align-items: stretch у .layout)
   * оно уезжало вверх вместе с содержимым, и на длинном списке маршрутов
   * навигация и выход оказывались недосягаемы без прокрутки обратно.
   *
   * sticky, а не fixed: fixed выпадает из потока, и .main пришлось бы
   * подпирать отступом, пересчитывая его при каждом сворачивании.
   * align-self обязателен — иначе stretch перебивает высоту. */
  position: sticky;
  top: 0;
  align-self: flex-start;
  height: 100vh;
  /* На низком окне список разделов прокручивается внутри самой колонки,
   * а не обрезается. */
  overflow-y: auto;
}

/* Свернуть анимируем только ширину. Кто просил не двигать интерфейс,
 * получит своё: перехода не будет вовсе. */
@media (prefers-reduced-motion: reduce) {
  .sidebar { transition: none; }
}

/* Логотип — заголовок колонки и ничего больше. Сворачивание уехало
 * отдельным пунктом вниз, поэтому в разметке здесь <span>, а не <button>:
 * ни курсора-пальца, ни сброса кнопочных умолчаний тут быть не должно —
 * палец над логотипом обещает клик, которого нет. */
/* Значок логотипа лаймовый, подпись белая — так же, как в свёрнутом
 * состоянии, где от логотипа остаётся один значок. */
.sidebar__brand svg { color: var(--brand-lime); }

.sidebar__brand {
  display: flex;
  align-items: center;
  gap: var(--sp-5);
  padding: 0 var(--sp-4) var(--sp-9);
  color: #fff;
  font-size: var(--fs-lead);
  font-weight: var(--fw-black);
  border-bottom: 1px solid rgba(255, 255, 255, .09);
  margin-bottom: var(--sp-7);
}

/* Пункт сворачивания — выглядит как раздел, но это кнопка. */
.sidebar__toggle {
  background: none;
  border: 0;
  font: inherit;
  text-align: left;
  cursor: pointer;
  width: 100%;
}

/* Стрелка смотрит туда, куда поедет панель: влево — свернуть, вправо —
 * развернуть. В разметке всегда chevron-left, направление задаёт ЭТО
 * правило, а не скрипт: класс .sidebar--collapsed приходит из PHP по куке,
 * поэтому первый же кадр верный. Пока разворотом занимался скрипт, он
 * считал, что в DOM всегда стрелка влево, и на странице, открытой уже
 * свёрнутой, переворачивал нарисованную PHP правую — до конца сессии
 * стрелка показывала в обратную сторону. */
.sidebar--collapsed .sidebar__toggle svg { transform: scaleX(-1); }

/* Низ колонки: сворачивание, кто вошёл и выход. Прижат к нижнему краю,
 * чтобы разделы не разъезжали по высоте от экрана к экрану. */
.sidebar__foot {
  margin-top: auto;
  padding-top: var(--sp-7);
  border-top: 1px solid rgba(255, 255, 255, .09);
  display: flex;
  flex-direction: column;
  gap: var(--sp-2);
}

.sidebar__user {
  display: flex;
  align-items: center;
  gap: 11px;
  padding: var(--sp-3) var(--sp-6);
  color: #B7C8CC;
  font-size: var(--fs-small);
  min-width: 0;
}

/* Длинное имя обрезаем, а не переносим: иначе строка распирает колонку. */
.sidebar__user .sidebar__label {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.sidebar--collapsed .sidebar__user {
  justify-content: center;
  padding: var(--sp-3) 0;
}

.sidebar__item {
  position: relative;
  display: flex;
  align-items: center;
  gap: 11px;
  padding: 11px var(--sp-6);
  border-radius: var(--r-md);
  color: #B7C8CC;
  font-size: var(--fs-body);
  font-weight: var(--fw-semi);
}

.sidebar__item:hover { color: #fff; }

/* Активный раздел — лаймом, а не белым: белого на тёмной колонке и так
 * много (логотип, наведение), и активный пункт им не выделялся. Лайм —
 * фирменный акцент, отдельного цвета не заводим. */
.sidebar__item--active {
  background: rgba(187, 210, 90, .16);
  color: var(--brand-lime);
}

/* Лаймовая полоса 3px у левого края активного пункта. Спека называет её
 * дважды — в разметке сайдбара и в примечании к нему — и она делает ту же
 * работу, что полоса у экстренной строки таблицы: заливка rgba(...,.16)
 * на тёмном фоне почти неразличима, засечка у края видна сразу.
 * Отступы 8px сверху и снизу — из спеки, полоса короче самого пункта. */
.sidebar__item--active::before {
  content: "";
  position: absolute;
  left: 0;
  top: var(--sp-4);
  bottom: var(--sp-4);
  width: 3px;
  border-radius: 3px;
  background: var(--brand-lime);
}

/* Счётчик заявок. В развёрнутом сайдбаре стоит справа в строке.
 *
 * Высота НЕ задаётся напрямую: 15px строки плюс по 2px сверху и снизу дают
 * ровно 19px, и вертикальное поле вокруг цифры симметрично по построению.
 * Раньше стояла фиксированная высота с межстрочной, прижатой к кеглю, —
 * тогда цифра садилась выше середины кружка: line-height 1 центрирует
 * строчную коробку, а глиф внутри неё стоит на базовой линии, то есть
 * не по центру. */

/* Жёлтый счётчик: работа, которую ждут ОТ этой роли, а не тревога.
   Тон намеренно отличается от красного на «Заявках» — иначе складской
   оператор читает чужую срочность как свою. */
.sidebar__count {
  min-width: 19px;
  padding: 2px 6px;
  margin-left: auto;
  border-radius: var(--r-pill);
  background: var(--danger-solid);
  color: #fff;
  font-size: var(--fs-micro);
  font-weight: var(--fw-bold);
  line-height: 15px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
}

/* Жёлтый значок «Склада» ПОСЛЕ базового правила: у обоих селекторов одна
 * специфичность, и объявленный раньше проигрывал — значок склада красился в
 * красный «Заявок», ту самую чужую срочность, ради которой тон и разведён. */
.sidebar__count--warn {
  background: var(--warn-solid);
  color: var(--brand-deep);
}

/* Свёрнутый сайдбар: только иконки, подпись уходит в title. */
.sidebar--collapsed {
  width: var(--sidebar-w-collapsed);
  padding: var(--sp-9) var(--sp-5);
  align-items: center;
}

.sidebar--collapsed .sidebar__brand {
  color: var(--brand-lime);
  width: 100%;
  justify-content: center;
  padding: 0 0 var(--sp-9);
}

.sidebar--collapsed .sidebar__label { display: none; }

.sidebar--collapsed .sidebar__item {
  width: 100%;
  justify-content: center;
  padding: 11px 0;
}

/* Свёрнутый: счётчик уходит в угол — в строке для него места нет,
 * и становится мельче, чтобы не наезжать на иконку. */
.sidebar--collapsed .sidebar__count {
  position: absolute;
  top: var(--sp-3);
  right: var(--sp-4);
  margin-left: 0;
  min-width: 17px;
  height: 17px;
  padding: 0 var(--sp-2);
}

.main {
  flex: 1 1 auto;
  min-width: 0;          /* иначе таблица распирает флекс и ломает скролл */
  padding: var(--screen-pad);
}

/* ────────────────────── Шапка экрана ────────────────────── */

.screen-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-6);
  flex-wrap: wrap;
  margin-bottom: var(--sp-9);
}

.screen-head__title {
  display: flex;
  align-items: center;
  gap: var(--sp-5);
  font-size: var(--fs-title);
  font-weight: var(--fw-black);
  color: var(--text-strong);
}

/* ─────────────────────────── KPI ─────────────────────────── */

/* auto-fit — правило 5: при нехватке места плашки переносятся,
 * а не сжимаются до нечитаемого. */
.kpi-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(208px, 1fr));
  gap: var(--sp-7);
  margin-bottom: var(--sp-9);
}

.kpi {
  background: var(--bg-card);
  border: 1px solid var(--border-card);
  border-radius: var(--r-md);
  padding: var(--card-pad);
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--sp-6);
}

.kpi__value {
  font-family: var(--font-mono);
  font-size: var(--fs-hero);
  font-weight: var(--fw-black);
  color: var(--text-strong);
  /* Спека задаёт line-height 1 у всей ступени --fs-hero: число в карточке
   * стоит вплотную к своей подписи, отбивку даёт margin у подписи. */
  line-height: 1;
}

.kpi__label {
  font-size: var(--fs-small);
  /* Спека отдаёт подписи KPI отдельный тон --text-form (#6A7A7E), тот же,
   * что у подписей над полями фильтров, а не общий --text-muted. */
  color: var(--text-form);
}

.kpi__icon {
  width: 32px;
  height: 32px;
  border-radius: var(--r-md);
  background: var(--info-bg);
  color: var(--brand-steel);
  display: flex;
  align-items: center;
  justify-content: center;
  flex: none;
}

/* Тревожная плашка: экстренно завершённые, расхождения. */
.kpi--danger { border-color: rgba(192, 72, 59, .28); }

.kpi--danger .kpi__icon {
  background: var(--danger-bg);
  color: var(--danger-fg);
}

.kpi--danger .kpi__value { color: var(--danger-fg); }

/* Жёлтая плитка — «ждут действия»: очередь склада считает ею маршруты без
 * времени. Класс жил только в разметке страницы и не был определён — плитка
 * рисовалась как нейтральная. */
.kpi--warn { border-color: rgba(122, 101, 36, .32); }

.kpi--warn .kpi__icon {
  background: var(--warn-bg);
  color: var(--warn-fg);
}

.kpi--warn .kpi__value { color: var(--warn-fg); }

/* Подсказка под полем формы — тише подписи, короче абзаца. */
.field__hint {
  margin: 0;
  font-size: var(--fs-small);
  color: var(--text-muted);
}

/* ───────────────────── Фильтры и поля ───────────────────── */

.filters {
  background: var(--bg-card);
  border: 1px solid var(--border-card);
  border-radius: var(--r-md);
  padding: var(--card-pad);
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-8);
  align-items: flex-end;
  margin-bottom: var(--sp-9);
}

.field {
  display: flex;
  flex-direction: column;
  gap: 5px;
  flex: 0 0 168px;
}

.field--wide { flex: 1 1 260px; }

/* Подпись поля — без капса и трекинга. Капс с трекингом в спеке
 * принадлежит ШАПКЕ ТАБЛИЦЫ, у подписи поля свой, более тихий стиль. */
.field__label {
  font-size: var(--fs-micro);
  font-weight: var(--fw-semi);
  color: var(--text-form);
}

.input,
.select,
.textarea {
  height: var(--h-field);
  /* 11px — не из шкалы отступов: это внутренний отступ поля, спека задаёт
   * его отдельным значением. */
  padding: 0 11px;
  border: 1px solid var(--border-input);
  border-radius: var(--r-sm);
  background: var(--bg-card);
  color: var(--text-strong);
  font-family: var(--font-ui);
  font-size: var(--fs-body);
  width: 100%;
}

.textarea {
  height: auto;
  /* 9×11 — из спеки; 11px по горизонтали то же, что у остальных полей.
   * Своей минимальной высоты у поля нет: все textarea панели идут с
   * rows="2", и высоту задаёт сам браузер по строкам — в спеке это
   * ≈59px. Выдуманный min-height растягивал поле почти на треть. */
  padding: 9px 11px;
  line-height: 1.5;
  resize: vertical;
}

.input--num { font-family: var(--font-mono); }

/* Подсказка в поле. Своего токена в спеке нет — значение названо в примечании
 * к блоку «Поля ввода». Отличается от --text-faint: та только для прочерков. */
.input::placeholder,
.textarea::placeholder { color: #AEB7B6; }

/* Подпись над полем в форме модала и на входе — крупнее и темнее
 * фильтровой: там поле стоит одиноко и подпись несёт больше веса. */
.field__label--form {
  font-size: var(--fs-small);
  font-weight: var(--fw-semi);
  color: var(--text-second);
}

/* Компактная высота — экран обслуживания клиентов. */
.input--compact,
.select--compact { height: var(--h-control); }

/* Поле в форме модала выше фильтрового: 38px против 36px. */
.input--form,
.select--form { height: var(--h-button); }

/* Кнопки панели фильтров держатся парой и прижаты к низу — они в один
 * рост с полями, а не с подписями над ними. */
.filters__actions {
  display: flex;
  align-items: flex-end;
  gap: var(--sp-5);
}

/* Форма в модале — именно grid, а не колоночный flex: у .field стоит
 * flex: 0 0 168px, и в колоночном флексе эти 168px уходят в ВЫСОТУ поля. */
.form-stack {
  display: grid;
  gap: var(--sp-7);
}

.form-row {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: var(--sp-6);
}

.form-stack .field,
.form-row .field { flex: initial; }

/* Плашка с неизменяемым контекстом внутри модала: кому назначаем,
 * что удаляем. */
.info-box {
  background: var(--bg-subtle);
  border: 1px solid var(--border-card);
  border-radius: var(--r-sm);
  padding: var(--sp-6) var(--sp-8);
  display: flex;
  flex-direction: column;
  gap: var(--sp-2);
}

.info-box__label { font-size: var(--fs-micro); color: var(--text-muted); }

.info-box__value { font-size: var(--fs-body); font-weight: var(--fw-semi); color: var(--text-strong); }

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

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--sp-4);
  height: var(--h-button);
  padding: 0 var(--sp-8);
  border: 0;
  border-radius: var(--r-sm);
  font-family: inherit;
  font-size: var(--fs-body);
  font-weight: var(--fw-bold);
  cursor: pointer;
  white-space: nowrap;
}

.btn:disabled { opacity: .5; cursor: not-allowed; }

.btn--primary   { background: var(--brand-deep); color: #fff; }
.btn--accent    { background: var(--brand-lime); color: var(--brand-lime-ink); }
.btn--danger    { background: var(--danger-fg);  color: #fff; }

/* Кнопка с рамкой и кнопка фильтра сидят на 15px по бокам, а не на 16px,
 * как заливные: у них есть своя рамка, и лишний пиксель внутрь возвращает
 * подписи ту же оптическую отбивку. 15px — не ступень шкалы отступов,
 * спека называет его прямо, как и 11px у полей. */
.btn--secondary {
  padding: 0 15px;
  border: 1px solid var(--border-button);
  background: var(--bg-card);
  color: var(--text-second);
}

.btn--danger-outline {
  padding: 0 15px;
  border: 1px solid rgba(192, 72, 59, .4);
  background: var(--bg-card);
  color: var(--danger-solid);
}

/* Кнопка в панели фильтров. */
.btn--filter {
  height: var(--h-field);
  padding: 0 15px;
  gap: 7px;
  font-weight: var(--fw-semi);
}

/* Кнопка в строке таблицы. */
.btn--row {
  height: var(--h-icon-btn);
  padding: 0 11px;
  gap: var(--sp-3);
  font-size: var(--fs-small);
}

/* Иконочная кнопка. Правило 8: допустима только в строке таблицы
 * и только с title. */
.btn-icon {
  width: var(--h-icon-btn);
  height: var(--h-icon-btn);
  padding: 0;
  border: 1px solid var(--border-button);
  border-radius: var(--r-sm);
  background: var(--bg-card);
  color: var(--brand-steel);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
}

.btn-icon--danger {
  border-color: rgba(192, 72, 59, .35);
  color: var(--danger-solid);
}

.btn-icon--control { width: var(--h-control); height: var(--h-control); }

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

.table-card {
  background: var(--bg-card);
  border: 1px solid var(--border-card);
  border-radius: var(--r-md);
  overflow: hidden;
}

.table-card__head {
  padding: 13px var(--sp-9);
  background: var(--bg-subtle);
  border-bottom: 1px solid var(--border-head);
  font-size: var(--fs-lead);
  font-weight: var(--fw-black);
  color: var(--text-strong);
  display: flex;
  align-items: center;
  gap: var(--sp-5);
}

/* Правило 5: на узком окне таблица прокручивается, а не обрезается. */
.table-scroll { overflow-x: auto; }

/* Шапка и строка — общая сетка. Ширины колонок задаются НА МЕСТЕ через
 * --cols: каждая таблица панели имеет свой набор (спека, columnSets), и
 * шапка со строками обязаны получить один и тот же. Общая переменная нужна
 * потому, что шапка живёт в PHP-разметке, а строки собираются на JS: два
 * разных места, и разъехавшийся grid-template даёт съезд всех колонок. */
.table__head,
.table__row {
  display: grid;
  grid-template-columns: var(--cols);
  /* Зазор у каждой таблицы свой: «Маршруты» — 14px, «Заявки» — 10px,
   * остальные — 12px. Экран задаёт --table-gap рядом с --cols; без него
   * берётся 14px, как у маршрутов. */
  column-gap: var(--table-gap, var(--sp-7));
  /* Прокрутка на .table-scroll сама по себе таблицу не спасает: строка
   * шириной 100% контейнера обрывается на его правом краю, а колонки
   * продолжаются дальше. Всё, что нарисовано на самой строке — заливка
   * шапки, нижний разделитель, подсветка при наведении, красная полоса
   * экстренного маршрута — кончается там же, и последние колонки стоят
   * на голом белом. На «Маршрутах» при 1440 за краем оставалось 144px,
   * при 1366 — 218px.
   *
   * min-content, а НЕ max-content. Все наборы --cols собраны из фиксированных
   * ширин и minmax(Xpx, 1fr), то есть у каждой колонки есть свой предел снизу.
   * min-content складывает ровно эти пределы с зазорами — число одно и то же
   * для шапки и для любой строки, чем бы её ни заполнили. max-content считает
   * по содержимому КАЖДОЙ строки отдельно: на «Заявках» при 1366 это дало
   * шесть разных ширин строк от 1096 до 1344px и шесть разных раскладок
   * колонок в одной таблице — тот самый съезд, ради которого --cols и заведён
   * общей переменной. Там, где ширины и так совпадают («Маршруты», «Часы»,
   * «Водители»), min-content даёт то же число, что max-content. */
  min-width: min-content;
}

.table__head {
  padding: 13px var(--sp-9);
  background: var(--bg-subtle);
  border-bottom: 1px solid var(--border-head);
  font-size: var(--fs-micro);
  font-weight: var(--fw-bold);
  text-transform: uppercase;
  letter-spacing: .4px;
  color: var(--text-muted);
}

.table__row {
  position: relative;          /* якорь для полосы экстренного маршрута */
  padding: var(--sp-7) var(--sp-9);
  border-bottom: 1px solid var(--border-row);
  line-height: 19px;
  font-size: var(--fs-body);
  color: var(--text-body);
  /* по верху — иначе многострочные ячейки разъезжают */
  align-items: start;
}

.table__row:last-child { border-bottom: 0; }

/* Строка, которая что-то открывает, обязана об этом сообщать: палец на
 * курсоре и лёгкая подсветка. Без них строка выглядит как текст, и клик
 * по ней приходится угадывать. Подсветка — тот же оттенок, что у шапки
 * таблицы, отдельного цвета не заводим. */
.table__row[role="button"] { cursor: pointer; }

.table__row[role="button"]:hover,
.table__row[role="button"]:focus-visible { background: var(--bg-subtle); }

/* Ячейка не должна распирать сетку. У grid-элемента min-width по умолчанию
 * auto: он не сжимается ниже своего содержимого, и ячейка с тегами (у .tag
 * стоит nowrap) физически ложилась ПОВЕРХ соседней колонки — логист читал
 * сумму сквозь тег. Ширины из спеки рассчитаны на голое значение, поэтому
 * лишнее должно переноситься или обрезаться, а не наезжать. */
.table__head > *,
.table__row > * { min-width: 0; }

.table__sort {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-2);
  cursor: pointer;
  color: inherit;
  background: none;
  border: 0;
  font: inherit;
  text-transform: inherit;
  letter-spacing: inherit;
  padding: 0;
}

/* Значок сортировки приглушён: колонка сортируемая, но значок не должен
 * спорить с подписью. */
.table__sort svg { opacity: .35; }

.cell-num {
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
  text-align: right;
}

/* Номер маршрута — крупнее остальных ячеек и моно: это ключ строки. */
.cell-route {
  font-family: var(--font-mono);
  font-size: var(--fs-lead);
  font-weight: var(--fw-black);
  color: var(--brand-deep);
}

.cell-date {
  font-family: var(--font-mono);
  font-size: var(--fs-body);
  color: var(--text-mono);
}

.cell-status {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-4);
  height: 19px;
  font-size: var(--fs-body);
  font-weight: var(--fw-semi);
  color: var(--info-fg);
}

/* Экстренный маршрут: полоса 3px слева и точка у номера.
 * Заливку строки не трогаем — иначе теряются пилюли статусов. */
.table__row--emergency::before {
  content: "";
  position: absolute;
  left: 0;
  top: 0;
  bottom: 0;
  width: 3px;
  background: var(--danger-solid);
}

.emergency-dot {
  width: 7px;
  height: 7px;
  border-radius: var(--r-round);
  background: var(--danger-solid);
  display: inline-block;
  flex: none;
}

/* ───────────────────── Полоса прогресса ───────────────────── */

.progress {
  display: flex;
  align-items: center;
  gap: var(--sp-5);
  height: 19px;
}

.progress__track {
  flex: 1;
  height: 8px;
  border-radius: var(--r-sm);
  /* Подложка полосы. Своего токена в спеке нет — значение взято из
   * эталонной таблицы документа. */
  background: #E7EBEA;
  overflow: hidden;
  display: flex;
}

.progress__done      { height: 100%; background: var(--dot-progress); display: block; }
.progress__failed    { height: 100%; background: var(--danger-solid);  display: block; }

/* Дробь фиксированной ширины: иначе «1/8» и «12/14» дёргают колонку. */
.progress__value {
  font-family: var(--font-mono);
  font-size: var(--fs-body);
  font-weight: var(--fw-bold);
  color: var(--text-strong);
  width: 48px;
  text-align: right;
  flex: none;
}

/* Прочерк вместо пустоты. Единственное место, где допустим --text-faint. */
.dash { color: var(--text-faint); }

/* Прочерк обязан оставаться тише данных ВЕЗДЕ.
 *
 * Ячейки и число KPI задают свой цвет и по специфичности выигрывали
 * у одноклассового .dash: в тревожной карточке «—» краснело и читалось
 * как показание — ровно то, ради отказа от чего правило и заводилось.
 *
 * Тройное повторение класса поднимает вес до 0-3-0. Перечислять победителей
 * поимённо пробовали — список пришлось бы дописывать при каждом новом
 * экранном классе, и один уже успел проскочить. Правило про прочерк одно,
 * поэтому и вес у него должен быть выше любой раскраски ячейки. */
.dash.dash.dash { color: var(--text-faint); }

/* Моноширинная ячейка второго плана: почта, IP, идентификаторы. */
.cell-mono {
  font-family: var(--font-mono);
  font-size: var(--fs-small);
  color: var(--text-mono);
}

/* Группа кнопок в ячейке «Действия». */
.row-actions {
  display: flex;
  align-items: center;
  gap: var(--sp-3);
}

/* Двухстрочная ячейка «имя + почта». */
.cell-person { display: flex; flex-direction: column; gap: var(--sp-1); min-width: 0; }

.cell-person__name { font-weight: var(--fw-medium); }

/* nowrap здесь несущий: без него text-overflow не срабатывает вовсе и
 * длинная почта не обрезается, а переносится, делая строку выше. */
.cell-person__mail {
  font-size: var(--fs-small);
  color: var(--text-muted);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* ──────────────────── Пилюли, теги, счётчики ──────────────────── */

/* Пилюля статуса — скруглённый прямоугольник, а не овал.
 *
 * Полное скругление (--r-pill) осталось только там, где форма кругла по
 * смыслу: счётчик-кружок в меню, аватар, точка статуса, переключатель
 * доступа. У пилюли с текстом овал спорит с прямоугольными карточками
 * и полями вокруг — весь интерфейс собран на радиусах 6 и 10. */
.pill {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: var(--sp-2) var(--sp-5);
  border-radius: var(--r-sm);
  font-size: var(--fs-micro);
  font-weight: var(--fw-bold);
  white-space: nowrap;
}

.tag {
  display: inline-flex;
  align-items: center;
  padding: var(--sp-1) 7px;
  border-radius: var(--r-sm);
  font-size: var(--fs-micro);
  font-weight: var(--fw-bold);
  white-space: nowrap;
}

.tag--solid {
  /* 3px по вертикали — прямо из спеки: заливной тег стоит ровно между
   * мягкой пилюлей (4px) и обычным тегом (2px). Не ступень шкалы отступов,
   * поэтому числом. */
  padding: 3px var(--sp-5);
  color: #fff;
}

/* Пары «заливка + текст» — готовые, из токенов. Спека запрещает собирать
 * свои сочетания: каждая пара проверена на контраст AA. */
.pill--ok      { background: var(--ok-bg);      color: var(--ok-fg); }
.pill--info    { background: var(--info-bg);    color: var(--info-fg); }
.pill--warn    { background: var(--warn-bg);    color: var(--warn-fg); }
.pill--danger  { background: var(--danger-bg);  color: var(--danger-fg); }
.pill--neutral { background: var(--neutral-bg); color: var(--neutral-fg); }

.tag--ok      { background: var(--ok-bg);      color: var(--ok-fg); }
.tag--info    { background: var(--info-bg);    color: var(--info-fg); }
.tag--warn    { background: var(--warn-bg);    color: var(--warn-fg); }
.tag--danger  { background: var(--danger-bg);  color: var(--danger-fg); }
.tag--neutral { background: var(--neutral-bg); color: var(--neutral-fg); }

/* Тег «Водитель» из массива tags спеки: фон приложения, нейтральный текст. */
.tag--muted { background: var(--bg-app); color: var(--neutral-fg); }

/* Счётчик недоставленного рядом с полосой прогресса: крестик и число.
 * Моноширинный, чтобы в колонке цифры стояли друг под другом. */
.tag--nd {
  gap: 3px;
  padding: var(--sp-1) var(--sp-3);
  background: var(--danger-bg);
  color: var(--danger-fg);
  font-family: var(--font-mono);
  font-weight: var(--fw-black);
}

/* Тег магазина e-szklarnia. Спека рисует его цветом #6F8A2E, которого нет
 * в токенах, при собственном правиле «никаких новых цветов». Взята готовая
 * лаймовая пара --lime-bg/--lime-fg: контраст у неё выше. */
.tag--lime { background: var(--lime-bg); color: var(--lime-fg); }

/* Сплошные теги под белым текстом: скидка, доплата, наличные, смешанный. */
.tag--solid-neutral { background: var(--neutral-solid); }
.tag--solid-info    { background: var(--info-fg); }
.tag--solid-danger  { background: var(--danger-solid); }
.tag--solid-ok      { background: var(--ok-solid); }
.tag--solid-warn    { background: var(--warn-solid); }
.tag--solid-teal    { background: var(--teal-solid); }

.dot {
  width: 8px;
  height: 8px;
  border-radius: var(--r-round);
  display: inline-block;
  flex: none;
}

/* Точки статусов маршрута. В СПИСКЕ статус показывается точкой с подписью —
 * так он выглядит в эталонной таблице спеки; пилюли остаются для карточки
 * и для остальных экранов. */
.dot--preparing         { background: var(--dot-preparing); }
.dot--loading           { background: var(--dot-loading); }
.dot--loading_completed { background: var(--dot-loaded); }
.dot--in_progress       { background: var(--dot-progress); }
.dot--returning         { background: var(--dot-returning); }
.dot--at_base           { background: var(--dot-returning); }
.dot--fin_check         { background: var(--dot-fincheck); }
.dot--completed         { background: var(--dot-completed); }
.dot--cancelled         { background: var(--dot-cancelled); }

/* Экстренный маршрут: точка статуса краснеет независимо от самого статуса.
 * Так показано в эталонной таблице спеки — там «Завершён» у экстренного
 * маршрута идёт красной точкой, а не зелёной. */
.table__row--emergency .dot { background: var(--danger-solid); }

.avatar {
  width: 32px;
  height: 32px;
  border-radius: var(--r-round);
  background: var(--ok-bg);
  color: var(--ok-fg);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-family: var(--font-mono);
  font-size: var(--fs-small);
  font-weight: var(--fw-bold);
}

/* Переключатель доступа водителя. */
.toggle {
  width: 40px;
  height: 22px;
  border-radius: var(--r-pill);
  background: var(--neutral-bg);
  display: inline-flex;
  align-items: center;
  padding: var(--sp-1);
  justify-content: flex-start;
  border: 0;
  cursor: pointer;
}

.toggle--on {
  background: var(--brand-lime);
  justify-content: flex-end;
}

.toggle__knob {
  width: 18px;
  height: 18px;
  border-radius: var(--r-round);
  background: #fff;
  flex: none;
  /* Единственная тень мелкого элемента во всей спеке, и она здесь по делу:
   * выключенный переключатель — белый кружок на --neutral-bg (#ECEEED),
   * два почти одинаково светлых пятна. Без тени граница кружка не читается
   * и состояние «выключено» выглядит пустой пилюлей. */
  box-shadow: 0 1px 3px rgba(15, 37, 44, .3);
}

/* ─────────────────────────── Модалы ─────────────────────────── */

.overlay {
  position: fixed;
  inset: 0;
  background: var(--overlay);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: var(--sp-9);
  z-index: 50;
}

.modal {
  background: var(--bg-card);
  border-radius: var(--r-md);
  overflow: hidden;
  box-shadow: var(--shadow-modal);
  display: flex;
  flex-direction: column;
  max-height: calc(100vh - 44px);
}

/* Правило 4: ширины через min(), иначе на узком окне диалог уезжает
 * за экран и до кнопок не добраться. */
.modal--route   { width: var(--modal-route-w); }
.modal--form    { width: var(--modal-form-w); }
.modal--dialog  { width: var(--modal-dialog-w); }
.modal--message { width: var(--modal-message-w); }
.modal--small   { width: var(--modal-small-w); }

.modal__head {
  padding: var(--sp-8) var(--sp-10);
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-6);
  background: var(--brand-deep);
  color: #fff;
  flex: none;
}

.modal__title {
  display: flex;
  align-items: center;
  gap: var(--sp-5);
  font-size: var(--fs-lead);
  font-weight: var(--fw-black);
}

.modal__close {
  display: inline-flex;
  opacity: .75;
  background: none;
  border: 0;
  color: inherit;
  cursor: pointer;
  padding: 0;
}

.modal__close:hover { opacity: 1; }

.modal__body {
  padding: var(--sp-9) var(--sp-10);
  font-size: var(--fs-body);
  color: var(--text-body);
  line-height: 1.5;
  background: var(--bg-card);
  overflow-y: auto;
}

.modal__foot {
  padding: var(--sp-7) var(--sp-10);
  background: var(--bg-subtle);
  border-top: 1px solid var(--border-head);
  display: flex;
  gap: var(--sp-5);
  justify-content: flex-end;
  flex: none;
}

/* Шапки по назначению — пары «заливка + текст» из спеки. */
.modal__head--create  { background: var(--ok-solid); color: #fff; }
.modal__head--confirm { background: var(--warn-bg);  color: var(--warn-fg); }
.modal__head--success { background: var(--ok-solid); color: #fff; }
.modal__head--warning { background: var(--warn-solid); color: #fff; }
.modal__head--error   { background: var(--danger-fg); color: #fff; }

/* Тело модала маршрута — две колонки. */
.modal-route__grid {
  display: grid;
  grid-template-columns: 330px minmax(0, 1fr);
  gap: var(--sp-9);
  background: var(--bg-inset);
}

@media (max-width: 900px) {
  .modal-route__grid { grid-template-columns: minmax(0, 1fr); }
}

/* ───────────────────── Календарь часов ───────────────────── */

.cal-cell {
  min-height: var(--h-cell-cal);
  border-radius: var(--r-sm);
  padding: var(--sp-4) var(--sp-3);
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--sp-2);
  background: var(--bg-card);
  border: 1px solid rgba(15, 37, 44, .06);
}

.cal-cell--shift {
  background: var(--lime-bg);
  border-color: var(--lime-border);
}

.cal-cell--outside {
  background: var(--bg-subtle);
  border-color: rgba(15, 37, 44, .05);
}

.cal-cell--selected {
  outline: 2px solid var(--warn-fg);
  outline-offset: -2px;
}

/* ─────────────────────────── Чек-лист ─────────────────────────── */

.checklist__group {
  display: flex;
  flex-direction: column;
  gap: var(--sp-3);
}

.checklist__title {
  display: flex;
  align-items: center;
  gap: 7px;
  font-size: var(--fs-body);
  /* 800, а не 700: заголовок группы — единственное жирное место в
   * чек-листе, пункты под ним идут 12px обычным начертанием. */
  font-weight: var(--fw-black);
  color: var(--text-strong);
}

.checklist__item {
  display: flex;
  align-items: flex-start;
  gap: 7px;
  padding-left: var(--sp-11);
  font-size: var(--fs-small);
  color: var(--text-body);
  line-height: 1.45;
}

/* Отметка чек-листа. Две формы: отмеченная — заливка с галочкой внутри,
 * пустая — только контур. Смещение на 2px вниз ставит её на базовую линию
 * первой строки текста, а не по центру многострочного пункта. */
.checklist__box {
  width: 13px;
  height: 13px;
  border-radius: var(--r-xs);
  flex: none;
  margin-top: var(--sp-1);
}

.checklist__box--on {
  background: var(--ok-solid);
  color: #fff;
  display: flex;
  align-items: center;
  justify-content: center;
}

.checklist__box--off {
  border: 1.5px solid var(--border-button);
  background: var(--bg-card);
}

/* ─────────────────────────── Экран входа ─────────────────────────── */

/* Градиент назван владельцем: 135deg, три остановки. Средний цвет —
 * --brand-deep, крайний #2F5A44 своего токена не имеет. */
.login {
  min-height: 100vh;
  margin: 0;
  background: linear-gradient(135deg, #0F252C 0%, #203B32 55%, #2F5A44 100%);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: var(--sp-12);
}

/* Правило 4: ширина через min(), иначе на узком окне карточка
 * упирается в края. */
.login__card {
  width: min(400px, 94vw);
  background: var(--bg-card);
  border-radius: var(--r-md);
  box-shadow: var(--shadow-login);
  padding: var(--sp-12);
}

.login__logo {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--sp-5);
  margin-bottom: var(--sp-12);
  color: var(--brand-deep);
}

.login__title {
  font-size: var(--fs-hero);
  font-weight: var(--fw-black);
  color: var(--text-strong);
  margin: 0;
}

.login__subtitle {
  font-size: var(--fs-small);
  color: var(--text-muted);
  margin: 0;
}

/* Поле входа выше обычного: 46px против 36px у фильтров. */
.login__field { margin-bottom: var(--sp-8); }

.login .input { height: var(--h-input-lg); }

/* Поле с иконкой: иконка лежит поверх, слева, а полю добавляется
 * левый отступ под неё. Обёртка-«аффикс» из Bootstrap не нужна —
 * в спеке такого компонента нет, а рамка вокруг иконки ломала бы
 * ровный контур поля. */
.field-affix { position: relative; display: block; }

.field-affix > .input { padding-left: 38px; }

.field-affix__icon {
  position: absolute;
  left: var(--sp-6);
  top: 50%;
  transform: translateY(-50%);
  color: var(--brand-steel);
  display: inline-flex;
  pointer-events: none;
}

.login .btn { width: 100%; }

/* ──────────────── Шапка отдельного окна (обслуживание) ──────────────── */

.topbar {
  height: var(--h-topbar);
  background: linear-gradient(118deg, var(--brand-ink), var(--sidebar-top));
  color: #fff;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-6);
  padding: 0 var(--sp-12);
  flex: none;
}

.topbar__brand {
  display: flex;
  align-items: center;
  gap: var(--sp-5);
  font-size: var(--fs-lead);
  font-weight: var(--fw-black);
}

.topbar__brand svg { color: var(--brand-lime); }

/* ─────────────── Состояния списка: пусто, ошибка, загрузка ─────────────── */

/* Спека этих состояний не описывает, поэтому собраны из готовых пар и
 * добавлены сюда, а не разбросаны по экранам. Раньше часть из них
 * деградировала молча: отказ запроса уходил в console.error, и человек
 * навсегда оставался с надписью «Загрузка...». */

.state {
  padding: var(--sp-12) var(--sp-9);
  text-align: center;
  font-size: var(--fs-body);
  line-height: 1.5;
}

.state--empty { color: var(--text-muted); }

.state--error {
  background: var(--danger-bg);
  color: var(--danger-fg);
  text-align: left;
  border-radius: var(--r-sm);
  padding: var(--sp-6) var(--sp-8);
  display: flex;
  align-items: flex-start;
  gap: var(--sp-4);
}

/* Сервис недоступен — не ошибка запроса, а режим работы: янтарный,
 * чтобы отличался от красного отказа. */
.state--offline {
  background: var(--warn-bg);
  color: var(--warn-fg);
  text-align: left;
  border-radius: var(--r-sm);
  padding: var(--sp-6) var(--sp-8);
  display: flex;
  align-items: flex-start;
  gap: var(--sp-4);
}

/* Скелетон вместо спиннера: строки той же высоты, что и будущие данные,
 * поэтому таблица не прыгает в момент загрузки. */
.skeleton-row {
  height: 19px;
  margin: var(--sp-7) var(--sp-9);
  border-radius: var(--r-sm);
  background: var(--bg-subtle);
  animation: skeleton-pulse 1.2s ease-in-out infinite;
}

@keyframes skeleton-pulse {
  0%, 100% { opacity: 1; }
  50%      { opacity: .55; }
}

@media (prefers-reduced-motion: reduce) {
  .skeleton-row { animation: none; }
}

/* Утилита показа и скрытия. Заменяет .d-none из Bootstrap — он уходит
 * вместе с ним, а на нём в коде держится переключение блоков. */
[hidden],
.is-hidden { display: none !important; }
