/*
 * Мобильная вёрстка в первую очередь (история 30, R30): одна колонка,
 * целевые размеры нажатия от 44px, горизонтальной прокрутки нет ни на одном
 * экране. Ширина нигде не задаётся в пикселях, всё тянется по контейнеру.
 *
 * Цвета берутся из темы Telegram (`--tg-theme-*`, их проставляет WebApp SDK).
 * Вне Telegram переменных нет — работает запасная палитра, светлая или
 * тёмная по системной настройке браузера.
 *
 * ВЕСЬ набор значений живёт в `:root` ниже (R28, ADR 0050): цвет, отступы,
 * скругления, шрифт, размеры. Вне `:root` в этом файле нет ни одного
 * цветового литерала — ни hex, ни rgb, ни hsl; новый оттенок заводится
 * токеном, а не по месту. Тема Telegram при этом читается только CSS:
 * `themeParams` из JS не спрашивается нигде, поэтому смена темы на лету не
 * требует перезагрузки (R27.1).
 */

.offline-panel {
  margin: var(--space-3);
  padding: var(--space-3);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  background: var(--surface);
  overflow-wrap: anywhere;
}

@media (min-width: 900px) {
  .offline-panel { margin-left: calc(var(--sidebar-width) + var(--space-3)); }
}

/* --- запасная палитра: светлая -------------------------------------------
   Работает там, где переменных Telegram нет вовсе, — в обычном браузере. */
:root {
  --fallback-bg: #f2f3f5;
  --fallback-surface: #ffffff;
  --fallback-text: #16181c;
  --fallback-hint: #707579;
  --fallback-link: #2d7ff9;
  --fallback-link-text: #ffffff;
  --fallback-border: #dfe1e5;
  --fallback-danger: #d1435b;
  --fallback-warning: #a86b12;
  --fallback-success: #2f9e44;
}

/* --- запасная палитра: тёмная --------------------------------------------
   Пара к светлой, по системной настройке браузера (R43i.1). Полная: у каждой
   роли, включая «опасно», «внимание» и «успех», здесь свой тёмный вариант —
   иначе один и тот же оттенок читался бы на одном фоне и терялся на другом. */
@media (prefers-color-scheme: dark) {
  :root {
    --fallback-bg: #17212b;
    --fallback-surface: #232e3c;
    --fallback-text: #f5f5f5;
    --fallback-hint: #93a3b3;
    --fallback-link: #62a0e8;
    --fallback-border: #38495c;
    --fallback-danger: #ff7a8a;
    --fallback-warning: #e8a33d;
    --fallback-success: #51cf66;
  }
}

/* --- токены --------------------------------------------------------------
   Четыре группы: цвет, отступы, скругления, шрифт (плюс размеры). Слева —
   роль, справа — переменная темы Telegram, из которой роль берётся; запасное
   значение стоит вторым аргументом `var()` и подхватывается вне Telegram.

   У «внимания» (`--warning`) и «успеха» (`--success`) своей переменной у
   Telegram нет вовсе — у них только запасная пара, светлая и тёмная. */
:root {
  --bg: var(--tg-theme-secondary-bg-color, var(--fallback-bg));
  --surface: var(--tg-theme-bg-color, var(--fallback-surface));
  --text: var(--tg-theme-text-color, var(--fallback-text));
  --hint: var(--tg-theme-hint-color, var(--fallback-hint));
  --subtitle: var(--tg-theme-subtitle-text-color, var(--hint));
  --link: var(--tg-theme-button-color, var(--fallback-link));
  --link-text: var(--tg-theme-button-text-color, var(--fallback-link-text));
  --border: var(--tg-theme-section-separator-color, var(--fallback-border));
  --danger: var(--tg-theme-destructive-text-color, var(--fallback-danger));
  --warning: var(--fallback-warning);
  --success: var(--fallback-success);
  /* Единственная прозрачность заливки значков: плашка красится тем же цветом,
     что и её текст, разведённым до фона. Второй доли в файле быть не должно —
     иначе плашки разъедутся по насыщенности. */
  --tint: 14%;

  /* Отступы — ровно пять ступеней (R28.1). Шестой не заводится: значение,
     не попавшее в шкалу, и есть та самая «разрозненность по экранам».

     Роли ступеней постоянные, и по ним разложен весь файл — иначе «взять из
     шкалы» превращается в «взять из шкалы наугад». Роль описывает КАЖДОЕ
     применение ступени: нашлось применение мимо роли — переезжает оно, а не
     переписывается роль.
       --space-1  расстояние, которое не должно читаться как разрыв: между
                  узлами одного целого (строка и подпись под ней, плашки
                  одного ряда, стрелки и подпись месяца, значок и подпись
                  пункта панели) и внутри мелкого узла — плашки, пункта
                  нижней панели, хвоста раскрытой детали
       --space-2  между соседями: строки списка, поля карточки, кнопки в ряду
       --space-3  поперёк строки (левая колонка ↔ правая) и внутренний отступ
                  всего нажимаемого и всего, что обведено рамкой
       --space-4  крупный вертикальный РАЗМЕР, а не зазор между узлами: воздух
                  под содержимым над панелью и прибавка к высоте узла, которому
                  мало одной строки
       --space-5  самая широкая ступень; на сегодня свободна */
  --space-1: 4px;
  --space-2: 8px;
  --space-3: 12px;
  --space-4: 16px;
  --space-5: 24px;

  /* Скругления — четыре (R28.3), и выбирает между ними вложенность, а не вкус:
       --radius-sm    контрол (поле, кнопка, вкладка) и мелкая ячейка остатка
       --radius-md    строка списка и вложенный в карточку блок
       --radius-lg    карточка верхнего уровня и сообщение
       --radius-pill  плашка состояния
     Внутреннее скругление обязано быть мельче внешнего: угол, повторяющий
     родительский, читается как отклеившийся край. */
  --radius-sm: 8px;
  --radius-md: 12px;
  --radius-lg: 16px;
  --radius-pill: 999px;

  /* Шрифт — шесть ступеней (R28.2) плюс отдельный кегль поля ввода. Роли у
     них такие же постоянные, как у отступов выше, и по той же причине: без
     ролей «взять из шкалы» превращается в «взять из шкалы наугад», и пять
     списков набираются пятью разными способами.

     Роль описывает КАЖДОЕ применение ступени, и читается она прескриптивно:
     нашлось применение, исполняющее ЧУЖУЮ роль, — переезжает оно, а не
     переписывается роль. Дописывается роль ровно в одном случае: применению
     не подходит НИ ОДНА ступень. Роли описывают содержимое, а хром —
     нажимаемое и глифы — содержимым не является, и переехать ему некуда.
     Тогда хром называется в роли по имени и с причиной, а молчаливым
     исключением не остаётся: прескриптивный список, который врёт, хуже
     отсутствующего — по нему делают следующий экран.
       --text-xs    подпись под значением или под значком: бренд, состояние
                    словом и плашкой, вторая строка списка, пометка шапки,
                    подпись пункта нижней панели, штучный хвост величины;
                    сюда же подробность отказа моноширинным
       --text-sm    единица измерения и пояснение при числе, метка поля и
                    метка раздела, вторичный текст абзацем; из хрома —
                    надпись кнопки периода: ряд из четырёх обязан помещаться
                    в строку, и кегль здесь несёт компактность
       --text-base  название и величина в ЛЮБОЙ строке списка; основной текст
       --text-lg    крупное в карточке: её заголовок, значение факта, подпись
                    месяца в «‹ месяц ›», денежный итог секции
       --text-xl    заголовок экрана; из хрома — одиночный глиф в кнопке
                    («×», «‹», «›»): знак вместо надписи читается размером, и
                    ступени «знак» в шкале нет и не будет — седьмую заводить
                    нельзя (ADR 0050)
       --text-hero  ЕДИНСТВЕННОЕ главное число карточки товара, и больше
                    ничьё: в строке списка величина набрана кеглем названия
                    (R13), потому что число вдвое крупнее названия спорит с
                    ним за внимание, а не помогает его прочесть

     `--text-base` — 16px, а не 15px: этого просит бриф дословно («16px /
     500»), и заодно поле ввода перестаёт быть единственным местом с чужим
     кеглем. `--text-lg` едет следом (17px → 18px): при базе 16 ступень в 17px
     неразличима и перестаёт быть ступенью. */
  --text-xs: 12px;
  --text-sm: 13px;
  --text-base: 16px;
  --text-lg: 18px;
  --text-xl: 20px;
  --text-hero: 26px;
  /* НЕ трогать: ниже 16px Safari на iOS зумит страницу при фокусе поля.
     Совпадение с `--text-base` — совпадение, а не связь: переписать это
     значение в `var(--text-base)` значит отдать вёрстку поля чужой правке
     кегля списков, и зум вернётся молча, на телефоне владельца. */
  --text-input: 16px;

  /* Веса — четыре ступени, и дисциплина у них ровно та же, что у кеглей
     (R18.1): числовой вес, вписанный по месту, — та же дыра в наборе, что и
     кегль мимо шкалы, и ловит обе один сторож.
       --weight-regular   единица измерения при числе, вторичный текст, подпись
       --weight-medium    название и величина в строке списка, плашка
                          состояния, подпись пункта нижней панели
       --weight-semibold  заголовок карточки и подпись месяца, метка раздела,
                          значение факта, состояние формы; из хрома — надпись
                          всего нажимаемого: кнопки, вкладки, кнопки периода,
                          ссылки-перехода
       --weight-bold      заголовок экрана, денежный итог секции и главное
                          число карточки товара */
  --weight-regular: 400;
  --weight-medium: 500;
  --weight-semibold: 600;
  --weight-bold: 700;

  --tap-target: 44px;
  --tabbar-height: 56px;
  /* Колонки оболочки: прежний предел на телефоне и до входа, шире —
     рабочая область рядом с боковой панелью от 900px (R02). */
  --compact-content-max: 720px;
  --sidebar-width: 220px;
  --content-max: 1040px;
  /* Квадрат значка нижней панели, и только он: ровно та сетка, в которой
     `icons.js` рисует пути (`viewBox="0 0 24 24"`). Ничего, кроме значка
     раздела, отсюда размер не берёт — иначе правка панели молча двигала бы
     чужой контрол. */
  --icon-size: 24px;
}

*,
*::before,
*::after {
  box-sizing: border-box;
}

html {
  /* Страховка от горизонтальной прокрутки: длинное название товара или
     заметка к бою не должны уводить всю страницу вбок. */
  overflow-x: hidden;
}

/* Свёрнутые блоки прячутся атрибутом `hidden`, а браузерное правило для него
   — `display: none` из UA-таблицы, и любой авторский `display` его перебивает.
   Нашлось при проверке кликами: `<div class="stack" hidden>` с закрытыми
   документами оставался на экране, потому что у `.stack` стоит `display: flex`.
   Правило общее, а не «не ставить hidden на flex-контейнеры»: последнее
   пришлось бы помнить в каждом новом экране. */
[hidden] {
  display: none !important;
}

body {
  margin: 0;
  /* Сверху — вырез телефона: этот отступ держала шапка приложения, а её
     больше нет (R04). Снизу отступа нет: панель разделов прижата к низу окна
     и уносит safe-area себе, а содержимое отступает от панели через
     `.app-main`. Два отступа сразу дали бы двойной зазор. */
  padding: env(safe-area-inset-top) 0 0;
  background: var(--bg);
  color: var(--text);
  /* Базовый кегль страницы — `--text-base`: его наследует всё, что берёт
     `font: inherit` (кнопки, вкладки, строки списка, свёрнутые заголовки),
     и от него же считается `1.45` межстрочного. Поля ввода его НЕ наследуют:
     у них свой `--text-input`, и он единственный, кому нельзя опускаться
     ниже 16px. */
  font: var(--text-base)/1.45 -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Arial, sans-serif;
  overflow-wrap: anywhere;
}

/* --- нижняя панель разделов, полоса подразделов и шапка экрана ------------ */

/* Верхней полосы из восьми вкладок больше нет (R04): восемь русских слов
   переносились на три строки и съедали четверть экрана. Разделов пять, они
   прижаты к низу окна, и до любого достаёт большой палец (R06, R07).

   `position: fixed`, а не `sticky`: рядом стоит `html { overflow-x: hidden }`,
   из-за которого `sticky` в WebView может не сработать вовсе — см. комментарий
   к `.picker__footer`. Узел при этом лежит последним в `body`: если `fixed`
   всё-таки не сработает, панель окажется под содержимым и достанется
   прокруткой вниз, а не пропадёт совсем. Проверить это на устройстве нечем —
   браузера на машине сборки нет. */
.tabbar {
  position: fixed;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 2;
  display: flex;
  background: var(--surface);
  border-top: 1px solid var(--border);
  /* На iPhone под панелью проходит полоса home-индикатора; без этого отступа
     подписи уезжают под неё (R06.5). */
  padding-bottom: env(safe-area-inset-bottom);
}

/* Пункт панели: иконка над подписью, пятая часть ширины, высота — вся панель.
   `min-height` выше 44px берёт цель нажатия с запасом (R06). Подпись не
   переносится: перенос вырастил бы панель во вторую строку, а высота у неё
   постоянная — на неё отступает содержимое. */
.tabbar__link {
  flex: 1 1 0;
  min-width: 0;
  min-height: var(--tabbar-height);
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--space-1);
  padding: var(--space-1);
  color: var(--hint);
  text-decoration: none;
  font-size: var(--text-xs);
  font-weight: var(--weight-medium);
  white-space: nowrap;
}

/* Раздел, в котором находимся: цветом — и `aria-current="page"` рядом,
   потому что цвет в одиночку не сообщает ничего экранному диктору. */
.tabbar__link--active {
  color: var(--link);
}

/* Подпись при нехватке ширины обрезается многоточием, а не выпирает за свой
   пункт: `white-space: nowrap` выше запрещает ей перенос, и без этого узкий
   экран получил бы наползающие друг на друга слова. */
.tabbar__label {
  max-width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
}

.tabbar__icon {
  display: block;
  flex: 0 0 auto;
  width: var(--icon-size);
  height: var(--icon-size);
}

/* Полоса подразделов — её рисует роутер там, где в разделе больше одного
   верхнеуровневого экрана: «Заказы / История» и «Взаиморасчёты / Расходы»
   (R09, R10). Кнопки берутся у `.tabs`/`.tab`, которыми уже переключается
   история внутри себя: второго набора переключателей в проекте быть не
   должно.

   Узел лежит над `main` и вне его (D01): экран перезаписывает `main` целиком,
   и полоса, стоящая внутри, уезжала бы вместе с содержимым.

   Ширина и боковые отступы у полосы те же, что у содержимого экрана, и
   названы один раз на оба узла: разъехавшись, они дали бы ступеньку —
   переключатель и список под ним стоят в одной колонке. */
.subnav,
.app-main {
  max-width: var(--compact-content-max);
  margin: 0 auto;
  padding-left: var(--space-3);
  padding-right: var(--space-3);
}

.subnav {
  padding-top: var(--space-3);
}

/* Шапка экрана: один ряд вместо трёх (R05, R30.2). Заголовок слева, короткая
   пометка справа — сколько позиций, за какой месяц, чей это заказ.

   `flex-wrap: wrap` — починка находки ревью, и она несущая. Пометка коротка
   «по уговору» только на списочных экранах: там это счётчик («4 позиции»).
   На карточке товара в неё уходит бренд, и на 380px он забирал полряда,
   оставляя заголовку колонку в семь знаков — название рвалось посреди слова
   на одиннадцать строк («минерал / ьная»). Переполнения при этом нет никакого,
   и `layout_check.py` такую шапку по устройству не видит.

   Перенос решает это без обрезки: пока обе половины помещаются, ряд остаётся
   одним (счётчик стоит справа, как и стоял), а как только заголовку не хватает
   собственной ширины — пометка уходит под него отдельной строкой, и заголовок
   переносится по словам во всю колонку. Многоточие здесь было бы хуже: на
   карточке товара обрезался бы либо бренд, либо само название товара, ради
   которого карточку и открыли.

   Зазор поэтому двумя ступенями: поперёк ряда — `--space-2` («между
   соседями»), между строками — `--space-1`, потому что перенесённая пометка
   это подпись под заголовком, а не следующий узел. */
.screen-head {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-1) var(--space-2);
}

/* Заголовок забирает свободное место и ужимается сам — но не уже собственной
   строки: перенос выше уводит пометку вниз раньше, чем колонка станет узкой. */
.screen-head__title {
  flex: 1 1 auto;
  min-width: 0;
  margin: 0;
  font-size: var(--text-xl);
  font-weight: var(--weight-bold);
}

/* Пометка шапки — это прямо роль `--text-xs` (см. `:root`), и `--text-sm` она
   занимала мимо неё. Мельче — заодно и полряда она забирает реже.

   `flex: 0 1 auto` вместо прежнего `0 0 auto`: пометка длиннее всей строки
   (бренд на 380px) при запрете сжатия выпирала бы за экран, а так она
   ужимается и переносится внутри себя. `text-align: right` снят — на общей
   строке её бокс и так прижат вправо `space-between`, а на своей строке он
   стоит слева, и выключка вправо развернула бы подпись в другую сторону. */
.screen-head__note {
  flex: 0 1 auto;
  min-width: 0;
  color: var(--hint);
  font-size: var(--text-xs);
}

.app-main {
  padding-top: var(--space-3);
  /* Высота панели плюс safe-area плюс воздух: последняя строка любого списка
     обязана быть видна над панелью, а не под ней (R06.4). */
  padding-bottom: calc(var(--tabbar-height) + env(safe-area-inset-bottom) + var(--space-4));
}

/* --- блоки --------------------------------------------------------------- */

/* Зазор между соседями списка один на весь файл — `--space-2` (R30.1).
   Его берут `.stack`, `.list`, `.rows` и `.stock-list`: до редизайна у них
   стояли 12, 6, 10 и 4, и четыре плотности на четырёх экранах читались как
   четыре разных приложения. Значение выбрано не средним: 8px — минимум, при
   котором обводка фокуса (`outline-offset: 2px` с обеих сторон, то есть 8px
   между соседями) не наползает на соседнюю строку. */
.stack {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}

/* Скругления разложены по вложенности, и это всё, чем они различаются
   (R28.3): `--radius-lg` — карточка верхнего уровня и сообщение, `--radius-md`
   — строка и вложенный блок внутри карточки, `--radius-sm` — контрол (поле,
   кнопка, вкладка) и мелкая ячейка остатка, `--radius-pill` — плашка. Радиус,
   выбранный не по вложенности, а по вкусу, и есть те самые «10 хардкодом в
   пяти местах». */
.card {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  padding: var(--space-3);
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}

.card__title {
  margin: 0;
  font-size: var(--text-lg);
  font-weight: var(--weight-semibold);
}

/* Подзаголовок подтягивается к своему заголовку: `.card` разводит детей на
   `--space-2`, а подпись под названием — часть названия, и стоять она должна
   на `--space-1`. Отрицательный отступ поэтому считается из тех же двух
   ступеней, а не подбирается на глаз: поменяется зазор карточки — подтяжка
   поменяется вместе с ним. */
.card__subtitle {
  margin: calc(var(--space-1) - var(--space-2)) 0 0;
  color: var(--subtitle);
  font-size: var(--text-sm);
}

.card__note {
  margin: 0;
  color: var(--hint);
  font-size: var(--text-sm);
}

/* --- остатки ------------------------------------------------------------- */

.facts {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--space-2);
  margin: 0;
}

/* Витрина остатка — в один столбец, а не в два (R01, R02).
   Половина строки на 380px — это около 137px, а «11 поддонов 79 упаковок 7 шт»
   полными словами занимает под 200px. Пока величина была строкой текста, она
   молча ложилась в две-три строки внутри ячейки; узлом она неразрывна
   (`.qty`), и та же ячейка стала бы её распирать — то есть ровно тем, из-за
   чего переделан главный экран, только этажом ниже.

   Сокращения здесь не выход: короткий словарь дал бы «11 подд 79 упак 7 шт»,
   а это те же ~140px — в 137 не влезает и он. Значит, дело не в словаре, а в
   ширине ячейки: величине нужна вся строка, и она её получает.

   Модификатор, а не правка `.facts`: витрину показывают ещё два экрана —
   заказ (`facts--triple`) и сторона взаиморасчётов, — и там в ячейках стоят
   короткие суммы, которым две колонки в самый раз. */
.facts--stacked {
  grid-template-columns: minmax(0, 1fr);
}

@media (min-width: 900px) {
  .facts--debt {
    grid-template-columns: repeat(3, minmax(0, 1fr));
  }
}

.facts__item {
  min-width: 0;
  background: var(--bg);
  border-radius: var(--radius-sm);
  padding: var(--space-2) var(--space-3);
}

.facts dt {
  color: var(--hint);
  font-size: var(--text-sm);
}

.facts dd {
  margin: var(--space-1) 0 0;
  font-size: var(--text-lg);
  font-weight: var(--weight-semibold);
}

/* Отрицательное «Доступно»: не ошибка счёта, но и не рядовое число —
   выделяется вместе с подписью, объясняющей, что это значит. */
.facts__item--warning {
  background: color-mix(in srgb, var(--danger) var(--tint), transparent);
}

.facts__item--warning dd {
  color: var(--danger);
}

/* Подпись под величиной. Красной она была, пока говорила одно — про нехватку;
   теперь она говорит ещё и итог упаковок (ADR 0069), а это спокойное число, и
   красный на нём означал бы проблему там, где её нет. Красный вернулся туда,
   где и был, — в ячейку, помеченную как проблемная. */
.facts__caption {
  display: block;
  margin-top: var(--space-1);
  font-size: var(--text-xs);
  font-weight: var(--weight-regular);
  line-height: 1.25;
  color: var(--hint);
}

.facts__item--warning .facts__caption {
  color: var(--danger);
}

/* Главное число витрины остатков — «Доступно»: продавать или нет решают по
   нему, поэтому оно занимает всю ширину карточки, набрано крупнее соседей и
   выделено цветом. «На складе» и «В резерве» стоят под ним обычными ячейками
   и объясняют, откуда доступное взялось. Три ячейки при этом остаются в
   двух колонках и помещаются в 390px без горизонтальной прокрутки. */
.facts__item--lead {
  grid-column: 1 / -1;
}

.facts__item--lead dd {
  font-size: var(--text-hero);
  color: var(--link);
}

/* Величина внутри главного числа берёт кегль ячейки, а не свой (R37i).
   `.qty__value` набран `--text-base` ради строки списка, где число стоит
   вровень с названием и спорить с ним за внимание не должно; здесь число одно
   на экран, соревноваться ему не с чем, и мельчить его незачем.

   `inherit`, а не вторая запись `--text-hero`: ступень эта ровно одна и живёт
   она строкой выше — на ячейке, которая и есть «главное число карточки
   товара». Написать её здесь второй раз значило бы завести второе место, где
   её правят, и первое же расхождение прошло бы молча.

   Штучный хвост правило обходит СТОРОНОЙ, а не проигрывает ему: `:not()`
   исключает хвостовую часть из самого селектора, и `--text-xs` у «11 шт»
   (R11) держится тем, что его никто не переопределял. Написанное в лоб
   (`.facts__item--lead .qty__value`) весило бы ровно столько же, сколько
   `.qty__part--tail .qty__value`, и хвост оставался бы мелким только потому,
   что блок `.qty*` лежит ниже по файлу; компонентный блок однажды подвинут к
   соседям — и хвост молча станет 26px, чего не увидят ни сторож набора, ни
   проверка вёрстки. */
.facts__item--lead .qty__part:not(.qty__part--tail) .qty__value {
  font-size: inherit;
  font-weight: var(--weight-bold);
}

/* Отрицательное «Доступно» остаётся красным: «продавать нечем» важнее, чем
   «это главное число экрана». Селектор из двух классов, чтобы порядок правил
   в файле ничего не решал. */
.facts__item--lead.facts__item--warning dd {
  color: var(--danger);
}

.card__note--warning {
  color: var(--danger);
}

/* --- величина количества --------------------------------------------------- */

/* Узел `quantity-view.js`: «11 подд 79 упак 7 шт», набранное тремя кеглями.
   Одной строкой текста этого не сказать — строка набирается одним кеглем
   целиком, — и ровно поэтому величина здесь узел, а не строка.

   Перенос запрещён при любом содержимом (R02): `nowrap` держит текст внутри
   частей, а ряд частей не переносится сам, потому что это флексбокс без
   `flex-wrap`. Если величина всё же не помещается, сокращаются СЛОВА —
   лестницу словарей ведёт `quantity-view.js`, и вёрстка о ней не знает.

   Пробелов между числом и словом в разметке нет ни одного: их даёт `gap`.
   Текст в узлы попадает через `textContent`, и пробел, поставленный там,
   был бы частью самого значения.

   Зазоров два, и они разные ступени намеренно: число и его единица — одно
   целое (`--space-1`, «внутри мелкого узла»), а части стоят соседями
   (`--space-2`, «между соседями»). Сделай их равными — и «11 подд 79 упак»
   прочитается как четыре слова подряд, то есть ровно как строка текста, ради
   ухода от которой величина и стала узлом. */
.qty {
  display: inline-flex;
  align-items: baseline;
  gap: var(--space-2);
  white-space: nowrap;
}

.qty__part {
  display: inline-flex;
  align-items: baseline;
  gap: var(--space-1);
}

/* Число — кеглем и весом названия строки (R13): различаются они цветом и
   местом, а не размером. Цвет здесь не задан вовсе — его ставит тот, кто
   величину показывает: приглушённый в норме, красный при нехватке.

   Раскладка та же, что у главного числа списка (`.list__amount`/`.row__badge`,
   §1a), и объявлена она здесь отдельно намеренно: величина — узел, а не
   строка, и живёт эта пара ступеней вместе с его устройством. Правишь ступень
   здесь — правь и в паре: разойдись они, склад и заказы стали бы двумя
   разными списками. */
.qty__value {
  font-size: var(--text-base);
  font-weight: var(--weight-medium);
}

/* Единица измерения — мельче числа и обычным весом (R15): она подпись к
   числу, а не второе число рядом. */
.qty__unit {
  font-size: var(--text-sm);
  font-weight: var(--weight-regular);
}

/* Штучный хвост — самым мелким кеглем строки, и число, и слово (R11). Это
   остаток старых записей, не кратный упаковке: он обязан быть виден, но
   спорить с поддонами и упаковками ему не за что. */
.qty__part--tail .qty__value,
.qty__part--tail .qty__unit {
  font-size: var(--text-xs);
}

/* --- склад: панель и строка товара ---------------------------------------- */

/* Поиск и действие в одном ряду (R25.1): отдельная строка под «+ приёмка»
   стоила бы одной строки списка из шести видимых. Поле тянется, кнопка держит
   свою ширину. */
.toolbar {
  display: flex;
  align-items: center;
  gap: var(--space-2);
}

.toolbar .input {
  flex: 1 1 auto;
  min-width: 0;
}

.toolbar .button {
  flex: 0 0 auto;
}

/* Список склада берёт тот же зазор, что и все прочие списки: своих 4px у него
   больше нет (R30.1). Шесть строк на экран от этого не пострадали — строка
   держит 72px, и разница в четыре пикселя на зазоре стоит четверти строки на
   шести, а не строки. Взамен обводка фокуса перестала наползать на соседа. */
.stock-list {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}

/* Строка товара — один узел примерно в 72 px (R14, R15), и читается он сверху
   вниз (R04): первой строкой — что за товар, второй — сколько его. Нажимается
   строка целиком, а не кнопка внутри неё: попасть пальцем в цель размером со
   строку легко, в кнопку внутри — нет.

   Колонка, а не ряд, и это главная правка строки. Прежняя раскладка разносила
   название и число по краям (`justify-content: space-between`), отдавая числу
   треть ширины; на 380px длинная величина в эту треть не влезала — «шт»
   уезжал на второй этаж колонки, а название, выровненное по СЕРЕДИНЕ ряда,
   повисало между этажами и у каждой строки стояло на своей высоте (R03, R06).
   Друг под другом название и величина за ширину не спорят вовсе: величине
   достаётся вся строка, а положение названия от длины числа не зависит ни на
   пиксель.

   `justify-content: center` центрирует по вертикали ПАРУ строк целиком — это
   не прежнее `align-items: center`, из-за которого название и болталось: та
   центровка равняла название по чужой высоте, эта ставит на место весь блок
   разом. Две строки текста короче 72px, и прижимать их к верхнему краю
   незачем; сами 72px остаются — цель нажатия меньше делать нельзя. */
.stock-row {
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: var(--space-1);
  min-height: 72px;
  padding: var(--space-2) var(--space-3);
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  background: var(--surface);
  color: var(--text);
  text-decoration: none;
}

/* Первая строка: название и бренд следом за ним, оба прижаты влево (R06).
   `min-width: 0` — то, чем длинное название удерживается внутри строки
   (R16.1): без него flex-ребёнок не сжимается уже своего содержимого, и
   строка уводит вбок весь экран. */
.stock-row__main {
  display: flex;
  align-items: baseline;
  gap: var(--space-1);
  min-width: 0;
}

/* Обе половины первой строки обрезаются многоточием, и объявлено это ОДИН раз
   на обе (R01.1): четыре строчки обрезки, набранные по месту дважды, — уже то
   самое второе вхождение, после которого в этом проекте заводят общее.

   Общим классом-утилитой («.ellipsis» на весь файл) это не сделано намеренно:
   двух одинаковых обрезок в `app.css` до сих пор не было вовсе. `.tabbar__label`
   обходится без `nowrap` (подпись пункта — одно слово, переносить нечего), а
   `select.input` — без `overflow: hidden` (нативный `<select>` режет сам).
   То есть общей у трёх мест была бы не обрезка, а только слово «многоточие»,
   и утилита свела бы вместе три разных решения одной задачи. Появится
   четвёртое место с ровно этим набором — тогда и заводить.

   Запрет переноса здесь несущий: без `nowrap` длинный текст просто ляжет
   вторым этажом (`overflow-wrap: anywhere` у `body` рвёт его хоть по букве),
   обрезать станет нечего, а строка вырастет вдвое. */
.stock-row__name,
.stock-row__brand {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* Главное слово строки — то, по чему товар ищут глазами (R13): кегль и вес те
   же, что у величины под ним, а различаются они цветом и местом (R14). Прежде
   название было полужирным при вдвое более крупном числе справа — то есть
   проигрывало взгляду ровно то, с чего чтение начинается.

   Раскладка та же, что у главного слова списка (`.list__title`/`.row__name`,
   §1a), и стоит она здесь отдельной строкой намеренно: имя склада объявлено
   вместе со своей обрезкой многоточием и отдельно от неё не живёт. Правишь
   ступень здесь — правь и в паре, и наоборот: сюда полезут первыми, это
   главный экран. */
.stock-row__name {
  font-size: var(--text-base);
  font-weight: var(--weight-medium);
}

/* Бренд — подпись при названии (R16), и место он уступает первым: сжатие в
   флексбоксе идёт пропорционально «коэффициент × длина», и без перевеса
   первым обрезалось бы как раз длинное название, а короткий бренд стоял бы
   целым. Порядок нужен обратный: по названию товар ищут, бренд его почти
   всегда повторяет.

   Уступает — но не исчезает. Два числа здесь работают в паре, и по отдельности
   каждое неверно:

     `flex-shrink` — перевес, а не точная пропорция: важно, что бренд отдаёт
     место заметно охотнее названия, а не во сколько именно раз. Бесконечного
     перевеса тут быть не должно — при нём бренд схлопывается в ноль, и первая
     строка теряет половину того, чем она объявлена («название и бренд»);

     `min-width` — пол, ниже которого не сжимает уже никакой перевес. Несколько
     знаков — это ровно та ширина, на которой `text-overflow` выше начинает
     показывать «· Сер…» вместо пустоты: обрезок сообщает, что бренд у товара
     есть и какой примерно, а целиком он виден на карточке товара, куда строка
     ведёт.

   Разделитель печатается вместе с брендом, а не отдельным узлом до него
   (`screens/stock.js`): у товара без бренда не остаётся и «· » (R01.2). */
.stock-row__brand {
  flex-shrink: 8;
  min-width: 7ch;
  font-size: var(--text-xs);
  color: var(--subtitle);
}

/* Вторая строка: величина и подпись состояния рядом, обе от левого края.
   Переносится здесь только подпись — величина внутри себя неразрывна
   (`.qty`), и `flex-wrap` уводит вниз её соседку, а не «шт» (R02). Предела
   ширины у строки больше нет и не нужно: делить её не с кем.

   Зазор здесь на ступень шире, чем между частями величины (`--space-2` внутри
   `.qty`), и это несущее: на равном подпись читалась бы четвёртой частью
   тройки — «11 шт в резерве» вместо «11 шт · в резерве». Ступень та же, что
   разделяет левую и правую половины любой строки в этом файле.

   Прежняя правая колонка звалась так же и держала `max-width: 65%` — тот
   предел и был причиной жалобы: величина в него не влезала и ломалась надвое,
   а названию оставалась треть строки. */
.stock-row__value {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--space-3);
  min-width: 0;
}

/* Величина — приглушённая (R05, R14): кегль и вес у неё те же, что у названия
   над ней, и различает их только цвет. Кегль здесь не задан вовсе — его
   ставит `.qty__value`/`.qty__unit`, одинаково во всех списках; экрану
   остаётся цвет. */
.stock-row__count {
  color: var(--hint);
}

/* Красный — проблема: доступного меньше порога или меньше нуля (R31).
   Оранжевой величина не становится никогда — оранжевый живёт в подписи. */
.stock-row__count--danger {
  color: var(--danger);
}

/* Подпись состояния — 12px у всех четырёх состояний, а цвет разный, и
   противоречия здесь нет (spec.md §3): размер говорит «это подпись», цвет —
   что именно она сообщает. Серое «не хватает» сообщать бы перестало, а
   красный в этом наборе носит проблему и только её (ADR 0050) — отменить его
   требованием про размер нельзя. Рядом с цветом всегда стоит слово (R16.1). */
/* Итог упаковок — та же величина одним числом (ADR 0069). Кегль между цифрами
   разложения (`--text-base`) и подписью состояния (`--text-xs`): это
   по-прежнему количество, а не пояснение, но главным числом строки остаётся
   разложение.

   `nowrap` — десятый в файле и той же природы, что остальные девять: «= 607
   упак» не должно рваться между числом и словом, а перенос соседей строке
   разрешён — `.stock-row__value` над ним `flex-wrap: wrap`. */
.stock-row__total {
  font-size: var(--text-sm);
  color: var(--hint);
  white-space: nowrap;
}

.stock-row__caption {
  font-size: var(--text-xs);
  color: var(--hint);
}

.stock-row__caption--danger {
  color: var(--danger);
}

/* Оранжевый — внимание, а не проблема: резерв это норма работы (R31). */
.stock-row__caption--warning {
  color: var(--warning);
}

/* Стоимость остатка — справка при количестве, а не третья величина строки
   (просьба владельца от 2026-09-08): ступень подписи и её же цвет, то есть тот
   самый «второй план». Крупнее её набирать нельзя — деньги перетянули бы на
   себя взгляд с названия, ради которого строку и переделывали.

   Своей ступени, своего отступа и своего скругления узел не заводит вовсе:
   геометрию второй строки держит `.stock-row__value`, зазор между частями —
   её `gap`.

   `white-space: nowrap` здесь НЕ нужен, и это не упущение: сумму печатает
   `format.rubles`, а у неё неразрывны и пробелы тысяч, и пробел перед «₽» —
   строка «12 480 000 ₽» не рвётся сама по себе. Десятый `nowrap` файла
   остаётся последним. */
.stock-row__money {
  font-size: var(--text-xs);
  color: var(--hint);
}

/* --- формы --------------------------------------------------------------- */

.field {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  min-width: 0;
}

.field__label {
  color: var(--hint);
  font-size: var(--text-sm);
}

/* Внутренний отступ всего нажимаемого один — `--space-3` по горизонтали
   (R30.3, R30.4): до редизайна вкладка держала 10, поле 12, кнопка 16, и
   ряд «поле и кнопка рядом» (`.toolbar`, `.month`) стоял ступенькой. По
   вертикали у поля `--space-2`, но решает не он, а `min-height`: цель
   нажатия обязана остаться 44px даже на пустом однострочном поле. */
.input {
  width: 100%;
  min-width: 0;
  min-height: var(--tap-target);
  padding: var(--space-2) var(--space-3);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--surface);
  color: var(--text);
  /* Минимум, при котором Safari на iOS не зумит страницу при фокусе. */
  font-size: var(--text-input);
  font-family: inherit;
}

/* Поле даты на iPhone (истории 66-68; R37, R38).
   `width: 100%` выше на нём не срабатывает: Safari на iOS даёт нативному
   `input[type=date]` собственную внутреннюю ширину, и пока нативная отрисовка
   не снята, поле не сжимается под родителя — правый край уходит за карточку.
   Проверено владельцем вживую на приёмке; чинится здесь, а не на экране, чтобы
   правило действовало на все пять мест, где поле даты создаётся (приёмка,
   расход, платёж, отгрузка, курс), включая шестое, которого ещё нет.
   `max-width` и `min-width` держат его внутри и flex-строки, и ячейки грида:
   у flex-ребёнка `min-width` по умолчанию `auto`, то есть «не уже содержимого»,
   а содержимое здесь — нативный виджет.
   Остальное — `box-sizing: border-box` у всех элементов, `min-width: 0` у
   `.field` и `minmax(0, …)` у `.row__pair` — уже стояло; недостающим звеном
   была именно `appearance`. */
.input[type='date'] {
  -webkit-appearance: none;
  appearance: none;
  max-width: 100%;
  min-width: 0;
}

/* Тем же правилом — выборы `monthPicker` и все прочие `select` формы. У
   `<select>` нативная ширина считается по самому длинному варианту («Сентябрь»,
   имя товара целиком), и без этого он раздвигает свою колонку изнутри. Текст
   при этом обрезается многоточием, а не переносится: строка выбора одна.
   Год в `monthPicker` от этого не страдает — `.row__pair` уже отдаёт ему свои
   2fr, а обрезаться в «2026» нечему. */
select.input {
  max-width: 100%;
  min-width: 0;
  text-overflow: ellipsis;
}

.input:focus {
  outline: 2px solid var(--link);
  outline-offset: -1px;
}

/* Многострочное поле начинается с однострочного плюс ещё строка: заметку к
   бою пишут в две-три строки, и поле, открывающееся ровно в одну, каждый раз
   приходится тянуть. Считается от цели нажатия, а не задаётся числом. */
textarea.input {
  min-height: calc(var(--tap-target) + var(--space-4));
  resize: vertical;
}

.rows {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}

.row {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  padding: var(--space-2) var(--space-3);
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  background: var(--bg);
}

.row__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-2);
}

.row__title {
  color: var(--hint);
  font-size: var(--text-sm);
  font-weight: var(--weight-semibold);
  text-transform: uppercase;
  letter-spacing: 0.04em;
}

/* --- кнопки -------------------------------------------------------------- */

/* Одна кнопка на весь проект (R30.3): одна высота (`--tap-target`), один
   внутренний отступ (`--space-3`, тот же, что у поля и у вкладки) и одно
   скругление (`--radius-sm`, тоже общее с полем). Модификаторы ниже меняют
   только цвет и кегль надписи — ни один из них не переопределяет ни padding,
   ни border-radius, ни высоту, и заводить такой модификатор нельзя: кнопка,
   выбивающаяся из ряда, и есть та «разница между экранами», ради которой
   редизайн затевался. */
.button {
  min-height: var(--tap-target);
  min-width: var(--tap-target);
  padding: 0 var(--space-3);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--surface);
  color: var(--link);
  font: inherit;
  font-weight: var(--weight-semibold);
  cursor: pointer;
}

.button:disabled {
  opacity: 0.6;
  cursor: default;
}

.button--primary {
  background: var(--link);
  border-color: var(--link);
  color: var(--link-text);
}

/* Кнопка с одним знаком вместо надписи — «×» удаления строки. Своего
   `padding: 0` у неё больше нет: знак узкий, и общий `--space-3` с обеих
   сторон вместе с ним не дотягивает до `min-width: var(--tap-target)`, то
   есть ширина всё равно приходит от цели нажатия — прежние 44px, только
   теперь одним правилом на все кнопки, а не исключением. */
.button--icon {
  font-size: var(--text-xl);
  line-height: 1;
  color: var(--danger);
}

/* Ссылка, выглядящая кнопкой: из тупика «нечего принимать» владелец уходит
   на экран справочника, и нажимается этот выход так же, как обычная кнопка. */
a.button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  text-decoration: none;
}

/* --- фокус --------------------------------------------------------------- */

/* Видимый фокус у всего нажимаемого. До редизайна он стоял ровно у одного
   элемента — `.input`, — и владелец, добравшийся до приложения с клавиатуры
   или с внешнего управления, не знал, где находится.

   `:focus-visible`, а не `:focus`: браузер сам решает, показывать ли обводку,
   и после нажатия пальцем она не остаётся висеть на кнопке. У `.input` выше
   намеренно остался `:focus` — текстовое поле показывает фокус и по нажатию
   тоже, иначе непонятно, куда пойдёт набранное.

   Отбор — по признаку нажимаемости, а не по списку классов, и это несущее:
   список пришлось бы дописывать при каждом новом нажимаемом классе, а забыть
   его нечем — автотестов у CSS в проекте нет вовсе. Признак покрывает всё:
   нажимаемое в этом приложении — либо `<button>`, либо `<a href>`, третьего
   нет (нативный `confirm()` не зовётся, обработчиков на `div` нет). Заодно
   отбор точнее прежнего: `.list__item--static` — неподвижная строка курсов,
   собранная `<div>`, — обводки не получает, потому что и фокус на неё не
   встаёт.

   `outline-offset: 2px` уводит обводку наружу элемента, и это тоже несущее: у
   `.button--primary` и `.tab--active` фон — сам `--link`, и обводка того же
   цвета внутри границы была бы невидима. Наружу ей есть куда лечь: зазор
   между соседями списка — `--space-2`, то есть 8px, ровно вдвое больше
   вылета. */
button:focus-visible,
a[href]:not(.tabbar__link):focus-visible {
  outline: 2px solid var(--link);
  outline-offset: 2px;
}

/* Пункт нижней панели — единственное исключение по направлению вылета: панель
   прижата к краю окна, снаружи у неё места нет вовсе, и обводка уходит внутрь.
   Пункты при этом не соприкасаются — каждый занимает пятую часть ширины.
   Из общего правила он вычтен явно (`:not`), а не перекрыт удельностью:
   перекрытие здесь зависело бы от порядка двух правил в файле. */
.tabbar__link:focus-visible {
  outline: 2px solid var(--link);
  outline-offset: -2px;
}

/* --- сообщения ----------------------------------------------------------- */

.notice {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  padding: var(--space-3);
  margin: 0;
}

.notice--error {
  border-color: var(--danger);
}

/* Заголовок сообщения — тот же заголовок карточки, что и `.card__title`:
   одна ступень кегля и одна ступень веса. Вес назван явно, потому что узел
   это `<h2>`, и без него сюда приходил жирный шрифт из таблицы браузера —
   вес мимо шкалы, которого сторож набора не видит: числа в файле нет. */
.notice__title {
  margin: 0 0 var(--space-1);
  font-size: var(--text-lg);
  font-weight: var(--weight-semibold);
}

.notice__hint,
.notice__detail {
  color: var(--hint);
  font-size: var(--text-sm);
}

.notice__detail {
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: var(--text-xs);
}

.rate {
  margin: 0;
  font-size: var(--text-sm);
  color: var(--hint);
}

.rate--missing {
  color: var(--danger);
  font-weight: var(--weight-semibold);
}

/* Предупреждение рядом со ссылкой «К курсам»: отдельной строкой и без
   нажимаемости — это подпись к переходу, а не второй переход. */
.rate__warning {
  display: block;
  color: var(--hint);
  font-weight: var(--weight-regular);
  font-size: var(--text-sm);
}

.status {
  margin: 0;
  font-size: var(--text-base);
  min-height: 1.45em;
}

.status--error {
  color: var(--danger);
  font-weight: var(--weight-semibold);
}

.status--ok {
  color: var(--success);
  font-weight: var(--weight-semibold);
}

.status--pending {
  color: var(--hint);
}

/* --- списки, вкладки и детали (заказы, отгрузка, история) ----------------- */

.list {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}

.list__group {
  display: flex;
  flex-direction: column;
}

/* Строка списка — и ссылка (заказ), и кнопка (раскрытие детали в истории):
   выглядят одинаково, потому что и нажимаются одинаково. */
.list__item {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  width: 100%;
  min-height: var(--tap-target);
  padding: var(--space-2) var(--space-3);
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  background: var(--bg);
  color: var(--text);
  font: inherit;
  text-align: left;
  text-decoration: none;
  cursor: pointer;
}

/* Строка справочника без правки (курсы): раскрывать нечего — и курсор об
   этом не врёт. */
.list__item--static {
  cursor: default;
}

.list__main {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  min-width: 0;
}

/* Главное слово строки — имя клиента, сторона, номер документа, название
   справочной записи (R13, §1a). Кегль и вес те же, что у числа строки, а
   различаются они цветом и местом (R14): слово — `--text` из `.list__item`,
   число — `--hint`. Полужирным оно было при таком же полужирном числе
   рядом — двое соревновались, и выигрывал тот, кто правее.

   Кегль назван явно, хотя `.list__item` и наследует его от `body`: строка
   списка обязана читаться одинаково во всех пяти списках, а не пока никто не
   тронул `font` у родителя.

   Селекторов два, а правило одно: `.row__name` — то же главное слово в строке
   формы и в карточке расхода, и совпадать они обязаны всегда, а не пока три
   таска подряд пишут одно и то же двумя копиями. Третье такое место —
   `.stock-row__name`; к этой паре оно не присоединено намеренно, потому что
   объявлено вместе со своей обрезкой многоточием и отдельно от неё не живёт.

   `margin: 0` — не косметика: класс лежит и на `<span>` строки списка, и на
   `<p>` записи расхода, а браузерный отступ абзаца ступенью шкалы не
   является и в колонку карточки, разведённую своим `gap`, не входит. */
.list__title,
.row__name {
  margin: 0;
  font-size: var(--text-base);
  font-weight: var(--weight-medium);
}

/* Главное число строки — сумма заказа, сумма отгрузки, прибыль месяца, сумма
   расхода (§1a). Кегль и вес те же, что у слова слева (R13), различает их
   цвет: слово `--text`, число `--hint`. Полужирным оно было ровно там, где
   спорило с названием, — теперь не спорит.

   Селекторов снова два по той же причине: `.row__badge` — это же число в
   строке формы и в карточке расхода. Правило одно, и разъехаться им нечем.
   Третье такое место — `.qty__value`: к паре оно не присоединено потому же,
   почему `.stock-row__name` не присоединено к паре выше, — объявлено вместе
   с устройством узла величины. */
.list__amount,
.row__badge {
  flex: 0 0 auto;
  color: var(--hint);
  font-size: var(--text-base);
  font-weight: var(--weight-medium);
}

/* Число не переносится нигде: `format.groupThousands` разделяет тысячи
   обычным пробелом, и «1 875 430,55 ₽», разорванное по нему, читается как два
   разных числа — а проверка вёрстки перенос по устройству не видит вовсе.
   Правило отдельным объявлением, а не строкой в раскладке выше: там — кегль,
   вес и цвет, здесь — поведение, и `.row__badge` его берёт не весь, а
   модификаторами (`--amount`, `--markup`).

   Единственное исключение названо у `.list__amount--phrase`. */
.list__amount {
  white-space: nowrap;
}

/* Вторая строка — дата, номер, комментарий, поставщик: это подпись, а не
   текст (R17.1), и `--text-xs` прямо назван её ролью в `:root`. `margin: 0` —
   по той же причине, что у главного слова: класс лежит и на `<span>`, и на
   `<p>` карточки расхода. */
.list__sub {
  margin: 0;
  color: var(--subtitle);
  font-size: var(--text-xs);
}

.list__chevron {
  flex: 0 0 auto;
  color: var(--hint);
}

.list__item[aria-expanded="true"] .list__chevron {
  transform: rotate(180deg);
}

.detail {
  padding: var(--space-2) var(--space-3) var(--space-1);
}

.detail__body {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}

.detail__section {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
}

.detail__title {
  margin: 0;
  color: var(--hint);
  font-size: var(--text-sm);
  font-weight: var(--weight-semibold);
  text-transform: uppercase;
  letter-spacing: 0.04em;
}

/* Строка детали: подпись слева, значение справа. Переносится целиком — тем
   же приёмом и по той же причине, что шапка экрана. Правая половина не всегда
   короткое число: в карточке товара туда уходит «в упаковке 12 шт · на
   поддоне 80 упаковок», и подписи оставалось шесть знаков — «Упаков / ка».
   Переполнения при этом нет, и проверка вёрстки такую строку не видит.
   Не помещаются вдвоём — значение уходит под подпись целой строкой. */
.detail__row {
  display: flex;
  flex-wrap: wrap;
  justify-content: space-between;
  gap: var(--space-1) var(--space-3);
  font-size: var(--text-base);
}

.detail__label {
  min-width: 0;
}

/* `flex: 0 1 auto`, а не `0 0 auto`: значение обязано уметь сжиматься.
   Правая половина строки — не всегда короткое число: в «Истории» туда уходит
   заметка к бою («12 шт · разбилось при погрузке, поддон завалился»), а её
   пишет пользователь и длину её никто не ограничивает. Нешринкующееся значение
   на 390px выдавливало бы строку за карточку — ровно то переполнение, от
   которого `html { overflow-x: hidden }` стоит страховкой, а не лечением.
   Ужимается при этом сначала подпись слева (у неё тот же `flex-shrink`, но
   меньше базовая ширина), перенос внутри слова даёт `overflow-wrap: anywhere`
   у `body`, а выравнивание вправо остаётся: короткие значения по-прежнему
   стоят столбиком. */
.detail__value {
  flex: 0 1 auto;
  /* Прижимает значение вправо и на своей строке тоже: `space-between` держит
     его у правого края, только пока строка делится на двоих. */
  margin-left: auto;
  min-width: 0;
  color: var(--hint);
  text-align: right;
}

/* --- переключатели: вкладки и кнопки периода ------------------------------- */

/* Переключателей в приложении два вида, и выглядят они **одинаково**: вкладка
   («Заказы / История» у роутера, «Приёмки / Отгрузки» внутри истории) и кнопка
   периода («Сегодня · Месяц · Год · Все» над списком). Общий вид объявлен
   здесь один раз на оба селектора — не ради краткости, а потому, что
   совпадение обязано держаться само: две одинаковые копии, стоящие рядом,
   расходятся ровно тогда, когда одну из них правят, и никто этого не замечает,
   пока не сравнит два экрана подряд. Тем же приёмом и по той же находке ревью
   сведены раскладки строки списка (`.list__title, .row__name`).

   Различаются переключатели тем, что несёт смысл, и только им (см. своё у
   каждого ниже): вкладка занимает равную долю ряда и держит полную цель
   нажатия — она главный выбор экрана; кнопка периода компактна, потому что
   стоит третьим контролом подряд над списком, и ряд в 44px отодвинул бы первую
   строку списка ещё на строку вниз.

   Кегль и вес у обоих — из шкалы, но ни одна её роль этот вид не называет:
   роли не описывают хром (решено в таске 04, там же стоят вне ролей вес
   заголовка экрана и кегль глифов). */
.tab,
.periods__button {
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--surface);
  color: var(--hint);
  font: inherit;
  font-weight: var(--weight-semibold);
  cursor: pointer;
}

.tabs {
  display: flex;
  gap: var(--space-2);
}

/* Своё у вкладки: равная доля ряда и 44px цели нажатия. */
.tab {
  flex: 1 1 0;
  min-height: var(--tap-target);
  padding: 0 var(--space-3);
}

a.tab {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  text-decoration: none;
}

/* Ряд «Сегодня · Месяц · Год · Все» над списком заказов и над списком истории
   (R21-R23, R21.1). Стоит вплотную под полем поиска и над самим списком —
   рядом с поиском, как просит бриф, и в шаге от того, что он сужает.

   Своей строкой, а не внутри `.toolbar` с полем поиска: `.toolbar` — флексбокс
   без `flex-wrap`, и поле ввода с четырьмя кнопками в одной нерушимой строке
   на 380px не помещаются вовсе. На узком экране разницы нет никакой — там ряд
   и был бы второй строкой; на широком кнопки не встают справа от поля, и это
   единственная цена.

   Обещание у ряда другое, чем у вкладок, и в разметке это `aria-pressed`, а не
   `aria-selected` (`period.js`): вкладка меняет, ЧТО показано, период сужает
   ОДИН И ТОТ ЖЕ список. Общий с вкладкой у него вид, а не смысл.

   Свой `:focus-visible` не пишется: общее правило фокуса накрывает `button`. */
.periods {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
}

/* Своё у кнопки периода — ровно то, чем она обязана отличаться от вкладки.

   `flex: 1 0 auto` — растянуться по свободному месту, но не сжаться ни на
   пиксель: `overflow-wrap: anywhere` у `body` делает min-content любой надписи
   равным одному символу, и сжимающаяся кнопка ушла бы уже кегля своего слова.
   Вместе с `nowrap` и `flex-wrap` у родителя это значит, что не поместившаяся
   кнопка переезжает строкой ниже целиком, а не режет «Се / годня».

   32px — размер конкретного узла, а не ступень шкалы (как 72px строки склада):
   меньше — не попасть пальцем, больше — ряд перестаёт быть компактным. Ширина
   у кнопки при этом с запасом, и целиться приходится только по вертикали. */
.periods__button {
  flex: 1 0 auto;
  min-height: 32px;
  padding: var(--space-2) var(--space-3);
  font-size: var(--text-sm);
  white-space: nowrap;
}

/* Выбранное — залито `--link`, и это единственное, чем оно отличается. Одно
   правило на оба по той же причине, что и общий вид выше.

   Стоит **последним** в разделе намеренно: у всех правил здесь одна и та же
   специфичность (один класс), и спор между ними решает порядок в файле. Пока
   выбранное объявлено ниже, дописанный кому-нибудь `background` в собственное
   правило вкладки или кнопки перекрасит невыбранную, а не выбранную. */
.tab--active,
.periods__button--active {
  background: var(--link);
  border-color: var(--link);
  color: var(--link-text);
}

/* --- заказы --------------------------------------------------------------- */

/* Плашка состояния: короткое слово, которое не переносится — «Част. оплачен»,
   разорванное пополам, читается как два разных состояния. Стоит она в
   контейнерах с `flex-wrap: wrap`, поэтому переносится строка целиком, а не
   режется слово. */
.badge {
  flex: 0 0 auto;
  padding: var(--space-1) var(--space-3);
  border-radius: var(--radius-pill);
  background: var(--bg);
  color: var(--hint);
  font-size: var(--text-xs);
  font-weight: var(--weight-medium);
  white-space: nowrap;
}

.badge--new {
  background: color-mix(in srgb, var(--link) var(--tint), transparent);
  color: var(--link);
}

/* «В работе»: оранжевый — внимание, а не проблема (R31). Раньше он стоял
   хардкодом и требовал себе отдельного `@media (prefers-color-scheme: dark)`;
   теперь это токен `--warning`, у которого обе запасные пары уже свои. */
.badge--active {
  background: color-mix(in srgb, var(--warning) var(--tint), transparent);
  color: var(--warning);
}

.badge--done {
  background: color-mix(in srgb, var(--success) var(--tint), transparent);
  color: var(--success);
}

/* Отменённый заказ — приглушённая плашка, а не красная (§9, R32). Красный
   в этом файле занят проблемой: просрочкой, долгом, перебранным лимитом,
   нехваткой на складе — тем, где владельцу надо что-то сделать. Отмена
   сделана и делать по ней нечего; она уже названа словом «Отменён», и
   красить её значило бы красить понятное. Цвета своего у плашки поэтому
   нет вовсе — она берёт нейтральную пару `.badge`, названную здесь ещё раз
   явно, чтобы модификатор не зависел от порядка правил. */
.badge--muted {
  background: var(--bg);
  color: var(--hint);
}

/* Карточка внутри карточки: свой фон и скругление на ступень мельче — угол,
   повторяющий родительский, читается как отклеившийся край. Зазор между
   детьми она НЕ переопределяет: у `.card` он уже `--space-2`, и второе
   значение здесь было бы второй плотностью. */
.card--nested {
  background: var(--bg);
  border-radius: var(--radius-md);
}

.card__subtitle--section {
  margin: var(--space-1) 0 0;
  font-size: var(--text-sm);
  font-weight: var(--weight-semibold);
  text-transform: uppercase;
  letter-spacing: 0.04em;
}

.card__line {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  margin: 0;
  flex-wrap: wrap;
}

.facts--triple {
  grid-template-columns: repeat(3, minmax(0, 1fr));
}

.actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
}

.actions .button {
  flex: 1 1 auto;
}

/* Опасное действие: красной здесь надпись, а рамка — разведённая. Доля в
   `color-mix` тут НЕ `--tint`, и это намеренно: `--tint` разводит цвет до
   прозрачного и годится заливке плашки, а рамка смешивается с `--border`,
   то есть с уже видимой линией. На 14% она стала бы неотличима от обычной
   рамки, и «удалить» перестало бы отличаться от «сохранить». Смешение с
   рамкой в файле одно; появится второе — доля станет токеном, как стал им
   `--tint` на шести заливках. */
.button--danger {
  color: var(--danger);
  border-color: color-mix(in srgb, var(--danger) 40%, var(--border));
}

/* Внутристраничные ссылки-переходы: назад к списку заказов и от «курса на
   дату нет» — к экрану курсов. Обе набирают полную высоту нажатия, потому
   что попасть в них пальцем нужно с первого раза. */
.link-back,
.rate__link {
  display: inline-flex;
  align-items: center;
  min-height: var(--tap-target);
  color: var(--link);
  text-decoration: none;
  font-weight: var(--weight-semibold);
}

/* --- взаиморасчёты -------------------------------------------------------- */

/* Итог секции — вторая по важности вещь на экране после имени раздела,
   поэтому стоит в одной строке с заголовком и прижат к правому краю. */
.section__total {
  margin-left: auto;
  font-size: var(--text-lg);
  font-weight: var(--weight-bold);
  white-space: nowrap;
}

/* Пометки строки стороны: просрочка и перебранный лимит. Они прижаты к
   правому краю и переносятся, а не расталкивают имя клиента — на 390px имя
   важнее пометок и обрезаться не должно. */
.list__marks {
  flex: 0 1 auto;
  display: flex;
  flex-wrap: wrap;
  justify-content: flex-end;
  gap: var(--space-1);
}

.list__sub--warning {
  color: var(--danger);
}

/* Просрочка и перебранный лимит — то, на что смотрят в первую очередь, и
   единственное красное среди плашек. «Выключенное» состояние отменённого
   заказа (`badge--muted`) стоит рядом нейтральным: красный занят проблемой,
   с которой владельцу надо что-то делать, а с отменой делать нечего. */
.badge--danger {
  background: color-mix(in srgb, var(--danger) var(--tint), transparent);
  color: var(--danger);
}

/* --- форма отгрузки ------------------------------------------------------- */

/* Название товара в строке формы — в отличие от `.row__title` не набирается
   капсом: это данные пользователя, а не подпись интерфейса.

   Кегль и вес сюда не пишутся: это главное слово строки, и набрано оно одним
   правилом вместе с `.list__title` (§1a). Здесь остаётся только то, что
   принадлежит месту, — право сжиматься уже своего содержимого. */
.row__name {
  min-width: 0;
}

/* Подсказка строки формы — «в упаковке 12 шт · на поддоне 80 упаковок»,
   себестоимость, остаток позиции: справочный текст при поле, то есть роль
   `--text-sm`. Второй строкой СПИСКА она не является — там стоит
   `.list__sub` и `--text-xs`. */
.row__hint {
  color: var(--hint);
  font-size: var(--text-sm);
}

.row__pair {
  display: grid;
  grid-template-columns: minmax(0, 3fr) minmax(0, 2fr);
  gap: var(--space-2);
}

/* --- расходы и справочники (Этап 4) --------------------------------------- */

/* Флажок «в работе»/«работает» в форме справочника. Ширину `.input` он не
   наследует: растянутый на строку чекбокс выглядит полем ввода, которым не
   является. Нажимается при этом вся строка — контрол лежит внутри `<label>`,
   и высоту цели даёт поле вместе с подписью. */
.input--flag {
  /* Флажок считается от строки, в которой стоит, и больше ни от чего: строка
     `<label>` обязана остаться высотой в `--tap-target`, поля сверху и снизу
     берут по `--space-3`, квадрату достаётся остаток. Основание одно, и со
     значком нижней панели у флажка общего нет ничего — правка `--icon-size`
     контрол в семи формах справочников не двигает. Имя локальное: в `:root`
     ему не место, своего значения оно не вносит. */
  --flag-size: calc(var(--tap-target) - var(--space-3) * 2);
  width: var(--flag-size);
  min-width: var(--flag-size);
  height: var(--flag-size);
  min-height: var(--flag-size);
  margin: var(--space-3) 0;
  padding: 0;
  accent-color: var(--link);
}

/* Заголовок свёрнутого блока: выглядит заголовком карточки, а нажимается как
   строка списка — потому и `button`, а не `h2` с обработчиком. */
.fold {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  width: 100%;
  min-height: var(--tap-target);
  padding: 0;
  border: 0;
  background: none;
  color: var(--text);
  font: inherit;
  text-align: left;
  cursor: pointer;
}

.fold[aria-expanded="true"] .list__chevron {
  transform: rotate(180deg);
}

/* Шапка «‹ месяц ›» с итогом. Итог прижимается вправо своим `.section__total`,
   подпись месяца занимает середину и не переносится; на узком экране вся
   строка переносится целиком, а не режет число пополам. */
.month {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--space-1);
}

.month__label {
  flex: 1 1 auto;
  min-width: 0;
  text-align: center;
  font-size: var(--text-lg);
  font-weight: var(--weight-semibold);
  white-space: nowrap;
}

/* Стрелки месяца: та же кнопка, что и везде, — и без красного
   `.button--icon`, который значит «удалить». Ширину, как и у «×», задаёт
   `min-width: var(--tap-target)`: знак «‹» с общим внутренним отступом до
   неё не дотягивает, поэтому своего `padding: 0` стрелке больше не нужно. */
.button--step {
  flex: 0 0 auto;
  font-size: var(--text-xl);
  line-height: 1;
}

/* --- аналитика (Этап 5) --------------------------------------------------- */

/* Единственное место, где в главное число попадает не число, а фраза: сальдо
   стороны — «Мы должны 1 875 430,55 ₽», «Аванс 12 000 ₽», «Рассчитались»
   (`format.balanceText`). Запрет переноса ей не годится: строка стороны делит
   ширину с плашками («Просрочка: 2», «Лимит перебран»), и на 320px левой
   половине остаётся около трети экрана — неразрывная фраза уезжает за край
   строки, это замерено.

   Размен назван вслух: перенос может лечь и внутрь суммы («Должен 240 /
   000 ₽»). Терпимо это ровно здесь и ровно потому, что первым стоит слово
   («Должен», «Аванс»), и оно отвечает на вопрос строки даже тогда, когда
   цифры ушли на второй этаж; у голого числа такой опоры нет, и там перенос
   запрещён. Для настоящих чисел модификатор не заводить: строка, ради
   которой он взят, обязана начинаться со слова. */
.list__amount--phrase {
  white-space: normal;
}

/* Долг стороны во взаиморасчётах — то же главное число строки, только
   красное: красный здесь носит проблему, а аванс, переплата и
   «Рассчитались» остаются приглушёнными — платить по ним нечего. Модификатор
   свой, а не `amount--loss`: тот красит убыток отчёта, и назвать им долг
   клиента значило бы сказать про деньги неправду. Стоит он ниже своего
   базового класса — но уже не рядом с ним: после сведения раскладки в одно
   правило (§1a) база уехала на четыре сотни строк выше, в раздел строк
   списка. Порядок от этого несущим быть не перестал: специфичность у них
   одна, и спор решает файл — окажись `color` базового ниже, красный он
   перекрыл бы. */
.list__amount--danger {
  color: var(--danger);
}

/* Знак прибыли — цветом, но никогда только цветом: рядом всегда стоит слово
   («Прибыль» или «Убыток»), которое эти же классы и подкрашивают. Цвет без
   слова ничего не сообщает ни дальтонику, ни чёрно-белому скриншоту.
   Модификаторы общие для строки списка и для итога `.section__total` — оба
   красят один и тот же смысл.

   Хозяин назван в селекторе, а не подразумевается: у `.list__amount` выше
   теперь есть собственный приглушённый цвет, и одиночный `.amount--profit`
   перебивал бы его только тем, что лежит ниже по файлу. Тот же приём, что у
   `.badge--muted` и `.facts__item--lead.facts__item--warning`: модификатор не
   должен зависеть от порядка правил. */
.list__amount.amount--profit,
.section__total.amount--profit {
  color: var(--success);
}

.list__amount.amount--loss,
.section__total.amount--loss {
  color: var(--danger);
}

/* --- набор заказа и отгрузка (доработка) ---------------------------------- */

/* Счётчик «Выбрано позиций» и итог не должны уезжать вверх вместе со списком:
   заказ набирают, прокручивая сотню товаров, и «сколько уже набрал и на какую
   сумму» — то, ради чего строка вообще существует (бриф, R29.1). `sticky`
   прижимает её к верху окна; смещения ей не нужно — общей шапки у приложения
   нет вовсе, а панель разделов стоит внизу. Фон непрозрачный — иначе строки
   списка просвечивали бы сквозь число. */
.picker__summary {
  position: sticky;
  top: 0;
  z-index: 1;
  padding: var(--space-2) 0;
  background: var(--surface);
}

/* Денежный итог набора — в подвале панели, под последней позицией (R19,
   R20). Липкость здесь **усиление, а не механизм**: рядом стоит
   `html { overflow-x: hidden }`, из-за которого `position: sticky` в
   WebView может не сработать вовсе, и это же, скорее всего, и было причиной
   жалобы «итог не видно» — прежде он стоял в липкой шапке панели. Не
   сработает и снизу — итог всё равно там, где просили: сразу под позициями и
   над кнопкой, а не в другом конце формы.

   Отступ снизу — на всю панель разделов вместе с её safe-area: `bottom: 0`
   прилипал бы к краю окна, то есть **под** панель, и итог, ради видимости
   которого его сюда и переносили, снова оказался бы не виден. Правило общее
   для всякого прижатого к низу узла в этом файле; второго такого сейчас нет.

   Черта сверху отделяет его от последней строки набора: без неё итог читался
   бы как ещё одна строка товара. */
.picker__footer {
  position: sticky;
  bottom: calc(var(--tabbar-height) + env(safe-area-inset-bottom));
  z-index: 1;
  padding: var(--space-2) 0;
  border-top: 1px solid var(--border);
  background: var(--surface);
}

/* Тот же приём в карточке готового заказа: «Сумма заказа» и строка оплаты
   стоят под составом (R19.1). Липкости здесь нет — карточка не набирается, и
   прокручивать её, держа итог перед глазами, незачем. */
.card__line--footer {
  padding-top: var(--space-2);
  border-top: 1px solid var(--border);
}

/* Наценка в строке набора: стоит рядом с себестоимостью, под полем цены.
   Не переносится по той же причине, что сумма позиции: «+30 %», разорванное
   пополам, читается как два разных числа. Прижата к правому краю — так она
   встаёт в одну колонку с суммой строки над ней.

   От `.row__badge` отличается одним кеглем — на ступень мельче: наценка это
   пояснение к цене, а не второе главное число строки; сумму позиции в той же
   колонке она перекрикивать не должна. Цвет и вес не переопределяются вовсе
   — второе их место было бы вторым местом правки. */
.row__badge--markup {
  margin-left: auto;
  flex: 0 0 auto;
  font-size: var(--text-sm);
  white-space: nowrap;
}

/* Сумма позиции в строке набора: то же место, где в форме отгрузки стоит
   «осталось N упак». Не переносится по той же причине, что `.list__amount`:
   разорванное пополам число читается как два. */
.row__badge--amount {
  white-space: nowrap;
}

/* Строка «везём весь остаток целиком» под нажатой кнопкой (история 36а).
   Обычная подсказка строки говорит справочное — как упакован товар и почём
   уходит; эта говорит про **выбранное сейчас** состояние, и цветом отделена
   от справки: гаснущие рядом поля количества иначе читались бы как поломка,
   а не как решение владельца. */
.row__hint--active {
  color: var(--link);
  font-weight: var(--weight-semibold);
}

/* --- фотографии товара (прогон `public-catalog`, истории 78-81) ------------
   Список снимков в карточке товара: слева превью, справа то, что с ним можно
   сделать. Колонкой, а не сеткой плиток: у каждой строки две кнопки со
   словами, и на 380px плитка с ними под собой была бы шире экрана.  */
.photos {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}

.photo {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-2);
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
}

/* 96px — размер конкретного узла, а не ступень шкалы: превью должно быть
   узнаваемым и не отбирать место у двух кнопок рядом. `height: auto` вместе с
   атрибутами `width`/`height` из ответа держит пропорцию снимка и не даёт
   раскладке прыгнуть, когда картинка доедет. */
.photo__image {
  flex: 0 0 auto;
  width: 96px;
  height: auto;
  max-width: 96px;
  border-radius: var(--radius-sm);
  object-fit: cover;
}

/* Слово вместо снимка, пока хранилище не настроено: занимает то же место, что
   заняла бы картинка, — иначе строки списка стояли бы разной высоты и
   выглядели бы сломанными. */
.photo__missing {
  display: flex;
  flex: 0 0 auto;
  align-items: center;
  justify-content: center;
  width: 96px;
  min-height: var(--tap-target);
  border: 1px dashed var(--border);
  border-radius: var(--radius-sm);
  color: var(--hint);
  font-size: var(--text-sm);
  text-align: center;
}

/* Правая половина строки: «сделать главной» (или плашка «главная») и
   удаление со своим подтверждением. `min-width: 0` — то же, чем во всём
   приложении держится flex-ребёнок, которому нельзя распирать строку. */
.photo__side {
  display: flex;
  flex: 1 1 auto;
  min-width: 0;
  flex-direction: column;
  gap: var(--space-2);
  align-items: flex-start;
}

/* --- переключатель рядом со строкой справочника (истории 98, 99) -----------
   Верх строки: сама строка тянется, контрол держит свою ширину. Заводится
   только там, где контрол есть, — шесть справочников из семи отдают строку как
   отдавали, без обёртки. */
.list__lead {
  display: flex;
  align-items: center;
  gap: var(--space-2);
}

.list__lead .list__item {
  flex: 1 1 auto;
  min-width: 0;
}

/* `<label>` вокруг флажка, а не голый `input`: нажимать пальцем в квадрат
   16 px на телефоне нечем, и область нажатия обязана быть с саму строку
   высотой. Отступы те же, что у флажка в форме, — контрол один и тот же. */
.list__switch {
  display: flex;
  flex: 0 0 auto;
  align-items: center;
  justify-content: center;
  min-width: var(--tap-target);
  min-height: var(--tap-target);
  cursor: pointer;
}

/* Строка состояния под строкой справочника: пустая места не занимает. Своя
   высота у `.status` заведена ради формы, где сообщение не должно дёргать
   раскладку; в списке из сорока строк она растянула бы его вдвое, а место
   резервировать не для чего — сообщение появляется под той строкой, которую
   владелец только что тронул. */
.list__note .status {
  min-height: 0;
}

/* На компьютере те же разделы стоят слева; экраны и их две колонки
   по-прежнему получают ширину своего контейнера. Мобильные правила выше
   действуют до 899px без изменений. */
@media (min-width: 900px) {
  .tabbar {
    top: 0;
    right: auto;
    width: var(--sidebar-width);
    flex-direction: column;
    gap: var(--space-2);
    border-top: 0;
    border-right: 1px solid var(--border);
    padding: calc(env(safe-area-inset-top) + var(--space-5)) var(--space-3)
      calc(env(safe-area-inset-bottom) + var(--space-3));
  }

  .tabbar__link {
    flex: 0 0 auto;
    min-height: var(--tap-target);
    flex-direction: row;
    justify-content: flex-start;
    gap: var(--space-3);
    padding: var(--space-3);
    border-radius: var(--radius-sm);
    /* Подпись рядом со значком — основной текст навигации, а не подпись
       под ним: тот же кегль, что у строки списка. */
    font-size: var(--text-base);
  }

  .tabbar__link--active {
    background: color-mix(in srgb, var(--link) var(--tint), transparent);
  }

  .subnav,
  .app-main {
    width: calc(100% - var(--sidebar-width));
    max-width: var(--content-max);
    /* Центр оставшейся справа области; пока она уже предела, её левый
       край совпадает с краем панели. Внутренний отступ даёт воздух. */
    margin-left: max(var(--sidebar-width), calc((100% + var(--sidebar-width) - var(--content-max)) / 2));
    margin-right: auto;
    padding-left: var(--space-5);
    padding-right: var(--space-5);
  }

  .app-main {
    padding-bottom: var(--space-5);
  }

  .picker__footer {
    bottom: 0;
  }

  /* До входа навигация спрятана роутером: форме не нужен пустой участок
     слева. Прежний предел колонки остаётся, форма стоит по центру окна. */
  body:has(.tabbar[hidden]) .app-main {
    width: 100%;
    max-width: var(--compact-content-max);
    margin-left: auto;
  }
}
