/* ============================================================================
   Yolka WMS — дизайн-система приложения (этап 20).

   Единственный источник правды по цвету/радиусам/типографике: токены ниже
   (:root). Tailwind Play CDN в templates/layout/base.html читает ЭТИ ЖЕ
   переменные (colors: "rgb(var(--rgb-accent) / <alpha-value>)"), поэтому
   утилиты вида bg-surface / text-ink-muted / bg-warning/20, которых в
   шаблонах несколько сотен, автоматически подхватывают палитру отсюда —
   менять цвет проекта нужно ровно в одном месте, в этом файле.

   Второй слой — классы компонентов (.btn/.card/.table/.badge/.field/...).
   Правило: НОВЫЕ экраны собираются из них, утилиты Tailwind остаются для
   раскладки (grid/flex/отступы) и точечных исключений. Причина, по которой
   компоненты появились: до этого одна кнопка была строкой из 14 утилит,
   скопированной в 40 мест — любая правка формы кнопки означала правку 40
   файлов, и часть мест неизбежно отставала (у таблиц так и было: у одних
   был hover, у других нет). Подробности — docs/design-system.md.

   Файл подключается как обычная статика (<link rel="stylesheet">) ДО
   Tailwind CDN: Play CDN вставляет свой <style> в конец <head> в рантайме,
   поэтому при равной специфичности утилита в разметке перебивает
   компонент — это осознанно, так работает и обычный Tailwind.
   ========================================================================= */

/* ---------------------------------------------------------------- токены */
:root {
  /* RGB-каналы (без rgb(...)) — нужны Tailwind-конфигу для <alpha-value>,
     то есть чтобы работали bg-accent/10, bg-warning/20 и т.п.
     ВАЖНО: каналы записаны через пробел, поэтому прозрачность от них
     берётся ТОЛЬКО слэш-синтаксисом — rgb(var(--rgb-accent) / .12).
     Запись rgba(var(--rgb-accent), .12) невалидна (смесь пробелов и
     запятой), браузер молча выбрасывает всё правило: фон становится
     прозрачным, border-color — currentColor. */
  /* Метрика подписи поля и высоты контрола — по ним выравнивается строка
     полей документа (.doc-row__fields / .doc-row__actions). */
  --field-label-size: 12.5px;
  --field-label-leading: 1.5;
  --field-label-gap: .3rem;
  --field-label-line: calc(var(--field-label-size) * var(--field-label-leading));
  /* В строке полей документа под подпись резервируются ДВЕ строки — см.
     .doc-row__fields, там же объяснено зачем. */
  --doc-row-label-h: calc(var(--field-label-line) * 2 + var(--field-label-gap));
  --control-height: 36px;

  /* Палитра «AgentQL» (этап 59). Четыре ступени поверхностей идут
     СТРОГО по возрастанию светлоты (canvas → surface → surface-2 →
     surface-3), поэтому глубина по-прежнему набирается самими
     поверхностями и границей, а не тенью. Все цифры контраста в
     комментариях посчитаны, а не прикинуты на глаз — методика и
     пороги в docs/design-system.md, разбор этапа — CLAUDE.md п.96. */
  --rgb-canvas: 11 12 14;          /* Void   #0b0c0e — фон страницы */
  --rgb-surface: 14 17 27;         /* Abyss  #0e111b — карточки, шапка, меню */
  --rgb-surface-2: 13 23 43;       /* Deep Sea #0d172b — шапка таблицы, поля, hover */
  --rgb-surface-3: 18 36 79;       /* Cobalt #12244f — вложенный уровень, нейтральный бейдж */
  --rgb-line: 23 37 64;            /* Obsidian Edge #172540 */
  --rgb-line-light: 36 55 90;      /* Sapphire Hairline #24375a */
  --rgb-accent: 40 98 215;         /* Signal Blue #2862d7 — бренд: заливки, границы, данные на графиках */
  --rgb-accent-solid: 40 98 215;   /* фон кнопки/заливки: белый текст на нём 5.5:1 (AA) */
  --rgb-accent-hover: 48 95 189;   /* Pulse Violet #305fbd — ховер ГЛУБЖЕ, а не светлее: белый 6.0:1 */
  --rgb-accent-ink: 133 166 233;   /* Frosted Lilac #85a6e9 — акцентный ТЕКСТ: 6.2-8.0:1.
                                      Сам Signal Blue как текст даёт только 3.3:1 — та же
                                      ловушка, что и у старого фиолетового бренд-цвета,
                                      поэтому разделение solid/ink сохранено. */
  --rgb-ink: 255 255 255;          /* #ffffff — 15.1-19.6:1 на всех четырёх поверхностях */
  /* ВНИМАНИЕ: в исходном описании стиля Ash #abaebb назван
     «secondary», а Mist #c7c9d1 — «tertiary», но по светлоте они ровно
     наоборот (#c7c9d1 СВЕТЛЕЕ). У нас ink-muted — вторичный, ink-dim —
     третичный, и этот порядок зашит в сотни мест в шаблонах.
     Поэтому цвета назначены по СВЕТЛОТЕ, а не по имени из файла
     стиля: иначе «третичный» текст стал бы заметнее «вторичного». */
  --rgb-ink-muted: 199 201 209;    /* Mist #c7c9d1 — 9.1-11.8:1 */
  --rgb-ink-dim: 171 174 187;      /* Ash  #abaebb — 6.8-8.9:1 */
  --rgb-success: 34 197 94;        /* без изменений: 6.6-8.6:1 на новых поверхностях */
  --rgb-warning: 245 158 11;       /* без изменений: 7.0-9.1:1 */
  /* danger и info пришлось ОСВЕТЛИТЬ, и это не косметика под новую
     палитру, а починка СТАРОГО бага: #ef4444 на прежнем surface-3
     давал 3.9:1, а в собственном бейдже (текст на своём же фоне
     16%) — 4.1:1, то есть ниже порога ещё до этого этапа. Причина
     промаха в том, что раньше считали контраст к ЧИСТОЙ поверхности,
     а бейдж рисует текст на КОМПОЗИТЕ своей полупрозрачной
     подложки — он светлее, и запаса там меньше. Проверять новые
     статусные цвета теперь нужно ИМЕННО на композите.
     Заодно info ушёл в голубой: старый #3b82f6 почти сливался бы с
     новым синим акцентом (контраст между ними 1.5:1), а это ДВА ИЗ
     ТРЁХ цветов данных на графиках (CLAUDE.md п.19) — две линии
     стали бы неразличимы. Сейчас между accent и info 2.4:1. */
  --rgb-danger: 248 113 113;       /* #f87171 — 5.2-7.1:1, в своём бейдже 5.2-5.8:1 */
  --rgb-info: 40 182 255;          /* #28b6ff — 6.6-8.6:1, в своём бейдже 5.9-6.8:1 */

  /* Фиолетовый свечения — ТОЛЬКО для декоративных градиентов витрины и
     шапки. Как цвет текста или заливки кнопки не использовать: #625fff
     как текст даёт 3.9-4.3:1, ниже порога. */
  --rgb-aurora-purple: 98 95 255;  /* #625fff */
  /* Розовый — и половина аврора-градиента, и ВТОРОЙ ЦВЕТ ДАННЫХ на
     графике движений (этап 61). Полноценный токен, а не «декор»: как
     линия и как текст он даёт 7.8-8.6:1 на всех поверхностях, а с синим
     акцентом различается на 2.4:1 — столько же, сколько accent с info,
     то есть две линии графика не сливаются. Исходный стиль использует
     этот же цвет и в подсветке синтаксиса, роль «только фон» за ним не
     закреплена. */
  --rgb-pink: 255 125 218;         /* #ff7dda */

  /* Фирменные цвета площадок — только для метки «Источник». Взяты не с
     логотипов буквально, а осветлены до читаемых на тёмном фоне: WB
     #481173 и Ozon #005BFF как ТЕКСТ на нашем фоне дают около 2:1, то
     есть подпись нечитаема. Оттенок при этом узнаётся — фиолетовый,
     синий, жёлтый, — а это всё, ради чего цвет здесь и нужен. */
  --rgb-wb: 168 85 247;            /* Wildberries — фиолетовый, 7.4:1 на surface */
  --rgb-ozon: 91 149 255;          /* Ozon — синий, 7.1:1 */
  --rgb-yandex: 255 204 0;         /* Яндекс Маркет — жёлтый, 12.6:1 */
  /* Текст бейджа WB — отдельный, более светлый оттенок того же
     фиолетового. Сам #a855f7 на своём же фоне 16% даёт 3.8-4.2:1 —
     ниже порога, причём был ниже и ДО этого этапа (3.4-4.2:1 на
     старой палитре): в комментарии выше стояла цифра «7.4:1 на
     surface», но текст бейджа лежит не на surface, а на своей
     подложке. Заливка/граница остаются брендовыми — меняется
     только цвет букв. Ozon и Яндекс проходят как есть (5.2 и 8.4). */
  --rgb-wb-ink: 192 132 252;       /* #c084fc — 5.7-6.3:1 на своём бейдже */

  --canvas: rgb(var(--rgb-canvas));
  --surface: rgb(var(--rgb-surface));
  --surface-2: rgb(var(--rgb-surface-2));
  --surface-3: rgb(var(--rgb-surface-3));
  --line: rgb(var(--rgb-line));
  --line-light: rgb(var(--rgb-line-light));
  --accent: rgb(var(--rgb-accent));
  --accent-solid: rgb(var(--rgb-accent-solid));
  --accent-hover: rgb(var(--rgb-accent-hover));
  --accent-ink: rgb(var(--rgb-accent-ink));
  --ink: rgb(var(--rgb-ink));
  --ink-muted: rgb(var(--rgb-ink-muted));
  --ink-dim: rgb(var(--rgb-ink-dim));
  --success: rgb(var(--rgb-success));
  --warning: rgb(var(--rgb-warning));
  --danger: rgb(var(--rgb-danger));
  --info: rgb(var(--rgb-info));
  --wb: rgb(var(--rgb-wb));
  --ozon: rgb(var(--rgb-ozon));
  --yandex: rgb(var(--rgb-yandex));
  --wb-ink: rgb(var(--rgb-wb-ink));
  --pink: rgb(var(--rgb-pink));

  /* Гарнитуры. Сами файлы шрифтов подключает layout/base.html —
     здесь только стеки, чтобы компоненты ссылались на роль
     (display/mono), а не перечисляли фолбэки в каждом правиле. */
  --font-sans: "Inter", ui-sans-serif, system-ui, sans-serif;
  --font-display: "Figtree", "Inter", ui-sans-serif, system-ui, sans-serif;
  --font-mono: "IBM Plex Mono", ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;

  --radius-sm: 8px;
  --radius-md: 12px;
  --radius-lg: 16px;
  --radius-xl: 20px;
  --radius-pill: 999px;

  /* В тёмной теме тень почти не видна, поэтому глубина набирается
     границей + подсветкой верхней кромки; тени — только для того, что
     реально «над» страницей (выпадашки, липкие панели). */
  --shadow-pop: 0 12px 32px -12px rgba(0, 0, 0, .75), 0 0 0 1px rgb(var(--rgb-line-light) / .5);
  --shadow-sticky: 0 -8px 24px -12px rgba(0, 0, 0, .8);
  --glow-accent: 0 0 0 3px rgb(var(--rgb-accent) / .28);

  --header-h: 56px;
  --sidebar-w: 248px;
  --page-max: 1560px;

  color-scheme: dark;
}

/* ------------------------------------------------------------------ база */
html { -webkit-text-size-adjust: 100%; }

body {
  background: var(--canvas);
  color: var(--ink);
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

::selection { background: rgb(var(--rgb-accent) / .35); color: #fff; }

/* ---------------------------------------------------------------- аврора */
/* Декоративное свечение из файла стиля (этап 59). Два радиальных
   градиента — фоновое изображение САМОГО body, а не отдельный слой
   с position/z-index: так оно гарантированно оказывается позади всего
   содержимого и не воюет со стеками шапки (z-index 40) и бокового
   меню. Конец градиента — ТОТ ЖЕ цвет с нулевой альфой, а не
   transparent/rgba(0,0,0,0): иначе браузер интерполирует через чёрный
   и по краям появляется грязный серый ободок.

   Где включено (решение владельца проекта, вариант «вход + шапка»):
     • весь веб Администратора/Оператора — приглушённый след сверху
       (шапка липкая, поэтому и свечение fixed — иначе при прокрутке
       шапка бы «съезжала» со своего фона);
     • витрина (страница входа) — в полную силу и прокручивается вместе
       со страницей, как hero в исходном стиле;
     • МОБИЛЬНЫЙ ИНТЕРФЕЙС КЛАДОВЩИКА — НЕТ (mobile/_base.html гасит
       блок body_class): там работа одной рукой в перчатках, украшения
       только мешают, да и background-attachment: fixed на телефонах дорог.

   Проверено на контраст ПОВЕРХ свечения, а не только на чистом фоне:
   в самой яркой точке витрины белый текст даёт 12.5:1, вторичный — 7.6:1;
   в шапке приложения — 17.4:1 и 10.5:1. Оба выше порогов проекта. */
/* Геометрия здесь — не дело вкуса, её надо СЧИТАТЬ, и на этом уже
   один раз обожглись (владелец проекта: «нет свечения на дашборде»).
   Первая версия ставила центр эллипса на 14rem ВЫШЕ экрана и гасила
   прозрачность к 70% радиуса — арифметика: центр −224px, радиус 480px,
   ноль на 0.7×480 = 336px от центра, то есть на y = +112px. В видимую часть
   попадал только хвост: под шапкой оставалось буквально 2.4% альфы,
   что на почти чёрном фоне неразличимо глазом.

   Правило на будущее: центр держать у самой кромки экрана (0 или
   пара rem выше), а прозрачность гасить к 100% радиуса — тогда видимая
   зона равна радиусу, а не его остатку. Проверять не по числам в коде,
   а скриншотом в браузере. */
.has-aurora {
  background-image:
    radial-gradient(72rem 32rem at 20% 0,
      rgb(var(--rgb-aurora-purple) / .22), rgb(var(--rgb-aurora-purple) / 0) 100%),
    radial-gradient(52rem 24rem at 95% -2rem,
      rgb(var(--rgb-pink) / .12), rgb(var(--rgb-pink) / 0) 100%);
  background-repeat: no-repeat;
  background-attachment: fixed;
}
.has-aurora--hero {
  background-image:
    radial-gradient(80rem 46rem at 26% -4rem,
      rgb(var(--rgb-aurora-purple) / .34), rgb(var(--rgb-aurora-purple) / 0) 100%),
    radial-gradient(46rem 28rem at 86% 34rem,
      rgb(var(--rgb-pink) / .16), rgb(var(--rgb-pink) / 0) 100%);
  /* На витрине свечение прокручивается со страницей, как hero
     в исходном стиле. Второй градиент привязан в rem, а не в %:
     процент считался бы от высоты ВСЕЙ страницы, и на длинной
     витрине розовое пятно уехало бы в подвал. */
  background-attachment: scroll;
}


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

* { scrollbar-width: thin; scrollbar-color: var(--line-light) transparent; }
*::-webkit-scrollbar { width: 10px; height: 10px; }
*::-webkit-scrollbar-track { background: transparent; }
*::-webkit-scrollbar-thumb { background: var(--line-light); border-radius: var(--radius-pill); border: 3px solid transparent; background-clip: content-box; }
*::-webkit-scrollbar-thumb:hover { background: rgb(var(--rgb-ink-dim) / .8); background-clip: content-box; }

[x-cloak] { display: none !important; }

.skip-link {
  position: absolute; left: -9999px; top: 0; z-index: 100;
  background: var(--accent); color: #fff; padding: .6rem 1rem;
  border-radius: 0 0 var(--radius-sm) 0; font-weight: 600;
}
.skip-link:focus { left: 0; }

/* --------------------------------------------------------------- каркас */
.app-header {
  position: sticky; top: 0; z-index: 40;
  height: var(--header-h);
  background: rgb(var(--rgb-surface) / .85);
  backdrop-filter: saturate(140%) blur(10px);
  border-bottom: 1px solid var(--line);
}
.app-header__inner {
  height: 100%; display: flex; align-items: center; gap: .75rem;
  padding: 0 1rem;
}

.brand { display: inline-flex; align-items: center; gap: .55rem; }
.brand__mark {
  height: 26px; width: 26px; border-radius: 7px; flex: none;
  background: linear-gradient(140deg, var(--accent-solid), #8b5cf6);
  color: #fff; font-weight: 800; font-size: 14px;
  display: flex; align-items: center; justify-content: center;
}
.brand__name { font-weight: 800; letter-spacing: -.02em; font-size: 15px; color: var(--ink); }

.icon-btn {
  height: 34px; width: 34px; flex: none; border-radius: var(--radius-pill);
  display: inline-flex; align-items: center; justify-content: center;
  color: var(--ink-muted); background: transparent; border: 1px solid transparent;
  transition: background-color .12s ease, color .12s ease;
}
.icon-btn:hover { background: var(--surface-2); color: var(--ink); }

/* Выпадающее меню шапки (Alpine: x-data / x-show) */
.menu { position: relative; }
.menu__trigger {
  display: inline-flex; align-items: center; gap: .5rem;
  max-width: 15rem; height: 34px; padding: 0 .75rem;
  border-radius: var(--radius-pill); border: 1px solid transparent;
  color: var(--ink-muted); font-size: 13px; font-weight: 500;
  transition: background-color .12s ease, color .12s ease, border-color .12s ease;
}
.menu__trigger:hover { background: var(--surface-2); color: var(--ink); border-color: var(--line); }
.menu__trigger-label { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.menu__panel {
  position: absolute; right: 0; top: calc(100% + 6px); z-index: 50;
  min-width: 15rem; padding: .35rem;
  background: var(--surface-2); border: 1px solid var(--line-light);
  border-radius: var(--radius-md); box-shadow: var(--shadow-pop);
}
.menu__section { padding: .5rem .6rem .35rem; }
.menu__section-title { font-size: 11px; text-transform: uppercase; letter-spacing: .06em; color: var(--ink-dim); font-weight: 600; }
.menu__item {
  display: flex; align-items: center; gap: .55rem; width: 100%;
  padding: .5rem .6rem; border-radius: var(--radius-sm);
  font-size: 13.5px; color: var(--ink-muted); text-align: left;
}
.menu__item:hover { background: var(--surface-3); color: var(--ink); }
.menu__item--danger:hover { color: var(--danger); }
.menu__sep { height: 1px; margin: .35rem .3rem; background: var(--line); }

.avatar {
  height: 26px; width: 26px; flex: none; border-radius: var(--radius-pill);
  background: rgb(var(--rgb-accent) / .18); color: var(--accent-ink);
  border: 1px solid rgb(var(--rgb-accent) / .35);
  display: inline-flex; align-items: center; justify-content: center;
  font-size: 11px; font-weight: 700; text-transform: uppercase;
}

.app-body { display: flex; align-items: flex-start; }

.app-sidebar {
  position: fixed; inset-block: 0; left: 0; z-index: 40;
  width: var(--sidebar-w); flex: none;
  background: var(--surface); border-right: 1px solid var(--line);
  padding-top: var(--header-h);
  transform: translateX(-100%);
  transition: transform .16s ease-out;
  overflow-y: auto;
}
.app-sidebar.is-open { transform: translateX(0); }
.app-sidebar__scrim { position: fixed; inset: 0; z-index: 30; background: rgba(0, 0, 0, .6); }
@media (min-width: 1024px) {
  .app-sidebar {
    position: sticky; top: var(--header-h); z-index: auto;
    height: calc(100vh - var(--header-h)); padding-top: 0;
    transform: none;
  }
  .app-sidebar__scrim { display: none; }
}

.nav { padding: .875rem .625rem 1.5rem; display: flex; flex-direction: column; gap: 1.15rem; }
.nav__group { display: flex; flex-direction: column; gap: 1px; }
.nav__label {
  padding: 0 .6rem; margin-bottom: .3rem;
  font-size: 10.5px; font-weight: 700; text-transform: uppercase; letter-spacing: .08em;
  color: var(--ink-dim);
}
.nav__link {
  position: relative; display: flex; align-items: center; gap: .6rem;
  padding: .5rem .6rem; border-radius: var(--radius-sm);
  font-size: 13.5px; line-height: 1.25; color: var(--ink-muted);
  transition: background-color .12s ease, color .12s ease;
}
.nav__link:hover { background: var(--surface-2); color: var(--ink); }
.nav__link.is-active { background: rgb(var(--rgb-accent) / .12); color: var(--accent-ink); font-weight: 600; }
.nav__link.is-active::before {
  content: ""; position: absolute; left: -.625rem; top: 50%; transform: translateY(-50%);
  height: 18px; width: 3px; border-radius: 0 3px 3px 0; background: var(--accent);
}
.nav__icon { height: 17px; width: 17px; flex: none; opacity: .9; }

.app-main { flex: 1 1 auto; min-width: 0; }
.page { width: 100%; max-width: var(--page-max); margin: 0 auto; padding: 1.75rem 1.25rem 4rem; }
.page--narrow { max-width: 46rem; }   /* формы, карточки логина/приглашений */
.page--medium { max-width: 68rem; }   /* карточки документов */
/* Шаг между medium и полной шириной — для дашборда (этап 61). Ровно
   столько же полезной ширины, сколько в макете: 78rem минус боковые
   отступы 2rem даёт 1184px контента, то есть по 584px на колонку в
   двухколоночной сетке — как было нарисовано. Без ограничения страница
   растягивалась до 1560px, колонка выходила 740px, и карточки читались
   как «очень широкие» (замечание владельца проекта). */
.page--large { max-width: 78rem; }    /* дашборд */
.page--flush { max-width: none; padding-inline: 1.25rem; }
/* Плотная страница: нижний отступ 4rem нужен там, где под контентом ещё
   что-то догружается или липнет панель. На дашборде он съедает ровно тот
   запас, из-за которого страница переставала помещаться в 1080px целиком
   (требование этапа 39 — настенный монитор склада). */
.page--dense { padding-bottom: 1.5rem; }
@media (min-width: 1024px) { .page { padding-inline: 2rem; } }

/* ------------------------------------------------------- шапка страницы */
.page-header {
  display: flex; align-items: flex-start; justify-content: space-between;
  gap: 1rem; flex-wrap: wrap; margin-bottom: 1.5rem;
}
.page-header__actions { display: flex; align-items: center; gap: .5rem; flex-wrap: wrap; }
.page-title { font-family: var(--font-display); font-size: 24px; font-weight: 600; letter-spacing: -.02em; line-height: 1.15; }
.page-subtitle { margin-top: .3rem; font-size: 13.5px; color: var(--ink-muted); }
/* Заголовок документа + его статус в одной строке. Статус стоит ЗА
   названием, а не в блоке кнопок справа (запрос владельца проекта):
   рядом с кнопками он читался как ещё одна кнопка, а на печати вовсе
   пропадал — .page-header__actions скрыт в @media print (см. ниже). */
.page-title-row { display: flex; align-items: center; gap: .6rem; flex-wrap: wrap; }
.back-link {
  display: inline-flex; align-items: center; gap: .3rem; margin-bottom: .5rem;
  font-size: 13px; color: var(--ink-dim);
}
.back-link:hover { color: var(--ink-muted); }
/* Ряд возврата: шаг по истории + постоянный адрес родителя. Разделитель
   рисуется псевдоэлементом, чтобы в разметке не заводить пустой <span>. */
.back-row { display: flex; align-items: center; gap: .5rem; }
.back-row > * + *::before { content: "·"; margin-right: .5rem; color: var(--ink-dim); }

.section-title {
  display: flex; align-items: center; gap: .6rem;
  font-size: 11.5px; font-weight: 700; text-transform: uppercase; letter-spacing: .08em;
  color: var(--ink-dim); margin-bottom: .75rem;
}
.section-title::after { content: ""; flex: 1 1 auto; height: 1px; background: var(--line); }

/* Точки-разделители для строки мета-данных документа: «Склад · Организация · вх. №» */
.meta { display: flex; flex-wrap: wrap; align-items: center; gap: .45rem; font-size: 13.5px; color: var(--ink-muted); }
.meta > * + *::before { content: "·"; margin-right: .45rem; color: var(--ink-dim); }

/* Таблица строк документа с номером строки в первой колонке.

   Почему не `grid-cols-7` Tailwind: он делит ВСЕ дорожки поровну, а номер
   двузначный — доля наравне с номенклатурой ему не нужна и отнимается она
   как раз у номенклатуры. Здесь первая дорожка фиксированная, остальные —
   равные доли. Модификатор называет число дорожек ДАННЫХ (номенклатура
   занимает две), чтобы при правке таблицы было видно, что менять. */
.lines-grid { display: grid; gap: .5rem; align-items: center; }
.lines-grid--6 { grid-template-columns: 2.5rem repeat(6, minmax(0, 1fr)); }
/* Та же строка, но с отдельной колонкой действий справа (печать этикетки,
   удаление). Без неё кнопки жили ВНУТРИ колонки «Ячейка» и делили её долю с
   кодом ячейки: код переносился по дефисам в столбик, а кнопка печати
   вылезала за край таблицы (жалоба владельца проекта со скриншотом).
   Ширина фиксированная — содержимое колонки известно заранее и от данных не
   зависит. */
.lines-grid--6-actions { grid-template-columns: 2.5rem repeat(6, minmax(0, 1fr)) 13rem; }
/* Номер строки: он для ссылки «посмотри строку 7», а не для чтения подряд. */
.lines-grid__num { color: var(--ink-dim); font-variant-numeric: tabular-nums; }
/* Код ячейки не переносится: это идентификатор, а не текст. В узкой колонке
   браузер честно рвал его по дефисам — «KZN-\A 01-\A 01-\A 01» в четыре строки
   (жалоба владельца проекта со скриншотом), и прочитать это уже нельзя. */
.lines-grid__code { white-space: nowrap; }

/* ---------------------------------------------------------------- кнопки */
.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: .4rem;
  min-height: var(--control-height); padding: 0 1.05rem;
  border: 1px solid transparent; border-radius: var(--radius-pill);
  font-size: 13.5px; font-weight: 600; line-height: 1; white-space: nowrap;
  cursor: pointer; user-select: none;
  transition: background-color .12s ease, border-color .12s ease, color .12s ease, box-shadow .12s ease;
}
.btn:active { transform: translateY(1px); }
.btn svg { height: 16px; width: 16px; flex: none; }

/* Главное действие — БЕЛАЯ заливка, а не акцентная (этап 59).
   Это самое заметное изменение этапа и прямое требование стиля
   («никогда не делать главную кнопку насыщенной синей/фиолетовой»):
   на почти чёрном фоне белая заливка — самый сильный возможный
   акцент (20.3:1), и любой цвет рядом автоматически становится
   вторичным. Откат — одна строка: вернуть background: var(--accent-solid)
   и color: #fff. Зелёная .btn--success при этом остаётся зелёной: она
   не «главнее», а другая по смыслу — завершающее действие документа. */
.btn--primary { background: #fff; color: #050606; }
.btn--primary:hover { background: var(--ink-muted); }

.btn--secondary { background: var(--surface-2); color: var(--ink); border-color: var(--line-light); }
.btn--secondary:hover { background: var(--surface-3); border-color: var(--ink-dim); }

.btn--ghost { background: transparent; color: var(--ink-muted); }
.btn--ghost:hover { background: var(--surface-2); color: var(--ink); }

.btn--success { background: var(--success); color: #07240f; }
.btn--success:hover { background: #16a34a; color: #fff; }

.btn--danger { background: transparent; color: var(--danger); border-color: rgb(var(--rgb-danger) / .45); }
.btn--danger:hover { background: rgb(var(--rgb-danger) / .12); border-color: var(--danger); }

.btn--dashed {
  background: transparent; color: var(--accent-ink);
  border: 1px dashed var(--line-light);
}
.btn--dashed:hover { background: rgb(var(--rgb-accent) / .1); border-color: var(--accent); }

.btn--sm { min-height: 30px; padding: 0 .65rem; font-size: 12.5px; }
.btn--lg { min-height: 42px; padding: 0 1.15rem; font-size: 15px; }
.btn--block { width: 100%; }

.btn:disabled, .btn.is-disabled, .btn[aria-disabled="true"] {
  background: var(--surface-2); color: var(--ink-dim); border-color: var(--line);
  cursor: not-allowed; box-shadow: none;
}
.btn:disabled:hover, .btn.is-disabled:hover { background: var(--surface-2); color: var(--ink-dim); }
.btn:disabled:active, .btn.is-disabled:active { transform: none; }

.link { color: var(--accent-ink); }
.link:hover { text-decoration: underline; }
.link--muted { color: var(--ink-muted); }
.link--muted:hover { color: var(--ink); text-decoration: underline; }

/* -------------------------------------------------------------- карточки */
.card {
  background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--radius-md); padding: 1.15rem 1.25rem;
}
.card--flush { padding: 0; overflow: hidden; }
.card--link { display: block; transition: border-color .12s ease, background-color .12s ease; }
.card--link:hover { border-color: rgb(var(--rgb-accent) / .5); background: var(--surface-2); }
/* Плотный вариант карточки — для экранов, где важна высота (дашборд склада
   на настенном/офисном мониторе, CLAUDE.md п.37): тот же компонент, только
   меньше внутренних отступов, а не отдельный новый класс карточки. */
.card--compact { padding: .85rem 1rem; }
.card--compact .card__header { margin-bottom: .6rem; }
.card--compact .card__hint { margin-top: .1rem; }
.card__header { display: flex; align-items: flex-start; justify-content: space-between; gap: .75rem; margin-bottom: .9rem; }
.card__title { font-family: var(--font-display); font-size: 14px; font-weight: 600; letter-spacing: -.01em; }
.card__hint { margin-top: .15rem; font-size: 12px; color: var(--ink-muted); line-height: 1.45; }

/* Список-карточка: строки, разделённые линией (история статусов, серии) */
.list { background: var(--surface); border: 1px solid var(--line); border-radius: var(--radius-md); overflow: hidden; }
.list__row { display: flex; align-items: center; justify-content: space-between; gap: .75rem; padding: .7rem 1rem; font-size: 13.5px; }
.list__row + .list__row { border-top: 1px solid var(--line); }
.list__row--empty { color: var(--ink-dim); justify-content: flex-start; }

/* Плитка-метрика на дашборде */
.stat {
  display: block; position: relative;
  background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--radius-md); padding: 1rem 1.15rem;
  transition: border-color .12s ease, background-color .12s ease;
}
a.stat:hover { border-color: rgb(var(--rgb-accent) / .5); background: var(--surface-2); }
.stat__label { display: flex; align-items: center; gap: .45rem; font-size: 12.5px; color: var(--ink-muted); }
.stat__label svg { height: 15px; width: 15px; opacity: .8; }
.stat__value { display: block; margin-top: .45rem; font-family: var(--font-display); font-size: 28px; font-weight: 600; letter-spacing: -.02em; font-variant-numeric: tabular-nums; }
/* Строка под числом плитки: либо динамика со знаком, либо справочная
   подпись. Цвет несёт смысл и НЕ совпадает с «хорошо/плохо» вслепую —
   направление трактует шаблон: для завала документов рост это плохо, для
   скорости работы рост это тоже плохо, а для остатков — нейтрально.
   Поэтому здесь просто три цвета, а выбор делает вызывающая сторона. */
.stat__delta { margin-top: .4rem; display: inline-flex; align-items: center; gap: .3rem; font-size: 12px; font-weight: 500; }
.stat__delta svg { height: 13px; width: 13px; flex: none; }
.stat__delta--good { color: var(--success); }
.stat__delta--bad { color: var(--danger); }
.stat__delta--flat { color: var(--ink-dim); }
.stat__hint { margin-top: .4rem; font-size: 12px; color: var(--ink-dim); }

/* ------------------------------------------- список-рейтинг с полосами */
/* Топ номенклатур на дашборде (этап 61). Раньше это был столбчатый график
   Chart.js, и он резал названия: подпись оси Y ограничена шириной, которую
   библиотека выделила сама, а складская номенклатура длинная («21- ШТ без
   срока годности» приезжало обрезанным с начала). Здесь имя занимает всю
   строку, значение стоит рядом числом, а полоса под ними — только
   относительная величина. Заодно на карточке стало на одну библиотечную
   зависимость меньше: это чистый CSS. */
.bar-list { display: flex; flex-direction: column; gap: .75rem; }
.bar-row { display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: .25rem .75rem; align-items: baseline; }
.bar-row__name { grid-column: 1; font-size: 13.5px; color: var(--ink-muted); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.bar-row__value { grid-column: 2; font-family: var(--font-mono); font-size: 12.5px; color: var(--ink); font-variant-numeric: tabular-nums; }
/* display:block у обоих обязателен: это `span`-ы, а инлайновый элемент
   молча игнорирует width/height — полоса получалась нулевой ширины, и
   строки выглядели одинаковыми (поймано замером ширины в браузере, на
   глаз читалось как «полоски просто дим»). Тот же класс ошибки, что был
   у .stat__value. */
.bar-row__track { display: block; grid-column: 1 / -1; height: 6px; border-radius: var(--radius-pill); background: var(--surface-2); overflow: hidden; }
.bar-row__fill { display: block; height: 100%; border-radius: var(--radius-pill); background: linear-gradient(90deg, var(--accent-hover), var(--accent)); }

/* ------------------------------------------ крупное значение + разбивка */
/* «Время выполнения документа»: одно большое число сверху и разбивка по
   типам списком под ним. По той же причине, что и выше — типы документов
   читаются словами, а не угадываются по длине столбика. */
.cycle-hero { display: flex; align-items: baseline; gap: .6rem; }
.cycle-hero__value { font-family: var(--font-display); font-size: 34px; font-weight: 600; letter-spacing: -.02em; font-variant-numeric: tabular-nums; }
.cycle-hero__unit { font-size: 13px; color: var(--ink-dim); }
.cycle-rows { display: flex; flex-direction: column; gap: .55rem; margin-top: .9rem; }
.cycle-row { display: flex; align-items: baseline; justify-content: space-between; gap: 1rem; font-size: 13.5px; color: var(--ink-muted); }
.cycle-row__value { font-family: var(--font-mono); color: var(--ink); font-variant-numeric: tabular-nums; }

/* Выделенная карточка — единственный «цветной» блок страницы. Заливка
   surface-3 (Cobalt) взята из роли «spotlight» исходного стиля: ею нельзя
   красить фон страницы, но можно поднять ОДИН блок, который должен быть
   замечен. На дашборде это использование тарифа. */
.card--spotlight { background: var(--surface-3); border-color: var(--line-light); }
.card--spotlight .card__hint { color: var(--accent-ink); }
/* Гейдж НА выделенной карточке. Своя пара цветов нужна потому, что трек по
   умолчанию залит `--surface-3` — тем же токеном, которым покрашена сама
   карточка. На обычном фоне это работает, а здесь трек полностью сливался
   с подложкой: было видно только закрашенную часть, и пропорция — ради
   которой гейдж и существует — пропадала. Баг нашёл владелец проекта.
   Поэтому внутри `.card--spotlight` трек полупрозрачно-белый, а обычная
   заливка сплошная белая, как в макете; цвета порогов (warning/danger)
   при этом продолжают работать. */
.card--spotlight .meter__track { background: rgb(255 255 255 / .12); }
.card--spotlight .meter__fill--accent { background: #fff; }
.card--spotlight .meter__label { color: var(--accent-ink); }
.card--spotlight .meter__value { color: #fff; font-family: var(--font-mono); }

/* Крупный заголовок-приветствие дашборда. Отдельный модификатор, а не
   правка .page-title: на всех остальных страницах заголовок должен
   остаться прежнего кегля. */
.page-title--hero { font-size: 30px; }
@media (max-width: 640px) { .page-title--hero { font-size: 24px; } }

/* -------------------------------------------------------------- таблицы */
.table-wrap {
  background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--radius-md); overflow: auto; max-width: 100%;
}
.table { width: 100%; border-collapse: separate; border-spacing: 0; font-size: 13.5px; }
.table thead th {
  position: sticky; top: 0; z-index: 1;
  background: var(--surface-2); color: var(--ink-muted);
  font-size: 11px; font-weight: 600; text-transform: uppercase; letter-spacing: .05em;
  text-align: left; white-space: nowrap;
  padding: .5rem .75rem; border-bottom: 1px solid var(--line);
}
.table tbody td { padding: .45rem .75rem; border-bottom: 1px solid var(--line); vertical-align: middle; }
.table tbody tr:last-child td { border-bottom: 0; }
.table tbody tr { transition: background-color .1s ease; }
.table tbody tr:hover { background: rgb(var(--rgb-surface-2) / .6); }
.table tbody tr.is-muted td { color: var(--ink-dim); }
/* Индикатор отправки строки документа: показывается, пока идёт запрос
   (CLAUDE.md п.36). Без него автосохранение выглядит как «ничего не
   произошло» — особенно на медленной связи, где ответ приходит не сразу. */
.spinner {
  display: inline-block; width: 14px; height: 14px; flex: none;
  border: 2px solid rgb(var(--rgb-ink-dim) / .35);
  border-top-color: var(--accent-ink);
  border-radius: 50%;
  animation: spin .6s linear infinite;
}
@keyframes spin { to { transform: rotate(360deg); } }
/* Уважаем системную настройку «меньше движения»: вместо вращения — пульсация. */
@media (prefers-reduced-motion: reduce) {
  .spinner { animation: pulse 1.2s ease-in-out infinite; }
  @keyframes pulse { 50% { opacity: .35; } }
}

/* Кликабельная строка списка: сам переход делает делегированный обработчик
   в layout/base.html по атрибуту data-row-href. Настоящая ссылка внутри
   строки НЕ убирается — клик по строке это ускорение для мыши, а не замена
   ссылке (по ней работают Tab, средняя кнопка, «открыть в новой вкладке» и
   читалки экрана). Поэтому же ссылка приглушена до .link--quiet: подсветка
   всей строки на ховере и так показывает, что строка живая. */
.table tbody tr[data-row-href] { cursor: pointer; }
.table tbody tr[data-row-href]:hover { background: rgb(var(--rgb-surface-2) / .95); }
[data-row-href]:hover .link--quiet { color: var(--accent-ink); }
.list__row[data-row-href] { cursor: pointer; }
.list__row[data-row-href]:hover { background: rgb(var(--rgb-surface-2) / .95); }
.table .num { text-align: right; font-family: var(--font-mono); font-variant-numeric: tabular-nums; white-space: nowrap; }
.table .mono { font-family: var(--font-mono); font-size: 12.5px; }
.table .shrink { width: 1%; white-space: nowrap; }
.table__empty { padding: 2.25rem 1rem; text-align: center; color: var(--ink-dim); }
.table--compact thead th { padding: .45rem .75rem; }
.table--compact tbody td { padding: .4rem .75rem; }
/* Заголовок-ссылка сортировки */
.th-sort { display: inline-flex; align-items: center; gap: .3rem; color: inherit; }
.th-sort:hover { color: var(--ink); }
.th-sort__arrow { opacity: .55; }

.pagination {
  display: flex; align-items: center; justify-content: space-between; gap: 1rem;
  margin-top: .9rem; font-size: 13px; color: var(--ink-muted);
}
.pagination__pages { display: flex; align-items: center; gap: .4rem; }

/* --------------------------------------------------------------- бейджи */
.badge {
  display: inline-flex; align-items: center; gap: .35rem;
  padding: .2rem .55rem; border-radius: var(--radius-pill);
  font-size: 11.5px; font-weight: 600; line-height: 1.35; white-space: nowrap;
  border: 1px solid transparent;
}
.badge--neutral { background: var(--surface-3); color: var(--ink-muted); border-color: var(--line-light); }
.badge--accent { background: rgb(var(--rgb-accent) / .16); color: var(--accent-ink); border-color: rgb(var(--rgb-accent) / .35); }
.badge--success { background: rgb(var(--rgb-success) / .16); color: var(--success); border-color: rgb(var(--rgb-success) / .32); }
.badge--warning { background: rgb(var(--rgb-warning) / .16); color: var(--warning); border-color: rgb(var(--rgb-warning) / .32); }
.badge--danger { background: rgb(var(--rgb-danger) / .16); color: var(--danger); border-color: rgb(var(--rgb-danger) / .32); }
.badge--info { background: rgb(var(--rgb-info) / .16); color: var(--info); border-color: rgb(var(--rgb-info) / .32); }
.badge--lg { padding: .3rem .7rem; font-size: 12.5px; }

/* Площадки. Отдельные модификаторы, а не badge--info на все сразу: в списке
   заявок источник — то, что ищут глазами, и одинаковые синие метки для WB,
   Ozon и Яндекса заставляли читать подпись у каждой строки. Цвет здесь
   работает как этикетка, поэтому он и фирменный: смысл узнаётся раньше
   текста. Подпись при этом остаётся — цветом одним ничего не сообщаем
   (дальтонизм, ч/б печать). */
.badge--wb { background: rgb(var(--rgb-wb) / .16); color: var(--wb-ink); border-color: rgb(var(--rgb-wb) / .32); }
.badge--ozon { background: rgb(var(--rgb-ozon) / .16); color: var(--ozon); border-color: rgb(var(--rgb-ozon) / .32); }
.badge--yandex { background: rgb(var(--rgb-yandex) / .16); color: var(--yandex); border-color: rgb(var(--rgb-yandex) / .32); }
/* Источник «API клиента» — не площадка, поэтому и цвет не фирменный: это
   не бренд, а способ, которым заявка попала в систему. Нейтральный
   серо-синий отличает её от маркетплейсов и от ручной, не претендуя на
   чужую палитру. */
.badge--api { background: rgb(var(--rgb-ink-muted, 100 116 139) / .14); color: var(--ink-muted); border-color: rgb(var(--rgb-ink-muted, 100 116 139) / .30); }

/* Колонка таблицы, ради которой её и открывают. Не «раскрасить ячейку при
   проблеме», а выделить КОЛОНКУ целиком: иначе она работает только у того,
   кто и так знал, куда смотреть, — а именно этого знания у оператора и нет.
   Подсветка проблемы (--alarm) поверх — второй уровень, для строки. */
.cell-focus {
  background: var(--surface-2);
  border-left: 1px solid var(--line);
}
.cell-focus__head {
  background: var(--surface-2);
  border-left: 1px solid var(--line);
}
.cell-focus__value { font-size: 15px; font-weight: 700; line-height: 1.3; }
.cell-focus--alarm {
  background: rgb(var(--rgb-warning) / .14);
  border-left-color: rgb(var(--rgb-warning) / .45);
}
.text-warning { color: var(--warning); }

/* Поле ввода станции маркировки. Крупное намеренно: человек стоит у стола
   с коробкой в руках и смотрит на экран издалека, а не сидит перед ним. */
.scan-input {
  width: 100%;
  font-size: 22px;
  letter-spacing: .04em;
  min-height: 52px;
}

/* Две колонки рядом — очередь и отработанное. На узком экране схлопываются:
   станция может стоять и на планшете. */
.grid-2 { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 1rem; align-items: start; }
@media (max-width: 900px) { .grid-2 { grid-template-columns: minmax(0, 1fr); } }

/* --------------------------------------------------- гейдж использования */
/* Использование лимита тарифа на дашборде (§12 ТЗ). Заливка несёт статус
   (accent -> warning -> danger, тот же порог 90%, что и у текстового
   баннера plan_usage_warnings), незалитый трек — нейтральный surface-3,
   а не "светлый шаг той же рампы" из общего гайда по дата-визуализации:
   у проекта нет отдельных светлых ступеней accent/warning/danger, заводить
   их ради одного компонента — лишняя сущность. */
.meter + .meter { margin-top: 1rem; }
/* Гейджи, стоящие РЯДОМ, а не друг под другом. Нужна отдельная сетка из-за
   правила выше: в ряду второй `.meter` остаётся соседним элементом первого,
   получает `margin-top: 1rem` и уезжает вниз относительно соседа. На
   дашборде это было видно — «Документы в этом месяце» стояли ниже
   «Пользователей». */
.meter-grid { display: grid; gap: .75rem 2rem; }
.meter-grid .meter + .meter { margin-top: 0; }
@media (min-width: 640px) { .meter-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
.meter__row { display: flex; align-items: baseline; justify-content: space-between; gap: .75rem; font-size: 12.5px; margin-bottom: .3rem; }
.meter__label { color: var(--ink-muted); }
.meter__value { font-weight: 650; font-variant-numeric: tabular-nums; }
.meter__track { height: 8px; border-radius: var(--radius-pill); background: var(--surface-3); overflow: hidden; }
.meter__fill { height: 100%; border-radius: var(--radius-pill); transition: width .3s ease; }
.meter__fill--accent { background: var(--accent); }
.meter__fill--warning { background: var(--warning); }
.meter__fill--danger { background: var(--danger); }

/* ------------------------------------------------------- формы и фильтры */
.field { display: block; margin-bottom: 1rem; }
/* Подпись поля. Размер/междустрочие/отступ вынесены в переменные не ради
   красоты: по ним же считается высота подписи (--doc-row-label-h), на
   которую опускаются кнопки в строке полей документа (.doc-row__actions).
   Если правку внести только здесь, кнопки поедут — поэтому одно число. */
.field__label {
  display: block;
  margin-bottom: var(--field-label-gap);
  font-size: var(--field-label-size);
  line-height: var(--field-label-leading);
  font-weight: 600;
  color: var(--ink);
}

/* Строка полей внутри строки документа: «количество — серия — ячейка —
   кнопка». Раньше собиралась утилитами `flex flex-wrap items-end gap-3`, и
   держалось это лишь на том, что все блоки ровно одной высоты. Стоило в
   Приёмке выбрать «+ Новая партия…» — в блоке серии раскрывались ещё два
   поля и подсказка, — как флекс прижимал остальные поля к НИЗУ выросшего
   блока: «Количество по факту» уезжало вниз, на уровень срока годности
   (жалоба владельца проекта со скриншотом).

   Выравнивание по ВЕРХУ: колонки начинаются на одной линии, что бы ни
   раскрылось внутри соседа, — а за то, чтобы на одной линии оказались и
   сами поля, отвечает правило подписи ниже. */
.doc-row__fields { display: flex; flex-wrap: wrap; align-items: flex-start; gap: .75rem; }
/* Подпись колонки вынута из потока и прижата к низу отведённой ей
   полосы. Смысл: положение самого ПОЛЯ перестаёт зависеть от того,
   сколько строк заняла подпись. Выравнивания «по верху» для этого мало —
   достаточно одной длинной подписи («Количество по факту» в колонке
   шириной 9rem), чтобы она перенеслась на вторую строку и опустила своё
   поле ниже соседних; влезет подпись в строку или нет, зависит от шрифта
   системы, так что проверить один раз и успокоиться нельзя. Полоса
   рассчитана на две строки; подпись, которой мало и этого, вылезет вверх,
   в поля строки, — это видно сразу, в отличие от тихого перекоса.

   Два селектора, потому что подпись лежит на разной глубине: у
   «количества» и «ячейки» — прямо в колонке, а у серии — внутри партиала
   catalog/_series_picker.html, у которого свой корневой div (в него
   целится HTMX). Глубже не идём намеренно: «Срок годности новой партии»
   внутри раскрывающегося блока — не подпись колонки. */
.doc-row__fields > * { position: relative; padding-top: var(--doc-row-label-h); }
.doc-row__fields > * > .field__label:first-child,
.doc-row__fields > * > * > .field__label:first-child {
  position: absolute; top: 0; left: 0; right: 0; margin: 0;
  height: calc(var(--doc-row-label-h) - var(--field-label-gap));
  display: flex; align-items: flex-end;
}
/* Кнопки подписи не имеют: полосу под неё им отдаёт правило выше, а
   min-height добирает высоту поля — чтобы маленькая кнопка (.btn--sm,
   30px) стояла по центру против поля в 36px, а не липла к его краю. */
.doc-row__actions {
  display: flex; align-items: center; gap: .5rem;
  min-height: calc(var(--doc-row-label-h) + var(--control-height));
}
/* Звёздочка обязательного поля. Ставится из разметки по `field.field.required`,
   то есть по самой форме — переписанный руками признак разошёлся бы с
   валидацией при первой же правке (п. 1 разбора тестирования). */
.field__req { margin-left: .15rem; color: var(--danger); font-weight: 700; }
.field__hint { margin-top: .3rem; font-size: 12px; color: var(--ink-dim); line-height: 1.45; }
.field__error { margin-top: .3rem; font-size: 12px; color: var(--danger); }
/* Подсказка списком — требования к паролю. Селектор по `ul`, а не по классу:
   свой список приходит из `users.forms.password_help_html()` с классом
   `hint-list`, а список Django (`PasswordChangeForm.new_password1`) — с
   классом `helptext`; оформлять нужно оба. Маркеры у списков снимает
   preflight Tailwind, поэтому возвращаем их здесь (см. CLAUDE.md п.26). */
.field__hint ul { margin: .2rem 0 0; padding-left: 1.05rem; list-style: disc; }
.field__hint ul li { margin-top: .15rem; }
/* Поле-флажок: подпись справа от квадратика, а не над ним. Подпись внутри
   теряет свой нижний отступ — он рассчитан на подпись НАД полем и здесь
   сдвигал бы текст вверх относительно самого флажка. */
.field--inline { display: flex; align-items: center; gap: .5rem; margin-bottom: 0; }
.field--inline > .field__label { margin-bottom: 0; }
.form-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 0 1rem; }
.form-grid > .field--full { grid-column: 1 / -1; }
@media (max-width: 640px) { .form-grid { grid-template-columns: minmax(0, 1fr); } }
.form-actions { display: flex; align-items: center; gap: .6rem; margin-top: 1.25rem; padding-top: 1.25rem; border-top: 1px solid var(--line); }

/* Специфичность базового слоя полей ввода — ровно 0-0-2 («html body» +
   :where(), который сам не добавляет ничего), и это между двух огней:

   * выше 0-0-1 — preflight Tailwind (`input, select, textarea { padding: 0;
     font-size: 100%; font-weight: inherit }`) обнулял наш padding и кегль,
     из-за чего текст в поле стоял вплотную к рамке (баг владельца проекта:
     «ООО ЙОЛКА пиксель в пиксель»). Порядок подключения тут не спасает:
     0-0-1 бьёт 0-0-0 всегда, а Play CDN дописывает свой <style> ПОСЛЕ нас;
   * ниже 0-1-0 — любая утилита в разметке (.w-14, .text-xs) и компонент
     (.m-input) по-прежнему перебивают базу. Именно ради этого тут :where(),
     а не голый `input[type=number]` (0-1-1): раньше поле «кол-во этикеток»
     молча растягивалось на всю строку вместо w-14.

   Итог: не трогайте «html body» — без него уедут отступы и кегль полей,
   с классом вместо него сломаются утилиты. Ховер/фокус ниже — со своей
   специфичностью: они меняют только рамку и подсветку, с утилитами не
   спорят. */
html body :where(input[type=text], input[type=email], input[type=password], input[type=number],
input[type=date], input[type=datetime-local], input[type=search], input[type=tel],
select, textarea) {
  width: 100%;
  min-height: var(--control-height);
  padding: .45rem .7rem;
  background: var(--surface-2);
  border: 1px solid var(--line-light);
  border-radius: var(--radius-sm);
  color: var(--ink);
  font-size: 16px;                     /* 16px — iOS Safari не зумит поле */
  font-family: inherit;
  transition: border-color .12s ease, box-shadow .12s ease, background-color .12s ease;
}
@media (min-width: 640px) {
  html body :where(input[type=text], input[type=email], input[type=password], input[type=number],
  input[type=date], input[type=datetime-local], input[type=search], input[type=tel],
  select, textarea) { font-size: 13.5px; }
}
input::placeholder, textarea::placeholder { color: var(--ink-dim); }
input:hover:not(:disabled), select:hover:not(:disabled), textarea:hover:not(:disabled) { border-color: var(--ink-dim); }
input:focus, select:focus, textarea:focus {
  outline: none; border-color: var(--accent); background: var(--surface);
  box-shadow: var(--glow-accent);
}
input:disabled, select:disabled, textarea:disabled { opacity: .55; cursor: not-allowed; }
input[type=checkbox], input[type=radio] { width: 16px; height: 16px; min-height: 0; accent-color: var(--accent); cursor: pointer; }
html body :where(select) {
  appearance: none;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20' fill='%239aa0b8'%3E%3Cpath fill-rule='evenodd' d='M5.23 7.21a.75.75 0 011.06.02L10 11.19l3.71-3.96a.75.75 0 111.08 1.04l-4.25 4.53a.75.75 0 01-1.08 0L5.21 8.27a.75.75 0 01.02-1.06z' clip-rule='evenodd'/%3E%3C/svg%3E");
  background-repeat: no-repeat; background-position: right .5rem center; background-size: 18px;
  padding-right: 2rem;
}
.checkbox { display: inline-flex; align-items: center; gap: .5rem; font-size: 13.5px; color: var(--ink-muted); cursor: pointer; }
.checkbox input { flex: none; }

/* Один ряд фильтров над тем, что он скоупит */
.toolbar {
  display: flex; flex-wrap: wrap; align-items: flex-end; gap: .6rem;
  margin-bottom: 1rem;
}
.toolbar__field { display: flex; flex-direction: column; gap: .25rem; min-width: 0; }
.toolbar__label { font-size: 11px; text-transform: uppercase; letter-spacing: .05em; color: var(--ink-dim); font-weight: 600; }
.toolbar__field--grow { flex: 1 1 16rem; max-width: 26rem; }
.toolbar__spacer { flex: 1 1 auto; }

/* Сегментированный переключатель (период на дашборде и т.п.) */
.segmented { display: inline-flex; padding: 3px; gap: 2px; background: var(--surface-2); border: 1px solid var(--line); border-radius: var(--radius-pill); }
.segmented__item { padding: .3rem .8rem; border-radius: var(--radius-pill); font-size: 12.5px; font-weight: 600; color: var(--ink-muted); }
.segmented__item:hover { color: var(--ink); }
.segmented__item.is-active { background: var(--accent-solid); color: #fff; }

/* Вкладки разделов рабочего места.

   Намеренно НЕ `.segmented`, хотя задача похожа. На одном экране обе
   полосы встречаются рядом: вкладки отвечают на вопрос «в каком разделе
   я», а `.segmented` — «что показать в списке» (состояние карточек,
   период). Одинаковый вид у двух разных смыслов хуже, чем отсутствие
   единообразия: человек читает надписи, чтобы понять, что произойдёт.

   Поэтому вкладки — подчёркнутые, во всю ширину блока, а не
   кнопки-таблетки в рамке. Счётчик рядом с названием: он и есть главное,
   ради чего вкладки заведены — видно, где сегодня работа, не открывая
   каждый раздел. */
.tabs { display: flex; gap: 1.5rem; border-bottom: 1px solid var(--line); margin-bottom: 1.25rem; }
.tabs__item {
  display: inline-flex; align-items: center; gap: .45rem;
  padding: .6rem .1rem; margin-bottom: -1px;
  border-bottom: 2px solid transparent;
  font-size: 13.5px; font-weight: 600; color: var(--ink-muted);
  white-space: nowrap;
}
.tabs__item:hover { color: var(--ink); }
.tabs__item.is-active { color: var(--ink); border-bottom-color: var(--accent-solid); }
.tabs__count {
  min-width: 1.35rem; padding: 0 .35rem; border-radius: 999px;
  background: var(--surface-3); color: var(--ink-muted);
  font-size: 11.5px; font-weight: 700; text-align: center;
}
/* Счётчик — не украшение, а сигнал «здесь работа»: когда считать нечего,
   он не показывается вовсе, а не рисует ноль. */
.tabs__count--attention { background: rgb(var(--rgb-warning) / .2); color: var(--warning); }

/* Список «поле — значение»: характеристики номенклатуры и любые другие
   справочные пары на карточке. Две колонки на широком экране, одна на узком;
   подпись приглушена, значение — основным цветом. */
/* Вложенная карточка: раскрывающаяся форма добавления внутри блока
   (партии, штрихкоды). Фон на ступень темнее, чтобы форма читалась как
   врезка, а не как ещё один блок страницы. */
.card--inset { background: var(--surface-2); border-color: var(--line); padding: .9rem 1rem; }

/* Блок характеристик (карточка номенклатуры).

   Был «подпись слева — значение справа» в две колонки. Пока все значения
   короткие, это читается хорошо, но стоит появиться значению с пояснением
   («шт — штука · количество в документах только целое»), как выключка
   вправо рвёт фразу и уносит хвост под правый край. Попытка чинить это
   поштучно — часть строк в столбик, часть в строку — сделала только хуже:
   в одной зрительной строке слева оказывался бейдж в строку, справа тот же
   бейдж под подписью (две жалобы владельца проекта со скриншотами).

   Поэтому структура у всех характеристик теперь ОДНА: подпись сверху,
   значение под ней и по левому краю. Одинаковые по устройству блоки дают
   одинаковую высоту, а grid растягивает соседей по строке — разделители
   в обеих колонках стоят на одной линии, даже если значение перенеслось. */
.specs { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 0 2rem; }
.spec {
  display: flex; flex-direction: column; gap: .1rem;
  padding: .55rem 0; border-bottom: 1px solid var(--line);
}
.spec__label { font-size: 12px; color: var(--ink-muted); }
.spec__value { font-size: 13.5px; color: var(--ink); }
.spec__note { color: var(--ink-dim); }
@media (max-width: 720px) { .specs { grid-template-columns: minmax(0, 1fr); } }

/* ------------------------------------------------- алерты, пустые состояния */
.alert {
  display: flex; align-items: flex-start; gap: .6rem;
  padding: .7rem .9rem; border-radius: var(--radius-sm);
  border: 1px solid; font-size: 13.5px; line-height: 1.5;
}
.alert svg { height: 17px; width: 17px; flex: none; margin-top: .1rem; }
.alert--success { background: rgb(var(--rgb-success) / .1); border-color: rgb(var(--rgb-success) / .3); color: var(--success); }
.alert--error { background: rgb(var(--rgb-danger) / .1); border-color: rgb(var(--rgb-danger) / .3); color: var(--danger); }
.alert--warning { background: rgb(var(--rgb-warning) / .1); border-color: rgb(var(--rgb-warning) / .3); color: var(--warning); }
.alert--info { background: rgb(var(--rgb-info) / .1); border-color: rgb(var(--rgb-info) / .3); color: var(--info); }

.empty {
  display: flex; flex-direction: column; align-items: center; gap: .5rem;
  padding: 2.5rem 1.5rem; text-align: center;
  background: var(--surface); border: 1px dashed var(--line-light); border-radius: var(--radius-md);
}
.empty__icon {
  height: 40px; width: 40px; border-radius: var(--radius-md);
  background: var(--surface-2); color: var(--ink-dim);
  display: flex; align-items: center; justify-content: center; margin-bottom: .25rem;
}
.empty__title { font-size: 14.5px; font-weight: 650; }
.empty__text { font-size: 13px; color: var(--ink-muted); max-width: 34rem; line-height: 1.55; }

/* Подсказка-«что дальше» над таблицей строк документа */
.hint { display: flex; align-items: flex-start; gap: .55rem; font-size: 13px; color: var(--ink-muted); line-height: 1.5; }
.hint svg { height: 16px; width: 16px; flex: none; margin-top: .12rem; color: var(--ink-dim); }

.kbd {
  padding: .1rem .35rem; border-radius: 5px; font-size: 11.5px;
  background: var(--surface-2); border: 1px solid var(--line-light); color: var(--ink-muted);
  font-family: var(--font-mono);
}

/* ===========================================================================
   Мобильный интерфейс кладовщика (apps.mobile).
   Отдельная шкала размеров: работа в перчатках, одна рука, экран телефона —
   поэтому 56px минимальная высота цели нажатия и 17px базовый кегль.
   ======================================================================== */
.m-shell { max-width: 34rem; margin: 0 auto; padding: 1rem 1rem calc(1.5rem + env(safe-area-inset-bottom)); }
.m-header {
  position: sticky; top: 0; z-index: 30;
  display: flex; align-items: center; gap: .75rem;
  padding: .75rem 1rem calc(.75rem);
  background: rgb(var(--rgb-surface) / .92); backdrop-filter: blur(10px);
  border-bottom: 1px solid var(--line);
}
.m-title { font-size: 19px; font-weight: 800; letter-spacing: -.02em; }
.m-subtitle { font-size: 14px; color: var(--ink-muted); margin-top: .15rem; }

.m-card { background: var(--surface); border: 1px solid var(--line); border-radius: var(--radius-lg); padding: 1.15rem; }
.m-card--flush { padding: 0; overflow: hidden; }

.m-btn {
  display: flex; align-items: center; justify-content: center; gap: .55rem;
  width: 100%; min-height: 56px; padding: 0 1.1rem;
  border: 1px solid transparent; border-radius: var(--radius-lg);
  font-size: 17px; font-weight: 650; line-height: 1.2; text-align: center;
  cursor: pointer;
  transition: background-color .12s ease, border-color .12s ease, color .12s ease;
}
.m-btn:active { transform: scale(.99); }
.m-btn svg { height: 20px; width: 20px; flex: none; }
.m-btn--primary { background: var(--accent-solid); color: #fff; }
.m-btn--primary:hover { background: var(--accent-hover); }
.m-btn--success { background: var(--success); color: #07240f; }
.m-btn--success:hover { background: #16a34a; color: #fff; }
.m-btn--secondary { background: var(--surface-2); color: var(--ink); border-color: var(--line-light); }
.m-btn--secondary:hover { background: var(--surface-3); }
.m-btn--outline { background: transparent; color: var(--accent-ink); border-color: rgb(var(--rgb-accent) / .45); }
.m-btn--outline:hover { background: rgb(var(--rgb-accent) / .1); }
.m-btn--danger { background: transparent; color: var(--danger); border-color: rgb(var(--rgb-danger) / .45); }
.m-btn--danger:hover { background: rgb(var(--rgb-danger) / .12); }
.m-btn--auto { width: auto; flex: none; }
.m-btn:disabled, .m-btn.is-disabled {
  background: var(--surface-2); color: var(--ink-dim); border-color: var(--line); cursor: not-allowed;
}

.m-input, .m-field input, .m-field select, .m-field textarea {
  min-height: 56px; font-size: 17px; border-radius: var(--radius-lg); padding: .75rem 1rem;
}
.m-field { margin-bottom: 1rem; }
.m-field__label { display: block; margin-bottom: .35rem; font-size: 14px; font-weight: 600; color: var(--ink-muted); }

/* Шаг сценария: «Строка 2 из 7» + полоса прогресса */
.m-step { margin-bottom: 1rem; }
.m-step__meta { display: flex; align-items: baseline; justify-content: space-between; font-size: 13px; color: var(--ink-muted); margin-bottom: .4rem; }
.m-step__bar { height: 6px; border-radius: var(--radius-pill); background: var(--surface-2); overflow: hidden; }
.m-step__fill { height: 100%; border-radius: var(--radius-pill); background: var(--accent-solid); transition: width .25s ease; }

/* Липкая панель главного действия внутри .m-card: длинную форму строки
   можно прокручивать, а «Далее» остаётся под большим пальцем. Отрицательные
   поля равны padding карточки, поэтому панель тянется от края до края. */
.m-sticky {
  position: sticky; bottom: 0; z-index: 20;
  margin: 1.25rem -1.15rem -1.15rem;
  padding: .9rem 1.15rem calc(.9rem + env(safe-area-inset-bottom));
  border-top: 1px solid var(--line);
  background: linear-gradient(to top, var(--surface) 72%, rgb(var(--rgb-surface) / .75));
  backdrop-filter: blur(6px);
}

/* Крупная строка «ключ — значение» на экране кладовщика */
.m-kv { display: flex; align-items: baseline; justify-content: space-between; gap: 1rem; padding: .65rem 0; font-size: 16px; }
.m-kv + .m-kv { border-top: 1px solid var(--line); }
.m-kv__key { color: var(--ink-muted); }
.m-kv__value { font-weight: 600; text-align: right; }

@media print {
  .app-header, .app-sidebar, .page-header__actions, .toolbar, .pagination { display: none !important; }
  body { background: #fff; color: #000; }
  .card, .table-wrap { border-color: #ddd; background: #fff; }
}

/* ---------------------------------------------- добавочные компоненты слоя */

/* Карточка входа/регистрации/приглашения — узкая колонка по центру */
.auth-card { width: 100%; max-width: 26rem; margin: 2.5rem auto 0; padding: 1.75rem; }

/* Модальное окно (подтверждения, генератор ячеек) */
.modal-panel {
  position: relative; width: 100%; max-width: 28rem; padding: 1.25rem;
  background: var(--surface); border: 1px solid var(--line-light);
  border-radius: var(--radius-md); box-shadow: var(--shadow-pop);
}

/* Форма вокруг ОДНОЙ кнопки. В HTML действие — это всегда форма (POST и
   CSRF), но визуально пользователь видит кнопку, и никакой коробки вокруг
   неё быть не должно: форма перестаёт занимать место и кнопка встаёт в ряд
   с соседями сама.

   Заведён после того, как в разделе маркетплейсов все такие формы носили
   класс `.inline-form` — а он рисовал карточку с рамкой и отступами.
   Название читалось как «форма в строку», поэтому и прижилось; сам класс
   удалён, чтобы на те же грабли не наступили снова. Нужна вложенная
   форма-карточка — это `.card--inset`. */
.form-button { display: inline-flex; }

/* Кнопки в служебной колонке таблицы. Без общего контейнера формы
   склеивались пробелами разметки и разъезжались по высоте. */
.row-actions {
  display: flex; align-items: center; justify-content: flex-end;
  gap: .4rem; flex-wrap: wrap;
}

/* Подсказка «что делать дальше» над строками документа */
.hint-card {
  padding: .8rem 1rem; border-radius: var(--radius-md);
  background: rgb(var(--rgb-accent) / .08); border: 1px solid rgb(var(--rgb-accent) / .22);
  color: var(--ink); font-size: 13.5px; line-height: 1.55;
}
.hint-card--warning { background: rgb(var(--rgb-warning) / .08); border-color: rgb(var(--rgb-warning) / .28); }
.hint-card--info { background: rgb(var(--rgb-info) / .08); border-color: rgb(var(--rgb-info) / .28); }
.hint-card--danger { background: rgb(var(--rgb-danger) / .08); border-color: rgb(var(--rgb-danger) / .28); color: var(--danger); }

/* Строки документа, свёрстанные CSS-гридом (а не <table>): те же правила,
   что у .table — шапка на surface-2, разделители между строками. */
.grid-head {
  padding: .55rem .875rem; background: var(--surface-2); border-bottom: 1px solid var(--line);
  font-size: 11px; font-weight: 600; text-transform: uppercase; letter-spacing: .05em; color: var(--ink-muted);
}
.grid-row { padding: .6rem .875rem; font-size: 13.5px; }
.grid-row + .grid-row { border-top: 1px solid var(--line); }
.grid-row:hover { background: rgb(var(--rgb-surface-2) / .5); }

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

   Разделитель делаем заметнее, чем у обычной строки таблицы: блок высокий,
   волосяная линия на его фоне теряется. Плюс подсветка при наведении — она
   показывает границы текущего блока целиком, а не отдельной строки. */
.doc-row { padding: 1rem; }
.doc-row + .doc-row { border-top: 2px solid var(--line); }
.doc-row:hover { background: rgb(var(--rgb-surface-2) / .4); }

/* Ссылка внутри таблицы: цвет наследуется, акцент — только на hover, иначе
   таблица из 8 колонок превращается в сплошное фиолетовое поле. */
.link--quiet { color: inherit; }
.link--quiet:hover { color: var(--accent-ink); text-decoration: underline; }

/* Поля внутри ряда фильтров не тянутся на всю ширину */
.toolbar input[type=text], .toolbar input[type=search] { width: auto; min-width: 16rem; }
.toolbar input[type=date], .toolbar input[type=number], .toolbar select { width: auto; min-width: 11rem; }
.toolbar .checkbox, .toolbar label { white-space: nowrap; }
.link--danger { color: var(--danger); }
.link--danger:hover { color: var(--danger); text-decoration: underline; }

/* Ширина страницы мобильного интерфейса кладовщика: один столбец под
   большой палец, крупный кегль — см. блок .m-* выше. */
.page--mobile { max-width: 34rem; padding-top: 1.25rem; padding-bottom: calc(3rem + env(safe-area-inset-bottom)); }
/* Мобильный интерфейс кладовщика остаётся на Inter и своём весе
   по явному решению владельца проекта (этап 59 — только веб):
   .page-title общий у веба и мобильного, поэтому здесь он явно
   откатывается на прежнее начертание. Цвета при этом у мобильного
   ОБЩИЕ с вебом и поменялись — у проекта одна тема на всё
   приложение, разводить две палитры ради этого этапа было бы
   дороже, чем пользы (подробно — CLAUDE.md п.96). */
.page--mobile .page-title { font-family: var(--font-sans); font-size: 22px; font-weight: 800; letter-spacing: -.025em; }

/* ------------------------------------------- страница входа («витрина») */
.landing { max-width: 1200px; margin: 0 auto; }
.landing__bar { display: flex; align-items: center; justify-content: space-between; gap: 1rem; padding: .25rem 0 2.5rem; }
.landing__grid { display: grid; grid-template-columns: minmax(0, 3fr) minmax(0, 2fr); gap: 3rem; align-items: start; }
@media (max-width: 1023px) { .landing__grid { grid-template-columns: minmax(0, 1fr); gap: 2rem; } }
.landing__title { margin-top: 1rem; font-family: var(--font-display); font-size: clamp(28px, 4vw, 44px); font-weight: 600; letter-spacing: -.02em; line-height: 1.13; }
.landing__lead { margin-top: 1rem; max-width: 38rem; font-size: 16px; font-weight: 300; line-height: 1.6; letter-spacing: -.02em; color: var(--ink-muted); }
.landing__metric { font-family: var(--font-display); font-size: 22px; font-weight: 600; letter-spacing: -.02em; color: var(--accent-ink); }
.landing__metric-label { margin-top: .3rem; font-size: 12px; line-height: 1.4; color: var(--ink-muted); }
.landing__form { position: sticky; top: 1.5rem; }
@media (max-width: 1023px) { .landing__form { position: static; } }

.feature { display: flex; gap: .8rem; }
.feature__icon {
  flex: none; height: 34px; width: 34px; border-radius: var(--radius-sm);
  background: rgb(var(--rgb-accent) / .12); color: var(--accent-ink);
  display: flex; align-items: center; justify-content: center;
}
.feature__icon svg { height: 18px; width: 18px; }
.feature__title { font-size: 14px; font-weight: 650; }
.feature__text { margin-top: .2rem; font-size: 13px; line-height: 1.55; color: var(--ink-muted); }

/* --------------------------------------- 2D-схема ряда склада (.map)
   Сетка: первая колонка — номер уровня, дальше по колонке на секцию.
   grid-auto-columns вместо repeat(N) — число секций знает только сервер,
   а инлайновый style с числом колонок пришлось бы дублировать в двух
   шаблонах (страница и HTMX-фрагмент).
   Горизонтальная прокрутка на самом блоке: длинный ряд не должен растягивать
   страницу — иначе уезжает вообще вся вёрстка, включая меню. */
.map {
  display: grid;
  /* Число колонок = число секций ряда, поэтому grid-template-columns задаётся
     инлайном в шаблоне: его знает только сервер. Элемент .map существует в
     одном месте (фрагмент _warehouse_map_row.html), так что правило не
     раздваивается. */
  gap: .4rem;
  overflow-x: auto;
  padding-bottom: .5rem;
}
.map__corner, .map__section, .map__level {
  font-size: 12px; color: var(--ink-dim); display: flex; align-items: center;
}
.map__corner, .map__level { grid-column: 1; justify-content: flex-end; padding-right: .5rem; }
.map__level { font-variant-numeric: tabular-nums; font-weight: 600; color: var(--ink-muted); }
.map__section { justify-content: center; }

.map-cell {
  position: relative;
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: .15rem;
  min-height: 54px; padding: .35rem .3rem;
  border: 1px solid var(--line-light); border-radius: var(--radius-sm);
  background: var(--surface-2); color: var(--ink);
  font-size: 12px; text-align: center; text-decoration: none;
}
.map-cell:hover { border-color: var(--accent); }
.map-cell__code { font-weight: 600; letter-spacing: -.01em; }
.map-cell__badge { font-variant-numeric: tabular-nums; color: var(--ink-muted); }

/* Состояния. Цвет + ЗНАК на плитке (см. STATE_BADGES): одним цветом
   различать «неизвестно» и «нет норматива» нельзя — их путают и те, кто
   различает цвета. */
.map-cell--empty { background: var(--surface); color: var(--ink-muted); }
/* Загруженная ячейка залита снизу на высоту процента (--fill ставит шаблон):
   цветом одна четверть от девяти десятых не отличается, а вопрос к схеме
   ровно один — «где ещё есть место». */
.map-cell--loaded {
  border-color: rgb(var(--rgb-success) / .5);
  background:
    linear-gradient(to top,
      rgb(var(--rgb-success) / .38) var(--fill, 0%),
      rgb(var(--rgb-success) / .08) var(--fill, 0%));
}
.map-cell--loaded .map-cell__badge { color: var(--success); }
.map-cell--overfull { background: rgb(var(--rgb-danger) / .22); border-color: rgb(var(--rgb-danger) / .55); }
.map-cell--overfull .map-cell__badge { color: var(--danger); }
.map-cell--unknown { background: rgb(var(--rgb-warning) / .16); border-color: rgb(var(--rgb-warning) / .5); }
.map-cell--unknown .map-cell__badge { color: var(--warning); }
.map-cell--no-capacity { background: rgb(var(--rgb-info) / .14); border-color: rgb(var(--rgb-info) / .45); }
.map-cell--no-capacity .map-cell__badge { color: var(--info); }
/* Пропущенная секция: место занято, но это не ячейка — ни рамки, ни курсора. */
.map-cell--gap { background: transparent; border-style: dashed; border-color: var(--line); }

/* Подсказка с остатком. position: fixed — ЕДИНСТВЕННЫЙ вариант, который не
   обрезается: у .map стоит overflow-x (длинный ряд не должен растягивать
   страницу), а overflow обрезает всё, что внутри, — absolute-подсказка у
   нижнего уровня оказалась бы срезанной пополам. Фиксированный элемент
   позиционируется от окна и обрезку контейнера не замечает; координаты ему
   проставляет place() из x-data сетки (см. шаблон), CSS их знать не может. */
.map-cell__tip {
  position: fixed; z-index: 40;
  transform: translateX(-50%);
  display: none; flex-direction: column; gap: .2rem;
  min-width: 220px; max-width: 320px; padding: .55rem .65rem;
  border: 1px solid var(--line-light); border-radius: var(--radius-sm);
  background: var(--surface-3); box-shadow: 0 12px 28px rgb(0 0 0 / .45);
  font-size: 12px; line-height: 1.45; text-align: left; color: var(--ink-muted);
  white-space: normal; pointer-events: none;
}
.map-cell:hover .map-cell__tip, .map-cell:focus .map-cell__tip { display: flex; }
.map-cell__tip-title { font-weight: 650; color: var(--ink); }
.map-cell__tip-more { color: var(--ink-dim); }

.map-legend { display: flex; flex-wrap: wrap; gap: .9rem; font-size: 12.5px; color: var(--ink-muted); }
.map-legend__item { display: inline-flex; align-items: center; gap: .4rem; }
.map-legend__swatch {
  height: 14px; width: 14px; flex: none; border-radius: 4px;
  border: 1px solid var(--line-light);
}

/* Счётчик у пункта меню («Требует внимания»). Прижат вправо: число — это
   не часть названия, а состояние, и читают его отдельным взглядом. Ноль
   не рисуется вовсе (см. base.html): пустой бейдж тревожит зря. */
.nav__count {
  margin-left: auto; min-width: 20px; padding: 0 .35rem;
  border-radius: var(--radius-pill);
  background: rgb(var(--rgb-warning) / .18); color: var(--warning);
  font-size: 11px; font-weight: 700; line-height: 18px; text-align: center;
}
.nav__link.is-active .nav__count { background: rgb(var(--rgb-warning) / .26); }

/* Активный пункт в меню аккаунта — те же правила, что и в боковом. */
.menu__item.is-active { color: var(--accent-ink); font-weight: 600; }

