/* Общие части морды dev2mp. Значений здесь нет — только из `tokens.css`.
   Что понадобилось второму разделу, переезжает сюда; копия в разделе это
   второй набор политик, а не удобство (см. `DESIGN.md`).

   Имена классов без приставки: файл один на платформу, и `d2-btn` внутри
   `apps/ui` означал бы, что рядом живёт чей-то ещё `btn`. Не живёт. */

* { box-sizing: border-box }
body {
  margin: 0;
  font: var(--t-m)/1.45 var(--font);
  color: var(--ink);
  background: var(--bg);
  -webkit-font-smoothing: antialiased;
}
a { color: inherit; text-decoration: none }
button { font: inherit; cursor: pointer }

/* Видимая обводка по клавиатуре — не украшение: без неё по табу не пройти,
   а формой прав пользуются именно с клавиатуры. */
:focus-visible { outline: 2px solid var(--ink); outline-offset: 2px; border-radius: 6px }
:root[data-theme="dark"] :focus-visible { outline-color: var(--info-fg) }

@media (prefers-reduced-motion: reduce) { *, *::before, *::after { transition: none !important } }
/* Смена темы — не анимация: переходы гасятся на один кадр, иначе страница
   переползает из светлой в тёмную посекундно и выглядит сломанной. */
:root[data-switching] * { transition: none !important }


/* ── дальше — ЧАСТИ, и они действуют внутри `.ui` ───────────────────
   Выше — правила площадки: шрифт страницы, обводка по клавиатуре, уважение
   к «меньше движения». Они общие, потому что это не части, а условия.

   Части — словарь платформы: `btn`, `row`, `chip`, `panel`, `tile`. Имена
   намеренно короткие, и ровно поэтому их нельзя оставлять глобальными:
   оба раздела выбрали такие же ещё до появления кита, и китовое правило
   дотягивалось до чужой разметки там, где раздел этого свойства не задаёт.
   Так полоса состояния разведки растянулась на весь экран (11.09.2026).

   `:where(.ui)` — область БЕЗ веса: правила разделов по-прежнему выигрывают
   своей вложенностью, а кит действует там, где его позвали. Раздел, решивший
   пользоваться китом, ставит `ui` на своё поле. */
/* ── кнопки ────────────────────────────────────────────────────────
   Четыре вида, и пятый заводить не надо: каждый новый снимает у соседей
   часть смысла. Обычная — «посмотреть, перейти». Чёрная — главное действие
   формы. Оранжевая — действие, меняющее мир. Красная — отнимающее. */
:where(.ui) .btn {
  display: inline-flex; align-items: center; gap: 7px;
  padding: 9px 14px; border-radius: var(--r);
  border: 1px solid var(--line); background: var(--card);
  font-size: var(--t-b); font-weight: 700; color: var(--ink);
  transition: background .12s, border-color .12s;
}
:where(.ui) .btn:hover { background: var(--soft) }
:where(.ui) .btn svg { width: 15px; height: 15px; flex-shrink: 0 }
/* Знак из `icon()` — блоком по своему рисунку, а не строкой текста: иначе
   обёртка берёт высоту строки, и знак встаёт на базовую линию выше центра.
   Только там, где знак стоит рядом с подписью в ряд. Меню оболочки делает
   то же у себя, называя высоту строки явно (`shell.css`, 29.10.2026). */
:where(.ui) .btn > .ui-ico, :where(.ui) .page-head .ico > .ui-ico,
:where(.ui) .ui-menu-item > .ui-ico, :where(.ui) .notice > .ico > .ui-ico { display: inline-flex; flex-shrink: 0 }
:where(.ui) .btn.primary { background: var(--ink); border-color: var(--ink); color: var(--card) }
:where(.ui) .btn.primary:hover { filter: brightness(1.25) }
:where(.ui) .btn.accent { background: var(--accent-soft); border-color: var(--accent-line); color: var(--accent-ink) }
:where(.ui) .btn.accent:hover { border-color: var(--accent) }
/* Граница у красной кнопки — `--bad-line`, а не `--bad-bg`: фон на белом
   неразличим, и кнопка читалась надписью. Особенно это било по крестику
   в строке списка, где вся кнопка — один символ. */
:where(.ui) .btn.danger { color: var(--bad-fg); border-color: var(--bad-line) }
/* Под курсором — СПЛОШНАЯ заливка, как у пункта меню «Удалить…» (решение
   владельца 03.10.2026: «везде должно быть одинаковое поведение, как в ките»).
   До того кнопка под курсором лишь розовела, а пункт меню с тем же смыслом
   заливался красным — одно действие выглядело по-разному в двух местах. */
:where(.ui) .btn.danger:hover { background: var(--bad-fg); border-color: var(--bad-fg); color: var(--card) }
:where(.ui) .btn.ghost { border-color: transparent; color: var(--ink-2); font-weight: 600 }
:where(.ui) .btn.ghost:hover { color: var(--ink) }
:where(.ui) .btn.small { padding: 6px 11px; font-size: var(--t-s); border-radius: var(--r-s) }
:where(.ui) .btn[disabled] { opacity: .45; cursor: default; pointer-events: none }
/* Кнопка в работе НЕ становится «Подождите»: имя действия держится от нажатия
   до конца, иначе человек не уверен, что нажал именно то. */
:where(.ui) .btn[data-busy] { opacity: .6; cursor: progress; pointer-events: none }

/* ── взвод двумя кликами ───────────────────────────────────────────
   Не модалка: модалку посреди экрана отвечают «да» не глядя. Окно стоит
   у самой кнопки и перечисляет последствия поимённо. */
:where(.ui) .arm { position: relative; display: inline-flex }
:where(.ui) .armbox {
  /* ⚠️ `fixed`, а не `absolute`. Абсолют обрезает любой прокручиваемый
     предок, а у таблиц стоит `overflow-x: auto` — и по спецификации CSS
     вторая ось тогда тоже перестаёт быть `visible`. Координаты считает
     `place()` в `kit.js`. */
  position: fixed; z-index: 20;
  width: 300px; text-align: left;
  background: var(--warn-bg); border: 1px solid var(--accent-line);
  border-radius: var(--r); padding: 13px 15px;
  box-shadow: 0 8px 24px rgb(0 0 0 / .10);
  /* Окно лежит абсолютом и приземляется куда угодно — в том числе в правый
     край строки списка, где стоит `white-space: nowrap`, чтобы не переносились
     числа. Оттуда `nowrap` наследуется внутрь окна, и список последствий
     перестаёт переноситься ВООБЩЕ: строка «задача «…» с 29 якорями» уезжала
     за правый борт вместе с числом, ради которого окно и открывают. Окно —
     текстовый блок, где бы ни стояло. */
  white-space: normal;
}
:root[data-theme="dark"] :where(.ui) .armbox { box-shadow: 0 8px 24px rgb(0 0 0 / .5) }
:where(.ui) .armbox[hidden] { display: none }
:where(.ui) .armbox h4 { margin: 0 0 5px; font-size: var(--t-b); font-weight: 800; color: var(--warn-fg) }
/* Последствия переносятся ПО ЛЮБОМУ месту: в них попадают имена, которые
   человек сам и придумал, — «NEW_блузка_пайетки_бежевая» не разрывается ни
   по одному пробелу и уезжала за правый край окна вместе с концом фразы
   («…с 29 якорями»), то есть с тем самым числом, ради которого окно и открыли
   (поймано на снимке «Мониторинга» 12.09.2026). */
:where(.ui) .armbox ul { margin: 0 0 11px; padding-left: 17px; font-size: var(--t-s);
             color: var(--ink-2); line-height: 1.6; overflow-wrap: anywhere }
:where(.ui) .armbox .btns { display: flex; gap: 8px }
/* Стороны окна считает `place()` в `kit.js` и ставит КООРДИНАТАМИ: классов
   `up`/`left` больше нет — с `position: fixed` разворот выражается числами,
   а два способа сказать одно и то же однажды разойдутся.
   Ширина ужимается по экрану: на телефоне 300px не влезают ни с какого края,
   и без потолка окно уезжало бы за борт при любом развороте. */
:where(.ui) .armbox { max-width: calc(100vw - 24px) }

/* Взведённая кнопка ЗАЛИТА. Пока окно открыто, кнопка — уже не приглашение,
   а состояние: рука на спусковом крючке. Красная надпись на белом от обычной
   отличается только цветом текста, и на снимке было не видно, какая из двух
   десятков кнопок взведена. Текст — `--card`, а не белый: в тёмной теме
   `--bad-fg` СВЕТЛЫЙ, и белым по нему не прочесть. */
:where(.ui) .arm:has(.armbox:not([hidden])) > .btn.danger,
:where(.ui) .arm:has(.armbox:not([hidden])) > .btn.danger:hover {
  background: var(--bad-fg); border-color: var(--bad-fg); color: var(--card);
  /* Кольцо — потому что под курсором кнопка теперь залита ТАК ЖЕ (03.10.2026):
     без него «навёл» и «взвёл» выглядели бы одним состоянием. */
  box-shadow: 0 0 0 3px var(--bad-line);
}

/* И САМА строка, к которой относится окно. Окно перечисляет последствия
   поимённо, но не говорит, над чем сработает: в списке из двенадцати строк
   с одинаковыми кнопками это половина ответа. Строка красится тем же янтарным,
   что и окно, и помечается полосой слева — так видно, что это одно целое.
   Не красным: красная строка читается как «уже удалено». */
/* Взведённая строка — янтарная и с полосой слева, тем же цветом, что и окно
   «что уйдёт»: так видно, что это одно целое. Не красная — красная строка
   читается как «уже удалено». Здесь подсветка громче подсветки под курсором
   намеренно: курсор ходит по списку постоянно, а взвод это одно состояние
   на весь экран, и его надо найти глазами, а не искать. */
:where(.ui) .row:has(.armbox:not([hidden])),
:where(.ui) .row:has(.armbox:not([hidden])):hover,
:where(.ui) .table tbody tr:has(.armbox:not([hidden])),
:where(.ui) .table tbody tr:has(.armbox:not([hidden])):hover {
  background: var(--warn-bg);
}
:where(.ui) .row:has(.armbox:not([hidden])) { box-shadow: inset 3px 0 0 var(--accent) }
:where(.ui) .table tbody tr:has(.armbox:not([hidden])) td:first-child {
  box-shadow: inset 3px 0 0 var(--accent);
}

/* ── галочка ───────────────────────────────────────────────────────
   ОДНА на всю платформу. До 13.09.2026 правила здесь не было вовсе, и
   каждый раздел завёл своё: «Команда» чёрную, мониторинг и разведка
   оранжевую, старая морда оставалась на браузерной синей. Три вида
   получились не из трёх решений, а из одного отсутствующего.

   Чёрная — решение владельца 13.09.2026. Тот же `--ink`, что у главной
   кнопки и у знака; «Команда» так и рисовала. Оранжевой здесь быть
   не может по правилу самих токенов: оранжевый значит «действие,
   меняющее мир», и двадцать галочек в столбце обесценили бы его —
   тем более что действие рядом (кнопка выгрузки) как раз оранжевое.

   Если понадобится синяя (так было в старой морде, тон «к сведению»),
   менять здесь одну строку:
       accent-color: var(--info-fg);
   Разделу своей строки не заводить НИ В КАКОМ случае — ровно так
   и появились три вида.

   Форму рисует браузер: у нативной галочки настраивается только цвет,
   ни радиус, ни рамка. Это принято сознательно — рисованная своя стоила
   бы десятка строк и собственного состояния «отмечены не все». */
:where(.ui) input[type="checkbox"] {
  width: 16px; height: 16px; margin: 0; cursor: pointer;
  accent-color: var(--ink);
}
:where(.ui) input[type="checkbox"]:disabled { cursor: default; opacity: .5 }

/* ── плашки состояния ──────────────────────────────────────────────
   Тон присылает сервер: морда не сравнивает чисел, иначе её пороги
   разойдутся с порогами «Системы», и оба будут выглядеть правдой. */
/* ── НАЗВАНИЕ-ССЫЛКА ───────────────────────────────────────────────
   Заголовок строки, который ведёт на страницу этого предмета: бот → его
   раздел, магазин → его страница.

   ⚠️ Заведено потому, что `a` у нас НАРОЧНО голая (`color: inherit;
   text-decoration: none`, самая первая строка кита): ссылка внутри текста
   не должна пестрить. Но заголовок строки, ставший ссылкой, оказался
   НЕОТЛИЧИМ от обычного — и даже при наведении ничего не менялось.
   Замечание владельца 20.09.2026, сразу по двум местам: «Все боты»
   и список магазинов.

   Признака ДВА, и оба нужны. Постоянный — чтобы было видно, что ссылка
   тут есть, не наводя мышь: в таблице рядом стоят названия БЕЗ ссылки
   (бот платформы своего раздела не имеет), и отличить их надо глазом.
   При наведении — чтобы было понятно, что курсор уже на ней.

   ⚠️ **ЦВЕТ ТЕКСТА НЕ МЕНЯЕТСЯ** — работает только подчёркивание (выбор
   владельца 20.09.2026 из шести вариантов). Первым заходом здесь стоял
   акцентный `--accent-ink`, и это было ошибкой рода: оранжевый на платформе
   означает СОСТОЯНИЕ — «взведено», «на паузе», «изменено, но не сохранено».
   Ссылка не состояние: наводя мышь, человек ничего не меняет, и красить это
   единственным тревожным цветом значит тратить его на обычное действие.

   В ките, а не в разделе: понадобилось второму — переезжает сюда
   (то же правило, по которому переехали меню раздела и «ещё»). */
:where(.ui) .name-link {
  font-weight: 700;
  text-decoration: underline;
  text-decoration-color: var(--line);
  text-underline-offset: 3px;
  text-decoration-thickness: 1px;
  cursor: pointer;
  transition: text-decoration-color .12s, text-decoration-thickness .12s;
}
:where(.ui) .name-link:hover {
  text-decoration-color: var(--ink);
  text-decoration-thickness: 2px;
}

:where(.ui) .chip { display: inline-block; font-size: var(--t-cap); font-weight: 700;
        padding: 3px 9px; border-radius: 999px; white-space: nowrap }
:where(.ui) .chip.ok { background: var(--ok-bg); color: var(--ok-fg) }
:where(.ui) .chip.warn { background: var(--warn-bg); color: var(--warn-fg) }
:where(.ui) .chip.bad { background: var(--bad-bg); color: var(--bad-fg) }
:where(.ui) .chip.info { background: var(--info-bg); color: var(--info-fg) }
:where(.ui) .chip.idle { background: var(--soft); color: var(--ink-2) }
/* Отметка площадки (`mpMark`) — в цветах площадки, а не тоном: она говорит
   «где», а не «как дела». Неизвестная площадка рисуется `idle` — подписью
   без цвета, а не пустым местом. */
:where(.ui) .chip.mp-wb { background: var(--mp-wb); color: var(--mp-ink) }
:where(.ui) .chip.mp-ozon { background: var(--mp-ozon); color: var(--mp-ink) }
/* Сплошная — не «плохо», а «ВЫКЛЮЧЕНО»: состояние, из-за которого строка
   перестала считаться. Мягкие тона выше говорят «посмотри», этот — «уже
   случилось», и он единственный, кому положено быть громче своей строки:
   погашенная строка гасит всё вокруг, а причину гашения — не должна.

   Заливка тем же `--bad-fg`, которым платформа красит красный текст: нового
   цвета не заводим, иначе на одной строке окажется два разных красных —
   у плашки свой, у чисел свой. Текст `--card`, и это не «белый»: в тёмной
   теме `--bad-fg` светлеет, и тёмная карточка на нём читается лучше белого
   (6,3:1 против 5,4:1 на светлой). */
:where(.ui) .chip.solid { background: var(--bad-fg); color: var(--card) }
/* `unknown` — не «почти хорошо», а честное «данных нет», и выглядит оно
   так же: пунктир вместо заливки. Без своего правила плашка приходила голым
   текстом, а точка — вовсе невидимой: разметка на месте, вид сломан. */
:where(.ui) .chip.unknown { background: transparent; color: var(--mute);
          border: 1px dashed var(--line); padding: 2px 8px }

:where(.ui) .dot { width: 8px; height: 8px; border-radius: 50%; flex-shrink: 0; display: inline-block }
:where(.ui) .dot.ok { background: var(--ok-fg) }
:where(.ui) .dot.warn { background: var(--warn-fg) }
:where(.ui) .dot.bad { background: var(--bad-fg) }
:where(.ui) .dot.idle { background: var(--mute) }
:where(.ui) .dot.unknown { background: transparent; border: 1.5px dashed var(--mute) }

/* ── карточка и панель ─────────────────────────────────────────────
   Радиус один на всё — так в образце. Тень только у того, что всплывает
   над страницей (окно взвода): тень под каждой карточкой превращает
   список в стопку бумаг. */
:where(.ui) .card, :where(.ui) .panel { background: var(--card); border: 1px solid var(--line); border-radius: var(--r) }
:where(.ui) .panel { overflow: hidden }
/* Пока открыто окно взвода, панель НЕ режет: `overflow: hidden` нужен ей,
   чтобы таблица и строки не вылезали из скруглённых углов, но окно «что
   уйдёт» лежит абсолютом и обрезалось по нижнему краю панели — список
   последствий было не дочитать, а он и есть вся работа этого окна
   (поймано на снимке «Команды» 12.09.2026; та же беда была у «Системы»).
   Углы теряют обрезку только на время, пока окно открыто, — то есть ровно
   тогда, когда смотрят не на углы. */
:where(.ui) .panel:has(.armbox:not([hidden])) { overflow: visible }
:where(.ui) .panel-head { display: flex; align-items: center; gap: 10px;
              padding: 15px 18px; border-bottom: 1px solid var(--line) }
:where(.ui) .panel-head h2 { margin: 0; font-size: var(--t-h); font-weight: 800 }
:where(.ui) .panel-head .n { font-size: var(--t-s); font-weight: 700; color: var(--mute) }
:where(.ui) .panel-head .side { margin-left: auto; display: flex; gap: 8px; align-items: center }

/* ── плитка с числом ───────────────────────────────────────────────
   Надпись прописными — она называет МЕРУ («прогонов за сутки»), а не
   заглавие. Над заголовками прописных не бывает: там это украшение. */
:where(.ui) .tile { background: var(--card); border: 1px solid var(--line);
        border-radius: var(--r); padding: 15px 17px }
:where(.ui) .tile .cap { display: flex; align-items: center; justify-content: space-between; gap: 10px;
             font-size: var(--t-cap); font-weight: 700; letter-spacing: .05em;
             text-transform: uppercase; color: var(--ink-2) }
:where(.ui) .tile .cap .b { width: 26px; height: 26px; border-radius: var(--r-s);
                display: grid; place-items: center; flex-shrink: 0 }
:where(.ui) .tile .cap .b svg { width: 14px; height: 14px }
:where(.ui) .tile .num { font-size: var(--t-num); font-weight: 800; line-height: 1.2;
             margin-top: 8px; letter-spacing: -.02em }
/* ⚠️ `flex-wrap` — ради ДВУХ подписей в плитке (первой такую завели «Отзывы
   WB»: «вернутся опросом» слева, «не присылали 2» справа). На телефоне плитки
   идут по две в ряд, и без переноса обе подписи ломались по букве —
   «вернутся / опросом». Там, где подпись одна, переносить нечего, и правило
   ничего не меняет. */
:where(.ui) .tile .sub { display: flex; justify-content: space-between; gap: 8px;
             flex-wrap: wrap;
             font-size: var(--t-s); color: var(--ink-2); margin-top: 6px }
:where(.ui) .tile.ok .num { color: var(--ok-fg) }
:where(.ui) .tile.warn .num { color: var(--warn-fg) }
:where(.ui) .tile.bad .num { color: var(--bad-fg) }
:where(.ui) .b.ok { background: var(--ok-bg); color: var(--ok-fg) }
:where(.ui) .b.warn { background: var(--warn-bg); color: var(--warn-fg) }
:where(.ui) .b.bad { background: var(--bad-bg); color: var(--bad-fg) }
:where(.ui) .b.info { background: var(--info-bg); color: var(--info-fg) }
:where(.ui) .b.idle { background: var(--soft); color: var(--ink-2) }

/* ── полоса хода ───────────────────────────────────────────────────
   Серая, не цветная: ход работы — не оценка. Цвет тут спорил бы с тоном,
   который рядом уже сказан плашкой. */
:where(.ui) .track { height: 6px; border-radius: 999px; background: var(--soft); overflow: hidden }
:where(.ui) .track > i { display: block; height: 100%; border-radius: 999px; background: var(--ink-2) }

/* ── таблица ───────────────────────────────────────────────────────
   Числовые столбцы прижаты вправо (решение владельца 08.09.2026): ширину
   столбцу задаёт его самое длинное значение, а обычные строки вдвое короче,
   и при выравнивании влево эта разница висит пустотой внутри столбца. */
:where(.ui) .table { width: 100%; border-collapse: collapse; font-size: var(--t-b) }
:where(.ui) .table th { text-align: left; padding: 10px 16px; white-space: nowrap;
            font-size: var(--t-cap); font-weight: 700; letter-spacing: .04em;
            text-transform: uppercase; color: var(--ink-2);
            border-bottom: 1px solid var(--line) }
:where(.ui) .table td { padding: 12px 16px; border-bottom: 1px solid var(--line-soft); vertical-align: middle }
:where(.ui) .table tr:last-child td { border-bottom: none }
:where(.ui) .table tbody tr:hover { background: var(--hover) }
:where(.ui) .table .num, :where(.ui) .table th.num { text-align: right; font-variant-numeric: tabular-nums }

/* ── строка списка ─────────────────────────────────────────────────
   Отдельно от таблицы: в списке у строки есть своя вторая строка пояснения,
   и таблицей это верстается вдвое длиннее. */
:where(.ui) .row { display: flex; align-items: center; gap: 13px;
       padding: 13px 18px; border-bottom: 1px solid var(--line-soft) }
:where(.ui) .row:last-child { border-bottom: 0 }
/* Строка под курсором — просто заливка во всю ширину. Пробовали громче
   (контур по строке, потом плашка со скруглением внутри неё) — владелец
   вернул к этому варианту 13.09.2026: в списке, где строк дюжина и все они
   ссылки, заметная подсветка перетягивает на себя внимание с самих строк. */
:where(.ui) .row:hover { background: var(--hover) }
:where(.ui) .row .main { flex: 1; min-width: 0 }
:where(.ui) .row .t1 { font-size: 13.5px; font-weight: 700; overflow: hidden;
           text-overflow: ellipsis; white-space: nowrap }
:where(.ui) .row .t2 { display: flex; align-items: center; gap: 7px;
           font-size: var(--t-s); color: var(--ink-2); margin-top: 2px }
:where(.ui) .row .side { display: flex; align-items: center; gap: 12px; flex-shrink: 0 }

/* ── форма ─────────────────────────────────────────────────────────
   Ошибка 422 приходит от сервера с именем поля и встаёт ПОД своим полем.
   Сводка сверху — только для ошибок, которые не относятся ни к одному
   полю: иначе человек читает список сверху и ищет, к чему он. */
:where(.ui) .field { display: block; margin-bottom: 16px }
:where(.ui) .field > .lbl { display: block; font-size: var(--t-b); font-weight: 700; margin-bottom: 6px }
:where(.ui) .field > .note { display: block; font-size: var(--t-s); color: var(--ink-2); margin: -2px 0 6px }
:where(.ui) .input {
  width: 100%; padding: 9px 12px; border-radius: var(--r);
  border: 1px solid var(--line); background: var(--card); color: var(--ink);
  font: inherit; font-size: var(--t-b);
}
:where(.ui) .input::placeholder { color: var(--mute) }
:where(.ui) .input:focus { border-color: var(--ink); outline: none }
/* НЕДОСТУПНОЕ ПОЛЕ отвечает «что тут стоит» и не просит решения. Своего вида
   у него не было: браузеры красят `disabled` по-разному, а в тёмной теме
   нативное приглушение сливается с фоном — поле выглядело обычным, и человек
   не понимал, почему в него не печатается. */
:where(.ui) .input:disabled {
  color: var(--mute); background: var(--soft);
  border-color: var(--line-soft); cursor: default;
}
:where(.ui) .field[data-invalid] .input { border-color: var(--bad-fg); background: var(--bad-bg) }
:where(.ui) .field > .err { display: none; font-size: var(--t-s); color: var(--bad-fg); margin-top: 6px }
:where(.ui) .field[data-invalid] > .err { display: block }

/* ── ПОЛЯ БЕЗ КЛАССА ───────────────────────────────────────────────
   `.input` выше — класс, и его надо не забыть поставить. Забывали:
   21.09.2026 владелец показал ряд отборов «Отзывов WB», где три `<select>`
   стояли голыми браузерными — рядом с нашей кнопкой периода и нашей
   галочкой. Так же голыми они стоят в «Журнале», «Мандаринке» и «Штрафах»,
   а «Разведка» завела себе СВОЁ правило, причём дважды (`.d2-params select`
   и `.d2-target select`) — то есть второй набор политики о том, как выглядит
   поле на платформе.

   Поэтому вид поля задаётся ЭЛЕМЕНТОМ, а не только классом: чтобы новое поле
   выглядело нашим по умолчанию, а не по памяти того, кто его добавил.

   ⚠️ Частность здесь НОЛЬ (`:where`), и это главное свойство правила: любое
   правило раздела перебивает его само собой, ничего не ломая. Одетые вручную
   поля «Разведки» остаются как были.

   ⚠️ `input` берётся НЕ весь: галочка своя (выше), а у `range`, `file`
   и `color` рамка с отступами означает другое. Перечисляем роды поимённо. */
:where(.ui) select,
:where(.ui) input[type="text"], :where(.ui) input[type="search"],
:where(.ui) input[type="number"], :where(.ui) input[type="date"],
:where(.ui) input[type="time"], :where(.ui) input[type="password"],
:where(.ui) input[type="email"], :where(.ui) input[type="url"],
:where(.ui) input:not([type]) {
  padding: 8px 11px; border-radius: var(--r);
  border: 1px solid var(--line); background: var(--card); color: var(--ink);
  font: inherit; font-size: var(--t-b);
}
:where(.ui) select:focus, :where(.ui) input:focus-visible {
  border-color: var(--ink); outline: none }
:where(.ui) select:disabled, :where(.ui) input:disabled {
  color: var(--mute); background: var(--soft);
  border-color: var(--line-soft); cursor: default }

/* Стрелка своя, а не браузерная: нативную рисует система, и в тёмной теме
   она оставалась светло-серым треугольником на тёмном поле — единственная
   деталь экрана, не слушавшаяся темы. Сама картинка — токеном
   (`--select-arrow`): цвет зашит внутрь, и у тёмной темы она своя. */
:where(.ui) select {
  appearance: none; padding-right: 30px; cursor: pointer;
  background-image: var(--select-arrow);
  background-repeat: no-repeat; background-position: right 10px center;
  background-size: 12px;
}
:where(.ui) select:disabled { cursor: default }


:where(.ui) .form-err {
  display: flex; gap: 10px; padding: 12px 14px; margin-bottom: 16px;
  background: var(--bad-bg); border: 1px solid var(--bad-bg);
  border-radius: var(--r); font-size: var(--t-b); color: var(--bad-fg);
}
:where(.ui) .form-err[hidden] { display: none }

/* ── полоса состояния ──────────────────────────────────────────────
   Что раздел делает ПРЯМО СЕЙЧАС, одной строкой над содержимым. Заняла
   место рекламной полосы образца: у разведки прогон идёт час с лишним,
   у мониторинга — минуты, и без этой строки не отличить «работает»
   от «висит».

   Переехала в кит 13.09.2026, когда её попросил второй раздел. Тон здесь
   не китовый `ok|warn|bad`, а СВОЙ словарь состояний очереди, и это
   осознанно: «на паузе» — не «внимание», а «так решил человек», и красить
   её янтарным наравне с настоящей бедой значило бы сказать неправду.
   Порядок важен и в разметке: мёртвый исполнитель главнее паузы — «на паузе,
   потому и тихо» увело бы от настоящей причины. */
/* ⚠️ `wrap` и перенос длинных слов — про узкий экран (замер 17.09.2026,
   430 px): в полосе стоит имя задачи вроде `Блузка_воланы_голубая_A0019`,
   а это ОДНО слово без пробелов. Неразрывное слово flex-элемент не ужимает,
   поэтому текст вылезал за свой блок и ложился ПОД кнопку «Поставить
   на паузу» — читались оба поверх друг друга. Теперь имя переносится внутри
   себя, а кнопка, если ей не хватило места, уходит на свою строку.
   На широком экране не меняется ничего: там всё помещалось и так. */
:where(.ui) .statusbar { display: flex; align-items: center; gap: 12px;
             flex-wrap: wrap; overflow-wrap: anywhere;
             padding: 14px 16px; margin-bottom: 16px; font-size: var(--t-b);
             background: var(--soft); border: 1px solid var(--line);
             border-radius: var(--r) }
/* ⚠️ Размер знака задаёт СТИЛЬ, и без правила он берёт СВОЙ: у svg с одним
   `viewBox` браузер считает размер по месту, и в этой полосе значок вырос
   до 97 px — полоса стала 151 px вместо полусотни, а подпись под ним
   переехала на вторую строку. Замерено 15.09.2026 на «Мандаринке»; у соседей
   этого не видно, потому что они ставят сюда точку, а не знак. */
:where(.ui) .statusbar svg { width: 15px; height: 15px; flex: none }
:where(.ui) .statusbar > span { display: inline-flex; align-items: center; gap: 7px }
:where(.ui) .statusbar b { font-weight: 800 }
:where(.ui) .statusbar .dim { color: var(--ink-2) }
:where(.ui) .statusbar .side { margin-left: auto; display: flex; align-items: center;
                   gap: 10px; flex-shrink: 0 }
/* Работает — зелёная точка; простаивает — серая: это не беда, а покой. */
:where(.ui) .statusbar > .dot { background: var(--ok-fg) }
:where(.ui) .statusbar.idle > .dot { background: var(--mute) }
:where(.ui) .statusbar.dead { background: var(--bad-bg); border-color: var(--bad-line) }
:where(.ui) .statusbar.dead > .dot { background: var(--bad-fg) }
/* Пауза — свой цвет, тот же оранжевый, что у действий, меняющих мир.
   Красный здесь врал бы (ничего не сломано), серый терялся бы рядом
   с «простаивает», а разница между ними и есть весь смысл полосы. */
:where(.ui) .statusbar.paused { background: var(--accent-soft); border-color: var(--accent-line) }
:where(.ui) .statusbar.paused > .dot { background: var(--accent) }
/* Две строки (`statusBar` с `why`): что происходит — и ниже тише, почему.
   `align-items` перебивается НАМЕРЕННО: китовое `center` центрирует ряд,
   а две строки должны начинаться от левого края, иначе короткая вторая
   висит посередине под первой. Переехало из «Разведки» 22.10.2026. */
:where(.ui) .statusbar > .ui-status { display: flex; flex-direction: column;
                                      align-items: flex-start; gap: 3px; min-width: 0 }
:where(.ui) .statusbar .ui-status-why { color: var(--ink-2); font-size: 12px; line-height: 1.45 }

/* ── ряд отборов ───────────────────────────────────────────────────
   Срезы списка со счётчиками. Отбирают то, что УЖЕ пришло: список приходит
   целиком, поэтому отбор мгновенный и живёт в браузере. Счётчик у каждого
   среза обязателен — иначе отбор прячет строки молча. */
:where(.ui) .chips { display: flex; flex-wrap: wrap; gap: 8px; margin-bottom: 16px }
:where(.ui) .chips > button { padding: 7px 14px; border-radius: 999px;
                  border: 1px solid var(--line); background: var(--card);
                  font-size: var(--t-b); font-weight: 700; color: var(--ink) }
:where(.ui) .chips > button:hover { background: var(--soft) }
:where(.ui) .chips > button .n { color: var(--mute); font-weight: 700; margin-left: 6px;
                                 font-variant-numeric: tabular-nums }
/* Цифры одной ширины: счётчик меняется на глазах (лента, «Онлайн»), и таблетка
   не должна дёргаться шириной от смены «11» на «18» (решение владельца 23.10.2026). */
/* Выбранный срез — чёрной таблеткой: тем же чёрным, каким в этом стиле
   показывают выбранное. Не оранжевым: отбор ничего не меняет в мире. */
:where(.ui) .chips > button[aria-pressed="true"] { background: var(--ink);
                              border-color: var(--ink); color: var(--card) }
:where(.ui) .chips > button[aria-pressed="true"] .n { color: var(--mute) }
/* Выбор МАГАЗИНА — в цвете его площадки (`mpClass`, владелец 30.10.2026):
   выбранный залит, как отметка площадки, остальные — её рамкой и лёгким тоном.
   Надпись невыбранного — обычные чернила: малиновый текст на тёмной теме не читается. */
:where(.ui) .chips > button:is(.mp-wb, .mp-ozon) { --mp: var(--mp-wb);
        border-color: var(--mp); background: color-mix(in srgb, var(--mp) 8%, var(--card)) }
:where(.ui) .chips > button.mp-ozon { --mp: var(--mp-ozon) }
:where(.ui) .chips > button:is(.mp-wb, .mp-ozon):hover {
        background: color-mix(in srgb, var(--mp) 16%, var(--card)) }
:where(.ui) .chips > button:is(.mp-wb, .mp-ozon)[aria-pressed="true"] {
        background: var(--mp); border-color: var(--mp); color: var(--mp-ink) }

/* ── ряд плиток ────────────────────────────────────────────────────
   Четыре числа шапки. Сама плитка (`tile`) уже была; здесь только ряд,
   потому что его завели оба раздела одинаковым. */
:where(.ui) .tiles { display: grid; grid-template-columns: repeat(4, 1fr);
            gap: 12px; margin-bottom: 16px }
@media (max-width: 900px) { :where(.ui) .tiles { grid-template-columns: repeat(2, 1fr) } }

/* ── извещение ─────────────────────────────────────────────────────
   Занимает место рекламной полосы образца. У нас туда попадает то, что
   человеку надо знать до того, как он начнёт: сломанный раздел, пауза. */
:where(.ui) .notice { display: flex; gap: 14px; padding: 16px 18px; border-radius: var(--r);
          background: var(--soft); border: 1px solid var(--line) }
:where(.ui) .notice > .ico { width: 38px; height: 38px; border-radius: var(--r-s);
                 background: var(--card); border: 1px solid var(--line);
                 display: grid; place-items: center; flex-shrink: 0; color: var(--ink-2) }
:where(.ui) .notice > .ico svg { width: 19px; height: 19px }
:where(.ui) .notice.warn > .ico { color: var(--warn-fg) }
:where(.ui) .notice.bad > .ico { color: var(--bad-fg) }
/* Зелёная ступень — такой же полноправный тон, как жёлтая и красная:
   «состоялось и вот что дальше» (ссылка ушла человеку). Тон несёт ЗНАК,
   сама плашка остаётся нейтральной — иначе спокойное сообщение кричало бы
   громче предупреждения рядом. */
:where(.ui) .notice.ok > .ico { color: var(--ok-fg) }
:where(.ui) .notice h3 { margin: 0 0 3px; font-size: var(--t-m); font-weight: 800 }
:where(.ui) .notice p { margin: 0; font-size: var(--t-b); color: var(--ink-2); line-height: 1.65; max-width: 72ch }
:where(.ui) .notice .close { margin-left: auto; align-self: start; border: 0; background: none;
                 color: var(--mute); padding: 2px; line-height: 0 }
:where(.ui) .notice .close:hover { color: var(--ink) }
/* Действие извещения — справа, по центру строки («Открыть «Все боты»»). */
/* Только у извещения С действием: длинному пояснению без кнопки знак
   нужен сверху, у первой строки, а не посередине абзаца. */
:where(.ui) .notice:has(> .btn) { align-items: center }
:where(.ui) .notice > .btn { margin-left: auto; flex-shrink: 0 }
/* На узком экране кнопка уходит ПОД пояснение, вровень с ним (знак 38 px +
   зазор 14 px), а не жмёт его: на 430 px «Завести магазин» сжимало текст
   «Магазинов» в колонку по два слова (снимок 25.10.2026). */
@media (max-width: 700px) {
  :where(.ui) .notice:has(> .btn) { flex-wrap: wrap }
  :where(.ui) .notice:has(> .btn) > div:not(.ico) { flex: 1 1 calc(100% - 52px) }
  :where(.ui) .notice > .btn { margin-left: 52px }
}
:where(.ui) code { font-size: var(--t-s); background: var(--card); border: 1px solid var(--line);
       border-radius: 5px; padding: 0 5px; white-space: nowrap }

/* ── сообщение Телеграма (предпросмотр, `tgMessage`) ───────────────
   Переносы строк — как в Телеграме; кнопки рядами под текстом, ряд делится
   поровну. Колбэк-кнопка выглядит так же, как кнопка-ссылка: в чате их
   не различить, а нажимается она только там (подсказка у неё своя). */
:where(.ui) .tg-text { white-space: pre-wrap; line-height: 1.45; overflow-wrap: anywhere }
:where(.ui) .tg-kbd { display: grid; gap: 6px; margin-top: 8px }
:where(.ui) .tg-row { display: flex; gap: 6px; flex-wrap: wrap }
:where(.ui) .tg-key { flex: 1; min-width: 120px; padding: 6px 10px; text-align: center;
                      border-radius: calc(var(--r) - 4px); background: var(--card);
                      border: 1px solid var(--line); font-size: var(--t-s);
                      color: var(--ink); text-decoration: none }
:where(.ui) span.tg-key { cursor: default }
/* Подпись по частям (`tgMessage({parts})`): часть — свой блок, чтобы её
   можно было подсветить; пустая строка между частями — тоже блок. Подсветка —
   плашкой значения, а не оранжевым: показать не значит изменить. */
:where(.ui) .tg-part { display: block; border-radius: 4px; margin: 0 -4px; padding: 0 4px }
:where(.ui) .tg-part.hl { background: var(--plate) }

/* ── ритм страницы ─────────────────────────────────────────────────
   Между верхними блоками страницы раздела — ОДИН зазор, и задаёт его
   страница, а не блоки: панель кита — карточка, а не элемент списка,
   и своего `margin-bottom` не несёт, иначе панель внутри другой растолкала
   бы её содержимое. Пока ритм держался на отступах блоков, его не было
   там, где у соседа отступа нет: панели склеивались границами в двойную
   линию (замечание владельца 13.09.2026), а у «Отзывов» стояли впритык
   к вкладкам (21.09.2026).

   В ките с 03.10.2026: до того восемь разделов держали по своей копии
   `.<корень> > * + *`. Раздел ставит на своё поле ТРИ класса — `ui`,
   `page` и свой корень. Вес правила — один класс, как у прежних копий:
   своё правило раздела о зазоре у конкретного блока по-прежнему сильнее. */
:where(.ui).page > * + * { margin-top: 16px }

/* ── заголовок страницы ────────────────────────────────────────────
   Знак в плашке, название, действия справа. Одинаков на всех страницах
   платформы: до этапа 8 он жил в оболочке, но как только своя страница
   появилась у раздела, копия заголовка стала вторым мнением о том, какого
   размера плашка и сколько под ней воздуха. */
/* Заголовок ПЕРЕНОСИТСЯ, когда действий больше, чем влезает в строку.
   Без переноса flex не ужимает ни название, ни ряд кнопок ниже их
   содержимого — и распирает не заголовок, а всю страницу: у «Мониторинга»
   с длинным именем задачи и пятью действиями появлялась горизонтальная
   прокрутка САЙТА, и вместе с ней за правый край уезжали шапки таблиц
   (замерено 12.09.2026: scrollWidth 1508 при окне 1440). */
:where(.ui) .page-head { display: flex; align-items: center; gap: 13px; margin-bottom: 22px;
              flex-wrap: wrap }
:where(.ui) .page-head > .ico { width: 42px; height: 42px; border-radius: var(--r); flex-shrink: 0;
                   display: grid; place-items: center; background: var(--soft); color: var(--ink) }
:where(.ui) .page-head > .ico svg { width: 22px; height: 22px }
/* Название занимает остаток строки рядом со знаком, а не ширину своей самой
   длинной строки. Иначе подпись в одно длинное предложение («Карточки-доноры:
   у кого мы уже стоим в полке…») делала блок шире экрана, и на телефоне он
   уезжал строкой ниже, оставляя знак одного над пустотой (снимок «Разведки»
   на 430 px, 03.10.2026; у раздела было своё правило на это, и с переездом
   на кит оно ушло). 260 px — меньше название не сжимается: дальше кнопки
   действий уходят строкой ниже, а не мнут его в столбик. */
:where(.ui) .page-head .ttl { min-width: 0; flex: 1 1 260px }
:where(.ui) .page-head h1 { margin: 0; font-size: var(--t-l); font-weight: 800; letter-spacing: -.01em;
               min-width: 0; overflow-wrap: anywhere }
/* Подпись под названием: чем раздел занят. Тише названия и не обязательна —
   у «Системы» и «Журнала» их имена говорят сами за себя. */
:where(.ui) .page-head .sub { margin-top: 2px; font-size: var(--t-s); color: var(--ink-2) }
:where(.ui) .page-head .acts { margin-left: auto; display: flex; align-items: center; gap: 10px;
                  flex-wrap: wrap }
/* Отметка времени рядом с действиями — «когда снято». На узком экране
   уходит: место дороже, а число обновляется само. */
:where(.ui) .stamp { font-size: var(--t-s); color: var(--mute) }
@media (max-width: 560px) { :where(.ui) .stamp { display: none } }
/* На узком экране действия уходят ПОД название, а не жмут его.
   `acts` прижат вправо, а `h1` не сжимается — на 400 px заголовок раздела
   с тремя действиями наезжал на первое из них (поймано на снимке
   «Мониторинга» 12.09.2026). Прятать действия нельзя: среди них бывает
   «воркер не отвечает», а это единственное, ради чего в раздел зашли. */
@media (max-width: 700px) {
  :where(.ui) .page-head { flex-wrap: wrap }
  :where(.ui) .page-head .acts { margin-left: 0; width: 100%; flex-wrap: wrap }
}

/* ── пустой экран ──────────────────────────────────────────────────
   Приглашение к действию, а не сообщение о пустоте: «ничего не найдено»
   оставляет человека стоять, «заведите первый артикул» — нет. */
:where(.ui) .empty { padding: 46px 24px; text-align: center }
:where(.ui) .empty h3 { margin: 0 0 6px; font-size: var(--t-h); font-weight: 800 }
:where(.ui) .empty p { margin: 0 auto 16px; font-size: var(--t-b); color: var(--ink-2); max-width: 46ch }
/* ЗАГРУЗКА — тот же блок с пометкой `data-loading` и бегущей полосой над
   словом (решение владельца 03.10.2026: «состояния одним видом»). Разделы
   уже рисовали «ещё читаю» этим блоком, каждый своим словом, — полосу
   даёт кит одним правилом, и слово «Смотрю…» больше не читается как ответ.
   Бегущий отрезок говорит «идёт», не обещая доли: доли у загрузки нет.
   У кого движение выключено системой, отрезок стоит залитым и тише. */
:where(.ui) .empty[data-loading]::before {
  content: ''; display: block; width: min(220px, 60%); height: 4px; margin: 0 auto 14px;
  border-radius: 999px;
  background: linear-gradient(90deg, var(--soft) 0 35%, var(--ink-2) 35% 65%, var(--soft) 65%)
              0 0 / 300% 100%;
  animation: ui-loading 1.2s linear infinite;
}
:where(.ui) .empty[data-loading] p { margin-bottom: 0 }
@keyframes ui-loading { from { background-position: 100% 0 } to { background-position: 0 0 } }
@media (prefers-reduced-motion: reduce) {
  :where(.ui) .empty[data-loading]::before { animation: none; background: var(--plate) }
}

/* ── нет доступа ───────────────────────────────────────────────────
   Приходит на ПРЯМОЙ адрес без права: в меню такого раздела нет, но ссылку
   могли прислать. Говорит, какое право нужно и у кого его просить, — иначе
   человек идёт спрашивать «у меня не работает». */
:where(.ui) .denied { max-width: 46ch; margin: 12vh auto; text-align: center }
:where(.ui) .denied .ico { width: 52px; height: 52px; border-radius: var(--r); margin: 0 auto 16px;
               background: var(--soft); display: grid; place-items: center; color: var(--ink-2) }
:where(.ui) .denied .ico svg { width: 26px; height: 26px }
:where(.ui) .denied h1 { margin: 0 0 8px; font-size: var(--t-l); font-weight: 800 }
:where(.ui) .denied p { margin: 0 0 18px; font-size: var(--t-b); color: var(--ink-2); line-height: 1.6 }

/* ── матрица прав ──────────────────────────────────────────────────
   Шаблоны ЖИВЫЕ: правка шаблона меняет права всех, у кого он стоит.
   Поэтому матрица НЕ сохраняет по клику — она копит правки и держит рядом
   столбец «кого заденет». Это тот же взвод двумя кликами, растянутый
   во времени: увидеть последствие раньше, чем оно случится. */
:where(.ui) .matrix { width: 100%; border-collapse: collapse; font-size: var(--t-b) }
:where(.ui) .matrix th { padding: 10px 12px; font-size: var(--t-cap); font-weight: 700;
             letter-spacing: .04em; text-transform: uppercase; color: var(--ink-2);
             border-bottom: 1px solid var(--line) }
:where(.ui) .matrix thead th:first-child { text-align: left }
:where(.ui) .matrix td { padding: 0; border-bottom: 1px solid var(--line-soft) }
:where(.ui) .matrix tbody tr:last-child td { border-bottom: 0 }
/* Заголовок раздела внутри матрицы: права сгруппированы по разделам,
   иначе тридцать строк подряд читаются как один список без начала. */
:where(.ui) .matrix .group td { padding: 14px 12px 6px; font-size: var(--t-s); font-weight: 800;
                    color: var(--ink); background: var(--bg) }
:where(.ui) .matrix .perm { padding: 9px 12px; text-align: left }
:where(.ui) .matrix .perm .code { display: block; font-size: var(--t-cap); color: var(--mute); margin-top: 1px }
:where(.ui) .matrix .cell { text-align: center; width: 108px; padding: 9px 0 }
/* Отметка, а не запертая галочка: запертая читается как «этого нельзя»,
   хотя речь про «так есть». Отсутствие — прочерк, а не пустота: пустая
   клетка неотличима от неотрисованной. */
:where(.ui) .matrix .cell .mark { font-weight: 800; color: var(--ok-fg) }
:where(.ui) .matrix .cell .no { color: var(--mute) }
:where(.ui) .matrix tbody tr:hover { background: var(--hover) }
/* Строка, где выбранные шаблоны РАСХОДЯТСЯ, — то, ради чего сравнение
   и открывают. Она помечена полосой слева, чтобы её было видно и когда
   «только отличия» выключено. */
:where(.ui) .matrix tr[data-same="no"] .perm { box-shadow: inset 3px 0 0 var(--accent-line) }

:where(.ui) .impact { position: sticky; top: 20px; background: var(--warn-bg);
          border: 1px solid var(--accent-line); border-radius: var(--r); padding: 15px 17px }
:where(.ui) .impact[hidden] { display: none }
:where(.ui) .impact h3 { margin: 0 0 4px; font-size: var(--t-b); font-weight: 800; color: var(--warn-fg) }
:where(.ui) .impact > p { margin: 0 0 12px; font-size: var(--t-s); color: var(--ink-2) }
:where(.ui) .impact ul { margin: 0 0 14px; padding-left: 17px; font-size: var(--t-s); line-height: 1.7 }
:where(.ui) .impact ul b { font-weight: 800 }
:where(.ui) .impact .btns { display: flex; gap: 8px }
/* Пока правок нет, на месте столбца стоит объяснение, а не пустота: человек
   должен узнать про живые шаблоны ДО первой галочки, а не после. */
:where(.ui) .impact-idle { border: 1px dashed var(--line); border-radius: var(--r);
               padding: 15px 17px; font-size: var(--t-s); color: var(--ink-2); line-height: 1.6 }
:where(.ui) .impact-idle[hidden] { display: none }

/* ── подсказка ─────────────────────────────────────────────────────
   Вид панели, а не чёрный ярлык: наши подсказки длинные — три-пять строк
   с цитатой порога, — и читаться должны как кусок интерфейса.

   Узел ОДИН на страницу и лежит в `body`, поэтому правило не под `.ui`:
   внутри панели он срезался бы её краем (скругление плюс `overflow`).
   Поведение и задержка — в `kit.js`. */
.ui-tip {
  position: fixed; z-index: 60; max-width: 46ch; pointer-events: none;
  padding: 10px 13px; border-radius: var(--r-s);
  background: var(--tip-bg); color: var(--tip-ink);
  border: 1px solid var(--tip-line);
  box-shadow: 0 10px 26px -8px rgba(0, 0, 0, .22), 0 2px 6px -2px rgba(0, 0, 0, .10);
  font-size: var(--t-s); line-height: 1.6;
  opacity: 0; transform: translateY(4px);
  transition: opacity .12s ease, transform .12s ease;
}
.ui-tip[data-show] { opacity: 1; transform: none }
:root[data-theme="dark"] .ui-tip {
  box-shadow: 0 12px 30px -8px rgba(0, 0, 0, .7), 0 2px 6px -2px rgba(0, 0, 0, .5);
}
/* Подсказка есть — курсор об этом говорит. */
:where(.ui) [data-tip] { cursor: help }
:where(.ui) button[data-tip], :where(.ui) a[data-tip] { cursor: pointer }

/* ── МИНИ-ГРАФИК ───────────────────────────────────────────────────
   Ряд размером с ячейку: столбики или заливная кривая (`kit.miniChart`).

   Цвет — `currentColor`, то есть приходит из места, куда график поставили.
   Тон выбирает вызвавший классом; имена токенов раздел при этом не знает,
   и своего hex'а здесь нет ни одного: он пережил бы тёмную тему только
   по случайности.

   ⚠️ Высота задана ЗДЕСЬ числом, а не наследуется: холст с
   `preserveAspectRatio="none"` тянется во что угодно, и в ячейке без своей
   высоты он схлопнулся бы в ноль — узел есть, места не занимает, не виден.
   Разделу, которому нужна другая, хватит одной строки поверх. */
:where(.ui) .ui-chart {
  display: block;
  width: 100%;
  height: 34px;
  color: var(--chart);
}
/* Зелёный — «дошедшие» деньги, синий — «заказанные». Больше тонов у графика
   нет намеренно: цвет здесь отвечает на вопрос «чьи это деньги», а не
   «хорошо или плохо» — на последнее отвечает заливка ячейки. */
:where(.ui) .ui-chart.ok { color: var(--ok-fg) }

:where(.ui) .ui-chart .bar { fill: currentColor; opacity: .5 }
:where(.ui) .ui-chart .fill { fill: currentColor; opacity: .16 }
:where(.ui) .ui-chart .line {
  fill: none;
  stroke: currentColor;
  stroke-width: 1.8;
  stroke-linejoin: round;
  stroke-linecap: round;
  /* ⚠️ Без `non-scaling-stroke` толщина растягивается вместе с холстом
     (`preserveAspectRatio="none"`), и одна и та же линия выходит разной
     в широкой и узкой колонке. */
  vector-effect: non-scaling-stroke;
}

/* ── ВЫБОР ПЕРИОДА ─────────────────────────────────────────────────
   Одно окно на платформу: пресеты слева, два месяца справа.
   Всё под `:where(.ui)`, как и остальной кит. */

:where(.ui) .dr { position: relative; display: inline-block }

:where(.ui) .dr-field {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  height: 34px;
  padding: 0 12px;
  border: 1px solid var(--line);
  border-radius: 10px;
  background: var(--card);
  color: var(--ink);
  font: inherit;
  font-size: 13px;
  cursor: pointer;
}
:where(.ui) .dr-field:hover { border-color: var(--frame) }
/* ⚠️ Размер знака задаёт СТИЛЬ: в разметке у svg нет ни width, ни height
   (`icons.js` рисует только viewBox). Без правила знак схлопывается в 0×0 —
   он есть в дереве, занимает место в разметке и не виден вовсе. Ровно так
   пропали обе стрелки календаря. */
:where(.ui) .dr-field span svg { display: block; width: 15px; height: 15px; opacity: .55 }
:where(.ui) .dr-label { white-space: nowrap }

:where(.ui) .dr-box {
  position: absolute;
  z-index: 40;
  top: calc(100% + 6px);
  left: 0;
  display: flex;
  /* Своё поле задаёт фон ЯВНО: без него в тёмной теме окно останется
     светлым со светлым текстом, и видно это только глазами. */
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: 12px;
  box-shadow: 0 12px 32px -12px rgba(0, 0, 0, .35);
  /* Шире экрана окно быть не может: на телефоне иначе уезжает за кромку. */
  max-width: calc(100vw - 24px);
  overflow: hidden;
}
/* ⚠️ ОБЯЗАТЕЛЬНО. Браузер прячет `[hidden]` через `display: none` в своей
   таблице стилей, а она слабее авторской: наш `display: flex` выше строкой
   её перебивает, и окно видно ВСЕГДА — при том что `box.hidden` в коде
   честно `true`.
   Ошибка вдвойне тихая. Во-первых, проверка `el.hidden` из теста отвечает
   «скрыто», потому что смотрит на атрибут, а не на то, что на экране.
   Во-вторых, `place()` не вызывается (код считает окно закрытым), поэтому
   окно не переносится к левому краю и растягивает страницу горизонтальной
   прокруткой. Соседи по киту (`.armbox`, `.form-err`, `.impact`) это
   правило несут — оно тут не украшение. */
:where(.ui) .dr-box[hidden] { display: none }
:where(.ui) .dr-box.up { top: auto; bottom: calc(100% + 6px) }
:where(.ui) .dr-box.left { left: auto; right: 0 }

:where(.ui) .dr-presets {
  display: flex;
  flex-direction: column;
  gap: 2px;
  padding: 10px;
  border-right: 1px solid var(--line);
  background: var(--soft);
  min-width: 168px;
}
:where(.ui) .dr-preset {
  padding: 7px 10px;
  border: 0;
  border-radius: 8px;
  background: transparent;
  color: var(--ink);
  font: inherit;
  font-size: 13px;
  text-align: left;
  white-space: nowrap;
  cursor: pointer;
}
:where(.ui) .dr-preset:hover { background: var(--card) }
:where(.ui) .dr-preset[aria-pressed="true"] { background: var(--ink); color: var(--card) }

:where(.ui) .dr-cal { display: flex; gap: 22px; padding: 12px 14px }
:where(.ui) .dr-month { min-width: 232px }

:where(.ui) .dr-mhead {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  margin-bottom: 8px;
}
:where(.ui) .dr-mhead b { font-size: 13px }
:where(.ui) .dr-nav {
  display: inline-flex;
  width: 26px;
  height: 26px;
  align-items: center;
  justify-content: center;
  border: 0;
  border-radius: 8px;
  background: transparent;
  color: var(--ink);
  cursor: pointer;
}
:where(.ui) .dr-nav:hover { background: var(--soft) }
:where(.ui) .dr-nav svg { width: 16px; height: 16px }
/* Правой стрелки в наборе нет, и заводить её незачем: зеркальная левая
   совпадает с ней по рисунку точно, а новый знак пришлось бы подгонять. */
:where(.ui) .dr-nav.next svg { transform: scaleX(-1) }
:where(.ui) .dr-nav-gap { width: 26px }

:where(.ui) .dr-grid {
  display: grid;
  grid-template-columns: repeat(7, 1fr);
  gap: 2px;
}
:where(.ui) .dr-wd {
  text-align: center;
  font-size: 11px;
  color: var(--mute);
  padding-bottom: 4px;
}

:where(.ui) .dr-day {
  height: 30px;
  border: 0;
  border-radius: 8px;
  background: transparent;
  color: var(--ink);
  font: inherit;
  font-size: 13px;
  font-variant-numeric: tabular-nums;
  cursor: pointer;
}
:where(.ui) .dr-day:hover { background: var(--soft) }
/* Чужие дни месяца ПОКАЗАНЫ, но приглушены: без них первая строка прыгает,
   и месяц читается обрезанным. */
:where(.ui) .dr-day.out { color: var(--mute); opacity: .5 }
/* Внутри выбранного отрезка — заливка, на краях — сплошная. Отрезок без
   краёв не читается: непонятно, где он начался.
   Заливка заметнее, чем `--soft`: та почти сливается с фоном, и выбранный
   период приходилось искать глазами. Но и не ярче краёв — иначе середина
   спорит с границами за внимание. */
:where(.ui) .dr-day.in {
  background: color-mix(in srgb, var(--ink) 11%, transparent);
  border-radius: 0;
}
:where(.ui) .dr-day.in:hover { background: color-mix(in srgb, var(--ink) 18%, transparent) }
/* ⚠️ Край объявлен ВМЕСТЕ со своим наведением, и это не перестраховка.
   У `.in:hover` специфичность выше (два класса и псевдокласс против двух
   классов), поэтому он перебивал край: выбранный день под курсором делался
   серым. Заметно это ровно в тот миг, когда человек ткнул в начало отрезка
   и ещё не сдвинул руку, — то есть выбор пропадал с экрана именно тогда,
   когда на него смотрят. */
:where(.ui) .dr-day.edge,
:where(.ui) .dr-day.edge:hover {
  background: var(--ink); color: var(--card); border-radius: 8px;
}
/* Недосягаемый день: либо за пределом данных (`beyond`), либо дальше
   разрешённой длины от уже выбранного начала (`far`). Вид у них ОДИН —
   оба значат «нажать нельзя», и два разных серых человек прочитал бы как
   два разных запрета, не поняв ни одного. Классы при этом разные: причины
   не совпадают, и однажды их может понадобиться развести. */
:where(.ui) .dr-day.beyond,
:where(.ui) .dr-day.far { color: var(--mute); opacity: .35; cursor: default }
:where(.ui) .dr-day.beyond:hover,
:where(.ui) .dr-day.far:hover { background: transparent }
/* Погашенный день не подсвечивается как часть отрезка: он в него не войдёт. */
:where(.ui) .dr-day.far.in { background: transparent }

:where(.ui) .dr-hint {
  margin-top: auto;
  padding: 8px 10px 2px;
  font-size: var(--t-cap);
  line-height: 1.5;
  color: var(--mute);
}

/* На телефоне два месяца рядом не помещаются — второй уходит,
   а пресеты встают сверху строкой. */
@media (max-width: 700px) {
  :where(.ui) .dr-box { flex-direction: column }
  :where(.ui) .dr-presets {
    flex-direction: row;
    flex-wrap: wrap;
    border-right: 0;
    border-bottom: 1px solid var(--line);
    min-width: 0;
  }
  :where(.ui) .dr-cal { gap: 0 }
  :where(.ui) .dr-month + .dr-month { display: none }
}

/* ── подсказка мини-графика ────────────────────────────────────────
   Карточка у курсора: дата, штрих в цвет кривой, имя показателя, значение.
   Геометрия снята с образца владельца, а не выбрана на глаз. */
.ui-chart-box { position: relative; width: 100%; height: 100% }
.ui-chart-line {
  position: absolute; top: 0; bottom: 0; width: 1px;
  background: color-mix(in srgb, var(--chart) 60%, transparent);
  pointer-events: none; opacity: 0; transition: opacity .12s ease; z-index: 2;
}
.ui-chart-line[data-show] { opacity: 1 }

.ui-chart-tip {
  position: fixed; z-index: 60; pointer-events: none;
  padding: 10px 12px; border-radius: var(--r-s);
  background: var(--tip-bg); color: var(--tip-ink);
  border: 1px solid var(--tip-line);
  box-shadow: 0 10px 26px -8px rgba(0, 0, 0, .22), 0 2px 6px -2px rgba(0, 0, 0, .10);
  font-size: var(--t-b); line-height: 1.4; white-space: nowrap;
  opacity: 0; transition: opacity .12s ease;
}
.ui-chart-tip[data-show] { opacity: 1 }
.ui-chart-tip b { display: block; margin-bottom: 4px; color: var(--ink); font-size: var(--t-s) }
.ui-chart-tip .row { display: flex; align-items: center; gap: 8px }
/* Штрих ровно как у образца: 16×3 со скруглением 2. Цвет ставит кит —
   он берёт его из вычисленного стиля холста, то есть из токенов. */
.ui-chart-tip .swatch { width: 16px; height: 3px; border-radius: 2px; flex: none }
.ui-chart-tip .what { color: var(--tip-ink) }
.ui-chart-tip .val { margin-left: 10px; font-weight: 700; color: var(--ink);
                     font-variant-numeric: tabular-nums }
:root[data-theme="dark"] .ui-chart-tip {
  box-shadow: 0 12px 30px -8px rgba(0, 0, 0, .7), 0 2px 6px -2px rgba(0, 0, 0, .5);
}

/* ── окно поверх страницы (`modal` в `kit.js`) ─────────────────────
   Правила — по классу самого окна, а не `:where(.ui) …`: окно и есть `.ui`,
   потомком корня раздела оно не бывает. Прокрутку страницы под окном
   запирает `:has` — иначе колесо крутит список за затемнением. */
.ui-modal {
  width: min(620px, calc(100vw - 32px)); max-height: calc(100dvh - 48px);
  padding: 0; border: 1px solid var(--line); border-radius: var(--r-l);
  background: var(--card); color: var(--ink); overflow: auto;
  box-shadow: 0 24px 60px -20px rgba(0, 0, 0, .35);
}
.ui-modal::backdrop { background: rgb(0 0 0 / .32) }
:root[data-theme="dark"] .ui-modal::backdrop { background: rgb(0 0 0 / .6) }
html:has(dialog.ui-modal[open]) { overflow: hidden }
.ui-modal .ui-modal-head { display: flex; align-items: center; gap: 10px;
  padding: 14px 14px 14px 18px; border-bottom: 1px solid var(--line-soft) }
.ui-modal .ui-modal-head h2 { margin: 0; font-size: var(--t-h); font-weight: 800 }
.ui-modal .ui-modal-x { margin-left: auto }
.ui-modal .ui-modal-x svg { width: 16px; height: 16px }
.ui-modal .ui-modal-body { padding: 16px 18px 18px }

/* ── выбор из списка (`selectBox` в `kit.js`) ───────────────────────
   Поле — того же роста и рамки, что `.input`; окно — то же, что «Показатели»
   в РНП (`.ui-pop-box`) и строки как у него. Окно в верхнем слое (`popover`)
   и потомком `.ui` не бывает — поэтому правила окна по его классу, а класс
   `ui` оно несёт само (отметки внутри строк). */
/* Узел — строчный и по ширине пунктов, как `<select>`: в ряду отборов и шапке
   он не тянется на весь ряд. В поле формы (`.field`) — во всю ширину поля. */
:where(.ui) .ui-sel { position: relative; display: inline-block; vertical-align: middle;
  max-width: 100%; min-width: 0 }
:where(.ui) .field > .ui-sel { display: block; width: 100% }
:where(.ui) .ui-sel-btn {
  width: 100%; display: flex; align-items: center; gap: 8px; text-align: left;
  padding: 9px 12px; border-radius: var(--r); border: 1px solid var(--line);
  background: var(--card); color: var(--ink); font: inherit; font-size: var(--t-b);
  cursor: pointer;
}
:where(.ui) .ui-sel-btn:focus-visible, :where(.ui) .ui-sel-btn[aria-expanded="true"] {
  border-color: var(--ink); outline: none }
:where(.ui) .ui-sel-btn:disabled { color: var(--mute); background: var(--soft);
  border-color: var(--line-soft); cursor: default }
:where(.ui) .ui-sel-val { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis;
  white-space: nowrap }
:where(.ui) .ui-sel-btn > span:last-child { display: inline-flex; flex: none; color: var(--ink-2) }
:where(.ui) .ui-sel-btn svg { width: 14px; height: 14px; transition: transform .15s }
:where(.ui) .ui-sel-btn[aria-expanded="true"] svg { transform: rotate(180deg) }
:where(.ui) .ui-sel-btn:disabled > span:last-child { visibility: hidden }
:where(.ui) .field[data-invalid] .ui-sel-btn { border-color: var(--bad-fg); background: var(--bad-bg) }
.ui-sel-list {
  position: fixed; inset: auto; margin: 0; z-index: 60;
  max-height: min(320px, 50vh); overflow: auto; padding: 8px;
  background: var(--card); color: var(--ink); border: 1px solid var(--line);
  border-radius: var(--r); box-shadow: 0 12px 32px -12px rgba(0, 0, 0, .35);
  display: flex; flex-direction: column; gap: 2px; font-size: var(--t-b);
}
.ui-sel-list:not(:popover-open) { display: none }
.ui-sel-list .ui-sel-row {
  display: flex; align-items: center; gap: 9px; padding: 6px 8px; border-radius: 8px;
  cursor: pointer; color: var(--ink); white-space: nowrap;
}
.ui-sel-list .ui-sel-row[data-active] { background: var(--soft) }
.ui-sel-list .ui-sel-row[aria-selected="true"] { font-weight: 700 }
.ui-sel-list .ui-sel-t { flex: 1 }
.ui-sel-list .ui-sel-row > span:last-child { display: inline-flex; flex: none; color: var(--ink) }
.ui-sel-list .ui-sel-row svg { width: 16px; height: 16px }
.ui-sel-list .ui-sel-row:not([aria-selected="true"]) > span:last-child { visibility: hidden }

/* ── окно под кнопкой ──────────────────────────────────────────────
   Прижато к ПРАВОМУ краю кнопки правилом, а не расчётом: раздел собирает
   разметку до вставки в страницу, и замер в этот момент врёт нулями. */
:where(.ui) .ui-pop { position: relative; display: inline-block }
:where(.ui) .ui-pop-box {
  position: absolute; z-index: 40; top: calc(100% + 6px); right: 0;
  min-width: 240px; max-width: calc(100vw - 24px); padding: 8px;
  background: var(--card); border: 1px solid var(--line);
  border-radius: var(--r); box-shadow: 0 12px 32px -12px rgba(0, 0, 0, .35);
  display: flex; flex-direction: column; gap: 2px;
}
/* ⚠️ ОБЯЗАТЕЛЬНО: браузер прячет `[hidden]` своей таблицей стилей, а она
   слабее авторской — `display: flex` выше строкой её перебивает, и окно
   видно ВСЕГДА, при том что `hidden` в коде честно стоит. */
:where(.ui) .ui-pop-box[hidden] { display: none }

/* ── окно-дерево (`checkTree` в `kit.js`) ──────────────────────────
   Окно прижато к ЛЕВОМУ краю кнопки: дерево стоит первым в ряду отборов.
   Отступ уровня — переменной строки (`--lvl`), а не правилом на каждый
   уровень: глубину задаёт дерево, а не стиль. */
:where(.ui) .ui-tree .ui-pop-box { left: 0; right: auto; min-width: 290px }
:where(.ui) .ui-tree-row {
  display: flex; align-items: center; gap: 9px; border-radius: 8px; cursor: pointer;
  padding: 6px 8px 6px calc(8px + var(--lvl, 0) * 24px);
  color: var(--ink); font-size: var(--t-b); white-space: nowrap;
}
:where(.ui) .ui-tree-row:hover { background: var(--soft) }
:where(.ui) .ui-tree-row[data-level="0"] { font-weight: 700 }
:where(.ui) .ui-tree-row.all { border-bottom: 1px solid var(--line-soft);
  border-radius: 8px 8px 0 0; margin-bottom: 4px; padding-bottom: 9px }
:where(.ui) .ui-tree-note { margin-left: auto; padding-left: 14px; color: var(--mute);
  font-size: var(--t-s); font-weight: 400 }
:where(.ui) .ui-tree-row.off { color: var(--mute); cursor: default }
:where(.ui) .ui-tree-row.off:hover { background: transparent }

/* ── МЕНЮ «ЕЩЁ»: три точки и список действий ───────────────────────
   Переехало из «Разведки» 16.09.2026, когда понадобилось оболочке (смена
   своего пароля). Правило каркаса: общее, понадобившееся второму месту,
   переезжает в кит, а не копируется, — иначе через месяц это два меню
   с разными привычками.

   Цвет точек: `--muted` терялся, #5a5a5a вышел темноват — взят #757575
   (решение владельца 07.09.2026). Заметность держит РАЗМЕР рисунка,
   а не чернота: её добирали цветом, пока точки по ошибке рисовались вдвое
   меньше задуманного. */
:where(.ui) .ui-kebab-wrap { position: relative }
:where(.ui) .ui-kebab { display: inline-flex; align-items: center; justify-content: center;
            width: 34px; height: 34px; border: 0; border-radius: 9px;
            background: none; color: #757575; cursor: pointer }
/* ⚠️ Размер задаётся ЗДЕСЬ и с `flex-shrink: 0`: кнопка — flex-контейнер,
   и браузер ужимал svg вдвое (10px вместо 20), несмотря на атрибуты width
   и height. Замерено на живой морде 07.09.2026: точка выходила 2.33px. */
:where(.ui) .ui-kebab svg { width: 20px; height: 20px; flex-shrink: 0 }
:where(.ui) .ui-kebab:hover { background: var(--soft); color: var(--ink) }
:where(.ui) .ui-kebab[aria-expanded="true"] { background: var(--soft); color: var(--ink) }

/* Меню НЕЙТРАЛЬНОЕ, пока курсор не встал на пункт (решение владельца
   07.09.2026): раньше вся плашка была чёрной, и меню кричало само по себе —
   хотя ни один из его пунктов ещё не выбран. */
:where(.ui) .ui-menu { position: absolute; top: calc(100% + 8px); right: 0; z-index: 20;
           min-width: 268px; padding: 6px; border-radius: 12px;
           background: var(--card); border: 1px solid var(--line);
           box-shadow: 0 12px 32px rgba(0, 0, 0, .14) }
:where(.ui) .ui-menu[hidden] { display: none }
/* Раскрытое меню живёт НАД панелью, а не в ней: панель обрезает содержимое
   (`overflow: hidden`), и меню нижней строки срезалось её краем. Координаты
   ставит `kit.bindMenu` при открытии — здесь только режим. */
:where(.ui) .ui-menu.ui-menu-out { position: fixed; top: auto; right: auto; z-index: 60 }
:where(.ui) .ui-menu-narrow { min-width: 170px }
/* Календарь своего поля даты живёт в том же меню: от него нужны вынос
   в координаты экрана и закрытие щелчком мимо, а не список пунктов. */
:where(.ui) .ui-menu-cal { min-width: 0; padding: 12px }

/* ── СВОИ ПОЛЯ ДАТЫ И ВРЕМЕНИ ──────────────────────────────────────
   Нативные рисует браузер: у владельца они показывали «07/20/2026»
   и «09:00 AM», и стилем это не лечится. Поле своё — обычный текстовый
   ввод, одетый общим правилом полей выше.

   ⚠️ `align-self: flex-start` не ставим здесь, а требуем от ОБЁРТКИ:
   поле в колонке с подсказкой под ним растягивалось по ширине подсказки,
   и знак календаря уезжал вправо, повиснув отдельно от поля. Поймано
   макетом 21.09.2026. Поэтому ширина у поля своя и фиксированная. */
:where(.ui) .ui-datef, :where(.ui) .ui-timef {
  position: relative; display: inline-flex; align-items: center; width: max-content }
:where(.ui) .ui-datef-i { width: 132px; padding-right: 34px;
                          font-variant-numeric: tabular-nums }
:where(.ui) .ui-timef-i { width: 96px; font-variant-numeric: tabular-nums }
/* Кнопка календаря ВНУТРИ поля: снаружи она читается как отдельное действие,
   а это одно поле с двумя способами ввода. */
:where(.ui) .ui-datef-b {
  position: absolute; right: 5px; display: grid; place-items: center;
  width: 26px; height: 26px; padding: 0; border: 0; border-radius: var(--r-s);
  background: none; color: var(--mute); cursor: pointer }
:where(.ui) .ui-datef-b svg { width: 16px; height: 16px }
:where(.ui) .ui-datef-b:hover { background: var(--soft); color: var(--ink) }
:where(.ui) .ui-datef-b:disabled { cursor: default; opacity: .5 }
:where(.ui) .ui-datef-b:disabled:hover { background: none; color: var(--mute) }
/* ⚠️ У ВРЕМЕНИ ЗНАКА НЕТ, и это не экономия. Сперва он был — часы, серые,
   `pointer-events: none`. Владелец сказал прямо: «стоит иконка часов
   и ожидаешь какого-то интерактива» (21.09.2026). Он прав: рядом, у поля
   даты, точно такой же знак — КНОПКА, открывающая календарь. Два одинаковых
   знака, один нажимается, другой нет, — это обещание, которого мы не держим,
   ровно из семьи «нарисованная кнопка не работает».

   Род поля называет подпись над ним и маска `ЧЧ:ММ` в пустом поле; знак
   не добавлял ничего, кроме ложного обещания. */
:where(.ui) .ui-datef-i::placeholder, :where(.ui) .ui-timef-i::placeholder {
  color: var(--mute); letter-spacing: .02em }
:where(.ui) .ui-menu-item { display: flex; align-items: center; gap: 10px; width: 100%;
                padding: 10px 12px; border: 0; border-radius: 8px;
                background: none; color: var(--ink); font: inherit; font-size: var(--t-b);
                font-weight: 600; text-align: left; cursor: pointer;
                white-space: nowrap }
:where(.ui) .ui-menu-item svg { width: 18px; height: 18px; flex-shrink: 0 }
/* Под курсором пункт становится тёмной таблеткой — тем же чёрным, каким
   платформа показывает выбранное. */
:where(.ui) .ui-menu-item:hover, :where(.ui) .ui-menu-item:focus-visible {
  background: var(--ink); color: var(--card); outline: none }
/* Кроме опасного: оно подсвечивается красным — единственный пункт,
   у которого последствие необратимо.
   ⚠️ Класс называется `ui-danger`, а НЕ `danger`: последний занят стилем
   мониторинга (`button.danger { color: var(--red) }`) и бьёт по частности
   одноклассовое правило — текст пункта выходил красным на красной заливке,
   то есть невидимым. */
:where(.ui) .ui-menu-item.ui-danger:hover,
:where(.ui) .ui-menu-item.ui-danger:focus-visible,
/* Взведённый пункт — залит и с кольцом, ровно как взведённая красная кнопка
   (03.10.2026): одно действие обязано вести себя одинаково в кнопке и в меню. */
:where(.ui) .ui-menu .arm:has(.armbox:not([hidden])) > .ui-menu-item.ui-danger {
  background: var(--bad-fg); color: var(--card) }
:where(.ui) .ui-menu .arm:has(.armbox:not([hidden])) > .ui-menu-item.ui-danger {
  box-shadow: 0 0 0 3px var(--bad-line) }
/* Второй уровень: заголовок и пункты под ним. Не всплывающее подменю —
   ему на узком экране некуда раскрыться. */
:where(.ui) .ui-menu-label { padding: 10px 12px 4px; display: flex; align-items: center;
                 gap: 10px; color: var(--mute); font-size: var(--t-s);
                 font-weight: 700; white-space: nowrap }
:where(.ui) .ui-menu-item.ui-sub { padding-left: 34px; justify-content: space-between; gap: 12px }
:where(.ui) .ui-menu-item.ui-sub .dim { color: var(--mute); font-weight: 600 }
:where(.ui) .ui-menu-item.ui-sub:hover .dim,
:where(.ui) .ui-menu-item.ui-sub:focus-visible .dim { color: var(--card) }
:where(.ui) .ui-menu-item[disabled] { opacity: .45; cursor: default }
:where(.ui) .ui-menu-item[disabled]:hover { background: none; color: var(--ink) }
/* Пункт меню, обёрнутый взводом (`arm` кладёт его в `<span class="arm">`),
   обязан остаться строкой во всю ширину меню, а не сжаться по тексту.
   Правило жило в «Мониторинге» и переехало сюда 03.10.2026, когда удаление
   в «…» понадобилось и «Разведке». */
:where(.ui) .ui-menu .arm { display: block }
:where(.ui) .ui-menu .arm > .ui-menu-item { width: 100% }

/* ── горизонтальное меню раздела ───────────────────────────────────
   Переходы по страницам внутри раздела. Переключатель, а не подчёркнутые
   надписи: форма говорит «одно из трёх» сама.

   ⚠️ Не путать со срезами списка (`.chips`): вкладка меняет СТРАНИЦУ,
   срез — только то, какие строки видно. */
:where(.ui) .ui-tabs {
  display: inline-flex;
  gap: 2px;
  padding: 3px;
  background: var(--soft);
  border: 1px solid var(--line);
  border-radius: 10px;
  max-width: 100%;
}
:where(.ui) .ui-tab {
  padding: 7px 14px;
  border: 0;
  border-radius: 8px;
  background: transparent;
  color: var(--ink-2);
  font: inherit;
  font-size: var(--t-m);
  font-weight: 600;
  text-decoration: none;
  white-space: nowrap;
  cursor: pointer;
}
:where(.ui) .ui-tab:hover { background: var(--card); color: var(--ink) }
/* Выбранная помечена И заливкой, И цветом текста: одного признака мало,
   когда рядом наведённая — та тоже получает подложку. */
:where(.ui) .ui-tab[aria-current="page"] {
  background: var(--ink);
  color: var(--card);
  font-weight: 700;
}
:where(.ui) .ui-tab-n { margin-left: 7px; color: var(--mute);
                        font-variant-numeric: tabular-nums }
:where(.ui) .ui-tab[aria-current="page"] .ui-tab-n { color: var(--card); opacity: .7 }
/* На узком экране меню листается, а не переносится: перенесённая вкладка
   рвёт плашку пополам, и переключатель перестаёт читаться как один предмет. */
@media (max-width: 620px) {
  :where(.ui) .ui-tabs { overflow-x: auto; display: flex }
}

/* Ряд вкладок и ВЫБОР ПЕРИОДА справа (решение владельца 03.10.2026, вариант
   «б»). Период задаёт и плитки, и список, поэтому стоит ВЫШЕ плиток — а не
   в заголовке среди кнопок действий и не в ряду отборов под плитками, которые
   от него зависят. До этого он стоял в трёх разных местах, и, перейдя
   из раздела в раздел, его искали глазами заново. Образец — «Мандаринка». */
:where(.ui) .ui-tabrow { display: flex; align-items: center; gap: 12px 16px; flex-wrap: wrap }
:where(.ui) .ui-tabrow-side { display: flex; align-items: center; gap: 12px; margin-left: auto }
/* На телефоне правая часть встаёт своей строкой во всю ширину: переключатель
   и поле периода в 430 px рядом не помещаются (замер «Мандаринки»). */
@media (max-width: 760px) {
  :where(.ui) .ui-tabrow-side { margin-left: 0; width: 100%; justify-content: space-between }
}

/* Поле периода, показывающее НЕ ТО, что просили: тревожный контур и подсказка
   с объяснением. Отдельной полосы под шапкой для этого больше нет — вторая
   информационная плашка об одном экране только спорит с первой. */
:where(.ui) .dr-field[data-notice] {
  border-color: var(--warn-fg);
  background: var(--warn-bg);
  color: var(--warn-fg);
}
:where(.ui) .dr-field[data-notice] svg { opacity: .8 }

/* Картинка под курсором. Окно ПЛАВАЮЩЕЕ (`position: fixed`), потому что
   таблица прокручивается: привяжи его к строке — и оно уехало бы вместе с ней
   под шапку. */
:where(.ui) .ui-thumb { display: block; border-radius: var(--r-s); object-fit: cover }
/* Ссылка обнимает картинку ВПЛОТНУЮ: `inline` оставил бы под ней просвет
   строки, и колонка товара стала бы на пару пикселей выше соседних. */
:where(.ui) .ui-thumb-link { display: block; flex: 0 0 auto; line-height: 0 }
.ui-photo {
  position: fixed;
  z-index: 60;
  display: none;
  padding: 4px;
  border: 1px solid var(--line);
  border-radius: var(--r);
  background: var(--card);
  box-shadow: 0 12px 32px -12px rgba(0, 0, 0, .35);
  /* Курсор сюда не попадает и попадать не должен: окно стоит рядом
     с миниатюрой, и перехваченное наведение погасило бы само себя. */
  pointer-events: none;
}
.ui-photo[data-show] { display: block }
/* 210 px, а не больше: окно всплывает рядом со строкой и неизбежно закрывает
   соседние. Замер на ленте — при 258 px под ним пропадали название товара
   и артикул сразу трёх строк. */
.ui-photo img { display: block; width: 210px; height: auto; border-radius: var(--r-s) }
/* На телефоне наведения нет вовсе, а окно в 258 px закрыло бы полэкрана. */
@media (max-width: 700px) { .ui-photo { display: none !important } }

/* Поле периода, ЗАПЕРТОЕ живым показом. Не тревожное: янтарь на этом поле
   уже занят смыслом «показан не тот период, что просили», и второй повод
   сделал бы картинку неоднозначной. Запирает поле сам человек — пугать его
   тем, что он только что нажал, незачем (владелец выбрал этот вид из трёх
   16.09.2026). */
:where(.ui) .dr-field[data-locked] {
  background: var(--soft);
  border-color: var(--line);
  color: var(--mute);
  cursor: not-allowed;
}
:where(.ui) .dr-field[data-locked]:hover { border-color: var(--line) }
:where(.ui) .dr-field[data-locked] span svg { opacity: .5 }

/* Кнопка «Онлайн». Надпись одна на оба состояния, состояние держит заливка
   (`.primary`) и знак — поэтому своего цвета у кнопки нет. */
:where(.ui) .ui-live svg { width: 14px; height: 14px }

/* Кнопка «на всю ширину». ⚠️ На узком экране её нет: там потолок страницы
   и так не действует, и кнопка обещала бы то, чего не будет. */
:where(.ui) .ui-wide { display: inline-flex; align-items: center;
                       justify-content: center; width: 34px; padding: 0 }
:where(.ui) .ui-wide svg { width: 15px; height: 15px }
:where(.ui) .ui-wide.on { background: var(--ink); border-color: var(--ink);
                          color: var(--card) }
@media (max-width: 1400px) { :where(.ui) .ui-wide { display: none } }

/* ── ручка, переключатель и перестановка строк (`dragHandle`, `toggle`,
   `sortable`) ─────────────────────────────────────────────────────
   Вид — из РНП «Мандаринки», размеры те же. Ручка — рисунок, а не глиф «⠿»:
   глиф сидел выше середины строки. `touch-action: none` — иначе палец
   на ручке прокручивал бы страницу, а не тащил строку. */
:where(.ui) .ui-drag { color: var(--line); cursor: grab; flex: none; width: 12px; height: 16px;
                       display: inline-flex; align-items: center; justify-content: center;
                       touch-action: none; user-select: none; border-radius: 4px }
:where(.ui) .ui-drag svg { width: 8px; height: 14px; display: block }
:where(.ui) .ui-drag:hover, :where(.ui) .ui-drag:focus-visible { color: var(--ink-2) }
:where(.ui) .ui-drag:focus-visible { outline: 2px solid var(--ink); outline-offset: 2px }
:where(.ui) .ui-switch { width: 30px; height: 17px; border-radius: 9px; background: var(--plate);
                         position: relative; flex: none; cursor: pointer; border: 0; padding: 0 }
:where(.ui) .ui-switch::after { content: ""; position: absolute; top: 2px; left: 2px; width: 13px;
                                height: 13px; border-radius: 50%; background: var(--card) }
:where(.ui) .ui-switch.on { background: var(--ink) }
:where(.ui) .ui-switch.on::after { left: 15px }
:where(.ui) .ui-switch:disabled { cursor: default; opacity: .5 }
:where(.ui) .ui-switch:focus-visible { outline: 2px solid var(--ink); outline-offset: 2px }
/* Строку несут: на её месте пустая ячейка, черта — куда ляжет. Черта
   чёрная, а не оранжевая: перестановка ничего необратимого не делает. */
:where(.ui) .ui-sort-src { background: var(--soft) }
:where(.ui) .ui-sort-src > * { visibility: hidden }
:where(.ui) .ui-sort-over { box-shadow: inset 0 2px 0 var(--ink) }
:where(.ui) .ui-sort-under { box-shadow: inset 0 -2px 0 var(--ink) }
:where(.ui) [data-sort-axis="x"] > .ui-sort-over { box-shadow: -4px 0 0 -1px var(--ink) }
:where(.ui) [data-sort-axis="x"] > .ui-sort-under { box-shadow: 4px 0 0 -1px var(--ink) }
:where(.ui) .ui-sort-ghost { position: fixed; z-index: 60; margin: 0; pointer-events: none;
                             background: var(--card); border: 1px solid var(--line);
                             border-radius: 10px; box-shadow: 0 8px 24px rgb(0 0 0 / .12) }
:root[data-theme="dark"] :where(.ui) .ui-sort-ghost { box-shadow: 0 8px 24px rgb(0 0 0 / .5) }
:root.ui-sorting, :root.ui-sorting * { cursor: grabbing !important; user-select: none }

/* ── сортируемая шапка таблицы (`sortTh`) ──────────────────────────
   Вид — вариант Б владельца 16.09.2026, переехал из «Мандаринки» 16.10.2026.
   Кнопка одета как подпись шапки: шрифт, прописные и цвет — от ячейки.
   Во всю ширину ячейки — щелчок мимо подписи на пару пикселей не должен
   уходить в пустоту. */
:where(.ui) th.ui-colsort > button { all: unset; display: block; box-sizing: border-box; width: 100%;
                                     text-align: inherit; cursor: pointer; user-select: none }
:where(.ui) th.ui-colsort > button:focus-visible { outline: 2px solid var(--ink); outline-offset: 2px;
                                                   border-radius: 4px }
:where(.ui) th.ui-colsort:hover, :where(.ui) th.ui-colsort.on { color: var(--ink) }
:where(.ui) .ui-colsort-a { display: inline-block; margin-left: 5px; font-size: 10px;
                            vertical-align: 1px; opacity: .3 }
/* ⚠️ Действующая стрелка КРУПНЕЕ и плотнее подписи, а не вровень с ней
   (замечание владельца 16.09.2026): подпись — 11 px прописными, и тонкая
   стрелка того же роста рядом с ней теряется. Знак остаётся СТРЕЛКОЙ:
   треугольник читается как «раскрыть список» и не говорит, в какую сторону
   порядок. */
:where(.ui) th.ui-colsort.on .ui-colsort-a { opacity: 1; color: var(--ink); font-size: 13px;
                                             font-weight: 800; vertical-align: 0 }

/* ── ряд выбора магазинов (`scopeRow`) ────────────────────────────
   Вид — раунд 6 РНП, вариант Б; переехал из РНП 04.11.2026 значениями один
   в один (замер геометрии до и после). Поле продавца и площадки — китовое
   `checkTree({look: 'select'})`; высота — как у полей ряда отборов, иначе
   два ряда стоят разной высоты. */
:where(.ui) .ui-scope { display: flex; gap: 10px 18px; align-items: flex-end; flex-wrap: wrap }
:where(.ui) .ui-scope-f { display: flex; flex-direction: column; gap: 5px; min-width: 0 }
:where(.ui) .ui-scope-cap { font-size: var(--t-cap); color: var(--mute); font-weight: 600 }
:where(.ui) .ui-scope .ui-tree-sel { min-width: 170px; max-width: 260px }
:where(.ui) .ui-scope .ui-sel-btn { height: 34px; padding: 0 10px }
:where(.ui) .ui-scope .ui-sel-val .chip { padding: 0 6px }
:where(.ui) .ui-scope-shops { flex: 1 1 auto }
:where(.ui) .ui-stores { display: flex; gap: 8px; flex-wrap: wrap }
:where(.ui) .ui-stores-none { color: var(--mute); font-size: var(--t-b); line-height: 34px }
/* Плашка магазина: галочка, площадка, имя, свежесть данных (бывшая полоса
   «Данные на»). Снятая — тусклее рамкой и именем, но свежесть на месте: по ней
   и решают, вернуть ли магазин. Отмеченная, но не посчитанная (`refused`) —
   янтарный хвост: магазин в отборе, а в итоге его нет. */
:where(.ui) .ui-store { display: inline-flex; align-items: center; gap: 8px; height: 34px;
                        padding: 0 12px 0 10px; border: 1px solid var(--line);
                        border-radius: var(--r-s); background: var(--card); font-size: var(--t-b);
                        color: var(--ink-2); cursor: pointer; white-space: nowrap }
:where(.ui) .ui-store.on { border-color: var(--ink-2); color: var(--ink) }
:where(.ui) .ui-store.off { background: var(--soft); color: var(--mute); cursor: default }
:where(.ui) .ui-store b { font-weight: 700; color: inherit }
:where(.ui) .ui-store .chip { padding: 0 6px }
:where(.ui) .ui-store-at { display: inline-flex; align-items: center; gap: 4px; color: var(--mute);
                           font-size: var(--t-s) }
:where(.ui) .ui-store-at svg { width: 12px; height: 12px }
:where(.ui) .ui-store.refused { border-color: var(--warn-fg) }
:where(.ui) .ui-store.refused .ui-store-at { color: var(--warn-fg) }

/* ── ячейка товара (`goodCell`, `cardLine`, `codeButton`) ─────────
   Из «Обзора» РНП 04.11.2026, значения один в один. Ячейка — flex-строка:
   картинка, потом текст. `float` не годится: он вынимает картинку из потока,
   и текст обтекает её по букве. Строки карточек — под кодом товара (вариант А,
   раунд 5). Товар блоком (матрица РНП) кладёт их столбиком — своим правилом. */
:where(.ui) .ui-code { border: 0; background: transparent; padding: 1px 4px; border-radius: 5px;
                       font: inherit; font-size: var(--t-s); color: var(--ink-2); cursor: pointer;
                       max-width: 128px; overflow: hidden; text-overflow: ellipsis;
                       white-space: nowrap }
:where(.ui) .ui-code:hover { background: var(--soft); color: var(--ink) }
:where(.ui) .ui-code.sub { color: var(--mute); cursor: default }
:where(.ui) .ui-code.sub:hover { background: transparent; color: var(--mute) }
:where(.ui) .ui-pcode { font-weight: 800; color: var(--ink) }
:where(.ui) .ui-good { display: flex; gap: 12px; align-items: flex-start; min-width: 0 }
:where(.ui) .ui-good-text { min-width: 0; display: flex; flex-direction: column;
                            align-items: flex-start; gap: 2px }
:where(.ui) .ui-good-thumb { width: 40px; height: 53px; flex: 0 0 auto; background: var(--soft) }
:where(.ui) .ui-good-name { color: var(--ink); font-weight: 600; max-width: 100%;
                            overflow: hidden; text-overflow: ellipsis; white-space: nowrap }
:where(.ui) .ui-good .ui-code { margin-left: -4px }
:where(.ui) .ui-cards { width: 100%; display: flex; flex-direction: row; flex-wrap: wrap;
                        align-items: center; justify-content: flex-start; gap: 2px 14px;
                        margin-top: 2px; padding-top: 4px; border-top: 1px solid var(--line-soft) }
:where(.ui) .ui-card { display: flex; flex-direction: row; align-items: center; gap: 4px }
:where(.ui) .ui-card-store { display: inline-flex; align-items: center; gap: 5px;
                             font-size: var(--t-s); color: var(--ink-2); white-space: nowrap;
                             overflow: hidden }
:where(.ui) .ui-card-store .chip { padding: 0 6px; font-size: var(--t-cap) }

@media (max-width: 760px) {
  /* Продавец и площадка — пополам строки, плашки магазинов — по одной в строку,
     свежесть прижата вправо. Коду на телефоне ширины не жалко. */
  :where(.ui) .ui-scope > .ui-scope-f:not(.ui-scope-shops) { flex: 1 1 0 }
  :where(.ui) .ui-scope .ui-tree-sel { min-width: 0; max-width: none; width: 100% }
  :where(.ui) .ui-scope-shops { flex: 1 0 100% }
  :where(.ui) .ui-store { flex: 1 1 100% }
  :where(.ui) .ui-store-at { margin-left: auto }
  :where(.ui) .ui-code { max-width: none }
}

/* ── «Запомнить период» (`periodMemory().button`) ─────────────────
   Значения — замка РНП (скруглённый квадрат, решение владельца 15.09.2026;
   слово рядом — 03.10.2026). Нажат — залит чернилами; запомненный период,
   которого здесь не показывают, — тревожный контур, а не заливка. */
:where(.ui) .ui-keep { display: inline-flex; align-items: center; justify-content: center; gap: 6px;
                       height: 26px; padding: 0 10px; flex: none; border-radius: var(--r-s);
                       border: 1px solid var(--line); background: transparent; color: var(--mute);
                       cursor: pointer; font: inherit; font-size: var(--t-s); font-weight: 700;
                       line-height: 1; white-space: nowrap }
:where(.ui) .ui-keep:hover { border-color: var(--frame); color: var(--ink) }
:where(.ui) .ui-keep svg { width: 13px; height: 13px }
:where(.ui) .ui-keep.on { background: var(--ink); border-color: var(--ink); color: var(--card) }
:where(.ui) .ui-keep.warn { border-color: var(--warn-fg); color: var(--warn-fg); background: var(--warn-bg) }
