/*
 * Фундамент дизайн-системы портала Zemna v2.
 *
 * Значения (цвета, размеры, отступы, @keyframes) взяты дословно из
 * `docs/design-v2-components.md` — выжимки CSS-деклараций прототипа
 * `Zemna Portal v2.dc.html`. Имена классов взяты из словаря-контракта
 * `docs/design-v2-classes.md` — новых классов здесь не заводится, если
 * их нет в словаре.
 *
 * Правило: цвета только через переменные из блока :root /
 * body[data-theme="light"] ниже. Литерал цвета вне этих двух блоков —
 * ошибка, а не стиль.
 *
 * Разметка дерева-логотипа и переключение состояний (тема, аккордеоны,
 * копирование) — забота шаблонов и `zemna.js`, здесь только стили.
 *
 * Шрифты (Space Grotesk, JetBrains Mono, Source Serif 4) подключены
 * тегом <link> в portal/base.html, а не @import отсюда: @import
 * начинает грузиться только после того, как скачан сам этот файл, —
 * лишний последовательный round-trip перед первой отрисовкой текста.
 */

/* ---------------------------------------------------------------------
 * Переменные тем. Тёмная — дефолт в :root, светлая — атрибут на body
 * (body[data-theme="light"] всегда стоит явно, это забота шаблона).
 * ------------------------------------------------------------------- */

:root {
  --bg: #14170f;
  --bg-rail: #0b0d07;
  --bg-pane: #15190e;
  --bg-card: #191d12;
  --bg-code: #0f1209;
  --bg-active: #1a1f11;
  --bg-tab: #1f2415;
  --bg-soft: #122b2a;
  --bg-amber: #241c11;
  --bg-amber2: #1c1810;
  --bg-red: #1c1410;
  --hl-amber: #3a2f14;
  --line: #1e2216;
  --border: #262a1d;
  --border2: #33391f;
  --border3: #285d5b;
  --tx: #e9ece2;
  --tx2: #cfd6c2;
  --tx3: #b9c1ab;
  --tx4: #9aa389;
  --tx5: #8b937a;
  --tx6: #6e765d;
  --tx7: #5c6350;
  --dot-dim: #4d5442;
  --btn-spot: rgba(255, 255, 255, 0.12);
  /* Акцент — фирменная бирюза логотипа (#0d7a75, static/brand/logo.svg).
     В тёмной теме взят её светлый оттенок: сам #0d7a75 на фоне --bg
     не читается ни текстом, ни заливкой кнопки. Имена --lime* остались
     историческими. */
  --lime: #17cfc7;
  --lime-hi: #30e8e1;
  --lime-dk: #14b8b1;
  --lime-spot: rgba(20, 23, 15, 0.28);
  --lime-tx: #17cfc7;
  --on-lime: #14170f;
  --on-accent: #ffffff;
  --amber: #f0b95e;
  --amber-tx: #f0b95e;
  --amber2: #c9b28c;
  --amber3: #e0c79a;
  --red: #e08878;
  --red-tx: #e08878;
  --red2: #c9a79e;
  --red3: #e0b3a8;
  --scan: rgba(255, 255, 255, 0.22);
  --tree: #17cfc7;
  --tree-op: 0.15;
  --bg-card-soft: rgba(25, 29, 18, 0.62);
  /* Затемнение под выехавшим рельсом (D-10). Пары в каталоге нет — это
     новый элемент, которого в прототипе не было вовсе. В тёмной теме
     гасить нечего, поэтому плотность выше: полупрозрачный чёрный поверх
     и без того тёмного фона почти не читается. */
  --scrim: rgba(0, 0, 0, 0.62);
}

body[data-theme="light"] {
  --bg: #f6f7f2;
  --bg-rail: #eef0e6;
  --bg-pane: #f2f4ea;
  --bg-card: #ffffff;
  --bg-code: #fbfcf7;
  --bg-active: #e9eed9;
  --bg-tab: #f0f3e4;
  --bg-soft: #e1f4f3;
  --bg-amber: #fdf6ec;
  --bg-amber2: #fdf8f0;
  --bg-red: #fdf2f0;
  --hl-amber: #fbeec6;
  --line: #e6e9da;
  --border: #d9ddca;
  --border2: #c8cdb3;
  --border3: #75bdba;
  --tx: #191d12;
  --tx2: #2a3020;
  --tx3: #3b4230;
  --tx4: #5c6350;
  --tx5: #636b51;
  --tx6: #6f7760;
  --tx7: #6b7359;
  --dot-dim: #b6bca6;
  --btn-spot: rgba(0, 0, 0, 0.13);
  --lime: #0d7a75;
  --lime-hi: #0a6360;
  --lime-dk: #08514e;
  --lime-spot: rgba(255, 255, 255, 0.22);
  --lime-tx: #0a6360;
  --on-lime: #ffffff;
  --on-accent: #ffffff;
  --amber: #b8801c;
  --amber-tx: #8a5a10;
  --amber2: #7d5a12;
  --amber3: #7d5a12;
  --red: #c0533f;
  --red-tx: #a4382a;
  --red2: #8a3a38;
  --red3: #a83c2c;
  --scan: rgba(0, 0, 0, 0.13);
  --tree: #0d7a75;
  --tree-op: 0.18;
  --bg-card-soft: rgba(255, 255, 255, 0.6);
  /* В светлой теме тем же значением экран уходит в ночь — контраст с белым
     фоном слишком велик. Тон взят от --bg тёмной темы, а не чистый чёрный:
     затемнение должно читаться как та же палитра, а не как чужая заслонка. */
  --scrim: rgba(20, 23, 15, 0.42);
}

/* ---------------------------------------------------------------------
 * Keyframes — дословно из каталога, §1.
 * ------------------------------------------------------------------- */

@keyframes treeBreathe {
  0%,
  100% {
    transform: scale(1) translateY(0);
  }
  50% {
    transform: scale(1.013) translateY(-1.5px);
  }
}

@keyframes flutterA {
  0%,
  100% {
    transform: translate(0, 0) rotate(0deg);
  }
  30% {
    transform: translate(0.9px, -0.5px) rotate(0.18deg);
  }
  65% {
    transform: translate(-0.6px, 0.4px) rotate(-0.14deg);
  }
}

@keyframes flutterB {
  0%,
  100% {
    transform: translate(0, 0) rotate(0deg);
  }
  40% {
    transform: translate(-1px, 0.6px) rotate(-0.2deg);
  }
  75% {
    transform: translate(0.5px, -0.3px) rotate(0.12deg);
  }
}

@keyframes flutterC {
  0%,
  100% {
    transform: translate(0, 0) rotate(0deg);
  }
  50% {
    transform: translate(0.7px, 0.7px) rotate(0.22deg);
  }
}

@keyframes treeRise {
  0% {
    transform: translate(0, 0) scale(0.6);
    opacity: 0;
  }
  15% {
    opacity: 0.9;
  }
  80% {
    opacity: 0.5;
  }
  100% {
    transform: translate(14px, -120px) scale(1);
    opacity: 0;
  }
}

@keyframes treeGlow {
  0%,
  100% {
    opacity: 0.05;
    transform: scale(1);
  }
  50% {
    opacity: 0.12;
    transform: scale(1.06);
  }
}

@keyframes v2scan {
  0% {
    transform: translateX(-100%);
  }
  100% {
    transform: translateX(320%);
  }
}

/* ---------------------------------------------------------------------
 * Базовые стили. Скроллбар намеренно не переопределяется — в
 * прототипе используется системный дефолт (каталог, §1).
 * ------------------------------------------------------------------- */

html,
body {
  margin: 0;
  padding: 0;
}

* {
  box-sizing: border-box;
}

/* Атрибут hidden обязан гасить элемент сильнее любого нашего display.
   Браузерное правило [hidden] { display: none } живёт в UA-таблице, а
   она проигрывает любому авторскому: .z-group__body { display: flex }
   перебивала её, и свёрнутый аккордеон оставался на экране — тела групп
   просто не скрывались (аккордеоны переключаются атрибутом hidden,
   см. zemna.js, раздел 3). Пока был подключён Tailwind с CDN, его
   preflight вставлял такое же правило после нашего файла и случайно
   закрывал дыру; со снятием CDN (D-10) поведение сломалось.
   !important — потому что скрытость должна выигрывать у правил с любой
   специфичностью, а не только у одноклассовых. */
[hidden] {
  display: none !important;
}

button {
  font-family: inherit;
}

body {
  background: var(--bg);
  color: var(--tx);
  font-family: "Space Grotesk", system-ui, sans-serif;
  -webkit-font-smoothing: antialiased;
}

a {
  color: var(--lime-tx);
  text-decoration: none;
}

a:hover {
  color: var(--lime-tx);
  text-decoration: underline;
}

::selection {
  background: var(--lime);
  color: var(--on-lime);
}

input,
textarea {
  color: var(--tx);
}

/* ---------------------------------------------------------------------
 * Каркас.
 * ------------------------------------------------------------------- */

.z-shell {
  min-height: 100vh;
  /* dvh вторым объявлением, а не вместо vh: на мобильных браузерах 100vh
     считается по окну с убранной адресной строкой, и низ страницы уезжает
     под неё. Старые движки просто не поймут вторую строку и останутся на vh. */
  min-height: 100dvh;
  display: flex;
  flex-direction: column;
  background: var(--bg);
}

.z-body {
  display: flex;
  align-items: stretch;
  flex: 1;
}

.z-rail {
  width: 232px;
  flex: none;
  background: var(--bg-rail);
  border-right: 1px solid var(--border);
  display: flex;
  flex-direction: column;
  padding: 18px 0;
}

/* Точных значений шапки рельса в каталоге нет (только тонировка
   логотипа, см. блок z-rail__logo ниже) — отступы согласованы с
   горизонтальным полем пунктов навигации (18px), чтобы шапка не
   «прыгала» относительно списка. */
.z-rail__brand {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 0 18px 16px;
}

.z-rail__logo {
  width: 28px;
  height: 28px;
  display: block;
  /* Тонировка SVG под лайм в тёмной теме — значения из каталога, §4
     (конец раздела); в светлой теме фильтр снимается. */
  filter: brightness(0) saturate(100%) invert(89%) sepia(28%) saturate(1200%) hue-rotate(24deg)
    brightness(105%);
}

body[data-theme="light"] .z-rail__logo {
  filter: none;
}

.z-rail__name {
  font-size: 14px;
  font-weight: 600;
  color: var(--tx);
}

.z-rail__kicker {
  font-family: "JetBrains Mono", monospace;
  font-size: 11px;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--tx6);
}

/* Пара таблеток «EN/RU» и «Dark/Light» под шапкой рельса. Поля те же 18px,
   что у шапки и пунктов навигации, — иначе таблетки уезжают относительно
   логотипа и списка.

   flex-wrap обязателен: по-английски пара умещается в строку, а по-русски
   «Тёмная»/«Светлая» вместе с EN/RU шире 196px внутренней ширины рельса —
   без переноса вторая таблетка вылезала бы за границу панели. */
.z-rail__prefs {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px;
  padding: 0 18px 4px;
}

.z-rail__group {
  margin-top: 14px;
}

.z-rail__group-title {
  padding: 0 18px 6px;
  font-family: "JetBrains Mono", monospace;
  font-size: 10px;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--tx5);
}

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

   align-items: flex-start обязателен. Колонка по умолчанию растягивает
   детей по ширине, и форма выхода занимала бы весь рельс: кликабельная
   область уходила бы далеко вправо от самой надписи. */
.z-rail__user {
  margin-top: 14px;
  padding: 14px 18px 0;
  border-top: 1px solid var(--border);
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 10px;
}

/* Ссылки блока пользователя («Admin», «Log out») — мелкая служебная строка,
   а не элемент навигации. */
.z-rail__user .z-link {
  font-size: 13px;
}

.z-nav {
  display: flex;
  flex-direction: column;
}

.z-nav__item {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 8px 18px;
  color: var(--tx4);
  font-size: 14px;
  border-left: 2px solid transparent;
}

.z-nav__item:hover {
  background-color: var(--bg-pane);
  color: var(--tx);
  /* Гасим подчёркивание из общего правила a:hover — пункт рельса
     подсвечивается фоном, подчёркивание здесь только шумит. */
  text-decoration: none;
}

.z-nav__item--active {
  color: var(--tx);
  border-left: 2px solid var(--lime);
  background: var(--bg-active);
}

/* Счётчик прижат к правому краю пункта: в оглавлении отчёта числа
   должны стоять колонкой, иначе они болтаются за концом заголовка
   и сравнить «сколько где» глазом нельзя. */
.z-nav__count {
  margin-left: auto;
  font-family: "JetBrains Mono", monospace;
  font-size: 11px;
  color: var(--tx6);
}

/* Колонка, чтобы двухпанельные экраны доставали до низа окна: без этого
   панель списка обрывалась там, где кончались строки, и её правая граница
   висела в воздухе посреди страницы. Растягивается только тот блок, который
   сам об этом попросил (.z-split), остальные экраны занимают свою высоту. */
.z-main {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
}

.z-main > main {
  display: flex;
  flex-direction: column;
  flex: 1;
}

.z-page {
  padding: 26px 30px 46px;
}

/* Значение по умолчанию для {% block main_class %} — центрированная колонка
   ограниченной ширины. До D-10 то же самое делали четыре утилиты Tailwind
   (mx-auto max-w-3xl px-6 py-10), значения перенесены один в один: 768px —
   это max-w-3xl (48rem), поля 24px — px-6, 40px — py-10.

   margin: 0 auto центрирует именно потому, что <main> здесь флекс-элемент
   колонки .z-main: auto-поля делят свободное место поровну. */
.z-content {
  width: 100%;
  max-width: 768px;
  margin: 0 auto;
  padding: 40px 24px;
}

/* Полоса с кнопкой меню — только для узкого экрана, где рельс спрятан.
   На широком её нет вовсе (display: none), поэтому здесь сразу none, а
   раскрывает её медиазапрос в конце файла. */
.z-topbar {
  display: none;
  align-items: center;
  gap: 12px;
  padding: 10px 14px;
  border-bottom: 1px solid var(--border);
  background: var(--bg-rail);
}

.z-topbar__name {
  font-size: 14px;
  font-weight: 600;
  color: var(--tx);
}

/* Три полоски. Своей иконки-шрифта в проекте нет, тащить ради одной кнопки
   SVG-спрайт незачем — три <span> рисуются тем же CSS, что и всё остальное. */
.z-burger {
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: 4px;
  /* 40×40 — нижняя граница удобной цели для пальца; сами полоски меньше. */
  width: 40px;
  height: 40px;
  padding: 10px 8px;
  border: 1px solid var(--border2);
  border-radius: 4px;
  background: transparent;
  cursor: pointer;
}

.z-burger__bar {
  display: block;
  height: 2px;
  background: var(--tx3);
}

.z-burger:focus-visible {
  outline: 2px solid var(--lime);
  outline-offset: 1px;
}

/* Затемнение под выехавшим рельсом. Показывает его медиазапрос в конце
   файла и только при открытом рельсе: на широком экране этот элемент
   не должен ни перехватывать клики, ни участвовать в раскладке. */
.z-scrim {
  display: none;
}

.z-split {
  display: flex;
  align-items: stretch;
  flex: 1;
  min-height: 0;
}

.z-split__list {
  width: 336px;
  flex: none;
  border-right: 1px solid var(--border);
  background: var(--bg-pane);
  /* Колонка, чтобы футер (z-split__foot) прижимался к низу панели —
     нужно на экране отчёта, где в футере лежат кнопки скачивания. */
  display: flex;
  flex-direction: column;
}

.z-split__top {
  padding: 14px 16px;
  border-bottom: 1px solid var(--line);
}

.z-split__foot {
  margin-top: auto;
  border-top: 1px solid var(--line);
}

/* Панель, которая остаётся на месте при прокрутке (оглавление отчёта).
   Отчёт бывает в несколько экранов длиной, и без этого оглавление вместе
   с кнопками скачивания уезжает вверх ровно тогда, когда по нему хотят
   перейти. Своя прокрутка внутри — на случай, когда разделов больше,
   чем влезает в окно. */
.z-split__list--sticky {
  position: sticky;
  top: 0;
  /* Не stretch, иначе панель растянется на всю длину отчёта и sticky
     перестанет что-либо значить. Высота ровно в окно — тогда правая
     граница панели не обрывается посреди страницы там, где кончилось
     оглавление. */
  align-self: flex-start;
  height: 100vh;
  overflow-y: auto;
}

/* Кнопки в футере панели (скачивание на экране отчёта). Своих отступов
   у .z-split__foot нет намеренно: внутри него лежит блок со своим
   padding, и второй отступ сдвинул бы его относительно содержимого
   панели выше. */
.z-split__actions {
  display: flex;
  flex-direction: column;
  gap: 8px;
  padding: 14px 16px;
}

.z-split__detail {
  flex: 1;
  min-width: 0;
}

.z-detail__head {
  display: flex;
  align-items: baseline;
  gap: 10px;
  flex-wrap: wrap;
}

.z-detail__sub {
  margin: 6px 0 18px;
}

/* ---------------------------------------------------------------------
 * Экран входа. Рельса тут нет, вместо него — две колонки поверх
 * логотипа-дерева (каталог, §4; хендоф, «1. Login»).
 * ------------------------------------------------------------------- */

.z-login {
  display: grid;
  /* Две равные колонки. Раньше тут стоял auto-fit + minmax(340px), который
     складывал их сам, без медиазапроса, — но он складывал их по своей,
     никем не выбранной ширине (680px), и разделительная граница между
     колонками при этом оставалась вертикальной, то есть висела сбоку от
     уже составленных друг на друга блоков. Точка перелома теперь общая
     с остальным порталом (760px), и границу правит тот же медиазапрос. */
  grid-template-columns: 1fr 1fr;
  min-height: 640px;
  /* Экран входа занимает всё окно, а не свои 640px: body — flex-колонка
     высотой 100vh, и без flex:1 внизу оставалась пустая полоса, а дерево
     центрировалось по верхней части экрана, а не по окну. */
  flex: 1;
  /* Точка отсчёта для абсолютно спозиционированного .z-tree. */
  position: relative;
  /* Дерево шире экрана телефона, а absolute из потока элемент убирает, но
     от переполнения предка не спасает: без этой строки силуэт добавлял
     странице горизонтальную прокрутку. */
  overflow: hidden;
}

.z-login__intro,
.z-login__panel {
  padding: 56px 48px;
  display: flex;
  flex-direction: column;
  justify-content: center;
  /* Над деревом (z-index: 0), иначе полупрозрачный силуэт лезет в текст. */
  position: relative;
  z-index: 1;
}

.z-login__intro {
  border-right: 1px solid var(--border);
}

.z-login__panel {
  background: var(--bg-card-soft);
  border-left: 1px solid var(--border);
}

.z-login__box {
  width: 100%;
  max-width: 340px;
}

/* Кикер над заголовком: 14px до H1 (каталог, §4). Правило контекстное —
   .z-kicker встречается и там, где следом идёт не заголовок. */
.z-login__intro .z-kicker {
  margin-bottom: 14px;
}

/* Вертикальный ритм формы входа задаётся здесь, а не инлайн-стилями
   в шаблоне: поля и блок ошибок — однотипные строки одного столбца.
   16px между полями и 22px до кнопки — как в макете; лишние 6px даёт
   отступ самой кнопки, чтобы не выделять последнее поле отдельным
   селектором (полей два, но приезжают они циклом по форме). */
.z-login__box .z-field,
.z-login__box .z-error--block {
  margin-bottom: 16px;
}

.z-login__box .z-field {
  gap: 7px;
}

.z-login__box .z-label {
  letter-spacing: 0.1em;
  color: var(--tx5);
}

.z-login__box .z-btn--wide {
  margin-top: 6px;
}

.z-login__hint {
  margin: 20px 0 0;
  line-height: 1.55;
}

/* EN/RU слева, Dark/Light у правого края колонки — как в макете. */
.z-login__foot {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 14px;
  margin-top: 20px;
}

/* ---------------------------------------------------------------------
 * Типографика.
 * ------------------------------------------------------------------- */

.z-h1 {
  font-size: 24px;
  font-weight: 600;
  letter-spacing: -0.02em;
  margin: 0 0 8px;
}

.z-h1--login {
  font-size: 34px;
  font-weight: 600;
  letter-spacing: -0.02em;
  line-height: 1.1;
  margin: 0 0 10px;
}

.z-h2 {
  font-size: 18px;
  font-weight: 600;
  letter-spacing: -0.01em;
  margin: 0 0 6px;
}

.z-h3 {
  font-size: 15px;
  font-weight: 600;
  letter-spacing: -0.01em;
  margin: 0 0 6px;
}

.z-lead {
  font-family: "Source Serif 4", Georgia, serif;
  font-size: 16px;
  line-height: 1.6;
  color: var(--tx4);
  max-width: 66ch;
  margin: 0;
}

.z-lead--login {
  font-size: 17px;
  line-height: 1.55;
  max-width: 34ch;
}

.z-lead--report {
  font-size: 15.5px;
  line-height: 1.6;
  max-width: 72ch;
}

.z-kicker {
  font-family: "JetBrains Mono", monospace;
  font-size: 11px;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--tx6);
}

.z-mono {
  font-family: "JetBrains Mono", monospace;
  font-size: 11px;
}

.z-note {
  font-size: 13px;
  color: var(--tx6);
}

.z-link {
  color: var(--lime-tx);
  text-decoration: none;
}

.z-link:hover {
  text-decoration: underline;
}

/* Ссылкой может быть и <button> — выход из системы в Django 5 только POST,
   поэтому «Log out» это кнопка формы (B-74). Без сброса она приезжает
   с рамкой, серым фоном и своим кеглем и выглядит как кнопка действия. */
button.z-link {
  border: 0;
  padding: 0;
  background: none;
  font: inherit;
  cursor: pointer;
}

/* ---------------------------------------------------------------------
 * Компоненты.
 * ------------------------------------------------------------------- */

/* Кнопки. Общая база — пятно под курсором вставляется JS-ом (z-btn__spot),
   но место под него (position/overflow/transition) готовит CSS. */
.z-btn {
  cursor: pointer;
  font-family: inherit;
  position: relative;
  overflow: hidden;
  transition:
    background-color 0.28s ease,
    color 0.2s ease,
    border-color 0.2s ease;
}

.z-btn--primary {
  padding: 13px;
  border: 0;
  border-radius: 4px;
  background: var(--lime);
  color: var(--on-lime);
  font-size: 15px;
  font-weight: 600;
}

.z-btn--primary:hover {
  background-color: var(--lime-dk);
}

/* Кнопка во всю ширину блока — сабмит формы входа (каталог, §3.1). */
.z-btn--wide {
  width: 100%;
}

.z-btn--ghost {
  padding: 9px 15px;
  border: 1px solid var(--border2);
  border-radius: 4px;
  background: transparent;
  color: var(--tx2);
  font-size: 13px;
}

.z-btn--ghost:hover {
  border-color: var(--lime);
}

.z-btn--sm {
  padding: 5px 10px;
  border: 1px solid var(--border2);
  border-radius: 3px;
  background: transparent;
  color: var(--tx2);
  font-family: "JetBrains Mono", monospace;
  font-size: 11px;
}

.z-btn--sm:hover {
  border-color: var(--lime);
}

.z-btn__spot {
  position: absolute;
  left: 0;
  top: 0;
  width: 150px;
  height: 150px;
  margin: -75px 0 0 -75px;
  border-radius: 50%;
  background: radial-gradient(circle, var(--btn-spot) 0%, transparent 68%);
  opacity: 0;
  transition:
    opacity 0.28s ease,
    transform 0.13s ease-out;
  pointer-events: none;
}

/* Переключатели EN/RU, Dark/Light. */
.z-toggle {
  display: inline-flex;
  border: 1px solid var(--border2);
  border-radius: 4px;
  overflow: hidden;
  font-family: "JetBrains Mono", monospace;
  font-size: 11px;
}

.z-toggle__opt {
  padding: 4px 9px;
  /* border: 0 — пункты переключателя это <button>, а у него есть рамка
     по умолчанию; рамка нужна одна, на контейнере-таблетке. */
  border: 0;
  background: transparent;
  color: var(--tx5);
  font-weight: 400;
  font-family: inherit;
  font-size: inherit;
  cursor: pointer;
}

.z-toggle__opt--on {
  background: var(--lime);
  color: var(--on-lime);
  font-weight: 700;
}

/* Плоский вариант для экрана входа: таблетки нет, активный пункт
   отличается только цветом (каталог, §3.3, конец раздела). */
.z-toggle--flat {
  border: 0;
  gap: 14px;
  font-size: 12px;
}

.z-toggle--flat .z-toggle__opt {
  padding: 0;
  color: var(--tx6);
}

.z-toggle--flat .z-toggle__opt--on {
  background: transparent;
  color: var(--lime-tx);
}

/* Сворачиваемая секция (аккордеон). */
/* `.z-group` своего правила не имеет намеренно: нижнюю границу рисуют
   __head (у свёрнутой группы) и последняя __row списка (у развёрнутой),
   а повторная граница на контейнере дала бы двойную линию. Класс живёт
   в разметке как якорь для __head/__body — см. docs/design-v2-classes.md. */
.z-group__head {
  display: flex;
  align-items: center;
  gap: 9px;
  padding: 10px 16px;
  cursor: pointer;
  /* Заголовок группы — <button>, у него своя рамка и фон по умолчанию;
     сбрасываем их так же, как у .z-toggle__opt выше. */
  border: 0;
  border-bottom: 1px solid var(--line);
  width: 100%;
  background: none;
  font-family: inherit;
  text-align: left;
}

.z-group__body {
  display: flex;
  flex-direction: column;
}

.z-group__title {
  font-family: "JetBrains Mono", monospace;
  font-size: 10px;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--tx5);
}

.z-group--error .z-group__title {
  color: var(--red-tx);
}

.z-group__count {
  font-family: "JetBrains Mono", monospace;
  font-size: 11px;
  color: var(--tx6);
}

.z-group__sign {
  margin-left: auto;
  font-family: "JetBrains Mono", monospace;
  font-size: 11px;
  color: var(--lime-tx);
}

/* Список рукописей. */
.z-list {
  display: flex;
  flex-direction: column;
}

.z-list__row {
  padding: 13px 16px;
  border-bottom: 1px solid var(--line);
  cursor: pointer;
  /* Записи списка — <a>, гасим ссылочные умолчания браузера. */
  display: block;
  text-decoration: none;
  color: inherit;
}

.z-list__row:hover {
  background-color: var(--bg-active);
}

.z-list__row--active {
  padding: 13px 16px;
  background: var(--bg-active);
  border-left: 2px solid var(--lime);
  border-bottom: 1px solid var(--line);
}

.z-list__line {
  display: flex;
  align-items: baseline;
  gap: 8px;
}

.z-list__name {
  font-size: 13.5px;
  font-weight: 500;
  color: var(--tx2);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  /* Без min-width: 0 многоточие не появляется вовсе: флекс-элемент по
     умолчанию не сжимается ниже своего содержимого, и длинное имя файла
     распирает строку списка вместо того, чтобы обрезаться. На широком
     рельсе фиксированной ширины это было незаметно, на узком экране,
     где панель тянется по окну, — сразу горизонтальная прокрутка. */
  min-width: 0;
}

/* Карточка. */
.z-card {
  border: 1px solid var(--border);
  border-radius: 6px;
  padding: 17px 19px;
  background: var(--bg-card);
}

.z-card--soft {
  background: var(--bg-card-soft);
}

.z-card--pick {
  border: 1px solid var(--border);
  border-radius: 5px;
  padding: 15px 16px;
  cursor: pointer;
}

.z-card--pick:hover {
  border-color: var(--border3);
}

.z-card--pick-on {
  border: 1px solid var(--lime);
  border-radius: 5px;
  padding: 15px 16px;
  background: var(--bg-soft);
  cursor: pointer;
}

/* min(210px, 100%), а не просто 210px: минимум колонки — жёсткий пол, и на
   экране уже 210px плюс поля страницы сетка вылезала бы за край, растягивая
   <body> горизонтально. min() опускает пол до ширины контейнера. */
.z-picks {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(210px, 100%), 1fr));
  gap: 10px;
}

/* Внутренности карточки (D-4). Каталог описывает карточку как рамку,
   но не то, что внутри: шапку с бейджем, сводку счётчиков и ряд кнопок.
   Значения подобраны под уже описанные компоненты — интервал 17px как
   у padding карточки, шрифты из типографики. */
.z-card--detail {
  padding: 19px 21px;
}

.z-cardhead {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 16px;
}

/* min-width:0 обязателен: без него длинное имя журнала распирает flex-строку
   и выталкивает бейдж за край карточки вместо того, чтобы обрезаться. */
.z-cardhead__main {
  min-width: 0;
}

.z-cardhead__title {
  font-size: 14px;
  font-weight: 500;
  color: var(--tx2);
}

/* Отбивка любого блока, идущего следом за шапкой или сводкой. Отдельным
   классом, а не `margin-top` у каждого — блоки разные (ошибка, строка
   модели, абзац), а расстояние между ними одно. */
.z-cardline {
  margin-top: 11px;
}

.z-stats {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(120px, 1fr));
  gap: 12px;
  margin-top: 15px;
}

.z-stat__key {
  font-size: 11px;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--tx6);
}

.z-stat__val {
  margin-top: 3px;
  font-family: "JetBrains Mono", monospace;
  font-size: 13px;
  color: var(--tx2);
}

.z-actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 9px;
  margin-top: 15px;
}

/* Кнопка-иконка: тот же прямоугольник, но без текста, поэтому padding
   одинаковый со всех сторон, а размер задаёт сама иконка 16×16. */
.z-btn--icon {
  padding: 8px;
  line-height: 0;
}

/* Обрезка длинного текста тремя строками (требования журнала в карточке).
   -webkit-line-clamp — единственный способ, работающий во всех целевых
   браузерах; стандартный `line-clamp` дублируем на будущее. */
.z-clamp {
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 3;
  line-clamp: 3;
  overflow: hidden;
}

/* Панель с отделённой шапкой — гостевой доступ на странице отчёта. */
.z-panel {
  padding: 0;
  overflow: hidden;
}

.z-panel__head {
  padding: 15px 19px;
  border-bottom: 1px solid var(--border);
}

.z-panel__body {
  padding: 17px 19px;
}

/* Форма в одну строку: поле тянется, кнопка прижата к нему справа.
   На узком экране перенос — поэтому wrap, а не фиксированные колонки. */
.z-formrow {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  gap: 10px;
}

.z-formrow__grow {
  flex: 1;
  min-width: 220px;
}

/* Список получателей доступа: строки с разделителем. */
.z-rows {
  margin-top: 17px;
  border-top: 1px solid var(--line);
  list-style: none;
  padding: 0;
}

.z-rows__item {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: 10px 0;
  border-bottom: 1px solid var(--line);
  font-size: 13.5px;
  color: var(--tx2);
}

/* Чип выбора. */
.z-chip {
  padding: 6px 12px;
  border: 1px solid var(--border2);
  border-radius: 3px;
  background: transparent;
  color: var(--tx5);
  font-family: "JetBrains Mono", monospace;
  font-size: 11px;
  cursor: pointer;
  white-space: nowrap;
}

.z-chip:hover {
  border-color: var(--lime);
  color: var(--tx2);
}

/* Тот же чип в роли ярлыка (квартиль журнала, «Open access» в карточке
   подбора) — не кнопка и не фильтр, нажимать нечего. Курсор об этом и
   говорит: указатель на некликабельном элементе обещает несуществующее
   действие. */
span.z-chip {
  cursor: default;
}

/* :not(.z-chip--on) — иначе правило погасило бы и подсветку выбранного
   ярлыка (квартиль Q1/Q2 показывается именно им). */
span.z-chip:not(.z-chip--on):hover {
  border-color: var(--border2);
  color: var(--tx5);
}

.z-chip--on {
  border-color: var(--lime);
  background: var(--bg-soft);
  color: var(--lime-tx);
}

/* Бейдж статуса. */
.z-badge {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  padding: 4px 11px;
  border: 1px solid var(--border3);
  border-radius: 3px;
  background: var(--bg-soft);
  font-family: "JetBrains Mono", monospace;
  font-size: 10.5px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--lime-tx);
  white-space: nowrap;
}

.z-badge--amber {
  color: var(--amber-tx);
}

.z-badge--red {
  color: var(--red-tx);
}

.z-badge--dim {
  color: var(--tx6);
}

.z-badge__dot {
  width: 5px;
  height: 5px;
  background: var(--lime);
  flex: none;
}

.z-badge__dot--amber {
  background: var(--amber);
}

.z-badge__dot--red {
  background: var(--red);
}

.z-badge__dot--dim {
  background: var(--dot-dim);
}

/* Поля формы. */
/* Форма: расстояние между полями задаётся здесь, а не полями. У самого
   .z-field вертикальных отступов нет намеренно — он встречается и вне форм
   (в панелях, в карточках), где свои интервалы. */
/* max-width — та же мера, что у лидов: поле ввода, растянутое на всю
   ширину рабочей области, читается не лучше строки в 200 символов.
   align-self у кнопки обязателен: колонка растягивает детей по ширине,
   и кнопка «Find journals» уезжала во всю ширину экрана, хотя широкой
   она бывает только там, где это сказано явно (.z-btn--wide). */
.z-form {
  display: flex;
  flex-direction: column;
  gap: 16px;
  max-width: 74ch;
}

.z-form > .z-btn {
  align-self: flex-start;
}

.z-form > .z-btn--wide {
  align-self: stretch;
}

/* Вертикальная стопка карточек с полями и внутренним отступом: тела групп
   (.z-group__body) сами отступов не несут, потому что в списке рукописей
   интервалы рисуют границы строк, а не промежутки. */
.z-stack {
  display: flex;
  flex-direction: column;
  gap: 14px;
  padding: 16px;
}

.z-field {
  display: flex;
  flex-direction: column;
  gap: 6px;
}

.z-label {
  font-family: "JetBrains Mono", monospace;
  font-size: 11px;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--tx6);
}

.z-input {
  width: 100%;
  padding: 11px 13px;
  border: 1px solid var(--border2);
  border-radius: 4px;
  background: var(--bg-code);
  font-size: 15px;
  font-family: "JetBrains Mono", monospace;
  color: var(--tx);
}

/* Число не становится читаемее от поля в семьсот пикселей: порог H-index
   или SJR — это две-три цифры, и поле должно об этом говорить. */
.z-input[type="number"] {
  max-width: 220px;
}

/* Фокус — браузерный синий контур чужероден палитре, но убирать его совсем
   нельзя: по нему ориентируется тот, кто ходит с клавиатуры. Поэтому не
   снимаем, а перекрашиваем в акцент системы. focus-visible, а не focus:
   после клика мышью контур не нужен. */
.z-input:focus-visible,
.z-textarea:focus-visible {
  outline: 2px solid var(--lime);
  outline-offset: 1px;
}

/* Текст ошибки формы. Красным, но не крупнее подписи поля: ошибка входа —
   обычный рабочий случай (опечатка в пароле), а не авария. */
.z-error {
  font-size: 13px;
  line-height: 1.5;
  color: var(--red);
}

/* Список чекбоксов CheckboxSelectMultiple (фильтры подбора журналов, D-6).
   Разметку (ul/li/label/input) задаёт сам виджет Django, не шаблон, поэтому
   здесь список, оформленный как ряд компактных ярлыков, а не набор чипов
   (.z-chip рассчитан на кликабельные <button>, а не на пары label+checkbox). */
/* Раскладка задаётся и обёртке, и её прямому потомку. Причина в том, что
   в одних местах внутри лежат сразу метки (_area_picker.html рисует их
   сам, ему нужны крючки для фильтра), а в других — разметка виджета
   Django: CheckboxSelectMultiple с версии 4.0 отдаёт <div> с вложенными
   <div>, раньше отдавал <ul><li>. Одно правило на все три случая. */
.z-checks,
.z-checks > div,
.z-checks > ul {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-wrap: wrap;
  gap: 9px 16px;
}

.z-checks > div > div,
.z-checks li {
  margin: 0;
}

/* Список предметных областей — это ~310 позиций рядом с полем поиска.
   Без ограничения высоты он растягивает карточку на несколько экранов,
   и кнопка отправки уезжает за пределы видимого. */
.z-checks--scroll {
  max-height: 232px;
  overflow-y: auto;
  align-content: flex-start;
}

.z-checks label {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  font-size: 13px;
  color: var(--tx3);
}

.z-checks input {
  accent-color: var(--lime);
}

.z-textarea {
  width: 100%;
  padding: 15px 18px;
  border: 0;
  background: var(--bg-code);
  font-family: "JetBrains Mono", monospace;
  font-size: 12.5px;
  line-height: 1.75;
  color: var(--tx2);
  resize: vertical;
  display: block;
}

/* Виджеты Django, которым форма не проставила класс дизайн-системы, — это
   форма загрузки рукописи и форма добавления журнала на главной
   (ArticleUploadForm и JournalImportForm, в отличие от остальных, классы
   виджетам не назначают). Правило переехало сюда из тега <style> в
   portal/base_shell.html, где оно жило рядом с Tailwind и красило поля
   литералами: `background: white` и серая рамка `rgb(203 213 225)`. В тёмной
   теме такое поле светилось белым прямоугольником посреди тёмной карточки —
   ровно тот случай, ради которого заведён запрет на литералы. Значения
   теперь взяты у .z-input, то есть поля выглядят одинаково независимо от
   того, добралась ли до них форма.

   Типы полей заданы через исключения, а не перечислением. Перечисление уже
   подвело: прежний список знал про text, url и file, но не про email, и поле
   «Recipient's email» в панели доступа к отчёту всё это время рисовалось
   умолчанием браузера — в тёмной теме белой коробкой. Исключения называют то,
   чего касаться нельзя (кнопки, флажки, скрытые поля), и следующее поле нового
   типа получит вид дизайн-системы само, без правки этого блока.

   :not(.z-input):not(.z-textarea) обязателен: селектор по типу элемента
   специфичнее классов дизайн-системы, и без исключения он перекрывал бы уже
   переписанные поля — на экране входа логин (type=text) выходил чужим
   прямоугольником, а пароль рядом оставался полем макета. */
select:not(.z-input),
textarea:not(.z-textarea):not(.z-input),
input:not(.z-input):not([type="checkbox"]):not([type="radio"]):not([type="submit"]):not([type="button"]):not([type="hidden"]) {
  width: 100%;
  /* max-width страхует от переполнения: у <select> ширину задаёт самый
     длинный пункт списка (имя журнала бывает в сотню символов), и на
     телефоне он растягивал бы форму за край экрана. */
  max-width: 100%;
  padding: 11px 13px;
  border: 1px solid var(--border2);
  border-radius: 4px;
  background: var(--bg-code);
  font-family: inherit;
  font-size: 14px;
  color: var(--tx);
}

/* Кнопку «Choose File» внутри поля загрузки рисует сам браузер, и в тёмной
   теме она остаётся светлым пятном посреди тёмного поля. Достать её иначе,
   чем этим псевдоэлементом, нечем. */
input[type="file"]:not(.z-input)::file-selector-button {
  margin-right: 12px;
  padding: 6px 12px;
  border: 1px solid var(--border2);
  border-radius: 3px;
  background: var(--bg-card);
  color: var(--tx2);
  font-family: inherit;
  font-size: 13px;
  cursor: pointer;
}

select:not(.z-input):focus-visible,
textarea:not(.z-textarea):not(.z-input):focus-visible,
input:not(.z-input):not([type="checkbox"]):not([type="radio"]):not([type="submit"]):not([type="button"]):not([type="hidden"]):focus-visible {
  outline: 2px solid var(--lime);
  outline-offset: 1px;
}

/* Полоса прогресса с бегущим бликом. */
.z-progress {
  height: 4px;
  background: var(--border);
  overflow: hidden;
  margin-top: 12px;
  position: relative;
}

.z-progress__fill {
  height: 100%;
  background: var(--lime);
}

.z-progress__scan {
  position: absolute;
  top: 0;
  left: 0;
  width: 18%;
  height: 100%;
  background: var(--scan);
  animation: v2scan 2.4s linear infinite;
}

/* Блок кода / список референсов. */
.z-code {
  border: 1px solid var(--border);
  border-radius: 6px;
  background: var(--bg-code);
  padding: 16px 18px;
  display: flex;
  flex-direction: column;
  gap: 8px;
  font-family: "JetBrains Mono", monospace;
  font-size: 12px;
  line-height: 1.9;
  color: var(--tx4);
  /* Чистовой список литературы состоит из длинных неразрывных ссылок; без
     принудительного переноса блок кода растягивал страницу вбок. */
  overflow-wrap: anywhere;
}

/* Пустое состояние. Каталог значений для него не даёт (в исходнике
   такого блока нет вовсе) — рамка и отступы согласованы с масштабом
   карточки (§3.6), это осознанное приближение, а не цитата. */
.z-empty {
  border: 1px dashed var(--border2);
  border-radius: 6px;
  padding: 32px 24px;
  text-align: center;
  color: var(--tx5);
  font-size: 13px;
}

/* Подвал рабочей области: номер сборки. Прижат к низу и к левому краю
   контентной колонки — это метка, а не заголовок. */
.z-foot {
  margin-top: auto;
  padding: 16px 30px;
  font-family: "JetBrains Mono", monospace;
  font-size: 10px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--tx7);
}

/* Баннер сообщений Django (см. portal/base.html). Лежит над контентом,
   во всю ширину рабочей области: сообщение приходит к странице целиком,
   а не к какому-то её блоку. */
.z-messages {
  padding: 14px 30px 0;
}

.z-message {
  border: 1px solid var(--border);
  border-radius: 6px;
  background: var(--bg-card-soft);
  padding: 11px 14px;
  font-size: 13.5px;
  color: var(--tx4);
}

.z-message + .z-message {
  margin-top: 8px;
}

.z-message--error {
  border-color: var(--red);
  color: var(--red-tx);
}

/* Таблица результатов. В каталоге под одним именем описаны две разные
   сетки колонок (таблица цитирований 54/96/1fr/190, чек-лист 116/1fr) —
   словарь классов не различает их модификатором, поэтому здесь взята
   сетка таблицы цитирований как основная, более детально описанная. */
.z-table {
  display: grid;
  grid-template-columns: 54px 96px 1fr 190px;
  gap: 0;
}

/* Легенда цветов отчёта. Общий отступ между пунктами (gap) в каталоге
   не задан явно ("той же схемой") — оставлен на глаз, по масштабу
   остальных горизонтальных промежутков компонентов. */
.z-legend {
  display: flex;
  align-items: center;
  gap: 14px;
  flex-wrap: wrap;
}

.z-legend__item {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  font-family: "JetBrains Mono", monospace;
  font-size: 11px;
  color: var(--tx5);
}

.z-legend__item::before {
  content: "";
  width: 9px;
  height: 9px;
  background: var(--lime);
  flex: none;
}

.z-legend__item--amber::before {
  background: var(--amber);
}

.z-legend__item--red::before {
  background: var(--red);
}

.z-legend__item--dim::before {
  background: var(--dot-dim);
}

/* Пара «было → стало». Каталог этот компонент не описывает вовсе —
   значения ниже собраны из соседних блоков той же плотности
   (обрамление и отступы блока кода §3.11, шапка таблицы §3.12), это
   приближение, а не цитата. */
/* Колонка контента ограниченной ширины — для одноколоночных экранов
   (чек-лист готовности). Без неё на широком мониторе строка проверки
   растягивается на всю страницу, и подпись слева оказывается от бейджа
   справа на расстоянии, которое глаз уже не связывает. 860px — примерно
   те же 72ch, на которых держится .z-lead--report. */
.z-narrow {
  max-width: 860px;
}

/* Правая панель отчёта. Отдельно от .z-stack: у той свой внутренний
   отступ, а здесь поля уже держит .z-page, и второй отступ сдвинул бы
   разделы относительно заголовков. */
.z-report {
  display: flex;
  flex-direction: column;
  gap: 34px;
}

/* Строки сравнения внутри раздела стоят плотнее, чем сами разделы:
   пары одного раздела — один разговор, разделы — разные. */
.z-report__rows {
  display: flex;
  flex-direction: column;
  gap: 12px;
}

/* auto-fit вместо двух колонок по 1fr: на узком экране половины должны
   встать друг под друга целиком, а не сжаться каждая до нечитаемой полоски
   текста — grid сам решает, сколько колонок влезает по минимальной ширине. */
.z-diff {
  display: grid;
  /* min(280px, 100%) — тот же приём, что у .z-picks: 280px это минимум, ниже
     которого колонка не сжимается, и на телефоне пара половин выезжала за
     край страницы вместо того, чтобы встать друг под друга. */
  grid-template-columns: repeat(auto-fit, minmax(min(280px, 100%), 1fr));
  gap: 16px;
}

/* Одна половина по построению: у пропуска или противоречия нет
   исправленной версии, вторая колонка была бы заполнена одной фразой. */
.z-diff--single {
  grid-template-columns: 1fr;
}

.z-diff__side {
  min-width: 0;
  border: 1px solid var(--border);
  border-radius: 6px;
  background: var(--bg-card);
  padding: 16px 18px;
}

/* Тон половины по содержимому ячейки, а не по её стороне (было/стало).
   Левый край плоский, чтобы цветная граница читалась как отчёркивание
   смысла, а не как рамка вокруг блока. */
.z-diff__side--problem {
  background: var(--bg-red);
  border-left: 2px solid var(--red);
  border-radius: 0 6px 6px 0;
}

.z-diff__side--fix {
  background: var(--bg-soft);
  border-left: 2px solid var(--lime);
  border-radius: 0 6px 6px 0;
}

.z-diff__side--suggestion {
  background: var(--bg-amber);
  border-left: 2px solid var(--amber);
  border-radius: 0 6px 6px 0;
}

.z-diff__side--was {
  background: var(--bg-amber2);
  border-left: 2px solid var(--dot-dim);
  border-radius: 0 6px 6px 0;
}

.z-diff__head {
  font-family: "JetBrains Mono", monospace;
  font-size: 10px;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--tx7);
  margin: 0 0 8px;
}

.z-diff__badge {
  display: inline-block;
  font-family: "JetBrains Mono", monospace;
  font-size: 10.5px;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  padding: 3px 8px;
  border: 1px solid var(--border2);
  border-radius: 3px;
  color: var(--tx5);
  margin-bottom: 8px;
}

.z-diff__text {
  font-family: "Source Serif 4", serif;
  font-size: 14.5px;
  line-height: 1.6;
  color: var(--tx2);
  margin: 0;
  /* Библиографическая запись почти всегда кончается ссылкой вида
     https://doi.org/10.1016/j.bas.2023.102... — это один «слово» длиной
     в полсотни символов, которое браузер не переносит и которое на
     телефоне выталкивает ячейку за край экрана. */
  overflow-wrap: anywhere;
}

/* Курсивная пометка вместо текста: пустая ячейка читается как ошибка
   вёрстки, а не как факт, поэтому «показывать нечего» проговаривается. */
.z-diff__note {
  font-style: italic;
  font-size: 13.5px;
  color: var(--tx6);
  margin: 0;
}

.z-diff__sub {
  font-size: 12.5px;
  color: var(--tx5);
  margin-top: 8px;
}

.z-diff__actions {
  display: flex;
  gap: 10px;
  align-items: center;
  flex-wrap: wrap;
  margin-top: 12px;
}

/* Подсветка слов внутри .z-diff__text: что редактор дописал и что убрал
   (W2-7). Фон, а не только цвет текста: правка бывает в одно слово посреди
   абзаца, и одним оттенком буквы её глазами не найти. Зачёркивание у
   удалённого — вторая примета помимо цвета, для тех, кто цвета не
   различает. */
.z-diff__add {
  background: var(--bg-soft);
  color: var(--lime-tx);
  border-radius: 2px;
  padding: 0 2px;
}

.z-diff__del {
  background: var(--bg-red);
  color: var(--red-tx);
  border-radius: 2px;
  padding: 0 2px;
  text-decoration: line-through;
  text-decoration-color: var(--tx4);
}

/* Тон счётчика в оглавлении отчёта (переиспользует .z-nav / .z-nav__item /
   .z-nav__count существующего рельса навигации). Без модификатора счётчик
   остаётся приглушённым (--tx6), эти три — по серьёзности находки. */
.z-nav__count--red {
  color: var(--red-tx);
}

.z-nav__count--amber {
  color: var(--amber-tx);
}

.z-nav__count--lime {
  color: var(--lime-tx);
}

/* Карточка-предупреждение: разбор не состоялся, показывать нечего и
   молчать нельзя — та же форма, что .z-card, но янтарный тон вместо тишины. */
.z-card--warn {
  border-color: var(--amber);
  color: var(--amber-tx);
  background: var(--bg-amber);
}

/* Блок обратной связи на пункте отчёта: вопрос, ряд кнопок вердикта,
   чужие вердикты. Верхняя отбивка тоньше отступа карточки — это подраздел
   внутри половины diff, а не отдельный блок. */
.z-feedback {
  border-top: 1px solid var(--line);
  margin-top: 12px;
  padding-top: 10px;
}

.z-feedback__q {
  font-family: "JetBrains Mono", monospace;
  font-size: 10.5px;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--tx7);
  margin: 0 0 8px;
}

.z-feedback__row {
  display: flex;
  gap: 8px;
  flex-wrap: wrap;
  align-items: center;
}

/* Список чужих вердиктов — <ul> без маркеров и отступа: это подпись
   под рядом кнопок, а не перечень. */
.z-feedback__others {
  font-size: 12px;
  color: var(--tx5);
  margin-top: 8px;
  list-style: none;
  padding-left: 0;
}

/* Выбранный вариант в группе кнопок вердикта. Один цвет на все три
   вердикта — какой именно выбран, говорит текст кнопки, а не цвет:
   три разных цвета превратили бы ряд в светофор без легенды. */
.z-btn--sel {
  border-color: var(--lime);
  color: var(--lime-tx);
}

/* ---------------------------------------------------------------------
 * Логотип-дерево (каталог, §4). 4 слоя силуэта наложены через CSS-маску
 * поверх одного и того же logo.svg: базовый неподвижный слой держит
 * .z-tree__breathe (словарь не выделяет под него отдельный класс),
 * три анимированных — .z-tree__leaf--a/--b/--c.
 * ------------------------------------------------------------------- */

.z-tree {
  position: absolute;
  left: 50%;
  top: 50%;
  width: 470px;
  height: 479px;
  margin-left: -235px;
  margin-top: -239px;
  z-index: 0;
  pointer-events: none;
  opacity: var(--tree-op);
}

.z-tree__glow {
  position: absolute;
  left: 50%;
  top: 46%;
  width: 380px;
  height: 380px;
  margin-left: -190px;
  margin-top: -190px;
  border-radius: 50%;
  background: radial-gradient(circle, var(--tree) 0%, transparent 70%);
  animation: treeGlow 9s ease-in-out infinite;
}

.z-tree__breathe {
  position: absolute;
  inset: 0;
  transform-origin: 50% 100%;
  animation: treeBreathe 8s ease-in-out infinite;
  background: var(--tree);
  mask-image: url("../../brand/logo.ee390a671749.svg");
  mask-position: center;
  mask-size: contain;
  mask-repeat: no-repeat;
  -webkit-mask-image: url("../../brand/logo.ee390a671749.svg");
  -webkit-mask-position: center;
  -webkit-mask-size: contain;
  -webkit-mask-repeat: no-repeat;
}

.z-tree__leaf {
  position: absolute;
  inset: 0;
  background: var(--tree);
  mask-position: center;
  mask-repeat: no-repeat;
  mask-size: contain, 100% 100%;
  mask-composite: intersect;
  -webkit-mask-position: center;
  -webkit-mask-repeat: no-repeat;
  -webkit-mask-size: contain, 100% 100%;
  -webkit-mask-composite: source-in;
}

.z-tree__leaf--a {
  mask-image: url("../../brand/logo.ee390a671749.svg"), linear-gradient(180deg, #000 0%, #000 26%, transparent 44%);
  -webkit-mask-image: url("../../brand/logo.ee390a671749.svg"),
    linear-gradient(180deg, #000 0%, #000 26%, transparent 44%);
  animation: flutterA 5.5s ease-in-out infinite;
}

.z-tree__leaf--b {
  mask-image: url("../../brand/logo.ee390a671749.svg"),
    linear-gradient(95deg, transparent 8%, #000 26%, #000 48%, transparent 66%);
  -webkit-mask-image: url("../../brand/logo.ee390a671749.svg"),
    linear-gradient(95deg, transparent 8%, #000 26%, #000 48%, transparent 66%);
  animation: flutterB 7.2s ease-in-out infinite;
}

.z-tree__leaf--c {
  mask-image: url("../../brand/logo.ee390a671749.svg"),
    linear-gradient(255deg, transparent 14%, #000 32%, #000 52%, transparent 70%);
  -webkit-mask-image: url("../../brand/logo.ee390a671749.svg"),
    linear-gradient(255deg, transparent 14%, #000 32%, #000 52%, transparent 70%);
  animation: flutterC 6.3s ease-in-out infinite;
}

.z-tree__mote {
  position: absolute;
  width: 3px;
  height: 3px;
  border-radius: 50%;
  background: var(--tree);
  animation-name: treeRise;
  animation-timing-function: linear;
  animation-iteration-count: infinite;
}

.z-tree__mote--1 {
  left: 48%;
  top: 42%;
  animation-duration: 9s;
  animation-delay: 0s;
}

.z-tree__mote--2 {
  left: 60%;
  top: 52%;
  animation-duration: 11s;
  animation-delay: 2.5s;
}

.z-tree__mote--3 {
  left: 38%;
  top: 48%;
  animation-duration: 10s;
  animation-delay: 5s;
}

.z-tree__mote--4 {
  left: 55%;
  top: 38%;
  animation-duration: 12s;
  animation-delay: 7.5s;
}

.z-tree__mote--5 {
  left: 43%;
  top: 58%;
  animation-duration: 10.5s;
  animation-delay: 3.8s;
}

.z-tree__mote--6 {
  left: 65%;
  top: 45%;
  animation-duration: 13s;
  animation-delay: 9s;
}

/* ---------------------------------------------------------------------
 * Адаптивность (D-10).
 *
 * До этой задачи во всём файле не было ни одной точки перелома, и портал
 * существовал ровно в одной ширине: рельс 232px, панель списка 336px и
 * деталь рядом. На телефоне он не работал никак.
 *
 * Точек ровно две, и обе поставлены по тому, что физически перестаёт
 * помещаться, а не по списку диагоналей популярных устройств:
 *
 *   1100px — двухпанельные экраны (рабочий стол, отчёт) складываются в
 *            один столбец. Арифметика: рельс 232 + панель списка 336 +
 *            минимально читаемая колонка детали ~530 = 1098. Ниже этого
 *            деталь перестаёт быть колонкой текста и становится щелью,
 *            в которой сравнение «было → стало» уже не прочесть.
 *
 *    760px — рельс уходит за кнопку, экран входа встаёт в один столбец,
 *            поля страницы сжимаются. Ниже этой ширины рельс в 232px
 *            занимает треть окна постоянно, хотя нужен раз за сеанс.
 *
 * Между ними планшетный режим: рельс на месте, содержимое в один столбец.
 * Третьей точки нет намеренно — каждая следующая это ещё одно состояние,
 * которое придётся проверять глазами при каждой правке вёрстки.
 * ------------------------------------------------------------------- */

@media (max-width: 1100px) {
  /* flex-direction, а не display: block, — чтобы ниже работал order
     у .z-split--detail-first. */
  .z-split {
    flex-direction: column;
  }

  .z-split__list {
    width: auto;
    /* Разделитель поворачивается вместе с раскладкой: между колонками он
       был правой границей, между строками должен стать нижней. */
    border-right: 0;
    border-bottom: 1px solid var(--border);
  }

  /* Липкое оглавление отчёта в один столбец не просто бесполезно, а вредно:
     панель высотой в окно со своей прокруткой превращается в коробку,
     из-за которой самого отчёта не видно. align-self сбрасываем тоже —
     во флекс-колонке flex-start оставил бы панель уже страницы. */
  .z-split__list--sticky {
    position: static;
    align-self: auto;
    height: auto;
    overflow-y: visible;
  }

  /* Рабочий стол: карточка выбранной рукописи встаёт над списком.
     Почему именно так — см. комментарий у .z-split в portal/index.html. */
  .z-split--detail-first .z-split__detail {
    order: -1;
  }
}

@media (max-width: 760px) {
  .z-topbar {
    display: flex;
  }

  /* Рельс уезжает за левый край и возвращается по кнопке. position: fixed,
     а не сдвиг во флекс-потоке: панель должна ложиться поверх содержимого,
     а не расталкивать его, иначе при открытии страница под ней перевёрстывается
     и место, куда человек смотрел, уезжает. */
  .z-rail {
    position: fixed;
    top: 0;
    bottom: 0;
    left: 0;
    z-index: 40;
    width: 264px;
    /* На самых узких экранах панель не должна занимать всю ширину: полоска
       контента справа показывает, что это выехавший слой, а не новая страница. */
    max-width: 84vw;
    overflow-y: auto;
    transform: translateX(-100%);
    transition: transform 0.22s ease;
  }

  body[data-z-rail-open] .z-rail {
    transform: translateX(0);
  }

  body[data-z-rail-open] .z-scrim {
    display: block;
    position: fixed;
    inset: 0;
    /* На единицу ниже рельса: гасит всё, кроме него самого. */
    z-index: 39;
    background: var(--scrim);
  }

  /* Пока рельс открыт, страница под ним не прокручивается: иначе палец,
     промахнувшийся мимо панели, листает контент, которого не видно. */
  body[data-z-rail-open] {
    overflow: hidden;
  }

  /* Поля. 30px с каждой стороны на экране в 360px — это шестая часть ширины,
     отданная пустоте. */
  .z-page {
    padding: 18px 16px 32px;
  }

  .z-content {
    padding: 24px 16px 32px;
  }

  .z-messages {
    padding: 12px 16px 0;
  }

  .z-foot {
    padding: 14px 16px;
  }

  /* Экран входа: колонки друг под другом, разделительная граница из боковой
     становится горизонтальной. */
  .z-login {
    grid-template-columns: 1fr;
    min-height: auto;
  }

  .z-login__intro,
  .z-login__panel {
    padding: 34px 22px;
  }

  .z-login__intro {
    border-right: 0;
    border-bottom: 1px solid var(--border);
  }

  .z-login__panel {
    border-left: 0;
  }

  /* Форма входа во всю ширину колонки: 340px посреди экрана в 360px читаются
     как случайно съехавший блок. */
  .z-login__box {
    max-width: none;
  }

  .z-h1--login {
    font-size: 27px;
  }

  /* 34ch держали заголовок в две строки на широком экране; на узком колонка
     и так уже уже этого. */
  .z-lead--login {
    max-width: none;
  }

  /* Дерево в 470px на экране в 360px обрезается по обоим краям и читается
     как ошибка, а не как силуэт. Переполнение при этом гасит overflow
     у .z-login — здесь дело только в пропорции. */
  .z-tree {
    width: 300px;
    height: 306px;
    margin-left: -150px;
    margin-top: -153px;
  }
}

/* ---------------------------------------------------------------------
 * Уважение к prefers-reduced-motion: отключаем декоративную анимацию
 * дерева, пыльцы и блика прогресса. Функциональность (маски, позиции)
 * не трогаем — пропадает только движение.
 * ------------------------------------------------------------------- */

@media (prefers-reduced-motion: reduce) {
  .z-tree__glow,
  .z-tree__breathe,
  .z-tree__leaf--a,
  .z-tree__leaf--b,
  .z-tree__leaf--c,
  .z-tree__mote,
  .z-progress__scan {
    animation: none;
  }

  /* Рельс не выезжает, а появляется. Само открытие остаётся — пропадает
     только движение, как и у остальной анимации выше. */
  .z-rail {
    transition: none;
  }
}

/* ---------------------------------------------------------------------
 * Модалка профиля (PROF-3) и шкала надёжности пароля.
 *
 * Основа — нативный <dialog>: сторонней библиотеки, ловушки фокуса и
 * своего затемнения не нужно, всё это браузер делает сам, включая
 * Escape и ::backdrop. Отсюда padding: 0 на самом диалоге — внутренние
 * отступы несёт обёртка .z-modal__inner, иначе клик по полям диалога
 * попадал бы в него самого и читался скриптом как клик по подложке
 * (см. zemna.js, раздел 7).
 *
 * Цвета — существующие переменные темы: своих здесь нет, поэтому окно
 * одинаково читается и в светлой, и в тёмной.
 * ------------------------------------------------------------------- */

.z-modal {
  width: min(560px, calc(100vw - 32px));
  max-height: 86vh;
  padding: 0;
  border: 1px solid var(--border2);
  border-radius: 8px;
  background: var(--bg-card);
  color: var(--tx);
  overflow: hidden;
}

.z-modal::backdrop {
  background: var(--scrim);
}

.z-modal__inner {
  display: flex;
  flex-direction: column;
  max-height: 86vh;
}

.z-modal__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: 16px 20px;
  border-bottom: 1px solid var(--border);
  background: var(--bg-pane);
}

.z-modal__title {
  margin: 0;
  font-family: "Space Grotesk", sans-serif;
  font-size: 18px;
  font-weight: 600;
  color: var(--tx);
}

.z-modal__x {
  padding: 0 6px;
  border: 0;
  background: none;
  font-size: 22px;
  line-height: 1;
  color: var(--tx5);
  cursor: pointer;
}

.z-modal__x:hover {
  color: var(--tx);
}

/* Прокручивается не сама анкета, а обёртка: внутри неё две независимые
   секции — форма анкеты и блок привязки Telegram со своими формами
   (вложенных <form> не бывает), и уезжать под шапку они обязаны вместе.
   Шапка с заголовком и подвал с кнопками остаются на виду. */
.z-modal__scroll {
  display: flex;
  flex-direction: column;
  overflow-y: auto;
}

.z-modal__body {
  display: flex;
  flex-direction: column;
  gap: 14px;
  padding: 18px 20px;
}

/* Логин — не поле ввода, а строка: менять его владелец не может. Вид
   всё же взят у .z-input, чтобы он не выпадал из столбца полей. */
.z-modal__ro {
  padding: 11px 13px;
  border: 1px dashed var(--border2);
  border-radius: 4px;
  background: var(--bg-code);
  font-family: "JetBrains Mono", monospace;
  font-size: 15px;
  color: var(--tx4);
}

.z-modal__ok {
  padding: 10px 12px;
  border: 1px solid var(--border3);
  border-radius: 4px;
  background: var(--bg-soft);
  font-size: 14px;
  color: var(--lime-tx);
}

/* Разделитель перед сменой пароля: смысловая граница внутри одной формы,
   поэтому линия сверху, а не отдельная карточка. */
.z-modal__sep {
  display: flex;
  flex-direction: column;
  gap: 4px;
  margin-top: 6px;
  padding-top: 16px;
  border-top: 1px solid var(--border);
}

.z-modal__search {
  font-size: 14px;
}

.z-modal__foot {
  display: flex;
  gap: 10px;
  padding: 14px 20px;
  border-top: 1px solid var(--border);
  background: var(--bg-pane);
}

/* Шкала надёжности пароля. Украшение, а не проверка: решает серверный
   валидатор, здесь только подсказка на глаз (zemna.js, раздел 7). */
.z-meter {
  display: flex;
  align-items: center;
  gap: 10px;
}

.z-meter__track {
  flex: 1;
  height: 4px;
  border-radius: 2px;
  background: var(--line);
  overflow: hidden;
}

.z-meter__bar {
  display: block;
  width: 0;
  height: 100%;
  border-radius: 2px;
  background: var(--tx7);
  transition: width 0.18s ease, background-color 0.18s ease;
}

.z-meter__label {
  min-width: 7ch;
  font-family: "JetBrains Mono", monospace;
  font-size: 11px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--tx5);
}

/* Красный → янтарный → бирюзовый: те же три сигнальных цвета, что у
   статусов рукописей, новых фирменных не заводим. */
.z-meter--s1 .z-meter__bar,
.z-meter--s2 .z-meter__bar {
  background: var(--red);
}

.z-meter--s3 .z-meter__bar {
  background: var(--amber);
}

.z-meter--s4 .z-meter__bar {
  background: var(--lime);
}

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

/* -------------------------------------------------------------------
 * Привязка Telegram в модалке профиля (PROF-5)
 *
 * Своих цветов нет: код и команда берут вид у .z-code (моноширинный
 * шрифт на --bg-code), состояние — у обычного текста. Обе темы
 * получаются даром, потому что все значения — переменные каталога.
 * ------------------------------------------------------------------- */

.z-tg__state {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
}

.z-tg__chat {
  font-size: 13px;
  color: var(--tx6);
}

.z-tg__chatid {
  font-family: "JetBrains Mono", monospace;
  color: var(--tx4);
}

/* Код набирают руками с телефона, поэтому он крупный и разрежённый:
   6 цифр подряд мелким кеглем читаются с ошибкой. */
.z-tg__code {
  padding: 12px 14px;
  border: 1px solid var(--border);
  border-radius: 6px;
  background: var(--bg-code);
  font-family: "JetBrains Mono", monospace;
  font-size: 26px;
  letter-spacing: 0.22em;
  text-align: center;
  color: var(--tx);
}

.z-tg__cmd {
  padding: 9px 12px;
  border: 1px solid var(--border);
  border-radius: 4px;
  background: var(--bg-code);
  font-family: "JetBrains Mono", monospace;
  font-size: 14px;
  color: var(--tx4);
}

/* -------------------------------------------------------------------
 * Контекстная подсказка у поля или блока (H-5)
 *
 * Единственный собственный класс серии справки: элемента
 * «поповер-подсказка» в дизайн-системе нет, и его пришлось завести
 * (решение 2 плана H-5). Механика — нативный HTML popover, своего JS
 * ноль: кнопка с popovertarget открывает элемент с атрибутом popover,
 * Escape и клавиатура работают сами.
 *
 * Своих цветов нет — только переменные каталога, поэтому обе темы
 * (светлая и тёмная) выходят даром, как у .z-tg. Позиционирование —
 * штатное центрирование в окне (inset:0; margin:auto в UA-стилях
 * [popover]): якорной привязки не нужно, на узком экране центр окна
 * надёжнее пузыря у края.
 * ------------------------------------------------------------------- */

.z-hint {
  display: inline;
}

/* Кнопка «?»: маленький кружок вплотную к подписи. Сброс шрифта и
   рамки — как у button.z-link, иначе приезжает системным кеглем. */
.z-hint__btn {
  margin-left: 5px;
  width: 15px;
  height: 15px;
  padding: 0;
  border: 1px solid var(--border2);
  border-radius: 50%;
  background: var(--bg-soft);
  color: var(--lime-tx);
  font: inherit;
  font-size: 10px;
  line-height: 13px;
  text-align: center;
  cursor: pointer;
  vertical-align: middle;
}

.z-hint__btn:hover {
  border-color: var(--border3);
}

.z-hint__btn:focus-visible {
  outline: 2px solid var(--lime);
  outline-offset: 1px;
}

.z-hint__pop {
  max-width: min(340px, calc(100vw - 32px));
  padding: 12px 14px;
  border: 1px solid var(--border2);
  border-radius: 6px;
  background: var(--bg-card);
  color: var(--tx3);
  font-size: 13px;
  line-height: 1.5;
}

.z-hint__pop::backdrop {
  background: var(--scrim);
}

.z-hint__more {
  display: inline-block;
  margin-top: 8px;
  font-size: 13px;
}

@media (max-width: 760px) {
  .z-hint__pop {
    max-width: calc(100vw - 24px);
  }
}

/* ---------------------------------------------------------------------
 * Комбобокс выбора журнала (data-z-combo, zemna.js)
 *
 * Прогрессивное улучшение поверх обычного <select>: сам select остаётся
 * в DOM и несёт значение в POST как раньше, только визуально прячется
 * (класс, а не hidden-атрибут — он выключил бы отправку значения).
 * Поверх него JS ставит текстовое поле и всплывающий список совпадений.
 * ------------------------------------------------------------------- */

.z-combo {
  position: relative;
}

/* Стандартный приём «спрятать, но не убрать из DOM и доступности через
   display: none»: select остаётся частью формы и её значения, просто
   схлопнут до точки. tabIndex у элемента снят в JS отдельно — Tab не
   должен на нём останавливаться, пока рядом есть текстовое поле-дублёр. */
.z-combo__native--hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

.z-combo__input {
  width: 100%;
}

/* Поповер со списком совпадений — под полем, поверх соседних элементов
   карточки. Оформлен как остальные всплывающие панели в файле
   (.z-hint__pop): рамка и фон карточки, без теней — в палитре их нет. */
.z-combo__list {
  position: absolute;
  z-index: 20;
  top: calc(100% + 4px);
  left: 0;
  right: 0;
  margin: 0;
  padding: 4px;
  list-style: none;
  max-height: 280px;
  overflow-y: auto;
  border: 1px solid var(--border2);
  border-radius: 4px;
  background: var(--bg-card);
}

.z-combo__option {
  padding: 8px 10px;
  border-radius: 3px;
  font-size: 14px;
  color: var(--tx2);
  cursor: pointer;
}

.z-combo__option:hover,
.z-combo__option--active {
  background: var(--bg-active);
  color: var(--tx);
}

.z-combo__empty {
  padding: 8px 10px;
  font-size: 13px;
  color: var(--tx6);
}
