/* DorumaCRM — светлая тема, мятные акценты */

:root {
  --mint-dark: #4f6f64;      /* приглушённый пыльный тёмно-мятный */
  --mint-bright: #2ec4a6;    /* чистый мятный */
  --mint-bright-hover: #25a98e;
  --danger: #c97064;         /* приглушённый пыльный красный */
  --danger-bg: #f7e9e7;
  --warning: #b8933f;        /* приглушённый янтарный, в тон остальной палитре */
  --warning-bg: #f7f0e0;

  --bg: #f6faf8;
  --surface: #ffffff;
  --border: #e1e8e4;
  --text: #213129;
  --text-muted: #6b7d75;

  --radius: 10px;
  --shadow: 0 1px 3px rgba(33, 49, 41, 0.08);

  /* ПОДЛОЖКА модальных слоёв — одно значение на всех, кто приглушает
     страницу под собой: dialog.drawer::backdrop и .search-backdrop (меню
     поиска). Значения — те самые, что стояли у дроверов; вынесены сюда,
     чтобы затемнение не разошлось между дровером и поиском. ::backdrop —
     псевдоэлемент только у элементов верхнего слоя (dialog), обычному
     блоку его не назначить, поэтому у поиска свой элемент, но цвет и
     размытие общие. */
  --backdrop-bg: rgba(229, 219, 201, 0.3);
  --backdrop-blur: 2px;

  /* ЕДИНЫЕ ОТСТУПЫ БЛОКОВ (30.07). Четыре числа на всё приложение — любой
     блок собирается только из них, чтобы страницы не расходились.

     Эталон обычного блока — "карты" на /me: 14 сверху, 13 от шапки до
     первой строки, строки по 6+6 (=12 видимого зазора + пунктир между
     ними). Весь отступ шапка→тело даёт САМА шапка, первая строка своего
     верхнего отступа не добавляет (правило `.section-card__head + *`
     ниже) — иначе зазор плавает от 7 до 20px в зависимости от того, что
     стоит первым.

     Эталон блока СО ШКАЛОЙ — "Сделано на N%" на карточке заказа: шкала
     стоит к шапке ближе обычной строки (6px, --space-head-scale), под
     шкалой лежит трек --paper-sunk, а первая строка после шкалы отбита
     тем же --space-head-body, что и после шапки. Прежнее исключение
     "у блоков со шкалой свой ритм 6+6" ОТМЕНЕНО (решение 30.07): все
     блоки со шкалой одинаковые между собой и меряются по этому эталону.

     Разделителей под шапкой и под шкалой НЕ бывает нигде. */
  /* Высота шапки. Была захардкожена в трёх местах (.topbar, фон-подложка
     поиска, сам логотип) — теперь одно число, потому что на мобиле она
     другая (см. мобильный блок ниже). */
  --topbar-height: 62px;

  --space-card-top: 14px;
  --space-head-body: 13px;
  --space-head-scale: 6px;
  --space-row: 6px;
}

/* ==========================================================================
   МОБИЛЬНЫЙ РЕЖИМ — @media (max-width: 768px). ДОГОВОРЁННОСТЬ (24.08).

   Единый мобильный брейкпоинт проекта — 768px. Все НОВЫЕ мобильные правила
   пишем только в него; мобильные значения переменных живут здесь.

   Почему одно число, а не «ещё одно рядом»: в файле уже лежат исторические
   480/640/860/900/960 — они остались от переверстки отдельных страниц и
   массово НЕ трогаются (это отдельная уборка, чтобы не сломать работающее).
   Правило на будущее: новый брейкпоинт не заводим, дописываем сюда.

   ОТСТУПЫ (норма 1-М из CLAUDE.md — «единые отступы С ПЕРВОГО блока, а не
   потом причешем»): переопределяем те же четыре переменные, что и на
   десктопе, ОДИН раз и только здесь. Тогда вся уже написанная вёрстка едет
   на мобильные числа сама, без дублирования правил по блокам, и в отдельных
   блоках не заводится хардкод «здесь 7, здесь 10, здесь 12» — именно он и
   дал десктопный разнобой, который потом вычищали сквозным проходом.

   Числа взяты ЗАМЕРОМ по мобильному макету (Figma node 198:834, страница
   390px), а не подобраны на глаз и не пересчитаны из десктопных через
   коэффициент. Сверено по карточкам дашборда «Расписание» / «Работа» /
   «Долги» / «Труба денег» / «Лимит НПД» — во всех совпадает:
     верх карточки → шапка     12   (десктоп 14)
     шапка → первая строка     10   (десктоп 13)
     шапка → шкала             10   (десктоп 6)
     шкала → первая строка     10   (тот же --space-head-body, как и на десктопе)
     между строками            10   => --space-row 5 сверху+снизу (десктоп 6)

   ОТЛИЧИЕ ОТ ДЕСКТОПА, не ошибка: на мобиле «шапка → шкала» РАВНО «шапка →
   первая строка» (оба 10). На десктопе шкала прижата к шапке теснее строки
   (6 против 13). В макете так нарисовано во ВСЕХ блоках со шкалой, поэтому
   не «причёсываем» под десктопное правило без отдельного решения.

   Масштаб ×1.2 (CLAUDE.md, «плохое зрение») сюда НЕ применяется: он поднимал
   десктопный макет, нарисованный мельче реального экрана, а мобильный макет
   нарисован сразу в 390px — это и есть ширина телефона, пересчёт 1:1.
   Умножив, мы бы вышли за экран.
   ========================================================================== */

@media (max-width: 768px) {
  :root {
    --space-card-top: 12px;
    --space-head-body: 10px;
    --space-head-scale: 10px;
    --space-row: 5px;

    /* Шапка на мобиле худеет: навигация из неё уехала в нижний бар, в
       шапке остаются только дарума-логотип и название (Figma 236:2202). */
    --topbar-height: 52px;
    --bottomnav-height: 64px;
  }
}

* {
  box-sizing: border-box;
}

body {
  margin: 0;
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
  /* Стикербук color/paper/base (--paper-base, #FBF8F3), не старый мятный
     --bg (#F6FAF8) — страницы уже переверстанные под Стикербук (Заказы,
     Шаблоны, Заказ и т.д.) и старые нетронутые делили один фон случайно,
     не специально; теперь фон единый везде. */
  background: var(--paper-base);
  color: var(--text);
  line-height: 1.45;
}

a {
  color: var(--mint-bright-hover);
  text-decoration: none;
}
a:hover {
  text-decoration: underline;
}

h1, h2, h3 {
  margin: 0 0 0.5em;
  font-weight: 600;
}
h1 { font-size: 1.5rem; }
h2 { font-size: 1.1rem; color: var(--mint-dark); }

/* Шапка — дизайн-система «Стикербук», Figma node 50:105.
   Дарума-логотип наклеена внахлёст на текст (см. topbar-brand, отрицательный
   margin) — это сделано и в макете, не задел вёрстки. */

.topbar {
  display: flex;
  align-items: center;
  gap: 12px;
  height: var(--topbar-height);
  padding: 0 17px;
  background: var(--paper-card);
  border-bottom: 1px solid var(--paper-line);
}
.topbar-logo {
  display: flex;
  flex-shrink: 0;
  line-height: 0;
}
.topbar-logo-daruma {
  width: 150px;
  height: 62px;
}
.topbar-brand {
  flex-shrink: 0;
  font-size: var(--f-size-display);
  margin: 0 0 0 -22px;
  white-space: nowrap;
  color: var(--ink-base);
  text-decoration: none;
}
.topbar-brand:hover {
  text-decoration: none;
}
.topbar-tabs {
  display: flex;
  align-items: center;
  gap: 1px;
  flex: 1 0 0;
  min-width: 0;
  justify-content: flex-end;
}
.topbar-search-wrap {
  position: relative;
  display: flex;
  align-items: center;
}
.topbar-search-toggle {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 41px;
  height: 34px;
  padding: 0;
  border: none;
  background: none;
  cursor: pointer;
}
.topbar-search-form {
  position: absolute;
  right: 100%;
  top: 50%;
  transform: translateY(-50%);
  margin-right: 7px;
}
.topbar-search-form input {
  height: 34px;
  padding: 0 12px;
  border: 1px solid var(--paper-line);
  border-radius: var(--r-md);
  background: var(--paper-sunk);
  font-family: var(--f-family-body);
  font-size: var(--f-size-footnote);
  color: var(--ink-base);
  width: 192px;
}
.topbar-search-form input:focus {
  outline: none;
  border-color: var(--service-600);
}
/* Поле с запросом — активное состояние из макета (нода 461:1078): рамка
   1.5px service-600, фон карточки. Высоту 34px НЕ трогаем: в макете поле
   нарисовано отдельно от топбара, а тут оно живёт в 62px-шапке рядом с
   .topbar-search-toggle (41x34). */
.topbar-search-form--active input {
  width: 432px;
  padding-right: 30px;
  background: var(--paper-card);
  border: 1.5px solid var(--service-600);
}
/* Браузерный крестик очистки у input[type="search"] — свой у WebKit/Blink,
   свой у Edge. Рисуется поверх нашего ✕ из макета, получалось два подряд.
   Прячем только у поля поиска: других type="search" в проекте нет, но
   правило точечное, чтобы не задеть будущие. */
.topbar-search-form input[type="search"]::-webkit-search-cancel-button,
.topbar-search-form input[type="search"]::-webkit-search-decoration {
  -webkit-appearance: none;
  appearance: none;
  display: none;
}
.topbar-search-form input[type="search"]::-ms-clear {
  display: none;
  width: 0;
  height: 0;
}
.topbar-search-clear {
  position: absolute;
  right: 12px;
  top: 50%;
  transform: translateY(-50%);
  font-size: var(--f-size-caption);
  font-weight: var(--f-weight-medium);
  color: var(--ink-muted);
  text-decoration: none;
  line-height: 1;
}

/* Подложка под меню поиска: страница приглушается, топбар с полем и само
   меню остаются чёткими поверх. Начинается ровно под топбаром (его высота
   62px, см. .topbar) — поэтому шапку не накрывает и z-index ей не нужен.
   display НЕ задаём специально: у div он и так block, а значит браузерный
   [hidden]{display:none} работает сам и очередной раз наступать на эту
   ловушку не на чем. */
.search-backdrop {
  position: fixed;
  top: var(--topbar-height);
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 50;
  background: var(--backdrop-bg);
  backdrop-filter: blur(var(--backdrop-blur));
}

/* Дропдаун результатов поиска (нода 461:1084 "Меню"). Хардкод-размеры
   умножены на 1.2 (360 -> 432, паддинги 10/8 -> 12/10, gap 10 -> 12),
   шрифты взяты токенами: в макете 11px = caption (13px), 13px = body
   (16px) — макет нарисован в дошкальных размерах.
   [hidden] — та самая ловушка в третий... четвёртый раз за проект:
   авторский display:flex перебивает браузерный [hidden]{display:none}
   независимо от специфичности, поэтому display вынесен в :not([hidden]). */
.search-menu {
  position: absolute;
  top: calc(100% + 22px);
  right: 100%;
  margin-right: 7px;
  z-index: 60;
  width: 432px;
  flex-direction: column;
  gap: 12px;
  padding: 12px 12px 10px;
  background: var(--paper-card);
  border: 1px solid var(--paper-line);
  border-radius: var(--r-lg);
  max-height: calc(100vh - 100px);
  overflow-y: auto;
}
.search-menu:not([hidden]) {
  display: flex;
}
.search-menu__section {
  margin: 0;
  width: 100%;
  font-size: var(--f-size-caption);
  font-weight: var(--f-weight-bold);
  color: var(--ink-secondary);
}
.search-result {
  display: flex;
  flex-direction: column;
  gap: 2px;
  width: 100%;
  padding: 11px 12px;
  border-radius: 10px;
  text-decoration: none;
}
/* Подсветка ТОЛЬКО под курсором. В макете (нода 461:1086) первая строка
   подсвечена статично — решением Хильды от этого отказались: все строки
   одинаковые, желтеет ровно та, на которую навели. */
a.search-result:hover,
a.search-result:focus-visible {
  background: var(--chip-today);
}
a.search-result {
  cursor: pointer;
}
.search-result__head {
  display: flex;
  align-items: center;
  gap: 10px;
  width: 100%;
}
.search-result__title {
  font-size: var(--f-size-body);
  font-weight: var(--f-weight-bold);
  color: var(--ink-base);
}
.search-result__sub {
  width: 100%;
  font-size: var(--f-size-caption);
  font-weight: var(--f-weight-regular);
  color: var(--ink-secondary);
}
/* Чип статуса чека (нода 461:1103/461:1109) — залитая пилюля, НЕ .mini-badge:
   у той светлая заливка с рамкой и контрастным текстом, а тут "пробит" —
   сплошной тёмно-зелёный с бумажным текстом. */
.search-chip {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 2px 10px;
  border-radius: var(--r-pill);
  font-size: 12px;
  font-weight: var(--f-weight-medium);
  white-space: nowrap;
}
.search-chip--issued {
  background: var(--semantic-success-text);
  color: var(--paper-card);
}
.search-chip--planned {
  background: var(--paper-sunk);
  color: var(--ink-secondary);
}
.search-menu__all,
.search-menu__empty {
  margin: 0;
  width: 100%;
  padding: 0 12px;
  font-size: var(--f-size-caption);
  font-weight: var(--f-weight-medium);
  color: var(--ink-secondary);
  text-decoration: none;
}
.topbar-tab {
  display: inline-flex;
  align-items: center;
  padding: 7px 13px;
  border-radius: var(--r-md);
  font-family: var(--f-family-body);
  font-size: var(--f-size-footnote);
  font-weight: var(--f-weight-medium);
  color: var(--ink-secondary);
  text-decoration: none;
  white-space: nowrap;
}
.topbar-tab:hover {
  color: var(--ink-base);
}
.topbar-tab--active {
  background: var(--service-100);
  color: var(--ink-base);
  font-weight: var(--f-weight-bold);
}
.topbar-record-wrap {
  position: relative;
  display: flex;
  margin-left: 6px; /* +5px к обычному gap:1px между табами — по правке пользователя, тоже ×1.2 */
}
.topbar-record-btn {
  width: 120px;
}
.topbar-record-stub-note {
  position: absolute;
  top: 100%;
  right: 0;
  margin-top: 5px;
  padding: 7px 12px;
  background: var(--paper-card);
  border: 1px solid var(--paper-line);
  border-radius: var(--r-md);
  font-size: var(--f-size-footnote);
  color: var(--ink-secondary);
  white-space: nowrap;
  z-index: 5;
}
.topbar-divider {
  flex-shrink: 0;
  width: 1px;
  height: 26px;
  background: var(--paper-line);
}
.topbar-exit {
  display: flex;
  align-items: center;
  flex-shrink: 0;
}

.content {
  max-width: 1056px;
  margin: 0 auto;
  padding: 1.25rem;
}

/* Хлебные крошки */

.breadcrumbs {
  font-size: 0.85rem;
  color: var(--text-muted);
  margin-bottom: 0.85rem;
  display: flex;
  flex-wrap: wrap;
  gap: 0.3rem;
}
.breadcrumbs a {
  color: var(--text-muted);
}
.breadcrumbs a:hover {
  color: var(--mint-bright-hover);
}
.breadcrumb-sep {
  color: var(--border);
}

/* Заголовок страницы */

.page-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 1rem;
  margin-bottom: 1rem;
  flex-wrap: wrap;
}

/* Кнопки */

.btn {
  display: inline-block;
  padding: 0.55rem 1.1rem;
  border-radius: var(--radius);
  border: 1px solid transparent;
  font-size: 0.95rem;
  cursor: pointer;
  text-decoration: none;
}
.btn-primary {
  background: var(--mint-bright);
  color: #fff;
}
.btn-primary:hover {
  background: var(--mint-bright-hover);
  text-decoration: none;
}
.btn-ghost {
  background: var(--surface);
  border-color: var(--border);
  color: var(--text);
}
.btn-ghost:hover {
  border-color: var(--mint-dark);
  text-decoration: none;
}
.btn-ghost-danger {
  background: var(--surface);
  border: 1px solid var(--danger);
  color: var(--danger);
}
.btn-ghost-danger:hover {
  background: var(--danger-bg);
  text-decoration: none;
}
.btn-block {
  display: block;
  width: 100%;
  text-align: center;
}
.btn-link {
  background: none;
  border: none;
  color: var(--mint-bright-hover);
  cursor: pointer;
  padding: 0;
  font-size: 0.9rem;
}
.btn-link-danger {
  color: var(--danger);
  font-size: 1.1rem;
}

/* Формы */

.form-card, .login-card {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
  padding: 1.25rem;
  margin-bottom: 1.25rem;
}

form label {
  display: block;
  font-size: 0.85rem;
  color: var(--text-muted);
  margin: 0.75rem 0 0.25rem;
}
form input[type="text"]:not(.field):not(.category-edit__input):not(.new-template-input):not(.field--note):not(.field--task),
form input[type="password"]:not(.field):not(.category-edit__input):not(.new-template-input):not(.field--note):not(.field--task),
form input[type="email"]:not(.field):not(.category-edit__input):not(.new-template-input):not(.field--note):not(.field--task),
form input[type="url"]:not(.field):not(.category-edit__input):not(.new-template-input):not(.field--note):not(.field--task),
form input[type="number"]:not(.field):not(.category-edit__input):not(.new-template-input):not(.field--note):not(.field--task),
form input[type="date"]:not(.field):not(.category-edit__input):not(.new-template-input):not(.field--note):not(.field--task),
form select:not(.field):not(.category-edit__input):not(.new-template-input):not(.field--note):not(.field--task),
form textarea:not(.field):not(.category-edit__input):not(.new-template-input):not(.field--note):not(.field--task) {
  width: 100%;
  padding: 0.55rem 0.7rem;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  font-size: 0.95rem;
  font-family: inherit;
  background: var(--bg);
  color: var(--text);
}
form input:not(.field):not(.category-edit__input):not(.new-template-input):not(.field--note):not(.field--task):focus,
form select:not(.field):not(.new-template-input):not(.field--note):not(.field--task):focus,
form textarea:not(.field):not(.field--note):not(.field--task):focus {
  outline: none;
  border-color: var(--mint-bright);
}
.form-actions {
  margin-top: 1.1rem;
  display: flex;
  gap: 0.6rem;
  justify-content: flex-end;
}
.input-with-button {
  display: flex;
  gap: 0.5rem;
}
.input-with-button input {
  flex: 1;
}
.input-with-button .btn {
  white-space: nowrap;
}
.inline-form {
  display: inline-flex;
  align-items: center;
}
.inline-add-form {
  display: flex;
  flex-wrap: wrap;
  gap: 0.6rem;
  margin: 0.75rem 0;
}
.inline-add-form input,
.inline-add-form select,
.inline-add-form textarea {
  flex: 1;
}
/* single_date_field оборачивает нужный <input> в свой .field-group — тег-
   селектор выше его не находит (там только input/select/textarea), сайзинг
   дублируется точечно, чтобы дата вела себя в этой строке так же, как
   голый <input type="date"> раньше. */
.inline-add-form .field-group {
  flex: 1;
}
/* Поле "новая задача" теперь на эталонном .field--note (см. блок "ЭТАЛОН
   для полей ввода записей/задач" ниже) — тут только сайзинг в контексте
   этой конкретной flex-строки (дата + кнопка рядом), не сам вид поля.
   align-self:flex-start — без него растянулась бы по высоте вместе с
   соседями под дефолтным align-items:stretch строки .inline-add-form
   (трогать сам .inline-add-form не стали — общий класс на несколько форм,
   где такой проблемы нет). flex-basis 140px — иначе .inline-add-form
   textarea{flex:1}=flex-basis:0% ужимает textarea сильнее, чем сжимался бы
   <input> (реальный баг, найден на скриншоте — текст переносился по одной
   букве); нужна специфичность повыше (2 класса против класс+тег). */
.inline-add-form .field--note {
  flex: 1 1 140px;
  min-width: 140px;
  align-self: flex-start;
}

/* Вкладки активен/архив */

.tabs {
  display: flex;
  gap: 0.5rem;
  margin-bottom: 1rem;
}
.tabs a {
  padding: 0.4rem 0.9rem;
  border-radius: var(--radius);
  background: var(--surface);
  border: 1px solid var(--border);
  color: var(--text-muted);
}
.tabs a.active {
  background: var(--mint-dark);
  color: #fff;
  border-color: var(--mint-dark);
}

/* Списки карточек (заказчики / заказы) */

.card-list {
  display: flex;
  flex-direction: column;
  gap: 0.6rem;
}
.row-card {
  display: block;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 0.85rem 1rem;
  color: var(--text);
  box-shadow: var(--shadow);
}
.row-card:hover {
  border-color: var(--mint-bright);
  text-decoration: none;
}
.row-card-current {
  border-color: var(--mint-dark);
  background: var(--bg);
}
.row-card-title {
  font-weight: 600;
  margin-bottom: 0.25rem;
}
.row-card-sub {
  display: flex;
  gap: 0.75rem;
  flex-wrap: wrap;
  font-size: 0.85rem;
  color: var(--text-muted);
}

.empty-state {
  color: var(--text-muted);
  font-style: italic;
}

.badge {
  display: inline-block;
  background: var(--mint-dark);
  color: #fff;
  font-size: 0.75rem;
  padding: 0.15rem 0.55rem;
  border-radius: 999px;
}

.muted-link {
  color: var(--text-muted);
  font-size: 0.9rem;
}

/* Уведомления */

.alert {
  padding: 0.7rem 1rem;
  border-radius: var(--radius);
  margin-bottom: 1rem;
  font-size: 0.9rem;
}
.alert-danger {
  background: var(--danger-bg);
  color: var(--danger);
  border: 1px solid var(--danger);
}
.alert-muted {
  background: var(--border);
  color: var(--text-muted);
}
.alert-warning {
  background: var(--warning-bg);
  color: var(--warning);
  border: 1px solid var(--warning);
}


/* Чек-лист */

.checklist {
  list-style: none;
  padding: 0;
  margin: 0;
}
.checklist-item {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 0.5rem 0;
  border-bottom: 1px solid var(--border);
}
.checklist-item .inline-form:first-child {
  gap: 0.6rem;
  flex: 1;
}
.checklist-item input[type="checkbox"] {
  width: 18px;
  height: 18px;
  accent-color: var(--mint-bright);
  margin-right: 0.6rem;
}
.checklist-item.done span {
  text-decoration: line-through;
  color: var(--text-muted);
}

/* Дневник заметок */

.diary {
  list-style: none;
  padding: 0;
  margin: 0.75rem 0 0;
}
.diary-item {
  padding: 0.6rem 0;
  border-top: 1px solid var(--border);
}
.diary-date {
  font-size: 0.8rem;
  color: var(--text-muted);
  margin-bottom: 0.2rem;
}
.diary-text {
  white-space: pre-wrap;
}

/* Прогресс выполнения заказа */

.progress-bar {
  background: var(--border);
  border-radius: 999px;
  height: 10px;
  overflow: hidden;
  margin-bottom: 0.25rem;
}
.progress-bar-fill {
  background: var(--mint-bright);
  height: 100%;
  border-radius: 999px;
}
.progress-label {
  font-size: 0.85rem;
  color: var(--text-muted);
  margin-bottom: 1rem;
}

/* Профили реквизитов */

.profile-list {
  display: flex;
  flex-direction: column;
  gap: 0.75rem;
  margin-bottom: 1rem;
}
.profile-item {
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 0.75rem 1rem;
}
.profile-item-head {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  margin-bottom: 0.4rem;
}
.profile-item-body {
  font-size: 0.88rem;
  color: var(--text-muted);
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 0.15rem 1rem;
}

details.add-profile summary {
  cursor: pointer;
  color: var(--mint-bright-hover);
  font-weight: 600;
  margin-top: 0.5rem;
}

.quick-links {
  display: flex;
  gap: 1rem;
  flex-wrap: wrap;
  margin-top: 0.75rem;
  font-size: 0.88rem;
}

/* Заметки под полями форм */

.muted-note {
  font-size: 0.8rem;
  color: var(--text-muted);
  margin: 0.3rem 0 0.5rem;
}

/* Договоры */

.contract-list {
  display: flex;
  flex-direction: column;
  gap: 0.6rem;
  margin-bottom: 1rem;
}
.contract-item {
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 0.75rem 1rem;
}
.contract-item-head {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  flex-wrap: wrap;
}
.contract-children {
  margin: 0.5rem 0 0 1.25rem;
  padding-left: 0.75rem;
  border-left: 2px solid var(--border);
  display: flex;
  flex-direction: column;
  gap: 0.4rem;
}
.badge-muted {
  background: var(--border);
  color: var(--text-muted);
}
.badge-danger {
  background: var(--danger);
}
.badge-warning {
  background: var(--warning);
}

/* Модальное окно (<dialog>) */

dialog {
  border: none;
  border-radius: var(--radius);
  box-shadow: 0 8px 30px rgba(33, 49, 41, 0.25);
  padding: 1.25rem;
  width: 100%;
  max-width: 480px;
}
dialog::backdrop {
  background: rgba(33, 49, 41, 0.45);
}
dialog h2 {
  margin-top: 0;
}

/* Просмотр .docx (mammoth) — только структурные теги без своих классов,
   стилизуем по тегам внутри контейнера, чтобы не задеть остальную страницу */

.docx-preview {
  max-height: 70vh;
  overflow-y: auto;
  overflow-x: auto;
  padding: 1rem 1.25rem;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--bg);
  font-size: 0.92rem;
  line-height: 1.55;
}
.docx-preview h1, .docx-preview h2, .docx-preview h3 {
  color: var(--mint-dark);
  margin: 1em 0 0.4em;
}
.docx-preview h1:first-child, .docx-preview h2:first-child, .docx-preview h3:first-child {
  margin-top: 0;
}
.docx-preview p {
  margin: 0 0 0.7em;
}
.docx-preview table {
  border-collapse: collapse;
  margin: 0.7em 0;
  width: 100%;
}
.docx-preview table td, .docx-preview table th {
  border: 1px solid var(--border);
  padding: 0.35rem 0.55rem;
  vertical-align: top;
}
.docx-preview ul, .docx-preview ol {
  margin: 0 0 0.7em;
  padding-left: 1.4em;
}
.docx-preview img {
  max-width: 100%;
}

/* Просмотр шаблона в момент выбора (до генерации) — компактнее, чем на
   карточке готового документа, чтобы не разрывать форму */
.docx-preview-picker {
  margin: 0.5rem 0 0.75rem;
}
.docx-preview-picker .docx-preview {
  max-height: 40vh;
  margin-top: 0.5rem;
}

/* Расписание встреч (TUI Calendar) */

.schedule-toolbar {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 1rem;
  flex-wrap: wrap;
  margin-bottom: 1rem;
}
.schedule-nav {
  display: flex;
  align-items: center;
  gap: 0.5rem;
}
.schedule-range-label {
  margin-left: 0.4rem;
  color: var(--text-muted);
  font-size: 0.9rem;
}
.schedule-daylink {
  color: var(--text);
  text-decoration: none;
  font-weight: 600;
}
.schedule-daylink:hover {
  color: var(--mint-bright-hover);
  text-decoration: none;
}
.schedule-daylink.other-month {
  color: var(--text-muted);
  font-weight: 400;
}
.schedule-daylink.today {
  color: var(--mint-bright-hover);
}

/* Телефон */

@media (max-width: 640px) {
  .content {
    padding: 0.85rem;
  }
  .topbar {
    padding: 0.6rem 0.85rem;
  }
  .page-header {
    flex-direction: column;
    align-items: flex-start;
  }
  .form-actions {
    justify-content: stretch;
  }
  .form-actions .btn {
    flex: 1;
    text-align: center;
  }
}

/* ==========================================================================
   Дизайн-система «Стикербук» — компоненты на токенах (tokens.css/daruma.css).
   Новые страницы (первая — /login) собираются из этих классов. Старые классы
   выше (.btn-primary, .card-list, .alert-danger и т.п.) не трогаем — на них
   ещё стоит весь остальной интерфейс, страницы переверстываются по одной.

   ВАЖНО: бренд .btn НЕ переопределяем. У него уже свои паддинги/радиус на
   всех остальных страницах — редефайн задел бы кнопки во всём приложении.
   Поэтому .btn--primary/secondary/ghost/danger самодостаточны, без общего
   .btn (в demo.html они используются вместе с .btn, здесь — только модификатор).
   ========================================================================== */

/* Кнопки */

.btn--primary,
.btn--secondary,
.btn--ghost,
.btn--danger,
.btn--ghost-danger,
.btn--secondary-danger {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 7px;
  height: 43px;
  padding: 0 14px;
  border-radius: var(--r-md);
  border: 1px solid transparent;
  font-family: var(--f-family-body);
  font-size: var(--f-size-footnote);
  font-weight: var(--f-weight-bold);
  line-height: 1;
  cursor: pointer;
  text-decoration: none;
  transition: background var(--m-duration-fast) var(--m-easing-out),
              border-color var(--m-duration-fast) var(--m-easing-out),
              transform var(--m-duration-fast) var(--m-easing-out);
}
.btn--primary:active,
.btn--secondary:active,
.btn--ghost:active,
.btn--danger:active,
.btn--ghost-danger:active,
.btn--secondary-danger:active {
  transform: scale(0.98);
}
/* Фокус — свой контур вместо чёрного системного: Chrome рисует
   `outline: auto rgb(16,16,16)`, и он выбивался из палитры (проверено
   компьютед-стайлом на "Записать" в панели "нал"). Цвет тот же, что у
   рамки поля в фокусе (.field:focus) — фокус по всему сайту выглядит
   одинаково.

   :focus-visible, а не :focus — кольцо появляется при Tab'е с клавиатуры
   и не появляется по клику мышью, это решает сам браузер. Убирать фокус
   совсем нельзя: без него с клавиатуры не видно, где ты.

   outline (а не border/box-shadow) не занимает места в раскладке, поэтому
   соседние кнопки не сдвигаются в момент фокуса.

   Кнопки-виджеты с классом .field (дата, .select-custom__control) сюда не
   попадают: у .field:focus специфичность выше (0,2,0 против 0,1,1), его
   outline:none выигрывает — там остаётся своя подсветка рамкой, двойного
   кольца нет. */
button:focus-visible,
.btn--primary:focus-visible,
.btn--secondary:focus-visible,
.btn--ghost:focus-visible,
.btn--danger:focus-visible,
.btn--ghost-danger:focus-visible,
.btn--secondary-danger:focus-visible {
  outline: 2px solid var(--service-600);
  outline-offset: 2px;
}
/* Когда кнопка на самом деле <a> (напр. "Отписаться"), браузерный
   a:hover{text-decoration:underline} специфичнее (тип+псевдокласс) базового
   text-decoration:none выше (просто класс) и перебивает его именно на
   hover — подчёркивание вылезало только при наведении. Гасим той же
   группой селекторов, что и сама база. */
.btn--primary:hover,
.btn--secondary:hover,
.btn--ghost:hover,
.btn--danger:hover,
.btn--ghost-danger:hover,
.btn--secondary-danger:hover {
  text-decoration: none;
}

/* Модификаторы высоты — базовые 43px/14px хватает для рядовой кнопки, но
   по факту с макета входа кнопки-акценты крупнее (Войти/Прислать ссылку —
   53px/17px, подключить бота/переслать в телеграм — 48-53px/16px, кнопки
   контактов в подвале — компактные 41px/14px). Три готовых модификатора,
   чтобы новые страницы не подбирали размер на глаз. Высоты — исходная
   шкала ×1.2 (правка на читаемость, 2026-07-20). */
.btn--sm { height: 41px; font-size: var(--f-size-footnote); }
.btn--md { height: 48px; font-size: var(--f-size-body); }
.btn--lg { height: 53px; font-size: var(--f-size-bodylg); }

.btn--primary {
  background: var(--service-400);
  color: var(--ink-base);
}
.btn--primary:hover {
  background: var(--service-500);
}

.btn--secondary {
  background: var(--paper-card);
  color: var(--ink-base);
  border-color: var(--paper-line);
  font-weight: var(--f-weight-medium);
}
.btn--secondary:hover {
  background: var(--paper-sunk);
  border-color: var(--paper-linestrong);
}

.btn--ghost {
  background: none;
  color: var(--ink-secondary);
}
.btn--ghost:hover {
  background: var(--paper-sunk);
}

.btn--danger {
  background: var(--daruma-400);
  color: var(--paper-card);
}
.btn--danger:hover {
  background: var(--daruma-600);
}

/* Тревожный ghost — удаление в ряду действий дровера ("Удалить цель" рядом с
   "Отмена"/"Загадать"). Без рамки и заливки, как .btn--ghost: это не главное
   действие дровера, оно не должно спорить с "Загадать", но и не должно
   читаться как нейтральная "Отмена". Не путать с .btn--secondary-danger
   (контурная, для шапки страницы) и .btn--danger (залитая, для подтверждения
   внутри confirm_dialog). */
.btn--ghost-danger {
  background: none;
  color: var(--semantic-danger-text);
}
.btn--ghost-danger:hover {
  background: var(--semantic-danger-bg);
}

.btn--block {
  width: 100%;
}

/* Компактная кнопка для мелких "чипов" на карточках (Править/В архив/
   +профиль и т.п.) — по факту почти все такие кнопки в новых узлах Figma
   (144:334, 81:373) сгруппированы вокруг одной высоты ~25-26px, не стали
   заводить под каждый чуть отличающийся паддинг свой модификатор. */
.btn--xs { height: 31px; font-size: var(--f-size-caption); padding: 0 13px; }

/* Ещё компактнее .btn--xs — для рядов из 3+ кнопок-действий в узкой шапке
   карточки (карточка "Цель" на /me, Figma-нода 345:857/345:855/345:859):
   на 31px три кнопки+чип шапки переносятся на вторую строку, на 18px
   (×1.2=22px) остаются в один ряд, как в Figma. */
.btn--xxs { height: 22px; padding: 0 12px; font-size: 12px; border-radius: var(--r-md); }

/* Контурный danger — "В БАН" (карточка заказчика): рамка/текст тревожные,
   фон как у обычной secondary, не залитая кнопка (в отличие от .btn--danger). */
.btn--secondary-danger {
  background: var(--paper-card);
  color: var(--daruma-400);
  border-color: var(--daruma-400);
  border-radius: 10px;
  font-weight: var(--f-weight-medium);
}
.btn--secondary-danger:hover {
  background: var(--semantic-danger-bg);
}

/* Поле ввода */

.field-label {
  display: block;
  margin: 0 0 7px;
  font-size: var(--f-size-caption);
  font-weight: var(--f-weight-medium);
  color: var(--ink-secondary);
}
.field {
  display: block;
  width: 100%;
  height: 48px;
  padding: 10px 14px;
  background: var(--paper-sunk);
  border: 1px solid var(--paper-line);
  border-radius: var(--r-md);
  font-family: var(--f-family-body);
  font-size: var(--f-size-bodylg);
  color: var(--ink-base);
  transition: border-color var(--m-duration-fast) var(--m-easing-out),
              box-shadow var(--m-duration-fast) var(--m-easing-out);
}
/* input[type="time"] под дизайн-систему. Само поле уже .field (рамка/фон/
   радиус общие), но браузер добавляет сверху свою механику: часы-иконку
   спиннера справа, собственный шрифт цифр и вертикальное центрирование,
   из-за которых поле выбивалось из ряда обычных .field.

   Что можно и чего нельзя: разметку часы:минуты рисует сам браузер
   (::-webkit-datetime-edit-*), её не заменить — но шрифт, цвет и
   выравнивание наследуются, а иконку спиннера можно убрать целиком
   (appearance:none её не убирает, нужен именно ::-webkit-calendar-picker-
   indicator). Пикер по клику при этом остаётся — открывается по самому
   полю, как и раньше. */
input[type="time"].field {
  appearance: none;
  font-family: var(--f-family-body);
  font-size: var(--f-size-bodylg);
  color: var(--ink-base);
}
/* Числовое поле без браузерных стрелок вверх/вниз — просто ввод цифр.
   Модификатор, а не общее правило на input[type="number"]: спиннер нужен
   там, где значение крутят шагами (длительность встречи +5 минут и т.п.),
   и убирать его везде разом никто не просил. Нужны обе строки: Chrome
   рисует стрелки псевдоэлементами, Firefox — через appearance. */
.field--no-spinner::-webkit-outer-spin-button,
.field--no-spinner::-webkit-inner-spin-button {
  -webkit-appearance: none;
  margin: 0;
}
.field--no-spinner {
  appearance: textfield;
  -moz-appearance: textfield;
}
/* Иконку-часы НЕ прячем (display:none её убирает вместе с единственным
   мышиным способом открыть выпадающий список времени — остаётся только
   ввод с клавиатуры), а приглушаем до тона наших вторичных иконок. */
input[type="time"].field::-webkit-calendar-picker-indicator {
  opacity: 0.45;
  cursor: pointer;
  padding: 0;
  margin: 0;
}
input[type="time"].field::-webkit-calendar-picker-indicator:hover {
  opacity: 0.75;
}
input[type="time"].field::-webkit-datetime-edit {
  /* Браузер по умолчанию прижимает цифры вверх при height больше
     content-box — выравниваем по центру поля, как текст в обычном .field. */
  display: flex;
  align-items: center;
  height: 100%;
  padding: 0;
}
input[type="time"].field::-webkit-datetime-edit-fields-wrapper {
  padding: 0;
}
/* Подсветка выбранного разряда (часы/минуты) — токеном, а не системным
   синим: тот же chip/today, что у выбранной опции в select-custom. */
input[type="time"].field::-webkit-datetime-edit-hour-field:focus,
input[type="time"].field::-webkit-datetime-edit-minute-field:focus {
  background: var(--chip-today);
  border-radius: var(--r-sm);
  color: var(--ink-base);
  outline: none;
}

/* .field задаёт фиксированную height:48px для однострочных input — на
   textarea (БАН, заметки и т.п.) это душит текст в одну строку и рвёт
   вёрстку (плейсхолдер/значение обрезается, следующий элемент наезжает
   сверху). textarea сама считает высоту по атрибуту rows. */
textarea.field {
  height: auto;
  resize: vertical;
}
.field:hover {
  border-color: var(--paper-linestrong);
}
/* Залоченное значение (например, заказчик на дровере "Новый заказ", когда
   заказ заводят прямо с его карточки — значение уже не выбор, а факт). */
.field:disabled {
  opacity: 0.65;
  cursor: not-allowed;
}
.field:focus {
  outline: none;
  border-color: var(--service-600);
  box-shadow: 0 0 0 2px var(--service-100);
}
.field::placeholder {
  color: var(--ink-secondary);
}
/* Браузерный autofill красит поле в свой голубой фон поверх любого CSS —
   единственный способ перебить это — box-shadow inset тем же цветом, что
   и наш фон (сам background не поддаётся). Задержка transition — трюк,
   чтобы не было видно вспышки жёлтого/системного цвета до применения. */
.field:-webkit-autofill,
.field:-webkit-autofill:hover,
.field:-webkit-autofill:focus {
  -webkit-text-fill-color: var(--ink-base);
  -webkit-box-shadow: 0 0 0 1000px var(--paper-sunk) inset;
  box-shadow: 0 0 0 1000px var(--paper-sunk) inset;
  transition: background-color 5000s ease-in-out 0s;
}
/* Обычный <select class="field"> (не .select-custom) — там, где нужна
   настоящая браузерная валидация required на пустом значении (см.
   CLAUDE.md про "Кто платит"/payer_type — скрытому input'у у .select-custom
   такое не по силам). Раньше он оставался с браузерным дефолтным
   треугольником, который не похож на стрелку остальной дизайн-системы —
   свой chevron (та же тропинка, что у .select-custom__chevron) поверх
   appearance:none, цвет — ink-secondary. Сам список опций у native select
   рисует ОС и он не поддаётся CSS ни в одном браузере — это и есть предел
   того, что здесь можно поправить без потери required-валидации. */
select.field {
  appearance: none;
  -webkit-appearance: none;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 16 16' fill='none'%3E%3Cpath d='M4 6l4 4 4-4' stroke='%2323272BA3' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 14px center;
  padding-right: 38px;
}
.field--money {
  font-variant-numeric: tabular-nums;
  text-align: right;
}
.field--error {
  border-color: var(--daruma-400);
}
/* Поле с ошибкой в фокусе — красное, а не жёлтое.

   Без этого правила выигрывал бы .field:focus (специфичность 0,2,0 против
   0,1,0 у .field--error) и красил рамку в service-600: получалось бы жёлтое
   кольцо фокуса поверх красной рамки ошибки — две рамки разного цвета на
   одном поле. Здесь та же геометрия, что у обычного фокуса, но цвета одной
   семьи с ошибкой: 400 — сама рамка (тот же #E4526A, что в .field--error
   выше), 50 — мягкое гало. Читается как одна красная рамка.

   Правило стоит ПОСЛЕ .field:focus по файлу — при равной специфичности
   (0,2,0) выигрывает последнее, поэтому переносить его выше нельзя. */
.field--error:focus {
  border-color: var(--daruma-400);
  box-shadow: 0 0 0 2px var(--daruma-50);
}
.field-error {
  margin: 4px 0 0;
  font-size: var(--f-size-caption);
  color: var(--daruma-600);
}

/* ЭТАЛОН для полей ввода записей/задач (дневник заказа, заметки заказчика
   и т.п. — МНОГОСТРОЧНЫЕ поля, где текст живёт абзацами) — по прямой
   просьбе пользователя вынесен в собственный класс, а не унаследован от
   .field молча: значения сейчас совпадают с .field+textarea.field 1-в-1
   (это и есть образец — textarea "Новая запись" в дневнике заказа), но
   записан отдельно и самодостаточно, чтобы изменения общего .field (для
   имени/телефона/дат и т.п.) не утаскивали за собой вид полей записей.
   Самостоятельный класс, не модификатор — используется БЕЗ .field.
   Не забыт в легаси-исключениях form input/select/textarea:not(...) выше
   (та же ловушка specificity, что у .category-edit__input и
   .new-template-input).
   Для ОДНОСТРОЧНЫХ полей (одна задача/цель, без абзацев) — см. соседний
   .field--task ниже: та же тема (paper-sunk/paper-line/r-md), но короче
   и без переноса строк. .field--note специально НЕ трогаем под задачи —
   пользователь явно попросил развести дневник (многострочно) и
   задачи/цели (однострочно) по разным классам, не смешивать. */
.field--note {
  display: block;
  width: 100%;
  height: auto;
  padding: 10px 14px;
  background: var(--paper-sunk);
  border: 1px solid var(--paper-line);
  border-radius: var(--r-md);
  font-family: var(--f-family-body);
  font-size: var(--f-size-bodylg);
  color: var(--ink-base);
  resize: vertical;
  transition: border-color var(--m-duration-fast) var(--m-easing-out),
              box-shadow var(--m-duration-fast) var(--m-easing-out);
}
.field--note:hover {
  border-color: var(--paper-linestrong);
}
.field--note:focus {
  outline: none;
  border-color: var(--service-600);
  box-shadow: 0 0 0 2px var(--service-100);
}
.field--note::placeholder {
  color: var(--ink-secondary);
}
/* Авторост (data-autogrow в app.js, задача заказа "+ задача") сам считает
   высоту по scrollHeight — ручной resize-уголок и внутренний скролл только
   мешали бы. У обычных полей записи (дневник, заметки) без автороста
   resize:vertical из базового правила выше остаётся как есть. */
[data-autogrow].field--note {
  resize: none;
  overflow: hidden;
}

/* Однострочный вариант эталона — для одной задачи/цели без абзацев (цели
   месяца на "Неделе" и т.п.), в отличие от .field--note (дневник,
   многострочно). Та же тема (paper-sunk/paper-line/r-md), но короче: высота
   подобрана по факту (getBoundingClientRect) под высоту обычной строки
   задачи (.task-row) рядом — ~26-27px, чтобы поле визуально не выбивалось
   из списка задач под ним. Всегда <input>, никогда textarea — растущая
   высота тут не нужна и не ожидается. */
.field--task {
  display: block;
  width: 100%;
  height: 27px;
  padding: 0 11px;
  background: var(--paper-sunk);
  border: 1px solid var(--paper-line);
  border-radius: var(--r-md);
  font-family: var(--f-family-body);
  font-size: var(--f-size-footnote);
  color: var(--ink-base);
  transition: border-color var(--m-duration-fast) var(--m-easing-out),
              box-shadow var(--m-duration-fast) var(--m-easing-out);
}
.field--task:hover {
  border-color: var(--paper-linestrong);
}
.field--task:focus {
  outline: none;
  border-color: var(--service-600);
  box-shadow: 0 0 0 2px var(--service-100);
}
.field--task::placeholder {
  color: var(--ink-secondary);
}

/* Статус-плашка */

.pill {
  display: inline-flex;
  align-items: center;
  height: 26px;
  padding: 0 11px;
  border-radius: var(--r-pill);
  border: 1px solid transparent;
  font-family: var(--f-family-body);
  font-size: var(--f-size-footnote);
  font-weight: var(--f-weight-bold);
  line-height: 1;
}
.pill--success { background: var(--semantic-success-bg); border-color: var(--semantic-success-border); color: var(--semantic-success-text); }
.pill--warning { background: var(--semantic-warning-bg); border-color: var(--semantic-warning-border); color: var(--semantic-warning-text); }
.pill--danger  { background: var(--semantic-danger-bg);  border-color: var(--semantic-danger-border);  color: var(--semantic-danger-text); }
.pill--info    { background: var(--semantic-info-bg);    border-color: var(--semantic-info-border);    color: var(--semantic-info-text); }
.pill--neutral { background: var(--semantic-neutral-bg); border-color: var(--semantic-neutral-border); color: var(--semantic-neutral-text); }

/* Карточка */

.card {
  background: var(--paper-card);
  border: 1px solid var(--paper-line);
  border-radius: var(--r-lg);
  padding: var(--space-card-top);
}
.card--active {
  border: 1.5px solid var(--service-400);
  background: var(--service-50);
}

/* Мелкие переиспользуемые куски */

.link-quiet {
  background: none;
  border: none;
  padding: 0;
  color: var(--ink-secondary);
  font-family: var(--f-family-body);
  font-size: var(--f-size-footnote);
  font-weight: var(--f-weight-medium);
  cursor: pointer;
  text-decoration: none;
}
.link-quiet:hover {
  color: var(--ink-base);
  text-decoration: underline;
}

.divider-or {
  display: flex;
  align-items: center;
  gap: 10px;
  color: var(--ink-muted);
  font-size: var(--f-size-caption);
}
.divider-or::before,
.divider-or::after {
  content: "";
  flex: 1;
  height: 1px;
  background: var(--paper-line);
}

.stub-note {
  margin: 8px 0 0;
  font-size: var(--f-size-footnote);
  color: var(--ink-secondary);
}

.brand-mark {
  font-family: var(--f-family-display);
  font-weight: var(--f-weight-bold);
}
.brand-mark__crm {
  color: var(--brand-mint);
}

/* Аватар заказчика — зверь из инлайн-спрайта (_avatar_sprite.html) или своя
   загруженная .jpg. Цвет/плашка уже запечены в самом SVG (viewBox 0 0 50 50,
   свой скруглённый прямоугольник-фон внутри) — снаружи только размер. */
.avatar-tile {
  display: block;
  flex-shrink: 0;
  object-fit: cover;
}
.avatar-tile--sm { width: 36px; height: 36px; border-radius: var(--r-md); }
.avatar-tile--md { width: 41px; height: 41px; border-radius: 13px; }
.avatar-tile--lg { width: 48px; height: 48px; border-radius: 16px; }
/* Заказчик в архиве — зверь притушен (страница /archive). */
.avatar-tile--archived { opacity: 0.7; }

/* Хвосты — шкала лояльности заказчика 0-9, см. app/tails_logic.py. */
.tails {
  display: flex;
  gap: 2px;
}
.tails__cell {
  width: 6px;
  height: 17px;
  border-radius: 2px;
  background: var(--sticker-moomin);
}
.tails__cell--filled {
  background: var(--daruma-400);
}

/* Стикер-палитра для декоративных заливок (шкала прогресса заказа) —
   ротация по позиции карточки в списке, см. app/orders_logic.py. */
.sticker--sakura { background: var(--sticker-sakura); }
.sticker--soda   { background: var(--sticker-soda); }
.sticker--taro   { background: var(--sticker-taro); }
.sticker--mint   { background: var(--sticker-mint); }
.sticker--peach  { background: var(--sticker-peach); }
.sticker--moomin { background: var(--sticker-moomin); }

/* Чип — мелкая пилюля-факт (контакт, папка) на досье заказчика. */
.chip {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  height: 31px;
  padding: 0 11px;
  background: var(--paper-card);
  border: 1px solid var(--paper-line);
  border-radius: var(--r-md);
  font-size: var(--f-size-footnote);
  font-weight: var(--f-weight-medium);
  color: var(--ink-base);
  white-space: nowrap;
}

/* ============================== Страница «Заказы» ========================= */

body.orders-page {
  background: var(--paper-base);
}

.orders-head {
  display: flex;
  align-items: center;
  gap: 12px;
  margin-bottom: 11px;
}
.orders-head__title {
  margin: 0;
  font-size: var(--f-size-h2);
  font-weight: var(--f-weight-bold);
  color: var(--ink-base);
}
.orders-head__spacer {
  flex: 1;
}
.orders-head__summary {
  margin: 0;
  font-size: var(--f-size-caption);
  color: var(--ink-secondary);
  white-space: nowrap;
}

.orders-body {
  display: flex;
  align-items: flex-start;
  gap: 11px;
}

/* Левая колонка — заказчики */
.customers-col {
  flex-shrink: 0;
  width: 300px;
  background: var(--paper-card);
  border: 1px solid var(--paper-line);
  border-radius: var(--r-lg);
}
.customers-col__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 11px 13px 8px;
}
.customers-col__title {
  margin: 0;
  font-size: var(--f-size-footnote);
  font-weight: var(--f-weight-bold);
  color: var(--ink-base);
}
.customer-row {
  display: flex;
  align-items: center;
  gap: 11px;
  padding: 11px 13px;
  border-top: 1px dashed var(--paper-line);
  text-decoration: none;
  color: inherit;
}
.customer-row--selected {
  background: var(--chip-today);
}
.customer-row__info {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
}
.customer-row__name {
  margin: 0;
  font-size: var(--f-size-footnote);
  font-weight: var(--f-weight-bold);
  color: var(--ink-base);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.customer-row__sub {
  margin: 0;
  font-size: var(--f-size-caption);
  color: var(--ink-secondary);
  white-space: nowrap;
}
.customer-row--archive {
  display: flex;
  align-items: center;
  justify-content: space-between;
  opacity: 0.7;
}
.customer-row__archive-label,
.customer-row__archive-count {
  margin: 0;
}
.customer-row__archive-label {
  font-size: var(--f-size-footnote);
  font-weight: var(--f-weight-bold);
  color: var(--ink-base);
}
.customer-row__archive-count {
  font-size: var(--f-size-caption);
  color: var(--ink-secondary);
}

/* Правая колонка — досье выбранного заказчика + список заказов */
.orders-col {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 11px;
}

.dossier {
  display: flex;
  flex-direction: column;
  gap: 11px;
  padding: 13px 14px;
  background: var(--paper-card);
  border: 1px solid var(--paper-line);
  border-radius: var(--r-lg);
}
.dossier__head {
  display: flex;
  align-items: center;
  gap: 12px;
}
.dossier__info {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 4px;
}
.dossier__name {
  margin: 0;
  font-size: var(--f-size-h3);
  font-weight: var(--f-weight-bold);
  color: var(--ink-base);
}
.dossier__loyalty {
  display: flex;
  align-items: center;
  gap: 8px;
}
.dossier__tails-caption {
  margin: 0;
  font-size: var(--f-size-caption);
  color: var(--ink-secondary);
  white-space: nowrap;
}
.dossier__contacts {
  display: flex;
  align-items: center;
  gap: 7px;
  padding-top: 11px;
  border-top: 1px dashed var(--paper-line);
}
.dossier__contacts-spacer {
  flex: 1;
}
.dossier__total {
  margin: 0;
  font-size: var(--f-size-caption);
  color: var(--ink-secondary);
  white-space: nowrap;
}

.order-list-card {
  display: flex;
  flex-direction: column;
  background: var(--paper-card);
  border: 1px solid var(--paper-line);
  border-radius: var(--r-lg);
  overflow: hidden;
}
.order-row {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 12px 14px;
  border-top: 1px dashed var(--paper-line);
  text-decoration: none;
  color: inherit;
}
.order-list-card > .order-row:first-child {
  border-top: none;
}
.order-row--problem {
  background: var(--semantic-danger-bg);
}
.order-row__info {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 2px;
}
.order-row__title {
  margin: 0;
  font-size: var(--f-size-h3);
  font-weight: var(--f-weight-bold);
  color: var(--ink-base);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.order-row__sub {
  margin: 0;
  font-size: var(--f-size-caption);
  color: var(--ink-secondary);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.scale {
  width: 204px;
  height: 7px;
  background: var(--paper-sunk);
  border-radius: var(--r-pill);
  overflow: hidden;
}
.scale__fill {
  height: 7px;
  border-radius: var(--r-pill);
}
.order-row--problem .scale__fill {
  background: var(--sticker-taro);
}
.order-row--problem .scale {
  background: var(--paper-card);
}
.order-row__money {
  flex-shrink: 0;
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: 1px;
}
.order-row__amount {
  margin: 0;
  font-size: var(--f-size-bodylg);
  font-weight: var(--f-weight-bold);
  color: var(--ink-base);
  white-space: nowrap;
}
.order-row__note {
  margin: 0;
  font-size: var(--f-size-caption);
  color: var(--ink-secondary);
  white-space: nowrap;
}
.order-row__note--danger {
  color: var(--semantic-danger-text);
}
/* НОРМА 2 (блок 3 плана, 29.07): пустое состояние выравнивается по ЛЕВОМУ
   краю шапки блока, не по центру и не «как получится». Эталон — «На сегодня
   пусто.» в блоке «сегодня» на «Неделе»: 14px горизонтального паддинга,
   которые ровно компенсируют собственный padding-left чипа (12px), так что
   текст встаёт вровень с надписью на чипе (±2px).

   --flush — для блоков, где шапка НЕ чип, а обычный текстовый заголовок
   (архив, панель под-документов): там компенсировать нечего, и те же 14px
   увели бы текст вправо от заголовка. */
/* Норма 2: пустое состояние встаёт вровень с левым краем чипа-шапки и со
   строками блока — то есть без горизонтального паддинга, независимо от того,
   чип у блока в шапке или обычный текст.

   Раньше здесь стояло `13px 14px`: 14px компенсировали собственный
   `padding-left: 12px` чипа, чтобы текст встал вровень с НАДПИСЬЮ на чипе.
   По букве это работало (расхождение 2px), но выталкивало пустое состояние
   на 14px правее всего остального содержимого карточки — строки и край
   чипа-плашки стоят на 14.9 от края карточки, а текст оказывался на 28.9.
   Отменено 04.08 замером. Модификатор --flush, который раньше гасил этот
   паддинг для текстовых шапок, стал не нужен и удалён вместе с правилом. */
.order-list-empty {
  margin: 0;
  padding: 13px 0;
  font-style: italic;
  color: var(--ink-secondary);
}

/* Страница "Архив" — обе колонки (заказчики + заказы) видны одновременно,
   без вкладок, у каждой строки своя кнопка "Из архива". */
.archive-panel {
  display: flex;
  flex-direction: column;
}
.archive-panel--customers {
  width: 300px;
  flex-shrink: 0;
}
.archive-panel--orders {
  flex: 1;
  min-width: 0;
}
/* Шапка карточки ("Заказчики в архиве" / "Архивные заказы" + счётчик) —
   заголовок обычным текстом, без чипа, в отличие от .section-card__head на
   карточке заказчика. */
.archive-panel__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: 8px;
}
.archive-panel__title {
  margin: 0;
  font-size: var(--f-size-footnote);
  font-weight: var(--f-weight-bold);
  color: var(--ink-base);
}
.archive-panel__count {
  margin: 0;
  font-size: var(--f-size-caption);
  color: var(--ink-secondary);
}
.archive-panel__hint {
  margin: 0 0 8px;
  font-size: var(--f-size-caption);
  color: var(--ink-muted);
}
/* Заказчик в архиве — своя скруглённая строка (не дашед-разделённый поток,
   как .customer-row на /orders), т.к. у каждой есть отдельная кнопка "Из
   архива" рядом, а не общая на "выбранного". */
.archive-customer-row {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 10px;
  border-radius: 14px;
}
.archive-customer-row__link {
  display: flex;
  align-items: center;
  gap: 12px;
  flex: 1;
  min-width: 0;
  text-decoration: none;
  color: inherit;
}
.archive-customer-row__info {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 1px;
}
.archive-customer-row__name {
  margin: 0;
  font-size: var(--f-size-footnote);
  font-weight: var(--f-weight-bold);
  color: var(--ink-secondary);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.archive-customer-row__sub {
  margin: 0;
  font-size: var(--f-size-caption);
  color: var(--ink-muted);
  white-space: nowrap;
}
/* Кнопка "Из архива" — чип, не .btn--* (компактнее, без веса обычной
   кнопки — это второстепенное действие в строке списка, не основное). Форма
   через display:contents, чтобы не ломать flex-раскладку строки лишним
   контейнером. */
.chip-btn-form {
  display: contents;
}
.chip-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  padding: 4px 11px;
  border-radius: var(--r-pill);
  background: var(--paper-card);
  border: 1px solid var(--paper-line);
  font-family: var(--f-family-body);
  font-size: var(--f-size-caption);
  font-weight: var(--f-weight-medium);
  color: var(--ink-secondary);
  cursor: pointer;
  white-space: nowrap;
}
.chip-btn:hover {
  border-color: var(--paper-linestrong);
  color: var(--ink-base);
}

/* Дровер "Новый заказчик" (orders_list.html) — форма без явного gap между
   полями схлопывалась (div-ы стоят вплотную, form сама не flex) — отсюда
   "сплюснутые" отступы между значением поля и подписью следующего. */
.drawer-form {
  display: flex;
  flex-direction: column;
  gap: 11px;
  width: 100%;
}
/* Отступ подпись→поле внутри одного .field-group вдвое меньше, чем между
   разными полями (11px из .drawer-form выше) — не трогаем .field-group
   глобально (он общий с /login), сужаем только тут. */
.drawer-form .field-group {
  gap: 3px;
}
/* Шапка дровера "Профиль реквизитов" — аватар + заголовок/подзаголовок +
   крестик закрытия, в один ряд (в отличие от простого .drawer__head с
   одним только заголовком у других дроверов). */
.profile-drawer-head {
  display: flex;
  align-items: center;
  gap: 11px;
  width: 100%;
}
.profile-drawer-head__text {
  flex: 1;
  min-width: 0;
}
.profile-drawer-head__title {
  margin: 0;
  font-size: var(--f-size-h3);
  font-weight: var(--f-weight-bold);
  color: var(--ink-base);
}
.profile-drawer-head__lede {
  margin: 1px 0 0;
  font-size: var(--f-size-caption);
  color: var(--ink-secondary);
}

/* Группа полей с подписью-заголовком и дэш-разделителем сверху (дровер
   "Профиль реквизитов" — "реквизиты"/"банк"/"кто подписывает"/"связь").
   Дважды считанный отступ — так и в Figma: gap между группами (из
   .drawer-form выше) отделяет группу от предыдущего контента, а pt здесь —
   ещё немного воздуха у этой группы уже ПОСЛЕ её собственной черты. */
.drawer-group {
  display: flex;
  flex-direction: column;
  gap: 10px;
  width: 100%;
  padding-top: 11px;
  border-top: 1px dashed var(--paper-line);
}
.drawer-group__label {
  margin: 0;
  font-size: var(--f-size-caption);
  font-weight: var(--f-weight-bold);
  color: var(--ink-secondary);
}

/* Кастомный выпадающий список с описанием у каждой опции (напр. "Форма
   деятельности" в дровере профиля реквизитов) — по Figma-ноде 289:559.
   Закрытая кнопка нарочно = .field (совпадает с соседним полем в паре,
   а не отдельным бумажным фоном из макета-примера); раскрытое меню —
   уже точно по макету: опции с подписью-описанием, текущий выбор
   подсвечен --chip-today и галочкой справа. */
.select-custom {
  position: relative;
  width: 100%;
}
.select-custom__control {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  width: 100%;
  cursor: pointer;
  text-align: left;
  font-family: var(--f-family-body);
}
.select-custom__control--open {
  border-color: var(--service-600);
  border-width: 1.5px;
}
.select-custom__value {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.select-custom__chevron {
  flex-shrink: 0;
  color: var(--ink-secondary);
  transition: transform var(--m-duration-fast) var(--m-easing-out),
              color var(--m-duration-fast) var(--m-easing-out);
}
/* Figma-нода 289:559: открытая стрелка не просто разворачивается — она
   красится в тот же service-600, что и рамка "Селект · открыт" (была
   упущена при первой сборке — стрелка молча оставалась серой). */
.select-custom__control--open .select-custom__chevron {
  transform: rotate(180deg);
  color: var(--service-600);
}
/* [hidden] — author display:flex обычно перебивает браузерный [hidden]{display:none}
   (авторские правила всегда сильнее user-agent, при любой специфичности) —
   тот же приём, что и у dialog.drawer[open] в CLAUDE.md: включаем flex только
   когда hidden явно снят, а не безусловно на классе. */
/* Высота — 3-4 опции (230px), остальное скроллом внутри меню, не разворотом
   всей страницы (был баг: длинный список — напр. заказов — растягивал меню
   во весь экран). border-radius 14px — точное значение по эталонному
   Figma-узлу 304:1854 (не var(--r-lg)/16px — тот был взят с другого узла,
   289:559, при первой сборке компонента).

   position:fixed, не absolute (2026-07-25, найдено на поле «Заказ» в
   дровере «Записать» — раскрытие меню утягивало весь дровер в скролл).
   Проверено эмпирически: position:absolute внутри position:relative-предка,
   который лежит внутри overflow:auto-контейнера (тут — dialog.drawer),
   ВСЕГДА засчитывается в scrollHeight этого overflow:auto, даже когда сам
   абсолютный элемент визуально выглядит как оверлей поверх контента — это
   не баг конкретной вёрстки, а как CSS вообще считает overflow (единственный
   способ по-настоящему выйти из этого счёта, без портала в другой узел DOM —
   position:fixed, он не считается ни одним overflow-предком, кроме тех, что
   сами создают containing block для fixed через transform/filter/perspective/
   contain — у dialog.drawer и его предков такого нет). top/left/width
   выставляются в app.js при открытии (position: relative у .select-custom
   для этого больше не нужен, но не мешает — просто не используется). */
.select-custom__menu {
  list-style: none;
  margin: 0;
  padding: 7px;
  position: fixed;
  z-index: 50;
  flex-direction: column;
  gap: 2px;
  /* Ширина — по содержимому, а не по ширине поля. У узких полей (напр.
     «Периодичность» в дровере заказа — 164px в паре) описания опций
     («сумма спишется каждый месяц до дедлайна») ломались лесенкой. Нижнюю
     границу задаёт JS инлайновым min-width, равным ширине контрола: меню
     никогда не у́же своего поля. Потолок — чтобы длинные названия заказов в
     комбобоксе не растянули список на пол-экрана; они и так обрезаются
     многоточием. Выравнивание по краю, когда не влезает, — в app.js. */
  width: max-content;
  max-width: 420px;
  max-height: 230px;
  overflow-y: auto;
  background: var(--paper-card);
  border: 1px solid var(--paper-line);
  border-radius: 14px;
  box-shadow: 0 8px 24px rgba(36, 38, 43, 0.16);
}
/* Поиск внутри открытого меню (data-select-search в app.js) — sticky-строка
   поверх скроллящегося списка опций: отрицательный margin компенсирует
   padding меню, чтобы фон строки поиска доходил до самых краёв и не было
   щели по бокам/сверху, когда список опций скроллит под ней. */
.select-custom__search-row {
  position: sticky;
  top: -7px;
  z-index: 1;
  margin: -7px -7px 4px;
  padding: 7px 7px 5px;
  background: var(--paper-card);
  cursor: default;
}
.select-custom__search {
  display: block;
  width: 100%;
  height: 34px;
  padding: 0 10px;
  background: var(--paper-sunk);
  border: 1px solid var(--paper-line);
  border-radius: var(--r-sm);
  font-family: var(--f-family-body);
  font-size: var(--f-size-footnote);
  color: var(--ink-base);
}
.select-custom__search:focus {
  outline: none;
  border-color: var(--service-600);
}
.select-custom__search::placeholder {
  color: var(--ink-secondary);
}
.select-custom__menu:not([hidden]) {
  display: flex;
}
/* Figma-нода 289:574: gap между текстом и галочкой — 12px в макете (при
   масштабе ×1.2 — 14px), паддинг опции — 10px (→12px) — было упущено при
   первой сборке (стояло по 10px на оба, из-за чего опции выглядели заметно
   теснее макета). */
/* display:flex вынесен в :not([hidden]) — та же ловушка, что и с
   .select-custom__menu (см. комментарий выше): авторское display:flex
   перебивает браузерный UA-стиль [hidden]{display:none} по origin
   (авторские правила старше UA независимо от специфичности), поэтому без
   :not() отфильтрованные поиском опции (app.js: opt.hidden = true)
   оставались бы видимыми — найдено именно на этом месте при добавлении
   комбобокс-поиска. */
.select-custom__option {
  align-items: center;
  justify-content: space-between;
  gap: 14px;
  padding: 12px;
  border-radius: 10px;
  cursor: pointer;
}
.select-custom__option:not([hidden]) {
  display: flex;
}
.select-custom__option:hover {
  background: var(--paper-sunk);
}
.select-custom__option--selected,
.select-custom__option--selected:hover {
  background: var(--chip-today);
}
/* flex:1/min-width:0 — заставляет заголовок опции реально сжиматься внутри
   строки-флекса (иначе overflow/ellipsis ниже не сработают, флекс-айтемы по
   умолчанию не сжимаются меньше своего контента); ellipsis вместо переноса
   на вторую строку — длинное название заказа теперь обрезается «…», а не
   раздувает высоту строки (была реальная разница в высоте строк в списке —
   see .select-custom__menu max-height комментарий выше). */
.select-custom__option-title {
  margin: 0;
  font-size: var(--f-size-bodylg);
  font-weight: var(--f-weight-medium);
  color: var(--ink-base);
  flex: 1;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
/* В макете заголовок выбранной опции — Bold, у остальных — SemiBold
   (=--f-weight-medium выше); было упущено при первой сборке — все опции
   шли одним весом. */
.select-custom__option--selected .select-custom__option-title {
  font-weight: var(--f-weight-bold);
}
.select-custom__option-desc {
  margin: 2px 0 0;
  font-size: var(--f-size-caption);
  color: var(--ink-secondary);
}
.select-custom__check {
  flex-shrink: 0;
  color: var(--service-600);
  visibility: hidden;
}
.select-custom__option--selected .select-custom__check {
  visibility: visible;
}
/* Сетка месяцев (Figma-нода 336:926, «Дровер · Выбор месяца» — по образцу
   отдельного компонента-справочника «Полоса года» на том же фрейме) — тот
   же select-custom, тот же JS без единой правки (клик по [data-select-option]
   работает одинаково для строки списка и для клетки сетки), только другой
   визуальный модификатор меню/опции: 4 колонки вместо вертикального списка,
   без описания и галочки — состояние "выбран" читается по подсветке и
   жирности самого текста, как на "Полосе года". */
/* :not([hidden]) — тот же приём, что и у .select-custom__menu:not([hidden])
   чуть выше: обычный display:grid здесь той же специфичности, что и
   браузерный [hidden]{display:none}, и как автор-правило молча выигрывает
   каскад у него всегда, даже когда атрибут hidden стоит — меню висело бы
   открытым постоянно. Без :not() это и произошло при первой сборке. */
.select-custom__menu--grid:not([hidden]) {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 2px;
  padding: 10px;
  width: max-content;
}
.select-custom__option--month {
  justify-content: center;
  width: 52px;
  height: 19px;
  padding: 0;
  font-size: 11px;
  font-weight: var(--f-weight-regular);
  color: var(--ink-secondary);
  text-align: center;
}
.select-custom__option--month.select-custom__option--selected {
  font-weight: var(--f-weight-bold);
  color: var(--ink-base);
}
/* Группировка опций по категориям (Figma-нода 334:925 — "Шаблон документа"
   с категориями шаблонов сверху). Заголовок категории — тот же
   .select-custom__option (тот же паддинг/радиус/подсветка при выборе), но
   не кликабельный (не выбор сам по себе, JS не навешивает на него
   data-select-option) — только cursor:default отличает его от опции.
   Шаблоны внутри — .select-custom__option--sub: без описания и галочки,
   меньше текст, с отступом слева вместо центрирования по обычной сетке
   паддингов — раздельные строки визуально "вложены" под заголовком. */
.select-custom__option--header {
  cursor: default;
}
.select-custom__option--sub {
  padding: 6px 12px 6px 24px;
}
.select-custom__option-title--sub {
  font-size: var(--f-size-caption);
  font-weight: var(--f-weight-regular);
  color: var(--ink-secondary);
}
/* .select-custom__option--selected .select-custom__option-title (общее
   правило, выше) красит заголовок Bold при выборе — в макете сам шаблон
   внутри категории остаётся Regular даже выбранным (bold есть только у
   заголовка категории), поэтому здесь отдельно возвращаем вес обратно. */
.select-custom__option--selected .select-custom__option-title--sub {
  font-weight: var(--f-weight-regular);
}
/* data-select-required (app.js) — если отправили форму без выбора,
   подсвечиваем закрытую кнопку тем же цветом, что и .field--error. */
.select-custom__control--error {
  border-color: var(--daruma-400);
}

/* Пара полей в одну строку (Телефон/Email) и мелкая подсказка под полем. */
.field-pair {
  display: flex;
  gap: 10px;
  width: 100%;
}
.field-pair > .field-group {
  flex: 1;
  min-width: 0;
}
/* Блок «Напоминание» (см. _macros.html:reminder_block). Второе напоминание
   скрыто атрибутом hidden, поэтому display задан ТОЛЬКО под :not([hidden]) —
   авторский display перебил бы браузерный [hidden]{display:none}, и поле
   висело бы раскрытым всегда (в проекте на эти грабли уже наступали трижды:
   .select-custom__menu, .single-date-field__popover, .icon-btn). */
.reminder-block__second:not([hidden]) {
  display: block;
  margin-top: 8px;
}
/* Крестик у второго напоминания встаёт в ряд с полями, а не над ними: в
   .field-pair всё растягивается по высоте, а кнопке-значку это ни к чему. */
.reminder-block__drop {
  align-self: center;
  flex-shrink: 0;
}
.reminder-block__add {
  align-self: flex-start;
  margin-top: 8px;
  font-size: var(--f-size-footnote);
}
.field-hint {
  margin: 3px 0 0;
  font-size: var(--f-size-caption);
  color: var(--ink-secondary);
}
.field-checkbox {
  display: flex;
  align-items: center;
  gap: 8px;
  width: 100%;
  padding: 5px 0;
  font-size: var(--f-size-footnote);
  color: var(--ink-base);
  cursor: pointer;
  /* Без явного margin сюда протекал легаси "form label { margin: 0.75rem 0
     0.25rem }" (для обычных подписей над полями) — этот label не подпись
     поля, а сама кликабельная строка с чекбоксом. */
  margin: 0;
}

/* ============================== Страница «Заказчик» ======================= */

.customer-header {
  display: flex;
  align-items: center;
  gap: 12px;
  margin-bottom: 11px;
}
.customer-header__info {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 4px;
}
.customer-header__name {
  margin: 0;
  font-size: var(--f-size-h2);
  font-weight: var(--f-weight-bold);
  color: var(--ink-base);
}
.customer-body {
  display: flex;
  align-items: flex-start;
  gap: 11px;
}
.customer-col--left {
  flex-shrink: 0;
  width: 360px;
  display: flex;
  flex-direction: column;
  gap: 11px;
}
.customer-col--right {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 11px;
}

/* Плашка-заголовок секции внутри карточки ("контакты"/"папки"/...). */
.section-chip {
  display: inline-flex;
  align-items: center;
  height: 22px;
  padding: 0 12px;
  background: var(--chip-day);
  border-radius: var(--r-pill);
  font-size: var(--f-size-caption);
  font-weight: var(--f-weight-bold);
  color: var(--ink-base);
}
/* "Сегодня" — та же плашка, но жёлтая, не оранжевая (Figma-нода 14:6,
   карточка "День · сегодня" на "Неделе"). */
.section-chip--today {
  background: var(--chip-today);
}
/* Выходные дни в "Дни 4-7" на "Неделе" (Figma-нода 15:25) — тот же чип,
   розовый вместо оранжевого. */
.section-chip--off {
  background: var(--chip-off);
}
.section-card__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: 6px;
  padding-bottom: var(--space-head-body);
}
/* Весь отступ шапка→тело задаёт сама шапка (см. --space-head-body): первая
   строка своего верхнего отступа не добавляет, иначе одинаковые на вид
   карточки расходились на 7-20px в зависимости от того, что стоит первым
   (строка с padding, пустое состояние, ссылка без padding). Шкала — не
   исключение по величине зазора (см. --space-head-scale), но её отступы
   задаются отдельным правилом, поэтому она здесь исключена.

   `:not(.dash-pipe)` держит специфичность (0,2,0) — без него правило
   проигрывает по порядку файла более поздним `.money-row`/`.dash-debt-row`
   с их собственным padding, и зазор молча уезжает с 13 на 19px.

   `.subscription-leak` и `.order-line--problem` исключены 05.08: это не
   строки списка, а плашки со своим фоном (у первой ещё и рамка). Обнуление padding-top прижимало её текст к верхнему
   краю собственной подложки — снизу 7px, сверху 0, и уведомление выглядело
   съехавшим. У плашки свой симметричный padding, трогать его норме нечего. */
.section-card__head + *:not(.dash-pipe):not(.subscription-leak):not(.order-line--problem) {
  margin-top: 0;
  padding-top: 0;
  /* Разделителя под шапкой не бывает: строки-списки (.money-row и т.п.)
     несут border-top как разделитель МЕЖДУ собой, и первой строке он
     доставался просто по инерции — получалась лишняя линия сразу под
     чипом ("инструменты" на дашборде, "карты" на /me). */
  border-top: none;
}
/* Карточка долгов: у первой строки-расшифровки тоже был разделитель сразу
   под заголовком. Селектор с родителем — .dash-debt-row объявлена ниже по
   файлу и при равной специфичности выигрывала. */
.dash-debts .dash-debts__head + * {
  border-top: none;
}
.section-card__actions {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 6px;
}

/* Строка "подпись: значение" внутри карточек контактов/папок. */
.info-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  padding: 6px 0;
  border-top: 1px dashed var(--paper-line);
}
.info-row--first {
  border-top: none;
}
.info-row__label {
  margin: 0;
  font-size: var(--f-size-caption);
  color: var(--ink-secondary);
  white-space: nowrap;
}
/* Примечание под строками досье (описание заказа) — тем же тоном, что
   подписи, но многострочное и с принудительным переносом: сюда попадает
   произвольный текст пользователя, в том числе "слова" без пробелов
   (пути, адреса), которые иначе распирают карточку по ширине.
   Раньше это был .info-row__label с инлайновым white-space: normal —
   переносить по словам он умел, а рвать длинное слово нет. */
.info-row__note {
  margin: 0;
  padding-top: 6px;
  font-size: var(--f-size-caption);
  color: var(--ink-secondary);
  overflow-wrap: anywhere;
  word-break: break-word;
}
.info-row__value {
  margin: 0;
  font-size: var(--f-size-footnote);
  font-weight: var(--f-weight-bold);
  color: var(--ink-base);
  text-align: right;
}
.info-row__value--link {
  color: var(--brand-mint);
}
.info-row__value--muted {
  color: var(--ink-secondary);
}
/* "Кто платит" не заполнено (Figma-нода 131:327) — та же строка, только
   с розовой подложкой и красным значением "— выбрать —"; горизонтальный
   паддинг у подложки компенсирован отрицательным margin, чтобы текст не
   сдвигался относительно соседних строк без подложки. */
.info-row--danger {
  background: var(--semantic-danger-bg);
  border-radius: var(--r-sm);
  margin: 0 -7px;
  padding: 5px 7px;
}
.info-row__value--danger {
  color: var(--semantic-danger-text);
}
/* Крестик-стикер (01_мои/icon/icon_delete.svg) — единая иконка для закрытия
   дроверов (.drawer__close) и удаления строк (.icon-btn--danger/.btn-link-
   danger), см. _macros.html:icon_cross(). Сам рисунок двухцветный (не
   currentColor), только flex-shrink на случай использования внутри узких
   flex-рядов. */
.icon-cross {
  flex-shrink: 0;
}
.icon-btn {
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  width: 28px;
  height: 28px;
  padding: 0;
  border: none;
  border-radius: var(--r-md);
  background: var(--paper-card);
  color: var(--ink-secondary);
  cursor: pointer;
}
/* display вынесен в :not([hidden]) — та же ловушка [hidden], что и у
   .select-custom__menu/.single-date-field__popover (см. комментарии там):
   авторский display старше браузерного [hidden]{display:none} независимо
   от специфичности. .icon-btn раньше нигде не скрывался через hidden (все
   прежние применения — постоянно видимые кнопки), первый живой случай —
   .single-date-field__clear. */
.icon-btn:not([hidden]) {
  display: inline-flex;
}
.icon-btn:hover {
  background: var(--paper-sunk);
  color: var(--ink-base);
}
.icon-btn--copied {
  border-color: var(--semantic-success-border);
  color: var(--semantic-success-text);
}
.icon-btn--danger {
  color: var(--daruma-400);
}
.icon-btn--danger:hover {
  background: var(--semantic-danger-bg);
  color: var(--semantic-danger-text);
}
.icon-btn:disabled {
  opacity: 0.35;
  cursor: not-allowed;
}
.icon-btn:disabled:hover {
  background: var(--paper-card);
  color: var(--ink-secondary);
}
.notes-text {
  margin: 0;
  font-size: var(--f-size-footnote);
  color: var(--ink-base);
}

/* Карточка бана. */
.ban-pill {
  display: inline-flex;
  align-items: center;
  height: 22px;
  padding: 0 12px;
  border: 1px solid var(--semantic-danger-border);
  border-radius: var(--r-pill);
  font-size: var(--f-size-caption);
  font-weight: var(--f-weight-bold);
  color: var(--semantic-danger-text);
}
.ban-reason-readonly {
  margin: 0;
  font-size: var(--f-size-footnote);
  color: var(--ink-base);
  font-style: italic;
}
.ban-actions {
  display: flex;
  gap: 8px;
  justify-content: flex-end;
}

/* Профиль реквизитов — компактная строка в карточке. */
.profile-row {
  display: flex;
  flex-direction: column;
  gap: 4px;
  padding: 7px 0;
  border-top: 1px dashed var(--paper-line);
}
.profile-row--first {
  border-top: none;
}
.profile-row__top {
  display: flex;
  align-items: center;
  gap: 8px;
}
.profile-row__name {
  flex: 1;
  min-width: 0;
  margin: 0;
  font-size: var(--f-size-footnote);
  font-weight: var(--f-weight-bold);
  color: var(--ink-base);
}
.profile-row__sub {
  margin: 0;
  font-size: var(--f-size-caption);
  color: var(--ink-secondary);
}

/* Информационная плашка (приватность/публичность реквизитов). */
.info-box {
  display: flex;
  align-items: center;
  gap: 11px;
  padding: 8px 11px;
  background: var(--semantic-info-bg);
  border: 1px solid var(--semantic-info-border);
  border-radius: var(--r-md);
}
.info-box__title {
  margin: 0;
  font-size: var(--f-size-caption);
  font-weight: var(--f-weight-bold);
  color: var(--semantic-info-text);
}
.info-box__text {
  margin: 0;
  font-size: var(--f-size-caption);
  color: var(--ink-secondary);
}

/* Компактная строка заказа в карточке "Заказы" на странице заказчика —
   без аватара/шкалы, в отличие от .order-row на /orders. */
.order-line {
  display: flex;
  align-items: center;
  gap: 11px;
  padding: 6px 0;
  border-top: 1px dashed var(--paper-line);
  text-decoration: none;
  color: inherit;
}
.order-line--first {
  border-top: none;
}
.order-line--archived {
  opacity: 0.65;
}
/* Просроченный заказ выбивается из общего дашед-разделённого потока строк —
   собственный тонированный блок со скруглением, не просто смена фона. */
.order-line--problem {
  border-top: none;
  background: var(--semantic-danger-bg);
  padding: 6px 11px;
  border-radius: var(--r-md);
}
.order-line__info {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 1px;
  text-decoration: none;
  color: inherit;
}
.order-line__title {
  margin: 0;
  font-size: var(--f-size-footnote);
  font-weight: var(--f-weight-bold);
  color: var(--ink-base);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.order-line__sub {
  margin: 0;
  font-size: var(--f-size-caption);
  color: var(--ink-secondary);
  white-space: nowrap;
}
.order-line__money {
  flex-shrink: 0;
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: 1px;
}
.order-line__amount {
  margin: 0;
  font-size: var(--f-size-footnote);
  font-weight: var(--f-weight-bold);
  color: var(--ink-base);
  white-space: nowrap;
}
.order-line__note {
  margin: 0;
  font-size: var(--f-size-caption);
  color: var(--ink-secondary);
  white-space: nowrap;
}
.order-line__note--danger {
  color: var(--semantic-danger-text);
}

/* Дровер выбора зверя-аватара — сетка тайлов из инлайн-спрайта. Тайл 50px
   в Figma → 60px (×1.2, см. правило масштаба в CLAUDE.md). */
.avatar-grid {
  display: flex;
  flex-wrap: wrap;
  gap: 14px 12px;
}
.avatar-grid__tile {
  width: 60px;
  height: 60px;
  padding: 0;
  border: 2px solid transparent;
  border-radius: var(--r-md);
  background: none;
  cursor: pointer;
}
.avatar-grid__tile svg {
  width: 100%;
  height: 100%;
  border-radius: var(--r-md);
}
.avatar-grid__tile--selected {
  border-color: var(--service-600);
}
.avatar-grid__upload {
  width: 60px;
  height: 60px;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 2px;
  padding: 0;
  border: 2px dashed var(--paper-line);
  border-radius: var(--r-md);
  background: var(--paper-sunk);
  font-size: var(--f-size-caption);
  color: var(--ink-secondary);
  cursor: pointer;
}
.drawer__actions {
  display: flex;
  gap: 8px;
  justify-content: flex-end;
  padding-top: 4px;
}
/* Удаление в ряду действий дровера уезжает к левому краю, подальше от
   основного действия справа — чтобы не нажать его вслепую рядом с
   "Сохранить"/"Загадать". */
.drawer__actions .btn--ghost-danger {
  margin-right: auto;
}

/* Дровер — переиспользуемая карточка поверх размытой подложки (rgba бумажной
   линии, не туши — подложка тоже бумажная, не тёмная). Общий паттерн для
   «вернуть доступ» и будущих экранов (просмотр меток и т.п.), см. app.js —
   клик по подложке закрывает через bounding rect самого <dialog>. Плоская
   структура (без отдельной шапки с рамкой) — так на макете: заголовок и ✕
   в один ряд без разделителя, дальше просто gap:12 между блоками. */

/* display:flex только когда открыт ([open]) — иначе перебивает браузерный
   dialog:not([open]) { display: none } той же специфичностью и дровер висит
   в потоке страницы всегда, а не только после showModal(). */
dialog.drawer[open] {
  display: flex;
  flex-direction: column;
  gap: 14px;
}
dialog.drawer {
  padding: 19px;
  width: 100%;
  max-width: 552px;
  border: 1px solid var(--paper-line);
  border-radius: var(--r-xl);
  box-shadow: none;
}
dialog.drawer::backdrop {
  background: var(--backdrop-bg);
  backdrop-filter: blur(var(--backdrop-blur));
}

/* Подтверждение удаления шаблона — по Figma-ноде 175:582, вместо
   системного confirm(). Компактный, всегда по центру экрана (наравне с
   остальными dialog.drawer — <dialog> центрируется браузером сам при
   showModal(), position:fixed/margin:auto прибиты явно про запас). */
dialog.confirm-dialog {
  max-width: 401px;
  text-align: center;
  position: fixed;
  inset: 0;
  margin: auto;
}
.confirm-dialog__title {
  margin: 0;
  font-size: var(--f-size-bodylg);
  font-weight: var(--f-weight-bold);
  color: var(--ink-base);
}
.confirm-dialog__note {
  margin: 0;
  font-size: var(--f-size-caption);
  color: var(--ink-secondary);
}
.confirm-dialog__actions {
  display: flex;
  gap: 7px;
  justify-content: center;
  width: 100%;
  padding-top: 5px;
}
.confirm-dialog__form {
  flex: 1;
  display: flex;
}
.confirm-dialog__cancel {
  flex: 1;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  height: 43px;
  padding: 0 17px;
  background: transparent;
  border: 1px solid var(--semantic-danger-border);
  border-radius: var(--r-md);
  font-family: var(--f-family-body);
  font-size: var(--f-size-footnote);
  font-weight: var(--f-weight-medium);
  color: var(--ink-secondary);
  cursor: pointer;
}
.confirm-dialog__cancel:hover {
  background: var(--semantic-danger-bg);
}
.confirm-dialog__delete {
  width: 100%;
  text-transform: uppercase;
}
.drawer__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
}
/* Шапка дровера, когда над заголовком стоит плашка-чип (Figma-нода 310:2233,
   дровер "счет"): чип и заголовок столбиком слева, ✕ по-прежнему справа.
   Крестик при этом выравнивается по верху, а не по центру двухэтажной
   левой части. */
.drawer__head:has(> .drawer__head-main) {
  align-items: flex-start;
}
.drawer__head-main {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 6px;
}
.drawer__title {
  margin: 0;
  font-size: var(--f-size-h2);
  font-weight: var(--f-weight-bold);
  color: var(--ink-base);
}
.drawer__close {
  border: none;
  background: none;
  padding: 0;
  font-size: 16px;
  font-weight: var(--f-weight-medium);
  line-height: 1;
  color: var(--ink-secondary);
  cursor: pointer;
}
.drawer__close:hover {
  color: var(--ink-base);
}
/* Крестик закрытия кольцо фокуса не показывает НИКОГДА — единственное
   исключение из общего правила для кнопок (см. блок «Кнопки»).

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

   Фокус с крестика мы и так уводим (base.html), это — гарантия на случай
   любого другого пути. Доступность не страдает: крестик дублирует и Esc, и
   «Отмена», а Tab по остальным элементам кольцо по-прежнему показывает. */
.drawer__close:focus-visible {
  outline: none;
}
/* Сам дровер фокусируется программно (см. base.html) — это контейнер, а не
   орган управления, показывать на нём фокус нечего. */
dialog.drawer:focus {
  outline: none;
}
.drawer__lede {
  margin: 0;
  font-size: var(--f-size-body);
  color: var(--ink-secondary);
}
.drawer__method {
  display: flex;
  flex-direction: column;
  gap: 6px;
}
/* Форма "вернуть доступ" обнимает обе секции способов сразу (одно поле почты
   на оба канала), поэтому повторяет колонку и зазор самого дровера — иначе
   секции внутри неё слиплись бы, потеряв gap: 14px родителя. */
.drawer__recover {
  display: flex;
  flex-direction: column;
  gap: 14px;
}
.drawer__method-label {
  margin: 0;
  font-size: var(--f-size-caption);
  font-weight: var(--f-weight-medium);
  color: var(--ink-secondary);
}
.drawer__cancel {
  display: block;
  width: 100%;
  text-align: center;
  font-size: var(--f-size-footnote);
}

/* Дровер "Записать" (Figma-ноды 17:2/17:56/17:118) — единый дровер на три
   типа записи, переключение сегмента "Задача/Созвон/Встреча" чистым CSS
   (радио + :has(), без JS): скрытый через display:none блок автоматически
   исключается из required-валидации формы по спеке HTML ("barred from
   constraint validation"), так что required-поля неактивных табов не
   блокируют отправку. Первое использование :has() в проекте — тот же
   Chrome, что и весь остальной интерфейс, поддержка уже есть. */
.record-type-segment {
  display: flex;
  gap: 5px;
  width: 100%;
}
.record-type-tab {
  display: flex;
  flex: 1;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 4px;
  padding: 10px 7px;
  background: var(--paper-card);
  border: 1px solid var(--paper-line);
  border-radius: var(--r-md);
  cursor: pointer;
}
.record-type-icon {
  width: 22px;
  height: 22px;
  color: var(--ink-base);
}
/* Созвон/встреча в табах — тем же --daruma-400, что и умэ в соседнем табе
   "Задача" и что иконки на "Неделе" (см. .week-entry-row__icon): иконка
   теперь заливкой, а не контуром, и чернильный ink-base превращал бы её в
   тёмное пятно. Умэ не трогаем — у него свои переменные, цвет тот же. */
.record-type-icon--call,
.record-type-icon--meeting {
  color: var(--daruma-400);
}
.record-type-tab p {
  margin: 0;
  font-size: var(--f-size-caption);
  font-weight: var(--f-weight-bold);
  color: var(--ink-base);
}
.record-fields {
  display: none;
  flex-direction: column;
  gap: 11px;
}
.drawer-form:has(#record-type-task:checked) .record-fields[data-record-fields="task"],
.drawer-form:has(#record-type-call:checked) .record-fields[data-record-fields="call"],
.drawer-form:has(#record-type-meeting:checked) .record-fields[data-record-fields="meeting"] {
  display: flex;
}
.drawer-form:has(#record-type-task:checked) label[for="record-type-task"],
.drawer-form:has(#record-type-call:checked) label[for="record-type-call"],
.drawer-form:has(#record-type-meeting:checked) label[for="record-type-meeting"] {
  background: var(--service-100);
  border-color: var(--service-600);
}

/* ============================== Страница входа ============================ */

.content:has(.login-screen) {
  max-width: none;
  padding: 0;
}

/* Фон-узор — реальный ассет из Figma (node 244:4581), не самодельный паттерн:
   уже в нужном мятном оттенке (mascot-iris) и почти невидимой прозрачности
   (~0.12-0.13 на иконку), просто повторяется как обычная CSS-текстура. */
.login-screen {
  position: relative;
  min-height: 100vh;
  display: flex;
  justify-content: center;
  padding: 48px 16px 32px;
  background-color: var(--paper-base);
  background-image: url('/static/svg/login-background-tile.svg');
  background-repeat: repeat;
  background-size: 940px 979px;
}
.login-col {
  position: relative;
  width: 100%;
  max-width: 440px;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 14px;
  text-align: center;
}
.login-hero-daruma {
  width: 72px;
  height: 88px;
}
.login-brand {
  font-size: 30px;
  margin: 0;
}
.login-tagline {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  margin: 0;
  font-size: var(--f-size-h2);
  color: var(--ink-base);
  font-weight: var(--f-weight-bold);
}
.login-lede {
  margin: 0;
  font-size: var(--f-size-body);
  color: var(--ink-secondary);
}
.login-wish {
  width: 100%;
  padding: 12px 16px;
  text-align: center;
  background: var(--chip-cool);
  border-color: var(--chip-cool);
}
.login-wish__text {
  margin: 0;
  font-size: var(--f-size-body);
  font-weight: var(--f-weight-medium);
  color: var(--ink-base);
}
/* Ответ на запрос ссылки сброса и «ссылка устарела» — та же карточка входа,
   только текстом по центру: читается как продолжение /login, а не как
   отдельная страница неизвестно откуда. */
.recover-notice {
  text-align: center;
}
.recover-notice__title {
  margin: 0;
  font-size: var(--f-size-body);
  font-weight: var(--f-weight-bold);
  color: var(--ink-base);
}
.login-card {
  width: 100%;
  display: flex;
  flex-direction: column;
  gap: 12px;
  text-align: left;
}
.login-card__form {
  display: flex;
  flex-direction: column;
  gap: 12px;
}
.field-group {
  display: flex;
  flex-direction: column;
  gap: 6px;
}
.login-field-error {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
}
.login-field-error .field-error {
  margin: 0;
  font-size: 13px;
  font-weight: var(--f-weight-bold);
}
/* Дарума-неваляшка на ошибке пароля качается в стороны, не дышит (переопределяем
   анимацию daruma--sleep на d-wobble — тот же keyframe и transform-origin,
   что у daruma--wish/work/done в daruma.css, просто на сонном лице). */
.login-error-daruma {
  transform-origin: 50% 90%;
  animation: d-wobble var(--m-duration-wobble) var(--m-easing-inout) infinite;
}
.login-forgot {
  text-align: center;
}
.login-telegram {
  width: 100%;
  display: flex;
  flex-direction: column;
  gap: 8px;
  align-items: center;
  text-align: center;
  background: var(--paper-sunk);
  padding: 14px 16px;
}
.login-telegram__title {
  margin: 0;
  font-size: var(--f-size-body);
  font-weight: var(--f-weight-bold);
  color: var(--ink-base);
}
.login-telegram__text {
  margin: 0;
  font-size: var(--f-size-footnote);
  color: var(--ink-secondary);
}
.login-footer {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 6px;
  text-align: center;
}
.login-footer__line {
  margin: 0;
  font-size: var(--f-size-footnote);
  color: var(--ink-secondary);
}
.login-footer__line--muted {
  font-size: var(--f-size-caption);
  color: var(--ink-muted);
}
.login-footer__actions {
  display: flex;
  justify-content: center;
  gap: 8px;
  padding: 4px 0 2px;
}
.login-footer__note {
  margin: 0;
  font-size: var(--f-size-caption);
  color: var(--ink-muted);
}
.login-footer a {
  color: var(--ink-base);
}

@media (max-width: 480px) {
  .login-screen {
    padding: 32px 12px 24px;
  }
  .login-footer__actions {
    flex-direction: column;
    align-items: stretch;
  }
}

/* ============================== Страница «Шаблоны» ========================= */
/* По Figma-ноде 301:707 (список + читалка, ничего не выбрано) и 167:482
   (читалка с открытым шаблоном). Хардкод-размеры — исходные из Figma ×1.2
   (см. CLAUDE.md, «МАСШТАБ»), радиусы и цвета — как есть, через токены. */

.templates-head {
  display: flex;
  align-items: center;
  gap: 12px;
  margin-bottom: 14px;
}
.templates-head__titles {
  display: flex;
  flex-direction: column;
  gap: 4px;
}
.templates-head__title {
  margin: 0;
  font-size: var(--f-size-h1);
  font-weight: var(--f-weight-bold);
  color: var(--ink-base);
}
.templates-head__subtitle {
  margin: 0;
  font-size: var(--f-size-caption);
  color: var(--ink-secondary);
}
.templates-head__spacer {
  flex: 1;
}

.templates-body {
  display: flex;
  align-items: flex-start;
  gap: 12px;
}

/* Левая колонка — категории + метки, две отдельные карточки друг под другом */
.categories-col {
  flex-shrink: 0;
  width: 386px;
  display: flex;
  flex-direction: column;
  gap: 12px;
}
.categories-card {
  display: flex;
  flex-direction: column;
  gap: 2px;
  background: var(--paper-card);
  border: 1px solid var(--paper-line);
  border-radius: var(--r-lg);
  padding: 10px;
}
.labels-card {
  display: flex;
  flex-direction: column;
  gap: 2px;
  background: var(--paper-card);
  border: 1px solid var(--paper-line);
  border-radius: var(--r-lg);
  padding: 10px;
}
.labels-actions {
  display: flex;
  gap: 6px;
  margin-top: 2px;
  padding-top: 11px;
  border-top: 1px dashed var(--paper-line);
}
.labels-btn {
  display: flex;
  align-items: center;
  justify-content: center;
  height: 24px;
  padding: 0 16px;
  border-radius: var(--r-md);
  font-family: var(--f-family-body);
  font-size: var(--f-size-caption);
  color: var(--ink-secondary);
  text-decoration: none;
  white-space: nowrap;
  cursor: pointer;
}
.labels-btn--view {
  flex: 1;
  background: transparent;
  border: 1px solid var(--paper-linestrong);
}
.labels-btn--view:hover {
  background: var(--paper-sunk);
}
.labels-btn--download {
  width: 168px;
  background: var(--paper-base);
  border: 1px solid var(--paper-line);
}
.labels-btn--download:hover {
  background: var(--paper-sunk);
}
/* Дровер «Посмотреть метки» — по Figma-ноде 173:1352. */
dialog.labels-dialog {
  max-width: 552px;
}
.labels-dialog__head {
  display: flex;
  align-items: center;
  gap: 11px;
  width: 100%;
}
.labels-dialog__head .category-sticker {
  flex-shrink: 0;
}
.labels-dialog__text {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 1px;
}
.labels-dialog__lede {
  margin: 0;
  font-size: var(--f-size-caption);
  color: var(--ink-secondary);
}
.labels-dialog__caption {
  margin: 12px 0 0;
  font-size: var(--f-size-footnote);
  font-weight: var(--f-weight-bold);
  color: var(--ink-base);
}
.labels-list {
  display: flex;
  flex-direction: column;
  max-height: 50vh;
  overflow-y: auto;
  margin-top: 7px;
}
.labels-row {
  display: flex;
  align-items: flex-start;
  gap: 11px;
  padding: 5px 0;
}
.labels-row .icon-btn {
  flex-shrink: 0;
}
.labels-chip {
  flex-shrink: 0;
  padding: 1px 11px;
  background: var(--paper-sunk);
  border-radius: var(--r-sm);
  font-family: var(--f-family-body);
  font-size: var(--f-size-caption);
  font-weight: var(--f-weight-medium);
  color: var(--ink-base);
  white-space: nowrap;
}
.labels-row__desc {
  flex: 1;
  min-width: 0;
  margin: 0;
  font-size: var(--f-size-caption);
  color: var(--ink-secondary);
}
.labels-dialog__actions {
  display: flex;
  justify-content: flex-end;
  width: 100%;
  padding-top: 5px;
}
.shelf-head {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 5px 2px 10px;
}
.shelf-head__title {
  margin: 0;
  font-size: var(--f-size-body);
  font-weight: var(--f-weight-bold);
  color: var(--ink-base);
}
.shelf-head__count {
  margin: 0;
  font-size: var(--f-size-caption);
  color: var(--ink-secondary);
}

.category {
  width: 100%;
}
.category:not(:first-of-type) {
  border-top: 1px dashed var(--paper-line);
  padding-top: 2px;
}
.category-row {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 10px;
  border-radius: var(--r-md);
  cursor: pointer;
  list-style: none;
}
.category-row::-webkit-details-marker {
  display: none;
}
.category-row:hover {
  background: var(--paper-sunk);
}
.category[open] > .category-row {
  background: var(--chip-today);
}
.category[open] > .category-row:hover {
  background: var(--chip-today);
}
.category-sticker {
  flex-shrink: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 41px;
  height: 41px;
  border-radius: 10px;
}
.category-sticker--rose {
  background: var(--daruma-50);
}
.category-doodle {
  width: 24px;
  height: 24px;
  color: var(--ink-base);
}
.category-info {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 2px;
}
.category-info__name {
  margin: 0;
  font-size: var(--f-size-body);
  font-weight: var(--f-weight-bold);
  color: var(--ink-base);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.category-info__badge {
  font-weight: var(--f-weight-medium);
  color: var(--ink-secondary);
}
.category-info__count {
  margin: 0;
  font-size: var(--f-size-caption);
  color: var(--ink-secondary);
}
.category-chevron {
  flex-shrink: 0;
  width: 19px;
  height: 19px;
  color: var(--ink-secondary);
  transform: rotate(-90deg);
  transition: transform var(--m-duration-fast) var(--m-easing-out);
}
.category[open] > .category-row .category-chevron {
  transform: rotate(0deg);
}

.category-templates {
  display: flex;
  flex-direction: column;
  gap: 2px;
  padding: 2px 0 10px 53px;
}
.template-row {
  display: flex;
  flex-direction: column;
  gap: 2px;
  padding: 8px 11px;
  border-radius: var(--r-md);
  text-decoration: none;
  color: inherit;
}
.template-row:hover {
  background: var(--paper-sunk);
}
.template-row--active {
  background: var(--chip-today);
  border-left: var(--b-accent) solid var(--service-600);
  padding-left: calc(11px - var(--b-accent));
}
.template-row__title {
  margin: 0;
  font-size: var(--f-size-footnote);
  font-weight: var(--f-weight-bold);
  color: var(--ink-base);
}
.template-row__sub {
  margin: 0;
  font-size: var(--f-size-caption);
  color: var(--ink-secondary);
}

.category-edit {
  display: flex;
  align-items: center;
  gap: 4px;
  margin-top: 2px;
  margin-bottom: 4px;
  padding: 6px;
  background: var(--paper-sunk);
  border-radius: 10px;
}
.category-edit__rename-form {
  display: flex;
  align-items: center;
  flex: 1;
  min-width: 0;
  gap: 4px;
}
.category-edit__input {
  flex: 1;
  min-width: 0;
  height: 24px;
  padding: 0 10px;
  background: var(--paper-card);
  border: 1px solid var(--paper-line);
  border-radius: var(--r-md);
  font-family: var(--f-family-body);
  font-size: 12px;
  color: var(--ink-base);
}
.category-edit__input::placeholder {
  color: var(--ink-base);
  opacity: 1;
}
/* Исключён из легаси "form input:focus" (той же строкой, что и .field/
   .new-template-input/.pocket-goals__input), но без своего правила — падал
   на чёрный браузерный дефолт фокуса (тот же баг, что чинили у
   .pocket-goals__input). Цвет как у .field:focus — обычное текстовое поле. */
.category-edit__input:focus {
  outline: none;
  border-color: var(--service-600);
  box-shadow: 0 0 0 2px var(--service-100);
}
.category-edit__btn {
  flex-shrink: 0;
  height: 24px;
  padding: 0 16px;
  background: var(--service-400);
  border: none;
  border-radius: var(--r-md);
  font-family: var(--f-family-body);
  font-size: var(--f-size-caption);
  font-weight: var(--f-weight-bold);
  color: var(--ink-base);
  white-space: nowrap;
  cursor: pointer;
}
.category-edit__btn:hover {
  background: var(--service-500);
}
.category-new {
  display: flex;
  align-items: center;
  gap: 4px;
  padding: 11px 2px 2px;
  border-top: 1px dashed var(--paper-line);
  margin-top: 2px;
}
/* Кнопка удаления категории сидит на паперс-сунк подложке .category-edit —
   своя карточная заливка .icon-btn там смотрелась лишним квадратом, оставляем
   только саму иконку-корзину. */
.category-edit .icon-btn {
  background: transparent;
}

/* Правая колонка — читалка выбранного документа */
.reader-card {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 14px;
  background: var(--paper-card);
  border: 1px solid var(--paper-line);
  border-radius: var(--r-lg);
  padding: 19px;
}
.reader-card--empty {
  align-items: center;
  justify-content: center;
  min-height: 460px;
}
.reader-empty {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 14px;
}
.reader-empty__icon {
  width: 96px;
  height: 96px;
  color: var(--ink-disabled);
}
.reader-empty__title {
  margin: 0;
  font-size: var(--f-size-h3);
  font-weight: var(--f-weight-bold);
  color: var(--ink-disabled);
}
.reader-empty__text {
  margin: 0;
  max-width: 384px;
  font-size: var(--f-size-body);
  color: var(--ink-disabled);
  text-align: center;
}
.reader-empty__hint {
  margin: 0;
  max-width: 202px;
  font-size: var(--f-size-footnote);
  color: var(--ink-disabled);
  text-align: center;
}

.reader-head {
  display: flex;
  flex-direction: column;
  gap: 10px;
}
.reader-title-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
}
.reader-title {
  margin: 0;
  font-size: var(--f-size-h3);
  font-weight: var(--f-weight-bold);
  color: var(--ink-base);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.reader-actions {
  flex-shrink: 0;
  display: flex;
  align-items: center;
  gap: 10px;
}
.reader-chips {
  display: flex;
  align-items: center;
  gap: 7px;
}
.reader-chip {
  display: inline-flex;
  align-items: center;
  padding: 2px 11px;
  border-radius: var(--r-pill);
  border: 1px solid transparent;
  font-size: var(--f-size-caption);
  font-weight: var(--f-weight-medium);
  color: var(--ink-base);
  white-space: nowrap;
}
.reader-chip--doctype {
  background: var(--sticker-moomin);
}
.reader-chip--profile {
  background: var(--paper-sunk);
  border-color: var(--paper-linestrong);
}
.reader-meta {
  margin: 0;
  font-size: var(--f-size-caption);
  color: var(--ink-secondary);
}
.reader-note {
  margin: 0;
  font-size: var(--f-size-caption);
  color: var(--ink-secondary);
}
.reader-sheet {
  background: var(--paper-card);
  border: 1px solid var(--paper-line);
  border-radius: var(--r-md);
  padding: 19px;
  max-height: 728px;
  overflow-y: auto;
}
/* .docx-preview несёт свою рамку/фон/скролл (общий класс, используется и
   отдельно на карточке договора) — здесь она уже внутри .reader-sheet,
   поэтому гасим внутреннюю коробку, чтобы не было рамки в рамке. */
.reader-sheet .docx-preview {
  max-height: none;
  overflow: visible;
  padding: 0;
  border: none;
  background: transparent;
}
.reader-alert {
  display: flex;
  flex-direction: column;
  gap: 7px;
  padding: 12px 14px;
  background: var(--semantic-warning-bg);
  border: 1px solid var(--semantic-warning-border);
  border-radius: var(--r-md);
}
.reader-alert--danger {
  background: var(--semantic-danger-bg);
  border-color: var(--semantic-danger-border);
}
.reader-alert__text {
  margin: 0;
  font-size: var(--f-size-caption);
  color: var(--semantic-warning-text);
}
.reader-alert--danger .reader-alert__text {
  color: var(--semantic-danger-text);
}
.reader-alert__chips {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
}
.reader-alert__chip {
  display: inline-flex;
  align-items: center;
  padding: 1px 11px;
  background: var(--paper-card);
  border: 1px solid var(--semantic-warning-border);
  border-radius: var(--r-sm);
  font-size: var(--f-size-caption);
  font-weight: var(--f-weight-medium);
  color: var(--semantic-warning-text);
}

/* Читалка в режиме «Новый шаблон» — по Figma-ноде 303:1644. Кнопка
   "Загрузить" в шапке и весь дропзон-блок открывают один и тот же скрытый
   <input type=file> (data-file-picker) — двух путей к одному действию, как
   в макете, без единой лишней строчки JS для самого открытия пикера
   (обычный <label for>), JS только подставляет имя выбранного файла. */
.reader-upload-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  margin: 0;
  height: 31px;
  padding: 0 13px;
  background: var(--paper-card);
  border: 1px solid var(--semantic-warning-border);
  border-radius: var(--r-md);
  font-size: var(--f-size-caption);
  font-weight: var(--f-weight-medium);
  color: var(--ink-base);
  cursor: pointer;
  white-space: nowrap;
}
.reader-upload-btn:hover {
  background: var(--semantic-warning-bg);
}
.new-template-form {
  display: flex;
  flex-direction: column;
  gap: 14px;
  width: 100%;
}
.new-template-label {
  margin: 0 0 4px;
  font-size: var(--f-size-caption);
  font-weight: var(--f-weight-bold);
  color: var(--ink-secondary);
}
.new-template-input {
  width: 100%;
  height: 36px;
  padding: 8px 10px;
  background: var(--paper-card);
  border: 1px solid var(--paper-line);
  border-radius: var(--r-md);
  font-family: var(--f-family-body);
  font-size: 14px;
  color: var(--ink-base);
}
.new-template-input::placeholder {
  color: var(--ink-secondary);
}
/* Тот же баг, что у .category-edit__input выше — исключён из легаси
   focus-правила, своего не было, падал на чёрный браузерный дефолт.
   Применяется и к <input>, и к <button class="new-template-input
   select-custom__control"> (тот же класс на кнопке-имитации select). */
.new-template-input:focus {
  outline: none;
  border-color: var(--service-600);
  box-shadow: 0 0 0 2px var(--service-100);
}
.new-template-grid {
  display: flex;
  gap: 14px;
  width: 100%;
}
.new-template-grid > div {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
}
.new-template-dropzone {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 10px;
  width: 100%;
  min-height: 192px;
  background: var(--paper-card);
  border: 1px solid var(--paper-line);
  border-radius: var(--r-md);
  cursor: pointer;
  /* Тот же легаси "form label { margin }" leak, что и у .field-checkbox. */
  margin: 0;
}
.new-template-dropzone:hover {
  background: var(--paper-sunk);
}
.new-template-dropzone__icon {
  width: 96px;
  height: 96px;
  color: var(--ink-secondary);
}
.new-template-dropzone__text {
  margin: 0;
  font-size: var(--f-size-footnote);
  font-weight: var(--f-weight-bold);
  color: var(--ink-secondary);
  text-align: center;
}
.new-template-dropzone__filename {
  margin: 0;
  font-size: var(--f-size-caption);
  font-weight: var(--f-weight-bold);
  color: var(--ink-base);
  text-align: center;
  word-break: break-all;
}
/* Категория/Тип документа/Заказчик — только здесь текст в раскрытом меню
   на 2px меньше общего .select-custom (не трогаем дровер профиля/заказа). */
.new-template-form .select-custom__option-title {
  font-size: 15px;
}
.new-template-form .select-custom__option-desc {
  font-size: 11px;
}

/* ============================== Страница «Заказ» =========================== */
/* По Figma-ноде 127:260 — шапка заказа + шкала прогресса собраны первым
   заходом (см. CLAUDE.md), три колонки карточек (досье/договоры/счета,
   деньги/чеки, расходы/задачи/дневник) — следующими отдельными заходами. */

.order-head-row {
  display: flex;
  align-items: center;
  gap: 12px;
  width: 100%;
  margin-bottom: 11px;
}
.order-head__name {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 2px;
}
.order-head__title {
  margin: 0;
  font-size: var(--f-size-h3);
  font-weight: var(--f-weight-bold);
  color: var(--ink-base);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.order-head__meta {
  margin: 0;
  font-size: var(--f-size-caption);
  color: var(--ink-secondary);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* Алерт "не указано, кто платит" (Figma-нода 131:557) — между шапкой заказа
   и шкалой прогресса, заменяет старый однострочный .alert-danger. Целиком
   кликабелен (открывает дровер "Править" из Досье), поэтому <button>, а не
   <div> — доступность бесплатно, без role/tabindex вручную. */
.order-alert {
  display: flex;
  align-items: center;
  gap: 10px;
  width: 100%;
  padding: 11px 13px;
  margin-bottom: 14px;
  border: none;
  border-left: 3px solid var(--semantic-danger-border);
  background: var(--semantic-danger-bg);
  font-family: var(--f-family-body);
  text-align: left;
  cursor: pointer;
}
.order-alert__text {
  display: flex;
  flex-direction: column;
  gap: 1px;
  flex: 1;
  min-width: 0;
}
.order-alert__title {
  margin: 0;
  font-size: var(--f-size-footnote);
  font-weight: var(--f-weight-bold);
  color: var(--semantic-danger-text);
}
.order-alert__desc {
  margin: 0;
  font-size: var(--f-size-caption);
  color: var(--ink-secondary);
}

.order-progress {
  display: flex;
  flex-direction: column;
  gap: var(--space-head-scale);
  width: 100%;
  padding: var(--space-card-top) 13px;
  background: var(--paper-card);
  border: 1px solid var(--paper-line);
  border-radius: var(--r-lg);
  margin-bottom: 14px;
}
.order-progress__label-row {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 12px;
}
.order-progress__title {
  margin: 0;
  font-size: var(--f-size-footnote);
  font-weight: var(--f-weight-bold);
  color: var(--ink-base);
}
.order-progress__sub {
  margin: 0;
  font-size: var(--f-size-caption);
  color: var(--ink-secondary);
  white-space: nowrap;
}
.order-progress__track {
  width: 100%;
  height: 7px;
  background: var(--paper-sunk);
  border-radius: var(--r-pill);
  overflow: hidden;
}
.order-progress__fill {
  height: 7px;
  background: var(--sticker-sakura);
  border-radius: var(--r-pill);
}

/* .card, в отличие от .form-card, сам по себе без отступа снизу (на
   странице заказчика зазор между карточками даёт gap родительского флекса
   .customer-col--right) — здесь карточки идут прямо в потоке страницы, без
   такой обёртки, поэтому отступ между ними навешан явно, тем же 14px, что
   и у шкалы прогресса перед первой карточкой. */
.order-card-block {
  margin-bottom: 14px;
}

/* Тело заказа — три колонки по Figma-ноде 127:260 ("Колонка · стабильная"
   290 / "Колонка · деньги" 318 / "Колонка · работа" 290 — соотношение
   сохранено через fr, не жёсткие px: контейнер `.content` у́же (1056px
   с падингами), чем сумма фигма-ширин при масштабе ×1.2). Зазор между
   колонками — 9px раскладки ×1.2 = 11px, тот же приём, что и везде на
   странице. Ниже ~960px три узкие колонки читать неудобно — схлопывается
   в одну (тот же порядок карточек сверху вниз, просто друг под другом). */
.order-body-grid {
  display: grid;
  grid-template-columns: 290fr 318fr 290fr;
  gap: 11px;
  align-items: start;
}
.order-body-col {
  display: flex;
  flex-direction: column;
  min-width: 0;
}
@media (max-width: 960px) {
  .order-body-grid {
    display: block;
  }
}

/* Панель "новый договор" (Figma-нода 322:781) — по клику "+ договор" левая
   (стабильная) колонка остаётся как есть, а колонки "деньги"/"работа"
   заменяются одной широкой панелью (grid-column: 2 / 4 — занимает ровно то
   место, которое иначе делят между собой эти две .order-body-col). На
   схлопнутом мобильном брейкпоинте .order-body-grid сама становится
   display:block — grid-column там ни на что не влияет, отдельная медиа-
   правка не нужна. */
.contract-new-panel {
  grid-column: 2 / 4;
  display: flex;
  flex-direction: column;
  gap: 14px;
  min-width: 0;
  background: var(--paper-card);
  border: 1px solid var(--paper-line);
  border-radius: var(--r-lg);
  padding: 19px;
}
.contract-new-panel__head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 10px;
  width: 100%;
}
.contract-new-panel__title {
  margin: 0;
  font-size: var(--f-size-h3);
  font-weight: var(--f-weight-bold);
  color: var(--ink-base);
}
.contract-new-panel__hint {
  margin: 0;
  font-size: var(--f-size-caption);
  color: var(--ink-secondary);
  white-space: nowrap;
}
/* В Figma-ноде 322:781 у самой панели ОДНА плоская flex-колонка (gap-12) —
   Шапка/Форма сделки/подсказка/пара Шаблон+Дата/пара Сумма+Срок/Лист-превью/
   Кнопки все на одном уровне. В разметке между ними встали <form> (нужен
   для отправки) и обёртка [data-template-preview-group] (нужна только как
   область видимости для JS-переключателя превью, см. app.js) — у обеих
   изначально не было своего display:flex/gap, поэтому их дети слипались без
   отступов вовсе. display:contents убирает у них собственный бокс из
   раскладки: дети всплывают на уровень .contract-new-panel и получают её
   gap:14px — семантика формы и area JS-скоупа не страдают, страдает только
   разметка коробок. */
.contract-new-panel > form,
.contract-new-panel [data-template-preview-group] {
  display: contents;
}
/* .muted-note — старый класс с домятной темы, несёт свой margin (0.3rem/
   0.5rem) — на всех предыдущих страницах он был единственным источником
   отступа вокруг подсказки, здесь же отступ уже даёт gap:14px самой панели
   (см. выше), и margin наверх просто плюсуется, разъезжаясь с остальными
   строками. Гасим только в этой панели, не трогая .muted-note глобально. */
.contract-new-panel .muted-note {
  margin: 0;
}
/* "Лист · превью" — тот же приём, что и .reader-sheet (карточка договора),
   но фиксированная высота вместо max-height (это разговорный предпросмотр
   шаблона до генерации, а не сам сгенерированный документ) и собственный
   тонкий скроллбар вместо декоративного "ползунка" из Figma — настоящая
   прокрутка функционально важнее пиксель-в-пиксель клона декоративного
   элемента. */
.contract-preview-sheet {
  height: 364px;
  overflow-y: auto;
  padding: 19px 12px 19px 22px;
  background: var(--paper-card);
  border: 1px solid var(--paper-line);
  border-radius: var(--r-md);
  scrollbar-width: thin;
  scrollbar-color: var(--paper-linestrong) transparent;
}
.contract-preview-sheet::-webkit-scrollbar {
  width: 6px;
}
.contract-preview-sheet::-webkit-scrollbar-thumb {
  background: var(--paper-linestrong);
  border-radius: var(--r-pill);
}
.contract-preview-sheet .docx-preview {
  max-height: none;
  overflow: visible;
  padding: 0;
  border: none;
  background: transparent;
}

/* Карточка договора (Figma-нода 403:927) — order_detail.html,
   contract_view=contract, тот же приём, что и .contract-new-panel (правые
   две колонки заказа заменяются целиком), но здесь ДВЕ отдельные карточки
   друг под другом (в ноде это два самостоятельных белых блока), не одна —
   отсюда отдельная колонка-обёртка .contract-panel-col с gap между ними,
   а не одна большая панель. */
.contract-panel-col {
  grid-column: 2 / 4;
  display: flex;
  flex-direction: column;
  gap: 14px;
  min-width: 0;
}
.contract-panel-card {
  display: flex;
  flex-direction: column;
  gap: 14px;
  width: 100%;
  min-width: 0;
  background: var(--paper-card);
  border: 1px solid var(--paper-line);
  border-radius: var(--r-lg);
  padding: 19px;
}
.contract-panel__actions {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: 9px;
  flex-wrap: wrap;
  width: 100%;
}
.contract-panel__title {
  margin: 0;
  font-size: var(--f-size-h3);
  font-weight: var(--f-weight-bold);
  color: var(--ink-base);
}
.contract-panel-card > form {
  display: contents;
}
.field--readonly {
  display: flex;
  align-items: center;
  color: var(--ink-secondary);
}
.contract-panel__preview {
  display: flex;
  flex-direction: column;
  gap: 7px;
  width: 100%;
}
.contract-panel__empty {
  margin: 0;
  font-size: var(--f-size-footnote);
  color: var(--ink-muted);
}
.contract-panel__hint {
  margin: 0;
  font-size: var(--f-size-caption);
  color: var(--ink-muted);
}
.contract-subdocs-list {
  display: flex;
  flex-direction: column;
  gap: 6px;
  width: 100%;
}
/* Модификатор .contract-card-subdoc для этой панели — базовый класс
   (карточка заказа, левая колонка) просто оборачивает ссылку без бейджей;
   тут рядом с номером ещё и статусные mini-badge, нужен ряд. */
.contract-card-subdoc--row {
  display: flex;
  align-items: center;
  gap: 6px;
  flex-wrap: wrap;
}
.contract-panel__subdoc-form {
  display: flex;
  flex-direction: column;
  gap: 10px;
  width: 100%;
}
.contract-panel__subdoc-row {
  display: flex;
  align-items: center;
  gap: 8px;
  width: 100%;
}
.contract-panel__subdoc-row .select-custom {
  flex: 1;
  min-width: 0;
}

/* Страница "Я" — три колонки по Figma-фрейму "DorumaCRM · Я" (Налог 290 /
   Расходы+Подписки 318 / Цель+День рождения+Карты+Реквизиты+Пароль 290),
   тот же приём fr-пропорций и брейкпоинта, что у .order-body-grid выше —
   отдельный класс, а не переиспользование order-body-*, потому что здесь
   карточки прямо в потоке страницы (без своей общей обёртки), и колонке
   нужен собственный gap между карточками — на заказе это делает отдельный
   класс .order-card-block на каждой карточке, здесь проще держать gap на
   самой колонке. */
.me-body-grid {
  display: grid;
  grid-template-columns: 290fr 318fr 290fr;
  gap: 11px;
  align-items: start;
}
.me-body-col {
  display: flex;
  flex-direction: column;
  min-width: 0;
}
/* Отступ между карточками через margin на самих <section>, не через gap на
   колонке — gap пропадает вместе с display:grid на мобильном брейкпоинте
   ниже (колонки схлопываются в display:block), а margin остаётся и держит
   отступ и между карточками внутри колонки, и между самими колонками при
   схлопывании. <dialog> — не <section>, селектор их не задевает. */
.me-body-col > section {
  margin-bottom: 14px;
}
/* Ряд с одной кнопкой "+ цель" под карточками целей. Не <section>, поэтому
   правило выше его не задевает — отступ задаём сами тем же числом. */
.goal-add-row {
  display: flex;
  justify-content: flex-end;
  margin-bottom: 14px;
}
@media (max-width: 960px) {
  .me-body-grid {
    display: block;
  }
}

/* Компактная полоса "Лимит НПД" на всю ширину страницы, над трёхколоночной
   сеткой (Figma-нода 39:2 — не было в основном фрейме "Я", добавлена по
   отдельному указанию пользователя). Тоньше и площе обычной .card:
   собственные паддинги, не 14px по умолчанию. */
.npd-limit-bar {
  display: flex;
  flex-direction: column;
  gap: var(--space-head-scale);
  padding: var(--space-card-top) 10px;
  margin-bottom: 14px;
}
.npd-limit-bar__head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 10px;
}
.npd-limit-bar__title {
  margin: 0;
  font-size: var(--f-size-footnote);
  font-weight: var(--f-weight-bold);
  color: var(--ink-base);
}
.npd-limit-bar__value {
  margin: 0;
  font-size: var(--f-size-caption);
  color: var(--ink-secondary);
  white-space: nowrap;
}
.npd-limit-bar__scale {
  width: 100%;
  height: 5px;
  background: var(--paper-sunk);
  border-radius: var(--r-pill);
  overflow: hidden;
}
.npd-limit-bar__fill {
  height: 5px;
  border-radius: var(--r-pill);
  background: var(--brand-mintsoft);
}
.npd-limit-bar__fill--warning { background: var(--semantic-warning-border); }
.npd-limit-bar__fill--danger,
.npd-limit-bar__fill--exceeded { background: var(--semantic-danger-border); }

/* Мелкий бейдж-статус (не путать с .pill — тот крупнее, для шапки заказа) —
   переиспользуется в карточках "Тело" заказа: Договоры/Деньги/Чеки/Расходы. */
.mini-badge {
  display: inline-flex;
  align-items: center;
  padding: 2px 8px;
  border-radius: var(--r-pill);
  border: 1px solid transparent;
  font-size: 11px;
  font-weight: var(--f-weight-bold);
  line-height: 1.3;
  white-space: nowrap;
}
.mini-badge--success { background: var(--semantic-success-bg); border-color: var(--semantic-success-border); color: var(--semantic-success-text); }
.mini-badge--warning { background: var(--semantic-warning-bg); border-color: var(--semantic-warning-border); color: var(--semantic-warning-text); }
.mini-badge--danger  { background: var(--semantic-danger-bg);  border-color: var(--semantic-danger-border);  color: var(--semantic-danger-text); }
.mini-badge--neutral { background: var(--semantic-neutral-bg); border-color: var(--semantic-neutral-border); color: var(--semantic-neutral-text); }
.mini-badge--service { background: var(--service-100); border-color: var(--paper-linestrong); color: var(--ink-base); }

.contract-card-item {
  display: flex;
  flex-direction: column;
  gap: 6px;
  width: 100%;
}
.contract-card-item + .contract-card-item {
  margin-top: 10px;
}
.contract-card-row {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 6px;
}
.contract-card-number {
  font-size: var(--f-size-footnote);
  font-weight: var(--f-weight-bold);
  color: var(--ink-base);
  text-decoration: none;
}
.contract-card-number:hover {
  text-decoration: underline;
}
.contract-card-subdoc {
  border-left: 2px solid var(--paper-linestrong);
  padding: 4px 0 4px 12px;
}
.contract-card-subdoc a {
  font-size: var(--f-size-caption);
  color: var(--ink-secondary);
  text-decoration: none;
}
.contract-card-subdoc a:hover {
  text-decoration: underline;
}

/* Карточка "Счета" (Figma-нода 127:371) — Figma рисует только пустое
   состояние (плоская строка без чипа-шапки, "счёт не обязателен" — самая
   частая ситуация у физиков); для непустого списка счетов (своего
   Figma-состояния нет) используем ту же шапку-чип, что и у "Договор" выше,
   плюс строки-ссылки в духе .contract-card-row. */
.invoices-empty {
  display: flex;
  align-items: flex-start;
  gap: 10px;
  width: 100%;
}
.invoices-empty__text {
  flex: 1;
  min-width: 0;
}
.invoices-empty__title {
  margin: 0;
  font-size: var(--f-size-caption);
  font-weight: var(--f-weight-bold);
  color: var(--ink-base);
}
.invoices-empty__sub {
  margin: 0;
  font-size: var(--f-size-caption);
  color: var(--ink-secondary);
}
/* Строка счёта = кликабельная кнопка (открывает дровер правки) + крестик
   удаления рядом (Figma-нода 298:1581). Разделитель между строками теперь на
   обёртке, а не на самой строке: крестик должен лежать под той же чертой, что
   и строка. Первой строке разделитель снимает общее правило
   `.section-card__head + *` (разделителя под шапкой не бывает). */
.invoice-card-line {
  display: flex;
  align-items: flex-start;
  gap: 8px;
  border-top: 1px dashed var(--paper-line);
}
/* Крестик — по центру ПЕРВОЙ строки счёта ("№ ... от ...", 20px), а не по
   центру всего двухэтажного блока вместе с "остаток": верхний padding строки
   минус половина разницы высот кнопки (28px) и этой первой строки. */
.invoice-card-line > .icon-btn {
  margin-top: calc(var(--space-row) - 4px);
}
.invoice-card-row {
  flex: 1;
  min-width: 0;
  display: block;
  padding: var(--space-row) 0;
  background: none;
  border: none;
  font: inherit;
  color: inherit;
  text-align: left;
  text-decoration: none;
  cursor: pointer;
}
.invoice-card-row__main {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 10px;
}
.invoice-card-row__title {
  margin: 0;
  font-size: var(--f-size-footnote);
  font-weight: var(--f-weight-bold);
  color: var(--ink-base);
}
.invoice-card-row__amount {
  margin: 0;
  font-size: var(--f-size-footnote);
  font-weight: var(--f-weight-bold);
  color: var(--ink-base);
  white-space: nowrap;
}
.invoice-card-row__sub {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-top: 2px;
  font-size: var(--f-size-caption);
  color: var(--ink-secondary);
}

/* Карточка "Деньги · план и факт" (Figma-нода 128:298) — read-only лента:
   труба-прогресс + последние 2 строки; "▸ вся история" разворачивает более
   ранние строки И обе формы добавления (график/факт) — в Figma для форм
   добавления своего места не нарисовано, per решение пользователя. */
.money-pipe {
  display: flex;
  gap: 2px;
  width: 100%;
  margin-bottom: calc(var(--space-head-body) - var(--space-row));
}
/* Тот же стандарт блока со шкалой, что у "деньги · труба": шапка→шкала 6px
   (у карточки "деньги · план и факт" своего класса-обёртки нет, поэтому
   через :has), и никакого разделителя под шкалой. */
.card:has(> .money-pipe) .section-card__head {
  padding-bottom: var(--space-head-scale);
}
.money-pipe + .money-row {
  border-top: none;
}
.money-pipe__segment {
  height: 10px;
  border-radius: var(--r-pill);
}
.money-pipe__segment--paid {
  background: var(--sticker-mint);
}
.money-pipe__segment--pending {
  background: var(--semantic-warning-bg);
}
.money-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  padding: var(--space-row) 0;
  border-top: 1px dashed var(--paper-line);
}
.money-row:first-child {
  border-top: none;
}
.money-row__when {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: 6px;
  min-width: 0;
}
.money-row__date {
  font-size: var(--f-size-footnote);
  font-weight: var(--f-weight-bold);
  color: var(--ink-base);
  white-space: nowrap;
}
/* Дашборд (Figma-ноды 38:26/198:565.../323:874...) переиспользует .money-row
   для строк-"стадий" (пришло/по чекам/наличка.../подписки/личные расходы),
   где подпись слева — Regular, не Bold (в отличие от названия подписки в
   списке "инструменты" ниже, где .money-row__date остаётся Bold как есть —
   там это имя, а не служебная метка стадии). */
.money-row__date--regular {
  font-weight: var(--f-weight-regular);
}
.money-row__status {
  font-size: var(--f-size-caption);
  color: var(--ink-secondary);
  white-space: nowrap;
}
/* Дата следующего списания в строке подписки — служебная цифра, поэтому
   тише соседнего статуса ("3 месяца не трогала"): тот про тревогу, эта
   просто справка "когда спишут". */
.money-row__status--charge {
  color: var(--ink-muted);
}
.money-row__right {
  display: flex;
  align-items: center;
  gap: 4px;
  flex-shrink: 0;
}
.money-row__amount {
  font-size: var(--f-size-footnote);
  font-weight: var(--f-weight-bold);
  white-space: nowrap;
}
.money-row__amount--paid {
  color: var(--semantic-success-text);
}
.money-row__amount--pending {
  color: var(--semantic-warning-text);
}
.money-row__amount--muted {
  color: var(--ink-secondary);
  font-weight: var(--f-weight-regular);
}
.money-remainder {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  padding-top: var(--space-row);
  border-top: 1px solid var(--paper-line);
}
/* Тот же итог, но в начале карточки (например "За 2026 год" на "Налог ·
   оплата налогов", Figma-нода 247:5664) — отбивка снизу, не сверху. */
.money-remainder--header {
  border-top: none;
  border-bottom: 1px solid var(--paper-line);
  padding-top: 0;
  padding-bottom: var(--space-row);
}
/* Своя отбивка снизу у итога-заголовка + border-top у следующей строки
   давали ДВЕ линии подряд ("оплата налога НПД · год" на /me). Разделитель
   между ними один — тот, что уже нарисовал итог. */
.money-remainder--header + * {
  border-top: none;
}
.money-remainder__label {
  margin: 0;
  font-size: var(--f-size-caption);
  color: var(--ink-secondary);
}
.money-remainder__value {
  margin: 0;
  font-size: var(--f-size-footnote);
  font-weight: var(--f-weight-bold);
  color: var(--ink-base);
}
/* Крупная сумма-заголовок карточки (например "Налог · расчёт" на /me,
   Figma-нода 104:265) — число + приписка под ним. Не завязан на "деньги"
   конкретно (название нейтральное) — пригодится и для других карточек
   /me (цель, оплата налога) тем же приёмом. */
.stat-figure {
  padding-bottom: var(--space-row);
}
.stat-figure__value {
  margin: 0;
  font-size: var(--f-size-money);
  font-weight: var(--f-weight-bold);
  color: var(--ink-base);
}
.stat-figure__caption {
  margin: 2px 0 0;
  font-size: var(--f-size-caption);
  color: var(--ink-secondary);
}
.stat-note {
  margin: 8px 0 0;
  font-size: var(--f-size-caption);
  color: var(--ink-secondary);
}
/* "Сумма" + месяц + "Сохранить" одной строкой (карточка "Налог · оплата
   налогов" на /me, Figma-нода 247:5664: три поля в один "Действия"-ряд,
   не в два, как у панели "новый чек" на заказе). */
/* Ввод суммы на /me ("оплата налога НПД · год") стоит прямо над пунктиром
   истории: снизу у формы отступа не было вовсе, и разделитель прилипал к
   полям, хотя от него до "+ раскрыть" — 8px (padding самого тоггла).
   Симметрия по 8px с обеих сторон линии. */
.tax-payment-form {
  display: flex;
  flex-direction: column;
  gap: 6px;
  padding-top: 8px;
  padding-bottom: 8px;
}
/* Поля с кнопкой остались одной строкой — колонкой стала сама форма, чтобы
   под ними поместились галка «оплачен полностью» и подсказка про субсидию. */
.tax-payment-form__row {
  display: flex;
  gap: 6px;
}
.tax-paid-full {
  display: flex;
  align-items: center;
  gap: 8px;
  cursor: pointer;
}
.tax-paid-full__text {
  font-size: var(--f-size-footnote);
  color: var(--ink-secondary);
}
/* display НЕ задаём намеренно: подсказка скрывается атрибутом hidden, а
   авторский display перебил бы браузерный [hidden]{display:none} — на этих
   грабли в проекте наступали трижды (см. CLAUDE.md, .select-custom__menu,
   .single-date-field__popover, .icon-btn). */
.tax-paid-full__hint {
  margin: 0;
  font-size: var(--f-size-footnote);
  color: var(--ink-secondary);
}
.tax-paid-full__hint--active {
  color: var(--semantic-success-text);
  font-weight: var(--f-weight-medium);
}
.tax-payment-form .field {
  height: auto;
  padding: 8px 11px;
  font-size: var(--f-size-footnote);
}
.tax-payment-form input.field {
  max-width: 96px;
}
.tax-payment-form .select-custom {
  flex: 1;
  min-width: 0;
}
/* Кнопка «Сохранить» была на общей .btn--xs (31px) — заметно ниже соседних
   .field в этой форме (~37px, авто-высота от padding:8px + line-height
   footnote). Высота полей здесь не токенная (сама .tax-payment-form задаёт
   свой компактный padding, см. выше), поэтому подгоняется явным числом
   именно под них, а не под другой готовый модификатор кнопки. */
.tax-payment-form .btn--xs {
  height: 37px;
}

.money-history {
  width: 100%;
}
.money-history__toggle {
  display: flex;
  align-items: center;
  gap: 4px;
  padding: 8px 0 6px;
  border-top: 1px dashed var(--paper-line);
  font-size: var(--f-size-caption);
  font-weight: var(--f-weight-bold);
  color: var(--ink-secondary);
  cursor: pointer;
  list-style: none;
}
.money-history__toggle::-webkit-details-marker {
  display: none;
}
.money-history__toggle:hover {
  color: var(--ink-base);
}
.money-history__arrow {
  display: inline-flex;
  transition: transform var(--m-duration-fast) var(--m-easing-out);
}
.money-history[open] > .money-history__toggle .money-history__arrow {
  transform: rotate(90deg);
}
.money-history .money-row:first-child {
  border-top: 1px dashed var(--paper-line);
}

/* Строка подписки на /me (Figma-нода 105:260) — переиспользует .money-row*
   (та же геометрия дата/подпись слева, сумма справа, пунктирный разделитель
   между строками), просто вся строка — ссылка на карточку подписки. */
/* Сброс подчёркивания/цвета ссылки для .money-row, когда вся строка —
   ссылка (карты) или кнопка-триггер дровера (подписки — открывает
   дровер правки вместо перехода на отдельную страницу), а не просто div.
   display/padding остаются на .money-row — тут только то, что у <a>/<button>
   есть свои браузерные дефолты, а у div их нет. */
.money-row-link {
  width: 100%;
  background: none;
  border: none;
  font: inherit;
  text-align: left;
  text-decoration: none;
  color: inherit;
  cursor: pointer;
}
.subscription-dot {
  display: inline-block;
  width: 7px;
  height: 7px;
  border-radius: 50%;
  flex-shrink: 0;
}
.subscription-dot--success { background: var(--semantic-success-border); }
.subscription-dot--danger { background: var(--semantic-danger-border); }
.subscription-dot--info { background: var(--semantic-info-border); }
.subscription-row__note--info {
  color: var(--semantic-info-text);
}

/* "Утечка" — предупреждение по подписке, пора отписаться (Figma-нода
   105:265): акцентная левая полоса, не полная рамка, как у .notice-box. */
.subscription-leak {
  display: flex;
  align-items: center;
  gap: 9px;
  width: 100%;
  padding: 7px 9px;
  margin-bottom: 6px;
  background: var(--semantic-danger-bg);
  border-left: 3px solid var(--semantic-danger-border);
}
.subscription-leak .btn--secondary {
  background: var(--paper-base);
}
.subscription-leak .btn--secondary:hover {
  background: var(--service-400);
  border-color: var(--paper-linestrong);
}
.subscription-leak__title {
  margin: 0;
  font-size: var(--f-size-footnote);
  font-weight: var(--f-weight-bold);
  color: var(--semantic-danger-text);
}
.subscription-leak__sub {
  margin: 0;
  font-size: var(--f-size-caption);
  color: var(--ink-secondary);
}
.subscription-leak__text {
  flex: 1;
  min-width: 0;
}

/* Низ карточки "Подписки" — архив (список ссылок) + "+ подписка", разделены
   как обычная info-строка (не money-row — тут нет суммы слева). */
.subscriptions-footer {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  padding-top: 8px;
  margin-top: 4px;
  border-top: 1px dashed var(--paper-line);
  font-size: var(--f-size-caption);
  color: var(--ink-secondary);
}

/* Карточка "Цель" на /me (Figma-нода 104:302) — шкала во всю ширину карточки
   (не .scale/.scale__fill — те фиксированной ширины 204px, для строк
   заказов) и блок "Темп" с прогнозом (успеваешь/отстаёшь/срок прошёл/нет
   данных — см. app/goal_logic.py:compute_forecast). */
.goal-scale {
  width: 100%;
  height: 6px;
  margin: 8px 0 10px;
  background: var(--paper-sunk);
  border-radius: var(--r-pill);
  overflow: hidden;
}
.goal-scale__fill {
  height: 6px;
  background: var(--chip-cool);
  border-radius: var(--r-pill);
}
.goal-pace {
  width: 100%;
  padding: 6px 8px;
  border-radius: var(--r-md);
  border: 1px solid transparent;
}
.goal-pace--success { background: var(--semantic-success-bg); border-color: var(--semantic-success-border); }
.goal-pace--danger { background: var(--semantic-danger-bg); border-color: var(--semantic-danger-border); }
.goal-pace--neutral { background: var(--semantic-neutral-bg); border-color: var(--semantic-neutral-border); }
.goal-pace__title {
  margin: 0;
  font-size: var(--f-size-footnote);
  font-weight: var(--f-weight-bold);
}
.goal-pace--success .goal-pace__title { color: var(--semantic-success-text); }
.goal-pace--danger .goal-pace__title { color: var(--semantic-danger-text); }
.goal-pace--neutral .goal-pace__title { color: var(--ink-base); }
.goal-pace__sub {
  margin: 0;
  font-size: var(--f-size-caption);
  color: var(--ink-secondary);
}
/* Компактное поле выбора файла "Фото" в дровере "Правка цели" (Figma-нода
   112:304) — hidden <input> + видимое имя файла ("файл не выбран"/реальное
   имя) + кнопка. Тот же JS-приём, что у дропзоны шаблонов (app.js,
   data-file-picker/-input/-placeholder/-name) — просто другая, компактная
   однострочная вёрстка вокруг тех же атрибутов, без новых слушателей. */
.file-field {
  display: flex;
  align-items: center;
  gap: 6px;
  width: 100%;
  height: 48px;
  padding: 0 6px 0 14px;
  background: var(--paper-card);
  border: 1px solid var(--paper-line);
  border-radius: var(--r-md);
}
/* Файл не приняли — красим рамку самого выбора файла (текст отказа идёт под
   ним обычным .field-error). Отдельным правилом, а не одним .field--error:
   тот объявлен ВЫШЕ по файлу, специфичность у обоих одинаковая (0,1,0), и
   при равной специфичности выигрывает последний — border у .file-field
   перебил бы подсветку. */
.file-field.field--error {
  border-color: var(--daruma-400);
}
.file-field__name {
  flex: 1;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-size: var(--f-size-bodylg);
  color: var(--ink-secondary);
}
.file-field__name--chosen {
  color: var(--ink-base);
}

/* "Место под фото" в дровере "Правка цели" (Figma-нода 112:283) — превью
   уже загруженной картинки цели или заглушка "твоё фото", пока её нет. */
.goal-photo-preview {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 100%;
  /* 4:3 — та же пропорция, что у .dash-goal-photo (места показа на дашборде).
     Одна пропорция на превью и на показ — чтобы рамка кадрирования показывала
     ровно то, что потом увидишь на дашборде. Не height: раньше высота была
     фиксированной (192px), а ширина тянулась — пропорция плавала от ширины
     окна, и кадр уезжал. */
  aspect-ratio: 4 / 3;
  /* Ширину ограничиваем, пропорцию — нет: совпадать с местом показа должна
     ФОРМА кадра, а не размер. Без потолка превью занимало бы 512×384 —
     половину дровера по высоте, и на ноутбучном экране дровер начинал бы
     скроллиться внутри себя, уводя поля под скролл. 400×300 помещается
     всегда. По центру, а не по левому краю: блок у́же остальных полей
     формы, прижатый влево читался бы как случайно недотянутый. */
  max-width: 400px;
  margin-inline: auto;
  background: var(--paper-sunk);
  border-radius: var(--r-md);
  overflow: hidden;
}
.goal-photo-preview img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}
.goal-photo-preview__placeholder {
  margin: 0;
  font-size: var(--f-size-caption);
  color: var(--ink-secondary);
}

/* Кадрирование картинки (app.js, блок «Кроп»). ОДИН виджет на все места:
   фото цели (4:3) и зверь заказчика (квадрат).

   ПРОПОРЦИЯ ЖИВЁТ ЗДЕСЬ, а не в JS: вся геометрия виджета считается от
   clientWidth/clientHeight рамки, а высота результата выводится как
   outW * (frameH / frameW). Поэтому «сделать другую пропорцию» = задать
   другой aspect-ratio рамке, и ничего больше. Новое место кадрирования —
   это класс с размером и пропорцией плюс .crop-frame, без единой строчки JS.

   .is-cropping навешивается только после выбора НОВОГО файла: уже
   сохранённая картинка лежит на диске обрезанной, двигать в ней нечего.
   Класс намеренно нейтральный — раньше JS был завязан на имя
   goal-photo-preview--crop, и переиспользовать виджет было нельзя.

   position: relative на рамке — под absolute-картинку внутри. Сама рамка
   обрезает по краю, то есть буквально показывает будущий кадр. */
.crop-frame {
  position: relative;
}
.crop-frame.is-cropping {
  cursor: grab;
  /* Палец на телефоне должен двигать картинку, а не скроллить страницу;
     без этого pointermove прерывается браузерным скроллом. */
  touch-action: none;
  user-select: none;
}
.crop-frame.is-cropping.is-dragging {
  cursor: grabbing;
}
/* Специфичность (0,3,1) — перебивает .goal-photo-preview img (0,1,1) выше,
   где стоит width/height 100% + object-fit: cover. В режиме кадрирования
   размер картинки задаёт JS в пикселях, а cover только мешал бы. */
.crop-frame.is-cropping img {
  position: absolute;
  left: 50%;
  top: 50%;
  width: auto;
  height: auto;
  max-width: none;
  object-fit: fill;
  /* Картинку таскают мышью — просим браузер держать её на своём слое. */
  will-change: transform;
  /* Иначе браузер предложит перетащить саму картинку как файл. */
  -webkit-user-drag: none;
}

/* Рамка зверя заказчика — то же кадрирование, но КВАДРАТ: аватар везде
   показывается квадратной плашкой (.avatar-tile). Уже меньше фото цели —
   400px тут ни к чему, а дровер не резиновый. */
.avatar-crop-frame {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 100%;
  max-width: 260px;
  aspect-ratio: 1 / 1;
  margin: 0 auto;
  overflow: hidden;
  background: var(--paper-sunk);
  border: 1px solid var(--paper-line);
  border-radius: var(--r-md);
}
.avatar-crop-frame img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}
.avatar-crop-frame__placeholder {
  margin: 0;
  font-size: var(--f-size-caption);
  color: var(--ink-secondary);
}

/* :not([hidden]) обязателен — авторский display перебил бы браузерное
   [hidden]{display:none} (специфичность тут ни при чём, авторские правила
   всегда старше UA-стилей). В проекте на это уже наступали трижды. */
.crop-zoom:not([hidden]) {
  display: flex;
  align-items: center;
  gap: 10px;
  max-width: 400px;
  margin-inline: auto;
}
.crop-zoom__label {
  font-size: var(--f-size-caption);
  color: var(--ink-secondary);
}
.crop-zoom__slider {
  flex: 1;
  /* Одной строкой красит нативный ползунок в наш жёлтый — своя вёрстка
     range-инпута потребовала бы отдельных правил на три браузерных движка
     ради того же результата. */
  accent-color: var(--service-500);
  cursor: pointer;
}

/* "Вносила раньше" в дровере "Внести" (Figma-нода 115:275) — список прошлых
   взносов (goal_contribution_row, full_date=True) + итоговая строка. */
.goal-contribution-history {
  width: 100%;
  padding-top: 9px;
  border-top: 1px dashed var(--paper-line);
}
.goal-contribution-history__label {
  margin: 0;
  font-size: 10px;
  font-weight: var(--f-weight-bold);
  color: var(--ink-secondary);
}
.goal-contribution-total {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding-top: 5px;
}
.goal-contribution-total__label {
  margin: 0;
  font-size: var(--f-size-caption);
  color: var(--ink-secondary);
}
.goal-contribution-total__amount {
  margin: 0;
  font-size: var(--f-size-caption);
  font-weight: var(--f-weight-bold);
  color: var(--ink-base);
}

/* Дата рождения — ввод прямо в шапке карточки, без дровера (Figma-нода
   247:5590, по прямому решению пользователя после первой версии с
   дровером — "без переходов на дроверы", один инлайн-филд + кнопка "ок").
   Раньше здесь стоял .value-pill (только чтение) + .icon-btn (открывал
   дровер) — оба убраны вместе с самим дровером, .value-pill больше нигде
   не использовался, удалён целиком. */
.birthdate-inline {
  display: flex;
  align-items: center;
  gap: 6px;
}
.birthdate-inline__field {
  height: 22px;
  width: 104px;
  padding: 0 12px;
  border: 1px solid var(--paper-line);
  border-radius: var(--r-md);
  background: var(--paper-card);
  font-family: var(--f-family-body);
  font-size: var(--f-size-caption);
  color: var(--ink-secondary);
}
.birthdate-inline__field:focus {
  outline: none;
  border-color: var(--service-600);
}
.birthdate-inline__save {
  height: 22px;
  padding: 0 12px;
  background: var(--service-400);
  border: none;
  border-radius: var(--r-md);
  font-family: var(--f-family-body);
  font-size: var(--f-size-caption);
  font-weight: var(--f-weight-bold);
  color: var(--ink-base);
  cursor: pointer;
}
.birthdate-inline__save:hover {
  background: var(--service-500);
}
/* Карточка "дата рождения" — только шапка, без своего тела ниже. У
   .section-card__head есть padding-bottom:7px (отделяет шапку от тела на
   остальных карточках) — тут тела нет, и этот же паддинг просто добавлял
   лишнего воздуха только снизу, отступ съезжал относительно верхнего
   (14px от .card). Гасим здесь же, не трогая .section-card__head в местах,
   где тело действительно есть. */
#birth-date .section-card__head {
  padding-bottom: 0;
  flex-wrap: nowrap;
}

/* Кнопка-заглушка во всю ширину карточки (например "+ карта" на /me,
   Figma-нода 104:285) — пунктирная рамка вместо сплошной, чтобы отличаться
   от обычной .btn--secondary и явно читаться как место для добавления. */
.card-add-button {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 100%;
  margin-top: 8px;
  padding: 6px 11px;
  border: 1px dashed var(--paper-linestrong);
  border-radius: var(--r-md);
  font-size: var(--f-size-footnote);
  font-weight: var(--f-weight-medium);
  color: var(--ink-base);
  text-decoration: none;
  box-sizing: border-box;
}
/* Базовый a:hover{text-decoration:underline} (тип+псевдокласс) специфичнее
   голого класса выше и перебивает text-decoration:none именно на hover —
   та же ловушка, что уже чинилась для .btn--primary/secondary/ghost/danger. */
.card-add-button:hover {
  text-decoration: none;
}

/* Компактная карточка-строка (например "Мои реквизиты"/"Пароль" на /me,
   Figma-ноды 105:301/105:307) — заголовок+подпись слева, одна кнопка
   справа, без .section-chip (тут заголовок жирным текстом, не плашкой). */
.compact-card {
  display: flex;
  align-items: flex-start;
  gap: 8px;
}
.compact-card__text {
  flex: 1;
  min-width: 0;
}
/* Кнопка карточки может лежать внутри <form> (карточка "Телеграм" —
   "Подключить" меняет состояние, значит POST, а не ссылка). У <form> по
   умолчанию есть margin-block-end: 1em, и в этой флекс-строке он дал бы
   карточке лишние ~16px высоты по сравнению с соседней "Пароль", где кнопка
   лежит голой. Гасим только внутри compact-card, глобальный <form> не трогаем. */
.compact-card form {
  margin: 0;
}
.compact-card__title {
  margin: 0;
  font-size: var(--f-size-footnote);
  font-weight: var(--f-weight-bold);
  color: var(--ink-base);
}
.compact-card__sub {
  margin: 2px 0 0;
  font-size: var(--f-size-caption);
  color: var(--ink-secondary);
}

/* Баннер "Все платежи покрыты чеками" в карточке "Чеки" (Figma-нода 128:324
   — только success-состояние; warn — расширение для обратного случая, в
   макете не показано, но нужно оставить "нужен чек" видимым и здесь). */
.money-coverage {
  width: 100%;
  margin-top: 8px;
  /* Снизу столько же, сколько сверху: под баннером идёт пунктирный
     разделитель «+ раскрыть» (border-top у .money-history__toggle), и без
     этого отступа линия прилипала к баннеру вплотную. 8px — то же число, что
     у padding-top самого тоггла, поэтому разделитель стоит по центру
     промежутка, а не жмётся к одной из сторон. */
  margin-bottom: 8px;
  padding: 8px 11px;
  border-radius: var(--r-md);
  border: 1px solid transparent;
  font-size: var(--f-size-footnote);
  font-weight: var(--f-weight-bold);
}
.money-coverage--ok {
  background: var(--semantic-success-bg);
  border-color: var(--semantic-success-border);
  color: var(--semantic-success-text);
}
.money-coverage--warn {
  background: var(--semantic-danger-bg);
  border-color: var(--semantic-danger-border);
  color: var(--semantic-danger-text);
}

/* Чекбокс-переключатель без JS: три кнопки-<label> в шапке "Чеки" открывают
   один и тот же инлайн-блок "новый чек" (Figma-нода 298:775) — клик по
   любой из трёх снова закрывает, если уже открыт (обычное поведение
   чекбокса). Сам чекбокс визуально скрыт, но не display:none — иначе
   :checked ~ .receipt-panel перестал бы работать через общего предка. */
.receipt-panel-toggle:checked ~ .receipt-panel:not(.receipt-planned-panel):not(.receipt-cash-panel) {
  display: flex;
}
.receipt-planned-panel-toggle:checked ~ .receipt-planned-panel {
  display: flex;
}
.receipt-cash-panel-toggle:checked ~ .receipt-cash-panel {
  display: flex;
}
.receipt-panel {
  display: none;
  flex-direction: column;
  gap: 6px;
  width: 100%;
  margin-bottom: 8px;
  padding: 10px 11px;
  background: var(--paper-base);
  border: 1px solid var(--paper-line);
  border-radius: var(--r-lg);
}
.receipt-panel > .section-chip {
  align-self: flex-start;
}
/* Разделителя под шапкой не бывает — но общее правило `.section-card__head + *`
   до первой видимой строки карточки "чеки" не достаёт: между ними лежат
   скрытые чекбоксы-переключатели и сами панели. Гасим пунктир у строки,
   которая идёт следом за панелью (закрытой или открытой — у открытой своя
   рамка, две линии подряд там тоже не нужны). */
.receipt-panel + .money-row,
.receipt-cash-panel + .money-row,
.receipt-planned-panel + .money-row {
  border-top: none;
}
.receipt-panel__title {
  margin: 0;
  font-size: var(--f-size-footnote);
  font-weight: var(--f-weight-bold);
  color: var(--ink-base);
}
.receipt-panel__fields {
  display: flex;
  gap: 7px;
  padding-top: 6px;
}
.receipt-panel__fields .field-group {
  flex: 1;
  min-width: 0;
}
.receipt-panel__fields .field {
  flex: 1;
  min-width: 0;
  height: auto;
  padding: 8px 11px;
  background: var(--paper-card);
  font-size: var(--f-size-footnote);
}
.receipt-panel__actions {
  display: flex;
  justify-content: flex-end;
  flex-wrap: wrap;
  gap: 6px;
  padding-top: 6px;
  width: 100%;
}
.receipt-panel__divider {
  margin: 0;
  font-size: var(--f-size-footnote);
  font-weight: var(--f-weight-bold);
  color: var(--ink-base);
  width: 100%;
}

/* Кастомный чекбокс (Figma: жёлтый service/400 квадрат со скруглением,
   когда отмечен) — переиспользуется везде, где нужен чекбокс в духе
   дизайн-системы, не только в панели чеков. */
.checkbox-custom {
  appearance: none;
  -webkit-appearance: none;
  flex-shrink: 0;
  width: 15px;
  height: 15px;
  margin: 0;
  border-radius: 5px;
  border: 1px solid var(--paper-linestrong);
  background: var(--paper-card);
  cursor: pointer;
}
.checkbox-custom:checked {
  background: var(--service-400);
  border-color: var(--service-600);
}

.money-remainder__value--danger {
  color: var(--semantic-danger-text);
}

/* Карточка "Расходы" (Figma-нода 129:298) — плоский список (без "recent 2 +
   история", в отличие от Денег/Чеков — своего Figma-состояния для длинного
   списка нет, решили не выдумывать урезание). */
.expense-row {
  display: flex;
  flex-direction: column;
  gap: 4px;
  width: 100%;
  padding: 6px 0;
  border-top: 1px dashed var(--paper-line);
}
/* Оптическая, а не арифметическая симметрия вокруг пунктира. Сверху к нему
   подходит ТЕКСТ, снизу — кнопка "Возместили" с рамкой. По числам зазоры
   были равны (6px), но у текста над глифами есть ещё полулидинг
   ((line-height − font-size) / 2 ≈ 3px пустоты, которую видно как отступ, а
   в getBoundingClientRect её нет) — и линия визуально липла к кнопке.
   Компенсируем ровно на этот полулидинг, считая его от токенов. */
.expense-row:has(.expense-row__badges) {
  padding-bottom: calc(6px + (var(--f-size-footnote) * var(--f-lineheight-base) - var(--f-size-footnote)) / 2);
}
.expense-row:first-child {
  border-top: none;
}
.expense-row__top {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 10px;
}
.expense-row__what {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: 6px;
  min-width: 0;
}
.expense-row__desc {
  font-size: var(--f-size-footnote);
  font-weight: var(--f-weight-bold);
  color: var(--ink-base);
}
.expense-row__date {
  font-size: var(--f-size-caption);
  color: var(--ink-secondary);
  white-space: nowrap;
}
.expense-row__amount {
  font-size: var(--f-size-footnote);
  font-weight: var(--f-weight-bold);
  color: var(--ink-base);
  white-space: nowrap;
}
.expense-row__amount--unreimbursed {
  color: var(--semantic-danger-text);
}
.expense-row__badges {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 6px;
}
.expense-totals {
  display: flex;
  flex-direction: column;
  gap: 4px;
  width: 100%;
  margin-top: 8px;
  padding-top: 7px;
  border-top: 1px solid var(--paper-line);
}
.expense-totals__row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
}
.expense-totals__label {
  margin: 0;
  font-size: var(--f-size-caption);
  color: var(--ink-secondary);
}
.expense-totals__value {
  margin: 0;
  font-size: var(--f-size-footnote);
  font-weight: var(--f-weight-bold);
  color: var(--ink-base);
}
.expense-totals__value--danger {
  color: var(--semantic-danger-text);
}

/* Скрыть визуально, но оставить доступным для клика/скринридера — нативный
   чекбокс переключения задачи (иконка "умэ" — его визуальная замена). */
.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

/* Карточка "Задачи" (Figma-нода 129:329) — чек-лист без своей формы в
   шапке (в отличие от других карточек тела заказа): "+ задача" — только
   свёрнутый тоггл внизу, как "▸ + раскрыть" у Денег/Чеков, но здесь он
   на самом деле добавляет, а не разворачивает историю (имя в Figma
   "Форма · свёрнута · + задача" не врёт). Галка — символ "умэ" из общего
   спрайта маскота (`#ume`, _daruma_sprite.html) — тот же приём, что и с
   даруму: никогда не рисуем эту иконку заново, только `<use>` готового
   символа с переключением CSS-переменных на цвет. */
.task-ume {
  flex-shrink: 0;
  width: 11px;
  height: 11px;
  /* Центр умэ — по центру ПЕРВОЙ строки текста, а не по верху ряда:
     считаем от токенов (высота строки минус высота значка пополам), чтобы
     не переподбирать число при смене шрифтовой шкалы. */
  margin-top: calc((var(--f-size-footnote) * var(--f-lineheight-base) - 11px) / 2);
}
.task-ume--done {
  --u-petal: var(--paper-line);
  --u-v-core: none;
}
/* flex-start, а не center: у длинной задачи текст переносится на 2-3
   строки, и по центру умэ с крестиком уезжали в середину абзаца — читалось
   так, будто иконка относится ко второй строке. Теперь оба держатся первой
   строки, как в бумажном чек-листе. На однострочных рядах разницы нет:
   высота строки равна высоте иконок. */
.task-row {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 8px;
  padding: 3px 0;
}
.task-row__toggle-form {
  flex: 1;
  min-width: 0;
}
.task-row__label {
  display: flex;
  align-items: flex-start;
  gap: 6px;
  cursor: pointer;
  /* Реальный баг: легаси "form label { margin: 0.75rem 0 0.25rem }" (для
     обычных подписей над полями формы) утекал и сюда — .task-row__label
     ничего не задавал по margin, а по специфичности он не может проиграть,
     когда только одно правило вообще что-то указывает. Нашёлся при попытке
     отцентровать умэ/крестик по первой строке в "цели месяца": именно этот
     невидимый 12px/4px отступ и сдвигал всё вниз относительно кнопки
     удаления (та же .task-row, но без обёртки-label). */
  margin: 0;
}
.task-row__text {
  font-size: var(--f-size-footnote);
  color: var(--ink-base);
  /* Длинная запись без пробелов (одно сплошное "слово") иначе вылезает за
     правый край карточки — min-width:0 нужен отдельно от overflow-wrap:
     без него флекс-item (.task-row__label — строка) не даёт тексту сжаться
     ниже его min-content ширины, даже когда сам перенос уже разрешён. */
  overflow-wrap: break-word;
  word-break: break-word;
  min-width: 0;
}
.task-row__text--done {
  /* Плоский #727576, а не var(--ink-secondary) (23272B при alpha 64%) —
     тот же цвет на глаз, но без прозрачности, чтобы не плыл по тону от
     фона под текстом (по прямому запросу пользователя). */
  color: #727576;
  text-decoration: line-through;
}
/* Задачи "сегодня" на странице "Неделя" (Figma-нода 14:8) — жирным, в
   отличие от обычных regular (задачи заказа, задачи "ближайших" дней). */
.task-row__text--bold {
  font-weight: var(--f-weight-bold);
}

/* Строка созвона/встречи в списке дня на "Неделе" (macros.week_entry_row).
   Геометрия — как у .task-row (тот же padding 3px 0 и footnote-текст),
   чтобы задачи и встречи стояли одной ровной лентой; отличия только там,
   где отличается суть: вместо умэ-чекбокса — иконка типа (кликать нечего,
   у встречи нет "сделано"), справа — время начала. */
.week-entry-row {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 3px 0;
}
/* 20px, а не 13px как у умэ рядом: иконки типа — рисунки пользователя
   (ретро-телефон, зверь), рисунок детальный и на 13-15px превращается в
   мутное пятно (проверено скриншотом на 15/20/26/40px). Умэ у задачи —
   простой цветок, ему мелкий размер не мешает. */
/* Цвет — тот же, что у лепестков умэ (--u-petal в daruma.css, он же
   --daruma-400): пробовали красить по типу записи (soda / mint, как точки
   .day-dot--call/--meeting) — на бумажном фоне пастельные голубой и мятный
   на линейном рисунке почти не читались, проверено на живой странице. Тип
   записи по-прежнему виден по самому рисунку (телефон / зверь), а точки на
   экране дня и в легенде месяца остались цветными по типу, как были. */
.week-entry-row__icon {
  flex-shrink: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 20px;
  height: 20px;
  color: var(--daruma-400);
}
.week-entry-row__text {
  flex: 1;
  min-width: 0;
  font-size: var(--f-size-footnote);
  color: var(--ink-base);
  overflow-wrap: break-word;
  word-break: break-word;
}
.week-entry-row__text--bold {
  font-weight: var(--f-weight-bold);
}
.week-entry-row__time {
  flex-shrink: 0;
  font-size: var(--f-size-caption);
  font-weight: var(--f-weight-medium);
  color: var(--ink-secondary);
}

/* Карточка "Дневник" (Figma-нода 129:380) — плоский список записей,
   "+ запись" в шапке открывает дровер с формой (та же textarea, что
   раньше была всегда открыта инлайн). */
.note-row {
  display: flex;
  align-items: flex-start;
  gap: 6px;
  width: 100%;
  padding: 5px 0;
  border-top: 1px dashed var(--paper-line);
}
.note-row:first-child {
  border-top: none;
}
/* min-width: 0 обязателен — без него флекс-айтем не сжимается меньше своего
   содержимого, и длинное "слово" (ссылка без пробелов) распирало бы строку,
   выталкивая карандаш с крестиком за край карточки. */
.note-row__body {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 1px;
}
.note-row__summary {
  display: flex;
  flex-direction: column;
  gap: 1px;
  cursor: pointer;
  list-style: none;
}
.note-row__summary::-webkit-details-marker {
  display: none;
}
/* Свёрнутый текст — 2 строки, дальше обрез. Раскрытие снимает клэмп через
   display: block: это единственный надёжный способ выключить -webkit-box,
   само значение -webkit-line-clamp обратно не сбрасывается. */
.note-row__summary .note-row__text {
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}
.note-row__body[open] .note-row__text {
  display: block;
  overflow: visible;
}
.note-row__arrow {
  display: inline-flex;
  transition: transform var(--m-duration-fast) var(--m-easing-out);
}
.note-row__body[open] .note-row__arrow {
  transform: rotate(90deg);
}
.note-row__actions {
  flex-shrink: 0;
  display: flex;
  align-items: center;
  gap: 2px;
}
.note-row__date {
  margin: 0;
  font-size: var(--f-size-caption);
  color: var(--ink-secondary);
}
.note-row__text {
  margin: 0;
  font-size: var(--f-size-footnote);
  color: var(--ink-base);
  white-space: pre-wrap;
  overflow-wrap: break-word;
  word-break: break-word;
}

/* Блок-заглушка внутри карточки, когда чего-то не хватает, чтобы карточка
   работала как обычно (Figma-нода 146:429, "Нужен профиль" — первая из
   серии подобных подсказок на странице заказа, дальше таких будет больше,
   отсюда общее имя, не "no-profile-notice"). */
.notice-box {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 7px;
  width: 100%;
  padding: 10px 11px;
  border-radius: var(--r-md);
  border: 1px solid transparent;
}
/* .notice-box сам задаёт display:flex — той же специфичности, что и
   встроенный браузерный [hidden]{display:none}, но авторский CSS всегда
   бьёт UA-стили при равной специфичности, поэтому голый атрибут hidden
   на .notice-box молча игнорировался (виден был всегда, ещё до клика на
   "Отписаться" без ссылки — реальный баг, найден пользователем). Явный
   [hidden]-селектор специфичнее (0,2,0 против 0,1,0) и возвращает hidden
   его обычный смысл. */
.notice-box[hidden] {
  display: none;
}
/* Общее правило `.section-card__head + *` обнуляет верхний padding первой
   строки под шапкой — но уведомление это не строка списка, а коробка со
   своей рамкой и заливкой: без своего padding-top текст прилипал к верхней
   рамке ("У «...» нет профиля реквизитов" в блоке "договор"). Возвращаем
   его симметрично нижнему; специфичность (0,2,0) равна правилу шапки, но
   это правило ниже по файлу и потому выигрывает. */
.section-card__head + .notice-box {
  padding-top: 10px;
}
.notice-box--warning {
  background: var(--semantic-warning-bg);
  border-color: var(--semantic-warning-border);
}
/* Вариант --danger (Figma-нода 351:894, "Невозможно перейти по ссылки" в
   карточке "Подписки" на /me) — тот же .notice-box, просто тревожные
   цвета вместо жёлтых. */
.notice-box--danger {
  background: var(--semantic-danger-bg);
  border-color: var(--semantic-danger-border);
}
/* Вариант --info (Figma-нода 108:294, "Паспорт и ИНН система не хранит" в
   дровере "Мои реквизиты") — спокойные синие цвета, не тревога/предупреждение. */
.notice-box--info {
  background: var(--semantic-info-bg);
  border-color: var(--semantic-info-border);
}
.notice-box__title {
  width: 100%;
  margin: 0;
  font-size: var(--f-size-footnote);
  font-weight: var(--f-weight-bold);
}
.notice-box--warning .notice-box__title {
  color: var(--semantic-warning-text);
}
.notice-box--danger .notice-box__title {
  color: var(--semantic-danger-text);
}
.notice-box--info .notice-box__title {
  color: var(--semantic-info-text);
}
.notice-box__text {
  width: 100%;
  margin: 0;
  font-size: var(--f-size-caption);
  color: var(--ink-secondary);
}
.notice-box .btn--secondary {
  height: auto;
  min-height: 43px;
  padding: 10px 14px;
  white-space: normal;
  text-align: center;
  line-height: 1.4;
}
/* Вариант с иконкой слева (Figma-нода 136:336/404:1129, модалка "Данные для
   документа") — единственное место, где у .notice-box есть значок; сама
   .notice-box осталась flex-column (нужна остальным вариантам без иконки),
   этот модификатор просто перекладывает на строку и добавляет колонку-тело
   для заголовка/текста. */
.notice-box--with-icon {
  flex-direction: row;
  align-items: flex-start;
  gap: 11px;
}
.notice-box__icon {
  flex-shrink: 0;
  color: var(--semantic-info-text);
}
.notice-box__body {
  display: flex;
  flex-direction: column;
  gap: 2px;
  flex: 1;
  min-width: 0;
}

/* Модалка "Данные для документа" (Figma-ноды 136:334 физлицо / 404:1127
   юрлицо, 2026-07-27) — генерация .docx, паспорт/ИНН исполнителя и
   заказчика (только если сделка с физлицом, см. routers/contracts.py:
   is_individual). Данные нигде не хранятся в базе — см.
   app/sensitive_cache.py; сама эта правка меняет только внешний вид
   дровера, логика (какие поля показывать/сохранять) не тронута. */
.sensitive-modal__group {
  display: flex;
  flex-direction: column;
  gap: 10px;
  width: 100%;
}
/* Второй блок (заказчик) отделён пунктирной чертой — как в ноде; первый
   (я·исполнитель) идёт сразу после плашки приватности без черты. */
.sensitive-modal__group--customer {
  border-top: 1px dashed var(--paper-line);
  padding-top: 11px;
}
.sensitive-modal__tag-row {
  display: flex;
  align-items: center;
  gap: 8px;
  width: 100%;
}
.sensitive-modal__tag {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  padding: 4px 12px 4px 7px;
  background: var(--chip-day);
  border-radius: var(--r-pill);
  flex-shrink: 0;
}
.sensitive-modal__tag-avatar {
  flex-shrink: 0;
  width: 24px;
  height: 24px;
}
.sensitive-modal__tag-label {
  font-size: var(--f-size-caption);
  font-weight: var(--f-weight-bold);
  color: var(--ink-base);
  white-space: nowrap;
}
.sensitive-modal__tag-note {
  font-size: var(--f-size-footnote);
  color: var(--ink-secondary);
}
.sensitive-modal__footnote {
  margin: 0;
  font-size: var(--f-size-caption);
  color: var(--ink-secondary);
}

/* ============================== Страница «Карта» ============================ */
/* По Figma-ноде 97:186 ("DorumaCRM · Карта") — переверстка /cards/{id} (и
   /cards/new — та же страница, просто card=None: колонка-переключатель с
   формой "Новая карта" видна, правой колонки со списаниями/лентой нет).
   В отличие от "Заказа" (собирался по карточке за заход), тут бэкенд был
   полностью готов заранее (app/card_logic.py:operation_rows/
   upcoming_shortfall) — вся страница за один заход. */

.card-detail-head {
  display: flex;
  align-items: center;
  gap: 8px;
  width: 100%;
  flex-wrap: wrap;
  margin-bottom: 11px;
}
.card-detail-head__name {
  margin: 0;
  font-size: var(--f-size-h2);
  font-weight: var(--f-weight-bold);
  color: var(--ink-base);
  flex: 1;
  min-width: 0;
}
.card-detail-head__actions {
  display: flex;
  align-items: center;
  gap: 6px;
  flex-wrap: wrap;
}

.card-page-grid {
  display: flex;
  align-items: flex-start;
  gap: 11px;
  width: 100%;
}
.card-page-grid__switcher {
  display: flex;
  flex-direction: column;
  gap: 11px;
  width: 336px;
  flex-shrink: 0;
}
.card-page-grid__main {
  display: flex;
  flex-direction: column;
  gap: 11px;
  flex: 1;
  min-width: 0;
}
@media (max-width: 860px) {
  /* align-items:stretch обязателен вместе с разворотом в колонку: базовое
     правило .card-page-grid ставит flex-start, а во flex-column это значит
     «дети по ширине своего контента», а не на всю ширину. width:100% ниже
     спасает только карман — главная колонка осталась бы по контенту и
     распирала бы страницу. На карте это пока не выстреливало (контент у́же
     экрана), но мина та же, что на «Неделе» — гасим профилактически.
     Блок намеренно оставлен на 860px, а не переведён на общий мобильный
     768px: это не новое мобильное правило, а починка существующего, и
     переезд поменял бы ширины, на которых страница сворачивается. */
  .card-page-grid {
    flex-direction: column;
    align-items: stretch;
  }
  .card-page-grid__switcher {
    width: 100%;
  }
}

/* Карта-переключатель в левой колонке — своя карта выделена --chip-today
   (тот же токен, что у "сегодня" в календаре/выбранной опции select). */
.card-switch {
  display: block;
  width: 100%;
  padding: 13px 12px;
  background: var(--paper-card);
  border: 1px solid var(--paper-line);
  border-radius: var(--r-lg);
  text-decoration: none;
  color: inherit;
}
/* Ссылки-КАРТОЧКИ (не текстовые ссылки): глобальный `a:hover
   { text-decoration: underline }` подчёркивал в них весь текст сразу —
   название, суммы, подписи, — хотя собственное text-decoration:none у них
   стоит: оно просто не распространяется на :hover. Нашлось на карточке
   карты (по ней и пришла правка), но ровно то же было у строк заказов и
   заказчиков, плиток дашборда и остальных кликабельных карточек — поэтому
   правило общее, а не точечное. Текстовые ссылки (.muted-link,
   .info-row__value--link, ссылки внутри абзацев) не трогаем: там
   подчёркивание при наведении — правильный сигнал. */
/* .topbar-tab добавлен 05.08: у него, как и у карточек ниже, есть свой
   text-decoration: none, но на :hover он не распространяется, и глобальный
   a:hover перебивал его. Это НЕ регресс от правок фокуса/обводок — так было
   с переезда топбара на Стикербук (e02e2c0), просто в разбор 29.07 табы не
   попали. */
.topbar-tab:hover,
.card-switch:hover,
.row-card:hover,
.order-row:hover,
.customer-row:hover,
.order-line:hover,
.invoice-card-row:hover,
.template-row:hover,
.contract-card-item:hover,
.dash-work-row:hover,
.dash-meeting-slot:hover,
.day-deadline-row:hover,
.search-result:hover,
.search-result:focus,
.search-result:focus-visible,
.search-result:active {
  text-decoration: none;
}
.card-switch--selected {
  background: var(--chip-today);
}
.card-switch__head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 8px;
  width: 100%;
}
.card-switch__name {
  margin: 0;
  font-size: var(--f-size-h2);
  font-weight: var(--f-weight-bold);
  color: var(--ink-base);
}
/* 28px в Figma ×1.2=34 — крупнее любого готового money-токена (moneylg=31
   ближайший, но заметно меньше), свой "герой"-размер именно для баланса
   карты, той же логикой, что раньше отдельно масштабировали money/moneylg. */
.card-switch__balance {
  margin: 12px 0 0;
  font-size: 34px;
  font-weight: var(--f-weight-bold);
  color: var(--ink-base);
}
.card-switch__row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  width: 100%;
  padding: 6px 0;
  border-top: 1px dashed var(--paper-line);
}
.card-switch__row-label {
  margin: 0;
  font-size: var(--f-size-caption);
  color: var(--ink-secondary);
}
.card-switch__row-value {
  margin: 0;
  font-size: var(--f-size-footnote);
  font-weight: var(--f-weight-bold);
  color: var(--ink-base);
}

.card-switch-new {
  display: flex;
  flex-direction: column;
  gap: 10px;
  width: 100%;
  padding: 13px 12px;
  background: var(--paper-card);
  border: 1px solid var(--paper-line);
  border-radius: var(--r-lg);
}
.card-switch-new__title {
  margin: 0;
  font-size: var(--f-size-footnote);
  font-weight: var(--f-weight-bold);
  color: var(--ink-base);
}

/* Строка ленты операций карты (macros.card_operation_row, Figma-нода
   97:186, "Операция") — переиспользует .money-row, но дата тут вторична
   (регулярный секондари-текст, а не жирный акцент, как обычный
   .money-row__date), фиксированной ширины, чтобы теги/описания у всех
   строк начинались из одной точки (Figma w-58 ×1.2≈70). Описание — обычным
   ink-base текстом (.money-row__text), не приглушённым .money-row__status. */
.card-op-date {
  display: inline-block;
  width: 70px;
  flex-shrink: 0;
  font-size: var(--f-size-caption);
  color: var(--ink-secondary);
}
.money-row__text {
  font-size: var(--f-size-footnote);
  color: var(--ink-base);
}
/* .money-row__amount не имел красного варианта — были только --paid
   (зелёный)/--pending (жёлтый)/--muted; списанию за подписку в ленте карты
   нужен именно красный (Figma-нода 97:186, "Списание за «хз»" −34.00 €). */
.money-row__amount--danger {
  color: var(--semantic-danger-text);
}

/* ============================== Страница «Неделя» ============================ */
/* Личный дашборд дня (Figma-нода 12:2) — новая точка входа /schedule по
   умолчанию (app/week_logic.py, routers/schedule.py:_week_dashboard). Старый
   TUI Calendar (месяц/неделя/день) остался нетронутым, живёт на том же
   /schedule по явному ?view=. */

.week-page {
  display: flex;
  align-items: flex-start;
  gap: 12px;
  width: 100%;
}
.week-pocket {
  display: flex;
  flex-direction: column;
  gap: 11px;
  width: 240px;
  flex-shrink: 0;
}
.week-main {
  display: flex;
  flex-direction: column;
  gap: 11px;
  flex: 1;
  min-width: 0;
}
@media (max-width: 860px) {
  /* Тот же корень, что и у .card-page-grid выше, только здесь он уже
     выстрелил: .week-main держался на 686px (по ширине своего содержимого) и
     утаскивал страницу в горизонтальный скролл, хотя карман честно сворачивался
     — width:100% был прописан только ему. */
  .week-page {
    flex-direction: column;
    align-items: stretch;
  }
  .week-pocket {
    width: 100%;
  }
}

/* Общая коробка кармана (карманный месяц/полоса года/цели месяца) —
   Figma-ноды 13:4/13:103/13:130, одна и та же оболочка. */
.pocket-box {
  display: flex;
  flex-direction: column;
  gap: 5px;
  width: 100%;
  padding: 11px;
  background: var(--paper-card);
  border: 1px solid var(--paper-line);
  border-radius: var(--r-lg);
}
.pocket-box__head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  width: 100%;
}
.pocket-box__title {
  margin: 0;
  font-size: var(--f-size-footnote);
  font-weight: var(--f-weight-bold);
  color: var(--ink-base);
}
.pocket-box__sub {
  margin: 0;
  font-size: var(--f-size-caption);
  color: var(--ink-secondary);
}
/* Без align-self чип "цели месяца" растягивался на всю ширину карманной
   карточки (flex-column без align-items = stretch по умолчанию), и текст
   оставался прижат влево с большим пустым отступом справа до края пилюли —
   тот же баг уже чинился для .receipt-panel > .section-chip. */
.pocket-box > .section-chip {
  align-self: flex-start;
}

/* Карманный месяц (Figma-нода 13:4) — своя мини-сетка, не TUI Calendar:
   просто обзор + точки "есть дела", без интерактивности/навигации по датам
   (см. app/week_logic.py:month_busy_days). */
.pocket-month__grid {
  display: flex;
  flex-wrap: wrap;
  gap: 1px;
  width: 100%;
}
/* Числа месяца — теперь <a> (клик открывает /schedule/day/{дата}, 2026-07-26),
   не <div>: text-decoration:none явно, иначе браузерное подчёркивание ссылки
   остаётся (author color уже и так побеждал бы UA a:link по origin, но
   text-decoration ничем не переопределялся бы без этой строки). Заголовки
   букв дней недели (--head) остаются обычным <div> — им ссылка не нужна.

   Ячейка — flex-колонка из двух строк (число + слот под точку), а не число
   с точкой поверх абсолютным позиционированием (2026-07-27, правка по
   скриншоту — точка "прилипала" к числу): слот зарезервирован фиксированной
   высотой у ВСЕХ ячеек, есть дела в этот день или нет, поэтому строки сетки
   не съезжают — точка просто появляется/пропадает внутри уже отведённого
   места, а не меняет высоту самой ячейки. */
.pocket-month__cell {
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 3px;
  width: 30px;
  height: 24px;
  border-radius: 6px;
  font-size: 11px;
  color: var(--ink-base);
  text-decoration: none;
  cursor: pointer;
}
.pocket-month__cell--head {
  font-weight: var(--f-weight-bold);
  color: var(--ink-secondary);
}
.pocket-month__cell--outside {
  color: var(--ink-disabled);
}
.pocket-month__cell--today {
  background: var(--chip-today);
  font-weight: var(--f-weight-bold);
}
/* Подсветка при наведении — своя псевдо-подложка с отступом от края ячейки
   (2026-07-27: в исходном примере-макете подсветка была без отступа — по
   словам пользователя, её собственный недосмотр при рисовании примера,
   тут явно попросили поправить), не задний фон самой ячейки (тот залил бы
   весь блок вплотную к краям, как у .pocket-month__cell--today). z-index:-1
   держит подложку под числом/точкой, а не поверх них — они сами не
   позиционированы, но абсолютный псевдоэлемент по умолчанию рисуется поверх
   обычного контента, если явно не задвинуть его назад. Базовый глобальный
   a:hover{text-decoration:underline} (тип+псевдокласс) специфичнее голого
   класса выше и перебивает text-decoration:none именно на hover — та же
   ловушка, что уже чинилась для .btn--primary/secondary/ghost/danger и
   .card-add-button. */
.pocket-month__cell:hover {
  text-decoration: none;
}
.pocket-month__cell::before {
  content: "";
  position: absolute;
  inset: 1px;
  border-radius: 5px;
  z-index: -1;
}
.pocket-month__cell:hover::before {
  background: var(--paper-sunk);
}
.pocket-month__cell__day {
  line-height: 1;
}
.pocket-month__dot-slot {
  display: flex;
  align-items: center;
  justify-content: center;
  height: 4px;
}
.pocket-month__dot {
  width: 4px;
  height: 4px;
  border-radius: 50%;
  background: var(--service-600);
}

/* Полоса года (Figma-нода 13:103) */
.pocket-year__grid {
  display: flex;
  flex-wrap: wrap;
  gap: 2px;
  width: 100%;
}
/* Тоже теперь <a> (клик → /schedule/month/{год}/{месяц}, 2026-07-26) —
   тот же text-decoration:none, что и у .pocket-month__cell выше. */
.pocket-year__month {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 52px;
  height: 19px;
  border-radius: 7px;
  font-size: 11px;
  color: var(--ink-secondary);
  text-decoration: none;
  cursor: pointer;
}
/* Общий a:hover{text-decoration:underline} (specificity 0,1,1) специфичнее
   text-decoration:none выше (0,1,0) и перебивает его на ховере — та же
   ловушка, что уже чинилась для .btn--primary/secondary/ghost/danger. */
.pocket-year__month:hover {
  text-decoration: none;
}
.pocket-year__month--current {
  background: var(--chip-today);
  font-weight: var(--f-weight-bold);
  color: var(--ink-base);
}

/* Цели месяца: текст переносится на несколько строк чаще, чем задачи
   заказа (узкий карман 240px) — общий .task-row/.task-row__label с
   align-items:center центрировал умэ/крестик по высоте ВСЕГО блока текста,
   а не по первой строке. Тут (и только тут, не трогая .task-row глобально —
   задачи заказа так не переносятся) выравнивание по верху + компенсирующий
   отступ, подобранный по фактической высоте первой строки. Промежуток между
   строками задач тоже уменьшен вдвое (padding 3px → 1.5px). */
.pocket-box .task-row {
  align-items: flex-start;
  padding: 1.5px 0;
}
.pocket-box .task-row__label {
  align-items: flex-start;
}
/* Компенсирующий отступ, замерен по факту (getClientRects первой строки
   текста): без него умэ/крестик выравниваются по верху первой строки, а
   не по её центру. */
.pocket-box .task-row .task-ume {
  margin-top: 4px;
}
.pocket-box .task-row > .icon-btn {
  margin-top: -4.5px;
}

/* Цели месяца (Figma-нода 361:1020) — та же .task-row/.task-ume, что у
   задач заказа (macros.task_ume_row), с той же кнопкой удаления у каждой
   строки. Форма добавления — пунктирный разделитель сверху, поле
   фиксированной ширины (не тянется на всю колонку, как в Figma). */
.pocket-goals__form {
  display: flex;
  /* flex-start, не дефолтный stretch — иначе кнопка "+ запись" растягивалась
     бы по высоте вместе с textarea при авто-росте (data-autogrow). */
  align-items: flex-start;
  gap: 6px;
  width: 100%;
  padding-top: 8px;
  border-top: 1px dashed var(--paper-line);
}
/* Базовые .btn--* не задают white-space — в узком кармане (240px) кнопке
   "+ запись" не хватает места и текст рвётся на 2 строки внутри
   зафиксированной 22px высоты .btn--xxs (реальный баг, найден на скриншоте). */
.pocket-goals__form button {
  flex-shrink: 0;
  white-space: nowrap;
}
/* Поле на .field--task (см. блок "ЭТАЛОН для полей ввода записей/задач"
   ниже) — только сайзинг в контексте этой конкретной flex-строки: flex:1
   отдаёт полю всё место, что останется после кнопки "+ запись". */
.pocket-goals__form .field--task {
  flex: 1;
  min-width: 0;
}

/* День · сегодня (Figma-нода 14:3) — самая заметная карточка колонки,
   задачи + деньги за сегодня в одном ряду. */
.week-today {
  display: flex;
  flex-direction: column;
  gap: 5px;
  width: 100%;
  padding: 11px 12px;
  background: var(--paper-card);
  border: 1px solid var(--paper-line);
  border-radius: var(--r-lg);
}
.week-today__row {
  display: flex;
  gap: 12px;
  align-items: flex-start;
  width: 100%;
}
.week-today__tasks {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 5px;
}
/* Без align-self чип "сегодня" растягивался на всю ширину колонки
   (flex-column без align-items = stretch) — тот же баг, что уже чинился
   для .receipt-panel/.pocket-box > .section-chip. */
.week-today__tasks > .section-chip {
  align-self: flex-start;
}
.week-money {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 2px;
  padding-top: 17px;
  flex-shrink: 0;
}
.week-money__value {
  margin: 0;
  font-size: var(--f-size-caption);
  font-weight: var(--f-weight-bold);
  color: var(--ink-base);
}
.week-money__label {
  margin: 0;
  font-size: 11px;
  color: var(--ink-secondary);
}

/* Ближайшие дни (Figma-нода 14:49) — 2 карточки в ряд, задачи regular
   (не bold, как у "сегодня"). */
.week-upcoming {
  display: flex;
  gap: 11px;
  width: 100%;
}
.week-upcoming__day {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 5px;
  padding: 11px 12px;
  background: var(--paper-card);
  border: 1px solid var(--paper-line);
  border-radius: var(--r-lg);
}
/* Тот же chip-stretch баг, что у .week-today__tasks выше. */
.week-upcoming__day > .section-chip {
  align-self: flex-start;
}

/* Встречи · три дня (Figma-нода 15:2) — 3 колонки с акцентной левой
   полосой только у сегодняшней (если у неё есть встреча). */
.week-meetings__row {
  display: flex;
  gap: 11px;
  width: 100%;
}
.week-meeting {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 1px;
  padding-left: 10px;
  border-left: 2px solid var(--paper-line);
}
.week-meeting--accent {
  border-left-color: var(--service-400);
}
.week-meeting__when {
  margin: 0;
  font-size: var(--f-size-caption);
  color: var(--ink-secondary);
}
.week-meeting__title {
  margin: 0;
  font-size: var(--f-size-footnote);
  font-weight: var(--f-weight-bold);
  color: var(--ink-base);
}
.week-meeting__title--empty {
  font-weight: var(--f-weight-regular);
  color: var(--ink-secondary);
}
/* Вторая/третья встреча того же дня (week.html: day.meetings[1:]) — компактной
   строкой время+название под основной, без отдельной точки-типа (2026-07-26,
   по решению пользователя — важно видеть все встречи дня, не "+N ещё"). */
.week-meeting__extra {
  display: flex;
  flex-direction: column;
  gap: 1px;
  margin-top: 4px;
}
.week-meeting__extra-row {
  margin: 0;
  font-size: var(--f-size-caption);
  color: var(--ink-secondary);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* Дни 4-7 скользящего окна (Figma-нода 15:15, "Прошедшее и выходные" —
   имя из макета) — ровно 4 карточки (app/week_logic.py:week_leftover_days),
   ширина адаптивная: пустой день — узкая фиксированная плашка (90px, только
   чип), день с задачами — тянется и забирает освободившееся место. Раньше
   тут была бинарная past/future заливка (paper-sunk/paper-card) — в
   скользящем окне "прошедшего" не бывает, оба состояния теперь одна и та
   же карточка (paper-card + рамка), различаются только шириной и цветом
   чипа (будни/выходные). */
.week-leftover {
  display: flex;
  gap: 11px;
  width: 100%;
  flex-wrap: wrap;
}
.week-leftover__day {
  display: flex;
  flex-direction: column;
  gap: 5px;
  padding: 7px 10px;
  background: var(--paper-card);
  border: 1px solid var(--paper-line);
  border-radius: var(--r-lg);
}
/* Пустой день — узкая фиксированная плашка (только чип, без задач):
   flex-basis без grow/shrink, не растягивается и не сжимается. Заполненный
   день — flex:1, забирает всё место, что осталось от пустых соседей. Если
   все 4 дня пустые, .week-leftover--empty ни у одного нет — делятся
   поровну через flex:1 по умолчанию на .week-leftover__day ниже. */
.week-leftover__day--empty {
  flex: 0 0 90px;
}
.week-leftover__day--filled {
  flex: 1;
  min-width: 0;
}
/* Если задач нет вообще ни у одного из 4 дней — 90px-плашки выглядели бы
   потерянными в левой части ряда с пустым местом справа; вместо этого все
   четыре делятся поровну на всю ширину (см. leftover_all_empty в
   routers/schedule.py). */
.week-leftover--all-empty .week-leftover__day--empty {
  flex: 1;
}
/* Тот же chip-stretch баг, что у .week-today__tasks/.week-upcoming__day. */
.week-leftover__day > .section-chip {
  align-self: flex-start;
}
/* text-decoration:line-through был безусловным на этом классе — зачёркивал
   ЛЮБУЮ задачу в превью дней 4-7, даже невыполненную (реальный баг, никак
   не связанный с добавлением DayTask — нашёлся по ходу проверки, см.
   day.tasks[0].is_done в week.html). Зачёркиваем только выполненную. */
.week-leftover__text {
  margin: 0;
  font-size: var(--f-size-caption);
  color: var(--ink-secondary);
  overflow-wrap: break-word;
  word-break: break-word;
}
.week-leftover__text--done {
  text-decoration: line-through;
}

/* Отсчёт до отпуска (Figma-нода 20:5, сверено дословно 2026-07-27 —
   get_design_context/get_screenshot/get_variable_defs/download_assets,
   первая сборка была на глаз и разошлась с макетом по размерам — ни один
   хардкод-px не был умножен на ×1.2, плюс --f-weight-semibold, которым
   были покрашены и цифры, и "часов"/"дней", в токенах не существует
   (проект знает только regular/medium/bold) — тихо схлопывалось в обычный
   вес браузером). .week-countdown__days/__time обновляются вживую раз в
   секунду (app.js), от data-target на самом блоке.

   Вложенность gap повторяет структуру ноды, не один плоский ряд: 10px
   (×1.2→12px) — между "главной группой" (дарума+текст+цифры) и кнопкой
   "выбрать даты"; 23px (→28px) — внутри главной группы, items-end (низом
   по низу, не центром — дарума/текст/блоки разной высоты); 5px (→6px) —
   между блоком-цифрой и вертикальной подписью внутри .week-vacation__blocks. */
.week-vacation {
  display: flex;
  align-items: center;
  /* Вся группа (дарума+текст+числа+кнопка "даты") — единым пакетом по
     центру карточки; раньше это тоже стояло, но потерялось при опыте
     с центровкой текста отдельно (см. историю правки ниже) — вернули,
     подтверждено пользователем по скриншоту как верное поведение. */
  justify-content: center;
  gap: 12px;
  width: 100%;
  padding: 12px;
  background: var(--paper-sunk);
  border: 1px solid var(--paper-line);
  border-radius: var(--r-lg);
  flex-wrap: wrap;
}
.week-vacation__main {
  /* Была попытка (2026-07-27) центрировать текст "Дорума в шляпе"/"через:"
     ОТДЕЛЬНО по центру всей карточки через grid auto/1fr/auto — не
     сработала: числовой блок "154"/время (~440px) настолько шире дарумы
     (~50px), что геометрический центр карточки физически лежит ВНУТРИ
     цифр, и текст туда не поставить, не наехав на них (см. обсуждение с
     пользователем). Вернули простую строку-flex, вся пачка целиком
     центрируется через .week-vacation justify-content:center выше. */
  display: flex;
  /* Было flex-end (низ по низу, как в ноде 175:602) — но в ноде текстовый
     блок почти той же высоты, что и дарума (~57px против 60px), так что
     разница незаметна. У нас после сжатия шрифта/line-height/gap текстовый
     блок стал заметно короче дарумы (~52px против 72px) — с flex-end это
     давало заметную пустоту сверху и текст "прижимался" к низу. center
     убирает эффект независимо от разницы высот. */
  align-items: center;
  gap: 28px;
  /* Перенос и сжатие — БЕЗ медиа-запроса, намеренно. Ряд держал 660px
     (flex-wrap:nowrap + min-width:auto у flex-элемента = "не уже своего
     содержимого") и торчал за край в полосе 861-939px: ниже 861 "Неделя"
     сворачивается в колонку и ширины хватает, выше ~940 эти 660px и так
     влезают, а между - нет. Это не мобильная правка, поэтому и не в
     мобильном блоке. На широких экранах правило ничего не меняет: когда
     контент помещается, wrap не срабатывает (проверено на 1440/1200/1024). */
  flex-wrap: wrap;
  min-width: 0;
}
.week-vacation__daruma {
  flex-shrink: 0;
  /* 50×60 в ноде (×1.2→60×72) — крупнее стандартного avatar-тира дарумы
     (40×48, см. daruma.css). Своего размерного класса под 60×72 в
     daruma.css нет, переопределяем width/height точечно здесь, не заводя
     новый тир ради одного места использования. */
  width: 60px;
  height: 72px;
  /* .daruma--avatar по умолчанию гасит очки отпускной дарумы (--d-v-glass:
     none, общее правило для аватарного тира) — но по Figma-ноде 20:6
     именно у ЭТОЙ дарумы очки — часть образа, не лишняя деталь. Включаем
     их обратно точечно для этого места, не трогая общее правило тира
     (оно больше нигде не используется на символе с очками). */
  --d-v-glass: inline;
  /* Отступ до текста — на 1/3 меньше общего gap .week-vacation__main (28px),
     не трогая соседний gap текст↔блоки (тот же 28px общий gap на всех трёх
     детей): margin частично компенсирует свой gap, только справа от даруму. */
  margin-right: calc(-28px / 3);
}
.week-vacation__text {
  display: flex;
  flex-direction: column;
  /* Центрирует "Дорума в шляпе" и "через:" друг относительно друга (у них
     чуть разная ширина по факту рендера, см. .week-vacation__title). */
  align-items: center;
  /* Расстояние между строк — ещё вдвое меньше (было 1px). */
  gap: 0.5px;
}
.week-vacation__title {
  margin: 0;
  /* footnote (14px), не bodylg — при 17px текст заголовка ("Дорума в
     шляпе") заметно шире слова "через:" под ним (139px против 113px по
     факту рендера); на этом размере ширины визуально совпадают. */
  font-size: var(--f-size-footnote);
  font-weight: var(--f-weight-bold);
  color: var(--ink-base);
  white-space: nowrap;
  /* Без этого реальный зазор до "через:" определял не .week-vacation__text
     gap (0.5px), а межстрочный интерлиньяж параграфа (~1.45 глобально) —
     ~6px "воздуха" внутри строки-бокса, который gap не мог перебить. */
  line-height: 1;
}
.week-vacation__label {
  margin: 0;
  font-size: 38px;
  font-weight: var(--f-weight-bold);
  color: var(--ink-base);
  text-transform: uppercase;
  line-height: 1;
}
/* "через:" — на 2pt (≈2.67px) меньше обычного .week-vacation__label
   (38px), не трогая соседний "осталось:" на том же классе. */
.week-vacation__label--soon {
  font-size: 35px;
}
/* Состояние "отпуск не запланирован" (2026-07-27) — блок целиком раньше
   пропадал со страницы в этом случае (реальный баг), теперь всегда виден,
   с приглашением вместо счётчика. 38px заглавный "ЧЕРЕЗ:"-стиль тут
   смотрелся бы криком на обычном предложении — уменьшаем и возвращаем
   обычный регистр, вес/цвет наследуются от .week-vacation__label как есть. */
.week-vacation__label--hint {
  font-size: var(--f-size-h3);
  text-transform: none;
}
.week-vacation__invite {
  margin: 0;
  font-size: var(--f-size-footnote);
  color: var(--ink-secondary);
}
.week-vacation__blocks {
  display: flex;
  align-items: center;
  gap: 6px;
  /* тот же случай, уровнем глубже - см. комментарий у .week-vacation__main */
  flex-wrap: wrap;
  min-width: 0;
}
.week-vacation__block {
  display: flex;
  align-items: center;
  justify-content: center;
  height: 72px;
  /* 8px 14px, не 24px — при 24px "выбрать даты" не помещалась в ту же
     строку, что и счётчик, и переносилась под него; более узкий чип даёт
     ~20px на блок, ровно столько, сколько нужно, чтобы кнопка встала
     в одну строку без переноса. */
  padding: 8px 14px;
  background: var(--chip-cool);
  border: 1px solid var(--paper-line);
  border-radius: var(--r-md);
  font-size: 58px;
  font-weight: var(--f-weight-bold);
  color: var(--ink-base);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}
.week-vacation__unit {
  /* Половина f-size-h3 (18px→9px) + 1pt (≈1.333px). */
  font-size: calc(var(--f-size-h3) / 2 + 1.333px);
  font-weight: var(--f-weight-regular);
  color: var(--ink-base);
  writing-mode: vertical-rl;
  transform: rotate(180deg);
}
.week-vacation__edit {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 2px;
  color: var(--ink-secondary);
  text-decoration: none;
  /* Теперь <button>, не <a> (открывает дровер, а не переходит по ссылке) —
     сбрасываем браузерные дефолты кнопки, чтобы выглядела как раньше. */
  background: none;
  border: none;
  padding: 0;
  font: inherit;
  cursor: pointer;
}
.week-vacation__edit svg {
  /* 40×40 в ноде (×1.2→48×48) — реальная экспортированная иконка
     (шестерёнка+карандаш, download_assets), не самодельный path, как было
     раньше (нарисованный на глаз простой карандаш — нарушение канона
     CLAUDE.md "рисуем иконки из экспорта, не от руки", тут просто не было
     сразу замечено, т.к. это не маскот, на который правило прицельно
     указывает, но относится к любой иконке из макета). */
  width: 48px;
  height: 48px;
}
.week-vacation__edit-label {
  font-size: 10px;
  line-height: 1.2;
  text-align: center;
}

/* Банер идущего отпуска (Figma-нода 19:311, состояние "Неделя · Отпуск",
   2026-07-27) — заменяет собой "Ближайшие"/"Прошедшее и выходные" и
   маленький отсчётный виджет на время отпуска (см. week.html). Контейнер
   визуально идентичен .week-vacation (тот же paper-sunk/paper-line/r-lg/
   12px — сверено с нодой дословно), но не переиспользует её класс:
   внутренняя раскладка другая (текст слева по левому краю, не по центру
   вокруг короткой подписи), общего между ними было бы меньше, чем разного. */
.week-vacation-banner {
  display: flex;
  align-items: center;
  gap: 14px;
  padding: 12px;
  background: var(--paper-sunk);
  border: 1px solid var(--paper-line);
  border-radius: var(--r-lg);
  flex-wrap: wrap;
}
.week-vacation-banner__daruma {
  flex-shrink: 0;
  width: 108px;
  height: 129px;
  /* .daruma--avatar по умолчанию гасит очки (--d-v-glass:none) — тот же
     точечный оверрайд, что у .week-vacation__daruma, по той же причине
     (Figma-нода 20:6/19:312: очки — часть образа этой дарумы). */
  --d-v-glass: inline;
}
.week-vacation-banner__text {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 3px;
  flex: 1 0 0;
  min-width: 220px;
}
.week-vacation-banner__title {
  margin: 0;
  font-size: var(--f-size-footnote);
  font-weight: var(--f-weight-bold);
  color: var(--ink-base);
}
.week-vacation-banner__hint {
  margin: 0;
  font-size: var(--f-size-caption);
  color: var(--ink-secondary);
}
.week-vacation-banner__actions {
  display: flex;
  gap: 6px;
  padding-top: 6px;
}
.week-vacation-banner__actions form {
  display: contents; /* форма — не флекс-элемент сама по себе, кнопка внутри участвует в раскладке наравне со ссылкой */
}

/* Экраны дня/месяца (Figma-ноды 372:855/373:891, 2026-07-26) — детальные,
   навигируемые версии дашборда "Неделя": карман тот же (_week_pocket.html),
   справа вместо компактных превью — полный список без сокращений, с
   легендой цветов по ТИПУ записи (задача/деньги/созвон/встреча/дедлайн
   заказа) — один тип, один цвет везде на этих двух экранах. Карточки самих
   секций (задачи/события/деньги/дедлайны) — обычный .card +
   .section-card__head/.section-chip, тот же паттерн, что на карточке
   заказа, ничего нового не заводили. */
.day-dot {
  display: inline-block;
  width: 10px;
  height: 10px;
  border-radius: 50%;
  flex-shrink: 0;
}
.day-dot--sm {
  width: 6px;
  height: 6px;
}
.day-dot--task {
  background: var(--sticker-sakura);
}
.day-dot--money {
  background: var(--sticker-taro);
}
.day-dot--call {
  background: var(--sticker-soda);
}
.day-dot--meeting {
  background: var(--sticker-mint);
}
.day-dot--deadline {
  background: var(--daruma-400);
}

/* Заголовок дня/месяца — общий паттерн навигации (Figma 372:1237/373:1273):
   слева название+сводка, справа ‹ сегодня/этот месяц ›. */
.day-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  width: 100%;
}
.day-header__title {
  margin: 0;
  font-size: var(--f-size-h2);
  font-weight: var(--f-weight-bold);
  color: var(--ink-base);
}
.day-header__sub {
  margin: 2px 0 0;
  font-size: var(--f-size-footnote);
  color: var(--ink-secondary);
}
.day-header__nav {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-shrink: 0;
}
.day-header__arrow {
  font-size: 20px;
  font-weight: var(--f-weight-bold);
  line-height: 1;
  color: var(--ink-secondary);
  text-decoration: none;
  padding: 2px 3px;
}
.day-header__arrow:hover {
  color: var(--ink-base);
}
.day-header__today {
  font-size: var(--f-size-footnote);
  font-weight: var(--f-weight-medium);
  color: var(--ink-base);
  text-decoration: none;
  background: var(--paper-card);
  border: 1px solid var(--paper-line);
  border-radius: var(--r-md);
  padding: 5px 12px;
  white-space: nowrap;
}
.day-header__today:hover {
  border-color: var(--paper-linestrong);
}

/* Карточка "задачи" на экране дня — чекбокс-квадрат (не умэ — так в макете
   именно этого экрана, умэ остаётся везде в остальном приложении/дровере).
   Точка типа всегда --task (розовая) на этой карточке, см. комментарий
   выше про легенду. */
.day-task-row__form {
  width: 100%;
}
.day-task-row {
  display: flex;
  align-items: center;
  gap: 10px;
  width: 100%;
  padding: 8px 10px;
  background: var(--paper-base);
  border: 1px solid var(--paper-line);
  border-radius: var(--r-md);
  cursor: pointer;
}
.day-task-row__check {
  flex-shrink: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 22px;
  height: 22px;
  border: 1.5px solid var(--paper-linestrong);
  border-radius: 7px;
  background: transparent;
}
.day-task-row__check--done {
  border-color: var(--brand-mint);
  background: var(--brand-mint);
  color: var(--paper-card);
}
.day-task-row__check-icon {
  width: 14px;
  height: 14px;
}
.day-task-row__text {
  flex: 1;
  min-width: 0;
  font-size: var(--f-size-footnote);
  font-weight: var(--f-weight-bold);
  color: var(--ink-base);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.day-task-row__text--done {
  font-weight: var(--f-weight-regular);
  color: var(--ink-secondary);
}
.day-task-row__deadline {
  flex-shrink: 0;
  font-size: var(--f-size-caption);
  font-weight: var(--f-weight-medium);
  color: var(--ink-secondary);
}

/* Карточка "созвоны и встречи" — акцентная полоса слева (цвет = тип,
   call/meeting), время+название+подпись справа. */
.day-event-row {
  display: flex;
  gap: 12px;
  align-items: stretch;
  width: 100%;
  padding: 8px 0;
  border-top: 1px dashed var(--paper-line);
}
.day-event-row:first-child {
  border-top: none;
  padding-top: 0;
}
.day-event-row__time {
  flex-shrink: 0;
  align-self: center;
  margin: 0;
  font-size: var(--f-size-footnote);
  font-weight: var(--f-weight-bold);
  color: var(--ink-base);
}
.day-event-row__bar {
  flex-shrink: 0;
  width: 4px;
  border-radius: 999px;
}
.day-event-row__text {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 1px;
  justify-content: center;
}
.day-event-row__title {
  margin: 0;
  font-size: var(--f-size-footnote);
  font-weight: var(--f-weight-bold);
  color: var(--ink-base);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.day-event-row__subtitle {
  margin: 0;
  font-size: var(--f-size-caption);
  color: var(--ink-secondary);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
/* Карандаш "править" + крестик "удалить" в строке созвона/встречи. Видны
   всегда, а не по hover: на "Неделе" действий у записей нет вообще, экран
   дня — единственное место, где встречу можно поправить, прятать вход
   некуда. */
.day-event-row__actions {
  flex-shrink: 0;
  display: flex;
  align-items: center;
  gap: 2px;
}

/* Карточка "деньги за день" — итог справа сверху, построчно ниже (та же
   геометрия .money-row, что на карточке заказа, но с точкой типа слева
   вместо даты). */
.day-money-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  width: 100%;
}
.day-money-head__label {
  margin: 0;
  font-size: var(--f-size-footnote);
  font-weight: var(--f-weight-bold);
  color: var(--ink-base);
}
.day-money-head__total {
  margin: 0;
  font-size: var(--f-size-body);
  font-weight: var(--f-weight-bold);
  color: var(--semantic-success-text);
}
.day-money-row {
  display: flex;
  align-items: center;
  gap: 8px;
  width: 100%;
}
.day-money-row__label {
  flex: 1;
  min-width: 0;
  margin: 0;
  font-size: var(--f-size-caption);
  color: var(--ink-secondary);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.day-money-row__amount {
  flex-shrink: 0;
  margin: 0;
  font-size: var(--f-size-footnote);
  font-weight: var(--f-weight-bold);
  color: var(--ink-base);
}

/* Карточка "дедлайны заказов" — своего компонента в Figma для 372:855 нет
   (в примере дне дедлайна не было) — собрана по аналогии с .day-event-row,
   красная точка вместо цветной полосы, ссылка на заказ. */
.day-deadline-row {
  display: flex;
  align-items: center;
  gap: 10px;
  width: 100%;
  padding: 8px 0;
  border-top: 1px dashed var(--paper-line);
  text-decoration: none;
}
.day-deadline-row:first-child {
  border-top: none;
  padding-top: 0;
}
.day-deadline-row__title {
  flex: 1;
  min-width: 0;
  margin: 0;
  font-size: var(--f-size-footnote);
  font-weight: var(--f-weight-bold);
  color: var(--ink-base);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.day-empty {
  margin: 0;
  font-size: var(--f-size-caption);
  color: var(--ink-secondary);
}

/* Экран месяца — большая сетка (Figma-нода 373:1281), 7 колонок, мультиточки
   в ячейке (до 5, по DAY_TYPE_ORDER). Ячейки вне месяца — пустые (не тусклая
   цифра, как в карманном календаре: в этой сетке им вообще нечего
   показывать). */
.month-grid {
  width: 100%;
}
.month-grid__weekdays {
  display: flex;
  gap: 6px;
  width: 100%;
  margin-bottom: 6px;
}
.month-grid__weekday {
  flex: 1;
  text-align: center;
  font-size: var(--f-size-caption);
  font-weight: var(--f-weight-medium);
  color: var(--ink-secondary);
}
.month-grid__week {
  display: flex;
  gap: 6px;
  width: 100%;
  margin-bottom: 6px;
}
.month-grid__cell {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 3px;
  height: 53px;
  border-radius: var(--r-md);
  background: var(--paper-base);
  text-decoration: none;
}
.month-grid__cell--weekend {
  background: var(--paper-sunk);
}
.month-grid__cell--today {
  background: var(--service-100);
}
.month-grid__cell__day {
  margin: 0;
  font-size: var(--f-size-footnote);
  font-weight: var(--f-weight-medium);
  color: var(--ink-base);
}
.month-grid__cell--today .month-grid__cell__day {
  font-weight: var(--f-weight-bold);
}
.month-grid__cell__dots {
  display: flex;
  gap: 2px;
  align-items: center;
  height: 6px;
}

/* Легенда — статичная, всегда одни и те же пять пунктов в фиксированном
   порядке (Figma 373:1442). */
.month-legend {
  display: flex;
  flex-wrap: wrap;
  gap: 16px;
  width: 100%;
}
.month-legend__item {
  display: flex;
  align-items: center;
  gap: 6px;
}
.month-legend__label {
  margin: 0;
  font-size: var(--f-size-caption);
  color: var(--ink-secondary);
}

/* Календарь выбора диапазона дат (2026-07-27, "выбрать даты" у отпуска,
   _range_calendar.html + app.js: data-range-calendar) — своего Figma-узла
   нет (см. CLAUDE.md), собран по образцу уже готовой сетки месяца
   (.month-grid* выше), просто ячейки — <button>, не <a> (клик выбирает
   дату, а не переходит по ссылке), поэтому нужен сброс нативных стилей
   кнопки. Единственное место в проекте, где грid месяца считается на
   клиенте (app.js), а не сервером — нужна мгновенная навигация ‹/› без
   похода на сервер, чтобы не терять уже выбранное начало диапазона при
   пролистывании до конца в другом месяце. */
.range-calendar {
  display: flex;
  flex-direction: column;
  gap: 10px;
  width: 100%;
}
.range-calendar__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  width: 100%;
}
.range-calendar__head .day-header__arrow {
  border: none;
  background: none;
  font-family: inherit;
}
.range-calendar__title {
  margin: 0;
  font-size: var(--f-size-bodylg);
  font-weight: var(--f-weight-bold);
  color: var(--ink-base);
}
.range-calendar__weeks {
  display: flex;
  flex-direction: column;
  gap: 6px;
  width: 100%;
}
.range-calendar__day {
  border: none;
  background: transparent;
  font-family: inherit;
  cursor: pointer;
  padding: 0;
}
/* Найдено при проверке одиночного календаря (2026-07-28): .range-calendar__day
   выше той же специфичности, что и .month-grid__cell--today/--weekend, и
   идёт позже в файле — молча гасил и сегодняшний, и выходной фон (баг был
   и у отпуска, просто там его некому было заметить: --in-range/--start/--end
   объявлены ещё позже и потому случайно побеждали). Восстановлено точечно
   для .range-calendar__day, сами .month-grid__cell--* не трогали — они
   разделяются с обычной сеткой месяца (schedule_month.html и т.п.), где
   этой проблемы не было. */
.range-calendar__day.month-grid__cell--weekend {
  background: var(--paper-sunk);
}
.range-calendar__day.month-grid__cell--today {
  background: var(--service-100);
}
.range-calendar__day--outside {
  color: var(--ink-disabled);
}
/* Промежуток между началом и концом — заливка тем же chip/cool, что и у
   блоков обратного отсчёта до отпуска на "Неделе" (week-vacation, Figma
   20:5) — визуально одна и та же "отпускная" тема цвета. Начало/конец —
   акцентом ярче (service/400), чтобы сами границы диапазона читались явно
   отдельно от простого "внутри". */
.range-calendar__day.range-calendar__day--in-range {
  background: var(--chip-cool);
}
.range-calendar__day.range-calendar__day--start,
.range-calendar__day.range-calendar__day--end {
  background: var(--service-400);
  color: var(--ink-base);
  font-weight: var(--f-weight-bold);
}
.range-calendar__summary {
  margin: 0;
  font-size: var(--f-size-footnote);
  color: var(--ink-secondary);
}

/* Поле выбора ОДНОЙ даты (_macros.html:single_date_field, Figma-нода
   413:1421) — попап вместо системного <input type="date">, тот же
   position:fixed-приём, что у .select-custom__menu (координаты — в app.js,
   от кнопки-контрола при каждом открытии). Свой набор классов, не
   .range-calendar/.month-grid__cell — тем нужны 53px-строки полноразмерного
   месяца/страницы отпуска, здесь — компактный попап 34px×1.2≈41px строки,
   252px×1.2≈302px фиксированная ширина (не подгоняется под поле, в отличие
   от select-custom — у даты нет смысла тянуться на ширину произвольного
   поля). */
.single-date-field {
  position: relative;
  width: 100%;
}
.single-date-field__row {
  display: flex;
  align-items: center;
  gap: 6px;
  width: 100%;
}
.single-date-field__row .single-date-field__control {
  flex: 1;
  min-width: 0;
}
.single-date-field__clear {
  flex-shrink: 0;
}
/* Контрол — <input type="text">, а не кнопка: дату можно вписать руками.
   cursor остаётся текстовым (по умолчанию), а не pointer: поле, в которое
   печатают, не должно притворяться кнопкой. */
.single-date-field__control {
  width: 100%;
  text-align: left;
  font-family: var(--f-family-body);
}
.single-date-field__control--open {
  border-color: var(--service-600);
  border-width: 1.5px;
}
.single-date-field__control--error {
  border-color: var(--daruma-400);
}
.single-date-field__popover {
  position: fixed;
  z-index: 50;
  width: 302px;
  flex-direction: column;
  gap: 7px;
  padding: 14px;
  background: var(--paper-card);
  border: 1px solid var(--paper-line);
  border-radius: var(--r-lg);
  box-shadow: 0 8px 24px rgba(36, 38, 43, 0.16);
}
/* display:flex вынесен в :not([hidden]) — та же ловушка, что и у
   .select-custom__menu (см. выше): авторское display:flex в базовом
   правиле старше браузерного [hidden]{display:none} независимо от
   специфичности, попап оставался бы видимым куском без этого. */
.single-date-field__popover:not([hidden]) {
  display: flex;
}
.single-date-field__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  width: 100%;
}
.single-date-field__head .day-header__arrow {
  border: none;
  background: none;
  font-family: inherit;
}
.single-date-field__title {
  margin: 0;
  font-size: var(--f-size-bodylg);
  font-weight: var(--f-weight-bold);
  color: var(--ink-base);
}
.single-date-field__weekdays {
  display: flex;
  gap: 2px;
  width: 100%;
}
.single-date-field__weekday {
  flex: 1;
  margin: 0;
  text-align: center;
  font-size: var(--f-size-caption);
  font-weight: var(--f-weight-semibold);
  color: var(--ink-secondary);
}
.single-date-field__weeks {
  display: flex;
  flex-direction: column;
  gap: 2px;
  width: 100%;
}
.single-date-field__week {
  display: flex;
  gap: 2px;
  width: 100%;
}
.single-date-field__day {
  flex: 1;
  height: 41px;
  display: flex;
  align-items: center;
  justify-content: center;
  border: none;
  background: transparent;
  cursor: pointer;
  font-family: inherit;
  font-size: var(--f-size-footnote);
  color: var(--ink-base);
  border-radius: var(--r-sm);
}
.single-date-field__day--weekend {
  background: var(--paper-sunk);
}
.single-date-field__day--today {
  background: var(--service-100);
}
.single-date-field__day--outside {
  color: var(--ink-disabled);
}
.single-date-field__day--selected {
  background: var(--service-400);
  color: var(--ink-base);
  font-weight: var(--f-weight-semibold);
  border-radius: var(--r-pill);
}

/* Карточка-обёртка страницы отпуска (vacation_form.html/vacation_detail.html) —
   не на всю ширину контента, календарь выбора дат не нуждается в такой
   ширине. */
.vacation-card {
  max-width: 460px;
}

/* ==========================================================================
   Дашборд (главная, Figma-нода 35:2, 2026-07-27) — витрина всех данных
   системы. Три колонки той же пропорции, что и .order-body-grid ("стабильная"
   234 / "подвижная" 335 / "полуподвижная" 329 — сырые figma-ширины как fr,
   не жёсткие px, тот же приём). Много блоков переиспользуют уже готовые
   компоненты (.npd-limit-bar, .money-row, .subscription-dot,
   .subscription-leak, .goal-scale/.goal-pace, .avatar-tile, .sticker--*,
   .section-chip) — новые классы ниже только там, где готового не было.
   ========================================================================== */
.dashboard-grid {
  display: grid;
  grid-template-columns: 234fr 335fr 329fr;
  gap: 11px;
  align-items: start;
}
/* Сумма/статус справа равняется по ПЕРВОЙ строке левого блока, а не по центру
   всего блока: в узких колонках дашборда левая часть нередко становится
   двухстрочной (бейдж «вычет N ₽» под подписью, длинное название подписки со
   статусом), и при center сумма повисала между строк — напротив пустоты.
   baseline берёт ПЕРВУЮ базовую линию левого блока, то есть ровно первую
   строку; текст слева и справа тут одного кегля, поэтому у одностроч­ных
   строк ничего не меняется — center и baseline для них совпадают (проверено
   замером: смещение 0.6px до и после).

   Скоуп на дашборд намеренно: .money-row живёт ещё на карточке заказа, /me,
   карте и дне расписания — там свои строки (кнопки и бейджи справа), и общий
   класс без спроса не трогаем (правило CLAUDE.md). */
.dashboard-grid .money-row {
  align-items: baseline;
}
.dashboard-col {
  display: flex;
  flex-direction: column;
  gap: 11px;
  min-width: 0;
}
@media (max-width: 960px) {
  .dashboard-grid {
    display: block;
  }
  .dashboard-col > * {
    margin-bottom: 11px;
  }
}
/* "работа"/"расписание" (Figma-ноды 37:2/35:30) — карточка сама gap-[8px]
   в Figma (везде между чипом-шапкой и строками, и между строками), а
   .dash-work-row/.dash-meeting-slot — просто голые строки без своего
   отступа (в отличие от .money-row, у которой уже есть верт. паддинг +
   пунктир). Без этого класса соседние строки касались вплотную (проверено
   getBoundingClientRect — зазор был ровно 0px). */
.dash-card-gap {
  /* Тот же зазор между строками, что и у .money-row (6px снизу + 6px
     сверху): здесь у строк своего паддинга нет, поэтому весь зазор даёт
     gap контейнера. */
  --card-row-gap: calc(var(--space-row) * 2);
  display: flex;
  flex-direction: column;
  gap: var(--card-row-gap);
}
/* У flex-карточки к padding-bottom шапки прибавляется ещё и gap контейнера
   (шапка — такой же flex-ребёнок, как строки), из-за чего "работа" и
   "расписание · неделя" отъезжали на 23px против нормы 13. Вычитаем gap
   обратно, чтобы итог совпал с --space-head-body — вместе с ним и
   поедет, если норму когда-нибудь поменяют. */
.dash-card-gap > .section-card__head {
  padding-bottom: 0;
  margin-bottom: calc(var(--space-head-body) - var(--card-row-gap));
}
/* "Мой налог"/"деньги · труба"/"деньги · расходы" (Figma-ноды 38:3/198:557/
   323:866) — ни у одной "Стадии" в ноде нет border-top ни у одной из строк
   (не только у первой): весь список подряд — просто gap, без пунктиров.
   .money-row в остальных местах (список подписок на /me и т.п.) пунктир
   сохраняет — здесь только для этих трёх карточек убираем его точечно. */
.dash-money-flat .money-row {
  border-top: none;
}
/* Карточки СО ШКАЛОЙ ("деньги · труба"/"деньги · расходы") — единый
   стандарт блока со шкалой по эталону "Сделано на N%" (см. --space-head-scale
   в :root): шапка→шкала 6px, шкала→первая строка — тот же --space-head-body,
   что и шапка→строка в обычных блоках. "Мой налог" лежит в этом же
   .dash-money-flat, но шкалы у него нет — он идёт по обычной норме. */
.dash-money-flat:has(.dash-pipe) .section-card__head {
  padding-bottom: var(--space-head-scale);
}
/* Своего верхнего отступа шкала не даёт (его целиком задала шапка), а снизу
   добирает до --space-head-body вместе с padding-top первой строки. */
.dash-money-flat .dash-pipe {
  margin: 0 0 calc(var(--space-head-body) - var(--space-row));
}

/* "Мой налог · до N числа" — зелёная плашка-заголовок вместо обычной
   оранжевой .section-chip (Figma-нода 38:4/38:5). */
.section-chip--success {
  background: var(--semantic-success-bg);
}

/* "N висит · Дорума грустит" (Figma-нода 43:2) — акцентная левая полоса
   (border-left-3), не полная рамка, тот же приём, что у .subscription-leak
   и .notice-box. Три строки-долга ниже — свой формат (2 строки текста слева
   + сумма справа), не .money-row (у той текст в одну строку). */
.dash-debts {
  display: flex;
  flex-direction: column;
  gap: 8px;
  width: 100%;
  padding: 11px 13px;
  background: var(--semantic-danger-bg);
  border-left: 3px solid var(--semantic-danger-border);
  border-radius: var(--r-lg);
}
.dash-debts__head {
  display: flex;
  align-items: center;
  gap: 12px;
  width: 100%;
}
.dash-debts__daruma {
  flex-shrink: 0;
  width: 36px;
  height: 43px;
}
.dash-debts__text {
  flex: 1;
  min-width: 0;
}
.dash-debts__title {
  margin: 0;
  font-size: var(--f-size-bodylg);
  font-weight: var(--f-weight-bold);
  color: var(--semantic-danger-text);
}
.dash-debts__sub {
  margin: 1px 0 0;
  font-size: var(--f-size-caption);
  color: var(--ink-secondary);
}
/* «81 день» — одним куском: число и единица не расходятся по строкам,
   сколько бы ни сузилась колонка (см. комментарий в dashboard.html). */
.dash-debts__age {
  white-space: nowrap;
}
/* Слева тут ВСЕГДА две строки и больше (подпись + расшифровка, которая ещё и
   переносится), справа — одна сумма. Она равняется по ПЕРВОЙ строке подписи:
   первая строка главная, по ней и читается сумма. При center она вставала
   между строк — напротив пустоты (правка 14.08). */
.dash-debt-row {
  display: flex;
  align-items: flex-start;
  gap: 8px;
  width: 100%;
  padding-top: 6px;
  border-top: 1px dashed var(--semantic-danger-border);
}
.dash-debt-row .dash-stage-dot {
  flex-shrink: 0;
  /* Точка — по центру первой строки, а не по верху блока: высота строки
     подписи минус сам кружок, пополам. 1.45 — базовый интерлиньяж body
     (см. правило body в начале файла), 7px — размер точки из .dash-stage-dot. */
  margin-top: calc((var(--f-size-footnote) * 1.45 - 7px) / 2);
}
.dash-debt-row__body {
  flex: 1;
  min-width: 0;
}
.dash-debt-row__label {
  margin: 0;
  font-size: var(--f-size-footnote);
  font-weight: var(--f-weight-bold);
  color: var(--ink-base);
}
.dash-debt-row__detail {
  margin: 0;
  font-size: var(--f-size-caption);
  color: var(--ink-secondary);
}
/* Строка-ссылка (когда долг в категории ровно один) — цвет и подчёркивание
   гасим, чтобы она выглядела ровно как обычная строка; кликабельность видно
   по курсору и подсветке на наведении. Тот же приём, что у .dash-work-row. */
.dash-debt-row--link {
  color: inherit;
  text-decoration: none;
  cursor: pointer;
}
.dash-debt-row--link:hover,
.dash-debt-row--link:hover .dash-debt-row__label,
.dash-debt-row--link:hover .dash-debt-row__amount {
  color: var(--semantic-danger-text);
}
/* Мини-список объектов под строкой-итогом (когда долгов в категории
   несколько). Развёрнут всегда — раскрытие по клику тут было бы лишним
   действием. Отступ слева равен точке + gap строки выше (7 + 8), чтобы
   список встал под текстом, а не под маркером. */
.dash-debt-list {
  display: flex;
  flex-direction: column;
  gap: 2px;
  width: 100%;
  padding-left: 15px;
}
.dash-debt-list__row {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 8px;
  color: var(--ink-secondary);
  text-decoration: none;
  font-size: var(--f-size-caption);
}
.dash-debt-list__row:hover {
  color: var(--semantic-danger-text);
  text-decoration: none;
}
.dash-debt-list__label {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.dash-debt-list__days {
  flex-shrink: 0;
}
.dash-debt-row__amount {
  flex-shrink: 0;
  font-size: var(--f-size-bodylg);
  font-weight: var(--f-weight-bold);
  color: var(--ink-base);
  /* Свой margin у <p> тут не был сброшен (в отличие от __label/__detail), и
     дефолтные 1em сверху/снизу СЛУЧАЙНО совпали с высотой двухстрочного тела —
     из-за этого центрирование выглядело осмысленным, хотя держалось на
     совпадении. Сброс обязателен, иначе сумму не прижать к первой строке. */
  margin: 0;
  /* Строка суммы приравнена к строке ПОДПИСИ (её кегль × базовый интерлиньяж
     body), а не к собственной: сумма крупнее (bodylg), её родная строка выше,
     и по ней сумма центровалась бы ниже подписи. Габарит строки от этого не
     растёт — высоту задаёт двухстрочное тело слева. */
  line-height: calc(var(--f-size-footnote) * 1.45);
}

/* "деньги · труба" / "деньги · расходы" (Figma-ноды 198:557/323:866) —
   узкая многосегментная шкала, каждый сегмент — своя доля денег. Строки
   расшифровки под ней — обычный .money-row (уже есть), просто с точкой
   нужного цвета вместо чего-то нового. */
.dash-pipe {
  display: flex;
  gap: 2px;
  width: 100%;
  height: 9px;
  /* Трек, как у эталона "Сделано на N%". Без него скруглённый левый край
     пропадал, когда первый сегмент нулевой ширины ("деньги · расходы"):
     заливка начиналась с 2px (gap), то есть уже за скруглением, и угол
     читался прямым. */
  background: var(--paper-sunk);
  border-radius: var(--r-pill);
  overflow: hidden;
}
.dash-pipe__segment {
  height: 100%;
}
.dash-pipe__segment--received { background: var(--sticker-mint); }
.dash-pipe__segment--uncovered { background: var(--chip-day); }
.dash-pipe__segment--awaiting { background: var(--semantic-warning-bg); }
.dash-pipe__segment--overdue { background: var(--semantic-danger-bg); }
.dash-pipe__segment--subscriptions { background: var(--sticker-taro); }
.dash-pipe__segment--personal { background: var(--chip-off); }
.dash-stage-dot {
  display: inline-block;
  width: 7px;
  height: 7px;
  border-radius: 50%;
  flex-shrink: 0;
}
.dash-stage-dot--received { background: var(--sticker-mint); }
.dash-stage-dot--receipt { background: var(--chip-cool); }
.dash-stage-dot--cash { background: var(--sticker-taro); }
.dash-stage-dot--awaiting { background: var(--semantic-warning-border); }
.dash-stage-dot--overdue { background: var(--semantic-danger-border); }
.dash-stage-dot--subscriptions { background: var(--sticker-taro); }
.dash-stage-dot--personal { background: var(--chip-off); }

/* "Работа" (Figma-нода 37:2) — топ-заказов, зверь-аватар + мини-шкала
   прогресса (сама шкала — sticker--* ротация, та же, что и на /orders,
   см. app/orders_logic.py:progress_color). */
.dash-work-row {
  display: flex;
  align-items: center;
  gap: 8px;
  width: 100%;
}
.dash-work-row__body {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 3px;
}
.dash-work-row__head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 8px;
  width: 100%;
}
.dash-work-row__title {
  margin: 0;
  font-size: var(--f-size-footnote);
  font-weight: var(--f-weight-bold);
  color: var(--ink-base);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.dash-work-row__percent {
  margin: 0;
  flex-shrink: 0;
  font-size: var(--f-size-footnote);
  font-weight: var(--f-weight-bold);
  color: var(--ink-base);
}
.dash-work-row__track {
  width: 100%;
  height: 7px;
  background: var(--paper-sunk);
  border-radius: var(--r-pill);
  overflow: hidden;
}
.dash-work-row__fill {
  height: 100%;
  border-radius: var(--r-pill);
}

/* "расписание · неделя" плоским списком (Figma-нода 35:30) — 2 ближайшие
   встречи целиком (акцентная жёлтая полоса у самой ближней), "дальше" —
   построчно по дням. Не путать со старыми .week-meeting* (те — по
   дням-корзинам на "Неделе"), это отдельная, более компактная вёрстка. */
.dash-meeting-slot {
  display: flex;
  flex-direction: column;
  gap: 1px;
  width: 100%;
  padding-left: 8px;
  border-left: 2px solid var(--paper-line);
  text-decoration: none;
}
.dash-meeting-slot--accent {
  border-left-color: var(--service-400);
}
.dash-meeting-slot__when {
  margin: 0;
  font-size: var(--f-size-caption);
  color: var(--ink-secondary);
}
.dash-meeting-slot__title {
  margin: 0;
  font-size: var(--f-size-bodylg);
  font-weight: var(--f-weight-bold);
  color: var(--ink-base);
}
.dash-meeting-slot__sub {
  margin: 0;
  font-size: var(--f-size-caption);
  color: var(--ink-secondary);
}
.dash-then {
  display: flex;
  flex-direction: column;
  gap: 2px;
  width: 100%;
  padding-top: 7px;
  border-top: 1px dashed var(--paper-line);
}
.dash-then__row {
  margin: 0;
  font-size: var(--f-size-footnote);
  color: var(--ink-base);
}
.dash-then__day {
  font-weight: var(--f-weight-bold);
}

/* "цель · Япония" (Figma-нода 37:17) — фото + дни до даты + сумма-цель +
   шкала/темп (шкала и блок "Темп" переиспользуют .goal-scale/.goal-pace,
   уже готовые на /me — см. _macros.html:goal_card; здесь своя обёртка,
   потому что на дашборде карточка ещё показывает фото, которого в
   goal_card нет — там оно только в дровере правки). */
.dash-goal-photo {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 100%;
  /* 4:3 — канон пропорции фото цели, см. .goal-photo-preview (превью в
     дровере повторяет ЭТУ форму). Было height: 160px при тянущейся ширине —
     фактическая пропорция зависела от ширины окна. */
  aspect-ratio: 4 / 3;
  margin-bottom: 8px;
  background: var(--paper-sunk);
  border-radius: var(--r-md);
  overflow: hidden;
}
.dash-goal-photo img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}
.dash-goal-photo__placeholder {
  margin: 0;
  font-size: var(--f-size-caption);
  color: var(--ink-secondary);
}
.dash-goal-days {
  display: flex;
  align-items: baseline;
  gap: 5px;
  margin-bottom: 8px;
}
.dash-goal-days__value {
  margin: 0;
  font-size: var(--f-size-h1);
  font-weight: var(--f-weight-bold);
  color: var(--ink-base);
}
.dash-goal-days__label {
  margin: 0;
  font-size: var(--f-size-caption);
  color: var(--ink-secondary);
}
/* Две суммы («набрано X · осталось Y») одной строкой в узкой колонке. Кегль
   на ступень ниже прежнего h3 (там была ОДНА сумма на всю ширину): при 18px
   пара семизначных сумм с копейками уже не помещалась в колонку и рвалась на
   две строки, при 16px остаётся ~25px запаса даже в этом худшем случае —
   мерено на живой колонке. Подписи и точка тише цифр, читаются деньги. */
.dash-goal-amount {
  margin: 0 0 8px;
  font-size: var(--f-size-body);
  font-weight: var(--f-weight-bold);
  color: var(--ink-base);
}
/* Пара «подпись + сумма» неразрывна. Без этого перенос мог случиться ВНУТРИ
   числа: фильтр rub отбивает тысячи обычным пробелом, и «12 396 ₽» на узкой
   колонке распадалось на «12» и «396 ₽». Разрыв возможен только по « · »
   между парами — на совсем узком экране строка честно станет двумя. */
.dash-goal-amount__pair {
  white-space: nowrap;
}
.dash-goal-amount__label {
  margin: 0;
  font-size: var(--f-size-caption);
  font-weight: var(--f-weight-regular);
  color: var(--ink-secondary);
}
.dash-goal-amount__sep {
  color: var(--ink-secondary);
  font-weight: var(--f-weight-regular);
}

/* Общая страница ошибки (HTTPException/500, app/main.py) — вместо голого
   JSON/трейсбека. Нет своей Figma-ноды (инфраструктурная страница, не часть
   макета) — минимальный набор классов, переиспользует .card/.btn--primary/
   токены, не пиксель-в-пиксель ничему конкретному. */
.error-page {
  min-height: calc(100vh - 200px);
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 12px;
  text-align: center;
  padding: 32px 16px;
}
.error-page__code {
  margin: 0;
  font-size: var(--f-size-caption);
  font-weight: var(--f-weight-bold);
  color: var(--ink-secondary);
  text-transform: uppercase;
  letter-spacing: 0.04em;
}
.error-page__title {
  margin: 0;
  font-size: var(--f-size-h2);
  font-weight: var(--f-weight-bold);
  color: var(--ink-base);
}
.error-page__message {
  margin: 0;
  max-width: 420px;
  font-size: var(--f-size-body);
  color: var(--ink-secondary);
}

/* ============================================================
   Экран подписки — /subscriptions/{id} (Figma-нода 458:1001)
   Двухколоночный раздел: слева переключалка по подпискам, справа
   управление выбранной.
   ============================================================ */

body.subscriptions-page {
  background: var(--paper-base);
}

.subs-head {
  display: flex;
  align-items: center;
  gap: 24px;
  margin-bottom: 11px;
}
.subs-head__title {
  margin: 0;
  font-size: var(--f-size-h2);
  font-weight: var(--f-weight-bold);
  color: var(--ink-base);
}
.subs-head__spacer {
  flex: 1;
}

/* Фильтр "Активные · N / Архив · N" рядом с заголовком. Таб — не .pill:
   тот крупнее и несёт статус сущности, а это переключалка списка. */
.subs-tabs {
  display: flex;
  gap: 2px;
}
.subs-tab {
  padding: 6px 13px;
  border-radius: var(--r-md);
  font-size: var(--f-size-caption);
  font-weight: var(--f-weight-semibold);
  color: var(--ink-secondary);
  white-space: nowrap;
}
.subs-tab--active {
  background: var(--chip-today);
  font-weight: var(--f-weight-bold);
  color: var(--ink-base);
}
/* Архивный таб до сборки экрана архива только показывает счётчик. */
.subs-tab--disabled {
  cursor: default;
}

.subs-body {
  display: flex;
  align-items: flex-start;
  gap: 14px;
}

/* Левая колонка — список подписок. Ритм свой (padding 14, gap 5 между
   строками), как у .customers-col на /orders: это список-навигация из
   строк-карточек со своим фоном, а не блок с шапкой-чипом и info-строками,
   на который рассчитана общая норма отступов 14/13/6. */
.subs-col {
  flex-shrink: 0;
  width: 336px;
  display: flex;
  flex-direction: column;
  gap: 5px;
  padding: 14px;
  background: var(--paper-card);
  border: 1px solid var(--paper-line);
  border-radius: var(--r-lg);
}
.subs-col__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
}
.subs-col__title {
  margin: 0;
  font-size: var(--f-size-footnote);
  font-weight: var(--f-weight-bold);
  color: var(--ink-base);
}

.sub-nav-row {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 10px;
  border-radius: var(--r-md);
  text-decoration: none;
  color: inherit;
}
/* text-decoration тут не лишний: базовый a{text-decoration:none} — просто
   класс (0,1,0), а браузерный a:hover{underline} — тип+псевдокласс (0,1,1)
   и перебивает его именно на наведении. Строка списка — карточка, а не
   ссылка в тексте, подчёркиваться не должна (та же правка, что делали для
   строки карты). Модификатор --selected ниже гасит только фон, поэтому
   правило нужно и здесь. */
.sub-nav-row:hover {
  background: var(--paper-sunk);
  text-decoration: none;
}
.sub-nav-row--selected,
.sub-nav-row--selected:hover {
  background: var(--chip-today);
}
.sub-nav-row__dot {
  width: 8px;
  height: 8px;
  border-radius: var(--r-pill);
  flex-shrink: 0;
}
/* Три состояния активной подписки. Сиреневый (--sticker-taro) сюда не
   входит — он закреплён за архивом (Figma-нода 459:1094). */
.sub-nav-row__dot--alarm { background: var(--daruma-400); }
.sub-nav-row__dot--soon  { background: var(--sticker-peach); }
.sub-nav-row__dot--calm  { background: var(--sticker-mint); }
.sub-nav-row__info {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 1px;
}
.sub-nav-row__name {
  font-size: var(--f-size-footnote);
  font-weight: var(--f-weight-bold);
  color: var(--ink-base);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.sub-nav-row__status {
  font-size: var(--f-size-caption);
  color: var(--ink-secondary);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.sub-nav-row__amount {
  flex-shrink: 0;
  font-size: var(--f-size-caption);
  font-weight: var(--f-weight-bold);
  color: var(--ink-base);
  white-space: nowrap;
}

/* Утечка в узкой колонке (Figma-нода 458:1052): рамка целиком и тёплая
   палитра warning, без кнопки "Отписаться" — в 336px она не помещается,
   а ссылка на отписку лежит рядом, в блоке "Отписка" выбранной подписки.
   Базовый .subscription-leak (акцентная полоса слева, danger, с кнопкой)
   остаётся как есть — он живёт на /me и на дашборде. */
.subscription-leak--compact {
  margin-bottom: 0;
  padding: 10px 12px;
  background: var(--semantic-warning-bg);
  border: 1px solid var(--semantic-warning-border);
  border-left: 1px solid var(--semantic-warning-border);
  border-radius: var(--r-md);
}
.subscription-leak--compact .subscription-leak__title {
  font-size: var(--f-size-caption);
  color: var(--semantic-warning-text);
}
.subscription-leak--compact .subscription-leak__sub {
  font-size: var(--f-size-caption);
  color: var(--semantic-warning-text);
}

.subs-main {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 17px;
}

@media (max-width: 900px) {
  .subs-body {
    display: block;
  }
  .subs-col {
    width: auto;
    margin-bottom: 14px;
  }
}

/* --- Правая колонка экрана подписки (Figma-ноды 351:899 / 464:1116 /
   458:1116 / 458:1140). Карточки — общий .card; внутренний ритм по норме
   проекта (14 сверху, 13 шапка->тело), а не по Figma-16. --- */
.sub-card {
  padding: var(--space-card-top) 14px 14px;
}
.sub-card__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  margin-bottom: var(--space-head-body);
}
.sub-card__title {
  margin: 0;
  font-size: var(--f-size-h3);
  font-weight: var(--f-weight-bold);
  color: var(--ink-base);
}
.sub-card__subtitle {
  margin: 14px 0 var(--space-head-body);
  font-size: var(--f-size-footnote);
  font-weight: var(--f-weight-bold);
  color: var(--ink-base);
}
/* Первым в карточке подзаголовок стоит сразу под её верхним краем —
   отбивка сверху нужна только когда над ним другая группа полей. */
.sub-card > .sub-card__subtitle:first-child {
  margin-top: 0;
}
/* Подпись группы полей ("чтобы не проспать") — мельче и тише заголовка
   карточки: это не отдельный блок, а ярлык над парой полей. */
.sub-group__label {
  margin: 0 0 8px;
  font-size: var(--f-size-caption);
  font-weight: var(--f-weight-bold);
  color: var(--ink-secondary);
}
.sub-fields {
  display: flex;
  flex-direction: column;
  gap: 14px;
}
/* Ряд полей. Пропорции — из Figma как относительные доли (flex-grow), а не
   жёсткие px: контейнер шире/уже макета, фиксированные ширины не влезли бы
   (тот же приём, что у трёх колонок карточки заказа). */
.sub-field-row {
  display: flex;
  align-items: flex-start;
  gap: 10px;
}
.sub-field-row > * {
  flex: 1;
  min-width: 0;
}
.sub-field--amount   { flex: 126; }
.sub-field--currency { flex: 151; }
.sub-field--card     { flex: 299; }
.sub-field-row--when {
  align-items: flex-start;
}
.sub-when-dates {
  flex: 354;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 8px;
}
.sub-when-period {
  flex: 230;
  min-width: 0;
}

/* Отметка месяца — своя форма внутри карточки "чтобы не проспать". */
.usage-form {
  display: flex;
  align-items: center;
  gap: 7px;
  margin-bottom: var(--space-row);
}
.usage-form .field-group {
  flex: 1;
  min-width: 0;
}
/* Поле месяца у́же соседнего статуса — оно фиксированной длины ("Сентябрь
   2026"), а статус тянется. */
.usage-form .field-group:first-child {
  flex: 0 0 187px;
}
/* В макете (нода 458:1110) у "Отметить" мягкая жёлтая заливка, а не белый
   фон обычной контурной кнопки — рядом стоят два поля, и белая на белом
   терялась бы. Правка точечная, глобальный .btn--secondary не трогаем. */
.usage-form .btn--secondary {
  flex-shrink: 0;
  background: var(--chip-today);
}
.usage-row {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: var(--space-row) 0;
}
.usage-row__month {
  font-size: var(--f-size-caption);
  color: var(--ink-secondary);
}
.usage-chip {
  padding: 2px 10px;
  border-radius: var(--r-pill);
  font-size: var(--f-size-caption);
  font-weight: var(--f-weight-semibold);
}
.usage-chip--used {
  background: var(--semantic-success-text);
  color: var(--paper-card);
}
/* "Не пользовалась" в макете не нарисована — нейтральный серый, чтобы не
   читалась как ошибка: не пользоваться месяц это факт, а не проблема
   (проблема — стрик, у неё свой красный алерт в левой колонке). */
.usage-chip--idle {
  background: var(--paper-sunk);
  color: var(--ink-secondary);
}

/* Отписка: три поля в ряд + кнопка, выровненная по низу полей. */
.sub-cancel-row {
  display: flex;
  align-items: flex-end;
  gap: 10px;
  margin-bottom: 14px;
}
.sub-cancel-row > .field-group {
  flex: 1;
  min-width: 0;
}
.sub-cancel-row > .btn--primary {
  flex-shrink: 0;
}
.sub-link-row {
  display: flex;
  align-items: center;
  gap: 10px;
}
.sub-link-row .field {
  flex: 1;
  min-width: 0;
}
.sub-link-row .btn--primary {
  flex-shrink: 0;
}

/* Управление: две равные кнопки в ряд + пояснение под ними. */
.sub-manage-row {
  display: flex;
  gap: 10px;
  margin-bottom: 10px;
}
.sub-manage-btn {
  flex: 1;
  min-width: 0;
}
.sub-manage-note {
  margin: 0;
  font-size: var(--f-size-caption);
  color: var(--ink-muted);
}

/* --- Архивный вид экрана подписки (Figma-нода 459:1038) --- */

/* Подзаголовок под шапкой левой колонки ("история и стрик сохранены"):
   отбивку до первой строки даёт он, поэтому свой margin-bottom не нужен —
   зазор берётся из общего gap колонки. */
.subs-col__note {
  margin: 0;
  font-size: var(--f-size-caption);
  color: var(--ink-muted);
}
/* Архивная строка целиком приглушена: и название, и подпись. Активная
   строка называет подписку основным цветом — тут это лишний вес. */
.sub-nav-row--archived .sub-nav-row__name {
  color: var(--ink-secondary);
}
.sub-nav-row--archived .sub-nav-row__status {
  color: var(--ink-muted);
}
.sub-nav-row__dot--archived {
  background: var(--sticker-taro);
}

.sub-archive-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
}
.sub-archive-head__text {
  flex: 1;
  min-width: 0;
}
.sub-archive-head__meta {
  margin: 2px 0 0;
  font-size: var(--f-size-caption);
  color: var(--ink-secondary);
}
/* "в архиве" — не .pill (та крупная, для шапки заказа) и не .mini-badge
   (тот цветной, несёт статус-сигнал): здесь нейтральная отметка режима. */
.sub-archive-chip {
  flex-shrink: 0;
  padding: 3px 12px;
  border-radius: var(--r-pill);
  background: var(--paper-sunk);
  font-size: var(--f-size-caption);
  font-weight: var(--f-weight-semibold);
  color: var(--ink-secondary);
}
/* Пояснение над кнопками (в отличие от .sub-manage-note, который под ними
   и потому не отбивается сверху). */
.sub-manage-note--lead {
  margin-bottom: 10px;
  color: var(--ink-secondary);
}

/* ТОСТЫ — результат действия, нижний правый угол (Figma-ноды 471:1038
   "Ошибки" и 471:1051 "Положительные", механика — app/flash.py).
   Хардкод-размеры из ноды умножены на 1.2 (паддинг 9x7 -> 11x8, ширина
   320 -> 384), скругление 10px не масштабируется (правило про radius.*),
   шрифты токенами: 11px в ноде = caption (13px), 10px = 12px.
   Не .notice-box: та живёт в потоке карточки и шире по паддингам — тост
   плотнее и всплывает поверх. */
.toasts {
  position: fixed;
  right: 20px;
  bottom: 20px;
  z-index: 80;
  display: flex;
  flex-direction: column-reverse;
  gap: 8px;
  pointer-events: none;
}
.toast {
  display: flex;
  align-items: flex-start;
  gap: 8px;
  width: 384px;
  max-width: calc(100vw - 40px);
  padding: 8px 11px;
  border: 1px solid transparent;
  border-radius: 10px;
  pointer-events: auto;
  opacity: 1;
  transition: opacity var(--m-duration-slow, 240ms) var(--m-easing-out),
              transform var(--m-duration-slow, 240ms) var(--m-easing-out);
}
/* Гаснет не мгновенно: класс вешает JS за 240мс до снятия узла. */
.toast--leaving {
  opacity: 0;
  transform: translateY(6px);
}
.toast__body {
  display: flex;
  flex-direction: column;
  gap: 1px;
  flex: 1;
  min-width: 0;
}
.toast__title {
  margin: 0;
  font-size: var(--f-size-caption);
  font-weight: var(--f-weight-bold);
}
.toast__text {
  margin: 0;
  font-size: 12px;
  font-weight: var(--f-weight-regular);
  overflow-wrap: break-word;
}
.toast__close {
  flex-shrink: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 18px;
  height: 18px;
  padding: 0;
  border: none;
  background: none;
  color: inherit;
  opacity: 0.55;
  cursor: pointer;
}
.toast__close:hover {
  opacity: 1;
}
.toast--danger {
  background: var(--semantic-danger-bg);
  border-color: var(--semantic-danger-border);
}
.toast--danger .toast__title {
  color: var(--semantic-danger-text);
}
.toast--danger .toast__text {
  color: var(--ink-secondary);
}
/* У успеха фон ГОЛУБОЙ (semantic/info/bg), а рамка и заголовок зелёные —
   так в ноде 471:1052, это не описка. */
.toast--success {
  background: var(--semantic-info-bg);
  border-color: var(--semantic-success-border);
}
.toast--success .toast__title {
  color: var(--semantic-success-text);
}
.toast--success .toast__text {
  color: var(--ink-base);
}

/* Напоминание — третий вид тоста, и единственный, который НЕ гаснет сам
   (см. TOAST_LIFETIME в app.js): его закрывают крестиком, когда прочитали.
   Успел отвести глаза — напоминание всё ещё висит, в этом и смысл.

   Фон — service.100, тот самый тёплый жёлтый «где я», которым подсвечены
   активный таб и сегодняшний день; отдельного токена заводить не пришлось.
   Рамка и заголовок — из семантики warning: она и есть тёплая жёлтая семья
   этой палитры, поэтому вид собран из готового, а не из своих цветов.
   Контраст проверен: заголовок 6.59:1, текст 6.13:1 — оба выше AA. */
.toast--reminder {
  background: var(--service-100);
  border-color: var(--semantic-warning-border);
}
.toast--reminder .toast__title {
  color: var(--semantic-warning-text);
}
.toast--reminder .toast__text {
  color: var(--ink-base);
}

/* График платежей в дровере «Править» — отдельная секция под кнопками формы
   (см. _edit_order_drawer.html, почему не внутри неё). Пунктирная граница
   сверху отделяет «применится по Сохранить» от «срабатывает сразу». */
.schedule-block {
  display: flex;
  flex-direction: column;
  gap: var(--space-row);
  padding-top: var(--space-head-body);
  border-top: 1px dashed var(--paper-line);
}
.schedule-list {
  display: flex;
  flex-direction: column;
}
.schedule-row {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: var(--space-row) 0;
}
.schedule-row + .schedule-row {
  border-top: 1px solid var(--paper-line);
}
.schedule-row__date {
  flex: 1;
  min-width: 0;
  font-size: var(--f-size-footnote);
  color: var(--ink-base);
}
/* Будущая строка — приглушённо: этих денег ещё не было, и именно они уходят
   по кнопке «убрать будущие». */
.schedule-row--future .schedule-row__date,
.schedule-row--future .schedule-row__amount {
  color: var(--ink-secondary);
}
.schedule-row__amount {
  font-size: var(--f-size-footnote);
  font-weight: var(--f-weight-bold);
  font-variant-numeric: tabular-nums;
}
.schedule-block__actions {
  display: flex;
  align-items: center;
  gap: 8px;
}
/* Тот же чекбокс-хак, что у панелей чека на карточке заказа: :checked ~
   раскрывает панель, JS не нужен. После отправки страница перезагружается и
   чекбокс сбрасывается сам. */
.schedule-fill-panel {
  display: none;
  flex-direction: column;
  gap: var(--space-row);
  padding: 12px;
  background: var(--paper-base);
  border-radius: var(--r-md);
}
.schedule-fill-toggle:checked ~ .schedule-fill-panel {
  display: flex;
}

/* Пустое состояние в колонке заказов — единственное исключение из нормы 2.
   Норма велит держать пустое состояние вровень с левым краем строк, но здесь
   «строки» — это карточки .order-row со своим padding 12px 14px, и их
   содержимое (аватар) начинается на 14px правее края колонки. Текст без
   отступа прилипал к краю и стоял левее всего, что над ним. Ровно этот
   случай норма 2 и оговаривает как исключение: строка-карточка с аватаром
   живёт по своим отступам. */
.orders-col .order-list-empty {
  padding-left: 14px;
}

/* Просроченный заказ на карточке заказчика — тонированный блок, у него нет
   разделителя, зато есть свой фон. Без внешнего отступа он упирался прямо в
   пунктир соседней строки: фон, сразу линия, сразу фон. Даём 6px (--space-row,
   как вокруг любого разделителя) — получается фон · отступ · разделитель ·
   отступ · фон. Первому блоку в карточке margin-top обнуляет норма 1
   (.section-card__head + *), лишнего зазора под шапкой не появится. */
/* Горизонтальные отступы отрицательные ровно на свой padding (11px): подложка
   у блока есть, а вот ТЕКСТ в нём обязан стоять в том же столбце, что текст
   обычных строк и левый край чипа-шапки. Без этого содержимое просроченной
   строки уезжало на 25.9 от края карточки при 14.9 у всех соседей — на одном
   экране это видно. Тот же приём, что у `.info-row--danger` на странице
   заказа. Подложка при этом вылезает на 11px в padding карточки (он 14px),
   до края не достаёт. */
.order-line--problem {
  margin: var(--space-row) -11px;
}
/* Первому блоку внешний отступ сверху не нужен: его даёт padding-bottom самой
   шапки (норма 1). Из общего правила `.section-card__head + *` этот блок
   исключён, чтобы ему не обнуляли ВНУТРЕННИЙ padding — содержимое иначе
   прилипало к верхнему краю собственной подложки, — но margin-top всё равно
   гасим, иначе зазор под шапкой уезжает с 13 на 19px. */
.section-card__head + .order-line--problem {
  margin-top: 0;
}


/* ==========================================================================
   МОБИЛЬНОЕ ШАССИ — слой 2: шапка 52px + нижняя навигация 64px.

   Лечит «корень C» из разведки: на 390px шапка не переполнялась, но душила
   навигацию — логотип 150px и название 122px съедали 272px из 390, и на
   вкладки оставалось 43px. Теперь разделы уходят вниз, шапка остаётся
   вывеской.

   Весь блок живёт под @media (max-width: 768px) — единым брейкпоинтом,
   заведённым слоем 1. Десктоп не затрагивается ничем: .bottom-nav скрыт
   вне медиа-запроса, шапка держит прежние 62px через --topbar-height.
   ========================================================================== */

/* Вне мобильного режима бара нет совсем. Держим display:none в безусловном
   правиле, а не «показываем на десктопе» — так бар не мелькнёт до применения
   медиа-запроса и не попадёт в поток на печати. */
.bottom-nav {
  display: none;
}

@media (max-width: 768px) {

  /* --- Шапка: только вывеска (Figma 236:2202 «Топбар верхняя часть») --- */

  /* Высоту даёт --topbar-height (52px). Здесь — горизонтальные поля 14px по
     макету; они же перебивают старое правило .topbar из блока 640px, которое
     лежит выше по файлу и ставило свои 0.6rem/0.85rem. */
  .topbar {
    padding: 0 14px;
    gap: 8px;
  }

  /* Логотип отдаётся в родном соотношении символа (viewBox 125x52) — на
     52px-шапке это ровно 125x52 без подгонки. Символ уже нарисован
     «наклеенным и обрезанным краем», как требует макет: заново ничего не
     рисуем и не кропаем. */
  .topbar-logo-daruma {
    width: 125px;
    height: 52px;
  }

  /* 18px по макету (мобильный макет нарисован в 390px = 1:1, множитель 1.2
     сюда не применяется — см. комментарий у мобильных переменных выше).
     Отрицательный margin не трогаем: при логотипе 125px он даёт началу
     надписи 103px, а в макете 102px. */
  .topbar-brand {
    font-size: 18px;
  }

  /* Разделы, разделитель и выход переехали в нижний бар; поиск на мобиле
     живёт отдельным экраном (Figma 435:1353 «Поиск · mobile»), в шапке его
     нет — поэтому прячем .topbar-tabs целиком вместе с ним. */
  .topbar-tabs,
  .topbar-divider,
  .topbar-exit {
    display: none;
  }

  /* ...и ВМЕСТЕ С НИМИ — подложку меню поиска. Она рендерится сервером
     ({% if search %} в base.html, то есть на любом адресе с ?q=) и лежит
     СНАРУЖИ .topbar-tabs — сразу после </header>, — поэтому строчка выше её
     не накрывала: строка поиска и дропдаун с результатами схлопывались на
     мобиле в 0x0 вместе с .topbar-tabs, а затемнение на весь экран
     оставалось. Получалась пелена без хозяина: страница размыта, показать
     ей нечего, и почему — не видно. Тапбар при этом работал (z-index 70
     против 50 у подложки), из-за чего это и читалось как «залипший слой
     поверх сайта».

     Убрать надо именно затемнение, а не ?q= в адресе: поиск на мобиле —
     отдельный экран, а на десктопе тот же адрес по-прежнему показывает
     дропдаун с результатами и подложку под ним, как задумано. */
  .search-backdrop {
    display: none;
  }

  /* --- Нижняя навигация (Figma 198:1468 / 198:1528) --- */

  .bottom-nav {
    display: flex;
    position: fixed;
    left: 0;
    right: 0;
    bottom: 0;
    z-index: 70;

    /* Высота = 64px содержимого + системная зона снизу. box-sizing:border-box
       у нас глобальный, поэтому padding-bottom съел бы табы изнутри — высоту
       увеличиваем ровно на ту же величину, и содержимое остаётся 64px.

       В iOS Safari safe-area-inset-bottom — это НЕ только «полоска домой»: при
       развёрнутой нижней панели браузера сюда попадает и её высота. Отсюда и
       баг: бар стоял вплотную к bottom:0, панель Safari ложилась поверх него, и
       табы переставали нажиматься. Фон при этом честно дотягивается до самого
       низа экрана, а сами кнопки живут выше системной зоны.

       На экранах без выреза и без нижней панели env() = 0 — правило
       безвредно, бар остаётся ровно 64px. */
    height: calc(var(--bottomnav-height) + env(safe-area-inset-bottom, 0px));
    padding-bottom: env(safe-area-inset-bottom, 0px);
    align-items: center;
    justify-content: space-between;
    /* В макете поля 20px при семи табах по 50px: 20+350+20 = ровно 390.
       На экранах уже 390 (360, 320) фиксированные 20px выпихнули бы табы за
       край, поэтому поле сжимается само и отдаёт место табам. */
    padding-inline: max(4px, calc((100% - 350px) / 2));
    background: var(--paper-base);
    border-top: 1px dashed var(--paper-linestrong);
  }

  /* Таб — и <a>, и <button> («Запись»): обнуляем кнопочные умолчания, чтобы
     оба выглядели одинаково. 50x50 — это и размер по макету, и требуемый
     тач-таргет (>=44px): при сжатии на узких экранах ширина падает не ниже
     ~44px, высота остаётся 50. */
  .bottom-nav__tab {
    flex: 0 1 50px;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 3px;
    height: 50px;
    padding: 0;
    border: none;
    border-radius: 12px;
    background: none;
    color: var(--ink-secondary);
    font-family: inherit;
    text-decoration: none;
    cursor: pointer;
    -webkit-tap-highlight-color: transparent;
  }

  /* Активный раздел — заливка paper-sunk, как в «Нижней навигации с выбором».
     Текст заодно уходит в полный ink-base: на бумажной заливке приглушённый
     ink-secondary читался бы хуже. */
  .bottom-nav__tab--active {
    background: var(--paper-sunk);
    color: var(--ink-base);
  }

  .bottom-nav__tab:hover {
    text-decoration: none;
  }

  .bottom-nav__icon {
    display: block;
    width: 22px;
    height: 22px;
    flex-shrink: 0;
  }

  /* 11px, а не 8px из макета: в ноде размер шрифта 8px (11 там — высота
     текстового блока), но 8px мелко, а весь проект поднимали ради читаемости.
     Решение владельца. */
  .bottom-nav__label {
    font-size: 11px;
    font-weight: var(--f-weight-medium);
    line-height: 1;
    white-space: nowrap;
  }

  /* «Дом» — центральный таб. Подписи нет, вместо иконки 22x22 стоит дарума
     30x36: в макете он выделен не сдвигом вверх, а тем, что рисунок крупнее
     и занимает всю высоту содержимого таба (7..43px против 7..29px у иконок
     соседей). */
  .bottom-nav__daruma {
    display: block;
    width: 30px;
    height: 36px;
  }

  /* --- Что нижний бар перекрывает --- */

  /* Бар прибит к низу и накрывает конец страницы — добавляем ему место.
     Правило стоит ниже блока 640px, где .content задан шорткатом padding,
     поэтому переопределяет именно нижнюю сторону и не трогает остальные. */
  .content {
    /* Тот же расчёт, что и высота бара, плюс запас: иначе последний элемент
       страницы прятался бы уже не за баром, а за его новой системной частью. */
    padding-bottom: calc(var(--bottomnav-height) + env(safe-area-inset-bottom, 0px) + 16px);
  }

  /* Тосты всплывают в правом нижнем углу (bottom: 20px) — ровно там, где
     теперь бар. Поднимаем их над ним, иначе сообщение об успехе оказалось бы
     под панелью. */
  .toasts {
    bottom: calc(var(--bottomnav-height) + env(safe-area-inset-bottom, 0px) + 12px);
  }
}


/* ==========================================================================
   МОБИЛЬНЫЕ РАСКЛАДКИ — слой 3, «КОРЕНЬ A» из разведки.

   Три двухколоночные раскладки не имели медиа-запроса ВООБЩЕ: боковая колонка
   стояла фиксированной шириной с flex-shrink:0, поэтому на 390px соседке
   доставались остатки — 52px на /orders, 0px на карточке заказчика, а
   .categories-col (386px) была шире всего вьюпорта. Отсюда и брались съезды
   в горизонтальный скролл.

   Лечится парой правил: разворот в колонку + снятие фиксированной ширины.
   align-items:stretch тут не «на всякий случай», а обязателен — у всех трёх
   контейнеров базовый align-items:flex-start, а во flex-column он означает
   «по ширине контента» (ровно корень B, из-за которого «Неделя» держала
   686px). И width:auto тоже обязателен: явная ширина сильнее stretch, одним
   только stretch колонка осталась бы на своих 300/360/386px.

   Порядок карточек на мобиле не задаём (никаких order/row-reverse): в макете
   боковая колонка везде идёт первой, а это и есть обычный порядок разметки.
   ========================================================================== */

@media (max-width: 768px) {
  .orders-body,
  .customer-body,
  .templates-body {
    flex-direction: column;
    align-items: stretch;
  }

  .customers-col,
  .customer-col--left,
  .categories-col {
    width: auto;
  }

  /* --- Ряды, которые не переносились (остаток горизонтальных съездов) ---

     После разворота колонок съезды остались ещё на четырёх узлах, и все они
     одной природы: flex-ряд с flex-wrap:nowrap, внутри которого стоит распорка
     со flex:1 1 0%. Из-за нулевой flex-basis распорка ничего не занимает при
     раскладке по строкам, ряд считает, что всё помещается, и последний элемент
     (обычно кнопка) просто торчит за краем. Одного flex-wrap:wrap хватает:
     реальные ширины сразу перестают влезать в строку и перенос срабатывает. */
  .orders-head,
  .subs-head,
  .customer-header {
    flex-wrap: wrap;
  }

  /* Исключение из предыдущего правила: у шапки заказчика распорки нет, зато
     сам блок с именем стоит flex:1 1 0%. По базовым ширинам строка сходится
     (48+97+65+77), поэтому перенос не наступает, а блок имени ужимается до
     14px — и подпись из него торчит наружу. Отдаём ему всю строку: имя и
     хвосты сверху, статус и кнопки переезжают на вторую. */
  .customer-header__info {
    flex-basis: 100%;
  }

  /* Подпись хвостов стояла white-space:nowrap (230px) — на десктопе это
     защита от рваного переноса, на 390px превращалось в вылет за край. */
  .dossier__tails-caption {
    white-space: normal;
  }
}


/* ==========================================================================
   ДРОВЕРА -> БОТТОМ-ШИТЫ - слой 4.

   Все 30 дроверов приложения - это <dialog class="drawer"> с одной общей
   механикой в app.js (data-open-dialog -> showModal, data-close-dialog ->
   close, клик мимо -> close). Поэтому весь перевод на мобильные шторки -
   один блок правил, без единой строчки JS и без правки разметки.

   Шапка макета: любой из 28 sheet-фреймов, например 227:564 "Удаление ·
   подтверждение". Оттуда же ручка (40x4, radius 999, paper/line-strong),
   скругление только сверху (20px = --r-xl) и паддинги 8/16/20.

   Что НЕ трогаем здесь: .select-custom__menu и .single-date-field__popover -
   они не <dialog>, а position:fixed с координатами из JS, им нужна ветка в
   app.js. Это слой 5.
   ========================================================================== */

@media (max-width: 768px) {

  dialog.drawer {
    /* Прижать к низу. inset:0 + margin:auto auto 0 - тот же приём, которым
       <dialog> центрируется браузером, только нижний margin убран в 0. */
    position: fixed;
    inset: 0;
    margin: auto auto 0;
    width: 100%;
    max-width: none;

    /* Низ прямой - он лежит на краю экрана. */
    border-radius: var(--r-xl) var(--r-xl) 0 0;

    /* 8 сверху (под ручку), 16 по бокам, 20 снизу - по макету. К нижнему
       добавлен вырез под домашний индикатор: шторка прибита к самому краю,
       и без env() последняя кнопка попала бы под системную полоску. На
       экранах без выреза env() = 0, поэтому calc безопасен везде. */
    padding: 8px 16px calc(20px + env(safe-area-inset-bottom, 0px));

    /* Длинная форма ("Профиль реквизитов" в макете - 1086px при экране ~844)
       не должна уезжать за верх: упираем в 90% высоты и скроллим внутри.
       Две строки подряд - фолбэк: браузер без dvh проглотит первую и
       проигнорирует вторую. dvh, а не vh, потому что на телефоне адресная
       строка ездит, и vh считается по РАЗВЁРНУТОМУ вьюпорту - шторка на
       90vh оказалась бы частично выше видимой области. */
    max-height: 90vh;
    max-height: 90dvh;
    overflow-y: auto;
  }

  /* Ручка-граббер. ::before, а не элемент в разметке - иначе пришлось бы
     править 30 шаблонов ради одной полоски. Работает потому, что
     dialog.drawer[open] уже flex-column: псевдоэлемент просто становится
     первым flex-ребёнком, а gap:14px из того же правила отбивает его от
     содержимого. */
  dialog.drawer[open]::before {
    content: "";
    flex: none;
    align-self: center;
    width: 40px;
    height: 4px;
    border-radius: 999px;
    background: var(--paper-linestrong);
  }

  /* Два дровера со своим max-width. Оба и так попадают под dialog.drawer выше
     (класс drawer у них тоже есть) и оно стоит позже по файлу, но пишем явно:
     иначе правило держится на порядке строк и сломается при первой же
     перестановке блоков. confirm-dialog вдобавок сам себя центрирует
     собственным margin:auto - перебиваем и его. */
  dialog.confirm-dialog,
  dialog.labels-dialog {
    max-width: none;
  }
  dialog.confirm-dialog {
    margin: auto auto 0;
  }
}

/* ==========================================================================
   ВЫПАДАЮЩИЙ СПИСОК -> БОТТОМ-ШИТ - слой 5, заход 1 (select-custom).

   Дровера (слой 4 выше) - это <dialog>, им хватило чистого CSS. Здесь не
   так: .select-custom__menu - обычный <ul> с position:fixed и координатами
   из app.js, и пока JS их считает, никакое правило отсюда его не сдвинет
   (инлайн-стиль старше медиа-блока). Поэтому у пары есть вторая половина -
   ветка isSheet() в app.js: она координаты не считает и снимает инлайновые
   остатки от прошлого десктопного открытия. Одно без другого не работает.

   Визуальный язык - тот же, что у дроверов-шторок: ручка 40x4 / radius 999 /
   --paper-linestrong, скругление только сверху (--r-xl), паддинги 16 по
   бокам и 20 снизу + env(safe-area-inset-bottom), потолок 90dvh со
   скроллом внутри.

   Что сюда НЕ попало: .select-custom__menu--grid (сетка месяцев на /me) -
   у неё display:grid и фиксированные клетки, ручка ::before стала бы
   клеткой сетки и порвала раскладку. Она остаётся на координатах из JS
   (app.js:isSheet() её исключает). И .single-date-field__popover - это
   заход 2 того же слоя.
   ========================================================================== */

/* Подложка мобильного шита. Отдельный элемент, а не ::backdrop: тот есть
   только у <dialog>, а шит - обычный position:fixed блок. Ровно как
   .search-backdrop у меню поиска, и теми же токенами - затемнение всех
   модальных слоёв приложения должно быть одним.

   z-index 90: выше .bottom-nav (70) и топбара - иначе на обычной странице
   тапбар торчал бы сквозь затемнение поверх шита. Внутри дровера порядок
   не важен (top-layer), но значение одно на оба случая.

   Скрытого состояния у этого класса НЕТ и быть не должно: app.js не прячет
   подложку атрибутом, а удаляет узел из DOM целиком (hideSheetBackdrop).
   Поэтому здесь нечему конфликтовать с [hidden]{display:none} - ровно той
   ловушке, на которой проект стоял четырежды (.select-custom__menu,
   .single-date-field__popover, .search-menu, .icon-btn). Нет узла - нет и
   шанса, что он где-то залипнет видимым. */
.sheet-backdrop {
  position: fixed;
  inset: 0;
  z-index: 90;
  background: var(--backdrop-bg);
  backdrop-filter: blur(var(--backdrop-blur));
}

@media (max-width: 768px) {

  .select-custom__menu {
    /* Прижать к низу во всю ширину. top:auto - страховка на случай, если
       инлайновый top всё-таки где-то уцелеет: без него элемент с заданными
       top и bottom растянулся бы от старой координаты до низа экрана. */
    left: 0;
    right: 0;
    bottom: 0;
    top: auto;
    z-index: 91;

    /* Ширину на десктопе задаёт содержимое (width:max-content) плюс
       инлайновый min-width от app.js. На шите ширина - весь экран, поэтому
       гасим обе: min-width app.js на мобиле не ставит, но правило оставлено
       поясом - инлайн его всё равно перебьёт, зато при любом другом пути
       (кэш старого app.js у Хильды в телефоне) шит не съедет в узкую полосу. */
    width: auto;
    min-width: 0;
    max-width: none;

    /* Низ прямой - он лежит на краю экрана. */
    border-radius: var(--r-xl) var(--r-xl) 0 0;
    border-bottom: none;

    /* Верхний паддинг ноль: сверху сидит ручка (::before), она свой отступ
       несёт сама. По бокам и снизу - как у dialog.drawer, включая вырез под
       домашний индикатор. */
    padding: 0 16px calc(20px + env(safe-area-inset-bottom, 0px));

    /* Потолок и фолбэк - как у дровера: dvh, потому что адресная строка на
       телефоне ездит, и 90vh считался бы по развёрнутому вьюпорту. Строка с
       vh идёт первой: браузер без dvh проглотит её и проигнорирует вторую.
       Десктопные 230px тут малы - шит на пол-экрана читается лучше, чем
       полоска в четыре строки у самого низа. */
    max-height: 90vh;
    max-height: 90dvh;

    box-shadow: 0 -8px 24px rgba(36, 38, 43, 0.16);
  }

  /* Ручка-граббер. У дровера она бесплатна (тот уже flex-column с gap:14px),
     здесь так не выйдет: корень меню сам скроллится (overflow-y:auto), и
     обычный первый ребёнок уехал бы вверх при первом же движении пальца.
     Поэтому ручка - sticky-полоса во всю ширину с фоном карточки: опции
     уходят ПОД неё, а не мимо. Сам граббер нарисован фоном-градиентом по
     центру полосы - отдельного элемента под него в разметке нет и заводить
     его нельзя (правка только в CSS/JS).

     20px = 8 сверху + 4 ручка + 8 снизу, как в шапке дровера. */
  .select-custom__menu::before {
    content: "";
    position: sticky;
    top: 0;
    z-index: 2;
    flex: none;
    height: 20px;
    margin: 0 -16px 4px;
    border-radius: var(--r-xl) var(--r-xl) 0 0;
    background:
      linear-gradient(var(--paper-linestrong), var(--paper-linestrong)) center / 40px 4px no-repeat,
      var(--paper-card);
  }

  /* Сетка месяцев в шит не переводится (см. шапку блока) - у неё ::before
     стал бы клеткой сетки. Гасим его явно, а не полагаемся на то, что
     app.js её и так исключил: правило выше цепляется к классу меню, а класс
     --grid лежит на том же элементе. */
  .select-custom__menu--grid::before {
    content: none;
  }
  .select-custom__menu--grid:not([hidden]) {
    left: auto;
    right: auto;
    bottom: auto;
    width: max-content;
    border-radius: 14px;
    border-bottom: 1px solid var(--paper-line);
    box-shadow: 0 8px 24px rgba(36, 38, 43, 0.16);
  }

  /* Строка поиска тоже sticky и на десктопе липнет к самому верху
     (top:-7px, компенсируя padding:7px). Под ручкой ей нужно встать ниже
     на её высоту, иначе обе прилипнут в одну точку и поиск уедет под
     граббер. Отрицательные боковые margin'ы пересчитаны под мобильные 16px. */
  .select-custom__search-row {
    top: 20px;
    margin: 0 -16px 4px;
    padding: 0 16px 6px;
  }

  /* Пальцем, а не курсором: опции разъезжаются до нормальной высоты нажатия
     и получают зазор между собой. Радиус оставлен десктопный. */
  .select-custom__option {
    padding: 12px 10px;
  }

  /* --- КАЛЕНДАРЬ ОДНОЙ ДАТЫ -> шторка (слой 5, заход 2) ---

     Тот же язык и та же пара "ветка в app.js + правило здесь", что у
     .select-custom__menu выше: пока JS считает координаты, отсюда его не
     сдвинуть (инлайн старше медиа-блока), поэтому у openPopover есть своя
     мобильная ветка, а здесь - раскладка.

     Плюс к вёрстке в app.js на мобиле поле становится readonly: дату тут не
     печатают, а выбирают тапом по числу, и системная клавиатура поверх
     шторки не поднимается. */
  .single-date-field__popover {
    left: 0;
    right: 0;
    bottom: 0;
    top: auto;
    z-index: 91;

    /* Жёсткие 302px - под координатный десктоп (там же число продублировано
       в app.js:POPOVER_WIDTH). На шторке ширина - весь экран; клетки дней
       разъедутся сами, они на flex:1, а не на фиксированной сетке. */
    width: auto;
    max-width: none;

    border-radius: var(--r-xl) var(--r-xl) 0 0;
    border-bottom: none;

    /* Верхний паддинг ноль - сверху ручка (::before), свой отступ она несёт
       сама. По бокам и снизу - как у dialog.drawer, с вырезом под домашний
       индикатор. */
    padding: 0 16px calc(20px + env(safe-area-inset-bottom, 0px));

    /* Календарь короткий и в потолок не упрётся, но правило нужно на случай
       мелкого экрана в альбомной ориентации. Порядок строк - фолбэк:
       браузер без dvh проглотит vh и проигнорирует вторую. */
    max-height: 90vh;
    max-height: 90dvh;
    overflow-y: auto;

    box-shadow: 0 -8px 24px rgba(36, 38, 43, 0.16);
  }

  /* Ручка-граббер. Корень попапа - flex-column, так что псевдоэлемент просто
     становится первым ребёнком, а gap:7px отбивает его от шапки календаря.
     sticky и с фоном бумаги - как у меню: строкой выше стоит overflow-y:auto,
     и на коротком экране обычная ручка уехала бы в скролл. */
  .single-date-field__popover::before {
    content: "";
    position: sticky;
    top: 0;
    z-index: 2;
    flex: none;
    height: 20px;
    margin: 0 -16px;
    border-radius: var(--r-xl) var(--r-xl) 0 0;
    background:
      linear-gradient(var(--paper-linestrong), var(--paper-linestrong)) center / 40px 4px no-repeat,
      var(--paper-card);
  }
}
