/* Компоненты vesquadra поверх Bootstrap — этап 3 UI-фундамента (ui-plan.md
   ч.2, ч.3.2). Порядок каскада: base.css → bootstrap.min.css → tokens.css →
   этот файл. Здесь: мост совместимости с Bootstrap reboot, статусные бейджи
   на Bootstrap-механике с нашими токенами, утилиты чисел и кодов. */

/* ── Мост совместимости с Bootstrap reboot ──────────────────────────────
   Reboot глобально меняет элементные дефолты (размеры заголовков, margins
   абзацев и списков, жирность dt, small). Текущий вид программы — эталон
   до референсов Ильи, поэтому возвращаем браузерные значения там, где
   каркас на них живёт. Пересмотр моста — при применении референсов
   (этап 10). */

/* Канон «Модули»: заголовки — Manrope (через --font-sans / --font-ui), без засечек */
h1, h2, h3, h4, h5, h6 { font-family: var(--font-sans); }
h1 { font-size: 2em; font-weight: 700; margin: .67em 0; line-height: normal; }
h2 { font-size: 1.5em; font-weight: 700; margin: .83em 0; line-height: normal; }
h3 { font-size: 1.17em; font-weight: 700; margin: 1em 0; line-height: normal; }
h4 { font-size: 1em; font-weight: 700; margin: 1.33em 0; line-height: normal; }
h5 { font-size: .83em; font-weight: 700; margin: 1.67em 0; line-height: normal; }
h6 { font-size: .67em; font-weight: 700; margin: 2.33em 0; line-height: normal; }
p { margin: 1em 0; }
ul, ol { margin: 1em 0; padding-left: 40px; }
dl { margin: 1em 0; }
dt { font-weight: normal; }
small { font-size: smaller; }

/* ── Статусные бейджи: Bootstrap .badge + наши токены ───────────────────
   Разметка прежняя (`badge badge-active|-eol|-off`); базовую механику даёт
   Bootstrap-компонент, вид диктуют токены через его компонентные
   переменные (они объявлены на самом .badge, поэтому переопределяются
   здесь, а не с :root). */

.badge {
  --bs-badge-padding-x: 8px;
  --bs-badge-padding-y: 2px;
  --bs-badge-font-size: var(--fs-11);
  --bs-badge-font-weight: 600;
  --bs-badge-border-radius: var(--radius-1);
  line-height: normal; /* как прежний бейдж; у Bootstrap 1 — строка ниже */
}
.badge-active { background: var(--ok-bg); color: var(--ok); }
.badge-eol { background: var(--danger-bg); color: var(--danger); }
.badge-off { background: var(--neutral-bg); color: var(--neutral); }

/* ── Кружок присутствия .status-dot: канон «Сотрудники», витрина /ui/ (раздел 12 паспорта снят 15.09, решения о виде — в карточках VES-DESIGN).
   Лампочка 9px: .is-online — var(--ok) зелень, .is-offline — var(--neutral)
   («выключено» = #6B7280 по карточке «Палитра»; волна 1, доводка 08.09,
   было --gray-400 старой шкалы). Без текста — aria-label на span обязателен
   для скринридеров. */
.status-dot {
  display: inline-block;
  width: 9px; height: 9px;
  border-radius: 50%;
  vertical-align: middle;
}
.status-dot.is-online  { background: var(--ok); }
.status-dot.is-offline { background: var(--neutral); }

/* ── Утилиты чисел и кодов (ui-plan.md ч.3.1) ──────────────────────────── */

/* Деньги и числовые колонки: табличные цифры, выравнивание вправо.
   white-space:nowrap добавлен волной 2 (перенос «База знаний», 11.09) —
   находка Дизайнера на узкой ширине (1366 + открытая панель помощника):
   без него короткие значения вроде «156 КБ» переносились по слову. */
.num { font-variant-numeric: tabular-nums; text-align: right; white-space: nowrap; }
/* Коды 1С, артикулы, EAN, даты — IBM Plex Mono */
.mono { font-family: var(--font-mono); }
/* Встроенный код в тексте */
code { font-family: var(--font-mono); font-size: var(--fs-12); }

/* .spinner ПЕРЕД .htmx-indicator ниже — порядок несущий, не косметика
   (Ревизор, находка 3 приёмки 11.09). Заглушка ожидания чата ставит .spinner
   БЕЗ .htmx-indicator (она не привязана к живому htmx-запросу — стоит, пока
   идёт фоновая задача) — как обычный <span>, inline по умолчанию, он
   игнорирует width/height и рисовался чёрточкой шириной с border, а не
   12px-кругом. У .spinner и .htmx-indicator одинаковая специфичность
   (один класс) — при равенстве побеждает ПОСЛЕДНЕЕ правило в файле, поэтому
   .spinner обязан стоять РАНЬШЕ .htmx-indicator{display:none}, иначе кнопка
   «Отправить» (несёт ОБА класса) показывала бы спиннер постоянно, а не
   только во время запроса. */
.spinner {
  display: inline-block;
  width: 12px; height: 12px; margin-left: 6px; vertical-align: -1px;
  border: 2px solid currentColor; border-top-color: transparent;
  border-radius: var(--radius-pill);
  animation: spin .6s linear infinite;
}
@keyframes spin { to { transform: rotate(360deg); } }

/* ── Индикация htmx-запроса (этап 4) ────────────────────────────────────
   Элемент с .htmx-indicator виден только пока идёт запрос: htmx ставит
   класс .htmx-request на элемент с hx-indicator (по умолчанию — на сам
   инициирующий элемент/форму). */

.htmx-indicator { display: none; }
.htmx-request .htmx-indicator,
.htmx-request.htmx-indicator { display: inline-block; }

/* ── Кнопки: Bootstrap .btn + наши токены (ui-plan.md ч.3.2) ─────────────
   Механику (размер, фокус, disabled, переходы) даёт Bootstrap .btn; вид —
   наши варианты через компонентные переменные --bs-btn-* (как у .badge).
   Канонический класс новых P1-компонентов и витрины. Обещанный здесь проход P0
   по старым кнопкам закрыт 05.09, каждая по-своему: .filter-btn снят вместе с
   перелицовкой каталога — правил под него в своде нет вовсе, а его отсутствие на
   экране стережёт тест; .chat-form button оставлена вне канона решением Ильи
   (канон-рамка делает единственное действие панели чата тише сплошной заливки),
   ей дано только сжатие при нажатии; .search-form не применён ни в
   одном шаблоне — строка в дорожной карте. */

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 7px;                    /* карточка «Кнопки»: зазор между значком и подписью */
  min-height: var(--btn-h, 36px); /* НЕ `height` — находка Ревизора живьём 22.09:
     `height` — отдельное свойство, не перебивается модификаторами размера ни
     при какой специфичности (`.btn.btn-sm{min-height:26px}` не может выключить
     чужой `height`, `box`-модель берёт max(height,min-height) = 36 всегда).
     `min-height` — тот же по числу пол, но именно ЕГО модификаторы размера
     (`.btn.btn-sm`, специфичность 0,2,0) и легли-высотные виды
     (`.btn-secondary`/`.btn-inline`/`.btn-link`, ниже по файлу) обязаны уметь
     перебить — специфичностью или порядком в каскаде. */
  --bs-btn-padding-x: 14px;    /* карточка «Кнопки»: паддинг 0/14 — высоту держит min-height, не паддинг */
  --bs-btn-padding-y: 0;
  --bs-btn-font-family: var(--font-ui);
  --bs-btn-font-size: var(--fs-13); /* 13,5px — карточка «Кнопки» */
  --bs-btn-font-weight: 500;
  --bs-btn-line-height: var(--leading-flat); /* 1 — кит §04 (flat) */
  --bs-btn-border-radius: var(--radius-2);
  --bs-btn-border-width: 1px;
  /* Bootstrap-кольцо фокуса гасим: единое кольцо даёт глобальный :focus-visible
     (иначе двойное — box-shadow Bootstrap + наш outline). */
  --bs-btn-focus-box-shadow: none;
  /* Двойное гашение недоступной (находка Ревизора): карточка «недоступна» уже
     даёт свой серый фон/текст/рамку — vendor поверх ЕЩЁ умножает на opacity
     .65, контраст подписи падал до 1,72:1. Карточка прозрачности не просит —
     opacity:1 снимает второе гашение, серые --sub/--text-faint/--line
     остаются собственным, единственным способом показать «недоступна».
     Контраст #9CA3AF на #FAFBFC — 2,45:1, это число карточки, не подгонка. */
  --bs-btn-disabled-opacity: 1;
  /* Высота задана числом (карточка «Кнопки», волна 4 — решение Ильи 22.09).
     До этой волны кит высоту кнопки не фиксировал вовсе — она складывалась из
     паддингов и кегля (9+9+11.5+2 = 31.5px), а .btn-neutral в том же ряду со
     СВОИМИ паддингом/кеглем выходил 29px: ряд действий читался разнобоем.
     Явный `min-height` + нулевой вертикальный паддинг держат одно число для
     любого вида, включая .btn-neutral (его личные паддинг/кегль сняты —
     components.css, .btn-neutral). Токен --control-h (36px) намеренно не
     переиспользован: это высота ПОЛЕЙ ВВОДА, карта у него своя — числа сейчас
     совпадают, но раздел 3.9.3 паспорта уже разводил их однажды.
     Первый заход (22.09) поставил сюда `height` вместо `min-height` — поймано
     замером в браузере на приёмке, до показа Илье: `.btn.btn-sm{min-height:26px}` и лёгшие на паддинг виды
     (.btn-secondary/.btn-inline/.btn-link) растянулись до 36, потому что
     `height` не отменяется чужим `min-height` ни при какой специфичности —
     это разные свойства, и итоговая высота блока берёт max(height,min-height).
     `min-height` даёт то же число 36 для обычной кнопки (контент 13.5px
     кегля меньше 36 — пол побеждает), но оставляет модификаторам размера
     законный способ его перебить. */
  /* Сжатие при нажатии — паспорт 3.9.3 (решение Ильи 2026-08-31). Свойства
     названы поимённо, а не `all`: Bootstrap уже ведёт цвет, фон, рамку и
     тень своими переходами, и голый `transition-property: transform` их бы
     отменил — наведение стало бы резким. Длительность --dur-2 (150ms) —
     та же, что у переходов Bootstrap, чтобы нажатие и наведение шли в лад. */
  transition-property: transform, color, background-color, border-color, box-shadow;
  transition-duration: var(--dur-2);
}
/* Ровно 0.96 — паспорт 3.9.3; порог «меньше 0.95 не брать» стережёт
   test_css_btn_kit. Форма остаётся плоской, скруглений и теней правило
   не вводит. Строкам таблиц и пунктам меню сжатие не даётся: там движение
   мешает. */
.btn:active { transform: scale(0.96); }
/* Кольцо фокуса кнопки (карточка «Кнопки»: «Фокус: кольцо 3px #ECE1EE +
   рамка акцент») — находка Ревизора: общий `:focus-visible` (ниже по файлу)
   на кнопках не срабатывал вовсе. Vendor `.btn:focus-visible` (bootstrap.min.css)
   несёт ТУ ЖЕ специфичность класс+псевдокласс (0,1,1) и явно гасит контур
   (`outline:0`) — общий `:focus-visible` (0,0,1) слабее и проигрывает
   независимо от порядка файлов. Здесь та же специфичность (0,1,1), но
   components.css грузится ПОСЛЕДНИМ (base→bootstrap→tokens→этот файл) —
   при равной специфичности выигрывает более поздний по каскаду. Рамка —
   тонкий контур акцентом (тот же приём, что .f-uline:focus-within), кольцо —
   мягкое свечение 3px --plum-100 (#ECE1EE) снаружи через box-shadow. */
.btn:focus-visible {
  outline: 1px solid var(--accent);
  outline-offset: 1px;
  box-shadow: 0 0 0 3px var(--plum-100, #ECE1EE);
}
/* Площадь нажатия у встроенной кнопки строки таблицы (паспорт 3.9.3: третий
   орган, где промахнуться дорого). Зона добирается по высоте до 40
   псевдоэлементом; ширина не трогается, кнопка и так шире. Замер 05.09:
   строка справочника 44 высотой, зона в неё влезает и соседнюю строку не
   задевает. .btn-edit объявлен в шаблоне товаров (ретро-долг канона) —
   правило зоны стоит здесь, чтобы не разносить один орган по двум файлам. */
.data-table .btn-sm,
.data-table .btn-edit { position: relative; }
/* Сжатие при нажатии тем же кнопкам строки (паспорт 3.9.3). Правило стоит здесь,
   а не в шаблоне товаров: класс объявлен там, но файл держит соседняя стройка,
   а разносить один орган по двум файлам хуже, чем держать его поведение в своде.
   .btn-add — кнопка «Добавить» редактора товара, тот же случай. */
.btn-edit, .btn-add {
  transition-property: transform, color, background, border-color;
  transition-duration: var(--dur-2);
}
.btn-edit:active, .btn-add:active { transform: scale(0.96); }
.data-table .btn-sm::after,
.data-table .btn-edit::after {
  content: ""; position: absolute; top: 50%; left: 0; right: 0;
  height: 40px; transform: translateY(-50%);
}
/* Малая §04 (решение Ильи 14.09, стандарт VES-DESIGN «Кнопки»): служебный
   модификатор РАЗМЕРА (26px/11,5px/радиус 6), не цвета — сочетается с любым
   видом (.btn-primary.btn-sm — залитая малая, .btn-default.btn-sm и т.д.).
   Селектор `.btn.btn-sm` (не голый `.btn-sm`) — специфичность (0,2,0) бьёт
   ЛЮБОЙ вид (`.btn-primary`/`.btn-neutral`/… — все (0,1,0)) НЕЗАВИСИМО от
   порядка блоков в файле; голый `.btn-sm`, стоящий раньше видов, отдавал
   padding/кегль виду по каскаду (позже в файле — побеждает), находка
   Ревизора живьём («Excel» рисовался 29px/11px вместо 26/11,5). padding-y
   нулевой — высоту держит min-height, а не строка текста; display:inline-flex
   центрирует надпись внутри этой высоты; gap — свой (7px, карточка «.btn»),
   т.к. inline-flex НЕ схлопывает пробел между иконкой и подписью так, как
   это делает inline-block у обычных кнопок (текстовый узел-пробел между
   `<img>` и текстом рискует потеряться как анонимный flex-item). */
.btn.btn-sm {
  --bs-btn-padding-x: 8px;
  --bs-btn-padding-y: 0;
  --bs-btn-font-size: var(--size-control); /* 11.5px — то же число, что «кегль 11,5» карточки */
  --bs-btn-border-radius: var(--radius-1);
  min-height: 26px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 7px;
}

/* Основная §04 (решение Ильи 14.09, стандарт VES-DESIGN): залитая баклажаном —
   ОДНА на экран (паспорт 3.4.3, абзац «Волна «Кнопки»»). Была контурной
   (кромка+текст акцент, фон прозрачный, заливка только при :active) — теперь
   заливка постоянная, активная лишь темнеет. Vendor .btn-primary (bootstrap.min.css)
   объявляет 14 переменных на этом селекторе; здесь закрыты все 12 цветовых
   (color/bg/border-color × база/hover/active/disabled) — оставшиеся два,
   --bs-btn-focus-shadow-rgb и --bs-btn-active-shadow, проверены чтением
   compiled-правил: .btn:active не читает active-shadow вовсе, а focus-box-shadow
   целиком гасится на .btn{--bs-btn-focus-box-shadow:none} выше — синий Bootstrap
   ни на одном состоянии не проступает без их переопределения.
   active-bg/active-border-color — --accent-strong, НЕ --accent: иначе нажатие
   визуально неотличимо от покоя (тот же цвет). disabled — единый серый (паспорт 3.4.3). */
.btn-primary {
  --bs-btn-color: var(--text-invert);         --bs-btn-bg: var(--accent);
  --bs-btn-border-color: var(--accent);
  --bs-btn-hover-color: var(--text-invert);   --bs-btn-hover-bg: var(--accent-strong);
  --bs-btn-hover-border-color: var(--accent-strong);
  --bs-btn-active-color: var(--text-invert);  --bs-btn-active-bg: var(--accent-strong);
  --bs-btn-active-border-color: var(--accent-strong);
  --bs-btn-disabled-color: var(--text-faint);
  --bs-btn-disabled-bg: var(--sub);            --bs-btn-disabled-border-color: var(--line);
}
/* Нейтральная карточки «Кнопки» (.gho) — решение Ильи 14.09: рамка «Линия»
   видимая (была transparent — единственная кнопка совсем без рамки, отсюда и
   имя волны «кнопки с рамкой»), текст приглушённый #4B5563. Карточка называет
   токен --ink-2 — такого имени в tokens.css нет, взят --text-muted (#6B7280,
   ближайший семантический, тот же приём, что и в .gho-варианте «Кнопок
   второго набора»); расхождение — в отчёте. */
.btn-default {
  --bs-btn-color: var(--text-muted);         --bs-btn-bg: transparent;
  --bs-btn-border-color: var(--line);
  --bs-btn-hover-color: var(--text);         --bs-btn-hover-bg: var(--hover-bg);
  --bs-btn-hover-border-color: var(--line);
  --bs-btn-active-color: var(--text);        --bs-btn-active-bg: var(--hover-bg);
  --bs-btn-active-border-color: var(--line);
  --bs-btn-disabled-color: var(--text-faint);
  --bs-btn-disabled-bg: var(--sub);          --bs-btn-disabled-border-color: var(--line);
}
/* Нейтральная §04 — второй по частоте вид кита (11 вхождений: «Удалить», «Назад», «Отменить»).
   Геометрия (высота/паддинг/кегль) общая с .btn, волна 4 (решение Ильи 22.09,
   карточка «Кнопки») — своих здесь только рамка module-edge и фон module-surface.
   Hover: цвет data, фон surface-hover. Источник: _ds_bundle.js variant==='neutral'. */
/* Нейтральная §04 (кит variant=neutral, 11 вхождений: «Удалить», «Назад», «Отменить»):
   рамка --edge-module, подложка --surface-module, текст --text-muted; при наведении
   текст --text-data, фон --surface-hover. NB состояния active/disabled кит для neutral
   НЕ задаёт — заданы вынужденно: без них Bootstrap подставляет собственные дефолты
   (синие), что хуже отсутствия. Значения повторяют наведение, своего вида не вводят.
   Решение штаба по оговорке Ревизора 2026-08-20. */
/* Обычная карточки «Кнопки» (решение Ильи 14.09): текст тёмный (был приглушён)
   + тень --shadow-1 (её не было) — «обычная белая кнопка» карточки, для
   «Печать»/«Excel»/«Столбцы»/«Экспорт» в шапках экранов. --shadow-1 в
   tokens.css уже даёт литерал карточки (0 1px 2px … + 0 1px 3px …) — новый
   токен заводить не пришлось. Недоступна — без тени (правило ниже) и с
   единым серым фоном (паспорт 3.4.3) (была transparent). */
.btn-neutral {
  --bs-btn-color: var(--text-data, var(--ink, #1C2128));
  --bs-btn-bg: var(--surface-module, #FFFFFF);
  --bs-btn-border-color: var(--edge-module, var(--gray-200, #D5DAE0));
  --bs-btn-hover-color: var(--text-data, var(--ink, #1C2128));
  --bs-btn-hover-bg: var(--surface-hover, var(--gray-50, #F6F7F9));
  --bs-btn-hover-border-color: var(--edge-module, var(--gray-200, #D5DAE0));
  --bs-btn-active-color: var(--text-data, var(--ink, #1C2128));
  --bs-btn-active-bg: var(--surface-hover, var(--gray-50, #F6F7F9));
  --bs-btn-active-border-color: var(--edge-module, var(--gray-200, #D5DAE0));
  --bs-btn-disabled-color: var(--text-faint, var(--ink-faint, #9CA3AF));
  /* Рамка недоступной — --line, не --edge-module: паспорт 3.4.3 — один серый
     вид disabled у всех кнопок (сейчас оба алиасят --rule и дают тот же
     цвет, но единое имя не разъедется, если когда-нибудь один из ярусов
     токенов перевяжут по-своему). */
  --bs-btn-disabled-bg: var(--sub, #FAFBFC); --bs-btn-disabled-border-color: var(--line, var(--rule, #E5E7EB));
  box-shadow: var(--shadow-1);
}
.btn-neutral:disabled, .btn-neutral.disabled { box-shadow: none; }
/* .btn-sm — служебный размер без своей тени (карточка «малая: без тени»);
   двухклассовый селектор бьёт .btn-neutral{box-shadow} специфичностью, не
   порядком в файле — можно не сдвигать блоки. */
.btn-sm.btn-neutral { box-shadow: none; }
/* Деструктивное действие §04 — бледно-розовая заливка (решение Ильи 2026-09-01).
   Прежняя нейтраль не отличалась от «Назад»: необратимое действие стояло рядом с
   безобидным одним и тем же серым. Розовый бледный, не сигнально-красный: он
   отделяет удаление, но не кричит. Чернила красные (--danger).
   active-color — находка Ревизора: vendor bootstrap.min.css несёт СВОЙ
   .btn-danger с --bs-btn-active-color:#fff; custom-property наследуется
   поштучно, не блоком — не задав active-color здесь, получаем БЕЛЫЙ текст
   вендора на КРАСНОМ нажатии вместо своего --danger. */
.btn-danger {
  --bs-btn-color: var(--danger);             --bs-btn-bg: var(--surface-danger);
  --bs-btn-border-color: var(--edge-danger-soft);
  --bs-btn-hover-color: var(--danger);       --bs-btn-hover-bg: var(--surface-danger-strong);
  --bs-btn-hover-border-color: var(--edge-danger);
  --bs-btn-active-color: var(--danger);      --bs-btn-active-bg: var(--surface-danger-strong);
  --bs-btn-active-border-color: var(--edge-danger);
  /* Недоступна — единый серый (паспорт 3.4.3), не бледно-розовый: раньше вовсе не
     задавалась, кнопка «недоступна» проступала бы Bootstrap-дефолтом. */
  --bs-btn-disabled-color: var(--text-faint);
  --bs-btn-disabled-bg: var(--sub);          --bs-btn-disabled-border-color: var(--line);
}

/* Стадия подтверждения сохранения (решение Ильи 14.09, «при подтверждении
   кнопка светлеет»): проверено по живым шаблонам (panel_agenta.html,
   prava_dostupa.html, _sotrudnik_form.html и другие формы-сохранения) —
   .btn-arm И ЕСТЬ сама кнопка «Подтвердите сохранение», отдельной серой
   стадии ПЕРЕД ней в разметке нет (билет двухфазки — на сервере, twophase.py;
   до него форма показывает обычный .btn-primary «Сохранить»). Паспорт
   называл этот шаг .btn-commit — тот класс объявлен и тестируется только на
   витрине (демо-кнопка display:none, «Как задумывался третий этап»), ни в
   одном живом шаблоне не отрисован — расхождение в отчёте, здесь правится
   класс, который реально приходит с сервера.
   Фон карточка называет --accent-tint (#ECE1EE) — токен с этим ИМЕНЕМ в
   tokens.css уже занят другим (тем более светлым) оттенком, #ECE1EE держит
   --plum-100 (ровно тем же токеном красился снятый canon .btn-save-confirm —
   вторая, чуть разошедшаяся версия этой же роли, см. заметку у G3 ниже) —
   взят --plum-100, расхождение с именем из карточки — в отчёте. */
.btn-arm {
  --bs-btn-color: var(--accent);              --bs-btn-bg: var(--plum-100, #ECE1EE);
  --bs-btn-border-color: var(--accent-edge);
  --bs-btn-hover-color: var(--accent-strong); --bs-btn-hover-bg: var(--plum-050, #F6F0F7);
  --bs-btn-hover-border-color: var(--accent-edge);
  --bs-btn-active-color: var(--accent);       --bs-btn-active-bg: var(--plum-050, #F6F0F7);
  --bs-btn-active-border-color: var(--accent-edge);
  --bs-btn-disabled-color: var(--text-faint);
  --bs-btn-disabled-bg: var(--sub);           --bs-btn-disabled-border-color: var(--line);
}

/* Стадия подтверждения удаления — модификатор поверх .btn-arm (объявлен после
   него, иначе не перебьёт). Гуще обычной кнопки удаления: подтверждение обязано
   читаться тяжелее шага, который его вызвал. */
.btn-arm-danger {
  --bs-btn-color: var(--danger);             --bs-btn-bg: var(--surface-danger-strong);
  --bs-btn-border-color: var(--edge-danger);
  --bs-btn-hover-color: var(--danger);       --bs-btn-hover-bg: var(--edge-danger-soft);
  --bs-btn-hover-border-color: var(--edge-danger);
  /* active-color — находка Ревизора: без своего значения наследует --accent
     от .btn-arm (модификатор объявлен позже и перебивает только то, что
     сам задаёт) — на нажатии сиреневый текст поверх розовой заливки удаления. */
  --bs-btn-active-color: var(--danger);      --bs-btn-active-bg: var(--edge-danger-soft);
  --bs-btn-active-border-color: var(--edge-danger);
}
.btn-commit {
  --bs-btn-color: var(--accent);             --bs-btn-bg: transparent;
  --bs-btn-border-color: var(--accent);
  --bs-btn-hover-color: var(--accent-strong); --bs-btn-hover-bg: var(--accent-soft);
  --bs-btn-hover-border-color: var(--accent-strong);
  --bs-btn-active-color: var(--text-invert); --bs-btn-active-bg: var(--accent);
}

/* ── Фокус с клавиатуры: единое кольцо (ui-plan.md ч.3.2 P1) ─────────────
   :focus-visible показывается только при навигации с клавиатуры, не по клику
   мышью. Раньше фокус-обводки нигде не было — это добавление, а не эталон. */
:focus-visible { outline: var(--focus-ring); outline-offset: 2px; }

/* ── Состояния экрана: пусто / ошибка / нет прав (партиалы core/ui/) ───── */
.ui-state { padding: var(--sp-36) var(--sp-20); text-align: center; color: var(--text-muted); }
.ui-state-title { font-size: var(--fs-14); font-weight: 600; color: var(--text); margin-bottom: var(--sp-4); }
.ui-state-text { font-size: var(--fs-13); color: var(--text-muted); margin-bottom: var(--sp-12); }
.ui-state-error .ui-state-title { color: var(--danger); }
.ui-state-no-access .ui-state-title { color: var(--warn); }

/* ── Скелетон загрузки (партиал core/ui/_skeleton.html) ─────────────────── */
.skeleton { display: flex; flex-direction: column; gap: var(--sp-8); padding: var(--sp-12) 0; }
.skeleton-row {
  height: 14px; width: 80%; border-radius: var(--radius-1);
  background: var(--skeleton-strong);
}
.skeleton-row-wide { width: 100%; }
.skeleton-row-narrow { width: 45%; }

/* ── Тосты (уведомления): позиционируются Bootstrap-утилитами в оболочке,
   наш только z-слой поверх модалки (ui-plan.md ч.3.2 P1). ──────────────── */
.toast-container { z-index: var(--z-toast); }
/* Варианты тостов — наши статусные пары (светлый фон + тёмный текст, как
   бейджи): тёмный крестик закрытия контрастен на всех вариантах. Классы
   совпадают с картой MESSAGE_TAGS в settings (ok/danger/warn/info/neutral). */
.toast-ok      { background: var(--ok-bg);      color: var(--ok); }
.toast-danger  { background: var(--danger-bg);  color: var(--danger); }
.toast-warn    { background: var(--warn-bg);    color: var(--warn); }
.toast-info    { background: var(--info-bg);    color: var(--info); }
.toast-neutral { background: var(--neutral-bg); color: var(--neutral); }

/* ── Открытые документы: лента вкладок-закладок (канон «Ведомость») ────────
   Полоса всегда видна: «Главная» — постоянная вкладка-якорь (см. _open_documents.html),
   поэтому лента не пуста ни в одном состоянии — единый вид везде.
   Заливка полосы -- sub (подложка, тон между --paper и --sheet — волна 1,
   доводка 08.09, было --gray-100 старой шкалы), чтобы лента была заметна и
   не сливалась с фоном страницы; активная вкладка -- sheet
   (светлее полосы) читается «поднятым листом»; граница низа -- rule-strong
   (по стандарту равен --rule, одна линия #E5E7EB);
   активная «поднята» на 1px (position top:1px). */
.opendocs {
  display: flex; align-items: flex-end; gap: 6px;
  height: var(--opendocs-h, 34px);
  padding: 4px 20px 0;
  background: var(--sub); border-bottom: 1px solid var(--rule-strong);
  /* overflow-x: auto — прокрутка нужна, вкладок бывает больше ширины экрана.
     overflow-y: hidden — по вертикали лента не прокручивается по замыслу;
     полоса скрыта как в ленте вкладок браузера (scrollbar-width/::-webkit-scrollbar),
     а не средствами overflow, иначе прокрутка колесом/перетаскиванием пропадёт. */
  overflow-x: auto; overflow-y: hidden; flex-shrink: 0;
  scrollbar-width: none;
}
.opendocs::-webkit-scrollbar { display: none; }
.opendocs-empty { display: none; }
.opendoc {
  display: inline-flex; align-items: center; gap: 2px;
  padding: 6px 14px 7px; max-width: 240px; white-space: nowrap;
  /* Площадь нажатия — паспорт 3.9.3 (решение Ильи 2026-08-31): зона крестика
     40×40 не должна лечь на подпись вкладки — это соседний орган (переключение),
     поэтому промежуток до крестика разводит их зоны. Высота вкладки НЕ растёт:
     зона выходит за неё вверх и вниз, в поля полосы, где нажимать нечего.
     Так плотность шапки остаётся прежней. */
  gap: 14px; padding-right: 6px; min-height: 40px;
  font-size: var(--fs-12); color: var(--ink-soft);
  background: transparent; border: 1px solid transparent; border-bottom: none;
  position: relative; top: 1px;
}
.opendoc-label {
  text-decoration: none; color: inherit;
  overflow: hidden; text-overflow: ellipsis; cursor: pointer;
}
.opendoc-label:hover { color: var(--ink); }
/* Активная вкладка — лист, рамка rule-strong, Manrope (без PT Serif) */
.opendoc.is-active {
  background: var(--sheet); border-color: var(--rule-strong);
  color: var(--ink); font-family: var(--font-sans); font-weight: var(--weight-medium);
}
.opendoc.is-active .opendoc-label { color: var(--ink); }
.opendoc-close {
  border: 0; background: none; cursor: pointer; line-height: 1;
  color: var(--ink-faint); font-size: 11px; padding: 0 3px;
  border-radius: var(--radius-1);
}
.opendoc-close:hover { color: var(--danger); background: var(--hover-bg); }
/* Зона нажатия 40×40 псевдоэлементом — знак остаётся 11px, вид не меняется
   (паспорт 3.9.3, «Площадь нажатия»). Зона центрирована на крестике и держится
   внутри вкладки: соседний орган — подпись — отодвинут промежутком. */
.opendoc-close { position: relative; }
.opendoc-close::after {
  content: ""; position: absolute; top: 50%; left: 50%;
  width: 40px; height: 40px; transform: translate(-50%, -50%);
}

/* ── Рабочая область вкладок: панели держатся в DOM, видна только активная ──
   Панель неактивной вкладки не удаляется (её состояние — форма, фильтры, скролл —
   переживает переключение), а прячется. tabs.js переключает .is-active. */
.tab-pane { display: none; }
.tab-pane.is-active { display: block; }
/* Внутри .content--stretch (волна 2, добор 10.09, base.css) — вкладки тоже
   флекс-цепочка, передающая растяжку насквозь до .table-mod внутри пары.
   Модификатор родителя не запирает правило, как утверждалось раньше: панели
   ВСЕХ открытых документов — соседи в одном #tab-panes одного
   <main class="content…"> (base.html), поэтому .content--stretch на главной
   вкладке (каталог) распространяется на #tab-panes целиком, включая панели
   документов без своего .table-mod (карточка/правка товара, поставщики) —
   найдено приёмкой 10.09 живым замером (панель товара вкладкой из «Товаров»:
   803×748, display:flex, нижний отступ 28px потерян; той же панелью
   отдельной страницей — 803×1461, display:block, отступ на месте).
   Растяжку получает только панель, которой она нужна — с модулем таблицы
   внутри (:has(.table-mod)); панели без него остаются на исходном
   .tab-pane.is-active { display:block } выше. */
.content--stretch .tab-panes {
  display: flex;
  flex-direction: column;
  flex: 1;
  min-height: 0;
}
.content--stretch .tab-pane.is-active:has(.table-mod) {
  display: flex;
  flex-direction: column;
  flex: 1;
  min-height: 0;
}
/* Остаток находки выше: панель без .table-mod теряет свой padding-bottom при
   переполнении флекса — чинить изнутри САМОЙ переполняющейся блочной панели
   (::after-ребёнком в обычном потоке .tab-pane), не паддингом снаружи: сосед
   по тому же флекс-контейнеру (.content) ловит тот же квирк. Детали замера —
   в докстринге test_plain_pane_gets_bottom_spacer ниже. */
.content--stretch .tab-pane.is-active:not(:has(.table-mod))::after {
  content: "";
  display: block;
  height: 28px;
}

/* ── Метка авторства поля: заполнил ИИ / человек (ui-plan.md ч.3.2 P1) ──── */
.mark {
  display: inline-block; padding: 1px 6px; border-radius: var(--radius-1);
  font-size: var(--fs-11); font-weight: 600; line-height: 1.5;
}
.mark-ai { background: var(--mark-ai-bg); color: var(--mark-ai); }
.mark-human { background: var(--mark-human-bg); color: var(--mark-human); }

/* ── Ячейка цены «нет прав»: заглушка вместо значения ────────────────────
   Оформление; само право режется на сервере при сборке данных (ui-plan.md
   ч.3.3 п.8) — на клиент значение не приходит. */
.price-hidden {
  display: inline-block; color: var(--price-hidden); background: var(--price-hidden-bg);
  padding: 0 8px; border-radius: var(--radius-1); font-size: var(--size-body);
  letter-spacing: 1px;
}

/* ── Стадия «сохранено» двухшаговой кнопки (текст-подтверждение) ────────── */
.two-phase-done { color: var(--confirm-commit); font-size: var(--fs-13); font-weight: 600; }
.two-phase-summary { font-size: var(--fs-13); color: var(--text); margin-bottom: var(--sp-8); }
.two-phase-summary .from { color: var(--text-muted); }
.two-phase-summary .to { color: var(--text); font-weight: 600; }

/* ── Боковая панель (sidebar-panel) ─────────────────────────────────────────
   Переиспользуемый компонент: партиал core/_sidebar.html.
   Ширина задаётся через CSS-токен --sidebar-w на экране-потребителе. */

.sidebar-panel {
  width: var(--sidebar-w);
  flex: 0 0 var(--sidebar-w);
  border-right: 1px solid var(--line);
  padding: var(--sp-12) 0;
  background: var(--panel);
  overflow-y: auto;
}

.sidebar-panel__title {
  padding: 0 var(--sp-12) var(--sp-8);
  font-size: var(--fs-11);
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: .05em;
  color: var(--text-faint);
}

.sidebar-panel__list {
  list-style: none;
  margin: 0;
  padding: 0;
}

.sidebar-panel__link {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: var(--sp-4) var(--sp-12);
  font-size: var(--fs-13);
  color: var(--text);
  text-decoration: none;
  gap: var(--sp-8);
}

.sidebar-panel__link:hover {
  background: var(--hover-bg);
  color: var(--text);
}

.sidebar-panel__link.is-active {
  background: var(--accent-soft);
  color: var(--accent);
  font-weight: 600;
}

.sidebar-panel__label { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

.sidebar-panel__badge {
  flex-shrink: 0;
  font-size: var(--fs-11);
  color: var(--text-faint);
  background: var(--neutral-bg);
  padding: 1px var(--sp-4);
  border-radius: var(--radius-pill);
  font-variant-numeric: tabular-nums;
}

/* ── База знаний: раскладка экранов (kb-*) ───────────────────────────────── */

.kb-layout {
  display: flex;
  height: 100%;
  min-height: 0;
}

/* Волна 2 (перенос «База знаний», 11.09) — «Библиотека» получила
   .screen-head сверху (заголовок + «+ Загрузить файл»), которого раньше не
   было: height:100% у .kb-layout считал не ту высоту (соседство с заголовком
   внутри content--stretch). Модуль (.table-mod) должен забирать именно
   остаток высоты под заголовком и растягиваться сам — тот же приём, каким
   уже размечен .table-mod в content--stretch (ui-kit «Модуль таблицы»). */
.content--stretch .kb-layout { flex: 1; min-height: 0; height: auto; }
/* Без min-width:0 флекс-элемент в строке не сжимается ниже содержимого
   (естественной ширины таблицы) и раздувает .kb-layout шире родителя. */
.kb-layout .table-mod { min-width: 0; }
/* Доводка приёмки (11.09): единственная колонка, которую есть смысл сжимать
   («Название»), держала фиксированный потолок общего правила выше по файлу
   — на 1440 без панели помощника это оставляло таблицу шире модуля и давало
   лишнюю горизонтальную прокрутку. width:100% отдаёт ей весь остаток ширины
   ряда, max-width:0 разрешает сжаться ниже контента (многоточие продолжает
   резать длинное имя, остальные свойства берутся из общего правила выше). */
.kb-layout .table-mod .data-table td.dt-strong { max-width: 0; width: 100%; }
/* Тот же приём общим модификатором — не привязан к .kb-layout (волна 2,
   шестой заход прайсов, 14.09, корзина «Сошлось»). Решение Ильи: «Наш товар»
   забирает остаток ширины, «Название поставщика» остаётся своей колонкой;
   пол 160px — без него на 1440 без панели помощника колонка могла бы
   схлопнуться до нуля при длинных значениях соседних колонок. С открытой
   панелью помощника таблица прокручивается вбок внутри .table-mod-wrap —
   принято сознательно, не режем состав колонок дальше. */
.table-mod.table-mod--name-rest .data-table td.dt-strong {
  max-width: 0;
  width: 100%;
  min-width: 160px;
}
/* Зазор сайдбар/модуль: «Библиотека» ставит .table-mod ПРЯМЫМ соседом
   .sidebar-panel, без обёртки .kb-main (см. выше — .kb-main несёт
   overflow-y:auto, что дало бы двойной скролл поверх собственной прокрутки
   модуля). У .kb-main уже есть свой padding: var(--sp-16) слева — зазор для
   «О программе» есть и без этого правила; :has(> .table-mod) не задевает ту
   раскладку, только новую (иначе зазор задвоился бы 16+16). */
.kb-layout:has(> .table-mod) { gap: var(--sp-16); }

.kb-main {
  flex: 1;
  min-width: 0;
  padding: var(--sp-16);
  overflow-y: auto;
}

.kb-dropzone {
  border: 2px dashed var(--line);
  border-radius: var(--radius-2);
  padding: var(--sp-20);
  text-align: center;
  cursor: pointer;
  background: var(--bg);
  transition: background var(--dur-2), border-color var(--dur-2);
  margin-bottom: var(--sp-8);
}

.kb-dropzone.is-dragover {
  background: var(--accent-soft);
  border-color: var(--accent);
}

.kb-dropzone__label { cursor: pointer; display: block; }
.kb-dropzone__icon { font-size: var(--fs-20); display: block; margin-bottom: var(--sp-4); }
.kb-dropzone__hint { font-size: var(--fs-13); color: var(--text-muted); }
.kb-dropzone__input { display: none; }

/* Просмотрщики файлов */
.kb-viewer { margin-top: var(--sp-8); }

.kb-viewer-frame {
  width: 100%;
  height: 70vh;
  border: 1px solid var(--line);
  border-radius: var(--radius-2);
}

.kb-viewer-img {
  max-width: 100%;
  border-radius: var(--radius-2);
  border: 1px solid var(--line);
}

.kb-viewer-text {
  font-family: var(--font-mono);
  font-size: var(--fs-12);
  white-space: pre-wrap;
  overflow-x: auto;
  background: var(--bg);
  border: 1px solid var(--line);
  border-radius: var(--radius-2);
  padding: var(--sp-12);
  max-height: 70vh;
  overflow-y: auto;
}

.kb-viewer-other {
  padding: var(--sp-36);
  text-align: center;
  color: var(--text-muted);
}

/* Страница «О программе» — три колонки: «Разделы» (sidebar-panel) + текст.
   Текст на всю ширину своего блока (--kb-main), поэтому max-width не задаём. */
.kb-about {
  font-size: var(--fs-14);
}

.kb-about h1 { margin-bottom: var(--sp-16); }
.kb-about h2 { margin-top: 0; margin-bottom: var(--sp-8); }
.kb-about h3 { margin-top: var(--sp-20); margin-bottom: var(--sp-4); font-size: var(--fs-16); }
.kb-about .kb-lead { font-size: var(--fs-16); }
.kb-about .kb-muted { color: var(--text-muted); }

/* Кнопка-переключатель в колонке «Разделы»: вид ссылки, семантика <button>. */
.sidebar-panel__link--btn {
  width: 100%;
  border: 0;
  background: transparent;
  text-align: left;
  font: inherit;
  cursor: pointer;
  color: var(--text);
}

/* Режим переключения включает JS: класс is-switching ставится скриптом.
   Без JS класса нет → все разделы видимы (деградация в сплошной текст,
   ничего не пропадает). */
.kb-about.is-switching .kb-sec { display: none; }
.kb-about.is-switching .kb-sec.is-active { display: block; }

/* ── Форма-сохранение: канон «Заголовок экрана + паттерн сохранения» (витрина /ui/) ────
   Обязательный компонент экранов настройки/редактирования: заголовок экрана с
   действиями справа наверху (Сохранить/Отменить/Назад в .screen-head-actions,
   у «Назад» — не внизу формы), лист-бланк (sheet), блоки поля (f-block), радио-
   реестр, подменю выбора (модель), сегменты уровня с подписью-эффектом. Статус
   записи — .form-status под заголовком. Двухэтапность держит сервер (билет
   twophase.py; .btn-arm/.btn-commit красят стадии). Кнопки submit — вне <form>,
   связаны через HTML5-атрибут form=. Поля — линия-подчёркивание, не боксы. */

/* Заголовок экрана: титул серифом + действия справа наверху */
.screen-head {
  display: flex; align-items: baseline; justify-content: space-between;
  gap: var(--sp-16); margin-bottom: var(--sp-16);
}
/* Заголовок экрана §06: Golos Text 600 22px/1.15 трекинг -.02em (карточка
   «Типографика», волна 1, доводка 08.09 — было 1/-.045em) */
.screen-head h1 {
  margin: 0;
  font-family: var(--font-sans);
  font-size: var(--size-screen-title);
  font-weight: var(--weight-semibold);
  letter-spacing: var(--track-screen-title, -.02em);
  line-height: var(--leading-screen-title, 1.15);
}
.screen-head-actions { flex-shrink: 0; display: flex; gap: var(--sp-8); align-items: baseline; }
/* Подпись магазина рядом с заголовком — карточка VES-DESIGN «Экран „Кабинет“»
   (.meta рядом с h1), решение Ильи 17.09 «Второй магазин и хвосты» (вариант А).
   Замер Ревизора 18.09 (1280px): .screen-head — flex+space-between, без
   margin-right:auto третий ребёнок уезжает в середину (h1 right=304px,
   meta left=457px); без white-space:nowrap «BBQ Expert ru» рвётся на три
   строки (43px шириной); align-self:baseline держит подпись на общей
   базовой линии .screen-head (align-items: baseline). */
.screen-head-meta {
  font-size: var(--fs-12); color: var(--text-faint); font-weight: 400;
  white-space: nowrap; margin-right: auto; align-self: baseline;
}
.screen-sub { margin: 0 0 var(--sp-20); font-size: var(--fs-13); color: var(--text-muted); }

/* ── Канон «Модули» G3: заголовок экрана с паттерном сохранения ──────────────
   Новый канон волны: название слева + строка действий справа, под ними жирная линия.
   Строка действий (.save-head-actions) меняет состав по состоянию:
   (clean) «Назад» · (dirty) «Отменить» + «Сохранить» ·
   (confirm) «Отменить» + «Подтвердите сохранение» (.btn-arm) ·
   (saved) «Сохранено».
   .screen-head и .save-head существуют раздельно и не пересекаются каскадом.
   .btn-save-confirm снят волной «кнопки с рамкой» (14.09, находка Ревизора):
   после переноса реальной ARM-стадии на .btn-arm (везде в живых шаблонах)
   этот класс остался только на витрине и красил ТУ ЖЕ роль другим текстом
   (--ink вместо --accent) — второй, слегка расходящийся канон одного и того
   же состояния. Витрина переведена на .btn-arm ниже. */

/* Заголовок экрана (канон G3): название слева, действия справа, линия снизу */
.save-head {
  display: flex; align-items: center; justify-content: space-between;
  gap: var(--space-8); padding-bottom: var(--space-6);
  border-bottom: 1px solid var(--edge-strong);
}
/* Заголовок экрана G3: Golos Text 600 22px/1.15 трекинг -.02em (карточка
   «Типографика», волна 1, доводка 08.09 — было 1/-.045em) */
.save-head h1 {
  margin: 0;
  font-family: var(--font-sans);
  font-size: var(--size-screen-title);
  font-weight: var(--weight-semibold);
  letter-spacing: var(--track-screen-title, -.02em);
  line-height: var(--leading-screen-title, 1.15);
}
.save-head-actions { flex-shrink: 0; display: flex; gap: var(--space-6); align-items: center; }

/* Стандарт формы — dirty-кнопки (form-dirty.js): «Сохранить»/«Отменить» живут в
   #dirty-actions (или data-dirty-actions — экраны, где документ может открыться
   в нескольких панелях сразу и общий id дал бы дубль, находка Ревизора
   2026-08-31) и скрыты на чистом GET (атрибут hidden), проступают при первом
   изменении поля формы; «Назад» вне контейнера — видна всегда. В состоянии ARM
   сервер отдаёт контейнер без hidden (двухфазка требует «Подтвердить»). */
#dirty-actions, [data-dirty-actions] { display: inline-flex; gap: var(--sp-8); }
#dirty-actions[hidden], [data-dirty-actions][hidden] { display: none; }

/* Постоянный ряд «Назад» (+ метка «Сохранено», карточка VES-DESIGN «Заголовок
   экрана и сохранение», 15.09): скрыт формой на стадиях «есть изменения» и
   «подтверждение» (form-dirty.js, атрибут data-dirty-back), виден на «без
   изменений» и «сохранено». Явный display нужен по той же причине, что и у
   #dirty-actions выше — автор-правило должно перебить UA-правило [hidden]. */
[data-dirty-back] { display: inline-flex; align-items: baseline; gap: var(--sp-8); }
[data-dirty-back][hidden] { display: none; }

/* Лист-бланк: поверхность документа (линия вместо тени, канон).
   Радиус --radius-3 (10, «модуль») — волна 1, доводка (08.09): лист карточки
   по стандарту это модуль, не элемент (было --radius-2 = 8). */
.sheet {
  background: var(--sheet); border: 1px solid var(--line);
  border-radius: var(--radius-3); padding: var(--sp-24);
}
.form-sheet { max-width: 560px; }

/* Блок поля */
.f-block { margin-bottom: var(--sp-24); }
.f-block:last-of-type { margin-bottom: 0; }
.f-label {
  display: block; margin-bottom: var(--sp-8);
  font-size: var(--fs-13); font-weight: 700; color: var(--text);
}
.f-label-opt { font-weight: 400; color: var(--text-faint); }
.f-help { margin: var(--sp-8) 0 0; font-size: var(--fs-12); color: var(--text-muted); font-style: italic; }

/* Поле-подчёркивание (линия, не бокс). Общая высота input/select — родной select
   даёт браузеру больший intrinsic line-box, чем input, на тех же padding и
   font-size (Chrome: ~28px против ~26px) — фиксируем line-height явно на обоих,
   а не гадаем отступом: это единственное свойство, которого не хватало для
   равенства строк (padding и font-size уже совпадали). */
.f-uline { display: block; border-bottom: 1px solid var(--rule-strong); }
.f-uline input,
.f-uline select {
  width: 100%; box-sizing: border-box; border: 0; background: transparent;
  padding: var(--sp-4) 0; font-size: var(--fs-13); line-height: 20px; color: var(--text);
  font-family: var(--font-ui);
}
/* outline:none только когда фокус НЕ клавиатурный (:not(:focus-visible)) — иначе
   этот селектор (сила 0,2,1) молча перебивает глобальное кольцо :focus-visible
   (сила 0,1,0) и на клике мышью, и при Tab: измерено, кольцо пропадало в обоих
   случаях. :not(:focus-visible) заставляет правило не совпадать вовсе, когда
   фокус клавиатурный, — кольцо остаётся глобальному правилу. */
.f-uline input:focus:not(:focus-visible),
.f-uline select:focus:not(:focus-visible) { outline: none; }
/* Своя стрелка списка вместо браузерной: appearance:none снимает нативный вид
   (в т.ч. синее кольцо фокуса Chrome/Edge), двойной linear-gradient рисует
   шеврон цветом токена --text-muted — не растровая иконка, не сырой hex, меняется
   вместе с темой, если та появится. padding-right освобождает место под стрелку,
   чтобы длинный текст опции её не перекрывал. Кликабельность и открытие с
   клавиатуры (Enter/Space/стрелки) — родное поведение select, appearance:none
   его не трогает. */
.f-uline select {
  padding-right: var(--sp-20);
  -webkit-appearance: none;
  -moz-appearance: none;
  appearance: none;
  background-image:
    linear-gradient(45deg, transparent 50%, var(--text-muted) 50%),
    linear-gradient(135deg, var(--text-muted) 50%, transparent 50%);
  background-position: calc(100% - 12px) center, calc(100% - 7px) center;
  background-size: 5px 5px, 5px 5px;
  background-repeat: no-repeat;
}
.f-uline:focus-within { border-bottom-color: var(--accent); }

/* Ошибка поля — вариант A: красное подчёркивание (border-bottom), 2px вместо 1px.
   Применяется к обёртке .f-uline при наличии ошибок Django-формы.
   При фокусе остаётся красным (не сбрасывается в --accent).
   Строка причины .f-error-msg идёт сразу после .f-uline внутри .f-block.
   Якорь скролла: Django рендерит первое ошибочное поле с autofocus. */
.f-uline--error {
  border-bottom-color: var(--error-field);
  border-bottom-width: 2px;
}
.f-uline--error:focus-within { border-bottom-color: var(--error-field); }
.f-error-msg {
  display: flex; align-items: baseline; gap: var(--sp-4);
  font-size: var(--fs-12); color: var(--error-field);
  margin-top: var(--sp-4); line-height: 1.4;
}
.f-error-msg::before { content: "\26A0"; font-style: normal; flex-shrink: 0; }

/* Поле со знаком единицы: короткое числовое поле + знак валюты/процента рядом
   с полем, а не в подписи (правка Ильи 09.09). Перенесено из карточки
   поставщика (postavshchiki/edit.html) в общий набор 14.09.2026 (схема
   расходов, стройка 2) — второй потребитель (категория, «Логистика за кг»)
   появился на канон-разметке .f-uline, где общее правило `.f-uline input {
   width: 100% }` растянуло бы число на всю линию и увело знак далеко вправо.
   `max-width` здесь недостаточен: `.f-uline` — flex-item с процентным
   ребёнком (`input{width:100%}`) без собственной заданной ширины, браузер
   резолвит его basis мимо max-width (живой замер CDP дал 79px вместо
   заявленных ~120 при одном max-width). Фикс — ЯВНАЯ `width: 120px` +
   `flex: none` (не участвует в flex-shrink/grow): живой замер (headless
   Edge, 1440 и 1280 — на этой форме результат ОДИНАКОВ, .sheet не тянется
   за вьюпорт) дал ровно 120px поле, зазор 8px до знака «$», знак не
   перекрывает соседей. Ширина у полей поставщика — тем же числом, но там
   ограничение стоит локально (`.doc-editor input[type="number"] {
   max-width: 120px }`) на обычном (не flex) блоке, где max-width работает
   штатно. */
.field-with-unit { display: flex; align-items: center; gap: var(--sp-8); }
.field-with-unit .f-uline { width: 120px; flex: none; }
.field-unit { font-size: var(--fs-13); color: var(--text-muted); }

/* Пара полей в одну строку: слева правимое поле, справа связанный вычисленный
   сосед (капс-подпись + .inset) — «Логистика за кг» / «Логистика монофура»
   (паспорт 6.9.3, прототип «Справочники расходов» 10.09). */
.f-pair { display: flex; gap: var(--sp-24); align-items: flex-start; }
.f-pair > .f-block { flex: 1; margin-bottom: 0; }

/* Выпадашка роли колонки предпросмотра прайса (.col-role-select, _preview.html,
   решение Ильи 04.09). Не обёрнута в .f-uline (голая ячейка в шапке таблицы) —
   сохраняет коробку/кромку Bootstrap .form-select, но снимает браузерную
   стрелку тем же приёмом, что .f-uline select выше (правило «Браузерные
   умолчания»): appearance:none + свой двойной linear-gradient-шеврон, later в
   каскаде (components.css после bootstrap.min.css) перебивает стрелку
   Bootstrap без !important. .is-set — модификатор состояния «роль выбрана»:
   фон --surface-selected, тот же тон, что у выделенной строки таблицы данных
   (раздел кита «DataTable расширенная» — там та же переменная на модификаторе
   строки). Сознательно красится САМА выпадашка, не вся колонка (прототип
   Дизайнера с классом на колонке отклонён Ильёй после показа). Подстрока
   «dt» + «selected» слитно здесь не пишется нарочно: якорь другого дизайн-
   теста (test_design_group_e.py) ищет её первое вхождение в файле для поиска
   правила DataTable — второе упоминание раньше по файлу сдвинуло бы якорь. */
.col-role-select {
  padding-right: 18px;
  -webkit-appearance: none;
  -moz-appearance: none;
  appearance: none;
  background-image:
    linear-gradient(45deg, transparent 50%, var(--text-muted) 50%),
    linear-gradient(135deg, var(--text-muted) 50%, transparent 50%);
  background-position: calc(100% - 10px) center, calc(100% - 6px) center;
  background-size: 5px 5px, 5px 5px;
  background-repeat: no-repeat;
}
.col-role-select.is-set {
  background-color: var(--surface-selected, var(--wax-soft));
}

/* Радио-реестр (провайдеры): список под верхней чертой, точка акцентом (фиолет),
   бейджи-штампы «активный»/«резерв» (канон §Радио-реестр) */
.radio-list { border-top: 1px solid var(--rule-strong); }
.radio-row {
  display: flex; align-items: center; gap: var(--sp-12);
  padding: var(--sp-8) var(--sp-4); border-bottom: 1px solid var(--rule);
  cursor: pointer; margin: 0;
}
.radio-row:hover { background: var(--hover-bg); }
.radio-input { position: absolute; opacity: 0; width: 0; height: 0; }
.rdot {
  flex-shrink: 0; width: 14px; height: 14px; border-radius: var(--radius-pill);
  border: 1.5px solid var(--rule-strong); background: var(--sheet); position: relative;
}
.radio-input:checked + .rdot { border-color: var(--accent); }
.radio-input:checked + .rdot::after {
  content: ""; position: absolute; inset: 2.5px;
  border-radius: var(--radius-pill); background: var(--accent);
}
.radio-input:focus-visible + .rdot { outline: var(--focus-ring); outline-offset: 2px; }
.r-main { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 1px; }
.r-name { font-size: var(--fs-13); color: var(--text); }
.r-model { font-size: var(--fs-11); color: var(--text-faint); }
.radio-row.is-checked .r-name { font-weight: 700; }

/* Штампы-бейджи статуса (рамка + капс с разрядкой) — инлайн-вариант канона */
.stamp {
  flex-shrink: 0; display: inline-block; padding: 1px 7px;
  border: 1px solid currentColor; border-radius: var(--radius-1);
  font-size: var(--fs-11); font-weight: 700;
  text-transform: uppercase; letter-spacing: .06em; line-height: 1.5;
}
.stamp-ok { color: var(--ok); background: var(--ok-bg); }
.stamp-neutral { color: var(--neutral); background: var(--neutral-bg); }
/* Ревизор 05.09 (ozon_supply.html): недостающие цвета штампа — токены уже
   объявлены (tokens.css, --danger/--warn/--info), тот же приём, что
   .badge-eol/.toast-warn/.toast-info выше в этом файле. На витрину /ui/ и
   в раздел «Компоненты» паспорта не добавлены — вне рамок задания. */
.stamp-danger { color: var(--danger); background: var(--danger-bg); }
.stamp-warn { color: var(--warn); background: var(--warn-bg); }
.stamp-info { color: var(--info); background: var(--info-bg); }

/* ── Цепочка мозгов (.chain-list/.chain-row) ─────────────────────────────────
   Задание «Цепочка мозгов в настройках» (12.09), прототип Дизайнера, вид
   утверждён Ильёй 12.09. Список пяти звеньев резерва: номер, галочка
   (.checkbox), имя+id (.r-main/.r-name/.r-model — те же классы, что и
   .radio-row), штамп состояния (.stamp), действие «Изменить»/«Добавить».
   Сетка колонок фиксирована (не flex, как .radio-row) — номер и штамп
   держат свою ширину независимо от длины имени модели. */
.chain-list { border-top: 1px solid var(--rule-strong); }
.chain-row {
  display: grid; grid-template-columns: 22px 40px 1fr 150px 84px;
  align-items: center; gap: var(--sp-12, 12px);
  min-height: var(--row-h-normal, 44px); padding: var(--sp-4, 4px);
  border-bottom: 1px solid var(--rule);
}
.chain-row:hover { background: var(--hover-bg); }
.chain-rank {
  font-family: var(--font-mono); font-size: var(--fs-12);
  color: var(--text-faint); text-align: right; font-variant-numeric: tabular-nums;
}
.chain-check { justify-self: center; }
.chain-row-flags { display: flex; justify-content: flex-end; }
.chain-row-note {
  display: block; margin-top: 2px; font-size: var(--fs-11);
  font-style: italic; color: var(--warn);
}
.chain-row.is-off .r-main, .chain-row.is-off .chain-row-flags { opacity: .55; }
.chain-row.is-empty .r-name { color: var(--text-faint); font-style: italic; font-weight: 400; }
/* Пустая строка несёт настоящий checkbox (форма всегда шлёт cepochka_N_vkl),
   но визуально он ни к чему, пока модель не выбрана — скрыт, ячейка сетки
   держит ширину (visibility, не display: none). Снимается тем же классом
   .is-empty, что и JS убирает при выборе модели в звено (panel_agenta.html). */
.chain-row.is-empty .chain-check { visibility: hidden; }

/* ── Окно выбора мозга (.model-table) — таблица экзамена внутри модалки ─────
   Расширяет канон-класс .data-table (base.css) под плотность модального окна:
   узкие отступы, sticky-шапка, двухъярусные ячейки (.dt-two-name/.dt-two-sub
   уже канон, E2 DataTable расширенная — переиспользуются без правки).
   Группа-разделитель строк — .mt-group (та же роль, что thead, но внутри
   tbody: «Актуальные у поставщика» / «Недоступны у поставщика»). */
.dialog--wide { max-width: 1040px; }
/* Bootstrap .modal-xl держит ширину переменной (--bs-modal-width): 800px
   ниже брейкпоинта 1200px, 1140px выше — оба мимо утверждённых 1040px
   прототипа. Задаём max-width НАПРЯМУЮ на #modal-dialog (не переменной),
   правило вне медиа-запроса перебивает оба брейкпоинта разом (компонентный
   CSS подключается после Bootstrap — base.html). */
.modal-xl { max-width: 1040px; }
.model-table-wrap { overflow-x: auto; border: 1px solid var(--line); border-radius: var(--radius-2); }
table.data-table.model-table { width: 100%; border-collapse: collapse; }
.model-table thead th {
  position: sticky; top: 0; z-index: 1; background: var(--thead-bg);
  padding: 8px 10px; white-space: nowrap;
}
.model-table tbody td { padding: 8px 10px; font-size: 12px; white-space: normal; vertical-align: top; }
/* Колонка «Модель» (2-я) — тот же 236px, что уже держит colgroup в партиале,
   но как настоящий min-width: атрибут width на <col> — лишь подсказка
   table-layout:auto, браузер её ужимает под давлением соседних колонок
   (найдено 12.09: при узком окне «NVIDIA: Nemotron 3 Super (free)»
   переносилось на три строки). */
.model-table th:nth-child(2), .model-table td:nth-child(2) { min-width: 236px; }
.model-table tbody tr.is-selectable { cursor: pointer; }
.model-table tbody tr.is-unavailable { opacity: .58; }
.model-table tbody tr.is-unavailable td,
.model-table tbody tr.is-ineligible td { cursor: not-allowed; }
.model-table tbody tr.is-unavailable:hover td { background: transparent; }
.model-table td.mt-pick { text-align: center; white-space: nowrap; }
.model-table td.mt-score { min-width: 190px; }
.model-table .mt-score-num { font-size: 15px; font-weight: 700; line-height: 1.25; }
.model-table .mt-rating { font-weight: 700; font-size: 13.5px; }
.model-table tr.mt-group td {
  background: var(--sub, var(--thead-bg)); color: var(--text-muted);
  font-family: var(--font-mono); font-size: 10px; letter-spacing: .08em;
  text-transform: uppercase; padding: 7px 10px;
  border-top: 1px solid var(--rule-strong); border-bottom: 1px solid var(--rule);
}
.model-legend {
  margin-top: 14px; padding-top: 12px; border-top: 1px dashed var(--rule);
  font-size: var(--fs-11); color: var(--text-muted);
}
.model-legend b { color: var(--text); font-weight: 600; }

/* ── Личный мозг в шапке помощника (.chat-brain) — D4, _chat_brain.html ─────
   Три состояния (не задан/задан/без права выбора): имя мозга + пометка
   («— как у всех» либо штамп .stamp «мой выбор»), действия .btn-link. */
.chat-brain { display: flex; align-items: baseline; flex-wrap: wrap; gap: 5px; margin-top: 2px; }
.chat-brain .lbl { font-family: var(--font-mono); font-size: var(--fs-11); color: var(--text-faint); }
.chat-brain .name { font-family: var(--font-mono); font-size: var(--fs-11); font-weight: 500; color: var(--text-muted); }
.chat-brain .note { font-size: var(--fs-11); font-style: italic; color: var(--text-faint); }
.chat-brain .stamp { padding: 0 6px; font-size: 9.5px; line-height: 1.6; }
.chat-brain-actions { display: flex; align-items: center; gap: 6px; margin-top: 4px; }
/* .chat-brain-actions .sep снят волной «кнопки с рамкой» (14.09, находка
   Ревизора) — разделитель «·» ушёл вместе с ссылками-кнопками, класс не
   встречается ни в одном шаблоне. */

/* Сегменты (уровень рассуждения): ярлыки в общей рамке, актив залит чернилами;
   под сегментами — подпись-эффект (канон §Сегменты). */
.seg-field { display: flex; flex-wrap: wrap; align-items: stretch; }
.seg-radio { position: absolute; opacity: 0; width: 0; height: 0; }
.seg-item {
  margin: 0; padding: var(--sp-4) var(--sp-12); cursor: pointer;
  font-size: var(--fs-13); color: var(--text); background: var(--sheet);
  border: 1px solid var(--rule-strong); border-left-width: 0;
}
.seg-item:first-of-type { border-left-width: 1px; border-radius: var(--radius-1) 0 0 var(--radius-1); }
.seg-radio:last-of-type + .seg-item,
.seg-item:last-of-type { border-radius: 0 var(--radius-1) var(--radius-1) 0; }
.seg-item:hover { background: var(--hover-bg); }
.seg-radio:checked + .seg-item { background: var(--ink); color: var(--sheet); border-color: var(--ink); }
.seg-radio:focus-visible + .seg-item { outline: var(--focus-ring); outline-offset: 2px; }
.seg-note {
  flex-basis: 100%; margin: var(--sp-8) 0 0; font-size: var(--fs-12);
  color: var(--text-muted); font-style: italic;
}

/* Полоса действий: главная кнопка справа (не во всю ширину снизу).
   ЛЕГАСИ низа формы — на экранах, ещё не переведённых на канон «кнопки справа
   наверху у Назад» (витрина /ui/). Эталон «Настройки ИИ»
   держит действия в .screen-head-actions, статус — в .form-status. */
.form-actions {
  display: flex; align-items: center; justify-content: flex-end; gap: var(--sp-12);
  margin-top: var(--sp-24); padding-top: var(--sp-16); border-top: 1px solid var(--rule);
}
.arm-note { margin-right: auto; font-size: var(--fs-12); color: var(--warn); font-style: italic; }

/* Статус-строка формы (канон «кнопки справа наверху»): подтверждение записи /
   нейтральная подсказка ARM — под заголовком, действия живут в .screen-head-actions. */
.form-status { margin: 0 0 var(--sp-16); font-size: var(--fs-13); }
.form-status-ok { color: var(--confirm-commit); font-weight: 600; }
.form-status-arm { color: var(--warn); font-style: italic; }

/* Подменю выбора модели (появляется у OpenRouter): смещено вправо, отделено
   тонкой левой линией — читается как уточнение выбранного провайдера. */
.model-submenu {
  margin-left: var(--sp-16); padding-left: var(--sp-16);
  border-left: 2px solid var(--rule);
}

/* ── Таблица-ведомость (table.ledger): канон «Модули» ────────────────────────
   Бухгалтерский бланк: волосяная линовка строк, узкая колонка № слева, итоговая
   строка (.led-total) под двойной чертой начертанием Manrope 700 faux-italic
   (через --font-serif = Manrope; настоящего italic у Manrope нет).
   Разметка: <table class="ledger"> с <thead>/<tbody>; итог — <tr class="led-total">
   в <tbody> или <tfoot>. Колонка № — ячейка .led-num; числовые — .num. */
.ledger {
  width: 100%; border-collapse: collapse;
  font-size: var(--fs-13); color: var(--text); background: var(--sheet);
}
/* Шапка бланка: тонкая капс с разрядкой под сильной чертой */
.ledger thead th {
  font-family: var(--font-ui); font-weight: 600; font-size: var(--fs-11);
  text-transform: uppercase; letter-spacing: .04em; color: var(--text-faint);
  text-align: left; white-space: nowrap;
  padding: var(--sp-4) var(--sp-8);
  border-bottom: 1px solid var(--rule-strong);
}
/* Волосяная линовка строк */
.ledger tbody td {
  padding: var(--sp-4) var(--sp-8);
  border-bottom: 1px solid var(--rule);
}
.ledger tbody tr:hover { background: var(--row-hover-bg); }
/* Колонка № — узкая, приглушённая, моно, вправо; не выделяется мышью */
.ledger .led-num,
.ledger thead th.led-num {
  width: 1%; white-space: nowrap; text-align: right;
  padding-right: var(--sp-12);
  color: var(--text-faint); font-family: var(--font-mono);
  font-variant-numeric: tabular-nums; user-select: none;
}
/* Числовые колонки — табличные цифры, вправо */
.ledger .num { font-variant-numeric: tabular-nums; text-align: right; }
.ledger .mono { font-family: var(--font-mono); }
/* Итоговая строка: двойная черта «подведения итога» + Manrope 700 faux-italic */
.ledger .led-total td {
  border-top: 3px double var(--ink); border-bottom: 0;
  padding-top: var(--sp-8);
  font-family: var(--font-serif); font-weight: 700; font-style: italic;
  color: var(--ink);
}
.ledger .led-total:hover { background: transparent; }
/* Итоговая строка в теле не должна нести нижнюю волосяную линию */
.ledger tbody .led-total td { border-bottom: 0; }

/* ── Тепловая карта остатков по кластерам (table.heatmap-table, .hm-*) ──────
   Перенесено из core/marketplaces/segodnya.html (B2, второй аудит): правила
   жили только в <style> этого шаблона, на core/marketplaces/ozon_supply.html
   (тот же разметочный слой) не действовали — тепловая карта там была без
   цвета. .hm-legend/.hm-legend-dot — новые, минимально на токенах уже
   показанного варианта подписи (инлайн-стили в segodnya.html). */
.heatmap-table td, .heatmap-table th { padding: 5px 8px; font-size: var(--fs-12); }
.hm-ok { background: var(--green-bg); color: var(--green-text); text-align: center; }
.hm-warn { background: var(--yellow-bg); color: var(--yellow-text); text-align: center; }
.hm-danger { background: var(--red-bg); color: var(--red-text); text-align: center; }
.hm-neutral { text-align: center; }
.hm-legend {
  margin-top: var(--sp-8); display: flex; gap: var(--sp-12);
  font-size: var(--fs-11); color: var(--text-muted);
}
.hm-legend-dot {
  display: inline-block; width: 10px; height: 10px;
  border-radius: 2px; margin-right: 4px;
}

/* ── Права доступа (.perm-table, .emp-name, .emp-dept, .perm-note, .toggle) ── */
/* Таблица-сетка прав (экран admin_prava_dostupa) — .perm-table красится
   модулем как ЛЮБОЙ .data-table (доводка приёмки волны 2 админ-экранов,
   11.09: своя шапка/строка/рамка перебивали общий вид — 11px капс 600 на
   --bg, строка 64px, рамка вплотную к рамке модуля). Оставлено только то,
   что нужно тумблерам (центр по колонкам, кроме первой) и ширине первой
   колонки (имя+отдел) — остальное (шрифт, фон, границы, hover) даёт
   .data-table сама. */
.perm-table thead th {
  /* white-space: nowrap уже даёт .data-table th (base.css) — своей копии не нужно. */
  text-align: center;
}
.perm-table thead th:first-child { text-align: left; min-width: 200px; padding-left: var(--sp-16); }
.perm-table tbody td {
  text-align: center; vertical-align: middle;
}
.perm-table tbody td:first-child { text-align: left; padding-left: var(--sp-16); }

/* Имя и отдел сотрудника в первой колонке */
.emp-name { font-weight: 600; display: block; color: var(--text); }
.emp-dept { display: block; font-size: var(--fs-11); color: var(--text-faint); margin-top: 2px; }

/* Примечание под таблицей прав */
.perm-note {
  margin-top: var(--sp-16);
  font-size: var(--fs-12); color: var(--text-muted); font-style: italic;
  border-top: 1px solid var(--line); padding-top: var(--sp-12);
}

/* Поиск над таблицей прав — ограничение ширины */
.perm-search { max-width: 400px; }

/* Заглушка «сотрудники не найдены» в tbody */
.perm-empty-cell { padding: 24px var(--sp-16); font-style: italic; }

/* ── Группа B: компоненты KPI и данных (Шаг 9, 2026-08-14) ─────────────────

   B1 TileGrid + Tile — сетка плиток KPI (зазор 1px, фон-подложка серая просвечивает)
   B2 LineChart — линейный график с осями (inline-SVG, без заливки, без маркеров)
   B3 BarMeter — горизонтальные полосы воронки/доли + полоса сравнения
   B4 Metric — крупное моно-число шапки модуля (опц. дельта)
   B5 ApprovalSteps — шаги согласования (sent/done/waiting)
   B6 BulkActionBar — панель массовых действий при выборе строк
*/

/* ── B1 TileGrid + Tile ─────────────────────────────────────────────────────
   Сетка плиток KPI: серая подложка, зазор 1px, плитки светлые.
   Разметка: <div class="tile-grid"> <div class="tile">…</div> … </div> */

.tile-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
  gap: 1px;
  background: var(--rule-strong); /* серая подложка просвечивает в зазор */
  border: 1px solid var(--rule-strong);
}

.tile {
  background: var(--surface-module, var(--sheet));
  padding: 14px 16px 12px;
  display: flex;
  flex-direction: column;
  gap: 4px;
}

/* Заголовок плитки — моно uppercase мета */
/* Кит §11 (плитки): font:400 9.5px IBM Plex Mono uppercase — type-label */
.tile-label {
  font-family: var(--font-mono);
  font-size: var(--size-label);
  font-weight: 400;
  text-transform: uppercase;
  letter-spacing: var(--track-label);
  color: var(--text-label, var(--ink-faint));
  line-height: 1.2;
}

/* Значение плитки — крупное моно-число */
/* Кит §11 (плитки): font:500 21px IBM Plex Mono — type-metric */
/* Кит §03 строка 156: letter-spacing -.03em = --track-metric */
.tile-value {
  font: var(--type-metric);
  color: var(--text-data, var(--ink));
  letter-spacing: var(--track-metric, -.03em);
}

/* Единица к значению плитки — мета Manrope */
/* Кит §03 строка 157: font:400 11px/1 'Manrope',sans-serif, цвет #9CA3AF = --ink-faint */
.tile-unit {
  font-family: var(--font-sans);
  font-size: var(--size-small);
  color: var(--text-faint, var(--ink-faint, #9CA3AF));
}

/* Дельта плитки — сигнальный цвет */
/* Кит §11: font:400 10.5px IBM Plex Mono */
.tile-delta {
  font-family: var(--font-mono);
  font-size: var(--size-meta);
  margin-top: 2px;
}
.tile-delta.is-ok       { color: var(--signal-ok,       var(--ledger-green)); }
.tile-delta.is-critical { color: var(--signal-critical,  var(--red-ink)); }
.tile-delta.is-warning  { color: var(--signal-warning,   var(--amber)); }
.tile-delta.is-neutral  { color: var(--signal-neutral,   var(--ink-faint)); }

/* Критичная плитка (critical=true) — лёгкий фон сигнала */
.tile.is-critical { background: var(--surface-error, var(--red-050)); }


/* ── B2 LineChart (inline-SVG контейнер + оси) ──────────────────────────────
   Линейный график «Расход помощника»: 14-дневная динамика, именованные оси X/Y,
   без заливки под линией, без маркеров, чернила var(--chart-line).
   Разметка: <figure class="line-chart"> <figcaption>…</figcaption> <svg>…</svg>
   <div class="line-chart-x-label">ось X</div> </figure>
   SVG-атрибуты: class="line-chart-svg", линия — class="lc-line" */

.line-chart {
  display: flex;
  flex-direction: column;
  gap: 4px;
  margin: 0;
}

.line-chart-header {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 8px;
}

.line-chart-label {
  font-family: var(--font-mono);
  font-size: var(--size-meta);
  text-transform: uppercase;
  letter-spacing: var(--track-label, .06em);
  color: var(--text-label, var(--ink-faint));
}

.line-chart-delta {
  font-family: var(--font-mono);
  font-size: var(--size-meta);
}
.line-chart-delta.is-ok       { color: var(--signal-ok,      var(--ledger-green)); }
.line-chart-delta.is-critical { color: var(--signal-critical, var(--red-ink)); }
.line-chart-delta.is-warning  { color: var(--signal-warning,  var(--amber)); }

/* SVG-контейнер */
.line-chart-svg {
  display: block;
  overflow: visible;
}

/* Линия графика */
.lc-line {
  fill: none;
  stroke: var(--chart-line, var(--ink));
  stroke-width: 1.25;
  stroke-linejoin: round;
  stroke-linecap: round;
}

/* Легенда под полным графиком §08 (строка: отрезок + подпись ряда) */
.lc-legend {
  display: flex;
  align-items: center;
  gap: 26px;
  padding-top: 20px;
}

.lc-legend-item {
  display: flex;
  align-items: center;
  gap: 8px;
  font-family: var(--font-sans);
  font-size: var(--size-small);
  color: var(--text-label, var(--ink-faint));
}

.lc-legend-line {
  display: inline-block;
  width: 22px;
  height: 1.25px;
  background: var(--chart-line, var(--ink));
}

/* Сетка — горизонтальные линии */
.lc-grid line {
  stroke: var(--chart-axis, var(--rule-strong));
  stroke-width: 1;
}

/* Оси */
.lc-axis line, .lc-axis path {
  stroke: var(--chart-axis, var(--rule-strong));
  stroke-width: 1;
  fill: none;
}

/* Подписи осей — моно uppercase */
.lc-tick {
  font-family: var(--font-mono);
  font-size: 9px;
  fill: var(--text-label, var(--ink-faint));
}

/* Подпись оси (название) */
.line-chart-axis-label {
  font-family: var(--font-mono);
  font-size: var(--size-meta);
  color: var(--text-label, var(--ink-faint));
  text-align: center;
}

/* Подпись вертикальной оси Y — SVG-текст с поворотом */
.lc-axis-label-y {
  font-family: var(--font-mono);
  font-size: 9px;
  fill: var(--text-label, var(--ink-faint));
  text-anchor: middle;
}

/* Итоговое значение (totalValue) */
.line-chart-total {
  font-family: var(--font-mono);
  font-size: var(--size-module-title);
  font-weight: 400;
  color: var(--text-data, var(--ink));
  line-height: 1.1;
}


/* ── B3 BarMeter + полоса сравнения ─────────────────────────────────────────
   Горизонтальные полосы-доли (воронка/доля): заголовок, полоса, значение сбоку.
   Полоса сравнения: основной сегмент --wax, резерв --ink-soft (было --ink-500
   до волны 1, добор), значение моно справа.
   Разметка воронки: <div class="bar-meter"> <div class="bar-meter-row"> …
   Разметка сравнения: <div class="bar-compare"> <div class="bar-compare-row"> … */

.bar-meter { display: flex; flex-direction: column; gap: 8px; }

.bar-meter-label {
  font-family: var(--font-mono);
  font-size: var(--size-meta);
  text-transform: uppercase;
  letter-spacing: var(--track-label, .06em);
  color: var(--text-label, var(--ink-faint));
  margin-bottom: 2px;
}

.bar-meter-row { display: flex; flex-direction: column; gap: 3px; }

.bar-meter-name {
  font-family: var(--font-mono);
  font-size: var(--size-meta);
  color: var(--text-body, var(--ink-soft));
}

.bar-meter-track {
  height: 6px;
  background: var(--chart-track, var(--rule));
  position: relative;
}

.bar-meter-fill {
  position: absolute;
  left: 0; top: 0; bottom: 0;
  background: var(--chart-bar-active, var(--accent));
}

.bar-meter-value {
  font-family: var(--font-mono);
  font-size: var(--size-meta);
  color: var(--text-data, var(--ink));
}

.bar-meter-caption {
  font-family: var(--font-mono);
  font-size: 9px;
  color: var(--text-label, var(--ink-faint));
}

/* Полоса сравнения (§08 «Разрез по зонам»):
   две части (основной #6E3A78 + резерв #A2A8B0), значения моно сбоку справа */
.bar-compare { display: flex; flex-direction: column; gap: 10px; }

.bar-compare-label {
  font-family: var(--font-mono);
  font-size: var(--size-meta);
  text-transform: uppercase;
  letter-spacing: var(--track-label, .06em);
  color: var(--text-label, var(--ink-faint));
  margin-bottom: 4px;
}

.bar-compare-row {
  display: grid;
  grid-template-columns: 1fr auto;
  gap: 10px;
  align-items: center;
}

.bar-compare-track {
  height: 8px;
  display: flex;
  background: var(--chart-track, var(--rule));
}

.bar-compare-main {
  background: var(--wax); /* #6E3A78 основной сегмент */
  height: 100%;
}

.bar-compare-reserve {
  background: var(--ink-soft, var(--ink-faint)); /* резерв */
  height: 100%;
}

.bar-compare-values {
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: 1px;
}

.bar-compare-val-main {
  font-family: var(--font-mono);
  font-size: var(--size-meta);
  color: var(--text-data, var(--ink));
}

.bar-compare-val-reserve {
  font-family: var(--font-mono);
  font-size: 9px;
  color: var(--text-label, var(--ink-faint));
}

.bar-compare-row-name {
  font-family: var(--font-mono);
  font-size: var(--size-meta);
  color: var(--text-body, var(--ink-soft));
  margin-bottom: 3px;
}


/* ── B4 Metric — крупное моно-число шапки модуля ────────────────────────────
   Самостоятельный класс: крупный кегль моно, опциональная дельта.
   Разметка: <div class="metric"> <span class="metric-value">…</span>
             <span class="metric-unit">…</span>
             <span class="metric-delta is-ok">…</span> </div> */

.metric {
  display: flex;
  align-items: baseline;
  gap: 6px;
  flex-wrap: wrap;
}

.metric-value {
  font-family: var(--font-mono);
  font-size: var(--size-module-title);
  font-weight: 400;
  color: var(--text-data, var(--ink));
  line-height: 1.1;
  letter-spacing: -.01em;
}

.metric-unit {
  font-family: var(--font-mono);
  font-size: var(--size-meta);
  color: var(--text-label, var(--ink-faint));
}

.metric-delta {
  font-family: var(--font-mono);
  font-size: var(--size-meta);
}
.metric-delta.is-ok       { color: var(--signal-ok,      var(--ledger-green)); }
.metric-delta.is-critical { color: var(--signal-critical, var(--red-ink)); }
.metric-delta.is-warning  { color: var(--signal-warning,  var(--amber)); }
.metric-delta.is-neutral  { color: var(--signal-neutral,  var(--ink-faint)); }


/* ── B5 ApprovalSteps — шаги согласования ───────────────────────────────────
   Шаги: sent / done / waiting; переиспользует .status-dot через модифицированный
   вариант (переопределяет размер и цвет внутри компонента).
   Разметка: <ol class="approval-steps"> <li class="approval-step is-done">…</li>…</ol> */

.approval-steps {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 0;
}

.approval-step {
  display: grid;
  grid-template-columns: 16px 1fr;
  gap: 10px;
  padding: 10px 0;
  border-bottom: 1px solid var(--edge-row, var(--rule));
  align-items: start;
}

.approval-step:last-child { border-bottom: 0; }

/* Точка-статус шага (переопределяем размер из .status-dot) */
.approval-step-dot {
  display: inline-block;
  width: 7px; height: 7px;
  border-radius: 50%;
  margin-top: 4px; /* вертикальное выравнивание с первой строкой текста */
  flex-shrink: 0;
}
.approval-step.is-done    .approval-step-dot { background: var(--signal-ok,      var(--ledger-green)); }
.approval-step.is-sent    .approval-step-dot { background: var(--signal-neutral,  var(--ink-faint)); }
.approval-step.is-waiting .approval-step-dot {
  background: transparent;
  border: 1.5px solid var(--rule-strong);
}

.approval-step-body { display: flex; flex-direction: column; gap: 2px; }

/* Кит §17 строка 914: font:500 12px/1.3 'Manrope',sans-serif, цвет динамический по состоянию */
/* Цвет #6B7280 — хвост для группы состояний (раскраска по {{ s.ink }}) */
.approval-step-who {
  font-family: var(--font-sans);
  font-size: var(--size-body);
  font-weight: var(--weight-medium);
  color: var(--text-muted, var(--ink-soft, #6B7280));
  line-height: 1.3;
}

.approval-step-role {
  font-family: var(--font-mono);
  font-size: var(--size-meta);
  color: var(--text-label, var(--ink-faint));
}

.approval-step-state {
  font-family: var(--font-mono);
  font-size: var(--size-meta);
}
.approval-step.is-done    .approval-step-state { color: var(--signal-ok,     var(--ledger-green)); }
.approval-step.is-sent    .approval-step-state { color: var(--text-label,    var(--ink-faint)); }
.approval-step.is-waiting .approval-step-state { color: var(--text-label,    var(--ink-faint)); font-style: italic; }


/* ── B6 BulkActionBar — панель массовых действий ────────────────────────────
   Появляется при выборе строк. Кнопки групп + danger-кнопка + «снять выбор».
   Разметка: <div class="bulk-bar"> <span class="bulk-bar-count">…</span>
             <div class="bulk-bar-actions">…</div>
             <button class="bulk-bar-clear btn btn-default btn-sm">Снять</button> </div>
   Danger-кнопка: class="btn btn-danger btn-sm" внутри .bulk-bar-actions. */

.bulk-bar {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 8px 18px;
  background: var(--surface-selected, var(--wax-soft));
  border: 1px solid var(--edge-module, var(--rule-strong));
  border-top: 0;
  flex-wrap: wrap;
}

.bulk-bar-count {
  font-family: var(--font-mono);
  font-size: var(--size-meta);
  font-weight: 500;
  color: var(--text-data, var(--ink));
  white-space: nowrap;
}

.bulk-bar-actions {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
  flex: 1;
}

.bulk-bar-sep {
  width: 1px;
  height: 16px;
  background: var(--rule-strong);
  flex-shrink: 0;
}

/* Тумблер вкл/выкл (пилюля) */
.toggle {
  position: relative; display: inline-block;
  width: 36px; height: 20px; cursor: pointer; margin: 0;
}
.toggle input { opacity: 0; width: 0; height: 0; position: absolute; }
.toggle-track {
  position: absolute; inset: 0;
  background: var(--rule-strong);
  border-radius: var(--radius-pill);
  transition: background var(--dur-2);
}
.toggle input:checked + .toggle-track { background: var(--ok); }
.toggle-thumb {
  position: absolute; top: 3px; left: 3px;
  width: 14px; height: 14px;
  background: var(--panel);
  border: 1px solid var(--edge-control);
  border-radius: var(--radius-pill);
  transition: transform var(--dur-2);
  pointer-events: none;
}
.toggle input:checked ~ .toggle-thumb { transform: translateX(16px); }
.toggle:focus-within .toggle-track { outline: var(--focus-ring); outline-offset: 2px; }
/* Площадь нажатия у тумблера (паспорт 3.9.3: промахнуться дорого — тумблер
   прав доступа даёт или снимает право сотрудника). Зона добирается
   псевдоэлементом, вид органа не меняется: те же 36 на 20. Ширина зоны 40
   при шаге ячеек таблицы прав заведомо шире — соседние зоны не в нахлёст
   (проверено живьём точками). */
.toggle::after {
  content: ""; position: absolute; top: 50%; left: 50%;
  width: 40px; height: 40px; transform: translate(-50%, -50%);
}

/* ── Группа C: компоненты ввода и навигации (Шаг 10, 2026-08-15) ────────────

   C1 FilterBar — полоса фильтров с чипсами активных фильтров
   C2 Pagination — постраничная навигация с номерами и стрелками
   C3 UnsavedBar — полоса несохранённых изменений
   C4 Inset — вставка mono-текста с подложкой
   C5 priority-row — строка «Radio + Select» реестра выбора (приоритеты)
*/

/* ── C1 FilterBar — полоса фильтров с чипсами ───────────────────────────────
   Горизонтальная полоса под шапкой экрана: поля и селекты слева, чипсы активных
   фильтров рядом, кнопки «Применить»/«Сбросить» справа.
   Кромка 1px снизу, лейблы полей — mono uppercase. Radius 0.

   Разметка:
     <div class="filter-bar">
       <div class="filter-bar-fields">…поля/селекты…</div>
       <div class="filter-bar-chips">…чипсы…</div>
       <div class="filter-bar-actions">…кнопки…</div>
     </div>
   Чипс активного фильтра:
     <span class="filter-chip">Текст <button class="filter-chip-remove">×</button></span>
*/

.filter-bar {
  display: flex;
  align-items: flex-end;
  flex-wrap: wrap;
  gap: 10px 14px;
  padding: 12px 0 10px;
  border-bottom: 1px solid var(--rule-strong);
}

.filter-bar-fields {
  display: flex;
  align-items: flex-end;
  flex-wrap: wrap;
  gap: 10px 14px;
  flex: 1;
  min-width: 0;
}

/* Лейбл поля фильтра — IBM Plex Mono uppercase */
.filter-bar .f-label,
.filter-label {
  font-family: var(--font-mono);
  font-size: var(--fs-11);
  font-weight: 500;
  text-transform: uppercase;
  letter-spacing: var(--track-label, .06em);
  color: var(--text-label, var(--ink-faint));
  display: block;
  margin-bottom: 4px;
}

/* Поле-инпут в полосе фильтра */
.filter-input {
  border: 0;
  border-bottom: 1px solid var(--rule-strong);
  background: transparent;
  font-size: var(--fs-13);
  color: var(--text);
  font-family: var(--font-ui);
  padding: 4px 0;
  min-width: 0;
  width: 100%;
  box-sizing: border-box;
}
.filter-input:focus {
  outline: none;
  border-bottom-color: var(--accent);
}

/* Чипсы активных фильтров */
.filter-bar-chips {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  align-items: center;
}

.filter-chip {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  padding: 2px 8px 2px 10px;
  border: 1px solid var(--rule-strong);
  background: var(--surface-tab-idle, var(--hover-bg));
  font-family: var(--font-mono);
  font-size: var(--fs-11);
  color: var(--text);
  white-space: nowrap;
}

.filter-chip-remove {
  border: 0;
  background: none;
  cursor: pointer;
  font-size: 14px;
  line-height: 1;
  color: var(--ink-faint);
  padding: 0 2px;
  display: inline-flex;
  align-items: center;
}
.filter-chip-remove:hover { color: var(--danger); }

/* Кнопки полосы фильтра */
.filter-bar-actions {
  display: flex;
  align-items: flex-end;
  gap: 8px;
  flex-shrink: 0;
}


/* ── C2 Pagination — постраничная навигация ──────────────────────────────────
   Номера страниц + стрелки «предыдущая/следующая». Активная страница залита
   чернилами (ink). Кромка 1px на каждой ячейке. Radius 0. Mono-цифры.

   Разметка:
     <nav class="pagination" aria-label="Навигация по страницам">
       <a class="page-item page-prev" href="…">‹</a>
       <a class="page-item" href="…">1</a>
       <a class="page-item is-active" aria-current="page">2</a>
       <a class="page-item" href="…">3</a>
       <span class="page-item page-ellipsis">…</span>
       <a class="page-item page-next" href="…">›</a>
     </nav>
*/

.pagination {
  display: inline-flex;
  align-items: stretch;
  gap: 0;
  border: 1px solid var(--rule-strong);
}

.page-item {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 32px;
  height: 30px;
  padding: 0 8px;
  font-family: var(--font-mono);
  font-size: var(--fs-12);
  font-variant-numeric: tabular-nums;
  color: var(--text-muted, var(--ink-soft));
  text-decoration: none;
  background: var(--surface-module, var(--sheet));
  border-right: 1px solid var(--rule-strong);
  cursor: pointer;
  user-select: none;
  transition: background var(--dur-1), color var(--dur-1);
}

.page-item:last-child { border-right: 0; }

.page-item:hover:not(.is-active):not(.page-ellipsis) {
  background: var(--hover-bg);
  color: var(--text);
}

/* Активная страница — заливка чернилами, текст инвертируется */
.page-item.is-active {
  background: var(--ink);
  color: var(--sheet);
  cursor: default;
  font-weight: 500;
}

/* Стрелки — чуть шире */
.page-prev,
.page-next {
  font-size: var(--fs-14);
  padding: 0 10px;
  color: var(--text-muted, var(--ink-soft));
}

/* Многоточие — не интерактивное */
.page-ellipsis {
  cursor: default;
  color: var(--ink-faint);
}


/* ── T1 TableModule — модуль-контейнер вокруг таблицы (волна 2, 09.09) ──────
   Источник: inbox/volna-2-standart-iz-kartochek.md §1/§2/§6/§7 (карточки
   aosnova/09-modul, bupravlenie/05-polya-i-filtry, fekrany/01-tovary).
   Первый живой потребитель --shadow-1 (объявлен волной 1, до сих пор не
   подключён ни к одному компоненту — карточка «Модуль»). --radius-3 НЕ
   первый — до этой волны уже стоял в восьми местах (.chat-text/.chat-input/
   .chat-form button/.opendoc, .sheet, .dialog и др.) — поправлено в приёмке
   09.09, прежняя формулировка была неверной для обоих токенов сразу.

   Полоса фильтров стоит ВНУТРИ модуля, сверху (не отдельной строкой над ним —
   прежнее решение отменено). Устройство сверху вниз:
     <div class="table-mod">
       <div class="table-mod-tools">
         <div class="table-mod-search">…лупа + <input name="q">…</div>
         <div class="table-mod-chip-wrap">
           <select class="table-mod-chip-select" aria-label="…">…</select>
           <span class="table-mod-chip is-on">Раздел: <b>МБТ</b> <span class="table-mod-chip-v">×</span></span>
         </div>
         <div class="table-mod-actions">…«Сбросить»/«Применить»…</div>
       </div>
       <div class="table-mod-wrap"><table class="data-table">…</table></div>
       <div class="table-mod-foot">
         <span class="table-mod-count">Показано 1–100 из 1866</span>
         <nav class="table-mod-pages">…</nav>
       </div>
     </div>

   Выбор поля фильтра — настоящий <select>, растянутый прозрачным поверх
   стилизованного .table-mod-chip (тот же приём, что дают select-триггеры в
   таблицах кита): доступность и клавиатура остаются на нативном контроле,
   вид рисует span под ним — раскладку, крестик и стрелку красит сервер по
   тому же filters, что уже выбирает <option selected>,JS не нужен.
*/
.table-mod {
  display: flex;
  flex-direction: column;
  background: var(--sheet);
  border: 1px solid var(--line);
  border-radius: var(--radius-3);
  box-shadow: var(--shadow-1);
  overflow: hidden;
}
/* Пол высоты внутри растянутого экрана (.content--stretch) — измерено
   живьём. Справочники без полосы фильтров (.table-mod-tools отсутствует —
   «Бренды»/«Страны»/«Категории»/«Разделы») намеренно БЕЗ flex-grow: у
   флекс-item без явного flex исходные значения (grow:0; shrink:1;
   basis:auto) дают ровно нужное — короткий список не тянется пустотой до
   низа экрана, а длинный сжимается (shrink:1, автоминимум уже обнулён
   overflow:hidden выше) до доступного места и не ниже пола. Родное
   содержимое меряется от шапки+строки+подвала (≈107px, thead 24.8 + строка
   44 + подвал 38.5) — низкий пол сюда не дотягивается даже до короткого
   списка «Разделы» (2 строки, родная высота ≈151px), не раздувая его
   пустотой внутри рамки.

   «Товары»/«Прайсы» несут ещё и .table-mod-tools — им нужен пол выше (до
   3 строк полосы фильтров на узком окне, иначе подвалу не остаётся места) и
   flex-grow (без него короткая выдача/пустой список «Ничего не найдено»
   держит только пол 280px, а не тянется до низа окна). .table-mod:has(
   .table-mod-tools) — сигнал «есть полоса», не привязан к конкретному
   экрану; растёт именно и только этот модуль, справочники без полосы
   остаются по контенту. */
.content--stretch .table-mod { min-height: 110px; }
.content--stretch .table-mod:has(.table-mod-tools) { flex: 1; min-height: 280px; }
/* Ширина по тому же признаку «есть полоса / нет полосы» (решение Ильи
   15.09, вариант «В»): модуль без .table-mod-tools сжимается по содержимому
   вместо растяжки на всю рабочую область (справочники «Поставщики»/«Бренды»/
   «Категории»/«Разделы»/«Страны» — три узкие колонки расползались пустотой).
   width:fit-content работает и как флекс-item в .content--stretch (явная
   ширина отменяет align-items:stretch родителя), и как блочный потомок вне
   флекс-цепочки. max-width:100% — не пиксельный потолок: на широких таблицах
   (prajsy/stroki.html) fit-content уже не может быть меньше доступной
   ширины, если контент шире неё, — модуль остаётся во всю ширину и
   прокрутка внутри .table-mod-wrap работает как раньше.
   :not(.modal *) — исключение для всплывающих окон (находка Ревизора 15.09,
   решение Ильи): .dialog «Перенести в карточку» (_v_kartochku.html) несёт
   .table-mod БЕЗ .table-mod-tools, но узкая колонка тут не нужна — окно и
   так задаёт ширину диалогом (.modal-lg = 750px), сжатие по содержимому
   только полупустило его. Модуль внутри .modal остаётся во всю ширину окна,
   как было до этого правила; узкий модуль — только на экранах-списках. */
.table-mod:not(:has(.table-mod-tools)):not(.modal *) { width: fit-content; max-width: 100%; }

.table-mod-tools {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 8px;
  padding: 12px 14px;
  border-bottom: 1px solid var(--line);
  flex-shrink: 0;
}
/* Карточка не даёт правил под узкое окно (§9: «собраны на 1440») — без
   flex-wrap кнопки «Сбросить»/«Применить» уезжают за overflow:hidden модуля
   и пропадают целиком (замер живьём 09.09: панель помощника открыта, ширина
   тулбара 800px при контенте 875px). Перенос строки — не решение за карточку
   про вид, а страховка от исчезающих кнопок на любой рабочей ширине окна. */

/* Форма фильтров внутри .table-mod-tools не создаёт своего flex-бокса —
   иначе она перестаёт быть прямым flex-ребёнком полосы. Общее правило
   (доводка приёмки волны 2 админ-экранов, 11.09): было местной копией в
   events.html/tovary.html/prajsy/root.html — «Сотрудники» взяли класс
   `.filter-form` без правила вовсе (полоса рушилась в столбик), третий
   независимый потребитель того же текста — сигнал переносить в свод, тот же
   приём, что раньше объединил sticky-шапку .table-mod-wrap. */
.filter-form { display: contents; }

.table-mod-wrap {
  flex: 1;
  min-height: 0;
  overflow-y: auto;
}

/* Шапка/ячейка §3 (доводка 11.09: переехало из трёх местных копий —
   tovary.html, prajsy/root.html, admin/prava_dostupa.html; карточка
   ctablicy/01-tablica-dannyh). Шапка: 12px/500 обычным шрифтом (не моно,
   без капса), padding 9px 14px, цвет --text-muted. Ячейка: padding 0 14px,
   13.5px, #4B5563 (буквальный hex карточки — нет именованного токена под
   именно этот оттенок). Высота строки остаётся общей 44px (--row-height,
   base.css) — оверрайд правит только шрифт/отступы, не height. */
.table-mod .data-table th {
  font-family: var(--font-sans, var(--font-ui));
  font-size: 12px;
  font-weight: 500;
  letter-spacing: normal;
  text-transform: none;
  color: var(--text-muted, var(--ink-soft));
  padding: 9px 14px;
}
.table-mod .data-table td {
  padding: 0 14px;
  font-size: 13.5px;
  color: #4B5563;
}

/* ── Sticky thead + обрезка «Названия» многоточием — общее свойство ЛЮБОГО
   .table-mod (доводка 10.09: приёмка нашла обе копии только в местных
   <style> «Товаров»/«Прайсов» — четыре справочника без копии не получали ни
   sticky-шапку, ни обрезку вовсе). .table-mod-wrap несёт свой overflow-y:auto
   (объявлен выше) — здесь только слой шапки поверх строк при прокрутке;
   радиус модуля живёт на .table-mod (overflow:hidden) уровнем выше
   .table-mod-wrap, sticky поэтому не ломается (витрина /ui/, «Модуль
   таблицы»: position:sticky шапки считается от .table-mod-wrap). */
.table-mod-wrap .data-table thead th {
  position: sticky;
  top: 0;
  z-index: 2;
}
/* Название не переносит текст — обрезает многоточием, иначе длинное имя
   рвёт фиксированную высоту строки переносом на вторую строку. */
.table-mod .data-table td.dt-strong {
  max-width: 420px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.table-mod-foot {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 10px 14px;
  border-top: 1px solid var(--line);
  background: var(--sub);
  font-size: 12.5px;
  color: var(--text-muted, var(--ink-soft));
  flex-shrink: 0;
}

.table-mod-count { color: var(--text-muted, var(--ink-soft)); }

/* .table-mod--compact — многолистовой экран прайса (решение владельца 16.09):
   несколько модулей .table-mod друг под другом на одном экране, один — один
   лист книги. Плотнее обычного модуля (меньше отступов в полосе/шапке/
   ячейке) и с потолком высоты своей прокруткой — «прокрутка строго внутри
   рамки таблицы листа, страница и соседние листы не едут» (запись владельца).
   Временное: числа (padding/font-size/max-height) — первый проход
   Программиста, не финальная карточка VES-DESIGN — ждёт правки Дизайнера. */
/* Ряд .table-mod-tools листа — одна строка, не перенос (находка Ревизора,
   живой замер 1440+помощник: длинное имя листа переносило «Перечитать» на
   вторую строку). Общий .table-mod-tools переносит строку НАРОЧНО (кнопки
   фильтров не должны пропадать за overflow — сноска выше по файлу), но у
   компактного листового ряда переносить нечего: сжимаются само имя и мета
   (сноска у .sheet-name/.sheet-meta), поле шапки и кнопка держат размер. */
.table-mod--compact .table-mod-tools { padding: 8px 10px; gap: 6px; flex-wrap: nowrap; }
.table-mod--compact .data-table th { padding: 6px 6px; font-size: 11px; }
.table-mod--compact .data-table td { padding: 0 6px; font-size: 12.5px; }
.table-mod--compact .table-mod-wrap { max-height: 220px; }
.table-mod--compact .table-mod-tools > .checkbox,
.table-mod--compact .table-mod-tools > .table-mod-field,
.table-mod--compact .table-mod-tools > .btn,
.table-mod--compact .table-mod-tools > .ty { flex-shrink: 0; }
/* Найдено Ревизором (второй заход): 1280 + помощник открыт, 5 колонок
   (COMING STOCK) — scrollWidth 688 > clientWidth 626 (перебор 62px). Причина
   — .col-role-select min-width:110px (канон, не локальный) + отступы ячейки.
   Локально внутри .table-mod--compact сужаем и то, и другое: min-width 110→90px
   (−20px×5=−100px) с запасом на отступы ячейки (10→6px, ещё −8px×5=−40px) —
   риска не пересчитал живьём повторно в этом заходе (нет браузера под рукой),
   Ревизору стоит перемерить после этой правки на том же стенде/файле. */
.table-mod--compact .col-role-select { min-width: 90px; font-size: var(--fs-11); }

/* Имя листа + мета листа (карточка VES-DESIGN, прототип — Ревизор сверил
   живой экран с ним 17.09): галочка несёт только общий текст «Включить в
   прайс», имя конкретного листа — своя строка рядом, мета (строк · сколько
   пустых колонок скрыто из скольки всего) — под ним, тише текстом.
   Живой замер Ревизора (1280 + помощник, 626px) поймал первую версию: имя
   резалось у ВСЕХ листов (READY STOCK PERSONAL CARE — 148 из 214px) — имя
   единственное, что человек обязан прочесть целиком, режется только мета.
   .sheet-name flex-shrink:0 (не сжимается вовсе от нехватки места — только
   max-width:260px ловит нелепо длинные имена, обычные проходят целиком);
   .sheet-meta flex-shrink:1, min-width:0 (без него flex-item не сжимается
   уже содержимого) — на 626px ей остаётся ~24px, обрезанный текст — только
   в title (полный текст в обоих). */
.sheet-name {
  font-weight: 700;
  max-width: 260px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  flex-shrink: 0;
}
.sheet-meta {
  display: block;
  font-size: var(--fs-11);
  color: var(--text-muted);
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  flex-shrink: 1;
}

/* Инлайны экрана многолистового прайса (_multi_sheet_preview.html, находка
   Ревизора, третий заход) перенесены в классы — числа те же, что были в
   style="", просто больше не в разметке. Значения из шкалы --sp-* берутся
   там, где совпадают день-в-день (12/16/8/4px); там, где исходное число не
   попадает в шкалу (10px у чекбокса «Запомнить раскладку»), снято до
   ближайшего токена (12px) — разница 2px на глаз не ловится, отдельного
   утверждения вида не просилось.
   Селекторы — по id/атрибуту/структуре, где у элемента и так была своя
   опора (id, name, родитель) — новый класс на разметке заведён только там,
   где такой опоры не было (.table-mod-field-input, .alert-list[--muted],
   .table-mod-foot--block). */
#zagruzka-multi-sheet > .f-block { margin-bottom: var(--sp-12); }
#zagruzka-multi-sheet-fields > .table-mod--compact { margin-bottom: var(--sp-16); }
.table-mod-field-input { width: 44px; }
.alert-list { margin: 6px 0 0; padding-left: 18px; }
.alert-list--muted { color: var(--text-muted, #6c757d); }
.table-mod-foot--block { display: block; }
.table-mod-foot--block details { margin-top: var(--sp-4); }
#multi-sheet-price-file { margin-bottom: var(--sp-12); display: none; }
#multi-sheet-file-status { margin-bottom: var(--sp-8); font-size: 13px; color: var(--text-muted); }
#zagruzka-multi-save-form .f-block { margin-bottom: var(--sp-12); }
#zagruzka-multi-save-form input[name="profile_name"] {
  font-size: 13px;
  padding: 3px 8px;
  border: 1px solid var(--rule-strong, #ccc);
  border-radius: 4px;
  min-width: 200px;
}

.table-mod-pages {
  display: inline-flex;
  align-items: center;
  gap: 2px;
  margin-left: auto;
}

.table-mod-page {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 30px;
  height: 30px;
  padding: 0 8px;
  border-radius: var(--radius-2);
  color: var(--text-muted, var(--ink-soft));
  font-size: 12.5px;
  font-variant-numeric: tabular-nums;
  border: 1px solid transparent;
  text-decoration: none;
}
.table-mod-page.is-active {
  border-color: var(--line);
  background: var(--sheet);
  color: var(--text, var(--ink));
  font-weight: 500;
  box-shadow: var(--shadow-1);
}
.table-mod-page.is-dim { color: var(--text-faint, var(--ink-faint)); cursor: default; }

/* ── T2 Поиск и ярлычки фильтра (§6) ─────────────────────────────────────── */

.table-mod-search {
  display: flex;
  align-items: center;
  gap: 8px;
  height: var(--control-h, 36px);
  padding: 0 12px;
  border: 1px solid var(--line);
  border-radius: var(--radius-2);
  background: var(--sheet);
  flex: 1;
  min-width: 300px;
  max-width: 420px;
}
.table-mod-search-icon {
  position: relative;
  width: 14px;
  height: 14px;
  flex-shrink: 0;
}
.table-mod-search-icon::before {
  content: "";
  position: absolute;
  top: 0;
  left: 0;
  width: 9px;
  height: 9px;
  border-radius: var(--radius-pill);
  border: 1.6px solid var(--text-faint, var(--ink-faint));
}
.table-mod-search-icon::after {
  content: "";
  position: absolute;
  left: 7px;
  top: 8px;
  width: 6px;
  height: 1.6px;
  background: var(--text-faint, var(--ink-faint));
  transform: rotate(45deg);
  transform-origin: left center;
}
.table-mod-search input {
  flex: 1;
  min-width: 0;
  border: 0;
  background: transparent;
  font: inherit;
  font-size: 13.5px;
  color: var(--text, var(--ink));
}
/* outline:none только когда фокус НЕ клавиатурный (:not(:focus-visible)) —
   тот же приём, что .f-uline input/select выше. Безусловный outline:none
   прямо в базовом правиле (сила 0,1,1) молча перебивал глобальное кольцо
   :focus-visible (0,1,0) всегда, а не только при фокусе мышью — найдено 09.09
   живой пробой (Tab: поле поиска без кольца, соседний ярлычок-фильтр — с
   кольцом). */
.table-mod-search input:focus:not(:focus-visible) { outline: none; }
.table-mod-search input::placeholder { color: var(--text-faint, var(--ink-faint)); }
.table-mod-search-clear {
  flex-shrink: 0;
  border: 0;
  background: none;
  padding: 0 0 0 4px;
  color: var(--text-faint, var(--ink-faint));
  cursor: pointer;
  line-height: 1;
  font-size: 14px;
}
.table-mod-search-clear:hover { color: var(--danger, var(--red-ink)); }

/* ── Список подсказок живого поиска (.suggest-list) — общий компонент ───────
   Соседствует с .table-mod-search: сосед в позиционированном контейнере
   (сама страница даёт position:relative — .search-wrap на «Товарах»,
   position:relative на обёртке в _korziny.html), не сам якорь. До 14.09
   правила жили местным <style> tovary.html — на карточке прайса («Найти в
   каталоге», третий потребитель) список был голым: без паддингов и hover,
   подпись бренда приклеена к имени без зазора. Перенесено в общий файл —
   оба места получают одно и то же дословно, не два похожих правила. */
.suggest-list {
  position: absolute;
  top: calc(100% + 4px);
  left: 0;
  width: 360px;
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: var(--radius-2);
  z-index: var(--z-dropdown);
  max-height: 260px;
  overflow-y: auto;
}
.suggest-list:empty { display: none; }
.suggest-item {
  display: block;
  padding: 8px 12px;
  font-size: 13px;
  cursor: pointer;
  color: inherit;
  text-decoration: none;
  border-bottom: 1px solid var(--hover-bg);
}
.suggest-item:last-child { border-bottom: 0; }
.suggest-item:hover { background: var(--accent-soft); color: var(--accent); }
.suggest-brand { font-size: 11px; color: var(--text-muted); margin-left: 6px; }

.table-mod-chip-wrap { position: relative; display: inline-flex; }
.table-mod-chip-select {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  opacity: 0;
  border: 0;
  margin: 0;
  padding: 0;
  cursor: pointer;
}
.table-mod-chip {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  height: 30px;
  padding: 0 9px 0 11px;
  border-radius: var(--radius-2);
  border: 1px solid var(--line);
  color: var(--ink-body, var(--text-muted));
  font-size: 13px;
  pointer-events: none;
  white-space: nowrap;
}
.table-mod-chip b { font-weight: 500; color: var(--text, var(--ink)); }
.table-mod-chip-v { color: var(--text-faint, var(--ink-faint)); font-size: 11px; }
.table-mod-chip.is-on {
  background: var(--accent-soft);
  border-color: var(--accent-edge, var(--plum-200));
  color: var(--accent);
  /* Резерв под крестик-сброс (.table-mod-chip-clear), который лежит НАД
     select'ом отдельным настоящим элементом — без резерва текст значения
     упирался бы прямо в него. */
  padding-right: 24px;
}
.table-mod-chip.is-on b { color: var(--accent); }
/* Кольцо клавиатурного фокуса — на реальном (прозрачном) select, показываем
   его на видимом ярлычке-соседе (тот же приём, что у .f-uline select,
   components.css). */
.table-mod-chip-select:focus-visible ~ .table-mod-chip {
  outline: var(--focus-ring);
  outline-offset: 2px;
}
/* Крестик сброса ярлычка — настоящая ссылка (не декоративный текст внутри
   pointer-events:none чипа), НАД select'ом по z-index: приёмка 09.09 нашла,
   что старый декоративный × лежал ПОД select'ом (тот занимает всю площадь
   .table-mod-chip-wrap) и нажатие на него открывало список вместо сброса. */
.table-mod-chip-clear {
  position: absolute;
  top: 0;
  bottom: 0;
  right: 9px;
  z-index: 1;
  display: inline-flex;
  align-items: center;
  color: var(--accent);
  font-size: 11px;
  line-height: 1;
  text-decoration: none;
}
.table-mod-chip-clear:hover { color: var(--danger, var(--red-ink)); }
.table-mod-chip-clear:focus-visible { outline: var(--focus-ring); outline-offset: 1px; }

.table-mod-actions {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-left: auto;
  flex-shrink: 0;
}

/* ── T2b Второй вид полосы фильтров (§6, экран «Прайсы», волна 2 09.09) ─────
   Не ярлычки-чипы (T2 выше) — поля с подписью ВНУТРИ рамки: подпись, тонкий
   разделитель, значение (нативный <input>/<select>) в общей коробке высотой
   36, скруглением 8. Та же высота, что у поиска/ярлычка первого вида — оба
   вида полосы взаимозаменимы внутри .table-mod-tools.
   Разметка:
     <label class="table-mod-field">
       <span class="table-mod-field-label">Дата с</span>
       <span class="table-mod-field-sep" aria-hidden="true"></span>
       <input type="date" name="date_from">
     </label>
   <label> оборачивает подпись и контрол — доступное имя поля читалке экрана
   без отдельных for/id. Фокус — без явного outline:none внутри: глобальное
   :focus-visible (правило выше в этом файле) само докрашивает контрол кольцом
   --focus-ring, гасить/подменять его здесь незачем (иначе пришлось бы
   единообразно ставить guard :not(:focus-visible), как у .table-mod-search
   input, — здесь это не нужно, потому что нет своего безусловного outline:none). */
.table-mod-field {
  display: flex;
  align-items: center;
  gap: 8px;
  height: var(--control-h, 36px);
  padding: 0 12px;
  border: 1px solid var(--line);
  border-radius: var(--radius-2);
  background: var(--sheet);
}
.table-mod-field-label {
  flex-shrink: 0;
  font-size: 12.5px;
  color: var(--text-muted, var(--ink-soft));
  white-space: nowrap;
}
.table-mod-field-sep {
  flex-shrink: 0;
  width: 1px;
  height: 16px;
  background: var(--line);
}
.table-mod-field input,
.table-mod-field select {
  flex: 1;
  min-width: 0;
  border: 0;
  padding: 0;
  background: transparent;
  font: inherit;
  font-size: 13.5px;
  color: var(--text, var(--ink));
}
/* Стрелка своя — тот же приём, что .f-uline select/.col-role-select
   (components.css): appearance:none + двойной linear-gradient
   вместо стрелки браузера/ОС. */
.table-mod-field select {
  padding-right: 18px;
  -webkit-appearance: none;
  -moz-appearance: none;
  appearance: none;
  background-image:
    linear-gradient(45deg, transparent 50%, var(--text-muted) 50%),
    linear-gradient(135deg, var(--text-muted) 50%, transparent 50%);
  background-position: calc(100% - 10px) center, calc(100% - 6px) center;
  background-size: 5px 5px, 5px 5px;
  background-repeat: no-repeat;
}


/* ── C3 UnsavedBar — полоса несохранённых изменений ─────────────────────────
   Нейтральная полоса между шапкой и содержимым при наличии несохранённых полей.
   Мета-текст mono (количество полей, время). Кромка низа 1px. Без янтарных заливок.

   Разметка:
     <div class="unsaved-bar">
       <span class="unsaved-bar-meta">2 поля · с 09:38</span>
       <div class="unsaved-bar-actions">…кнопки…</div>
     </div>
*/

.unsaved-bar {
  display: flex;
  align-items: center;
  gap: 14px;
  padding: 8px 0;
  border-bottom: 1px solid var(--rule-strong);
  flex-wrap: wrap;
}

/* Кит §21: font:400 10.5px IBM Plex Mono — type-meta составной */
.unsaved-bar-meta {
  font: var(--type-meta);
  color: var(--text-label, var(--ink-faint));
  white-space: nowrap;
}

.unsaved-bar-actions {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-left: auto;
}


/* ── C4 Inset — вставка mono-текста с подложкой ──────────────────────────────
   Блок технического текста внутри карточки/модуля: лёгкая подложка, кромка 1px,
   radius 0, IBM Plex Mono, размер meta (11px). Используется для источника данных,
   технических меток, артикулов.

   Разметка:
     <div class="inset">артикул 1234 · источник 1С 08:52</div>
     — или многострочно:
     <div class="inset">строка 1<br>строка 2</div>
*/

.inset {
  display: block;
  padding: 6px 10px;
  background: var(--surface-inset, var(--hover-bg));
  border: 1px solid var(--edge-field, var(--rule-strong));
  font-family: var(--font-mono);
  font-size: var(--fs-11);
  color: var(--text-body, var(--ink-soft));
  line-height: 1.5;
}


/* ── C5 priority-row — строка «Radio + Select» реестра выбора ────────────────
   Горизонтальная строка приоритета: radio слева, mono-лейбл ранга, Select на всю
   оставшуюся ширину, StatusDot ok справа. Переиспользует .radio-list/.radio-row
   как базу; этот класс добавляет inline-Select и statusdot.
   Кромки 1px (наследует от .radio-row). Mono-значения. Radius 0.

   Разметка (внутри .radio-list):
     <label class="radio-row priority-row">
       <input type="radio" class="radio-input" name="priority" value="1">
       <span class="rdot"></span>
       <span class="priority-rank">1</span>
       <span class="priority-select-wrap">
         <select class="priority-select">…</select>
       </span>
       <span class="priority-dot status-dot is-online" aria-label="активен"></span>
     </label>
*/

/* Строка приоритета расширяет .radio-row: те же кромки/hover из .radio-list */
.priority-row {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: var(--sp-8) var(--sp-4);
  border-bottom: 1px solid var(--rule);
  cursor: pointer;
  margin: 0;
}
.priority-row:hover { background: var(--hover-bg); }

/* Ранг строки — моно-число (1/2/3) */
.priority-rank {
  font-family: var(--font-mono);
  font-size: var(--size-body);
  font-weight: 500;
  color: var(--text-label, var(--ink-faint));
  min-width: 14px;
  flex-shrink: 0;
}

/* Обёртка селекта — занимает оставшееся место */
.priority-select-wrap {
  flex: 1;
  min-width: 0;
}

/* Сам селект приоритета — без рамки-бокса, нижняя кромка */
.priority-select {
  width: 100%;
  border: 0;
  border-bottom: 1px solid var(--rule-strong);
  background: transparent;
  font-family: var(--font-mono);
  font-size: var(--size-body);
  color: var(--text);
  padding: 2px 0;
  cursor: pointer;
  appearance: none;
  -webkit-appearance: none;
}
.priority-select:focus { outline: none; border-bottom-color: var(--accent); }

/* Индикатор-точка статуса (переиспользует .status-dot) */
.priority-dot {
  flex-shrink: 0;
}

/* ── Группа E: мелкие новые паттерны (Шаг 11, 2026-08-15) ──────────────────
   E1 soft-delete-row — строка таблицы в состоянии «удалено»
   E2 data-table расширенная — модификаторы колонок quiet/strong/mono/tone, итог, выделение
   E3 dialog — канон-диалог (eyebrow + title + text + details + actions)
*/

/* ── E1 Мягкое удаление (soft-delete-row) ───────────────────────────────────
   Строка записи в состоянии «удалено»: зачёркнутый текст + приглушённый цвет,
   тег «удалено» (нейтральный, не сигнальный) + кнопки «Вернуть» / «Стереть сейчас».
   Мета-строка (кто удалил, срок окончательного стирания) — IBM Plex Mono, нейтраль.
   Разметка:
     <div class="soft-delete-row">
       <div class="soft-delete-row__head">
         <span class="soft-delete-row__name">Название записи</span>
         <span class="soft-delete-tag">удалено</span>
       </div>
       <div class="soft-delete-row__meta">удалил: и. петров · 12.08 09:38</div>
       <div class="soft-delete-row__actions">
         <button class="btn btn-default btn-sm">Вернуть</button>
         <button class="btn btn-danger btn-sm">Стереть сейчас</button>
       </div>
     </div>
*/

.soft-delete-row {
  padding-bottom: 11px;
  border-bottom: 1px solid var(--edge-row, var(--rule));
}

.soft-delete-row__head {
  display: grid;
  grid-template-columns: 1fr auto;
  align-items: center;
  gap: 14px;
  padding-bottom: 11px;
  border-bottom: 1px solid var(--edge-row, var(--rule));
}

/* Название удалённой записи — зачёркнуто, приглушено */
/* Кит §16: font:500 12.5px/1.4 Manrope -.02em #A2A8B0 — row-title + letter-spacing */
.soft-delete-row__name {
  font: var(--type-row-title);
  letter-spacing: var(--track-row-title, -.02em);
  color: var(--text-label, var(--ink-soft, #A2A8B0));
  text-decoration: line-through;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* Тег «удалено» — нейтральный (не сигнальный: без красного, без янтаря) */
.soft-delete-tag {
  display: inline-block;
  padding: 3px 6px;
  /* Кит §16: font:400 9px IBM Plex Mono uppercase — type-tag составной */
  font: var(--type-tag);

  text-transform: uppercase;
  letter-spacing: var(--track-tag, .06em);
  color: var(--signal-neutral, var(--ink-faint));
  border: 1px solid var(--edge-module, var(--rule-strong));
  flex-shrink: 0;
}

/* Мета-строка — моно, нейтраль */
/* Кит §16: font:400 10.5px IBM Plex Mono — type-meta составной */
.soft-delete-row__meta {
  padding-top: 11px;
  font: var(--type-meta);
  color: var(--signal-neutral, var(--ink-faint));
  line-height: 1.6;
}

/* Кнопки действий */
.soft-delete-row__actions {
  display: flex;
  align-items: center;
  gap: 16px;
  margin-top: 14px;
}


/* ── E2 DataTable расширенная — модификаторы колонок ────────────────────────
   Расширяет семантику существующей .data-table (base.css): добавляет
   модификаторы ячеек и строк. Не ломает текущую .data-table — только аддитивно.

   Модификаторы ячеек (на th/td):
     .dt-quiet  — приглушённый текст (--text-muted)
     .dt-strong — акцентный/жирный текст (--text + font-weight 600)
     .dt-mono   — IBM Plex Mono, tabular-nums
     .dt-tone   — сигнальная окраска по значению (нужны is-ok/is-critical/is-warning/is-neutral)

   Итоговая строка:
     <tr class="dt-total"> — моно, кромка сверху, без нижней линовки

   Выделение строки:
     <tr class="dt-selected"> — фон --surface-selected

   Строка-предупреждение (требует внимания):
     <tr class="dt-warn"> — фон --warn-bg (янтарь)

   Светофор (волна 2, 09.09 — карточка «Метки и ячейки», список товаров):
     <th class="dt-light">/<td class="dt-light"> — узкая колонка 34px слева
     <span class="dt-light-dot is-ok|is-bad|is-warn|is-neutral"> — точка 8px, aria-label
       обязателен (is-warn/is-neutral — 14.09, заказ отчётов Ozon: «готовится
       у Ozon» / «не заказан»)
     <span class="dt-light-tip"> — тёмный ярлык на наведение (не системный title)
     <div class="dt-light-legend"><span class="dt-light-legend-item">…</span></div> —
       подписи к точкам над таблицей (12.09, «Отчёты» Ozon)

   Двухъярусная ячейка (волна 2, 09.09 — раздел+категория одной колонкой):
     .dt-two-name — верхняя строка (значение, medium)
     .dt-two-sub  — нижняя строка (приглушённая, 11.5px)
*/

/* Приглушённый столбец. Селектор ячейки (td/th.dt-quiet) остаётся первым —
   класс вешают и на саму ячейку («Прайсы» и др.), и на встроенный <span>
   ВНУТРИ ячейки («Сотрудники», «Изменения»: приглушённая подпись рядом с
   основным значением) — второй селектор ловит этот случай, находка приёмки
   волны 2 админ-экранов (11.09): span без правила красился обычным цветом
   текста, приглушение терялось. */
.data-table td.dt-quiet,
.data-table th.dt-quiet,
.data-table .dt-quiet {
  color: var(--text-muted, var(--ink-soft));
}

/* Обрезка многоточием без изменения веса шрифта — тот же приём, что
   .table-mod .data-table td.dt-strong (сноска выше по файлу), но для ячеек,
   которым не нужен акцентный/жирный текст (класс без правила и без веса до
   доводки 11.09: `.dt-quiet` в _vhod_table.html обрезался инлайн-стилем). */
.table-mod .data-table td.dt-clip,
.table-mod .data-table td .dt-two-name.dt-clip {
  max-width: 300px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* .dt-clip на вложенном элементе, а не на самой ячейке — двухъярусная ячейка
   (.dt-two-name), где обрезать нужен только верхний ярус: второй ярус
   (.dt-two-sub) остаётся на своей строке под ним. max-width: 100% на вложенном
   блоке ничего не режет — при auto table layout колонка сама растёт до
   ширины несжатого текста (живая проба показала scrollWidth == clientWidth,
   обрезки не было вовсе), поэтому берём тот же жёсткий предел 300px, что и
   .dt-clip на самой ячейке (правило выше) — единое число для класса, а не два
   разных смысла обрезки. Доводка приёмки волны 2, 11.09: длинная категория
   без переноса разгоняла строку до 94px вместо канонных 44. .dt-two-name уже
   display:block сам по себе (правило ниже по файлу) — здесь только явное
   подтверждение для .dt-clip-комбинации, чтобы white-space/overflow работали
   на строчном контексте предсказуемо. */
.table-mod .data-table td .dt-two-name.dt-clip {
  display: block;
}

/* Колонка «Лист» на «Строках прайса» многолистового пути (находка Ревизора,
   второй заход): общий .dt-clip (300px) не сдерживает короткое имя листа —
   при table-layout:auto браузер всё равно даёт ему непропорционально много
   места («COMING STOCK» тянул колонку до 231px), из-за чего вся таблица
   (1101px) вылезала за рамку контента (991px на 1440 с открытым помощником) —
   у однолистового пути (без содержимого в «Лист») те же 843px укладывались
   ровно. Более тесный предел — того же порядка, что соседние колонки
   короткого кода (Артикул/EAN, класс .mono без своего max-width, но реально
   узкие на живых данных). */
.table-mod .data-table td.dt-clip-narrow {
  max-width: 140px;
}

/* Значение, не переносящее строку, без обрезки многоточием (не хватало
   узкого класса «телефон/короткий код» — доводка приёмки волны 2 админ-
   экранов, 11.09: «Сотрудники», колонка «Телефон» разгоняла строку до 93px
   на пробеле внутри номера). */
.data-table td.dt-nowrap {
  white-space: nowrap;
}

/* Акцентный столбец */
.data-table td.dt-strong {
  color: var(--text, var(--ink));
  font-weight: 600;
}
.data-table th.dt-strong {
  color: var(--text-muted, var(--ink-soft));
}

/* Моно-столбец (числа, артикулы) */
.data-table td.dt-mono,
.data-table th.dt-mono {
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
}

/* Тональный столбец — сигнальный цвет значения (ТОЛЬКО на данных, не на шапке) */
.data-table td.dt-tone.is-ok       { color: var(--signal-ok,       var(--ledger-green)); }
.data-table td.dt-tone.is-critical { color: var(--signal-critical,  var(--red-ink)); }
.data-table td.dt-tone.is-warning  { color: var(--signal-warning,   var(--amber)); }
.data-table td.dt-tone.is-neutral  { color: var(--signal-neutral,   var(--ink-faint)); }

/* Итоговая строка: моно, кромка сверху (1px), без нижней линовки */
.data-table tr.dt-total td {
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
  font-weight: 500;
  border-top: 1px solid var(--rule-strong);
  border-bottom: 0;
  padding-top: 8px;
  color: var(--text, var(--ink));
}
.data-table tr.dt-total:hover { background: transparent; }

/* Выделенная строка — фон surface-selected */
.data-table tr.dt-selected td,
.data-table tr.dt-selected:hover td {
  background: var(--surface-selected, var(--wax-soft));
}

/* Строка-предупреждение — тёплый янтарный фон (например, закреплённый
   приёмник «Без категории», когда на нём есть неразобранные товары) */
.data-table tr.dt-warn td,
.data-table tr.dt-warn:hover td {
  background: var(--warn-bg, var(--amber-soft));
}

/* Светофор — узкая колонка вместо плашки состояния (волна 2, 09.09).
   Не кнопка: карточка молчит про нажатие, образец даёт только наведение —
   зона 40×40 (паспорт 3.9.3) ему не нужна. */
.data-table th.dt-light,
.data-table td.dt-light {
  width: 34px;
  padding: 0 0 0 16px;
  position: relative;
  overflow: visible;
}
.dt-light-dot {
  display: block;
  width: 8px;
  height: 8px;
  border-radius: var(--radius-pill);
}
.dt-light-dot.is-ok  { background: var(--ok,      var(--ledger-green)); }
.dt-light-dot.is-bad { background: var(--danger,  var(--red-ink)); }
/* 14.09 — заказ отчётов Ozon: «готовится у Ozon» / «не заказан». */
.dt-light-dot.is-warn    { background: var(--warn); }
.dt-light-dot.is-neutral { background: var(--neutral); }

/* Ярлык на наведение — тёмный, не системный title (карточка «Метки и ячейки») */
.dt-light-tip {
  display: none;
  position: absolute;
  left: 30px;
  top: 8px;
  background: var(--ink);
  color: var(--text-invert, #fff);
  font-size: var(--fs-12);
  line-height: 1.3;
  padding: 6px 9px;
  border-radius: var(--radius-1);
  white-space: nowrap;
  box-shadow: 0 6px 16px rgba(16, 24, 40, .18);
  z-index: var(--z-dropdown);
}
.dt-light-tip::before {
  content: "";
  position: absolute;
  left: -4px;
  top: 10px;
  width: 8px;
  height: 8px;
  background: var(--ink);
  transform: rotate(45deg);
}
.data-table td.dt-light:hover .dt-light-tip { display: block; }

/* Легенда светофора — подписи к точкам над таблицей (было инлайном
   d-flex/flex-wrap/gap-3/mb-3 + style="font:var(--type-meta);
   color:var(--text-label)" на «Отчётах» Ozon, 12.09 — тот же вид, заведён
   классом). .dt-light-legend-item без вложенной .dt-light-dot — запись
   «без точки — …». */
.dt-light-legend {
  display: flex;
  flex-wrap: wrap;
  gap: 16px;
  margin-bottom: 16px;
  font: var(--type-meta);
  color: var(--text-label);
}
.dt-light-legend-item {
  display: flex;
  align-items: center;
  gap: 8px;
}

/* Двухъярусная ячейка — раздел под категорией одной колонкой (волна 2, 09.09) */
.dt-two-name { display: block; color: var(--text, var(--ink)); font-weight: 500; }
.dt-two-sub  { display: block; color: var(--text-faint, var(--ink-faint)); font-size: 11.5px; margin-top: 1px; }

/* ── E2a Синхронная сетка колонок (11.09, находка Ильи на «Отчётах» Ozon) ───
   Несколько .data-table с ОДИНАКОВЫМ набором колонок (пять таблиц-групп на
   одном экране) обязаны совпадать по границам колонок. Обычный (auto) table
   layout считает ширины по содержимому КАЖДОЙ таблицы отдельно — «способ»
   уезжал от 268 до 437px между группами (разные имена отчётов). Модификатор
   .data-table--report-grid переводит таблицу на table-layout:fixed; порядок
   колонок задаёт <colgroup> с классами .dt-col-* — ширина колонки не зависит
   от содержимого конкретной таблицы, только от класса, поэтому одинаковые
   колонки во всех таблицах встают на одно и то же место. Одна колонка без
   явной ширины (.dt-col-report) забирает весь остаток (CSS 2.1 §17.5.2.1
   «Table width algorithms»: «remaining columns equally divide the remaining
   horizontal table space» — при ровно одной такой колонке ей достаётся
   всё). Длинное название отчёта переносится по словам (white-space по
   умолчанию normal на td), не обрезается многоточием.

   Остальные пять колонок — фиксированные px, измерены живым CDP-замером
   (table-layout:auto, width:auto) как максимум по всем пяти группам:
   способ 77px, период 194px (крайний случай — скользящее окно «Спроса»,
   «12.08.2026–11.09.2026», без пробелов, не переносится), свежесть 156px
   (d.m.Y H:i моно), действия — до 455px по самому длинному тексту ошибки
   («Ozon не отдал эти данные…» и т.п., это НЕ ограничение «без переноса» —
   длинный текст ошибки переносится, колонка держит только удобную ширину
   под две кнопки). Сумма фиксированных колонок (34+80+195+160+210=679px)
   уже близка к доступной ширине контента на 1280px (сайдбар раздела +
   открытая панель помощника 360px съедают большую часть окна) — без
   min-width и прокрутки колонка «Отчёт» схлопывалась в 0 и текст полз
   поверх соседних ячеек (живой CDP-замер 11.09, а не расчёт на бумаге).
   min-width держит «Отчёт» не уже ~220px, ниже него — не сжатие, а
   горизонтальная прокрутка модуля (.module-body ниже). */
.data-table--report-grid { table-layout: fixed; min-width: 900px; }
.data-table--report-grid .dt-col-signal    { width: 34px; }
.data-table--report-grid .dt-col-method    { width: 80px; }
.data-table--report-grid .dt-col-period    { width: 195px; }
.data-table--report-grid .dt-col-freshness { width: 160px; }
.data-table--report-grid .dt-col-actions   { width: 210px; }
/* .dt-col-report — без width, вся оставшаяся ширина таблицы (не менее
   ~221px — гарантирует min-width выше). */

/* Ниже min-width таблицы — прокрутка модуля, не сжатие колонок до нуля
   (родитель напрямую несёт .data-table--report-grid — прямой потомок). */
.module-body:has(> .data-table--report-grid) { overflow-x: auto; }

/* ── E2a-2 Перестройка по ширине КОНТЕЙНЕРА (11.09, решение Ильи) ───────────
   Экран «Отчёты» открывается рядом с панелью помощника (360px) — на типовом
   ноутбуке (1280px окна) модулю остаётся меньше 900px, и вместо перестройки
   колонок таблица просто скроллится вбок (правило выше). Ниже — второй порог,
   переключающий саму раскладку колонок: «способ»/«период» уходят во вторую
   строку названия отчёта, «свежесть» и «действия» остаются колонками без
   горизонтальной прокрутки (макет Дизайнера, прототип 11.09).

   Порог — @container, не @media: источник размера здесь — .module-body
   (контентная область экрана, между сайдбаром раздела и панелью помощника),
   а не окно вкладки. Открыл/закрыл помощника — перестроилось сразу, без
   перезагрузки; на широком экране без помощника то же 900px могут никогда
   не наступить. Первое применение @container в своде — до сих пор кит решал
   адаптивность только через @media на окно (base.css). Контейнер объявлен
   ТОЛЬКО там, где уже стоит .data-table--report-grid (тот же прицельный
   селектор, что overflow-x:auto строкой выше) — не на всех .module-body
   разом, чтобы контейнерный контекст не задел модули других экранов молча.

   Порог 899.98px — буквально «меньше 900px» (min-width таблицы выше), не
   отдельное число: на ровно 900px контейнера остаётся широкая раскладка
   (совпадает с min-width, прокрутки ещё нет). */
.module-body:has(> .data-table--report-grid) {
  container-type: inline-size;
  container-name: report-grid-body;
}

.dt-two-sub--narrow { display: none; }

@container report-grid-body (max-width: 899.98px) {
  /* Пол узкой раскладки (находка Ревизора 11.09): без него min-width:0 не
     держит НИКАКОГО пола для .dt-col-report, и на достаточно узком
     контейнере (замер: окно 1100px и 1024px с открытым помощником) колонка
     схлопывается до 21px/0px — заголовок «ОТЧЁТ» наезжает на «СВЕЖЕСТЬ».
     600px = сумма фиксированных колонок узкой раскладки (34 signal + 160
     freshness + 210 actions = 404px) + ~196px названию — живой замер
     (getBoundingClientRect, CDP) подтвердил именно эту цифру после правки:
     колонка «Отчёт» получила ровно 196px и на 1100, и на 1024 (одинаково —
     ниже пола ширина таблицы фиксирована самим min-width, не зависит от
     того, насколько именно контейнер уже пола). Ниже 600px контейнера —
     возвращается горизонтальная прокрутка (.module-body:has(...) выше), не
     дальнейшее сжатие колонок до нуля. */
  .data-table--report-grid { min-width: 600px; }
  .data-table--report-grid .dt-col-method,
  .data-table--report-grid .dt-col-period {
    display: none;
  }
  .data-table--report-grid .dt-two-sub--wide   { display: none; }
  .data-table--report-grid .dt-two-sub--narrow { display: block; }
}

/* ── E2b TypeMark — типографские метки состояния (.ty, волна 2, 09.09) ──────
   Источник: inbox/volna-2-standart-iz-kartochek.md §5 (карточка
   ctablicy/04-metki-i-yachejki, экран «Прайсы»). Взамен цветных плашек с
   фоном (.badge-ok/.badge-warn/.badge-err в prajsy/root.html) — голая
   метка: капс, разрядка, без фона/рамки/скругления. Капс даёт CSS
   (text-transform), не заглавные буквы в шаблоне — иначе поиск по странице
   и копирование текста отдают капс. Цвета совпадают буквально с уже живыми
   токенами (--ok/--warn/--danger/--text-muted/--accent — те же hex, что
   называет карточка), новых токенов не заводилось.
   Разметка: <span class="ty ok|warn|bad|info">Текст</span>
   .ty.off снят приёмкой 09.09 (находка Ревизора): потребителя не было ни на
   живом экране, ни на витрине — по контракту неиспользуемый вариант либо
   показывается на витрине, либо снимается; витрина вне периметра этой
   правки, снят. */
.ty {
  font-size: 9px;
  font-weight: 600;
  letter-spacing: .07em;
  text-transform: uppercase;
}
.ty.ok   { color: var(--ok,      var(--ledger-green)); }
.ty.warn { color: var(--warn,    var(--amber)); }
.ty.bad  { color: var(--danger,  var(--red-ink)); }
.ty.info { color: var(--accent); }


/* ── E3 Dialog — канон-диалог ────────────────────────────────────────────────
   Модалка канона: eyebrow (мета mono uppercase) + title + text + details-блок.
   Подложка ≈50% (Bootstrap default), БЕЗ теней, БЕЗ цветного бортика, radius --radius-3.
   Обёртка внутри Bootstrap .modal — класс .dialog (содержимое диалога).
   Bootstrap управляет позиционированием и backdrop; .dialog задаёт только вид.

   Разметка (внутри #modal-dialog, передаётся htmx):
     <div class="dialog">
       <div class="dialog-eyebrow">мета-метка</div>
       <div class="dialog-title">Заголовок диалога</div>
       <div class="dialog-text">Пояснительный текст.</div>
       <div class="dialog-details">
         <span class="dialog-detail-item">Деталь 1</span>
         …
       </div>
       <div class="dialog-actions">
         <button class="btn btn-primary">Подтвердить</button>
         <button class="btn btn-default" data-bs-dismiss="modal">Отменить</button>
       </div>
     </div>
*/

/* Контейнер диалога — поверхность sheet, кромка 1px, radius модуль (10), без теней */
.dialog {
  background: var(--surface-module, var(--sheet));
  border: 1px solid var(--edge-module, var(--rule-strong));
  padding: 24px;
  /* Радиус модуля поверх Bootstrap-дефолта */
  border-radius: var(--radius-3);
  box-shadow: none;
  /* Форма «в карточку» (04.09): семь строк сравнения без .modal-content могут
     не влезть на низком экране — своя прокрутка вместо обрезки содержимого. */
  max-height: 88vh;
  overflow-y: auto;
  /* Наследие: у Bootstrap .modal-dialog pointer-events выключен, а обратно на
     auto его возвращает только .modal-content — без этой обёртки (наш вид,
     п.выше) .dialog наследует выключенное значение, клик проваливается на
     .modal и закрывает диалог по фону вместо нажатия кнопки/поля внутри.
     Замерено 04.09: getComputedStyle возвращал "none" у #modal-dialog, у
     .dialog и у кнопки внутри, пока этого правила не было. */
  pointer-events: auto;
}

/* Eyebrow — мета-метка: IBM Plex Mono uppercase */
/* Кит §15: font:400 9.5px IBM Plex Mono uppercase .12em #A2A8B0 — type-label составной */
.dialog-eyebrow {
  font: var(--type-label);
  text-transform: uppercase;
  letter-spacing: var(--track-tag, .06em);
  color: var(--text-label, var(--ink-soft, #A2A8B0));
  margin-bottom: 8px;
}

/* Заголовок диалога — Manrope 500 */
/* Кит §15: font:500 15px Manrope #14161B */
.dialog-title {
  font-family: var(--font-ui);
  font-size: var(--size-dialog-title);
  font-weight: 500;
  color: var(--text, var(--ink));
  line-height: 1.3;
  margin-bottom: 10px;
}

/* Пояснительный текст */
/* Кит §15: font:400 11.5px/1.65 Manrope #6A7079 */
.dialog-text {
  font-family: var(--font-ui);
  font-size: var(--size-secondary);
  color: var(--text-secondary, var(--ink-soft, #6A7079));
  line-height: 1.65;
  margin-bottom: 14px;
}

/* Блок деталей — список затронутых записей (моно, мелкий) */
/* Кит §15: font:400 10.5px/1.7 IBM Plex Mono #6A7079 */
.dialog-details {
  font-family: var(--font-mono);
  font-size: var(--size-meta);
  color: var(--text-secondary, var(--ink-soft, #6A7079));
  background: var(--surface-inset, var(--hover-bg));
  border: 1px solid var(--edge-field, var(--rule-strong));
  padding: 8px 10px;
  margin-bottom: 20px;
  line-height: 1.7;
}

.dialog-detail-item {
  display: block;
}

/* Блок кнопок */
.dialog-actions {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
}

/* ══════════════════════════════════════════════════════════════════════════
   ГРУППА D — Компоненты оболочки (Шаг 12, 2026-08-15)
   Аддитивно: НЕ трогаем живые .chatpanel / .opendocs / .subnav-left / .topnav
   ══════════════════════════════════════════════════════════════════════════ */

/* ── D1 AssistantPanel (.assistant-panel) ──────────────────────────────── */

/*
  Использование:
    <div class="assistant-panel">
      <div class="assistant-panel-header">
        <span class="assistant-panel-title">Помощник</span>
        <span class="assistant-panel-model">модель-б · быстрая</span>
        <button class="assistant-panel-close" type="button" aria-label="Свернуть">×</button>
      </div>
      <div class="assistant-panel-body">
        <!-- сообщения -->
      </div>
      <div class="assistant-panel-input">
        <input class="assistant-panel-field" type="text" placeholder="Введите сообщение…">
        <button class="btn btn-primary btn-sm" type="button">↵</button>
      </div>
    </div>
*/

/* Контейнер панели: ширина из токена --assistant-width, flex-колонка */
.assistant-panel {
  width: var(--assistant-width, 320px);
  display: flex;
  flex-direction: column;
  background: var(--surface-module, var(--sheet));
  border: 1px solid var(--edge-module, var(--rule-strong));
  border-radius: var(--radius-3);
  box-shadow: none;
}

/* Шапка: заголовок + мета-строка модели + кнопка «свернуть» */
.assistant-panel-header {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 10px 14px;
  border-bottom: 1px solid var(--edge-row, var(--rule));
  flex: none;
}

/* Кит §22: font:500 13px Manrope */
.assistant-panel-title {
  font-family: var(--font-sans);
  font-size: var(--size-module-title);
  font-weight: 500;
  color: var(--text, var(--ink));
  flex: 1;
}

/* Кит §22: font:400 9.5px IBM Plex Mono #9CA3AF — type-label составной */
.assistant-panel-model {
  font: var(--type-label);
  color: var(--text-faint, var(--ink-faint, #9CA3AF));
}

.assistant-panel-close {
  background: none;
  border: none;
  padding: 2px 6px;
  cursor: pointer;
  color: var(--text-muted, var(--ink-soft));
  line-height: 1;
}
.assistant-panel-close:hover {
  color: var(--text, var(--ink));
}

/* Тело чата: прокручивается, занимает свободное место */
.assistant-panel-body {
  flex: 1;
  overflow-y: auto;
  padding: 12px 14px;
  display: flex;
  flex-direction: column;
  gap: 10px;
  min-height: 0;
}

/* Поле ввода: прилипает к низу */
.assistant-panel-input {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 8px 14px;
  border-top: 1px solid var(--edge-row, var(--rule));
  flex: none;
}

/* Кит §22: font:400 12px Manrope */
.assistant-panel-field {
  flex: 1;
  border: none;
  border-bottom: 1px solid var(--edge-field, var(--rule-strong));
  background: transparent;
  font-family: var(--font-sans);
  font-size: var(--size-body);
  color: var(--text, var(--ink));
  padding: 4px 0;
  outline: none;
}
.assistant-panel-field::placeholder {
  color: var(--text-placeholder, var(--ink-faint));
}
.assistant-panel-field:focus {
  border-bottom-color: var(--accent, var(--wax));
}

/* ── D2 AssistantSpine (.assistant-spine) ──────────────────────────────── */

/*
  Использование:
    <div class="assistant-spine" role="button" tabindex="0" aria-label="Развернуть помощника">
      <span class="assistant-spine-icon">❯</span>
      <span class="assistant-spine-dot is-unread" aria-label="Есть непрочитанные"></span>
    </div>
*/

/* Узкий вертикальный корешок: 44px, кромка слева */
.assistant-spine {
  width: 44px;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 6px;
  background: var(--surface-module, var(--sheet));
  border-left: 1px solid var(--edge-module, var(--rule-strong));
  cursor: pointer;
  flex: none;
}
.assistant-spine:hover {
  background: var(--hover-bg, var(--surface-hover));
}

/* Иконка разворота */
.assistant-spine-icon {
  font-size: 16px;
  color: var(--text-muted, var(--ink-soft));
  user-select: none;
}

/* Точка-маркер непрочитанных */
.assistant-spine-dot {
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: transparent;
}
.assistant-spine-dot.is-unread {
  background: var(--accent, var(--wax));
}

/* ── D3 AssistantSuggestion (.assistant-suggestion) ────────────────────── */

/*
  Использование:
    <div class="assistant-suggestion">
      <div class="assistant-suggestion-header">
        <span class="assistant-suggestion-number">№ 140</span>
        <span class="assistant-suggestion-field">ST250E · цена, ₽</span>
      </div>
      <div class="assistant-suggestion-change">
        <span class="assistant-suggestion-from">4 290</span>
        <span class="assistant-suggestion-arrow">→</span>
        <span class="assistant-suggestion-to">4 390</span>
      </div>
      <div class="assistant-suggestion-source">прайс поставщика от 01.08, строка 218</div>
      <div class="assistant-suggestion-actions">
        <button class="btn btn-primary btn-sm" type="button">Принять</button>
        <button class="btn btn-default btn-sm" type="button">Отклонить</button>
      </div>
    </div>
*/

/* Карточка предложения: кромка акцента слева, лёгкий фон */
.assistant-suggestion {
  border: 1px solid var(--edge-module, var(--rule-strong));
  border-left: 3px solid var(--accent, var(--wax));
  background: var(--surface-inset, var(--hover-bg));
  padding: 10px 12px;
  display: flex;
  flex-direction: column;
  gap: 6px;
  border-radius: var(--radius-2);
}

/* Шапка карточки: номер + поле */
.assistant-suggestion-header {
  display: flex;
  align-items: center;
  gap: 8px;
}

/* Кит §22: font:400 9.5px IBM Plex Mono #9CA3AF — type-label составной */
.assistant-suggestion-number {
  font: var(--type-label);
  color: var(--text-faint, var(--ink-faint, #9CA3AF));
  flex: none;
}

/* Кит §22: font:400 9.5px IBM Plex Mono — type-label составной (weight 400, не 500) */
.assistant-suggestion-field {
  font: var(--type-label);
  color: var(--text, var(--ink));
}

/* Строка «from → to» */
.assistant-suggestion-change {
  display: flex;
  align-items: baseline;
  gap: 8px;
}

/* Кит §22: font:400 15px IBM Plex Mono зачёркнутое — type-metric-sm составной */
.assistant-suggestion-from {
  font: var(--type-metric-sm);
  font-weight: 400;
  color: var(--text-muted, var(--ink-soft));
  text-decoration: line-through;
}

.assistant-suggestion-arrow {
  color: var(--text-muted, var(--ink-soft));
}

/* Кит §22: font:500 19px IBM Plex Mono — 19px токена нет, литерал */
.assistant-suggestion-to {
  font-family: var(--font-mono);
  font-size: 19px;
  font-weight: 500;
  color: var(--text, var(--ink));
}

/* Источник (mono, мелкий) */
/* Кит §22: font:400 10px/1.5 IBM Plex Mono #9CA3AF — --text-faint */
.assistant-suggestion-source {
  font-family: var(--font-mono);
  font-size: var(--size-micro);
  color: var(--text-faint, var(--ink-faint, #9CA3AF));
}

/* Кнопки принять/отклонить */
.assistant-suggestion-actions {
  display: flex;
  gap: 8px;
  flex-wrap: wrap;
  margin-top: 2px;
}

/* ── D4 nav-hover-demo (канон hover-пункта §25, НЕ живой .topnav) ─────── */

/*
  ДЕМО-КЛАСС. НЕ применять к живому .topnav — только витрина /ui/.
  Показывает hover-состояние пункта навигации §25: заливка --nav-hover,
  подчёркивание --wax (акцент), текст --ink.

  Использование (только в showcase.html):
    <a class="nav-hover-demo" href="#">Пункт меню</a>
*/

/* Кит §25 строка 137: пункт меню 12px = --size-body */
.nav-hover-demo {
  display: inline-flex;
  align-items: center;
  padding: 6px 14px;
  background: var(--nav-hover, #EADDEC);
  color: var(--ink, #111827);
  box-shadow: inset 0 -2px 0 var(--wax, #6E3A78);
  font-family: var(--font-sans);
  font-size: var(--size-body);
  text-decoration: none;
  cursor: pointer;
  border-radius: var(--radius-2);
}

/* ── D5 Favicon «V» — документировано, не дублируется (уже в base.html) ── */

/*
  Favicon-марка V подключена в base.html строкой:
    <link rel="icon" type="image/svg+xml" href="{% static 'core/img/favicon.svg' %}">
  SVG: <rect fill="#6E3A78"/>, <polygon fill="#E8EBEE"/> — фон баклажан, клин серо-белый.
  Размер 32×32, border-radius 0.
  CSS-класс не нужен — favicon браузерный. Запись только для документирования.
*/

/* ── D6 TabStrip (.tab-strip, .tab-item, .tab-close) ───────────────────── */

/*
  НЕ трогает живой .opendocs. Канон-класс новой полосы вкладок.

  Использование:
    <div class="tab-strip" role="tablist">
      <button class="tab-item is-active" role="tab" aria-selected="true">
        Название вкладки
        <span class="tab-close" aria-label="Закрыть вкладку">×</span>
      </button>
      <button class="tab-item" role="tab" aria-selected="false">
        Другая вкладка
        <span class="tab-close" aria-label="Закрыть вкладку">×</span>
      </button>
    </div>
*/

/* Полоса вкладок: горизонтальный flex, кромка снизу */
.tab-strip {
  display: flex;
  align-items: stretch;
  border-bottom: 1px solid var(--edge-module, var(--rule-strong));
  background: var(--surface-canvas, var(--surface));
  overflow-x: auto;
  flex: none;
}

/* Вкладка: кнопка без вида, flex, кромки */
/* Кит §04 строка 211: неактивная вкладка #9CA3AF = --ink-faint = --text-faint */
.tab-item {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 7px 14px;
  border: none;
  border-right: 1px solid var(--edge-row, var(--rule));
  background: var(--surface-tab-idle, var(--hover-bg));
  color: var(--text-faint, var(--ink-faint, #9CA3AF));
  font-family: var(--font-sans);
  font-size: var(--size-small);
  cursor: pointer;
  max-width: var(--tab-max-width, 104px);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  border-radius: var(--radius-2);
  flex: none;
}
.tab-item:hover {
  background: var(--hover-bg);
  color: var(--text, var(--ink));
}

/* Активная вкладка: акцентная кромка снизу, поверхность sheet */
.tab-item.is-active {
  background: var(--surface-module, var(--sheet));
  color: var(--text, var(--ink));
  font-weight: 500;
  box-shadow: inset 0 -2px 0 var(--accent, var(--wax));
  border-bottom-color: transparent;
}

/* Крестик закрытия вкладки */
/* Кит §19: cross font:400 12px Manrope */
.tab-close {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 14px;
  height: 14px;
  font-size: var(--size-body);
  color: var(--text-muted, var(--ink-soft));
  border-radius: 50%;
  flex: none;
  line-height: 1;
}
/* Зона нажатия 40×40 — паспорт 3.9.3: знак 14px не меняется, зона добирается
   псевдоэлементом. */
.tab-close { position: relative; }
.tab-close::after {
  content: ""; position: absolute; top: 50%; left: 50%;
  width: 40px; height: 40px; transform: translate(-50%, -50%);
}
.tab-close:hover {
  background: var(--edge-row, var(--rule));
  color: var(--text, var(--ink));
}

/* ── D7 SideNav (.side-nav) ─────────────────────────────────────────────── */

/*
  Канон-класс бокового меню. Источник — карточка VES-DESIGN
  «Боковое меню» (dobolochka/03-bokovoe-menyu.html), перевод 22.09.2026.

  Использование:
    <nav class="side-nav" aria-label="Пункты раздела">
      <div class="side-nav-title">Раздел</div>
      <div class="side-nav-group">
        <div class="side-nav-label">Группа</div>
        <a class="side-nav-item is-active" href="#">Активный пункт</a>
        <a class="side-nav-item" href="#">Обычный пункт</a>
      </div>
      <a class="side-nav-item" href="#">Пункт без группы</a>
    </nav>
*/

/* Боковое меню: вертикальный flex, кромка справа */
.side-nav {
  display: flex;
  flex-direction: column;
  border-right: 1px solid var(--edge-module, var(--rule-strong));
  background: var(--surface-module, var(--sheet));
  width: var(--sidenav-width, 206px);
  flex: none;
  overflow-y: auto;
}

/* Заголовок секции над пунктами */
/* Кит §22: font:400 9.5px IBM Plex Mono uppercase #A2A8B0 — type-label составной */
.side-nav-title {
  padding: 12px 16px 8px;
  font: var(--type-label);
  text-transform: uppercase;
  letter-spacing: .06em;
  color: var(--text-label, var(--ink-soft, #A2A8B0));
  border-bottom: 1px solid var(--edge-row, var(--rule));
}

/* Группировка пунктов */
.side-nav-group {
  display: flex;
  flex-direction: column;
}

/* Заголовок группы (без ссылки) */
/* Карточка VES-DESIGN «Боковое меню»: 11px, --ink-faint, шрифт текстовый
   (не mono), без uppercase и разрядки; горизонтальные поля держим 16px,
   как у пунктов. Кегль числом — общий токен --size-label (9.5px) держит
   подписи по всей программе, здесь другое число намеренно. */
.side-nav-label {
  padding: 10px 16px 4px;
  font-family: var(--font-sans);
  font-size: 11px;
  color: var(--ink-faint);
}

/* Пункт меню */
/* Кит §04 строки 222-224: обычный пункт был #828992 (= --ink-600); волна 1,
   добор repoint-нула --text-tertiary на --ink-soft (#6B7280) */
.side-nav-item {
  display: block;
  padding: 7px 16px;
  font-family: var(--font-sans);
  font-size: var(--size-body);
  color: var(--text-tertiary, var(--ink-soft, #828992));
  text-decoration: none;
  border-radius: var(--radius-2);
}
.side-nav-item:hover {
  background: var(--hover-bg);
  color: var(--text, var(--ink));
}

/* Активный пункт: карточка VES-DESIGN «Боковое меню» — подложка --accent-soft,
   текст --accent, без кромки слева (старая слива-кромка рисовала дугу из-за
   border-radius и выглядела как оторванная скобка). */
.side-nav-item.is-active {
  background: var(--accent-soft);
  color: var(--accent, var(--wax));
  font-weight: 500;
}


/* ══════════════════════════════════════════════════════════════════════════
   ГРУППА G — Канон-контейнер «Модуль» (re-sync витрины, 2026-08-19)
   Источник: _ds_bundle.js / components/core/Module.jsx
   ══════════════════════════════════════════════════════════════════════════

   G1 Module — белый контейнер с 1px кромкой, шапкой (title + meta) и телом.
   Без тени и скруглений — основная поверхность системы.

   Свойство rule (линия под шапкой) — три варианта:
     hair   (по умолчанию) — волосяная, --edge-row   (обычный модуль)
     strong                 — чёрная,   --edge-strong (перед таблицей)
     none                   — нет линии (когда содержимое — плитка/график)

   Разметка:
     <section class="module">
       <header class="module-head">
         <span class="module-title">Заголовок</span>
         <span class="module-meta">мета</span>
         <span class="module-actions">…кнопки…</span>
       </header>
       <div class="module-body">…содержимое…</div>
     </section>

   Модификаторы на корневом элементе:
     .module--wide          — широкие отступы (24px / 30px вместо 16px / 18px)
     .module--rule-hair     — волосяная линия (явно; совпадает с умолчанием)
     .module--rule-strong   — сплошная чёрная линия (перед таблицей)
     .module--rule-none     — линии нет (содержимое — плитка или график)
*/

.module {
  background: var(--surface-module, #FFFFFF);
  border: 1px solid var(--edge-module, var(--rule-strong));
  padding: var(--module-pad-y, 16px) var(--module-pad-x, 18px);
  /* Тень модуля (замер Дизайнера 15.09) — то же значение, что уже несёт
     .table-mod (T1, выше в файле): один и тот же контракт «поверхность
     системы», второй канон-компонент не должен выглядеть площе первого. */
  box-shadow: var(--shadow-1);
}

.module--wide {
  padding: var(--module-pad-y-wide, 24px) var(--module-pad-x-wide, 30px);
}

/* Шапка модуля: заголовок слева, мета-подпись следом, действия через auto */
.module-head {
  display: flex;
  align-items: center;
  gap: 14px;
  padding-bottom: 12px;
  /* Линия под шапкой — умолчание hair */
  border-bottom: 1px solid var(--edge-row, var(--rule));
}

/* Модификаторы линии под шапкой (ставятся на корневой .module) */
.module--rule-hair   .module-head { border-bottom-color: var(--edge-row,    var(--rule)); }
.module--rule-strong .module-head { border-bottom-color: var(--edge-strong, var(--ink)); }
.module--rule-none   .module-head { border-bottom-color: transparent; }

/* Заголовок модуля: Manrope 500 13px/1, трекинг -.03em, цвет --text-heading */
.module-title {
  font: var(--type-module-title);
  letter-spacing: var(--track-module-title, -.03em);
  color: var(--text-heading, var(--ink));
}

/* Мета-подпись модуля: IBM Plex Mono 400 10.5px/1, цвет --text-label */
.module-meta {
  font: var(--type-meta);
  color: var(--text-label, var(--ink-faint));
}

/* Блок действий шапки: auto-margin прижимает вправо */
.module-actions {
  margin-left: auto;
  display: flex;
  gap: 10px;
  align-items: center;
}

/* Тело модуля: верхний отступ 14px (если есть шапка) */
.module-body {
  padding-top: 14px;
}


/* ── Группа G2: атомы ввода (re-sync витрины 2026-08-19) ────────────────────
   G2-A  Кнопка-вторая   (.btn-secondary)
   G2-B  Кнопка-встроенная (.btn-inline) — отличается от служебной .btn-sm
         .btn-sm — служебный модификатор (мелкие кнопки-действия);
         .btn-inline — новый вид кита «встроенная в поток» (accent + edge)
   G2-C  Кнопка-ссылка    (.btn-link)
   G2-D  Галочка           (.checkbox)
   G2-E  Сегментированный переключатель (.seg-ctrl)
*/

/* ── G2-A .btn-secondary — текстовая второстепенная ─────────────────────────
   Источник: _ds_bundle.js Button variant=secondary.
   Вид: нет рамки, нет фона. Цвет --text-tertiary, подчёркивание снизу при hover.
   Паддинг: 0 0 3px (оставляет место для нижней тени).
   Шрифт: regular (400), size-control (11.5px). */
.btn-secondary {
  --bs-btn-color: var(--text-tertiary);
  --bs-btn-bg: transparent;
  --bs-btn-border-color: transparent;
  --bs-btn-font-size: var(--size-control);
  --bs-btn-font-weight: 400;
  --bs-btn-padding-x: 0;
  --bs-btn-padding-y: 0;
  --bs-btn-hover-color: var(--text-data, var(--ink));
  --bs-btn-hover-bg: transparent;
  --bs-btn-hover-border-color: transparent;
  --bs-btn-active-color: var(--text-data, var(--ink));
  --bs-btn-active-bg: transparent;
  --bs-btn-active-border-color: transparent;
  --bs-btn-disabled-color: var(--text-disabled, var(--text-faint));
  --bs-btn-disabled-bg: transparent;
  --bs-btn-disabled-border-color: transparent;
  /* 16.5px — своя высота, записана числом (Ревизор живьём 22.09, находка
     волны 4: раньше держалась НЕЯВНО, тем, что база вовсе не знала ни
     height, ни min-height; своя же геометрия не менялась — 0(паддинг-верх)
     + 3(padding-bottom ниже) + 11.5(кегль size-control × leading-flat 1)
     + 1+1(рамка, прозрачная, но занимает место) = 16.5). `min-height: 0`
     здесь раньше был способом молча отключить чужой height — байка сработала,
     пока у базы не завёлся min-height 36 (специфичность .btn-secondary и
     .btn — обе 0,1,0, а .btn-secondary позже в файле, честно перебивала
     0 против 36 порядком, но это тот же неявный расчёт, который просили
     закрыть числом). */
  min-height: 16.5px;
  padding-bottom: 3px;
}
/* Подчёркивание — box-shadow при hover, исчезает в disabled */
.btn-secondary:hover:not(:disabled) {
  box-shadow: inset 0 -1px 0 var(--rule-strong, var(--ink-faint, #C3C8CE));
}
.btn-secondary:disabled { box-shadow: none; }

/* ── G2-B .btn-inline — встроенная в поток (новый вид кита) ─────────────────
   Источник: _ds_bundle.js Button variant=inline.
   Вид: контурная на светлом фоне с accent-кромкой (фиолет бледный).
   Паддинг: 8px/13px (--btn-inline-pad-y/x). Шрифт: regular 11px (--type-inline-action).
   Hover/active: более насыщенная кромка + слабый accent-фон.
   НЕ путать с .btn-sm (служебная, другой назначение). */
.btn-inline {
  --bs-btn-color: var(--accent, var(--wax));
  --bs-btn-bg: var(--surface-module, var(--panel));
  --bs-btn-border-color: var(--accent-edge, var(--plum-200, #DCC9E0));
  --bs-btn-font-size: var(--size-small);
  --bs-btn-font-weight: 400;
  --bs-btn-padding-x: var(--btn-inline-pad-x, 13px);
  --bs-btn-padding-y: var(--btn-inline-pad-y, 8px);
  --bs-btn-border-radius: var(--radius-2);
  --bs-btn-hover-color: var(--accent-press, var(--wax-dark));
  --bs-btn-hover-bg: var(--accent-tint, var(--accent-soft));
  --bs-btn-hover-border-color: var(--accent, var(--wax));
  --bs-btn-active-color: var(--accent-press, var(--wax-dark));
  --bs-btn-active-bg: var(--accent-tint, var(--accent-soft));
  --bs-btn-active-border-color: var(--accent, var(--wax));
  --bs-btn-disabled-color: var(--text-disabled, var(--text-faint));
  --bs-btn-disabled-bg: transparent;
  --bs-btn-disabled-border-color: var(--edge-field, var(--rule));
  /* 29px — своя высота, записана числом (тот же заход 22.09/Ревизор):
     8(--btn-inline-pad-y, верх) + 8(низ) + 11(кегль --size-small × leading-flat 1)
     + 1+1(рамка --accent-edge) = 29. .btn.btn-inline — живой канон
     (_ozon_report_row.html), специфичность 0,2,0 против базовой .btn 0,1,0 —
     перебивает чужой min-height числом, а не только порядком в файле. */
  min-height: 29px;
}

/* ── G2-C .btn-link — текстовая ссылка-кнопка ───────────────────────────────
   Источник: _ds_bundle.js Button variant=link.
   Вид: только текст акцент, без рамки, без фона. Паддинг 0.
   Hover: нижняя тень (как ссылка). Disabled: --text-disabled. */
.btn-link {
  --bs-btn-color: var(--accent, var(--wax));
  --bs-btn-bg: transparent;
  --bs-btn-border-color: transparent;
  --bs-btn-font-size: var(--size-small);
  --bs-btn-font-weight: 400;
  --bs-btn-padding-x: 0;
  --bs-btn-padding-y: 0;
  --bs-btn-hover-color: var(--accent, var(--wax));
  --bs-btn-hover-bg: transparent;
  --bs-btn-hover-border-color: transparent;
  --bs-btn-active-color: var(--accent-press, var(--wax-dark));
  --bs-btn-active-bg: transparent;
  --bs-btn-active-border-color: transparent;
  --bs-btn-disabled-color: var(--text-disabled, var(--text-faint));
  --bs-btn-disabled-bg: transparent;
  --bs-btn-disabled-border-color: transparent;
  /* 13px — своя высота, записана числом (тот же заход 22.09/Ревизор):
     0+0(паддинг) + 11(кегль --size-small × leading-flat 1) + 1+1(рамка,
     прозрачная, но занимает место) = 13. */
  min-height: 13px;
  text-decoration: none;
}
.btn-link:hover:not(:disabled) {
  box-shadow: inset 0 -1px 0 var(--accent, var(--wax));
}
.btn-link:disabled { box-shadow: none; }
/* .chat-brain-actions .btn-link снят 14.09 вместе с живым partial
   (_chat_brain.html перешёл на .btn-default.btn-sm, решение Ильи «ссылок-
   кнопок больше нет») — правило было точечным размером ИМЕННО для .btn-link
   в этом месте и с уходом класса из разметки стало мёртвым (0 совпадений
   grep по src/). Сам .btn-link (блок выше) остаётся в файле: канон-раздел
   витрины #kit-btn-g2 и test_design_group_g2.py всё ещё его показывают/пинят
   — из живых экранов класс ушёл, из «Кнопок второго набора» кита нет
   мандата снимать. */

/* ── G2-D .checkbox — галочка ───────────────────────────────────────────────
   Источник: _ds_bundle.js Checkbox.
   Размер бокса: 13px (--control-checkbox). Прямые углы (radius 0).
   Состояния: normal (border --edge-control, фон white),
              checked/indeterminate (border+bg --accent),
              disabled (border --edge-field, фон --surface-disabled).
   Полусостояние «–» (indeterminate): специальный content через ::before.
   Галочка «✓» — через ::before при :checked.
   Разметка:
     <label class="checkbox">
       <input type="checkbox" class="checkbox-input">
       <span class="checkbox-box" aria-hidden="true"></span>
       <span class="checkbox-label">текст</span>
     </label>
*/
.checkbox {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  cursor: pointer;
  /* Площадь нажатия — паспорт 3.9.3: промахнуться по галочке дорого.
     Высотой строки, не размером бокса: сам бокс остаётся 13×13, галочка
     и подпись стоят на той же вертикали. */
  min-height: 40px;
}
.checkbox.is-disabled { cursor: default; }
.checkbox-input {
  position: absolute;
  opacity: 0;
  width: 0;
  height: 0;
  pointer-events: none;
}
.checkbox-box {
  flex: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: var(--control-checkbox, 13px);
  height: var(--control-checkbox, 13px);
  border: 1px solid var(--edge-control, var(--rule-strong));
  background: var(--surface-module, var(--sheet));
  color: var(--text-on-accent, #FFFFFF);
  font: var(--weight-medium) 9px/1 var(--font-sans);
  flex-shrink: 0;
}
/* Отмечено */
.checkbox-input:checked ~ .checkbox-box {
  background: var(--accent, var(--wax));
  border-color: var(--accent, var(--wax));
}
.checkbox-input:checked ~ .checkbox-box::before { content: "✓"; }
/* Промежуточное (indeterminate) — через JS или статический класс .is-indeterminate */
.checkbox-input:indeterminate ~ .checkbox-box,
.checkbox-box.is-indeterminate {
  background: var(--accent, var(--wax));
  border-color: var(--accent, var(--wax));
}
.checkbox-input:indeterminate ~ .checkbox-box::before,
.checkbox-box.is-indeterminate::before { content: "–"; }
/* Отключено */
.checkbox:has(.checkbox-input:disabled) .checkbox-box,
.checkbox.is-disabled .checkbox-box {
  border-color: var(--edge-field, var(--rule));
  background: var(--surface-disabled, var(--paper));
}
.checkbox:has(.checkbox-input:disabled) { cursor: default; }
/* Подпись */
.checkbox-label {
  font: var(--weight-regular) var(--size-meta)/1 var(--font-sans);
  color: var(--text-tertiary, var(--text-muted));
}
.checkbox-input:disabled ~ .checkbox-label,
.checkbox.is-disabled .checkbox-label {
  color: var(--text-disabled, var(--text-faint));
}
/* Фокус */
.checkbox-input:focus-visible ~ .checkbox-box { outline: var(--focus-ring); outline-offset: 2px; }

/* ── G2-E .seg-ctrl — сегментированный переключатель ────────────────────────
   Источник: _ds_bundle.js / Модули недостающие блоки 03 segmentsA.
   Обёртка: inline-flex, 1px кромка --edge-field снаружи.
   Сегмент: padding 9px/15px (--seg-pad-y / --btn-pad-x), regular 11.5px (--size-control), цвет --text-muted.
   Активный: medium 11.5px, цвет --text-data, фон --seg-active-bg (#ECE1EE, темнее accent-tint).
   Разделитель: 1px solid --edge-field слева (кроме первого сегмента).
   Без скруглений (radius 0 — канон «Модулей»). */
.seg-ctrl {
  display: inline-flex;
  border: 1px solid var(--edge-field, var(--rule));
}
.seg-ctrl-item {
  display: inline-flex;
  align-items: center;
  padding: var(--seg-pad-y, 9px) var(--btn-pad-x, 15px);
  cursor: pointer;
  user-select: none;
  white-space: nowrap;
  font: var(--weight-regular, 400) var(--size-control, 11.5px)/1 var(--font-sans, var(--font-ui));
  color: var(--text-muted, var(--ink-soft));
  background: transparent;
  border: 0;
  border-left: 1px solid var(--edge-field, var(--rule));
}
.seg-ctrl-item:first-child { border-left: 0; }
.seg-ctrl-item:hover { color: var(--text-data, var(--ink)); background: var(--hover-bg); }
.seg-ctrl-item.is-active {
  font-weight: var(--weight-medium, 500);
  color: var(--text-data, var(--ink));
  background: var(--seg-active-bg, var(--plum-100));
}
/* Disabled сегмент */
.seg-ctrl-item:disabled {
  color: var(--text-disabled, var(--text-faint));
  cursor: default;
  background: transparent;
}
/* Фокус */
.seg-ctrl-item:focus-visible { outline: var(--focus-ring); outline-offset: -2px; }

/* ══════════════════════════════════════════════════════════════════════════
   ГРУППА G6 — Верхнее меню, Марка, Значки, Каркас (витрина /ui/)
   Источник: Дизайн-система Модули §13 / §16 / §25; tokens.css §Каркас окна.
   ДЕМО-КЛАССЫ: никакой из них не применяется к живым элементам программы.
   Живой .topnav в base.html не затронут.
   ══════════════════════════════════════════════════════════════════════════

   G6-1 demo-topnav — образец верхнего меню как оболочки окна (§13 кита).
   Разводка от живого .topnav:
     - Разные имена классов: .demo-topnav*, не .topnav / .topnav-items.
     - Только для витрины; живой base.html не тронут.

   Разметка:
     <div class="demo-topnav">
       <div class="demo-topnav-brand">
         <svg ...марка 16px.../>
         <span class="demo-topnav-wordmark">Весквадра</span>
       </div>
       <nav class="demo-topnav-zones">
         <a class="demo-topnav-item" href="#">Пункт</a>
         <a class="demo-topnav-item is-active" href="#">Активный</a>
       </nav>
       <div class="demo-topnav-right">и. петров</div>
     </div>
*/

/* Полоса верхнего меню: горизонтальный flex, чёрная кромка снизу */
.demo-topnav {
  display: flex;
  align-items: stretch;
  flex-wrap: wrap;
  background: var(--surface-module, #FFFFFF);
  border-bottom: 1px solid var(--ink, #111827);
}

/* Блок марки слева: значок + слово, кромка справа */
.demo-topnav-brand {
  display: flex;
  align-items: center;
  gap: 9px;
  padding: 0 18px;
  border-right: 1px solid var(--edge-row, var(--rule));
  flex: none;
}

/* Подпись «Весквадра» рядом с маркой */
/* Кит §25: 13px/600 Manrope — размер и вес OK, убираем запасное значение */
.demo-topnav-wordmark {
  font-family: var(--font-sans);
  font-size: var(--size-module-title);
  font-weight: 600;
  letter-spacing: -.02em;
  color: var(--text, var(--ink, #111827));
}

/* Зоны: горизонтальный flex пунктов */
.demo-topnav-zones {
  display: flex;
  align-items: stretch;
  flex-wrap: wrap;
  min-width: 0;
}

/* Пункт зоны: обычное состояние */
/* Кит §25: font:400 12px Manrope #6B7280 (normal = --text-muted = #6B7280) */
.demo-topnav-item {
  display: inline-flex;
  align-items: center;
  padding: 15px 11px;
  font-family: var(--font-sans);
  font-size: var(--size-body);
  font-weight: 400;
  color: var(--text-muted, var(--ink-soft, #6B7280));
  text-decoration: none;
  cursor: pointer;
  white-space: nowrap;
  user-select: none;
  border-radius: var(--radius-2);
  background: transparent;
}

/* Hover (показывается в витрине через Alpine или фиксированный класс) */
.demo-topnav-item:hover,
.demo-topnav-item.is-hover {
  background: var(--nav-hover, #EADDEC);
  color: var(--ink, #111827);
}

/* Активная зона: заливка + полоса 2px снизу + полужирное */
.demo-topnav-item.is-active {
  background: var(--nav-hover, #EADDEC);
  color: var(--ink, #111827);
  font-weight: 600;
  box-shadow: inset 0 -2px 0 var(--accent, var(--wax, #6E3A78));
}

/* Правая область: имя пользователя */
/* Кит §25: font:400 10.5px IBM Plex Mono, было #A2A8B0 (= --ink-500); волна 1,
   добор repoint-нула --text-label на --ink-soft (#6B7280) */
.demo-topnav-right {
  margin-left: auto;
  display: flex;
  align-items: center;
  padding: 0 18px;
  font-family: var(--font-mono);
  font-size: var(--size-meta);
  color: var(--text-label, var(--ink-soft, #A2A8B0));
}

/* G6-2 demo-brand-mark — образцы марки в масштабах (§16).
   Вспомогательные: метка размера под SVG, вид в меню.
   CSS минимален — вид управляется inline SVG и flex-атрибутами. */

/* Обёртка одного размера: SVG + метка снизу */
.demo-brand-size {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 8px;
}

/* Метка размера под SVG */
.demo-brand-label {
  font-family: var(--font-mono);
  font-size: var(--size-meta);
  color: var(--text-muted, var(--ink-soft));
}

/* Плашка «в меню» — светлый фон */
.demo-brand-in-menu {
  display: inline-flex;
  align-items: center;
  gap: 9px;
  padding: 8px 11px;
  background: var(--surface-module, var(--sheet, #F2F4F6));
  border: 1px solid var(--edge-row, var(--rule));
}

/* Плашка «в тёмном меню» */
.demo-brand-in-menu-dark {
  display: inline-flex;
  align-items: center;
  gap: 9px;
  padding: 8px 11px;
  background: var(--ink, #111827);
}

/* G6-3 demo-icon-row — строка-образец значка с подписью (§16).
   Значок + текст на одной строке — цвет акцент, как в системе. */
.demo-icon-row {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  font-family: var(--font-sans);
  font-size: var(--size-body);
  color: var(--accent, var(--wax, #6E3A78));
}

/* G6-4 demo-frame-table — таблица ширин каркаса окна (§15 кита).
   Две колонки: название + значение. */
.demo-frame-table {
  display: grid;
  grid-template-columns: 1fr auto;
  gap: 10px 18px;
  align-items: baseline;
}

.demo-frame-name {
  font-family: var(--font-sans);
  font-size: var(--size-module-title);
  color: var(--text-body, var(--text, var(--ink)));
  line-height: 1.4;
}

.demo-frame-value {
  font-family: var(--font-mono);
  font-size: var(--size-body);
  font-weight: 500;
  color: var(--text, var(--ink, #111827));
  text-align: right;
  white-space: nowrap;
}

/* ── Карточка товара (просмотр + правка) — общие классы ─────────────────
   Классы .ro-field/.ro-label/.ro-val/.ro-list и лента комментариев
   (.comment-item/.comment-meta/.comment-text) подняты из инлайн-стилей
   edit.html сюда — они нужны партиалам на просмотре (detail.html).
   Стили формы (form-*, btn-save, btn-cancel, arm, photo-*, price-ledger,
   doc-head sticky) НЕ перенесены — их держит edit.html.
   Первый живой потребитель китовых токенов — карточка товара (решение штаба 2026-08-20). */

/* Заголовок секции карточки — роль «module-title» (13px / 500 / Manrope),
   с прописной первой буквы (text-transform: none — текст пишется в шаблоне нормально).
   ОСТОРОЖНО: .card-section h3 в base.css переопределён в edit.html инлайн;
   в edit.html локальные стили уберут это правило когда снимут инлайн-блок.
   Скоуп через .product-card чтобы не задеть card-section на других экранах. */
.product-card .card-section h3 {
  font: var(--type-module-title);
  letter-spacing: var(--track-module-title);
  color: var(--text-heading);
  text-transform: none; /* с прописной первой буквы — правка приёмки 2026-08-20 */
  margin: 0 0 12px;
}

/* Подпись поля — «label» (9.5px / 400 / IBM Plex Mono / uppercase) */
.product-card .ro-label {
  font: var(--type-label);
  letter-spacing: var(--track-label);
  text-transform: uppercase;
  color: var(--text-label);
  margin-bottom: 4px;
}

/* Значение поля */
.product-card .ro-val {
  font-size: 13px;
  color: var(--text-body);
}

/* Мета-подвал карточки (строка «Создан / Изменён») — роль «мета» кита:
   --type-meta (regular / 10.5px / IBM Plex Mono / leading-flat) + --text-secondary.
   Заменяет <small class="text-muted"> (Bootstrap: ~11.4px regular / --text-muted).
   Сверено с ProductCardScreen.jsx (meta под ScreenHead) и AppShell.jsx:48. */
.product-card .card-meta-footer {
  margin-top: 20px;
  padding-top: 10px;
  border-top: 1px solid var(--edge-row);
}
.product-card .card-meta-text {
  font: var(--type-meta);
  color: var(--text-secondary);
}
.product-card .card-meta-sep {
  font: var(--type-meta);
  color: var(--text-secondary);
}

/* Обёртка поля read-only */
.ro-field { margin-bottom: 14px; }

/* Список значений (EAN, страны и т.п.) */
.ro-list { list-style: none; padding: 0; margin: 0; }
.ro-list li { font-size: 13px; padding: 2px 0; color: var(--text-body, var(--text)); }
.ro-list li small { color: var(--text-faint); margin-left: 6px; }

/* Лента комментариев */
.comment-item {
  border: 1px solid var(--line);
  border-radius: var(--radius-2);
  padding: 8px 10px;
  background: var(--panel);
  margin-bottom: 6px;
}
.comment-meta { font-size: 11.5px; color: var(--text-faint); margin-bottom: 2px; }
.comment-text { font-size: 13px; color: var(--text-body, var(--text)); }

/* Крестик удаления строки EAN/ссылки/страны — перенесён из инлайн-стиля
   edit.html (находка Ревизора, 17.09): делит канон с состоянием «взвод
   удаления» ниже, оба живут рядом на одном экране. */
.btn-del {
  padding: 2px 7px; background: var(--panel); color: var(--danger);
  border: 1px solid var(--danger); border-radius: var(--radius-2); font: inherit;
  font-size: 12px; cursor: pointer; margin-left: 6px;
}
.btn-del:hover { background: var(--danger-bg); }

/* ── Взвод удаления EAN/ссылки/страны — карточка VES-DESIGN «Заголовок
   экрана и сохранение», пятое состояние (решение Ильи 17.09, заменяет
   отменённую плашку .arm-summary внутри списка). Вопрос и кнопки едут в
   шапку (.delete-confirm-row, общий include core/tovary/_delete_confirm_
   row.html на все три поля); здесь — подсветка взведённой строки списка и
   замок тела карточки.
   Пустой ряд не должен занимать место в .head-actions (flex-gap иначе
   оставлял бы зазор даже без контента). ── */
.delete-confirm-row:empty { display: none; }
.delete-confirm-row { display: flex; align-items: center; gap: 8px; }
/* Решение Ильи (18.09, после двух кругов приёмки с переносом на вторую
   строку — оба варианта отвергнуты): ряд ВСЕГДА в одну строку, без
   переноса. Кнопочная группа ниже — фиксированной ширины, вопрос —
   единственный, кто ужимается (правило ниже, класс ty-value),
   растяжимым флексом, многоточием, а не переносом кнопок. Ширина ряда
   (440px = кнопки ~252px + запас ~180px) выбрана, чтобы 14-значный EAN
   («Удалить EAN «12345678901234»?», натуральная ширина 171px) влезал
   ЦЕЛИКОМ без обрезки — это было явное условие Ильи. h1 забирает всё,
   что осталось от .doc-head сверх этой ширины.
   Живой замер (CDP, htmx:afterSettle, document.fonts.ready — без них
   числа плавали от FOUT/CPU-нагрузки, см. память), 1280 с помощником:
   ОБА сценария — одна строка, .doc-head scrollWidth === clientWidth
   (без прокрутки), кнопка «Подтвердите удаление» под elementFromPoint.
   36092 (короткий вопрос про EAN, влезает ЦЕЛИКОМ без многоточия): h1
   140px высотой — ТОЧНО как в состоянии «Подтвердите сохранение».
   40818 (ссылка 170 символов, вопрос обрезан многоточием, кнопки целы):
   h1 — 280px высотой / 140px шириной, ХУЖЕ состояния «Подтвердите
   сохранение» (140px/289px) — это НЕ опечатка и не забытый фикс: два
   требования математически противоречат друг другу при 643px ширины
   .doc-head. Бюджет: h1 на уровне «Подтвердите сохранение» нужно ≥289px,
   кнопки+гэп ≥260px, вместе с 16px зазора это 565px из 643 — на запас
   под вопрос остаётся всего ~78px, МЕНЬШЕ даже предложенных Ильёй
   ~120px, не говоря о 171px, нужных для целого 14-значного EAN. Ряд
   насколько раз крутили: 280px даёт LINK его 140px (принят круг 1), но
   вопрос EAN обрезан до ~20px (нечитаем); 380px даёт EAN ~120px (ещё
   обрезан — код 14 цифр НЕ влезает при этом запасе, несмотря на цифру
   из примера) и LINK уже на 234px; 440px — компромисс в другую сторону:
   EAN целиком, LINK на аварийном полу. Выбрана буквальная просьба «код
   влезает целиком» — если это не тот компромисс, нужно явное решение
   Ильи, какое из двух требований важнее при текущей ширине помощника. */
.delete-confirm-row.is-armed {
  flex-wrap: nowrap; width: 440px; justify-content: flex-end;
}
/* Обе кнопки — ОДИН атомарный флекс-item (не два отдельных ребёнка ряда):
   fixed-width, не участвует в сжатии — весь запас ужатия отдан вопросу. */
.delete-confirm-btns { display: flex; gap: 8px; flex: none; }

/* .ty капитализирует ЛЮБОЙ текст (text-transform: uppercase) — вопрос несёт
   буквальное значение (штрихкод/URL/страна) в кавычках, капс его искажает.
   Модификатор снимает капс только для этой метки. flex:1 1 auto с
   min-width:0 (решение Ильи 18.09, вместо переноса на вторую строку —
   см. коммент у .delete-confirm-row.is-armed выше) — вопрос растягивается
   на весь запас, что остался после кнопок фиксированной ширины, и ужимается
   многоточием, если контента больше; text-align:right держит видимый текст
   рядом с кнопками, а не с зазором слева от них, когда вопрос короче
   отведённого места. */
.ty.ty-value {
  text-transform: none; flex: 1 1 auto; min-width: 0; text-align: right;
  overflow: hidden; white-space: nowrap; text-overflow: ellipsis;
}

/* Строка списка со взведённым удалением — бледно-розовая подсветка (канон
   §04, --surface-danger); крестик рядом красится «нажатым» (гуще заливка,
   --surface-danger-strong/--edge-danger — тот же контраст, что у стадии
   подтверждения .btn-arm-danger). z-index поднимает строку НАД накладкой
   замка ниже — единственный элемент тела карточки, который она не тушит. */
.ro-list li.is-delete-armed {
  position: relative; z-index: 6;
  background: var(--surface-danger);
  border-radius: var(--radius-2);
}
.ro-list li.is-delete-armed .btn-del {
  background: var(--surface-danger-strong);
  border-color: var(--edge-danger);
}

/* Карточка заперта на время подтверждения удаления (правило Ильи: поля,
   «+», другие крестики, ряд сохранения не откликаются). .doc-body — тело
   карточки БЕЗ шапки (.doc-head — сосед вне этого узла); замок не
   перерисовывает содержимое (несохранённые правки в других полях не
   теряются) — чистый CSS: pointer-events:none на всё поддерево + полу-
   прозрачная накладка ::before НИЖЕ взведённой строки по z-index. */
.doc-body { position: relative; }
.pane-body:has(.delete-confirm-row.is-armed) .doc-body { pointer-events: none; }
.pane-body:has(.delete-confirm-row.is-armed) .doc-body::before {
  content: ""; position: absolute; inset: 0; z-index: 5;
  background: var(--panel); opacity: .6;
}

/* Ряд «Отменить/Сохранить»/«Назад» прячется, пока висит подтверждение
   удаления — САМИ узлы data-dirty-actions/data-dirty-back не трогаются
   (только CSS): form-dirty.js держит на них живые ссылки, outerHTML-OOB
   их бы состарил (см. память ves-programmer/htmx-i-shablony.md). */
.head-actions:has(.delete-confirm-row.is-armed) [data-dirty-actions],
.head-actions:has(.delete-confirm-row.is-armed) [data-dirty-back] {
  display: none;
}

/* ══════════════════════════════════════════════════════════════════════════
   Справочник «Логистика» (docs/passport/06-purchases.md §6.14, решения
   зафиксированы прототипом 15.09):
   строка списка с полями-инпутами внутри .module (без своей рамки .table-mod),
   группа «машина + 2 прицепа», поле-фраза Европы. Переиспользует
   .module/.f-uline/.field-with-unit (витрина /ui/) и .opendoc-close
   (components.css) — новых цветов/отступов не заводит. ══════════════════════ */

.ll-table-wrap { overflow-x: auto; }
table.ll-mini { width: 100%; border-collapse: collapse; font-family: var(--font-sans, var(--font-ui)); }
table.ll-mini thead tr { border-bottom: 1px solid var(--edge-row); }
table.ll-mini th {
  padding: 9px 5px; font-size: 12px; font-weight: 500; color: var(--text-muted, var(--ink-soft));
  text-align: left; white-space: nowrap;
}
table.ll-mini th.num { text-align: right; }
table.ll-mini tbody tr { border-bottom: 1px solid var(--edge-row); }
table.ll-mini tbody tr:last-child { border-bottom: 0; }
table.ll-mini td { padding: 4px 5px; height: 44px; font-size: 13.5px; vertical-align: middle; }
table.ll-mini td.num { text-align: right; font-variant-numeric: tabular-nums; }

/* Таблица машин (8 колонок) — table-layout:auto давал 3px переполнения на
   1280 с открытым помощником (табличная раскладка сама плавает в
   субпиксель, живой замер CDP 15.09: table.scrollWidth 608 против
   wrap.clientWidth 605 — верно даже с урезанными padding/max-width выше,
   причина не в них). table-layout:fixed с явными % по th (одна и та же
   раскладка колонок что для строки-машины со слитой ячейкой размеров
   colspan=3, что для строки-прицепа с тремя раздельными) убирает саму
   возможность переполнения контентом — сумма 100%, ширина таблицы точно
   равна контейнеру. */
/* min-width — ниже него fixed-колонки схлопывались друг на друга («3000,00»
   резалось, шапка слипалась: находка Архитектора 15.09, окно ~1220 с
   помощником даёт область ~547px). .ll-table-wrap уже несёт overflow-x:auto
   — с min-width таблица не сжимается тесней 540px, а прокручивается, как
   таблицы без fixed-раскладки. На 1280 с помощником (область ~605px) запас
   остаётся (max-width ниже не даёт вылезти за 12px), на 605 min-width не
   действует вовсе (540 < 605). */
#ll-mashina-table { table-layout: fixed; max-width: calc(100% - 12px); min-width: 540px; }
#ll-palleta-table { min-width: 540px; }
#ll-mashina-table th:nth-child(1) { width: 18%; }
#ll-mashina-table th:nth-child(2) { width: 12%; }
#ll-mashina-table th:nth-child(3) { width: 13%; }
#ll-mashina-table th:nth-child(4) { width: 12%; }
#ll-mashina-table th:nth-child(5) { width: 14%; }
#ll-mashina-table th:nth-child(6) { width: 9%; }
#ll-mashina-table th:nth-child(7) { width: 13%; }
#ll-mashina-table th:nth-child(8) { width: 9%; }

/* Ячейка-ввод строки — подчёркивание вместо бокса, язык .f-uline (§«Чего нет» п.1).
   max-width 92px (не 120) — таблица машин (8 колонок) обязана умещаться в
   область модуля на 1280 с открытым помощником без горизонтальной прокрутки
   (витрина /ui/, «Проверка любого нового элемента»), живой замер 15.09. */
.cell-in {
  width: 100%; max-width: 92px; border: 0; border-bottom: 1px solid var(--rule-strong, var(--edge-field));
  background: transparent; padding: 6px 2px; font: inherit; font-size: 13.5px; color: var(--text);
}
.cell-in.num-in { text-align: right; font-family: var(--font-mono); font-variant-numeric: tabular-nums; }
.cell-in:focus { outline: 2px solid var(--accent); outline-offset: 1px; border-bottom-color: var(--accent); }
/* !important — сознательно: обычная одиночная `.cell-in--error` проигрывала
   `.ll-sentence .sfld` (два класса) и `.f-uline input` (класс+тег), поле
   Европы/скидки не краснело на ошибке (находка Архитектора 15.09). Тройной
   повтор класса поднял специфичность до (0,0,3,0) и решил случай .sfld, но
   `.f-uline input { border: 0 }` (border-bottom-width:0 через шорткод)
   остался сильнее живьём (проверено CDP getComputedStyle, найдена ещё одна
   находка в той же доводке) — состояние ошибки обязано побеждать любой
   контекст поля, это ровно тот случай, для которого !important предназначен
   (не борьба каскада, а явный сигнал состояния). */
.cell-in--error {
  border-bottom-color: var(--error-field, var(--danger)) !important;
  border-bottom-width: 2px !important;
}
td.ll-name-cell .cell-in { max-width: none; }

/* Вычисленная ячейка — не ввод (объём/паллеты машины и паллеты, считает вид) */
.cell-computed {
  display: inline-block; text-align: right; font-family: var(--font-mono);
  font-variant-numeric: tabular-nums; color: var(--text-muted, var(--ink-soft));
}
.cell-computed.is-empty { color: var(--text-faint); font-style: italic; }

/* Удаление строки — крестик по образцу .opendoc-close (витрина /ui/, «Площадь
   нажатия», зона 40×40 псевдоэлементом, вид органа не меняется) */
.row-del {
  position: relative; border: 0; background: none; cursor: pointer; line-height: 1;
  color: var(--text-faint, var(--ink-faint)); font-size: 14px; padding: 0 3px; border-radius: var(--radius-1);
}
.row-del:hover { color: var(--danger); background: var(--hover-bg, var(--surface-inset)); }
.row-del::after {
  content: ""; position: absolute; top: 50%; left: 50%;
  width: 40px; height: 40px; transform: translate(-50%, -50%);
}
.row-del-cell { width: 34px; text-align: center; }

/* Группа «машина + прицепы» (§«Чего нет» п.2) */
tr.mash-group td { background: var(--surface-inset, var(--sub)); font-weight: 500; }
tr.mash-child td { background: transparent; }
tr.mash-child .mash-label {
  color: var(--text-muted, var(--ink-soft)); font-size: 12.5px; padding-left: 26px; position: relative;
  white-space: nowrap;
}
tr.mash-child .mash-label::before {
  content: ""; position: absolute; left: 12px; top: 0; bottom: 0; width: 1px;
  background: var(--rule-strong, var(--edge-field));
}
.mash-dims-note { color: var(--text-faint); font-style: italic; font-size: 12px; }
/* min-width:0 на детях — классический флекс-оверфлоу: без него `<input>`
   отказывается сжаться ниже intrinsic-минимума контента даже с max-width,
   таблица машин на 1280 с открытым помощником переполнялась на 3px (замер
   CDP 15.09, table.scrollWidth 608 против wrap.clientWidth 605 — см. также
   витрину /ui/, «Проверка любого нового элемента»). */
.mash-own-dims { display: flex; gap: 6px; justify-content: flex-end; }
.mash-own-dims > .cell-in { min-width: 0; }

/* Строка-фраза «Логистика по Европе» — .sfld инлайн внутри абзаца, тот же язык,
   что .f-uline, короче и по центру (короткое число, не полноразмерное поле) */
.ll-sentence { font-size: 14px; line-height: 2.1; color: var(--text-body, var(--text)); max-width: 640px; }
.ll-sentence .sfld {
  display: inline-block; width: 58px; border: 0; border-bottom: 1px solid var(--rule-strong, var(--edge-field));
  background: transparent; font: inherit; font-family: var(--font-mono); font-size: 13.5px;
  text-align: center; color: var(--text); padding: 2px 0; margin: 0 2px;
}
.ll-sentence .sfld:focus { outline: 2px solid var(--accent); outline-offset: 1px; border-bottom-color: var(--accent); }
