/* ds-components.css — доводка компонентов дизайн-системы (ds-*),
   общих для карточек объекта и контрагента (partials/modal_ui.html).
   Заводится 11.08.2026 как единое место для таких правок: компонент общий,
   а страницы разные — иначе правило пришлось бы дублировать в
   properties.css и contractors.css. */

/* Кнопка закрытия модалки: сама кнопка 32×32 из unified-bundle.css, но
   иконка внутри задана только по высоте (класс h-*), и во флекс-контейнере
   ширина схлопывалась — SVG измерен как 7×24 px, крестик почти не читался. */
.ds-modal-close-btn {
  width: 36px;
  height: 36px;
  flex: 0 0 auto;
}

.ds-modal-close-btn svg {
  width: 18px;
  height: 18px;
  flex: 0 0 auto;
}

/* ── сводные плитки (Поступления / Расходы / Баланс) ────────────────── */
/* Было `card bg-success text-white`: заливка целиком цветом, а `.card-title`
   внутри перекрывал белый текст своим тёмным — «Поступления» на зелёном
   давали контраст 3.23, «Баланс» 3.73 (ниже AA), и ни один из трёх цветов
   не был из токенов. Теперь нейтральная карточка, цвет — только у значения
   и иконки. Измерено 12.08.2026 в светлой теме. */
.summary-tile {
  background: var(--surface-primary);
  border: 1px solid var(--border-color);
}

.summary-tile .card-title {
  margin: 0 0 0.25rem;
  font-size: var(--font-size-xs);
  font-weight: 600;
  letter-spacing: 0.03em;
  text-transform: uppercase;
  color: var(--text-muted);
}

.summary-tile .summary-value {
  margin: 0;
  font-size: var(--font-size-2xl);
  font-weight: 700;
  color: var(--text-primary);
}

/* Внимание: в проекте два семейства «красного». `--danger-*` в светлой теме
   это #cb968e — блёклый розовый (им и была залита плитка «Расходы»), а
   настоящий красный лежит в `--error-*`. Для текста берём `--error-600`
   (#dc2626): контраст на белом 4.83. В тёмной теме он же даёт ~3.0, поэтому
   там светлее. Измерено 12.08.2026. */
.summary-tile--in .summary-value {
  color: var(--success-500);
}
.summary-tile--out .summary-value {
  color: var(--error-600, #dc2626);
}

[data-theme="dark"] .summary-tile--in .summary-value {
  color: #34d399;
}
[data-theme="dark"] .summary-tile--out .summary-value {
  color: #f87171;
}

.summary-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 40px;
  height: 40px;
  border-radius: var(--radius-md);
  font-size: var(--font-size-lg);
}

.summary-icon--in {
  background: color-mix(in srgb, var(--success-500) 14%, transparent);
  color: var(--success-500);
}

.summary-icon--out {
  background: color-mix(in srgb, var(--error-500) 14%, transparent);
  color: var(--error-600, #dc2626);
}

/* Две недостающие ступени того же компонента: фирменная и «информация».
   Плитки-итоги на «Шаблонах» стояли плоскими, тогда как эталонные плитки
   админки несут цветной значок — набор вариантов просто не покрывал эти
   два смысла. 23.08.2026 */
.summary-icon--brand {
  background: color-mix(in srgb, var(--accent) 14%, transparent);
  color: var(--accent-text);
}

.summary-icon--info {
  background: var(--info-bg);
  color: var(--status-info-text);
}

.summary-icon--net {
  background: var(--surface-secondary);
  color: var(--text-secondary);
}

/* ── заголовок страницы ─────────────────────────────────────────────── */
/* `.page-header` в unified-bundle.css красит текст в `var(--text-inverse)`
   — расчёт на цветную заливку шапки. Заливки нет: фон прозрачный или белый,
   и в светлой теме заголовки становились белым по белому. Проверено
   12.08.2026: «Финансовый журнал» контраст 1.00, «Договоры аренды» и
   «Банковские счета» — белые на #f7f9fc.
   Если где-то шапке вернут цветную заливку, там понадобится свой цвет. */
.page-header {
  color: var(--text-primary);
}

/* ── статусные бейджи финансовой цепочки ────────────────────────────── */
/* Модификаторы `--danger` брали розовое семейство `--danger-*`
   (см. заметку про два «красных»): «нет договора» давало контраст 2.65,
   «Требует вмешательства» — 3.48, оба ниже AA. Цвет статуса обязан
   читаться: по нему отличают строку, требующую вмешательства, от связанной.
   Переведено на `--error-*`, зелёный усилен до `--success-600`.
   Измерено 12.08.2026, светлая тема. */
.finance-diagnostics__badge--danger,
.finance-chain__state--danger {
  background: color-mix(in srgb, var(--error-500) 12%, transparent);
  /* На розоватой подложке #dc2626 даёт 4.11 — всё ещё ниже AA, поэтому
     на ступень темнее. Проверено замером, а не на глаз. */
  color: #b91c1c;
}

.finance-chain__state--ok {
  background: color-mix(in srgb, var(--success-500) 12%, transparent);
  color: var(--success-600, #065f46);
}

[data-theme="dark"] .finance-diagnostics__badge--danger,
[data-theme="dark"] .finance-chain__state--danger {
  color: #f87171;
}

[data-theme="dark"] .finance-chain__state--ok {
  color: #34d399;
}

/* Те же статусы на /accounting названы иначе (`chain-state--*`,
   `problem-strip__badge`) и красились тем же розовым: «Требует
   вмешательства» 3.35, «Проблем: N» 3.40. Приводим к одним цветам —
   статус должен читаться одинаково на обеих страницах. */
.chain-state--danger,
.problem-strip__badge--danger,
.problem-strip__badge {
  color: #b91c1c;
}

.chain-state--warning,
.problem-strip__badge--warning {
  color: #92400e;
}

.chain-state--ok,
.problem-strip__badge--ok {
  color: var(--success-600, #065f46);
}

/* Уточнено 12.08.2026: #f87171 меряли на подложке карточки (6.20), а чип
   стоит на своей — красной, 35 %. На ней тот же цвет даёт 4.25, ниже
   нормы для текста 12 px. Ступенью светлее: 6.19. */
[data-theme="dark"] .chain-state--danger,
[data-theme="dark"] .problem-strip__badge--danger,
[data-theme="dark"] .problem-strip__badge {
  color: var(--error-300);
}

[data-theme="dark"] .chain-state--warning,
[data-theme="dark"] .problem-strip__badge--warning {
  color: #fbbf24;
}

[data-theme="dark"] .chain-state--ok,
[data-theme="dark"] .problem-strip__badge--ok {
  color: #34d399;
}

/* ── мониторинг: предупреждения и подписи ───────────────────────────── */
/* `.status-value.warning` красился #d97706 — 3.19 на светлой карточке;
   этим цветом показывается «Предупреждение» и проценты памяти/диска, то
   есть ровно то, ради чего страницу открывают. Подписи полей процессов
   (11 px, серый #64748b) давали 4.31. Измерено 12.08.2026. */
.status-value.warning {
  color: #92400e;
}

.monitoring-process-meta-label {
  color: var(--text-secondary);
}

[data-theme="dark"] .status-value.warning {
  color: #fbbf24;
}

/* ── шапка страницы: один вид вместо пяти ───────────────────────────── */
/* Аудит 11.08.2026 нашёл пять разных шапок: полоса с крошками, карточка,
   голый заголовок, заголовок с кнопками под ним, заголовок вплотную к краю
   окна. Замер 12.08.2026 подтвердил числами: 30/24/37.6/37.6/24 px,
   вес 700/700/600/600/700, левый край 97/413/81/81/89.
   Канон взят с эталона `/admin`: 30 px, вес 700. Разметка — макрос
   `partials/page_head.html`; страница своих размеров заголовку не назначает.

   Отступа сверху у шапки НЕТ. Замер 26.08.2026: между полосой поиска и
   крошками выходило 48 px — 24 давала колонка (`.main-content`), ещё 24
   сам заголовок, и это на самой аккуратной странице; на /reports было 80,
   на /ai-assistant 72, на списках финансового блока 64. Верхний зазор
   задаёт колонка и только она — один раз, 24 px, как в кабинете
   (`.portal-page-head`). */
.page-head {
  margin: 0 0 1.25rem;
}

.page-head__crumbs {
  font-size: var(--font-size-xs);
  line-height: var(--line-height-snug);
  color: var(--text-secondary);
  margin-bottom: 0.5rem;
}

.page-head__crumbs a {
  color: inherit;
  text-decoration: none;
}

.page-head__crumbs a:hover {
  text-decoration: underline;
}

.page-head__row {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 1rem;
  flex-wrap: wrap;
}

/* Блок текста жмётся, чтобы кнопки оставались справа от заголовка, а не
   уезжали на вторую строку из-за длинного подзаголовка (/documents). */
.page-head__text {
  flex: 1 1 22rem;
  min-width: 0;
}

.page-head__title {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  margin: 0;
  font-size: 1.875rem;
  line-height: var(--line-height-tight);
  font-weight: 700;
  color: var(--text-primary);
}

.page-head__subtitle {
  max-width: 72ch;
  margin: 0.375rem 0 0;
  font-size: var(--font-size-sm);
  line-height: var(--line-height-snug);
  color: var(--text-secondary);
}

.page-head__actions {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  flex-wrap: wrap;
}

@media (max-width: 640px) {
  .page-head__title {
    font-size: 1.5rem;
  }

  .page-head {
    margin: 0 0 1rem;
  }

  .page-head__row {
    gap: var(--space-3);
  }

  .page-head__actions {
    width: 100%;
  }

  .page-head__actions > .btn,
  .page-head__actions > a.btn {
    flex: 1 1 12rem;
    justify-content: center;
  }

  /* Иконка «?» и переключатель «Архив» — компактные контролы, а не кнопки
     действия. Растяжка до 12rem оставляла у «?» пустую рамку, а у тумблера
     отрывала бегунок от дорожки (бегунок позиционируется от левого края).
     flex: 0 0 auto возвращает им собственный размер. B-034/M-01 */
  .page-head__actions > .btn.btn-icon,
  .page-head__actions > .btn.btn-toggle {
    flex: 0 0 auto;
  }
}

/* ── плитки статистики: цвет значению, не подписи ───────────────────── */
/* На /bank-accounts-normalized/ подписи «Всего счетов», «Активные счета»
   красились в primary/success/warning/info, а значения оставались
   нейтральными — цвет привлекал к слову, а не к числу (аудит 11.08.2026). */
.bank-accounts-stat-label {
  color: var(--text-secondary);
}

/* ── красный текст: семейство --error-*, не --danger-* ──────────────── */
/* `.text-danger` брался из `--danger-500` = #cb968e, блёклый розовый:
   контраст на белом 2.53 при норме 4.5. Настоящий красный лежит в
   `--error-*` (память ui-danger-vs-error-tokens). #dc2626 даёт 4.83.
   Правило чинит разом 39 применений в шаблонах и 5 в скриптах —
   в том числе суммы расходов на /payments-normalized/. Замер 12.08.2026. */
/* Парный токен вместо двух правил: `--status-error-text` заведён ровно
   под красный ТЕКСТ (#b91c1c в светлой — 6,47 против 4,83 у --error-600;
   #fca5a5 в тёмной). Тёмный дубль с зашитым #f87171 не нужен, и печать
   больше не выводит светло-красный на белом. 18.08.2026 */
.text-danger {
  color: var(--status-error-text);
}

/* `.text-warning` = #f59e0b, контраст на белом 2.15 — тем же цветом уже
   пришлось чинить «Предупреждение» на /admin/monitoring, там взят #92400e.
   Держим один янтарный на всю систему. Замер 12.08.2026. */
/* Тот же приём, что у `.text-danger`: парный токен вместо двух правил с
   литералами. Значения совпадают (#92400e / #fbbf24), но токен знает про
   печатную тему — литерал выводил жёлтый на белом листе, контраст 1,67.
   18.08.2026 */
.text-warning {
  color: var(--status-warning-text);
}

/* ── боковое меню админки ───────────────────────────────────────────── */
/* Пункты меню рисовались подчёркнутыми — семь ссылок подряд с линиями
   читаются как список ссылок в тексте, а не как навигация. Замер
   12.08.2026: text-decoration-line = underline на /admin/ai-management,
   /admin/monitoring и соседях. */
.admin-sidenav a {
  text-decoration: none;
}

.admin-sidenav a:hover,
.admin-sidenav a:focus-visible {
  text-decoration: none;
}

/* ── пустое состояние внутри таблицы ────────────────────────────────── */
/* Строка-заглушка «Нет данных» серым по центру не отвечала на главный
   вопрос: пусто вообще или не нашлось по фильтру, и что делать дальше.
   Вид тот же, что у ds_empty, но без рамки и подложки — их даёт таблица,
   иначе выходит коробка в коробке. Разметка: макрос ds_empty_row
   (partials/ds.html) и window.dsEmptyRow (unified.js). */
.ds-empty.ds-empty--in-table {
  border: 0;
  background: transparent;
  padding: 40px 16px;
}

.ds-empty-row > td {
  border-bottom: 0;
}

/* ── чекбоксы: один акцент на систему ───────────────────────────────── */
/* Замер 12.08.2026 на /documents: чекбоксы в таблице красятся
   `--accent-interactive` (#7c3aed), а тумблер «PDF → img» рядом с
   кнопками остался с браузерным серо-синим (#475569) и на пиксель
   крупнее. Правило задаёт акцент всем чекбоксам, у кого его не задали
   отдельно; страничные правила с большей специфичностью не задеты. */
input[type="checkbox"],
input[type="radio"] {
  accent-color: var(--accent-interactive);
}

/* ── селекты: стрелка ───────────────────────────────────────────────── */
/* Замер 12.08.2026 на /properties: `select.filter-control` шёл с
   `appearance: none` и без стрелки — селект не отличить от поля ввода.
   Причина не в отсутствии стиля: стрелка ЕСТЬ в `unified-bundle.css`
   (две градиентные заливки), но постраничные правила вида
   `.properties-page .filters .filter-control { background: … }` стирали
   её сокращением `background`. В семи файлах сокращение заменено на
   `background-color`. Здесь — только то, чего в общем наборе нет:
   селекты на бутстраповских классах. */
/* 13.08.2026: правило било только по бутстраповским классам, а замер по
   десяти страницам нашёл 12 селектов без них — на /documents/, /calendar,
   /admin и /documents/review. Они рисовались системной стрелкой: в одном
   окне соседствовали наша галочка и стрелка операционной системы.
   Расширено на все селекты, кроме сторонних (flatpickr) и списков
   с видимыми строками. */
select.form-control,
select.form-select,
select:not([multiple]):not([size]):not([class*="flatpickr"]) {
  appearance: none;
  -webkit-appearance: none;
  background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='14' height='14' viewBox='0 0 24 24' fill='none' stroke='%23475569' stroke-width='2.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 0.7rem center;
  background-size: 14px 14px;
  padding-right: var(--space-8);
}

[data-theme="dark"] select.form-control,
[data-theme="dark"] select.form-select,
[data-theme="dark"]
  select:not([multiple]):not([size]):not([class*="flatpickr"]) {
  background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='14' height='14' viewBox='0 0 24 24' fill='none' stroke='%23cbd5e1' stroke-width='2.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'/%3E%3C/svg%3E");
}

/* ── контейнер тостов не должен ловить клики ────────────────────────── */
/* Замер 12.08.2026 на /admin/invitations/manage: `.toast-container`
   стоял `fixed` с `top: 20px` от нашего CSS и `bottom: 0` от
   бутстраповского класса в разметке — получилась невидимая колонка
   24 × 829 px вдоль правого края, поверх иконки поиска, с
   `pointer-events: auto`. Пустой контейнер не должен перехватывать
   ничего; кликабельны только сами тосты. */
.toast-container {
  bottom: auto;
  pointer-events: none;
}

.toast-container > * {
  pointer-events: auto;
}

/* Подписи групп в боковом меню админки.

   Стояли с классами `hidden lg:block` — то есть спрятаны на маленьком
   экране и показаны на большом. Показаны не были никогда: `.lg\:block`
   в tailwind.css — обычное правило внутри медиазапроса, а
   `.hidden { display: none !important }` в unified-bundle.css — с флагом,
   и флаг бьёт медиазапрос. 19 ссылок — двенадцать на другие страницы
   админки и семь на разделы этой — читались одним сплошным списком.
   Сочетание `hidden` с любой утилитой `sm:/md:/lg:/xl:` в этом проекте
   не работает в принципе. 19.08.2026

   24.08.2026: групп стало шесть, и делят они меню по ТЕМЕ (пользователи,
   обращения, документы, ИИ, система, учётная запись), а не по устройству
   («страницы» против «разделов этой страницы»). Модификатор `--second`
   поэтому означает не «вторая группа», а «любая, кроме первой»: он даёт
   разделительную черту. Шесть черт вместо одной — это плюс пять кромок
   по высоте, поэтому ритм заголовков подтянут ниже, в правиле широкого
   экрана. */
.admin-sidenav__group-title {
  margin: 0 0 6px;
  padding: 0 8px;
  font-size: var(--font-size-xs, 12px);
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--text-muted);
}
.admin-sidenav__group-title--second {
  margin-top: 14px;
  padding-top: 10px;
  border-top: 1px solid var(--border-color);
}

/* Пункты бокового меню админки: подсветка активного на фирменном цвете,
   а не на бутстраповском синем (его в палитре нет). */
.admin-sidenav__item {
  color: var(--text-secondary);
}

/* 23.08.2026. В вертикальной раскладке меню прятало и текст, и разделы.
   Контейнеры групп несут `overflow-x-auto` + `scrollbar-hide` — это для
   узкого экрана, где пункты стоят в строку. На широком правила оставались
   в силе, а `whitespace-nowrap` не давал длинному названию перенестись:
   «Конфигурация и ключи» занимал 233 px в контейнере 212 px, и конец
   уезжал под СКРЫТУЮ полосу прокрутки. Не видно ни текста, ни того, что
   он обрезан.

   Второе: восемнадцать пунктов не помещались в высоту экрана, а
   `.admin-nav` ограничен `max-height` с `overflow-y: auto` — нижние
   разделы просто оказывались за краем. Каждые 2 px вертикали здесь стоят
   36 px общей высоты, поэтому ритм подтянут: на экране 812 px меню теперь
   помещается целиком (было 862 px содержимого против 708 px места).

   Селекторы в два класса — чтобы победить утилиты Tailwind (`py-2`,
   `whitespace-nowrap`), у которых специфичность (0,1,0). */
@media (min-width: 1024px) {
  .admin-sidenav .admin-sidenav__item {
    width: 100%;
    white-space: normal;
    align-items: flex-start;
    line-height: 1.35;
    padding-top: 0.3125rem;
    padding-bottom: 0.3125rem;
  }

  .admin-sidenav .admin-sidenav__item > i {
    flex: 0 0 1.5rem;
    margin-top: 0.1rem;
  }

  /* Скрытая горизонтальная прокрутка нужна только строке на узком экране. */
  .admin-sidenav .scrollbar-hide {
    overflow-x: visible;
  }

  .admin-sidenav .admin-nav {
    padding: 0.75rem;
  }

  .admin-sidenav .admin-sidenav__group-title {
    margin-bottom: 3px;
  }

  /* Пять разделительных черт вместо одной: каждый лишний пиксель здесь
     стоит пять пикселей общей высоты меню. 24.08.2026 */
  .admin-sidenav .admin-sidenav__group-title--second {
    margin-top: 7px;
    padding-top: 5px;
  }
}

.admin-sidenav__item:hover {
  background: var(--surface-secondary);
  color: var(--text-primary);
}

.admin-sidenav__item--active,
.admin-sidenav__item--active:hover {
  color: var(--accent-interactive);
  background: color-mix(in srgb, var(--accent-interactive) 12%, transparent);
}

/* В тёмной теме цвет пунктов задаёт правило из unified-bundle.css с
   `!important` и селектором на пять классов — перебить его обычным
   правилом нельзя, а подсветка активного там своя и слабее, поэтому
   не срабатывала никогда. Вместо войны специфичностей подменяем сами
   переменные на активном пункте: правило читает их с элемента. */
.admin-sidenav__item--active {
  --sidebar-nav-text: var(--sidebar-nav-active-text);
  --sidebar-nav-bg: var(--sidebar-nav-active-bg);
}

/* ── статусная точка «опасно» ───────────────────────────────────────── */
/* В общем наборе (`unified-bundle.css`) есть `--ok`, `--warn`, `--muted`,
   а `--danger` нет: его определяли постранично, через `.calendar-page`,
   `.accounting-page`, `.documents-queue-page`. На `/calendar` класса
   `.calendar-page` в разметке нет вовсе — поэтому у «Высокого» приоритета
   в легенде точка была прозрачной, а у «Среднего» и «Низкого» видимой
   (дефект из docs/ui-audit.md, замер 12.08.2026: background rgba(0,0,0,0)).
   Плюс постраничные правила брали `--danger-500` = #cb968e, блёклый
   розовый; красный лежит в `--error-*`. */
.ds-status-dot--danger {
  background: var(--error-500);
}

/* ── роли пользователей: системный админ виден ──────────────────────── */
/* Замер 12.08.2026 на /admin/users: у SYSADMIN бейдж такой же серый
   (rgba(148,163,184,.08)), как у «Пользователя» и «Арендатора» —
   правило есть только для `.role-admin`. Самая сильная роль в системе
   ничем не выделялась. Даём ей акцент проекта. */
/* Акцент как ТЕКСТ берётся из `--accent-text`, а не из `--accent-interactive`:
   первый — пара токенов (#7c3aed светлая / #c084fc тёмная), второй — заливка
   кнопки. Пока текст красился заливочным токеном, каждое такое правило
   требовало ручного дубля под тёмную тему с зашитым #a78bfa — пять штук в
   этом файле. Токен снимает дубли. 17.08.2026 */
.user-role.role-sysadmin {
  background: color-mix(in srgb, var(--accent-interactive) 14%, transparent);
  color: var(--accent-text);
  border-color: color-mix(in srgb, var(--accent-text) 35%, transparent);
  font-weight: 600;
}

/* Подпись фильтра «Только активные» ломалась на две строки в узкой
   колонке фильтров (замер: 89 × 39 px при высоте строки 20). */
.admin-users-page label.inline-flex,
label.inline-flex > input[type="checkbox"] + * {
  white-space: nowrap;
}

.admin-users-page label.inline-flex {
  white-space: nowrap;
}

/* ── заметка-плашка ─────────────────────────────────────────────────── */
/* Первая попытка собрать её из утилит Tailwind провалилась ровно по мине
   этой фазы: `dark:bg-amber-900/20` в сборке нет (проба 12.08.2026 дала
   прозрачный фон), а `dark:text-amber-200` есть — вышел светлый текст на
   светлой подложке, контраст около 1.2. Поэтому на токенах. */
.ui-note {
  border-radius: var(--radius-md, 8px);
  border: 1px solid var(--border-color);
  background: var(--surface-secondary);
  color: var(--text-primary);
  padding: 0.5rem 14px;
  font-size: var(--font-size-xs);
  line-height: var(--line-height-normal);
}

.ui-note--warning {
  border-color: color-mix(in srgb, #f59e0b 45%, transparent);
  background: color-mix(in srgb, #f59e0b 12%, var(--surface-primary));
  color: var(--text-primary);
}

/* ── ссылка в тексте ────────────────────────────────────────────────── */
/* На /support ссылки были `text-blue-600 underline` — синие подчёркнутые,
   таких в системе нет больше нигде (аудит 11.08.2026, замер 12.08:
   rgb(37, 99, 235)). Цвет действия в проекте — фиолетовый; подчёркивание
   появляется при наведении, чтобы ссылка не рябила в абзаце. */
.ui-link {
  color: var(--accent-text);
  text-decoration: none;
}

.ui-link:hover,
.ui-link:focus-visible {
  text-decoration: underline;
}

/* ── поле даты ──────────────────────────────────────────────────────── */
/* Видимое поле рисует flatpickr вместо `input[type=date]`, и родная
   иконка календаря с ним пропадает — остаётся пустая рамка. Возвращаем
   иконку фоном, как стрелку у селектов. */
.ds-date-input {
  background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 24 24' fill='none' stroke='%23475569' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Crect x='3' y='4' width='18' height='18' rx='2'/%3E%3Cline x1='16' y1='2' x2='16' y2='6'/%3E%3Cline x1='8' y1='2' x2='8' y2='6'/%3E%3Cline x1='3' y1='10' x2='21' y2='10'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 0.6rem center;
  background-size: 16px 16px;
  padding-right: var(--space-8);
}

[data-theme="dark"] .ds-date-input,
html[data-theme="dark"] .ds-date-input {
  background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 24 24' fill='none' stroke='%23cbd5e1' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Crect x='3' y='4' width='18' height='18' rx='2'/%3E%3Cline x1='16' y1='2' x2='16' y2='6'/%3E%3Cline x1='8' y1='2' x2='8' y2='6'/%3E%3Cline x1='3' y1='10' x2='21' y2='10'/%3E%3C/svg%3E");
}

/* ── плашка сообщения (flash) ───────────────────────────────────────── */
/* Замер 12.08.2026 на /api/commercial_properties/ в тёмной теме: текст
   ошибки `text-red-700` = rgb(185, 28, 28) на подложке карточки
   rgb(21, 28, 37) — контраст 2.65; информационная `text-blue-700` — 2.56.
   Плашку рисует base.html, то есть дефект был на каждой странице системы,
   где что-то сообщается флэшем: ровно тот случай, что и с cookies —
   постоянный элемент, который примелькался. Цвета — из токенов, с
   вариантом для тёмной темы, а не из утилит Tailwind. */
.ui-note--flash {
  display: flex;
  align-items: flex-start;
  gap: 10px;
  padding: 0.75rem 1rem;
  font-size: var(--font-size-sm);
  margin-bottom: 1rem;
}

.ui-note--flash > i {
  flex: 0 0 auto;
  margin-top: 2px;
}

.ui-note--danger {
  border-color: color-mix(in srgb, var(--error-600) 45%, transparent);
  background: color-mix(in srgb, var(--error-600) 10%, var(--surface-primary));
  color: var(--text-primary);
}

.ui-note--danger > i {
  color: var(--error-700);
}

[data-theme="dark"] .ui-note--danger > i,
html[data-theme="dark"] .ui-note--danger > i {
  color: var(--error-400);
}

.ui-note--info {
  border-color: color-mix(in srgb, var(--accent-interactive) 40%, transparent);
  background: color-mix(
    in srgb,
    var(--accent-interactive) 8%,
    var(--surface-primary)
  );
  color: var(--text-primary);
}

.ui-note--info > i {
  color: var(--accent-text);
}

/* ── зелёный текст в тёмной теме ────────────────────────────────────── */
/* `.text-success` = #15803d: на белом 7.13, на тёмной карточке 3.42.
   Тем же путём, что `.text-danger` и `.text-warning` до него, —
   светлый вариант из шкалы для тёмной темы (#4ade80, 9.84).
   Замер 12.08.2026 на списке коммерческих объектов: этим цветом набраны
   суммы аренды и имена арендаторов в таблице. */
[data-theme="dark"] .text-success,
html[data-theme="dark"] .text-success {
  color: var(--success-400);
}

/* ── подпись и значение KPI-плитки ──────────────────────────────────── */
/* На /api/commercial_properties/ четыре одинаковых нуля были подписаны
   четырьмя разными цветами: `text-primary` и `text-info` давали серый
   (шкала --primary-* серая, память ui-primary-scale-is-grey),
   `text-success` — тёмно-зелёный с контрастом 3.42, `text-warning` —
   янтарный. Цвет ничего не кодировал: он повторял то, что уже написано
   словом. Подпись — вторичный текст, значение — основной. */

/* ── поля фильтров: одна высота с кнопками ──────────────────────────── */
/* Замер 12.08.2026 на /api/commercial_properties/: в одном ряду селект
   37 px, поле ввода 38, кнопки 40 — три высоты. Разница у селекта с
   полем берётся из `appearance: none`, которое меняет внутреннюю
   раскладку. Задаём высоту явно, а не подгонкой паддингов. */
.filter-control {
  min-height: 40px;
}

/* Стрелка у `select.filter-control` рисовалась двумя градиентами —
   сплошной треугольник, тогда как у `select.form-control` и
   `select.form-select` стоит нарисованная линиями «птичка». Две разные
   стрелки в одной системе; приводим к одной. */
select.filter-control {
  background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='14' height='14' viewBox='0 0 24 24' fill='none' stroke='%23475569' stroke-width='2.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 0.7rem center;
  background-size: 14px 14px;
  padding-right: var(--space-8);
}

[data-theme="dark"] select.filter-control,
html[data-theme="dark"] select.filter-control {
  background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='14' height='14' viewBox='0 0 24 24' fill='none' stroke='%23cbd5e1' stroke-width='2.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'/%3E%3C/svg%3E");
}

/* ── кнопка закрытия модалки ────────────────────────────────────────── */
/* Замер 12.08.2026 на /admin: у семейства `modal-*-beautiful` жив только
   размер 44 × 44 из `unified-bundle.css`. Кнопки в подвале модалки
   отрисовывались браузером по умолчанию — серые #6b6b6b, шрифт 13.33 px,
   без скруглений; в шаблонах их было семь, в двух файлах. Они переведены
   на `btn btn-secondary` / `btn btn-primary`: заводить вторую систему
   кнопок ради модалки незачем.
   Крестику своя система нужна — это иконка без подписи, а не кнопка
   с текстом. */
.modal-close-beautiful {
  border: 0;
  background: transparent;
  color: var(--text-secondary);
  border-radius: var(--radius-base, 0.5rem);
  font-size: var(--font-size-base);
  cursor: pointer;
  transition:
    background-color 0.15s ease,
    color 0.15s ease;
}

.modal-close-beautiful:hover,
.modal-close-beautiful:focus-visible {
  background: var(--surface-hover);
  color: var(--text-primary);
}

/* Шапка модалки — строка, а не столбик. Замер 12.08.2026 на /admin:
   `display: block`, крестик уезжал под заголовок и вставал у левого края,
   шапка занимала 106 px вместо ~60. Подвал (`modal-footer-beautiful`) уже
   был flex с прижатием вправо — шапке этого не досталось. */
.modal-header-beautiful {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 1rem;
}

.modal-header-beautiful .modal-title-beautiful {
  flex: 1 1 auto;
  margin: 0;
}

/* ── строчные кнопки-действия в таблицах ────────────────────────────── */
/* Замер 12.08.2026 на /accounting: 178 из 263 кнопок страницы рисовались
   браузером по умолчанию — серая плашка #6b6b6b. Классов `.btn-edit`,
   `.btn-history`, `.btn-debug`, `.btn-void`, `.manager-quick-action`
   в CSS нет вовсе; единственное, что до них доходило, — цвет из
   `text-brand-600` (а это серая шкала) и `text-red-600`. Получилось
   серое по серому: «Изменить» 1.12, «Аннулировать» 1.10 — надписи
   не читались.
   Разметку клеит accounting.js, поэтому чинится классами, а не шаблоном. */
.btn-edit,
.btn-history,
.btn-debug,
.btn-void,
.manager-quick-action {
  appearance: none;
  -webkit-appearance: none;
  background: transparent;
  border: 0;
  border-radius: var(--radius-base, 0.5rem);
  padding: var(--space-1) var(--space-2);
  font: inherit;
  font-size: var(--font-size-xs);
  line-height: var(--line-height-snug);
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  gap: 6px;
}

.btn-edit,
.manager-quick-action {
  color: var(--accent-text);
}

.btn-history,
.btn-debug {
  color: var(--text-secondary);
}

.btn-void {
  color: var(--error-600);
}

[data-theme="dark"] .btn-void {
  color: var(--error-400);
}

.btn-edit:hover,
.btn-history:hover,
.btn-debug:hover,
.btn-void:hover,
.manager-quick-action:hover,
.btn-edit:focus-visible,
.btn-history:focus-visible,
.btn-debug:focus-visible,
.btn-void:focus-visible,
.manager-quick-action:focus-visible {
  background: var(--surface-hover);
  text-decoration: underline;
}

/* ── «фирменный» цвет текста ────────────────────────────────────────── */
/* `.text-brand-600` в сборке Tailwind указывает на `--primary-600`, а это
   серая шкала проекта (память ui-primary-scale-is-grey): 21 применение в
   шаблонах и скриптах, на /accounting — 48 элементов на странице, включая
   ссылки на документы в таблице. Замер 12.08.2026: контраст 3.07 при
   14 px, и это не «фирменный» цвет, а серый. Ведём на цвет действия. */
.text-brand-600 {
  color: var(--accent-text);
}

/* Цветные шкалы Tailwind как ЦВЕТ ТЕКСТА не проходят норму на нейтральных
   поверхностях. Замер 17.08.2026 (светлая тема, `--surface-primary` /
   `--surface-tertiary`):

     text-green-600   3,30 / 2,67
     text-yellow-600  2,94 / 2,38
     text-red-500     3,76 / 3,05
     text-red-600     4,83 / 3,92
     text-blue-600    5,17 / 4,19

   В тёмной теме проваливается `text-red-600` (3,55 / 3,01).

   У проекта уже заведены парные токены ровно под мелкий цветной текст —
   `--status-*-text`. Их замер на тех же поверхностях: 5,25…7,13 в светлой
   и 7,67…12,21 в тёмной. Ведём шкалы на них — тем же приёмом, каким выше
   уже приведён `.text-brand-600`.

   Оттенки 200/300/400 не трогаем: это варианты под тёмную тему, там они
   проходят, и в сборке они объявлены с флагом — спорить с ними бессмысленно. */
.text-green-600:where(:not(input[type="checkbox"]):not(input[type="radio"])),
.text-green-700:where(:not(input[type="checkbox"]):not(input[type="radio"])) {
  color: var(--status-success-text);
}

.text-red-500:where(:not(input[type="checkbox"]):not(input[type="radio"])),
.text-red-600:where(:not(input[type="checkbox"]):not(input[type="radio"])),
.text-red-700:where(:not(input[type="checkbox"]):not(input[type="radio"])) {
  color: var(--status-error-text);
}

.text-yellow-600:where(:not(input[type="checkbox"]):not(input[type="radio"])),
.text-amber-600:where(:not(input[type="checkbox"]):not(input[type="radio"])),
.text-amber-700:where(:not(input[type="checkbox"]):not(input[type="radio"])) {
  color: var(--status-warning-text);
}

/* Флажки и переключатели исключены: у них `color` задаёт не текст, а
   заливку самого элемента, и токен текста там читается как ошибка —
   в тёмной теме флажок становился бледно-голубым с белой галочкой.
   `:where()` не добавляет специфичности, правило остаётся (0,1,0). */
.text-blue-600:where(:not(input[type="checkbox"]):not(input[type="radio"])),
.text-blue-700:where(:not(input[type="checkbox"]):not(input[type="radio"])) {
  color: var(--status-info-text);
}

.text-brand-700,
.text-brand-800 {
  color: color-mix(in srgb, var(--accent-interactive) 82%, #000000);
}

[data-theme="dark"] .text-brand-700,
[data-theme="dark"] .text-brand-800 {
  color: #c4b5fd;
}

/* Бейдж роли «Администратор» правило красит в #1d4ed8 без варианта для
   тёмной темы: замер 12.08.2026 на своей подложке (синяя, 12 %) — 2.36.
   Соседние бейджи на той же странице дают 10.95 (Пользователь, Арендатор)
   и 6.09 (SYSADMIN), то есть нечитаемым был ровно один. Светлая ступень
   того же синего: 8.23. */
[data-theme="dark"] .user-role.role-admin,
html[data-theme="dark"] .user-role.role-admin {
  color: #93c5fd;
  border-color: rgba(147, 197, 253, 0.32);
}

/* ── атрибут hidden обязан прятать ──────────────────────────────────── */
/* Браузерное правило `[hidden] { display: none }` — самое слабое: любой
   класс, задающий `display`, его перебивает. Замер 12.08.2026 на
   /units-normalized/list: у `#loadingState` стоял `hidden`, а `.loading`
   из общего набора задавала `display: flex` — спиннер «Загрузка
   информации об объекте…» висел вечно, хотя код его честно прятал.
   Там же нашлась вторая: `#back-to-top` с атрибутом `hidden` показывался
   на каждой странице в самом верху, хотя должен появляться при прокрутке.
   `!important` здесь уместен: `hidden` — это не оформление, а состояние,
   и спорить с ним оформлению нечем. */
[hidden] {
  display: none !important;
}

/* ── ритм страницы: один шаг между смысловыми блоками ───────────────── */
/* Замер 13.08.2026 по колонкам содержимого: зазор между секциями принимал
   значения 16 / 20 / 24 / 32 / 36 / 40 — то есть его не задавали ни разу.
   Хуже: на /admin/users, /admin/invoice-recognition и /leases-normalized
   он был МЕНЬШЕ паддинга внутри карточки (16 против 20 и 24), поэтому
   карточки читались не как отдельные блоки, а как одна колонка всего
   подряд. Один шаг на систему — 24 px.
   Селекторы — колонки содержимого, найденные замером; `:not(:last-child)`
   даёт 0,2,0 и выигрывает у `.mb-4` без опоры на порядок файлов.
   26.08.2026: `.container > .container.py-4` из списка убран — списки
   финансового блока перестали заворачиваться во вложенный `.container`
   и попали под общий `.page-container`. */
.page-container > :not(:last-child),
.accounting-page > :not(:last-child),
.admin-users-content > :not(:last-child),
.admin-theme-scope > .container > :not(:last-child),
.admin-layout-main > :not(:last-child) {
  margin-bottom: var(--space-6);
}

/* Обёртки-невидимки: остаются в потоке с нулевой высотой, но общий шаг
   выше давал им по 24 px пустоты — на /documents/ зазор под шапкой выходил
   48 вместо 24. Форма загрузки состоит из скрытых input, стек алертов пуст,
   пока алертов нет. Это общий признак: контейнер, который «ждёт» контента,
   не должен занимать шаг ритма. */
.documents-queue-page > .documents-upload-form {
  margin-bottom: 0;
}

.documents-queue-page > .docs-alerts-stack:not(:has(#alerts-container > *)) {
  margin-bottom: 0;
}

/* Тот же признак у ещё двух: `.sr-only` — текст только для скринридера,
   `.home-mobile-secondary` — блок, который живёт только на мобильных.
   На десктопе оба нулевой высоты и шаг ритма им не нужен.
   Специфичность повторяет правило шага (0,2,0 и выше) — иначе исключение
   ему проигрывает и не срабатывает. */
.page-container > .sr-only,
.accounting-page > .sr-only,
.admin-users-content > .sr-only,
.admin-theme-scope > .container > .sr-only,
.admin-layout-main > .sr-only,
.page-container > .home-mobile-secondary:not(:has(> * > *)) {
  margin-bottom: 0;
}

/* ── бейдж «инфо»: заливка была серой, текст на ней нечитаем ──────────── */
/* Замер 13.08.2026 на /admin/invoice-recognition: `.badge.bg-info` красится
   `--info-500`, а он в тёмной теме равен #94a3b8 — slate-400, то есть
   светло-серая заливка. Белый текст на ней даёт 2.45 при норме 4.5, и это
   50 узлов на странице. Причина та же, что у сегодняшнего числа в
   календаре: токен с «цветным» именем означает серый.
   Правим не генерируемый tailwind.css, а сам бейдж: тёмная заливка под
   светлый текст, как у остальных бейджей системы. Контраст 7.2. */
/* Светлая тема ломалась ровно так же, только наоборот: тёмный текст
   #0f172a на slate-500 давал 3.75, а `.badge.bg-success` — 3.26. Бейдж
   в системе должен быть светлым текстом на насыщенной заливке в ОБЕИХ
   темах, а не менять полярность. Контраст: инфо 7.5, успех 5.6 и 4.7. */
.badge.bg-info {
  background-color: #475569;
  color: #ffffff;
}

.badge.bg-success {
  color: #ffffff;
}

/* ── один макет админки ─────────────────────────────────────────────── */
/* Замер 13.08.2026: боковое меню админки было сведено в один партиал ещё
   12.08, а обёртка вокруг него — нет. Из одиннадцати шаблонов шесть клали
   заголовок страницы ВНУТРЬ правой колонки, и он уезжал вправо на 394-418
   px, а два (`/admin`, `/admin/workspaces`) — над меню, как на обычных
   страницах системы. Отсюда пять разных положений `h1` по горизонтали при
   том, что макрос шапки один.
   Канон: заголовок над меню, во всю ширину — так админка совпадает с
   остальной системой, а не только сама с собой. Здесь — только каркас
   «меню + содержимое» под ним. */
.admin-layout-shell {
  display: flex;
  flex-direction: column;
  gap: var(--space-6);
}

@media (min-width: 1024px) {
  .admin-layout-shell {
    flex-direction: row;
  }
}

.admin-layout-main {
  flex: 1 1 auto;
  min-width: 0;
}

/* ── карточка-ссылка не подчёркивается ──────────────────────────────── */
/* Замер 13.08.2026 на /admin: четыре плитки показателя обёрнуты в <a> без
   стилей ссылки — браузер красил их #0000ee и, главное, ПОДЧЁРКИВАЛ всё
   содержимое: число, подпись и пояснение. Подчёркивание распространяется
   от предка, и `text-decoration: none` у детей его не снимает — снимать
   надо на самой ссылке. Ещё один случай «элементу не задали ничего». */
a:has(> .stats-card),
a:has(> .summary-card),
a:has(> .status-card),
.admin-theme-scope a.rounded-xl,
.admin-theme-scope a.block,
a.dashboard-card,
a.home-action-tile {
  color: inherit;
  text-decoration: none;
}

/* ── вкладки внутри страницы ─────────────────────────────────────────── */
/* Замер 13.08.2026 на /admin: семь <section> переключались скриптом по
   `.admin-nav a[href^="#"]`. 12.08 боковое меню админки заменили общим
   макросом со ссылками на настоящие страницы — хеш-ссылок не осталось,
   navLinks стал пустым, и шесть секций из семи (в том числе «Конфигурация
   и ключи», 47 КБ разметки с SMTP, Telegram, OAuth и секретами) стали
   недостижимы: они висели с `hidden` и снять его было нечем.
   Вкладки — второй уровень навигации: слева меню между страницами,
   здесь переключение внутри страницы. Подчёркивание, а не «папки»:
   ровная базовая линия, ничего не прыгает при переключении. */
/* Полоса вкладок прокручивалась вбок, а полосу прокрутки при этом
   гасили (`scrollbar-width: none`). На `/admin` семь вкладок не
   помещались, и последняя обрывалась на «Интегр…» — без единого
   признака, что справа есть ещё что-то. Спрятанная прокрутка хуже
   переноса: перенос видно. Замер 17.08.2026. */
.ds-tabs {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-1);
  border-bottom: 1px solid var(--border-color);
}

.ds-tab {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  padding: var(--space-3) var(--space-4);
  margin-bottom: -1px;
  border: 0;
  border-bottom: 2px solid transparent;
  background: transparent;
  color: var(--text-secondary);
  font-family: inherit;
  font-size: var(--font-size-sm);
  font-weight: 500;
  line-height: var(--line-height-tight);
  /* Вкладка-ссылка: браузер подчёркивает <a> по умолчанию, и подчёркивание
     спорит с линией активной вкладки — две черты под одной строкой. */
  text-decoration: none;
  white-space: nowrap;
  cursor: pointer;
  transition:
    color 0.15s ease,
    border-color 0.15s ease;
}

.ds-tab:hover {
  color: var(--text-primary);
  text-decoration: none;
}

.ds-tab:focus-visible {
  outline: 2px solid var(--accent-text, #7c3aed);
  outline-offset: -2px;
  border-radius: var(--radius-sm, 4px) var(--radius-sm, 4px) 0 0;
}

/* Акцент здесь --accent-text, а не --color-primary: последний в этом
   проекте равен #475569, то есть сланцево-серому — токен с цветным именем
   означает серый. Фирменный цвет живёт в --accent, но как ТЕКСТ он берётся
   из --accent-text: на тёмной теме --accent (#9333ea) даёт 3.38 при норме
   4.5, --accent-text там светлее (#c084fc, 7.62). */
.ds-tab--active,
.ds-tab[aria-selected="true"] {
  color: var(--accent-text, #7c3aed);
  border-bottom-color: var(--accent-text, #7c3aed);
  font-weight: 600;
}

.ds-tab i {
  font-size: 0.875em;
  opacity: 0.85;
}

/* ── иконка у заголовка раздела ──────────────────────────────────────── */
/* Замер 13.08.2026 на /admin: семь разделов, пять разных цветов иконки —
   синий, фиолетовый, зелёный, индиго, оранжевый. Цвет ничего не кодировал,
   разделы не связаны ни с одной шкалой; при переключении вкладок менялся
   акцент страницы. Иконка рядом с заголовком — украшение: один приглушённый
   цвет, чтобы героем строки оставался текст. */
.admin-section-icon {
  color: var(--text-muted, #475569);
}

/* ── карточка админки ────────────────────────────────────────────────── */
/* Замер 13.08.2026: `.admin-card`, `.admin-card-header`, `.admin-card-body`
   и `.admin-grid` встречаются в разметке двух страниц (`/admin/ai-management`
   и `/admin/integrations/docai`), но правил у них нет НИ В ОДНОМ файле CSS:
   фон прозрачный, рамки нет, радиуса нет, паддинга нет. Карточки не рисуются
   вовсе — содержимое лежит на фоне страницы. Ещё один случай «элементу не
   задали ничего», и первый, где не задали целому компоненту.
   Значения взяты с карточки на /admin, чтобы семейство было одно. */
.admin-card {
  background: var(--surface-primary);
  border: 1px solid var(--border-color);
  border-radius: var(--radius-xl);
  box-shadow: var(--shadow-sm);
  overflow: hidden;
}

.admin-card-header {
  padding: var(--space-4) var(--card-padding-md);
  border-bottom: 1px solid var(--border-color);
  background: var(--surface-secondary);
}

.admin-card-title {
  margin: 0;
  font-size: var(--font-size-base);
  font-weight: 600;
  color: var(--text-primary);
}

.admin-card-body {
  padding: var(--card-padding-md);
}

.admin-grid {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--space-6);
}

@media (min-width: 768px) {
  .admin-grid {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}

/* ── звёздочка обязательного поля ────────────────────────────────────── */
/* ── звёздочка обязательного поля: один вид на четыре имени ─────────── */
/* Замер 13.08.2026: цвет к этому моменту уже был сведён, но вид — нет.
   `.ds-required` рисовалась 14/500 с отступом 4 px, `.required-mark` —
   12/600 с отступом 1.5, `.docs-required-mark` — 14/500 без отступа,
   а `.required::after` подставляла " *" с пробелом внутри содержимого.
   Четыре размера и четыре отступа у одного и того же знака.
   Размер наследуется от подписи (1em) — это верно, подпись бывает 12 и 14;
   вес и отступ заданы одинаково. Имена классов оставлены: их четыре,
   и переименование разметки — отдельная задача. */
.required-mark,
.ds-required,
.docs-required-mark,
.form-label.required::after,
.contracts-page .required::after,
.property-party-create-page .form-label.required::after {
  color: var(--status-error-text, #b91c1c);
  font-size: 1em;
  font-weight: 600;
  margin-left: 0.25em;
}

/* Замер 13.08.2026: шесть звёздочек были покрашены `text-red-500`
   (#ef4444) — 3.76 на белом при норме 4.5. Это единственный признак того,
   что поле обязательно; ему нельзя быть на грани читаемости.
   Первая попытка была `--error-700` (#b91c1c) — и тут же дала 2.65 в
   тёмной теме: токен один на обе темы. Тот же капкан, про который написан
   раздел 16 отчёта, и я угодил в него через десять минут после того, как
   его описал. Правильный токен — парный: --danger-text.
   Вторая поправка, там же: звёздочка встречается и внутри модалок, а одна
   из них стоит на янтарной панели (#78350f). На ней #f87171 даёт 3.28.
   Взят --status-error-text: он светлее в тёмной теме (#fca5a5) и держит
   и фон страницы, и цветную панель. Модалки обычным обходом не
   измеряются — их пришлось раскрывать принудительно. */
.required-mark {
  color: var(--status-error-text, #b91c1c);
}

/* Замер 13.08.2026: «звёздочка обязательного поля» была объявлена ШЕСТЬ раз
   в пяти файлах — `.ds-required` (дважды, --danger-500 и --danger-600),
   `.form-label.required::after` (трижды, в трёх разных файлах),
   `.docs-required-mark`, плюс шесть штук голым `text-red-500` в разметке.
   Четыре разных токена на один и тот же знак, и три из четырёх не проходили
   по контрасту в тёмной теме. Цвет у всех сведён к --status-error-text.
   Сами имена классов оставлены: переименовывать разметку в этот заход
   рискованнее, чем оставить синонимы. Кандидат на следующий заход. */

/* ── заголовок карточки настроек ─────────────────────────────────────── */
/* Замер 13.08.2026 на /settings/: девять одинаковых карточек, у двух
   заголовок был `h2` (24 px), у семи `h3` (20 px) — разница ни на чём не
   основана. Сведено к `h3`, а размер — к тому же 16/600, что у заголовка
   карточки на /admin и у `.admin-card-title`. Карточка в продукте одна,
   значит и её заголовок один. */
.settings-card > h3,
.monitoring-card > h3 {
  margin: 0 0 var(--space-4);
  font-size: var(--font-size-base);
  font-weight: 600;
  color: var(--text-primary);
}

.settings-card > h3 > i,
.monitoring-card > h3 > i {
  color: var(--text-muted, #475569);
  font-size: 0.875em;
}

/* ── поле с прилегающей кнопкой ──────────────────────────────────────── */
/* Замер 13.08.2026: класс `.input-group` применён 28 раз в двух шаблонах,
   а правил не имеет НИ ОДНОГО — есть только два уточняющих селектора,
   задающих ему ширину. Класс бутстраповский, но бутстрап здесь не
   подключён, и вместо строки «поле + кнопка» браузер рисует два блока
   один под другим: на /settings/ кнопка «Показать» стоит ПОД полем во
   всех восьми секретах. Ещё один «элементу не задали ничего», и снова
   не на теге, а на компоненте.
   Кнопка примыкает к полю: общие углы гасим на стыке. */
.input-group {
  display: flex;
  align-items: stretch;
  width: 100%;
  min-width: 0;
}

.input-group > * {
  min-width: 0;
}

.input-group > :not(:last-child) {
  border-top-right-radius: 0;
  border-bottom-right-radius: 0;
}

.input-group > :not(:first-child) {
  margin-left: -1px;
  border-top-left-radius: 0;
  border-bottom-left-radius: 0;
}

.input-group > input,
.input-group > select,
.input-group > .filter-control {
  flex: 1 1 auto;
}

.input-group > .btn {
  flex: 0 0 auto;
  white-space: nowrap;
}

.input-group > .btn:hover,
.input-group > .btn:focus-visible {
  position: relative;
  z-index: 1;
}

/* ── заголовок страницы: один размер на все виды разметки ────────────── */
/* Замер 13.08.2026: страницы, перешедшие на макрос `page_head`, дают
   `.page-head__title` = 30/700/36 — одинаково на /admin, /settings/,
   /documents/, /accounting. Страницы, оставшиеся на `.page-title`,
   давали 32/700, 28/700, 28/600 и 30/700 — четыре размера на один
   и тот же по смыслу заголовок.

   Определений `.page-title` восемь, в четырёх файлах: `contractors_create`
   (1.75rem), `contracts_normalized` (1.75rem, с префиксом страницы),
   `portal_base` (28px, плюс отдельно для `h1`/`h2` внутри), `accounting`
   (1.6rem в медиазапросе), `index`, `documents_queue` — и все они
   постранично подключаемые.

   Здесь не переписываем восемь мест, а задаём канон последним файлом.
   Имена классов удвоены сознательно: `.contracts-page .page-title` имеет
   вес 0,2,0 и обычный `.page-title` (0,1,0) его не перебивает. Удвоение
   даёт ровно тот же вес, а порядок подключения решает в нашу пользу.
   Медиазапрос `accounting` (1.6rem) при этом остаётся сильнее только
   внутри своего условия — это и правильно. */
.page-title.page-title,
.page-title.page-title > h1,
.page-title.page-title > h2,
h1.page-title.page-title,
h2.page-title.page-title,
.page-title-section.page-title-section > h1,
.page-title-section.page-title-section > h2 {
  margin: 0;
  font-size: var(--font-size-3xl);
  font-weight: 700;
  line-height: var(--line-height-tight);
  color: var(--text-primary);
}

/* ── страница ошибки ─────────────────────────────────────────────────── */
/* Замер 13.08.2026 на 404: восемь классов этого компонента — `.error-state`,
   `.error-state__icon/__title/__text/__path/__actions`, `.error-state-card`,
   `.error-state-shell` — не имеют НИ ОДНОГО правила ни в одном файле CSS.
   Карточка брала фон от `.card`, но паддинг нулевой, иконка размером с
   букву, заголовок неотличим от текста: всё лежало в столбик по левому
   краю. Четвёртый за день компонент без правил (после .admin-card,
   .badge-success и .input-group) — и, в отличие от них, на странице,
   которую человек видит именно тогда, когда у него уже что-то не так. */
.error-state-shell {
  display: flex;
  flex-direction: column;
}

.error-state-card {
  max-width: 720px;
  padding: var(--card-padding-md);
}

.error-state {
  display: flex;
  align-items: flex-start;
  gap: var(--space-4);
}

.error-state__icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: 0 0 auto;
  width: 44px;
  height: 44px;
  border-radius: 50%;
  background: color-mix(
    in srgb,
    var(--status-warning-text) 16%,
    var(--surface-primary)
  );
  color: var(--status-warning-text);
  font-size: var(--font-size-lg);
}

.error-state-card--danger .error-state__icon {
  background: color-mix(
    in srgb,
    var(--status-error-text) 16%,
    var(--surface-primary)
  );
  color: var(--status-error-text);
}

.error-state__title {
  margin-bottom: var(--space-2);
  color: var(--text-primary);
  font-size: var(--font-size-lg);
  font-weight: 600;
  line-height: var(--line-height-snug);
}

.error-state__text {
  color: var(--text-secondary);
  font-size: var(--font-size-sm);
  line-height: var(--line-height-normal);
}

/* Адрес, по которому не получилось: моноширинный, чтобы его можно было
   сверить символ в символ. */
.error-state__path {
  margin-top: var(--space-3);
  padding: var(--space-2) var(--space-3);
  border: 1px solid var(--border-color);
  border-radius: var(--radius-sm, 6px);
  background: var(--surface-secondary);
  color: var(--text-secondary);
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: var(--font-size-xs);
  word-break: break-all;
}

.error-state__actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
  margin-top: var(--space-5);
}

/* ── состояние строки в очереди документов ───────────────────────────── */
/* Замер 13.08.2026: состояние документа было сказано ТРИ раза подряд в
   одной строке — цветной полосой слева, точкой того же цвета и залитой
   плашкой со словами. Три канала, один смысл.
   Решение Tim: оставить полосу и слова.
   Полоса отвечает за скольжение глазом по списку — по ней видно, где
   что, не читая. Слова отвечают за смысл. Заливка у слов при этом
   становится третьим шумом: рядом с полосой того же цвета она кричит
   дважды. Поэтому плашка в очереди — просто текст: цвет и вес, без фона
   и рамки. В других таблицах плашка остаётся плашкой: там полосы нет. */
#queue-body .status-badge,
.status-compact .status-badge {
  padding: 0;
  border: 0;
  border-radius: 0;
  background: transparent;
  font-weight: 600;
}

#queue-body .status-needs_review,
#queue-body .status-quarantine_ocr,
#queue-body .status-quarantine_google_ocr,
.status-compact .status-needs_review,
.status-compact .status-quarantine_ocr,
.status-compact .status-quarantine_google_ocr {
  color: var(--status-warning-text);
}

#queue-body .status-error,
.status-compact .status-error {
  color: var(--status-error-text);
}

#queue-body .status-completed,
#queue-body .status-ocr_ready,
.status-compact .status-completed,
.status-compact .status-ocr_ready {
  color: var(--status-success-text);
}

#queue-body .status-processing,
#queue-body .status-active,
.status-compact .status-processing,
.status-compact .status-active {
  color: var(--status-info-text);
}

#queue-body .status-queued,
#queue-body .status-waiting,
#queue-body .status-archived,
.status-compact .status-queued,
.status-compact .status-waiting,
.status-compact .status-archived {
  color: var(--status-neutral-text);
}

/* ── связь в строке очереди: тоже словами, а не плашкой ──────────────── */
/* Замер 13.08.2026, после того как состояние в §26 стало тихим текстом:
   самой громкой вещью в строке оказалась СВЯЗЬ — залитая плашка с рамкой
   и скруглением 999px. Получилось, что связь кричит громче состояния,
   хотя первым читают состояние.
   Решение Tim: приглушить так же. Плашка становится словами: цвет и вес,
   без фона, рамки и скругления. Цвет — из тех же парных текстовых токенов,
   что и у состояния, чтобы «связано» в одной колонке и «готово» в другой
   были одним зелёным.
   Ограничено `.docs-relations-cell`: `.finance-chain__state` живёт только
   в очереди, но если завтра переедет — там плашка останется плашкой.
   Это ССЫЛКА на /accounting, поэтому подчёркивание на наведении
   обязательно: без заливки её иначе не отличить от простого текста. */
.docs-relations-cell .finance-chain__state {
  padding: 0;
  border: 0;
  border-radius: 0;
  background: transparent;
  box-shadow: none;
  font-size: var(--font-size-sm);
}

.docs-relations-cell .finance-chain__state:hover,
.docs-relations-cell .finance-chain__state:focus-visible {
  box-shadow: none;
  text-decoration: underline;
  text-underline-offset: 2px;
}

.docs-relations-cell .finance-chain__state--ok {
  color: var(--status-success-text);
}

.docs-relations-cell .finance-chain__state--warning {
  color: var(--status-warning-text);
}

.docs-relations-cell .finance-chain__state--danger {
  color: var(--status-error-text);
}

/* ── полоса режима аудита портала ────────────────────────────────────── */
/* Пока администратор смотрит продукт глазами арендатора, это должно быть
   видно на каждой странице и ни с чем не путаться. Поэтому полоса
   единственная в продукте, кто красится в «предупреждение» во всю ширину:
   если она похожа на обычный элемент интерфейса, её перестают замечать —
   а забыть, под кем ты, здесь опаснее, чем некрасиво. */
.impersonation-bar {
  position: sticky;
  top: 0;
  z-index: 1200;
  /* Заливка полосы — своя, не токен текста.

     Здесь стоял `--status-warning-text`, а это цвет ДЛЯ текста: в светлой
     теме он тёмно-коричневый (#92400e) и как фон под белыми буквами
     работал случайно, а в тёмной он становится светло-янтарным (#fcd34d)
     — и белый текст на нём давал контраст 1.44 при норме 4.5. Полоса,
     единственная задача которой быть замеченной и прочитанной, в тёмной
     теме не читалась вовсе.

     Это ровно та ошибка, которую разбор называет повторяющейся причиной:
     цвет заливки и цвет текста — разные роли, и подменять одну другой
     нельзя. Здесь я допустил её сам.

     Тёмно-коричневый держим в обеих темах: белый текст на нём даёт 7.4,
     и предупреждающий смысл не зависит от того, светлая тема или
     тёмная. */
  background: #92400e;
  color: #ffffff;
  box-shadow: 0 1px 3px rgb(0 0 0 / 0.2);
}

.impersonation-bar__inner {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-3);
  max-width: 1280px;
  margin: 0 auto;
  padding: var(--space-2) var(--space-4);
  font-size: var(--font-size-sm);
  line-height: var(--line-height-snug);
}

.impersonation-bar__mark {
  flex: 0 0 auto;
  font-size: var(--font-size-base);
}

.impersonation-bar__text {
  flex: 1 1 auto;
  min-width: 0;
}

.impersonation-bar__note {
  opacity: 0.85;
  white-space: nowrap;
}

.impersonation-bar__form {
  flex: 0 0 auto;
  margin: 0;
}

/* Кнопка на цветной полосе: своя, потому что и btn-primary, и btn-secondary
   на янтарном фоне читаются плохо. */
.impersonation-bar__exit,
.impersonation-bar__exit:hover {
  border: 1px solid rgb(255 255 255 / 0.55);
  background: rgb(255 255 255 / 0.12);
  color: #ffffff;
}

.impersonation-bar__exit:hover {
  background: rgb(255 255 255 / 0.22);
}

@media (max-width: 640px) {
  .impersonation-bar__note {
    display: block;
    white-space: normal;
  }
}

/* Переключение на учётную запись в «Аудите порталов».
   Отделено линией от обычного «Войти в кабинет»: это разные по силе
   действия, и человек должен видеть границу, а не два одинаковых
   ряда кнопок. */
.portal-audit-switch {
  padding-top: var(--space-3, 0.75rem);
  border-top: 1px solid var(--border-subtle, var(--border-color, #e5e7eb));
}

.portal-audit-switch p {
  line-height: var(--line-height-snug, 1.35);
  color: var(--text-tertiary, var(--text-secondary, #6b7280));
}

/* Шрифт в полях ввода.
   Браузер не наследует font-family в input/select/textarea — он ставит
   свой умолчательный. Правила на этот счёт в проекте не было, и на одной
   странице оказывалось три гарнитуры: интерфейс Inter, textarea
   monospace, input Arial. Причину в модалке «Переключение на учётную
   запись» человек читал моноширинным шрифтом — ровно то ощущение
   самоделки, из-за которого начат аудит.

   Намеренная моноширина не задета: она задаётся классами (.font-mono,
   .api-key-field и подобные), а у класса специфичность выше, чем у
   этого селектора по типу элемента. */
input,
select,
textarea,
button {
  font-family: inherit;
}

/* Утилита Tailwind `focus:outline-none` ставит прозрачную обводку
   (`outline: 2px solid transparent`) и грузится позже общего слоя — на
   полях, где она стоит в шаблоне, кольца фокуса не оставалось вовсе.
   Замер 17.08.2026 на разборе документа: обводка `rgba(0,0,0,0)`.
   Возвращаем кольцо тем же токеном; этот файл грузится после Tailwind,
   так что хватает специфичности, без флага. */
.focus\:outline-none:focus-visible {
  outline: 2px solid var(--focus-ring-color);
  outline-offset: 0;
}

/* ── Карточка сущности: шапка-сводка и вкладки ───────────────────────── */
/* 27.08.2026, решение Tim. Карточки объекта и контрагента были анкетами в
   три экрана (2310 px и 1606 px при окне в 800): чтобы дойти до площади или
   до банковских реквизитов, надо было проехать мимо всего остального, — а на
   вопрос «что это за объект» и «кто этот контрагент» карточка не отвечала
   вовсе: адрес и ИНН были полями ввода среди двух десятков других. Сверху
   строка фактов, ниже вкладки. Классы общие: карточек две, и расходиться им
   незачем. */
.ds-entity-modal .ds-card-head {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  margin-top: calc(-1 * var(--space-1));
  margin-bottom: var(--space-1);
  /* Явно, потому что обёртка окна несёт утилиту `text-center` с парой
     `sm:text-left`, и в карточке контрагента побеждала первая: подзаголовок
     вставал по центру под заголовком, выровненным влево. */
  text-align: left;
}

.ds-entity-modal .ds-card-subtitle {
  color: var(--text-secondary);
  font-size: var(--font-size-sm);
  line-height: var(--line-height-snug);
}

.ds-entity-modal .ds-card-subtitle:empty {
  display: none;
}

.ds-entity-modal .ds-card-facts {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2);
}

/* Обёртка для сгенерированных фишек: сама она в раскладке не участвует,
   иначе строка фактов ломалась бы на «блок фишек» и всё остальное. */
.ds-entity-modal .ds-card-chips {
  display: contents;
}

.ds-entity-modal .ds-card-chip {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  padding: 0.2rem 0.6rem;
  border: 1px solid var(--border-color);
  border-radius: 999px;
  background: var(--surface-secondary);
  color: var(--text-primary);
  font-size: var(--font-size-xs);
  line-height: 1.6;
  white-space: nowrap;
}

.ds-entity-modal .ds-card-chip--muted {
  color: var(--text-secondary);
}

.ds-entity-modal .ds-card-chip--mono {
  font-family: var(--font-mono, ui-monospace, SFMono-Regular, monospace);
  letter-spacing: 0.01em;
}

/* «Площадь не указана» и прочие предупреждения-бейджи. Стиль status-warning,
   а не красный danger: объект рабочий, просто данных не хватает (план v2). */
.warning-badge {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  padding: 0.15rem 0.55rem;
  border: 1px solid color-mix(in srgb, var(--warning-500) 32%, transparent);
  border-radius: 999px;
  background: var(--status-warning-bg, rgba(245, 158, 11, 0.12));
  color: var(--status-warning-text, #92400e);
  font-size: var(--font-size-xs);
  line-height: 1.6;
  white-space: nowrap;
}

/* Состояние — единственная цветная фишка: цвет здесь кодирует смысл
   (сдан / срок истёк / свободен, в работе / в архиве), а не украшает. */
.ds-entity-modal .ds-card-chip--state::before {
  content: "";
  width: 0.5rem;
  height: 0.5rem;
  border-radius: 999px;
  background: var(--text-secondary);
  flex: none;
}

.ds-entity-modal .ds-card-chip--state[data-state="rented"]::before,
.ds-entity-modal .ds-card-chip--state[data-state="ok"]::before {
  background: var(--success-500);
}

.ds-entity-modal .ds-card-chip--state[data-state="expired"]::before,
.ds-entity-modal .ds-card-chip--state[data-state="warn"]::before {
  background: var(--warning-500);
}

/* «Свободен» — тоже зелёная точка: объект в порядке и готов к сдаче, серая
   читалась как «что-то не так». Замечание Tim 28.08.2026. Серая остаётся у
   состояний, которые ничего не утверждают (например «в архиве»). */
.ds-entity-modal .ds-card-chip--state[data-state="vacant"]::before {
  background: var(--success-500);
}

.ds-entity-modal .ds-card-chip--state[data-state="muted"]::before {
  background: var(--border-color-strong);
}

.ds-entity-modal .ds-card-chip-action {
  color: var(--accent-text, #7c3aed);
  font-weight: 600;
  text-decoration: none;
}

.ds-entity-modal .ds-card-chip-action:hover {
  text-decoration: underline;
}

/* Фишка-действие: срок договора открывает договор, кадастровый номер —
   вкладку с ним, приборы учёта — их список. Отдельное слово «Открыть»
   рядом с надписью для этого не нужно: щёлкают по самой надписи.
   Замечание Tim 28.08.2026. */
.ds-entity-modal a.ds-card-chip--action,
.ds-entity-modal button.ds-card-chip--action {
  cursor: pointer;
  text-decoration: none;
  font: inherit;
  font-size: var(--font-size-xs);
}

.ds-entity-modal a.ds-card-chip--action:hover,
.ds-entity-modal button.ds-card-chip--action:hover {
  border-color: var(--accent-text, #7c3aed);
  color: var(--text-primary);
}

.ds-entity-modal a.ds-card-chip--action:focus-visible,
.ds-entity-modal button.ds-card-chip--action:focus-visible {
  outline: 2px solid var(--accent-text, #7c3aed);
  outline-offset: 2px;
}

.ds-entity-modal .ds-card-tabs {
  position: sticky;
  top: 0;
  z-index: 3;
  margin-bottom: var(--space-1);
  padding-top: var(--space-1);
  background: var(--surface-primary);
}

.ds-entity-modal .ds-card-tabs .ds-tab {
  padding: var(--space-2) var(--space-3);
}

.ds-entity-modal .ds-card-tab-panel {
  display: flex;
  flex-direction: column;
  gap: 1rem;
}

.ds-entity-modal .ds-card-tab-panel[hidden] {
  display: none;
}
