/* Дизайн-система SSO. Единственное место, где живут значения.
 *
 * Правила и обоснования — DESIGN_SYSTEM.md в корне проекта. Здесь только
 * реализация; расходиться им нельзя, и за этим следит
 * tests/unit/test_design_system_values.py.
 *
 * Направление: строгий корпоративный, дизайнерский (указание заказчика
 * 2026-08-17). Имя продукта — Сим-Сим (указание заказчика 2026-08-23).
 * Характер несёт типографика и сетка — украшений нет.
 *
 * Устройство страницы — «бланк»: угловой штамп (марка и принадлежность)
 * в узкой левой колонке, заголовок документа и форма — в основной, обе
 * колонки висят на одной базовой линии. Самое крупное на экране — то,
 * зачем человек пришёл, а не то, кому это принадлежит.
 */

/* --- Начертания едут с приложением ---------------------------------------
 *
 * До 2026-08-23 в таблице стилей стоял только перечень имён, а ни одного
 * файла начертаний в образе не было. Подставить браузеру было нечего, и страница рисовалась
 * **системным шрифтом** — тем самым, который в основании портфеля назван
 * отсутствием решения. Заказчик увидел это первым и назвал «дизайн привет
 * нулевые»; причина оказалась не в композиции.
 *
 * Файлы лежат в `static/fonts/` и попадают в образ вместе с `src`
 * (`.dockerignore` разрешает `src/` целиком). Внешних обращений нет:
 * университетская сеть не должна зависеть от чужого узла, чтобы нарисовать
 * букву. По той же причине в правило содержимого добавлено `font-src 'self'` —
 * без него `default-src 'none'` запрещал загрузку начертаний, и объявленные
 * файлы всё равно не доехали бы.
 *
 * Наборы разделены по `unicode-range`: кириллица и латиница, больше ничего.
 * Русскому интерфейсу вьетнамский и греческий наборы не нужны, а весят они
 * столько же.
 *
 * Лицензия — SIL Open Font License 1.1, тексты лежат рядом с файлами
 * (`PT-SANS-OFL.txt`, `PT-SERIF-OFL.txt`): условие 2 лицензии выполняется
 * буквально — уведомление едет вместе с копией, и в исходниках, и в образе.
 */

@font-face {
  font-family: "PT Serif";
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url("/static/fonts/pt-serif-cyrillic-400-normal.woff2") format("woff2");
  unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}

@font-face {
  font-family: "PT Serif";
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url("/static/fonts/pt-serif-latin-400-normal.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+2000-206F, U+2122;
}

@font-face {
  font-family: "PT Serif";
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  src: url("/static/fonts/pt-serif-cyrillic-700-normal.woff2") format("woff2");
  unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}

@font-face {
  font-family: "PT Serif";
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  src: url("/static/fonts/pt-serif-latin-700-normal.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+2000-206F, U+2122;
}

@font-face {
  font-family: "PT Sans";
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url("/static/fonts/pt-sans-cyrillic-400-normal.woff2") format("woff2");
  unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}

@font-face {
  font-family: "PT Sans";
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url("/static/fonts/pt-sans-latin-400-normal.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+2000-206F, U+2122;
}

@font-face {
  font-family: "PT Sans";
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  src: url("/static/fonts/pt-sans-cyrillic-700-normal.woff2") format("woff2");
  unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}

@font-face {
  font-family: "PT Sans";
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  src: url("/static/fonts/pt-sans-latin-700-normal.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+2000-206F, U+2122;
}

:root {
  /* --- Палитра организации. Взята из «Деканата», не изобретена ---------- */
  --mgimo-blue: #002855;
  --mgimo-blue-light: #1a5276;
  --mgimo-accent: #2980b9;

  /* --- Роли. В разметке используются только они ------------------------- */
  --ink: #14181d;
  --ink-muted: #4a5560;
  --paper: #fbfbf9;
  --surface: #ffffff;
  --rule: #d4d8dd;
  --rule-strong: #7d858e;
  --action: var(--mgimo-blue);
  --action-hover: var(--mgimo-blue-light);
  --danger: #8c1d18;
  --pending: #6b4e00;

  /* --- Шкала отступов портфеля. Промежуточных значений нет -------------- */
  --space-1: 4px;
  --space-2: 8px;
  --space-3: 12px;
  --space-4: 16px;
  --space-5: 24px;
  --space-6: 32px;
  --space-7: 48px;
  --space-8: 64px;

  /* --- Форма ------------------------------------------------------------ */
  --radius-field: 4px;
  --radius-panel: 6px;
  --rule-width: 1px;

  /* --- Меры бланка -------------------------------------------------------
   *
   * Числа, а не слова. `DESIGN_PORTFOLIO.md` §1в, поправка 2026-08-24:
   * «строгость» дважды прочитали как пустоту, и оба раза заказчик назвал
   * это «привет нулевые». Поэтому мера формы задана **пределом**, а не
   * «сколько поместится»: поле логина шириной в две трети экрана — дефект,
   * а не сдержанность.
   *
   * `--measure-form` — 400 px, середина заданного портфелем промежутка
   * 360-420. Мера в `rem`: при увеличении шрифта в браузере бланк растёт
   * вместе с текстом, а не рвётся.
   *
   * **Второй меры больше нет.** До 2026-08-24 рядом стояла `--measure-rail`
   * — полоса слева, в которой жили марка и принадлежность. Полосу отвели
   * под содержание, которого до входа не бывает: о системе мы до входа
   * не показываем ничего, значит держать колонке нечего. Пустая колонка
   * на настольном виде и оказалась тем самым «привет нулевые» в третий раз,
   * только пустота переехала из-под содержимого в столб посреди экрана.
   */
  --measure-form: 25rem;

  /* Меры журнала — второй поверхности. Режим Operate: работают несколько
     человек службы ИТ, подолгу, с таблицами. Поле шире порога, но не во всю
     ширину монитора: строка прозы длиннее 75 знаков перестаёт читаться,
     а таблица шире 60rem начинает разъезжаться глазами по строке. */
  --measure-ledger: 60rem;
  --measure-prose: 42rem;

  /* --- Типографика. Отношение 1.25, основание 16px ----------------------
   *
   * Пара из одной государственной программы: PT Serif и PT Sans созданы
   * ParaType по заказу Российской Федерации («Общественные шрифты»,
   * 2009-2010) — государственное учреждение набрано начертаниями, сделанными
   * для государственного языка. Это и есть носитель характера при запрете
   * на украшения: не приём, а происхождение.
   *
   * Отступление стека сохранено и после поставки файлов: если начертание
   * почему-либо не доехало, экран обязан остаться собранным — он построен
   * на шкале и ритме, а не на очертаниях.
   */
  --font-display: "PT Serif", Cambria, Georgia, "Times New Roman", serif;
  --font-body: "PT Sans", "Segoe UI", Inter, system-ui, sans-serif;

  --text-xs: 0.8rem;
  --text-sm: 0.875rem;
  --text-base: 1rem;
  --text-md: 1.25rem;
  --text-lg: 1.5625rem;
  --text-xl: 1.953rem;
  --text-2xl: 2.441rem;
  --text-3xl: 3.052rem;

  /* Ступень знака одноразового кода. Отдельным токеном, потому что на узком
     экране она берёт **не ту** ступень, что заголовок: шесть знаков в ряд
     упираются в ширину, а заголовок — в высоту. Ступень всё равно из шкалы,
     подобранных чисел нет. */
  --text-code: var(--text-xl);

  --leading-text: 1.5;
  --leading-head: 1.15;

  /* --- Движение: три длительности и одна кривая -------------------------
   *
   * Правило портфеля: анимация ради анимации запрещена, движение оправдано,
   * только если объясняет переход. Поэтому длительностей ровно столько,
   * сколько разных вещей движение сообщает, а не сколько бывает красиво.
   *
   * `--state-change` — отклик управляющего элемента: цвет кнопки меняется
   * быстрее, чем человек успевает счесть страницу задумавшейся.
   * `--motion-land`  — знак занял своё место в строке кода.
   * `--motion-travel`— каретка перешла к следующему знаку: это движение
   *                    и есть сообщение «принято, осталось столько-то».
   * `--motion-refuse`— ответ сервера «код не подошёл». Смещение держится
   *                    в пределах двух пикселей: больше читается как трюк,
   *                    а не как отказ.
   *
   * Кривая одна на всё — торможение при прибытии. Ускорение при появлении
   * задерживает первый кадр, то есть ровно тот, на который человек смотрит.
   */
  --ease-arrive: cubic-bezier(0.23, 1, 0.32, 1);
  --state-change: 120ms var(--ease-arrive);
  --motion-land: 140ms;
  --motion-travel: 180ms;
  --motion-refuse: 240ms;
}

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

*,
*::before,
*::after {
  box-sizing: border-box;
}

html {
  font-size: 100%;
}

body {
  margin: 0;
  background: var(--paper);
  color: var(--ink);
  font-family: var(--font-body);
  font-size: var(--text-base);
  line-height: var(--leading-text);
  -webkit-text-size-adjust: 100%;
}

/* Фокус виден всегда. Снятие обводки без замены запрещено. */
:focus-visible {
  outline: 2px solid var(--action);
  outline-offset: 2px;
}

/* Ссылки красятся ролью действия.
 *
 * До 2026-08-23 правила не было вовсе, и браузер красил ссылки своим
 * умолчанием — краской, которой в палитре нет. Проверка на значения вне
 * токенов молчала честно: она смотрит объявленное, а не унаследованное
 * от таблицы стилей браузера (DESIGN_SYSTEM.md §10а — тот же класс дефекта,
 * что у соседа с чужой темой).
 *
 * `--mgimo-accent` здесь непригоден: 4.15:1 ниже порога обычного текста. */
a {
  color: var(--action);
  /* Подчёркивание снято (`DESIGN_PORTFOLIO.md` §1в): синяя подчёркнутая
     строка — подпись вёрстки нулевых, и заказчик назвал её первой.
     Взамен различимость несут вес и краска действия, а под указателем
     и под фокусом линия возвращается — тогда она сообщает «сюда можно
     нажать», а не просто стоит.
     Ссылка, ведущая к действию, у нас вообще не ссылка на вид:
     она берёт `.button--quiet` и обводится, как управляющий элемент. */
  font-weight: 500;
  text-decoration: none;
  text-underline-offset: 0.15em;
}

a:hover,
a:focus-visible {
  color: var(--action-hover);
  text-decoration: underline;
}

/* Ссылка, надетая на кнопку, подчёркиванием не обзаводится: у неё есть
   обводка, и вторая метка была бы шумом. */
a.button:hover,
a.button:focus-visible {
  text-decoration: none;
}

/* Слышно, но не видно.
 *
 * Приём владельца проекта, принятый 2026-08-21 на экранах задачи и здесь
 * распространённый: разметка остаётся полной для того, кто её слышит,
 * а показ освобождается для того, кто смотрит. Так на экран возвращается
 * полное официальное название учреждения, не занимая четырёх строк. */
.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

/* --- Сетка порога: бланк -------------------------------------------------
 *
 * Осознанный риск, и он в сетке и типографике. Обоснование — DESIGN_SYSTEM.md §2.
 */

.threshold {
  min-height: 100vh;
  /* Настоящая высота окна на телефоне: `100vh` там больше видимой части
     на высоту скрывающейся полосы браузера, и низ композиции уезжает
     под неё. Объявления идут парой: браузер, не знающий `dvh`, оставит
     первое. */
  min-height: 100dvh;
  display: grid;
  /* Пара объявлений, а не одно. Второе — то, что нужно: содержимое стоит
     посередине доступной высоты, и «пустых двух третей» не остаётся ни
     на одной ширине (`DESIGN_PORTFOLIO.md` §1в). Первое — что произойдёт,
     если браузер не знает `safe`: он отбросит второе объявление целиком
     и оставит `start`, то есть прижмёт к верху, а не срежет верх.
     Слово `safe` здесь и означает ровно это: содержимое, переросшее экран,
     ведёт себя как прижатое к началу, и до него можно докрутить.

     Осмотр 2026-08-24 нашёл этим замером два провала на 390: короткие
     экраны (отказ по праву, ссылка составлена неверно) занимали 337 px
     из 844 и стояли сверху — 483 px пустоты под ними. Ровно то, что
     заказчик назвал недогруженной страницей, только на телефоне. */
  align-content: start;
  align-content: safe center;
  gap: var(--space-6);
  padding: var(--space-6) var(--space-4) var(--space-8);
}

/* Ступень для узкого экрана.
 *
 * Найдено осмотром 2026-08-17: дисплейная ступень на ширине 390 занимала
 * пять строк, и основное действие уходило к нижнему краю — на 360×640
 * за него. Вход с телефона — основной сценарий студента, и кнопка под
 * сгибом означает, что часть людей до неё не доберётся.
 *
 * Шкала не ломается: берутся **соседние ступени той же шкалы**, а не
 * подобранные на глаз числа.
 */
@media (max-width: 639px) {
  :root {
    /* Две ступени вниз, а не одна: замер 2026-08-17 показал, что после
       первой правки кнопка всё ещё уходила за сгиб на 360×640 (685 при 640)
       и на 390 с самым длинным сообщением (849 при 844). */
    --text-3xl: 1.953rem;
    --text-2xl: 1.5625rem;
    --text-xl: 1.25rem;
    --text-md: 1.125rem;
    /* Знак кода спускается на одну ступень, а не на две: шесть знаков
       на 360 px укладываются в ряд и при 25 px, а заголовок при этом
       остаётся крупнее — иерархия не переворачивается. */
    --text-code: var(--text-lg);
  }

  .threshold {
    gap: var(--space-5);
    padding: var(--space-5) var(--space-4) var(--space-6);
  }

  /* Разделитель между двумя соседними короткими блоками на самом узком
     экране не разделяет ничего, а стоит 33 px высоты. Осмотр 2026-08-21:
     без него кнопка на 360×640 возвращается из-за сгиба (645 при 640). */
  .stamp__rule {
    display: none;
  }

  /* Экран задачи (ожидание и запрос кода) на узком экране: строка
     принадлежности уходит из показа, оставаясь в дереве доступности.
     Осмотр 2026-08-21: в состоянии «код не подошёл» на 360×640 кнопка
     оказалась ниже сгиба на 93 px. Человек на этом экране уже знает, где
     он: его задача — ввести код, а не прочитать, чей это вход.
     Свойства повторяют `.visually-hidden`: составить их в CSS нельзя. */
  .threshold--task .stamp__owner {
    position: absolute;
    width: 1px;
    height: 1px;
    margin: -1px;
    padding: 0;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
  }
}

/* Настольный вид — та же одна колонка, что и на телефоне.
 *
 * ## Почему не две
 *
 * Двухколоночный бланк держался с 2026-08-17 и 2026-08-24 был снят: левой
 * колонке **нечего держать**. До входа мы намеренно не показываем ничего
 * о системе (DESIGN_SYSTEM.md §6, «что на экране быть не должно»), значит
 * в колонке всегда стоят две короткие строки и пустота между ними. Растянув
 * колонку на всю высоту, мы растянули и пустоту: на 1440 между маркой
 * и принадлежностью оставалось около 700 px, а справа от формы — 360 px.
 * Пустота не исчезла, а стала вертикальным столбом посреди экрана.
 *
 * ## Что взято образцом
 *
 * Телефонный вид, который вышел: марка, под ней принадлежность одной
 * строкой, под ними форма во всю ширину колонки. Настольный отличается
 * от него **только** тем, что колонка не растягивается шире 400 px
 * и стоит по центру — по горизонтали и по вертикали.
 *
 * Одна композиция на все ширины, а не две. Разъезжаться нечему.
 */
@media (min-width: 640px) {
  .threshold {
    /* Колонка одна и её ширина — предел, а не «сколько поместится».
       Поле ввода шириной в две трети экрана заказчик дважды назвал
       вёрсткой нулевых. */
    grid-template-columns: minmax(0, var(--measure-form));
    justify-content: center;
    padding: var(--space-8) var(--space-6);
  }

  .stamp,
  .entry {
    width: 100%;
    max-width: var(--measure-form);
  }
}

/* --- Угловой штамп: марка и принадлежность ------------------------------ */

.stamp {
  max-width: var(--measure-form);
}

/* Марка — не акцент. Акцент один, и он означает действие; покрашенная им
   марка отняла бы у него значение. Различие несут начертание и разрядка. */
.stamp__mark {
  margin: 0;
  font-family: var(--font-display);
  font-size: var(--text-lg);
  font-weight: 700;
  line-height: var(--leading-head);
  letter-spacing: 0.01em;
}

.stamp__owner {
  margin: var(--space-2) 0 0;
  font-size: var(--text-xs);
  line-height: 1.4;
  letter-spacing: 0.02em;
  color: var(--ink-muted);
  /* Строка стоит в одну строку и не переносится.
   *
   * До 2026-08-24 здесь было `text-wrap: balance` — оно ставилось, когда
   * строка жила в колонке шириной 191 px и всё равно ломалась. Выравнивание
   * половин **не лечило дефект, а делало его ровным**: разделитель `·`
   * оставался висеть в конце первой строки, и это первое, что видно
   * на снимке.
   *
   * В колонке 400 px (и в 328 px на самом узком телефоне) строка помещается
   * целиком — замерено на всех пяти ширинах прогоном. Заявление держится
   * не свойством `text-wrap`, а **мерой**: перенос запрещён, и если строка
   * когда-нибудь перестанет помещаться, прогон уронит не вёрстку,
   * а проверку — и тогда сокращается текст, а не остаётся висячий знак. */
  white-space: nowrap;
}

/* Линия бланка — единственная линия на пороге, и с 2026-08-24 она же
   единственное, чем экран объявляет себя бланком, а не карточкой.
   Вертикальная линия во весь экран убрана вместе с колонкой, которую она
   подпирала: линия, отчёркивающая пустоту, отчёркивает пустоту.

   Идёт во всю ширину колонки: осмотр 2026-08-23 показал, что обрезанная
   по 12rem она кончалась раньше текста над ней и читалась как случайный
   штрих, а не как линия документа. На самом узком телефоне снята —
   там она стоила 33 px и кнопка уходила за сгиб (осмотр 2026-08-21). */
.stamp__rule {
  height: var(--rule-width);
  background: var(--rule);
  border: 0;
  margin: var(--space-4) 0 0;
}

/* --- Основная колонка: заголовок документа и форма ----------------------- */

.entry {
  width: 100%;
  max-width: var(--measure-form);
}

/* Самое крупное на экране — то, зачем человек пришёл. */
.entry__title {
  margin: 0 0 var(--space-5);
  font-family: var(--font-display);
  font-size: var(--text-3xl);
  font-weight: 700;
  line-height: var(--leading-head);
  letter-spacing: -0.015em;
  text-wrap: balance;
}

/* Строка, которая нужна именно на этом шаге, а не рассказ об устройстве
   системы. Пусто — тоже допустимое состояние: на странице входа её нет. */
.entry__lead {
  margin: 0 0 var(--space-5);
  color: var(--ink-muted);
  text-wrap: pretty;
}

.entry__lead strong {
  color: var(--ink);
}

/* Обходной путь: то, что делать, если основной не сработал. Стоит ПОСЛЕ
   действия, а не перед ним, — иначе отодвигает кнопку за сгиб. */
.entry__aside {
  margin: var(--space-6) 0 0;
  padding-top: var(--space-4);
  border-top: var(--rule-width) solid var(--rule);
  font-size: var(--text-sm);
  color: var(--ink-muted);
  text-wrap: pretty;
}

.entry__aside p {
  margin: 0 0 var(--space-2);
}

.entry__aside p:last-child {
  margin-bottom: 0;
}

/* --- Поля --------------------------------------------------------------- */

.field {
  margin: 0 0 var(--space-4);
}

/* Поле кода — единственное на экране и главное на нём. Промежуток под ним
   больше обычного не ради воздуха: там показывается состояние «отправлено,
   ответа нет», и при обычном промежутке полоса читалась как обводка
   кнопки (осмотр 2026-08-23). */
.field--code {
  margin-bottom: var(--space-5);
}

.field__label {
  display: block;
  margin-bottom: var(--space-2);
  font-size: var(--text-sm);
  color: var(--ink-muted);
}

.field__input {
  width: 100%;
  padding: var(--space-3) var(--space-3);
  font-family: inherit;
  font-size: var(--text-base);
  color: var(--ink);
  background: var(--surface);
  border: var(--rule-width) solid var(--rule-strong);
  border-radius: var(--radius-field);
}

/* Умолчание браузера бледнее порога: подсказка в поле — тоже текст. */
.field__input::placeholder {
  color: var(--ink-muted);
  opacity: 1;
}

/* Обводка фокуса одна, и это утверждение о том, что видно, а не о том,
 * сколько правил написано.
 *
 * Прежде их было две: общая обводка `:focus-visible` в двух пикселях
 * от поля — и граница самого поля под ней. Два концентрических кольца
 * читаются как умолчание браузера поверх своего (разбор 2026-08-24),
 * и снятие одного лишь цвета этого не лечило: кольцо и граница остаются
 * двумя линиями, какого бы цвета они ни были.
 *
 * Поэтому обводка **накладывается на границу**: отрицательный отступ ведёт
 * её ровно по краю поля, граница красится тем же, и человек видит одну
 * линию в два пикселя. Первый снимок 2026-08-24 показал ровно это место —
 * поле с `autofocus` на странице входа. */
.field__input:focus-visible {
  border-color: var(--action);
  outline: 2px solid var(--action);
  outline-offset: -1px;
}

/* --- Кнопка ------------------------------------------------------------- */

.button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  /* 48 px — ступень шкалы и с запасом выше нижней границы цели нажатия.
     44 px давали кнопку, читавшуюся как плоский прямоугольник: при ширине
     формы 400 px высота — единственное, чем она заявляет себя главным
     действием экрана. */
  min-height: var(--space-7);
  padding: var(--space-3) var(--space-5);
  font-family: inherit;
  font-size: var(--text-base);
  font-weight: 600;
  letter-spacing: 0.01em;
  text-align: center;
  color: var(--surface);
  background: var(--action);
  border: var(--rule-width) solid var(--action);
  border-radius: var(--radius-field);
  cursor: pointer;
  transition: background-color var(--state-change), border-color var(--state-change);
}

.button:hover {
  background: var(--action-hover);
  border-color: var(--action-hover);
}

/* Нажатие возвращает кнопку к основному тону: она «уходит внутрь», а не
   подпрыгивает. Отклик на действие, а не украшение. */
.button:active {
  background: var(--action);
  border-color: var(--action);
}

.button[disabled] {
  background: var(--ink-muted);
  border-color: var(--ink-muted);
  cursor: not-allowed;
}

.button--wide {
  width: 100%;
}

.button--quiet {
  color: var(--action);
  background: transparent;
  border-color: var(--rule-strong);
}

.button--quiet:hover,
.button--quiet:active {
  color: var(--action);
  background: transparent;
  border-color: var(--action);
}

/* Запасной путь — не второе основное действие.
 *
 * ## Что было не так
 *
 * На пороге стояли **две кнопки одной ширины**, одна под другой, и между
 * ними висела отдельной строкой подпись «Не помните пароль?». Три блока
 * подряд, из которых главный отличался только заливкой: запасной путь
 * читался как равный основному, а подпись — как ничья.
 *
 * ## Чем это лечится
 *
 * Не размером кнопки, а **разрядом элемента**. Основное действие — залитая
 * кнопка во всю ширину; запасной путь — текст, набранный краской действия.
 * Обводки у него нет: обводка и есть то, что уравнивает его с кнопкой.
 * Вопрос и ответ соединены в одну строку — «Не помните пароль? Войти
 * по коду из письма», — потому что вместе они и есть одно предложение.
 *
 * ## Цель нажатия остаётся 48 px
 *
 * Текстовый вид не освобождает от нижней границы цели нажатия на телефоне
 * (`DESIGN_REVIEW_PLAN.md` §5.5): высота набирается отступом, невидимым
 * на экране, а не рамкой. `min-height` наследуется от `.button`.
 */
.button--text {
  padding: 0;
  font-size: var(--text-sm);
  color: var(--action);
  background: transparent;
  border-color: transparent;
  border-radius: 0;
}

/* Подчёркивание возвращается под указателем — тогда оно сообщает «сюда
   можно нажать», а не стоит всегда (`DESIGN_PORTFOLIO.md` §1в: синяя
   подчёркнутая строка — подпись вёрстки нулевых). Правило для `a.button`
   подчёркивание снимает, поэтому здесь имя тега названо: без него
   побеждает оно.

   **Под фокусом подчёркивания нет, и это то же требование, что и у поля.**
   Обводка фокуса одна: кольцо и подчёркивание вместе — две метки на одно
   состояние, то есть тот же дефект, что и два кольца, только другой формы. */
a.button--text:hover,
button.button--text:hover {
  color: var(--action-hover);
  background: transparent;
  border-color: transparent;
  text-decoration: underline;
  text-underline-offset: 0.15em;
}

a.button--text:focus-visible,
button.button--text:focus-visible {
  background: transparent;
  border-color: transparent;
  text-decoration: none;
}

.button--quiet[disabled] {
  color: var(--ink-muted);
  background: transparent;
  /* Не `--rule`: 1.38:1 к фону. Недоступная кнопка формально освобождена
     от порога 3:1, но она здесь и есть сообщение «повтор ещё не разрешён» —
     невидимая, она это сообщение не передаёт. */
  border-color: var(--rule-strong);
}

/* --- Сообщения о состоянии ---------------------------------------------- */
/* Различаются не только цветом: цвет — не единственный носитель различия. */

.notice {
  margin: 0 0 var(--space-5);
  padding: var(--space-3) var(--space-4);
  border-left: 3px solid var(--rule-strong);
  background: var(--surface);
  font-size: var(--text-base);
}

.notice__title {
  margin: 0 0 var(--space-1);
  font-weight: 700;
}

.notice__body {
  margin: 0;
  color: var(--ink-muted);
  text-wrap: pretty;
}

.notice--error {
  border-left-color: var(--danger);
}

.notice--error .notice__title {
  color: var(--danger);
}

.notice--pending {
  border-left-color: var(--pending);
}

.notice--pending .notice__title {
  color: var(--pending);
}

.notice--denied {
  border-left-color: var(--action);
}

.notice--denied .notice__title {
  color: var(--action);
}

/* --- Отказ: отдельная страница ------------------------------------------ */

/* Обходной путь стоит рядом с отказом, а не в другом разделе, и кнопки
   «Повторить» здесь нет: от повтора отказ не изменится. Образец портфеля
   для состояния «нельзя, и вот почему» (DESIGN_PORTFOLIO.md §1). */
.route {
  margin: var(--space-5) 0 0;
}

/* Обходной путь — это действие, а не строка в тексте. Поэтому он выглядит
   управляющим элементом: обведён, с целью нажатия из шкалы. Синяя
   подчёркнутая строка на её месте была первым, что заказчик назвал
   вёрсткой нулевых. */
.route .button {
  width: 100%;
}

.route--inline {
  display: flex;
  flex-wrap: wrap;
  /* Не `baseline`: у кнопки своя высота цели нажатия, и опорная линия
     у неё берётся не оттуда, откуда кажется. Оба ребёнка — один и тот же
     кегль, поэтому середина строки и опорная линия здесь совпадают,
     а `center` не зависит от того, как посчитана вторая. */
  align-items: center;
  column-gap: var(--space-2);
  margin: var(--space-4) 0 0;
}

/* Кнопка в строке ширину строки не занимает: `.route .button` растягивает
   одиночный обходной путь на экранах отказа, а здесь путь не одиночный. */
.route--inline .button {
  width: auto;
}

.route__lead {
  font-size: var(--text-sm);
  color: var(--ink-muted);
}

/* --- Ввод одноразового кода: разлинованная строка на шесть знаков --------
 *
 * ## Что это и почему не шесть полей
 *
 * Шесть отдельных полей — обычный способ показать одноразовый код, и он
 * ломает ровно то, ради чего этот экран существует: подстановку кода
 * из письма и работу диктора. Поле здесь **одно** — `code`, с подписью,
 * с `autocomplete="one-time-code"`, — а шесть знакомест **нарисованы**
 * и скрыты от дерева доступности. Диктор слышит одно поле с одним именем;
 * телефон подставляет код в настоящее поле; вставка из буфера работает
 * сама собой, потому что вставлять есть куда.
 *
 * ## Почему знакоместа — линии, а не рамки
 *
 * Шесть скруглённых рамок — тот самый шаблонный вход, от которого заказчик
 * отказывался. Строка бланка, разлинованная под вписывание от руки, —
 * из мира этого продукта: зачётная книжка, ведомость, поле для номера.
 * Тот же приём, что и в остальной сетке порога, только в масштабе поля.
 *
 * ## Без сценариев экран остаётся рабочим
 *
 * Знакоместа показываются только при `data-enhanced`, который ставит
 * сценарий. Без него это обычное поле с табличными цифрами — то, что было
 * до сегодняшнего дня. Улучшение не должно быть условием входа.
 */

.code-entry {
  position: relative;
}

/* Строка знакомест. Показывается только тогда, когда есть кому её вести. */
.code-entry__slots {
  display: none;
}

.code-entry[data-enhanced] .code-entry__slots {
  position: relative;
  display: grid;
  grid-template-columns: repeat(6, minmax(0, 1fr));
  gap: var(--space-2);
}

/* Знакоместо: линия бланка и знак над ней. Высота из шкалы — 48 px, она же
   нижняя граница цели нажатия, хотя нажимать здесь можно куда угодно:
   поле накрывает строку целиком. */
.code-slot {
  display: flex;
  align-items: center;
  justify-content: center;
  height: var(--space-7);
  font-family: var(--font-display);
  font-size: var(--text-code);
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  line-height: 1;
  color: var(--ink);
  border-bottom: 2px solid var(--rule-strong);
  transition: border-color var(--state-change);
}

/* Знакоместо, куда попадёт следующий знак. Линия берёт цвет действия:
   здесь и вправду происходит действие человека. */
.code-slot[data-active] {
  border-bottom-color: var(--action);
}

/* Знак занял место. Движение сообщает ровно это и длится ровно столько,
   сколько нужно, чтобы это заметить.
   `--land-delay` ставит сценарий: при вставке кода целиком знаки садятся
   по очереди, и видно, что принят весь код, а не один знак. */
.code-slot[data-landed] {
  animation: code-land var(--motion-land) var(--ease-arrive) both;
  animation-delay: var(--land-delay, 0ms);
}

@keyframes code-land {
  from {
    opacity: 0;
    transform: translateY(-4px);
  }
  to {
    opacity: 1;
    transform: none;
  }
}

/* Каретка. Настоящая скрыта (`caret-color`), эта — нарисована, потому что
   строка разбита на знакоместа и системная каретка стояла бы не там.
   Её перемещение и есть показ остатка: она идёт слева направо и
   останавливается там, где кончился ввод. */
.code-entry__caret {
  position: absolute;
  bottom: 0;
  left: 0;
  width: 2px;
  height: var(--space-5);
  background: var(--action);
  opacity: 0;
  transform: translateX(var(--caret-x, 0));
  transition: transform var(--motion-travel) var(--ease-arrive);
}

/* Каретка не мигает, и это решение, а не упущение.
 *
 * Мигание ничего не сообщает — оно воспроизводит поведение системной
 * каретки, которую мы же и спрятали. Осмотр 2026-08-23 показал и вторую
 * причину: в нижней фазе каретка почти пропадала, и на снимке её не было
 * видно вовсе. Движение здесь несёт только ПЕРЕХОД каретки к следующему
 * знакоместу; стоять на месте она обязана молча. */
.code-entry[data-enhanced][data-focused]:not([data-complete]) .code-entry__caret {
  opacity: 1;
}

/* Поле поверх строки: невидимое, но настоящее. Кегль не опускается ниже
   основного — поле с мелким кеглем телефон увеличивает при фокусе
   и сдвигает макет, даже когда текста в нём не видно. */
.code-entry[data-enhanced] .code-input {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  padding: 0;
  background: transparent;
  border: 0;
  border-radius: var(--radius-field);
  color: transparent;
  caret-color: transparent;
  letter-spacing: 0;
  font-size: var(--text-base);
}

.code-entry[data-enhanced] .code-input::selection {
  color: transparent;
  background: transparent;
}

/* Отказ сервера, а не длины ввода.
 *
 * Признак ставит шаблон и **только** когда сервер ответил «код не подошёл».
 * Движение, привязанное к шестому знаку, сообщало бы приговор раньше, чем
 * его вынесли, — это `П-DEC-025` в исполнении оформления.
 * Смещение — два пикселя: отклик, а не трюк. */
.code-entry[data-refused] .code-slot {
  border-bottom-color: var(--danger);
}

.code-entry[data-refused][data-enhanced] .code-entry__slots {
  animation: code-refuse var(--motion-refuse) var(--ease-arrive) 1;
}

@keyframes code-refuse {
  0%,
  100% {
    transform: none;
  }
  25% {
    transform: translateX(-2px);
  }
  75% {
    transform: translateX(2px);
  }
}

/* «Отправлено, ответа нет» — состояние, а не пауза.
 *
 * Знаки гаснут до приглушённого тона (правка невозможна: поле переведено
 * в режим чтения), а под строкой идёт полоса без известного конца — потому
 * что конец и вправду неизвестен. Это то самое обязательное состояние,
 * которое нельзя показывать ни как «сделано», ни как «не сделано». */
.code-entry[data-state="checking"] .code-slot {
  color: var(--ink-muted);
  /* Не `--rule`: 1.38:1 к фону. Ровно на этом уже спотыкались 2026-08-23
     с недоступной кнопкой повтора — приглушить и **погасить** не одно
     и то же. Гаснут знаки (7.35:1 вместо 17.2:1), строка остаётся видимой:
     она и есть то, чего сейчас ждут. */
  border-bottom-color: var(--rule-strong);
}

.code-entry[data-state="checking"] .code-entry__caret {
  opacity: 0;
}

.code-entry__progress {
  display: none;
}

.code-entry[data-state="checking"] .code-entry__progress {
  position: absolute;
  left: 0;
  right: 0;
  bottom: calc(-1 * var(--space-2));
  display: block;
  height: 2px;
  overflow: hidden;
}

.code-entry__progress::after {
  content: "";
  position: absolute;
  top: 0;
  bottom: 0;
  width: 30%;
  background: var(--action);
  animation: code-checking 1100ms linear infinite;
}

@keyframes code-checking {
  from {
    transform: translateX(-100%);
  }
  to {
    transform: translateX(430%);
  }
}

/* Строка заполнена: все шесть линий берут цвет действия разом.
   Это последнее, что движение обязано сообщить до отправки, — «на месте
   все шесть», а не «пятый принят». Каретка при этом гаснет: вписывать
   больше некуда. */
.code-entry[data-complete] .code-slot {
  border-bottom-color: var(--action);
}

/* --- Ожидание кода ------------------------------------------------------ */

.countdown {
  font-variant-numeric: tabular-nums;
}

.hint {
  margin: var(--space-2) 0 0;
  font-size: var(--text-sm);
  color: var(--ink-muted);
}

.code-input {
  font-variant-numeric: tabular-nums;
  letter-spacing: 0.3em;
  font-size: var(--text-md);
}

.resend {
  margin: var(--space-5) 0 0;
}

/* Остаток до повторного запроса — настоящая величина, а не украшение.
 *
 * Полоса показывает **долю** оставшегося от той задержки, которая
 * назначена сервером (минута, потом пять, потом пять). Обе величины
 * приходят с сервера: и остаток, и сама задержка. Считать в браузере
 * нельзя — браузерный отсчёт врёт при возврате на вкладку и разрешил бы
 * нажать раньше, чем сервер примет.
 *
 * Цвет — не цвет действия: ожидание действием не является, а акцент
 * означает действие. Иначе он перестаёт что-либо означать. */
.countdown-track {
  display: none;
}

.countdown-track[data-enhanced] {
  display: block;
  height: 2px;
  margin: var(--space-2) 0 var(--space-3);
  background: var(--rule);
  overflow: hidden;
}

.countdown-track__fill {
  display: block;
  height: 100%;
  width: calc(var(--countdown-left, 1) * 100%);
  /* Не `--rule-strong`: он даёт 3.61:1 **к бумаге**, но лежит-то полоса
     на дорожке `--rule`, и там выходило 2.61:1. Замерено на собранном
     экране 2026-08-23 — сравнивать надо ту границу, которую человек
     и различает, а не ту, что удобнее посчитать. С `--ink-muted` — 5.3:1
     к дорожке и 7.35:1 к бумаге.
     Цвет по-прежнему не цвет действия: ожидание действием не является. */
  background: var(--ink-muted);
  transition: width 1000ms linear;
}

/* --- Итог выхода: настоящая таблица, а не разметка под таблицу ----------- */

.outcomes {
  width: 100%;
  margin: 0;
  border-collapse: collapse;
  font-size: var(--text-sm);
}

.outcomes caption {
  margin-bottom: var(--space-2);
  text-align: left;
  color: var(--ink-muted);
}

.outcomes th,
.outcomes td {
  padding: var(--space-2) var(--space-3) var(--space-2) 0;
  text-align: left;
  vertical-align: top;
  border-bottom: var(--rule-width) solid var(--rule);
}

.outcomes th {
  font-weight: 500;
  color: var(--ink-muted);
}

/* Линия последней строки и линия приписки под таблицей стояли рядом
   и читались как двойная — осмотр 2026-08-23. Закрывает таблицу одна. */
.outcomes tr:last-child td {
  border-bottom: 0;
}

/* --- Занятость ---------------------------------------------------------- */

[aria-busy="true"] .button {
  background: var(--ink-muted);
  border-color: var(--ink-muted);
}

/* --- Движение объясняет переход или отсутствует ------------------------- */

/* Переход между нашими экранами, а не подмена одного другим.
 *
 * Вход по коду — два шага одного дела: номер, потом код. Шаги — наши
 * страницы, и переход между ними браузер может показать переходом, если
 * обе страницы об этом попросили. Штамп при этом остаётся на месте
 * (у него своё имя перехода), а меняется только содержимое бланка —
 * то есть движение сообщает ровно то, что произошло: тот же бланк,
 * следующий шаг.
 *
 * Успешный ввод кода уводит человека в сервис, из которого он пришёл.
 * Его страница о переходе не просила, и там не будет ничего — это
 * не поломка, а граница: обещать переход туда, где его не будет,
 * значит обещать неправду. Здесь заявлено только своё.
 *
 * Браузеры без поддержки правило просто не поймут — ни к чему не обязывает.
 */
@view-transition {
  navigation: auto;
}

.stamp {
  view-transition-name: stamp;
}

.entry {
  view-transition-name: entry;
}

/* Человек, отключивший движение, получает то же самое без него — спокойный
 * экран, а не сломанный.
 *
 * Общее правило гасит длительности, и этого достаточно для переходов
 * и откликов. Но двум местам общего правила мало, и оба надо назвать:
 *
 * 1. **бегущая полоса проверки.** Погашенная длительность оставила бы её
 *    замершей за краем строки — состояние «ответа нет» пропало бы вовсе.
 *    Поэтому здесь полоса не бежит, а стоит во всю ширину: то же сообщение
 *    без движения. Проверено осмотром 2026-08-23 отдельным снимком;
 * 2. **переход между экранами.** Кросс-документный переход браузер ведёт
 *    своей анимацией, а не нашей длительностью, и общее правило его
 *    не касается. Он отключается здесь поимённо.
 */
@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
  }

  ::view-transition-group(*),
  ::view-transition-old(*),
  ::view-transition-new(*) {
    animation: none !important;
  }

  .code-entry__progress::after {
    width: 100%;
    background: var(--rule-strong);
    animation: none !important;
  }

}

/* =========================================================================
   ПОВЕРХНОСТЬ ВТОРАЯ: журнал службы ИТ
   =========================================================================

   `DESIGN_SYSTEM.md` §1: поверхностей две, и режимы у них разные.

   | Порог                        | Журнал                              |
   |------------------------------|-------------------------------------|
   | четыре тысячи человек         | несколько человек службы ИТ         |
   | раз в день, мельком           | подолгу, по делу                    |
   | один путь на экран            | разделы, таблицы, перечни           |
   | крупный кегль, объясняющий тон| плотность и сканируемость           |

   Общее — грамматика: та же пара начертаний, та же шкала отступов, та же
   палитра, тот же способ показать состояние. Разное — плотность и ритм.

   ## Что здесь несёт характер

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

   Дисплейное начертание работает ровно в двух местах — марка и заголовок
   страницы. Подписи, кнопки и данные набраны интерфейсным: дисплейное
   в подписи управляющего элемента — костюм, а не решение.

   ## Основной текст 16 px и на этой поверхности

   Режим Operate обычно берёт 14 px. Здесь нельзя: осмотр считает провалом
   основной текст меньше 16 px на мобильном виде, и не из вкуса — меньший
   вызывает автоматическое увеличение при фокусе и сдвигает макет. Плотность
   набирается отступами, высотой строки и высотой строки таблицы, а не
   уменьшением кегля. Подписи столбцов и приписки — ступень вниз: они
   не основной текст.
   ------------------------------------------------------------------------ */

/* Разделы стоят перед содержанием на каждом экране. Тот, кто ходит
   с клавиатуры, обязан иметь способ их пропустить — иначе он проходит
   их заново на каждой странице. */
.skip {
  position: absolute;
  left: -10000px;
  top: 0;
  padding: var(--space-2) var(--space-4);
  background: var(--surface);
  border: var(--rule-width) solid var(--action);
  z-index: 1;
}

.skip:focus-visible {
  left: var(--space-4);
  top: var(--space-2);
}

/* --- Шапка: марка, раздел, разделы -------------------------------------- */

.ledger__head {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--space-2) var(--space-5);
  max-width: var(--measure-ledger);
  margin: 0 auto;
  padding: var(--space-4) var(--space-4) var(--space-3);
  border-bottom: var(--rule-width) solid var(--rule);
}

/* Марка мельче, чем на пороге: здесь она подпись бланка, а не имя,
   которое человек читает впервые. */
.ledger__mark {
  margin: 0;
  font-family: var(--font-display);
  font-size: var(--text-base);
  font-weight: 700;
  letter-spacing: 0.01em;
}

.ledger__desk {
  margin: 0;
  font-size: var(--text-sm);
  color: var(--ink-muted);
  letter-spacing: 0.02em;
}

.ledger__nav {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2) var(--space-4);
  margin-left: auto;
}

.ledger__nav a {
  display: inline-block;
  padding: var(--space-1) 0;
  border-bottom: 2px solid transparent;
  font-size: var(--text-sm);
}

/* Где я — не только краской. Признак в разметке (`aria-current`) слышен
   диктору, линия и вес видны глазу. */
.ledger__nav a[aria-current="page"] {
  color: var(--ink);
  font-weight: 700;
  border-bottom-color: var(--action);
}

/* --- Лист --------------------------------------------------------------- */

.ledger {
  max-width: var(--measure-ledger);
  margin: 0 auto;
  padding: var(--space-6) var(--space-4) var(--space-8);
}

.work__title {
  margin: 0 0 var(--space-2);
  font-family: var(--font-display);
  font-size: var(--text-xl);
  font-weight: 700;
  line-height: var(--leading-head);
  letter-spacing: -0.01em;
  text-wrap: balance;
}

/* Идентификатор субъекта — не заголовок и не подпись, а реквизит документа:
   набран табличными цифрами и стоит под заголовком, как номер под шапкой. */
.work__id {
  margin: 0 0 var(--space-5);
  font-size: var(--text-sm);
  color: var(--ink-muted);
  overflow-wrap: anywhere;
}

.work__lead {
  max-width: var(--measure-prose);
  margin: 0 0 var(--space-6);
  color: var(--ink-muted);
  text-wrap: pretty;
}

/* --- Разделы ------------------------------------------------------------ */

.sheaf {
  display: grid;
  gap: 0;
}

.leaf {
  padding: var(--space-5) 0;
  border-top: var(--rule-width) solid var(--rule);
}

.leaf__title {
  margin: 0 0 var(--space-2);
  font-size: var(--text-md);
  font-weight: 700;
  line-height: var(--leading-head);
}

.leaf__note {
  max-width: var(--measure-prose);
  margin: 0 0 var(--space-3);
  font-size: var(--text-sm);
  color: var(--ink-muted);
  text-wrap: pretty;
}

.row {
  margin: var(--space-4) 0 0;
}

.row--actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-3);
  align-items: center;
}

/* Приписка под полем. Постоянная, а не подсказка внутри поля: подсказка
   пропадает при первом знаке, ровно когда становится нужна. */
.field__note {
  max-width: var(--measure-prose);
  margin: var(--space-2) 0 0;
  font-size: var(--text-sm);
  color: var(--ink-muted);
  text-wrap: pretty;
}

.field__input--lines {
  font-family: inherit;
  line-height: var(--leading-text);
  resize: vertical;
}

code {
  font-family: ui-monospace, "Cascadia Mono", Menlo, Consolas, monospace;
  font-size: var(--text-sm);
}

.tabular {
  font-variant-numeric: tabular-nums;
}

/* --- Кнопка необратимого действия --------------------------------------- */

/* Краска смысловая, а не четвёртый акцент: `--danger` уже означает потерю
   и уже посчитан. Отделена от обычных действий и стоит одна. */
.button--danger {
  background: var(--danger);
  border-color: var(--danger);
}

.button--danger:hover,
.button--danger:active {
  background: var(--danger);
  border-color: var(--ink);
}

/* --- «Нельзя, и вот почему» --------------------------------------------- */

/* Тон нейтральный: это не поломка системы и не ошибка человека. Красный
   здесь означал бы «что-то сломалось» и звал бы повторить — а повторять
   нечего: право от нажатия не появится. */
.refusal {
  max-width: var(--measure-prose);
  margin: var(--space-4) 0 0;
  padding: var(--space-4);
  background: var(--surface);
  border: var(--rule-width) solid var(--rule-strong);
  border-radius: var(--radius-panel);
}

.refusal__title {
  margin: 0 0 var(--space-2);
  font-weight: 700;
}

.refusal__body {
  margin: 0 0 var(--space-2);
  color: var(--ink-muted);
  text-wrap: pretty;
}

.refusal__note {
  margin: 0;
  font-size: var(--text-sm);
  color: var(--ink-muted);
  text-wrap: pretty;
}

.refusal__route {
  margin: var(--space-4) 0 0;
}

/* --- Корешок исхода ------------------------------------------------------
 *
 * Отрывной листок необратимого действия — то, ради чего эти экраны и нужны
 * поверх JSON. Две части, и разделение между ними и есть предмет:
 * достоверное отдельно, неизвестное отдельно.
 *
 * Перфорация понизу — не украшение: она и означает отрывной корешок,
 * то есть запись о том, что уже произошло и чего не отменить. Верхняя
 * линия тяжёлая, нижняя пунктирная — так корешок и выглядит в бумаге. */
.stub {
  margin: 0 0 var(--space-6);
  padding: var(--space-5) var(--space-5) var(--space-4);
  background: var(--surface);
  border-top: 2px solid var(--ink);
  border-bottom: var(--rule-width) dashed var(--rule-strong);
}

.stub__title {
  margin: 0 0 var(--space-3);
  font-size: var(--text-md);
  font-weight: 700;
  line-height: var(--leading-head);
}

.stub__done {
  max-width: var(--measure-prose);
  margin: 0 0 var(--space-4);
  text-wrap: pretty;
}

/* Неизвестное — не ошибка и не успех. Краска ожидания, та же, что
   на пороге у состояния «сейчас проверить не удаётся». */
.stub__unknown {
  max-width: var(--measure-prose);
  margin: 0 0 var(--space-3);
  padding-left: var(--space-3);
  border-left: 3px solid var(--pending);
  color: var(--pending);
  text-wrap: pretty;
}

.stub__note {
  max-width: var(--measure-prose);
  margin: var(--space-4) 0 0;
  font-size: var(--text-sm);
  color: var(--ink-muted);
  text-wrap: pretty;
}

/* --- Предупреждение перед необратимым ----------------------------------- */

.caution {
  max-width: var(--measure-prose);
  margin: var(--space-5) 0 0;
  padding: var(--space-4);
  border: var(--rule-width) solid var(--pending);
  border-radius: var(--radius-panel);
}

.caution__title {
  margin: 0 0 var(--space-2);
  font-weight: 700;
  color: var(--pending);
}

.caution__body {
  margin: 0;
  color: var(--ink-muted);
  text-wrap: pretty;
}

/* --- Таблица журнала ---------------------------------------------------- */

/* Настоящая таблица: связь строки со столбцом нужна не глазу, а диктору.
   Щёлкающихся строк здесь нет — строка журнала никуда не ведёт. */
.grid {
  width: 100%;
  margin: var(--space-3) 0 0;
  border-collapse: collapse;
  font-size: var(--text-base);
}

.grid caption {
  margin-bottom: var(--space-2);
  text-align: left;
  font-size: var(--text-sm);
  color: var(--ink-muted);
}

.grid th,
.grid td {
  padding: var(--space-3) var(--space-3) var(--space-3) 0;
  text-align: left;
  vertical-align: top;
  border-bottom: var(--rule-width) solid var(--rule);
  overflow-wrap: anywhere;
}

.grid th {
  font-size: var(--text-sm);
  font-weight: 500;
  color: var(--ink-muted);
  border-bottom-color: var(--rule-strong);
}

.grid td:last-child,
.grid th:last-child {
  padding-right: 0;
}

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

.pairs {
  margin: var(--space-3) 0 0;
}

.pairs dt {
  font-size: var(--text-sm);
  color: var(--ink-muted);
}

.pairs dd {
  margin: 0 0 var(--space-3);
  overflow-wrap: anywhere;
}

.listing {
  max-width: var(--measure-prose);
  margin: var(--space-3) 0 0;
  padding-left: var(--space-5);
}

.listing li {
  margin-bottom: var(--space-2);
  text-wrap: pretty;
}

/* --- Куда войти --------------------------------------------------------- */

/* Перечень служб на пороге. Различаются названием и строкой о деле —
   ни плиток, ни значков: логотипов служб у нас нет, и придумывать их нельзя. */
.listing--destinations {
  padding-left: 0;
  list-style: none;
}

.destination {
  padding: var(--space-3) 0;
  border-bottom: var(--rule-width) solid var(--rule);
}

.destination__name {
  display: block;
  font-size: var(--text-md);
}

.destination__note {
  display: block;
  margin-top: var(--space-1);
  font-size: var(--text-sm);
  color: var(--ink-muted);
  text-wrap: pretty;
}

/* --- Отметка выбора ----------------------------------------------------- */

/* Недоступная отметка остаётся видимой и читаемой: она несёт сведение —
   какой способ существует и почему его пока нельзя выбрать. Приглушается
   она подписью, а не исчезновением. */
.choice {
  display: grid;
  grid-template-columns: auto 1fr;
  align-items: baseline;
  gap: var(--space-1) var(--space-3);
  margin: 0 0 var(--space-3);
}

.choice__box {
  width: 18px;
  height: 18px;
}

.choice__label {
  font-weight: 500;
}

.choice__note {
  grid-column: 2;
  font-size: var(--text-sm);
  color: var(--ink-muted);
}

.choice__box:disabled + .choice__label {
  color: var(--ink-muted);
}

/* --- Кто вошёл и выход -------------------------------------------------- */

/* Имя читается первым, опознаватель — служебная строка под ним. Выход стоит
   рядом с именем: это одно и то же сведение о себе, а не отдельный раздел. */
.ledger__viewer {
  display: flex;
  align-items: baseline;
  gap: var(--space-4);
  margin-left: auto;
}

.ledger__who {
  margin: 0;
  text-align: right;
}

.ledger__id {
  display: block;
  font-size: var(--text-xs);
  color: var(--ink-muted);
}

/* --- Второстепенное в ячейке -------------------------------------------- */

/* Опознаватель и категория стоят рядом с именем и не спорят с ним: глазами
   ищут имя, а идентификатор нужен через раз — но нужен, поэтому он на месте,
   а не спрятан. */
.grid__aside {
  display: block;
  margin-top: var(--space-1);
  font-size: var(--text-sm);
  color: var(--ink-muted);
}

/* Перечень внутри ячейки: без маркеров и без отступа — иначе таблица
   расползается, а маркер здесь ничего не значит. */
.listing--tight {
  margin: 0;
  padding-left: 0;
  list-style: none;
}

.listing--tight li {
  margin-bottom: var(--space-2);
}

.listing--tight li:last-child {
  margin-bottom: 0;
}

/* --- Страницы перечня --------------------------------------------------- */

.pages {
  display: flex;
  gap: var(--space-3);
  margin-top: var(--space-4);
}

/* --- Пусто -------------------------------------------------------------- */

/* Пустое состояние говорит, что это значит и что делать. «Ничего не найдено»
   без продолжения — тупик, а человечков и картинок здесь не будет. */
.empty {
  max-width: var(--measure-prose);
  margin: var(--space-3) 0 0;
  padding: var(--space-4);
  border: var(--rule-width) dashed var(--rule-strong);
  border-radius: var(--radius-panel);
}

.empty__title {
  margin: 0 0 var(--space-2);
  font-weight: 700;
}

.empty__body {
  margin: 0 0 var(--space-2);
  font-size: var(--text-sm);
  color: var(--ink-muted);
  text-wrap: pretty;
}

.empty__body:last-child {
  margin-bottom: 0;
}

/* --- Ширина от 640: реквизиты в две колонки ----------------------------- */

@media (min-width: 640px) {
  .ledger__head,
  .ledger {
    padding-left: var(--space-6);
    padding-right: var(--space-6);
  }

  /* Наименование и значение рядом — так реквизиты и читают. На узком экране
     это невозможно: наименования длинные, и в две колонки они рвутся. */
  /* Промежутка между колонками нет намеренно: с ним линия под строкой
     рвётся посередине и читается как сломанная. Расстояние даёт отступ
     справа у наименования, и линия остаётся одной. Осмотр 2026-08-24. */
  .pairs {
    display: grid;
    grid-template-columns: minmax(0, 22rem) minmax(0, 1fr);
    column-gap: 0;
  }

  .pairs dt {
    padding: var(--space-2) var(--space-5) var(--space-2) 0;
    border-top: var(--rule-width) solid var(--rule);
  }

  .pairs dd {
    margin: 0;
    padding: var(--space-2) 0;
    border-top: var(--rule-width) solid var(--rule);
  }
}

/* --- Экран подтверждения: две половины рядом ----------------------------- */

/* Достоверное и неизвестное стоят **рядом**, а не одно под другим: различие
   между ними и есть предмет экрана. Побочно это убирает кнопку из-за сгиба
   на настольных ширинах — осмотр 2026-08-24 нашёл её там на 1366 (952
   при 768), и человек нажимал бы, не дочитав.

   На узком экране колонка одна: две колонки по 160 px не читаются. Там
   кнопка остаётся ниже сгиба, и это решение, а не недосмотр — довод
   в `DESIGN_REVIEW_PLAN.md` §5. */
.consequences > .leaf:first-child {
  border-top: var(--rule-width) solid var(--rule);
}

@media (min-width: 1024px) {
  .consequences {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    column-gap: var(--space-6);
  }
}

/* --- Мера поля ввода на поверхности журнала ------------------------------
 *
 * Поле ввода не растягивается на всю ширину листа. Осмотр 2026-08-24: поле
 * под идентификатор в сорок знаков занимало 896 px — та же вёрстка форм
 * нулевых, за которую заказчик дважды отчитал порог, только на второй
 * поверхности. Ширина поля обязана намекать на длину значения.
 *
 * Приписка под полем меру не наследует: она текст и живёт по своей мере
 * строки. Поэтому предел стоит на самом поле, а не на обёртке.
 */
.ledger .field__input {
  max-width: var(--measure-form);
}

/* Значения, которым 400 px мало по существу: адреса, перечни адресов,
   полные названия сервисов. Мера строки, а не ширина листа. */
.ledger .field--wide .field__input {
  max-width: var(--measure-prose);
}

/* Таблица исходов в корешке — по мере строки, а не во всю ширину листа:
   разъехавшиеся на 900 px две колонки заставляют вести глаз через пустоту. */
.stub .grid {
  max-width: var(--measure-prose);
}

/* ========================================================================
 * Композиция порога: две половины
 * ========================================================================
 *
 * Правка 2026-08-26 по третьему отзыву заказчика на один и тот же экран.
 * Прежняя мера — «колонка 400 px по центру» — сняла дефект, названный
 * в §1в (форма во всю ширину), и создала следующий: столбец в пустоте.
 * На 1440×900 это 520 px пустоты слева, 520 справа, 200 сверху, 200 снизу
 * и ни одного знака принадлежности, кроме строчки текста.
 *
 * Пустота вокруг формы — не строгость. Строгость — это composition,
 * в которой каждой части отведено место. Здесь их две:
 *
 *   левая  — институциональная: сплошное поле цвета организации,
 *            марка, принадлежность, назначение;
 *   правая — рабочая: светлое поле, заголовок, форма, запасной путь.
 *
 * Разметка не меняется: `.stamp` и `.entry` и так были двумя детьми
 * `.threshold` на всех пяти экранах порога. Меняется только сетка.
 *
 * Ограничения §1а соблюдены: ни картинок, ни узоров, ни градиентов,
 * ни теней. Плоский цвет, набор и одна линейка.
 */

/* --- Узкий экран: полоса сверху, форма под ней -------------------------- */

/* Отступы сняты с сетки и отданы содержимому. Иначе полосу приходится
   вытягивать за края отрицательными полями, а они обязаны совпадать
   с отступом сетки — который на трёх ширинах разный. Совпадение,
   которое нужно поддерживать руками, рано или поздно перестаёт
   совпадать, и полоса окажется оборванной. */
.threshold {
  padding: 0;
  /* Сетка во всю ширину на любой ширине: прежнее правило сжимало
     единственную колонку до меры формы и ставило её по центру — вместе
     с полосой. Ширину держит содержимое, а не сетка. */
  grid-template-columns: minmax(0, 1fr);
  justify-content: stretch;
  /* Содержимое идёт от верха. Центрирование по высоте вводилось, когда
     страница была столбцом в пустоте и «висела» одинаково при любом
     выравнивании. Теперь у страницы есть якорь — полоса принадлежности,
     и оторванная от верхнего края она читается как сбой отрисовки. */
  align-content: start;
}

.stamp {
  background: var(--mgimo-blue);
  color: #ffffff;
  margin: 0;
  padding: var(--space-5) var(--space-4);
  max-width: none;
}

.entry {
  max-width: var(--measure-form);
  padding-inline: var(--space-4);
  padding-block-end: var(--space-8);
}

.stamp__mark {
  color: #ffffff;
}

.stamp__owner {
  color: #b9c6d4;
}

.stamp__rule {
  background: #1f4470;
}

/* --- Широкий экран: две половины --------------------------------------- */

@media (min-width: 900px) {
  .threshold {
    grid-template-columns: minmax(320px, 40%) 1fr;
    /* Половины тянутся во всю высоту: цвет обязан доходить до краёв,
       иначе это снова плашка в пустоте. */
    align-content: stretch;
    align-items: stretch;
    justify-content: stretch;
    padding: 0;
  }

  /* Части собраны в один блок и стоят посередине высоты.
     Разведение марки к верху, а назначения к низу давало 650 px пустоты
     между ними — ту же пустоту, из-за которой переделывался весь экран,
     только перекрашенную. */
  .stamp {
    display: flex;
    flex-direction: column;
    justify-content: center;
    margin: 0;
    padding: var(--space-8);
    max-width: none;
  }

  .stamp__mark {
    font-size: var(--text-2xl);
  }

  .stamp__owner {
    /* В поле 40% ширины строка помещается целиком; запрет переноса
       остаётся мерой, а не украшением. */
    margin-top: var(--space-3);
  }

  .stamp__rule {
    margin-top: var(--space-5);
  }

  /* Рабочая половина: форма стоит по левому краю своей половины
     и по середине высоты. Не по центру экрана — по центру своей части. */
  /* Мера формы — 25rem внутри, а не снаружи: `box-sizing: border-box`
     означает, что внутренние отступы съели бы её, и форма стала бы
     уже трёхсот пикселей. Расстояние до края половины даёт поле,
     а не отступ. */
  .entry {
    align-self: center;
    justify-self: start;
    max-width: var(--measure-form);
    padding: 0;
    margin: var(--space-8);
  }

  /* Заголовок — ступень над подписями полей, а не пять ступеней.
     Прежний размер спорил с набором формы, а не вёл его. */
  .entry__title {
    font-size: var(--text-xl);
    margin-bottom: var(--space-6);
  }
}

/* Очень широкий экран: левая половина не растёт бесконечно, иначе поле
   цвета начинает перевешивать работу. */
@media (min-width: 1600px) {
  .threshold {
    grid-template-columns: minmax(320px, 560px) 1fr;
  }
}

/* ========================================================================
 * Отделка порога: набор официального бланка
 * ========================================================================
 *
 * Правка 2026-08-26, вторая за день. Первая дала композицию, но отделка
 * осталась вида «по умолчанию»: подписи полей — мелкий серый строчный,
 * рамки и скругления браузерные, кнопка плоская полоса.
 *
 * Направление взято от предмета, а не от вкуса. Университет —
 * дипломатический, и его собственный письменный обиход — официальный
 * бланк: надстрочная строка, марка, принадлежность, линейка, назначение.
 * Отсюда **разрядка** — приём русской типографики, которым в бланках
 * набирают служебные строки. Она здесь несёт различие ролей: разрядкой
 * набрано то, что называет (надстрочная строка, подписи полей), обычным
 * начертанием — то, что говорит.
 *
 * Риск сознательный и один: подписи полей набраны прописными вразрядку.
 * На малом кегле это читается как графа бланка, а не как крик; проверено
 * на снимке. Больше нигде прописных нет.
 *
 * Начертания — PT Serif и PT Sans: они лежат в проекте, покрывают
 * кириллицу и сделаны для русского набора. Внешних начертаний нет
 * и не может быть — политика содержимого разрешает только своё.
 */

.stamp__eyebrow {
  margin: 0 0 var(--space-4);
  font-size: var(--text-xs);
  font-weight: 700;
  line-height: 1;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: #7f97b4;
}

/* Марка — единственное место, где засечное начертание работает в полную
   силу. Разрядка отрицательная: крупный кегль сам разводит знаки. */
.stamp__mark {
  letter-spacing: -0.01em;
}

.stamp__owner {
  letter-spacing: 0.04em;
}

/* --- Графы бланка: подписи полей ---------------------------------------- */

.field__label {
  font-size: var(--text-xs);
  font-weight: 700;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--ink-muted);
}

/* --- Поля ввода ---------------------------------------------------------
 *
 * Скругление малое: 4 px на поле высотой 46 читается как кнопка мессенджера,
 * 2 px — как графа документа. Рамка тоньше и светлее, чем была: прежняя
 * `--rule-strong` спорила с обводкой фокуса, и поле выглядело выделенным
 * всегда.
 */
.field__input {
  min-height: 46px;
  padding: var(--space-3) var(--space-4);
  border-color: #c3cad2;
  border-radius: 2px;
}

.field__input:hover {
  border-color: #9aa4af;
}

/* Обводка фокуса одна и прилегает к рамке. Отступ в 2 px давал второе
   кольцо вокруг первого — то самое «двойное», которое запрещено §1в. */
.field__input:focus-visible {
  outline: 2px solid var(--action);
  outline-offset: 0;
  border-color: var(--action);
}

/* --- Основное действие --------------------------------------------------- */

.button--wide {
  min-height: 46px;
  border-radius: 2px;
  letter-spacing: 0.02em;
}

/* Вторичное действие стоит отдельной строкой под кнопкой и называется
   по исходу — «Восстановить пароль», а не по средству. Ведущий вопрос
   «Не помните пароль?» снят: он спрашивал ровно то, что отвечает ссылка. */
.route--inline {
  margin-top: var(--space-5);
}

/* --- Одно движение при открытии ------------------------------------------
 *
 * Не россыпь: створка бланка приходит слева, работа проявляется следом.
 * Порядок повторяет смысл — сначала «куда вы попали», потом «что делать».
 * Тем, кто движение отключил, не показывается ничего: правило целиком
 * стоит внутри запроса.
 */
@media (prefers-reduced-motion: no-preference) {
  @keyframes threshold-panel {
    from { opacity: 0; transform: translateX(-12px); }
    to   { opacity: 1; transform: none; }
  }

  @keyframes threshold-work {
    from { opacity: 0; transform: translateY(8px); }
    to   { opacity: 1; transform: none; }
  }

  .stamp {
    animation: threshold-panel 420ms cubic-bezier(0.2, 0.7, 0.3, 1) both;
  }

  .entry {
    animation: threshold-work 380ms cubic-bezier(0.2, 0.7, 0.3, 1) 120ms both;
  }
}
