/* =====================================================================
   Библиотека церкви — единая дизайн-система
   Спокойный светлый интерфейс, тёплые книжные цвета, минимализм.
   Никаких сторонних фреймворков: только собственный CSS.

   Главное правило файла: НИ ОДНОГО «случайного» числа.
   Любой отступ, размер текста, скругление и высота элемента управления
   берутся из шкал в разделе 1. Если нужного значения в шкале нет —
   значит, его быть не должно.

   Порядок разделов: переменные → базовое → каркас → компоненты → адаптив.
   Все медиазапросы собраны в конце файла (разделы 26–28): правила ниже
   по файлу выигрывают у правил той же силы, поэтому телефонная раскладка
   гарантированно перекрывает настольную.
   ===================================================================== */

/* ------------------------------------------------------ 1. Переменные */
:root {
  /* --- Цвета текста -------------------------------------------------
     Тёплый почти-чёрный вместо серого: на кремовой бумаге он читается
     мягче чистого #000 и выглядит как типографская краска, а не как экран. */
  --ink:        #17130f;
  --ink-2:      #544c43;
  /* Вторичные чернила. Прежний #857a6c на кремовой бумаге давал 3,9:1 —
     а именно им набрана вся мелкая капитель системы: счёт фонда, подписи
     полей, крошки, счётчики разделов. На 11 кегле это чтение на пределе,
     и вся вторичная информация выглядела выцветшей. Новый тон держит
     5,4:1 и остаётся тем же тёплым серо-коричневым: это по-прежнему
     тихий голос, просто он теперь действительно слышен. */
  --ink-3:      #6e6457;

  /* --- Поверхности: тёплая бумага --- */
  --bg:         #faf6ef;
  /* Рабочий стол библиотекаря. Тот же бумажный мир, но на полтона
     спокойнее: витрина — про созерцание, кабинет — про несколько часов
     работы подряд, и лишняя теплота там начинает утомлять. Разница
     намеренно едва заметная (тёплый сдвиг вдвое меньше, чем у витрины) —
     это по-прежнему бумага, а не серая панель управления. */
  --bg-work:    #faf8f4;
  /* Полоса разделов. Не панель другого цвета — тот же лист, лежащий на
     полтона глубже: колонка и стол должны читаться как две плоскости
     одной страницы, а не как два приложения рядом. Разница взята ровно
     посередине между рабочим холстом и --surface-2, то есть меньше, чем
     между витриной и столом. Одной волосяной линии для этого не хватало:
     на большом мониторе граница есть, а плоскости нет. */
  --bg-rail:    #f6f3ec;
  --surface:    #ffffff;
  --surface-2:  #f5f0e6;
  --line:       #e5ddce;
  --line-soft:  #f0eade;
  /* Граница элемента управления. Волосяная --line продолжает разделять
     плоскости страницы и обязана оставаться тихой. Но она же стояла на
     кнопках и полях — и там читалась светлее собственного текста, из-за
     чего контуры выглядели выключенными, а поля — ненарисованными.
     Отдельный токен разводит два разных смысла линии: --line говорит
     «здесь заканчивается блок», --line-strong — «сюда можно нажать».
     Только для интерактивного: кнопки, поля, поиск, действие карточки. */
  --line-strong: #c9bda6;

  /* --- Акцент: тёмный эспрессо. Им набраны главные действия.
     Он намеренно почти чёрный: в издательской вёрстке цвет даёт обложка,
     а интерфейс молчит. --- */
  --accent:      #2c2118;
  --accent-dark: #17110b;
  --accent-soft: #f2ece0;
  --accent-line: #ded2bd;

  /* --- Латунь: единственный «дорогой» цвет. Ссылки, тонкие подчёркивания,
     активные состояния. Больше нигде — иначе перестанет работать. --- */
  --brass:      #8a6a21;
  --brass-2:    #a9862f;
  --brass-soft: #f7f0dd;
  --brass-line: #e6d8b4;
  --link:       var(--brass);
  /* Вспышка под пальцем на телефоне. Латунь, разведённая до следа:
     заметно, что нажатие засчитано, и не заметно как пятно. Значение
     живёт здесь, а не в правиле, — цвета в этом файле объявляются в
     одном месте, и подсветка не исключение. */
  --tap-highlight: rgba(138, 106, 33, .14);

  /* --- Цветов знака в шкале нет и быть не должно.
     Знак шапки — готовый файл дизайнера (app/static/img/logo), и его
     чернила с латунью зашиты внутрь векторных контуров: система их не
     назначает и не имеет права переназначать — «не менять цвета» стоит
     в правилах использования знака. Совпадение при этом полное: подпись
     знака набрана #8a6a21 — той же латунью, что --brass у ссылок сайта.
     Странице от знака достаётся только одна мера — его рост (--brand-h
     ниже), всё остальное решено в самом файле. */

  /* --- Состояния. Одна тройка на каждый смысл: заливка, рамка, текст. ---
     Успех / доступно / возвращено ......... green
     Ожидание / бронь / срок сегодня ....... amber
     Ошибка / просрочено / потеряна ........ red
     Нейтральное / выдано / скрыто ......... grey
     Информация / в обработке .............. accent                        */
  --green:      #2f6b4a;
  --green-soft: #e9f1ea;
  --green-line: #d2e3d6;
  --green-ink:  #205038;

  --amber:      #8a6321;
  --amber-soft: #f8f0de;
  --amber-line: #ead9b6;
  --amber-ink:  #6e4e17;

  --red:        #9c3232;
  --red-soft:   #f9eae8;
  --red-line:   #eed2ce;
  --red-ink:    #742525;

  --grey-soft:  #f0ece4;

  /* --- Тёмной плоскости в продукте больше нет.
     Служебная строка кабинета была последней и стала такой же бумагой,
     что и стол под ней. Пятёрку оттенков «по тёмному» (--dark-surface,
     --dark-line, --on-dark…) после этого не читал никто, а держать их
     в шкале — значит приглашать чёрную полосу обратно. Светлый кабинет
     набирается теми же чернилами, что и витрина: --ink / --ink-2 /
     --ink-3 по --bg-work. --- */

  /* --- Производные состояний --- */
  --green-dark:   #24553a;
  --red-tint:     #fffafa;
  --red-line-2:   #eec6c6;

  /* --- Чистый белый и печать --- */
  --white:        #ffffff;
  --print-line:   #cccccc;

  /* --- Тени. Тёплые и очень мягкие: бумага не отбрасывает синих теней.
     Отдельная ступень для обложек — книга должна лежать на странице. --- */
  --shadow-sm:   0 1px 2px rgba(35, 26, 17, .04);
  --shadow-md:   0 2px 4px rgba(35, 26, 17, .04), 0 12px 28px rgba(35, 26, 17, .06);
  --shadow-lg:   0 4px 10px rgba(35, 26, 17, .05), 0 28px 60px rgba(35, 26, 17, .10);
  --shadow-book: 0 1px 2px rgba(35, 26, 17, .10), 0 10px 20px rgba(35, 26, 17, .13),
                 0 28px 48px rgba(35, 26, 17, .12);
  /* Книгу приподняли с полки: та же тень плюс рассеянный слой. Раньше
     эта пара собиралась прямо в правиле hover — то есть значение жило
     мимо шкалы, лишь выглядя как набор токенов. */
  --shadow-book-hover: var(--shadow-lg), var(--shadow-book);
  /* Кромка обложки. Светлые переплёты — а их в фонде десятки — совпадают
     с бумагой страницы по тону, и книга теряет край: остаётся тень без
     предмета. Волосяная линия внутрь возвращает обрез. Внутрь, а не
     наружу: рамка вокруг книги — это уже паспарту, а не переплёт. */
  --cover-edge: inset 0 0 0 1px rgba(23, 19, 15, .08);

  /* --- Скругления. Мелкие: у печатной страницы нет толстых закруглений. --- */
  --radius-xs:   2px;
  --radius-sm:   4px;
  --radius:      6px;
  --radius-lg:   8px;
  --radius-pill: 999px;

  /* --- Шаг отступов: всё кратно 4 px --- */
  --s-05:  2px;
  --s-075: 3px;
  --s-1:   4px;
  --s-2:   8px;
  --s-25:  9px;   /* только высота поля ввода: 9 + 9 + текст = 44 */
  --s-3:  12px;
  --s-4:  16px;
  --s-5:  20px;
  --s-6:  24px;
  --s-8:  32px;
  --s-10: 40px;
  --s-12: 48px;
  --s-14: 56px;
  --s-16: 64px;
  --s-20: 80px;
  --s-24: 96px;
  --s-32: 128px;  /* верхняя ступень: пауза перед сменой смысла страницы */

  /* Волосяная линия. В шкалу отступов она не входит и входить не должна:
     это не расстояние, а линия — тот самый разделитель, которым в этой
     системе заменены коробки. Имя ей нужно там, где ширину линии
     приходится складывать с отступами, чтобы левый край страницы сошёлся
     точка в точку: голое «1px» посреди calc — значение мимо шкалы, и
     проверка дизайн-системы справедливо на него ругается. */
  --hairline: 1px;

  /* --- Шкала текста. Никаких дробных размеров. --- */
  --fs-hero:    56px;   /* только заголовок главной                        */
  --fs-display: 40px;   /* заголовки страниц-«витрин», название книги      */
  --fs-h1:      32px;
  --fs-h2:      24px;
  --fs-h3:      19px;
  --fs-lg:      18px;   /* заголовки карточек, крупный ввод                */
  --fs-base:    17px;   /* основной текст                                  */
  --fs-md:      15px;   /* элементы управления, плотный текст              */
  --fs-sm:      14px;   /* таблицы, вторичный текст                        */
  --fs-xs:      13px;   /* подсказки                                       */
  --fs-2xs:     11px;   /* микроподписи и разрядка капителью               */
  --fs-num:     34px;   /* код заявки на странице «Спасибо»                */

  --fw-normal:   400;
  --fw-medium:   500;
  --fw-semibold: 600;
  --fw-bold:     700;

  --lh-display: 1.08;   /* крупные заголовки-«витрины»                     */
  --lh-tight:   1.2;    /* заголовки                                       */
  --lh-snug:    1.4;    /* элементы управления и плотные блоки             */
  --lh-base:    1.6;    /* основной текст                                  */
  --lh-read:    1.75;   /* длинные описания книг                           */

  /* --- Разрядка --- */
  --ls-caps:   .14em;   /* мелкие подписи заглавными                       */
  /* Капитель покрупнее — навигация шапки и корзина. С ростом кегля
     разрядку убавляют: на 13-м .14em растаскивает «ПРОВЕРИТЬ ЗАЯВКУ»
     в ленту отдельных букв, и строка перестаёт читаться словом. */
  --ls-caps-lg: .10em;
  --ls-tight: -.02em;   /* крупные заголовки                               */

  /* --- Высоты элементов управления: кнопка и поле всегда одного роста --- */
  --control-h:    44px;
  --control-h-sm: 36px;
  --control-h-lg: 54px;

  /* --- Единая длительность движения ---
     Третья ступень медленнее двух первых намеренно: она не для отклика
     органа управления, а для проявления целой полосы книг. Отклик обязан
     быть мгновенным, появление — спокойным, иначе полка «моргает». */
  --t-fast: .12s ease;
  --t:      .22s cubic-bezier(.2, .6, .3, 1);
  --t-slow: .45s ease-out;

  /* --- Каркас --- */
  --gap:            var(--s-4);
  --gap-grid:       var(--s-8);
  --container:      1180px;
  --container-book: 960px;   /* страница книги: ей нужна камерность, а не
                                размах — разворот на всю полосу разводит
                                обложку и текст слишком далеко друг от друга */
  --container-text: 680px;   /* предел строки для длинной прозы            */
  --container-form: 560px;   /* колонка формы: три поля, которые заполняют
                                один раз и сосредоточенно                  */
  --container-list: 860px;   /* список выбранных книг: обложка, название
                                и действие рядом, без пустой половины
                                экрана справа                              */
  --pad-card:       var(--s-8);
  --pad-box:        var(--s-5);

  /* --- Знак шапки. Знак — одна картинка, и странице он предъявляет
     единственную меру: рост. Ширина следует за ним сама, по пропорции
     файла (236,85 : 40,02, то есть 5,92 : 1), поэтому нигде в системе
     нет и не должно быть ширины знака — только высота.

     44 — величина из правил использования знака (десктоп 40–48). На
     телефоне столько не помещается: знак стоит в строке с корзиной и
     кнопкой меню, а они не ужимаются — это цели для пальца. 34 — нижняя
     граница, разрешённая правилами знака: на 375 точках она оставляет
     строке четырнадцать запасных против двух у 36 (замеры — в
     DEVIATIONS-LOGO-HEADER.md, отклонение 3).

     На ступени 360 в шапку встаёт другой файл — знак без подписи, одна
     книга, — и у него своя мера: 36. Она больше телефонной не по ошибке:
     книга почти квадратная (42 × 36), места по ширине просит втрое
     меньше, и опускать её вместе с полным знаком незачем. */
  --brand-h:      44px;   /* полный знак: десктоп                        */
  --brand-h-sm:   34px;   /* полный знак: телефон (ступень 520)          */
  --brand-mark-h: 36px;   /* знак без подписи: самые узкие (ступень 360) */

  /* --- Кабинет библиотекаря -------------------------------------------
     Рабочий стол плотнее витрины: у него своя шкала строк, своя ширина
     и своя высота служебных полос. Отдельные переменные нужны для того,
     чтобы «сделать плотнее» можно было в одном месте, а не правкой сотни
     значений по файлу. */
  --admin-container: 1320px;  /* стол шире витрины: на нём лежат таблицы   */
  --admin-bar-h:     52px;    /* тёмная строка с именем и человеком        */
  --admin-nav-h:     46px;    /* строка разделов на телефоне               */
  /* Колонка разделов. Ровно столько, сколько нужно самой длинной подписи
     («Пользователи») со значком: колонка — это указатель, а не панель, и
     каждый лишний десяток точек она отнимает у рабочей области. */
  --admin-sidebar-w: 224px;
  /* Ширина всего рабочего места: колонка разделов + линия + стол. Одна
     мера на весь кабинет — тёмная строка, разделы, работа и сообщения
     живут в её границах и вместе стоят по центру широкого монитора.
     Раньше меру знал только стол: разделы и полоса оставались во всю
     ширину окна, стол упирался в 1320 и прижимался влево, и на 1920
     справа оставалась мёртвая полоса в 360 точек — не поле, а обрыв.
     Сама мера стола при этом не изменилась ни на точку. */
  /* Направляющих у листа две — слева и справа от работы, — и обе входят
     в общую меру: иначе стол недосчитывается ровно той точки, которую
     забирает правая линия, и «1320» перестаёт быть 1320. */
  --admin-workspace: calc(var(--admin-sidebar-w) + var(--hairline) * 2 + var(--admin-container));
  --admin-pad-x:     var(--s-8);   /* поля рабочей области                 */
  --admin-row-y:     var(--s-3);   /* воздух в строке рабочего списка      */
  /* Единый рост строки. Шкала отступов высот строк не знает, а --control-h
     — это высота органа управления, не строки с обложкой: без своего
     токена строка с обложкой (90) и строка без неё (52–58) жили каждая
     своим ритмом, и соседние экраны листались с разным шагом. */
  --admin-row-min:  64px;  /* единая минимальная высота строки списка и таблицы */
  /* Столбец действий был записан тремя разными числами (178/152/178) —
     и правый край листа «дышал» от экрана к экрану. Число одно. */
  --admin-act-w:   184px;  /* единая ширина столбца действий в рабочих списках */
  --admin-stick-z:   5;    /* липкая шапка списка: ниже меню (20), выше строк   */
  --admin-gap:       var(--s-6);   /* между смысловыми блоками страницы    */
  /* Миниатюра обложки в списке. 38 точек — это уже значок, а не книга:
     по такой картинке издание не узнать, а узнавание и есть её работа.
     44 — предел, после которого начинает расти строка: обложка и так
     задаёт высоту строки, и каждая лишняя точка множится на полсотни
     записей. Это по-прежнему рабочая таблица, а не витрина. */
  --admin-thumb-w:   44px;    /* миниатюра обложки в списке                */
  --admin-thumb-h:   66px;    /* строго 2:3, как настоящая книга           */
  /* Правое поле строки панели: состояние заявки или срок возврата. Мера
     нужна затем, чтобы слова разной длины («ВЫДАНА» и «В ОБРАБОТКЕ»)
     начинались с одной вертикали, а не держались правого края каждое
     по-своему. Взята по самому длинному: «просрочено на N дней». */
  --dash-aside-w:   140px;

  /* --- Шрифты ---------------------------------------------------------
     Разделение труда прежнее и менять его не нужно: антиква — голос
     содержания (заголовки, названия книг, описания), гротеск — голос
     системы (навигация, формы, кнопки, таблицы, подписи). Поменялись
     только сами гарнитуры.

     Literata сделана для чтения книг с экрана и имеет ось оптического
     размера: в крупном кегле она характерная, в мелком — спокойная.
     Inter сделан для интерфейсов: выверенные малые кегли и табличные
     цифры. Кириллица у обоих родная, не пересаженная.

     Ряды запасных выбраны так, чтобы страница осталась собой, если файл
     шрифта не доедет: у антиквы следом Georgia — она есть и на macOS,
     и на Windows, и у неё полная кириллица; у гротеска — системный ряд.
     Файлы и лицензии: app/static/fonts (см. раздел «1б» ниже). */
  --font:         Inter, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
  --font-display: Literata, Georgia, "Palatino Linotype", Palatino, "Times New Roman", serif;
  --font-mono:    ui-monospace, SFMono-Regular, Menlo, monospace;
}

/* --------------------------------------------------- 1б. Свои шрифты
   Файлы лежат здесь же, в app/static/fonts, и больше нигде: ни одного
   обращения на сторону — ни при первом визите, ни потом. Это то же
   правило, по которому в проекте нет ни CDN, ни внешних картинок.

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

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

   Происхождение, версии и лицензии: app/static/fonts/README.md
   ------------------------------------------------------------------- */
/* Literata · cyrillic */
@font-face {
  font-family: 'Literata';
  font-style: normal;
  font-weight: 400 700;
  font-display: swap;
  src: url('/static/fonts/literata-cyrillic.woff2') format('woff2');
  unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
/* Literata · cyrillic-ext */
@font-face {
  font-family: 'Literata';
  font-style: normal;
  font-weight: 400 700;
  font-display: swap;
  src: url('/static/fonts/literata-cyrillic-ext.woff2') format('woff2');
  unicode-range: U+0460-052F, U+1C80-1C8A, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
}
/* Literata · latin */
@font-face {
  font-family: 'Literata';
  font-style: normal;
  font-weight: 400 700;
  font-display: swap;
  src: url('/static/fonts/literata-latin.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
/* Literata · latin-ext */
@font-face {
  font-family: 'Literata';
  font-style: normal;
  font-weight: 400 700;
  font-display: swap;
  src: url('/static/fonts/literata-latin-ext.woff2') format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
/* Inter · cyrillic */
@font-face {
  font-family: 'Inter';
  font-style: normal;
  font-weight: 400 700;
  font-display: swap;
  src: url('/static/fonts/inter-cyrillic.woff2') format('woff2');
  unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
/* Inter · cyrillic-ext */
@font-face {
  font-family: 'Inter';
  font-style: normal;
  font-weight: 400 700;
  font-display: swap;
  src: url('/static/fonts/inter-cyrillic-ext.woff2') format('woff2');
  unicode-range: U+0460-052F, U+1C80-1C8A, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
}
/* Inter · latin */
@font-face {
  font-family: 'Inter';
  font-style: normal;
  font-weight: 400 700;
  font-display: swap;
  src: url('/static/fonts/inter-latin.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
/* Inter · latin-ext */
@font-face {
  font-family: 'Inter';
  font-style: normal;
  font-weight: 400 700;
  font-display: swap;
  src: url('/static/fonts/inter-latin-ext.woff2') format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

/* --------------------------------------------------------- 2. Базовое */
*, *::before, *::after { box-sizing: border-box; }

html { -webkit-text-size-adjust: 100%; }

/* Вспышка под пальцем. Без этой строки телефон подсвечивает каждое
   касание своим синим (rgba(51,181,229,.4) — умолчание Android), и на
   бумажном фоне библиотеки он читается как чужая деталь интерфейса.
   Берём латунь — тот же цвет, которым подчёркнут текущий раздел.

   Не убираем в прозрачность: отклик на касание нужен, иначе палец не
   знает, попал он по ссылке или мимо. Своим должен быть цвет, а не
   наличие отклика. */
html { -webkit-tap-highlight-color: var(--tap-highlight); }

/* Атрибут hidden должен побеждать любые display из наших классов:
   иначе скрытая кнопка вроде «Удалить обложку» осталась бы видимой. */
[hidden] { display: none !important; }

/* Единое кольцо фокуса: видно, где находишься при работе с клавиатуры. */
:where(a, button, input, select, textarea, summary, [tabindex]):focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
  border-radius: var(--radius-sm);
}
.site-main:focus { outline: none; }

/* Ссылка для клавиатуры: появляется только когда до неё дошли табом. */
.skip-link {
  position: absolute; left: var(--s-3); top: -60px; z-index: 100;
  background: var(--accent); color: var(--white);
  padding: var(--s-3) var(--s-5); border-radius: var(--radius-sm);
  font-weight: var(--fw-semibold); transition: top var(--t);
}
.skip-link:focus { top: var(--s-3); color: var(--white); text-decoration: none; }

body {
  margin: 0;
  min-height: 100vh;
  display: flex;
  flex-direction: column;
  background: var(--bg);
  color: var(--ink);
  font: var(--fs-base)/var(--lh-base) var(--font);
  -webkit-font-smoothing: antialiased;
}

/* Заголовки набраны антиквой — это главное, что отличает витрину
   библиотеки от обычной панели управления. Начертание обычное, а не жирное:
   у антиквы крупный кегль сам создаёт вес, жир делает её грубой. */
h1, h2, h3, h4 {
  margin: 0 0 var(--s-3);
  font-family: var(--font-display);
  font-weight: var(--fw-normal);
  line-height: var(--lh-tight);
  letter-spacing: var(--ls-tight);
  color: var(--ink);
}
h1 { font-size: var(--fs-h1); }
h2 { font-size: var(--fs-h2); }
h3 { font-size: var(--fs-h3); letter-spacing: 0; }
h4 { font-size: var(--fs-lg); letter-spacing: 0; }
p  { margin: 0 0 var(--s-4); }

/* Ссылки.
   По умолчанию подчёркивания нет: большинство ссылок в приложении — это
   элементы интерфейса (вкладки, кнопки, карточки), и линия под ними
   создаёт визуальный шум. Подчёркивание возвращается там, где ссылка
   стоит внутри текста и её нужно отличить от обычных слов. */
a {
  color: var(--link);
  text-decoration: none;
  transition: color var(--t-fast), text-decoration-color var(--t-fast);
}
a:hover { color: var(--accent); }

p a, .prose a, li a,
.empty__text a, .field__hint a, .note a, .card__hint a,
.book-page__description a, .thanks__hint a {
  text-decoration: underline;
  text-decoration-thickness: 1px;
  text-underline-offset: .18em;
  text-decoration-color: var(--brass-line);
}
p a:hover, .prose a:hover, li a:hover,
.empty__text a:hover, .field__hint a:hover, .note a:hover, .card__hint a:hover,
.book-page__description a:hover, .thanks__hint a:hover { text-decoration-color: currentColor; }

img { max-width: 100%; display: block; }
small { font-size: var(--fs-xs); }

::selection { background: var(--brass-soft); color: var(--ink); }

/* Мелкая подпись заглавными с разрядкой — единственный «декоративный»
   приём в системе. Им набраны названия разделов, метки и подзаголовки. */
.eyebrow {
  display: block;
  font-family: var(--font);
  font-size: var(--fs-2xs);
  font-weight: var(--fw-semibold);
  letter-spacing: var(--ls-caps);
  text-transform: uppercase;
  color: var(--ink-3);
}

/* Длинная проза: антиква, крупный интерлиньяж, ограниченная строка. */
.prose {
  font-family: var(--font-display);
  font-size: var(--fs-lg);
  line-height: var(--lh-read);
  color: var(--ink-2);
  max-width: var(--container-text);
}
.prose p { margin: 0 0 .85em; }
.prose p:last-child { margin-bottom: 0; }

/* Тонкая разделительная линейка между смысловыми блоками страницы. */
.rule { height: 1px; border: 0; background: var(--line); margin: var(--s-12) 0; }

/* Горизонтальные поля задаём отдельными свойствами: сокращённое padding
   обнуляло бы верхний отступ у страниц, которые его задают своим классом. */
.container {
  width: 100%; max-width: var(--container); margin: 0 auto;
  padding-left: var(--s-5); padding-right: var(--s-5);
}
.site-main { flex: 1 0 auto; padding: var(--s-10) 0 var(--s-24); }

.muted       { color: var(--ink-3); }
.text-center { text-align: center; }
.nowrap      { white-space: nowrap; }
.stack > * + * { margin-top: var(--gap); }

/* Иконки: всегда наследуют цвет текста и не сжимаются во флексе. */
.ico { flex: 0 0 auto; vertical-align: middle; }

/* Небольшой набор служебных классов — чтобы в шаблонах не появлялись
   произвольные style="margin:14px" и размеры вне шкалы. */
.u-m-0  { margin: 0 !important; }
.u-mt-0 { margin-top: 0 !important; }
.u-mt-1 { margin-top: var(--s-1); }
.u-mt-2 { margin-top: var(--s-2); }
.u-mt-3 { margin-top: var(--s-3); }
.u-mt-4 { margin-top: var(--s-4); }
.u-mt-5 { margin-top: var(--s-5); }
.u-mt-6 { margin-top: var(--s-6); }
.u-mb-0 { margin-bottom: 0 !important; }
.u-mb-4 { margin-bottom: var(--s-4); }
.u-mb-5 { margin-bottom: var(--s-5); }

/* Подпись только для программ чтения с экрана: элемент есть в дереве
   доступности, но не занимает места на странице. */
.visually-hidden {
  position: absolute; width: 1px; height: 1px; overflow: hidden;
  clip: rect(0 0 0 0); clip-path: inset(50%); white-space: nowrap;
}

/* Короткая приписка под блоком: одинаковая на всех страницах. */
.note { margin: var(--s-3) 0 0; font-size: var(--fs-xs); color: var(--ink-3); }

/* ---------------------------------------------------------- 3. Шапка
   Шапка того же цвета, что и страница: она не «панель», а поле титула,
   отделённое одной волосяной линией. Липкой её не делаем — на витрине
   важнее целая высота экрана под обложки.                              */
.site-header {
  background: var(--bg);
  border-bottom: 1px solid var(--line);
}

.site-header__inner {
  display: flex;
  align-items: center;
  gap: var(--s-6);
  min-height: 84px;
}

/* Знак — готовая картинка (app/static/img/logo, подключение см. base.html).
   Цветов у него страница не назначает: они зашиты в контуры файла, и
   менять их правила использования знака запрещают. Странице остаётся
   одна мера — рост; ширину знак берёт из собственной пропорции.

   Знак не ужимается в тесной строке шапки: он не текст, чтобы становиться
   у́же, — либо стоит целиком, либо теряет смысл. На узких экранах он
   опускается ростом (ступень 520), а на самых узких уступает место
   версии без подписи (ступень 360). Высоту шапки знак не задаёт и
   задавать не должен: 44 в строке 84 и 34 в телефонной 68 — с запасом. */
.brand { display: inline-flex; align-items: center; text-decoration: none; }
.brand__logo { display: block; height: var(--brand-h); width: auto; flex: 0 0 auto; }
/* Знак без подписи ждёт своей ступени (360, внизу файла): до неё в
   строке стоит полный знак, а книга спит. Выбор делает CSS, а не
   <picture>, — почему, объяснено в base.html. */
.brand__logo--mark { display: none; }

.site-nav { margin-left: auto; display: flex; align-items: center; gap: var(--s-6); }
/* Навигация — второй по силе текст шапки. На 11-м кегле она читалась
   как сноска у титула и терялась в поле бумаги; 13-й ставит её вровень
   с именем библиотеки по уверенности, не споря с ним по величине.
   Высота цели — полная: 44, а не высота самих букв. */
.site-nav__link {
  display: inline-flex; align-items: center;
  min-height: var(--control-h);
  color: var(--ink-2);
  font-size: var(--fs-xs);
  font-weight: var(--fw-semibold);
  letter-spacing: var(--ls-caps-lg);
  text-transform: uppercase;
  text-decoration: none;
  border-bottom: 1px solid transparent;
  transition: color var(--t-fast), border-color var(--t-fast);
}
.site-nav__link:hover { color: var(--ink); border-bottom-color: var(--ink); text-decoration: none; }
/* Текущий раздел выглядит так же, как пункт под курсором: полные чернила
   и волосяная линия снизу. Своего цвета у него нет — латунь на «Кабинете»
   читалась как «вы здесь» и горела даже посреди каталога; теперь единственный
   отмеченный пункт — тот, чей раздел открыт. Признак не только цвет: линия
   видна и тому, кто цвета не различает, а для читалки стоит aria-current. */
.site-nav__link.is-active { color: var(--ink); border-bottom-color: var(--ink); }

.cart-button {
  display: inline-flex; align-items: center; justify-content: center; gap: var(--s-2);
  min-height: var(--control-h-sm); padding: 0 var(--s-4);
  background: none; color: var(--ink);
  border: 1px solid var(--ink); border-radius: var(--radius-pill);
  font-size: var(--fs-xs); font-weight: var(--fw-semibold);
  letter-spacing: var(--ls-caps-lg); text-transform: uppercase; text-decoration: none;
  transition: background var(--t-fast), color var(--t-fast);
}
.cart-button:hover { background: var(--ink); color: var(--bg); text-decoration: none; }
.cart-button__badge {
  font-variant-numeric: tabular-nums;
  letter-spacing: 0;
  opacity: .7;
}

.nav-toggle {
  display: none; margin-left: auto;
  width: var(--control-h); height: var(--control-h); padding: var(--s-3);
  background: none; border: 1px solid var(--line-strong); border-radius: var(--radius-sm);
  cursor: pointer; transition: border-color var(--t-fast);
}
.nav-toggle:hover { border-color: var(--ink-3); }
.nav-toggle span { display: block; height: 1px; background: var(--ink); }
.nav-toggle span + span { margin-top: var(--s-1); }

/* --------------------------------------------------------- 4. Подвал
   Здесь же теперь живёт порядок действий: он нужен один раз, тому, кто
   пришёл впервые, — и не должен занимать первый экран. Форма прежняя:
   одна полоса из четырёх подписей, а не четыре коробки. */
/* Подвал отделяет от страницы пауза, а не заливка. Бумага здесь та же —
   вводить второй тон ради конца страницы значило бы поставить на витрину
   плоскость, которой в ней нет. Работает расстояние: сорок точек своего
   поля сверху и ещё сорок отбивки над линией, поверх девяноста шести,
   которыми уже кончается содержимое. */
.site-footer {
  flex-shrink: 0;
  margin-top: var(--s-10);
  border-top: 1px solid var(--line);
  background: var(--bg);
  padding-top: var(--s-10);
  /* Нижнее поле с запасом под домашнюю полосу iPhone: без него последняя
     строка колофона упиралась бы в неё на телефонах без кнопки.

     Две строки, а не одна с запасным значением внутри env(). Первая —
     обычное поле по шкале; вторая добавляет отступ там, где телефон о
     нём сообщает. Браузер, который env() не знает, вторую строку просто
     отбросит как недействительную и оставит первую — поле не пропадёт.
     Запасное значение внутри env() дало бы то же самое, но протащило бы
     в файл число мимо шкалы отступов. */
  padding-bottom: var(--s-10);
  padding-bottom: calc(var(--s-10) + env(safe-area-inset-bottom));
  color: var(--ink-3);
  font-size: var(--fs-2xs);
}
.site-footer__inner { display: flex; flex-direction: column; gap: var(--s-8); }
.site-footer a { color: var(--ink-3); text-decoration: none; transition: color var(--t-fast); }
.site-footer a:hover { color: var(--ink); text-decoration: none; }
/* ---- колофон ----
   Концовка страницы собрана как колофон печатной книги: знак издателя,
   имя, год, служебная строка. Столбиком и по центру — колофон и в книге
   стоит столбиком, а разведённый по краям колонтитул читался бы как
   служебная полоса сайта, а не как конец издания.

   Прежде выходные данные стояли строкой: имя слева, вход справа. Строка
   ушла вместе с появлением знака — под ним центр стал единственной
   осмысленной осью, и всё остальное встало на неё. */
.colophon {
  display: flex; flex-direction: column; align-items: center;
  text-align: center;
}

/* Знак. Ширину задаём ему одну на все экраны, рост он берёт из своей
   пропорции 5,647 : 1. Двести точек дают рост 35,4 — выше объявленного
   дизайнером минимума в 34 для полного знака с подписью; ниже него
   правила использования требуют версию без подписи, поэтому сужать
   знак дальше нельзя, и на 320 он остаётся тем же. */
.colophon__mark {
  display: block; width: 200px; height: auto;
  /* Знак — печать в конце книги, а не вывеска: гасим его на волос,
     чтобы он не спорил с содержимым страницы над ним. */
  opacity: .92;
}

/* Имя по-русски под английским знаком: подпись к печати, а не заголовок.

   Кегль у имени и года одинаковый, а различает их насыщенность и тон.
   Так сделано намеренно: имя должно читаться первым, но ступень кегля
   вверх превратила бы его в заголовок и поспорила бы со знаком, ради
   которого колофон и затевался. Полужирный и более тёмные чернила дают
   нужный порядок, не трогая масштаб. */
.colophon__name {
  margin: var(--s-4) 0 0;
  font-size: var(--fs-xs); font-weight: var(--fw-semibold);
  letter-spacing: var(--ls-caps);
  text-transform: uppercase; color: var(--ink-2);
}

/* Год и служебный вход. Класс site-footer__meta оставлен прежним: на нём
   держатся телефонные правила подвала. */
.site-footer__meta {
  display: flex; flex-direction: column; align-items: center;
  gap: var(--s-4); margin: var(--s-3) 0 0;
  letter-spacing: var(--ls-caps); text-transform: uppercase;
}

/* Копирайт был набран самым мелким кеглем сайта и на бумажном фоне
   почти пропадал. Ступень вверх — 13 вместо 11 — возвращает ему вес
   выходных данных, оставляя тише имени над ним. */
.colophon__copy {
  font-size: var(--fs-xs); line-height: var(--lh-snug);
  color: var(--ink-3);
}

/* Вход библиотекаря — действие, а не строчка мелким шрифтом.
   Селектор с двумя классами: у общего правила .site-footer a
   специфичность выше одиночного класса, и цвет достался бы ему. */
.site-footer .colophon__login {
  display: inline-flex; align-items: center; justify-content: center;
  gap: var(--s-2);
  min-height: var(--control-h); padding: var(--s-2) var(--s-5);
  border: 1px solid var(--line); border-radius: var(--radius-sm);
  color: var(--ink-2); font-size: var(--fs-2xs);
  letter-spacing: var(--ls-caps); text-transform: uppercase;
  transition: color var(--t-fast), border-color var(--t-fast),
              background-color var(--t-fast), transform var(--t-fast);
}
/* Отклик держим в пределах палитры: латунная линия и её же разведённый
   фон. Ярких заливок в проекте нет ни у одного действия, и заводить их
   ради служебной ссылки в подвале незачем. */
.site-footer .colophon__login:hover {
  color: var(--ink); border-color: var(--brass-line);
  background: var(--brass-soft);
}
/* Нажатие — на волос вниз, как настоящая кнопка под пальцем.
   Общее правило «меньше движения» в конце файла это гасит. */
.site-footer .colophon__login:active { transform: translateY(1px); }
.colophon__lock { flex: none; }

/* Заголовок полосы набран капителью: он подводка к списку, а не
   название раздела страницы. Пауза под ним большая — дальше начинается
   таймлайн, и воздух до него входит в композицию. */
.steps__label {
  margin: 0 0 var(--s-16);
  font-family: var(--font);
  font-size: var(--fs-2xs); font-weight: var(--fw-semibold);
  letter-spacing: var(--ls-caps); text-transform: uppercase;
  color: var(--ink-3);
}
/* Порядок действий — одна полоса из четырёх шагов.

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

   Линию нельзя ставить и внутрь шага, между номером и его подписью:
   тогда блок распадается на три горизонтальных слоя — ряд номеров,
   черта, ряд подписей, — и «01» перестаёт принадлежать «Выбери книги».
   Разделитель внутри предмета разрывает предмет.

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

   Журнальный таймлайн. Через всю полосу идёт одна направляющая линия,
   и крупные номера стоят на ней, как вехи на пути: линия проходит
   ПОЗАДИ полосы, а каждый номер несёт собственную подложку цвета
   страницы с полем справа — в этом месте линия «обрывается», и номер
   читается стоящим на ней, а не перечёркнутым ею.

   Линия выставлена по низу строки цифр: межстрочное у номера — единица,
   значит высота его строки равна кеглю, и низ строки приходится ровно
   на 40 точек — значение из шкалы (--s-10). Замер глифов: базовая линия
   антиквы лежит на 37, низ краски (с учётом свеса круглых форм) — на
   37,5; линия проходит под цифрами, и они стоят на ней. Ставить её по
   самой базовой линии нельзя — та не выражается ни одной ступенью
   шкалы, а числа мимо шкалы в этом файле не живут. */
.steps {
  position: relative;
  display: grid; grid-template-columns: repeat(4, minmax(0, 1fr));
  column-gap: var(--s-6); row-gap: var(--s-6);
  padding: 0; margin: 0;
  list-style: none;
}
.steps::before {
  content: ""; position: absolute; z-index: 0;
  left: 0; right: 0; top: var(--s-10); height: 1px;
  background: var(--line);
}
.step { min-width: 0; }
/* Номер — крупная антиква тихим тоном: глазу есть за что зацепиться,
   но смысл несёт слово. Подложка и поле справа размыкают направляющую
   вокруг цифр; z-index поднимает номер над линией. */
.step__num {
  display: inline-block; position: relative; z-index: 1;
  margin-bottom: var(--s-6); padding-right: var(--s-6);
  background: var(--bg);
  font-family: var(--font-display);
  font-size: var(--fs-display); line-height: 1;
  letter-spacing: var(--ls-tight);
  color: var(--ink-3);
  font-variant-numeric: lining-nums tabular-nums;
}
/* Подпись и оговорка отставлены вправо от начала номера: ступень
   отделяет содержание шага от его вехи, как в журнальной вёрстке. */
.step__title {
  display: block; padding-left: var(--s-8);
  font-family: var(--font); font-size: var(--fs-lg); font-weight: var(--fw-medium);
  letter-spacing: 0; line-height: var(--lh-tight); color: var(--ink);
}
.step__note {
  display: block; margin-top: var(--s-1); padding-left: var(--s-8);
  color: var(--ink-3); font-size: var(--fs-xs); line-height: var(--lh-snug);
  letter-spacing: 0; text-transform: none;
}

/* ------------------------------------------------------ 5. Сообщения */
.flash-area { margin-top: var(--s-4); }
.flash {
  display: flex; align-items: center; gap: var(--s-3);
  padding: var(--s-3) var(--s-4); border-radius: var(--radius);
  border: 1px solid var(--line); background: var(--surface);
  box-shadow: var(--shadow-sm); margin-bottom: var(--s-2); font-size: var(--fs-md);
  animation: flash-in .22s ease-out;
}
.flash__icon { display: inline-flex; }
.flash--success { background: var(--green-soft); border-color: var(--green-line); color: var(--green-ink); }
.flash--error   { background: var(--red-soft);   border-color: var(--red-line);   color: var(--red-ink); }
.flash--warning { background: var(--amber-soft); border-color: var(--amber-line); color: var(--amber-ink); }
.flash__close {
  display: inline-flex; align-items: center; justify-content: center;
  margin-left: auto; width: 26px; height: 26px;
  background: none; border: 0; border-radius: var(--radius-xs);
  cursor: pointer; color: inherit; opacity: .55;
  transition: opacity var(--t-fast);
}
.flash__close:hover { opacity: 1; }

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

/* ------------------------------------------------------- 6. Карточки */
.card {
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
  padding: var(--pad-card);
}
.card + .card { margin-top: var(--s-6); }
.card__title {
  font-family: var(--font-display); font-size: var(--fs-h3);
  font-weight: var(--fw-normal); margin-bottom: var(--s-1);
}
.card__hint  { color: var(--ink-3); font-size: var(--fs-sm); margin-bottom: var(--s-5); }

/* Блок без коробки: только заголовок и линейка над ним. Основной способ
   делить витрину на разделы — коробки на светлой бумаге выглядят дёшево. */
.section { margin-bottom: var(--s-16); }
.section__head {
  display: flex; align-items: baseline; justify-content: space-between;
  gap: var(--s-4); flex-wrap: wrap;
  padding-bottom: var(--s-4); margin-bottom: var(--s-8);
  border-bottom: 1px solid var(--line);
}
.section__title { margin: 0; font-size: var(--fs-h2); }
.section__aside { font-size: var(--fs-2xs); letter-spacing: var(--ls-caps);
                  text-transform: uppercase; color: var(--ink-3); }
.section__aside a { color: var(--ink-3); text-decoration: none; }
.section__aside a:hover { color: var(--ink); }
.card__header {
  display: flex; align-items: flex-start; justify-content: space-between;
  gap: var(--s-4); flex-wrap: wrap;
}

.panel {
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: var(--pad-box);
}

/* --------------------------------------------------------- 7. Кнопки
   Три размера, одна высота с полями ввода того же размера.            */
.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: var(--s-2);
  min-height: var(--control-h);
  padding: var(--s-2) var(--s-6);
  border: 1px solid var(--accent); border-radius: var(--radius-sm);
  background: var(--accent); color: var(--bg);
  font-family: var(--font);
  font-size: var(--fs-md); font-weight: var(--fw-medium); line-height: var(--lh-snug);
  letter-spacing: .01em;
  text-decoration: none;
  cursor: pointer;
  transition: background var(--t-fast), border-color var(--t-fast),
              color var(--t-fast), transform var(--t-fast);
  text-align: center;
}
.btn:hover { background: var(--accent-dark); border-color: var(--accent-dark); color: var(--bg); text-decoration: none; }
.btn:active { transform: translateY(1px); }
.btn:focus-visible { outline: 2px solid var(--brass); outline-offset: 2px; }
/* Выключенная кнопка не растворяется, а гаснет: остаётся видимой формой
   с тихой заливкой. Прозрачность 0.4 на бумаге превращала кнопку в
   призрак — человек не понимал, элемент недоступен или ещё не загрузился. */
.btn[disabled],
.btn[aria-disabled="true"] {
  background: var(--surface-2); border-color: var(--line);
  color: var(--ink-3); opacity: 1;
  cursor: not-allowed; pointer-events: none; transform: none;
}
/* Отправка идёт. Форма остаётся на месте, гаснет только отзывчивость —
   никаких подмен разметки и спиннеров: заявка отправляется обычным POST. */
.btn[aria-busy="true"] { opacity: .65; cursor: progress; pointer-events: none; }

/* Второстепенное действие — только контур. Заливок в системе ровно одна.
   Контур набран --ink-3: прежняя --line была светлее собственной подписи
   кнопки, и «Выбрать ещё книги» читалось как недоступное действие. */
.btn--ghost { background: none; color: var(--ink); border-color: var(--ink-3); }
.btn--ghost:hover { background: var(--surface); color: var(--ink); border-color: var(--ink); }
.btn--ghost[disabled],
.btn--ghost[aria-disabled="true"] { background: var(--surface-2); border-color: var(--line); color: var(--ink-3); }
.btn--green { background: var(--green); border-color: var(--green); }
.btn--green:hover { background: var(--green-dark); border-color: var(--green-dark); }
/* Опасное действие: контур в полный тон, а не выцветшая рамка.
   --red-line — это тонированная линия для плашек, и на кнопке она была
   светлее собственной подписи: «Отменить заявку» выглядело выключенным
   ровно до наведения. Контур теперь тот же красный, что и текст. */
.btn--danger { background: none; color: var(--red); border-color: var(--red); }
.btn--danger:hover { background: var(--red); color: var(--bg); border-color: var(--red); }

/* Третий уровень: действие, набранное как ссылка. Нужен там, где кнопка
   рядом с кнопкой создавала бы ложное равноправие. */
.btn--quiet {
  background: none; border-color: transparent; color: var(--ink-2);
  padding-left: 0; padding-right: 0; min-height: var(--control-h-sm);
  text-decoration: underline; text-decoration-color: var(--line);
  text-underline-offset: .2em;
}
.btn--quiet:hover { background: none; border-color: transparent; color: var(--ink); text-decoration-color: currentColor; }
/* Выключенная тихая — по-прежнему строка, а не коробка: общая заливка
   выключенной кнопки легла бы полосой вокруг текста без горизонтальных
   полей. Гаснет то, чем эта кнопка нарисована, — цвет и подчёркивание. */
.btn--quiet[disabled],
.btn--quiet[aria-disabled="true"] {
  background: none; border-color: transparent;
  color: var(--ink-3); text-decoration-color: var(--line-soft);
}

/* Действие книжной полки. Главное действие витрины набрано подчёркнутой
   строчкой на уровне метаданных — то есть выглядит подписью, а не кнопкой.
   Здесь нужна форма, которая очевидно нажимается, но не спорит с названием
   книги: рост 36, волосяной контур управляющей силы, без заливки. Заливка
   в системе одна и принадлежит главному действию страницы, а не десяти
   карточкам разом.
   В Phase A класс только объявлен: разметку карточки он заменит в Phase B,
   там же правится _catalog_results.html. */
.btn--pill {
  min-height: var(--control-h-sm);
  padding: var(--s-1) var(--s-4);
  border-radius: var(--radius-pill);
  border-color: var(--line-strong);
  background: none;
  color: var(--ink);
  font-size: var(--fs-xs);
  font-weight: var(--fw-semibold);
}
.btn--pill:hover { background: var(--surface); border-color: var(--ink); color: var(--ink); }
.btn--pill[disabled],
.btn--pill[aria-disabled="true"] { background: var(--surface-2); border-color: var(--line); color: var(--ink-3); }

.btn--small { min-height: var(--control-h-sm); padding: var(--s-1) var(--s-4); font-size: var(--fs-xs); }
.btn--large { min-height: var(--control-h-lg); padding: var(--s-3) var(--s-10); font-size: var(--fs-base); }
.btn--block { width: 100%; }

.btn-row { display: flex; gap: var(--s-2); flex-wrap: wrap; }
.btn-row--center { justify-content: center; }

/* ---------------------------------------------------------- 8. Формы */
.field { margin-bottom: var(--s-6); }
.field__label {
  display: block; margin-bottom: var(--s-2);
  font-size: var(--fs-2xs); font-weight: var(--fw-semibold);
  letter-spacing: var(--ls-caps); text-transform: uppercase; color: var(--ink-2);
}
.field__label .req { color: var(--brass); }
.field__hint { display: block; margin-top: var(--s-2); font-size: var(--fs-xs); color: var(--ink-3); }

.input, .textarea, .select {
  width: 100%;
  min-height: var(--control-h);
  padding: var(--s-25) var(--s-3);
  border: 1px solid var(--line-strong);
  border-radius: var(--radius-sm);
  background: var(--surface);
  color: var(--ink);
  font-family: var(--font);
  font-size: var(--fs-md); line-height: var(--lh-snug);
  transition: border-color var(--t-fast), box-shadow var(--t-fast);
}
.input::placeholder, .textarea::placeholder { color: var(--ink-3); opacity: 1; }
.input:hover, .textarea:hover, .select:hover { border-color: var(--ink-3); }
.input:focus, .textarea:focus, .select:focus {
  outline: none;
  border-color: var(--brass);
  box-shadow: 0 0 0 3px var(--brass-soft);
}
/* Выключенное поле: тихая заливка и погашенная граница. Одно правило с
   выключенной кнопкой — по интерфейсу «недоступно» выглядит одинаково. */
.input[disabled], .textarea[disabled], .select[disabled] {
  background: var(--surface-2); color: var(--ink-3);
  border-color: var(--line); cursor: not-allowed;
}
.textarea { min-height: 112px; resize: vertical; }
.textarea--short { min-height: 80px; }
.select {
  appearance: none; padding-right: var(--s-8);
  /* Стрелка перерисована новым вторичным тоном: она обязана совпадать
     с --ink-3, но живёт в data:-адресе, куда переменные не доходят. */
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='8'%3E%3Cpath d='M1 1l5 5 5-5' stroke='%236e6457' stroke-width='1.6' fill='none' stroke-linecap='round'/%3E%3C/svg%3E");
  background-repeat: no-repeat; background-position: right var(--s-3) center;
}
/* Выбор файла: браузер считает высоту по своей кнопке — задаём явно,
   иначе поле оказывается на 1 px выше соседних. */
.input[type="file"] { height: var(--control-h); padding: var(--s-2) var(--s-3); }

.input--narrow { max-width: 280px; }
.input--short  { max-width: 160px; }
.input--tiny   { max-width: 96px; }

/* Флажок: коробочка и подпись выровнены по центру, высота как у поля. */
.chk {
  display: inline-flex; align-items: center; gap: var(--s-2); flex: 0 0 auto;
  min-height: var(--control-h); font-size: var(--fs-md); color: var(--ink-2);
  cursor: pointer;
}
.chk input { width: 18px; height: 18px; accent-color: var(--accent); margin: 0; cursor: pointer; }

.form-narrow { max-width: 460px; }

/* Форма в одну строку (переименовать категорию, добавить экземпляры). */
.inline-form { display: flex; gap: var(--s-2); align-items: center; flex-wrap: wrap; }

.field__error {
  display: block; margin-top: var(--s-2);
  font-size: var(--fs-xs); color: var(--red); font-weight: var(--fw-medium);
}
.input.is-invalid, .textarea.is-invalid, .select.is-invalid {
  border-color: var(--red); background: var(--red-tint);
}

/* --------------------------------------------------------- 9. Значки
   Один набор тонов на весь проект: он же у сообщений и у строк таблиц. */
.pill {
  display: inline-flex; align-items: center; gap: var(--s-2);
  padding: var(--s-075) 0; border-radius: 0;
  font-size: var(--fs-2xs); font-weight: var(--fw-semibold);
  letter-spacing: var(--ls-caps); text-transform: uppercase;
  line-height: var(--lh-snug); white-space: nowrap;
  color: var(--ink-3);
}
/* Точка вместо заливки: статус читается, но не кричит и не превращает
   витрину в набор ярлыков. Цветом остаётся только сама точка. */
.pill::before {
  content: ""; width: 6px; height: 6px; border-radius: 50%;
  background: currentColor; flex: 0 0 auto;
}
.pill--free    { color: var(--green-ink); }
.pill--busy    { color: var(--ink-3); }
.pill--warning { color: var(--amber-ink); }
.pill--danger  { color: var(--red-ink); }
.pill--accent  { color: var(--brass); }
.pill--big     { font-size: var(--fs-xs); }

/* --------------------------------------------------- 9б. Доступность
   Один компонент на весь читательский интерфейс: цветная точка, крупная
   подпись и необязательное уточнение. Читается за секунду и одинаково
   выглядит и в карточке полки, и на странице книги. */
.availability { display: flex; align-items: baseline; gap: var(--s-3); flex-wrap: wrap; }
.availability__mark {
  flex: 0 0 auto; width: 8px; height: 8px; border-radius: 50%;
  background: var(--ink-3); transform: translateY(-1px);
}
.availability__label {
  font-size: var(--fs-md); font-weight: var(--fw-semibold); color: var(--ink);
}
.availability__detail { font-size: var(--fs-sm); color: var(--ink-3); }

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

   Тревожные тона (жёлтый и красный ниже) остаются: их и должно быть
   видно раньше слов. */
.availability--free    .availability__mark { background: var(--ink); }
.availability--free    .availability__label { color: var(--ink); }
.availability--warning .availability__mark { background: var(--amber); }
.availability--warning .availability__label { color: var(--amber-ink); }
.availability--danger  .availability__mark { background: var(--red); }
.availability--danger  .availability__label { color: var(--red-ink); }
.availability--busy    .availability__mark { background: var(--ink-3); }
.availability--busy    .availability__label { color: var(--ink-3); }

/* В карточке полки та же вещь, но мельче и в одну строку. */
.availability--inline { gap: var(--s-2); align-items: center; }
.availability--inline .availability__mark { width: 6px; height: 6px; transform: none; }
.availability--inline .availability__label {
  font-size: var(--fs-2xs); letter-spacing: var(--ls-caps); text-transform: uppercase;
}

/* ------------------------------------------------- 11. Пустое состояние
   Пустое состояние — не поломка и не заглушка: это страница, на которой
   сейчас нечего показать, и говорить об этом она обязана тем же голосом,
   каким библиотека говорит обо всём остальном.

   Раньше здесь была коробка с рамкой, всё по центру и латунный круг
   56×56 — единственный такой предмет на витрине. Пустая полка выглядела
   заметнее полной, а латунь, единственный «дорогой» цвет системы, была
   потрачена на сообщение «здесь ничего нет». В кабинете этот круг уже
   запрещён и проверяется тестом; на витрине он оставался в трёх местах.

   Ничего нового взамен не придумано: нужный блок уже принят у пустой
   корзины — надстрочная подпись, заголовок антиквой, строка объяснения
   и одно действие между двух волосяных линеек, по левому краю полки. */
.empty {
  max-width: var(--container-list);
  padding: var(--s-10) 0;
  background: none; border: 0; border-radius: 0;
  border-top: 1px solid var(--line);
  border-bottom: 1px solid var(--line);
}
/* Надстрочная подпись называет причину пустоты: «Поиск», «Раздел»,
   «Наличие», «Фонд». По одному заголовку не видно, отчего пусто — от
   запроса, от отметки «только доступные» или оттого, что фонд ещё
   собирается, — а это разные новости. Класс общий, .eyebrow. */
.empty .eyebrow,
.cart-empty .eyebrow { margin-bottom: var(--s-3); }
.empty__title {
  font-family: var(--font-display); font-size: var(--fs-h2);
  font-weight: var(--fw-normal); line-height: var(--lh-tight);
  margin: 0 0 var(--s-3);
}
/* Строка объяснения. overflow-wrap обязателен: читатель вправе искать
   «преподобныйаввадорофей» одним словом, и без переноса такая строка
   утаскивает страницу вбок на 320. */
.empty__text {
  color: var(--ink-3); max-width: 52ch; margin: 0 0 var(--s-6);
  overflow-wrap: anywhere;
}
.empty__text:last-child { margin-bottom: 0; }
/* Запрос читателя внутри строки объяснения — антиквой курсивом: глаз
   сразу находит своё слово, но длинная строка не становится заголовком. */
.empty__query { font-family: var(--font-display); font-style: italic; color: var(--ink); }
/* Пустое состояние как целая страница (404, 403, ошибка): блок уже и
   стоит по центру полосы, но набран так же — по левому краю. */
.empty--page { max-width: 560px; margin: var(--s-12) auto; }
.empty--page .empty__title { font-size: var(--fs-h2); }

/* ------------------------------------------ 13. Каталожный аппарат
   Верх витрины — не афиша, а аппарат каталога: имя библиотеки, счёт
   фонда, поиск, указатель разделов и одна строка правил. Всё вместе
   укладывается примерно в 200 точек, поэтому первый ряд обложек попадает
   на первый экран. Раньше здесь стоял титул 56-м кеглем, лид в две
   строки и полоса из четырёх шагов — и книги начинались за фолдом.

   Флаг влево, ни рамки, ни коробки: первое, что видит человек, —
   буквы на бумаге, а сразу за ними книги. */
/* Пауза перед полкой. Спецификация просила 64, но на живой витрине это
   оказалось многовато: после строки правил взгляд успевал «провалиться»,
   прежде чем дойти до книг. 32 сохраняет макро-паузу — она по-прежнему
   крупнейшая на странице, — но не отпускает взгляд. */
.apparatus { padding: var(--s-10) 0 0; margin-bottom: var(--s-8); }
/* На внутренних страницах аппарату предшествуют хлебные крошки, поэтому
   верхний отступ ему уже не нужен. */
.apparatus--page { padding-top: 0; margin-bottom: var(--s-12); }

/* Титул и счёт стоят в одной строке на общей базовой линии: слева имя,
   справа справка. Так строка работает как шапка каталожной полосы,
   а не как два самостоятельных блока. */
.apparatus__head {
  display: flex; align-items: baseline; justify-content: space-between;
  gap: var(--s-4); flex-wrap: wrap; margin-bottom: var(--s-6);
}
/* Титул. Раньше на главной здесь стояло имя библиотеки — то же самое
   слово, что и в шапке двумя строками выше, только крупнее. Дубль ушёл:
   в титуле теперь стоит то, чем страница является, — книжный фонд, — а
   имя библиотеки осталось за шапкой, где ему и место. */
.apparatus__title {
  margin: 0;
  font-size: var(--fs-h1); line-height: var(--lh-display);
  max-width: 20ch;
}
/* Титул главной — единственный на витрине, набранный витринной ступенью.
   Кегль проверен в трёх: 32 терялся среди обложек, 56 спорил с ними.
   Ступень между ними — самый крупный текст страницы, но всё ещё подпись
   к фонду, а не афиша. Внутренним страницам она не нужна: там титул —
   указатель места («Каталог», имя раздела), а не лицо витрины, и 32-й
   кегль там остаётся. На планшете и телефоне обе страницы спускаются
   по общим ступеням — правила ниже по файлу той же силы и выигрывают. */
.apparatus__title--lead { font-size: var(--fs-display); }
/* Счёт фонда — капитель с разрядкой: это подпись аппарата, а не текст.
   Цифры табличные, чтобы при росте фонда строка не «плясала». */
.apparatus__count {
  margin: 0; color: var(--ink-3);
  font-size: var(--fs-2xs); font-weight: var(--fw-semibold);
  letter-spacing: var(--ls-caps); text-transform: uppercase;
  font-variant-numeric: tabular-nums;
}
/* Знак между двумя числами фонда. В капители на 11-м кегле типографская
   точка съёживается до крупинки и читается грязью на строке, а не паузой.
   Здесь это капля латуни — единственного «дорогого» цвета системы,
   которым набраны ссылки и подчёркивания.

   Нарисована фигурой, а не увеличенным глифом. Глиф «·» сидит в шрифте
   на середине СТРОЧНЫХ, и при тройном кегле эта середина уезжает вверх
   вместе с ним: замер показал каплю на 7,4 точки выше центра прописных,
   рядом с которыми она стоит. Подгонять это сдвигом — значит привязать
   строку к метрикам конкретного шрифта и сломать её при подмене
   гарнитуры. Фигура же встаёт туда, куда её поставили.

   Размер тот же, что у точки статуса во всём продукте: точка в системе
   одна. Поднята на волосяную линию над базовой — тогда её центр (3 точки
   выше низа) приходится ровно на середину прописных, а она у 11-го кегля
   лежит в четырёх точках над базовой линией.

   Сам знак «·» остаётся в разметке и прячется нулевым кеглем: при
   выключенных стилях разделитель никуда не денется.

   overflow здесь не украшение и убрать его нельзя. У inline-block
   базовой линией служит базовая линия последней строки внутри — а внутри
   лежит спрятанный знак, и его строка нулевой высоты уводила базовую
   линию к верхнему краю. Капля от этого висела ПОД строкой: замер
   показывал шесть точек ниже центра букв. Стоит задать overflow не
   visible — и базовой линией становится нижний край коробки, то есть
   ровно то, от чего считается подъём выше. */
.apparatus__dot {
  display: inline-block; overflow: hidden;
  width: 6px; height: 6px; border-radius: 50%;
  background: var(--brass);
  /* Поля равны с обеих сторон: капля стоит ровно посередине между словом
     и числом. Разрядка капители добавляет слева свой хвост примерно
     в полторы точки — на просвете в двенадцать это меньше десятой доли
     и глазом не ловится, а компенсировать её пришлось бы числом, взятым
     из кегля соседнего текста: сменится кегль — поедет и капля. */
  margin: 0 var(--s-3);
  vertical-align: var(--hairline);
  font-size: 0; letter-spacing: 0;
}
/* Строка правил — самая тихая в аппарате. Она закрывает верх страницы
   и отвечает на единственный вопрос, который возникает до выбора книг. */
.apparatus__hint {
  margin: var(--s-3) 0 0; color: var(--ink-3); font-size: var(--fs-xs);
}

/* ---- поиск ----
   Одно поле, а не форма из двух блоков. Рамка лежит на всей строке,
   лупа и кнопка живут внутри неё: поиск читается как типографский
   элемент, а не как элемент управления с тяжёлой тёмной кнопкой.
   Кнопка при этом настоящая — без JavaScript и с клавиатуры форма
   отправляется как обычно. */
.search {
  display: flex; align-items: stretch;
  max-width: 640px; min-height: var(--control-h-lg);
  background: var(--surface);
  border: 1px solid var(--line-strong); border-radius: var(--radius-sm);
  transition: border-color var(--t-fast);
}
.search:hover { border-color: var(--ink-3); }
/* Кольцо фокуса рисуется на всей строке: внутри неё два фокусируемых
   элемента, и подсвечивать нужно поиск целиком. */
.search:focus-within {
  border-color: var(--accent);
  outline: 2px solid var(--brass); outline-offset: 2px;
}
.search__icon {
  display: flex; align-items: center; padding-left: var(--s-4);
  color: var(--ink-3); flex: 0 0 auto;
}
.search__field {
  flex: 1 1 auto; min-width: 0;
  padding: 0 var(--s-3) 0 var(--s-3);
  background: none; border: 0; border-radius: 0;
  color: var(--ink); font-family: inherit; font-size: var(--fs-base);
  /* Подсказка в поле длиннее самого узкого экрана, и обрезать её всё
     равно придётся. Вопрос только в том, как: без этой строки браузер
     режет фразу ровно по краю поля, и «Название, автор или описание»
     превращается в «Название, автор или опис» — выглядит как поломка.
     С многоточием тот же обрыв читается как сокращение. */
  text-overflow: ellipsis;
}
.search__field::placeholder { color: var(--ink-3); }
.search__field:focus { outline: none; }
/* Кнопка отделена волосяной линией, а не заливкой: она подтверждает
   намерение, но не спорит с книгами за внимание. Подпись при этом
   чернильная: прежний --ink-2 рядом с крупным полем читался как ещё
   одна подсказка, а не как действие. */
.search__submit {
  flex: 0 0 auto; padding: 0 var(--s-6);
  background: none; border: 0; border-left: 1px solid var(--line);
  color: var(--accent); font-family: inherit; font-size: var(--fs-md);
  font-weight: var(--fw-semibold); cursor: pointer;
  transition: color var(--t-fast), background var(--t-fast);
}
.search__submit:hover { background: var(--surface-2); color: var(--ink); }

/* ---- указатель разделов ----
   Строка текстовых ссылок, а не панель кнопок: рубрикация — это
   оглавление, и вести себя она должна как оглавление. Разделов восемь,
   и все восемь обязаны остаться на виду — превращать их в плитки нельзя:
   восемь заливок наверху страницы забивают полку. */
.index {
  display: flex; flex-wrap: wrap; align-items: baseline;
  gap: var(--s-2) var(--s-6);
  margin-top: var(--s-6);
}
.index__link {
  display: inline-flex; align-items: baseline; gap: var(--s-2);
  padding: var(--s-1) 0;
  color: var(--ink-2); font-size: var(--fs-md); text-decoration: none;
  border-bottom: 2px solid transparent;
  transition: color var(--t-fast), border-color var(--t-fast);
}
/* Наведение — управляющая линия, а не структурная волосяная: она обязана
   быть заметно сильнее фона, иначе отклика просто не видно. */
.index__link:hover {
  color: var(--ink); text-decoration: none; border-bottom-color: var(--line-strong);
}
/* Текущий раздел подчёркнут латунью. Это единственная латунная линия на
   странице и её законное место: указатель — единственное, что говорит
   «ты здесь», и различать раздел приходится не по форме, а по одной
   черте. Слово при этом всё равно набрано полужирным — цвет не остаётся
   единственным носителем смысла. */
.index__link.is-active {
  color: var(--ink); font-weight: var(--fw-semibold);
  border-bottom-color: var(--brass-2);
}
.index__count {
  color: var(--ink-3); font-size: var(--fs-2xs);
  font-variant-numeric: tabular-nums;
}

/* ---- полка на главной ----
   Та же сетка, что в каталоге, плюс спокойный выход в полный каталог
   под последним рядом. */
.shelf-more { margin: var(--s-12) 0 0; text-align: center; }


.page-head {
  display: flex; align-items: flex-end; justify-content: space-between;
  gap: var(--s-4); flex-wrap: wrap; margin-bottom: var(--s-8);
}
.page-head h1 { margin: 0; font-size: var(--fs-display); line-height: var(--lh-display); }
/* Подзаголовок страницы набран обычным строчным: здесь бывают целые
   предложения, а фраза капителью с разрядкой читается тяжело. */
.page-head__sub {
  margin: var(--s-3) 0 0; font-size: var(--fs-md); color: var(--ink-3);
  max-width: 60ch;
}
.page-head__count { font-size: var(--fs-base); font-weight: var(--fw-normal); color: var(--ink-3); }

/* --------------------------------------------------- 15. Экран входа
   Порог рабочего места. Композиция спокойная и узкая: одно действие,
   один запасной путь, одна дорога назад. Коробки нет — форма стоит
   прямо на рабочем холсте, как и всё в кабинете. */
/* Порог шире прежнего: крупному слову «Кабинет» нужна своя полоса,
   а 380 точек резали бы его пополам. Всё остальное — по-прежнему одна
   колонка: одно действие, один запасной путь, одна дорога назад.

   Своя система координат нужна колонке ради знака архива за ней (см.
   .bg-watermark ниже): без неё поле ввода и кнопка оказались бы с ним
   в одном слое. */
.auth-screen {
  position: relative; z-index: 10;
  max-width: 460px; margin: 0 auto;
  padding: var(--s-24) var(--s-5) var(--s-24);
}

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

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

   Щелчки проходят насквозь, поэтому знак не может перехватить ни кнопку
   входа, ни поля формы; порядок обхода клавиатурой он тоже не меняет —
   в потоке его нет вовсе. При печати не выводится: краска на бумаге
   стоит денег, а знак — экранная деталь. */
.bg-watermark {
  position: fixed; right: -5%; bottom: -10%; z-index: 0;
  width: 60vmin; height: 60vmin;
  color: var(--ink); opacity: .03;
  pointer-events: none;
}
.bg-watermark svg { width: 100%; height: 100%; display: block; }
/* Заголовок в одно слово и во весь голос — витринным кеглем антиквы.
   Это единственный крупный предмет страницы, и он отвечает на вопрос
   «куда я попал» раньше, чем страница спросит логин. */
.auth-screen__title {
  margin: 0 0 var(--s-3);
  font-family: var(--font-display); font-size: var(--fs-display);
  line-height: var(--lh-display); letter-spacing: var(--ls-tight);
}
/* Пояснение — второй голос: гротеск, тише и мельче заголовка. */
.auth-screen__lead {
  font-family: var(--font);
  color: var(--ink-3); font-size: var(--fs-md); line-height: var(--lh-snug);
  margin: 0 0 var(--s-10);
}
.auth-screen__note {
  color: var(--ink-3); font-size: var(--fs-xs); line-height: var(--lh-snug);
  margin: var(--s-4) 0 0;
}
.auth-screen__form { margin-top: var(--s-6); }
/* Дорога назад отделена паузой, а не чертой: линия поперёк узкой колонки
   делила порог на две коробки там, где хватает расстояния. */
.auth-screen__back {
  margin: var(--s-16) 0 0;
  font-size: var(--fs-2xs); letter-spacing: var(--ls-caps); text-transform: uppercase;
  color: var(--ink-3);
}
.auth-screen__back a {
  display: inline-flex; align-items: center; gap: var(--s-2);
  color: var(--ink-3); text-decoration: none; transition: color var(--t-fast);
}
.auth-screen__back a:hover { color: var(--ink); text-decoration: none; }

/* Единственное действие страницы. Контур вместо заливки: тёмный
   прямоугольник посреди светлого порога читался как единственное, что
   на странице есть, — а страница про выбор способа входа, не про кнопку.
   Заливка приходит под курсором. Правило точечное: залитые кнопки
   кабинета и витрины не задеты. */
.auth-screen .btn {
  background: none; border: 1px solid var(--ink); border-radius: 0;
  color: var(--ink);
  font-family: var(--font); font-size: var(--fs-2xs);
  font-weight: var(--fw-semibold);
  letter-spacing: var(--ls-caps); text-transform: uppercase;
  transition: background var(--t-fast), border-color var(--t-fast), color var(--t-fast);
}
.auth-screen .btn:hover {
  background: var(--ink); border-color: var(--ink); color: var(--bg);
}

/* Аварийный вход по паролю: скромная строка, раскрывающая форму.
   Черты над ней больше нет — её место занял воздух. Работает без
   JavaScript: это обычный <details>. */
.auth-screen__fallback { margin-top: var(--s-10); }
.auth-screen__fallback > summary {
  display: flex; align-items: center; cursor: pointer; user-select: none;
  min-height: var(--control-h);
  color: var(--ink-3);
  font-size: var(--fs-2xs); letter-spacing: var(--ls-caps); text-transform: uppercase;
  transition: color var(--t-fast);
  list-style: none;
}
.auth-screen__fallback > summary::-webkit-details-marker { display: none; }
.auth-screen__fallback > summary::marker { content: ""; }
.auth-screen__fallback > summary:hover { color: var(--ink); }
.auth-screen__fallback[open] > summary { color: var(--ink); }

/* ------------------------------------- 16. Панель поиска и фильтров */
/* Панель каталога: строка над полкой, без коробки и без линеек. Раньше
   её держали две волосяные линии сверху и снизу — вместе с линией шапки
   и линией подвала на экране выходило четыре горизонтали, и страница
   начинала читаться как таблица. Границу здесь держит расстояние. */
/* Полоса каталога — два этажа, а не один ряд полей.
   Наверху единственный вопрос страницы: что ищем. Внизу — чем сужаем.
   Прежде поиск, сортировка, раздел и наличие стояли в общей строке
   равными по весу, и главное в ней приходилось находить взглядом. */
.toolbar {
  display: grid; gap: var(--s-6);
  background: none; border: 0; border-radius: 0;
  padding: 0; margin: 0;
}
/* min-width: 0 обязателен вместе с flex-basis.
   Без него у элемента гибкой строки минимальная ширина равна auto, то
   есть его собственному содержимому, и сжаться ниже своей основы в 320
   точек он не может в принципе. На экране в 320 точек полоса поиска
   вылезала на 35 точек за край, и каталог можно было утащить вбок.
   У панели кабинета эта же строка написана правильно (см. 26.5) —
   здесь её просто забыли. */
.toolbar .search { min-width: 0; }
.toolbar__sort { flex: 1 1 180px; }

/* --- главная строка запроса ------------------------------------------
   Поиск первого экрана: на главной и в каталоге это один и тот же вопрос,
   заданный на двух страницах подряд, и выглядеть он обязан одинаково.

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

   Коробка тихая. Подложка — кремовая ступень системы, на полтона глубже
   бумаги страницы: столько, чтобы поле читалось как поле, и не столько,
   чтобы оно спорило с обложками. Не белая: белым в этой системе набраны
   карточки и кнопка «Фильтры» рядом, и поле в белом встало бы с ними
   в один ряд вместо того, чтобы вести. Рамка волосяная, угол мелкий,
   тени нет: поиск остаётся типографским предметом, а не всплывшим над
   страницей виджетом.

   Высота — общая для органов управления, 44 точки: та же, что у кнопки
   «В корзину» и у ссылок шапки. Строка встаёт в общий ритм страницы, а
   не заводит собственный.

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

   Рамку и все три её состояния модификатор не трогает намеренно: покой,
   наведение и фокус берутся у общего .search целиком — иначе они
   разъезжаются. Проверено на живой странице: стоило объявить здесь свой
   :hover, и он начинал перебивать базовый :focus-within (специфичность
   та же, а правило ниже по файлу) — у поля под курсором фокус переставал
   темнить рамку, и мышь с клавиатурой показывали одно состояние
   по-разному. Одно правило на состояние, и оно живёт в одном месте.

   От общего .search сюда же приходят раскладка, мелкий угол, отступ лупы
   и кольцо фокуса. Кольцо здесь нужно: у коробки край ровный со всех
   сторон, и одного потемнения рамки клавиатуре мало. */
.search--lead {
  max-width: none;
  min-height: var(--control-h);
  background: var(--surface-2);
}
.search--lead .search__field {
  font-family: var(--font-display); font-size: var(--fs-lg);
}
/* Кнопку прячет скрипт — до тех пор она видна и работает: без неё при
   выключенных скриптах не применить ни сортировку, ни раздел, ни
   наличие. Класс ставит catalog.js, поэтому «спрятана» значит «скрипт
   на месте и берёт эту работу на себя». */
.search--lead .search__submit {
  padding: 0 var(--s-4);
  font-size: var(--fs-sm); font-weight: var(--fw-medium);
  letter-spacing: var(--ls-caps); text-transform: uppercase;
}
.search--lead .search__submit.is-quiet { display: none; }

/* --- выбор без коробки ------------------------------------------------
   Подпись говорит, что выбирают, значение — что выбрано. Рамка и заливка
   между ними ничего не добавляют, поэтому их нет: сам <select> прозрачен
   и лишён системной стрелки, а шеврон нарисован рядом нашим значком —
   одним на весь продукт. */
.pick { display: inline-flex; flex-direction: column; gap: var(--s-1); min-width: 0; }
.pick__label {
  font-size: var(--fs-2xs); font-weight: var(--fw-semibold);
  letter-spacing: var(--ls-caps); text-transform: uppercase; color: var(--ink-3);
}
.pick__value { display: inline-flex; align-items: center; min-width: 0; position: relative; }
.pick__select {
  min-width: 0; margin: 0;
  /* Правое поле — место под шеврон. Стрелка лежит НАД этим полем, а не
     рядом с селектом: так она часть его нажимаемой площади, и щелчок
     по ней открывает список сам, нативно и без скриптов. */
  padding: 0 var(--s-6) 0 0;
  background: none; border: 0; border-radius: 0;
  color: var(--ink); font-family: var(--font); font-size: var(--fs-md);
  line-height: var(--lh-snug); cursor: pointer;
  appearance: none; -webkit-appearance: none;
  /* Ширина — по выбранному значению, а не по самому длинному варианту
     списка: иначе шеврон отрывался от короткого слова и висел в пустоте.
     Свойство молодое; там, где его ещё нет, ту же работу делает скрипт
     каталога, а без скриптов селект просто остаётся широким. */
  field-sizing: content;
}
/* Кольца фокуса у выбора нет — и это единственное место продукта, где
   общее правило системы пришлось приглушить. Расчёт «кольцо рисуется
   только клавиатуре» на выборе не работает: <select> браузеры считают
   клавиатурным органом, и :focus-visible вспыхивает у него даже от
   щелчка мышью. Человек открывал список — и получал жирную скобу.

   Фокус при этом не немой: под значением появляется чернильная черта —
   тем же языком, каким отвечает на фокус строка запроса выше. Она видна
   и щёлкнувшему, и пришедшему по Tab.

   Кольцо не снято, а сделано прозрачным, и это не одно и то же:
   в режиме принудительных цветов Windows прозрачность заменяется
   системным цветом, и там, где наша черта не рисуется вовсе (теней
   в этом режиме нет), кольцо возвращается само. */
.pick__select:focus-visible { outline-color: transparent; }
.pick:focus-within .pick__value { box-shadow: 0 1px 0 0 var(--ink); }
/* Шеврон стоит в правом поле селекта и пропускает щелчки сквозь себя:
   нажатие на стрелку — это нажатие на сам селект. */
.pick__chevron {
  position: absolute; right: 0; top: 50%; transform: translateY(-50%);
  display: inline-flex; color: var(--ink-3);
  pointer-events: none;
}

/* --- переключатель наличия -------------------------------------------
   Системный флажок спрятан, но остаётся настоящим полем: щелчок, Tab и
   озвучивание достаются ему. Рисунок — дорожка и кружок — только рисунок.

   Включённое состояние — чернильная дорожка, выключенное — тон линии
   органа управления. Цвет здесь не единственный признак: кружок ещё и
   переезжает, и это видно тому, кто цвета не различает. */
.switch { display: inline-flex; align-items: center; gap: var(--s-3); cursor: pointer; }
.switch__track {
  position: relative; flex: 0 0 auto;
  width: 36px; height: 20px; border-radius: var(--radius-pill);
  background: var(--line-strong);
  transition: background var(--t);
}
.switch__knob {
  position: absolute; top: 2px; left: 2px;
  width: 16px; height: 16px; border-radius: 50%;
  background: var(--surface);
  transition: transform var(--t);
}
.switch__input:checked ~ .switch__track { background: var(--ink); }
.switch__input:checked ~ .switch__track .switch__knob { transform: translateX(16px); }
.switch__input:focus-visible ~ .switch__track { outline: 2px solid var(--accent); outline-offset: 2px; }
.switch__text {
  font-size: var(--fs-2xs); font-weight: var(--fw-semibold);
  letter-spacing: var(--ls-caps); text-transform: uppercase; color: var(--ink-2);
}

/* Фильтры каталога. На широком экране кнопка «Фильтры» не нужна: поля
   стоят прямо в панели, между поиском и кнопкой «Найти». На телефоне блок
   превращается в раскрывающийся (см. адаптив в конце файла).
   Без JavaScript блок всегда открыт — ни один фильтр не теряется. */
.toolbar__filters { min-width: 0; }
.toolbar__filters > summary { display: none; }
/* Выбор слева, переключатель наличия справа: сужение по признаку книги
   и сужение по её состоянию — разные вопросы, и разводит их пустота
   между ними, а не линия. */
.toolbar__filters-body {
  display: flex; align-items: flex-end; gap: var(--s-8); flex-wrap: wrap;
}
.toolbar__filters-body .switch { margin-left: auto; }
.toolbar__filters-body .toolbar__sort { flex: 1 1 170px; }

.results-count {
  color: var(--ink-3); font-size: var(--fs-2xs); margin: 0 0 var(--s-8);
  letter-spacing: var(--ls-caps); text-transform: uppercase; font-weight: var(--fw-semibold);
}

/* Страницы каталога. Не орган управления, а выходные данные полосы:
   две тихие подписи капителью по краям и номер страницы между ними.
   Рамок нет — обведённые кнопки под полкой книг забирали бы себе вес,
   который принадлежит обложкам. Воздуха вокруг много: это конец полосы,
   и пауза перед следующей такая же, как между разделами витрины. */
.pager {
  display: flex; align-items: center; justify-content: center; gap: var(--s-12);
  margin-top: var(--s-16);
}
.pager__link {
  display: inline-flex; align-items: center; gap: var(--s-3);
  font-size: var(--fs-2xs); font-weight: var(--fw-medium);
  letter-spacing: var(--ls-caps); text-transform: uppercase;
  color: var(--ink-3); text-decoration: none; white-space: nowrap;
  transition: color var(--t-fast);
}
.pager__link:hover { color: var(--ink); text-decoration: none; }
/* Стрелка отъезжает в сторону перехода — короткое движение вместо
   заливки или рамки говорит, что строка нажимается. Гаснет вместе со
   всей системной настройкой «меньше движения» в конце файла. */
.pager__link .ico { transition: transform var(--t); }
.pager__link--prev:hover .ico { transform: translateX(calc(-1 * var(--s-1))); }
.pager__link--next:hover .ico { transform: translateX(var(--s-1)); }
/* На краю списка стрелка гаснет, но остаётся на месте: счётчик не
   должен смещаться от того, что кончились страницы. */
.pager [aria-disabled="true"] { opacity: .35; pointer-events: none; }

/* Счётчик — дробь антиквой: она держит середину строки и читается как
   номер страницы в книге, а не как показание прибора. */
.pager__count {
  display: inline-flex; align-items: baseline; gap: var(--s-2);
  font-family: var(--font-display); font-size: var(--fs-h2);
  line-height: 1; color: var(--ink);
  font-variant-numeric: lining-nums tabular-nums;
}
.pager__slash {
  font-family: var(--font); font-size: var(--fs-lg); font-weight: var(--fw-normal);
  color: var(--line-strong);
}
.pager__total { color: var(--ink-3); }

/* ---------------------------------------------- 18. Форма книги
   Обложка слева отдельной колонкой: она главная в карточке книги, и
   выбирать её удобнее рядом с названием, а не в конце длинной анкеты. */
.book-form__grid { display: grid; grid-template-columns: 320px minmax(0, 1fr); gap: var(--gap-grid); align-items: start; }
.book-form__fields { min-width: 0; }

/* ---------------------------------------------- 19. Зона загрузки */
/* Зона загрузки обложки: волосяной пунктир на бумаге страницы.
   Двухточечная рамка вокруг серой плашки — это диалог загрузки файла
   из другой системы. Пунктир остаётся (он говорит «сюда можно бросить»),
   но волосяной, и заливки под ним нет: место для картинки обозначено
   границей, а не поверхностью. */
.dropzone {
  position: relative;
  border: 1px dashed var(--line-strong); border-radius: var(--radius-sm);
  background: none;
  min-height: 260px;
  display: flex; align-items: center; justify-content: center;
  padding: var(--s-4); cursor: pointer; text-align: center;
  transition: border-color var(--t), background var(--t);
}
.dropzone:hover,
.dropzone.is-dragover { border-color: var(--accent); background: var(--accent-soft); }
.dropzone.is-invalid { border-color: var(--red); background: var(--red-tint); }

.dropzone__input { position: absolute; width: 1px; height: 1px; opacity: 0; pointer-events: none; }
/* Значок без медальона: круглая заливка под ним была единственной
   причиной, по которой зона загрузки читалась виджетом. */
.dropzone__icon {
  display: inline-flex; align-items: center; justify-content: center;
  width: 56px; height: 56px; margin: 0 auto var(--s-3);
  background: none; color: var(--ink-3);
}
.dropzone__text { font-weight: var(--fw-semibold); font-size: var(--fs-md); }
.dropzone__hint { color: var(--ink-3); font-size: var(--fs-xs); margin-top: var(--s-1); }

.dropzone__preview { width: 100%; }
.dropzone__preview img {
  max-height: 300px; margin: 0 auto; border-radius: var(--radius-sm);
  box-shadow: var(--shadow-md);
}
.dropzone__meta {
  margin-top: var(--s-2); font-size: var(--fs-2xs); color: var(--ink-3);
  overflow-wrap: anywhere;
}
.dropzone__actions { display: flex; gap: var(--s-2); margin-top: var(--s-3); flex-wrap: wrap; }
.dropzone__error {
  margin-top: var(--s-2); padding: var(--s-2) var(--s-3); border-radius: var(--radius-sm);
  background: var(--red-soft); color: var(--red-ink); font-size: var(--fs-xs);
}

/* ============================ 21. Читательская часть ==================== */

/* ---- хлебные крошки ---- */
.crumbs {
  display: flex; flex-wrap: wrap; align-items: center; gap: var(--s-2);
  margin: 0 0 var(--s-8); font-size: var(--fs-2xs); color: var(--ink-3);
  letter-spacing: var(--ls-caps); text-transform: uppercase; font-weight: var(--fw-semibold);
}
.crumbs__link { color: var(--ink-3); text-decoration: none; }
.crumbs__link:hover { color: var(--ink); text-decoration: none; }
.crumbs__sep { opacity: .5; }
.crumbs__current { color: var(--ink-2); font-weight: var(--fw-semibold); overflow-wrap: anywhere; }

/* ---- полка с книгами ---- */
/* Сетка полки. Колонки заданы явно, а не через auto-fill: при малом числе
   книг auto-fill растягивал карточки и оставлял одинокую книгу во второй
   строке. Явные колонки дают одинаковый ритм и на трёх книгах, и на ста. */
/* Пять колонок на широком экране. При контейнере 1180 и зазоре 32 это
   даёт обложку около 210 точек и слот 316 — размер настоящей книги на
   полке, а не витринного постера. Четыре колонки давали 271: обложка
   раздувалась, в ряд помещалось меньше, и полка читалась как выкладка
   товара. Межрядный интервал заметно больше колоночного зазора —
   вертикальная пауза и есть то, что разделяет полки. */
/* Полка мягко проявляется при смене страницы. Это единственное место
   витрины, где содержимое меняется под руками, не перезагружаясь:
   постраничная навигация каталога перехвачена скриптом и заменяет полку
   целиком. Без проявления новая двадцатка книг возникала мгновенно и
   читалась как подмена, а не как переход.

   Сделано переходом, а не анимацией, и это не вкусовщина. Анимация
   с первым кадром opacity: 0 прячет содержимое ДО того, как проиграется:
   в любом окружении, где анимации остановлены — а такие есть, проверено
   замером, — полка книг осталась бы невидимой навсегда. Здесь же
   невидимость наступает только на один кадр и только по воле скрипта:
   базовое состояние полки — видимое. Без JavaScript, при остановленных
   переходах и при системной настройке «меньше движения» читатель просто
   получает книги сразу. Главное содержимое каталога не может зависеть
   от того, доиграет ли что-нибудь до конца. */
.shelf-grid {
  display: grid;
  grid-template-columns: repeat(5, minmax(0, 1fr));
  gap: var(--s-14) var(--s-8);
  transition: opacity var(--t-slow);
}
.shelf-grid.is-entering { opacity: 0; }
/* Похожие книги — тот же компонент, только плотнее. */
.shelf-grid--compact { grid-template-columns: repeat(5, minmax(0, 1fr)); gap: var(--s-10) var(--s-6); }
.shelf-card--compact .shelf-card__title { font-size: var(--fs-lg); }
.shelf-card--compact .shelf-card__body { padding: var(--s-4) 0 0; }

[data-catalog-results] { transition: opacity var(--t); }
[data-catalog-results].is-loading { opacity: .45; }

/* Карточка без карточки: обложка лежит прямо на бумаге страницы.
   Коробка вокруг книги отнимала у обложки и внимание, и место — а обложка
   здесь главный носитель цвета. */
.shelf-card {
  display: flex; flex-direction: column;
  background: none; border: 0; border-radius: 0;
}

/* Коробка обложки строго 2:3, и её высота НЕ зависит от картинки внутри:
   height:0 + padding-bottom считаются от ширины, поэтому широкая книга
   не может растянуть ряд. Сама книга внутри сохраняет свои пропорции,
   не обрезается и стоит на нижней линии — как корешки на полке.
   Эту механику менять нельзя: обложки приходят какими сняты, сервер их
   не кроит (см. cover_service), и ровный ряд держит именно коробка. */
.shelf-card__cover {
  position: relative; display: block;
  width: 100%; height: 0; padding-bottom: 150%;
  background: none; text-decoration: none;
}
.shelf-card__cover img,
.shelf-card__nocover {
  position: absolute; left: 50%; bottom: 0;
  transform: translateX(-50%);
  max-width: 100%; max-height: 100%;
}
/* Книга лежит плоско, как в журнальном развороте: тени сняты, край
   отдан настоящей волосяной границе — без неё светлые переплёты
   сливаются с бумагой страницы. Единственное движение объекта —
   короткий подъём при наведении: он и говорит «нажимается», не
   добавляя ни рамок, ни заливок. Настройка «меньше движения»
   гасится общим правилом в конце файла. */
.shelf-card__cover img {
  width: auto; height: auto;
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  transition: transform var(--t-fast);
}
.shelf-card:hover .shelf-card__cover img,
.shelf-card:hover .shelf-card__nocover,
.shelf-card__cover:focus-visible img {
  transform: translateX(-50%) translateY(-4px);
}
/* Книга без обложки занимает ровно столько же места, сколько с обложкой:
   иначе ряд полки рассыпается и объекты перестают быть одной сеткой.
   Обложку рисуем сами — из названия и автора, как на настоящей: тон на
   полтона глубже бумаги, тонкая внутренняя рамка вместо переплёта, та же
   книжная тень и та же полка. Никаких иконок «нет фото». */
.shelf-card__nocover {
  display: flex; flex-direction: column; justify-content: flex-start;
  width: 100%; height: 100%;
  /* Подпись стоит в верхней трети — там же, где название на настоящей
     обложке, а не прижата к самому краю. */
  padding: var(--s-10) var(--s-4) var(--s-4); text-align: center;
  background: var(--surface-2);
  border: 1px solid var(--line); border-radius: var(--radius-sm);
  overflow: hidden;
  transition: transform var(--t-fast);
}
.shelf-card__nocover-title {
  font-family: var(--font-display);
  font-size: var(--fs-md); line-height: var(--lh-tight); color: var(--ink-2);
  display: -webkit-box; -webkit-line-clamp: 4; -webkit-box-orient: vertical;
  overflow: hidden;
}
.shelf-card__nocover-author {
  margin-top: var(--s-3);
  font-size: var(--fs-2xs); letter-spacing: var(--ls-caps); text-transform: uppercase;
  color: var(--ink-3);
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
  overflow: hidden;
}

.shelf-card__body {
  padding: var(--s-5) 0 0; display: flex; flex-direction: column; flex: 1;
}
.shelf-card__title {
  font-family: var(--font-display);
  font-size: var(--fs-h3); font-weight: var(--fw-normal);
  line-height: var(--lh-tight); margin: 0 0 var(--s-2); letter-spacing: 0;
  color: var(--ink);
}
/* Название всегда занимает две строки: тогда автор и нижняя строка стоят
   на одной высоте во всём ряду, и подписи под книгами образуют ровную
   строку — как этикетки на полке. Резерв считает и воздух под последней
   строкой (см. ссылку ниже) — иначе двухстрочная карточка была бы на
   эти четыре точки выше однострочной, и ряд расходился. */
.shelf-card__title { min-height: calc(2.4em + var(--s-1)); margin-bottom: var(--s-1); }
/* Обрезка в две строки лежит на самой ссылке, и у ссылки есть воздух
   снизу — обе вещи ради подчёркивания при наведении.

   Линия при наведении рисуется со сдвигом .18em ниже базовой линии —
   у ВТОРОЙ строки это ниже кромки коробки высотой ровно в две строки,
   и overflow: hidden срезал линию: подчёркнутой оказывалась только
   первая строка. Обрезка режет по краю поля, а не содержимого, поэтому
   собственные четыре точки поля снизу возвращают линии место. Сдвиг
   и цвет линии — общие для всех названий системы, их не трогаем. */
.shelf-card__title a {
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
  overflow: hidden;
  padding-bottom: var(--s-1);
  color: var(--ink); text-decoration: none;
}
.shelf-card__title a:hover { color: var(--ink); text-decoration: underline;
                             text-decoration-color: var(--line); text-underline-offset: .18em; }
/* Автор — приглушённым тоном: рядом с антиквой названия он второй
   голос, а не второй заголовок. */
.shelf-card__author {
  font-size: var(--fs-sm); color: var(--ink-3); margin-bottom: var(--s-3);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}

/* Нижняя строка объекта: действие или, если книги нет на месте, статус.
   Линии здесь больше нет. Раньше каждую книгу подчёркивала волосяная
   черта, и на экране из двадцати книг возникала сетка из двадцати линий —
   ровно то, от чего книжный объект должен быть свободен. Ряд держит
   не черта, а резерв высоты: он одинаков у всех и сохраняет ритм даже
   когда у одной книги действие есть, а у другой нет. */
.shelf-card__foot {
  margin-top: auto; padding-top: var(--s-2);
  display: flex; align-items: center; gap: var(--s-3);
  flex-wrap: wrap; min-height: var(--control-h-sm);
}
.shelf-card__foot form { margin: 0; }

/* Действие видно всегда. Прятать главное действие витрины под наведение
   нельзя: на телефоне наведения не существует, а с клавиатуры его
   пришлось бы искать.

   Набрано оно капителью на уровне выходных данных, без коробки:
   двадцать обведённых кнопок под двадцатью обложками превращали полку
   в выкладку товара, а контур спорил с краем самой книги. Что строка
   нажимается, говорят знак «+» перед словом и отклик цветом; кольцо
   фокуса даёт общий .btn:focus-visible. Правило намеренно перекрывает
   и .btn, и .btn--pill — классы на кнопке остаются: за .btn держатся
   фокус и защита от двойной отправки, за собственным классом — место
   действия в объекте и телефонная цель в 44 точки (см. медиазапросы).
   Роста в 36 точек строка не теряет: область нажатия больше рисунка. */
.shelf-card__action {
  display: inline-flex; align-items: center; gap: var(--s-2);
  flex: 0 0 auto; min-height: var(--control-h-sm);
  padding: 0; background: none;
  border: 0; border-radius: 0;
  font-family: var(--font); font-size: var(--fs-2xs);
  font-weight: var(--fw-semibold);
  letter-spacing: var(--ls-caps); text-transform: uppercase;
  color: var(--ink-3);
  transition: color var(--t-fast);
}
.shelf-card__action:hover { background: none; border-color: transparent; color: var(--ink); }

/* ---- добавление в корзину без перезагрузки ----
   Подтверждение — само слово на кнопке: цвет здесь не единственный
   сигнал. Движение короткое и едва заметное; при системной настройке
   «меньше движения» оно снимается общим правилом в конце файла. */
.btn.is-added,
.shelf-card__action.is-added {
  border-color: var(--green-line); color: var(--green-ink);
  text-decoration-color: var(--green-line);
  animation: cart-added .18s ease-out;
}
/* Галочка — украшение к слову, а не носитель смысла: подтверждение
   читается словом «Добавлено», поэтому программе чтения с экрана она
   не нужна и в разметку не попадает. */
.btn.is-added::after,
.shelf-card__action.is-added::after { content: "\2009\2713"; }

/* Счётчик корзины коротко наливается латунью. Ни подпрыгивания, ни
   масштаба: единственное движение здесь — цвет. */
.cart-button__badge.is-bumped { animation: cart-bump .22s ease-in-out; }
@keyframes cart-bump {
  40% { opacity: 1; color: var(--brass); }
}
.btn.is-in-cart,
.shelf-card__action.is-in-cart {
  border-color: var(--line); color: var(--ink-3);
  opacity: 1;   /* не путать с выключенной кнопкой: книга добавлена, а не недоступна */
}
/* На полке «уже в заявке» — не кнопка, а пометка. Десять книг в заявке
   не должны давать десять контуров: действие исчерпано, и от него
   остаётся строка с галочкой. К этому виду приходят оба пути — и
   обычная перезагрузка, которая рисует ссылку, и скрипт, который гасит
   нажатую кнопку на месте, — поэтому правило снимает и рамку, и поля,
   и заливку выключенной кнопки из общего набора. */
.shelf-card__action.is-in-cart {
  padding: 0; background: none; border-color: transparent;
  font-weight: var(--fw-medium);
  text-decoration: none; cursor: default;
}

@keyframes cart-added {
  from { transform: scale(.96); }
  to   { transform: none; }
}

/* Отказ сервера показываем рядом с кнопкой, а не наверху страницы:
   читатель смотрит именно сюда. Успех кнопка уже подтвердила словом,
   поэтому его текст остаётся только для программ чтения с экрана. */
.cart-status { display: block; }
.cart-status:empty { display: none; }
.cart-status--error {
  margin-top: var(--s-2);
  font-size: var(--fs-xs); line-height: var(--lh-snug);
  color: var(--amber-ink);
}

/* ---- страница книги ----------------------------------------------------
   Правило страницы: один смысл — один читаемый блок.
   Знакомство (обложка, название, доступность, действие) отделено от
   описания, описание — от выходных данных, выходные данные — от похожих
   книг. Разделяют расстояние, размер заголовка и волосяная линия;
   коробок с рамками по-прежнему нет.
   ------------------------------------------------------------------- */
/* Странице книги нужна камерность, а не размах: на полосе в 1180 обложка
   и текст расходятся так далеко, что перестают читаться как один предмет,
   а строка описания вырастает вдвое длиннее удобной. */
.book { max-width: var(--container-book); margin: 0 0 var(--s-20); }

/* 1. Знакомство */
/* Разворот книги. Колоночный зазор шире межстрочного намеренно: обложка
   и текст — два разных предмета на одном развороте, и воздух между ними
   отделяет их вернее любой линии. */
.book-page {
  display: grid; grid-template-columns: 320px minmax(0, 1fr);
  gap: var(--s-12) var(--s-20);
  padding: var(--s-6) 0 var(--s-12);
  align-items: start;
}
.book-page__cover { display: flex; align-items: flex-start; justify-content: center; }
/* Обложка лежит плоско, как на полке: тени сняты, край держит настоящая
   волосяная граница — без неё светлые переплёты сливаются с бумагой.

   Углы прямые. Скруглённый угол — свойство экранной карточки, а у книги
   он прямой: обрез бумаги режут ножом, а не циркулем. На развороте, где
   всё остальное набрано прямыми линейками, четыре точки радиуса — это
   единственная кривая на странице, и глаз её находит. */
.book-page__cover img {
  max-width: 100%; height: auto;
  border: 1px solid var(--line); border-radius: 0;
}
.book-page__nocover {
  width: 100%; aspect-ratio: 2 / 3;
  display: flex; align-items: center; justify-content: center;
  padding: var(--s-6); text-align: center; color: var(--ink-3);
  font-family: var(--font-display); font-size: var(--fs-lg);
  background: var(--surface-2);
  border: 1px solid var(--line); border-radius: 0;
}

.book-page__category {
  display: inline-block; font-size: var(--fs-2xs); letter-spacing: var(--ls-caps);
  text-transform: uppercase; font-weight: var(--fw-semibold);
  color: var(--brass); margin-bottom: var(--s-4); text-decoration: none;
}
.book-page__category:hover { color: var(--accent); text-decoration: none; }
.book-page__title {
  font-size: var(--fs-display); line-height: var(--lh-display);
  margin: 0 0 var(--s-3); letter-spacing: var(--ls-tight);
  overflow-wrap: break-word;
}
.book-page__author {
  font-family: var(--font-display); font-size: var(--fs-h3);
  font-style: italic; color: var(--ink-2); margin: 0 0 var(--s-8);
  overflow-wrap: break-word;
}

/* Доступность — собственная строка, и разделяет её воздух, а не линейки.
   Две черты вокруг единственной строки превращали её в коробку посреди
   типографической колонки; крупная подпись с цветной точкой находится
   глазами и без рамок. */
.book-page__status { margin-bottom: var(--s-6); }
.book-page__status .availability__label { font-size: var(--fs-lg); }

/* Главное действие крупное, но контурное: рядом с обложкой — самым
   тёмным и цветным предметом страницы — залитая кнопка спорила с ней
   за вес. Контур в чернила с капителью держит важность формой и
   размером, а не заливкой; заливка приходит только под курсором.
   Возврат в каталог — обычная ссылка, чтобы два действия не выглядели
   равными по важности.

   Правило точечное — только эта страница. Классы btn btn--large на
   кнопке остаются: за них держатся тест, кольцо фокуса и защита от
   двойной отправки, а «Оформить заявку» в корзине и кнопки кабинета
   сохраняют общий залитый вид. */
.book-page__actions {
  display: flex; flex-wrap: wrap; align-items: center; gap: var(--s-6);
  margin-bottom: var(--s-4);
}
.book-page__actions form { margin: 0; }
.book-page__actions .btn {
  background: none;
  border: 1px solid var(--ink); border-radius: 0;
  color: var(--ink);
  font-family: var(--font); font-size: var(--fs-2xs);
  font-weight: var(--fw-semibold);
  letter-spacing: var(--ls-caps); text-transform: uppercase;
}
.book-page__actions .btn:hover {
  background: var(--ink); border-color: var(--ink); color: var(--bg);
}
/* Ответы корзины остаются своими: подтверждение — зелёным словом,
   «уже в корзине» — тихой пометкой. Стоят ПОСЛЕ ховера намеренно:
   у нажатой кнопки заливка под курсором уже ничего не значит. */
.book-page__actions .btn.is-added {
  background: none; border-color: var(--green-line); color: var(--green-ink);
}
.book-page__actions .btn.is-in-cart {
  background: none; border-color: var(--line); color: var(--ink-3);
}
.book-page__back {
  display: inline-flex; align-items: center; gap: var(--s-2);
  min-height: var(--control-h-sm);
  font-size: var(--fs-md); color: var(--ink-2); text-decoration: none;
}
.book-page__back:hover { color: var(--ink); text-decoration: none; }
/* Оговорка под действием — примечание, а не вторая строка кнопки.
   Она стояла вплотную к блоку действий и читалась его продолжением;
   между поступком и примечанием к нему нужна пауза. Двадцать четыре
   точки: заметно больше, чем расстояние между строками самой оговорки,
   и заметно меньше, чем пауза перед следующим разделом страницы. */
.book-page__hint {
  margin: var(--s-6) 0 0; max-width: 52ch;
  font-size: var(--fs-xs); color: var(--ink-3);
}

/* 2 и 3. Смысловые разделы страницы книги */
/* Рассказ о книге и её паспорт — два столбца одного разворота.
   Описание не может быть шире книжной строки (65 знаков, см. ниже), и
   пока выходные данные стояли под ним, справа от текста пустовала целая
   половина листа, а страница вырастала на лишний экран. Теперь слева
   рассказ, справа паспорт, и обе секции начинаются от одной линейки:
   align-items: start не даёт короткому столбцу растянуться под длинный.

   Доли 2:1 — по природе содержимого: слева связный текст в несколько
   абзацев, справа четыре коротких строки «подпись — значение». Равные
   колонки заставили бы паспорт растягивать свои строки на пустоту.

   Вертикальный зазор сетки повторяет прежний отступ между секциями,
   поэтому на узком экране, где столбцы складываются в один, ритм листа
   не меняется ни на точку. */
.book-columns {
  display: grid; grid-template-columns: 2fr 1fr;
  gap: var(--s-10) var(--s-16); align-items: start;
}
.book-section { padding-top: var(--s-12); border-top: 1px solid var(--line); }
/* Внутри сетки расстояние держит gap: собственный отступ секции удвоил бы
   его и сдвинул правый столбец вниз относительно левого. */
.book-columns > .book-section + .book-section { margin-top: 0; }
.book-section + .book-section { margin-top: var(--s-10); }
.book-section__title {
  font-size: var(--fs-2xs); font-family: var(--font);
  letter-spacing: var(--ls-caps); text-transform: uppercase;
  font-weight: var(--fw-semibold); color: var(--ink-3);
  margin: 0 0 var(--s-6);
}

/* Описание — книжная длина строки. Тянуть его на всю ширину нельзя:
   глаз теряет начало следующей строки. */
/* Мера строки описания задана в знаках, а не в точках: 65ch держит
   комфортные ~65 знаков при любом кегле антиквы. Интерлиньяж остаётся
   читальным --lh-read — это его токен по назначению. */
.book-page__description {
  font-family: var(--font-display);
  font-size: var(--fs-lg); line-height: var(--lh-read);
  color: var(--ink-2); max-width: 65ch;
}
.book-page__description p { margin: 0 0 .85em; }
.book-page__description p:last-child { margin-bottom: 0; }

/* Выходные данные — таблица, а не продолжение описания: подпись капителью
   сверху, значение крупнее и темнее под ней. */
/* Паспорт книги раскладывается по месту, а не по жёсткому числу колонок.
   Четыре колонки были рассчитаны на строку во всю ширину листа; когда
   выходные данные переехали в правый столбец разворота, каждая из них
   стала шириной в шестьдесят точек, и «Толкования Писания» рассыпалось
   по слогам: «Толко / вания / Писа / ния». Теперь колонка не уже своего
   самого длинного значения: сколько таких поместится — столько и будет.
   В узком столбце это один список сверху вниз, на планшете — три-четыре
   в ряд, как было. */
/* auto-fill, а не auto-fit: у книги бывает одна строка паспорта — только
   раздел, — и auto-fit растянул бы её на всю ширину листа, превратив
   «Толкования Писания» в заголовок во весь разворот. Со fill пустые
   колонки сохраняются, и одинокое значение стоит в своей мере. */
.book-facts {
  display: grid; grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
  gap: var(--s-8) var(--s-6); margin: 0;
}
.book-facts__row { min-width: 0; }
.book-facts dt {
  font-size: var(--fs-2xs); text-transform: uppercase; letter-spacing: var(--ls-caps);
  font-weight: var(--fw-semibold); color: var(--ink-3);
}
.book-facts dd {
  margin: var(--s-2) 0 0; font-size: var(--fs-lg); color: var(--ink);
  font-family: var(--font-display); overflow-wrap: break-word;
}
.book-facts dd a { color: var(--ink); }
.book-facts dd a:hover { color: var(--brass); }

/* 4. Похожие книги — отдельная зона внизу страницы.
   Показываем ровно один ряд: подборка здесь вспомогательная, и вторая
   неполная строка выглядела бы как оборванный каталог. Сколько карточек
   помещается в ряд — столько и видно (см. раздел 29). */
.similar { margin-bottom: var(--s-16); }
.similar__title { margin: 0; }
.shelf-grid--compact > .shelf-card:nth-child(n + 5) { display: none; }

/* ---- корзина ----
   Список, а не набор карточек: строки разделены волосяной линией,
   обложка стоит слева как миниатюра корешка. */
/* Колонка корзины. Список выбранных книг — это лист перед заявкой,
   а не таблица во всю ширину монитора: на 1180 px одна строка оставляла
   «Убрать» почти в километре от названия и полупустой правый край. */
.cart { max-width: var(--container-list); }

.cart-list { display: flex; flex-direction: column; gap: 0; border-top: 1px solid var(--line); }

.cart-item {
  display: flex; align-items: flex-start; gap: var(--s-5);
  background: none; border: 0; border-bottom: 1px solid var(--line-soft);
  border-radius: 0; padding: var(--s-5) 0; box-shadow: none;
}
/* Последнюю линию списка снимаем: сразу под ней идёт линия зоны действий,
   и две черты в 24 px делали низ страницы похожим на оборванную таблицу. */
.cart-item:last-child { border-bottom: 0; }

.cart-item__cover {
  flex: 0 0 84px; width: 84px; aspect-ratio: 2 / 3;
  border-radius: var(--radius-sm); overflow: hidden;
  background: var(--surface-2); box-shadow: var(--shadow-book);
}
/* Кромка та же, что на полке и на странице книги: светлый переплёт не
   должен растворяться в бумаге и здесь. Линия лежит на самой картинке,
   а не на коробке: коробка перекрыта картинкой во всю её площадь, и
   внутренняя тень на ней просто не была бы видна. */
.cart-item__cover img {
  width: 100%; height: 100%; object-fit: cover;
  box-shadow: var(--cover-edge);
}
/* Подпись вместо обложки. Длинное название обязано обрываться многоточием,
   а не вылезать за коробку: у книги «Толкование на Псалтирь святителя
   Афанасия Великого с приложением…» текст занимал 154 точки в коробке
   высотой 96 и обрезался посреди слова без всякого знака, что он обрезан.
   Тот же приём, что и у книжного объекта на полке (.shelf-card__nocover-title). */
.cart-item__nocover {
  display: -webkit-box; -webkit-line-clamp: 5; -webkit-box-orient: vertical;
  overflow: hidden;
  padding: var(--s-2); font-size: var(--fs-2xs); color: var(--ink-3); text-align: center;
  line-height: var(--lh-snug);
}
.cart-item__body { flex: 1 1 auto; min-width: 0; }
/* Свой класс, а не одолженный у карточки полки: корзина не должна
   зависеть от правок, сделанных ради каталога. */
.cart-item__category {
  font-size: var(--fs-2xs); letter-spacing: var(--ls-caps); text-transform: uppercase;
  font-weight: var(--fw-semibold); color: var(--ink-3); margin-bottom: var(--s-2);
}
.cart-item__title {
  font-family: var(--font-display); font-size: var(--fs-h3);
  font-weight: var(--fw-normal); line-height: var(--lh-tight);
  margin: 0 0 var(--s-2); letter-spacing: 0; overflow-wrap: anywhere;
}
.cart-item__title a { color: var(--ink); text-decoration: none; }
.cart-item__title a:hover { color: var(--ink); text-decoration: underline;
                            text-decoration-color: var(--line); text-underline-offset: .18em; }
.cart-item__author { font-size: var(--fs-sm); color: var(--ink-2); overflow-wrap: anywhere; }

/* Наличие и «Убрать» стоят в одной строке под книгой. */
.cart-item__foot {
  display: flex; align-items: center; justify-content: space-between;
  gap: var(--s-3); flex-wrap: wrap; margin-top: var(--s-3);
}
/* «Убрать» держится правого края независимо от того, сказано ли слева
   что-нибудь о наличии: у доступной книги слева пусто, и без этого
   действие уезжало бы под название. */
.cart-item__remove { flex: 0 0 auto; margin-left: auto; }
/* «Убрать» — тихое действие: обычный текст, и только при наведении
   и фокусе он краснеет. Красная кнопка была самым громким элементом
   спокойной страницы, хотя это не главное действие, а поправка выбора. */
.cart-item__remove .btn { color: var(--ink-3); }
.cart-item__remove .btn:hover,
.cart-item__remove .btn:focus-visible { color: var(--red); text-decoration-color: currentColor; }

.cart-item__warning { font-size: var(--fs-xs); color: var(--amber-ink); margin: var(--s-2) 0 0; }

/* Зона действий закрывает композицию одной линией. Главное действие
   справа, возврат в каталог рядом с ним, а редкое и необратимое
   «Очистить корзину» — на другом краю строки. */
.cart-footer {
  display: flex; align-items: center; justify-content: space-between; gap: var(--s-4);
  flex-wrap: wrap; margin-top: var(--s-6); padding-top: var(--s-5); border-top: 1px solid var(--line);
}
.cart-footer__actions { display: flex; gap: var(--s-3); align-items: center; flex-wrap: wrap; }
.cart-footer__clear { color: var(--ink-3); }
.cart-footer__clear:hover, .cart-footer__clear:focus-visible { color: var(--red); }

/* Пустая корзина: заголовок, строка и одно действие между двух линеек.
   Декоративная полка на пол-экрана только отодвигала единственное
   осмысленное действие — вернуться в каталог. */
.cart-empty {
  max-width: var(--container-list);
  padding: var(--s-10) 0;
  background: none; border: 0; border-top: 1px solid var(--line);
  border-bottom: 1px solid var(--line); border-radius: 0; box-shadow: none;
}
.cart-empty__title {
  font-family: var(--font-display); font-size: var(--fs-h2);
  font-weight: var(--fw-normal); margin-bottom: var(--s-3);
}
.cart-empty__text { color: var(--ink-3); max-width: 52ch; margin: 0 0 var(--s-6); }

/* ============================ 22. Заявки =============================== */

/* ---- оформление заявки ---- */
/* Оформление заявки: две колонки на бумаге, а не две белые коробки.
   Раньше форма и список выбранного лежали в карточках с рамками и тенями —
   на экране получались два предмета мебели вместо одного спокойного
   разворота. Состав отделён от формы волосяной линией слева: ровно
   столько разделения, сколько нужно, чтобы не перепутать колонки. */
.checkout { display: grid; grid-template-columns: 1fr 340px; gap: var(--s-16); align-items: start; }
.checkout__form { max-width: var(--container-form); }
.checkout__books { padding-left: var(--s-8); border-left: 1px solid var(--line); }
.checkout__books-title {
  font-family: var(--font-display); font-size: var(--fs-h3); color: var(--ink);
}
.checkout__books-hint { margin: var(--s-1) 0 var(--s-5); color: var(--ink-3); font-size: var(--fs-xs); }
.checkout__note { font-size: var(--fs-xs); margin: var(--s-3) 0 0; }

.checkout-item {
  display: flex; gap: var(--s-3); align-items: flex-start;
  padding: var(--s-3) 0; border-bottom: 1px solid var(--line-soft);
}
.checkout-item:first-of-type { padding-top: 0; }
.checkout-item:last-of-type { border-bottom: 0; padding-bottom: 0; }
/* Возврат в корзину — не последняя строка списка, а действие после него.
   Нижнее поле у последней книги снято (строка выше), и кнопка вставала
   вплотную к обложке: на 320 px они просто соприкасались, и список
   выглядел так, будто последняя книга въехала в кнопку. Расстояние здесь
   и разделяет две разные вещи — перечень и выход из него. */
.checkout__books > .btn { margin-top: var(--s-6); }
.checkout-item__cover {
  flex: 0 0 44px; width: 44px; aspect-ratio: 2 / 3;
  border-radius: var(--radius-xs); overflow: hidden;
  background: var(--surface-2); border: 1px solid var(--line-soft);
}
.checkout-item__cover img { width: 100%; height: 100%; object-fit: cover; }
.checkout-item__body { min-width: 0; }
.checkout-item__title { font-size: var(--fs-sm); font-weight: var(--fw-semibold); line-height: var(--lh-tight); }
.checkout-item__author { font-size: var(--fs-xs); color: var(--ink-3); margin-bottom: var(--s-1); }

/* ---- страница «Спасибо» ---- */
.thanks {
  max-width: 620px; margin: var(--s-12) auto var(--s-16); text-align: center;
  background: none; border: 0; border-radius: 0; box-shadow: none;
  padding: 0;
}
.thanks__art { width: 108px; height: 108px; margin: 0 auto var(--s-6); display: block; }
/* Знак «принято». В разметке остаётся только геометрия, цвет — отсюда,
   из той же зелёной тройки, что у статусов «доступна» и «возвращена». */
.thanks__art-halo  { fill: var(--green-soft); }
.thanks__art-disc  { fill: var(--surface); }
.thanks__art-check { fill: none; stroke: var(--green); }
.thanks__title {
  font-size: var(--fs-display); line-height: var(--lh-display); margin-bottom: var(--s-4);
}
.thanks__text {
  font-family: var(--font-display); color: var(--ink-2);
  font-size: var(--fs-lg); line-height: var(--lh-read); margin-bottom: var(--s-10);
}
/* Код заявки — единственное, что человек уносит с собой.
   Поэтому он набран как титул: крупно, антиквой, между двумя линейками. */
.thanks__code {
  display: block; padding: var(--s-8) var(--s-4); margin-bottom: var(--s-8);
  background: none; border-top: 1px solid var(--line); border-bottom: 1px solid var(--line);
  border-radius: 0;
}
.thanks__code-label {
  font-size: var(--fs-2xs); text-transform: uppercase; letter-spacing: var(--ls-caps);
  font-weight: var(--fw-semibold); color: var(--ink-3); margin-bottom: var(--s-3);
}
.thanks__code-value {
  font-family: var(--font-display);
  font-size: var(--fs-num); font-weight: var(--fw-normal); letter-spacing: .06em;
  color: var(--ink); font-variant-numeric: tabular-nums lining-nums;
}
.thanks__hint { color: var(--ink-3); font-size: var(--fs-sm); max-width: 46ch; margin: 0 auto var(--s-6); }

/* ---- заявка глазами читателя ----
   Главное на экране — состояние заявки, набранное словами и крупно.
   Ленты-прогресса здесь нет намеренно: состояний шесть, у каждой книги
   внутри заявки своё, и линейка из четырёх шагов такую жизнь описать
   не может. Ниже состояния — сами книги, каждая со своей подписью. */
.order-view { max-width: var(--container-list); margin-top: var(--s-10); }
.order-view__meta {
  margin: 0 0 var(--s-2); color: var(--ink-3);
  font-size: var(--fs-2xs); font-weight: var(--fw-semibold);
  letter-spacing: var(--ls-caps); text-transform: uppercase;
  font-variant-numeric: tabular-nums lining-nums;
}
.order-view__state {
  margin: 0 0 var(--s-6);
  font-size: var(--fs-h1); line-height: var(--lh-tight);
}
/* Цветом состояние говорит вполголоса: смысл несёт само слово, поэтому
   тревожные тона берут только те состояния, которые их заслуживают. */
.order-view__state--free   { color: var(--green-ink); }
.order-view__state--danger { color: var(--red-ink); }
.order-view__help { margin: var(--s-6) 0 0; color: var(--ink-3); font-size: var(--fs-sm); }

/* Отказ поиска: текст один и тот же на любой случай — см. шаблон. */
.status-empty { max-width: var(--container-text); margin-top: var(--s-8); }
.status-empty__title {
  font-family: var(--font-display); font-size: var(--fs-h3); color: var(--ink);
}
.status-empty__text { margin: var(--s-2) 0 0; color: var(--ink-3); font-size: var(--fs-md); }

.order-books {
  display: flex; flex-direction: column; gap: 0;
  margin-top: var(--s-4); padding: 0; list-style: none;
}

.order-book {
  display: flex; gap: var(--s-4); align-items: flex-start;
  padding: var(--s-4) 0; border: 0; border-bottom: 1px solid var(--line); border-radius: 0;
  background: none;
}
.order-book:last-child { border-bottom: 0; }
.order-book__cover {
  flex: 0 0 52px; width: 52px; aspect-ratio: 2 / 3;
  border-radius: var(--radius-xs); overflow: hidden;
  background: var(--grey-soft); border: 1px solid var(--line-soft);
}
.order-book__cover img { width: 100%; height: 100%; object-fit: cover; }
.order-book__body { flex: 1 1 auto; min-width: 0; }
.order-book__title { font-size: var(--fs-md); font-weight: var(--fw-semibold); line-height: var(--lh-tight); }
.order-book__title a { color: var(--ink); }
.order-book__title a:hover { color: var(--accent); text-decoration: none; }
.order-book__author { font-size: var(--fs-xs); color: var(--ink-2); }
.order-book__meta { font-size: var(--fs-2xs); color: var(--ink-3); margin-top: var(--s-1); }
.order-book__note { font-size: var(--fs-xs); color: var(--ink-3); margin: var(--s-2) 0 0; }
.order-book__state { flex: 0 0 auto; text-align: right; max-width: 240px; }
.order-book__tools { display: flex; gap: var(--s-2); flex-wrap: wrap; margin-top: var(--s-3); align-items: flex-start; }

.order-message {
  margin-top: var(--s-4); padding: var(--s-3);
  background: var(--accent-soft); border-radius: var(--radius-sm);
  font-size: var(--fs-sm); color: var(--ink-2);
}

/* ---- список заявок в кабинете ---- */
.order-grid {
  display: grid; grid-template-columns: repeat(auto-fill, minmax(320px, 1fr)); gap: var(--s-4);
}
.order-card {
  display: flex; flex-direction: column; gap: var(--s-3);
  background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--radius); padding: var(--pad-box); box-shadow: var(--shadow-sm);
  transition: box-shadow var(--t), transform var(--t);
}
.order-card:hover { box-shadow: var(--shadow-md); transform: translateY(-2px); }
.order-card__head { display: flex; justify-content: space-between; gap: var(--s-3); align-items: flex-start; }
.order-card__code {
  font-size: var(--fs-lg); font-weight: var(--fw-bold); letter-spacing: .03em;
  font-variant-numeric: tabular-nums;
}
.order-card__date { font-size: var(--fs-2xs); color: var(--ink-3); margin-top: var(--s-1); }
.order-card__reader { padding-top: 0; }
.order-card__name { font-weight: var(--fw-semibold); font-size: var(--fs-md); }
.order-card__contact { font-size: var(--fs-xs); color: var(--ink-2); overflow-wrap: anywhere; }
.order-card__counts {
  display: flex; gap: var(--s-3); flex-wrap: wrap; font-size: var(--fs-2xs); color: var(--ink-3);
  padding: var(--s-2) 0; border-top: 1px solid var(--line-soft); border-bottom: 1px solid var(--line-soft);
}
.order-card__counts b { color: var(--ink); font-size: var(--fs-xs); }
.order-card__counts .is-warn b { color: var(--amber); }
.order-card__counts .is-bad b { color: var(--red); }
.order-card__message {
  font-size: var(--fs-xs); color: var(--ink-2);
  background: var(--surface-2); border-radius: var(--radius-sm); padding: var(--s-2) var(--s-3);
}
.order-card__actions { display: flex; gap: var(--s-2); flex-wrap: wrap; margin-top: auto; }

/* ================== 23. Выдача, возврат, сроки, хронология ============== */

/* ---- блок подтверждения (работает без JavaScript) ---- */
.issue-box { display: inline-block; }
.issue-box > summary { list-style: none; cursor: pointer; user-select: none; }
.issue-box > summary::-webkit-details-marker { display: none; }
.issue-box > summary::marker { content: ""; }
.issue-box[open] > summary { background: var(--accent-dark); }
.issue-box[open] > summary.btn--ghost { background: var(--surface-2); border-color: var(--ink-3); color: var(--ink); }
/* Тихий и опасный вход в блок подтверждения заливки не имеет вовсе —
   значит, и «нажатого» состояния у него не заливка, а тон подписи. Иначе
   «Снять бронь» и «Отменить заявку» в раскрытом виде наливались тёмным
   и переставали быть тем, чем набраны. */
.issue-box[open] > summary.btn--quiet { background: none; color: var(--ink); text-decoration-color: currentColor; }
.issue-box[open] > summary.btn--danger { background: none; border-color: var(--red); color: var(--red); }

/* Внутри — связный текст, а не подпись к кнопке. Столбец действий таблицы
   запрещает переносы (иначе кнопка ломается пополам), и это правило
   наследовалось внутрь блока подтверждения: строка «читатель · выдана …
   · срок …» вытягивалась в одну линию под семьсот точек и вылезала за
   таблицу — страница начинала ездить вбок даже при закрытом блоке,
   потому что разметка у <details> считается и в свёрнутом виде. */
.issue-box__form {
  margin-top: var(--s-2); padding: var(--pad-box);
  background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--radius); box-shadow: var(--shadow-md);
  min-width: 260px; text-align: left; white-space: normal;
  animation: reveal .18s ease-out;
}
.issue-box__book { font-size: var(--fs-xs); line-height: 1.5; margin-bottom: var(--s-3); }
.issue-box__list {
  margin: 0 0 var(--s-3); padding-left: var(--s-5); font-size: var(--fs-xs); color: var(--ink-2);
}
.issue-box__list li { margin-bottom: var(--s-1); }
.issue-box__row {
  display: flex; gap: var(--s-3); flex-wrap: wrap; align-items: flex-end; margin-bottom: var(--s-3);
}
.issue-box__row .field__label { margin-bottom: var(--s-1); font-size: var(--fs-2xs); color: var(--ink-3); }

/* Подтверждение внутри меню строки. Механика та же — <details> внутри
   панели меню, — поэтому вопрос раскрывается прямо под пунктом, к
   которому относится, и не требует ни второго меню, ни всплывающего
   окна. Собственной коробки у формы здесь нет: коробка уже нарисована
   вокруг всего меню, и вторая внутри неё читалась бы как окно в окне. */
.row-menu__panel .issue-box { display: block; }
/* Раскрытый вопрос внутри меню: заливки нет и здесь — раскрытый пункт
   отмечен полным тоном своих чернил, как и всё остальное в этой панели. */
.row-menu__panel .issue-box[open] > summary.row-menu__item { background: none; color: var(--red); }
.row-menu__panel .issue-box__form {
  margin-top: 0; padding: var(--s-2) var(--s-3) var(--s-3);
  background: none; border: 0; border-radius: 0; box-shadow: none;
  min-width: 0;
}
.row-menu__panel .issue-box__book { color: var(--ink-2); }

/* «Сегодня» рядом с полем даты — это ответ, а не второе поле. Плашка
   с рамкой ставила его вровень с настоящим полем ввода справа, и глаз
   искал, куда там нажать. Осталась строка на той же высоте, что поле:
   сравнивать их по-прежнему удобно, нажимать больше не тянет. */
.issue-box__value {
  display: flex; align-items: center;
  min-height: var(--control-h); padding: var(--s-25) 0;
  border: 0; border-radius: 0;
  background: none; color: var(--ink-2); font-size: var(--fs-md);
}

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


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

   Геометрия связана: точка шириной --s-2 стоит у левого края колонки,
   её середина приходится на --s-1, и ровно там же стоит волосяная
   вертикаль. Меняя одно, пересчитать второе. */
.timeline { list-style: none; margin: var(--s-4) 0 0; padding: 0 0 0 var(--s-5); position: relative; }
.timeline::before {
  content: ""; position: absolute; left: var(--s-1); top: 6px; bottom: 6px;
  width: var(--hairline); background: var(--line);
}
.timeline__item { position: relative; padding: 0 0 var(--s-4); }
.timeline__item:last-child { padding-bottom: 0; }
.timeline__dot {
  position: absolute; left: calc(-1 * var(--s-5)); top: 6px;
  width: var(--s-2); height: var(--s-2); border-radius: 50%;
  background: var(--ink-3); border: 0;
}
.timeline__item--item_issued .timeline__dot { background: var(--green); }
.timeline__item--item_returned .timeline__dot { background: var(--accent); }
.timeline__item--item_unavailable .timeline__dot,
.timeline__item--item_cancelled .timeline__dot,
.timeline__item--order_cancelled .timeline__dot { background: var(--red); }
.timeline__item--order_completed .timeline__dot { background: var(--green); }

.timeline__title { font-weight: var(--fw-semibold); font-size: var(--fs-sm); }
.timeline__details { font-size: var(--fs-xs); color: var(--ink-2); margin-top: var(--s-1); }
.timeline__meta { font-size: var(--fs-2xs); color: var(--ink-3); margin-top: var(--s-1); }

/* ========================= 24. Мастер импорта ========================== */

/* Где лежат фотографии. Своей рамки у этого блока больше нет: он стоял
   коробкой внутри коробки, а сказать ему нужно ровно две вещи — путь и
   нашлась ли папка. Держит их слева тонкая линия, как цитату читателя
   в списке заявок. */
/* Путь к папке — выключка на поле, как цитата: линейка волосяная,
   той же толщины, что все линии кабинета. Двухточечная была единственной
   в файле после кромок-сигналов и читалась третьим весом линии
   в системе, где их два. */
.import-folder {
  padding: var(--s-1) 0 var(--s-1) var(--s-4);
  border-left: 1px solid var(--line-strong);
  margin-bottom: var(--s-4);
}
.import-folder__label {
  font-size: var(--fs-2xs); text-transform: uppercase; letter-spacing: .06em;
  color: var(--ink-3); margin-bottom: var(--s-1);
}
/* Путь показывается целой строкой, а не разрезается по буквам.
   Перенос «в любом месте» рвал его посреди слова — «…/BIBLIOT/EKA/…», —
   а путь читают, чтобы сверить: не та ли это папка.
   Менять это решение не понадобилось — понадобилось дать строке место.
   Раньше она жила в колонке шириной 273 точки и прокручивалась внутри
   неё: из 1025 точек пути было видно 27%, то есть «/private/tmp/claude…»
   и ничего из того, ради чего путь и читают. Теперь блок стоит во всю
   меру стола: окно строки выросло вчетверо, и живой путь виден целиком.
   Прокрутка осталась — но как запасной выход, а не как способ чтения.
   Выделяется и копируется по-прежнему вся строка. */
.import-folder__path {
  font-size: var(--fs-xs); color: var(--ink); margin-bottom: var(--s-2);
  font-family: var(--font-mono);
  white-space: nowrap; overflow-x: auto; -webkit-overflow-scrolling: touch;
  padding-bottom: var(--s-1);
}

/* ---- полоса прогресса ---- */
.progress { display: flex; align-items: center; gap: var(--s-3); }
.progress__bar {
  flex: 1 1 auto; height: var(--s-2); border-radius: var(--radius-pill);
  background: var(--line-soft); overflow: hidden;
}
.progress__bar span {
  display: block; height: 100%; border-radius: var(--radius-pill);
  background: var(--accent); transition: width .3s ease;
}
.progress__label { font-size: var(--fs-xs); color: var(--ink-3); white-space: nowrap; }
.progress--slim { margin-bottom: var(--s-5); }

/* ---- раскладка мастера ---- */
.wizard { display: grid; grid-template-columns: 280px minmax(0, 1fr); gap: var(--gap-grid); align-items: start; }
.wizard__main { display: flex; flex-direction: column; gap: var(--s-6); min-width: 0; }

/* Полоса «где я»: сколько разобрано и сколько осталось. */
.wizard__progress { margin-bottom: var(--s-6); }
.wizard__counts { margin: var(--s-2) 0 0; font-size: var(--fs-sm); color: var(--ink-3); }
.wizard__counts b { color: var(--ink); font-variant-numeric: tabular-nums; }

/* Снимок и форма — две поверхности: между ними человек переводит взгляд
   десятки раз подряд, и граница помогает не терять место. */
.wizard__preview,
.wizard__form {
  background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--radius); padding: var(--pad-box);
}
.wizard__form .form-section:last-of-type { margin-bottom: var(--s-6); }
.wizard__file-meta { font-size: var(--fs-xs); color: var(--ink-3); margin-top: var(--s-1); }

.wizard__queue-box {
  background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--radius); box-shadow: var(--shadow-sm); overflow: hidden;
}
.wizard__queue-head {
  padding: var(--s-3) var(--s-4); font-weight: var(--fw-semibold); font-size: var(--fs-sm);
  cursor: pointer; border-bottom: 1px solid var(--line-soft);
}
.queue-list { list-style: none; margin: 0; padding: var(--s-1); max-height: 70vh; overflow-y: auto; }
.queue-item a {
  display: flex; gap: var(--s-2); align-items: flex-start;
  padding: var(--s-2); border-radius: var(--radius-sm); color: var(--ink);
  transition: background var(--t-fast);
}
/* Пункт очереди — это строка списка, а не ссылка внутри текста: общее
   правило подчёркивания «li a» здесь только зашумляло бы имена файлов. */
.queue-item a,
.queue-item a:hover { text-decoration: none; }
.queue-item a:hover { background: var(--surface-2); }
.queue-item.is-current a { background: var(--accent-soft); }
.queue-item--imported .queue-item__name { color: var(--ink-3); }

.queue-item__thumb {
  flex: 0 0 34px; width: 34px; aspect-ratio: 2 / 3; overflow: hidden;
  border-radius: var(--radius-xs); background: var(--grey-soft); border: 1px solid var(--line-soft);
}
.queue-item__thumb img { width: 100%; height: 100%; object-fit: cover; }
.queue-item__body { min-width: 0; display: flex; flex-direction: column; align-items: flex-start; gap: var(--s-1); }
.queue-item__name {
  font-size: var(--fs-xs); font-weight: var(--fw-semibold);
  overflow-wrap: anywhere; line-height: var(--lh-tight);
}
.queue-item__meta { font-size: var(--fs-2xs); color: var(--ink-3); }
.queue-item__book { font-size: var(--fs-2xs); color: var(--accent); overflow-wrap: anywhere; }

/* ---- просмотр фотографии ---- */
.wizard__preview-head {
  display: flex; justify-content: space-between; align-items: flex-start;
  gap: var(--s-3); flex-wrap: wrap; margin-bottom: var(--s-3);
}
.wizard__file { font-weight: var(--fw-bold); font-size: var(--fs-md); overflow-wrap: anywhere; }

.wizard__image {
  background: var(--surface-2); border: 1px solid var(--line-soft);
  border-radius: var(--radius); padding: var(--s-3);
  display: flex; align-items: center; justify-content: center;
  min-height: 320px; max-height: 60vh; overflow: auto;
}
/* object-fit: contain — картинка вписывается целиком, без искажений */
.wizard__image img { max-width: 100%; max-height: 55vh; object-fit: contain; display: block; }
.wizard__image.is-zoomed { justify-content: flex-start; align-items: flex-start; }
.wizard__image.is-zoomed img { max-width: none; }
.wizard__broken { text-align: center; padding: var(--s-6); }

/* ---- форма и кнопки ---- */
.wizard__warnings {
  background: var(--amber-soft); border: 1px solid var(--amber-line);
  border-radius: var(--radius); padding: var(--pad-box); margin-bottom: var(--s-5);
}
.wizard__warnings-title {
  display: flex; align-items: center; gap: var(--s-2);
  font-weight: var(--fw-semibold); font-size: var(--fs-sm); margin-bottom: var(--s-2);
}
.wizard__warnings ul { margin: 0 0 var(--s-2); padding-left: var(--s-5); font-size: var(--fs-xs); color: var(--ink-2); }
.wizard__warnings li { margin-bottom: var(--s-1); }

.hint-button {
  display: inline-flex; align-items: center;
  min-height: var(--control-h-sm); margin-top: var(--s-2); padding: 0 var(--s-3);
  background: var(--accent-soft); color: var(--accent-dark);
  border: 1px solid transparent; border-radius: var(--radius-pill);
  font: var(--fs-xs)/var(--lh-snug) var(--font); cursor: pointer; text-align: left;
  transition: border-color var(--t-fast);
}
.hint-button:hover { border-color: var(--accent); }

.wizard__hotkey { font-size: var(--fs-2xs); color: var(--ink-3); }

.wizard__toolbar {
  display: flex; gap: var(--s-2); flex-wrap: wrap; align-items: center;
  background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--radius); padding: var(--s-3); box-shadow: var(--shadow-sm);
}
.wizard__toolbar [aria-disabled="true"] { opacity: .45; pointer-events: none; }

.wizard__help { font-size: var(--fs-xs); color: var(--ink-3); margin: 0; }
.wizard__help kbd {
  background: var(--surface); border: 1px solid var(--line); border-bottom-width: 2px;
  border-radius: var(--radius-xs); padding: var(--s-05) var(--s-1); font-size: var(--fs-2xs); font-family: inherit;
}

/* =====================================================================
   26. КАБИНЕТ БИБЛИОТЕКАРЯ

   Тот же продукт, но другая задача. Витрина — это чтение: воздух, обложки,
   антиква, спокойствие. Кабинет — это работа: за минуту нужно увидеть, что
   требует внимания, найти книгу среди тысячи и оформить выдачу.

   Отсюда три правила, которые отличают этот слой от читательского:

   1. ПЛОТНЕЕ. Строка списка живёт на 15-м кегле, а не на 17-м; отступы
      берутся из нижней половины шкалы. На экран должно помещаться два-три
      десятка строк, а не пять карточек.
   2. ГРОТЕСК. Антиква осталась ровно там, где она несёт смысл: заголовок
      страницы, крупный показатель, название книги. Таблицу, фильтры,
      кнопки и статусы набирать антиквой нельзя — она рассыпается на
      мелком кегле и замедляет чтение по диагонали.
   3. ЛИНИИ ВМЕСТО КОРОБОК. Разделяют расстояние, вес шрифта и волосяная
      линия. Коробка появляется только там, где она правда отделяет
      функциональный контекст (форма выдачи, опасная зона).

   Всё в этом разделе начинается с .admin-, .data-, .scope-, .row-menu,
   .status, .form-section или .danger-zone и нигде не пересекается с
   читательскими классами: сломать витрину правкой этого раздела нельзя.
   ===================================================================== */

/* ------------------------------------------------- 26.1 Каркас кабинета */

/* Одна ДНК — две осанки. Холст кабинета берётся из того же бумажного
   мира, но на полтона спокойнее витрины: цвет здесь должен приходить
   от обложек в списках и от точек статусов, а не от самого стола.
   Больше про кабинет фон нигде не переопределяется — одна строка. */
.is-admin { background: var(--bg-work); }


/* Стол шире витрины: таблице с семью столбцами нужна ширина, а книжной
   полосе набора — нет. Переопределяем только внутри кабинета. */
.is-admin .container { max-width: var(--admin-container); }
.is-admin .site-main { padding: 0; }

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

   clip, а не hidden: hidden сделал бы из рабочего места прокручиваемый
   блок, а прокручиваемый блок — это своя система координат, в которой
   прилипание колонки разделов работает уже не так. clip просто обрезает
   и ничего не прокручивает. */
/* Одна мера на весь кабинет. Рабочее место, тёмная строка и сообщения
   стоят в одних и тех же границах и вместе центрируются на широком
   мониторе. Пока окно уже меры (то есть на любом ноутбуке), ни одно из
   этих правил ничего не двигает: max-width просто не срабатывает. */
/* Полоса разделов — вторая плоскость того же листа, на полтона глубже.
   Красить сам <details> нельзя: он ростом по своему содержимому, а не по
   странице, и полоса обрывалась бы на середине длинного списка. Растянуть
   его тоже нельзя — колонка перестанет прилипать (см. ниже). Поэтому цвет
   лежит на самом рабочем месте, а рабочая область закрывает его собой:
   слева остаётся ровная полоса во всю высоту страницы, и ни коробки, ни
   тени, ни рамки для этого не понадобилось. */
/* Рабочее место занимает высоту страницы целиком. Без этого на коротком
   экране — одна заявка, пустой список, форма настроек — лист обрывался
   там же, где кончался текст, и обе вертикальные направляющие оставались
   огрызками в треть окна. Лист, который кончается раньше страницы, не
   читается как лист.

   Высоту даёт не 100vh, а сама раскладка: .site-main уже растянут между
   тёмной строкой и низом окна, остаётся сделать его гибкой строкой и
   позволить рабочему месту занять её целиком. Так высота остаётся верной
   и когда над рабочим местом стоит сообщение — vh пришлось бы угадывать. */
.is-admin .site-main { display: flex; }

.admin-shell {
  display: flex; align-items: flex-start; overflow-x: clip;
  max-width: var(--admin-workspace); margin: 0 auto;
  background: var(--bg-rail);
  /* Гибкая основа обязательна: в строке .site-main рабочее место без неё
     ужалось бы по содержимому, а не заняло бы отведённую ему меру. */
  flex: 1 1 auto;
}
.admin-shell__main { flex: 1 1 auto; min-width: 0; align-self: stretch; }
.is-admin .admin-shell__main { background: var(--bg-work); }

.admin-page {
  max-width: var(--admin-container);
  padding: var(--s-8) var(--admin-pad-x) var(--s-20);
}

/* Сообщения относятся ко всей странице, а не к одному разделу, поэтому
   стоят над рабочим местом целиком — но по левому краю РАБОТЫ, а не окна:
   сообщение начиналось над колонкой разделов и рвало единственный
   вертикальный край страницы. */
.is-admin .flash-area {
  max-width: var(--admin-workspace); margin: 0 auto;
  padding: var(--s-4) var(--admin-pad-x) 0;
  padding-left: calc(var(--admin-sidebar-w) + var(--hairline) + var(--admin-pad-x));
}
/* На пороге кабинета колонки разделов нет — значит нет и отступа под неё:
   иначе «Неверный логин или пароль» уезжало бы на 257 точек вправо от
   формы, к которой относится. Порог центрируется сам. */
/* --- служебная строка порога --------------------------------------
   Порог первым ушёл со светлой бумаги на светлую же: чёрная полоса
   поперёк входа была самым громким предметом страницы. Теперь на той же
   бумаге стоит и строка самого кабинета (см. .admin-topbar ниже), и
   порогу остаются только его собственные отличия — воздух вместо
   рабочей высоты, косая черта вместо линейки и ни одной черты поперёк
   страницы. Заливку и цвет чернил он больше не переопределяет: они
   пришли в общее правило. */
.is-auth .admin-topbar__inner {
  min-height: var(--s-16);
  /* Направляющая колонтитула принадлежит рабочему листу. На пороге листа
     ещё нет — есть одна узкая колонка, и линия во всю ширину страницы
     разрезала бы её поперёк ровно там, где нужен воздух. */
  border-bottom: 0;
}
.is-auth .admin-topbar__brand { gap: var(--s-2); }
/* Косая черта вместо линейки-разделителя: тот же знак, что разводит
   заголовки в выходных данных книги. */
.is-auth .admin-topbar__slash { color: var(--line-strong); }
.is-auth .admin-topbar__mark {
  padding-left: 0; border-left: 0;
}

.is-auth .flash-area {
  max-width: 380px;
  padding-left: var(--s-5); padding-right: var(--s-5);
}

/* Сообщение — не заголовок страницы.
   Во всю ширину стола зелёная плашка становилась самым крупным объектом
   экрана: 1361 точка заливки ради 220 точек текста. Смысл, тон, крестик
   и озвучивание остались прежними — изменилась только величина: плашка
   ужимается по своему содержимому и не растёт длиннее книжной строки.
   Никаких всплывающих уведомлений и никакого JavaScript ради этого. */
/* Заливки у сообщения кабинета тоже нет.
   «Добро пожаловать» приходило зелёной плашкой с зелёным текстом — то
   есть цветом происшествия ради вежливости. На светлом столе это была
   единственная цветная поверхность экрана, и глаз шёл к ней раньше, чем
   к очереди дел. Здесь это издательская заметка: волосяной контур,
   обычные чернила, крестик того же тона.

   Смысл сообщения при этом не потерян — он просто вернулся туда, где
   ему и место: в слово. Тревожные тона (ошибка, предупреждение) остаются
   различимы, но тоже без плашки — их держат цвет чернил и контур, а не
   заливка во всю строку. Ровно то же правило, по которому в кабинете
   набрана очередь дел: цвет — второй признак, не первый. */
.is-admin .flash {
  width: fit-content;
  max-width: min(100%, var(--container-text));
  box-shadow: none;
  background: none;
  border-color: var(--line-strong);
  color: var(--ink);
}
.is-admin .flash--error   { border-color: var(--red-line);   color: var(--red-ink); }
.is-admin .flash--warning { border-color: var(--amber-line); color: var(--amber-ink); }
/* Крестик и значок набраны чернилами строки — они её часть, а не
   отдельный орган управления. Тихими их держит непрозрачность, и она же
   возвращается в полный тон под курсором. */
.is-admin .flash__icon,
.is-admin .flash__close { color: inherit; }

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

   Тон здесь держат кегль и разрядка, а не цвет: имя набрано чернилами
   в полный голос, служебные соседи — на ступень тише. Ровно тот же приём,
   которым уже набран порог кабинета (.is-auth ниже): теперь вход и стол
   стоят на одной бумаге, и переход с одного на другой не мигает чёрным. */
.admin-topbar { background: none; color: var(--ink); }
/* Содержимое строки стоит в общей мере кабинета, иначе на широком мониторе
   название библиотеки и «Выйти» разъезжаются к краям стекла, потеряв связь
   с рабочим местом под ними.

   Линия под строкой принадлежит листу, а не окну: на 1920 черта во всю
   ширину стекла вернула бы ту же тёмную полосу, только светлее. Здесь она
   ровно по мере рабочего места и набрана самой тихой из линий — это
   направляющая колонтитула, а не разрез страницы. */
.admin-topbar__inner {
  display: flex; align-items: center; gap: var(--s-4);
  min-height: var(--admin-bar-h);
  max-width: var(--admin-workspace); margin: 0 auto;
  padding: 0 var(--admin-pad-x);
  border-bottom: 1px solid var(--line-soft);
}
.admin-topbar__brand {
  display: inline-flex; align-items: baseline; gap: var(--s-3);
  min-width: 0; color: var(--ink); text-decoration: none;
}
.admin-topbar__brand:hover { color: var(--ink); text-decoration: none; }
.admin-topbar__name {
  font-family: var(--font-display); font-size: var(--fs-lg);
  line-height: 1; letter-spacing: var(--ls-tight);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
/* Подпись «Кабинет» — не украшение, а ответ на вопрос «где я». */
.admin-topbar__mark {
  flex: 0 0 auto;
  font-size: var(--fs-2xs); font-weight: var(--fw-semibold);
  letter-spacing: var(--ls-caps); text-transform: uppercase;
  color: var(--ink-3);
  padding-left: var(--s-3); border-left: 1px solid var(--line);
}

.admin-topbar__side {
  display: flex; align-items: center; gap: var(--s-2);
  margin-left: auto; min-width: 0;
}
.admin-topbar form { display: inline-flex; }
/* Правая половина колонтитула набрана теми же чернилами, но тише имени:
   «Сайт библиотеки» — переход, значит второй голос; имя человека — подпись,
   значит третий. На тёмной полосе эту разницу давал светлый серый по
   чёрному, на бумаге её даёт ступень чернил. */
.admin-topbar__link,
.admin-topbar__user {
  display: inline-flex; align-items: center; gap: var(--s-2);
  min-height: var(--control-h-sm); padding: 0 var(--s-2);
  font-size: var(--fs-sm);
  text-decoration: none; white-space: nowrap;
  max-width: 260px;
  transition: color var(--t-fast);
}
.admin-topbar__link { color: var(--ink-2); }
.admin-topbar__user { color: var(--ink-3); }
.admin-topbar__link:hover { color: var(--ink); text-decoration: none; }
.admin-topbar__user-name,
.admin-topbar__link-text { overflow: hidden; text-overflow: ellipsis; }
/* Выход — строка, а не кнопка. Коробка вокруг двух слов на светлой бумаге
   читалась как главное действие полосы, хотя из кабинета выходят раз
   в день, а работают в нём часами. Набран той же капителью, что подписи
   разделов и «Кабинет» слева: это надпись колонтитула, и вес ей даёт
   разрядка, а не рамка. Заливка не приходит и под курсором — темнеют
   только чернила: любой прямоугольник здесь снова сделает выход самым
   заметным предметом строки. */
.admin-topbar__exit {
  display: inline-flex; align-items: center;
  min-height: var(--control-h-sm); padding: 0 var(--s-2);
  background: none; border: 0; color: var(--ink-2);
  font-family: var(--font); font-size: var(--fs-2xs);
  font-weight: var(--fw-semibold); line-height: var(--lh-snug);
  letter-spacing: var(--ls-caps); text-transform: uppercase;
  cursor: pointer;
  transition: color var(--t-fast);
}
.admin-topbar__exit:hover { color: var(--ink); }

/* --- колонка разделов ---
   Это <details>. На широком экране крышка спрятана, а список принудительно
   развёрнут: правило ниже сильнее браузерного, поэтому меню видно всегда,
   даже когда JavaScript выключен. На телефоне (см. адаптив) всё наоборот.

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

   Прилипает сам <details>, а не список внутри него. Внутри у details
   есть собственная коробка (::details-content) высотой ровно по
   содержимому — sticky внутри неё держаться не за что, и колонка молча
   уезжала вверх вместе со страницей. У самого details containing block —
   растянутая строка .admin-shell, и места для прилипания достаточно. */
.admin-nav {
  flex: 0 0 var(--admin-sidebar-w);
  position: sticky; top: 0;
  /* Меню разделов всегда выше липкой шапки списка (у той z = 5). */
  z-index: 20;
  max-height: 100vh; overflow-y: auto; overscroll-behavior: contain;
}
.admin-nav > .admin-nav__body { display: block; }
.admin-nav::details-content { content-visibility: visible; block-size: auto; }
.admin-nav__summary { display: none; }

.admin-nav__body { padding: var(--s-6) var(--s-3) var(--s-8); }

/* Две направляющие рабочего листа. Левая отделяет работу от полосы
   разделов и есть всегда; правая закрывает лист справа — но только на
   широких экранах (≥1281, см. раздел 30), где справа от листа лежит
   полоса рельса: без линии на 1920 и шире лист растворялся в странице —
   бумага рабочей области и бумага страницы одного тона. Пока окно уже
   меры, правого рельса нет и закрывать нечего.

   Линии принадлежат рабочей области, а не колонке: колонка ростом
   по своему списку, а рабочая область растянута на всю страницу (см.
   align-self выше), поэтому направляющие доходят донизу всегда. */
.admin-shell__main {
  border-left: 1px solid var(--line);
}

/* Пауза между группами — единственное, что их разделяет: ни линии, ни
   заливки. «Библиотека» и «Управление» — это два разных дня работы, и
   расстояние между ними обязано быть заметно больше, чем между соседними
   разделами внутри группы (там 0 — строки стоят вплотную). Прежние
   24 точки давали отношение к строке меньше единицы, и восемь пунктов
   читались одной лентой с двумя подписями внутри. */
.admin-nav__group + .admin-nav__group { margin-top: var(--s-8); }
/* Подпись группы — служебный указатель, как заголовок раздела страницы.
   Воздух под ней вдвое больше прежнего: подпись должна принадлежать
   списку под собой, а не висеть между двумя списками посередине. */
.admin-nav__group-title {
  display: block; padding: 0 var(--s-3); margin-bottom: var(--s-4);
  font-size: var(--fs-2xs); font-weight: var(--fw-semibold);
  letter-spacing: var(--ls-caps); text-transform: uppercase; color: var(--ink-3);
}
.admin-nav__list { list-style: none; margin: 0; padding: 0; }

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

   Волосяная линия слева объявлена у всех пунктов сразу и прозрачна —
   так текущий раздел не сдвигает текст на точку вбок, когда линия
   загорается. Ось списка одна на все восемь строк, и переход между
   разделами не дёргает колонку. */
.admin-nav__link {
  display: flex; align-items: center; gap: var(--s-2);
  min-height: var(--control-h-sm); padding: var(--s-1) var(--s-3);
  border-left: 1px solid transparent;
  color: var(--ink-3); font-size: var(--fs-md); line-height: var(--lh-snug);
  text-decoration: none;
  transition: color var(--t-fast), border-color var(--t-fast);
}
/* Наведение меняет только тон — на ступень темнее, к чернилам текущего.
   Заливка под курсором возвращала бы коробку, от которой мы и уходим. */
.admin-nav__link:hover { color: var(--ink-2); text-decoration: none; }
/* Текущий раздел: полный вес, полные чернила и волосяная линия на поле.
   Цвет здесь не единственный признак — начертание видно и без него,
   а линия работает и в чёрно-белой печати.

   Линия — одна точка, а не три. Трёхточечная кромка в этой системе
   принадлежит сигналу в списке (просрочка, владелец): она обязана
   останавливать взгляд. Указателю на своё же место останавливать нечего —
   он отмечает, а не предупреждает, и на светлой полосе волосяной линии
   для этого достаточно. Латунь ушла с ней вместе: это был единственный
   цвет колонки, и он спорил с обложками в списках справа. */
.is-admin .admin-nav__link.is-active {
  color: var(--ink); font-weight: var(--fw-semibold);
  border-left-color: var(--ink);
}
.admin-nav__text { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* Значок раздела: число, а не точка. «5 заявок» — это задача, а не сигнал. */
.admin-nav__badge {
  margin-left: auto;
  display: inline-flex; align-items: center; justify-content: center;
  min-width: 20px; height: 20px; padding: 0 var(--s-1);
  background: var(--accent-soft); color: var(--accent-dark);
  border-radius: var(--radius-pill);
  font-size: var(--fs-2xs); font-weight: var(--fw-semibold);
  font-variant-numeric: tabular-nums;
}
.admin-nav__badge--danger { background: var(--red-soft); color: var(--red-ink); }

/* ------------------------------------------------ 26.2 Шапка страницы */
/* Линеек на светлом листе две, и они делают разную работу.
   --line держит структуру: заголовок страницы, заголовок раздела, рамки
   врезки. --line-soft разделяет однородные строки внутри списка. Пока
   обе были одинаково тихими, лист остался без каркаса вовсе: восемь
   почти невидимых черт одного веса не строят иерархию, а просто гаснут,
   и содержимое читалось сваленным в кучу.

   Эта линия — верхняя рамка очереди дел под ней (см. .attention ниже),
   поэтому её вес обязан совпадать с весом нижней рамки врезки. */
.admin-head {
  display: flex; align-items: flex-end; justify-content: space-between;
  gap: var(--s-4); flex-wrap: wrap;
  padding-bottom: var(--s-4); margin-bottom: var(--admin-gap);
  border-bottom: 1px solid var(--line);
}
.admin-head__titles { min-width: 0; }
.admin-head__eyebrow {
  display: block; margin-bottom: var(--s-2);
  font-size: var(--fs-2xs); font-weight: var(--fw-semibold);
  letter-spacing: var(--ls-caps); text-transform: uppercase; color: var(--ink-3);
}
/* Заголовок антиквой — но на две ступени мельче витринного: под ним сразу
   начинается работа, а не разворот с обложкой. */
.admin-head__title {
  margin: 0; font-size: var(--fs-h1); line-height: var(--lh-tight);
  overflow-wrap: anywhere;
}
.admin-head__count {
  margin-left: var(--s-3);
  font-family: var(--font); font-size: var(--fs-md); font-weight: var(--fw-normal);
  color: var(--ink-3); font-variant-numeric: tabular-nums;
  letter-spacing: 0;
}
.admin-head__sub {
  margin: var(--s-2) 0 0; font-size: var(--fs-sm); color: var(--ink-3);
  max-width: 72ch;
}
.admin-head__actions { display: flex; gap: var(--s-2); flex-wrap: wrap; align-items: center; }

/* Главное действие раздела — контур, а не заливка.
   «Добавить книгу», «Добавить библиотекаря», «Продолжить мастер» — это
   переходы к форме, а не подтверждения. Тёмный залитый прямоугольник
   в правом верхнем углу светлого листа оказывался самым тяжёлым
   предметом экрана и спорил с очередью дел, ради которой библиотекарь
   и открыл страницу. Заливка в системе одна, и она принадлежит кнопке,
   которая что-то делает: «Сохранить» в форме, «Подтвердить выдачу»
   в заявке. Их правило (.btn) не тронуто.

   Набрано той же капителью, что «Выйти» в колонтитуле и подписи
   разделов: действие читается как надпись на полях, а не как призыв.
   Заливка приходит под курсором — тогда она отвечает на движение
   человека, а не занимает угол постоянно. Ровно тот же приём, что
   у единственного действия порога кабинета (.auth-screen .btn). */
.admin-head__actions .btn {
  background: none; border: 1px solid var(--ink); border-radius: 0;
  color: var(--ink);
  font-family: var(--font); font-size: var(--fs-2xs);
  font-weight: var(--fw-semibold);
  letter-spacing: var(--ls-caps); text-transform: uppercase;
  transition: background var(--t-fast), border-color var(--t-fast), color var(--t-fast);
}
.admin-head__actions .btn:hover {
  background: var(--ink); border-color: var(--ink); color: var(--bg);
}
/* Второстепенные действия шапки («Экземпляры», «История») остаются
   контуром обычным строчным: если и они наберутся капителью, две ступени
   действия сольются в одну ленту одинаковых надписей. Их правило нужно
   вернуть здесь дословно — контекстный селектор выше сильнее .btn--ghost
   и иначе перекрасил бы их вместе с главным. */
.admin-head__actions .btn--ghost {
  border-color: var(--ink-3); border-radius: var(--radius-sm);
  color: var(--ink);
  font-size: var(--fs-md); font-weight: var(--fw-medium);
  letter-spacing: .01em; text-transform: none;
}
.admin-head__actions .btn--ghost:hover {
  background: var(--surface); border-color: var(--ink); color: var(--ink);
}

/* Возврат на уровень выше. Ссылка, а не кнопка: это переход, а не действие. */
.admin-back {
  display: inline-flex; align-items: center; gap: var(--s-2);
  min-height: var(--control-h-sm); margin-bottom: var(--s-2);
  font-size: var(--fs-sm); color: var(--ink-3); text-decoration: none;
}
.admin-back:hover { color: var(--ink); text-decoration: none; }

/* ------------------------------------------- 26.3 Разделы внутри страницы */
.admin-section { margin-bottom: var(--s-10); }
.admin-section:last-child { margin-bottom: 0; }
/* Линейка раздела — якорь списка под ней, и набрана она структурным
   весом, как и линейка заголовка страницы. Капительный заголовок сам
   по себе набран 11-м кеглем третьими чернилами — это самая тихая
   строка листа, и удержать под собой десяток строк она не может.
   Черта под ней и есть то, от чего список начинается: без неё
   «ПОСЛЕДНИЕ ЗАЯВКИ» и первая заявка стояли двумя равноправными
   строками, и раздел не читался разделом. */
.admin-section__head {
  display: flex; align-items: flex-end; justify-content: space-between;
  gap: var(--s-3) var(--s-4); flex-wrap: wrap;
  padding-bottom: var(--s-3); margin-bottom: var(--s-4);
  border-bottom: 1px solid var(--line);
}
/* Заголовок раздела набран гротеском капителью: он служебный указатель,
   а не название главы. Антиква на этом уровне спорила бы с h1. */
.admin-section__title {
  margin: 0; font-family: var(--font);
  font-size: var(--fs-2xs); font-weight: var(--fw-semibold);
  letter-spacing: var(--ls-caps); text-transform: uppercase; color: var(--ink-2);
}
.admin-section__hint {
  margin: var(--s-2) 0 0; font-size: var(--fs-xs); color: var(--ink-3); max-width: 74ch;
}
.admin-section__aside {
  font-size: var(--fs-xs); color: var(--ink-3);
  display: flex; align-items: center; gap: var(--s-3); flex-wrap: wrap;
}
/* Короткая форма справа от заголовка раздела («добавить экземпляры»,
   «добавить категорию») не должна сжиматься до столбика. */
.admin-section__head > form { flex: 0 0 auto; }
.admin-section__head .inline-form { flex-wrap: nowrap; align-items: flex-end; }

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

   Живёт только на странице заявки. Раньше класс назывался .admin-panel и
   выглядел общей «поверхностью кабинета» — но применён был в одном
   шаблоне и ровно для этого. Имя теперь говорит, что это. */
.order-step {
  padding: var(--s-4) 0;
  border-top: 1px solid var(--line-soft);
  border-bottom: 1px solid var(--line-soft);
}
/* Действие шага набрано контуром и капителью — тем же, чем главное
   действие в шапке любого раздела кабинета. Тёмный залитый прямоугольник
   был здесь единственной заливкой страницы и перетягивал на себя всё
   внимание с книг, ради которых страницу и открыли.

   Селектор берёт только прямых потомков: внутри раскрытой формы стоит
   зелёное «Подтвердить выдачу» — последнее слово перед необратимым
   действием, и заливка принадлежит именно ему. */
.order-step > form > .btn,
.order-step > .issue-box > summary.btn {
  min-height: var(--control-h);
  padding: var(--s-2) var(--s-5);
  background: none; border: 1px solid var(--ink); border-radius: 0;
  color: var(--ink);
  font-size: var(--fs-xs); font-weight: var(--fw-semibold);
  letter-spacing: var(--ls-caps); text-transform: uppercase;
  transition: background var(--t-fast), border-color var(--t-fast), color var(--t-fast);
}
.order-step > form > .btn:hover,
.order-step > .issue-box > summary.btn:hover {
  background: var(--ink); border-color: var(--ink); color: var(--bg);
}
/* Раскрытый вопрос: вход в него залит, потому что сейчас он и есть то
   место, где человек находится. Общее правило .issue-box[open] заливает
   его --accent-dark мимо чернил страницы — здесь тон свой. */
.order-step > .issue-box[open] > summary.btn {
  background: var(--ink); border-color: var(--ink); color: var(--bg);
}

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

   Ступень эта держалась на разнице двух линий: работа — --line, справка —
   --line-soft. Разница в один тон между двумя почти невидимыми чертами
   ступени не даёт: на листе её попросту не видно. Поэтому у справки
   линии нет совсем — её отделяет пауза в 64 точки, тот же приём, которым
   в этой системе отделена дорога назад с порога кабинета. Ступеней снова
   три, и все три различимы: линия структурная, линия строковая и её
   отсутствие. */
.admin-section--service { margin-top: var(--s-16); }
.admin-section--service > .admin-section__head {
  border-bottom: 0;
}
.admin-section--service .admin-section__title { color: var(--ink-3); }

/* ------------------------------------------------- 26.4 Панель управления */

/* А. ТРЕБУЕТ ВНИМАНИЯ.
   Очередь дел, а не витрина показателей. Строка на задачу: число слева,
   что это значит — рядом, стрелка справа, нажимается вся строка. Плитки
   с крупными цифрами отсюда убраны намеренно: четыре коробки занимали
   первый экран целиком ради четырёх однозначных чисел, и работу
   приходилось искать под ними.

   Цвет здесь — второй признак, не первый: что именно случилось, сказано
   словом («книги просрочены»), а красная кромка лишь помогает найти
   строку взглядом. Заливки нет — иначе три строки подряд превращают
   очередь в светофор.

   Отбита очередь как врезка в наборе: две линии во всю ширину листа и
   по 24 точки воздуха внутри. До этого она стояла в общем потоке с одной
   линией сверху — то есть выглядела началом чего-то, а не отдельной
   вещью, и «1 заявка в обработке» сливалась с заголовком над ней и
   списками под ней в одну кучу. Врезка отвечает на первый вопрос утра
   и обязана читаться первой. */
.attention {
  padding: var(--s-6) 0;
  border-top: 1px solid var(--line);
  border-bottom: 1px solid var(--line);
  margin-bottom: var(--admin-gap);
}
/* Очередь стоит сразу под шапкой страницы, и верхней рамкой ей служит
   линия самой шапки: своя вторая черта дала бы под заголовком две
   параллели в 24 точки друг от друга. Врезка подтягивается к линии
   вплотную — тем же приёмом, что и панель фильтров ниже по файлу.

   Селектор общий (~), а не соседний (+): между шапкой и очередью стоит
   заголовок для программ чтения с экрана. Он невидим и места не
   занимает, но в дереве документа он есть, и соседним селектором
   очередь после него не достать — прежнее правило .admin-head + .attention
   молча не срабатывало ни разу. .attention на весь кабинет одна. */
.admin-head ~ .attention {
  border-top: 0;
  margin-top: calc(-1 * var(--admin-gap));
}
/* Последняя строка очереди не отчёркивается: её и так закрывает нижняя
   рамка врезки, а две линии подряд — это уже не набор, а таблица. */
.attention__item:last-child { border-bottom: 0; }
/* Строка очереди — один этаж: крупное число, подпись, шеврон. Второй
   строки-пояснения больше нет — она пересказывала подпись и стоила
   первому экрану двух сотен точек. Высота строки — общий нижний предел
   листа: очередь листается тем же шагом, что рабочие списки. */
.attention__item {
  display: grid; grid-template-columns: 48px minmax(0, 1fr) 16px;
  align-items: center; gap: var(--s-3);
  min-height: var(--admin-row-min);
  padding: var(--s-2) var(--s-3);
  border-bottom: 1px solid var(--line-soft);
  color: var(--ink); text-decoration: none;
  transition: transform var(--t-fast);
}
/* Отклик тот же, что у строк рабочих списков ниже: строка подаётся
   навстречу курсору. Держать на одном экране два разных ответа на одно
   и то же движение — заливку здесь и сдвиг там — значит заставлять
   человека гадать, одинаково ли они нажимаются. */
.attention__item:hover { color: var(--ink); text-decoration: none; transform: translateX(var(--s-1)); }
.attention__value {
  text-align: right;
  font-family: var(--font-display); font-size: var(--fs-h2); line-height: 1;
  font-weight: var(--fw-semibold);
  font-variant-numeric: tabular-nums lining-nums;
}
.attention__body { min-width: 0; }
.attention__label { font-size: var(--fs-md); font-weight: var(--fw-medium); line-height: var(--lh-snug); }
/* Шеврон — тихий указатель «строка ведёт дальше»; живёт только на
   широком экране (на телефоне вся карточка и так очевидно нажимается). */
.attention__chevron { display: inline-flex; color: var(--ink-3); }

.attention__item--danger  { box-shadow: inset 3px 0 0 var(--red); }
.attention__item--danger  .attention__value { color: var(--red); }
.attention__item--warning .attention__value { color: var(--amber-ink); }
.attention__item--accent  .attention__value { color: var(--brass); }

/* Тихо — значит тихо: одна строка ростом со строкой очереди, без заливки
   и без жирного. Врезка при этом та же самая: рамки, воздух и подтяжка
   к линии шапки повторены здесь один в один. Место, отведённое первому
   вопросу утра, не должно менять размер и очертания оттого, что ответ на
   него сегодня «дел нет», — иначе страница дёргается через день. */
.attention--calm {
  display: flex; align-items: center; gap: var(--s-3);
  min-height: var(--admin-row-min);
  padding: var(--s-6) 0;
  border-top: 1px solid var(--line);
  border-bottom: 1px solid var(--line);
  margin: 0 0 var(--admin-gap);
  font-size: var(--fs-md); color: var(--ink-2);
}
.admin-head ~ .attention--calm {
  border-top: 0;
  margin-top: calc(-1 * var(--admin-gap));
}
.attention__calm-icon { color: var(--green); display: inline-flex; }

/* В. Быстрые действия. Их ровно столько, сколько есть настоящих: всё, что
   является переходом, стоит в колонке разделов, и повторять его кнопкой
   значит спорить с собственной навигацией. Осталось одно действие —
   создание книги, — и оно живёт в шапке страницы, как и на «Книгах». */
.quick { display: flex; gap: var(--s-2); flex-wrap: wrap; }

/* Г. Рабочие списки: две колонки на широком экране.
   Перед ними — настоящая пауза. Очередь дел и рабочие списки отвечают на
   разные вопросы («что горит» и «что происходит»), а стояли в одном
   потоке через тот же отступ, что и строки внутри самой очереди: два
   этажа сливались в один.

   Между колонками — полоса в 64 точки. Прежние 32 читались как отступ
   внутри одного блока: два списка с одинаковыми капительными заголовками
   и одинаковыми строками стояли так близко, что глаз брал их за одну
   таблицу в четыре столбца и пытался читать поперёк. Полоса шириной
   с полтора десятка знаков — это межколонник газетной полосы: она
   говорит «две разные колонки» раньше, чем человек прочтёт заголовки.
   Ниже 1025 сетка всё равно складывается в одну колонку (см. раздел
   адаптива), и межколонник там не участвует. */
.dash-grid {
  display: grid; grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--s-10) var(--s-16); margin: var(--s-12) 0 var(--s-16);
}
.dash-grid > * { min-width: 0; }
/* Обе колонки начинаются с одной линии. Раньше отступ слева был только у
   просроченной строки — под красную кромку, — и правый столбец, где такие
   строки есть, стоял на двенадцать точек правее левого. Отступ получают
   все строки сразу: ровно тот же приём, что в очереди дел выше, где
   кромка тоже ничего не сдвигает. */
.dash-grid .dash-row { padding-left: var(--s-3); }

/* Строка короткого списка панели: вся строка — ссылка, поэтому попасть
   в неё легко и мышью, и пальцем, и с клавиатуры.

   Записи разделены так, как их разделяют в наборе: воздухом и волосяной
   линейкой, без единой заливки. Ритм несимметричный, и это осознанно —
   16 точек над линейкой и 32 под ней. Линейка принадлежит записи, под
   которой стоит: она её закрывает, а не открывает следующую. Поставленная
   ровно посередине пробела (как было раньше: 16 и 16), она читается
   ничьей — просто чертой через равные промежутки, — и пять заявок снова
   собираются в один сплошной столбик.

   Считается это так: padding-bottom доводит запись до линейки, а
   margin-bottom отбивает от неё следующую. Ни gap, ни отступов у самого
   списка: промежуток между элементами оторвал бы линейку от записи и
   подвесил её в пустоте, а этого в наборе не бывает. */
.dash-row {
  display: flex; align-items: baseline; justify-content: space-between;
  gap: var(--s-3) var(--s-4); min-height: var(--control-h);
  padding: var(--s-4) var(--s-2) var(--s-4) 0;
  margin-bottom: var(--s-4);
  border-bottom: 1px solid var(--line-soft);
  color: var(--ink); text-decoration: none;
  transition: transform var(--t-fast);
}
/* Строка подаётся навстречу курсору вместо того, чтобы подсвечиваться
   плашкой. Заливка под курсором — последнее, что осталось от коробок:
   на листе без единой поверхности серый прямоугольник под строкой
   выглядел выделением текста, а не откликом ссылки. Сдвиг на 4 точки
   говорит «эта строка ведёт дальше» и не добавляет на лист ни одного
   нового цвета. Тем, кто просил у системы меньше движения, переход
   гасит общее правило в конце файла — вместе со всеми остальными. */
.dash-row:hover { transform: translateX(var(--s-1)); color: var(--ink); text-decoration: none; }
/* Последняя запись списка не отчёркивается и не отбивается: закрывает её
   не своя линейка, а конец раздела. Линейка в самом низу колонки читалась
   бы обещанием продолжения, которого нет, а лишние 32 точки под ней
   разводили бы левую и правую колонки на разную глубину — их низ и так
   не совпадает, списки разной длины. */
.dash-row:last-child {
  border-bottom: 0;
  padding-bottom: 0;
  margin-bottom: 0;
}
.dash-row.is-late { box-shadow: inset 3px 0 0 var(--red); padding-left: var(--s-3); }
.dash-row__main { min-width: 0; }
/* Опознание строки — на ступень крупнее рабочих списков. Панель держит
   по пять строк в колонке, а не полсотни: здесь можно позволить имени
   заявителя и названию книги полный книжный кегль. В «Книгах» и «Заявках»
   название остаётся на 15-м — там кегль умножается на длину списка. */
.dash-row__title {
  display: block;
  font-family: var(--font-display); font-size: var(--fs-base); line-height: var(--lh-tight);
  color: var(--ink);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}

/* Подпись строки — не фраза, а несколько разных сведений рядом.
   Раньше они были склеены точками в одну серую строку: «ORD-000002 ·
   1 книга · 14 августа 2026, 02:14». Такую строку нельзя окинуть
   взглядом — её можно только прочитать целиком, слева направо, и так
   пять раз подряд. Разложенные по отдельным тегам, те же сведения
   получают каждое свой голос, и главное из них — сколько книг —
   становится видно раньше, чем человек начал читать.

   Разделяет их расстояние, а не точка: точка между кусками разного
   веса снова сшивает их в предложение. Перенос разрешён — на узкой
   колонке подпись сложится в два этажа вместо того, чтобы оборваться
   многоточием на середине даты. */
.dash-row__meta {
  display: flex; align-items: baseline; flex-wrap: wrap;
  gap: var(--s-1) var(--s-4);
  margin-top: var(--s-2); min-width: 0;
  font-size: var(--fs-xs); color: var(--ink-3);
}
/* Сколько книг — единственное, ради чего библиотекарь смотрит на список
   заявок утром: заявку на одну книгу и заявку на восемь собирают
   по-разному. Поэтому число стоит первым и набрано чернилами страницы,
   а не подписью. Стоя первым, все числа колонки выстраиваются в одну
   вертикаль — их можно пересчитать столбцом, не читая строк. */
.dash-row__count {
  flex: 0 0 auto; white-space: nowrap;
  font-weight: var(--fw-medium); color: var(--ink);
  font-variant-numeric: tabular-nums lining-nums;
}
/* Служебный номер — техническая ссылка, а не слово. Набран капителью
   с разрядкой, как все служебные подписи системы: по нему находят
   заявку, но читают его редко. ORD-000002 и LIB-000001 — одна и та же
   вещь в двух списках, и выглядеть они обязаны одинаково. */
.dash-row__ref {
  flex: 0 0 auto; white-space: nowrap;
  font-size: var(--fs-2xs); letter-spacing: var(--ls-caps); text-transform: uppercase;
  color: var(--ink-3); font-variant-numeric: tabular-nums lining-nums;
}
/* Когда это было — самое тихое сведение строки: дата отвечает на вопрос,
   который задают уже после того, как заявку открыли. Не переносится
   внутри себя: «14 августа 2026,» и «02:14» на разных этажах читаются
   двумя разными сведениями. */
.dash-row__when { flex: 0 0 auto; white-space: nowrap; color: var(--ink-3); }
/* У кого книга. Имя длиннее колонки обрывается многоточием — в отличие
   от номера и даты, оно единственное здесь неограниченной длины. */
.dash-row__who {
  min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  color: var(--ink-3);
}
/* Правое поле строки — столбец, а не то, что осталось справа.
   Состояние и срок стоят в конце строки через space-between, то есть
   держатся правого края: «ВЫДАНА» шириной 74 и «В ОБРАБОТКЕ» шириной 108
   начинались в двух разных местах — 718 и 684. Сверху вниз это читается
   как ступенька, хотя ничего не случилось: просто слова разной длины.
   Твёрдая мера возвращает столбцу левый край, и все состояния списка
   встают на одну вертикаль. Мера взята по самому длинному, что здесь
   бывает: «просрочено на 5 дней» под датой возврата. */
.dash-row > .status,
.dash-row > .due { flex: 0 0 var(--dash-aside-w); }

/* Статус садится на базовую линию имени, а не рядом с ней.
   Строка выровнена по базовым линиям (align-items: baseline выше), но
   у самого статуса базовой линией служила точка-индикатор: она стоит
   первой, текста в ней нет, и её «базовая линия» — просто нижний край
   кружка. Статус висел на 1,3 точки ниже имени — мало, чтобы заметить,
   достаточно, чтобы строка выглядела неаккуратно набранной.

   Точку выводим из выравнивания собственным align-self, и базовую линию
   статуса начинает задавать его текст. Сама точка при этом остаётся ровно
   там же — по центру строчных букв (замерено: центр точки и центр текста
   расходятся на 0,2 точки, как и раньше). Правило заперто внутри строки
   панели: общий .status живёт ещё на пяти экранах и не тронут. */
.dash-row .status { align-items: baseline; }
.dash-row .status__dot { align-self: center; }

/* Д. Состояние фонда — одной спокойной строкой.
   Это справка, а не сигнал: на вопрос «сколько у нас всего» отвечают раз
   в месяц, и шесть крупных чисел внизу страницы только спорили с очередью
   дел наверху. Числа те же, набраны гротеском по размеру обычной строки:
   в кабинете антиква имеет право только на название книги. */
.metrics {
  display: flex; flex-wrap: wrap; align-items: baseline; gap: 0;
  row-gap: var(--s-1);
  font-size: var(--fs-sm); color: var(--ink-3);
}
.metric { display: inline-flex; align-items: baseline; gap: var(--s-1); }
/* Пары «число подпись» разделяет типографская точка, а не расстояние:
   строка читается одной фразой, а не рядом показателей. На узком экране
   фраза просто переносится по парам — никаких колонок и таблиц. */
.metric + .metric::before { content: "·"; padding: 0 var(--s-2); color: var(--line-strong); }
.metric__value {
  font-size: var(--fs-sm); font-weight: var(--fw-semibold); color: var(--ink-2);
  font-variant-numeric: tabular-nums lining-nums;
}
.metric__label { font-size: var(--fs-sm); color: var(--ink-3); }

/* ------------------------------------- 26.5 Поиск и фильтры (одна панель) */
/* Во всех разделах кабинета панель устроена одинаково: строка поиска и
   управляющих полей, под ней — переключатель охвата со счётчиками. Дважды
   изобретать фильтры не приходится, и библиотекарь узнаёт панель в лицо.

   Панель — полоса без рамки: собственных линий у неё нет, расстояния
   задают ритм листа (шапка → тулбар 20, тулбар → список 16), а между
   этажами — 12 из зазора сетки. Панель из одного этажа (только охват)
   просто ниже двухэтажной — разница высот не выравнивается искусственно. */
.admin-toolbar {
  display: grid; gap: var(--s-3);
  margin-bottom: var(--s-4);
}
/* Панель сразу под шапкой страницы. Вертикальный ритм листа утверждён:
   линия шапки → тулбар — 20 (--s-5); собственный отступ шапки — 24
   (--admin-gap), разницу снимает отрицательная поправка. */
.admin-head + .admin-toolbar { margin-top: calc(var(--s-5) - var(--admin-gap)); border-top: 0; }
.admin-toolbar__form {
  display: flex; gap: var(--s-2); flex-wrap: wrap; align-items: center;
}
.admin-toolbar__search { flex: 3 1 260px; min-width: 0; }
.admin-toolbar__control { flex: 1 1 190px; min-width: 0; }
.admin-toolbar__form .btn { flex: 0 0 auto; }
.admin-toolbar__form .input,
.admin-toolbar__form .select { min-height: var(--control-h-sm); font-size: var(--fs-sm); }
.admin-toolbar__form .btn { min-height: var(--control-h-sm); font-size: var(--fs-sm); padding: 0 var(--s-4); }
/* Расстояние между формой и охватом даёт зазор сетки тулбара; прежние
   собственные отступ и линия этажа сняты, чтобы не удваивать ни то,
   ни другое. */
.admin-toolbar__form + .scope { margin-top: 0; padding-top: 0; border-top: 0; }

/* Переключатель охвата. Не «таблетки» витрины: мельче, плотнее, со счётчиком. */
/* Расстояние между пунктами держит теперь сама полоса: боковых полей у
   пункта больше нет, иначе линейка выбранного охвата тянулась бы шире
   своего слова и снова читалась плашкой. */
.scope { display: flex; flex-wrap: wrap; gap: var(--s-1) var(--s-5); }
/* Охват — это оглавление списка, а не панель кнопок.
   Тёмная плашка на выбранном фильтре была последней заливкой кабинета:
   лист без единой поверхности, и посреди него чёрный прямоугольник со
   скруглением — предмет из другой системы. Выбранный охват теперь набран
   тем же, чем набрано всё остальное здесь: полным тоном чернил, весом
   и волосяной линейкой под словом. Линейка — не рамка вокруг, а
   подчёркивание в наборе: она отмечает, а не обводит.

   Место под линейку отведено у всех пунктов сразу и прозрачно, поэтому
   выбор фильтра не сдвигает строку на точку вверх. */
.scope__link {
  display: inline-flex; align-items: center; gap: var(--s-2);
  min-height: var(--control-h-sm); padding: 0;
  border-bottom: 1px solid transparent;
  color: var(--ink-3); font-size: var(--fs-sm); text-decoration: none;
  transition: color var(--t-fast), border-color var(--t-fast);
}
.scope__link:hover { color: var(--ink-2); text-decoration: none; }
.scope__link.is-active {
  color: var(--ink); font-weight: var(--fw-medium);
  border-bottom-color: var(--ink);
}
.scope__link.is-active:hover { color: var(--ink); }
.scope__count { font-size: var(--fs-2xs); color: var(--ink-3); font-variant-numeric: tabular-nums; }
.scope__link.is-active .scope__count { color: var(--ink-3); }

/* ------------------------------------------------------ 26.6 Статусы */
/* Смысл всегда несёт подпись. Точка только помогает найти её взглядом —
   поэтому статус остаётся понятным и на чёрно-белой печати, и тому, кто
   не различает цвета. */
.status {
  display: inline-flex; align-items: center; gap: var(--s-2);
  font-size: var(--fs-2xs); font-weight: var(--fw-semibold);
  letter-spacing: var(--ls-caps); text-transform: uppercase;
  line-height: var(--lh-snug); color: var(--ink-3); white-space: nowrap;
}
.status__dot {
  flex: 0 0 auto; width: 6px; height: 6px; border-radius: 50%;
  background: currentColor;
}
.status--free    { color: var(--green-ink); }
.status--warning { color: var(--amber-ink); }
.status--danger  { color: var(--red-ink); }
.status--accent  { color: var(--brass); }
.status--busy    { color: var(--ink-3); }

/* Срок возврата: дата и, отдельной строкой, что она значит сегодня. */
.due { display: block; font-variant-numeric: tabular-nums; }
.due__date { display: block; font-size: var(--fs-sm); color: var(--ink); white-space: nowrap; }
.due__note { display: block; margin-top: var(--s-05); font-size: var(--fs-2xs); color: var(--ink-3); }
.due--late   .due__date { color: var(--red-ink); font-weight: var(--fw-semibold); }
.due--late   .due__note { color: var(--red); font-weight: var(--fw-semibold); }
.due--today  .due__date { color: var(--amber-ink); font-weight: var(--fw-semibold); }
.due--today  .due__note { color: var(--amber-ink); font-weight: var(--fw-semibold); }

/* ------------------------------------------- 26.7 Рабочий список записей */
/* Главный узор кабинета. Одна запись — одна строка сетки: слева опознание
   (обложка или имя), в середине столбцы, справа действия. Тот же DOM на
   телефоне превращается в компактную карточку — без горизонтальной
   прокрутки и без ужимания настольной таблицы.

   Ширины столбцов задаёт модификатор списка, а не сама строка: так шапка
   и строки всегда совпадают, сколько бы записей ни было — пять или тысяча. */
/* Единый отступ над списком — и над сеткой, и над настоящей <table>:
   раньше внешнего отступа не было ни у той, ни у другой, и расстояние
   до тулбара задавал только сам тулбар. Отступы соседей схлопываются,
   поэтому после заголовка раздела (16 снизу) расстояние не удваивается. */
.data-list, .admin-table-wrap { margin-top: var(--s-4); }
.admin-toolbar + .data-list, .admin-toolbar + .admin-table-wrap { margin-top: var(--s-4); }

/* Межколонник списка — 32 точки вместо 16.
   Шестнадцать оставляли между содержимым соседних столбцов просвет
   в двадцать точек: дата заявки кончалась почти вплотную к имени
   читателя, и два разных сведения читались одной фразой. В таблице
   зазор между столбцами обязан быть заметно больше, чем расстояние
   между строками внутри столбца, — иначе глаз перестаёт понимать,
   читать ему вниз или вбок. Здесь 32 против 12: отношение почти три
   к одному, и колонки наконец стоят колонками. */
.data-head,
.data-row {
  display: grid; gap: var(--s-3) var(--s-8); align-items: center;
}
/* Поля строки — нулевые с обеих сторон. Список стоит ровно в тех же
   вертикальных границах, что заголовок страницы, панель фильтров и
   линейки разделов: у листа два края, и всё, что на нём лежит, начинается
   и кончается на них. Раньше справа оставалось восемь точек, и столбец
   действий не доходил до края на глаз заметно.

   Верхняя линия листа живёт на самой шапке, а не на списке: шапка липкая
   (см. медиазапрос в 26.8) и при прокрутке отрывается от края — линия
   обязана уехать вместе с ней, иначе прилипшая шапка остаётся без
   верхнего края. В покое рисунок тот же, что был у линии списка. */
.data-head {
  padding: var(--s-2) 0;
  font-size: var(--fs-2xs); font-weight: var(--fw-semibold);
  letter-spacing: .06em; text-transform: uppercase; color: var(--ink-3);
  border-top: 1px solid var(--line);
  border-bottom: 1px solid var(--line);
}
/* Нижний предел роста строки общий с таблицей (26.8): строка с обложкой
   по-прежнему выше (66 + 24 = 90), но короткие строки — заявки, разделы —
   больше не опускаются ниже общего ритма листа. */
.data-row {
  min-height: var(--admin-row-min);
  padding: var(--admin-row-y) 0;
  border-bottom: 1px solid var(--line-soft);
  transition: transform var(--t-fast);
}
/* Строка подаётся навстречу курсору, а не подсвечивается плашкой.
   Заливка строки была последним следом «панели управления» в списках:
   серый прямоугольник во всю ширину листа читался выделением текста,
   а не откликом ссылки. Тот же сдвиг на четыре точки, что уже работает
   в строках панели и в очереди дел, — один отклик на все списки
   кабинета. Ни одного нового цвета на лист он не добавляет. */
.data-row:hover { transform: translateX(var(--s-1)); }
.data-row:last-child { border-bottom: 1px solid var(--line); }
.data-row.is-dimmed .data-row__title,
.data-row.is-dimmed .data-row__meta { color: var(--ink-3); }
.data-row.is-dimmed .thumb { opacity: .5; }

/* Опознание записи: миниатюра слева, название и приписка справа. */
.data-row__ident { display: flex; align-items: flex-start; gap: var(--s-3); min-width: 0; }
.data-row__main  { min-width: 0; }
/* Название — единственная антиква в строке. Оно и есть главный якорь:
   по нему библиотекарь находит нужную запись среди сотни. */
.data-row__title {
  font-family: var(--font-display); font-size: var(--fs-md);
  line-height: var(--lh-tight); color: var(--ink); overflow-wrap: anywhere;
}
.data-row__title a { color: var(--ink); text-decoration: none; }
.data-row__title a:hover { color: var(--ink); text-decoration: underline;
  text-decoration-color: var(--line); text-underline-offset: .18em; }
/* Воздух под опознанием строки.
   Замер показал 2,4 точки между «Иван Ярош» и его телефоном: два разных
   сведения стояли ближе друг к другу, чем строки внутри одного абзаца,
   и читались одним слипшимся блоком. Два пункта отступа — это меньше,
   чем межстрочный пробел собственного текста; чтобы имя и контакт
   разделились, нужен просвет заметно больше внутристрочного. Восемь
   точек дают ровно это и не растягивают строку списка. */
.data-row__meta {
  margin-top: var(--s-2); font-size: var(--fs-xs); color: var(--ink-3);
  overflow-wrap: anywhere;
}
.data-row__meta b { color: var(--ink-2); font-weight: var(--fw-medium); }
.data-row__cell { font-size: var(--fs-sm); color: var(--ink-2); min-width: 0; }
.data-row__cell b { color: var(--ink); font-weight: var(--fw-semibold); }
/* Статус в столбце списка переносится по словам. Неразрывная строка —
   правило для узких мест вроде шапки заявки, а здесь «Скрыта от
   читателей» просто не помещается в свой столбец и наезжает на соседний.
   Сокращать подпись нельзя: она объясняет, от кого книга скрыта. */
.data-row__cell .status { white-space: normal; align-items: baseline; }
/* Числовой столбец выключен вправо. Числа разной длины, выключенные
   влево, образуют рваный левый край и сравниваются только чтением: 9 и
   146 стоят началом в одной точке, и глазу кажется, что они одного
   порядка. Выключка вправо ставит их разрядом под разрядом, и столбец
   начинает читаться сверху вниз одним движением. Моноширинные цифры
   держат разряды на месте. Подпись столбца выключается туда же (см.
   .data-list--orders ниже) — иначе шапка перестаёт стоять над данными. */
.data-row__num {
  font-size: var(--fs-sm); color: var(--ink-2);
  font-variant-numeric: tabular-nums lining-nums; white-space: nowrap;
  text-align: right;
}
.data-row__actions {
  display: flex; align-items: center; gap: var(--s-2); justify-content: flex-end;
  flex-wrap: wrap;
}
.data-row__actions form { display: inline-flex; }
/* Тихое действие строки: подчёркивание приходит под курсором, а не стоит
   всегда. Полсотни подчёркнутых строк подряд превращали столбец действий
   в частокол из линий — ровно то, от чего в этом списке уже отказались,
   убрав кнопки. Что это ссылка, видно по чернилам: действие набрано
   полным тоном там, где вокруг служебные подписи третьим. */
.data-row__actions .btn--quiet {
  color: var(--ink); text-decoration: none;
  transition: text-decoration-color var(--t-fast), color var(--t-fast);
}
.data-row__actions .btn--quiet:hover {
  color: var(--ink); text-decoration: underline;
  text-decoration-color: currentColor; text-underline-offset: .2em;
}
/* Подпись столбца для телефона. На широком экране её заменяет шапка. */
.data-row__label { display: none; }

/* Код заявки — опознавательный знак строки. Моноширинные цифры, чтобы
   в столбце ORD-000007 и ORD-000112 стояли ровно друг под другом. */
/* Код заявки — служебный номер, а не заголовок строки.
   Набранный полужирным 15-м кеглем, он был самым тяжёлым словом строки и
   спорил с именем читателя справа: страницу открывают, чтобы увидеть, кто
   просит, а первым в глаза бросался ORD-000002. Теперь это капитель того
   же тона и веса, что все служебные номера системы — LIB-000144 в списке
   книг, «ЗАЯВКА ORD-000001» в надзаголовке самой заявки. Ссылкой он быть
   не перестал: по нему по-прежнему открывают заявку. */
.order-code {
  display: inline-block;
  font-size: var(--fs-2xs); font-weight: var(--fw-normal);
  letter-spacing: var(--ls-caps); text-transform: uppercase;
  font-variant-numeric: tabular-nums;
  color: var(--ink-3); white-space: nowrap; text-decoration: none;
  transition: color var(--t-fast);
}
a.order-code:hover { color: var(--ink); text-decoration: none; }

/* Имя читателя — то, ради чего строку читают, и оно набрано книжным
   кеглем антиквы. В соседних списках опознание строки остаётся на 15-м:
   там его умножают полсотни строк, здесь заявок в работе единицы. */
.data-list--orders .data-row__title { font-size: var(--fs-base); }
/* Просьба читателя: цитата, а не поле данных.
   Ровно одна строка. Двух хватало, чтобы заявка с длинным пожеланием
   стала вдвое выше соседних, и список начинал «дышать» рывками; при
   полусотне заявок это стоит целого экрана прокрутки. Одной строки
   достаточно, чтобы увидеть, что просьба есть, и понять её смысл, —
   а целиком она всегда лежит на странице самой заявки. */
/* Просьба читателя в списке — та же цитата, что на самой заявке, и
   набрана она так же: волосяная линейка на поле, а не двухточечная
   латунная скоба. Две разные выключки одной и той же фразы на двух
   экранах — самый заметный вид рассогласования: человек переходит из
   списка в заявку и видит, что слова читателя оформлены иначе. */
.data-row__quote {
  margin-top: var(--s-2); padding-left: var(--s-3);
  border-left: 1px solid var(--line-strong);
  font-style: italic;
  font-size: var(--fs-xs); color: var(--ink-2); line-height: var(--lh-snug);
  display: -webkit-box; -webkit-line-clamp: 1; -webkit-box-orient: vertical; overflow: hidden;
}

/* Наличие экземпляров: дробь «свободно / всего» и подпись под ней.
   Столбец числовой, поэтому дробь выключена вправо; подпись под дробью
   даёт словесную расшифровку — отдельного скрытого текста для программ
   чтения не нужно. Голос у числителя: свободные есть — зелёный, ноль —
   янтарный; знаменатель — тихий. Ни процентов, ни полоски прогресса. */
.tally { display: block; text-align: right; }
.tally__ratio { display: block; font-size: var(--fs-lg); font-weight: var(--fw-normal);
                color: var(--ink-3); font-variant-numeric: tabular-nums lining-nums; }
.tally__ratio b { font-weight: var(--fw-semibold); }
/* Контекст .tally__ratio здесь обязателен, а не для красоты: голый
   .tally__free весит меньше, чем .data-row__cell b выше по файлу, —
   а тот красит любой полужирный в строке в основные чернила. Без
   контекста числитель терял свой голос и стоял чёрным и при свободных
   экземплярах, и при нуле. */
.tally__ratio .tally__free { color: var(--green); }
.tally__ratio .tally__none { color: var(--amber-ink); }
.tally__slash { padding: 0 var(--s-05); color: var(--line-strong); }
.tally__note { display: block; margin-top: var(--s-05); font-size: var(--fs-2xs); color: var(--ink-3); }

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

   Гибкий столбец обязан иметь потолок. Пока он один на всю строку, весь
   запас ширины стола достаётся ему: на 1440 столбец читателя вырастал до
   524 точек, на 1920 — до 644, и между именем и числом книг открывалась
   пустая просека в две сотни точек, тянущаяся через весь список. Имя,
   контакт и однострочная просьба столько не занимают никогда. Потолок
   поставлен по длине самой длинной живой строки, а остаток запаса уходит
   в столбец действий — он выключен вправо, поэтому действие не отрывается
   от строки, а край списка остаётся ровным.

   Столбец действий один на все три списка — var(--admin-act-w): раньше
   он был записан тремя разными числами, и правый край действий гулял от
   экрана к экрану. Столбец «Экземпляры» на Книгах — 168: дробь «4 / 7»
   с подписью под ней шире прежних трёх чисел.

   Запас ширины стола делится между столбцами долями, а не достаётся
   одному. Это и есть разница между таблицей, которая «смотрится», и
   таблицей, которая разъехалась: пустота, собранная в одном месте,
   читается дырой, та же пустота, разложенная по всем столбцам поровну, —
   воздухом. На 1920 через это прошли дважды: сперва весь запас уходил
   в столбец действий (тот раздувался до 366 точек при ссылке в полторы
   сотни, и действие отрывалось от состояния), потом — в столбец
   читателя (622 точки под короткое имя, и просека открывалась уже перед
   числами). Оба раза строка распадалась на группы, разъехавшиеся по
   краям.

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

   Столбцы кода заявки и действий остаются на твёрдой мере: код набран
   моноширинными цифрами и всегда одной длины, а действие — та самая
   мера --admin-act-w, ради которой правый край всех трёх списков стоит
   на одной вертикали. */
/* Столбцу состояния — 180 вместо 148. Замерено: «Скрыта от читателей»
   в капители с разрядкой занимает 179 точек, «Сейчас недоступна» — 170,
   «Нет экземпляров» — 149. То есть в прежние 148 не помещалась ни одна
   из трёх, и подпись рвалась надвое: «СЕЙЧАС» над «НЕДОСТУПНА». Строка
   с таким состоянием становилась выше соседних, и список терял ритм.
   Тридцать две точки отданы гибким столбцом названия — там перебор
   закрывает многоточие, а здесь рвать нечего: подпись состояния
   сокращать нельзя, она объясняет, что именно с книгой. */
.data-list--books .data-head,
.data-list--books .data-row  {
  grid-template-columns:
    minmax(0, 2fr) minmax(168px, 1fr) minmax(180px, 1fr) var(--admin-act-w);
}
/* Столбцу состояния заявок — 152 вместо 128. Замер подписей: «Готова
   к выдаче» занимает 145 точек, «В обработке» — 108, «Отменена» — 90.
   В прежние 128 самая длинная не помещалась и рвалась надвое. */
/* Столбцу заявки — 168 вместо 146. Мера взята по самому длинному, что
   в нём бывает: не по коду (89 точек), а по дате под ним. Замер по всем
   месяцам: «28 сентября 2026, 23:59» занимает 157 точек — то есть в
   прежние 146 сентябрьская заявка не помещалась вовсе и обрывалась. */
.data-list--orders .data-head,
.data-list--orders .data-row {
  grid-template-columns:
    168px minmax(0, 2fr) minmax(88px, 1fr) minmax(152px, 1fr) var(--admin-act-w);
}
.data-list--users .data-head,
.data-list--users .data-row  {
  grid-template-columns:
    minmax(0, 2fr) minmax(168px, 1fr) minmax(168px, 1fr) var(--admin-act-w);
}

/* Узкие столбцы рабочих списков стоят по оси — и подпись, и то, что под
   ней. Прежде подпись числового столбца жалась вправо вместе с числами,
   а подпись состояния — влево, и два соседних столбца читались
   разложенными по разным правилам: «ЭКЗЕМПЛЯРЫ» над числами справа,
   «СОСТОЯНИЕ» над точкой слева. Ось у столбца одна, и всё, что в нём
   стоит, держится за неё. Тот же приём, что у числа под «КНИГ В ЗАЯВКЕ»
   в шапке заявки.

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

   Метка состояния центрируется целиком, вместе со своей точкой: точка
   и подпись — один предмет, и разлучать их, чтобы подогнать под ось
   ровно буквы, значило бы вывесить точку сбоку от столбца. */
.data-list--books  .data-head > :nth-child(2),
.data-list--books  .data-head > :nth-child(3),
.data-list--books  .data-row  > :nth-child(2),
.data-list--books  .data-row  > :nth-child(3),
.data-list--orders .data-head > :nth-child(3),
.data-list--orders .data-head > :nth-child(4),
.data-list--orders .data-row  > :nth-child(3),
.data-list--orders .data-row  > :nth-child(4) { text-align: center; }
.data-list--books .tally { text-align: center; }

/* ------------------------------------------------- 26.8 Таблица кабинета */
/* Для по-настоящему табличных данных (выдачи, история, категории, архивы)
   остаётся настоящая <table>: у неё правильная семантика для программ
   чтения с экрана и естественное выравнивание чисел и дат. */
/* position: relative здесь не украшение, а граница.
   В последнем столбце таблиц кабинета стоит подпись для программ чтения
   с экрана — <span class="visually-hidden">Действие</span>. Она вынута из
   потока (position: absolute) и шириной в одну точку, но собственной
   системы координат у неё не было: ближайшего позиционированного предка
   нет, значит считается она от всей страницы. И тогда её точка оказывалась
   там, где стоит седьмой столбец, — за правым краем окна, — а вынутый из
   потока элемент не удерживают ни прокрутка обёртки, ни overflow-x: clip
   рабочего места: они обрезают только то, что лежит внутри них.
   Результат: на 1024 «На руках» тянулась вбок на 30 точек в пустоту.
   Одна строка возвращает подписи её систему координат — и обёртка
   наконец удерживает всё, что в ней лежит. Подпись остаётся на месте
   и остаётся озвученной. */
.admin-table-wrap { position: relative; overflow-x: auto; -webkit-overflow-scrolling: touch; }
.admin-table {
  width: 100%; border-collapse: collapse;
  font-size: var(--fs-sm); font-family: var(--font);
}
/* Ритм таблицы — тот же, что у рабочего списка (26.7), до точки.
   Раньше это были два разных ритма на одном столе: строка списка шла от
   самого края листа, а ячейка таблицы отступала на двенадцать точек, и
   расстояние между столбцами у них отличалось в полтора раза. Два списка
   на соседних экранах читались как две разные программы.

   Теперь одинаково: восемь точек по бокам ячейки дают между столбцами
   те же шестнадцать, что даёт зазор сетки у строки списка; крайние
   ячейки поля не имеют вовсе и стоят на краях листа; воздух по вертикали
   и там и там задаёт --admin-row-y; содержимое выровнено по центру
   строки, как в списке, — иначе короткая ячейка «висит» у верхнего края
   строки, высоту которой задала обложка. */
.admin-table th {
  text-align: left; padding: var(--s-2) var(--s-2);
  font-size: var(--fs-2xs); font-weight: var(--fw-semibold);
  letter-spacing: .06em; text-transform: uppercase;
  color: var(--ink-3); white-space: nowrap;
  border-top: 1px solid var(--line); border-bottom: 1px solid var(--line);
}
.admin-table td {
  padding: var(--admin-row-y) var(--s-2);
  border-bottom: 1px solid var(--line-soft);
  color: var(--ink-2); vertical-align: middle;
}
/* Тот же нижний предел роста строки, что у рабочего списка. min-height
   в табличной раскладке не действует ни на строку, ни на ячейку — рядом
   стоит height: в таблице он работает как минимум, а не как потолок,
   поэтому строка с обложкой по-прежнему выше общего предела. */
.admin-table tbody tr { min-height: var(--admin-row-min); }
.admin-table tbody td { min-height: var(--admin-row-min); height: var(--admin-row-min); }
.admin-table th:first-child,
.admin-table td:first-child { padding-left: 0; }
.admin-table th:last-child,
.admin-table td:last-child  { padding-right: 0; }
/* Заливки под курсором у строки таблицы нет — как и у строк всех прочих
   списков кабинета. Здесь она была последней: полоса --surface-2 во всю
   ширину листа читалась выделением текста, а не откликом. Да и отвечать
   ей нечем: строка таблицы не ссылка, нажимают в ней поле и кнопку,
   и каждое из них о наведении говорит само. */
.admin-table tbody tr { transition: none; }
.admin-table tbody tr:last-child td { border-bottom: 1px solid var(--line); }
.admin-table td strong { color: var(--ink); font-weight: var(--fw-semibold); }
/* Столбец действий — как в рабочем списке: прижат вправо, между
   действиями восемь точек. Пробел между строчными элементами даёт около
   четырёх, остальное добирает поле — иначе два действия в одной ячейке
   («Скачать» и «Проверить» у архивов) стоят вплотную, а в списке те же
   два держат расстояние. */
.admin-table__actions { text-align: right; white-space: nowrap; }
.admin-table__actions > * { display: inline-flex; }
.admin-table__actions > * + * { margin-left: var(--s-1); }
.admin-table__title {
  display: block;
  font-family: var(--font-display); font-size: var(--fs-md);
  line-height: var(--lh-tight); color: var(--ink); overflow-wrap: anywhere;
}
.admin-table__sub {
  display: block; margin-top: var(--s-05);
  font-size: var(--fs-xs); color: var(--ink-3); overflow-wrap: anywhere;
}
.admin-table__code { font-variant-numeric: tabular-nums; white-space: nowrap; }
/* Числовой столбец таблицы. То же правило, что и у списка (см.
   .data-row__num): счёт, порядок, размер и любой другой счётчик
   выключаются вправо и набираются моноширинными цифрами, чтобы столбец
   читался разрядом под разрядом. Класс ставится и на подпись, и на
   ячейку — иначе шапка перестаёт стоять над данными. */
/* Селектор носит имя таблицы, потому что подпись столбца — это <th>,
   а у него уже есть собственная выключка влево от .admin-table th.
   Голый класс её не перебивал, и шапка числового столбца стояла слева
   от своих чисел — ровно то, что этот класс и должен был исправить. */
.admin-table th.admin-table__num,
.admin-table td.admin-table__num {
  text-align: right;
  font-variant-numeric: tabular-nums lining-nums; white-space: nowrap;
}
.admin-table__ident { display: flex; align-items: flex-start; gap: var(--s-3); min-width: 0; }
/* Пометка состояния под названием: появляется только там, где со строкой
   что-то не так, поэтому набрана вплотную и без собственного отступа. */
.admin-table__flag { display: block; margin-top: var(--s-1); }

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

   Здесь же — липкая шапка списка. Липнет ТОЛЬКО она: admin-head,
   тулбар и тёмная строка съедали бы 120 точек рабочей высоты. top: 0 —
   над шапкой ничего липкого нет; непрозрачная подложка --bg-work
   обязательна, иначе строки просвечивают сквозь подписи; обе волосяные
   линии шапка несёт сама (см. .data-head). z-index ниже мобильного
   меню (20) и панели меню строки (30). На ≤860 шапки нет вовсе.
   У таблиц то же правило стоит на th; внутри .admin-table-wrap с его
   overflow-x: auto вертикальному прилипанию держаться не за что —
   правило там сегодня без эффекта (см. DEVIATIONS, D5). */
@media (min-width: 861px) {
  .data-row:focus-within { background: var(--surface-2); box-shadow: inset 2px 0 0 var(--brass); }
  .admin-table tbody tr:focus-within td { background: var(--surface-2); }
  .admin-table tbody tr:focus-within td:first-child { box-shadow: inset 2px 0 0 var(--brass); }
  .is-admin .data-head { position: sticky; top: 0; z-index: var(--admin-stick-z); background: var(--bg-work); }
  .is-admin .admin-table thead th { position: sticky; top: 0; z-index: var(--admin-stick-z); background: var(--bg-work); }
}

/* Просрочка. Тонкая красная кромка слева и красный срок — заметно
   мгновенно, но строка остаётся читаемой, а список не превращается
   в красное полотно, когда просрочек становится десять. Компенсирующее
   поле у первой ячейки — то же, что у строки списка ниже: без него
   кромка режет обложку, стоящую на самом краю листа. */
/* tbody в селекторе — не украшение: он уравнивает вес с правилом
   focus-within выше, и красная кромка побеждает латунную порядком,
   как и задумано. Без него фокус в просроченной строке перекрашивал
   бы кромку в латунь. */
.admin-table tbody tr.is-late td:first-child { box-shadow: inset 3px 0 0 var(--red); padding-left: var(--s-3); }
.data-row.is-late { box-shadow: inset 3px 0 0 var(--red); padding-left: var(--s-3); }

/* ------------------------------------------------------- 26.9 Миниатюра */
.thumb {
  flex: 0 0 auto; display: block;
  width: var(--admin-thumb-w); height: var(--admin-thumb-h);
  border-radius: var(--radius-xs); overflow: hidden;
  background: var(--surface-2); border: 1px solid var(--line-soft);
}
.thumb img { width: 100%; height: 100%; object-fit: cover; }
.thumb__none {
  display: flex; align-items: center; justify-content: center;
  width: 100%; height: 100%; color: var(--ink-3);
}
.thumb--lg { width: 52px; height: 78px; }

/* --------------------------------------------------- 26.10 Меню действий */
/* Шесть кнопок в строке списка — это шум и риск промахнуться. В строке
   остаётся главное действие, остальное уезжает сюда. Обычный <details>:
   работает с клавиатуры и без JavaScript. */
.row-menu { position: relative; display: inline-block; }
/* «Ещё» — второстепенное действие строки, и весит оно ровно столько же,
   сколько соседнее второстепенное действие.

   Раньше у него была рамка управляющей силы, а у стоящего рядом
   «Изменить» — подчёркнутая строчка. Один и тот же уровень рисовался
   двумя разными способами, и в столбце из полусотни строк рамка читалась
   как главное действие списка: коробка всегда тяжелее строки. Теперь
   вход в меню набран тем же приёмом, что и .btn--quiet, — подчёркнутой
   строчкой того же кегля, цвета и с тем же тоном подчёркивания. Новой
   системы компонентов для этого не понадобилось: изменился вес, а не
   механика — это по-прежнему обычный <details> без JavaScript. */
/* Вход в меню строки набран так же, как соседнее «Изменить»: без
   постоянного подчёркивания, оно приходит под курсором. Пока «Ещё» было
   подчёркнуто всегда, а «Изменить» — только под курсором, два действия
   одного столбца выглядели разными по природе: подчёркнутое читалось
   активнее, хотя оно как раз второстепенное. Разницу между ними держит
   тон: главное действие — полными чернилами, вход в меню — на ступень
   тише. Полсотни строк подряд с двумя подчёркиваниями в каждой
   превращали правый край листа в частокол. */
.row-menu__summary {
  display: inline-flex; align-items: center; justify-content: center; gap: var(--s-2);
  min-height: var(--control-h-sm); padding: 0;
  border: 0; border-radius: 0;
  background: none; color: var(--ink-2);
  font-size: var(--fs-xs); line-height: var(--lh-snug);
  text-decoration: none; text-underline-offset: .2em;
  cursor: pointer; user-select: none; list-style: none; white-space: nowrap;
  transition: color var(--t-fast), text-decoration-color var(--t-fast);
}
.row-menu__summary::-webkit-details-marker { display: none; }
.row-menu__summary::marker { content: ""; }
/* Треугольник — указатель, а не часть слова, и набран он тише подписи.
   Залитый в цвет текста, он весил столько же, сколько само «Ещё», и в
   столбце из полусотни строк читался вторым словом. Уменьшен до трёх
   точек и погашен до третьих чернил: увидеть его нужно тогда, когда
   ищешь, куда нажать, а не всё время. */
.row-menu__summary::after {
  content: ""; width: 0; height: 0; flex: 0 0 auto;
  border-left: 3px solid transparent; border-right: 3px solid transparent;
  border-top: 3px solid var(--ink-3);
  transition: transform var(--t-fast), border-top-color var(--t-fast);
}
.row-menu__summary:hover { color: var(--ink); text-decoration: underline; text-decoration-color: currentColor; }
.row-menu__summary:hover::after { border-top-color: var(--ink); }
/* Открытое меню всё-таки должно быть видно: подчёркивание темнеет вместе
   с подписью, а треугольник ниже разворачивается. Заливки и рамки для
   этого не нужно — панель под ним и так говорит, что меню раскрыто. */
.row-menu[open] > .row-menu__summary {
  color: var(--ink); text-decoration: underline; text-decoration-color: currentColor;
}
.row-menu[open] > .row-menu__summary::after { transform: rotate(180deg); border-top-color: var(--ink); }
/* Панель меню — врезка на странице, а не всплывающее окно.
   Мягкое скругление, тёплая тень и светлая рамка делали из неё виджет
   операционной системы, случайно оказавшийся на бумаге. Здесь это лист,
   положенный поверх листа: прямые углы, чёткая управляющая линия по краю
   и плотный белый фон, чтобы строки под ним не просвечивали. Тень убрана
   вовсе — глубину даёт сама линия. */
.row-menu__panel {
  position: absolute; right: 0; top: calc(100% + var(--s-1)); z-index: 30;
  min-width: 208px; padding: var(--s-2) 0;
  background: var(--white); border: 1px solid var(--line-strong);
  border-radius: 0; box-shadow: none;
  animation: reveal .18s ease-out;
}
.row-menu__panel form { display: block; }
/* Пункт меню — строка оглавления, а не кнопка со скруглением.
   Заливка под курсором рисовала внутри панели вторую коробку — плашку
   в коробке, — и меню из пяти пунктов превращалось в пять прямоугольников.
   Отклик тот же, что у строк списка под ним: пункт подаётся навстречу
   курсору и темнеет до полных чернил. Один приём на весь кабинет. */
.row-menu__item {
  display: flex; align-items: center; width: 100%;
  min-height: var(--control-h-sm); padding: 0 var(--s-4);
  background: none; border: 0; border-radius: 0;
  color: var(--ink-3); font: var(--fs-sm)/var(--lh-snug) var(--font);
  text-align: left; text-decoration: none; cursor: pointer;
  transition: transform var(--t-fast), color var(--t-fast);
}
.row-menu__item:hover {
  background: none; color: var(--ink); text-decoration: none;
  transform: translateX(var(--s-1));
}
/* Опасное — за чертой и другим цветом: случайно нажать «Удалить» вместо
   «Скрыть» не должно быть возможно одним промахом мыши. Черта проходит
   во всю ширину панели, поэтому её поля снимаются полями пункта. */
.row-menu__danger {
  margin-top: var(--s-2); padding-top: var(--s-2);
  border-top: 1px solid var(--line-soft);
}
.row-menu__item--danger { color: var(--red); }
.row-menu__item--danger:hover { background: none; color: var(--red); }

/* ---------------------------------------------- 26.11 Пустые состояния */
/* В кабинете пустой список — рабочая пауза, а не витрина. Строка текста,
   одно действие, никакой иллюстрации в половину экрана. */
.admin-empty {
  padding: var(--s-8) 0;
  border-top: 1px solid var(--line); border-bottom: 1px solid var(--line);
}
.admin-empty__title {
  font-size: var(--fs-md); font-weight: var(--fw-semibold); color: var(--ink);
  margin-bottom: var(--s-2);
}
.admin-empty__text { margin: 0; font-size: var(--fs-sm); color: var(--ink-3); max-width: 68ch; }
.admin-empty__action { margin-top: var(--s-4); display: flex; gap: var(--s-2); flex-wrap: wrap; }

/* Короткая служебная строка над списком или формой. */
.admin-notice {
  margin: 0 0 var(--s-4);
  padding: var(--s-3) var(--s-4);
  border-left: 3px solid var(--line);
  background: var(--surface-2);
  font-size: var(--fs-sm); color: var(--ink-2);
}
.admin-notice--warning { border-left-color: var(--amber); background: var(--amber-soft); color: var(--amber-ink); }
.admin-notice--danger  { border-left-color: var(--red);   background: var(--red-soft);   color: var(--red-ink); }
.admin-notice--accent  { border-left-color: var(--brass); background: var(--brass-soft); }

/* Настройки: короткие формы встают рядом.
   Каждая из них — три поля, которые заполняют раз в год; поставленные
   друг под другом, они растягивали страницу на четыре экрана при пустой
   правой половине стола. Ширина самой формы при этом не меняется —
   она задана токеном и выбрана по длине строки, а не по ширине окна. */
.settings-grid {
  /* min() обязателен: голое minmax(360px, 1fr) на узком телефоне требует
     360 точек там, где их всего 265, и страница уезжает вбок. */
  display: grid; grid-template-columns: repeat(auto-fit, minmax(min(360px, 100%), 1fr));
  gap: var(--s-10) var(--gap-grid);
  margin-bottom: var(--s-10);
}
.settings-grid > .admin-section { margin-bottom: 0; min-width: 0; }

/* ------------------------------------------------------- 26.12 Формы */
/* Длинную форму нельзя оставлять одним полотном: пятнадцать полей подряд
   читаются как анкета в поликлинике. Делим по смыслу — заголовок раздела
   и волосяная линия, без вложенных коробок. */
.form-section { margin-bottom: var(--s-10); }
.form-section__head {
  padding-bottom: var(--s-3); margin-bottom: var(--s-5);
  border-bottom: 1px solid var(--line);
}
.form-section__title {
  margin: 0; font-family: var(--font);
  font-size: var(--fs-2xs); font-weight: var(--fw-semibold);
  letter-spacing: var(--ls-caps); text-transform: uppercase; color: var(--ink-2);
}
.form-section__hint { margin: var(--s-2) 0 0; font-size: var(--fs-xs); color: var(--ink-3); max-width: 74ch; }
/* Только прямой потомок: иначе последнее поле внутри .form-grid теряло
   отступ и слипалось со следующим разделом, когда колонки схлопывались. */
.form-section > .field:last-child { margin-bottom: 0; }

.form-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 0 var(--s-5); }
.form-grid--3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
.form-grid > .field--wide { grid-column: 1 / -1; }

/* Главное действие формы всегда на виду и всегда одно. */
.form-actions {
  display: flex; align-items: center; gap: var(--s-3); flex-wrap: wrap;
  padding-top: var(--s-5); border-top: 1px solid var(--line);
}
.form-actions__hint { font-size: var(--fs-xs); color: var(--ink-3); }

/* Значение, которое кабинет только показывает. Оно намеренно не выглядит
   как поле ввода: нажимать и править тут нечего, меняется оно на сервере. */
/* Значение, которое показывают, но не меняют. Оно изображало
   выключенное поле ввода — серая плашка в пунктирной рамке, — хотя полем
   никогда не было. Это просто строка: те же чернила, что у прочитанного
   текста, и волосяная линейка снизу вместо коробки, чтобы значение всё
   же читалось значением, а не абзацем. */
.readonly-value {
  display: flex; align-items: center; gap: var(--s-2);
  min-height: var(--control-h); padding: var(--s-25) 0;
  border: 0; border-bottom: 1px solid var(--line-soft); border-radius: 0;
  background: none; color: var(--ink-2);
  font-size: var(--fs-md); overflow-wrap: anywhere;
}
.readonly-value__icon { color: var(--ink-3); display: inline-flex; flex: 0 0 auto; }

/* Опасная зона: необратимые операции отделены и подписаны. Ставить их
   рядом с «Сохранить» нельзя — промах стоит слишком дорого.

   Отделяет их теперь пауза и одна красная линейка, а не розовая коробка.
   Коробка кричала постоянно — всё время, пока человек работал со
   страницей, внизу горел цветной прямоугольник, хотя отменяют заявку
   раз в сто открытий. Опасность обязана быть слышна в тот момент, когда
   к ней подошли, а не всё время. Линейка отчёркивает низ страницы, слово
   «необратимое действие» стоит красной капителью, а кнопка набрана
   красным контуром — трёх сигналов достаточно, четвёртый был заливкой. */
.danger-zone {
  margin-top: var(--s-16); padding: var(--s-6) 0 0;
  border: 0; border-top: 1px solid var(--red);
  border-radius: 0; background: none;
}
.danger-zone__title {
  font-size: var(--fs-2xs); font-weight: var(--fw-semibold);
  letter-spacing: var(--ls-caps); text-transform: uppercase; color: var(--red-ink);
  margin-bottom: var(--s-2);
}
.danger-zone__text { margin: 0 0 var(--s-4); font-size: var(--fs-sm); color: var(--ink-2); max-width: 74ch; }
.danger-zone__actions { display: flex; gap: var(--s-2); flex-wrap: wrap; }

/* -------------------------------------------------- 26.13 Постранично */
.admin-pager {
  display: flex; align-items: center; justify-content: center; gap: var(--s-3);
  margin-top: var(--s-6); padding-top: var(--s-5); border-top: 1px solid var(--line-soft);
}
.admin-pager__info { font-size: var(--fs-sm); color: var(--ink-3); font-variant-numeric: tabular-nums; }
.admin-pager [aria-disabled="true"] { opacity: .35; pointer-events: none; }

/* --------------------------------------- 26.14 Заявка: подробная страница */
/* Правая колонка больше не 300 точек под столбик подписей.
   Пока факты стояли друг под другом, колонка занимала пять строк против
   двух у контакта слева, и вся секция вытягивалась вниз ради трёх коротких
   значений. Теперь колонка ровно по содержимому строки фактов (auto), а
   когда места на неё не хватает, факты переносятся внутри себя и колонка
   сужается сама — левая половина получает всё, что осталось. */
.order-head {
  display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: var(--gap-grid);
  align-items: start; margin-bottom: var(--s-10);
}
/* Имени здесь больше нет — оно стало заголовком страницы, а правила для
   .order-reader__name удалены вместе с разметкой: класс без разметки
   через полгода прочтут как забытый, а не как отменённый. */
.order-reader__contact { font-size: var(--fs-md); color: var(--ink-2); overflow-wrap: anywhere; }
/* Просьба читателя — выключка цитатой, а не жёлтая наклейка.
   Заливка латунью делала три строки, написанные читателем, самым цветным
   предметом страницы: на светлом столе жёлтый прямоугольник читался
   предупреждением системы, хотя это просто чужие слова. Слова и набраны
   как чужие: курсивом, за линейкой на поле — так в книге набирают
   цитату, и никакой краски для этого не нужно. */
.order-reader__message {
  margin-top: var(--s-4); padding: 0 0 0 var(--s-3);
  border-left: 1px solid var(--ink); background: none;
  font-style: italic;
  font-size: var(--fs-sm); color: var(--ink-2);
}
/* Выходные данные заявки — строка, а не столбец.
   Три коротких значения, выстроенные друг под другом, читались списком
   из пяти строк и держали высоту всей секции; поставленные в ряд, они
   занимают два этажа — подпись и значение — и встают вровень с контактом
   слева. Это ровно тот же приём, что у строки состояния фонда на панели:
   справка о предмете набирается одной строкой, а не таблицей.

   По вертикали внутри пары ничего не меняется: dt и dd — блочные, подпись
   капителью стоит над значением. В ряд встают только сами пары. */
.order-facts {
  margin: 0;
  display: flex; flex-wrap: wrap; align-items: flex-start;
  gap: var(--s-4) var(--s-8);
}
/* Пара не сжимается и не рвётся посередине: при нехватке места она
   целиком уходит на следующий этаж. Без этого «14 августа 2026, 03:18»
   ломалось надвое, и одна дата читалась двумя разными. */
.order-facts > div { flex: 0 0 auto; }
/* Последняя пара — числовая, и число стоит по оси своей подписи.
   Ширину паре задаёт её самая длинная строка, а это подпись: под
   «КНИГ В ЗАЯВКЕ» однозначное число прижималось то к началу, то к концу
   и в обоих случаях висело сбоку от того, к чему относится. По центру
   оно читается вместе с подписью как один предмет — и выглядит одинаково
   и когда пара стоит последней в строке, и когда она осталась одна на
   своём этаже после переноса. */
.order-facts > div:last-child { text-align: center; }
.order-facts dt {
  font-size: var(--fs-2xs); font-weight: var(--fw-semibold);
  letter-spacing: var(--ls-caps); text-transform: uppercase; color: var(--ink-3);
  white-space: nowrap;
}
.order-facts dd {
  margin: var(--s-1) 0 0; font-size: var(--fs-sm); color: var(--ink);
  white-space: nowrap;
}

/* Строка книги внутри заявки.

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

   По середине они уравновешивают обложку, и строка книги в заявке
   начинает вести себя ровно так же, как строка книги в «Книгах»
   (.data-row выше по файлу): один и тот же предмет на двух экранах
   не должен выравниваться по-разному. */
.item-list { border-top: 1px solid var(--line); }
.item-row {
  display: grid; grid-template-columns: minmax(0, 1fr) 150px 230px;
  gap: var(--s-3) var(--s-4); align-items: center;
  padding: var(--s-4) 0; border-bottom: 1px solid var(--line-soft);
}
.item-row:last-child { border-bottom: 1px solid var(--line); }
.item-row__ident { display: flex; gap: var(--s-3); min-width: 0; }
.item-row__main { min-width: 0; }
.item-row__title {
  font-family: var(--font-display); font-size: var(--fs-md); line-height: var(--lh-tight);
  overflow-wrap: anywhere;
}
.item-row__title a { color: var(--ink); }
.item-row__title a:hover { color: var(--brass); text-decoration: none; }
.item-row__meta { margin-top: var(--s-2); font-size: var(--fs-xs); color: var(--ink-3); }
.item-row__meta b { color: var(--ink-2); font-variant-numeric: tabular-nums; }
.item-row__note { margin: var(--s-2) 0 0; font-size: var(--fs-xs); color: var(--ink-3); }
.item-row__actions { display: flex; gap: var(--s-2); flex-wrap: wrap; justify-content: flex-end; }
/* Два действия строки — две разные вещи, и набраны они по-разному.
   «Выдать» — контур капителью: это то, ради чего строку читают. «Снять
   бронь» остаётся подчёркнутой строчкой: отказ не должен выглядеть
   равным по весу с выдачей. Капитель здесь мельче, чем у действия шага
   наверху (11 против 13): строка книги — не страница. */
.item-row__actions .btn--ghost {
  border-radius: 0; border-color: var(--ink);
  color: var(--ink);
  font-size: var(--fs-2xs); font-weight: var(--fw-semibold);
  letter-spacing: var(--ls-caps); text-transform: uppercase;
}
.item-row__actions .btn--ghost:hover {
  background: var(--ink); border-color: var(--ink); color: var(--bg);
}
.item-row__actions .issue-box[open] > summary.btn--ghost {
  background: var(--ink); border-color: var(--ink); color: var(--bg);
}

/* -------------------------------------------------- 26.15 Мастер импорта */
/* Мастер — это последовательность, а не форма. Шаги подписаны и видно,
   на каком сейчас находишься. */
.steps-bar {
  display: flex; gap: var(--s-2); flex-wrap: wrap;
  padding: 0 0 var(--s-4); margin-bottom: var(--s-6);
  border-bottom: 1px solid var(--line);
  list-style: none;
}
/* Шаги мастера — оглавление, а не ряд таблеток.
   Белые пилюли с обводкой и залитыми кружками номеров были последним
   местом кабинета, где интерфейс изображал сам себя: четыре коробки
   в ряд ради четырёх слов. Осталось то же, чем набрано оглавление
   в книге: номер шага цифрой, название словом, текущий — в полный тон
   и с линейкой под ним. Ни заливки, ни рамки, ни кружка. */
.steps-bar__item {
  display: flex; align-items: baseline; gap: var(--s-2);
  padding: var(--s-1) 0;
  border: 0; border-bottom: 1px solid transparent; border-radius: 0;
  font-size: var(--fs-sm); color: var(--ink-3); background: none;
}
.steps-bar__num {
  display: inline-flex; flex: 0 0 auto;
  background: none; color: var(--ink-3);
  font-size: var(--fs-2xs); font-weight: var(--fw-semibold);
  font-variant-numeric: tabular-nums;
}
.steps-bar__item.is-current {
  color: var(--ink); font-weight: var(--fw-semibold);
  border-bottom-color: var(--ink);
}
.steps-bar__item.is-current .steps-bar__num { background: none; color: var(--ink); }
.steps-bar__item.is-done { color: var(--green-ink); }
.steps-bar__item.is-done .steps-bar__num { background: none; color: var(--green-ink); }

/* Три способа начать импорт — это не три предложения на выбор, а один
   основной путь и два запасных. Три одинаковые белые коробки в ряд
   говорили обратное: равный вес, равный размер, равная рамка — реклама,
   а не рабочий экран. Да ещё и коробка в коробке: путь к папке лежал в
   собственной рамке внутри первой карточки.

   Теперь это ступени. Основной путь идёт первым и во всю меру стола —
   ему и достаётся длинная строка пути. Два запасных стоят ниже, рядом,
   набраны тише и отделены одной волосяной линией. Коробок не осталось
   ни одной: разделяют расстояние, линия и вес текста, как везде в
   кабинете. Ни один адрес, ни одна форма, ни одно действие не изменились. */
.import-source { display: block; }

/* Основной путь: папка на этом компьютере. */
.import-way { padding: var(--s-5) 0 var(--s-6); }
.import-way__title {
  margin: 0 0 var(--s-2); font-family: var(--font);
  font-size: var(--fs-md); font-weight: var(--fw-semibold); letter-spacing: 0;
}
.import-way__text {
  margin: 0 0 var(--s-4); font-size: var(--fs-sm); color: var(--ink-3);
  max-width: 78ch;
}
/* Два запасных пути стоят рядом и оба тише основного. Их ровно два —
   auto-fit здесь незачем, он к тому же оставлял третью схлопнутую
   колонку. Одна волосяная линия отделяет запасное от основного. */
.import-alt {
  display: grid; grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--s-6) var(--gap-grid);
  padding-top: var(--s-6); border-top: 1px solid var(--line);
}
.import-alt > * { min-width: 0; }
.import-alt .import-way { padding: 0; border-bottom: 0; }
.import-alt .import-way__title { font-size: var(--fs-sm); color: var(--ink-2); }
.import-alt .import-way__text { font-size: var(--fs-xs); }
/* Поле выбора файлов умеет быть уже своей встроенной подписи. */
.import-source .input[type="file"] { max-width: 100%; }

/* ------------------------------------------------ 26.16 Владелец системы */
/* Владелец отличается спокойно: подпись рядом с именем и мягкая линия
   слева. Никакого золотого баннера — это не награда, а ограничение. */
.owner-mark {
  display: inline-flex; align-items: center; gap: var(--s-2);
  font-size: var(--fs-2xs); font-weight: var(--fw-semibold);
  letter-spacing: var(--ls-caps); text-transform: uppercase; color: var(--brass);
  white-space: nowrap;
}
/* Кромка владельца ничего не сдвигает.
   Отступ под неё был только у самой строки владельца — и весь список
   вставал двумя разными вертикалями: строка владельца начиналась на
   двенадцать точек правее остальных, а за ней съезжали и столбцы роли
   и последнего входа. В таблице это читается как сбитая сетка, хотя
   виновата одна кромка. Отступ получают все строки списка сразу, кромку
   рисует только владелец — ровно тот же приём, что у просроченной строки
   на панели. */
.data-list--users .data-row { padding-left: var(--s-3); }
.data-row--owner { box-shadow: inset 3px 0 0 var(--brass-line); }
.you-mark { font-size: var(--fs-2xs); color: var(--ink-3); font-weight: var(--fw-normal); }

/* ------------------------------------------------- 26.17 Мелкие детали */

/* Адрес почты и логин: моноширинный, чтобы точки и дефисы не сливались. */
.data-row__mono { font-family: var(--font-mono); font-size: var(--fs-2xs); }
/* Объяснение вместо кнопок — там, где действий нет и быть не должно. */
/* Объяснение вместо кнопок. Выключка вправо годится одному слову, но
   здесь стоят две-три строки связного текста: рваный левый край читается
   заметно медленнее, а прочесть это нужно один раз и до конца. Поэтому
   текст выключен влево, а к правому краю прижат сам блок. */
.data-row__locked {
  font-size: var(--fs-xs); color: var(--ink-3); line-height: var(--lh-snug);
  max-width: 30ch;
}
.row-menu__form { padding: var(--s-2) var(--s-3) var(--s-3); }
.row-menu__form .field__label { margin-bottom: var(--s-1); }
.row-menu__form .select { min-height: var(--control-h-sm); font-size: var(--fs-sm); }

/* Список «подпись → значение» для служебных сведений. */
.facts { margin: 0; border-top: 1px solid var(--line); }
.facts__row {
  display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1.4fr);
  gap: var(--s-3) var(--s-6);
  padding: var(--s-3) 0; border-bottom: 1px solid var(--line-soft);
}
.facts dt { font-size: var(--fs-sm); color: var(--ink-2); }
.facts dd {
  margin: 0; font-size: var(--fs-sm); color: var(--ink);
  font-family: var(--font-mono); overflow-wrap: anywhere;
}

/* Форма шире узкой, но не во всю ширину стола: два поля в ряд читаются
   лучше, чем восемь. */
.form-narrow-wide { max-width: 760px; }
.category-form__name  { flex: 1 1 280px; }
.category-form__order { flex: 0 0 auto; }

/* ------------------------------------------------- 26.19 Список разделов
   Справочник из двух десятков строк, и в каждой — поле ввода, «Сохранить»,
   два числа, состояние и «Скрыть». Больше всего шумели именно поля: два
   десятка белых прямоугольников подряд оказывались самым ярким объектом
   страницы, хотя переименовывают раздел раз в год.

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

   Убавлен вес покоя. Прямоугольник из четырёх линий — это коробка, и два
   десятка коробок подряд читаются как сетка, даже когда линии почти не
   видны. В покое от коробки остаётся одна нижняя черта: она говорит
   «сюда можно писать» тем же способом, каким это говорит бумажный бланк,
   и не рисует на странице ни одной вертикали. Коробка возвращается целиком
   ровно тогда, когда с полем начинают работать, — при наведении и в фокусе,
   вместе с белой заливкой.

   Текст в покое стоит на левом краю столбца, ровно под подписью «Название»:
   собственное поле поля вынесено в отрицательный отступ, поэтому при
   наведении заливка расходится наружу, а буквы не сдвигаются ни на точку.
   Прыжка текста под курсором в рабочем списке быть не должно. */
.category-row__name {
  background: none;
  border-color: transparent;
  border-bottom-color: var(--line);
  border-radius: 0;
  padding-left: var(--s-2); padding-right: var(--s-2);
  margin-left: calc(-1 * var(--s-2));
  transition: border-color var(--t-fast);
}
/* Ни наведение, ни фокус не меняют геометрию поля — темнеет только его
   линейка. Прежде поле обрастало рамкой со всех сторон, заливкой и
   скруглением, то есть на глазах превращалось из строки набора в
   коробку: в таблице из двух десятков разделов это читалось так, будто
   строка под курсором поехала. О фокусе здесь говорит черта, а не рамка:
   строка списка — не отдельный орган управления, а ячейка таблицы. */
.category-row__name:hover {
  border-color: transparent; border-bottom-color: var(--ink-3);
}
/* В фокусе гасим и рамку, и кольцо. Общее правило полей (.input:focus)
   красит латунью все четыре стороны и добавляет вокруг трёхточечное
   кольцо box-shadow — на обычном поле в форме это верно, а здесь именно
   оно и рисовало вокруг названия ту самую коробку посреди таблицы.
   Гасить приходится явно: у правил одинаковый вес, и выигрывает наше
   только порядком в файле — на такое опираться нельзя. */
.category-row__name:focus {
  border-color: transparent; border-bottom-color: var(--ink);
  box-shadow: none; outline: 0;
}


/* ------------------------------------------------------ 26.18 Экземпляры */
.copy-list { border-top: 1px solid var(--line); }
.copy-item {
  display: grid; grid-template-columns: minmax(0, 1fr) 170px 190px;
  gap: var(--s-3) var(--s-4); align-items: center;
  padding: var(--s-3) 0; border-bottom: 1px solid var(--line-soft);
}
.copy-item:last-child { border-bottom: 1px solid var(--line); }
.copy-item__form { display: flex; gap: var(--s-2); align-items: center; flex-wrap: wrap; }
.copy-item__no { flex: 0 1 170px; }
.copy-item__note { flex: 1 1 220px; min-width: 0; }
.copy-item__form .input { min-height: var(--control-h-sm); font-size: var(--fs-sm); }
.copy-item__state { font-size: var(--fs-xs); color: var(--ink-3); }
.copy-item__actions { display: flex; gap: var(--s-2); justify-content: flex-end; flex-wrap: wrap; }

/* =====================================================================
   27. Адаптив. Все медиазапросы собраны здесь, в конце файла:
   так телефонная раскладка гарантированно перекрывает настольную.
   Контрольные ширины: 320 / 360 / 390 / 430 (телефоны), 768 (планшет),
   1024 и 1440 (ноутбуки).

   Ступень 1024 объявлена один раз на обе половины продукта: правила
   кабинета стоят внутри неё отдельной частью, слово в слово прежние.
   Их селекторы с витринными не пересекаются, и ни один из них не
   переопределяется между старым и новым местом, поэтому порядок
   сложения стилей не изменился ни для одного элемента.

   Ступени 860 и 520 остались объявлены дважды — и должны остаться.
   Кабинет переопределяет в них собственные селекторы (.metrics,
   .copy-item, .item-row), объявленные выше по файлу на более широких
   ступенях, и выигрывает он только потому, что стоит позже. Перенос
   этих блоков в раздел 27 перевернул бы порядок и молча поменял бы
   раскладку рабочих списков на телефоне.
   ===================================================================== */

/* --- до 1024 px: две колонки уступают одной там, где вторая узкая --- */
@media (max-width: 1024px) {
  .checkout { grid-template-columns: 1fr; }
  /* Состав заявки встаёт ПЕРЕД формой. Пока колонки стояли рядом, список
     был виден одновременно с полями; в один столбец он уезжал под кнопку
     отправки, и человек заполнял имя и контакт, не видя, что именно он
     просит. Разделяет колонки теперь линия снизу, а не слева: сбоку от
     блока во всю ширину она стояла бы ни к чему.
     Порядок в разметке остаётся настольным — форма первая. Программе
     чтения с экрана состав по-прежнему доступен своим заголовком. */
  .checkout__books {
    order: -1;
    padding-left: 0; border-left: 0;
    padding-bottom: var(--s-6); border-bottom: 1px solid var(--line);
  }
  .book-form__grid { grid-template-columns: 260px 1fr; }
  .wizard { grid-template-columns: 240px 1fr; }

  /* Кабинет: двухколоночные экраны — сводка, экземпляры — становятся
     одноколоночными. Шапка заявки складывается раньше остальных, на
     ступени 1280: её правая половина шире прочих (см. .order-head там). */
  .dash-grid { grid-template-columns: 1fr; gap: var(--s-10); }
  .copy-item { grid-template-columns: minmax(0, 1fr) 150px; }
  .copy-item__actions { grid-column: 1 / -1; justify-content: flex-start;
                        padding-top: var(--s-2); border-top: 1px solid var(--line-soft); }
}

/* --- до 860 px: планшет и телефон --- */
@media (max-width: 860px) {
  h1 { font-size: var(--fs-h2); }
  h2 { font-size: var(--fs-h3); }
  .apparatus { padding: var(--s-8) 0 0; margin-bottom: var(--s-8); }
  .apparatus__title { font-size: var(--fs-h1); max-width: none; }
  .apparatus__head { margin-bottom: var(--s-5); }
  .search { max-width: none; }
  .section { margin-bottom: var(--s-12); }
  .section__head { margin-bottom: var(--s-6); }
  /* Четыре шага в строку на планшете не читаются: становятся 2×2.
     Направляющая при этом снимается: она тянулась бы через один ряд
     и обрывалась над вторым — путь читался бы только наполовину.
     Здесь порядок держат сами номера. */
  .steps { grid-template-columns: repeat(2, minmax(0, 1fr)); column-gap: var(--s-5); row-gap: var(--s-8); }
  .steps::before { display: none; }
  /* Номер на ступень мельче, и ступень отступа под ним не нужна:
     в колонке 171 точка она отняла бы у подписи целое слово. */
  .step__num { font-size: var(--fs-h1); margin-bottom: var(--s-3); padding-right: var(--s-3); }
  .step__title, .step__note { padding-left: 0; }
  .step__title { font-size: var(--fs-md); }
  .shelf-more { margin-top: var(--s-8); }
  .shelf-more .btn { width: 100%; }
  .site-main { padding: var(--s-6) 0 var(--s-12); }
  .card { padding: var(--s-5); border-radius: var(--radius); }

  /* Подвал ужимается вместе со страницей. Настольная мера — сорок точек
     отбивки, сорок своего поля, поверх девяноста шести, которыми кончается
     содержимое, — считана для широкого листа. На телефон переехало только
     поле страницы (96 → 64), а обе сороковки остались настольными, и над
     линией повисало 104 точки пустоты при 375 в ширину.

     Пересчитываем всю тройку на телефонную шкалу: 48 + 24 до линии и 32
     после неё. Пропорция настольного разворота сохранена, просто взята в
     масштабе экрана, который держат в руке. */
  .site-footer {
    margin-top: var(--s-6);
    padding-top: var(--s-8);
    padding-bottom: var(--s-8);
    padding-bottom: calc(var(--s-8) + env(safe-area-inset-bottom));
  }

  /* flex: 0 0 auto — не украшение. Кнопка меню стоит последней в гибкой
     строке шапки, и на 320 px строка сжимала именно её: 44 объявленных
     превращались в 39 настоящих, а квадрат — в прямоугольник. Ширина
     цели — не то, чем строка платит за тесноту. */
  .nav-toggle { display: block; flex: 0 0 auto; }
  .site-nav {
    display: none;
    position: absolute; left: 0; right: 0; top: 100%;
    flex-direction: column; align-items: stretch; gap: 0;
    padding: var(--s-2) var(--s-5) var(--s-5);
    background: var(--bg); border-bottom: 1px solid var(--line);
    box-shadow: var(--shadow-md); z-index: 60;
  }
  .site-nav.is-open { display: flex; animation: reveal .18s ease-out; }
  .site-nav__link {
    min-height: var(--control-h); padding: 0 var(--s-2); font-size: var(--fs-xs);
    border-bottom: 0; border-top: 1px solid var(--line-soft);
  }
  .site-nav__link:first-child { border-top: 0; }
  .site-nav__link:hover { border-bottom: 0; }
  /* Корзина остаётся в строке шапки, когда меню сворачивается: она стоит
     рядом с меню, а не в нём. Прижимаем её вправо — раскрытое меню лежит
     ниже, вне потока, и прижимать больше нечему. Подпись прячем, знак и
     счётчик остаются: строка шапки на 320 px и так тесная. */
  .cart-button {
    margin-left: auto; padding: 0 var(--s-4);
    min-height: var(--control-h);   /* 36 мимо пальца: это вход в заявку */
  }
  .cart-button__text { display: none; }
  .site-header__inner { position: relative; min-height: 68px; }
  /* Имя библиотеки — тоже ссылка, и на телефоне она была целью в 19 точек
     высотой: попасть в неё пальцем можно только прицелившись. Коробку
     поднимаем до общей высоты элемента управления, буквы остаются на
     месте — строка шапки и так выровнена по центру. */
  .brand { min-height: var(--control-h); align-items: center; }

  .book-form__grid { grid-template-columns: 1fr; }
  .dropzone { min-height: 200px; }
  .dropzone__preview img { max-height: 240px; }

  /* Панель каталога: поиск — строкой, фильтры — в раскрывающемся блоке */
  .toolbar { padding: var(--s-4) 0; gap: var(--s-2); }
  /* Было .toolbar__search — класса с таким именем в разметке нет и не
     было: строка поиска называется .search (см. catalog.html). Правило
     не срабатывало никогда, поэтому на телефоне поиск оставался с
     настольной основой в 320 точек. */
  .toolbar .search { flex: 1 1 100%; }
  .toolbar__sort { flex: 1 1 100%; }
  .toolbar > .btn { flex: 1 1 100%; }
  /* Переключатель наличия — цель для пальца, а не для курсора: сам
     тумблер ростом 20 точек, поэтому высоту набирает строка целиком.
     Рисунок при этом не растёт — растёт область нажатия. */
  .switch { min-height: var(--control-h); }
  /* В свёрнутом блоке фильтров строка идёт во всю ширину, и прижимать
     переключатель вправо больше не к чему. */
  .toolbar__filters-body .switch { margin-left: 0; }

  .toolbar__filters { display: block; flex: 1 1 100%; }
  .toolbar__filters > summary {
    display: inline-flex; align-items: center; justify-content: center; gap: var(--s-2);
    width: 100%; min-height: var(--control-h); padding: 0 var(--s-4);
    background: var(--surface); color: var(--ink-2);
    border: 1px solid var(--line-strong); border-radius: var(--radius-sm);
    font-size: var(--fs-md); font-weight: var(--fw-semibold);
    cursor: pointer; list-style: none; user-select: none;
    transition: border-color var(--t-fast), color var(--t-fast);
  }
  .toolbar__filters > summary::-webkit-details-marker { display: none; }
  .toolbar__filters > summary::marker { content: ""; }
  .toolbar__filters > summary:hover { border-color: var(--ink-3); color: var(--ink); }
  .toolbar__filters[open] > summary { border-color: var(--accent); color: var(--accent); }
  .toolbar__filters[open] > summary .toolbar__chevron { transform: rotate(180deg); }
  .toolbar__chevron { transition: transform var(--t); }
  .toolbar__count {
    display: inline-flex; align-items: center; justify-content: center;
    min-width: 20px; height: 20px; padding: 0 var(--s-1);
    background: var(--accent); color: var(--white);
    border-radius: var(--radius-pill); font-size: var(--fs-2xs);
  }
  .toolbar__filters-body {
    display: flex; flex-direction: column; align-items: stretch; gap: var(--s-2);
    padding-top: var(--s-2);
    animation: reveal .18s ease-out;
  }
  /* В колонке flex-basis задаёт высоту — сбрасываем настольное значение,
     иначе списки вытягиваются на 170 px в высоту. */
  .toolbar__filters-body .toolbar__sort { flex: 0 0 auto; }

  /* Читательская часть */
  .book-facts { gap: var(--s-3) var(--s-6); }

  /* --- Цели в 44 точки: всё, до чего дотрагиваются пальцем ---
     Ступень выбрана 860, а не 520: планшет — такое же устройство с
     сенсором, как телефон, просто экран больше. Раньше эти правила жили
     в узких медиазапросах, и на 768 палец промахивался ровно там же, где
     промахивался бы на 375: раздел указателя оставался 34 точки, «В
     корзину» и «Убрать» — 36 и 39. Ширина экрана не делает палец точнее.

     Здесь только высота цели. Всё, что относится к раскладке телефона —
     действие во всю ширину, прокручиваемая строка разделов, — осталось
     в своих ступенях ниже. */
  .index__link { min-height: var(--control-h); align-items: center; }
  .shelf-card__foot { min-height: var(--control-h); }
  .shelf-card__action { min-height: var(--control-h); align-items: center; }
  .pager__link { min-height: var(--control-h); }
  /* Пауза в 48 точек между стрелками и счётчиком — мера широкого листа;
     на телефоне она развела бы строку почти по краям экрана. */
  .pager { gap: var(--s-6); margin-top: var(--s-10); }
  .book-page__back { min-height: var(--control-h); }
  .cart-item__remove .btn,
  .cart-footer__clear { min-height: var(--control-h); }

  /* Мелкие текстовые ссылки: цель растёт, раскладка — нет.
     Крошки, раздел над названием книги, «весь раздел» и вход для
     библиотекаря набраны мелкой капителью — это 13–18 точек высоты,
     то есть вдвое меньше пальца. Поднимать строку нельзя: она стоит
     в ряду с другим текстом и потянет за собой всю вертикаль страницы.
     Поэтому цель расширяет прозрачный слой поверх ссылки: площадь
     нажатия — 44 точки, высота строки — прежняя, до точки. */
  .crumbs__link,
  .book-page__category,
  .book-facts dd a,
  .section__aside a,
  .site-footer__meta a { position: relative; }
  .crumbs__link::after,
  .book-page__category::after,
  .book-facts dd a::after,
  .section__aside a::after,
  .site-footer__meta a::after {
    content: ""; position: absolute; left: 0; right: 0;
    top: 50%; height: var(--control-h); transform: translateY(-50%);
  }

  /* Заявки */
  .thanks { padding: var(--s-8) var(--s-5); }
  .thanks__title { font-size: var(--fs-h2); }
  .thanks__text { font-size: var(--fs-base); }
  .thanks__code { padding: var(--s-3) var(--s-5); display: block; }
  .thanks__code-value { font-size: var(--fs-h1); }
  .order-book { flex-wrap: wrap; }
  .order-book__state { flex: 1 1 100%; text-align: left; max-width: none; }

  .issue-box, .issue-box > summary { display: block; width: 100%; }
  .issue-box__form { min-width: 0; }
  .issue-box__row { flex-direction: column; align-items: stretch; }
  .issue-box__row > div { width: 100%; }

  /* Мастер импорта: очередь сверху сворачивается, картинка и форма — ниже */
  .wizard { grid-template-columns: 1fr; }
  .queue-list { max-height: 320px; }
  .wizard__image { min-height: 220px; max-height: 45vh; }
  .wizard__image img { max-height: 40vh; }
  .wizard__toolbar > *, .wizard__toolbar form { flex: 1 1 calc(50% - var(--s-1)); }
  .wizard__toolbar .btn { width: 100%; }
}

/* --- до 520 px: узкие телефоны (390 и 430 px) --- */
@media (max-width: 520px) {
  .container { padding-left: var(--s-4); padding-right: var(--s-4); }
  /* Знак опускается ростом: 44 в телефонную строку не помещаются.
     Замер на 375 (самый распространённый узкий экран): полоса 343,
     корзина 60, кнопка меню 44, два промежутка по 12 — знаку остаётся
     215. Знак ростом 34 занимает 201: четырнадцать запасных, которые
     переживают любой счётчик в корзине. 44 потребовали бы 260, 36 —
     213 и оставили бы две точки, то есть ничего.

     Промежутки строки и поля корзины убавлены здесь же и ради того же:
     с настольными 24 и 16 знаку осталось бы 191 — меньше нужного.
     Высота корзины при этом не трогается: она цель для пальца. */
  .site-header__inner { gap: var(--s-3); }
  .cart-button { padding: 0 var(--s-3); }
  .brand__logo { height: var(--brand-h-sm); }
  .btn-row .btn { flex: 1 1 100%; }

  /* Аппарат на телефоне: титул на ступень мельче, поиск во всю ширину.
     Кнопка «Найти» остаётся внутри строки поиска — разворачивать её
     в отдельную полосу больше не нужно: строка и так во всю ширину,
     а лишняя полоса отодвигала бы книги. */
  .apparatus__title { font-size: var(--fs-h2); }

  /* Поиск на телефоне ужимает собственные поля, а не подсказку.
     Настольные 16 / 12 / 24 точки отбирали у строки ввода 28 из 155 —
     и «Название или автор» не помещалось на экране в 320 точек, хотя
     самой фразе места хватало. Уменьшаем поля, а не кегль: подсказка
     набрана тем же 17-м, что и запрос, который человек в неё впишет. */
  .search__icon { padding-left: var(--s-3); }
  .search__field { padding: 0 var(--s-2); }
  .search__submit { padding: 0 var(--s-4); }

  /* Раздел в прокручиваемой строке не сжимается: без этого «Биографии
     и свидетельства» отдали бы место соседям и подпись переносилась бы
     посреди строки. Высоту цели задаёт общее правило сенсорной ступени. */
  .index__link { flex: 0 0 auto; }

  /* Разделы на телефоне — одна прокручиваемая строка, а не стена.
     В столбик семь разделов занимали 356 px на 320-м экране и
     отодвигали книги почти на полтора экрана. Прокрутка здесь
     внутренняя: документ по-прежнему ровно по ширине экрана, а первым
     стоит «Все книги» — самый нужный пункт. Поля по краям оставлены
     под кольцо фокуса, иначе контейнер срезал бы его у крайних. */
  .index {
    flex-wrap: nowrap; overflow-x: auto; -webkit-overflow-scrolling: touch;
    padding: var(--s-05) var(--s-1);
    margin-left: calc(-1 * var(--s-1)); margin-right: calc(-1 * var(--s-1));
    scroll-padding-left: var(--s-1);
  }

  /* Правый край уходит в бумагу. Замер на 375: видно 351 точку, разделов
     на 1367 — тысяча с лишним спрятана справа, и до последнего, «Разное»,
     читатель не добирался вовсе. Строка обрывалась посреди слова, и обрыв
     читался как ошибка вёрстки, а не как приглашение листать.

     Затенение превращает обрыв в намерение: у полосы появляется край,
     и за край хочется заглянуть. Прокрутка при этом та же, что была.

     Маска считает не цвет, а непрозрачность, поэтому плотная часть взята
     currentColor: любой непрозрачный цвет здесь равен «показать целиком»,
     и заводить ради этого отдельное значение незачем. */
  .index {
    -webkit-mask-image: linear-gradient(to right, currentColor calc(100% - 2.5rem), transparent);
            mask-image: linear-gradient(to right, currentColor calc(100% - 2.5rem), transparent);
  }
  /* Дочитано до конца — прятать больше нечего, и затенение снимается:
     край, за которым ничего нет, обманывал бы. Класс ставит скрипт;
     если скрипт не отработал, остаётся постоянное затенение — это тоже
     правда, просто сказанная грубее. */
  .index.is-index-end {
    -webkit-mask-image: none;
            mask-image: none;
  }
  /* Шаги остаются 2×2 (правило выше): в один столбец инструкция вырастала
     до 445 px и отодвигала полку почти на полтора экрана. Ряды сходятся
     ближе — на телефоне высота дороже воздуха. */
  /* Столбцы сходятся, ряды расходятся — и вот почему.

     Внутри шага расстояния мелкие: от цифры до заголовка 12, от
     заголовка до подписи 4. Между рядами стояло 24 — всего вдвое
     больше внутреннего. Этого не хватает, чтобы глаз собрал четыре
     блока в сетку 2×2: группировка получалась спорной, и полоса
     читалась как сбившийся столбик, а не как ровные четыре шага.

     Сорок точек между рядами дают троекратный перевес над самым
     крупным внутренним расстоянием — при цифрах кеглем 32 это тот
     минимум, за которым ряды наконец разъезжаются по своим местам.
     Прибавка к высоте страницы — 32 точки, и только на главной:
     на других страницах полосы больше нет. */
  .steps { column-gap: var(--s-4); row-gap: var(--s-10); }
  /* Пауза под заголовком тоже: 64 точки воздуха — мера широкого листа,
     на телефоне она вытолкнула бы полосу за пределы экрана. */
  .steps__label { margin-bottom: var(--s-6); }

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

     Разворот column-reverse, стоявший здесь прежде, снят: в колофоне
     год идёт перед служебной строкой, потому что вход — действие, а
     действие в концовке стоит последним. */
  .site-footer__meta {
    flex-direction: column; align-items: center; justify-content: center;
    gap: var(--s-4); text-align: center;
  }

  /* Пустое состояние на телефоне — не уменьшенный настольный блок:
     воздух вокруг убавлен, заголовок опускается на ступень, а действие
     занимает всю ширину колонки. Линейки остаются — они и делают блок
     блоком. */
  .empty { padding: var(--s-8) 0; }
  .cart-empty { padding: var(--s-8) 0; }
  .empty__title, .cart-empty__title { font-size: var(--fs-h3); }
  .empty .btn,
  .cart-empty .btn { width: 100%; }
  .auth-screen { margin: var(--s-6) auto; }
  .auth-screen .card { padding: var(--s-5); }

  /* Корзина на телефоне. Обложка и текст остаются рядом — строка не
     превращается в высокую карточку: раньше «Убрать» уезжала на
     отдельную строку во всю ширину и одна книга занимала 388 px. */
  .cart-item { gap: var(--s-3); }
  .cart-item__cover { flex-basis: 64px; width: 64px; }
  /* Наличие и «Убрать» стоят в одной строке, пока помещаются. Жёсткий
     nowrap здесь ставить нельзя: на 320 px длинная подпись наличия
     («сейчас недоступна») вместе с кнопкой не влезает в узкую колонку
     и уводила всю страницу вбок на шесть точек. Перенос безопасен —
     кнопка не растягивается и остаётся своей ширины. */
  .cart-item__foot { gap: var(--s-2); }
  .cart-item__remove { flex: 0 0 auto; }

  /* Главное действие первым и во всю ширину, «Очистить» — последним. */
  .cart-footer { flex-direction: column; align-items: stretch; gap: var(--s-3); }
  .cart-footer__actions { flex-direction: column-reverse; align-items: stretch; gap: var(--s-2); }
  .cart-footer__actions .btn { width: 100%; }
  .cart-footer > form { order: 2; }
}

/* --- Уважаем системную настройку «меньше движения» --- */
/* =====================================================================
   29. Читательские страницы: каталог и книга по ширинам экрана

   Ступени выбраны после реального просмотра на 1440 / 1280 / 1024 / 768 / 390:
     ≥1280  — пять колонок, обложка ≈210 px: размер книги на полке;
     ≤1279  — четыре колонки;
     ≤899   — три колонки;
     ≤599   — две колонки, книга по-прежнему стоит вертикально.

   Про телефон отдельно, потому что решение здесь поменялось. Раньше на
   узком экране объект ложился набок: маленькая обложка слева, текст
   справа. Тогда это было верно — сравнивали с ОДНОЙ книгой во всю
   ширину, при которой на экран не помещалось и одной целиком.

   Теперь сравнение другое: две колонки. Обложка получается около 170 px
   — это по-прежнему книга, а не миниатюра; на экран помещаются четыре
   объекта, столько же, сколько давала лежачая строка; и главное — полка
   остаётся полкой на всех ширинах, а не превращается в ленту карточек
   с картинкой сбоку. Метафора «книги стоят на полке» не должна ломаться
   ровно там, где продукт читают чаще всего.
   ===================================================================== */

@media (max-width: 1279px) {
  .shelf-grid,
  .shelf-grid--compact { grid-template-columns: repeat(4, minmax(0, 1fr)); }
  .shelf-grid--compact > .shelf-card:nth-child(n + 5) { display: none; }
  .book-page { grid-template-columns: 280px minmax(0, 1fr); gap: var(--s-12); }
}

@media (max-width: 899px) {
  .shelf-grid,
  .shelf-grid--compact { grid-template-columns: repeat(3, minmax(0, 1fr)); gap: var(--s-12) var(--s-6); }
  .shelf-grid--compact > .shelf-card:nth-child(n + 4) { display: none; }

  /* Страница книги перестаёт быть разворотом: обложка сверху, дальше
     всё в один столбец сверху вниз. Две колонки на планшете сжимали бы
     и обложку, и строку описания. */
  .book-page {
    grid-template-columns: 1fr; gap: var(--s-8);
    padding: 0 0 var(--s-12);
  }
  .book-page__cover { justify-content: flex-start; }
  .book-page__cover img, .book-page__nocover { max-width: 240px; }
  /* По той же причине складываются и два столбца ниже: паспорт книги
     в трети планшетной ширины начал бы переносить «Издательство» и
     «Год издания» по слогам, а описание слева потеряло бы строку.
     Расстояние между секциями держит тот же вертикальный gap сетки —
     ритм листа при этом не меняется. */
  .book-columns { grid-template-columns: 1fr; }
  .book-page__title { font-size: var(--fs-h1); }
  .book-page__author { font-size: var(--fs-lg); margin-bottom: var(--s-6); }
  .book-page__description { font-size: var(--fs-base); }
  .book-facts { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--s-6); }
  .book-section { padding-top: var(--s-10); }
  .book-section + .book-section { margin-top: var(--s-10); }
  .book { margin-bottom: var(--s-16); }
}

@media (max-width: 599px) {
  /* Телефон: две колонки, книга по-прежнему стоит. Полка остаётся полкой
     на всех ширинах — метафора не должна ломаться там, где продукт
     читают чаще всего. */
  /* minmax(0, 1fr), а не просто 1fr: у колонки 1fr минимальный размер
     равен min-content, поэтому длинное неразрывное слово растягивало
     колонку шире экрана — на 320 px страница уезжала вбок. С нулевым
     минимумом колонка сжимается, а многоточие наконец начинает работать. */
  .shelf-grid,
  .shelf-grid--compact { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--s-10) var(--s-4); }
  .shelf-grid--compact > .shelf-card:nth-child(n + 3) { display: none; }

  .shelf-card__body { padding: var(--s-4) 0 0; }
  .shelf-card__title { font-size: var(--fs-md); }
  .shelf-card__author { font-size: var(--fs-xs); margin-bottom: var(--s-2); }

  /* Страница книги: обложка по центру, дальше один столбец. */
  .book-page__cover { justify-content: center; }
  .book-page__cover img, .book-page__nocover { max-width: 200px; }
  .book-page__title { font-size: var(--fs-h2); }
  .book-page__author { font-size: var(--fs-base); }
  .book-page__status .availability__label { font-size: var(--fs-base); }
  .book-page__actions { flex-direction: column; align-items: stretch; gap: var(--s-4); }
  .book-page__actions form, .book-page__actions .btn { width: 100%; }
  /* Возврат в каталог на телефоне — второе действие страницы, и строка
     растянута во всю ширину: подпись встаёт по центру, как у кнопки над
     ней. Высоту цели задаёт общее правило сенсорной ступени. */
  .book-page__back { justify-content: center; }
  .book-facts { grid-template-columns: 1fr; gap: var(--s-5); }
  .book-facts dd { font-size: var(--fs-base); }
}

/* =====================================================================
   30. КАБИНЕТ ПО ШИРИНАМ ЭКРАНА

   Ступени выбраны после просмотра на 1440 / 1280 / 1024 / 768 / 390 / 320:
     ≤1280 — столбцы рабочих списков ужимаются, но список остаётся списком;
     ≤1024 — двухколоночные экраны (заявка, форма книги, мастер) в колонку;
     ≤860  — телефон и узкий планшет: разделы сворачиваются в меню,
             строка списка становится компактной карточкой;
     ≤520  — узкие телефоны: действия во всю ширину, минимум 44 px.

   Три из этих ступеней — 1024, 860 и 520 — общие с витриной, и объявлены
   они там же, в разделе 27, одним медиазапросом на обе половины продукта.
   Здесь остались только ступени, которых у витрины нет (1280 и 561–860),
   и большой блок 860, где кабинет перестраивается целиком.

   Главное правило телефона: настольную таблицу НЕ ужимаем. Тот же DOM
   раскладывается сверху вниз — сначала то, по чему запись узнают, потом
   подробности, внизу действие под большим пальцем. Горизонтальной
   прокрутки в основных рабочих операциях нет.
   ===================================================================== */

/* Правая направляющая листа. Появляется тогда же, когда справа от листа
   появляется полоса рельса: на 1281+ работа не доходит до края стекла,
   и лист обязан быть закрыт линией — иначе на 1920 и 2560 он обрывается
   в фон без края. Уже 1281 линия у самого края окна ничего не разделяла
   бы. Ширина линии уже входит в --admin-workspace. */
@media (min-width: 1281px) {
  .is-admin .admin-shell__main { border-right: var(--hairline) solid var(--line); }
}

@media (max-width: 1280px) {
  /* Колонка разделов ужимается раньше рабочей области: место на узком
     ноутбуке нужнее таблице, а подписи разделов короткие. Переопределяем
     в теле кабинета, а не в :root — блок переменных в файле ровно один. */
  .is-admin { --admin-sidebar-w: 196px; --admin-pad-x: var(--s-6); }

  /* Доли те же, что на широком экране, — ужимаются только минимумы.
     Столбец состояния свои 180 (у книг) и 152 (у заявок) держит и здесь:
     подписи состояния от ширины экрана короче не становятся, а рвутся
     надвое они одинаково плохо на любой ступени. Поступаются те столбцы,
     которым есть чем: название, экземпляры, код заявки. */
  .data-list--books .data-head,
  .data-list--books .data-row  {
    grid-template-columns:
      minmax(0, 2fr) minmax(148px, 1fr) minmax(180px, 1fr) var(--admin-act-w);
  }
  /* Столбец заявки не ужимается и здесь: дата в нём той же длины, что и
     на широком экране, а обрывать её нельзя — по дате заявки ищут.

     А вот межколонник возвращается к прежним 16. Просторные 32 — роскошь
     широкого стола: здесь четыре зазора по 32 отнимают у столбца читателя
     сто двадцать восемь точек, и вместо воздуха между колонками получается
     имя, переломленное надвое. Воздух между столбцами нужен ровно до тех
     пор, пока он не начинает есть содержимое. */
  .data-head,
  .data-row { gap: var(--s-3) var(--s-4); }
  .data-list--orders .data-head,
  .data-list--orders .data-row {
    grid-template-columns:
      168px minmax(0, 2fr) minmax(80px, 1fr) minmax(152px, 1fr) var(--admin-act-w);
  }
  .data-list--users .data-head,
  .data-list--users .data-row  {
    grid-template-columns:
      minmax(0, 2fr) minmax(148px, 1fr) minmax(140px, 1fr) var(--admin-act-w);
  }
  .item-row { grid-template-columns: minmax(0, 1fr) 132px 200px; }

  /* Шапка заявки складывается в одну колонку уже здесь, а не на 1024.
     Выходные данные заявки стоят строкой и просят под себя около 480
     точек — на 1025 после них левой половине оставалось 251, и контакт
     с просьбой читателя оказывались в колонке уже собственной подписи.
     Двум колонкам рядом нужен экран, на котором обе настоящие; на всех
     остальных факты уходят строкой под контакт и получают полную ширину
     работы. Прежнее правило на ступени 1024 стало лишним и убрано. */
  .order-head { grid-template-columns: 1fr; gap: var(--s-6); }
}

@media (max-width: 860px) {
  /* Обложка возвращается к прежнему размеру. На широком экране она задаёт
     высоту строки, и там прибавка в девять точек окупается узнаванием.
     Здесь строка уже не строка, а карточка в один столбец: высота — самый
     дорогой ресурс экрана, и платить за ту же прибавку девятью точками
     на каждой карточке нечем. Переопределяем токен, а не правило: размер
     миниатюры в файле по-прежнему задан в одном месте. */
  .is-admin { --admin-thumb-w: 38px; --admin-thumb-h: 57px; }

  /* Своего правила для последней пары выходных данных здесь больше нет.
     Оно снимало выключку вправо, которая после переноса строки оставляла
     число висеть поодаль от общего левого края. Число стоит по центру
     подписи (см. .order-facts выше) и выглядит одинаково на всех ширинах:
     и когда пара последняя в строке, и когда она одна на своём этаже. */

  /* --- Разделы кабинета: одна кнопка вместо колонки ---
     Восемь пунктов в узкую колонку не убрать: она съест половину экрана,
     а горизонтальная прокрутка прячет разделы без всякого знака, что там
     что-то есть. Поэтому колонка перестаёт быть колонкой и сворачивается
     в <details> с названием текущего раздела — видно, где находишься,
     и куда можно уйти. Разметка та же, JavaScript по-прежнему не нужен. */
  .admin-shell { display: block; }
  .admin-shell__main { border-left: 0; }
  .admin-nav {
    display: block; position: static;
    max-height: none; overflow: visible;
    background: var(--surface); border-bottom: 1px solid var(--line);
  }
  .admin-nav > .admin-nav__body { display: none; }
  .admin-nav[open] > .admin-nav__body { display: block; animation: reveal .18s ease-out; }
  .admin-nav::details-content { content-visibility: hidden; }
  .admin-nav[open]::details-content { content-visibility: visible; block-size: auto; }

  .admin-nav__summary {
    display: flex; align-items: center; gap: var(--s-3);
    min-height: var(--control-h); padding: 0 var(--s-5);
    cursor: pointer; user-select: none; list-style: none;
    color: var(--ink);
  }
  .admin-nav__summary::-webkit-details-marker { display: none; }
  .admin-nav__summary::marker { content: ""; }
  .admin-nav__summary-label {
    font-size: var(--fs-2xs); font-weight: var(--fw-semibold);
    letter-spacing: var(--ls-caps); text-transform: uppercase; color: var(--ink-3);
  }
  .admin-nav__summary-value { font-size: var(--fs-md); font-weight: var(--fw-semibold); }
  .admin-nav__summary .ico { margin-left: auto; transition: transform var(--t); color: var(--ink-3); }
  .admin-nav[open] .admin-nav__summary .ico { transform: rotate(180deg); }
  .admin-nav[open] .admin-nav__summary { border-bottom: 1px solid var(--line-soft); }

  /* Раскрытое меню: обычная прокрутка страницы, своя не нужна. */
  .admin-nav__body { padding: var(--s-3) var(--s-4) var(--s-4); }
  .admin-nav__group + .admin-nav__group {
    margin-top: var(--s-3); padding-top: var(--s-3);
    border-top: 1px solid var(--line-soft);
  }
  .admin-nav__list {
    display: grid; grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: var(--s-1);
  }
  .admin-nav__link { min-height: var(--control-h); }
  /* В колонке значок прижат к правому краю и выстраивается в столбик.
     В сетке из двух ячеек прижимать некуда: число уезжает от своей
     подписи через полэкрана и перестаёт к ней относиться. */
  .admin-nav__badge { margin-left: var(--s-2); }

  /* Поля листа на телефоне и планшете — через тот же токен, что и на
     столе: верх 24 (--s-6), бока 20 (--s-5); ниже 520 токен ужмётся ещё
     раз, и бока последуют за ним без второй записи. */
  .is-admin { --admin-pad-x: var(--s-5); }
  .admin-page { padding: var(--s-6) var(--admin-pad-x) var(--s-16); }

  /* Сообщение возвращается к левому краю страницы.
     На широком экране у него отступ под колонку разделов — чтобы левый
     край сообщения совпал с левым краем работы. Здесь колонки нет: она
     свернулась в кнопку во всю ширину (.admin-shell выше становится
     block). А отступ оставался — и на телефоне в 390 точек сообщение
     уезжало на 221 точку вправо, оставляя себе 145 точек на текст.
     Ровно та же поправка, что и у порога кабинета (.is-auth), только
     там колонки нет никогда, а здесь — начиная с этой ширины. */
  .is-admin .flash-area {
    padding-left: var(--admin-pad-x); padding-right: var(--admin-pad-x);
  }

  /* Служебная строка: имя библиотеки и выход. Подпись человека прячем —
     на телефоне важнее, чтобы название не переносилось. */
  .admin-topbar__inner { gap: var(--s-3); padding: 0 var(--s-5); }
  .admin-topbar__mark,
  .admin-topbar__link-text,
  .admin-topbar__user-name { display: none; }
  .admin-topbar__link, .admin-topbar__user { padding: 0 var(--s-1); }
  /* Название библиотеки — тоже ссылка, и ведёт она на Панель. Соседи по
     полосе («Сайт библиотеки», «Выйти») подняты до 44 точек правилом
     ниже, а эта осталась ростом со своей строкой: 18 точек, а на узком
     телефоне 15. Палец в неё не попадает. Поднимаем по тому же правилу,
     что и всё остальное, во что можно ткнуть. */
  .admin-topbar__brand { min-height: var(--control-h); align-items: center; }

  .admin-head { align-items: flex-start; flex-direction: column; gap: var(--s-4); }
  .admin-head__title { font-size: var(--fs-h2); }
  .admin-head__actions { width: 100%; }
  .admin-head__actions .btn { flex: 1 1 auto; }

  /* Очередь внимания и на телефоне остаётся очередью — но без шеврона:
     карточка и так очевидно нажимается, а колонка указателя отдаёт
     свои точки подписи. */
  .attention__chevron { display: none; }
  .attention__item { grid-template-columns: 48px minmax(0, 1fr); }
  .quick { flex-direction: column; align-items: stretch; }
  .quick .btn { width: 100%; }

  .admin-toolbar__search,
  .admin-toolbar__control { flex: 1 1 100%; }
  .admin-toolbar__form .btn { flex: 1 1 auto; }
  .admin-toolbar__form .input,
  .admin-toolbar__form .select,
  .admin-toolbar__form .btn { min-height: var(--control-h); font-size: var(--fs-md); }
  /* Расстояние между пунктами охвата убавляется, но не вдвое против
     стола: боковых полей у пункта нет, и на 8 точках слова начинают
     сливаться в одну строку. Пальцу при этом достаётся вся высота
     пункта (44) — правило ниже её и задаёт. */
  .scope { gap: var(--s-1) var(--s-4); }
  .scope__link { min-height: var(--control-h); }

  /* --- Рабочий список: строка становится карточкой ---
     Тот же DOM, другая раскладка. Сверху то, по чему запись узнают,
     ниже подробности с подписями, внизу действия во всю ширину. */
  .data-head { display: none; }
  /* Выключка по оси столбца — настольный приём: она держит значение под
     его подписью в шапке списка. Здесь шапки нет, строка развернулась в
     карточку, и подписи стоят слева над своими значениями. Центрирование
     в такой карточке разводит подписи по разным местам строки и ломает
     её единственный левый край — возвращаем всё к нему. */
  .data-list--books .data-row > :nth-child(2),
  .data-list--books .data-row > :nth-child(3),
  .data-list--orders .data-row > :nth-child(3),
  .data-list--orders .data-row > :nth-child(4),
  .data-list--books .tally { text-align: left; }
  /* Шапки списка здесь нет — и отступ над карточками на ступень меньше.
     Требуются ОБА условия. Смежный селектор — чтобы правило не проиграло
     по весу безусловному «после тулбара — 16». И собственный отступ
     тулбара снизу: соседние вертикальные поля схлопываются в наибольшее,
     поэтому список со своими 12 всё равно получал бы 16 от тулбара.
     Расстояние здесь задают двое, и убавлять его нужно тоже вдвоём. */
  .admin-toolbar { margin-bottom: var(--s-3); }
  .data-list, .admin-table-wrap,
  .admin-toolbar + .data-list, .admin-toolbar + .admin-table-wrap { margin-top: var(--s-3); }
  .data-list--books .data-row,
  .data-list--orders .data-row,
  .data-list--users .data-row {
    grid-template-columns: minmax(0, 1fr);
    gap: var(--s-3);
    padding: var(--s-4) var(--s-4);
    margin-bottom: var(--s-3);
    border: 1px solid var(--line); border-radius: var(--radius);
    background: var(--surface);
  }
  .data-row:hover { background: var(--surface); }
  .data-row:last-child { border-bottom: 1px solid var(--line); }
  /* Нижний предел строки — правило настольного листа. Здесь строка уже
     карточка и ростом по своему содержимому; предел ей нечего делать. */
  .data-row { min-height: 0; }
  .data-row.is-late,
  .data-list--users .data-row { padding-left: var(--s-4); }
  .data-row__title { font-size: var(--fs-lg); }
  /* Подпись столбца появляется только здесь: на широком экране её роль
     играет шапка списка. */
  .data-row__label {
    display: block; margin-bottom: var(--s-05);
    font-size: var(--fs-2xs); font-weight: var(--fw-semibold);
    letter-spacing: .06em; text-transform: uppercase; color: var(--ink-3);
  }
  /* Короткому значению две строки не нужны. «Книг · 2» и «Состояние ·
     ВЫДАНА» занимали по два этажа каждое — четыре строки карточки ради
     одной цифры и одного слова. Подпись встаёт слева от значения, как
     в карточке выдачи; длинные поля (код, читатель) по-прежнему сверху,
     иначе имя переносится в узкую вторую колонку. Мера подписи — 88px,
     общая с колонкой подписи табличной карточки ниже: на 390 её хватает
     самой длинной подписи («СОСТОЯНИЕ»). */
  .data-row__num > .data-row__label,
  .data-row__cell > .data-row__label {
    display: inline-block; min-width: 88px; margin-bottom: 0;
    padding-right: var(--s-3); vertical-align: baseline;
  }
  /* Наличие — тоже короткое значение, и уходить на свой этаж ему незачем.
     В карточке дробь встаёт за подписью и выключается влево, как все
     короткие значения рядом. */
  .data-row__cell > .tally { display: inline-block; vertical-align: baseline; text-align: left; }
  .data-row__actions {
    justify-content: stretch; gap: var(--s-2);
    padding-top: var(--s-3); border-top: 1px solid var(--line-soft);
  }
  .data-row__actions > *, .data-row__actions form { flex: 1 1 auto; }
  .data-row__actions .btn,
  .data-row__actions .row-menu__summary { width: 100%; min-height: var(--control-h); }

  /* Таблицы кабинета: строка разворачивается в карточку, подпись стоит
     НАД значением и слева. Прежний вариант с прижатым вправо текстом
     ломался на длинных названиях и адресах. */
  .admin-table--cards,
  .admin-table--cards thead, .admin-table--cards tbody,
  .admin-table--cards tr, .admin-table--cards th, .admin-table--cards td {
    display: block; width: 100%;
  }
  .admin-table--cards thead { display: none; }
  .admin-table--cards tr {
    padding: var(--s-4); margin-bottom: var(--s-3);
    border: 1px solid var(--line); border-radius: var(--radius);
    background: var(--surface);
  }
  .admin-table--cards tr.is-late { box-shadow: inset 3px 0 0 var(--red); }
  /* Кромка переехала на карточку целиком — с первой ячейки снимаются и
     сама кромка, и настольное компенсирующее поле под неё. tbody в
     селекторе обязателен: настольное правило носит его ради победы над
     focus-within, и без него сброс проигрывает по весу, а на карточке
     остаются ДВЕ красные кромки — своя и ячейкина. */
  .admin-table--cards tbody tr.is-late td:first-child { box-shadow: none; padding-left: 0; }
  .admin-table--cards tbody tr:hover td { background: transparent; }
  /* Подпись слева, значение справа — но обе колонки прижаты влево.
     Значение НАД подписью читалось бы лучше, да карточка выходила вдвое
     выше: семь строк по две — это полтора экрана на одну выдачу. */
  /* Сетка, а не флекс: во второй колонке у ячейки бывает несколько строк
     (имя и контакт, дата и «просрочено на N дней»), и они обязаны встать
     друг под другом, а не в ряд. grid-column: 2 у детей это и обеспечивает.
     Колонка подписи — одна мера с карточкой списка: не уже 88px, чтобы
     подпись не ломалась на 390, и не шире трети, чтобы значение не
     теснилось. */
  .admin-table--cards td {
    display: grid; grid-template-columns: minmax(88px, 33%) minmax(0, 1fr);
    column-gap: var(--s-3); align-items: baseline;
    padding: var(--s-2) 0; border-bottom: 0; text-align: left;
    color: var(--ink-2);
  }
  .admin-table--cards td > * { grid-column: 2; }
  /* Ячейка перестала быть табличной — height больше не «минимум», а
     потолок, и обложке в 57 точек он мал. Рост отдаётся содержимому. */
  .admin-table--cards tbody tr { min-height: 0; }
  .admin-table--cards tbody td { min-height: 0; height: auto; }
  .admin-table--cards td + td { border-top: 1px solid var(--line-soft); }
  .admin-table--cards tbody tr:last-child td { border-bottom: 0; }
  .admin-table--cards td::before {
    content: attr(data-label);
    grid-column: 1; grid-row: 1; min-width: 0;
    font-size: var(--fs-2xs); font-weight: var(--fw-semibold);
    letter-spacing: .06em; text-transform: uppercase; color: var(--ink-3);
    line-height: 1.8;
  }
  .admin-table--cards td[data-label=""]::before { display: none; }
  /* Опознавательная ячейка и ячейка действий занимают всю ширину:
     название книги и кнопку зажимать в треть строки нельзя. */
  .admin-table--cards .admin-table__lead,
  .admin-table--cards .admin-table__actions { display: block; }
  .admin-table--cards .admin-table__lead::before {
    display: block; margin-bottom: var(--s-1);
  }
  .admin-table--cards .admin-table__actions { text-align: left; white-space: normal; }
  .admin-table--cards .admin-table__actions > *,
  .admin-table--cards .admin-table__actions .btn { display: flex; width: 100%; }
  .admin-table--cards .issue-box,
  .admin-table--cards .issue-box > summary { display: block; width: 100%; }
  /* В режиме карточек боковая прокрутка не нужна и мешает. */
  .admin-table-wrap:has(.admin-table--cards) { overflow-x: visible; }

  /* Меню действий раскрывается на всю ширину карточки. */
  .row-menu { display: block; }
  .row-menu__panel { left: 0; right: 0; min-width: 0; }

  /* --- Палец, а не курсор ---
     На широком экране многие органы управления кабинета намеренно
     компактны: 36 px экономят высоту в плотном списке, а мышь попадает
     и в 36. Пальцем — не попадает. Поэтому на телефоне ВСЁ, во что можно
     ткнуть, поднимается до 44 px: и кнопки в строках, и пункты меню, и
     поля внутри форм строки, и выход из кабинета.
     Правила ограничены телом кабинета (.is-admin) — витрины не касаются. */
  .is-admin .btn--small,
  .is-admin .btn--quiet,
  .is-admin .row-menu__summary,
  .is-admin .row-menu__item,
  .is-admin .admin-topbar__exit,
  .is-admin .admin-topbar__link,
  .is-admin .copy-item__form .input,
  .is-admin .admin-toolbar__form .input,
  .is-admin .admin-toolbar__form .select,
  .is-admin .row-menu__form .select,
  .is-admin .inline-form .input,
  .is-admin .inline-form .btn { min-height: var(--control-h); }

  .is-admin .btn--small {
    font-size: var(--fs-sm);
    padding-left: var(--s-4); padding-right: var(--s-4);
  }
  .is-admin .row-menu__item { padding-top: var(--s-2); padding-bottom: var(--s-2); }

  /* Заявка, экземпляры, формы — в одну колонку. */
  .item-row { grid-template-columns: 1fr; gap: var(--s-3); }
  .item-row__actions { justify-content: flex-start; }
  .item-row__actions .btn { flex: 1 1 auto; }
  .copy-item { grid-template-columns: 1fr; padding: var(--s-4) 0; }
  .copy-item__actions { justify-content: flex-start; border-top: 0; padding-top: 0; }
  .copy-item__no, .copy-item__note { flex: 1 1 100%; }
  .form-grid, .form-grid--3 { grid-template-columns: 1fr; }
  .form-actions { flex-direction: column; align-items: stretch; }
  .form-actions .btn { width: 100%; }
  .danger-zone__actions .btn { width: 100%; }
  /* Два запасных пути встают друг под друга: рядом им на телефоне тесно,
     а поле выбора файлов и без того шире своей колонки. */
  .import-alt { grid-template-columns: 1fr; }
  .steps-bar { gap: var(--s-1); }
  .steps-bar__item { flex: 1 1 100%; }

}

/* =====================================================================
   ПЛАНШЕТ: 561–860. Пятая ступень, и единственная, добавленная после
   PHASE 3. Она не отменяет ни одной из четырёх основных — она уточняет
   ровно одну вещь внутри уже существующей ступени «≤860».

   Зачем понадобилась. Настольная строка выдачи стоит 129 точек. Ниже 860
   та же запись становится карточкой в один столбец — и стоит 436 точек,
   ОДИНАКОВО на 390 и на 860. Двадцать пять выдач превращались в 11 558
   точек прокрутки: две с половиной карточки на экран планшета, при том
   что в строке 731 точка ширины и заняты из них едва половина. Платили
   высотой за ширину, которой некуда деться.

   Что делаем. Ничего не прячем и не переставляем: тот же DOM, те же поля,
   те же подписи. Короткие поля (номер, даты, код заявки) просто перестают
   занимать целую строку каждое и встают по двое в ряд, а опознание записи
   и действие по-прежнему во всю ширину карточки — их сжимать нельзя.

   Почему 561. Ниже этой ширины вторая колонка становится уже 240 точек,
   и «Мария Николаевна Соколова» начинает переноситься на три строки —
   то есть экономия высоты съедается переносами. 560 — это верхняя граница
   существующей телефонной ступени (≤520 плюс запас на 540-е экраны),
   поэтому новая ступень начинается сразу над ней и ни с чем не спорит.

   Действует на все четыре экрана с карточками — «На руках», «Категории»,
   «История книги», «Настройки»: у всех одна и та же анатомия «опознание →
   короткие поля → действие», поэтому и правило одно на всех.
   ===================================================================== */
@media (min-width: 561px) and (max-width: 860px) {
  .admin-table--cards tr {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    column-gap: var(--s-6);
    align-items: start;
  }
  /* Опознание и действие — во всю ширину карточки. Название книги в
     половину строки переносится вчетверо, а кнопка возврата обязана
     оставаться целью в 44 точки. */
  .admin-table--cards .admin-table__lead,
  .admin-table--cards .admin-table__actions { grid-column: 1 / -1; }

  /* Столбец подписи — фиксированный, а не доля. Доля в 33% от половинной
     ячейки оставляла значению 190 точек, и «Мария Николаевна Соколова»
     переносилась в три строки: экономия от двух колонок уходила обратно
     в переносы. 96 точек хватает самой длинной подписи кабинета
     («ВОЗВРАЩЕНА» — 87), а значение получает на полсотни точек больше и
     чаще встаёт в одну строку. Фиксированная величина ещё и выравнивает
     значения обеих колонок по одной вертикали. */
  .admin-table--cards td { grid-template-columns: 96px minmax(0, 1fr); }

  /* Линейки между полями снимаются: в две колонки они превращаются
     в лесенку из обрывков, которая делит карточку не по смыслу.
     Поля разделяет расстояние — так же, как в остальном кабинете. */
  .admin-table--cards td + td { border-top: 0; }
  .admin-table--cards td { padding: var(--s-1) 0; }
  /* Но опознание от полей и поля от действия отделить нужно: это три
     разные части карточки, а не семь одинаковых строк. */
  .admin-table--cards .admin-table__lead { padding-bottom: var(--s-3); }
  .admin-table--cards .admin-table__actions {
    padding-top: var(--s-3); margin-top: var(--s-2);
    border-top: 1px solid var(--line-soft);
  }
  /* Подпись «Книга» над обложкой с названием ничего не сообщает: и так
     видно, что это книга. В остальных полях подпись несёт смысл —
     «Выдана» и «Вернуть до» без неё не различить. */
  .admin-table--cards .admin-table__lead::before { display: none; }
}

/* Этот блок обязан стоять ПОСЛЕ ступени 860 выше: кабинет переопределяет
   здесь собственные селекторы, объявленные в ней (.attention__item,
   .admin-page), и выигрывает только порядком. Поэтому с одноимённым
   блоком витрины (раздел 27) он не объединён — объединение перевернуло
   бы порядок сложения стилей. Строка фонда (.metrics) своего мобильного
   правила больше не имеет: она переносится по парам сама, разделитель
   ставит ::before. */
@media (max-width: 520px) {
  .admin-topbar__name { font-size: var(--fs-md); }
  /* Число очереди на ступень мельче, его колонка уже — подписи на
     узком экране нужнее точки, чем числу. */
  .attention__item { grid-template-columns: 40px minmax(0, 1fr); }
  .attention__value { font-size: var(--fs-h3); }
  /* Узкий телефон: поля листа ещё на ступень уже, верх 20, низ 48.
     Токен объявлен на теле кабинета, а не на самом листе: его читает и
     полоса сообщений, которая лежит ВНЕ листа, — иначе левый край
     сообщения перестаёт совпадать с левым краем работы. */
  .is-admin { --admin-pad-x: var(--s-4); }
  .admin-page { padding-top: var(--s-5); padding-bottom: var(--s-12); }
  /* Охват не переносится в три этажа, а прокручивается строкой — как
     разделы на главной. Полоса выпущена до краёв экрана, чтобы крайние
     кнопки не обрезались на полуслове, а поля совпали с полями листа. */
  .scope {
    flex-wrap: nowrap; overflow-x: auto; scrollbar-width: none;
    margin-inline: calc(-1 * var(--admin-pad-x)); padding-inline: var(--admin-pad-x);
  }
  .scope::-webkit-scrollbar { display: none; }
  .scope__link { flex: 0 0 auto; }
  .admin-pager { flex-wrap: wrap; }
  .admin-pager .btn { flex: 1 1 auto; }
}

/* --- до 360 px: самые узкие телефоны. Общий блок для обеих половин --- */
@media (max-width: 360px) {
  /* Здесь в шапке меняется сам знак: полный уходит, встаёт версия без
     подписи — одна книга.

     Почему не полный. На 320 полоса шапки — 288 (320 минус поля по 16),
     корзина 60, кнопка меню 44; даже при нулевых промежутках знаку
     остаётся 184, а полный знак нижнего разрешённого роста (34) требует
     201. Он не помещается ни при какой экономии промежутков: чтобы
     влезть, ему пришлось бы опуститься до 27–28, и подпись «CHURCH
     LIBRARY» превратилась бы в пять точек штрихов. Книга ростом 36
     занимает 42 — строка стоит свободно, и знак остаётся собой, просто
     говорит короче. */
  .site-header__inner { gap: var(--s-2); }
  .brand__logo { display: none; }
  .brand__logo--mark { display: block; height: var(--brand-mark-h); }

  /* Та же экономия в поиске. Убавлять здесь можно только поля лупы и
     кнопки: кегль подсказки трогать нельзя — в это же поле человек
     впишет запрос, и запрос обязан набираться основным кеглем.

     Что это даёт. Подсказка главной («Название или автор», 179 точек)
     начиная с 360 помещается целиком; на 320 ей не хватает восьми,
     и она обрывается многоточием на «Название или ав…». Подсказка
     каталога длиннее вдвое («Название, автор или описание») и на узком
     телефоне не поместится ни при какой экономии полей — она обрывается
     всегда. Обрыв здесь честный: многоточие ставит правило text-overflow
     у .search__field, и фраза читается как сокращённая, а не как
     обрезанная краем поля. Полностью убрать обрыв может только более
     короткая подсказка — а это уже текст, а не вёрстка. */
  .search__icon { padding-left: var(--s-2); }
  .search__submit { padding: 0 var(--s-3); }

  /* Две колонки разделов держатся до последнего: так открытое меню не
     закрывает страницу целиком. Но когда «Пользователи» перестают
     помещаться в половину строки, список становится одноколоночным. */
  .admin-nav__list { grid-template-columns: 1fr; }
  /* Строка снова во всю ширину — значит значкам снова есть куда встать
     столбиком у правого края, как в настольной колонке. */
  .admin-nav__badge { margin-left: auto; }
  .admin-topbar__inner { gap: var(--s-2); }

  /* Шаги главной остаются 2×2 и здесь. Раньше они переводились
     в один столбец, но причина была не в ширине: подписи ошибочно
     набирались 17-м кеглем вместо 13-го. Кегль исправлен — заголовки
     шагов помещаются в половину 320-й строки без переносов. */
  .steps { column-gap: var(--s-3); row-gap: var(--s-4); }
}

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

/* ------------------------------------------------------- 26. Печать */
@media print {
  .site-header, .site-footer, .admin-topbar, .admin-nav,
  .btn, .flash, .row-menu, .admin-toolbar, .bg-watermark { display: none !important; }
  body { background: var(--white); }
  .card { box-shadow: none; border-color: var(--print-line); }
}
