/* Токены и компоненты панели. Значения — из docs/designe (спецификация стилей). */

/* Inter: веса 400/500/600 живут в одном variable-файле на сабсет, как в референсе. */
@font-face { font-family: "InterPanel"; font-style: normal; font-weight: 400 600;
  font-display: swap; src: url("../fonts/inter-cyrillic.de93fdc85e3d.woff2") format("woff2");
  unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116; }
@font-face { font-family: "InterPanel"; font-style: normal; font-weight: 400 600;
  font-display: swap; src: url("../fonts/inter-cyrillic-ext.251d36f2b5a6.woff2") format("woff2");
  unicode-range: U+0460-052F, U+1C80-1C8A, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F; }
@font-face { font-family: "InterPanel"; font-style: normal; font-weight: 400 600;
  font-display: swap; src: url("../fonts/inter-latin.260c81a4759b.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
  U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193,
  U+2212, U+2215, U+FEFF, U+FFFD; }
@font-face { font-family: "InterPanel"; font-style: normal; font-weight: 400 600;
  font-display: swap; src: url("../fonts/inter-latin-ext.1ad231aac0a8.woff2") format("woff2");
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF,
  U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020,
  U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF; }

@font-face { font-family: "PTSerifPanel"; font-style: normal; font-weight: 400;
  font-display: swap; src: url("../fonts/ptserif-400-cyrillic.093d7682a4bb.woff2") format("woff2");
  unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116; }
@font-face { font-family: "PTSerifPanel"; font-style: normal; font-weight: 400;
  font-display: swap; src: url("../fonts/ptserif-400-latin.48b1fa647f5c.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+2000-206F, U+20AC, U+2122; }
@font-face { font-family: "PTSerifPanel"; font-style: normal; font-weight: 700;
  font-display: swap; src: url("../fonts/ptserif-700-cyrillic.0c5cf5874944.woff2") format("woff2");
  unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116; }
@font-face { font-family: "PTSerifPanel"; font-style: normal; font-weight: 700;
  font-display: swap; src: url("../fonts/ptserif-700-latin.cd87c62c9c9c.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+2000-206F, U+20AC, U+2122; }

:root, [data-theme="dark"] {
  /* Браузер обязан знать тему: без `color-scheme` нативные контролы —
     невыбранный флажок, скроллбар таблицы, иконка календаря в фильтре дат —
     рисуются светлой палитрой ОС и светятся белым на тёмной странице. */
  color-scheme: dark;
  --bg: #0B0D0C; --sunken: #0E1110; --panel: #121514; --chip: #141716;
  /* Токены таблицы — из палитры референса (его `<style>`), не подобраны:
     `--stripe` — чётная строка (зебра), `--rowline` — линия между строками
     и между числовыми колонками, `--hover2` — наведение на строку,
     `--tile`/`--tile2` — штриховка заглушки изображения. */
  --stripe: #141817; --rowline: #171B1A; --hover2: #191E1C;
  --tile: #1B201E; --tile2: #20261F; --axis: #333A37; --dotted: #23282655;
  --hover: #171B19; --line: #1A1F1D; --track: #1C211F; --border: #1F2422;
  --border2: #232827; --border3: #262B29; --border4: #2A302D; --avatar: #233029;
  --faint: #3A403D; --muted4: #5E6663; --muted3: #6F7773; --muted2: #8A928E;
  --muted: #9AA29E; --text2: #C7CFCB; --text: #E9EDEB;
  --header-bg: rgba(11, 13, 12, 0.86);
  --link: #7FA8E8; --ok: #2FCB74; --err: #F1626F; --warn: #F1A33B;
  --green: #2FCB74; --green-soft: #43DD88;   /* зелёный текст и его наведение */
  /* Синий графиков железа: полоса памяти в референсе мониторинга — #5B9BF8,
     а не зелёная. Отдельный токен, а не `--link`: ссылка и метрика меняются
     независимо. Светлое значение спецификация не задаёт — берём пару
     `--link` светлой темы, чтобы синий на белом оставался читаемым. */
  --info: #5B9BF8;
}
[data-theme="light"] {
  color-scheme: light;
  --bg: #F6F8F4; --sunken: #F1F3EF; --panel: #FFFFFF; --chip: #FFFFFF;
  --stripe: #F7F8F5; --rowline: #EDEFEA; --hover2: #EAEDE7;
  --tile: #EFF1EC; --tile2: #E6E9E2; --axis: #C3C9C0; --dotted: #DBE0D8;
  --hover: #F1F3EF; --line: #EBEEE8; --track: #E7EAE4; --border: #E3E7E0;
  --border2: #DBE0D8; --border3: #D6DBD3; --border4: #CFD5CC; --avatar: #DCF0E4;
  --faint: #B5BCB4; --muted4: #8B938C; --muted3: #7C857E; --muted2: #6C756E;
  --muted: #5F6862; --text2: #3D473F; --text: #16201A;
  --header-bg: rgba(246, 248, 244, 0.88);
  --link: #2C6FD1; --ok: #12904E; --err: #CF3A48; --warn: #A9700A;
  --green: #12904E; --green-soft: #0E7A43;   /* значения из спецификации */
  --info: #2C6FD1;
}
:root {
  /* `--brand` — заливка (кнопки, бейджи); одинакова в обеих темах.
     Зелёный ТЕКСТ — отдельный токен `--green`: по спецификации в светлой
     теме он темнее (#12904E), иначе на белом фоне ярко-зелёные ссылки и
     значения не читаются и расходятся с макетом. Определяется в блоках тем
     ниже — здесь только то, что от темы не зависит. */
  --brand: #2FCB74; --brand-hover: #43DD88; --on-brand: #07130C;
  --ozon: #0A5CE6; --wb: #7B2FD0; --yandex: #FFCC00;
  /* Заливки корзин кошелька — категорийные, как и цвета маркетплейсов выше, и
     точно так же одинаковы в обеих темах. Брать для них `--warn`/`--info`
     нельзя: это токены ТЕКСТА, в светлой теме они темнеют ради контраста на
     белом, и полоса состава уходила бы в оливковый и грязно-синий. Значения —
     из макета (`docs/designe`, `walletSegs`); `--brand` совпал с ним точно и
     переиспользован как есть. */
  --bucket-bonus: #F1A33B; --bucket-purchased: var(--brand); --bucket-earned: #5B9BF8;
  /* 18px — радиус карточки на экране товаров референса (карточка дашборда
     там 16px; ориентир — таблица, она у панели основной экран). */
  --radius-card: 18px; --radius-control: 10px;
  /* Размеры элементов, а не ступени ритма: от них зависят отступы соседей,
     поэтому величина живёт в одном месте, а не переписывается числом в
     каждом правиле.
       --header-h    высота липкой шапки; её же компенсирует `scroll-margin`
                     при переходе по якорю и `top` липкого оглавления
       --field-icon  место под иконку внутри поля ввода: отступ от края
                     (--sp-row) + иконка 16px + зазор до текста (--sp-snug)
       --mp-tile     плитка маркетплейса в шапке карточки магазина
       --fb-row      высота содержимого строки раздела обратной связи: к ней
                     приведены все четыре ячейки (бейдж со звёздами, почта с
                     подписью, две строки текста, миниатюра скриншота), чтобы
                     шаг строк не зависел от того, сколько написал клиент и что
                     он приложил */
  --header-h: 56px; --field-icon: 34px; --mp-tile: 44px; --fb-row: 44px;

  /* ── Шкала кегля ───────────────────────────────────────────────────────
     Одна на всю панель. Значения сняты с карточки магазина — она собрана
     по макету и служит образцом для остальных экранов.

     До этой шкалы в статике жило восемнадцать разных кеглей: 9, 9.5, 10,
     10.5, 11, 11.5, 12, 12.5, 13, 13.5, 14, 14.5, 15, 16, 20, 23, 24, 26,
     28, 32, 34, 40. Соседние ступени отличались на полпикселя и роли не
     несли — одна и та же по смыслу строка на двух экранах оказывалась 13 и
     13.5, подпись — 11 и 11.5, заголовок блока — 14.5, 15 и 21 (h2 без
     явного размера). Разница в полпикселя не читается как иерархия, но
     отчётливо видна как неряшливость, когда экраны стоят рядом.

     Ступеней десять, каждая с ролью. Новый размер в панели не заводится:
     если ни одна ступень не подходит, значит роль дублирует существующую.

       --fs-display  имя записи в шапке экрана (h1, засечный)
       --fs-metric   значение KPI и крупной метрики
       --fs-lead     заголовок диалога, крупное значение внутри блока
       --fs-head     заголовок блока и панели (h2)
       --fs-row      основная строка: подпись поля, имя действия, поле ввода,
                     кнопка, ячейка таблицы раздела
       --fs-body     плотная строка: мета, ячейка встроенного журнала,
                     вторичный текст
       --fs-hint     ремарка, подсказка, примечание под значением
       --fs-eyebrow  надзаголовок экрана и группы, шапка любой таблицы
                     (uppercase + трекинг)
       --fs-micro    подпись НАД значением внутри блока: KPI, поле сводки
                     (uppercase, трекинг .1–.12em)
       --fs-tag      код маркетплейса в цветной плитке

     Вес, трекинг и цвет ступень НЕ задаёт: одна и та же ступень бывает и
     обычной, и полужирной (`--fs-row` — строка данных и подпись кнопки).
     Задаётся только размер. */
  --fs-display: 32px;
  --fs-metric: 23px;
  --fs-lead: 15px;
  --fs-head: 14.5px;
  --fs-row: 13.5px;
  --fs-body: 12.5px;
  --fs-hint: 11.5px;
  --fs-eyebrow: 10.5px;
  --fs-micro: 9.5px;
  --fs-tag: 9px;

  /* ── Шкала отступов ────────────────────────────────────────────────────
     Одна на всю панель, ровно как шкала кегля выше и по той же причине.

     До неё в статике жило тридцать четыре разных величины отступа: padding
     встречался в ста четырёх разных комбинациях, gap — в двадцати девяти.
     Одна и та же по смыслу роль на соседних экранах оказывалась разной:
     поле блока — 16 и 18, шаг строк внутри блока — 9, 10, 11 и 12, подпись
     под значением — 3, 4 и 5, пустое состояние — 30, 34, 44 и 46. Разница
     в пиксель не читается как замысел, но при переходе между окнами видна
     как дрожь: блоки стоят почти одинаково, и именно «почти» бросается в
     глаза.

     Ступеней восемь плюс поле страницы, каждая с ролью. Новая величина в
     панели не заводится: если ни одна ступень не подходит, значит роль
     дублирует существующую.

       --sp-hair     щель внутри составного элемента: деления сегмента,
                     соседние бары, отбивка точки статуса от текста
       --sp-tight    подпись ↔ значение, иконка ↔ метка
       --sp-snug     элементы внутри одной строки
       --sp-item     связанные контролы в ряду, вертикаль плотной строки
       --sp-row      строка ↔ строка, вертикаль шапки блока и ячейки таблицы
       --sp-block    воздух внутри блока: тело карточки, зазор колонок сетки
       --sp-gutter   поле карточки по горизонтали, зазор между блоками
       --sp-section  между секциями экрана, поле диалога
       --sp-page     поле страницы, вертикаль пустого состояния

     Ступень задаёт только величину. Ось (padding или gap, вертикаль или
     горизонталь) и роль элемента остаются за правилом: `--sp-gutter` — и
     поле карточки, и зазор между карточками, это одна и та же величина в
     двух ролях, а не две разные.

     Мимо шкалы допустимы ровно три вещи: `0`, `auto` и пиксельная
     компенсация толщины линии в `margin` (±1px, −.5px) — она привязана к
     `border-width`, а не к ритму, и от ступени не зависит. Отрицательный
     отступ, выносящий элемент за поле контейнера, шкалу не покидает:
     пишется как `calc(var(--sp-*) * -1)`, чтобы не разъехаться с
     `padding`, который он компенсирует. */
  --sp-hair: 2px;
  --sp-tight: 4px;
  --sp-snug: 6px;
  --sp-item: 9px;
  --sp-row: 12px;
  --sp-block: 16px;
  --sp-gutter: 18px;
  --sp-section: 24px;
  --sp-page: 32px;
}
/* Стрелка `select`: нативная рисуется цветами ОС и в тёмной теме выглядит
   чужой. Цвет зашит в data-URI, поэтому вариантов два — по одному на тему. */
:root, [data-theme="dark"] {
  --select-arrow: url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 8' fill='none' stroke='%239AA29E' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M1 1.5 6 6.5 11 1.5'/%3E%3C/svg%3E");
  --date-icon: url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%239AA29E' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round'%3E%3Crect x='3.5' y='5' width='17' height='15' rx='2.5'/%3E%3Cpath d='M3.5 9.5h17M8 3.5v3M16 3.5v3'/%3E%3C/svg%3E");
  --clear-icon: url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 14 14' fill='none' stroke='%236F7773' stroke-width='1.8' stroke-linecap='round'%3E%3Cpath d='M3 3l8 8M11 3l-8 8'/%3E%3C/svg%3E");
  --search-icon: url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='%236F7773' stroke-width='1.6' stroke-linecap='round'%3E%3Ccircle cx='7' cy='7' r='4.6'/%3E%3Cpath d='M10.4 10.4 14 14'/%3E%3C/svg%3E");
}
[data-theme="light"] {
  --select-arrow: url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 8' fill='none' stroke='%235F6862' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M1 1.5 6 6.5 11 1.5'/%3E%3C/svg%3E");
  --date-icon: url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%235F6862' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round'%3E%3Crect x='3.5' y='5' width='17' height='15' rx='2.5'/%3E%3Cpath d='M3.5 9.5h17M8 3.5v3M16 3.5v3'/%3E%3C/svg%3E");
  --clear-icon: url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 14 14' fill='none' stroke='%237C857E' stroke-width='1.8' stroke-linecap='round'%3E%3Cpath d='M3 3l8 8M11 3l-8 8'/%3E%3C/svg%3E");
  --search-icon: url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='%237C857E' stroke-width='1.6' stroke-linecap='round'%3E%3Ccircle cx='7' cy='7' r='4.6'/%3E%3Cpath d='M10.4 10.4 14 14'/%3E%3C/svg%3E");
}

* { box-sizing: border-box; }
html, body { margin: 0; padding: 0; }
body {
  background: var(--bg); color: var(--text);
  font-family: "InterPanel", system-ui, sans-serif;
  font-size: var(--fs-row); letter-spacing: -0.01em; font-variant-numeric: tabular-nums;
  -webkit-font-smoothing: antialiased;
}
/* Засечный шрифт в панели ровно один — имя записи в шапке экрана. Так задано
   в макете, и так это и работало на карточке магазина, которая возвращала
   заголовкам блоков наследование от `body` своим правилом. Правило переехало
   сюда: пока оно жило только в `shop-detail.css`, все остальные экраны
   рисовали заголовки блоков засечным PT Serif размером «по умолчанию» (21px
   у h2) — рядом с таблицами это читалось как чужая гарнитура, а размер
   заголовка блока отличался от экрана к экрану, потому что его никто не
   задавал. */
h1 { font-family: "PTSerifPanel", Georgia, serif; font-weight: 700;
  letter-spacing: -0.02em; line-height: 1.05; margin: 0;
  font-size: var(--fs-display); color: var(--text); }
h2, h3 { font-family: inherit; letter-spacing: normal; line-height: 1.3;
  font-weight: 600; color: var(--text); margin: 0; }
h2 { font-size: var(--fs-head); }
h3 { font-size: var(--fs-row); }
/* Цвет ссылки — `--green` (в светлой теме темнее), наведение — #5FE09B в
   обеих темах: так задано в референсе (`a:hover { color:#5FE09B }`). */
a { color: var(--green); text-decoration: none; }
a:hover { color: #5FE09B; }
:focus-visible { outline: 2px solid var(--brand); outline-offset: 3px; }
.adm-num { font-variant-numeric: tabular-nums; }

.adm-shell { display: flex; min-height: 100vh; }
/* Ширина панели разделов — переменной, а не числом в двух местах: её читают и
   сама панель, и правила свёрнутого состояния ниже. */
:root { --nav-w: 250px; --nav-w-min: 64px; }
.adm-sidebar {
  width: var(--nav-w); flex: 0 0 var(--nav-w); background: var(--sunken);
  border-right: 1px solid var(--line); position: sticky; top: 0; height: 100vh;
  display: flex; flex-direction: column;
  transition: width .2s ease, flex-basis .2s ease;
  /* Выше содержимого экрана (шкала: шапка 40, тост 50, комбо 60): у
     `.adm-content` стоит `animation … both`, а сохранённый трансформ создаёт
     контекст наложения — без своего `z-index` панель оказывается ПОД ним, и
     всплывающий список разделов свёрнутой полосы просвечивает насквозь. */
  z-index: 45;
}
/* Скролл — у списка разделов, а не у всего сайдбара: бренд сверху и карточка
   пользователя снизу остаются на месте, как в референсе. */
.adm-sidebar__nav { flex: 1; overflow-y: auto; padding-bottom: var(--sp-item); }
.adm-sidebar__user { display: flex; align-items: center; gap: var(--sp-item); padding: var(--sp-row) var(--sp-block);
  border-top: 1px solid var(--line); }
.adm-sidebar__who { display: flex; flex-direction: column; min-width: 0; flex: 1; }
.adm-sidebar__name { font-size: var(--fs-body); font-weight: 500; color: var(--text2);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.adm-sidebar__email { font-size: var(--fs-hint); color: var(--muted4);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.adm-avatar { width: 32px; height: 32px; flex: 0 0 32px; border-radius: 50%;
  background: var(--avatar); color: var(--green); display: flex;
  align-items: center; justify-content: center; font-size: var(--fs-body); font-weight: 600; }
/* Бренд — инлайновый SVG (`apps/admin/templates/admin/_logo.svg`), а не картинка: тексту логотипа
   отдан `currentColor`, знаку — `var(--brand)`, поэтому обе темы получаются
   сами собой, без второго файла и без инверсии фильтром. */
.adm-sidebar__brand { display: flex; align-items: center; padding: var(--sp-section) var(--sp-section) var(--sp-gutter);
  color: var(--text); }
.adm-sidebar__brand:hover { color: var(--text); }
.adm-logo { height: 26px; width: auto; display: block; }
.adm-nav-item { display: flex; align-items: center; gap: var(--sp-row); padding: var(--sp-item) var(--sp-row);
  border-radius: 10px; color: var(--muted); transition: background .16s ease; }
.adm-nav-item:hover { background: var(--hover); color: var(--text); }
/* Активный пункт залит акцентом на 10% (значение из референса), а не серым:
   серая заливка на тёмном фоне почти не отличается от hover, и оператор
   теряет, в каком он разделе. */
.adm-nav-item--active { background: rgba(47, 203, 116, .10); color: var(--text);
  font-weight: 600; }
.adm-nav-item--active .adm-ico { color: var(--green); }

/* ── Кластер: пункт верхнего уровня со своим списком разделов ──────────────
   Правила стоят ПОСЛЕ `.adm-nav-item`, и это не косметика порядка: у той
   объявлен шорткат `padding`, который при обратном порядке затирает
   `padding-left` подпункта — список разъезжается влево, к самому краю
   панели. */
.adm-nav-cluster { padding: 0 var(--sp-row); margin-bottom: var(--sp-hair); }
.adm-nav-cluster__row { display: flex; align-items: center; }
.adm-nav-cluster__row .adm-nav-item { flex: 1; min-width: 0; }
.adm-nav-cluster__toggle { flex: 0 0 24px; display: flex; align-items: center;
  justify-content: center; color: var(--muted4); background: none; border: 0;
  cursor: pointer; padding: 0; }
.adm-nav-cluster__toggle:hover { color: var(--text2); }
.adm-nav-cluster__toggle .adm-ico { width: 14px; height: 14px; flex: 0 0 14px;
  transition: transform .16s ease; }
.adm-nav-cluster.is-open .adm-nav-cluster__toggle .adm-ico { transform: rotate(180deg); }
/* Раскрытие едет высотой в ПИКСЕЛЯХ, которую на время перехода проставляет
   `js/nav-clusters.js`. Так вышло не сразу, и обе отвергнутые попытки стоит
   помнить:

   `display: none → block` меняется скачком — в главном движении сайдбара это
   читается как поломка. Приём `grid-template-rows: 0fr → 1fr` (одна строка
   CSS, без скрипта) движение даёт, но негодной формы: доля `fr`
   интерполируется нелинейно, и покадровый замер показал две трети высоты за
   первые 37 мс и ползущий у нуля хвост на оставшиеся 130 мс — на глаз это
   тот же рывок, только с вялым довеском. `height: auto` не интерполируется
   вовсе, отсюда явное значение от скрипта.

   `visibility` обязателен и меняется ВМЕСТЕ с высотой: свёрнутый список
   нулевой высоты остаётся в порядке обхода клавиатурой, и Tab уводил бы
   фокус на ссылки, которых на экране нет. Переключается дискретно в конце
   перехода — на глаз незаметно, зато фокус не проваливается в схлопывающийся
   список. */
/* Кривая, а не `ease`: у той половина высоты уходит за первую четверть
   времени, и старт читается как рывок. Эта трогается мягко и мягко же
   останавливается — движение видно целиком. */
.adm-nav-cluster__items { overflow: hidden; height: 0; visibility: hidden;
  transition: height .24s cubic-bezier(.32, .72, 0, 1), visibility .24s; }
.adm-nav-cluster.is-open .adm-nav-cluster__items { height: auto;
  visibility: visible; }
/* Отступы — внутри обёртки, а не на самом списке: заданные снаружи, они
   остались бы у свёрнутого кластера и давали бы пустую полосу между
   пунктами. */
.adm-nav-cluster.is-open .adm-nav-cluster__inner { padding: var(--sp-hair) 0 var(--sp-item); }
/* Подпункт втянут на левое поле пункта, ширину иконки заголовка и её зазор
   до текста — и своей иконки не имеет: список читается как продолжение
   своего кластера, а не как второй плоский набор разделов. */
.adm-nav-item--sub { padding-left: calc(var(--sp-row) * 2 + var(--sp-gutter));
  font-size: var(--fs-body); }
/* У раскрытого кластера заливку держит активный ПОДПУНКТ, а заголовок
   остаётся только выделенным цветом. Две залитые строки подряд читаются как
   два активных раздела, хотя раздел один. Свёрнутый кластер заливку
   сохраняет: там заголовок — единственный представитель активного
   раздела. */
.adm-nav-cluster.is-open > .adm-nav-cluster__row > .adm-nav-item--active {
  background: transparent; }
/* По той же причине у раскрытого кластера прячется и счётчик: он суммирует
   счётчики подпунктов, а они в этот момент видны строкой ниже — «11» над
   «11» читается как две разные очереди работы. */
.adm-nav-cluster.is-open > .adm-nav-cluster__row .adm-nav-item__badge { display: none; }
.adm-nav-item__icon { display: flex; flex: 0 0 18px; }
.adm-nav-item__label { flex: 1; min-width: 0; overflow: hidden;
  text-overflow: ellipsis; white-space: nowrap; }
/* Счётчик «здесь ждёт работа»: заливка акцентом, как в референсе, — цифра
   должна читаться как повод открыть раздел, а не как справочное число. */
.adm-nav-item__badge { flex: 0 0 auto; background: var(--brand); color: var(--bg);
  border-radius: 20px; padding: var(--sp-hair) var(--sp-snug); font-size: var(--fs-eyebrow); font-weight: 600; }
.adm-ico { width: 18px; height: 18px; flex: 0 0 18px; }

/* ── Свёрнутая панель разделов ─────────────────────────────────────────────
   Состояние — класс на `<html>`, поставленный СЕРВЕРОМ по куке (`base.html`,
   `PanelView.nav_collapsed`): панель SSR, и класс, который вешал бы скрипт,
   давал бы разъезжающуюся панель на каждом переходе.

   Свёрнутая — полоса иконок, а не пустое место: разделы остаются доступны в
   один клик, подпись уходит в подсказку (`title` пункта). Полное скрытие
   отвергнуто владельцем именно поэтому — за навигацией пришлось бы каждый раз
   возвращать панель. */
.is-nav-collapsed .adm-sidebar { width: var(--nav-w-min); flex-basis: var(--nav-w-min); }
/* Текстовые части просто уходят: `display: none` вместо прозрачности — иначе
   они остаются в порядке обхода клавиатурой и в озвучке, где их уже нет
   визуально. */
.is-nav-collapsed .adm-nav-item__label,
.is-nav-collapsed .adm-sidebar__who { display: none; }
.is-nav-collapsed .adm-nav-cluster { padding: 0 var(--sp-item); margin-bottom: var(--sp-snug);
  position: relative; }
/* Аккордеон в полосе 64px развернуть некуда, поэтому подпункты всплывают
   рядом с иконкой. Чистый CSS (`:hover`, `:focus-within`) — скрипт здесь не
   нужен: список не хранит состояния и открывается ровно на время наведения
   или фокуса, то есть работает и с клавиатуры. Шеврон в полосе прячется: он
   переключал бы состояние, которого не видно. */
.is-nav-collapsed .adm-nav-cluster__toggle { display: none; }
/* Список разделов выезжает ЗА границу полосы, а `.adm-sidebar__nav` объявлена
   `overflow-y: auto`, то есть является контейнером прокрутки и обрезает по
   обеим осям (`overflow-x` при этом тоже становится `auto`, даже если про
   него не сказано) — всплывающий список превращался в горизонтальную полосу
   прокрутки у панели. В свёрнутом виде прокрутка не нужна: пунктов шесть, и
   в полосу иконок они помещаются на любом экране. Тот же приём, что у
   `.adm-table-wrap` с залипающей шапкой (`js/sticky-head.js`). */
.is-nav-collapsed .adm-sidebar__nav { overflow: visible; }
/* В полосе список не раскрывается вниз, а выезжает сбоку: высота ему уже не
   нужна (`1fr` открыт всегда), движение отдано прозрачности и сдвигу. */
/* `!important` — против inline-высоты, которую мог оставить скрипт раскрытия:
   в полосе список показывается целиком и высотой не управляется. Единственное
   место в файле, где он оправдан: перебить inline-стиль иначе нечем. */
.is-nav-collapsed .adm-nav-cluster__items,
.is-nav-collapsed .adm-nav-cluster.is-open .adm-nav-cluster__items {
  position: absolute; left: 100%; top: 0; z-index: 30; min-width: 190px;
  height: auto !important; padding: var(--sp-snug); background: var(--sunken);
  border: 1px solid var(--line); border-radius: 12px;
  box-shadow: 0 12px 30px rgba(0, 0, 0, .28);
  opacity: 0; visibility: hidden; transform: translateX(-6px);
  transition: opacity .16s ease, transform .16s ease, visibility .16s; }
.is-nav-collapsed .adm-nav-cluster.is-open .adm-nav-cluster__inner { padding: 0; }
.is-nav-collapsed .adm-nav-cluster:hover .adm-nav-cluster__items,
.is-nav-collapsed .adm-nav-cluster:focus-within .adm-nav-cluster__items {
  opacity: 1; visibility: visible; transform: none; }
/* Во всплывающем списке подпись видна и место есть — втягивание вправо и
   общее правило «в полосе подписей нет» здесь не действуют. */
.is-nav-collapsed .adm-nav-cluster__items .adm-nav-item { justify-content: flex-start;
  padding: var(--sp-snug) var(--sp-row); }
.is-nav-collapsed .adm-nav-cluster__items .adm-nav-item__label { display: block; }
/* Счётчик во всплывающем списке — снова число, а не точка: точку заводили для
   полосы 64px, где цифре негде поместиться, а здесь ширины хватает. */
.is-nav-collapsed .adm-nav-cluster__items .adm-nav-item__badge { position: static;
  min-width: 0; height: auto; padding: var(--sp-hair) var(--sp-snug); border: 0; border-radius: 20px;
  font-size: var(--fs-eyebrow); line-height: normal; }
.is-nav-collapsed .adm-nav-item { justify-content: center; padding: var(--sp-item) 0;
  position: relative; }
/* Счётчик «здесь ждёт работа» в полосе не помещается цифрой, но исчезнуть не
   вправе — это единственное, что зовёт открыть раздел. Остаётся точкой на
   углу иконки: сообщение «тут есть работа» сохраняется, число читается после
   раскрытия. */
.is-nav-collapsed .adm-nav-item__badge { position: absolute; top: 5px; right: 11px;
  min-width: 8px; height: 8px; padding: 0; border-radius: 50%;
  font-size: 0; line-height: 0; border: 2px solid var(--sunken); }
.is-nav-collapsed .adm-sidebar__brand { justify-content: center; padding: var(--sp-gutter) 0 var(--sp-block); }
.is-nav-collapsed .adm-sidebar__user { justify-content: center; flex-direction: column;
  gap: var(--sp-item); padding: var(--sp-row) 0; }

/* В свёрнутой полосе от логотипа остаётся ЗНАК — тот же самый, не отдельная
   картинка и не монограмма-заменитель. Достигается обрезкой: знак и написание
   в `_logo.svg` — соседние `path` одного рисунка, причём знак стоит первым и
   занимает левые 190 единиц из 1074, то есть обёртка шириной с него показывает
   ровно его. Копия контура во втором файле разъехалась бы с оригиналом при
   первой смене логотипа, а `viewBox` из CSS не переопределить — отсюда
   обрезка, а не второй SVG.

   Высота знака НЕ меняется при сворачивании (общие 26px `.adm-logo`): рост
   иконки читается как другой логотип, а не как та же марка в узкой полосе.

   Ширина считается из пропорций рисунка (`viewBox="0 0 1074 237"`): знак
   занимает 3…181 по горизонтали, написание начинается с 231-й единицы. При
   высоте 26px это 20px под знак и 25px до первой буквы — обёртка в 22px
   показывает знак целиком и гарантированно не задевает текст. */
.adm-sidebar__logo { display: block; overflow: hidden; }
.is-nav-collapsed .adm-sidebar__logo { width: 22px; }

/* Переключатель — первый элемент шапки, слева от хлебных крошек: он управляет
   тем, что лежит слева, и стоит на границе с этим же. */
.adm-nav-toggle { flex: 0 0 auto; margin-left: calc(var(--sp-snug) * -1); }

@media (prefers-reduced-motion: reduce) {
  .adm-sidebar { transition: none; }
}

.adm-main { flex: 1; min-width: 0; display: flex; flex-direction: column; }
.adm-header { position: sticky; top: 0; z-index: 40; height: var(--header-h); display: flex;
  align-items: center; gap: var(--sp-block); padding: 0 var(--sp-page); background: var(--header-bg);
  backdrop-filter: blur(12px); border-bottom: 1px solid var(--line); font-size: var(--fs-row);
  color: var(--muted2); }
.adm-crumbs { display: flex; align-items: center; gap: var(--sp-item); min-width: 0; }
.adm-header__sep { color: var(--faint); }
.adm-header__current { color: var(--text); font-weight: 500; }
.adm-header__tools { margin-left: auto; display: flex; align-items: center; gap: var(--sp-item); }

/* ── Переключатели ─────────────────────────────────────────────────────────
   Их в панели ровно два, и оба объявлены здесь, вместе со всеми своими
   модификаторами. Раньше правила жили в трёх файлах (`--icons` — в стилях
   карточки магазина, `--filter` — в блоке мониторинга через полторы тысячи
   строк от компонента, активный сегмент — двумя одинаковыми правилами в
   разных концах файла), и переключатели разъехались: у вкладок записи не
   совпадали с сегментом ни рамка, ни высота пункта, ни цвет покоя, ни цвет
   наведения, ни вес активного — пять несогласованных значений там, где
   различие никто не задумывал.

   Уровня два, и различаются они НАМЕРЕННО — так в макете (`docs/designe`,
   карточка клиента):

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

   Разница между уровнями — только форма активного состояния; кегль, цвета
   покоя и наведения, вес активного у обоих одни и те же. */

/* Вкладки записи. Линия проведена под всей полосой, активная вкладка
   перекрывает её своей — иначе двухпиксельная полоса встаёт НАД линией
   контейнера и читается как две разные черты.

   Линия — ВНУТРЕННЯЯ тень, а не `border-bottom`, и вкладка не носит
   компенсирующий `margin-bottom: -1px` (правка 2026-08-16). Полоса
   прокручивается по горизонтали, а `overflow-x: auto` по спецификации делает
   `auto` и вертикальную ось; отрицательный margin оставлял ровно 1px
   вертикального переполнения (замерено: `scrollHeight` 38 при `clientHeight`
   37), и Windows рисовал справа настоящую вертикальную полосу прокрутки —
   пару стрелок вверх/вниз в 15px шириной, съедавшую край полосы. Тень
   рисуется по padding-box и в поток не входит: линия та же, переполнения
   нет. Высота полосы при этом не изменилась (38px до и после), а нижний край
   активной вкладки совпал с нижним краем полосы — её подчёркивание по-прежнему
   ложится ровно на линию. */
/* Своего нижнего отступа у полосы нет: она стоит в стеке экрана
   (`.adm-content`, `.clientcard`), и зазор до содержимого вкладки задаёт его
   `gap` — как у любого другого соседа. Пока отступ был здесь, он складывался
   с зазором стека, и вкладки отодвигались от своего содержимого дальше, чем
   блоки друг от друга. */
.adm-tabs { display: flex; gap: var(--sp-hair);
  box-shadow: inset 0 -1px 0 var(--border); overflow-x: auto; }
.adm-tabs__item { padding: var(--sp-item) var(--sp-block); border-bottom: 2px solid transparent;
  font-size: var(--fs-row); color: var(--muted2); text-decoration: none;
  white-space: nowrap; transition: color .16s ease, border-color .16s ease; }
.adm-tabs__item:hover { color: var(--text); }
.adm-tabs__item.is-active { color: var(--text); font-weight: 600;
  border-bottom-color: var(--brand); }
/* Экран вкладки грузится без перезагрузки страницы (`tabs.js`) — на время
   запроса полоса тускнеет и не принимает второй клик. Без этого переход
   выглядит как «нажал, ничего не произошло»: браузер своей полосы загрузки
   здесь не рисует. */
.adm-tabs.is-loading { opacity: .55; pointer-events: none; }

/* Сегмент: утопленная дорожка, активный сегмент — приподнятая плашка. */
.adm-seg { display: inline-flex; gap: var(--sp-hair); padding: var(--sp-tight); background: var(--sunken);
  border: 1px solid var(--border2); border-radius: var(--radius-control); }
.adm-seg__item { display: inline-flex; align-items: center; justify-content: center;
  gap: var(--sp-snug); padding: var(--sp-tight) var(--sp-row); border: 0; background: transparent; cursor: pointer;
  border-radius: 8px; color: var(--muted2); font: inherit; font-size: var(--fs-body);
  transition: background .16s ease, color .16s ease; }
.adm-seg__item:hover { color: var(--text2); }
/* Активное состояние во всей панели зовётся `.is-active` — одним именем на
   оба переключателя и на любой их модификатор. Второго имени (`--active`,
   которое ставил переключатель тем) больше нет: под него держалось отдельное
   правило-двойник, и подсветка темы правилась мимо всех остальных. */
.adm-seg__item.is-active { background: var(--border2); color: var(--text); font-weight: 600; }
/* Сегмент кластера стоит отдельной строкой над карточкой экрана, а не в её
   шапке: у списка шапка занята фильтрами и залипает при прокрутке — тащить
   туда ещё и переключатель разделов значило бы держать в липкой строке два
   разных уровня навигации.

   `align-self` обязателен: `.adm-content` — флекс-колонка, и её дети
   растягиваются по ширине независимо от собственного `inline-flex`; без него
   переключатель из двух кнопок растянут во весь экран. */
.adm-seg--cluster { align-self: flex-start; }
/* Подпись экрана под заголовком — с ПОСТОЯННЫМ местом под неё.
   Полоса вкладок кластера стоит сразу под шапкой, а подписи у разделов разной
   длины: без резерва шапка получается разной высоты, и вкладка уезжает
   из-под курсора при переходе между разделами. Резерв — ровно две строки, и
   считается он от ступени кегля, а не подобран числом; текст обязан в две
   строки помещаться (держит `apps/admin/tests/test_cluster_header.py`).
   Ширина ограничена длиной читаемой строки: иначе текст тянется во всю ширину
   монитора, а кнопка «Создать» уезжает под него вторым рядом (`.adm-page` —
   flex с переносом). */
.adm-lede { max-width: 78ch; line-height: 1.5; text-wrap: pretty;
  min-height: calc(var(--fs-row) * 3); }
/* Фильтр журнала мониторинга — у правого края шапки карточки. */
.adm-seg--filter { margin-left: auto; }
/* Иконки вместо подписей (режим графика в шапке финансового блока): у пункта
   с подписью отступы под иконку велики, и дорожка получалась вдвое выше
   заголовка блока, рядом с которым стоит. Отличается только геометрия
   пункта — дорожка, радиусы и состояния общие. */
.adm-seg--icons { gap: var(--sp-tight); background: var(--panel); }
.adm-seg--icons .adm-seg__item { width: 30px; height: 24px; padding: 0; color: var(--muted3); }

/* Кнопка-иконка: выход из панели, шаги пагинации. */
.adm-icon-btn { display: inline-flex; align-items: center; justify-content: center;
  width: 30px; height: 30px; padding: 0; border-radius: 9px; cursor: pointer;
  background: transparent; border: 1px solid transparent; color: var(--muted3);
  transition: all .16s ease; }
.adm-icon-btn:hover { background: var(--hover); color: var(--text); border-color: var(--border2); }
.adm-icon-btn[disabled] { opacity: .4; cursor: default; }
.adm-icon-btn[disabled]:hover { background: transparent; color: var(--muted3);
  border-color: transparent; }
/* Ширина зависит от содержимого экрана, а не задана одна на всю панель.

   Списки и карточки — во всю ширину: у таблицы нет строк текста, ради
   которых существует предел ширины, зато есть до девяти колонок, и каждый
   лишний пиксель убирает горизонтальный скролл. Формы — колонкой
   (`--narrow`): поля разложены сеткой, и на широком мониторе форма настроек
   развернулась бы в шесть колонок, заставляя глаз прыгать через весь экран
   от подписи к значению. Референс здесь не указ: у него предел 1400px на
   двухколоночный дашборд клиента, а не на админские таблицы. */
.adm-content { padding: var(--sp-page) var(--sp-page) calc(var(--sp-page) + var(--sp-section)); width: 100%; margin-inline: auto;
  display: flex; flex-direction: column; gap: var(--sp-gutter);
  /* Своя анимация появления, короче и мягче общей `adm-rise` (та осталась у
     тостов и экрана входа — они появляются поверх готовой страницы, и им
     заметность нужна). Экран раздела перерисовывается на КАЖДОМ переходе,
     включая переключение вкладки кластера: подъём на 10px за 0,35 с читается
     там не как появление, а как прыжок содержимого. */
  animation: adm-content-in .2s ease both; }
@keyframes adm-content-in { from { opacity: 0; transform: translateY(4px); }
  to { opacity: 1; transform: none; } }
.adm-content--narrow { max-width: 1120px; }

/* Шапка экрана: надзаголовок — раздел, заголовок — предмет, справа —
   действия. Без неё h1 висел в пустоте, а «Создать» и «Экспорт» стояли в
   шапке карточки вперемешку с полями фильтра — кнопка создания записи
   выглядела частью формы поиска. */
.adm-page { display: flex; align-items: flex-end; gap: var(--sp-section); flex-wrap: wrap;
  padding: var(--sp-tight) var(--sp-hair) var(--sp-hair); }
.adm-page__eyebrow { font-size: var(--fs-eyebrow); letter-spacing: 0.14em; font-weight: 600;
  text-transform: uppercase; color: var(--muted4); margin-bottom: var(--sp-item); }
.adm-page__meta { margin-top: var(--sp-item); font-size: var(--fs-row); color: var(--muted3); }
.adm-page__actions { margin-left: auto; display: flex; align-items: center;
  gap: var(--sp-item); flex-wrap: wrap; }

/* Пилюли-итоги раздела (`Resource.list_stats`): «4 всего · 1 без
   подтверждения». Число впереди подписи и крупнее её — читают именно цифру, а
   подпись объясняет, что она значит.

   Тон окрашивает ЧИСЛО, а не всю пилюлю: строка тревожных плашек во всю шапку
   спорила бы по весу с заголовком экрана, хотя сообщает справочную величину.
   Нейтральная пилюля (без тона) — тот же фон, что у чипов фильтров: обе
   говорят «столько-то записей такого рода». */
.adm-stats { display: flex; align-items: center; gap: var(--sp-item); flex-wrap: wrap; }
.adm-stat { display: inline-flex; align-items: center; gap: var(--sp-snug); font-size: var(--fs-body);
  color: var(--muted); background: var(--chip); border: 1px solid var(--border2);
  border-radius: 20px; padding: var(--sp-tight) var(--sp-row); }
.adm-stat b { font-weight: 600; color: var(--text); font-variant-numeric: tabular-nums; }
.adm-stat--ok { background: rgba(47, 203, 116, .08); border-color: rgba(47, 203, 116, .25); }
.adm-stat--ok b { color: var(--ok); }
.adm-stat--warn { background: rgba(241, 163, 59, .08); border-color: rgba(241, 163, 59, .25); }
.adm-stat--warn b { color: var(--warn); }
.adm-stat--err { background: rgba(241, 98, 111, .07); border-color: rgba(241, 98, 111, .22); }
.adm-stat--err b { color: var(--err); }
/* «Найдено: N» рядом с пилюлями — без рамки: это не итог раздела, а состояние
   текущего отбора, и пилюлей оно спорило бы с ними за одно и то же место. */
.adm-stats__found { font-size: var(--fs-body); color: var(--muted3); }

.adm-card { background: var(--panel); border: 1px solid var(--border);
  border-radius: var(--radius-card); overflow: hidden; }
/* Карточка с формой: `overflow: hidden` выше существует ради содержимого,
   доходящего до края (таблица списка, полосатый футер), и в форме такого
   нет — зато есть попапы. Раскрытый список своего комбобокса
   (`js/select.js`) — потомок карточки, и скругление обрезало его на пару
   строк: длинный список выглядел коротким, а последний пункт был
   недостижим. Календарь этим не страдает, он живёт в `body`. */
.adm-card--form { overflow: visible; }
/* Карточка списка: `clip` вместо `hidden`. Обрезает он так же (скругления и
   полосатый футер на месте), но, в отличие от `hidden`, НЕ создаёт контейнер
   прокрутки — а именно из-за него `position: sticky` внутри прилипал бы к
   границам карточки, то есть не показывался вовсе. Держит липкий тулбар
   ниже. Проверено на живом экране: с `hidden` прилипание не работает.

   Условие — «есть строка фильтров ИЛИ есть таблица» (второе добавлено
   2026-08-16). Строка фильтров липнет сама, но таблица со своей залипающей
   шапкой стоит и в карточке БЕЗ фильтров — заявки на скидку
   (`shop_discount_requests.html`), — и там шапка не липла вовсе: замер в
   браузере показал, что при `overflow: hidden` на карточке `th` уезжает вверх
   вместе со строками (top −37 вместо 56 = высота шапки панели), причём
   `is-fit` на обёртке эту причину не лечит — карточка сама остаётся
   контейнером прокрутки. Привязывать правило к фильтрам значило описывать
   следствие: липнуть обязано всё, что объявлено `sticky`, а не только то, что
   стоит рядом с отбором. */
.adm-card:has(.adm-card__header--filters),
.adm-card:has(.adm-table-wrap) { overflow: clip; }
.adm-card__header { padding: var(--sp-row) var(--sp-gutter); border-bottom: 1px solid var(--line);
  display: flex; align-items: center; gap: var(--sp-row); font-size: var(--fs-head); font-weight: 600; }
.adm-card__body { padding: var(--sp-block) var(--sp-gutter); }
.adm-card__footer { padding: var(--sp-row) var(--sp-gutter); border-top: 1px solid var(--line);
  display: flex; align-items: center; flex-wrap: wrap; gap: var(--sp-item);
  font-size: var(--fs-body); color: var(--muted3); }
/* Футер формы: подтверждение справа, отмена слева — как в остальных
   диалогах панели. */
.adm-card__footer--actions { justify-content: flex-end; padding: var(--sp-block) var(--sp-gutter); }
.adm-card__footer--actions .adm-btn { padding: var(--sp-row) var(--sp-section); }
.adm-card__footer--actions .adm-btn--ghost { margin-right: auto; }
/* Кнопки действий карточки объекта: своя форма внутри футера, слева от
   ссылок навигации («К списку», «Удалить», «Изменить»). */
.adm-detail-actions { display: inline-flex; gap: var(--sp-item); margin-right: auto; }

/* Скролл у таблицы, а не у страницы: широкий раздел не должен растягивать
   макет и уводить сайдбар за край экрана. */
.adm-table-wrap { width: 100%; overflow-x: auto; }
/* `is-fit` ставит `js/sticky-head.js` тем таблицам, которые в свою обёртку
   ПОМЕЩАЮТСЯ, и только ради залипающей шапки: `overflow-x: auto` делает
   обёртку контейнером прокрутки (по спецификации `overflow-y` при этом тоже
   становится `auto`, даже если про него не сказано), а `sticky` внутри такого
   контейнера отсчитывается от него, а не от окна — и шапка при прокрутке
   страницы уезжает вместе с таблицей. `clip` обрезает так же, но контейнера
   прокрутки не создаёт. Переполненной таблице класс не достаётся: там
   горизонтальная прокрутка важнее залипающей шапки — без неё часть колонок
   становится недостижима. */
.adm-table-wrap.is-fit { overflow-x: clip; overflow-y: visible; }
.adm-table { width: 100%; border-collapse: collapse; }
/* Шапка таблицы — значения сняты с таблицы товаров референса (тот самый
   экран, на который ориентируемся): заливка `--sunken`, высота 36px,
   10.5px/`.1em`/`--muted4`, снизу `--border2`. */
/* Заголовок ПЕРЕНОСИТСЯ, в отличие от ячеек под ним. Он задаёт минимальную
   ширину колонки, а подписи в панели длиннее данных: «КОШЕЛЁК ВЛАДЕЛЬЦА»
   одной строкой держал 171px под колонку, в которой стоит число шириной
   9px, — и это одна из причин, по которым таблица магазинов упиралась в
   собственную горизонтальную прокрутку, стоило раскрыть колонку действий.
   При достатке места ничего не меняется: перенос случается только там, где
   иначе была бы полоса прокрутки. */
/* Шапка залипает при прокрутке: на полусотне строк оператор к середине списка
   уже не помнит, которая колонка «оптимизация», а которая «каталог».

   `top` считает `js/sticky-head.js` и кладёт в `--adm-head-top`: над таблицей
   списка стоит своя залипающая строка фильтров, и её высота непостоянна —
   чипы отбора переносятся на вторую строку. Запасное значение `--header-h`
   (высота шапки панели) верно для таблиц без строки фильтров и для страницы
   без скрипта.

   Линия под шапкой — `box-shadow`, а не `border-bottom`: при
   `border-collapse: collapse` границы принадлежат таблице, а не ячейке, и у
   отлепившейся шапки просто не рисуются. */
.adm-table th { position: sticky; top: var(--adm-head-top, var(--header-h)); z-index: 2;
  padding: var(--sp-row) var(--sp-gutter); text-align: left;
  font-size: var(--fs-eyebrow); letter-spacing: 0.1em; text-transform: uppercase;
  color: var(--muted4); font-weight: 600; background: var(--sunken);
  box-shadow: inset 0 -1px 0 var(--border2); }
.adm-table th a { color: inherit; }
.adm-table th a:hover { color: var(--text2); }
/* Ячейки не переносятся: у таблицы есть собственный горизонтальный скролл,
   и ряд одинаковой высоты читается лучше, чем колонка, где «Процент от
   выручки» рвётся на три строки, а «1 000,00 ₽» — посреди числа. Слишком
   длинное значение обрезается многоточием: целиком оно есть в карточке.

   Строка таблицы референса: `border-bottom: 1px solid var(--rowline)` плюс
   чередование фона (`--panel` / `--stripe`) — там и линия, и зебра, а не
   что-то одно. Наведение — `--hover2` (для строк), не `--hover` (для меню).
   Числовые колонки отделены вертикальной линией того же `--rowline`. */
.adm-table td { padding: var(--sp-gutter); border-bottom: 1px solid var(--rowline);
  white-space: nowrap; max-width: 380px; overflow: hidden; text-overflow: ellipsis; }
.adm-table tbody tr:nth-child(even) { background: var(--stripe); }
.adm-table tbody tr:hover { background: var(--hover2); }
.adm-table tbody tr:last-child td { border-bottom: 0; }
/* Числовые колонки: правое выравнивание и вертикальная линия слева — как в
   таблице товаров референса. Размер и цвет задаются только ячейкам данных:
   заголовки во всей шапке одного размера, иначе колонка с числами получает
   подпись крупнее соседних и читается как заголовок другого уровня. */
.adm-table .is-right { padding-left: var(--sp-block); padding-right: var(--sp-block); }
.adm-table td.is-right { text-align: right; font-size: var(--fs-row); color: var(--muted);
  font-variant-numeric: tabular-nums; }
/* Подписи всей шапки выровнены одинаково — по левому краю: у числовых
   колонок правая подпись отрывается от своего столбца и липнет к соседнему. */
.adm-table th.is-right { text-align: left; }
/* Разделительная линия — у первой колонки блока, а не у каждой числовой:
   в таблице товаров референса отделены «закуп» (начало цен) и «прибыль»
   (начало результата), а «текущая» и «мин. цена» идут слитно со своим
   блоком. По умолчанию линию получает каждая правая колонка — остальные
   разделы панели этим и пользуются (`Column.has_divider`). */
.adm-table .is-divider { border-left: 1px solid var(--rowline); }
.adm-table th.is-divider { border-left-color: var(--line); }

/* Вес числовой колонки: `--strong` — то, ради чего оператор пришёл (текущая
   цена товара), `--dim` — справочная величина рядом (мин. безопасная). Без
   этой разницы блок из четырёх цен читается как одна серая масса. */
.adm-table td.adm-cell--strong { font-size: var(--fs-row); font-weight: 600; color: var(--text); }
.adm-table td.adm-cell--dim { color: var(--muted3); }

/* ── Позиция каталога: миниатюра, название, SKU ───────────────────────────
   Ячейка живёт по своим правилам переноса: общий для таблицы `nowrap`
   оставил бы от названия товара одну строку с многоточием, а опознают
   позицию как раз по названию. */
.adm-table td.adm-cell--product { white-space: normal; min-width: 320px; max-width: 520px;
  overflow: visible; }
.adm-prod { display: flex; align-items: center; gap: var(--sp-row); min-width: 0; }
.adm-prod__thumb { width: 42px; height: 42px; flex: 0 0 42px; border-radius: 10px;
  border: 1px solid var(--border2); overflow: hidden;
  background: repeating-linear-gradient(135deg, var(--tile), var(--tile) 4px,
    var(--tile2) 4px, var(--tile2) 8px); }
/* Картинка перекрывает штриховку, но не заменяет её: оборвалась ссылка на
   CDN маркетплейса — под ней остаётся та же плитка, а не пустое место. */
.adm-prod__thumb img { display: block; width: 100%; height: 100%; object-fit: cover; }
.adm-prod__body { min-width: 0; }
.adm-prod__name { font-size: var(--fs-row); line-height: 1.35; color: var(--text);
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
  overflow: hidden; }
.adm-prod__meta { display: flex; align-items: center; gap: var(--sp-item); margin-top: var(--sp-tight);
  font-size: var(--fs-hint); color: var(--muted3); font-variant-numeric: tabular-nums; }
.adm-prod__meta a { color: var(--link); }
.adm-prod__meta a:hover { color: var(--text2); }
.adm-prod__dot { color: var(--faint); }

/* ── Заявки на скидку ─────────────────────────────────────────────────── */
/* Разметка кабинета продавца, к которой оператор привык: скидка — чипом,
   новая цена крупная и цветная, старая рядом зачёркнутой. Читается сравнением
   двух чисел, а не колонкой одинаковых сумм. */
.adm-table .is-center { text-align: center; padding-left: var(--sp-block); padding-right: var(--sp-block); }
.adm-table th.is-center { text-align: center; }
.adm-discounts__date { font-size: var(--fs-body); color: var(--muted3); white-space: nowrap; }
.adm-discounts__pct { display: inline-block; padding: var(--sp-tight) var(--sp-item); border-radius: 7px;
  background: var(--chip); color: var(--text2); font-size: var(--fs-body); font-weight: 600;
  font-variant-numeric: tabular-nums; white-space: nowrap; }
/* Цена со скидкой — главный столбец строки: зелёная, пока продажа прибыльна,
   и красная, когда уходит ниже минимально безопасной. Цвет здесь не украшение,
   а ответ на вопрос, ради которого экран и открывают. */
.adm-table td.adm-discounts__price { font-size: var(--fs-row); font-weight: 600; color: var(--ok);
  white-space: nowrap; }
.adm-table td.adm-discounts__price.is-loss { color: var(--err); }
/* Порога нет — сравнивать не с чем, и вывода не существует. Янтарь той же
   осью «требует решения», что и в остальной панели: зелёный тут соврал бы про
   проверенную прибыльность, красный — про убыток, которого никто не считал. */
.adm-table td.adm-discounts__price.is-unknown { color: var(--warn); }
/* Прежняя цена — второй строкой той же ячейки, тише и зачёркнутой: она
   объясняет запрошенную, а не спорит с ней за внимание. */
.adm-discounts__was { display: block; margin-top: var(--sp-tight); font-size: var(--fs-hint);
  font-weight: 400; color: var(--muted4); text-decoration: line-through;
  white-space: nowrap; }
/* Порога нет — колонка сама называет причину, тем же янтарём, что и цена. */
.adm-table td.adm-discounts__nomin { color: var(--warn); white-space: nowrap; }

/* Приписка у числа («−15%», «нет цены закупа») — вторым, тихим уровнем: она
   поясняет значение, а не спорит с ним за внимание. */
.adm-cell-note { display: block; margin-top: var(--sp-tight); font-size: var(--fs-hint); color: var(--muted3);
  font-variant-numeric: tabular-nums; }
/* Значение, которое означает убыток (запрошенная цена ниже минимально
   безопасной). Красным именно ячейка, а не строка целиком: остальные её
   колонки в порядке, и подкрашенная строка врала бы про них. */
.adm-table td.adm-cell--below { color: var(--err); font-weight: 600; }

/* Таблица-скелет: показывает, что именно грузится, и держит высоту, чтобы
   экран не прыгал в момент подстановки данных. */
.adm-table--skeleton tbody td { padding-top: var(--sp-block); padding-bottom: var(--sp-block); }
.adm-table--skeleton tbody span { display: block; height: 10px; border-radius: 5px;
  background: var(--sunken); animation: admSkeleton 1.4s ease-in-out infinite; }
.adm-table--skeleton tbody tr:nth-child(even) span { animation-delay: .2s; }
@keyframes admSkeleton { 0%, 100% { opacity: .45; } 50% { opacity: .9; } }

/* ── Прибыль и её дельта одной ячейкой ────────────────────────────────── */
.adm-table td.adm-cell--profit { font-size: inherit; color: inherit; }
.adm-profit__value { font-size: var(--fs-row); font-weight: 600;
  font-variant-numeric: tabular-nums; }
.adm-profit__value.is-up { color: var(--ok); }
.adm-profit__value.is-down { color: var(--err); }
.adm-profit__row { display: flex; align-items: center; justify-content: flex-end;
  gap: var(--sp-item); margin-top: var(--sp-tight); }
/* Шкала фиксированной ширины: доли, отмеренные на разной длине трека, между
   строками не сравнить — а сравнение строк здесь и есть смысл колонки.
   Имя своё, а не общее `.adm-bar` мониторинга: там полоса тянется на всю
   ширину, и одно имя на два поведения обрезало полосы железа до 56px. */
.adm-profit__bar { flex: 0 0 56px; width: 56px; height: 3px; border-radius: 2px;
  background: var(--track); overflow: hidden; }
.adm-profit__bar-fill { display: block; height: 100%; background: var(--green); }
.adm-profit__delta { font-size: var(--fs-hint); font-variant-numeric: tabular-nums; }
.adm-profit__delta.is-up { color: var(--ok); }
.adm-profit__delta.is-down { color: var(--err); }

/* Остаток: число — товар есть, «нет» — торговать нечем. Пилюля, а не голое
   число: нулевой остаток обязан читаться с той же дистанции, что и цена. */
/* ── Составные ячейки раздела магазинов ────────────────────────────────────
   Строка магазина собрана из шести блоков по две линии: крупное значение и
   объясняющая подпись под ним. Общий для таблицы `white-space: nowrap` этим
   ячейкам снимается — иначе вторая линия не переносится, а обрезается. */
.adm-table td.adm-cell--shop, .adm-table td.adm-cell--state,
.adm-table td.adm-cell--catalog, .adm-table td.adm-cell--optimization,
.adm-table td.adm-cell--automation, .adm-table td.adm-cell--wallet,
.adm-table td.adm-cell--client, .adm-table td.adm-cell--access,
.adm-table td.adm-cell--shops, .adm-table td.adm-cell--turnover,
.adm-table td.adm-cell--activity {
  white-space: normal; padding: var(--sp-row) var(--sp-row);
  font-size: inherit; color: inherit; }
/* Заголовки — тем же отступом, что и ячейки под ними: с общими 18px шапка
   разъезжалась с данными на треть колонки. Отступ ужат ради колонки действий
   справа — шесть составных колонок и 404px рядом иначе не помещаются в
   рабочее окно без горизонтальной прокрутки. */
.adm-table th { padding-left: var(--sp-row); padding-right: var(--sp-row); }
.adm-table td.adm-cell--shop { min-width: 240px; max-width: 340px; }
/* Раскрытая колонка действий забирает 360px, и колонка магазина — единственная,
   у которой есть что отдать: `max-width` она занимает всегда и целиком, поэтому
   именно она упирала таблицу в собственную горизонтальную прокрутку на окне
   уже 1650px. Ужимается только на время раскрытия: название обрежется
   многоточием чуть раньше, но многоточие честнее полосы прокрутки, а целиком
   название есть в карточке. */
.adm-list-layout.is-actions-open .adm-table td.adm-cell--shop { max-width: 300px; }

.adm-shop { display: flex; flex-direction: column; gap: var(--sp-tight); min-width: 0; }
.adm-shop__head { display: flex; align-items: center; gap: var(--sp-item); min-width: 0; }
/* Плитка площадки — фирменным цветом: маркетплейс опознаётся раньше, чем
   прочитано название. Метрики мельче, чем у той же плитки на карточке
   (`shopcard__mp`): там она заголовок экрана, здесь — метка в строке. */
.adm-shop__mp { flex: 0 0 auto; display: inline-flex; align-items: center;
  justify-content: center; min-width: 38px; padding: var(--sp-tight) var(--sp-snug); border-radius: 8px;
  font-size: var(--fs-tag); font-weight: 700; letter-spacing: .04em; text-transform: uppercase;
  color: #fff; background: var(--faint); }
.adm-shop__mp--ozon { background: var(--ozon); }
.adm-shop__mp--wb { background: var(--wb); }
/* Жёлтый Яндекса — единственный из трёх, на котором белый текст не читается. */
.adm-shop__mp--yandex { background: var(--yandex); color: #1A1A1A; }
.adm-shop__name { font-size: var(--fs-row); font-weight: 600; color: var(--text);
  min-width: 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.adm-shop__name:hover { color: var(--link); }
.adm-shop__meta { font-size: var(--fs-hint); color: var(--muted3); min-width: 0;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.adm-shop__meta a { color: var(--muted2); }
.adm-shop__meta a:hover { color: var(--link); }
/* Имя владельца — третьей строкой под адресом и мельче его: адрес опознаёт
   запись однозначно, имя помогает её обсудить. Одинаковый кегль сделал бы из
   двух строк лестницу без старшинства. */
.adm-shop__owner { font-size: var(--fs-hint); color: var(--muted4); min-width: 0;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }

.adm-state { display: flex; flex-direction: column; align-items: flex-start; gap: var(--sp-tight); }
.adm-state__meta { font-size: var(--fs-hint); color: var(--muted3); }
/* Приостановленные операции — единственное состояние, при котором магазин
   выглядит рабочим, а фоновые операции по нему не идут; отсюда тревожный тон
   и отдельная строка, а не приписка через точку. */
.adm-state__alert { font-size: var(--fs-hint); font-weight: 500; color: var(--warn); }

/* Число с подписью: каталог, оптимизация, кошелёк. Одна раскладка на три
   колонки — цифры в них сравнивают взглядом по вертикали, и разъехавшиеся
   кегли сломали бы это сравнение. */
.adm-figure { display: flex; flex-direction: column; gap: var(--sp-hair); }
/* Значение не переносится: «не запускалась», разорванное на «не» и
   «запускалась», читается как две разные подписи, а дата — тем более. Ужимать
   при нехватке места положено подписи под значением, не ему самому. */
.adm-figure__value { font-size: var(--fs-row); font-weight: 600; color: var(--text);
  font-variant-numeric: tabular-nums; white-space: nowrap; }
/* Дата в этой же колонке — не число: моноширинные цифры и жирность делали бы
   её похожей на величину, которую можно сравнивать. */
.adm-figure__value.is-plain { font-size: var(--fs-row); font-weight: 500; color: var(--text2); }
.adm-figure__unit { font-size: var(--fs-hint); font-weight: 500; color: var(--muted4); }
.adm-figure__meta { font-size: var(--fs-hint); color: var(--muted4); }
.adm-figure__meta.is-warn { color: var(--warn); font-weight: 500; }
/* Красный у подписи — только про «уже случилось и необратимо» (сообщение
   удалено клиентом), в отличие от янтарного «требует решения» выше. */
.adm-figure__meta.is-err { color: var(--err); font-weight: 500; }
.adm-figure.is-empty .adm-figure__value { color: var(--faint); }
.adm-figure.is-busy .adm-figure__meta { color: var(--link); }
.adm-figure.is-debt .adm-figure__value { color: var(--err); }
.adm-figure.is-debt .adm-figure__meta { color: var(--err); }
.adm-cell--catalog .adm-figure, .adm-cell--optimization .adm-figure,
.adm-cell--wallet .adm-figure, .adm-cell--turnover .adm-figure,
.adm-cell--activity .adm-figure { align-items: flex-end; }
.adm-table td.adm-cell--optimization { text-align: right; }
/* «Ни разу не входил» — не величина: тем же приглушённым тоном, что пустая
   ячейка, иначе отсутствие события выглядит как результат замера. */
.adm-figure__value.is-never { color: var(--muted4); }

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

   Пилюли — СТОЛБИКОМ, по одной в линию. В строку они держали колонку шириной
   в 172px, которые на раскрытой колонке действий отнимались у названия
   магазина и состояния; столбиком колонка ужимается вдвое. Ширина у пилюль
   общая (`align-items: stretch`) — вертикальный список из плашек разной длины
   читается как лестница. */
.adm-auto { display: inline-flex; flex-direction: column; align-items: stretch;
  gap: var(--sp-tight); }
.adm-auto__pill { font-size: var(--fs-hint); font-weight: 600; text-align: center;
  padding: var(--sp-tight) var(--sp-item); border-radius: 8px; border: 1px solid var(--border2);
  background: var(--chip); color: var(--muted3); }
.adm-auto__pill.is-on { color: var(--ok); background: rgba(47, 203, 116, .10);
  border-color: rgba(47, 203, 116, .28); }

/* ── Составные ячейки раздела клиентов ─────────────────────────────────────
   Устройство то же, что у магазинов, поэтому подписи и числа берут готовые
   `.adm-figure`/`.adm-badge`/`.adm-state`: строка клиента и строка магазина
   стоят в панели рядом по смыслу (у клиента — его магазины, у магазина — его
   владелец), и разные кегли в них читались бы как разная важность. */
.adm-table td.adm-cell--client { min-width: 240px; max-width: 340px; }
/* Та же уступка, что у колонки магазина: раскрытая колонка действий забирает
   404px, и отдать их может только самая широкая колонка таблицы. */
.adm-list-layout.is-actions-open .adm-table td.adm-cell--client { max-width: 300px; }

.adm-client { display: flex; align-items: center; gap: var(--sp-row); min-width: 0; }
.adm-client__body { display: flex; flex-direction: column; gap: var(--sp-tight); min-width: 0; }
.adm-client__head { display: flex; align-items: center; gap: var(--sp-item); min-width: 0; }
.adm-client__email { font-size: var(--fs-row); font-weight: 600; color: var(--text);
  min-width: 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.adm-client__email:hover { color: var(--link); }
/* Имя и id — второй строкой и мельче: адрес опознаёт запись однозначно, имя
   помогает её обсудить, id нужен в обращении в поддержку. */
.adm-client__meta { font-size: var(--fs-hint); color: var(--muted3); min-width: 0;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
/* Партнёрка — третьей строкой и ещё мельче: у большинства клиентов её нет
   вовсе, и весом она не должна спорить с адресом и именем. */
.adm-client__ref { font-size: var(--fs-hint); color: var(--muted4); min-width: 0;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.adm-client__ref a { color: var(--muted3); }
.adm-client__ref a:hover { color: var(--link); }
/* Код партнёра — общий `.adm-copy` (моноширинный, с отметкой «скопировано»),
   уменьшенный до кегля своей строки: собственного оформления он не требует,
   а второй копирующий элемент со своим поведением разошёлся бы с первым. */
.adm-client__code { font-size: var(--fs-hint); padding: var(--sp-hair) var(--sp-tight);
  margin: calc(var(--sp-hair) * -1) calc(var(--sp-tight) * -1);
  color: var(--muted3); }
/* Метка сотрудника — рамкой цвета ссылки, а не заливкой бейджа: она сообщает
   не состояние записи (для этого есть колонка «доступ»), а принадлежность к
   команде, и заливка ставила бы её вровень со статусами. */
.adm-client__staff { flex: 0 0 auto; font-size: var(--fs-eyebrow); font-weight: 600;
  letter-spacing: .06em; text-transform: uppercase; color: var(--link);
  border: 1px solid var(--link); border-radius: 6px; padding: var(--sp-hair) var(--sp-snug);
  opacity: .75; }
/* Аватар в строке — тон по состоянию: заблокированный клиент виден раньше,
   чем прочитан его адрес. Зелёный по умолчанию наследуется от `.adm-avatar`. */
.adm-client__ava.is-staff { background: rgba(127, 168, 232, .14); color: var(--link); }
.adm-client__ava.is-blocked { background: rgba(241, 98, 111, .12); color: var(--err); }

/* Магазины клиента: пилюли-ссылки, окрашенные по статусу. Обычная пилюля не
   красится вовсе — цвет здесь означает «требует внимания», и раскрашенный
   список из пяти рабочих магазинов это значение обесценил бы. */
.adm-shops { display: flex; align-items: center; gap: var(--sp-snug); flex-wrap: wrap;
  min-width: 0; }
.adm-shops__pill { font-size: var(--fs-hint); color: var(--text2); background: var(--sunken);
  border: 1px solid var(--border2); border-radius: 7px; padding: var(--sp-tight) var(--sp-item);
  white-space: nowrap; max-width: 100%; overflow: hidden; text-overflow: ellipsis; }
.adm-shops__pill:hover { border-color: var(--ok); color: var(--ok); }
.adm-shops__pill.is-invalid { color: var(--err); border-color: rgba(241, 98, 111, .38);
  background: rgba(241, 98, 111, .07); }
.adm-shops__pill.is-invalid:hover { color: var(--err); border-color: var(--err); }
.adm-shops__pill.is-off { color: var(--muted3); border-style: dashed; }
.adm-shops__pill.is-off:hover { color: var(--muted2); border-color: var(--border4); }
.adm-shops__more { font-size: var(--fs-hint); color: var(--muted3); padding: var(--sp-tight) var(--sp-hair); }
/* «Нет магазинов» — не ошибка, а частое штатное состояние (клиент только
   зарегистрировался), поэтому приглушено, а не окрашено тревожно. */
.adm-shops__empty { font-size: var(--fs-body); color: var(--muted4); }

/* ── Составные ячейки раздела отчётов ──────────────────────────────────────
   Четыре блока по две линии — как у магазинов и клиентов, поэтому числа и
   подписи берут готовые `.adm-figure`/`.adm-badge`, а первая линия отчёта —
   `.adm-shop__mp`/`.adm-shop__name`/`.adm-shop__meta` магазина: отчёт всегда
   про конкретный магазин, и одна и та же площадка обязана выглядеть на
   соседних экранах одинаково.

   Ширину забирает колонка отчёта (`width: 100%` — в таблице это «остаток
   после того, как соседи получили своё»): в ней лежит название магазина,
   единственное значение раздела, длину которого мы не контролируем. Три
   остальные ужимаются по содержимому — числа и бейдж лишней ширины не
   просят, а получив её, разъезжаются с шапкой. */
.adm-table td.adm-cell--report, .adm-table td.adm-cell--outcome,
.adm-table td.adm-cell--decision {
  white-space: normal; padding: var(--sp-row) var(--sp-row);
  font-size: inherit; color: inherit; vertical-align: middle; }
.adm-table td.adm-cell--report { width: 100%; min-width: 240px; max-width: none; }
.adm-table td.adm-cell--outcome, .adm-table td.adm-cell--decision { width: 1%; }
.adm-cell--outcome .adm-figure { align-items: flex-end; }
/* Подписи двух правых колонок не переносятся: `width: 1%` ужимает колонку до
   содержимого, и разрешённый перенос ломал «45 300 → 41 120 ₽» на две строки.
   Строки таблицы становились разной высоты — ровно то, ради чего в разделе
   обратной связи заведено правило «две линии в каждой ячейке». С `nowrap`
   колонка берёт ширину по самой длинной подписи, а строки остаются одного
   роста. */
.adm-cell--outcome .adm-figure__meta,
.adm-cell--decision .adm-figure__meta { white-space: nowrap; }

/* Счётчики позиций — три отдельных столбца одного рода: всего, в минусе, в
   акции. Ужимаются по содержимому и стоят вплотную друг к другу; линию слева
   получает только первый (`--count-lead`) — линиями размечены ГРУППЫ колонок
   («сколько позиций» и «сколько денег»), а не каждое число. */
.adm-table td.adm-cell--count, .adm-table td.adm-cell--count-lead {
  width: 1%; text-align: right; vertical-align: middle;
  padding-left: var(--sp-block); padding-right: var(--sp-block); }
/* Подпись счётчика — в одну строку, вопреки общему для шапки переносу
   (`.adm-table th` без `nowrap`). Перенос там нужен широким колонкам, чтобы
   длинный заголовок не растягивал столбец; здесь он делал ровно обратное —
   «В МИНУСЕ» и «В АКЦИИ» вставали в две строки и растили шапку вдвое над
   столбцом в одно число. */
.adm-table th.adm-cell--count, .adm-table th.adm-cell--count-lead {
  white-space: nowrap; }
.adm-num { font-size: var(--fs-row); font-variant-numeric: tabular-nums;
  color: var(--text2); }
/* Ноль — величина, а не пустота, но и не новость: приглушён, чтобы взгляд в
   колонке цеплялся за ненулевые. Прочерк «величины нет» приглушён сильнее и
   объясняет себя подсказкой (состав отчёта не сохранён / разбивку не считали):
   ноль на его месте был бы утверждением о результате, которого не получали. */
.adm-num.is-zero { color: var(--muted4); }
.adm-num.is-unknown { color: var(--faint); cursor: help; }
/* Красный — «уже сломано»: позиции, которые продаются в минус. Тревожный тон
   только у них; «в акции» рядом остаётся признаком, объясняющим низкую цену. */
.adm-num.is-loss { color: var(--err); font-weight: 600; }

.adm-report { display: flex; flex-direction: column; gap: var(--sp-tight); min-width: 0; }
.adm-report__head { display: flex; align-items: center; gap: var(--sp-item); min-width: 0; }
/* PDF — ссылка внутри второй линии, а не колонка со словом «Открыть PDF» в
   каждой строке. Вес отличает её от соседних значений той же линии (номер и
   дата — тоже мелкие), цвет — обычный для меты: файл открывают не каждый раз,
   и звать в него ярче, чем зовёт сам отчёт, незачем. */
.adm-report__pdf { font-weight: 600; color: var(--muted2); }
.adm-report__pdf:hover { color: var(--link); }
/* Файла нет (сбой генерации, отчёт старше записи файла) — это отклонение, и
   тем же приглушённым тоном, что пустая ячейка: пустое место на его месте
   читалось бы как «здесь ничего и не должно быть». */
.adm-report__nopdf { color: var(--faint); }
/* Разница прибыли — единственное цветное число раздела: знак у неё и есть
   решение («применить или нет»). Те же две роли, что у дельты в таблице
   товаров (`.adm-profit__delta`), поэтому и цвета те же. */
.adm-figure__value.is-up { color: var(--ok); }
.adm-figure__value.is-down { color: var(--err); }

/* ── Составные ячейки журнала действий ─────────────────────────────────────
   Четыре блока по две линии — тот же канон, что у магазинов, клиентов и
   отчётов, поэтому бейдж, аватар с почтой и время берут готовые
   `.adm-badge`/`.adm-state`/`.adm-client`/`.adm-figure`: сотрудник в журнале
   и клиент в своём разделе — одна и та же сущность, и разные кегли в них
   читались бы как разная важность.

   Ширину забирает колонка объекта (`width: 100%` — в таблице это «остаток
   после того, как соседи получили своё»): в ней лежат две строки, длину
   которых мы не контролируем вовсе, — имя объекта и дифф правки. Остальные
   три ужимаются по содержимому: бейдж, почта и дата лишней ширины не просят,
   а получив её, разъезжаются с шапкой. */
.adm-table td.adm-cell--logkind, .adm-table td.adm-cell--logtarget,
.adm-table td.adm-cell--logactor, .adm-table td.adm-cell--logwhen {
  white-space: normal; padding: var(--sp-row) var(--sp-row);
  font-size: inherit; color: inherit; vertical-align: middle; }
.adm-table td.adm-cell--logkind { width: 1%; }
.adm-table td.adm-cell--logtarget { width: 100%; min-width: 280px; max-width: none; }
/* Почте нужен предел, иначе многоточие не сработает и адрес вида
   `very.long.name@company-domain.ru` растянет колонку за счёт объекта. */
.adm-table td.adm-cell--logactor { width: 1%; min-width: 200px; max-width: 280px; }
.adm-table td.adm-cell--logwhen { width: 1%; text-align: right; }
.adm-cell--logwhen .adm-figure { align-items: flex-end; }
/* Подписи служебных колонок не переносятся: `width: 1%` ужимает колонку до
   содержимого, и разрешённый перенос рвал бы «5 минут назад» и «адрес
   неизвестен» на две строки — строки таблицы становились бы разной высоты,
   ровно то, ради чего в разделе обратной связи заведено правило «две линии в
   каждой ячейке». */
.adm-cell--logwhen .adm-figure__meta, .adm-cell--logkind .adm-figure__meta,
.adm-log__ip, .adm-log__nobody { white-space: nowrap; }

.adm-log { display: flex; flex-direction: column; gap: var(--sp-tight); min-width: 0; }
/* Имя объекта — метриками названия магазина и почты клиента
   (`.adm-shop__name`, `.adm-client__email`): роль одна — «то, чем строка
   называется». Свой класс, а не общий с ними, потому что объектом журнала
   бывает не магазин и не клиент, а письмо, тариф или заявка на вывод.

   Обрезка — `line-clamp: 1` с `overflow-wrap: anywhere`, а НЕ `nowrap` с
   многоточием, как у магазина и клиента. У тех ячейка ограничена `max-width`,
   а колонка объекта забирает остаток строки (`max-width: none`), и в ней
   лежат значения, длину которых мы не контролируем: `object_repr` — 255
   символов, дифф склеивает шесть полей. Нерушимая `nowrap`-строка требует под
   себя всю свою длину, таблица уезжает в горизонтальную прокрутку, и колонки
   «сотрудник» и «когда» уходят за край экрана — ровно это и было видно на
   первой живой проверке. Тот же приём и по той же причине, что у текста
   обращения (`.adm-fbmsg__text`); `anywhere`, а не `break-word`, потому что
   только он учитывается при расчёте минимальной ширины блока. */
.adm-log__object { font-size: var(--fs-row); font-weight: 600; color: var(--text);
  min-width: 0; display: -webkit-box; -webkit-line-clamp: 1;
  -webkit-box-orient: vertical; overflow: hidden; overflow-wrap: anywhere; }
a.adm-log__object:hover { color: var(--link); }
/* Объекта у события нет вовсе (действие над выборкой, неудачный вход) — тем же
   приглушённым тоном, что пустая ячейка, и не жирным: это не имя. */
.adm-log__object.is-none { font-weight: 500; color: var(--faint); }
/* Дифф — вторая линия и тон меты: он событие объясняет, а не называет.
   Не переносится: перенос дал бы ячейке третью линию, а с ней разный шаг
   строк. Целиком длинный дифф лежит в подсказке и в карточке записи. */
.adm-log__diff { font-size: var(--fs-hint); color: var(--muted3); min-width: 0;
  display: -webkit-box; -webkit-line-clamp: 1; -webkit-box-orient: vertical;
  overflow: hidden; overflow-wrap: anywhere; }
.adm-log__diff:hover { color: var(--link); }
/* «Подробностей нет» — тоже ссылка в карточку записи (вход в неё у всех строк
   на одном месте), поэтому приглушено слабее, чем отсутствующий объект:
   совсем погасив её, мы спрятали бы от оператора единственную дорогу внутрь. */
.adm-log__diff.is-none { color: var(--muted4); }
/* Адрес — цифры моноширинно: адреса в журнале сравнивают между строками
   («это всё тот же человек?»), а пропорциональные цифры сбивают колонки. */
.adm-log__ip { font-size: var(--fs-hint); color: var(--muted4);
  font-variant-numeric: tabular-nums; }
.adm-log__ip.is-none { color: var(--faint); }
/* Сотрудника нет — строка на месте почты, теми же метриками: это ответ на
   вопрос «кто», а не пустое место. */
.adm-log__nobody { font-size: var(--fs-row); font-weight: 500; color: var(--muted3); }
/* Аватар без сотрудника — без зелени `.adm-avatar`: зелёный кружок означает
   живого человека, и над неудачным входом он обещал бы автора, которого нет. */
.adm-client__ava.is-nobody { background: var(--chip); color: var(--muted4);
  border: 1px solid var(--border3); }
/* Слаг раздела не записан (событие вне разделов) — приглушение, а не прочерк. */
.adm-figure__meta.is-none { color: var(--faint); }

/* ── Составные ячейки дневных финансов ─────────────────────────────────────
   Тот же канон, что у отчётов и журнала: блоки по две линии, готовые
   `.adm-figure` (сумма и подпись) и `.adm-profit` (значение со шкалой). Своих
   примитивов раздел не заводит вовсе — маржа дня и прибыль позиции это одна и
   та же роль «сколько осталось продавцу», и разные метрики у них читались бы
   как разные величины.

   Ширину забирает колонка дня (`width: 100%` — в таблице это «остаток после
   того, как соседи получили своё»): числа лишней ширины не просят, а получив
   её, разъезжаются с шапкой. Здесь это особенно заметно — все четыре правые
   колонки числовые и сравниваются взглядом по вертикали. */
.adm-table td.adm-cell--fday, .adm-table td.adm-cell--fturn,
.adm-table td.adm-cell--fcost, .adm-table td.adm-cell--fnet,
.adm-table td.adm-cell--fstamp {
  white-space: normal; padding: var(--sp-row) var(--sp-row);
  font-size: inherit; color: inherit; vertical-align: middle; }
.adm-table td.adm-cell--fday { width: 100%; min-width: 150px; max-width: none; }
.adm-table td.adm-cell--fturn, .adm-table td.adm-cell--fcost,
.adm-table td.adm-cell--fnet, .adm-table td.adm-cell--fstamp {
  width: 1%; text-align: right; }
.adm-cell--fturn .adm-figure, .adm-cell--fcost .adm-figure,
.adm-cell--fstamp .adm-figure { align-items: flex-end; }
/* Подписи не переносятся: `width: 1%` ужимает колонку до содержимого, и
   разрешённый перенос рвал бы «128 ед · 41 SKU» и «Комиссия 69%» на две
   строки — шаг строк переставал быть ровным (та же ловушка, что в разделе
   отчётов). */
.adm-cell--fday .adm-figure__meta, .adm-cell--fturn .adm-figure__meta,
.adm-cell--fcost .adm-figure__meta, .adm-cell--fstamp .adm-figure__meta {
  white-space: nowrap; }
/* Цифры моноширинно во ВСЕХ четырёх числовых колонках: строки этой таблицы
   читают сверху вниз и сравнивают порядок величин, а пропорциональные цифры
   сбивают разряды между соседними днями. */
.adm-cell--fday .adm-figure__value, .adm-cell--fturn .adm-figure__value,
.adm-cell--fcost .adm-figure__value, .adm-cell--fstamp .adm-figure__value {
  font-variant-numeric: tabular-nums; }
/* Дата — вход в карточку дня, но не зовущая ссылка: в списке кликабельна
   ровно она, и зелёный в каждой строке первой колонки читался бы как
   выделение. Цвет приходит на наведении. */
.adm-cell--fday .adm-figure__value a { color: var(--text); }
.adm-cell--fday .adm-figure__value a:hover { color: var(--link); }
/* Шкала маржи — тот же трек, что у прибыли позиции (`.adm-profit__bar`), но
   заливка красится по знаку: убыток рисуется красной полосой, а не пустой
   шкалой, потому что «потеряли половину оборота» и «потеряли копейку» —
   разные новости. Зелёный `.is-up` совпадает с умолчанием трека и объявлен
   явно, чтобы пара читалась в одном месте. */
.adm-profit__bar-fill.is-up { background: var(--green); }
.adm-profit__bar-fill.is-down { background: var(--err); }
/* Доли нет (нулевой оборот) — тем же приглушённым тоном, что отсутствующее
   значение в других ячейках: «0%» на его месте был бы утверждением о доле,
   которую не от чего считать. */
.adm-profit__delta.is-none { color: var(--faint); }

/* Пара «подпись — значение» на карточке: детализация дня по категориям и
   самые ходовые позиции. Раньше на их месте стоял `str()` от JSON-словаря,
   который оператор читал глазами и переводил в уме. */
.adm-kv { display: flex; align-items: baseline; justify-content: space-between;
  gap: var(--sp-block); padding: var(--sp-hair) 0; }
.adm-kv span { font-size: var(--fs-body); color: var(--muted3); }
.adm-kv b { font-size: var(--fs-body); font-weight: 600; color: var(--text);
  font-variant-numeric: tabular-nums; white-space: nowrap; }

/* ── Составные ячейки раздела обратной связи ───────────────────────────────
   Единственная таблица панели, которую читают, а не сверяют: главная колонка
   здесь текстовая, и ширину под неё отдают все остальные. Служебные ячейки
   (тип, автор, время) намеренно ужаты `max-width` — иначе почта клиента и
   бейдж вида забирают половину строки у того, ради чего экран открыт.

   ШАГ СТРОК ОДИН НА ВСЮ ТАБЛИЦУ. В первой версии высота ячейки складывалась
   из того, что попало: текст занимал одну–три строки, миниатюры вставали ПОД
   ним ещё одной полосой, метка «правился» — третьей, а предмет обращения
   добавлял четвёртую линию в колонку автора. Соседние строки отличались
   вдвое, и список читался как рваный — притом что просматривают его как раз
   сверху вниз, а ровный шаг для этого и нужен.

   Лечится не подпорками, а одним правилом: содержимое КАЖДОЙ ячейки ровно две
   линии и ровно `--fb-row` в высоту; всё, что не поместилось по вертикали,
   уезжает вправо (скриншоты, метка правки) или обрезается многоточием (почта,
   предмет). `height` у ячейки таблицы работает как минимум, поэтому строка,
   где текст короче двух строк, всё равно занимает ту же высоту. */
.adm-table td.adm-cell--fbkind, .adm-table td.adm-cell--fbauthor,
.adm-table td.adm-cell--fbtext, .adm-table td.adm-cell--fbwhen {
  white-space: normal; padding: var(--sp-row) var(--sp-row);
  font-size: inherit; color: inherit; vertical-align: middle;
  height: var(--fb-row); }
.adm-table td.adm-cell--fbkind { width: 1%; }
.adm-table td.adm-cell--fbauthor { min-width: 200px; max-width: 260px; }
/* Текст занимает всё, что осталось: `width: 100%` в таблице — не «во всю
   ширину», а «забери остаток после того, как соседи получили своё». */
.adm-table td.adm-cell--fbtext { width: 100%; min-width: 320px; max-width: none; }
.adm-table td.adm-cell--fbwhen { width: 1%; text-align: right; }
.adm-cell--fbwhen .adm-figure { align-items: flex-end; }
/* Все четыре блока — одной высоты и с содержимым, прижатым к её краям:
   бейдж вида и почта встают на одну линию, звёзды и подпись — на другую.
   Без этого выравнивания колонки живут каждая по себе, и глаз не находит
   горизонталь, по которой читать строку. */
.adm-fbkind, .adm-cell--fbauthor .adm-client, .adm-fbmsg,
.adm-cell--fbwhen .adm-figure { min-height: var(--fb-row); }
.adm-fbkind, .adm-cell--fbauthor .adm-client__body,
.adm-cell--fbwhen .adm-figure { justify-content: center; }

.adm-fbkind { display: flex; flex-direction: column; align-items: flex-start;
  gap: var(--sp-tight); }
/* Звёзды — не число: ряд глифов сравнивается между строками взглядом, а «4» в
   колонке пришлось бы читать. Пустые звёзды всегда приглушены до `--faint` —
   это фон шкалы; цвет получают только заполненные, поэтому вся окраска — один
   класс на элемент.

   Три ступени (решение владельца, 2026-08-16): 0–2 красные, 3 нейтральная,
   4–5 золотые. Золото здесь — привычный цвет заполненной звезды, а не янтарь
   панельной оси «требует решения»: шкала оценки и шкала тревоги — разные
   вещи, и хороший отзыв ничего не требует. Красный ось как раз держит: такой
   отзыв — недовольный клиент. */
.adm-stars { font-size: var(--fs-body); letter-spacing: .08em; color: var(--faint);
  white-space: nowrap; }
.adm-stars b { font-weight: inherit; color: var(--muted2); }
.adm-stars.is-good b { color: var(--warn); }
.adm-stars.is-bad b { color: var(--err); }

/* Номер записи, магазин и отчёт — одной линией и с многоточием: предмет есть
   не у каждого обращения, и отдельной строкой он делал ячейку то двух-, то
   трёхлинейной. */
.adm-cell--fbauthor .adm-client__meta a { color: var(--muted3); }
.adm-cell--fbauthor .adm-client__meta a:hover { color: var(--link); }

/* Текст и то, что к нему приложено, — В ОДНУ ЛИНИЮ, а не столбиком: под
   текстом скриншоты добавляли ячейке ещё 44px, но только тем строкам, где они
   есть. Справа они укладываются в ту же высоту, что и сам текст. */
.adm-fbmsg { display: flex; align-items: center; gap: var(--sp-row); min-width: 0; }
/* Две строки — компромисс между «мысль оборвана» и «строка на пол-экрана».
   Высота фиксирована, а не ограничена сверху: строка с коротким «Спасибо!»
   обязана быть той же высоты, что строка с абзацем.

   Переносы клиента здесь НЕ сохраняются (текст схлопывает `FeedbackMessage
   .render`): на двух видимых строках пустая строка абзаца съедала половину
   превью, и оператор читал заголовок и многоточие вместо сути. Разметка
   клиента цела на карточке — там `pre-line` и полный текст. */
/* `overflow-wrap: anywhere` — не украшение, а защита раскладки: клиент
   вставляет в обращение ссылку на карточку товара или идентификатор заказа,
   и такое «слово» без пробелов не переносится по обычным правилам. Ячейка
   тогда требует под него всю свою длину (замер: 2130px), таблица уезжает в
   горизонтальную прокрутку, и остальные колонки уходят за край экрана.
   Именно `anywhere`, а не `break-word`: только он учитывается при расчёте
   минимальной ширины блока, то есть мешает колонке раздуться, а не просто
   рвёт строку постфактум. */
.adm-fbmsg__text { flex: 1 1 auto; min-width: 0;
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
  overflow: hidden; overflow-wrap: anywhere;
  font-size: var(--fs-row); line-height: 1.5; color: var(--text2); }
.adm-fbmsg__text:hover { color: var(--text); }
/* Правая часть ячейки: миниатюры и метка правки. Не сжимается и не
   переносится — она и есть то, что держит высоту строки постоянной. */
.adm-fbmsg__aside { flex: 0 0 auto; display: flex; align-items: center;
  gap: var(--sp-snug); }
/* «Правился» — свойство текста, а не состояние записи: рамкой, как метка
   сотрудника у клиента, а не заливкой бейджа, чтобы не спорить с бейджем вида
   в соседней колонке. */
.adm-fbmsg__edited { flex: 0 0 auto; font-size: var(--fs-eyebrow); letter-spacing: .06em;
  text-transform: uppercase; color: var(--muted3); border: 1px solid var(--border3);
  border-radius: 6px; padding: var(--sp-hair) var(--sp-snug); white-space: nowrap; }

/* Миниатюры скриншотов в строке списка. Плитка со штриховкой под картинкой,
   как у товара: файл в хранилище может не открыться, и ячейка обязана остаться
   цельной, а не показать сломанный значок.

   Размер — тот же `--fb-row`, что и высота содержимого строки: миниатюра
   ровно вписывается в неё и не может её растянуть. Переносов нет
   (`nowrap`) — перенесённая четвёртая миниатюра создала бы вторую полосу,
   ровно ту, ради избавления от которой они и переехали вправо. */
.adm-shots { display: flex; align-items: center; gap: var(--sp-snug);
  flex-wrap: nowrap; }
.adm-shot { display: block; width: var(--fb-row); height: var(--fb-row);
  flex: 0 0 var(--fb-row);
  border-radius: 8px; border: 1px solid var(--border2); overflow: hidden;
  background: repeating-linear-gradient(135deg, var(--tile), var(--tile) 4px,
    var(--tile2) 4px, var(--tile2) 8px);
  transition: border-color .14s ease, transform .14s ease; }
.adm-shot:hover { border-color: var(--brand); transform: translateY(-1px); }
.adm-shot img { display: block; width: 100%; height: 100%; object-fit: cover; }
.adm-shot__more { font-size: var(--fs-hint); color: var(--muted3); }

/* Просмотрщик скриншота: нативный `<dialog>` в top layer — он не подчиняется
   ни `overflow` предков, ни их `z-index`, поэтому одинаково работает и в
   строке таблицы, и внутри карточки. Ставит его `js/lightbox.js`. */
.adm-lightbox { border: 0; padding: 0; background: transparent; max-width: 96vw;
  max-height: 96vh; overflow: visible; }
.adm-lightbox::backdrop { background: rgba(0, 0, 0, .72); }
.adm-lightbox img { display: block; max-width: 96vw; max-height: 90vh;
  border-radius: var(--radius-control); background: var(--panel); }
/* Крестик поверх угла картинки: у изображения нет своей шапки, а закрывать
   диалог только клавишей Esc оператор не обязан догадываться. */
.adm-lightbox__close { position: absolute; top: var(--sp-row); right: var(--sp-row);
  width: 32px; height: 32px; display: inline-flex; align-items: center;
  justify-content: center; border: 0; border-radius: 50%; cursor: pointer;
  font: inherit; font-size: var(--fs-head); line-height: 1;
  color: var(--text); background: var(--panel); opacity: .85; }
.adm-lightbox__close:hover { opacity: 1; }

.adm-pill { display: inline-block; font-size: var(--fs-body); font-weight: 600;
  font-variant-numeric: tabular-nums; border-radius: 7px; padding: var(--sp-tight) var(--sp-item); }
.adm-pill--ok { color: var(--text); background: rgba(47, 203, 116, .10); }
.adm-pill--empty { color: var(--muted3); background: var(--hover); }

.adm-check { font-size: var(--fs-row); color: var(--ok); }
.adm-check.is-off { color: var(--faint); }

/* Значение, копируемое щелчком (`js/copy.js`): выглядит как техническая
   строка, а не как кнопка, — жать по ней должно быть естественно. */
.adm-copy { font: inherit; font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: var(--fs-body); color: var(--text2); background: transparent; border: 0;
  padding: var(--sp-tight) var(--sp-snug);
  margin: calc(var(--sp-tight) * -1) calc(var(--sp-snug) * -1); border-radius: 7px; cursor: pointer;
  max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  transition: background .14s ease, color .14s ease; }
.adm-copy:hover { background: var(--sunken); color: var(--text); }
.adm-copy.is-copied { background: rgba(47, 203, 116, .12); color: var(--green); }
/* Отметка об успехе: подпись появляется на месте, не сдвигая строку. */
.adm-copy.is-copied::after { content: " скопировано"; font-family: inherit;
  font-size: var(--fs-eyebrow); letter-spacing: .04em; }

.adm-table__empty td { padding: var(--sp-page) var(--sp-gutter); text-align: center; }
.adm-empty__title { display: block; font-size: var(--fs-head); color: var(--text2);
  margin-bottom: var(--sp-tight); }
.adm-empty__hint { display: block; font-size: var(--fs-body); color: var(--muted3); }
/* Карточка объекта — сетка пар «подпись/значение». Колонок столько, сколько
   влезает: на 1400px это три, и карточка магазина из 17 полей читается
   экраном, а не свитком. */
.adm-fields { display: grid; gap: var(--sp-hair) var(--sp-section); margin: 0;
  grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); }
.adm-fields__item { padding: var(--sp-row) 0; border-bottom: 1px solid var(--line);
  min-width: 0; }
.adm-fields__label { font-size: var(--fs-eyebrow); letter-spacing: 0.1em; text-transform: uppercase;
  color: var(--muted4); font-weight: 600; margin-bottom: var(--sp-snug); }
.adm-fields__value { margin: 0; font-size: var(--fs-row); color: var(--text);
  overflow-wrap: anywhere; }

.adm-btn { display: inline-flex; align-items: center; gap: var(--sp-item); font: inherit;
  font-size: var(--fs-row); font-weight: 500; padding: var(--sp-item) var(--sp-block); white-space: nowrap;
  border-radius: var(--radius-control); cursor: pointer; transition: all .16s ease; }
.adm-btn--primary { background: var(--brand); border: none; color: var(--on-brand);
  font-weight: 600; box-shadow: 0 6px 18px -8px rgba(47, 203, 116, .8); }
/* Цвет текста повторяется в `:hover` у каждого варианта намеренно: кнопкой
   бывает и ссылка (`<a class="adm-btn">`), а общий `a:hover` выше красит любую
   ссылку в зелёный. Без этого «Удалить» зеленела под курсором, а зелёная
   основная кнопка теряла бы контраст текста на своём же фоне. */
.adm-btn--primary:hover { background: var(--brand-hover); color: var(--on-brand);
  transform: translateY(-1px); }
.adm-btn--ghost { background: var(--chip); border: 1px solid var(--border4); color: var(--text2); }
.adm-btn--ghost:hover { color: var(--text); border-color: var(--brand); }
.adm-btn--danger { background: transparent; border: 1px solid rgba(241, 98, 111, .35);
  color: var(--err); }
.adm-btn--danger:hover { background: rgba(241, 98, 111, .09); color: var(--err);
  border-color: rgba(241, 98, 111, .55); }
.adm-btn--block { width: 100%; justify-content: center; padding: var(--sp-row) var(--sp-block); }
/* Кнопка, ждущая ответа сервера: гаснет и не принимает повторный клик —
   иначе «Показать ещё» успевает уйти дважды и порция дублируется. */
.adm-btn.is-busy { opacity: .5; pointer-events: none; }
/* Мелкий размер (значения референса) — для действий внутри строк и футеров,
   где обычная кнопка спорит по весу с самими данными. */
.adm-btn--sm { padding: var(--sp-snug) var(--sp-row); font-size: var(--fs-body); border-radius: 9px; }

/* `nowrap`: у составных ячеек снят общий для таблицы запрет переносов, и
   пилюля статуса рвалась на две строки («Готов к» / «работе») — форма пилюли
   при этом разваливается, а строка таблицы вырастает в высоте. */
.adm-badge { display: inline-flex; align-items: center; gap: var(--sp-snug); font-size: var(--fs-body);
  font-weight: 500; border-radius: 20px; padding: var(--sp-tight) var(--sp-item); white-space: nowrap; }
.adm-badge--ok { color: var(--ok); background: rgba(47, 203, 116, .10);
  border: 1px solid rgba(47, 203, 116, .25); }
.adm-badge--warn { color: var(--warn); background: rgba(241, 163, 59, .09);
  border: 1px solid rgba(241, 163, 59, .25); }
.adm-badge--err { color: var(--err); background: rgba(241, 98, 111, .09);
  border: 1px solid rgba(241, 98, 111, .28); }
.adm-badge--info { color: var(--link); background: rgba(127, 168, 232, .10);
  border: 1px solid rgba(127, 168, 232, .25); }
.adm-badge--neutral { color: var(--muted2); background: var(--chip);
  border: 1px solid var(--border2); }

/* Тумблер: нативный флажок, перерисованный в переключатель. Настоящий
   `<input type="checkbox">` остаётся источником истины — на нём слушают
   `change` и карточка магазина (`shop-detail.js`), и экран создания
   (`shop-form.js`), и он же уходит в POST обычной формой.
   Селектор двойной: правила общие для нового `.adm-switch` и для тумблеров
   блока автоматизации карточки, чей класс появился раньше. Держать их двумя
   копиями значило бы развести оформление одного и того же элемента при
   первой же правке палитры. */
.adm-switch input[type="checkbox"],
.shopcard__toggle input[type="checkbox"] { appearance: none; -webkit-appearance: none;
  flex: 0 0 34px; width: 34px; height: 20px; margin: var(--sp-hair) 0 0; border-radius: 20px;
  background: var(--track); border: 1px solid var(--border2); position: relative;
  cursor: pointer; transition: background .18s ease, border-color .18s ease; }
.adm-switch input[type="checkbox"]::after,
.shopcard__toggle input[type="checkbox"]::after { content: ""; position: absolute;
  top: 2px; left: 2px; width: 14px; height: 14px; border-radius: 50%;
  background: var(--muted3); transition: left .18s ease, background .18s ease; }
.adm-switch input[type="checkbox"]:checked,
.shopcard__toggle input[type="checkbox"]:checked { background: var(--brand);
  border-color: var(--brand); }
.adm-switch input[type="checkbox"]:checked::after,
.shopcard__toggle input[type="checkbox"]:checked::after { left: 16px; background: var(--on-brand); }
.adm-switch input[type="checkbox"]:focus-visible,
.shopcard__toggle input[type="checkbox"]:focus-visible { outline: 2px solid var(--brand);
  outline-offset: 2px; }

/* Метрики обзора — та же карточка, что у плиток разделов (радиус как у
   `--radius-card`, без тени: референс её не имеет). */
.adm-metrics { display: grid; gap: var(--sp-gutter);
  grid-template-columns: repeat(auto-fill, minmax(190px, 1fr)); }
.adm-metric { padding: var(--sp-block); background: var(--panel);
  border: 1px solid var(--border); border-radius: var(--radius-card); }
.adm-metric.is-alert { border-color: var(--err); }
/* Подпись, значение и примечание плитки — те же, что у KPI карточки магазина
   и мониторинга (`.shopcard__kpi-*`, `.adm-kpi__*`). Три ряда плиток в панели
   отличались трекингом, весом и кеглем примечания, хотя показывают одно и то
   же: подпись, число, пояснение. */
.adm-metric__label { font-size: var(--fs-micro); letter-spacing: .12em;
  font-weight: 600; text-transform: uppercase; color: var(--muted4); }
.adm-metric__value { margin-top: var(--sp-snug); font-size: var(--fs-metric); font-weight: 600;
  letter-spacing: -.03em; line-height: 1; color: var(--text); }
.adm-metric__hint { margin-top: var(--sp-snug); font-size: var(--fs-hint); color: var(--muted3); }
/* Блок «Сейчас в работе» лежит внутри сетки метрик (общий корень поллинга),
   но сам не плитка метрики — растягивается на всю ширину ряда, а не сжимается
   в одну колонку auto-fill. */
.adm-metrics > [data-live-metric="jobs_html"] { grid-column: 1 / -1; }

/* Плитки разделов на обзоре — по образцу «Быстрых действий» референса:
   иконка, подпись с надписью группы и число справа. */
.adm-tiles-group { display: flex; flex-direction: column; gap: var(--sp-item); }
.adm-tiles { display: grid; gap: var(--sp-gutter);
  grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); }
.adm-tile { display: flex; align-items: center; gap: var(--sp-row); padding: var(--sp-block);
  background: var(--panel); border: 1px solid var(--border);
  border-radius: var(--radius-card); color: var(--text);
  transition: border-color .16s ease, background .16s ease; }
.adm-tile:hover { border-color: var(--border4); background: var(--hover); color: var(--text); }
.adm-tile__icon { display: flex; align-items: center; justify-content: center;
  width: 36px; height: 36px; flex: 0 0 36px; border-radius: 11px;
  background: var(--avatar); color: var(--green); }
.adm-tile__body { display: flex; flex-direction: column; gap: var(--sp-tight); min-width: 0; flex: 1; }
.adm-tile__label { font-size: var(--fs-row); font-weight: 500; overflow: hidden;
  text-overflow: ellipsis; white-space: nowrap; }
/* Заголовок группы плиток повторяет заголовок кластера в сайдбаре — обзор и
   меню обязаны читаться как один список разделов. Подписи группы внутри
   плитки (`.adm-tile__group`) больше нет: она была одна на весь ряд. */
/* Отступов заголовок не носит: от предыдущей группы его отделяет `gap`
   стека экрана, от своих плиток — `gap` обёртки `.adm-tiles-group`. Пока
   отступы стояли здесь, они складывались с зазором стека, и заголовок
   оказывался дальше от своей группы, чем группы друг от друга. */
.adm-tiles__title { font-size: var(--fs-eyebrow); letter-spacing: .14em; text-transform: uppercase;
  color: var(--muted4); font-weight: 600; margin: 0; }
.adm-tile__nums { display: flex; flex-direction: column; align-items: flex-end; gap: var(--sp-tight); }
.adm-tile__total { font-size: var(--fs-metric); font-weight: 600; }
.adm-tile__badge { font-size: var(--fs-eyebrow); font-weight: 600; color: var(--green);
  background: rgba(47, 203, 116, .10); border: 1px solid rgba(47, 203, 116, .25);
  border-radius: 20px; padding: var(--sp-hair) var(--sp-item); white-space: nowrap; }

.adm-toast-stack { display: flex; flex-direction: column; gap: var(--sp-item); }
.adm-toast { padding: var(--sp-row) var(--sp-block); border-radius: var(--radius-control); font-size: var(--fs-row);
  font-weight: 500; background: var(--chip); border: 1px solid var(--border4);
  color: var(--text2); animation: adm-rise .35s ease both; }
.adm-toast--success { color: var(--ok); background: rgba(47, 203, 116, .10);
  border-color: rgba(47, 203, 116, .25); }
.adm-toast--warning { color: var(--warn); background: rgba(241, 163, 59, .09);
  border-color: rgba(241, 163, 59, .25); }
.adm-toast--error { color: var(--err); background: rgba(241, 98, 111, .09);
  border-color: rgba(241, 98, 111, .28); }
.adm-toast--info, .adm-toast--debug { color: var(--link); background: rgba(127, 168, 232, .10);
  border-color: rgba(127, 168, 232, .25); }

/* Индикатор потери связи поллинга (`js/live.js`): сессия истекла, ручка
   live/metrics стала отвечать не-JSON'ом (страница входа, 403, 404, 500) —
   один общий баннер в углу на все опрашиваемые корни разом, а не тост на
   каждый: причина обычно одна на всех (общая cookie сессии), а тост исчез
   бы сам по себе, хотя проблема ещё не решена. Скрыт по умолчанию —
   JS переключает `.is-visible`, пока опрос не начнёт снова получать данные.
   Не модальный: не перехватывает фокус и не блокирует работу со страницей. */
.adm-live-status { display: none; position: fixed; right: 20px; bottom: 20px; z-index: 50;
  max-width: 320px; padding: var(--sp-row) var(--sp-block); border-radius: var(--radius-control);
  font-size: var(--fs-body); font-weight: 500; color: var(--warn);
  background: rgba(241, 163, 59, .12); border: 1px solid rgba(241, 163, 59, .3);
  box-shadow: 0 10px 24px -12px rgba(0, 0, 0, .45); }
.adm-live-status.is-visible { display: block; }

/* Предупреждение о последствиях на экране подтверждения опасного действия
   (применение цен отчёта, откат к бэкапу — задача 12). Та же цветовая пара,
   что у `.adm-toast--error`/`.adm-badge--err`, но своя оболочка: это абзац внутри
   карточки формы, не всплывающий тост. */
.adm-alert { padding: var(--sp-row) var(--sp-block); border-radius: var(--radius-control); font-size: var(--fs-row);
  font-weight: 500; margin: 0 0 var(--sp-block); }
.adm-alert--danger { color: var(--err); background: rgba(241, 98, 111, .09);
  border: 1px solid rgba(241, 98, 111, .28); }
/* Неполный список (например, заявки на скидку урезаны лимитом страницы
   маркетплейса) — тревожнее обычного текста, но не отказ, поэтому цвет
   `--warn`, а не `--err`. */
.adm-alert--warn { color: var(--warn); background: rgba(241, 163, 59, .09);
  border: 1px solid rgba(241, 163, 59, .28); }

/* Экран подтверждения действия: предупреждение → на что действие ляжет →
   параметры. Расстояния держит сетка, а не отступы отдельных блоков —
   у части экранов нет предупреждения, у части нет формы, и собственные
   margin'ы давали разный ритм в зависимости от того, что именно
   отсутствует. */
.adm-confirm { display: grid; gap: var(--sp-section); }
.adm-confirm > .adm-alert, .adm-confirm > .adm-form-error { margin: 0; }
.adm-confirm__label { font-size: var(--fs-hint); font-weight: 600; letter-spacing: .1em;
  text-transform: uppercase; color: var(--muted3); margin-bottom: var(--sp-item); }
/* Имена записей: тот же чип, что и в фильтрах, но статичный — это перечень
   того, что затронет действие, а не элемент управления, и подсветка при
   наведении обещала бы клик. */
.adm-confirm .adm-chip { cursor: default; max-width: 100%; }
.adm-confirm .adm-chip:hover { color: var(--muted); border-color: var(--border2); }
.adm-confirm__note { margin: var(--sp-item) 0 0; }

.adm-chip { display: inline-flex; align-items: center; gap: var(--sp-snug); font-size: var(--fs-body);
  background: var(--sunken); border: 1px solid var(--border2); color: var(--muted);
  border-radius: 20px; padding: var(--sp-snug) var(--sp-row); }
.adm-chip--active { background: var(--border2); color: var(--text); font-weight: 500; }

/* Тулбар списка — значения из референса: фон карточки (не утопленный),
   padding 16/20, промежуток 12, снизу тонкая `--line`. Утопленный фон,
   который я ставил раньше, делал из тулбара отдельную полосу — в референсе
   он часть карточки. */
/* Тулбар прилипает к верху под шапкой панели (`.adm-header`, `--header-h`, z-index
   40): в нём стоят и отметки-фильтры, и кнопка вызова панели действий, а на
   списке в сотни строк оператору нужно и то и другое, не возвращаясь наверх.
   `z-index` ниже шапки — прокручиваемый тулбар обязан уходить ПОД неё, а не
   поверх крошек. */
.adm-card__header--filters { background: var(--panel); padding: var(--sp-row) var(--sp-gutter);
  gap: var(--sp-row); border-bottom: 1px solid var(--line);
  position: sticky; top: var(--header-h); z-index: 6; }
.adm-filters__reset { font-size: var(--fs-body); color: var(--muted3); align-self: center; }
.adm-filters__reset:hover { color: var(--green); }

/* Автоприменение отбора (`js/filters.js`): скрипт помечает форму `data-auto`,
   и кнопка «Применить» уходит — нажимать её больше не надо. Прячет её CSS, а
   не шаблон: без JS кнопка остаётся единственным способом отправить выбор
   селекта. */
.adm-filters[data-auto] .adm-filters__apply { display: none; }
/* Обёртка зоны чипов — только граница обновления, не элемент раскладки:
   `display: contents` оставляет сами чипы прямыми участниками флекса тулбара,
   иначе они схлопнулись бы в один элемент и перестали переноситься по одному. */
.adm-filters__flags { display: contents; }
/* Пока сервер не ответил, таблица гаснет: без кнопки это единственный отклик
   на то, что отбор принят. */
.adm-card.is-busy .adm-table-wrap { opacity: .45; pointer-events: none;
  transition: opacity .12s ease; }

/* Чип-переключатель отбора (`Flag`): щелчок применяет его переходом по
   адресу, а не отправкой формы, поэтому он выглядит кнопкой, а не полем.
   Значения — из панели над таблицей товаров референса. */
.adm-chip { display: inline-flex; align-items: center; gap: var(--sp-snug); align-self: center;
  font-size: var(--fs-body); border-radius: 20px; padding: var(--sp-snug) var(--sp-row);
  background: var(--sunken); border: 1px solid var(--border2); color: var(--muted);
  white-space: nowrap; transition: color .14s ease, border-color .14s ease; }
.adm-chip b { font-weight: 600; }
.adm-chip:hover { color: var(--text2); border-color: var(--border4); }
/* Тревожный тон — для отборов, за которыми стоит потеря денег («ниже мин.
   безопасной»). Тона совпадают с бейджами: один и тот же смысл в панели
   обязан выглядеть одинаково. */
.adm-chip--err { color: var(--err); background: rgba(241, 98, 111, .09);
  border-color: rgba(241, 98, 111, .28); }
.adm-chip--err:hover { color: var(--err); border-color: rgba(241, 98, 111, .5); }
.adm-chip--warn { color: var(--warn); background: rgba(241, 163, 59, .09);
  border-color: rgba(241, 163, 59, .25); }
.adm-chip--warn:hover { color: var(--warn); border-color: rgba(241, 163, 59, .5); }
/* Включённый чип держит рамку бренда независимо от тона: оператор должен
   видеть, что список сужен, даже не читая подписи. */
.adm-chip.is-on { color: var(--text); border-color: var(--brand);
  background: rgba(47, 203, 116, .10); }

/* Пагинация: шаги кнопками-иконками, положение — плашкой между ними. */
.adm-pager { margin-left: auto; display: flex; align-items: center; gap: var(--sp-snug); }
.adm-pager__state { border: 1px solid var(--border2); border-radius: 8px;
  padding: var(--sp-tight) var(--sp-row); font-size: var(--fs-body); color: var(--muted3); }

/* Полоса действий карточки объекта (общий `detail.html`): там действие
   применяется к ОДНОЙ открытой записи, тарифов и выбора нет, и полосы кнопок
   достаточно. Список ушёл от неё к блоку строк ниже — у него другая задача. */
.adm-card__footer--bulk { background: var(--sunken); }
.adm-bulk__hint { color: var(--muted4); }

/* ── Блок действий под списком ─────────────────────────────────────────────
   Тот же язык, что в блоке «Действия» карточки магазина (`shop-detail.css`):
   строка = подпись, тариф, кнопка. Классы свои, а не общие с карточкой: там
   строка описывает ОДИН магазин и знает про него всё (хватает ли токенов,
   умеет ли маркетплейс), здесь — произвольную выборку, о которой на момент
   отрисовки не известно ничего. Общий CSS заставил бы один из двух экранов
   притворяться другим. */

/* Экран списка — таблица и колонка действий справа, по умолчанию свёрнутая в
   узкий корешок (44px) и раскрываемая нажатием либо первой же отметкой строки.
   Колонка прилипает к верху под шапкой панели (`--header-h`): список прокручивают
   долго, а действия должны оставаться на виду.

   Ширину меняет сама сетка, а не смещение колонки поверх таблицы: место под
   раскрытую колонку отбирается у таблицы, поэтому ничего не перекрывается и
   экран не гаснет затемнением. Три отвергнутые компоновки до этой — блок под
   таблицей, полоса у низа окна и выезжающая ПОВЕРХ таблицы панель с
   затемнением (см. panel-roadmap): первые две недостижимы или закрывают
   строки, третья прячет то, к чему применяется.

   Переход по `grid-template-columns`: обе раскладки — пара дорожек одного
   вида, и браузер интерполирует их. Там, где не умеет, колонка просто
   меняет ширину скачком — поведение сохраняется. */
.adm-list-layout { display: grid; gap: var(--sp-gutter); align-items: start;
  grid-template-columns: minmax(0, 1fr) 44px;
  transition: grid-template-columns .22s ease; }
.adm-list-layout.is-actions-open { grid-template-columns: minmax(0, 1fr) 404px; }
.adm-list-layout--solo,
.adm-list-layout--solo.is-actions-open { grid-template-columns: minmax(0, 1fr); }
/* `overflow: hidden` — не украшение, а лекарство от горизонтальной прокрутки
   ВСЕЙ страницы на первых кадрах раскрытия. Дорожка сетки едет от 44px к
   404px, а тело панели уже отрисовано целиком и ниже своей min-content ширины
   не сжимается: пару кадров кнопка «Выполнить» торчит за правым краем окна, и
   у страницы успевает мигнуть горизонтальная полоса. Обрезка по дорожке
   превращает это в честный выезд из-за края. */
.adm-actions { position: sticky; top: 72px; min-width: 0; overflow: hidden; }

/* Корешок свёрнутой колонки. Он же индикатор выбора: пока ничего не отмечено —
   спокойная серая полоса, с отметками получает брендовую рамку и счётчик.
   Без счётчика свёрнутая колонка прятала бы единственную цифру, которую
   оператор обязан видеть в момент запуска. */
.adm-actions__rail { display: flex; flex-direction: column; align-items: center;
  gap: var(--sp-row); width: 100%; padding: var(--sp-row) 0 var(--sp-block); font: inherit;
  color: var(--muted3); background: var(--panel); border: 1px solid var(--border);
  border-radius: 14px; cursor: pointer;
  transition: background .16s ease, border-color .16s ease, color .16s ease; }
.adm-actions__rail:hover { background: var(--hover); border-color: var(--border4);
  color: var(--text2); }
.adm-actions__rail.is-on { border-color: rgba(47, 203, 116, .45); }
.adm-actions__rail-ico { display: flex; align-items: center; justify-content: center;
  flex: 0 0 auto; width: 26px; height: 26px; border-radius: 8px;
  background: var(--track); }
.adm-actions__rail-ico svg { width: 15px; height: 15px; }
.adm-actions__rail.is-on .adm-actions__rail-ico { background: rgba(47, 203, 116, .12);
  color: var(--brand); }
/* Подпись вертикально — как в референсе: 44px по ширине не вмещают её иначе,
   а сокращать «Действия» до иконки значит оставить орган управления без имени. */
.adm-actions__rail-label { writing-mode: vertical-rl; font-size: var(--fs-body);
  font-weight: 600; letter-spacing: .04em; color: var(--muted2); }
.adm-actions__rail.is-on .adm-actions__rail-label { color: var(--text); }
.adm-actions__rail-count { display: flex; align-items: center; justify-content: center;
  min-width: 20px; height: 20px; padding: 0 var(--sp-tight); border-radius: 20px;
  background: var(--brand); color: var(--on-brand); font-size: var(--fs-hint);
  font-weight: 700; font-variant-numeric: tabular-nums; }
.adm-actions__rail-count[hidden] { display: none; }
.adm-actions__rail-chevron { display: flex; }
.adm-actions__rail-chevron svg { width: 13px; height: 13px; }

/* Раскрытая колонка. `display:none` у скрытой половины — не украшение: он же
   убирает её из порядка обхода с клавиатуры, иначе Tab уводил бы фокус на
   кнопки, которых на экране нет. */
.adm-actions__body { display: none; }
.adm-list-layout.is-actions-open .adm-actions__rail { display: none; }
.adm-list-layout.is-actions-open .adm-actions__body { display: flex;
  flex-direction: column; max-height: calc(100vh - 92px); background: var(--panel);
  border: 1px solid var(--border); border-radius: var(--radius-card);
  animation: adm-actions-in .18s ease both; }
@keyframes adm-actions-in { from { opacity: 0; transform: translateX(8px); }
  to { opacity: 1; transform: none; } }
.adm-actions__head { display: flex; align-items: center; gap: var(--sp-item);
  padding: var(--sp-block) var(--sp-block) 0; }
.adm-actions__title { font-size: var(--fs-head); font-weight: 600; color: var(--text); }
/* Свернуть — иконкой в шапке, а не второй подписью: колонку сворачивают в
   ответ на «мешает», и объяснять этот жест словами незачем. */
.adm-actions__collapse { display: flex; align-items: center; justify-content: center;
  flex: 0 0 26px; width: 26px; height: 26px; margin-left: auto; padding: 0;
  border: 0; border-radius: 8px; background: none; color: var(--muted3);
  cursor: pointer; transition: background .14s ease, color .14s ease; }
.adm-actions__collapse:hover { background: var(--hover); color: var(--text); }
.adm-actions__collapse svg { width: 14px; height: 14px; }

/* Узкий экран: колонка уходит под таблицу, а корешок из вертикального
   становится обычной горизонтальной полосой во всю ширину — вертикальная
   подпись под таблицей выглядела бы оторванной ни к чему полоской.

   Порог высокий (1660px) намеренно и подобран по факту, а не на глаз: у
   магазинов шесть составных колонок, и на окне уже (замер на данных с самыми
   длинными подписями) таблица рядом с раскрытой колонкой упирается в
   собственную горизонтальную прокрутку. Правило, которое порог держит:
   пока сетка двухколоночная, таблица не прокручивается горизонтально
   НИКОГДА — ни свёрнутой колонкой, ни раскрытой.

   Блок стоит НИЖЕ правил корешка, а не рядом с сеткой: у обоих селекторов
   одинаковый вес, и медиазапрос, стоящий выше по файлу, проигрывал бы
   обычному правилу — полоса оставалась бы вертикальной. */
@media (max-width: 1660px) {
  .adm-list-layout,
  .adm-list-layout.is-actions-open { grid-template-columns: minmax(0, 1fr); }
  .adm-actions { position: static; }
  .adm-actions__rail { flex-direction: row; justify-content: flex-start;
    gap: var(--sp-item); padding: var(--sp-item) var(--sp-block); }
  .adm-actions__rail-label { writing-mode: horizontal-tb; }
  .adm-actions__rail-count { margin-left: auto; }
  .adm-actions__rail-chevron { transform: rotate(-90deg); }
}
@media (prefers-reduced-motion: reduce) {
  .adm-list-layout { transition: none; }
  .adm-list-layout.is-actions-open .adm-actions__body { animation: none; }
}
.adm-actions__meta { display: flex; align-items: baseline; gap: var(--sp-item);
  padding: var(--sp-snug) var(--sp-block) var(--sp-row); font-size: var(--fs-hint); font-weight: 600;
  letter-spacing: .04em; text-transform: uppercase; color: var(--muted3);
  border-bottom: 1px solid var(--line); }
/* Есть выбор — счётчик брендовым: единственная цифра колонки, отвечает на
   вопрос «сколько записей я сейчас затрону». */
.adm-actions__meta .is-on { color: var(--brand); }
.adm-actions__clear { margin-left: auto; font: inherit; font-size: var(--fs-hint);
  letter-spacing: 0; text-transform: none; padding: 0; border: 0;
  background: none; color: var(--muted3); cursor: pointer; }
.adm-actions__clear:hover { color: var(--text2); }
.adm-actions__clear[hidden] { display: none; }
/* Список — сплошная таблица строк, без промежутков и внутренних отступов:
   поля задаёт сама строка, а разделяют их линии. Тринадцать однолинейных
   строк укладываются примерно в 500px — на рабочем экране без прокрутки, а
   на низком окне список прокрутится внутри колонки, не уехав за её край. */
.adm-actions__list { display: flex; flex-direction: column; overflow-y: auto;
  border-bottom-left-radius: var(--radius-card);
  border-bottom-right-radius: var(--radius-card); }

/* Строка действия — тот же приём, что в блоке «Действия» карточки магазина:
   список читается как таблица (строки разделены линиями), а не как набор
   островков с рамками и скруглениями. Рамка вокруг каждой строки в узкой
   колонке дробит её на тринадцать самостоятельных плашек, и глазу не за что
   зацепиться при сравнении.

   Классы свои, а не общие с карточкой: та строка описывает ОДИН магазин и
   знает про него всё (хватает ли токенов, умеет ли маркетплейс), эта —
   произвольную выборку, о которой до подтверждения не известно ничего. Общий
   CSS заставил бы один из экранов притворяться другим; одинаковыми держим
   метрики, а не селекторы. */
/* Высота строки фиксирована, и это условие всей раскладки: тринадцать строк
   одинаковой высоты сравниваются взглядом, а «прыгающая» превращает список в
   лестницу. Внутрь этой высоты помещаются и две линии названия — см. ниже. */
.adm-run { display: flex; align-items: center; gap: var(--sp-item); min-width: 0;
  min-height: 44px; padding: var(--sp-tight) var(--sp-block); border-bottom: 1px solid var(--rowline);
  transition: background .14s ease; }
.adm-run:last-child { border-bottom: none; }
.adm-run:hover { background: var(--hover); }
/* Длинное название ПЕРЕНОСИТСЯ на вторую линию, а не обрезается многоточием:
   «Выключить авто-модерацию ск…» и «Выключить автоприменение от…» отличались
   только хвостом, который как раз и съедался. Две линии с плотным
   межстрочным (1.2 при кегле 12.5px = 30px) укладываются в ту же высоту
   строки, что и одна, — поэтому список остаётся ровным.

   Третьей линии не будет (`line-clamp: 2`): она бы высоту всё-таки сломала.
   Сегодня в неё не упирается ни одно название раздела. */
.adm-run__label { flex: 1; min-width: 0; font-size: var(--fs-body); font-weight: 500;
  color: var(--text); line-height: 1.2; display: -webkit-box;
  -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; }
/* Справа — тариф ЗА ЕДИНИЦУ (в карточке на этом месте итог по магазину):
   выборка до подтверждения неизвестна, и одна сумма на всех была бы
   выдумкой. Итог считает диалог подтверждения. */
.adm-run__total { flex: 0 0 auto; font-size: var(--fs-hint); color: var(--muted4);
  font-variant-numeric: tabular-nums; white-space: nowrap; }
/* Кнопка запуска: цвет — это ПЛАТНОСТЬ операции, как в карточке. Сплошная
   зелёная у бесплатной, янтарный контур у платной. Опасность цветом не
   передаётся: цвет уже занят, а предупреждение показывает диалог
   подтверждения — там же, где красная кнопка. */
.adm-run__go { flex: 0 0 auto; font: inherit; font-size: var(--fs-hint); font-weight: 600;
  padding: var(--sp-tight) var(--sp-row); border-radius: 9px; cursor: pointer; white-space: nowrap;
  background: var(--brand); border: 1px solid var(--brand); color: var(--on-brand);
  transition: filter .16s ease, background .16s ease; }
.adm-run__go:hover { filter: brightness(1.08); }
.adm-run__go:focus-visible { outline: 2px solid var(--brand); outline-offset: 2px; }
/* Пока не отмечена ни одна строка, действие применять не к чему: кнопка гаснет
   (состояние ставит `js/list-select.js`). Приглушённая, а не спрятанная —
   список действий раздела должен оставаться видимым: по нему оператор и
   понимает, что здесь вообще можно сделать. */
.adm-run__go[disabled] { background: var(--track); border-color: var(--border2);
  color: var(--muted4); cursor: not-allowed; filter: none; }
.adm-run.is-paid .adm-run__go[disabled] { background: var(--track);
  border-color: var(--border2); color: var(--muted4); }
.adm-run.is-paid .adm-run__go { background: rgba(241, 163, 59, .12);
  border-color: rgba(241, 163, 59, .30); color: var(--warn); }
.adm-run.is-paid .adm-run__go:hover { background: rgba(241, 163, 59, .18); }
/* Платная операция: тариф тоже янтарный — та же ось, что и кнопка. */
.adm-run.is-paid .adm-run__total { color: var(--warn); }

/* Отметка «понимаю масштаб» на подтверждении массового необратимого действия
   (`_bulk_ack.html`). Тон тревожный: она стоит рядом с красной кнопкой и
   отвечает за ту же ошибку, что и предупреждение над ней. */
.adm-ack { display: flex; align-items: flex-start; gap: var(--sp-item); cursor: pointer;
  padding: var(--sp-row) var(--sp-block); border-radius: var(--radius-control);
  border: 1px solid rgba(241, 98, 111, .28); background: rgba(241, 98, 111, .06); }
.adm-ack input { margin-top: var(--sp-hair); flex: 0 0 auto; }
.adm-ack__text { font-size: var(--fs-row); line-height: 1.45; color: var(--text2); }
.adm-ack.is-invalid { border-color: var(--err); }

/* Колонка выбора: узкая, чтобы чекбокс не растягивал первую колонку данных. */
.adm-table__pick { width: 44px; padding-right: 0 !important; }

.adm-filters { display: flex; align-items: flex-end; flex-wrap: wrap; gap: var(--sp-row); }
/* Поля тулбара — метрики поля поиска референса: `--sunken`, `--border2`,
   радиус 11px, 13.5px. */
.adm-filters input, .adm-filters select {
  background: var(--sunken); border: 1px solid var(--border2);
  border-radius: 11px; padding: var(--sp-item) var(--sp-block); color: var(--text);
  font-family: inherit; font-size: var(--fs-row); outline: none;
}
/* Поиск с лупой внутри поля: в референсе иконка лежит слева, под неё
   отведено 38px левого отступа. */
.adm-filters input[type="search"] {
  padding-left: var(--field-icon); background-image: var(--search-icon);
  background-repeat: no-repeat; background-position: left var(--sp-row) center;
  background-size: 16px 16px;
}
.adm-filters input:focus, .adm-filters select:focus {
  border-color: var(--brand); box-shadow: 0 0 0 3px rgba(47, 203, 116, .18);
}
/* Подпись границы периода: без неё два одинаковых поля дат неразличимы. */
.adm-filters__field { display: flex; flex-direction: column; gap: var(--sp-tight); }
.adm-filters__field span { font-size: var(--fs-hint); color: var(--muted3); }

/* Блок вложенного формсета (профиль магазина) — отделён от полей родителя. */
.adm-inline { border-top: 1px solid var(--line); }
.adm-inline__title { font-size: var(--fs-head); font-weight: 600; color: var(--muted2);
  text-transform: uppercase; letter-spacing: .06em; margin-bottom: var(--sp-tight); }

/* Группы полей формы (fieldset) — без них 26 полей настроек читаются
   сплошной простынёй; наследует масштаб заголовка от .adm-inline__title. */
.adm-fieldset { border: 0; margin: 0; padding: 0; }
/* Отбивка между группами: у формы настроек их восемь, и без разделителя
   заголовок следующей группы читается как подпись последнего поля
   предыдущей. */
.adm-fieldset + .adm-fieldset { margin-top: var(--sp-section); padding-top: var(--sp-section);
  border-top: 1px solid var(--line); }
.adm-fieldset__title { font-size: var(--fs-head); font-weight: 600; color: var(--muted2);
  text-transform: uppercase; letter-spacing: .06em; margin: 0 0 var(--sp-row); padding: 0; }

/* Поля группы — сетка, а не колонка: в один столбец форма настроек (26 полей)
   растягивается на три экрана, а числу «16» достаётся километр ширины. */
.adm-fieldset__fields { display: grid; gap: var(--sp-gutter) var(--sp-section);
  grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); }

.adm-field { display: flex; flex-direction: column; gap: var(--sp-snug); min-width: 0; }
.adm-field__label { font-size: var(--fs-body); color: var(--muted2); }
/* Подсказка обязана отличаться от подписи и держаться ближе к своему полю,
   чем к следующему: одинаковый с подписью размер читался как заголовок
   следующего поля. */
.adm-field__hint { font-size: var(--fs-hint); line-height: 1.45; color: var(--muted3); }
.adm-field__error, .adm-form-error { font-size: var(--fs-body); color: var(--err); }
.adm-field input, .adm-field select, .adm-field textarea {
  background: var(--sunken); border: 1px solid var(--border2);
  border-radius: var(--radius-control); padding: var(--sp-row) var(--sp-block); color: var(--text);
  font-family: inherit; font-size: var(--fs-row); outline: none; width: 100%;
}
/* `height`, а не только `min-height`: Django отдаёт textarea с rows="10", и
   десять строк высоты остаются даже у поля, где лежит пустой список. */
.adm-field textarea { height: 96px; min-height: 96px; resize: vertical; }
.adm-field input:focus, .adm-field select:focus, .adm-field textarea:focus {
  border-color: var(--brand); box-shadow: 0 0 0 3px rgba(47, 203, 116, .18);
}
/* Поле на всю ширину сетки (textarea, JSON) — в одной колонке оно нечитаемо. */
.adm-field--full { grid-column: 1 / -1; }
/* Флажок стоит в строку с подписью: в колонке он уезжает от неё на всю
   ширину ячейки и выглядит как поле без подписи. */
.adm-field--check { flex-direction: row-reverse; justify-content: flex-end;
  align-items: center; flex-wrap: wrap; gap: var(--sp-item); }
.adm-field--check input { width: 18px; height: 18px; padding: 0; flex: 0 0 auto; }
.adm-field--check .adm-field__hint, .adm-field--check .adm-field__error { width: 100%; }
/* Флажки везде — списки, формы, фильтры: браузерный синий выбивается из темы. */
input[type="checkbox"], input[type="radio"] { accent-color: var(--brand); }

/* Ниже правил `.adm-field`/`.adm-filters` намеренно: там `background` задан
   shorthand'ом, и он обнулил бы `background-image` со стрелкой. */
.adm-field select, .adm-filters select {
  appearance: none; -webkit-appearance: none; cursor: pointer;
  background-image: var(--select-arrow); background-repeat: no-repeat;
  background-position: right var(--sp-row) center; background-size: 12px 8px;
  padding-right: var(--field-icon);
}
.adm-field select:hover, .adm-filters select:hover { border-color: var(--border4); }
/* Раскрытый список рисует ОС; без явных цветов пунктов он на части систем
   остаётся светлым даже при `color-scheme: dark`. */
.adm-field option, .adm-filters option { background: var(--panel); color: var(--text); }

/* Поля ввода: одинаковый отклик на наведение у всех типов, включая date и
   search, — иначе «живыми» выглядят только текстовые. */
.adm-field input:hover, .adm-field textarea:hover,
.adm-filters input:hover { border-color: var(--border4); }

/* Дата: нативная иконка календаря приходит от браузера и не совпадает по
   стилю со стрелкой селекта — заменяем своей, той же толщины обводки. */
.adm-field input[type="date"], .adm-filters input[type="date"] {
  background-image: var(--date-icon); background-repeat: no-repeat;
  background-position: right var(--sp-row) center; background-size: 16px 16px;
  padding-right: var(--field-icon); cursor: text;
}
.adm-field input[type="date"]::-webkit-calendar-picker-indicator,
.adm-filters input[type="date"]::-webkit-calendar-picker-indicator {
  /* Штатная иконка спрятана, но кликабельной остаётся вся правая зона: так
     пикер по-прежнему открывается щелчком по нарисованному календарю. */
  opacity: 0; cursor: pointer; width: 22px; margin-right: calc(var(--sp-snug) * -1);
}
.adm-field input[type="date"]::-webkit-datetime-edit,
.adm-filters input[type="date"]::-webkit-datetime-edit { color: var(--text); }
.adm-filters input[type="date"]:invalid::-webkit-datetime-edit { color: var(--muted4); }

/* Поиск: браузерный крестик очистки светится белым на тёмной теме. */
.adm-filters input[type="search"]::-webkit-search-cancel-button {
  -webkit-appearance: none; appearance: none; width: 14px; height: 14px;
  cursor: pointer; background: var(--clear-icon) center / contain no-repeat;
}

/* Свой выпадающий список (`js/select.js`): раскрытый список нативного
   <select> рисует ОС, и на тёмной панели он остаётся светлым системным
   прямоугольником — CSS до него не достаёт. Само поле-кнопка повторяет
   геометрию текстовых полей, чтобы строка фильтров не разъехалась. */
.adm-combo { position: relative; display: inline-flex; }
/* В форме комбо занимает ячейку целиком: рядом стоят поля ввода на всю
   ширину, и список по ширине своего текста читался как поле другого сорта.
   В фильтрах ширина по-прежнему по содержимому — там поля стоят в строку. */
.adm-field .adm-combo { display: flex; width: 100%; }
.adm-field .adm-combo__button { width: 100%; }
/* Нативный select остаётся в форме (значение и валидация — его), но не
   виден и не участвует в обходе табом. `display: none` не годится: часть
   браузеров исключает такие поля из отправки. */
.adm-combo__native { position: absolute; opacity: 0; pointer-events: none;
  width: 100%; height: 100%; margin: 0; }
.adm-combo__button { display: inline-flex; align-items: center; gap: var(--sp-item);
  background: var(--sunken); border: 1px solid var(--border2);
  border-radius: var(--radius-control);
  padding: var(--sp-item) var(--field-icon) var(--sp-item) var(--sp-row);
  color: var(--text); font: inherit; font-size: var(--fs-row); cursor: pointer;
  text-align: left; white-space: nowrap;
  background-image: var(--select-arrow); background-repeat: no-repeat;
  background-position: right 12px center; background-size: 12px 8px; }
.adm-combo__button:hover { border-color: var(--border4); }
.adm-combo__button:focus-visible { border-color: var(--brand);
  box-shadow: 0 0 0 3px rgba(47, 203, 116, .18); outline: none; }
/* Раскрытый список позиционирует скрипт (`js/popup.js`): он переводит слой в
   `fixed` и тем выводит его из-под `overflow` карточки, тулбара или диалога —
   иначе список обрезался бы границей своего блока. Правила ниже описывают
   вид и запасное положение (когда скрипт не отработал), координаты задаются
   инлайном поверх них. */
.adm-combo__list { position: absolute; top: calc(100% + 6px); left: 0; z-index: 60;
  min-width: 100%; max-height: 280px; overflow-y: auto; display: none;
  background: var(--panel); border: 1px solid var(--border2);
  border-radius: var(--radius-control); padding: var(--sp-tight);
  box-shadow: 0 18px 40px -24px rgba(0, 0, 0, .75); }
.adm-combo--open .adm-combo__list { display: block; }
.adm-combo__item { padding: var(--sp-item) var(--sp-row); border-radius: 8px; font-size: var(--fs-row);
  color: var(--text2); cursor: pointer; white-space: nowrap; }
.adm-combo__item:hover { background: var(--hover); color: var(--text); }
.adm-combo__item--selected { background: rgba(47, 203, 116, .10);
  color: var(--text); font-weight: 500; }

/* Свой календарь (`js/datepicker.js`) — по той же причине, что и список:
   всплывающий пикер рисует браузер. Поле остаётся `type="date"`, поэтому
   формат значения и серверная валидация не меняются. */
.adm-cal { position: absolute; z-index: 70; width: 268px; padding: var(--sp-row);
  background: var(--panel); border: 1px solid var(--border2);
  border-radius: var(--radius-card);
  box-shadow: 0 22px 48px -26px rgba(0, 0, 0, .8); }
.adm-cal__head { display: flex; align-items: center; justify-content: space-between;
  margin-bottom: var(--sp-item); }
.adm-cal__title { font-size: var(--fs-row); font-weight: 500; color: var(--text); }
.adm-cal__grid { display: grid; grid-template-columns: repeat(7, 1fr); gap: var(--sp-hair); }
.adm-cal__weekday { display: flex; align-items: center; justify-content: center;
  height: 26px; font-size: var(--fs-eyebrow); letter-spacing: .06em; text-transform: uppercase;
  color: var(--muted4); font-weight: 600; }
.adm-cal__day { height: 32px; border: 0; background: transparent; cursor: pointer;
  border-radius: 8px; color: var(--text2); font: inherit; font-size: var(--fs-row);
  transition: background .12s ease; }
.adm-cal__day:hover { background: var(--hover); color: var(--text); }
.adm-cal__day--today { color: var(--green); font-weight: 600; }
.adm-cal__day--selected { background: var(--brand); color: var(--on-brand);
  font-weight: 600; }
.adm-cal__foot { display: flex; justify-content: space-between; gap: var(--sp-item);
  margin-top: var(--sp-item); padding-top: var(--sp-item); border-top: 1px solid var(--line); }
/* Вход — единственный экран без оболочки панели, поэтому центрирование и
   карточка живут здесь, а не наследуются от `.adm-content`. */
.adm-login { min-height: 100vh; display: flex; flex-direction: column;
  align-items: center; justify-content: center; gap: var(--sp-section); padding: var(--sp-section);
  animation: adm-rise .35s ease both; }
.adm-login .adm-card { width: 100%; max-width: 380px; }
.adm-login .adm-card__body { padding: var(--sp-section); }
.adm-login__brand { color: var(--text); }
.adm-login__brand .adm-logo { height: 30px; }
.adm-login__title { font-size: var(--fs-metric); margin-bottom: var(--sp-snug); }
.adm-login__hint { margin: 0 0 var(--sp-section); font-size: var(--fs-row); color: var(--muted3); }
.adm-login__form { display: flex; flex-direction: column; gap: var(--sp-block); }

@keyframes adm-rise { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: none; } }
@keyframes adm-grow { from { transform: scaleX(0); } to { transform: scaleX(1); } }
@keyframes adm-pulse { 0%, 100% { opacity: 1; transform: scale(1); } 50% { opacity: .45; transform: scale(.8); } }

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after { animation: none !important; transition: none !important; }
}

/* ---- Мониторинг ---- */
/* Стек экрана. Он же корень поллинга (`monitoring.html`): всё содержимое
   мониторинга живёт внутри одного `data-live-src`, и без своего `gap` ряд KPI
   упирался бы в сетку колонок — их разделял `margin-bottom` самих блоков,
   который складывался с зазорами внутри колонок и давал экрану третий ритм
   вдобавок к двум. Зазор здесь тот же, что у `.adm-content` на прочих
   экранах. */
.adm-mon { display: flex; flex-direction: column; gap: var(--sp-gutter); }
/* Плитки раскладываются по числу, а не по фиксированным семи колонкам: набор
   менялся уже дважды (появилась память), и жёсткое число пришлось бы править
   вместе с ним, а на узком экране семь колонок по 150px не помещаются вовсе. */
.adm-kpis { display: grid; grid-template-columns: repeat(auto-fit, minmax(148px, 1fr));
  gap: var(--sp-gutter); }
.adm-kpi { padding: var(--sp-block); background: var(--panel);
  border: 1px solid var(--border); border-radius: 13px; }
.adm-kpi__label { font-size: var(--fs-micro); letter-spacing: .12em; color: var(--muted4); font-weight: 600; margin-bottom: var(--sp-item); }
.adm-kpi__value { font-size: var(--fs-metric); font-weight: 600; letter-spacing: -.03em; line-height: 1; }
.adm-kpi__sub { font-size: var(--fs-hint); color: var(--muted3); margin-top: var(--sp-snug); }
/* Ступени те же, что у полос железа (`sysinfo.level`), и цвета обязаны
   совпадать: плитка «CPU» и полоса «CPU» под ней показывают одно число, а
   разная окраска одного и того же значения читается как разная тревожность.
   `busy` тут не было вовсе — от 50 до 75% плитка молчала, пока полоса уже
   синела. Согласованность пришпилена тестом (`test_kpi_levels_match_hardware`). */
.adm-kpi--busy .adm-kpi__value { color: var(--info); }
.adm-kpi--warn .adm-kpi__value { color: var(--warn); }
.adm-kpi--alert .adm-kpi__value { color: var(--err); }

.adm-mon-grid { display: grid; grid-template-columns: minmax(0, 1fr) 380px;
  gap: var(--sp-gutter); align-items: start; }
.adm-mon-col { display: flex; flex-direction: column; gap: var(--sp-gutter); min-width: 0; }
/* Правая колонка едет со скроллом: состояние процессов и железа — то, ради
   чего экран открыт, а левая колонка (лента, пропускная способность) длиннее
   экрана. `top` — высота липкой шапки панели (56px) плюс её зазор. */
.adm-mon-col--side { position: sticky; top: calc(var(--header-h) + var(--sp-gutter)); }

.adm-jobs__head, .adm-job { display: grid;
  grid-template-columns: minmax(200px, 1.4fr) 150px minmax(220px, 1.3fr) 92px 96px;
  gap: var(--sp-row); align-items: center; padding: var(--sp-row) var(--sp-gutter); }
.adm-jobs__head { font-size: var(--fs-eyebrow); letter-spacing: .1em; color: var(--muted4); font-weight: 600; border-bottom: 1px solid var(--line); }
/* Экран очереди: четыре колонки вместо пяти у «Сейчас в работе» — там нет
   фазы/прогресса, задача ещё не начала выполняться. Модификатор навешивается
   и на строку (`.adm-job--queued`), и на шапку (`.adm-jobs__head--queued`) —
   иначе подписи столбцов разъедутся со значениями; сетку шапки экрана
   мониторинга (пять колонок, правило выше) не трогаем. */
.adm-job--queued, .adm-jobs__head--queued { grid-template-columns: minmax(200px, 1.6fr) 180px 120px 100px; }
.adm-job { padding: var(--sp-row) var(--sp-gutter); border-bottom: 1px solid var(--rowline); }
.adm-job:nth-child(even) { background: var(--stripe); }
.adm-job__id { font-size: var(--fs-hint); color: var(--muted4); }
.adm-job__phase { display: flex; gap: var(--sp-item); font-size: var(--fs-body); color: var(--muted2); }
.adm-job__phase b { margin-left: auto; font-weight: 400; }
.adm-jobs__foot { display: flex; justify-content: space-between; padding: var(--sp-row) var(--sp-gutter); font-size: var(--fs-body); color: var(--muted3); }

.adm-bar { display: flex; height: 5px; border-radius: 3px; background: var(--track); overflow: hidden; margin-top: var(--sp-snug); }
/* Без анимации появления: живые блоки обновляются поллингом раз в 5 с, и
   `adm-grow` проигрывался бы на каждом тике — полосы CPU, памяти и диска
   заново «вырастали» из нуля по 0,7 секунды, что читается как мельтешение,
   а не как жизнь. Различить «первый показ» и «обновление» в CSS нельзя:
   фрагмент приходит с сервера целиком, узлы каждый раз новые. */
.adm-bar__fill { height: 100%; background: var(--brand); transform-origin: left; }
.adm-bar__fill--warn { background: var(--warn); }
.adm-bar__fill--alert { background: var(--err); }
/* Полосатая бегущая заливка несёт смысл, а не украшение: так рисуется
   задача, которая прогресс не публикует (модерация, опрос скидок, финансы) —
   работа идёт, но измерять её нечем. */
.adm-bar__fill--indeterminate { background: repeating-linear-gradient(115deg, var(--border4) 0 8px, var(--track) 8px 16px); background-size: 28px 28px; }

/* ---- Вкладка «Воркеры»: строка на каждый поток пула ---- */
.adm-card__head--tabs { padding: var(--sp-row) var(--sp-gutter); }
.adm-slots__head, .adm-slot { display: grid;
  grid-template-columns: 46px minmax(150px, 1.4fr) 104px minmax(130px, 1.2fr) 62px;
  gap: var(--sp-item); align-items: center; padding: var(--sp-row) var(--sp-gutter); }
.adm-slots__head { font-size: var(--fs-eyebrow); letter-spacing: .1em;
  color: var(--muted4); font-weight: 600; border-bottom: 1px solid var(--line); }
.adm-slot { border-bottom: 1px solid var(--rowline); font-size: var(--fs-row); }
.adm-slot:nth-child(even) { background: var(--stripe); }
.adm-slot:hover { background: var(--hover); }
.adm-slot__id { display: flex; align-items: center; gap: var(--sp-item); }
.adm-slot__name { font-family: ui-monospace, monospace; font-size: var(--fs-body); color: var(--text2); }
.adm-slot__kind { display: flex; align-items: center; gap: var(--sp-snug); min-width: 0;
  color: var(--text2); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
/* Простой слот приглушён целиком: он часть картины «пул недогружен», но
   читать его построчно оператору незачем — взгляд должен цепляться за
   работающие строки. */
.adm-slot--idle, .adm-slot--idle .adm-slot__name, .adm-slot--idle .adm-slot__kind { color: var(--muted4); }
.adm-slot__idle, .adm-slot__free { color: var(--muted4); font-size: var(--fs-body); }
/* Источник запуска — точка с ободком вместо колонки с пилюлей: колонок в
   строке слота и так пять, а «вручную или планово» нужно видеть, а не
   вычитывать. Легенда рядом с вкладками объясняет обе точки. */
.adm-src { width: 5px; height: 5px; border-radius: 50%; flex: 0 0 5px;
  background: var(--faint); box-shadow: 0 0 0 1.5px var(--border2); }
.adm-src--manual { background: var(--info); box-shadow: 0 0 0 1.5px rgba(91, 155, 248, .24); }
.adm-legend { display: inline-flex; align-items: center; gap: var(--sp-row);
  font-size: var(--fs-hint); color: var(--muted4); }
.adm-legend span { display: inline-flex; align-items: center; gap: var(--sp-tight); }
/* Карта занятости пула: те же слоты, что в таблице, но одним взглядом —
   «сколько всего и сколько занято» читается быстрее списка. */
/* Счёт занятых слотов с картой пула и фильтр журнала (`.adm-seg--filter`,
   объявлен вместе с компонентом) занимают одно место — правый край шапки: на
   экране одновременно видна только одна из вкладок. */
.adm-slotmap__count { margin-left: auto; }
.adm-slotmap__count { font-size: var(--fs-body); color: var(--muted2);
  font-variant-numeric: tabular-nums; }
.adm-slotmap { display: inline-flex; gap: var(--sp-hair); }
.adm-slotmap__cell { width: 7px; height: 14px; border-radius: 2px; background: var(--border2); }
.adm-slotmap__cell.is-busy { background: var(--brand); }

.adm-mon-down { padding: var(--sp-page) var(--sp-section); display: flex; flex-direction: column; align-items: center; text-align: center; gap: var(--sp-item); }
.adm-mon-down__title { font-size: var(--fs-head); font-weight: 600; }
.adm-mon-down__text { font-size: var(--fs-row); color: var(--muted2); max-width: 460px; }
.adm-mon-down__key { font-family: ui-monospace, monospace; font-size: var(--fs-body); color: var(--muted3); }

.adm-proc { padding: var(--sp-row) var(--sp-gutter); border-bottom: 1px solid var(--rowline); }
.adm-proc__row { display: flex; align-items: center; gap: var(--sp-item); }
.adm-proc__name { font-family: ui-monospace, monospace; font-size: var(--fs-body); }
.adm-proc__meta { font-size: var(--fs-hint); color: var(--muted3); margin-top: var(--sp-snug); padding-left: var(--sp-block); }
.adm-dot { width: 7px; height: 7px; border-radius: 50%; background: var(--faint); flex: 0 0 7px; }
/* Бесконечных анимаций в живых блоках нет намеренно. Фрагменты приходят с
   сервера целиком, узлы каждый тик новые — пульсация точки и бегущие полоски
   стартовали заново каждые пять секунд и читались как рывки, а не как жизнь.
   Признак «работает» несёт цвет, а не движение. */
.adm-dot--running { background: var(--brand); }
.adm-dot--silent, .adm-dot--alert { background: var(--err); }

/* Три метрики железа — один padded-блок с зазором, а не строки списка:
   разделительные линии в референсе есть у процессов и инфраструктуры (там
   строка = сущность), а у железа их нет — метрика занимает три яруса
   (числа, полоса, график), и линия между ярусами читалась бы как граница
   внутри метрики. */
.adm-hw-list { padding: var(--sp-block) var(--sp-gutter); display: flex; flex-direction: column; gap: var(--sp-block); }
/* Цвет метрики — ступень загрузки (`sysinfo.level`), а не вид ресурса:
   зелёный «вдоволь», синий «рабочая загрузка», янтарный «сильно»,
   красный «на грани». Задаётся один раз на метрике, `currentColor`
   разносит его на полосу и график. */
.adm-hw { color: var(--ok); }
.adm-hw--busy { color: var(--info); }
.adm-hw--warn { color: var(--warn); }
.adm-hw--alert { color: var(--err); }
.adm-hw__row { display: flex; gap: var(--sp-item); align-items: baseline; font-size: var(--fs-body); }
.adm-hw__label { color: var(--muted2); }
/* Процент окрашен только тогда, когда цвет что-то сообщает: до янтарного
   порога он обычный текст, иначе полоса и число кричат об одном и том же. */
.adm-hw__row b { margin-left: auto; font-weight: 600; color: var(--text); }
.adm-hw--warn .adm-hw__row b, .adm-hw--alert .adm-hw__row b { color: currentColor; }
.adm-hw__row b.is-muted { color: var(--muted3); font-weight: 400; }
.adm-hw__abs { color: var(--muted4); font-size: var(--fs-hint); }
.adm-hw .adm-bar { height: 6px; margin-top: var(--sp-snug); }
.adm-hw .adm-bar__fill { background: currentColor; border-radius: 3px; }
.adm-spark { width: 100%; height: 34px; display: block; margin-top: var(--sp-item); opacity: .85; }
.adm-hw__axis { display: flex; justify-content: space-between; font-size: var(--fs-eyebrow); color: var(--muted4); }

.adm-throughput { display: grid; grid-template-columns: minmax(0, 1fr) 260px; }
.adm-throughput__bars { padding: var(--sp-block) var(--sp-gutter); display: flex; flex-direction: column; gap: var(--sp-item); border-right: 1px solid var(--line); }
.adm-throughput__row { display: grid; grid-template-columns: 150px minmax(0, 1fr) 92px; gap: var(--sp-row); align-items: center; font-size: var(--fs-body); }
.adm-throughput__stats { padding: var(--sp-block) var(--sp-gutter); display: flex; flex-direction: column; gap: var(--sp-row); }
.adm-throughput__stat { display: flex; align-items: baseline; justify-content: space-between; font-size: var(--fs-body); color: var(--muted2); }
/* Тон значения перечислен здесь явно: утилиты `.is-warn`/`.is-alert` слабее
   селектора с элементом и молча проигрывали ему — тревожные числа выходили
   обычным белым. */
.adm-throughput__stat b { font-weight: 600; color: var(--text); }
.adm-throughput__stat b.is-warn { color: var(--warn); }
.adm-throughput__stat b.is-alert { color: var(--err); }
/* Легенда объясняет двухцветную полосу: без неё красный хвост читается как
   вторая метрика, а не как доля ошибок внутри того же объёма. */
.adm-throughput__legend { display: flex; gap: var(--sp-block); padding-top: var(--sp-tight); font-size: var(--fs-hint); color: var(--muted3); }
.adm-throughput__legend span { display: inline-flex; align-items: center; gap: var(--sp-snug); }
.adm-throughput__legend i { width: 10px; height: 4px; border-radius: 2px; background: var(--ok); }
.adm-throughput__legend i.is-alert { background: var(--err); }
.adm-throughput__legend .is-right { margin-left: auto; }
/* Полоса «успешно / с ошибкой» — не прогресс-бар строки задачи: она сама
   единица чтения строки, поэтому выше и без верхнего отступа сетки. */
.adm-bar--split { height: 16px; border-radius: 5px; margin-top: 0; }
.adm-bar--split .adm-bar__fill { border-radius: 0; }
.adm-bar--split .adm-bar__fill--alert { opacity: .75; }

.adm-recent, .adm-recent__head { display: grid;
  grid-template-columns: 74px minmax(150px, 1fr) 130px 84px minmax(0, 1.2fr);
  gap: var(--sp-row); align-items: center; padding: var(--sp-row) var(--sp-gutter);
  font-size: var(--fs-body); }
.adm-recent { border-bottom: 1px solid var(--rowline); }
.adm-recent__head { font-size: var(--fs-eyebrow); letter-spacing: .1em;
  color: var(--muted4); font-weight: 600; border-bottom: 1px solid var(--line); }
/* Зебра считается от позиции в карточке, а не от номера строки: перед
   строками идут шапка вкладок и шапка колонок, поэтому первая строка
   приходится на нечётную позицию и остаётся без подложки. */
.adm-recent:nth-child(even) { background: var(--stripe); }
.adm-recent:hover, .adm-job:hover, .adm-anomaly:hover { background: var(--hover); }
.adm-recent__time { font-size: var(--fs-body); color: var(--muted3); }
.adm-recent__shop { display: flex; align-items: center; gap: var(--sp-item); min-width: 0; }
/* Имя магазина в ленте и в списке задач — обычный текст, зелёный только под
   курсором: строк много, и сплошь зелёный столбец ссылок перетягивает
   внимание с того, ради чего экран открыт, — со статуса и ошибки. */
.adm-recent__shop a { white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  color: var(--text2); }
.adm-recent__shop a:hover, .adm-job__shop a:hover { color: var(--green); }
.adm-job__shop a { color: var(--text); font-weight: 500; }
.adm-recent__kind { color: var(--muted); }
.adm-recent__text { color: var(--muted3); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.adm-recent__text.is-alert { color: var(--err); }

.adm-infra, .adm-anomaly { display: flex; align-items: center; gap: var(--sp-item); padding: var(--sp-row) var(--sp-gutter); border-bottom: 1px solid var(--rowline); font-size: var(--fs-body); }
.adm-infra__value { margin-left: auto; color: var(--muted); text-align: right; }
.adm-anomaly__n { width: 24px; height: 24px; border-radius: 7px; display: inline-flex; align-items: center; justify-content: center; font-size: var(--fs-hint); font-weight: 600; color: var(--warn); background: rgba(241, 163, 59, .1); }
.adm-anomaly__n.is-hot { color: var(--err); background: rgba(241, 98, 111, .1); }
.adm-anomaly { color: var(--text2); }
.adm-anomaly:hover { color: var(--text2); }
.adm-anomaly__n { flex: 0 0 24px; }
.adm-anomaly__go { margin-left: auto; flex: 0 0 auto; color: var(--muted4); }


@keyframes adm-stripes { from { background-position: 0 0; } to { background-position: 28px 0; } }

/* ---- Мониторинг: обвязка карточек и утилитные классы ----
   Референс задаёт содержимое и раскладку блоков (выше), но не называет
   заголовок/примечание карточки и мелкие утилиты (`is-right` вне таблицы,
   `is-ok`/`is-warn`/`is-alert` на произвольном элементе, метку маркетплейса).
   Они собраны здесь по образцу уже существующих `.adm-card__header`,
   `.adm-page__actions`, `.adm-table .is-right` — без единого нового цвета. */
/* Два семейства шапки карточки не пересекаются: `.adm-card__header` —
   старый панельный (списки/детали ресурсов), `.adm-card__head` — новый,
   мониторинговый (этот блок, экран очереди, карточка клиента). Не смешивать
   в одной карточке.

   Фон и отступы — как у шапки панели в карточке магазина
   (`.shopcard__panel-head`): утопленная полоса отделяет заголовок от
   содержимого, иначе блок читается одним полотном. Раньше фон был только у
   магазина, и одинаковые по смыслу блоки на двух экранах выглядели
   по-разному. */
.adm-card__head { padding: var(--sp-row) var(--sp-gutter); border-bottom: 1px solid var(--line);
  background: var(--sunken);
  display: flex; align-items: center; flex-wrap: wrap; gap: var(--sp-item) var(--sp-row); }
.adm-card__title { font-size: var(--fs-head); font-weight: 600; }
.adm-card__note { font-size: var(--fs-hint); color: var(--muted3); }
.adm-chips { display: flex; flex-wrap: wrap; gap: var(--sp-snug); width: 100%; }
.adm-empty { padding: var(--sp-page) var(--sp-gutter); text-align: center; font-size: var(--fs-body); color: var(--muted3); }
.is-right { text-align: right; }
.is-ok { color: var(--ok); }
.is-warn { color: var(--warn); }
.is-alert { color: var(--err); }
.adm-muted { color: var(--muted3); }
.adm-mp { display: inline-flex; align-items: center; justify-content: center;
  min-width: 22px; padding: var(--sp-hair) var(--sp-tight); border-radius: 5px; font-size: var(--fs-eyebrow);
  font-weight: 700; letter-spacing: .02em; color: var(--muted2);
  background: var(--sunken); border: 1px solid var(--border2); }
.adm-job__shop { display: inline-flex; align-items: center; gap: var(--sp-snug); min-width: 0; }
.adm-job__kind { font-size: var(--fs-body); color: var(--muted2); }
.adm-dot--disabled { background: var(--faint); }
.adm-dot--unknown { background: var(--warn); }
.adm-pill--running { color: var(--green); background: rgba(47, 203, 116, .10); }
.adm-pill--silent { color: var(--err); background: rgba(241, 98, 111, .09); }
.adm-pill--disabled, .adm-pill--unknown { color: var(--muted3); background: var(--hover); }
.adm-pill--manual { color: var(--muted2); background: var(--sunken); border: 1px solid var(--border2); }
/* Бейдж БЕЗ собственного тона в шапке блока — янтарный: там он всегда счётчик
   того, что требует внимания (число аномалий в мониторинге). Бейдж, которому
   тон задан явно (`--err`, `--info`, …), сохраняет свой: в шапке карточки
   сообщения стоит вид обращения, и перекрашенный янтарём баг-репорт врал бы
   про «требует решения» вместо «уже сломано». Селектор проверяет именно
   отсутствие модификатора, а не перечисляет исключения — новый тон не должен
   заново попадать в эту ловушку. */
.adm-card__head .adm-badge:not([class*="adm-badge--"]) {
  color: var(--warn); background: rgba(241, 163, 59, .09);
  border: 1px solid rgba(241, 163, 59, .25); }

/* ── Выбор периода у графика ─────────────────────────────────────────────
   Показан выбранный период, остальные — в списке. Значения сняты с макета
   (168×31, `--sunken` в рамке `--border2`, радиус 10).

   Правила лежат здесь, а не в стилях карточки: контрол один и тот же у
   финансового блока магазина и у графика расхода на карточке клиента, а
   вторая копия геометрии разъехалась бы с первой при первой же правке.
   Имена `.shopcard__period*` перечислены рядом как псевдонимы — разметка
   финансового блока их ещё носит; новые экраны берут `.adm-period*`. */
.adm-period, .shopcard__period { position: relative; }
.adm-period__trigger, .shopcard__period-trigger { display: flex; align-items: center;
  gap: var(--sp-item); min-width: 168px; padding: var(--sp-snug) var(--sp-row); border-radius: 10px; cursor: pointer;
  background: var(--sunken); border: 1px solid var(--border2); color: var(--text);
  font: inherit; font-size: var(--fs-body); font-weight: 500;
  transition: border-color .16s ease; }
.adm-period__trigger:hover, .shopcard__period-trigger:hover { border-color: var(--border4); }
.adm-period__trigger svg, .shopcard__period-trigger svg { margin-left: auto;
  color: var(--muted3); transition: transform .16s ease; }
.adm-period.is-open .adm-period__trigger svg,
.shopcard__period.is-open .shopcard__period-trigger svg { transform: rotate(180deg); }
/* Положение — тем же `js/popup.js`, что и у комбобокса: шапка блока живёт в
   карточке с `overflow: hidden`, и абсолютное меню обрезалось её границей. */
.adm-period__menu, .shopcard__period-menu { display: none; position: absolute;
  top: calc(100% + 6px); left: 0; min-width: 220px; z-index: 60; padding: var(--sp-snug);
  border-radius: 12px; background: var(--panel); border: 1px solid var(--border4);
  box-shadow: 0 24px 48px -20px rgba(0, 0, 0, .75); }
.adm-period.is-open .adm-period__menu,
.shopcard__period.is-open .shopcard__period-menu { display: block; }
.adm-period__menu a, .adm-period__menu button,
.shopcard__period-menu a, .shopcard__period-menu button {
  display: flex; align-items: center; gap: var(--sp-item); width: 100%; padding: var(--sp-item) var(--sp-item);
  border: 0; background: none; text-align: left; cursor: pointer;
  border-radius: 8px; font: inherit; font-size: var(--fs-row); color: var(--text2);
  transition: background .14s ease; }
.adm-period__menu a:hover, .adm-period__menu button:hover,
.shopcard__period-menu a:hover, .shopcard__period-menu button:hover {
  background: var(--hover); color: var(--text); }
.adm-period__menu a.is-active, .shopcard__period-menu a.is-active {
  color: var(--text); font-weight: 500; }
/* Галочка держит свою колонку и у невыбранных пунктов — иначе подписи
   разъезжаются по горизонтали при каждом переключении. */
.adm-period__check, .shopcard__period-check { width: 14px; flex: 0 0 14px;
  color: var(--green); }
.adm-period__sep, .shopcard__period-sep { height: 1px; background: var(--line);
  margin: var(--sp-tight) var(--sp-item); }

/* ── Подсказка точки графика ─────────────────────────────────────────────
   Коробка одна на все графики панели: у финансового блока магазина внутри
   приход/расход/маржа, у графика расхода клиента — запуски и токены. Общее
   тут — сама всплывающая карточка, и она обязана выглядеть одинаково.
   Позицию ставит скрипт (тултип ведётся за курсором), поэтому здесь только
   вид. `.shopcard__chart-tip` — псевдоним для разметки финансового блока. */
.adm-charttip, .shopcard__chart-tip { position: absolute; top: 0; z-index: 5;
  min-width: 172px; padding: var(--sp-row) var(--sp-row); border-radius: 11px; pointer-events: none;
  background: var(--panel); border: 1px solid var(--border4);
  box-shadow: 0 18px 36px -18px rgba(0, 0, 0, .8); }
.adm-charttip[hidden], .shopcard__chart-tip[hidden] { display: none; }
.adm-charttip__date, .shopcard__chart-tip-date { font-size: var(--fs-body);
  font-weight: 600; margin-bottom: var(--sp-item); color: var(--text); }
.adm-charttip > div:not(.adm-charttip__date),
.shopcard__chart-tip > div:not(.shopcard__chart-tip-date) { display: flex;
  align-items: baseline; justify-content: space-between; gap: var(--sp-block);
  font-size: var(--fs-body); padding: var(--sp-hair) 0; color: var(--muted2); }
.adm-charttip span:last-child, .shopcard__chart-tip span:last-child {
  font-variant-numeric: tabular-nums; color: var(--text2); white-space: nowrap; }
/* Итоговая строка отделена линией: она подводит черту под перечислением
   выше, а не продолжает его. */
.adm-charttip__total, .shopcard__chart-tip-net { margin-top: var(--sp-tight); padding-top: var(--sp-snug);
  border-top: 1px solid var(--rowline); }
.adm-charttip__total span:last-child,
.shopcard__chart-tip-net span:last-child { font-weight: 600; }

/* ── Экран настроек (`admin/settings.html`) ──────────────────────────────
   Двадцать шесть числовых полей — пульт, а не форма записи: слева липкое
   оглавление с поиском и единственной кнопкой применения, справа секции.
   Строка параметра — три колонки: «что это» (подпись и ключ), «сколько»
   (значение с единицей и дефолтом) и «зачем» (подсказка сбоку, а не под
   полем: под полем она вдвое удлиняет список, и её перестают читать). */
.adm-set { display: grid; grid-template-columns: 212px minmax(0, 1fr);
  gap: var(--sp-gutter); align-items: start; }
.adm-set__toc { position: sticky; top: calc(var(--header-h) + var(--sp-gutter)); display: flex;
  flex-direction: column; gap: var(--sp-hair); }
.adm-set__search { margin-bottom: var(--sp-item); }
.adm-set__search input { width: 100%; background: var(--sunken);
  border: 1px solid var(--border2); border-radius: var(--radius-control);
  padding: var(--sp-item) var(--sp-row) var(--sp-item) var(--field-icon);
  color: var(--text); font: inherit; font-size: var(--fs-row);
  outline: none; background-image: var(--search-icon);
  /* Размер обязателен: у иконки в data-URI нет собственных width/height, и
     без него браузер растягивает её на всю высоту поля. */
  background-repeat: no-repeat; background-position: left var(--sp-row) center;
  background-size: 16px 16px; }
.adm-set__search input:hover { border-color: var(--border4); }
.adm-set__search input:focus { border-color: var(--brand); }
.adm-set__nav { display: flex; flex-direction: column; gap: var(--sp-hair); }
.adm-set__nav-item { position: relative; display: flex; align-items: center;
  gap: var(--sp-item); padding: var(--sp-snug) var(--sp-row); border-radius: 8px; font-size: var(--fs-row);
  color: var(--muted2); transition: background .16s ease, color .16s ease; }
.adm-set__nav-item:hover { background: var(--hover); color: var(--text); }
.adm-set__nav-item::before { content: ""; position: absolute; left: 0;
  top: 7px; bottom: 7px; width: 2.5px; border-radius: 2px; background: transparent; }
.adm-set__nav-item.is-active { color: var(--text); font-weight: 500;
  background: rgba(47, 203, 116, .10); }
.adm-set__nav-item.is-active::before { background: var(--brand); }
.adm-set__nav-count { margin-left: auto; font-size: var(--fs-eyebrow); font-weight: 600;
  color: var(--muted4); background: var(--chip); border-radius: 20px; padding: var(--sp-hair) var(--sp-snug); }
.adm-set__nav-item.is-dirty .adm-set__nav-count { color: var(--warn);
  background: rgba(241, 163, 59, .10); }
/* Секция, из которой поиск не оставил ни одного поля, скрывается целиком —
   вместе со своим пунктом оглавления: пункт, ведущий в пустоту, хуже, чем
   его отсутствие. */
.adm-set__nav-item[hidden], .adm-set-card[hidden] { display: none; }

.adm-set__apply { margin-top: var(--sp-row); padding-top: var(--sp-row);
  border-top: 1px solid var(--border); display: flex; flex-direction: column; gap: var(--sp-item); }
.adm-set__state { display: flex; align-items: baseline; gap: var(--sp-item); }
.adm-set__state-dot { width: 7px; height: 7px; flex: 0 0 7px; border-radius: 50%;
  background: var(--faint); margin-top: var(--sp-tight); }
.adm-set.is-dirty .adm-set__state-dot { background: var(--warn); }
.adm-set__state-text { font-size: var(--fs-body); color: var(--muted3); font-weight: 500; }
.adm-set.is-dirty .adm-set__state-text { color: var(--warn); }
.adm-set__state-hint { margin-top: var(--sp-tight); font-size: var(--fs-hint); line-height: 1.45;
  color: var(--muted4); font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  text-wrap: pretty; }
.adm-set__undo { background: none; border: 0; font: inherit; font-size: var(--fs-body);
  color: var(--muted3); cursor: pointer; padding: 0; }
.adm-set__undo:hover { color: var(--text); }

.adm-set__body { display: flex; flex-direction: column; gap: var(--sp-gutter); min-width: 0; }
.adm-set-card { background: var(--panel); border: 1px solid var(--border);
  border-radius: var(--radius-card); overflow: hidden;
  /* Липкая шапка панели перекрыла бы заголовок секции при переходе по
     якорю из оглавления. */
  scroll-margin-top: calc(var(--header-h) + var(--sp-gutter)); }
.adm-set-card__head { display: flex; align-items: center; gap: var(--sp-row);
  padding: var(--sp-row) var(--sp-gutter); border-bottom: 1px solid var(--line); background: var(--sunken); }
.adm-set-card__abbr { width: 26px; height: 26px; flex: 0 0 26px; border-radius: 8px;
  display: flex; align-items: center; justify-content: center; font-size: var(--fs-eyebrow);
  font-weight: 700; color: var(--green); background: rgba(47, 203, 116, .10); }
.adm-set-card__title { font-size: var(--fs-head); font-weight: 600; }
.adm-set-card__note { font-size: var(--fs-hint); color: var(--muted3); }
.adm-set-card__dirty { margin-left: auto; font-size: var(--fs-hint); font-weight: 600;
  color: var(--warn); background: rgba(241, 163, 59, .10);
  border: 1px solid rgba(241, 163, 59, .25); border-radius: 20px; padding: var(--sp-hair) var(--sp-item); }

.adm-set-row { display: grid; grid-template-columns: minmax(230px, .9fr) 168px minmax(0, 2fr);
  gap: var(--sp-section); align-items: start; padding: var(--sp-row) var(--sp-gutter);
  border-bottom: 1px solid var(--rowline); transition: background .14s ease; }
.adm-set-row:last-child { border-bottom: 0; }
/* Зебра — тем же `--stripe`, что и в таблицах панели, но по классу от
   скрипта, а не по `nth-child`: после фильтрации поиском в секции остаются
   строки вразбивку, и чередование, посчитанное браузером по позиции в
   разметке, дало бы две одинаковые полосы подряд. Правила наведения и
   «изменено» идут ниже с той же специфичностью и потому перебивают её. */
.adm-set-row.is-even { background: var(--stripe); }
.adm-set-row:hover { background: var(--hover); }
.adm-set-row.is-dirty { background: rgba(241, 163, 59, .04); }
.adm-set-row[hidden] { display: none; }
.adm-set-row__head { display: flex; align-items: baseline; gap: var(--sp-item);
  padding-top: var(--sp-item); min-width: 0; }
.adm-set-row__dot { width: 5px; height: 5px; flex: 0 0 5px; border-radius: 50%;
  background: var(--faint); margin-top: var(--sp-snug); }
.adm-set-row.is-dirty .adm-set-row__dot { background: var(--warn); }
.adm-set-row__label { display: block; font-size: var(--fs-row); color: var(--text);
  text-wrap: pretty; cursor: pointer; }
.adm-set-row__key { margin-top: var(--sp-tight); font-size: var(--fs-hint); color: var(--muted4);
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace; }
.adm-set-row__control { display: flex; flex-direction: column; gap: var(--sp-snug); }
.adm-set-input { display: flex; align-items: center; background: var(--sunken);
  border: 1px solid var(--border2); border-radius: var(--radius-control);
  overflow: hidden; transition: border-color .16s ease; }
.adm-set-input:hover { border-color: var(--border4); }
.adm-set-input:focus-within { border-color: var(--brand); }
.adm-set-row.is-dirty .adm-set-input { border-color: rgba(241, 163, 59, .45); }
.adm-set-input input { width: 100%; min-width: 0; background: transparent;
  border: 0; padding: var(--sp-item) var(--sp-tight) var(--sp-item) var(--sp-row); color: var(--text); font: inherit;
  font-size: var(--fs-row); font-weight: 500; text-align: right; outline: none;
  font-variant-numeric: tabular-nums; }
/* Спиннеры `type=number` в ячейке шириной 168px съедают треть поля и
   меняются колесом мыши случайно — значение здесь набирают, а не докручивают. */
.adm-set-input input::-webkit-outer-spin-button,
.adm-set-input input::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; }
.adm-set-input input[type="number"] { -moz-appearance: textfield; appearance: textfield; }
.adm-set-input__unit { padding: 0 var(--sp-item) 0 var(--sp-snug); font-size: var(--fs-hint);
  color: var(--muted3); white-space: nowrap; }
.adm-set-row__default { display: flex; align-items: center; gap: var(--sp-item);
  font-size: var(--fs-hint); color: var(--muted4); }
.adm-set-row__reset { background: none; border: 0; padding: 0; font: inherit;
  font-size: var(--fs-hint); font-weight: 500; color: var(--green); cursor: pointer; }
.adm-set-row__reset:hover { color: var(--brand-hover); }
.adm-set-row.is-invalid .adm-set-input { border-color: var(--err); }

.adm-set-row__hint { padding-top: var(--sp-snug); font-size: var(--fs-body); line-height: 1.55;
  color: var(--muted3); text-wrap: pretty; }
/* Длинные подсказки свёрнуты до трёх строк: иначе строка параметра
   растягивается на пол-экрана и таблица перестаёт читаться как таблица.
   Кнопку «подробнее» показывает `js/settings.js` — и только там, где текст
   действительно обрезан. */
.adm-set-row__hint-text { display: -webkit-box; -webkit-box-orient: vertical;
  -webkit-line-clamp: 3; line-clamp: 3; overflow: hidden; }
.adm-set-row__hint-text.is-open { display: block; overflow: visible; }
.adm-set-row__more { background: none; border: 0; padding: 0; margin-left: var(--sp-tight);
  font: inherit; font-size: var(--fs-body); color: var(--muted2); cursor: pointer;
  border-bottom: 1px dotted var(--faint); }
.adm-set-row__more:hover { color: var(--text); }

.adm-set__empty { background: var(--panel); border: 1px solid var(--border);
  border-radius: var(--radius-card); padding: var(--sp-page) var(--sp-section); text-align: center; }
.adm-set__empty-title { font-size: var(--fs-head); font-weight: 600; }
.adm-set__empty-hint { margin-top: var(--sp-item); font-size: var(--fs-row); color: var(--muted2); }

/* Узкий экран: оглавление перестаёт быть липкой колонкой и уезжает наверх —
   иначе на 1100px значение и подсказка делят триста пикселей на двоих. */
@media (max-width: 1180px) {
  .adm-set { grid-template-columns: minmax(0, 1fr); }
  .adm-set__toc { position: static; }
  .adm-set-row { grid-template-columns: minmax(0, 1fr) 168px; }
  .adm-set-row__hint { grid-column: 1 / -1; padding-top: 0; }
}

/* Неактивная кнопка панели: применять нечего — нажимать не на что. Общее
   правило для всех кнопок, а не только для «Применить»: до сих пор
   `disabled` у `.adm-btn` нигде не встречался и выглядел бы как обычная
   активная кнопка, которая молча не работает. */
.adm-btn[disabled] { opacity: .45; cursor: default; }
.adm-btn--primary[disabled]:hover { background: var(--brand); transform: none; }

/* Модальное подтверждение (первое в панели; экран подтверждения действий
   над записями — отдельная страница и остаётся как есть). Нативный
   `<dialog>`: фокус, Esc и подложка достаются от браузера. */
.adm-modal { width: min(420px, calc(100vw - var(--sp-page))); padding: var(--sp-section);
  background: var(--panel); color: var(--text); border: 1px solid var(--border);
  border-radius: var(--radius-card); box-shadow: 0 24px 60px rgba(0, 0, 0, .35); }
.adm-modal::backdrop { background: rgba(7, 10, 9, .55); backdrop-filter: blur(2px); }
.adm-modal__title { font-size: var(--fs-lead); font-weight: 600; }
.adm-modal__note { margin-top: var(--sp-snug); font-size: var(--fs-body); line-height: 1.5;
  color: var(--muted3); text-wrap: pretty; }
.adm-modal__list { list-style: none; margin: var(--sp-block) 0 0; padding: 0;
  display: flex; flex-direction: column; gap: var(--sp-hair);
  /* Правок может быть все двадцать шесть — список прокручивается сам,
     чтобы диалог не выехал за экран вместе с кнопками. */
  max-height: 44vh; overflow-y: auto; }
.adm-modal__list li { display: flex; align-items: baseline; gap: var(--sp-item);
  padding: var(--sp-item); border-radius: 8px; background: var(--sunken); font-size: var(--fs-body); }
.adm-modal__item-name { min-width: 0; color: var(--text2); text-wrap: pretty; }
.adm-modal__item-change { margin-left: auto; white-space: nowrap;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: var(--fs-body); color: var(--warn); }
/* ── Опасная зона ────────────────────────────────────────────────────────
   Одна на карточку магазина и карточку клиента: рамка тревожного тона,
   строка «что произойдёт» с кнопкой справа и каскад удаления сеткой
   счётчиков. `.shopcard__panel--danger`/`.shopcard__danger-head` и соседи —
   прежние имена того же места на карточке магазина.

   Раздел показывается всем с правом просмотра, гейтится только кнопка:
   оператор обязан понимать последствия удаления, даже если выполнить его не
   вправе. */
.adm-danger, .shopcard__panel--danger { border-color: rgba(241, 98, 111, .22); }
.adm-danger .adm-card__head { background: rgba(241, 98, 111, .05); }
.adm-danger .adm-card__head h2 { color: var(--err); }
.adm-danger__head, .shopcard__danger-head { display: flex; align-items: center;
  gap: var(--sp-row); padding: var(--sp-row) var(--sp-gutter); }
.adm-danger__head > div, .shopcard__danger-head > div { flex: 1; min-width: 0; }
.adm-danger__head > div > div:first-child,
.shopcard__danger-head > div > div:first-child { font-size: var(--fs-row);
  font-weight: 500; color: var(--text); }
.adm-danger__head > div > div + div { margin-top: var(--sp-hair); line-height: 1.45; }
.adm-danger .adm-btn, .shopcard__panel--danger .adm-btn { flex: 0 0 auto; }

/* Каскад удаления — сетка счётчиков: решает количество, а во фразе через
   запятую числа тонут между подписями. Плитка на связь, число крупно. */
.adm-cascade, .shopcard__cascade { padding: 0 var(--sp-gutter) var(--sp-block); }
.adm-cascade__label, .shopcard__cascade-label { font-size: var(--fs-eyebrow);
  font-weight: 600; letter-spacing: .07em; text-transform: uppercase;
  color: var(--muted4); margin-bottom: var(--sp-item); }
.adm-cascade__grid, .shopcard__cascade-grid { display: grid;
  grid-template-columns: repeat(3, 1fr); gap: var(--sp-snug); }
.adm-cascade__item, .shopcard__cascade-item { padding: var(--sp-item) var(--sp-item) var(--sp-item);
  border-radius: 10px; background: rgba(241, 98, 111, .06);
  border: 1px solid rgba(241, 98, 111, .18); }
.adm-cascade__item strong, .shopcard__cascade-item strong { display: block;
  font-size: var(--fs-lead); font-weight: 600; line-height: 1;
  letter-spacing: -.02em; color: var(--err); font-variant-numeric: tabular-nums; }
.adm-cascade__item span, .shopcard__cascade-item span { display: block;
  margin-top: var(--sp-tight); font-size: var(--fs-eyebrow); line-height: 1.25;
  color: var(--muted3); }
/* Пустая связь остаётся на месте, но гаснет: убрать её значило бы показывать
   разный список у разных записей, а сохранить красной — пугать нулём. */
.adm-cascade__item.is-empty, .shopcard__cascade-item.is-empty {
  background: transparent; border-color: var(--line); }
.adm-cascade__item.is-empty strong, .shopcard__cascade-item.is-empty strong,
.adm-cascade__item.is-empty span, .shopcard__cascade-item.is-empty span {
  color: var(--muted4); }

/* Вариант пошире — под формы в несколько полей (правка блока карточки):
   базовые 420px режут двухколоночную пару «Начало/Конец» и длинные подписи
   прав. `.shopcard__modal` — прежнее имя того же места на карточке магазина. */
.adm-modal--wide, .shopcard__modal, #burn-range-modal {
  width: min(480px, calc(100vw - 32px)); }
.adm-modal--wide form, .shopcard__modal form, #burn-range-modal form {
  display: flex; flex-direction: column; gap: var(--sp-block); }
.adm-modal__actions { margin-top: var(--sp-block); display: flex; justify-content: flex-end; gap: var(--sp-item); }
/* Две границы диапазона — рядом: они одна настройка, а не две. Правило общее,
   потому что диалог «Настроить диапазон» открывают два блока карточки —
   финансовый и «Расход токенов»; `.shopcard__range` осталось прежним именем
   того же места. */
.adm-modal__range, .shopcard__range { display: grid;
  grid-template-columns: 1fr 1fr; gap: var(--sp-block); }

/* ── Составные ячейки цен закупа ───────────────────────────────────────────
   Раздел — история датированных срезов, и строка отвечает на три вопроса:
   что за товар, сколько стоит относительно прошлого среза, действует ли эта
   цена сегодня. Примитивы те же, что у отчётов (`.adm-report` для ключа с
   подписью, `.adm-figure` для числа с объяснением): срез цены и строка отчёта
   стоят в панели рядом по смыслу, и разные метрики в них читались бы как
   разная важность.

   Ширину забирает колонка товара (`width: 100%`) — ключ товара единственное
   значение раздела, длину которого мы не контролируем (у Яндекса это артикул
   продавца, а не число). Цена и период ужимаются по содержимому. */
.adm-table td.adm-cell--purchase, .adm-table td.adm-cell--purchase-price,
.adm-table td.adm-cell--purchase-period {
  white-space: normal; padding: var(--sp-row) var(--sp-row);
  font-size: inherit; color: inherit; vertical-align: middle; }
.adm-table td.adm-cell--purchase { width: 100%; min-width: 200px; max-width: none; }
.adm-table td.adm-cell--purchase-price,
.adm-table td.adm-cell--purchase-period { width: 1%; }
.adm-cell--purchase-price .adm-figure { align-items: flex-end; }
/* Подписи обеих правых колонок — в одну строку: `width: 1%` ужимает столбец
   до содержимого, и перенос разорвал бы «+60,00 ₽ к 200,00 ₽» пополам, сделав
   строки таблицы разной высоты. */
.adm-cell--purchase-price .adm-figure__meta,
.adm-cell--purchase-period .adm-figure__meta { white-space: nowrap; }
/* Действующий срез — зелёным: это ответ на вопрос, с которым в раздел
   заходят. Будущий — синим (он ни на что ещё не влияет, но ошибкой не
   является: цены заводят заранее), заменённый остаётся приглушённым, как
   любая история. */
.adm-figure__meta.is-ok { color: var(--ok); font-weight: 500; }
.adm-figure__meta.is-info { color: var(--link); font-weight: 500; }

/* ── Составные ячейки бэкапов цен ──────────────────────────────────────────
   Те же примитивы, что у отчётов и цен закупа (`.adm-report` для момента с
   подписью, `.adm-figure` для числа, `.adm-state` для бейджа с объяснением) —
   бэкап стоит в панели рядом с ними по смыслу: это ещё одна операция над
   ценами магазина, и разные метрики читались бы как разная важность.

   Ширину забирает колонка состояния: в ней лежит текст ошибки операции,
   единственное значение раздела, длину которого мы не контролируем. */
.adm-table td.adm-cell--backup, .adm-table td.adm-cell--backup-state,
.adm-table td.adm-cell--backup-apply, .adm-table td.adm-cell--backup-restore {
  white-space: normal; padding: var(--sp-row) var(--sp-row);
  font-size: inherit; color: inherit; vertical-align: middle; }
.adm-table td.adm-cell--backup-state { width: 100%; min-width: 220px; max-width: none; }
.adm-table td.adm-cell--backup, .adm-table td.adm-cell--backup-apply,
.adm-table td.adm-cell--backup-restore { width: 1%; }
.adm-cell--backup-apply .adm-figure { align-items: flex-end; }
.adm-cell--backup-apply .adm-figure__meta,
.adm-cell--backup-restore .adm-figure__meta { white-space: nowrap; }
/* Срок хранения тревожен только у истекающих: вместе с бэкапом исчезает
   возможность отката к нему, и это «требует решения», а не «уже сломано» —
   отсюда янтарь, а не красный. */
.adm-backup__ttl.is-warn { color: var(--warn); font-weight: 500; }
/* Текст ошибки операции — в одну строку с многоточием: полный лежит в
   подсказке и на карточке, а перенос сделал бы строки таблицы разной высоты. */
.adm-cell--backup-state .adm-figure__meta { white-space: nowrap; overflow: hidden;
  text-overflow: ellipsis; max-width: 46ch; }

/* Шкала доли — «сколько из скольких». Общая, не только для бэкапов: полоска
   отвечает на вопрос «какая часть сделана», и заводить её второй раз в
   следующем разделе значило бы развести метрики. Ширина фиксированная, как у
   шкалы прибыли: доли сравнивают между строками, а плавающая ширина сделала
   бы это сравнение невозможным. */
.adm-meter { display: inline-block; vertical-align: middle; width: 56px; height: 3px;
  border-radius: 2px; background: var(--track); overflow: hidden; }
.adm-meter__fill { display: block; height: 100%; background: var(--green); }
.adm-meter.is-warn .adm-meter__fill { background: var(--warn); }
.adm-meter.is-err .adm-meter__fill { background: var(--err); }
