/* Euclera AI Tracker — визуальная система кабинета аналитика.
   ------------------------------------------------------------------------
   Направление выбрано пользователем как «привычное»: мейнстримный
   современный продуктовый язык, исполненный на высоком уровне ремесла.
   Осознанно БЕЗ метафоры и «мира» — предыдущая, метафорическая версия была
   построена целиком и отвергнута (см. PRODUCT.md, Brand Commitments).

   Планка: iOS 26, Linear, Notion, штатные приложения Apple, Height.
   Дух, а не клон: язык — мягкая скруглённая геометрия, воздух, осмысленная
   слоистая полупрозрачность, чистый акцентный цвет, крупные спокойные
   заголовки. Но раскладка остаётся вебовой и плотной: смена из 65
   материалов не ложится в телефонный список.

   Правила, которые переживают смену оформления и заданы продуктом:
   — состояние никогда не несёт один лишь цвет: у каждого статуса есть слово
     (WCAG 2.1 AA зафиксирован в PRODUCT.md);
   — догадка Gemini читается легче решения аналитика: у машины — тонкая
     обводка, у человека — заливка;
   — счётчики общие, считаются на сервере: под одним логином работают
     несколько аналитиков.                                                  */

/* --- токены -------------------------------------------------------------- */

:root {
  /* Основа: тонированный холст, на нём белые карточки. Модель сгруппированного
     списка — она и даёт «воздух», которого просили. */
  --canvas:       #f2f2f7;
  --surface:      #ffffff;
  --sunken:       #f7f7fa;
  --raised:       #ffffff;

  /* Слоистое стекло: плавающая хромировка, сквозь которую видно содержимое. */
  --glass:        rgba(255, 255, 255, 0.72);
  --glass-heavy:  rgba(255, 255, 255, 0.86);

  --text:         #1c1c1e;
  --text-2:       #55555c;
  --text-3:       #64646c;

  --sep:          rgba(60, 60, 67, 0.13);
  --sep-strong:   rgba(60, 60, 67, 0.26);

  --accent:       #0b5ed7;
  --accent-tint:  rgba(11, 94, 215, 0.10);
  --accent-text:  #ffffff;

  --ok:           #0d6c36;
  --ok-tint:      rgba(13, 108, 54, 0.12);
  --no:           #bd1d0f;
  --no-tint:      rgba(189, 29, 15, 0.10);
  --warn:         #7d4900;
  --warn-tint:    rgba(154, 91, 0, 0.12);
  --neutral-tint: rgba(60, 60, 67, 0.08);

  /* Скругления концентричны: вложенный элемент всегда мягче контейнера. */
  --r-xs: 6px; --r-sm: 8px; --r-md: 12px; --r-lg: 16px; --r-xl: 20px;
  --r-pill: 999px;

  /* Общий рост органов управления. Своего у поля, списка и кнопки нет:
     каждый считает высоту от собственной фурнитуры, и в одной строке они
     расходятся на пять точек. Число, а не «как получится». */
  --control-h: 2.375rem;

  /* Каркас страницы числами, а не литералами по месту. Высота плавающей
     шапки была записана трижды и тремя разными числами (4.5rem у липкого
     оглавления справки, 4.75rem у отступа якоря, -72px у наблюдателя в
     help-toc.js): любая правка панели разъезжалась с двумя из трёх. */
  --toolbar-h: 4.5rem;
  /* Якорь не должен вставать вплотную под шапку — отсюда воздух сверху. */
  /* Якорь не должен вставать вплотную под шапку — отсюда воздух сверху.
     6.25rem, а не 4.75: со сводкой смены панель занимает не одну строку, а
     две, и замер на источниках даёт 95 точек вместо 72. Тем же числом
     держится фокус клавиатуры — он уходил под панель по той же причине. */
  --anchor-offset: 6.25rem;
  --page-pad-b: 6rem;
  /* Страница, кончающаяся закреплённой панелью, донизу заполнена ею самой:
     шести сантиметрам пустоты под кнопками взяться неоткуда. */
  --page-pad-b-sticky: 1.75rem;
  /* Высота полосы «Оригинал и карточка». Одно число на пару колонок: два
     независимых потолка у одной пары расстроили бы их по высоте. */
  --columns-h: 26rem;

  /* Шкала слоёв. Раньше числа стояли по месту (30 у шапки, 60 у ссылки
     обхода), и закреплённой панели некуда было встать, не угадывая. */
  --z-toolbar: 30;
  --z-sticky: 40;
  --z-skip: 60;

  /* Тени со смещением и мягким размытием — не цветное гало вокруг элемента. */
  --shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.04), 0 1px 3px rgba(0, 0, 0, 0.05);
  --shadow-md: 0 1px 2px rgba(0, 0, 0, 0.04), 0 6px 16px rgba(0, 0, 0, 0.07);
  --shadow-lg: 0 2px 6px rgba(0, 0, 0, 0.05), 0 14px 36px rgba(0, 0, 0, 0.11);

  /* Типографика: одна гарнитура, восемь ступеней. Golos Text — современный
     гротеск с отличной кириллицей и мягким характером. */
  --font: 'Golos Text', system-ui, -apple-system, 'Segoe UI', sans-serif;
  --font-mono: 'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, monospace;

  --t-display: 1.75rem;
  --t-title:   1.3125rem;
  --t-head:    1.0625rem;
  --t-body:    0.9375rem;
  --t-call:    0.875rem;
  --t-sub:     0.8125rem;
  --t-foot:    0.75rem;
  --t-cap:     0.6875rem;

  --ease: cubic-bezier(0.32, 0.72, 0, 1);

  --chevron: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' \
viewBox='0 0 12 12' fill='none' stroke='%23000' stroke-width='1.7' \
stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M4.5 2l4 4-4 4'/%3E%3C/svg%3E");

  color-scheme: light;
}

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --canvas:       #0b0b0d;
    --surface:      #18181b;
    --sunken:       #202024;
    --raised:       #232327;
    --glass:        rgba(24, 24, 27, 0.72);
    --glass-heavy:  rgba(24, 24, 27, 0.88);
    --text:         #f2f2f5;
    --text-2:       #b5b5bd;
    --text-3:       #93939c;
    --sep:          rgba(255, 255, 255, 0.10);
    --sep-strong:   rgba(255, 255, 255, 0.20);
    --accent:       #4d9bff;
    --accent-tint:  rgba(77, 155, 255, 0.16);
    --accent-text:  #0b0b0d;
    --ok:           #3ad07a;
    --ok-tint:      rgba(58, 208, 122, 0.16);
    --no:           #ff6a5e;
    --no-tint:      rgba(255, 106, 94, 0.15);
    --warn:         #ffab35;
    --warn-tint:    rgba(255, 171, 53, 0.16);
    --neutral-tint: rgba(255, 255, 255, 0.09);
    --shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.35);
    --shadow-md: 0 2px 6px rgba(0, 0, 0, 0.40);
    --shadow-lg: 0 8px 30px rgba(0, 0, 0, 0.50);
    color-scheme: dark;
  }
}

:root[data-theme="dark"] {
  --canvas:       #0b0b0d;
  --surface:      #18181b;
  --sunken:       #202024;
  --raised:       #232327;
  --glass:        rgba(24, 24, 27, 0.72);
  --glass-heavy:  rgba(24, 24, 27, 0.88);
  --text:         #f2f2f5;
  --text-2:       #b5b5bd;
  --text-3:       #93939c;
  --sep:          rgba(255, 255, 255, 0.10);
  --sep-strong:   rgba(255, 255, 255, 0.20);
  --accent:       #4d9bff;
  --accent-tint:  rgba(77, 155, 255, 0.16);
  --accent-text:  #0b0b0d;
  --ok:           #3ad07a;
  --ok-tint:      rgba(58, 208, 122, 0.16);
  --no:           #ff6a5e;
  --no-tint:      rgba(255, 106, 94, 0.15);
  --warn:         #ffab35;
  --warn-tint:    rgba(255, 171, 53, 0.16);
  --neutral-tint: rgba(255, 255, 255, 0.09);
  --shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.35);
  --shadow-md: 0 2px 6px rgba(0, 0, 0, 0.40);
  --shadow-lg: 0 8px 30px rgba(0, 0, 0, 0.50);
  color-scheme: dark;
}

/* Шеврон селекта повторяет тот же путь и толщину штриха, что и рисованный
   набор иконок; для background-image цвет нельзя унаследовать, поэтому он
   задаётся отдельной темизируемой переменной. */
:root {
  --select-chevron: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12' fill='none' stroke='%2355555c' stroke-width='1.7' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M2 4.5l4 4 4-4'/%3E%3C/svg%3E");
}
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --select-chevron: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12' fill='none' stroke='%23b5b5bd' stroke-width='1.7' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M2 4.5l4 4 4-4'/%3E%3C/svg%3E");
  }
}
:root[data-theme="dark"] {
  --select-chevron: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12' fill='none' stroke='%23b5b5bd' stroke-width='1.7' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M2 4.5l4 4 4-4'/%3E%3C/svg%3E");
}

/* --- основание ----------------------------------------------------------- */

*, *::before, *::after { box-sizing: border-box; }
html { -webkit-text-size-adjust: 100%; }
/* Закреплённая панель накрывает низ окна: без этого поле, к которому пришли
   клавишей Tab, браузер прокручивал ровно под неё. */
html:has(.decision--sticky) { scroll-padding-bottom: 15rem; }
/* Панель липнет к верху окна, и всё, что под ней, браузер считает видимым:
   ссылку, к которой пришли Shift+Tab, он оставляет под стеклом и не
   прокручивает вовсе. Снизу такой запас заведён строкой выше; сверху его не
   было ни для фокуса, ни для якоря, ни для «Перейти к содержимому» — то есть
   единственный инструмент клавиатурного обхода вёл под шапку. Запас общий, а
   не только для справки: `scroll-padding-top` работает на области просмотра
   и потому накрывает все три случая разом. */
html { scroll-padding-top: var(--anchor-offset); }

body {
  margin: 0;
  background: var(--canvas);
  color: var(--text);
  font-family: var(--font);
  font-size: var(--t-body);
  line-height: 1.5;
  -webkit-font-smoothing: antialiased;
}

::selection { background: var(--accent-tint); color: var(--text); }
input, textarea, select { caret-color: var(--accent); }

* { scrollbar-color: var(--sep-strong) transparent; scrollbar-width: thin; }
*::-webkit-scrollbar { width: 12px; height: 12px; }
*::-webkit-scrollbar-track { background: transparent; }
*::-webkit-scrollbar-thumb {
  background: var(--sep-strong); border-radius: var(--r-pill);
  border: 4px solid transparent; background-clip: content-box;
}
*::-webkit-scrollbar-thumb:hover { background-clip: content-box;
  background-color: var(--text-3); }

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

a { color: var(--accent); text-decoration: none; }
a:hover { text-decoration: underline; text-underline-offset: 0.2em; }

h1, h2, h3 { margin: 0; font-weight: 600; letter-spacing: -0.01em; }

.sr-only {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0;
}

.skip-link {
  position: absolute; left: -9999px; top: 0; z-index: var(--z-skip);
  background: var(--accent); color: var(--accent-text);
  padding: 0.7rem 1.1rem; border-radius: 0 0 var(--r-md) 0;
  font-size: var(--t-call); font-weight: 600;
}
.skip-link:focus { left: 0; }

/* --- плавающая хромировка ------------------------------------------------ */

/* Панель из слоистого стекла: содержимое уезжает под неё и просвечивает.
   Материал уплотняется при прокрутке — единственный авторский момент
   движения в интерфейсе, и он сообщает о положении, а не украшает. */
.toolbar {
  position: sticky; top: 0; z-index: var(--z-toolbar);
  background: var(--glass);
  backdrop-filter: saturate(180%) blur(20px);
  -webkit-backdrop-filter: saturate(180%) blur(20px);
  border-bottom: 1px solid transparent;
  transition: background 0.25s var(--ease), border-color 0.25s var(--ease);
}
.toolbar.is-scrolled {
  background: var(--glass-heavy);
  border-bottom-color: var(--sep);
}
@supports not (backdrop-filter: blur(1px)) {
  .toolbar { background: var(--surface); }
}

.toolbar-inner {
  max-width: 74rem; margin: 0 auto;
  display: flex; align-items: center; gap: 0.7rem;
  padding: 0.6rem 1.5rem; flex-wrap: wrap;
}

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

   Считаем по ширине САМОЙ панели, а не окна: панель ограничена 74rem, и на
   мониторе в 2560 точек ей всё равно доступно 1184 — медиазапрос по окну
   отвечал бы на другой вопрос и не срабатывал никогда.

   Уступают по очереди те, кто несёт меньше смысла: сперва слово бренда
   (значок остаётся), потом имя учётной записи (кнопка «Выйти» остаётся). */
.toolbar-inner { container-type: inline-size; container-name: toolbar; }

/* Пороги сняты замером, а не на глаз: на очереди сводка смены занимает до
   510 точек (широкая полоса партии), навигация 366, и вместе с остальным
   получается 1263 при доступных 1184. Уступают по очереди те, чьи числа
   продублированы на самой странице или нужны раз в смену.

   Селекторы намеренно длиннее нужного: правила ниже по файлу (`.stat`,
   `.toolbar-aside`) имеют ту же силу, и при равной силе выигрывает
   последнее. Короткий `.stat-optional` здесь молча ничего не делал. */
@container toolbar (max-width: 1250px) {
  .toolbar-inner .brand span:last-child { display: none; }
  .toolbar-inner .stats .stat-optional { display: none; }
}
@container toolbar (max-width: 1080px) {
  .toolbar-inner .stats .stat-secondary { display: none; }
}
@container toolbar (max-width: 940px) {
  .toolbar-inner .session-name { display: none; }
}

/* Ниже 430 точек уступать больше нечем: разделы просят 366, ссылки рядом —
   ещё 130, а всей панели остаётся 288. Прятать их — не выход. «Правила» и
   «Справка» не продублированы нигде на страницах: спрятав `.toolbar-aside`,
   мы отнимали у телефона два раздела целиком, а не переносили их поближе.
   Разделы же не помещались молча: `.segmented` объявлен `flex-shrink: 0`,
   то есть берёт ширину по содержимому, и собственный `flex-wrap` внутри
   него не мог сработать никогда — строка ровно по содержимому. Он не
   переносился, а торчал за краем документа, и вместе с ним вбок уезжал
   весь кабинет.

   Здесь обе группы получают по своей строке. `order` расставлен явными
   разными числами, а не порядком тегов в base.html: иначе перестановка
   разметки молча меняла бы вид шапки. Приём тот же, каким сводка смены
   отправлена на свою строку на 48rem. */
@container toolbar (max-width: 430px) {
  .toolbar-inner .segmented { order: 2; flex: 1 1 100%; }
  .toolbar-inner .toolbar-aside { order: 3; flex: 1 1 100%; }
  /* Последняя уступка в цепочке, и по её же правилу: сводка смены нужна раз
     в смену, а разделы — каждую минуту. Две строки на источниках («211
     страниц · 84 ведомств · 2363 материалов» переносится) — это 61 точка,
     из-за которых липкая шапка занимала треть телефонного экрана. Числа не
     пропадают: очередь печатает своё «1–200 из 2313» над списком, сводка
     целиком состоит из этих же величин, источники — из своего перечня. */
  .toolbar-inner .stats { display: none; }
}

.brand {
  display: inline-flex; align-items: center; gap: 0.5rem;
  font-size: var(--t-call); font-weight: 600; letter-spacing: -0.01em;
  color: var(--text); text-decoration: none; flex-shrink: 0;
}
.brand:hover { text-decoration: none; }
.brand-glyph {
  width: 1.5rem; height: 1.5rem; border-radius: var(--r-sm);
  background: var(--accent); color: var(--accent-text);
  display: grid; place-items: center;
  font-size: var(--t-foot); font-weight: 700;
}

/* Сегментированный переключатель разделов — привычный, спокойный. */
.segmented {
  display: inline-flex; gap: 2px; padding: 2px;
  background: var(--neutral-tint); border-radius: var(--r-md);
  /* Четыре рабочих раздела помещаются в строку на любом ноутбуке; перенос
     оставлен на случай крупного шрифта, а не как обычное поведение. */
  flex-wrap: wrap; flex-shrink: 0;
}

/* Настройка и справка — рядом с учётной записью, а не в рабочей панели:
   туда ходят раз в неделю, и место в шапке они занимали как ежедневные. */
.toolbar-aside {
  display: inline-flex; align-items: center; gap: 0.8rem; flex-shrink: 0;
  font-size: var(--t-sub);
}
.toolbar-aside a { color: var(--text-3); text-decoration: none; }
.toolbar-aside a:hover { color: var(--text); }
.toolbar-aside a[aria-current="page"] { color: var(--text); font-weight: 600; }
.segmented a {
  padding: 0.32rem 0.8rem; border-radius: calc(var(--r-md) - 3px);
  font-size: var(--t-sub); font-weight: 500; color: var(--text-2);
  text-decoration: none; transition: background 0.18s var(--ease),
                                      color 0.18s var(--ease);
}
.segmented a:hover { color: var(--text); text-decoration: none; }
.segmented a[aria-current="page"] {
  background: var(--surface); color: var(--text);
  box-shadow: var(--shadow-sm); font-weight: 600;
}

/* Распорка отжимает сводку и переключатель вправо. Авто-отступ нельзя вешать
   на .stats: на карточке этого блока нет, и переключатель оставался в
   середине панели — ровно тот дефект, который правка закрывала. */
.toolbar-gap { flex: 1 1 auto; min-width: 0; }

/* Сводка смены — спокойные чипы, а не сетка стат-карточек. */
.stats {
  display: flex; align-items: center; gap: 0.35rem; flex-wrap: wrap;
  /* Сводка смены уступает место первой: её числа продублированы на самой
     странице, а навигация и выход — нет. */
  min-width: 0; flex-shrink: 1;
}
.icon-btn { flex-shrink: 0; }
.stat {
  display: inline-flex; align-items: baseline; gap: 0.35rem;
  padding: 0.25rem 0.6rem; border-radius: var(--r-pill);
  background: var(--neutral-tint);
  font-size: var(--t-foot); color: var(--text-2); white-space: nowrap;
}
.stat b { font-weight: 600; color: var(--text); font-size: var(--t-sub);
          font-variant-numeric: tabular-nums; }

/* Полоса выработки: тонкий индикатор, а не «прогресс-кольцо». */
.progress {
  width: 4.5rem; height: 5px; border-radius: var(--r-pill);
  background: var(--neutral-tint); overflow: hidden; flex-shrink: 0;
}
.progress > span { display: block; height: 100%; border-radius: inherit;
                   background: var(--accent); }

.icon-btn {
  display: inline-grid; place-items: center;
  width: 2rem; height: 2rem; border-radius: var(--r-pill);
  border: none; background: var(--neutral-tint); color: var(--text-2);
  cursor: pointer; transition: background 0.18s var(--ease),
                               color 0.18s var(--ease);
}
.icon-btn:hover { background: var(--sep); color: var(--text); }
.icon-btn svg { width: 1rem; height: 1rem; }

/* Переключатель языка. Двумя кнопками, а не списком: языков два, и выбор из
   двух, спрятанный под раскрытие, стоит лишнего нажатия каждый раз. Форма
   стоит в одном ряду с темой и держит их общий рост. */
.lang-switch {
  display: inline-flex; align-items: center; gap: 1px; flex-shrink: 0;
  padding: 1px; border-radius: var(--r-pill); background: var(--neutral-tint);
}
.lang-btn {
  border: none; background: none; cursor: pointer;
  padding: 0.2rem 0.5rem; border-radius: var(--r-pill);
  font: inherit; font-size: var(--t-foot); font-weight: 600;
  color: var(--text-2); letter-spacing: 0.02em;
  transition: background 0.18s var(--ease), color 0.18s var(--ease);
}
.lang-btn:hover { color: var(--text); }
/* Выбранный язык назван не одним лишь цветом: `aria-current` в разметке
   говорит то же самое тому, кто цвета не видит. */
.lang-btn.is-current { background: var(--bg); color: var(--text); }

/* --- каркас страницы ----------------------------------------------------- */

.page { max-width: 74rem; margin: 0 auto;
        padding: 1.5rem 1.5rem var(--page-pad-b); }
/* Карточка кончается закреплённой панелью решения: она и есть низ страницы. */
.page:has(> .decision--sticky) { padding-bottom: var(--page-pad-b-sticky); }

.page-title {
  font-size: var(--t-display); font-weight: 700; letter-spacing: -0.025em;
  line-height: 1.15; margin: 0.75rem 0 0.25rem;
}
.page-sub { color: var(--text-2); font-size: var(--t-call); margin: 0 0 1.5rem; }

.section-title {
  font-size: var(--t-sub); font-weight: 600; color: var(--text-2);
  margin: 2rem 0 0.6rem; padding: 0 0.25rem;
  display: flex; align-items: baseline; gap: 0.5rem;
}
.section-title .count { color: var(--text-3); font-weight: 400; }

/* --- сгруппированные карточки -------------------------------------------- */

.card {
  background: var(--surface);
  border-radius: var(--r-lg);
  box-shadow: var(--shadow-sm);
  overflow: hidden;
}
.card-pad { padding: 1.1rem 1.25rem; }

/* --- список материалов --------------------------------------------------- */

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

.row {
  position: relative;
  display: flex; align-items: flex-start; gap: 0.9rem;
  padding: 0.85rem 1.1rem;
  transition: background 0.15s var(--ease);
}
/* Разделитель с отступом слева — как в сгруппированных списках, чтобы
   строки читались единым блоком, а не набором отдельных плашек. */
.row + .row::before {
  content: ""; position: absolute; top: 0; left: 1.1rem; right: 0;
  height: 1px; background: var(--sep);
}
.row:hover { background: var(--sunken); }
.row:focus-within { background: var(--sunken); }

.row-main { flex: 1; min-width: 0; }

.row-title {
  font-size: var(--t-head); font-weight: 500; line-height: 1.35;
  margin: 0 0 0.2rem; color: var(--text);
  letter-spacing: -0.01em;
}
.row-title a { color: inherit; text-decoration: none; }
.row-title a::after { content: ""; position: absolute; inset: 0; }
.row-title a:hover { text-decoration: none; }

.row-meta {
  display: flex; align-items: center; gap: 0.45rem; flex-wrap: wrap;
  font-size: var(--t-sub); color: var(--text-3);
}
.row-meta .num, .facts .num { font-variant-numeric: tabular-nums; }

/* Плашка состояния и догадка не сжимаются и не переносятся — на широком
   экране это верно, там они влезают всегда. При двойном размере шрифта
   (WCAG 1.4.4, уровень AA) плашка перерастает строку, сжаться ей запрещено,
   и `overflow: hidden` карточки срезает ей хвост — вместе с причиной
   отклонения, то есть с единственным, что объясняет цвет словом. Потолок в
   ширину строки возвращает слово на место; на десктопе плашка уже своего
   потолка, и там не меняется ничего. */
.row-meta > .status, .row-meta > .guess { max-width: 100%; white-space: normal; }

/* Ряд фактов — гибкий ребёнок `.row-meta`, а гибкому ребёнку по умолчанию
   запрещено быть уже самого длинного нерушимого куска внутри себя. Один
   китайский номер документа в 745 точек растягивал по себе весь ряд — при
   187 доступных, — и `overflow: hidden` карточки срезал всё остальное:
   дата, ведомство, отрасль и сам номер не «уезжали за край», а исчезали, и
   добраться до них нечем — прокрутки внутри карточки нет. Ноль — та же
   оговорка, что у `.row-main` выше, и заведена по той же причине.
   `anywhere`, а не `break-word`: он заодно уменьшает внутреннюю минимальную
   ширину, так что оговорка и разрешение работают одним движением. Так уже
   решено у ячеек таблиц и у встроенного кода. */
.facts { display: inline; min-width: 0; overflow-wrap: anywhere; }

/* Точка-разделитель рисуется перед элементом, а не стоит отдельным флекс-
   ребёнком: иначе при переносе она остаётся висеть в конце строки. Одного
   этого мало — перед китайским текстом перенос разрешён и без пробела, и
   точка всё равно повисала сиротой в 23 строках из 200 на 320 точках.
   Словосоединитель (U+2060) шириной в ноль запрещает разрыв ровно в этом
   месте, а зазор по-прежнему держит `margin`. */
.facts > * + *::before {
  content: "·\2060"; margin: 0 0.4rem; opacity: 0.5;
}
.row-meta > .facts::before {
  content: "·"; margin: 0 0.4rem; opacity: 0.5;
}
.summary-meta > .facts::before { content: none; }

.row-note {
  margin: 0.4rem 0 0; font-size: var(--t-sub); color: var(--text-2);
  line-height: 1.45;
}
.row-note b { font-weight: 600; color: var(--text); }

.chevron { color: var(--text-3); flex-shrink: 0; margin-top: 0.3rem; }
.chevron svg { width: 0.75rem; height: 0.75rem; display: block; }

/* --- статусы ------------------------------------------------------------- */

/* У каждого статуса есть СЛОВО. Цвет только усиливает его и никогда
   не работает в одиночку — обязательство WCAG 2.1 AA из PRODUCT.md. */
.status {
  display: inline-flex; align-items: center; gap: 0.3rem; flex-shrink: 0;
  padding: 0.16rem 0.55rem; border-radius: var(--r-pill);
  font-size: var(--t-foot); font-weight: 600; white-space: nowrap;
  letter-spacing: -0.005em;
}
.status::before {
  content: ""; width: 0.4rem; height: 0.4rem; border-radius: 50%;
  background: currentColor; flex-shrink: 0;
}
.status-wire     { background: var(--neutral-tint); color: var(--text-2); }
.status-fault    { background: var(--no-tint);      color: var(--no); }
.status-query    { background: var(--warn-tint);    color: var(--warn); }
.status-approved { background: var(--ok-tint);      color: var(--ok); }
.status-confirm  { background: var(--warn-tint);    color: var(--warn); }
.status-rejected { background: var(--no-tint);      color: var(--no); }
/* Отложенное — не решение и не поломка: нейтральный тон, а слово рядом с
   плашкой отличает его от «ждёт проверки». Цветом состояния не различаются
   ни одно (WCAG 2.1 AA, PRODUCT.md). */
.status-deferred { background: var(--neutral-tint); color: var(--text-2); }
/* Архив — сделанное, а не требующее внимания: тон нейтральный и тише
   отложенного, потому что возвращаться к нему не нужно вовсе. */
.status-archived { background: var(--neutral-tint); color: var(--text-3); }
.status-excluded { background: var(--neutral-tint); color: var(--text-3); }
.status-fresh    { background: var(--accent-tint);  color: var(--accent); }
.status-pending  { background: var(--neutral-tint); color: var(--text-2); }

/* Догадка Gemini — обводка без заливки: машина предполагает, человек решает,
   и по весу они не должны совпадать. */
.guess {
  display: inline-flex; align-items: center; gap: 0.3rem; flex-shrink: 0;
  padding: 0.14rem 0.5rem; border-radius: var(--r-pill);
  font-size: var(--t-foot); font-weight: 500; white-space: nowrap;
  color: var(--text-3); border: 1px solid var(--sep-strong);
  background: transparent;
}

/* --- органы управления --------------------------------------------------- */

.field { display: flex; flex-direction: column; gap: 0.3rem; min-width: 0; }
/* Ширину закрытого списка браузер считает по самому длинному пункту, а не
   по выбранному: среди ведомств есть «National Forestry and Grassland
   Administration», и без предела «Ведомство» распирало свою ячейку. Сетка
   ограничивает колонку, но не содержимое — список всё равно попросил бы
   750 точек и раздвинул бы колонку под себя. */
.filter-grid select { max-width: 100%; text-overflow: ellipsis; }
.field > label {
  font-size: var(--t-foot); font-weight: 500; color: var(--text-2);
  padding-left: 0.15rem;
}

input, select, textarea {
  font-family: inherit; font-size: var(--t-body); color: var(--text);
  background: var(--sunken);
  border: 1px solid var(--sep);
  border-radius: var(--r-sm);
  padding: 0.45rem 0.65rem;
  max-width: 100%;
  transition: border-color 0.15s var(--ease), background 0.15s var(--ease);
}

/* Один рост на все органы управления.
   Собственной высоты у них не было, и каждый считал её сам от своей
   фурнитуры: список выходил 33.9, поле даты — 35.9 (место под значок
   календаря), кнопка — 33.4 (у button не наследуется межстрочный интервал).
   В строке фильтра три соседних поля стояли на трёх разных высотах, а
   «Показать» и «Сбросить» — одна кнопкой, другая ссылкой — расходились на
   4.5 точки: рядом это читается как съехавшая вёрстка, а не как разные
   элементы. Мелкая кнопка (`.btn-small`) в это правило не входит: она
   намеренно ниже и стоит в плотных местах. */
.btn:not(.btn-small):not(.btn-link),
select,
input:not([type="checkbox"]):not([type="radio"]) {
  min-height: var(--control-h);
}
input:hover, select:hover, textarea:hover { border-color: var(--sep-strong); }
input:focus, select:focus, textarea:focus {
  background: var(--surface); border-color: var(--accent);
}
textarea { width: 100%; resize: vertical; line-height: 1.55; }
::placeholder { color: var(--text-3); }

/* Единственные органы, остававшиеся с платформенной фурнитурой на
   собственноручно нарисованной поверхности. */
select {
  appearance: none; -webkit-appearance: none;
  padding-right: 1.9rem;
  background-image: var(--select-chevron);
  background-repeat: no-repeat;
  background-position: right 0.6rem center;
  background-size: 0.6rem;
}
select::-ms-expand { display: none; }

input[type="date"]::-webkit-calendar-picker-indicator {
  opacity: 0.45; cursor: pointer; transition: opacity 0.15s var(--ease);
}
input[type="date"]:hover::-webkit-calendar-picker-indicator { opacity: 0.9; }

.btn {
  display: inline-flex; align-items: center; justify-content: center;
  gap: 0.4rem;
  font-family: inherit; font-size: var(--t-call); font-weight: 600;
  /* Явно, а не по наследству: `button` межстрочный интервал от body не
     берёт и считает свой `normal`, а `a` — берёт. Одна и та же кнопка
     выходила разной высоты в зависимости от того, каким тегом написана. */
  line-height: 1.5;
  padding: 0.48rem 0.9rem; border-radius: var(--r-sm);
  border: 1px solid transparent; cursor: pointer; text-decoration: none;
  background: var(--accent); color: var(--accent-text);
  transition: filter 0.15s var(--ease), background 0.15s var(--ease),
              transform 0.08s var(--ease);
}
.btn:hover { filter: brightness(1.08); text-decoration: none; }
.btn:active { transform: scale(0.98); }

.btn-quiet {
  background: var(--neutral-tint); color: var(--text);
}
.btn-quiet:hover { background: var(--sep); filter: none; }

.btn-plain {
  background: transparent; color: var(--accent); padding-left: 0.4rem;
  padding-right: 0.4rem;
}
.btn-plain:hover { background: var(--accent-tint); filter: none; }

.btn-danger { background: var(--no); color: #fff; }
.btn-small { font-size: var(--t-foot); padding: 0.3rem 0.6rem; }

/* Мелкая кнопка в строке С ПОЛЯМИ выравнивается с ними по росту. Три полосы
   отбора писались порознь: на очереди кнопка полноразмерная, на источниках
   и в сводке — мелкая, и «Отобрать» стояла 30 точками против поля в 38.
   То же на источниках: «Отключить» рядом с «Причиной отключения».
   Строки без полей правило не задевает — там, где мелкая кнопка стоит одна
   («Применить» в подсказке модели, действия над кандидатами), она остаётся
   негромкой, ради чего и заведена. */
.row-inline:has(input, select) .btn-small { min-height: var(--control-h); }
.btn[disabled] { background: var(--neutral-tint); color: var(--text-3);
                 cursor: not-allowed; }

kbd {
  font-family: var(--font); font-size: var(--t-cap); font-weight: 600;
  background: rgba(255, 255, 255, 0.22); border-radius: var(--r-xs);
  padding: 0.05rem 0.3rem; margin-left: 0.15rem;
}
.btn-quiet kbd, .kbd-inline {
  background: var(--neutral-tint); color: var(--text-2);
  font-family: var(--font); font-size: var(--t-cap); font-weight: 600;
  border-radius: var(--r-xs); padding: 0.05rem 0.32rem;
}

/* --- назад --------------------------------------------------------------- */

.back {
  display: inline-flex; align-items: center; gap: 0.25rem;
  color: var(--accent); font-size: var(--t-call); font-weight: 500;
  text-decoration: none;
  /* Возврат в очередь — конец каждого цикла разбора, и он же самая мелкая
     цель карточки: 21 точка в высоту при минимуме в 24. Растёт область
     нажатия, а не подпись: отбивка снизу уходит внутрь, в padding, а
     отрицательный отступ сверху возвращает ссылку на прежнюю строку —
     заголовок под ней не съезжает. */
  padding: 0.3rem 0;
  margin-top: -0.3rem; margin-bottom: 0;
}
.back:hover { text-decoration: none; opacity: 0.8; }
.back svg { width: 0.8rem; height: 0.8rem; }

/* --- сводка материала ---------------------------------------------------- */

.summary-meta {
  display: flex; align-items: center; gap: 0.5rem; flex-wrap: wrap;
  font-size: var(--t-sub); color: var(--text-3); margin-bottom: 0.6rem;
}
.summary-original {
  font-size: var(--t-body); color: var(--text-2); margin: 0.4rem 0 0;
  max-width: 62ch;
}
.summary-reason {
  margin: 0.9rem 0 0; font-size: var(--t-body); color: var(--text-2);
  max-width: 62ch; line-height: 1.55;
}
/* Реферат стоит третьим абзацем подряд с двумя соседями выше, но своего
   правила не имел вовсе: браузер давал ему отбивку абзаца и всю ширину
   кабинета, и колонка обрывалась на 62 знаках у соседей и тянулась на 1136
   точек у него — на одном экране, встык. */
.summary-abstract {
  margin: 0.9rem 0 0; font-size: var(--t-body); color: var(--text-2);
  max-width: 62ch; line-height: 1.55;
}

/* --- решение ------------------------------------------------------------- */

/* Решение аналитика названо в PRODUCT.md основным событием продукта. Панель
   стоит последней в потоке и закреплена у нижнего края: так она видна на
   любой высоте прокрутки, а документ не оттеснён ею вниз. Закрепляется САМА
   секция — панель приезжает hx-swap-oob, и вторая копия кнопок осталась бы
   со старой версией карточки.

   Материал тот же, что у плавающей шапки: содержимое уезжает под панель и
   просвечивает — иначе закреплённая полоса читается как обрыв страницы. */
.decision {
  background: var(--glass-heavy);
  backdrop-filter: saturate(180%) blur(20px);
  -webkit-backdrop-filter: saturate(180%) blur(20px);
  border: 1px solid var(--sep);
  border-radius: var(--r-xl);
  box-shadow: var(--shadow-lg);
  padding: 0.85rem 1.05rem;
  margin: 1.25rem 0 0;
}
@supports not (backdrop-filter: blur(1px)) {
  .decision { background: var(--surface); }
}
.decision--sticky {
  position: sticky; bottom: 0; z-index: var(--z-sticky);
  /* На низком окне панель с плашкой присутствия и заметкой способна закрыть
     экран целиком: тогда прокручивается она сама, а не страница под ней. */
  max-height: 78vh; overflow-y: auto;
}

/* Полоса панели: слева состояние словом, справа действия текущего шага. */
.decision-bar {
  display: flex; align-items: center; gap: 1rem; flex-wrap: wrap;
}
/* Слева. Заголовок «Решение» остался именем секции для средства чтения с
   экрана, но места в полосе не занимает: над двумя кнопками «Подходит» и
   «Не подходит» слово «Решение» ничего не добавляет. */
.decision-head {
  display: flex; align-items: center; gap: 0.5rem; flex-wrap: wrap;
  flex: 1 1 13rem; min-width: 0;
}
/* Обстоятельства состояния — одной строкой через точку, а не четырьмя
   серыми сносками подряд: в закреплённой полосе они занимали бы больше
   места, чем сами кнопки. */
.decision-facts {
  margin: 0; font-size: var(--t-sub); color: var(--text-3);
}
/* Сказать нечего — и строки нет: пустой абзац занимал бы в закреплённой
   полосе ровно столько же места, сколько заполненный. */
.decision-facts:not(:has(span)) { display: none; }
/* Точка-разделитель рисуется перед элементом и переносится вместе с ним —
   но только если внутри элемента переносить нечего: иначе строка рвётся
   сразу за точкой и та повисает в конце строки сиротой. */
.decision-facts > span { white-space: nowrap; }

.decision-actions {
  display: flex; gap: 0.5rem; flex-wrap: wrap; align-items: stretch;
  justify-content: flex-end;
}
/* Каждое действие — своя форма (у выгрузки свой адрес, у решения свой), и
   формы стоят в той же полосе, что и кнопки. */
.decision-bar > .decision-actions { flex: 1 1 auto; }
.btn-decision {
  font-size: var(--t-body); padding: 0.55rem 1rem;
  border-radius: var(--r-md); min-width: 9rem;
}
/* Отказ экспорта живёт в самой кнопке и называет недостающие поля: прежде
   это была прозаическая плашка выше, которую читали как ещё одну оговорку.
   Кнопка при этом остаётся кнопкой — второй строкой, а не абзацем. */
.btn-decision:has(.btn-sub) {
  flex-direction: column; gap: 0.1rem;
  padding-top: 0.4rem; padding-bottom: 0.4rem;
}
.btn-line { display: inline-flex; align-items: center; gap: 0.2rem; }
.btn-sub {
  font-size: var(--t-cap); font-weight: 500; line-height: 1.3;
  max-width: 15rem; white-space: normal;
}
/* «Подходит» и «Не подходит» — два равноправных исхода, а не основное
   действие и его противоположность. Одинаковый вес: заливка появляется
   только под курсором, чтобы интерфейс не подталкивал к одному из них. */
.btn-approve { background: var(--ok-tint); color: var(--ok); }
.btn-approve:hover { background: var(--ok); color: #fff; filter: none; }
.btn-reject  { background: var(--no-tint); color: var(--no); }
.btn-reject:hover { background: var(--no); color: #fff; filter: none; }
.btn-approve kbd, .btn-reject kbd { background: rgba(0, 0, 0, 0.06); }
.btn-approve:hover kbd, .btn-reject:hover kbd {
  background: rgba(255, 255, 255, 0.22);
}

/* Сноски под решением своей ширины не имели и разворачивались во всю
   карточку: 1096 точек при 13-м кегле — 136 знаков в строке, вдвое длиннее
   меры, на которой глаз возвращается к началу строки не промахиваясь. */
.decision-note {
  font-size: var(--t-sub); color: var(--text-3); margin: 0.85rem 0 0;
  line-height: 1.5; max-width: 68ch;
}
/* Пропустить материал — четвёртый исход, а не примечание: голос ссылки, а
   не сноски. Он стоял серой строкой в ряду из трёх, между подсказкой про
   клавиши и справкой про экспорт, и читался как ещё одна оговорка. */
.decision-skip { margin: 0.7rem 0 0; font-size: var(--t-call); }
/* Заметка о несохранённом — под кнопками, потому что говорит, что́ они
   сделают. Не предупреждение и потому не жёлтая: терять больше нечего,
   правка уезжает вместе с решением. Но и не сноска — она про то, что
   произойдёт сейчас, поэтому голос чуть громче серого. */
.decision-carry {
  margin: 0.7rem 0 0; max-width: 68ch;
  font-size: var(--t-sub); color: var(--text-2); font-weight: 500;
}
/* Выключатель клавиш стоит в самой фразе про клавиши: отдельной строкой он
   читался бы как ещё одна настройка, которую надо где-то искать. Пока
   фраза шла во всю карточку, он оказывался в 1083-й точке — у правого
   края, вдалеке от слов, к которым относится. */
.keys-toggle {
  display: inline-flex; align-items: center; gap: 0.3rem;
  white-space: nowrap; cursor: pointer;
}
.keys-toggle input { margin: 0; }

/* Очередь общая: решение мог поставить сосед, пока вкладка была открыта. */
.alert {
  display: flex; gap: 0.6rem; align-items: flex-start;
  background: var(--warn-tint); color: var(--text);
  border-radius: var(--r-md); padding: 0.7rem 0.85rem;
  font-size: var(--t-call); line-height: 1.45; margin-bottom: 0.9rem;
}
.alert svg { width: 1rem; height: 1rem; flex-shrink: 0; margin-top: 0.15rem;
             color: var(--warn); }
/* Столкновение двух аналитиков — не предупреждение, а отказ: нажатие не
   применилось. Тон отличается от жёлтого, иначе сообщение читается как
   очередная подсказка и его пролистывают. */
.alert-conflict { background: var(--no-tint); }
.alert-conflict svg { color: var(--no); }

/* Заметка и причина отклонения над кнопками решения. В строку, а не
   столбиком: панель закреплена у низа экрана, и каждая её строка отнимает
   место у документа, ради которого аналитик сюда и пришёл. */
.decision-aside {
  display: flex; gap: 0.5rem; align-items: flex-start; flex-wrap: wrap;
  margin-bottom: 0.6rem;
}
.decision-aside textarea {
  flex: 1 1 20rem; width: auto; min-width: 0; resize: vertical;
}
.decision-aside select { flex: 0 1 16rem; }
/* Материал закрыт: править нечего, но заметку, ради которой его закрывали,
   читать надо — и она же должна уехать обратно в сохранении полей. */
.decision-aside textarea[readonly] { color: var(--text-2); resize: none; }

/* Отмена внутри тоста: форма не должна ломать строку текста. */
.inline-form { display: inline; }
.btn-link {
  background: none; border: 0; padding: 0; font: inherit; cursor: pointer;
  color: var(--accent); text-decoration: underline;
}
.btn-link:hover { text-decoration: none; }

/* --- уведомления --------------------------------------------------------- */

.notice {
  display: flex; gap: 0.7rem; align-items: flex-start;
  background: var(--surface); border-radius: var(--r-md);
  box-shadow: var(--shadow-sm);
  padding: 0.8rem 1rem; margin-bottom: 0.9rem;
  font-size: var(--t-call); line-height: 1.5;
}
.notice svg { width: 1.05rem; height: 1.05rem; flex-shrink: 0;
              margin-top: 0.15rem; }
.notice-ok svg    { color: var(--ok); }
.notice-warn svg  { color: var(--warn); }
.notice-error svg { color: var(--no); }
.notice p,
.notice > div { margin: 0; }
.notice p + p { margin-top: 0.35rem; }
.notice ul { margin: 0.4rem 0 0; padding-left: 1.1rem; }
.notice strong { font-weight: 600; }

/* Внешняя отбивка уменьшена ровно на ту, что ушла внутрь summary. */
details.tech-detail { margin-top: 0.25rem; }
details.tech-detail summary {
  cursor: pointer; font-size: var(--t-sub); color: var(--text-3);
  list-style: none; display: inline-flex; align-items: center; gap: 0.3rem;
  /* Соседние раскрывающие элементы уже стоят на своей отбивке, а этот жил
     одной высотой строки: 13 точек при межстрочном 1.5 — цель ростом 19.5
     при минимуме в 24. Отбивка уходит внутрь, к цели, а внешняя у самого
     `details` уменьшается на ту же величину: столбик не разъезжается. */
  padding: 0.25rem 0;
}
details.tech-detail summary::-webkit-details-marker { display: none; }
/* Шеврон нарисован в той же манере и толщине штриха, что и остальной набор
   иконок; юникодный знак здесь был единственным заимствованным символом. */
details.tech-detail summary::before {
  content: ""; display: inline-block; width: 0.6rem; height: 0.6rem;
  background-color: currentColor;
  -webkit-mask: var(--chevron) center / contain no-repeat;
  mask: var(--chevron) center / contain no-repeat;
  transition: transform 0.18s var(--ease);
}
details.tech-detail[open] summary::before { transform: rotate(90deg); }
details.tech-detail summary:hover { color: var(--text); }
details.tech-detail pre {
  margin: 0.5rem 0 0; font-family: var(--font-mono); font-size: var(--t-foot);
  color: var(--text-2); background: var(--sunken); border-radius: var(--r-sm);
  padding: 0.6rem 0.75rem; white-space: pre-wrap; overflow-wrap: anywhere;
  max-height: 16rem; overflow-y: auto;
}

/* --- сравнение оригинала и карточки -------------------------------------- */

.compare { }
.compare-head {
  display: grid; grid-template-columns: 10rem 1fr 1fr; gap: 1rem;
  padding: 0.7rem 1.1rem 0.4rem;
  font-size: var(--t-foot); font-weight: 600; color: var(--text-3);
}
.compare-row {
  position: relative;
  display: grid; grid-template-columns: 10rem 1fr 1fr; gap: 1rem;
  /* Оригинал и карточка — пара: читают слева, пишут справа, сверяя строку
     со строкой. По верху они расходились по высоте — у реферата 39 точек
     слева против 156 справа, — и между коробками оставалась дыра в 117
     точек. Тянутся до высоты строки обе. */
  padding: 0.85rem 1.1rem; align-items: stretch;
}
.compare-row + .compare-row::before {
  content: ""; position: absolute; top: 0; left: 1.1rem; right: 1.1rem;
  height: 1px; background: var(--sep);
}
.compare-label {
  font-size: var(--t-sub); color: var(--text-2); padding-top: 0.5rem;
  font-weight: 500;
}
.compare-original {
  background: var(--sunken); border-radius: var(--r-sm);
  padding: 0.55rem 0.7rem; white-space: pre-wrap;
  font-size: var(--t-call); line-height: 1.55; color: var(--text-2);
  max-height: 12rem; overflow-y: auto;
}
.compare-row--tall .compare-original { max-height: 18rem; }
/* Источник поля не дал — и залитая коробка под заголовком «Оригинал — что
   пришло» обещает данные, которых нет: пустая ячейка носила поверхность
   содержимого. Поверхность снимаем, отступы оставляем — строка стоит там
   же, где текст в соседних, заполненных. */
.compare-original.is-empty { background: none; color: var(--text-3); }

/* Тянется до высоты строки поле для абзаца, а не для строки: однострочный
   ввод ростом в 156 точек — не пара реферату, а недоразумение. */
.compare-editable { display: flex; flex-direction: column; }
.compare-editable input, .compare-editable textarea { width: 100%; }
.compare-editable textarea { flex: 1 1 auto; }

/* --- полоса работы с текстом ---------------------------------------------
   Чтение и правка — разная работа, и делить между ними место поровну
   неправильно. Замер по базе: у оригинала медиана 850 знаков, p90 — 6014;
   у текста, который пишет аналитик, медиана 679. Читать надо вдесятеро
   больше, чем писать. В общей сетке обе панели получали по 454 точки при
   высоте 288, и от документа было видно 26% — на p90 было бы около 5%.

   Поэтому текст статьи выходит из сетки коротких реквизитов: подпись
   строкой сверху (её колонка в 10rem возвращается тексту), оригинал
   забирает остаток ширины, поле правки остаётся колонкой шириной с абзац —
   расширять его вслед за окном незачем, строка длиннее 75 знаков читается
   хуже, а писать в неё надо семьсот знаков, не шесть тысяч. */
.compare-row--text { grid-template-columns: minmax(0, 1fr); gap: 0.5rem; }
.compare-row--text .compare-label { padding-top: 0; }

/* Оригинал и карточка — две колонки ОДНОЙ фиксированной высоты, у каждой
   своя прокрутка. Прежде высоту держал один потолок в min(60vh, 46rem) на
   панели документа: полэкрана на одно поле, и всё, ради чего аналитик сюда
   пришёл, — кнопки решения и реквизиты — оказывалось вытолкнуто ниже сгиба.
   Теперь высота полосы задана числом, а документ едет внутри своей колонки.

   Ручка изменения размера переехала с документа на саму полосу: потолок
   один на пару, и тянуть его порознь значило бы расстроить колонки. */
.card-columns, .text-panes {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(20rem, 26rem);
  gap: 1rem; align-items: stretch;
  height: var(--columns-h);
  overflow: hidden;
  resize: vertical;
}
/* Без `min-height: 0` элемент сетки не сжимается ниже своего содержимого, и
   прокрутка внутри колонки не появляется вовсе. */
.card-column, .text-panes > .pane { min-height: 0; overflow: hidden; }
/* Высоту полосы задаёт один уголок — тот, что на панели оригинала: тянут
   её ради документа, а поле правки идёт следом. Второй уголок на поле
   означал бы два независимых потолка у одной пары. */
.pane-card .compare-editable { flex: 1 1 auto; min-height: 0; }
.pane-card .compare-editable textarea { resize: none; }
.pane { display: flex; flex-direction: column; gap: 0.35rem; min-width: 0; }
/* Шапка столбцов стоит наверху карточки и равняется по коротким строкам;
   сюда, на две строки ниже, она не достаёт — поэтому у каждой панели своя
   короткая подпись. Полная формулировка («что пришло», «что уйдёт дальше»)
   уже сказана выше и повторения не требует. */
.pane-cap {
  margin: 0; font-size: var(--t-foot); font-weight: 600; color: var(--text-3);
}

/* Высоту держит полоса, а не панель: обе колонки ростом с неё. */
.pane-source .compare-original { max-height: none; }
/* Переключатель видов остаётся на месте, прокручивается только документ. */
.pane-source .original-views {
  display: flex; flex-direction: column;
  flex: 1 1 auto; min-height: 0; max-height: none; overflow: hidden;
}
.pane-source .view-scroll {
  overflow-y: auto; flex: 1 1 auto; min-height: 0;
  /* Пробелы шаблона между видами здесь не значащие. Обёртка блочная и
     наследует `pre-wrap` от ячейки оригинала — переводы строк между тремя
     `.view-body` рисовались пустыми строками и добавляли 238 точек пустоты
     сверху документа. Значащие пробелы живут внутри самих видов, у них своё
     правило. */
  white-space: normal;
}

/* Столбец карточки у читателя. Формы ему не показывают, а класса на этот
   случай не было вовсе: столбец шёл голым текстом без подложки и полей —
   рядом с залитым оригиналом это выглядело как незагрузившаяся половина.
   Повторяем поверхность поля ввода: пара «оригинал — карточка» должна
   читаться так же, как у аналитика, только без возможности править. */
.compare-readonly {
  background: var(--sunken); border: 1px solid var(--sep);
  border-radius: var(--r-sm); padding: 0.45rem 0.65rem;
  font-size: var(--t-body); line-height: 1.55; white-space: pre-wrap;
  max-height: 12rem; overflow-y: auto;
  /* Незаполненное поле — тоже поле: без нижнего предела пустая ячейка
     схлопывалась в полоску в 16 точек, и «поле пустое» не отличалось от
     «поля здесь нет». Ростом с обычный орган управления. */
  min-height: var(--control-h);
}
.compare-row--tall .compare-readonly { max-height: 18rem; }

.form-footer {
  display: flex; align-items: center; gap: 0.7rem; flex-wrap: wrap;
  padding: 0.9rem 1.1rem; border-top: 1px solid var(--sep);
}
.save-status { font-size: var(--t-sub); color: var(--ok); font-weight: 500; }
/* Сервер отказал — сказать об этом надо словами: htmx настроен не
   подставлять ответы 4xx/5xx, и без этой строки неудавшееся сохранение
   выглядело бы точно так же, как удавшееся. */
.save-status-error { color: var(--no); }

/* Автосохранение по уходу из поля. Состояние стоит у самого поля, потому
   что оно про то, что аналитик только что напечатал, — но цветом кольца
   ничего не СКАЗАНО: слова живут в общей строке #save-status с ролью
   `status`/`alert` (WCAG 2.1 AA, PRODUCT.md), а кольцо их лишь усиливает. */
.field--saving input, .field--saving textarea {
  border-color: var(--sep-strong);
}
.field--saved input, .field--saved textarea {
  border-color: var(--ok); box-shadow: 0 0 0 3px var(--ok-tint);
}
.field--conflict input, .field--conflict textarea {
  border-color: var(--warn); box-shadow: 0 0 0 3px var(--warn-tint);
}
.field--saved input, .field--saved textarea,
.field--conflict input, .field--conflict textarea {
  transition: box-shadow 0.35s var(--ease), border-color 0.35s var(--ease);
}
@media (prefers-reduced-motion: reduce) {
  .field--saved input, .field--saved textarea,
  .field--conflict input, .field--conflict textarea { transition: none; }
}

/* --- реквизиты ----------------------------------------------------------- */

.rows { }
.data-row {
  position: relative;
  display: grid; grid-template-columns: 12rem 1fr; gap: 1rem;
  padding: 0.65rem 1.1rem;
}
.data-row + .data-row::before {
  content: ""; position: absolute; top: 0; left: 1.1rem; right: 1.1rem;
  height: 1px; background: var(--sep);
}
.data-key { font-size: var(--t-call); color: var(--text-2); }
.data-val { font-size: var(--t-call); overflow-wrap: anywhere; }
.data-val.num { font-variant-numeric: tabular-nums; }

/* Строка расхода на модель в «Сводке»: сумма за скользящее окно и тариф,
   по которому она посчитана. Числа табличными цифрами — их сравнивают
   глазами со вчерашними; тариф идёт следом строкой ниже и тише, потому что
   это обстоятельство суммы, а не второе число.

   Просроченный тариф помечен И словом (оно в шаблоне), и тоном: цвет здесь
   усиливает предупреждение, но в одиночку не работает — WCAG 2.1 AA из
   PRODUCT.md. */
.spend-note {
  margin: 0; line-height: 1.5; font-variant-numeric: tabular-nums;
}
.spend-note .quiet { display: block; font-variant-numeric: normal; }
.spend-note--stale {
  border-left: 2px solid var(--warn); padding-left: 0.6rem;
}
.spend-note--stale > span:last-child { color: var(--warn); font-weight: 500; }

.links { list-style: none; margin: 0; padding: 0; }
.links li { padding: 0.5rem 1.1rem; position: relative; }
.links li + li::before {
  content: ""; position: absolute; top: 0; left: 1.1rem; right: 1.1rem;
  height: 1px; background: var(--sep);
}

/* --- анализ -------------------------------------------------------------- */

.analysis-head {
  display: flex; align-items: center; gap: 0.55rem; flex-wrap: wrap;
  margin-bottom: 0.5rem;
}
.analysis-head h2 { font-size: var(--t-head); font-weight: 600; }
.machine-line { font-size: var(--t-foot); color: var(--text-3);
                margin: 0.7rem 0 0; }
.prose { max-width: 62ch; line-height: 1.55; }

/* --- пустое состояние ---------------------------------------------------- */

.empty {
  text-align: center; padding: 3.5rem 1.5rem; color: var(--text-2);
}
.empty-glyph {
  width: 2.75rem; height: 2.75rem; margin: 0 auto 0.9rem;
  border-radius: var(--r-pill); background: var(--neutral-tint);
  display: grid; place-items: center; color: var(--text-3);
}
.empty-glyph svg { width: 1.35rem; height: 1.35rem; }
.empty h2 { font-size: var(--t-head); margin-bottom: 0.3rem; }
.empty p { margin: 0; font-size: var(--t-call); color: var(--text-3); }

/* --- индикатор занятости ------------------------------------------------- */

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

.spinner {
  width: 1rem; height: 1rem; border-radius: 50%; flex-shrink: 0;
  /* `--sep` почти невидим на тёмном фоне (10% белого): от кольца оставалась
     только акцентная дуга, и на скриншоте её приняли за мигающий курсор.
     Трек — `--text-3`, он уже достаточно плотный без надбавки прозрачности. */
  border: 2px solid var(--text-3); border-top-color: var(--accent);
  animation: spin 0.7s linear infinite;
}
@keyframes spin { to { transform: rotate(360deg); } }

.loading-row { display: flex; align-items: center; gap: 0.5rem; }

/* --- источники ----------------------------------------------------------- */

details.level { position: relative; }
details.level + details.level::before {
  content: ""; position: absolute; top: 0; left: 1.1rem; right: 0;
  height: 1px; background: var(--sep);
}
details.level > summary {
  cursor: pointer; padding: 0.8rem 1.1rem; list-style: none;
  display: flex; align-items: center; gap: 0.6rem;
  /* Строка источника переносится. Не переносилась — и состояние сбора
     («успех 08.09 04:12», «Требует внимания») уезжало за правый край на
     68 точек, где карточка его обрезала: раздел открывают ровно ради этого
     состояния, а на узком экране его не было видно вовсе. */
  flex-wrap: wrap;
  transition: background 0.15s var(--ease);
}
details.level > summary::-webkit-details-marker { display: none; }
details.level > summary:hover { background: var(--sunken); }
.level-caret { flex-shrink: 0; color: var(--text-3);
               transition: transform 0.18s var(--ease); }
details.level[open] > summary .level-caret { transform: rotate(90deg); }
.level-title { font-weight: 500; font-size: var(--t-body); }
.level-meta {
  margin-left: auto; display: flex; align-items: center; gap: 0.75rem;
  flex-wrap: wrap; font-size: var(--t-sub); color: var(--text-3);
}
.level-body { padding: 0.25rem 1.1rem 1.1rem 2.4rem; }
.page-level > summary { font-weight: 400; }

/* Состояние источника — своя ось, отдельная от решения аналитика. */
.mark {
  display: inline-flex; align-items: center; gap: 0.35rem;
  font-size: var(--t-foot); font-weight: 500; white-space: nowrap;
}
.mark::before {
  content: ""; width: 0.5rem; height: 0.5rem; border-radius: 50%;
  background: currentColor; flex-shrink: 0;
}
.mark-ok   { color: var(--ok); }
.mark-warn { color: var(--warn); }
.mark-fail { color: var(--no); }
.mark-off  { color: var(--text-3); }
/* Выбранный отбор на «Источниках». Раньше различие делалось гашением
   невыбранных, и они проваливали контраст вдвое; теперь подсвечивается
   выбранный, а остальные остаются в своём полном цвете. */
.mark-on {
  background: var(--neutral-tint);
  border-radius: var(--r-pill);
  padding: 0.125rem 0.5rem;
  font-weight: 600;
}
.mark-off::before { background: transparent; border: 1.5px solid currentColor; }
.mark-warn::before { border-radius: 2px; }
.mark-fail::before { border-radius: 1px; transform: rotate(45deg); }

.reason { font-size: var(--t-sub); color: var(--warn); margin: 0.25rem 0; }
.reason-fail { color: var(--no); }
/* Принятое — не предупреждение: приглушённым, чтобы не звало чинить. */
.reason-accepted { color: var(--text-3); }

.tech {
  background: var(--sunken); border-radius: var(--r-md);
  padding: 0.85rem 1rem; margin-top: 0.7rem;
  /* Ширину этому блоку раздаёт не окно, а глубина разворотов: ведомство и
     страница берут по 17.6 точки поля с каждой стороны, сама подложка — ещё
     по 16, и на телефоне от 320 точек здесь остаётся 186. Поэтому мера для
     вложенного — контейнерная, а не оконная: та же таблица на карточке
     источника шире, чем в двойном развороте раздела. */
  container-type: inline-size; container-name: tech;
}

/* Таблица «подпись → значение»: адрес списка, шаблон, полнота извлечения.
   Подписи в таблицах не переносятся (`th { white-space: nowrap }` ниже), и
   «Первичное наполнение» забирало 171 точку из 186 — значению оставался
   один глиф. Адрес списка вставал лесенкой шириной 11 точек и высотой 621:
   столбец букв, в котором не прочесть даже имя домена. Здесь подпись
   переносится, и ширину делят по смыслу, а не по самому длинному слову.
   На широком экране подпись и так стоит в строку — там правка молчит. */
.tech .kv-table th { white-space: normal; }

/* А когда на таблицу остаётся меньше 22rem, пара «подпись → значение»
   перестаёт быть парой столбцов и становится парой строк: значению
   достаётся вся ширина — 159 точек вместо 11, адрес в две строки вместо
   тридцати. Журнал запусков это не задевает: у него шапка, и он остаётся
   таблицей, которую прокручивают. */
@container tech (max-width: 22rem) {
  .tech .kv-table,
  .tech .kv-table tbody,
  .tech .kv-table tr,
  .tech .kv-table th,
  .tech .kv-table td { display: block; }
  .tech .kv-table th { padding-bottom: 0; border-bottom: none; }
  .tech .kv-table td { padding-top: 0.1rem; }
}

/* Причина отказа — проза, а не строка сетки. Ячейка растянута на все шесть
   столбцов, то есть на 477 точек при видимых 186: две трети каждой строки
   стояли за краем, и самое нужное на красном источнике читалось протяжкой
   вправо-влево на каждой строке. Ширину тексту задаёт видимая часть блока,
   а не таблица; сама сетка не трогается — её столбцы прокручиваются как
   были. */
.tech table td.reason > .note-text {
  display: block; max-width: calc(100cqw - 1.2rem);
}
/* Конфигурация длиннее своего окна и обрезается по высоте. Без собственной
   подложки и полей строка просто обрывалась на середине и читалась как
   поломка, а не как прокручиваемый блок: даём ему видимый край. */
.tech pre, .level-body > pre {
  margin: 0.4rem 0 0; font-family: var(--font-mono); font-size: var(--t-foot);
  line-height: 1.6;
  color: var(--text-2); white-space: pre-wrap; overflow-wrap: anywhere;
  /* Высота кратна строке: 13 строк по 1.6em плюс поля, иначе панель
     заканчивалась на середине глифа и это читалось как обрыв. */
  max-height: calc(13 * 1.6em + 1.2rem + 2px); overflow-y: auto;
  background: var(--surface); border: 1px solid var(--sep);
  border-radius: var(--r-sm); padding: 0.6rem 0.75rem;
}

table { width: 100%; border-collapse: collapse; }
th, td {
  text-align: left; padding: 0.45rem 0.6rem; font-size: var(--t-sub);
  border-bottom: 1px solid var(--sep); vertical-align: top;
}
tr:last-child > th, tr:last-child > td { border-bottom: none; }
th { font-weight: 500; color: var(--text-3); white-space: nowrap; }
td { overflow-wrap: anywhere; }
td.num, .tabular { font-variant-numeric: tabular-nums; }

.gauge {
  display: inline-block; width: 3.5rem; height: 5px; border-radius: var(--r-pill);
  background: var(--neutral-tint); overflow: hidden; vertical-align: middle;
  margin-right: 0.45rem;
}
.gauge > span { display: block; height: 100%; background: var(--ok); }
.gauge.low > span { background: var(--warn); }

/* --- утилиты ------------------------------------------------------------- */

.quiet { color: var(--text-3); }
.small { font-size: var(--t-sub); }
.nowrap { white-space: nowrap; }
.row-inline { display: flex; gap: 0.6rem; flex-wrap: wrap;
              align-items: center; margin: 0.7rem 0; }
/* Полоса прокрутки — область, а не безымянная обёртка. Таблица шире окна
   (на сводке за краем 320 точек из 608, в журнале запусков — 280 из 466), а
   дотянуться до правого края может только палец или мышь: у div нет ни
   фокуса, ни имени, и с клавиатуры область не прокручивается вовсе. Имя и
   фокус даёт разметка (role, aria-labelledby, tabindex), обводку и
   подсказку — здесь.

   Обводка внутрь. `.card` обрезает по себе (`overflow: hidden`), а полоса на
   сводке лежит ровно по его границе: обводка со смещением наружу срезалась
   бы целиком, и фокус стал бы невидимым — то есть клавиатура получила бы
   доступ, но не увидела, куда попала.

   Подсказка о продолжении — тень у края под покрывалом цвета подложки.
   Покрывало едет вместе с содержимым (`local`), тень стоит на месте
   (`scroll`): пока прокручивать нечего, они совпадают и не видно ничего;
   ушли вправо — слева проступила тень. Ничего не считает и не слушает.

   `overscroll-behavior-x: contain` — чтобы протяжка таблицы у края не
   срабатывала как системный жест «назад» и не уносила со страницы. */
.scroll-x {
  overflow-x: auto;
  overscroll-behavior-x: contain;
  --scroll-bg: var(--surface);
  background:
    linear-gradient(to right, var(--scroll-bg), transparent) 0 0 / 1.5rem 100% no-repeat local,
    linear-gradient(to left,  var(--scroll-bg), transparent) 100% 0 / 1.5rem 100% no-repeat local,
    radial-gradient(farthest-side at 0 50%,   var(--sep-strong), transparent) 0 0 / 0.5rem 100% no-repeat scroll,
    radial-gradient(farthest-side at 100% 50%, var(--sep-strong), transparent) 100% 0 / 0.5rem 100% no-repeat scroll;
}
/* Техническая часть лежит на утопленной подложке — покрывало должно быть её
   цвета, иначе у края встанет светлая полоса. */
.tech .scroll-x { --scroll-bg: var(--sunken); }
.scroll-x:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
.gap-above { margin-top: 1rem; }
/* Две карточки подряд разделяются сами. Раньше отступ ставили руками
   классом, и на новой странице его просто забывали: блоки склеивались в
   один, и граница между «оглавлением» и «текстом» пропадала. Карточки,
   разделённые заголовком раздела, соседями не считаются — их и не задевает. */
main .card + .card { margin-top: 1rem; }
/* …но не в сетке. Там расстояние задаёт `gap`, а `margin-top` ячейки не
   раздвигает — он сдвигает карточку внутри её собственной ячейки. Первая
   в ряду оставалась на месте, каждая следующая уезжала на 16 точек вниз, и
   ряд шёл ступенькой. Заметно это стало на «Проверке», где сеток четыре,
   но верно для любой сетки карточек. */
.check-grid > .card + .card { margin-top: 0; }
.gap-none { margin: 0; }

/* --- узкие экраны -------------------------------------------------------- */

@media (max-width: 62rem) {
  .compare-head { display: none; }
  .compare-row { grid-template-columns: 1fr; gap: 0.4rem; }
  .compare-label { padding-top: 0; font-weight: 600; color: var(--text); }
  /* Рядом две панели не помещаются: оригинал ужался бы до полосы, ради
     ухода от которой полоса чтения и заводилась. Идут друг под другом, и
     каждой достаётся вся ширина. Потолок высоты ниже: два блока по 60vh
     подряд — это две прокрутки экрана только на одно поле. */
  .card-columns, .text-panes {
    grid-template-columns: minmax(0, 1fr);
    height: auto; resize: none; overflow: visible;
  }
  .pane-source .view-scroll { max-height: 22rem; }
  .pane-card .compare-editable textarea { resize: vertical; }
}

@media (max-width: 48rem) {
  /* Шапка на телефоне стоит в три строки и занимает 156 точек — одинаково
     на всех разделах. Отсюда и запас: 168, то есть с воздухом в 12 точек.
     Число привязано к составу шапки; поменяется состав — пересчитать. */
  :root { --t-display: 1.5rem; --anchor-offset: 10.5rem; }
  .toolbar-inner, .page { padding-left: 1rem; padding-right: 1rem; }
  .toolbar-inner { gap: 0.6rem; }
  /* Сводка уходит на свою строку, переключатель остаётся в первой и держится
     правого края — одинаково на списке и на карточке. */
  /* Четвёртой, а не третьей: ниже 430 точек свои строки занимают разделы
     (2) и ссылки на правила со справкой (3). Сводка смены замыкает шапку —
     она справочная, а не навигационная. */
  .stats { order: 4; flex: 1 1 100%; }
  .icon-btn { margin-left: auto; }
  /* Точка-разделитель нужна только когда группе фактов что-то предшествует
     в той же строке; на узком экране она открывала строку сиротой. */
  .row-meta > .facts::before { content: none; }
  /* Каждое действие — своя форма, и делить ширину должна она: кнопка внутри
     неё сама по себе полосы не займёт. По две в ряд, а не по одной: четыре
     полосы подряд — это уже экран, отданный кнопкам. */
  .decision-actions > form { flex: 1 1 45%; }
  .btn-decision { flex: 1 1 auto; min-width: 0; }
  .decision-actions { justify-content: stretch; }
  .data-row { grid-template-columns: 1fr; gap: 0.15rem; }
  /* Поля фильтра тянутся на всю ширину: иначе под полноширинным
     «Ведомством» оставался рваный правый край. Раскладку самой панели
     держит сетка — доли ей задавать вручную больше не нужно. */
  .field { flex: 1 1 100%; }
  .level-body { padding-left: 1.1rem; }
}

/* Закреплённая панель решения закрывает экран целиком — то есть прячет сам
   материал, ради которого она и нужна. По ширине это телефон; по высоте — он
   же боком (844×390) и окно 1920×1080 при увеличении вдвое (960×540): 844
   больше 768, и правило по ширине их не ловит. Условия перечислены через
   запятую, а не вторым блоком: правила одни и те же, и разъезжаться им
   незачем. Здесь панель просто замыкает страницу, как всё остальное. */
@media (max-width: 48rem), (max-height: 36rem) {
  .decision--sticky { position: static; max-height: none; overflow: visible; }
  .page:has(> .decision--sticky) { padding-bottom: var(--page-pad-b); }
  /* Панель низа окна не занимает — и запас прокрутки под неё больше не нужен. */
  html:has(.decision--sticky) { scroll-padding-bottom: 0; }
}

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
  }
}

/* Правка конфигурации адаптера прямо в разделе «Источники». Моноширинный
   ввод: правят здесь JSON с CSS-селекторами, а пропорциональный шрифт
   прячет лишний пробел внутри `div.list > li` ровно там, где он всё
   ломает. */
.config-input {
  font-family: ui-monospace, "SF Mono", Menlo, Consolas, monospace;
  font-size: 0.82rem;
  tab-size: 2;
  min-height: 12rem;
}
.config-actions {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  flex-wrap: wrap;
  margin-top: 0.6rem;
}

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

.filters > * + * { border-top: 1px solid var(--sep); }

/* Первый ярус: статус. Тот же сегментированный переключатель, что у
   разделов в шапке — это и есть навигация по очереди, а не поле формы. */
.status-bar {
  display: flex; margin: 0.7rem 1.1rem; flex-wrap: wrap;
  border-radius: var(--r-md);
}
.filters > .status-bar { border-top: 0; }

/* Первый ярус: поиск. Во всю ширину — искать в нём будут заголовок или
   номер документа, а не одно слово. */
.filter-search {
  display: flex; gap: 0.5rem; align-items: center; padding: 0.75rem 1.1rem;
}
.filter-search input { flex: 1 1 auto; min-width: 0; }

/* Второй ярус: что сейчас включено. Строка переносится и никогда не
   обрезается — условие, спрятанное за краем, хуже отсутствующего. */
.chips-on {
  display: flex; align-items: center; gap: 0.4rem; flex-wrap: wrap;
  padding: 0.6rem 1.1rem;
}
.chip-off {
  display: inline-flex; align-items: center; gap: 0.35rem;
  padding: 0.2rem 0.5rem 0.2rem 0.6rem; border-radius: var(--r-pill);
  background: var(--accent-tint); color: var(--accent);
  font-size: var(--t-foot); font-weight: 500; text-decoration: none;
  max-width: 100%; overflow-wrap: anywhere;
}
.chip-off:hover { text-decoration: none; background: var(--accent);
                  color: var(--accent-text); }
.chip-off svg { width: 0.7rem; height: 0.7rem; flex-shrink: 0; opacity: 0.75; }
/* Двоеточие стоит в разметке, а не в псевдоэлементе: чип читают вслух
   экранным диктором и копируют мышью, а `content` не попадает ни туда, ни
   туда — выходило «ВедомствоChina Iron and Steel Association». */
/* Имя условия не рвётся: на узком экране «Ведомство:» переносилось так, что
   двоеточие уезжало на строку к значению. */
.chip-key { color: inherit; opacity: 0.7; white-space: nowrap; }

/* Третий ярус: редкие условия. Свёрнуты — но раскрываются сами, когда
   что-то включено: иначе отбор действовал бы из закрытого ящика. */
.filter-more > summary {
  cursor: pointer; list-style: none; padding: 0.7rem 1.1rem;
  display: flex; align-items: center; gap: 0.45rem;
  font-size: var(--t-sub); color: var(--text-2);
  transition: background 0.15s var(--ease);
}
.filter-more > summary::-webkit-details-marker { display: none; }
.filter-more > summary:hover { background: var(--sunken); color: var(--text); }
.filter-more[open] > summary { color: var(--text); }
.filter-more[open] > summary .level-caret { transform: rotate(90deg); }
.filter-more .level-caret { display: inline-flex; }
.filter-more .level-caret svg { width: 0.7rem; height: 0.7rem; display: block; }
.chip-count {
  display: inline-grid; place-items: center; min-width: 1.25rem;
  padding: 0 0.3rem; height: 1.25rem; border-radius: var(--r-pill);
  background: var(--accent); color: var(--accent-text);
  font-size: var(--t-cap); font-weight: 700;
}
/* Поля панели — сеткой, а не флекс-мешком. Мешок переносил элементы там,
   где кончалось место: пока полей было девять и все одной формы, это
   работало, но списку, паре дат и кнопке нужны разные доли, и раскладка
   рассыпалась. У сетки колонки общие для всех рядов, поэтому подписи
   стоят по одной вертикали, а не там, куда упал перенос. */
/* Порог колонки — 20rem, а не «сколько влезет». При 13rem на полосу в 1101
   точку вставало пять колонок, списков же три: два столбца пустовали, а
   «Ведомство» ужималось до 209 точек и обрезало «China Iron and Steel
   Association» на середине. С 20rem их ровно три, по 357 точек, и ряд
   заполнен. Порог, а не жёсткое «repeat(3)»: на планшете колонок станет
   две, на телефоне одна, и правило переживёт четвёртый список. */
/* Порог колонки — предел, а не обязательство. 20rem считались от полосы в
   1101 точку, а на телефоне превращались в приказ «дай 320», которого
   карточке негде взять: содержимого у панели 253, дорожка вставала в 320, и
   правые 50 уходили под `overflow: hidden` карточки вместе со стрелкой
   списка. `min()` снимает требование ровно там, где его нечем выполнить;
   пока полоса шире 20rem, раскладка прежняя. */
.filter-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(20rem, 100%), 1fr));
  gap: 0.7rem 0.9rem; align-items: end;
  padding: 0.5rem 1.1rem 0.9rem;
}
/* Обе пары дат — одной строкой сетки на всю ширину. Иначе первая пара
   догоняла ряд списков, вторая уезжала на следующий ряд, и два условия
   про одно и то же событие оказывались в разных концах панели.

   Внутри — те же колонки, что снаружи: рамки дат встают ровно под
   «Ведомством» и «Отраслью», а не приставляются к левому краю своей
   шириной. Вертикаль в панели одна, и её видно.

   Здесь `auto-fill`, а не `auto-fit`, как у внешней сетки: `auto-fit`
   схлопывает пустые дорожки, и две рамки дат делили полосу пополам по 543
   точки вместо трёх колонок по 357 — вертикаль снова расходилась. */
.filter-dates {
  grid-column: 1 / -1;
  display: grid;
  /* Тот же предел и по той же причине: внутренняя сетка живёт в ячейке
     внешней, и без `min()` рамки дат вынесло бы за край карточки следом за
     списками — теперь уже в одиночку. */
  grid-template-columns: repeat(auto-fill, minmax(min(20rem, 100%), 1fr));
  gap: 0.7rem 0.9rem;
}

/* Границы одной даты — в одной рамке с именем. Различить «не раньше» и
   «поступил не раньше» можно было только по длине подписи, а на переносе
   строки рядом оказывались концы разных пар. */
.field-range {
  display: flex; gap: 0.5rem; align-items: flex-end;
  /* Паре дат нужно 312 точек, и до планшета они у рамки есть. На телефоне
     рамке достаётся 231, каждая дата ужимается до 111 и печатает
     «дд.мм.ггг» — год, ради которого фильтр и заводят, не помещается.
     Перенос отдаёт каждой границе всю ширину рамки; там, где пара
     помещается, он не срабатывает ни разу. */
  flex-wrap: wrap;
  margin: 0; padding: 0.45rem 0.6rem 0.55rem;
  border: 1px solid var(--sep); border-radius: var(--r-sm);
  /* У `fieldset` минимальная ширина по содержимому вшита в тег и не
     снимается общим `min-width: auto`. На 390 точках рамке доставалась
     колонка в 323, а она держала 339 и вылезала за край на 9. */
  min-width: 0;
}
.field-range legend {
  padding: 0 0.3rem; font-size: var(--t-foot); font-weight: 500;
  color: var(--text-2);
}
.field-range .field > label { text-align: center; }
/* Границы делят рамку пополам и ужимаются вместе с ней. Фиксированные
   9.5rem держались до 390 точек, где паре нужно 312 при доступных 302, и
   вторая дата вылезала за рамку. Основа та же, но с правом сжаться. */
.field-range .field { flex: 1 1 9.5rem; min-width: 0; }
.field-range input[type="date"] { width: 100%; }

/* Поиску нужно место: искать в нём будут заголовки, а не одно слово. */
.field-wide { flex: 1 1 16rem; }

/* Панель массовых действий стоит всегда, а не выезжает по первому
   выбранному. Выезжающая смещает список ровно в тот момент, когда по нему
   кликают, и следующий клик попадает не в ту строку. */
/* Полоса стоит всегда, но не всегда заряжена: пока не выбрано ни одной
   строки, нажимать в ней нечего. Приглушённая подложка говорит это раньше,
   чем аналитик успеет прицелиться, а при первом же выборе полоса
   подсвечивается — тем же акцентом, что и чипы отбора выше. */
.bulk-bar {
  display: flex; align-items: center; gap: 0.9rem; flex-wrap: wrap;
  padding: 0.55rem 0.85rem; margin-bottom: 0.6rem;
  background: var(--sunken); border-radius: var(--r-md);
  border: 1px solid transparent;
  transition: background 0.18s var(--ease), border-color 0.18s var(--ease);
}
.bulk-bar.is-armed { background: var(--accent-tint); border-color: var(--accent-tint); }

/* Ростом с прочие органы управления, как и всё в полосе: вход в работу с
   двумя сотнями строк имел 19 точек по высоте против 38 у любой кнопки
   рядом. На высоту самой полосы это не влияет — её и так задают кнопки. */
.bulk-all { display: flex; align-items: center; gap: 0.45rem; cursor: pointer;
            min-height: var(--control-h); }
.bulk-all span { font-size: var(--t-foot); color: var(--text-2); }
/* Счётчик стоит рядом с «выбрать всё» — это одна мысль, — а действия
   уходят вправо. Раньше авто-отступ висел на счётчике, и он оказывался
   посередине полосы, ни с чем не связанный. */
.bulk-count { font-size: var(--t-foot); color: var(--text-3); }
/* Выбрано двести строк — это уже не примечание. Приглушённым серым число
   читалось наравне с подписью чекбокса рядом. */
.bulk-count.is-armed { color: var(--text); font-weight: 600; }

/* Переносится, как и сама панель. Без этого группа из трёх кнопок и списка
   причин держала 552 точки в окне на 390 и вылезала за край страницы: на
   узком экране весь кабинет получал горизонтальную прокрутку. */
.bulk-actions {
  display: flex; gap: 0.5rem; flex-wrap: wrap; align-items: center;
  margin-left: auto;
  /* Переноса мало: свой минимум флекс-ребёнок считает по самому широкому
     неделимому куску, а пара «не подходит + причина» сжиматься не умела и
     держала 299 точек при 261 доступной. Полоса вылезала за край страницы,
     и весь кабинет получал прокрутку вбок. */
  min-width: 0;
}

/* «Не подходит» и причина — одна слитная пара: общий контур, между ними
   только шов. Порознь список стоял на равном расстоянии от обеих кнопок и
   с равным правом читался как причина для «подходит». */
/* Пара ужимается вместе с полосой, а не выталкивает её за экран. Шов между
   кнопкой и списком от этого не расходится: он держится радиусами и
   отрицательным полем ниже, а не шириной. */
.bulk-reject { display: inline-flex; max-width: 100%; min-width: 0; }
.bulk-reject .btn {
  border-top-right-radius: 0; border-bottom-right-radius: 0;
}
.bulk-reject select {
  border-top-left-radius: 0; border-bottom-left-radius: 0;
  /* Границы кнопки и списка сходятся в один шов, а не в два. */
  margin-left: -1px;
  font-size: var(--t-call);
  /* Ширину закрытого списка браузер считает по самому длинному пункту —
     здесь это «новость, а не документ», 204 точки. На широком экране пусть
     так и будет: лишнего места там некуда девать. На узком список отдаёт
     лишнее и обрезает пункт многоточием — тем же приёмом, что уже принят у
     полей отбора. */
  flex: 1 1 8rem; min-width: 0; text-overflow: ellipsis;
}
.bulk-reject select:focus-visible { position: relative; z-index: 1; }

/* Возврат в очередь отбит от решений: это отмена, а не третий исход. */
.bulk-undo { margin-left: 0.4rem; }

/* Недоступное видно недоступным — не только кнопки, но и список причин:
   выбирать причину, когда отклонять нечего, незачем. */
.bulk-actions select[disabled] {
  opacity: 0.5; cursor: not-allowed; background: var(--neutral-tint);
}

/* Область попадания больше самого квадратика: промах по чекбоксу в списке
   из двухсот строк стоит дороже, чем лишние восемь пикселей. */
.row-pick {
  display: flex; align-items: center; justify-content: center;
  padding: 0.15rem 0.1rem 0 0; cursor: pointer; flex: none;
  /* Заголовок строки растянут невидимым слоем на всю строку
     (`.row-title a::after`), а слой этот позиционированный и потому лежит
     поверх обычного содержимого — метка выбора оказывалась под ним, и
     касание открывало карточку вместо выбора. Одного `relative` мало: при
     равных z-index спор решает порядок в разметке, а ссылка в ней позже.
     Поднят ровно квадратик, 24 точки из 288: вся остальная строка
     по-прежнему открывает карточку. */
  position: relative; z-index: 1;
}
.row-pick input { width: 1rem; height: 1rem; cursor: pointer; }
/* Тем же слоем накрывало ссылки фактов — отрасль и «↗». У пальца нет ни
   средней кнопки, ни строки состояния: промах виден только потерянным
   местом в списке из двухсот строк. Накрытым остаётся то, ради чего слой и
   заводился, — текст. */
.row-meta a { position: relative; z-index: 1; }

.pager { display: flex; align-items: center; gap: 0.9rem; flex-wrap: wrap; }
/* Крайняя страница: кнопка остаётся на месте, чтобы соседняя не прыгала
   под курсор, но перестаёт откликаться. */
.is-disabled { opacity: 0.45; pointer-events: none; }

/* Отказ сохранить из-за чужой правки — не успех и не поломка: сделанное на
   экране цело, но в базу не ушло. Цвет предупреждения, а не ошибки. */
.save-status-conflict {
  color: var(--warn);
  font-weight: 500;
}

/* --- Раздел «Проверка» -------------------------------------------------
   Карточки состояния контура. Сетка, а не список: проверок восемь, и
   в столбик они занимают два экрана, между которыми ничего не сравнить. */

.check-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(19rem, 1fr));
  gap: 0.85rem;
  margin-bottom: 1.6rem;
  /* Порядок карточек задаёт сервер: сперва отказы, потом замечания, потом
     исправное. Раздел открывают с одним вопросом — что не так, — и ответ
     должен лежать в левом верхнем углу. */
  /* Карточка ростом по своему содержимому, а не по соседу. По умолчанию
     сетка равняет всю строку по самой высокой: рядом со «Сбором
     публикаций», который перечисляет 146 несобранных страниц, карточка
     «База данных» с одним словом «Отвечает» растягивалась на 3818 точек —
     почти четыре экрана пустой заливки. */
  align-items: start;
}

/* Заголовок и состояние — по краям одной строки. Состояние читают первым
   при беглом взгляде и последним при чтении, поэтому оно у правого края:
   столбец состояний выстраивается по вертикали и просматривается сверху
   вниз, не мешая читать названия. */
.check-head {
  display: flex; align-items: baseline; justify-content: space-between;
  gap: 0.75rem; margin-bottom: 0.4rem;
}
.check-head .mark { font-weight: 600; flex-shrink: 0; }

.check-title {
  font-size: var(--t-call);
  font-weight: 600;
  margin: 0;
}

.check-summary { margin: 0; color: var(--text-2); }

/* Отказ отличается поверхностью, а не только точкой в восемь точек. Восемь
   одинаковых карточек различались одним значком у заголовка, и отказ весил
   на экране ровно столько же, сколько норма. Тон, слово и форма метки —
   три носителя; цвет не работает в одиночку (WCAG, PRODUCT.md).

   Тонируется сама карточка, а не блок внутри неё: тонированная вставка
   внутри белой карточки давала рамку в рамке с полоской подложки по краю.
   Тон кладётся слоем поверх поверхности карточки, иначе полупрозрачный
   красный смешался бы с холстом, а не с ней.

   Подложка, а не полоса слева: цветная планка сбоку — самый узнаваемый
   признак сгенерированного интерфейса, и детектор её ловит.

   Тонируется только отказ. Если так же красить и замечания, страница
   становится цветной целиком, и разница между «чинить сейчас» и
   «посмотреть потом» пропадает — а она тут главная. */
.check-grid > .card:has(.check-fail) {
  background-color: var(--surface);
  background-image: linear-gradient(var(--no-tint), var(--no-tint));
  box-shadow: var(--shadow-sm), inset 0 0 0 1px var(--no-tint);
}
.check-warn .check-summary { color: var(--text); }

.check-lines {
  margin: 0.7rem 0 0;
  font-size: var(--t-sub);
  display: grid;
  gap: 0.1rem;
  /* Перечень ничем не ограничен, а длина его задана не вёрсткой, а
     состоянием контура: «ни разу не собирались» перечисляет каждую
     страницу, и на 146 строках карточка выходила выше трёх экранов —
     раздел, заведённый чтобы сравнивать проверки между собой, сравнить
     ничего не давал. Предел по высоте, как у технических блоков выше. */
  max-height: 15rem;
  overflow-y: auto;
}

/* Пара «метка — значение», а не пункт списка. Проверки отдают строки
   именно парами («страниц собрано: 197, с ошибкой: 17»), и маркер перед
   ними превращал данные в лог. Метка тише значения, значение — обычным
   цветом текста и табличными цифрами: столбец чисел читается сверху вниз. */
.check-line {
  display: grid; grid-template-columns: minmax(0, auto) minmax(0, 1fr);
  gap: 0.5rem; padding: 0.12rem 0;
}
.check-line dt { color: var(--text-3); }
.check-line dd {
  margin: 0; color: var(--text-2); overflow-wrap: anywhere;
  font-variant-numeric: tabular-nums;
}
/* Строка без двоеточия — целая фраза, а не пара: занимает обе колонки. */
.check-line--plain { grid-template-columns: minmax(0, 1fr); }

.check-hint {
  margin: 0.7rem 0 0;
  padding-top: 0.65rem;
  border-top: 1px solid var(--sep);
  font-size: var(--t-sub);
  color: var(--text-2);
}

/* Нижний предел роста карточки. Содержимое проверок разнится в разы — от
   одного слова «Отвечает» до перечня на пятнадцать строк, — и при росте
   строго по содержимому короткие карточки проваливались рядом с длинными
   рваными уступами. Растягивать всю строку по самой высокой нельзя: так
   карточка в 52 точки вытягивалась на 3818 и давала четыре экрана пустоты.
   Предел снизу выравнивает мелочь, не трогая крупное. */
.check-grid > .card { min-height: 8.5rem; }

/* Живая проверка — действие, а не результат: кнопка отбита от описания так
   же, как подвал формы от её полей. */
.probe-slot {
  margin-top: auto; padding-top: 0.75rem; display: grid; gap: 0.6rem;
  justify-items: start;
}
.check-grid > .card:has(.probe-slot) { display: flex; flex-direction: column; }

/* Пока запрос в пути, htmx держит на элементе класс htmx-request. Без
   отклика кнопка выглядит нажатой впустую, а сетевая проверка идёт
   секунды. */
.probe-slot .btn.htmx-request {
  opacity: 0.6;
  pointer-events: none;
}

/* Текст ожидания берётся из разметки: `content:` в CSS не переводится и
   на английском интерфейсе оставался русским. Кнопка несёт его в
   `data-busy`, а `attr()` подставляет. */
.probe-slot .btn.htmx-request::after {
  content: " — " attr(data-busy);
  font-weight: 400;
}

/* Итог живой пробы. Метка состояния несёт сам результат, а не название
   проверки: название уже стоит заголовком карточки выше. */
.check-result { margin: 0 0 0.1rem; }
.check-result .mark { font-weight: 600; align-items: flex-start; }

/* Негромкое действие: переподбор селекторов нужен редко — когда источник
   переверстался, — и не должен спорить за внимание с «Сохранить». */
.relearn {
  display: flex;
  align-items: baseline;
  gap: 0.5rem;
  flex-wrap: wrap;
  margin-top: 0.9rem;
  padding-top: 0.75rem;
  border-top: 1px solid var(--sep);
}

.btn-link {
  background: none;
  border: none;
  padding: 0;
  font: inherit;
  font-size: var(--t-sub);
  color: var(--accent);
  cursor: pointer;
  text-decoration: underline;
  text-underline-offset: 2px;
}

.btn-link:hover { text-decoration: none; }
.btn-link:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

/* Ссылка-кнопка бывает двух родов, и мерить их одинаково нельзя. Внутри
   строки текста — отмена в тосте, «Повторить» у упавшей задачи — это цель в
   предложении: WCAG её из минимального размера исключает, и растить её
   вредно, область нажатия налезет на соседние строки абзаца. А переподбор
   селекторов стоит сам, отдельной формой: 13 точек при межстрочном 1.5 дают
   цель ростом 19.5 при минимуме в 24, и на телефоне её спасал один лишь
   перенос подписи на вторую строку.
   `.btn-link` объявлен в файле дважды; действует это, второе, объявление —
   поэтому правка стоит здесь, а не у первого. */
.relearn .btn-link { display: inline-flex; align-items: center; min-height: 1.5rem; }

/* --- вход ----------------------------------------------------------------
   Единственная страница кабинета без списка, и единственная форма вне
   `.controls`. Своей ширины у неё не было: карточка растягивалась на все
   74rem полосы, поля ввода — вместе с ней, а два `.field` шли встык, потому
   что вертикальную отбивку в `.controls` даёт флекс-контейнер, которого
   здесь нет. Ширину задаёт поле ввода, а не ширина кабинета. */

.login-card {
  max-width: 21rem;
  margin: max(6vh, 2rem) auto 0;
  padding: 1.4rem 1.5rem 1.6rem;
}

.login-card .page-title { margin-top: 0; }
.login-card .page-sub { margin-bottom: 1.3rem; }
.login-card .notice { margin-bottom: 1.1rem; padding: 0.7rem 0.85rem; }

.login-form { display: grid; gap: 0.85rem; }
.login-form input { width: 100%; }

/* Кнопка во всю ширину карточки: действие здесь ровно одно, и целиться
   в него мышью незачем. */
.login-form .btn { width: 100%; margin-top: 0.25rem; }

/* --- сводка ---------------------------------------------------------------
   Разбивка по ведомствам — рабочая поверхность, а не отчётная простыня.
   Планка задана обязательствами бренда в PRODUCT.md: Linear, Height, Notion;
   воздух и спокойствие, мягкая геометрия, акцент только там, где он несёт
   состояние. В таблице «воздух» — это в первую очередь высота строки, во
   вторую — то, чего в ней НЕТ.

   Ширины по-прежнему раздаёт браузер: с `table-layout: fixed` числовые
   столбцы делили остаток поровну, и на пустой таблице длинные заголовки не
   влезали в свою долю и вылезали за край карточки. */
.summary-table { width: 100%; border-collapse: collapse; table-layout: auto; }

/* Строка выросла с 33 до 46 точек. Тридцать три — плотность табличного
   редактора: данные стоят стеной, и глаз не отделяет строку от строки без
   линии-подпорки под каждой. По верхнему краю, а не по центру: первые
   строки всех ячеек встают на одну базовую линию, включая ту единственную,
   где название ведомства переносится на три строки. */
.summary-table th, .summary-table td {
  padding: 0.55rem 0.6rem; text-align: left; vertical-align: top;
}
/* Крайние столбцы отбиты от края карточки на те же 1.1rem, что и строки
   списка материалов: у кабинета одна левая вертикаль на всех экранах. */
.summary-table th:first-child, .summary-table td:first-child { padding-left: 1.1rem; }
.summary-table th:last-child, .summary-table td:last-child { padding-right: 1.1rem; }

/* Имя ведомства ведёт строку, отрасль ей подчинена. До этого вся строка шла
   одним кеглем в 13 точек — зацепиться было не за что, и восемьдесят строк
   читались одинаково серой массой.

   Переносится по словам и по слогам: «National Forestry and Grassland
   Administration» иначе держал бы столбец в одну строку и выталкивал бы
   таблицу за край карточки. */
.summary-table td:first-child {
  font-size: var(--t-call); font-weight: 500; color: var(--text);
  overflow-wrap: anywhere; hyphens: auto;
}
/* Название — обычным текстом, а не синей ссылкой. Ссылка здесь каждая
   строка, и восемьдесят синих подряд читались стеной цвета, в которой не
   видно ни одной. Так же решено в списке материалов (`.row-title a`). */
.summary-table td:first-child a { color: inherit; text-decoration: none; }
.summary-table tbody tr:hover td:first-child a {
  text-decoration: underline; text-underline-offset: 0.18em;
  text-decoration-color: var(--sep-strong);
}

/* Отрасль — в одну строку и тише имени. Значений восемь, самое длинное
   «энергетика и экология»; перенос давал строки то в 15 точек, то в 26.

   Классом, а не по счёту. Позиционный селектор писался под столбец,
   который есть не всегда: под фильтром по отрасли столбец не рисуется, и
   вторым становится «поступило» — оно уезжало в приглушённый серый, тогда
   как соседнее «ожидает» с тем же числом оставалось белым. Два одинаковых
   числа в одной строке разного цвета. */
.summary-table .col-sector {
  white-space: nowrap; font-size: var(--t-foot); color: var(--text-3);
}

/* Строка отзывается на курсор. Каждая из них — ссылка в очередь по этому
   ведомству, и до сих пор поверхность на восемьдесят ссылок не отвечала
   ничем. Отклик тот же, что у строки списка материалов, и та же длительность
   — у кабинета один словарь состояний. */
.summary-table tbody tr { transition: background 0.15s var(--ease); }
.summary-table tbody tr:hover,
.summary-table tbody tr:focus-within { background: var(--sunken); }

/* Шапка. Мельче строки и с разрядкой: это подписи к столбцам, а не данные,
   и различать их надо не размером, а характером.

   По нижнему краю, а не по верхнему: «Ведомство» и «Отрасль» растянуты на
   обе строки шапки (`rowspan`), и при выравнивании по верху они висели в
   63-точечной шапке с сорока точками пустоты под собой. */
.summary-table thead th {
  position: sticky; top: 0; background: var(--surface);
  font-size: var(--t-cap); font-weight: 600; color: var(--text-3);
  letter-spacing: 0.02em;
  vertical-align: bottom; padding-top: 0.9rem; padding-bottom: 0.45rem;
}
.summary-table thead th:first-child { hyphens: auto; }
/* Подписи столбцов переносятся: по-английски они из двух слов, и запрет
   переноса растил бы самый широкий столбец за счёт имени ведомства. Двум
   строкам шапки здесь ничто не мешает — выравнивание по нижнему краю выше
   поставлено ровно под этот случай. */
.summary-table thead th.num { white-space: normal; }
/* Название группы стоит над своими столбцами и отделено от них тем же
   волоском, что и вся шапка от тела. */
.summary-table .group-head {
  text-align: center; color: var(--text-2);
  border-bottom: 1px solid var(--sep); padding-bottom: 0.3rem;
}
/* Шапка отделена от тела одной линией во всю ширину: до этого граница была
   только под названиями групп, то есть под частью столбцов. */
.summary-table thead tr:last-child th { border-bottom: 1px solid var(--sep); }

/* Между строками — воздух, а не решётка. Волосок под каждой из восьмидесяти
   строк давал сетку, которая спорила с данными; при строке в 46 точек
   разделять уже нечего. Единственная линия в таблице — под шапкой, и она
   отделяет подписи от значений, то есть несёт смысл. */

.summary-table td.num, .summary-table th.num {
  text-align: right; white-space: nowrap;
  font-variant-numeric: tabular-nums; font-size: var(--t-call);
}
/* Ноль — знаком, но не наравне с числами: пустая ячейка неотличима от «не
   посчитали», а полновесный ноль спорит за внимание с тем, что произошло.
   Тише — цветом текста третьего уровня, а не цветом разделителя: разделитель
   даёт около 2.2:1 к подложке, и прочерк переставал быть читаемым. AA
   зафиксирован в PRODUCT.md и распространяется на этот знак тоже. */
.summary-table td.num.is-zero { color: var(--text-3); }

/* Полоска величины — под числом и по его правому краю.
   Число и полоска стоят одной вертикалью и читаются как одно целое, а общий
   правый край у всех строк делает длины сравнимыми: их сравнивают от него,
   как от оси. Раньше полоска шла заливкой из левого угла, пока число стояло
   у правого, — два предмета в одной ячейке.
   Линией, а не заливкой: заливка спорит с цифрами за то же место, линия
   занимает воздух, который у строки и так появился. */
.summary-table td.cell-bar { min-width: 6rem; }
.summary-table td.cell-bar .bar-value { display: block; }
.summary-table td.cell-bar .bar-track {
  display: flex; justify-content: flex-end;
  height: 3px; margin-top: 0.35rem;
}
.summary-table td.cell-bar .bar {
  display: block; height: 100%; border-radius: var(--r-pill);
  background: var(--accent); opacity: 0.5;
}

/* На телефоне отрасль уступает место имени. Ширины на всё не хватает:
   таблица держала 549 точек при доступных 358, имя ужималось до четырёх
   слов в строку, и «China Chamber of Commerce of Metals, Minerals and
   Chemicals Importers and Exporters» разворачивало строку на 187 точек —
   двадцать строк складывались в три экрана. Отрасль здесь вторична и, в
   отличие от имени, доступна фильтром выше. */
@media (max-width: 48rem) {
  .summary-table .col-sector { display: none; }
  .summary-table td:first-child { min-width: 15rem; }
}
/* Итог над разбивкой: семь счётчиков периода и текущего состояния, включая
   нулевые. Держит те столбцы, которых в таблице ниже нет.

   Два ряда — сеткой с зазором. Без него ряды шли обычными блоками впритык:
   пилюли высотой 26 точек со скруглением в 999 касались краями, и на глаз
   это ровно «наезжают друг на друга». */
.totals-strip { display: grid; gap: 0.5rem; }

/* Группировку держит расстояние, а не подложка. Пилюля была у ненулевых и
   отсутствовала у нулевых, и ряд выходил из разных фигур: три числа в
   капсулах, три без — читалось так, будто часть значений подсвечена по
   какой-то причине, а причина была только в величине. Пары «число —
   подпись» теперь связаны тесным зазором, а разделены широким: внутри пары
   0.35rem, между парами 1.15rem. */
.totals-group {
  display: flex; align-items: baseline; gap: 1.15rem; flex-wrap: wrap;
}
.totals-cap {
  font-size: var(--t-foot); font-weight: 600; color: var(--text-3);
  min-width: 5.5rem;
}
.total {
  display: inline-flex; align-items: baseline; gap: 0.35rem;
  font-size: var(--t-foot); color: var(--text-2); white-space: nowrap;
}
.total b {
  font-weight: 600; color: var(--text); font-size: var(--t-sub);
  font-variant-numeric: tabular-nums;
}
/* Ноль остаётся на виду, но не спорит за внимание с тем, что произошло:
   отличается весом и тоном, а не наличием фигуры вокруг. */
.total.is-zero, .total.is-zero b { color: var(--text-3); font-weight: 500; }

/* Хвост свёрнут, и об этом сказано под таблицей, а не додумывается. */
.table-more {
  padding: 0.7rem 1.1rem; border-top: 1px solid var(--sep);
  font-size: var(--t-sub); color: var(--text-3);
}

/* --- правила разбора ------------------------------------------------------
   Здесь пишут прозу, которая уйдёт модели, и перечитывают написанное. Поле
   во всю ширину карточки давало строку в 1096 точек — около ста сорока
   знаков, вдвое длиннее меры, на которой глаз возвращается к началу строки
   не промахиваясь. Колонка под чтение, а карточка остаётся во всю ширину:
   подвал с кнопкой и хешем ей нужен целиком. */
.rules-form .field { max-width: 44rem; }
.rules-form textarea { min-height: 14rem; }
/* Подвал формы отбит чертой, как у карточки материала: сохранение — это не
   ещё одно поле, а конец работы с формой. */
.rules-footer { margin: 0.9rem -1.25rem -1.1rem; }
.field-note { margin-top: 0.45rem; }
/* Счёт набранного стоит перед словесным пределом, а не заменяет его: без
   JavaScript останется текст, с ним — ещё и число. */
.char-count { color: var(--text-2); font-variant-numeric: tabular-nums; }
.char-count.is-near { color: var(--warn); font-weight: 600; }

/* Два числа, ради которых открывают «Переразобрать нерешённое». */
.rerun-scope {
  display: flex; align-items: baseline; gap: 1.15rem; flex-wrap: wrap;
  margin: 0 0 0.7rem;
}

/* Промпты остальных обращений — на чтение. Строки набраны той же мерой,
   что и список материалов: те же отступы и тот же разделитель с отступом
   слева, чтобы пять промптов читались единым блоком, а не набором плашек. */
.prompt-item { position: relative; padding: 0.85rem 1.1rem; }
.prompt-item + .prompt-item::before {
  content: ""; position: absolute; top: 0; left: 1.1rem; right: 0;
  height: 1px; background: var(--sep);
}
.prompt-head {
  display: flex; align-items: baseline; gap: 0.5rem; flex-wrap: wrap;
}
.prompt-title { font-size: var(--t-body); font-weight: 500; margin: 0; }
/* Пометка стоит у названия, а не в подписи снизу: без неё показанный текст
   промпта читается как приглашение его поправить, а правится отсюда один. */
.prompt-flag {
  font-size: var(--t-foot); font-weight: 500; white-space: nowrap;
  color: var(--accent); background: var(--accent-tint);
  padding: 0.1rem 0.45rem; border-radius: var(--r-pill);
}
.prompt-flag.is-quiet { color: var(--text-3); background: var(--neutral-tint); }
/* Имя файла и хеш уходят вправо: их ищут глазами, когда уже знают, какой
   промпт нужен, — а читают строку ради названия. */
.prompt-meta {
  margin-left: auto; font-size: var(--t-sub); color: var(--text-3);
}
.prompt-purpose { margin: 0.25rem 0 0; max-width: 52rem; }

@media (max-width: 700px) {
  /* На узком экране «слева название, справа хеш» превращается в две строки
     с провалом посередине — метаданные уходят под заголовок. */
  .prompt-meta { margin-left: 0; }
}

/* Встроенный код — хеш промпта, номер, имя переменной. Браузерная
   моноширинная по умолчанию не принадлежит ни одной системе: у кабинета
   своя, и подложка отделяет данные от текста вокруг. */
code {
  font-family: var(--font-mono); font-size: 0.9em;
  background: var(--neutral-tint); color: var(--text-2);
  padding: 0.08em 0.35em; border-radius: var(--r-xs);
  overflow-wrap: anywhere;
}

/* --- справка ------------------------------------------------------------- */
/* Длинный текст читают, а не просматривают: колонка уже основной, интервал
   крупнее, заголовки отбиты сверху. */
/* Ширину ограничивает текст внутри карточки, а не сама карточка: с
   `max-width` на карточке она становилась уже соседних и вылезала из общего
   левого края — страница выглядела съехавшей. */
/* Длинный текст читают, а не просматривают, поэтому строка ограничена по
   ширине. Но ограничение стоит на колонке целиком и по центру карточки, а
   не прижато к левому краю: прижатая колонка оставляла справа пустую треть,
   и карточка со справкой читалась как съехавшая рядом с остальными —
   разница между «так задумано» и «сломалось» тут только в симметрии. */
/* Оглавление сбоку и липнет. Карточкой над текстом оно уезжало с первой же
   прокруткой, а руководство идёт на три с лишним экрана: вернуться к списку
   разделов можно было только промотав всё обратно. */
.guide-layout {
  display: grid; grid-template-columns: 14rem minmax(0, 1fr);
  gap: 1.25rem; align-items: start;
}
.guide-nav {
  position: sticky; top: var(--toolbar-h);
  font-size: var(--t-sub);
}
.guide-nav-cap {
  margin: 0 0 0.5rem; padding-left: 0.6rem;
  font-size: var(--t-cap); font-weight: 600; letter-spacing: 0.02em;
  color: var(--text-3);
}
.guide-nav ul { list-style: none; margin: 0; padding: 0; display: grid; gap: 1px; }
/* Ссылка раздела, а не метка состояния. Оглавление было набрано классом
   `.mark mark-off` — компонентом статуса, у которого свой кружок в
   псевдоэлементе: каждый раздел выглядел так, будто у него есть состояние,
   а словарь состояний тратился на навигацию. */
.guide-nav a {
  display: block; padding: 0.35rem 0.6rem; border-radius: var(--r-sm);
  color: var(--text-3); text-decoration: none;
  transition: background 0.15s var(--ease), color 0.15s var(--ease);
}
.guide-nav a:hover { color: var(--text); background: var(--sunken);
                     text-decoration: none; }
.guide-nav a.is-current {
  background: var(--neutral-tint); color: var(--text); font-weight: 600;
}

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

   Ограничение стоит на колонке и по центру карточки, а не прижато к левому
   краю: прижатая колонка оставляла справа пустую треть, и карточка со
   справкой читалась как съехавшая рядом с остальными. */
.guide { line-height: 1.65; }
/* 41rem — это около 70 знаков в строке. Было 46rem, то есть 79: за верхней
   границей читаемой меры, и на возврате к началу строки глаз терял место. */
.guide-column { max-width: 41rem; margin: 0 auto; }
/* Ступень заголовка видна: 1.1rem против 15px основного текста читались
   почти одинаково, и разделы не отделялись от абзацев. */
.guide h2 {
  font-size: var(--t-title); letter-spacing: -0.015em;
  margin: 2.4rem 0 0.7rem;
}
.guide h2:first-child { margin-top: 0; }
.guide h3 { font-size: var(--t-head); margin: 1.7rem 0 0.5rem; }
/* Переход по якорю не должен прятать заголовок под плавающей шапкой: она
   держится сверху, и без этого отступа раздел открывался со второй строки. */
.guide p { margin: 0 0 0.8rem; }
.guide ul { margin: 0 0 0.9rem; padding-left: 1.2rem; }
.guide li { margin-bottom: 0.35rem; }
.guide hr { border: 0; border-top: 1px solid var(--sep); margin: 1.8rem 0; }

@media (max-width: 62rem) {
  /* Оглавление складывается обратно в полосу над текстом: колонке в 14rem
     рядом с текстом на узком экране места нет. */
  .guide-layout { grid-template-columns: minmax(0, 1fr); }
  .guide-nav { position: static; }
  .guide-nav ul { display: flex; flex-wrap: wrap; gap: 0.25rem; }
  .guide-nav a { padding: 0.3rem 0.6rem; border-radius: var(--r-pill);
                 background: var(--sunken); }
}

/* --- три вида оригинала --------------------------------------------------- */
/* Переключатель стоит внутри ячейки оригинала, а не над всей карточкой:
   он про то, как читать этот документ, и рядом с ним же. */
.original-views { display: flex; flex-direction: column; gap: 0.5rem; }
.view-tabs { display: inline-flex; gap: 2px; padding: 2px; align-self: flex-start;
  background: var(--neutral-tint); border-radius: var(--r-md); }
.view-tab {
  padding: 0.2rem 0.6rem; border: 0; background: transparent; cursor: pointer;
  border-radius: calc(var(--r-md) - 3px); font: inherit;
  font-size: var(--t-foot); color: var(--text-2);
}
.view-tab.is-current {
  background: var(--surface); color: var(--text); font-weight: 600;
  box-shadow: var(--shadow-sm);
}
.view-body { white-space: pre-wrap; }
/* Разметка пришла из документа ведомства: у неё свои таблицы, и ширину им
   задаём мы, а не автор страницы. Перенос строк здесь не нужен — в разметке
   абзацы свои. */
.original-markup { white-space: normal; overflow-x: auto; }
/* Перечень ставок на десяток колонок — самый частый повод открыть разметку.
   Без горизонтальной прокрутки он сжался бы по ширине колонки сравнения, и
   числа разъехались бы переносами: та же каша, ради ухода от которой вид и
   заводился. Поэтому не `width: 100%`, а минимум по содержимому. */
.original-markup table {
  border-collapse: collapse; min-width: 100%; width: max-content;
  margin: 0.5rem 0; font-size: var(--t-foot);
}
.original-markup th, .original-markup td {
  border: 1px solid var(--sep); padding: 0.25rem 0.4rem; text-align: left;
  vertical-align: top;
}
.original-markup th { background: var(--neutral-tint); font-weight: 600; }
.original-markup p { margin: 0 0 0.5rem; }
.original-markup ul, .original-markup ol { margin: 0 0 0.5rem; padding-left: 1.2rem; }
.original-markup h1, .original-markup h2, .original-markup h3,
.original-markup h4, .original-markup h5, .original-markup h6 {
  font-size: 1em; font-weight: 600; margin: 0.6rem 0 0.3rem;
}

/* --- что предлагала модель ------------------------------------------------ */
/* Подсказка стоит под правленным полем и намеренно тише его: она справка, а
   не второе поле ввода. Появляется только при расхождении, поэтому места
   занимает ровно там, где есть что сравнивать. */
.model-hint {
  /* Подсказка — четвёртый ребёнок в сетке из трёх столбцов, то есть новая
     строка сетки. Без указания столбца она вставала в первый — в колонку
     подписи шириной 10rem: текст статьи от модели показывался в полосе
     160 точек внутри строки на 1136, с собственной прокруткой. Считаем от
     конца (`-2 / -1`), а не «третий»: на узком экране столбец в строке
     один, и та же запись кладёт подсказку в него же. */
  grid-column: -2 / -1;
  margin-top: 0.4rem; padding: 0.45rem 0.55rem;
  /* Волосок, а не полоса: двухточечная планка сбоку — самый узнаваемый
     признак сгенерированного интерфейса, и детектор её ловит. Смысл тот же
     (блок принадлежит полю слева), исполнение тише. */
  border-left: 1px solid var(--sep); background: var(--sunken);
  border-radius: var(--r-sm); display: flex; flex-direction: column; gap: 0.35rem;
}
/* Предложение модели — второе, с чем сверяются, и восьми строк ему мало:
   при 980 знаках из него было видно 43%, а сверяют его целиком со своим. */
.model-hint .compare-original { max-height: 14rem; font-size: var(--t-foot); }

/* Перечень прежних разборов: четыре узких столбца, читается сверху вниз. */
.versions { width: 100%; border-collapse: collapse; font-size: var(--t-foot); }
.versions td { padding: 0.2rem 0.5rem 0.2rem 0; vertical-align: top; }
.versions tr + tr td { border-top: 1px solid var(--sep); }

/* --- палец вместо мыши -----------------------------------------------------
   Всё, что зависит не от ширины окна, а от того, чем в него тычут. Ширина об
   этом не говорит: узкое окно на десктопе — по-прежнему мышь, а планшет
   боком шире ноутбука и всё равно палец. Блок стоит последним нарочно: он
   поправляет правила, разбросанные по файлу, и должен читаться после них —
   `.config-input` весит (0,1,0), и голый `textarea` иначе бы его перебил. */
@media (pointer: coarse) {
  :root {
    /* Рост органа управления считается от пальца, а не от курсора. 38 точек
       проходят WCAG 2.2 Target Size (Minimum) — там минимум 24, — и дело не в
       соответствии: 44 взяты у уровня AAA как эргономический запас. Причина
       конкретная: на источниках подряд стоят 210 «Скачать принудительно» и
       210 «Отключить», обе действуют сразу и на свой источник, а подушечка
       пальца шире кнопки. */
    --control-h: 2.75rem;
  }

  /* Safari увеличивает страницу при фокусе в любое поле мельче 16 точек и
     обратно масштаб не возвращает: аналитик набирает слово в поиске очереди и
     дальше листает шестьдесят тысяч точек списка в увеличенном кабинете, где
     шапка занимает уже не четверть экрана, а треть. Порог ровно 1rem, поэтому
     здесь литерал, а не ступень шкалы: --t-body равен 15 точкам, и любая его
     будущая правка снова уронила бы поле под порог. */
  input, select, textarea { font-size: 1rem; }
  /* Правка адаптера — тот же порог, но своя ступень (0.82rem = 13 точек) и
     собственный вес селектора: общее правило выше её не достаёт. */
  .config-input { font-size: 1rem; }
  /* Список причин приклеен к «Не подходит» и несёт собственную ступень
     (вес (0,1,1)): голый `select` весит (0,0,1) и до него не достаёт. */
  .bulk-reject select { font-size: 1rem; }
  /* Столбец читателя — не поле ввода, и автозум ему не грозит; кегль поднят
     затем, чтобы «оригинал» и «карточка» не встали рядом разным размером,
     ради чего .compare-readonly и заводился. */
  .compare-readonly { font-size: 1rem; }

  /* 24 точки метка выбора набирает служебными полями самого чекбокса, а не
     нашим правилом, и пальцу их мало. Цель дорастает до 44 за счёт отступа
     строки слева и просвета до заголовка справа; отрицательные поля
     возвращают коробку на место, поэтому ни квадратик, ни текст рядом
     заметно не двигаются. */
  .row-pick {
    min-width: 2.75rem; min-height: 2.75rem;
    margin: -0.6rem -0.6rem -0.6rem -0.45rem;
  }
}

/* Поля закрытого материала. Документ уже у заказчика, править его молча
   нельзя — сервер такую правку и не примет. Поле остаётся читаемым и
   выделяемым (текст из него копируют), но перестаёт выглядеть как место,
   куда печатают. */
.compare-editable input[readonly],
.compare-editable textarea[readonly] {
  background: var(--sunken);
  color: var(--text-2);
  cursor: default;
}

/* Отказ выгрузки — в самой панели решения, у кнопок. Прежде он писался в
   `#save-status` под заголовком «Оригинал и карточка»: тот уезжает за
   верхний край, стоит открыться таблице сравнения, и нажатие выглядело
   так, будто ничего не произошло. */
.decision-status {
  margin: 0 0 0.5rem;
  font-size: 0.8125rem;
  color: var(--no);
}
