@import url('./fonts.css');


/*
 * Основа дизайна. Значения перенесены из макета (theme/AlloMaster - UI Kit)
 * без округления: если в макете 219,218,222 — здесь #dbdade, а не «почти
 * серый». Подгонка на глаз — то, из-за чего потом никто не может ответить,
 * почему в одном месте одна рамка, а в соседнем другая.
 *
 * Два цвета вместо одного, и это не украшение. --primary зовёт нажать:
 * кнопки, ссылки, активный пункт меню, фокус в поле. --navy — цвет марки:
 * логотип, заголовки, боковое меню. Раньше они совпадали, и «сохранить»
 * выглядело так же, как заголовок страницы.
 */
:root {
  /* Чистый белый как отдельное имя.

     Это НЕ то же самое, что --surface. Поверхность в тёмной теме
     темнеет, а этот белый — нет: он значит «текст или значок на цветной
     подложке», и на фиолетовой кнопке он белый при любой теме. Раньше в
     таких местах стоял хардкод, и отличить «белый по смыслу» от «белый,
     потому что так вышло» было нельзя. */
  --white: #ffffff;

  --bg: #f8f7fa;
  --surface: #ffffff;
  --border: #dbdade;
  --text: #4b465c;
  --text-muted: #5d586c;
  --text-disabled: #a8aaae;

  /* Действие */
  --primary: #7367f0;
  --primary-dark: #675cd8;
  --primary-light: rgba(115, 103, 240, .16);

  /* Марка. Тот самый #131747 — он никуда не делся, но теперь отвечает
     за принадлежность, а не за действие. */
  --navy: #131747;
  --navy-soft: #8692d0;

  /* Боковое меню. Решение владельца 02.08.2026: меню больше не цвета
     марки, а цвета тёмного фона — и в светлой теме, и в тёмной.

     Значение написано числом, а не через var(--bg): --bg в светлой теме
     почти белый, и меню стало бы белым вместе с ним. Объявление стоит
     ЗДЕСЬ, вне блоков тем, — иначе тёмная тема его переопределит.

     Грань светлая, потому что меню тёмное в обеих темах. В светлой она
     просто очерчивает блок, а в тёмной делает всю работу: там фон
     страницы того же цвета, и без грани край меню исчезает. */
  --sidebar-bg: #25293c;
  --sidebar-edge: rgba(255, 255, 255, .08);
  /* Зазор между плавающими блоками оболочки: меню, шапка, содержимое.
     Один на всех — иначе блоки встанут лесенкой. */
  --shell-gap: 12px;

  --shadow-sidebar: 0 4px 18px rgba(75, 70, 92, .14);

  /* Ползунок прокрутки списка разделов. Родная полоса Windows — светлая,
     широкая и во всю высоту: на тёмной панели она выглядит как чужая
     деталь и спорит со скруглённым краем. Свой ползунок еле виден в
     покое и проявляется, когда мышь над меню, — сама прокрутка при этом
     остаётся видимой, а не прячется совсем: список длиннее экрана, и
     человек должен понимать, что внизу есть ещё разделы. */
  --sidebar-thumb: rgba(255, 255, 255, .10);
  --sidebar-thumb-hover: rgba(255, 255, 255, .26);

  --secondary: #a8aaae;
  --secondary-light: rgba(168, 170, 174, .16);
  --success: #28c76f;
  --success-light: rgba(40, 199, 111, .16);
  --danger: #ea5455;

  /* МЕТКА СОСТОЯНИЯ НА КАРТОЧКЕ ДОСКИ.

     Значок рисуется приглушённым цветом на подложке того же цвета. Прямо
     `--danger` брать нельзя: он рассчитан на белый текст поверх себя, а
     здесь наоборот — цветной значок на светлой подложке, и полная
     насыщенность на светлой теме выжигает глаз.

     Поэтому пара своих токенов, и в тёмной теме они другие: там подложка
     должна быть темнее фона карточки, а значок — светлее, иначе он
     сливается. */
  --mark-late-fg: #c0392f;
  --mark-late-bg: rgba(234, 84, 85, .12);
  --mark-term-fg: #b25f0a;
  --mark-term-bg: rgba(255, 159, 67, .14);

  /* ЦВЕТА СВЯЗИ — ЧУЖИЕ, И ЭТО НАРОЧНО.

     Зелёный WhatsApp узнают, не читая подписи: это не оформление, а знак
     службы. Ставить его «по нашей палитре» значило бы сделать кнопку
     менее узнаваемой ради единообразия, которого никто не просил.

     Второй цвет — синий для маршрута: так красят навигацию и карты
     повсеместно, и человек у ворот жмёт нужное, не разбирая надписей. */
  --whatsapp: #25a35a;
  --route: #1f6fb2;
  --danger-light: rgba(234, 84, 85, .16);
  --warning: #ff9f43;
  --warning-light: rgba(255, 159, 67, .16);
  --info: #00cfe8;
  --info-light: rgba(0, 207, 232, .16);

  /* ТЕКСТ И РАМКА СООБЩЕНИЙ — ОТДЕЛЬНЫМИ ПЕРЕМЕННЫМИ, А НЕ ЧИСЛОМ.
   *
   * Найдено 19.08.2026: у `.alert-*` фон брался переменной и на тёмной теме
   * честно темнел, а цвет текста и рамка стояли числом — и оставались
   * светлотемными навсегда. Тёмно-красная надпись на тёмно-красном фоне,
   * и так после КАЖДОГО сохранения и КАЖДОЙ ошибки, то есть везде.
   *
   * Одним цветом на обе темы это не решается: на светлом фоне читается
   * тёмный оттенок, на тёмном — светлый. Значит нужна пара, и тёмная тема
   * переопределяет её ниже. */
  --success-text: #157347;
  --success-line: #b8ecd0;
  --danger-text: #b3151a;
  --danger-line: #ffc9cb;
  --warning-text: #8a5300;
  --warning-line: #ffe0a3;

  /* Фирменный красный из знака. Намеренно отличается от --danger: тот
     означает ошибку и необратимое действие, этот — просто марку. Если бы
     они совпали, «удалить» и «наше» выглядели бы одинаково. */
  --accent: #e20613;
  --accent-light: #fdeaeb;

  /* Скругления. ОДНО значение на всё: поля, кнопки, карточки, модалы.
     Шкала из трёх ступеней (6/8/12) была взята из прежнего макета; макет
     поменяли, и новый бриф задаёт единый радиус 6px — решение владельца
     01.08.2026.

     Три имени вместо одного остались намеренно. Они разошлись по сотням
     правил, и замена их на одно дала бы огромную правку без единого
     видимого изменения. Пока значения совпадают, разницы нет; сторож
     следит, чтобы шкала снова не разъехалась втихую.

     Четвёртая ступень — только для круглых: аватар, счётчик, переключатель.
     Значения совпадают с noneerp-theme.css намеренно: тот файл грузится
     после и перебил бы их в любом случае. */
  --radius-sm: 6px;
  --radius: 6px;
  --radius-lg: 6px;
  --radius-pill: 999px;

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

  /* Высоты элементов управления: малый, обычный, крупный. Совпадают у
     кнопки, поля и выпадающего списка — иначе они не встают в одну строку. */
  --control-h-sm: 30px;
  --control-h: 38px;
  --control-h-lg: 48px;

  /* Значение взято из слоя автора: он грузится после и всё равно
     побеждал, так что здесь стояло число, которого никто никогда не
     видел. Страница без слоя выглядела не так, как с ним. */
  --shadow: 0 4px 18px rgba(75, 70, 92, .08);
  /* Всплывающий слой лежит поверх содержимого — ему нужна тень заметнее,
     иначе панель читается как часть страницы под ней. */
  --shadow-lg: 0 4px 18px rgba(75, 70, 92, .1);
  --sidebar-w: 260px;
}

/* ============================================================
   БАЗОВЫЙ СЛОЙ ТОКЕНОВ ИЗ МАКЕТА (docs/tokens.css, 01.08.2026)

   Зачем отдельным блоком, а не вперемешку с нашими: макет
   обновляется целиком, и сверять его с нашим набором надо будет
   ещё не раз. Смешав, мы через месяц не отличим своё от чужого.

   СВЕРЕНО ПЕРЕД ПЕРЕНОСОМ. Из 101 переменной макета 34 у нас уже
   были — и все 34 совпали по значению до символа. То есть автор
   выводил их из этого файла, а не придумывал заново. Расхождений
   ноль, поэтому ни одно существующее значение здесь не меняется:
   на них держится весь продукт.

   ЧАСТЬ ИМЁН — ЭТО НАШИ ЖЕ ЦВЕТА ПОД ДРУГИМ НАЗВАНИЕМ. Такие
   заведены ссылкой, а не копией, и это не стиль, а необходимость:
   тёмная тема переопределяет наш --text, и копия --text-primary
   осталась бы светлой. Тёмный текст на тёмном фоне — и мы бы это
   не заметили, потому что копия сама по себе выглядит правильной.
   ============================================================ */
:root {
  /* Наши цвета под именами макета. Только ссылки. */
  --bg-page:            var(--bg);
  --text-primary:       var(--text);
  --text-secondary:     var(--text-muted);
  --primary-muted:      var(--navy-soft);
  --primary-bg:         var(--primary-light);
  --success-bg:         var(--success-light);
  --danger-bg:          var(--danger-light);
  --warning-bg:         var(--warning-light);
  --info-bg:            var(--info-light);

  /* Новое: третья ступень текста и второй оранжевый. */
  --text-tertiary:      #6f6b7d;
  --warning-alt:        #fc9627;

  /* Подложки строк таблицы и нейтрального бейджа. --secondary-bg
     берётся от цвета текста, а не от нашего серого --secondary:
     на белой карточке они дают разный результат. */
  --secondary-bg:       rgba(75, 70, 92, .16);
  --row-hover:          rgba(75, 70, 92, .04);
  --row-selected:       rgba(115, 103, 240, .08);

  /* Тени по назначению. --shadow-card ссылается на нашу, потому
     что в тёмной теме карточка обходится без тени вовсе. */
  --shadow-card:        var(--shadow);
  --shadow-popover:     0 4px 16px rgba(165, 163, 174, .30);
  /* Значение взято из noneerp-theme.css, а не из tokens.css: автор макета
     разошёлся сам с собой — в документе 0 5px 20px .40, в рабочем слое
     0 24px 60px .3. Слой подключается позже и побеждает, значит именно он
     и есть то, что видит человек. Документ тут отстал от файла. */
  --shadow-modal:       0 24px 60px rgba(15, 20, 34, .3);
  --shadow-focus:       0 2px 4px rgba(115, 103, 240, .30);

  /* Шрифт. Семейство здесь же, чтобы правило было в одном месте. */
  --font-family:        'Montserrat', -apple-system, 'Segoe UI', sans-serif;
  --font-regular:       400;
  --font-medium:        500;
  --font-semibold:      600;
  --font-bold:          700;

  /* Размеры и высоты строк парами: кегль без интерлиньяжа не задаёт
     ритм, а именно ритм и делает набор похожим на макет. */
  --text-display:       25px;   --lh-display:    34px;
  --text-h1:            22px;   --lh-h1:         30px;
  --text-h2:            18px;   --lh-h2:         26px;
  --text-body:          15px;   --lh-body:       22px;
  --text-body-sm:       13px;   --lh-body-sm:    20px;
  --text-label:         12px;   --lh-label:      18px;
  --text-caption:       11px;   --lh-caption:    14px;
  --text-badge:         11px;   --lh-badge:      14px;
  --text-badge-min:     10px;

  /* Шаг отступов. */
  --space-1:            4px;
  --space-2:            8px;
  --space-3:            12px;
  --space-4:            16px;
  --space-6:            24px;
  --space-card-gap:     24px;
  --space-content:      24px;
  --space-content-sm:   16px;

  --radius-badge:       4px;
  --radius-circle:      50%;

  --btn-pad:            13px 20px;
  --btn-pad-sm:         9px 14px;
  --btn-icon-gap:       8px;

  --icon-sm:            16px;
  --icon-md:            18px;
  --icon-lg:            22px;
  --icon-stroke:        1.5px;

  /* Каркас. */
  --sidebar-w-collapsed: 80px;
  --topbar-h:           62px;

  /* Таблицы: плотный режим — по умолчанию для реестров. */
  --row-h-dense:        36px;
  --row-h-normal:       48px;
  --table-header-bg:    var(--bg-page);
  --cell-avatar:        32px;
  --cell-action-btn:    30px;

  --modal-w-confirm:    400px;
  --modal-w-form:       560px;
  --modal-w-document:   800px;
  --panel-ai-w:         400px;

  /* --focus-ring-w убрана 25.09.2026: она подставлялась ровно в одно место,
     и это место было сломано (см. флажки ниже). Толщина отметки живёт внутри
     --focus-ring, потому что отметка везде рисуется тенью. */
  --focus-ring:         0 0 0 2px var(--primary);
  --disabled-opacity:   .45;

  --transition-fast:    150ms ease-out;
  --transition-base:    200ms ease-out;

  /* Тап-цели. 44px — палец, 30px — плотная строка реестра мышью. */
  --touch-target:       44px;
  --hit-dense-min:      30px;
  --hit-min:            38px;

  /* Шкала слоёв. Заведена целиком, чтобы новый всплывающий элемент
     не подбирал число на глаз: подобранное на глаз однажды
     перекроет модалку, и найдётся это у клиента. */
  --z-sticky-thead:     10;
  --z-bulkbar:          90;
  --z-sidebar:          100;
  --z-topbar:           110;
  --z-sidebar-overlay:  120;
  --z-dropdown:         1000;
  --z-panel:            1100;
  --z-modal-backdrop:   1200;
  --z-modal:            1210;
  --z-toast:            1300;
  --z-tooltip:          1400;
}

/* ============================================================
   КОМПОНЕНТНЫЕ ТОКЕНЫ: ГАНТ, ЧАТ, ШАПКА (docs/tokens.css, часть II)

   Ради них базовый слой выше и переносился: половина этих значений
   ссылается на него, и без него они молча отваливались бы —
   неопределённая переменная в CSS не даёт ошибки, свойство просто
   становится недействительным.

   ЦВЕТА ЗДЕСЬ НЕ НОВЫЕ. Все до одного — ссылки на палитру. Полоса
   Ганта не должна знать, какой у нас danger: она знает, что
   просрочка красится в danger, а какой он — решает палитра.
   ============================================================ */
:root {
  /* Гант. Строка 36px — тот же плотный режим, что и в реестрах. */
  --gantt-row-h:        var(--row-h-dense);
  --gantt-left-w:       320px;
  --gantt-left-w-min:   40px;
  --gantt-bar-h:        20px;
  --gantt-bar-plan:     var(--border);
  --gantt-bar-fact:     var(--primary);
  --gantt-bar-late:     var(--danger);
  --gantt-critical:     var(--warning);
  --gantt-today:        var(--primary);
  --gantt-weekend-bg:   var(--bg-page);
  --gantt-mini-h:       8px;

  /* Чат и noneAI. Цвет info закреплён за помощником брифом. */
  --chat-panel-w:       var(--panel-ai-w);
  --chat-launcher:      48px;
  --chat-launcher-m:    52px;
  --ai-accent:          var(--info);
  --ai-badge-bg:        var(--info-bg);

  /* Шапка. */
  --topbar-h-mobile:    56px;
  --user-chip-avatar:   32px;
  --user-chip-name-max: 180px;
}

/*
 * Тёмная тема.
 *
 * Селектор один, и это важно: prefers-color-scheme здесь не появляется
 * нигде. Медиазапрос заставил бы повторить весь набор переменных вторым
 * блоком, а два списка цветов расходятся — вопрос только в том, через
 * сколько правок. Настройку системы читает скрипт в <head>
 * (components/theme-boot.blade.php) и сам ставит data-theme; CSS про неё
 * не знает вовсе.
 *
 * Положений три: светлая, тёмная и «как в системе». Последнее — для тех,
 * кто не выбирал: у прораба на ночной смене телефон темнеет сам, и
 * щёлкать тему вручную он не станет. Выбравший светлую остаётся в светлой
 * и ночью — настройка телефона его решение не отменяет.
 */
:root[data-theme="dark"] {
  /* Ночью наоборот: значок светлее фона, подложка гуще. */
  --mark-late-fg: #ff9b9c;
  --mark-late-bg: rgba(234, 84, 85, .18);
  --mark-term-fg: #ffbe7a;
  --mark-term-bg: rgba(255, 159, 67, .18);
  --whatsapp: #3ddc84;
  --route: #6db3f2;

  --bg: #25293c;
  --surface: #2f3349;
  --border: #434968;
  --text: #b6bee3;
  --text-muted: #8692d0;
  --text-disabled: #6a7192;

  /* Сообщения: на тёмном фоне читается светлый оттенок, а не тёмный.
     Фон у них полупрозрачный и темнеет сам — менять надо только текст и
     рамку, иначе выходит тёмно-красное на тёмно-красном. */
  --success-text: #6ee7a8;
  --success-line: rgba(40, 199, 111, .38);
  --danger-text: #ff9b9c;
  --danger-line: rgba(234, 84, 85, .38);
  --warning-text: #ffc98a;
  --warning-line: rgba(255, 159, 67, .38);
  --shadow: 0 2px 4px rgba(15, 20, 34, .3);
  --shadow-lg: 0 4px 18px rgba(15, 20, 34, .4);

  /* Токены макета, у которых своё тёмное значение.
     Остальные его имена — ссылки на наши цвета выше, и темнеют сами:
     --bg-page, --text-primary, --text-secondary и подложки статусов
     здесь не повторяются намеренно, иначе появился бы второй список,
     который однажды разойдётся с первым. */
  --text-tertiary: #8f95c6;
  --secondary-bg: rgba(208, 212, 241, .16);
  --row-hover: rgba(208, 212, 241, .04);
  /* На #2f3349 подложка в 8% не читается — отсюда 16%. */
  --row-selected: rgba(115, 103, 240, .16);

  /* Меню: цвет тот же, что в светлой, — он объявлен вне блоков тем и
     здесь намеренно НЕ повторяется. Меняются только тень и грань: на
     тёмном фоне серая тень не видна, нужна чёрная и глубже, а грань
     становится главным разделителем — фон страницы того же цвета. */
  --sidebar-edge: rgba(255, 255, 255, .11);
  --shadow-sidebar: 0 4px 20px rgba(0, 0, 0, .45);

  /* В тёмной теме иерархию держат разница поверхностей и обводки,
     а не тени: тень на тёмном фоне не видна, но съедает контраст. */
  --shadow-card: none;
  --shadow-popover: 0 4px 16px rgba(15, 20, 34, .45);
  --shadow-modal: 0 5px 20px rgba(15, 20, 34, .55);
}

/* ============================================================
   ИКОНКИ ШАПКИ — ОДНО ПРАВИЛО НА ВСЕХ (#436)

   Здесь стояли ДВА одинаковых набора: `.theme-toggle` и `.help-link`
   повторяли друг друга построчно, и рядом было честно написано, что
   повтор нарочный — «обе стоят в одном кластере, разнобой виден сразу».
   Намерение верное, способ нет: две копии одного правила расходятся
   молча, и разойдутся они как раз в высоте, ради которой их и писали.

   А колокольчик в этот ряд не входил вовсе: он рисовался общей кнопкой
   `.btn-sm` и был на четыре пикселя ниже соседей. Три иконки в одном
   кластере стояли трёх разных размеров — 20, 18 и 16.

   Теперь класс один. Отдельные имена сохранены: `.theme-toggle` несёт
   смену картинки по теме, `.help-link` — это ссылка, а не кнопка. Но
   КОРОБКА у них общая, и разъехаться ей больше негде.
   ============================================================ */
.topbar-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 34px;
  height: 34px;
  min-height: 34px;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--surface);
  color: var(--text-muted);
  text-decoration: none;
  cursor: pointer;
  padding: 0;
}
.topbar-icon:hover { border-color: var(--primary); color: var(--primary); }
.topbar-icon:focus-visible { outline: none; box-shadow: 0 0 0 3px var(--primary-light); }

/* Переключатель темы. Иконка одна, меняется по теме: рисовать две кнопки
   рядом — значит заставлять человека думать, какая из них нажата. */
/* Кнопка «?» — справка про текущий экран (#247). Ссылка, а не кнопка: у
   неё нет состояния и она уводит со страницы. */

/* ============================================================
   БЫСТРЫЕ ДЕЙСТВИЯ (#427)

   Один узел, два вида. В шапке — триггер «Действия» в ряду иконок
   (theme-toggle / help-link) с выпадающим поповером. На узком экране
   (media ниже) тот же узел становится плавающей кнопкой (FAB) снизу-справа,
   а панель раскрывается вверх над затемнением. Разводит вид CSS, DOM один —
   поповер живёт на сервере, как у колокольчика, экран не моргает.
   ============================================================ */
.quick-actions { position: relative; }

/* Триггер выделен заливкой (--primary-light), а не только рамкой: это не
   ссылка «прочти», а вход в частые действия — зовёт нажать. */
.quick-actions__trigger {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  height: 34px;
  padding: 0 var(--space-3);
  border: 1px solid var(--primary);
  border-radius: var(--radius);
  background: var(--primary-light);
  color: var(--primary);
  font: 500 13px/1 var(--font-family);
  cursor: pointer;
}
.quick-actions__trigger:hover { background: rgba(115, 103, 240, .24); }
.quick-actions__trigger:focus-visible { outline: none; box-shadow: 0 0 0 3px var(--primary-light); }
.quick-actions__trigger-label { white-space: nowrap; }

/* Точка табеля на триггере: смена идёт — --primary; не начат — приглушённая;
   день закрыт — точки нет вовсе (нечего подсказывать, см. blade). */
.quick-actions__dot { width: 8px; height: 8px; border-radius: 50%; }
.quick-actions__dot--out { background: var(--primary); }
.quick-actions__dot--in { background: var(--text-disabled); }

/* Затемнение фона — только на мобильном (см. media). */
.quick-actions__scrim { display: none; }

/* Поповер под триггером (десктоп). */
.quick-actions__panel {
  position: absolute;
  top: calc(100% + 6px);
  right: 0;
  width: 320px;
  z-index: 55;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-popover);
  padding: var(--space-4);
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  text-align: left;
}
.quick-actions__section {
  font: 600 var(--text-label) var(--font-family);
  letter-spacing: .08em;
  text-transform: uppercase;
  color: var(--text-muted);
}
.quick-actions__clock { width: 100%; justify-content: center; }
.quick-actions__clock-note { font-size: var(--text-caption); color: var(--text-muted); }
.quick-actions__done {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  font-size: var(--text-body-sm);
  color: var(--text-muted);
}
.quick-actions__divider { height: 1px; background: var(--border); }
.quick-actions__pick { display: flex; flex-direction: column; gap: var(--space-2); }
.quick-actions__field-label,
.quick-actions__hint { font-size: var(--text-caption); color: var(--text-muted); }
.quick-actions__hint { color: var(--text-disabled); }
.quick-actions__err { font-size: var(--text-caption); color: var(--danger); }

.quick-actions__item {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  width: 100%;
  padding: 8px var(--space-3);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: transparent;
  color: var(--text);
  font: 500 var(--text-body-sm) var(--font-family);
  text-decoration: none;
  text-align: left;
  cursor: pointer;
}
.quick-actions__item:hover { border-color: var(--primary); color: var(--primary); }
/* «Чат» — только на мобильном: на десктопе у чата своя плавающая кнопка. */
.quick-actions__item--chat { display: none; }
/* Слот живого бейджа (компонент chat-unread-badge). Абсолютом на весь узел,
   чтобы пустой div не занимал места в ряду шапки и не ломал раскладку; сам
   бейдж внутри позиционируется в угол. Кликам не мешает. */
.quick-actions__badge-slot { position: absolute; inset: 0; pointer-events: none; }
/* Бейдж непрочитанного чата — только мобильный FAB (media ниже). */
.quick-actions__chat-badge { display: none; }

@media (max-width: 767px) {
  /* Плавающая кнопка снизу-справа. z ниже панели чата (60): открытый чат
     всегда перекрывает FAB, а не наоборот. */
  .quick-actions {
    position: fixed;
    right: 16px;
    bottom: 16px;
    z-index: 59;
  }
  .quick-actions__trigger {
    width: 56px;
    height: 56px;
    padding: 0;
    justify-content: center;
    border-radius: 50%;
    border-color: transparent;
    background: var(--primary);
    color: var(--white);
    box-shadow: var(--shadow-popover);
  }
  .quick-actions__trigger-label { display: none; }
  /* Табель-точка уходит в левый угол FAB — правый занимает бейдж чата. */
  .quick-actions__dot {
    position: absolute;
    top: 12px;
    left: 12px;
    border: 2px solid var(--primary);
  }
  .quick-actions__dot--out { background: var(--white); }
  /* Бейдж непрочитанного чата — правый верхний угол FAB, как был у
     плавающей кнопки чата, которой на мобильном больше нет. */
  .quick-actions__chat-badge {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    position: absolute;
    top: -4px;
    right: -4px;
    min-width: 18px;
    height: 18px;
    padding: 0 5px;
    border-radius: var(--radius-pill);
    background: var(--danger);
    color: var(--white);
    font: 600 var(--text-caption) var(--font-family);
    box-shadow: 0 0 0 2px var(--bg-page);
  }

  .quick-actions__scrim {
    display: block;
    position: fixed;
    inset: 0;
    background: rgba(15, 20, 34, .4);
    z-index: 58;
  }
  .quick-actions__panel {
    position: fixed;
    right: 16px;
    bottom: 84px;
    left: auto;
    top: auto;
    width: min(320px, calc(100vw - 32px));
    z-index: 59;
  }
  /* Мишени под палец: минимум 44px. */
  .quick-actions__clock,
  .quick-actions__item { min-height: 44px; }
  .quick-actions__item--chat { display: flex; }

  /* Отдельной плавающей кнопки чата на мобильном нет — «Чат» живёт в FAB.
     Панель чата (со своим крестиком) не трогаем. */
  .chat-widget .chat-launcher { display: none; }
}

/* ============================================================
   ФОТОФИКСАЦИЯ: сетка + лайтбокс (#427, x-photo-grid)

   Одна вещь на карточку объекта, сводку руководителя и галерею. Только для
   своих — в портал заказчика этот блок не идёт.
   ============================================================ */
.photo-grid {
  /* Число колонок — переменной: витрины ставят 3, галерея 4 (inline style),
     мобильный ниже перебивает на 2. Объявлена здесь, а не только inline,
     чтобы сторож объявленных переменных её видел. */
  --photo-cols: 3;
  display: grid;
  grid-template-columns: repeat(var(--photo-cols), 1fr);
  gap: var(--space-3);
}
.photo-grid__item { margin: 0; display: flex; flex-direction: column; gap: var(--space-1); }

/* Миниатюра — кнопка (открывает лайтбокс), а не ссылка в новую вкладку. */
.photo-thumb {
  display: block;
  width: 100%;
  padding: 0;
  border: none;
  border-radius: var(--radius-lg);
  overflow: hidden;
  background: var(--bg-page);
  cursor: pointer;
}
.photo-thumb img {
  display: block;
  width: 100%;
  aspect-ratio: 140 / 100;
  object-fit: cover;
}
.photo-cap { display: flex; flex-direction: column; }
.photo-cap__object { font: 500 var(--text-caption) var(--font-family); color: var(--text); }
.photo-cap__meta { font: 400 var(--text-caption) var(--font-family); color: var(--text-muted); }

/* Лайтбокс — поверх всего, тёмная подложка (не зависит от темы: снимок
   читается на тёмном в обеих). */
.photo-lightbox {
  position: fixed;
  inset: 0;
  z-index: 80;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-3);
  padding: var(--space-4);
  background: rgba(15, 20, 34, .85);
}
.photo-lightbox__stage {
  margin: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-3);
  max-width: 100%;
}
.photo-lightbox__img {
  max-width: min(900px, calc(100vw - 120px));
  max-height: calc(100vh - 120px);
  object-fit: contain;
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-modal);
}
.photo-lightbox__meta {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  flex-wrap: wrap;
  justify-content: center;
  color: var(--white);
  font: 400 var(--text-body-sm) var(--font-family);
}
.photo-lightbox__object { font-weight: 500; }
.photo-lightbox__count { opacity: .7; }
.photo-lightbox__close {
  position: absolute;
  top: var(--space-4);
  right: var(--space-4);
  width: 40px;
  height: 40px;
  border: none;
  border-radius: var(--radius-sm);
  background: rgba(255, 255, 255, .12);
  color: var(--white);
  font-size: 24px;
  line-height: 1;
  cursor: pointer;
}
.photo-lightbox__nav {
  flex: 0 0 auto;
  width: 44px;
  height: 44px;
  border: none;
  border-radius: 50%;
  background: rgba(255, 255, 255, .12);
  color: var(--white);
  font-size: 26px;
  line-height: 1;
  cursor: pointer;
}
.photo-lightbox__close:hover,
.photo-lightbox__nav:hover { background: rgba(255, 255, 255, .22); }

@media (max-width: 767px) {
  /* Витрины — в два столбца на телефоне (см. решение по мобильному). */
  .photo-grid { grid-template-columns: repeat(2, 1fr); }
  /* Плитка сводки на мобильном показывает только последние 6 (data-mobile-cap):
     скрытые лениво-снимки не грузятся вовсе — сервер и хранилище не дёргаем. */
  .photo-grid[data-mobile-cap="6"] .photo-grid__item:nth-child(n+7) { display: none; }
  /* Лайтбокс во весь экран, стрелки поверх снимка снизу. */
  .photo-lightbox__img { max-width: calc(100vw - 24px); max-height: calc(100vh - 160px); }
  .photo-lightbox__nav--prev { position: absolute; left: var(--space-4); bottom: var(--space-4); }
  .photo-lightbox__nav--next { position: absolute; right: var(--space-4); bottom: var(--space-4); }
}

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

   В положении «как в системе» видно то, что система и выбрала, — это
   честно: человек смотрит на иконку, чтобы понять, светло сейчас или
   темно, а не чтобы вспомнить, что он отметил в меню. Отметка — в меню. */
.theme-toggle .icon-dark { display: none; }
:root[data-theme="dark"] .theme-toggle .icon-light { display: none; }
:root[data-theme="dark"] .theme-toggle .icon-dark { display: block; }

/* Пункт-положение: название слева, галочка справа. */
.theme-menu__item { display: flex; align-items: center; justify-content: space-between; gap: 16px; }
.theme-menu__mark { color: var(--primary); }

* { box-sizing: border-box; }

body {
  margin: 0;
  font-family: 'Montserrat', -apple-system, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
  background: var(--bg);
  color: var(--text);
  font-size: 14px;
}

/* ПОЛЯ И КНОПКИ НАСЛЕДУЮТ ШРИФТ СТРАНИЦЫ. ОДНОЙ СТРОКОЙ НА ВСЁ.

   Браузер набирает элементы формы СВОИМ шрифтом, а не шрифтом страницы:
   таково умолчание, и оно старше нас. Пока правило писали по месту —
   `font-family: inherit` встречается в этом файле полтора десятка раз, —
   про него забывали там, где стиль писали наспех. На витрине так набрались
   системным шрифтом кнопка бургер-меню, кнопка закрытия и поле поиска: три
   места, где человек первым делом и оказывается.

   НАЙДЕНО ЗАМЕРОМ, А НЕ ЧТЕНИЕМ. 21.08.2026, computed style в браузере.
   Чтением такое не находится: в исходнике на этих кнопках написано верно
   всё, кроме несказанного. Отсутствие правила не подчёркивается ничем.

   «Шрифт везде Montserrat» — правило проекта, см. CLAUDE.md.

   `optgroup` в списке не лишний: в части браузеров он не наследует шрифт
   даже от своего `select`. */
button, input, select, textarea, optgroup {
  font-family: inherit;
  /* Цвет здесь по той же причине и по требованию сторожа темы: кнопка не
     наследует и его тоже, а браузерный тёмен в любой теме — на тёмном
     фоне надпись пропадает. */
  color: inherit;
}

/* Логотип по теме: показывается один из двух, второй спрятан.

   Про `prefers-color-scheme` здесь намеренно ни слова. Тему ставит
   `theme-boot` — он читает выбор человека, а если выбора нет, спрашивает
   систему, — и всегда проставляет `data-theme` на корне. CSS про
   системную настройку знать не должен: иначе выбор «светлая» на ночной
   машине давал бы светлую страницу с ночным логотипом. */
/* `!important` здесь обязателен, и вот почему.

   Это переключатель видимости, а не оформление. Логотип стоит в шапке
   витрины, в шапке кабинета, в мобильном меню и на страницах ошибок, и
   почти в каждом из этих мест уже лежит своё правило вида
   `.site-header .logo img { display: block }` — весом выше, чем один
   класс. Без `!important` побеждает оно, и показываются ОБА знака, один
   под другим. Ровно это и случилось на витрине 21.08.2026.

   Поднимать вес селектора значило бы затеять гонку: следующее правило в
   чужом файле окажется тяжелее, и разберутся в этом не скоро. */
.brand-tone--dark { display: block !important; }
.brand-tone--white { display: none !important; }
:root[data-theme="dark"] .brand-tone--dark { display: none !important; }
:root[data-theme="dark"] .brand-tone--white { display: block !important; }

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

/* ---- Auth pages ---- */
.auth-wrap {
  min-height: 100vh;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 24px;
  /* Фон входа — тот же, что у рабочего экрана (решение владельца 03.08).
     Здесь стоял тёмный градиент цвета марки с доводом «это ещё не работа,
     а порог». Довод отменён: человек, который завтра будет работать в
     системе, сегодня должен видеть её цвета, а не отдельную заставку.
     Тёмным остаётся только левая колонка — тем же цветом, что боковое
     меню кабинета. */
  background: var(--bg);
}
.auth-card {
  width: 380px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  padding: 32px;
  box-shadow: var(--shadow-lg);
}
.auth-card h1 { font-size: 20px; margin: 0 0 4px; color: var(--navy); }
.auth-card p.subtitle { color: var(--text-muted); margin: 0 0 20px; }

/* ---- App shell ---- */
.app-shell { display: flex; min-height: 100vh; }
/* Меню — не полоса во всю высоту, а отдельный блок, лежащий на странице.

   Отступ по краям, скругление, грань и тень: в тёмной теме фон страницы
   того же цвета, что и меню, и разделяет их именно ЗАЗОР. Тень там —
   мягкий довесок, а не основа: тёмное на тёмном почти не читается, и по
   той же причине --shadow-card в тёмной теме выключен вовсе.

   sticky, а не fixed: меню остаётся на экране при прокрутке, но живёт в
   потоке и не требует отдельного отступа у содержимого. align-self нужен,
   чтобы блок не растянулся на всю высоту оболочки: у растянутого элемента
   sticky не работает. */
.sidebar {
  width: var(--sidebar-w);
  flex-shrink: 0;
  background: var(--sidebar-bg);
  color: rgba(255, 255, 255, .78);
  padding: 18px 0;
  display: flex;
  flex-direction: column;

  align-self: flex-start;
  position: sticky;
  top: var(--shell-gap);
  height: calc(100vh - var(--shell-gap) * 2);
  margin: var(--shell-gap) 0 var(--shell-gap) var(--shell-gap);
  border: 1px solid var(--sidebar-edge);
  border-radius: var(--radius);
  box-shadow: var(--shadow-sidebar);
  /* Скругление обязано резать содержимое: подсветка активного пункта
     идёт во всю ширину и без этого вылезала бы за срезанный угол. */
  overflow: hidden;
}
.sidebar .brand {
  display: flex;
  align-items: center;
  gap: 10px;
  font-weight: 700;
  font-size: 18px;
  letter-spacing: .01em;
  color: var(--white);
  padding: 4px 22px 22px;
}
/* Список разделов прокручивается ВНУТРИ блока: у меню теперь своя
   высота, и длинный список иначе вылез бы за срезанный край и пропал.
   min-height: 0 обязателен — без него потомок flex не сжимается ниже
   своего содержимого и прокрутка не включается. */
.sidebar nav { flex: 1; min-height: 0; overflow-y: auto; display: flex; flex-direction: column; }

/* Полоса прокрутки списка разделов — своя, а не родная.
   Firefox понимает scrollbar-width/scrollbar-color, остальные — ::-webkit-.
   Объявлены оба набора: правило, работающее в одном браузере из двух,
   и есть тот самый разъехавшийся интерфейс. */
.sidebar nav {
  scrollbar-width: thin;
  scrollbar-color: var(--sidebar-thumb) transparent;
}
.sidebar nav::-webkit-scrollbar { width: 6px; }
.sidebar nav::-webkit-scrollbar-track { background: transparent; }
.sidebar nav::-webkit-scrollbar-thumb {
  background: var(--sidebar-thumb);
  border-radius: var(--radius-pill);
}
.sidebar:hover nav { scrollbar-color: var(--sidebar-thumb-hover) transparent; }
.sidebar:hover nav::-webkit-scrollbar-thumb { background: var(--sidebar-thumb-hover); }
.sidebar nav a {
  display: flex;
  align-items: center;
  gap: 11px;
  padding: 11px 22px;
  color: rgba(255, 255, 255, .78);
  border-left: 3px solid transparent;
  font-size: 14px;
  font-weight: 500;
  transition: background .12s ease, color .12s ease;
}
.sidebar nav a:hover { background: rgba(255, 255, 255, .07); color: var(--white); text-decoration: none; }
.sidebar nav a.active {
  background: rgba(255, 255, 255, .12);
  color: var(--white);
  border-left-color: var(--white);
  font-weight: 600;
}
.sidebar nav a svg { opacity: .85; }
.sidebar nav a.active svg { opacity: 1; }
.sidebar nav a.sub-link { padding-left: 44px; font-size: 13.5px; }

.sidebar .nav-group-toggle {
  display: flex;
  align-items: center;
  gap: 11px;
  width: 100%;
  border: none;
  background: none;
  cursor: pointer;
  padding: 11px 22px;
  color: rgba(255, 255, 255, .78);
  font-size: 14px;
  font-weight: 500;
  font-family: inherit;
  border-left: 3px solid transparent;
}
.sidebar .nav-group-toggle:hover { background: rgba(255, 255, 255, .07); color: var(--white); }
.sidebar .nav-group-toggle .label { flex: 1; text-align: left; }
.sidebar .nav-group-toggle svg.chevron { transition: transform .18s ease; opacity: .6; }
.sidebar .nav-group-toggle[aria-expanded="true"] svg.chevron { transform: rotate(180deg); }
/* Раскрытие группы анимируется через grid-template-rows 0fr → 1fr.
   display: grid здесь обязателен: без него подпункты видны всегда. */
.sidebar .nav-group { display: grid; grid-template-rows: 0fr; transition: grid-template-rows .2s ease; }
.sidebar .nav-group-inner { overflow: hidden; }
.sidebar .nav-group-toggle.active { color: var(--white); border-left-color: var(--white); font-weight: 600; }

.sidebar .group-title {
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: .06em;
  color: rgba(255, 255, 255, .4);
  padding: 18px 22px 6px;
  font-weight: 600;
}

.main { flex: 1; min-width: 0; display: flex; flex-direction: column; }
.topbar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 16px 28px;
  background: var(--surface);
}

/* Шапка — такой же блок, как меню и карточки, а не полоса, приклеенная к
   верху окна.

   ПОЧЕМУ ОТДЕЛЬНЫМ ПРАВИЛОМ И С .app-shell. Слой макета грузится после
   нас и задаёт шапке своё: липкость (position: sticky; top: 0), высоту и
   боковое поле. Его файл обновляется целиком, править там нельзя —
   перебиваем отсюда, повысив вес селектора.

   ЛИПКОСТЬ ПРИШЛОСЬ ПОДВИНУТЬ. С top: 0 шапка при прокрутке прилипала
   вплотную к краю окна, и зазор, ради которого всё затевалось, пропадал
   ровно тогда, когда на него смотрят. Теперь она останавливается на том
   же расстоянии от края, что и меню.

   БОКОВОЕ ПОЛЕ — ИЗ ТОКЕНА СЛОЯ, А НЕ СВОИМ ЧИСЛОМ. Им же слой отступает
   содержимое, поэтому левый край шапки совпадает с краем карточек под
   ней сам собой. Своё число здесь означало бы второй список, который
   однажды разойдётся с первым; запасное значение — на случай, если слой
   не подключён. */
.app-shell .topbar {
  margin: var(--shell-gap) var(--gap-xl, 24px) 0;
  top: var(--shell-gap);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  box-shadow: var(--shadow-card);
}
.topbar h1 { font-size: 19px; margin: 0; font-weight: 700; color: var(--text); }

/* Заголовок сжимается и обрезается многоточием, а не выталкивает правый
   блок за экран: с правилом «Раздел и имя» заголовки стали длинными
   («График работ — ЖК „Алатау"»), и на телефоне целиком они не помещаются.
   Полное имя остаётся на самой странице и в title вкладки.

   Слой автора задаёт .topbar h1 { flex: 0 0 auto } с доводом «ужиматься
   должно правое крыло» — довод писался при коротких заголовках из одного
   раздела. Его файл не правим (обновляется целиком), перебиваем
   специфичностью: два класса против одного. Перенос по-прежнему запрещён —
   та беда, от которой автор защищался, не возвращается. */
.app-shell .topbar h1 {
  flex: 1 1 auto;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.topbar .user-chip { color: var(--text-muted); font-size: 13px; display: flex; align-items: center; gap: 12px; }

/* Счётчик непрочитанного поверх угла кнопки.

   Кнопке нужен position:relative — иначе счётчик уедет в угол ближайшего
   позиционированного предка, то есть куда-то в шапку. */
.btn-badged, .chat-launcher { position: relative; }
.btn-badge {
  position: absolute;
  top: -7px;
  right: -7px;
  min-width: 18px;
  height: 18px;
  padding: 0 4px;
  background: var(--danger);
  color: var(--white);
  font-size: 11px;
  line-height: 18px;
  font-weight: 600;
  text-align: center;
}

/* Выпадающая панель под кнопкой в шапке: уведомления, меню профиля.

   Оболочка — .popover-anchor: без неё панель считает координаты от
   страницы, а не от своей кнопки. */
.popover-anchor { position: relative; display: inline-block; }
.popover {
  position: absolute;
  right: 0;
  top: calc(100% + 6px);
  width: 340px;
  max-height: 400px;
  overflow-y: auto;
  background: var(--surface);
  border: 1px solid var(--border);
  box-shadow: var(--shadow-lg);
  z-index: 50;
  text-align: left;
}
.popover-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  padding: 10px 12px;
  border-bottom: 1px solid var(--border);
}
/* КНОПКА НЕ НАСЛЕДУЕТ ЦВЕТ ТЕКСТА — ЕЙ НАДО СКАЗАТЬ.
 *
 * Найдено 19.08.2026 по снимкам владельца: в тёмной теме фамилии в списке
 * упоминаний и текст уведомлений стали тёмно-серыми на тёмном фоне.
 *
 * Абзац цвет от родителя берёт, а `<button>` — нет: у неё свой браузерный
 * по умолчанию, и он тёмный в любой теме. Наши правила переопределяли вид
 * кнопки, помнили про шрифт (`font-family: inherit`) и забывали про цвет.
 * Примета оказалась точной: правил с унаследованным шрифтом и без цвета
 * нашлось ровно шесть, и все шесть — испорченные.
 *
 * ПОЧЕМУ `inherit`, А НЕ ПОДОБРАННЫЙ ЦВЕТ. Подобранный пришлось бы
 * переопределять в каждой теме и держать в голове при каждой правке
 * палитры. Наследование делает это само: родитель уже в теме.
 *
 * Сторож ButtonColorFollowsThemeTest держит правило на классе, а не на
 * списке: список из шести устарел бы на седьмом. */
.popover-item {
  display: block;
  width: 100%;
  text-align: left;
  border: none;
  border-bottom: 1px solid var(--border);
  padding: 10px 12px;
  background: transparent;
  font-family: inherit;
  color: inherit;
  font-size: 13px;
  cursor: pointer;
}
.popover-item.is-unread { background: var(--primary-light); }
.popover-item .when { color: var(--text-muted); font-size: 11px; margin-top: 2px; }

/* На телефоне панель шириной 340px, привязанная к правому краю своей
   кнопки, уезжает за левый край экрана. Отвязываем её от кнопки и
   растягиваем по ширине окна — привязка к кнопке нужна там, где рядом
   есть место, а на 375px его нет. */
@media (max-width: 760px) {
  .popover {
    position: fixed;
    top: 60px;
    left: 8px;
    right: 8px;
    width: auto;
    max-height: calc(100vh - 76px);
  }
}

.rates-strip { display: flex; align-items: center; gap: 6px; }
.rate-pill {
  background: var(--primary-light);
  color: var(--primary);
  padding: 3px 8px;
  font-size: 12px;
  font-weight: 600;
  white-space: nowrap;
}

/* min-width:0 обязателен: без него дочерний элемент шире экрана
   растягивает саму колонку, и вбок уезжает вся страница вместе с меню,
   а не только широкий блок. */
.content { padding: 26px 28px; flex: 1; min-width: 0; }

/* ---- Building blocks ---- */
.card {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  padding: 22px;
  box-shadow: var(--shadow);
  /* Широкая таблица прокручивается внутри карточки, а не тянет вбок всю
     страницу: иначе меню уезжает, а последние колонки просто обрезаются. */
  overflow-x: auto;
}
.card + .card { margin-top: 16px; }

/* ЗАГОЛОВОК РАЗДЕЛА ФОРМЫ НЕ НАКРЫВАЕТ ПОЛЯ ПОД СОБОЙ.
 *
 * Жалоба владельца 19.08.2026: «заголовки таких блоков перекрывают то, что
 * под ними, такого в проекте много». Замер подтвердил: перекрытие ровно
 * 44 пикселя, у КАЖДОГО заголовка раздела, ПОСТОЯННО — не только при
 * прокрутке. На одной карточке договора таких заголовков четыре.
 *
 * ЭТО СТОЛКНОВЕНИЕ ДВУХ ПРАВИЛ, КАЖДОЕ ИЗ КОТОРЫХ ПО ОТДЕЛЬНОСТИ ВЕРНО.
 *
 * Первое — строчкой выше: `overflow-x: auto`, чтобы широкая таблица не
 * тянула вбок всю страницу.
 *
 * Второе — в слое макета (noneerp-theme.css): заголовок раздела длинной
 * формы сделан липким, `position: sticky; top: 62px`, чтобы, пока крутишь
 * содержимое, было видно, какой это раздел. Шестьдесят два — высота шапки
 * страницы.
 *
 * А СЛОМАЛА ИХ СПЕЦИФИКАЦИЯ CSS: когда одна ось получает значение, отличное
 * от `visible`, вторая перестаёт быть `visible` сама и вычисляется как
 * `auto`. Карточка тем самым становится СВОИМ окном прокрутки. Липкий
 * заголовок цепляется уже к ней, а не к странице, и его 62 пикселя
 * отсчитываются от верха карточки — где у формы `padding-top: 0`.
 * Заголовок сразу упирается в порог и съезжает вниз на все 62, ложась на
 * подписи полей. Замер: по потоку он стоит на 192, нарисован на 254.
 *
 * ЛЕЧИТЬ НАДО НЕ ЗАГОЛОВОК, А ОКНО ПРОКРУТКИ. Сдвинуть `top` в ноль
 * убрало бы перекрытие, но липкость так и осталась бы бесполезной:
 * карточка не прокручивается — прокручивается страница, и заголовок всё
 * равно уехал бы вместе с ней. Замысел заработал впервые только после
 * того, как карточка перестала быть окном прокрутки.
 *
 * ШИРОКАЯ ТАБЛИЦА ВНУТРИ ФОРМЫ ПРОКРУТКУ СОХРАНЯЕТ: правило
 * `.card:has(.table-wide)` ниже задаёт `overflow: auto` и по весу (два
 * класса) сильнее, чем `form .card` (класс и тег). Порядок подключения
 * тут ни при чём — решает вес.
 *
 * ПРАВКА ЖИВЁТ ЗДЕСЬ, А НЕ РЯДОМ С ЛИПКИМ ПРАВИЛОМ: noneerp-theme.css —
 * внешний слой от автора макета, он приходит целиком и наше исправление
 * в нём не пережило бы следующей передачи. */
form .card { overflow: visible; }

/* ШАПКА ШИРОКОЙ ТАБЛИЦЫ НЕ УЕЗЖАЕТ ПРИ ПРОКРУТКЕ.
 *
 * Просьба владельца 19.08.2026: «когда вот такие таблицы скроллят, можно
 * закрепить верхнюю часть, чтобы было понятно, какие данные смотришь».
 * Речь про план-факт: шестьдесят восемь работ и десять колонок парами
 * «план / факт». Без заголовков колонка «Затраты» неотличима от «Маржи»,
 * а это деньги.
 *
 * ПОЧЕМУ ПРОКРУТКА У КАРТОЧКИ, А НЕ У СТРАНИЦЫ. Шапка страницы сама
 * липкая и стоит в 12 пикселях от края; заголовок таблицы, прилипший к
 * окну, уехал бы под неё. Значит нужен свой прокручиваемый блок —
 * решение владельца, и оно же уже принято у реестров (.registry-scroll).
 *
 * ПОЧЕМУ ЧЕРЕЗ :has, А НЕ ОБЁРТКОЙ В РАЗМЕТКЕ. Широких таблиц двадцать в
 * девяти видах, и обёртки не было у девятнадцати. Тридцать восемь ручных
 * правок ради того, что делается одним правилом, — это не аккуратность, а
 * способ ошибиться в одной из них.
 *
 * ВЫСОТА В ЕДИНИЦАХ ОКНА, А НЕ В ПИКСЕЛЯХ: на ноутбуке и на большом
 * мониторе «сколько влезает» — разные числа, а смысл один. Вычитаемое —
 * это шапка страницы, строка действий и отбор над таблицей. */
.card:has(.table-wide) {
  max-height: calc(100vh - 260px);
  min-height: 240px;
  overflow: auto;
}
.table-wide thead th {
  position: sticky;
  top: 0;
  z-index: 2;
  background: var(--surface);
  box-shadow: inset 0 -1px 0 var(--border);
}

.toolbar { display: flex; gap: 10px; align-items: center; margin-bottom: 18px; flex-wrap: wrap; }
.toolbar .spacer { flex: 1; }

/* Иконка-кнопка: только значок. Подпись есть, но её не видно.
 *
 * РАСКРЫТИЕ ПО НАВЕДЕНИЮ УБРАНО 19.08.2026. Жалоба владельца: «раскрывающиеся
 * кнопки при наведении сильно дёргают остальные элементы страницы».
 *
 * Так и было устроено: подпись лежала ВНУТРИ кнопки и росла с нуля до 220
 * пикселей. Кнопка — inline-flex, её ширина определяется содержимым, значит
 * росла и она, а за ней разъезжался весь ряд. В таблице, где таких кнопок
 * три в строке и двадцать строк, мышь через ряд превращала страницу в
 * гармошку.
 *
 * ПОЧЕМУ ПРОСТО УБРАТЬ, А НЕ СДЕЛАТЬ ВСПЛЫВАЮЩЕЙ. Потому что подсказка уже
 * есть: у каждой такой кнопки стоит `title` с тем же текстом, и её рисует
 * браузер поверх всего — её не обрежет ни контейнер с прокруткой, ни
 * карточка. Своя всплывающая подпись была бы третьим способом сказать одно
 * и то же и потребовала бы того же обхода обрезки, что и выпадающие списки.
 *
 * ПОДПИСЬ ОСТАЛАСЬ В РАЗМЕТКЕ, а не удалена: её читает программа чтения с
 * экрана, и на телефоне она показывается прямо в строке-карточке (правило
 * ниже, в @media). Значок без подписи там был бы загадкой.
 */
.btn-icon { padding: 8px 10px; overflow: hidden; }
.btn-icon .btn-label {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  white-space: nowrap;
  clip-path: inset(50%);
}
.btn {
  display: inline-flex;
  align-items: center;
  /* По центру и по горизонтали тоже. Пока стояло только align-items,
     кнопка с заданной шириной — в форме, в карточке реестра, в модалке —
     прижимала подпись к левому краю, и рядом с ней зияла пустота. */
  justify-content: center;
  gap: 6px;
  padding: 8px 16px;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--surface);
  color: var(--text);
  cursor: pointer;
  font-family: inherit;
  font-size: 14px;
  font-weight: 500;
  line-height: 1.3;
  /* Плавность только на цвете и тени. Анимировать размеры нельзя: кнопка,
     которая «дышит» при наведении, сдвигает соседние и промахивается мимо
     пальца на телефоне. */
  transition: background-color .15s ease, border-color .15s ease, box-shadow .15s ease;
}
.btn:hover { background: var(--bg); text-decoration: none; }
/* Фокус с клавиатуры виден отдельно от наведения мышью: человек, идущий
   по форме табом, должен понимать, где он находится. */
.btn:focus-visible { outline: none; box-shadow: 0 0 0 3px var(--primary-light); }
.btn-primary {
  background: var(--primary);
  border-color: var(--primary);
  color: var(--white);
  box-shadow: 0 2px 4px rgba(115, 103, 240, .35);
}
.btn-primary:hover { background: var(--primary-dark); border-color: var(--primary-dark); }
.btn-danger { color: var(--danger); border-color: var(--danger-light); background: var(--danger-light); }
.btn-danger:hover { background: rgba(234, 84, 85, .24); }
.btn-sm { padding: 5px 11px; font-size: 13px; border-radius: var(--radius-sm); }
/* Заблокированная кнопка обязана выглядеть заблокированной. Без этого
   человек жмёт её и решает, что система зависла. */
.btn[disabled], .btn:disabled {
  opacity: .55;
  cursor: not-allowed;
  box-shadow: none;
}

.form-group { margin-bottom: 14px; }
.form-group label { display: block; font-size: 13px; color: var(--text-muted); margin-bottom: 5px; font-weight: 500; }
.form-control, select.form-control, textarea.form-control {
  width: 100%;
  padding: 9px 12px;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  font-size: 14px;
  font-family: inherit;
  background: var(--surface);
  color: var(--text);
  transition: border-color .15s ease, box-shadow .15s ease;
}
.form-control:focus { outline: none; border-color: var(--primary); box-shadow: 0 0 0 3px var(--primary-light); }
/* Поле с ошибкой обведено красным до того, как человек полез читать
   подпись под ним: цвет замечают, текст — читают. */
.form-control[aria-invalid="true"], .form-control.is-invalid {
  border-color: var(--danger);
}
.form-control[aria-invalid="true"]:focus, .form-control.is-invalid:focus {
  box-shadow: 0 0 0 3px var(--danger-light);
}
.form-control[disabled], .form-control:disabled {
  background: var(--secondary-light);
  color: var(--text-disabled);
  cursor: not-allowed;
}
.form-error { color: var(--danger); font-size: 12px; margin-top: 4px; }
.form-row { display: grid; grid-template-columns: 1fr 1fr; gap: 14px; }
/* Поле и кнопка действия рядом: БИН и «Найти название». Кнопка не
   растягивается, поле забирает остаток. На узком экране переносится
   под поле, иначе для номера остаётся полстроки. */
.input-with-action { display: flex; gap: 8px; align-items: stretch; flex-wrap: wrap; }
.input-with-action .form-control { flex: 1 1 200px; min-width: 0; }
.input-with-action .btn { flex: 0 0 auto; white-space: nowrap; }
/* Форма в несколько колонок: число колонок подбирается по ширине экрана,
   поле на всю строку помечается утилитой .u-span-all. */
.form-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
  gap: 0 14px;
}

table.table { width: 100%; border-collapse: collapse; }
/* Ячейка 13px — плотный режим брифа. Было 13.5px: половина пикселя
   ниоткуда, и она же перебивала 13px из слоя автора, потому что
   селектор здесь весомее. */
table.table th, table.table td {
  text-align: left;
  padding: 8px 12px;
  border-bottom: 1px solid var(--border);
  font-size: 13px;
  line-height: 20px;
}
/* Заголовок колонки: 12px SemiBold без капса (бриф, решение 01.08.2026).
   Правило живёт и здесь, и в слое автора намеренно — селектор
   `table.table th` весомее, чем `.card th`, и слой автора его не
   перебивал. Расхождение было незаметно: слой честно ставил 12px без
   капса, а на экране всё равно стоял капс. */
table.table th { color: var(--text-muted); font-weight: 600; font-size: 12px; }
table.table tbody tr:hover { background: var(--bg); }

.badge {
  display: inline-block;
  padding: 3px 9px;
  border-radius: var(--radius-sm);
  font-size: 11.5px;
  font-weight: 600;
  letter-spacing: .02em;
  color: var(--white);
  white-space: nowrap;
  background: var(--text-muted);
}

.empty-state { text-align: center; color: var(--text-muted); padding: 40px 0; }
.empty-state-title { font-weight: 600; margin-bottom: 4px; color: var(--text); }

/* Страницы ошибок: 404, 403, 419, 429, 500, 503.

   Свой маленький набор правил, потому что у этих страниц нет ни бокового
   меню, ни шапки — они открываются и без входа в систему, и в момент,
   когда сломано всё остальное. Внутри — тот же блок .empty-state, что и
   в пустых списках: человек узнаёт карточку и понимает, что он всё ещё
   в системе, а не провалился в чужую страницу. */
.error-shell {
  max-width: 520px;
  margin: 0 auto;
  padding: 72px 20px;
  text-align: center;
}
.error-brand { display: inline-block; margin-bottom: 40px; }
/* Номер ошибки крупный, но приглушённый: он нужен поддержке, а не
   человеку, и не должен перебивать фразу под ним. */
.error-code {
  font-size: 44px;
  font-weight: 700;
  line-height: 1;
  color: var(--border);
  margin-bottom: 12px;
}
.error-shell .empty-state { padding: 0; }
.error-actions {
  display: flex;
  gap: 10px;
  justify-content: center;
  flex-wrap: wrap;
  margin-top: 28px;
}

.form-label { display: block; font-size: 13px; color: var(--text-muted); margin-bottom: 5px; font-weight: 500; }

/* Пустая подпись-распорка: держит высоту строки, чтобы кнопка в конце
   ряда фильтров встала вровень с полями. Пустой элемент схлопнулся бы,
   поэтому высота задаётся явно. */
.form-label-spacer { height: 18px; }
.form-hint { color: var(--text-muted); font-size: 12px; margin-top: 4px; }

/* Поле пароля с глазиком.

   Кнопка лежит поверх поля, а не рядом: рядом она отняла бы ширину
   у самого поля, а на телефоне там и так тесно. Полю добавлен отступ
   справа, чтобы длинный пароль не заезжал под кнопку. */
.password-field { position: relative; }
.password-field .form-control { padding-right: 42px; }

.password-eye {
  position: absolute;
  top: 0;
  right: 0;
  width: 40px;
  height: 100%;
  display: flex;
  align-items: center;
  justify-content: center;
  border: none;
  background: transparent;
  color: var(--text-muted);
  cursor: pointer;
}

.password-eye:hover { color: var(--primary); }

/* Показан открытый глаз, пока пароль скрыт: значок обещает действие,
   а не докладывает о состоянии. Перечёркнутый — когда пароль виден. */
.password-eye .eye-closed { display: none; }
.password-eye.is-shown .eye-open { display: none; }
.password-eye.is-shown .eye-closed { display: block; }

.alert { padding: 11px 14px; border-radius: var(--radius); margin-bottom: 16px; font-size: 13.5px; }
.alert-error { background: var(--danger-light); color: var(--danger-text); border: 1px solid var(--danger-line); }
.alert-success { background: var(--success-light); color: var(--success-text); border: 1px solid var(--success-line); }
/* Предупреждение: не ошибка, но требует внимания — незакрытый остаток,
   приближающийся срок, неполные данные. */
.alert-warning { background: var(--warning-light); color: var(--warning-text); border: 1px solid var(--warning-line); }

/* ---- Kanban board ---- */
.board { display: flex; gap: 14px; overflow-x: auto; padding-bottom: 8px; }
/* Граница у колонки видимая: одного фона мало — на светлой теме колонка
   сливается со страницей, и глазу не за что зацепиться, где кончается
   «В работе» и начинается «Проверка». */
.board-col { flex: 0 0 270px; background: var(--bg); padding: 10px; border: 1px solid var(--border); }
.board-col-head {
  display: flex;
  align-items: center;
  gap: 8px;
  margin: -10px -10px 10px;
  padding: 8px 10px;
  border-bottom: 1px solid var(--border);
  font-weight: 600;
}
.board-col-head .count { color: var(--text-muted); font-weight: 400; }
.board-card {
  background: var(--surface);
  padding: 10px 12px;
  margin-bottom: 8px;
  border: 1px solid var(--border);
  box-shadow: var(--shadow);
  display: block;
}
.board-card:hover { border-color: var(--primary); text-decoration: none; }
.board-card[draggable="true"] { cursor: grab; }
.board-card.dragging { opacity: 0.4; }

/* Перенос карточки пальцем (#221).
   Встроенного перетаскивания на сенсорном экране нет: палец не
   порождает dragstart. Поэтому там, где указатель грубый, у карточки
   появляется выбор колонки списком; на мыши работает перенос, и
   вторая дорога только загромождала бы доску. */
/* ВЗЯТАЯ ПАЛЬЦЕМ КАРТОЧКА И ЕЁ ПРИЗРАК.

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

   Теперь перенос делает public/js/board-drag.js, а здесь — только вид:
   исходная карточка бледнеет, а за пальцем едет её копия. Копия, а не
   сама карточка: вынь мы карточку из колонки, соседние подпрыгнули бы
   вверх, и человек потерял бы из виду место, откуда взял. */
.board-card--taken { opacity: .35; }

.board-card--ghost {
  position: fixed;
  z-index: 200;
  pointer-events: none;
  opacity: .95;
  transform: scale(1.03);
  box-shadow: var(--shadow-pop);
}
/* РАСКЛАДКА КАРТОЧКИ ДОСКИ.

   Было: номер и название в одной строке, а под ними четыре одинаковых
   серых строки — объект, сроки, часы, исполнитель. Номер при этом ломался
   пополам («№» и «00003» на разных строках) и спорил с названием за
   первое место. Владелец 21.08.2026: «Расположение самих элементов
   карточки сделай читабельнее».

   Стало: служебная шапка отдельной строкой — номер слева, состояние
   справа; название следом, крупнее и жирнее всего остального; под ним
   строки с иконками. Иконка отвечает на вопрос «что это за число», не
   тратя на ответ слова: часы — срок, человек — исполнитель, здание —
   объект.

   Порядок строк — по тому, как читают карточку: где (объект), когда
   (срок и часы), кто. Исполнитель последний намеренно: на своей доске
   человек и так знает, что задачи его, а на общей ищет глазами объект. */
.board-card .num { font-weight: 600; font-size: 13px; display: flex; align-items: center; gap: 6px; }

.board-card__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 6px;
  margin-bottom: 4px;
}

.board-card__num {
  font-size: 11px;
  color: var(--text-muted);
  letter-spacing: .02em;
  white-space: nowrap;
}

.board-card__title {
  font-weight: 600;
  font-size: 13.5px;
  line-height: 1.3;
  color: var(--text);
  margin-bottom: 6px;
}

/* Строка с иконкой. Иконка приглушена: она подсказка к числу, а не
   украшение — в ряду из трёх строк три ярких значка перетянули бы на себя
   внимание с того, ради чего карточку и читают. */
.board-card__line {
  display: flex;
  align-items: center;
  gap: 5px;
  color: var(--text-muted);
  font-size: 12px;
  margin-top: 3px;
}

.board-card__line svg { opacity: .75; }

/* Метка состояния: иконка и слово.

   Не заливка во всю метку, а мягкая подложка под цветным текстом. Ярких
   пятен на доске и так хватает — колонки, кнопки, ссылки; метка обязана
   выделяться среди СЕРОГО текста карточки, а не спорить с интерфейсом. */
.board-card__mark {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  font-size: 11px;
  font-weight: 600;
  padding: 3px;
  border-radius: var(--radius-pill);
  white-space: nowrap;
}

.board-card__mark--late { color: var(--mark-late-fg); background: var(--mark-late-bg); }
.board-card__mark--term { color: var(--mark-term-fg); background: var(--mark-term-bg); }

/* ПОЛОСА У КРАЯ КАРТОЧКИ.

   Значок в шапке отвечает на вопрос «что с этой задачей», но чтобы его
   заметить, карточку надо прочесть. Полоса видна раньше чтения: пробегая
   взглядом по колонке из двадцати карточек, человек считает красные, не
   разбирая ни одной.

   На спокойной карточке полосы нет вовсе — иначе доска стала бы рябой, и
   выделяться перестало бы всё сразу. */
.board-card--late,
.board-card--term { position: relative; padding-left: 14px; }

.board-card--late::before,
.board-card--term::before {
  content: '';
  position: absolute;
  left: 6px;
  top: 10px;
  bottom: 10px;
  width: 3px;
  border-radius: var(--radius-pill);
}

.board-card--late::before { background: var(--danger); }

/* КНОПКИ СВЯЗИ НА КАРТОЧКЕ ЗАЯВКИ.

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

   Человек открывает эту карточку одной рукой, стоя у ворот. Три
   одинаковые серые кнопки он будет разбирать глазами, три разноцветные —
   узнает. */
.btn-whatsapp {
  background: var(--whatsapp);
  border-color: var(--whatsapp);
  color: var(--white);
}

.btn-whatsapp:hover { filter: brightness(1.06); color: var(--white); }

.btn-route {
  background: var(--route);
  border-color: var(--route);
  color: var(--white);
}

.btn-route:hover { filter: brightness(1.06); color: var(--white); }

.board-card--term::before { background: var(--warning); }

.board-card .device { color: var(--text); font-size: 13.5px; margin: 3px 0; }
.board-card .customer { color: var(--text-muted); font-size: 12.5px; }

.board-col.drag-over { background: var(--primary-light); outline: 2px dashed var(--primary); outline-offset: -2px; }

/* ═══ Подсказки при наборе ══════════════════════════════════════

   ЧТО БЫЛО. Найденное показывалось россыпью кнопок ПОД полем: пять
   подсказок раздвигали форму на две строки, восемь — на четыре, и поля,
   стоявшие ниже, уезжали вниз прямо во время набора. Человек печатает,
   а форма под курсором шевелится.

   ЧТО СТАЛО. Список поверх формы, как во всех полях с подбором: он
   ничего не двигает, закрывается по Esc и по щелчку мимо, и читается
   столбцом, а не строкой из кнопок разной длины.

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

   ЗНАЧЕНИЕ СПРАВА — не украшение. У товара это единица и остаток: по ним
   выбирают между «Арматура А500С д.12» и «Арматура А240 d 12», не
   открывая ничего. У техники — мощность: названия там длинные и
   различаются как раз ею. */
.suggest-wrap { position: relative; }

.suggest {
  position: absolute;
  z-index: 90;
  top: calc(100% + 4px);
  left: 0;
  right: 0;
  max-height: 320px;
  overflow-y: auto;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  box-shadow: var(--shadow-pop);
  padding: 4px 0;
}

.suggest__group {
  padding: 6px 12px 2px;
  font-size: 10.5px;
  font-weight: 600;
  letter-spacing: .04em;
  text-transform: uppercase;
  color: var(--text-muted);
}

.suggest__item {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  width: 100%;
  padding: 7px 12px;
  border: 0;
  background: none;
  color: var(--text);
  font-size: 13px;
  text-align: left;
  cursor: pointer;
}

/* Наведение мышью и выбор с клавиатуры выглядят одинаково: иначе человек,
   пришедший со стрелок, ищет глазами вторую подсветку. */
.suggest__item:hover,
.suggest__item.is-active { background: var(--tint-strong); }

.suggest__name { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* Совпавшая часть — жирным. Показывает, ПОЧЕМУ строка нашлась. */
.suggest__name b { font-weight: 700; }

.suggest__meta { flex-shrink: 0; color: var(--text-muted); font-size: 12px; white-space: nowrap; }

/* Заведение новой позиции — тем же цветом, что и ссылки: это действие,
   а не выбор из имеющегося. */
.suggest__item--new { color: var(--primary); }

.suggest__foot {
  display: flex;
  gap: 12px;
  justify-content: center;
  padding: 6px 12px 4px;
  border-top: 1px solid var(--border);
  margin-top: 4px;
  font-size: 11px;
  color: var(--text-muted);
}

/* На касании подсказка по клавишам бессмысленна, а строки должны быть
   не меньше сорока четырёх точек: палец не попадает в тринадцать. */
@media (pointer: coarse) {
  .suggest__foot { display: none; }
  .suggest__item { padding: 12px; min-height: 44px; font-size: 14px; }
  .suggest { max-height: 60vh; }
}
.filter-tabs { display: flex; gap: 6px; flex-wrap: wrap; }
.filter-tab {
  border: 1px solid var(--border);
  background: var(--surface);
  padding: 6px 12px;
  font-size: 12.5px;
  font-weight: 500;
  color: var(--text-muted);
  cursor: pointer;
}
.filter-tab:hover { border-color: var(--primary); color: var(--text); }
.filter-tab.active { background: var(--primary); border-color: var(--primary); color: var(--white); }
.filter-tab-term.active { background: var(--warning); border-color: var(--warning); }
.filter-tab-overdue.active { background: var(--danger); border-color: var(--danger); }
.filter-tab-debt.active { background: var(--info); border-color: var(--info); }

.status-dot { display: inline-block; width: 9px; height: 9px; margin-right: 6px; }

/* ================================================================
   Библиотека компонентов
   Всё оформление живёт здесь, а не в инлайновых style="" шаблонов:
   так экраны выглядят одинаково и правятся в одном месте.
   ================================================================ */

/* --- Заголовок карточки и секции --- */
.card-title {
  margin: 0 0 14px;
  font-size: 15px;
  font-weight: 600;
  color: var(--text);
}
.card-title .sub {
  font-weight: 400;
  font-size: 12.5px;
  color: var(--text-muted);
  margin-left: 8px;
}
.card-tight { padding: 14px 16px; }

/* --- Плитка показателя (дашборд, отчёты) --- */
.stat-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(190px, 1fr));
  gap: 14px;
}

.stat {
  background: var(--surface);
  border: 1px solid var(--border);
  border-left: 3px solid var(--primary);
  padding: 16px 18px;
}
.stat .stat-label {
  font-size: 12px;
  color: var(--text-muted);
  font-weight: 600;
}
/* Метрика — 25px/34 Bold по брифу. Как и с шапкой колонки, значение
   стоит здесь: `.stat .stat-value` весомее одиночного класса в слое
   автора, и его 25px до экрана не доходили. */
.stat .stat-value {
  font-size: 25px;
  font-weight: 700;
  color: var(--text);
  margin-top: 6px;
  line-height: 34px;
  white-space: nowrap;
}

/* Ступени размера для длинных значений — сразу после базового правила, а
   не раньше: у них та же сила селектора, и стоящее ниже побеждает. Класс
   ставит <x-stat> по длине строки. */
.stat .stat-value--sm { font-size: 18px; line-height: 26px; }
.stat .stat-value--xs { font-size: 15px; line-height: 22px; }
.stat .stat-hint { font-size: 12.5px; color: var(--text-muted); margin-top: 4px; }
.stat-success { border-left-color: var(--success); }
.stat-danger { border-left-color: var(--danger); }
.stat-warning { border-left-color: var(--warning); }
.stat-info { border-left-color: var(--info); }

/* --- Индикатор выполнения --- */
.progress {
  background: var(--border);
  overflow: hidden;
  height: 8px;
  width: 100%;
}
.progress-bar { height: 100%; background: var(--primary); }
.progress-lg { height: 14px; }
.progress-sm { height: 6px; }
.progress-success .progress-bar { background: var(--success); }
.progress-danger .progress-bar { background: var(--danger); }
.progress-warning .progress-bar { background: var(--warning); }
.progress-wrap { display: flex; align-items: center; gap: 8px; }
.progress-wrap .progress { flex: 1; min-width: 60px; }
.progress-value { font-size: 12.5px; color: var(--text-muted); white-space: nowrap; font-variant-numeric: tabular-nums; }

/* --- Вложенная панель (списки выбора, блоки внутри форм) --- */
.panel {
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 10px 12px;
  background: var(--surface);
}
.panel + .panel { margin-top: 8px; }
.panel-scroll { max-height: 180px; overflow-y: auto; }
.panel-muted { background: var(--bg); }

/* Тон панели: цветом отмечена только левая кромка. Заливать весь блок
   тревожным цветом — значит кричать там, где достаточно указать. */
.panel-warning { border-left: 3px solid var(--warning); }
.panel-danger { border-left: 3px solid var(--danger); }
.panel-success { border-left: 3px solid var(--success); }

/* --- Пары «подпись — значение» на карточках --- */
.def-list { display: grid; grid-template-columns: auto 1fr; gap: 6px 14px; font-size: 13.5px; }
.def-list dt { color: var(--text-muted); }
.def-list dd { margin: 0; }

/* --- Бейджи по смыслу состояния --- */
.badge-success { background: var(--success); }
.badge-danger { background: var(--danger); }
.badge-warning { background: var(--warning); }
.badge-info { background: var(--info); }
.badge-primary { background: var(--primary); }
.badge-muted { background: var(--text-muted); }
.badge-outline {
  background: transparent;
  color: var(--text-muted);
  border: 1px solid var(--border);
}
/* Приглушённый вариант для «спокойных» отметок: имя ответственного, метка. */
.badge-soft { background: var(--primary-light); color: var(--primary); }
.badge-inline { display: inline-flex; align-items: center; gap: 6px; }

/* Подпись поля на карточке: «Адрес», «Ответственный», «Срок». */
.meta-label { font-size: 12px; color: var(--text-muted); margin-bottom: 2px; }
.meta-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
  gap: 14px;
  margin-bottom: 14px;
}

/* --- Модальное окно --- */
.modal-backdrop {
  position: fixed;
  inset: 0;
  background: rgba(19, 23, 71, .45);
  display: flex;
  align-items: flex-start;
  justify-content: center;
  padding: 60px 20px 20px;
  z-index: 100;
  overflow-y: auto;
}
.modal {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-lg);
  width: 100%;
  max-width: 560px;
}
.modal-wide { max-width: 860px; }
.modal-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: 16px 20px;
  border-bottom: 1px solid var(--border);
}
.modal-head h3 { margin: 0; font-size: 15px; font-weight: 600; }
.modal-close {
  border: none;
  background: none;
  cursor: pointer;
  font-size: 20px;
  line-height: 1;
  color: var(--text-muted);
  padding: 0 4px;
  font-family: inherit;
}
.modal-close:hover { color: var(--text); }
.modal-body { padding: 20px; }
.modal-foot {
  display: flex;
  gap: 10px;
  justify-content: flex-end;
  padding: 14px 20px;
  border-top: 1px solid var(--border);
  background: var(--bg);
}

/* --- Слитная группа кнопок-переключателей (разрезы списков) --- */
.btn-group { display: inline-flex; }
.btn-group .btn + .btn { margin-left: -1px; }
.btn-group .btn.btn-primary { position: relative; z-index: 1; }

/* --- Вкладки внутри страницы --- */
.tabs { display: flex; gap: 2px; border-bottom: 1px solid var(--border); margin-bottom: 18px; }
.tab {
  border: 1px solid transparent;
  border-bottom: none;
  background: none;
  padding: 9px 16px;
  font-family: inherit;
  font-size: 13.5px;
  font-weight: 500;
  color: var(--text-muted);
  cursor: pointer;
  margin-bottom: -1px;
}
.tab:hover { color: var(--text); }
.tab.active {
  background: var(--surface);
  border-color: var(--border);
  border-bottom: 1px solid var(--surface);
  color: var(--primary);
  font-weight: 600;
}

/* --- Строка фильтров --- */
/* Строка полей: и фильтры над списком, и форма в несколько колонок.

   Выравнивание по верху, а не по низу. По низу поле с подсказкой
   приподнималось на её высоту, и в одном ряду поля стояли лесенкой —
   форма выглядела съехавшей. Кнопке в конце строки фильтров ровняться
   помогает пустая подпись (x-field spacer), а не выравнивание всей
   сетки. */
.filters {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
  gap: 12px;
  align-items: start;
  margin-bottom: 16px;
}

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

   .form-row ДОБАВЛЕН 19.08.2026 ПО ЖАЛОБЕ ВЛАДЕЛЬЦА «поля не по одной
   линии, такого в системе полно». Правило было записано для фильтров и
   многоколоночной формы, а ряд из двух-четырёх полей его не получал —
   и подписи в нём вставали на разной высоте. Одно и то же правило,
   забытое у третьего селектора: списки, набранные руками, устаревают. */
.filters .form-group > label,
.form-grid .form-group > label,
.form-row .form-group > label,
.filters .form-label,
.form-grid .form-label,
.form-row .form-label { min-height: 18px; }
.filters .form-group { margin-bottom: 0; }

/* КНОПКА В РЯДУ НЕ ПОДНИМАЕТ СВОЮ ЯЧЕЙКУ НА ЧЕТЫРЕ ПИКСЕЛЯ.
 *
 * Жалоба владельца 19.08.2026: «вот такие места не в ряд стоят». Разница
 * была ровно четыре пикселя, и она не случайна.
 *
 * Кнопка — строчный элемент: она стоит на базовой линии, а строка под ней
 * оставляет место для свисающих букв. У поля такого места нет: там блок.
 * Ячейки становятся разной высоты — 66 против 62, — и при выравнивании
 * ряда по нижнему краю подпись над кнопкой поднимается над соседней.
 *
 * Проверено на живом экране дебиторки: до правила верх ячеек 235 и 231,
 * после — 231 и 231.
 *
 * vertical-align: top снимает именно это: коробка перестаёт висеть на
 * базовой линии, и запас под буквы не появляется. */
.filters .form-group > .btn,
.form-row .form-group > .btn,
.form-grid .form-group > .btn { vertical-align: top; }

/* ================================================================
   Диаграмма Ганта
   Полосы позиционируются в пикселях от левого края полотна: так
   масштабирование и прокрутка работают предсказуемо, а перетаскивание
   пересчитывается в дни делением на ширину дня.
   ================================================================ */
/* График владеет своей прокруткой, а не отдаёт её странице.
   БЫЛО: на объекте в 68 работ виджет вырастал до 2847px при окне 900, и
   горизонтальная полоса оказывалась на 2300px ниже видимой области. Чтобы
   уехать вправо, надо было прокрутить страницу до самого низа, подвигать
   полосу и вернуться — а нужные строки к этому моменту уже за экраном.
   Мерил в браузере: полотно шире видимой части в 5,6 раза.
   СТАЛО: высота ограничена окном, обе полосы всегда на месте. */
.gantt {
  display: flex;
  border: 1px solid var(--border);
  overflow: hidden;
  /* 480px — это то, что стоит над графиком и не меняется: полоса разделов,
     вкладки объекта, легенда, пояснение про критический путь и кнопки
     навигации. Мерил в браузере после каждой добавленной строки: пояснение
     в две строки сдвинуло низ виджета на 918 при окне 900, то есть за край.
     Запас на случай, если заголовок объекта переедет на две строки. */
  max-height: calc(100vh - 480px);
  min-height: 320px;
}

.gantt-legend { display: flex; gap: 16px; flex-wrap: wrap; font-size: 12px; color: var(--text-muted); align-items: center; }
.gantt-swatch { display: inline-block; width: 14px; height: 10px; vertical-align: middle; margin-right: 4px; }
.gantt-swatch-normal { background: var(--primary); }
.gantt-swatch-critical { background: var(--danger); }
.gantt-swatch-done { background: var(--success); }
.gantt-swatch-baseline { background: transparent; border: 1px dashed var(--text-muted); }
/* Квадратик нерабочих дней плотнее полосы на полотне: на 14×10 пикселях
   семипроцентная заливка не видна вовсе. */
.gantt-swatch-offday { background: var(--text-muted); opacity: .22; }
/* На квадратике 14×10 штриховка должна читаться, поэтому она плотнее, чем
   на самой полосе: там под ней есть цвет, здесь — ничего. */
.gantt-swatch-late {
  background: repeating-linear-gradient(
    45deg,
    color-mix(in srgb, var(--danger) 85%, transparent) 0 3px,
    color-mix(in srgb, var(--danger) 25%, transparent) 3px 6px
  );
}

/* Левая колонка с названиями не уезжает при горизонтальной прокрутке. */
.gantt-labels {
  flex: 0 0 260px;
  border-right: 1px solid var(--border);
  background: var(--surface);
  z-index: 2;
  /* Своя вертикальная прокрутка, ведомая от полотна: полосу здесь прячем,
     иначе их будет две и они будут спорить. */
  overflow: hidden;
}
.gantt-label {
  height: 30px;
  display: flex;
  align-items: center;
  gap: 6px;
  padding-right: 8px;
  font-size: 12.5px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  border-bottom: 1px solid var(--border);
}
.gantt-label a { color: var(--text); overflow: hidden; text-overflow: ellipsis; min-width: 0; flex: 1 1 auto; }
/* Ответственный — вторая по важности часть подписи, поэтому сжимается
   первым и никогда не переносится.
   БЕЗ ЭТОГО СТРОКА ЛОМАЛАСЬ. Тема задаёт .u-note { text-wrap: pretty },
   а это включает перенос обратно поверх унаследованного nowrap. «Ковалёв
   Игорь Петрович» вставал в две строки по 15px в строке высотой 30px, и
   overflow: hidden срезал вторую по глифам. Видно только на живых данных
   с длинными именами. */
.gantt-label .u-note { text-wrap: nowrap; overflow: hidden; text-overflow: ellipsis; min-width: 0; flex: 0 1 auto; }
.gantt-toggle {
  border: none;
  background: none;
  cursor: pointer;
  font-size: 11px;
  color: var(--text-muted);
  padding: 0 2px;
  font-family: inherit;
}

/* Полотно прокручивается в обе стороны, и обе полосы всегда видны. */
.gantt-scroll { flex: 1; overflow: auto; overscroll-behavior-x: contain; }
.gantt-canvas { position: relative; }

/* Шкала месяцев липнет к верху полотна: уехал на сотую строку — всё равно
   видно, какой месяц под курсором. Без этого приходилось возвращаться
   наверх, чтобы понять, где ты. */
.gantt-head {
  height: 40px;
  position: sticky;
  top: 0;
  z-index: 4;
  border-bottom: 1px solid var(--border);
  background: var(--bg);
}
.gantt-months { position: relative; height: 20px; }
.gantt-month {
  position: absolute;
  top: 0;
  height: 20px;
  line-height: 20px;
  font-size: 11.5px;
  font-weight: 600;
  color: var(--text);
  border-left: 1px solid var(--border);
  padding-left: 5px;
  overflow: hidden;
  white-space: nowrap;
  text-transform: capitalize;
}
.gantt-ticks { position: relative; height: 20px; }
.gantt-tick {
  position: absolute;
  top: 0;
  font-size: 10.5px;
  color: var(--text-muted);
  border-left: 1px solid var(--border);
  padding-left: 3px;
  line-height: 20px;
  white-space: nowrap;
}
.gantt-tick-weekend { color: var(--danger); }

.gantt-body { position: relative; }

/* Нерабочие дни — фон под сеткой и полосами (#347). Заливка бледная
   намеренно: это причина, по которой полоса длиннее, а не событие графика.
   Заметный цвет здесь спорил бы с критическим путём, ради которого на
   график и смотрят. */
.gantt-offday { position: absolute; top: 0; bottom: 0; background: var(--text-muted); opacity: .07; z-index: 0; }

.gantt-grid-line { position: absolute; top: 0; bottom: 0; width: 1px; background: var(--border); opacity: .5; }
.gantt-today { position: absolute; top: 0; bottom: 0; width: 2px; background: var(--warning); z-index: 3; }
.gantt-arrows { position: absolute; top: 0; left: 0; pointer-events: none; z-index: 1; }

.gantt-row { position: absolute; left: 0; right: 0; border-bottom: 1px solid var(--border); }
.gantt-nodate { position: absolute; left: 8px; top: 8px; font-size: 11px; color: var(--text-muted); }

.gantt-bar {
  position: absolute;
  top: 5px;
  height: 20px;
  background: var(--primary-light);
  border: 1px solid var(--primary);
  overflow: hidden;
  z-index: 2;
}
.gantt-fill { height: 100%; background: var(--primary); opacity: .75; }
.gantt-bar-text {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  padding-left: 5px;
  font-size: 10.5px;
  font-weight: 600;
  color: var(--text);
  pointer-events: none;
  /* Выше штриховки просрочки: она рисуется псевдоэлементом после текста и
     без этого закрыла бы процент. */
  z-index: 2;
}
.gantt-bar-critical { background: var(--danger-light); border-color: var(--danger); }
.gantt-bar-critical .gantt-fill { background: var(--danger); }
.gantt-bar-done { background: var(--success-light); border-color: var(--success); }
.gantt-bar-done .gantt-fill { background: var(--success); }

.gantt-bar-group { top: 9px; height: 12px; background: var(--text-muted); border-color: var(--text); }
.gantt-bar-group .gantt-fill { background: var(--text); }

/* Просроченная незакрытая работа: срок прошёл, а работа не закрыта.
   Штриховка поверх обычного заполнения, а не сплошная заливка — иначе
   не отличить от критического пути, который сплошной danger. Работа
   бывает и просроченной, и критической одновременно: тогда под
   штриховкой красная полоса, и видно оба признака сразу. */
.gantt-bar-late { border-color: var(--danger); }
.gantt-bar-late::after {
  content: '';
  position: absolute;
  inset: 0;
  z-index: 1;
  pointer-events: none;
  background: repeating-linear-gradient(
    45deg,
    color-mix(in srgb, var(--danger) 55%, transparent) 0 3px,
    transparent 3px 6px
  );
}

/* Базовый план — полупрозрачная полоса под фактической. */
.gantt-bar-baseline {
  top: 22px;
  height: 5px;
  background: transparent;
  border: 1px dashed var(--text-muted);
  z-index: 1;
}

.gantt-draggable { cursor: grab; }
.gantt-draggable:active { cursor: grabbing; }
.gantt-handle {
  position: absolute;
  right: 0;
  top: 0;
  bottom: 0;
  width: 6px;
  cursor: ew-resize;
  background: rgba(19, 23, 71, .25);
}

/* Цепочка при наведении (библиотека, Г2): работы цепочки получают тень,
   стрелки между ними — primary 2px. Цвет полос не трогаем: критическая
   остаётся красной и внутри подсвеченной цепочки. */
.gantt-bar.gantt-chain { box-shadow: 0 2px 6px color-mix(in srgb, var(--primary) 40%, transparent); }
.gantt-arrows g.gantt-chain polyline { stroke: var(--primary); stroke-width: 2; }
.gantt-arrows g.gantt-chain polygon { fill: var(--primary); }

/* Выбранная работа — адресат клавиш Shift/Alt+стрелки. Обводка, а не
   перекраска: цвет полосы занят состоянием (критическая, готова). */
.gantt-bar.gantt-selected { outline: 2px solid var(--primary); outline-offset: 1px; }

/* ----------------------------------------------------------------
   Мобильный график (библиотека, Г4). Полотно на телефоне нечитаемо,
   поэтому до 768px вместо него список строк: название, статус,
   мини-шкала всего срока с маркером «сегодня». Только чтение.
   ---------------------------------------------------------------- */
.gantt-mobile { display: none; }

.gantt-mrow {
  display: flex;
  flex-direction: column;
  gap: 7px;
  padding: 12px 4px;
  border-bottom: 1px solid var(--border);
  color: var(--text);
  text-decoration: none;
  min-height: var(--touch-target);
  /* НА ВСЮ ШИРИНУ И ТЕКСТ ВЛЕВО. В кабинете строка — <a> (блочная, тянется
     сама), на витрине — <button> (по строке стучат и двигают работу), а
     кнопка по умолчанию жмётся к содержимому и центрит текст. Без этого
     витринные карточки выходили разной ширины с рваным правым краем. */
  width: 100%;
  text-align: left;
}
.gantt-mrow:hover { text-decoration: none; }

.gantt-mrow__top { display: flex; align-items: center; gap: 8px; }
.gantt-mrow__name {
  flex: 1;
  font-size: var(--text-body-sm);
  font-weight: 600;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.gantt-mpill {
  font-size: var(--text-caption);
  font-weight: 700;
  border-radius: var(--radius-pill);
  padding: 2px 8px;
  white-space: nowrap;
}
.gantt-mpill--done { color: var(--success); background: var(--success-bg); }
.gantt-mpill--work { color: var(--primary); background: var(--primary-bg); }
.gantt-mpill--late { color: var(--danger); background: var(--danger-bg); }
.gantt-mpill--plan { color: var(--text-secondary); background: var(--bg); }

.gantt-mrow__bottom { display: flex; align-items: center; gap: 8px; }
.gantt-mrow__dates {
  flex: 0 0 78px;
  font-size: var(--text-caption);
  color: var(--text-secondary);
  font-variant-numeric: tabular-nums;
}

.gantt-mtrack {
  flex: 1;
  position: relative;
  height: 8px;
  border-radius: var(--radius-badge);
  background: var(--bg);
}
.gantt-mplan {
  position: absolute;
  top: 0;
  bottom: 0;
  border-radius: var(--radius-badge);
  overflow: hidden;
  background: var(--border);
}
.gantt-mplan--late { background: color-mix(in srgb, var(--danger) 30%, transparent); }
.gantt-mfact { position: absolute; left: 0; top: 0; bottom: 0; background: var(--primary); }
.gantt-mplan--done .gantt-mfact { background: var(--success); }
.gantt-mplan--late .gantt-mfact { background: var(--danger); }

/* Маркер «сегодня» — warning, как вертикальная линия на полотне:
   один и тот же смысл обязан выглядеть одинаково в обоих видах. */
.gantt-mtoday {
  position: absolute;
  top: -3px;
  bottom: -3px;
  width: 2px;
  border-radius: var(--radius-badge);
  background: var(--warning);
}

.gantt-mav {
  flex: 0 0 auto;
  width: 22px;
  height: 22px;
  border-radius: 50%;
  /* Тот же цвет, что у аватара в шапке: инициалы человека везде одинаковые. */
  background: var(--user-chip-avatar);
  color: #fff;
  font-size: 8px;
  font-weight: 700;
  display: flex;
  align-items: center;
  justify-content: center;
}

@media (max-width: 768px) {
  /* Полотно и его навигация уходят целиком: панорама, клавиши и
     перетаскивание — настольные инструменты. */
  .gantt { display: none; }
  .gantt-mobile { display: block; }
}

/* ================================================================
   Утилиты раскладки
   Небольшой набор для повторяющихся мелочей: отступы, выравнивание,
   приглушённый текст. Нужен, чтобы не плодить одинаковые style=""
   в шаблонах. Всё сложнее пары свойств — оформляем компонентом.
   ================================================================ */
.u-muted { color: var(--text-muted); }

/* Числа, которые прямо сейчас пересчитываются (#311).

   Гасим не ради красоты: во время пересчёта на экране висят СТАРЫЕ
   значения — те, что были до смены отбора. Оставь их яркими, и человек
   прочитает их как ответ на новый вопрос.

   Не прячем совсем по той же причине, по какой не рисуем скелет: пустое
   место читается как «данных нет», а данные есть, просто вчерашние. */
.u-dimmed { opacity: .45; transition: opacity .15s ease; }
.u-danger { color: var(--danger); }
.u-success { color: var(--success); }
.u-sm { font-size: 12px; }
.u-md { font-size: 13px; }
.u-lg { font-size: 18px; }
.u-bold { font-weight: 600; }
.u-note { color: var(--text-muted); font-size: 12px; }
/* Предупреждение — не ошибка.
   Двадцать одно место в кабинете уже ставило .u-warning, а правила не
   было ни в одном слое: «касса уйдёт в минус» и «доверенность истекает»
   выглядели как обычная серая подсказка. Тот же случай, что с выходным
   днём ниже, и найден тем же способом — перебором классов из видов
   против объявленных. Оранжевый, а не красный: красный значит «нельзя»,
   а тут можно, но подумав. */
.u-warning { color: var(--warning); }

/* Спрятать совсем — для форм, которые отправляет скрипт.
   Без правила форма подтверждения почты просто висела на экране. */
.u-hidden { display: none; }

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

/* Обычный маркированный список внутри карточки. */
.u-list { margin: 0; padding-left: 18px; }
.u-list li + li { margin-top: 4px; }

/* Выбранная строка таблицы — например, принятое предложение поставщика.
   Зелёный тут значит «этот вариант взят», поэтому подложка, а не текст:
   цвет цифр в строке занят другим смыслом — переплата красная. */
.u-row-selected td { background: var(--success-light); }

/* Отступы, которых не хватало шагу набора. */
.u-ml-8 { margin-left: 8px; }
.u-mb-4 { margin-bottom: 4px; }
.u-mb-6 { margin-bottom: 6px; }
.u-mb-12 { margin-bottom: 12px; }
/* Выходной день в календаре-сетке.
   Класс ставился в разметке отсутствий, а правила для него не было ни в
   одном слое: суббота выглядела как вторник. Оттенок намеренно слабый —
   выходные должны читаться боковым зрением, а не спорить с отметками
   отсутствий, ради которых на календарь и смотрят. */
.u-weekend { background: var(--bg); }
/* Сегодня в календаре-сетке. Полоса, а не заливка: заливка спорила бы с
   выходным, а в паре «сегодня + суббота» важнее видеть обе вещи сразу.
   На шкале с полосами ту же роль играет .gantt-today. */
.u-today { box-shadow: inset 2px 0 0 var(--warning); }
/* Отпечаток документа — цепочка из шестидесяти четырёх знаков. Её
   сверяют глазами, а в пропорциональном шрифте единица и буква l
   выглядят одинаково. */
.u-mono { font-family: ui-monospace, "Cascadia Mono", Consolas, monospace; word-break: break-all; }
.u-metric { font-size: 18px; font-weight: 600; }
.u-right { text-align: right; }
.u-right-bold { text-align: right; font-weight: 600; }
/* Столбец с переключателями: галочка под своим заголовком, а не у левого
   края ячейки, где непонятно, к какому столбцу она относится. */
.u-center { text-align: center; }

.u-m-0 { margin: 0; }
.u-mb-0 { margin-bottom: 0; }
.u-mb-8 { margin-bottom: 8px; }
.u-mb-10 { margin-bottom: 10px; }
.u-mb-16 { margin-bottom: 16px; }
.u-mt-8 { margin-top: 8px; }
.u-mt-12 { margin-top: 12px; }
.u-mt-14 { margin-top: 14px; }
.u-mt-16 { margin-top: 16px; }

.u-span-all { grid-column: 1 / -1; }
/*
 * Раскладка карточки записи: главное слева, сопутствующее справа.
 *
 * Называется card-split, а не u-split, и это не придирка к имени.
 * В слое автора макета `.u-split` означает совсем другое — «развести два
 * элемента по краям строки», и объявлен он там через display: flex.
 * Слой автора подключается после нашего, поэтому побеждал он: display
 * становился flex, а grid-template-columns браузер просто отбрасывал.
 *
 * Итог был виден глазами: колонки переставали делиться два к одному и
 * вставали по ширине содержимого. На карточке задачи левая колонка,
 * которая должна быть вдвое шире, оказывалась 304px против 373px справа,
 * а треть экрана пустовала.
 *
 * Одно имя на два разных смысла — это не ошибка автора и не наша: это
 * столкновение. Чинится разведением имён, а не правкой чужого слоя:
 * его обновляют заменой файла целиком, и правка не пережила бы обновления.
 */
.card-split { display: grid; grid-template-columns: 2fr 1fr; gap: 14px; align-items: start; }

@media print {
  .no-print { display: none !important; }
}

/* --- Обсуждение на карточке --- */
.comment-thread {
  display: flex;
  flex-direction: column;
  gap: 10px;
  max-height: 420px;
  overflow-y: auto;
}

.comment {
  border: 1px solid var(--border);
  border-left: 3px solid var(--primary);
  padding: 10px 12px;
  background: var(--bg);
}

.comment-head {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-bottom: 6px;
  font-size: 13px;
}

.comment-time { color: var(--text-muted); font-size: 12px; }

.comment-delete {
  border: none;
  background: none;
  cursor: pointer;
  color: var(--text-muted);
  font-size: 18px;
  line-height: 1;
  padding: 0 2px;
}

.comment-delete:hover { color: var(--danger); }

.comment-body { font-size: 14px; line-height: 1.5; word-break: break-word; }

.mention {
  background: var(--primary-light);
  color: var(--primary);
  font-weight: 600;
  padding: 0 3px;
}

.mention-file { text-decoration: underline; }

.picker-row {
  display: flex;
  align-items: center;
  gap: 8px;
  width: 100%;
  text-align: left;
  border: none;
  background: none;
  cursor: pointer;
  padding: 8px;
  border-bottom: 1px solid var(--border);
  font-family: inherit;
  color: inherit;
  font-size: 14px;
}

.picker-row:hover { background: var(--primary-light); }

.picker-group {
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: .04em;
  color: var(--text-muted);
  padding: 8px 8px 4px;
  border-bottom: 1px solid var(--border);
}

.mention-ref { text-decoration: underline; }
.mention-gone { text-decoration: line-through; opacity: .65; }

.u-danger-row { background: var(--danger-light); }

/* --- Кликабельная плитка показателя --- */
.stat-link { display: block; text-decoration: none; color: inherit; }
.stat-link:hover { border-color: var(--primary); }
.stat-link .stat-value { text-decoration: none; }

.stat-bar {
  height: 4px;
  background: var(--border);
  margin: 6px 0 4px;
}

.stat-bar-fill { height: 100%; background: var(--primary); }

/* --- Столбчатая диаграмма --- */
.bar-chart {
  display: flex;
  align-items: flex-end;
  gap: 4px;
  height: 150px;
  overflow-x: auto;
}

.bar-col {
  flex: 1;
  min-width: 34px;
  display: flex;
  flex-direction: column;
  height: 100%;
}

.bar-slot { flex: 1; display: flex; flex-direction: column; }

.bar-chart:not(.bar-chart-signed) .bar-slot { justify-content: flex-end; }

.bar-half { flex: 1; display: flex; }
.bar-half-up { align-items: flex-end; }
.bar-half-down { align-items: flex-start; }

.bar-zero { height: 1px; background: var(--text-muted); }

.bar { width: 100%; }
.bar-positive { background: var(--primary); }
.bar-negative { background: var(--danger); }

.bar-label {
  font-size: 10px;
  color: var(--text-muted);
  text-align: center;
  padding-top: 4px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* ================================================================
   Адаптив: планшет и телефон
   ================================================================

   Раскладка на широком экране — боковое меню плюс рабочая область.
   На телефоне колонка в 240 px съедает две трети ширины, поэтому меню
   становится выезжающим, а всё остальное — в одну колонку.

   Гриды (form-grid, stat-grid, filters, meta-grid) построены на
   auto-fit/minmax и перестраиваются сами — им правила не нужны.
   Отдельно поджимаются только отступы и то, что задано жёстко.
   ================================================================ */

.menu-toggle {
  display: none;
  align-items: center;
  justify-content: center;
  background: none;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  color: var(--text);
  /* Палец — не курсор: меньше 44 px в него не попадают. */
  min-width: var(--touch-target);
  min-height: var(--touch-target);
  margin-right: 12px;
  cursor: pointer;
}

/* Правый кластер шапки: тема, уведомления, пользователь. */
/* Правый блок не сжимается: кнопки темы, колокольчик и аватар — цели для
   пальца, и ужимать их ради длинного заголовка нельзя. Сжимается заголовок. */
.topbar__right { display: flex; align-items: center; gap: 10px; flex-shrink: 0; }

/* ------------------------------------------------------------
   ЧИП ПОЛЬЗОВАТЕЛЯ

   Раньше здесь стояло полное ФИО, юрлицо и кнопка «Выйти» одной
   строкой — на 1200px это уже не помещалось, а «Выйти» стояла на
   расстоянии промаха от колокольчика.
   ------------------------------------------------------------ */
.user-chip__toggle {
  display: flex;
  align-items: center;
  gap: 8px;
  height: var(--control-h);
  padding: 0 10px 0 4px;
  background: none;
  border: 1px solid transparent;
  border-radius: var(--radius);
  color: var(--text);
  font: inherit;
  font-size: var(--text-body-sm);
  cursor: pointer;
}
.user-chip__toggle:hover { border-color: var(--border); }
.user-chip__toggle:focus-visible { outline: none; box-shadow: var(--focus-ring); }

.user-chip__avatar {
  display: flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  width: var(--user-chip-avatar);
  height: var(--user-chip-avatar);
  border-radius: var(--radius-circle);
  background: var(--primary-muted);
  color: var(--white);
  font-size: var(--text-caption);
  font-weight: var(--font-bold);
  letter-spacing: .3px;
}

/* Имя обрезается, а не переносится: шапка обязана остаться одной
   строки, иначе от неё поедет всё содержимое страницы. */
.user-chip__name {
  max-width: var(--user-chip-name-max);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-weight: var(--font-medium);
}

/* НА ТЕЛЕФОНЕ МЕНЮ ОТКРЫВАЕТСЯ ПО ИМЕНИ, А НЕ ПО КРУЖКУ (#435).

   Здесь стояло `display: none` у имени, и от чипа оставался аватар с
   двумя буквами. Довод был записан честно: имя, даже обрезанное,
   выталкивало шапку за край и включало горизонтальную прокрутку всей
   страницы. Довод верен — и поэтому имя возвращается НЕ ДОБАВЛЕНИЕМ.

   Меняем местами: кружок уходит, имя встаёт на его место с жёстким
   потолком ширины. Чип от этого не растёт — аватар занимал свои
   пиксели ровно так же, — а человек видит, под кем он работает, и
   понимает, куда нажать. «ЕК» в кружке этого не говорит: две буквы
   опознаются, только если уже знаешь, чьи они.

   Потолок в 92px — это примерно «Ерлан К.»: длинное имя обрежется
   многоточием, а шапка останется одной строки. Ширина при этом
   ОГРАНИЧЕНА СВЕРХУ, то есть вернуться к горизонтальной прокрутке
   она не может даже на самом узком экране. */
@media (max-width: 640px) {
  .user-chip__avatar { display: none; }
  .user-chip__name { max-width: 92px; }
  .user-chip__toggle { padding: 0 8px; gap: 4px; }
}

.user-chip__company {
  padding: 8px 14px 6px;
  color: var(--text-tertiary);
  font-size: var(--text-caption);
  border-bottom: 1px solid var(--border);
}
.user-chip__exit { border-top: 1px solid var(--border); }
.user-chip__exit .menu-item { color: var(--danger); }

/* ------------------------------------------------------------
   ПОИСК ПО РАЗДЕЛАМ

   Живёт в тёмной колонке, поэтому цвета здесь свои, а не из
   палитры интерфейса: --text на тёмно-синем не читается.
   ------------------------------------------------------------ */
.nav-search {
  display: flex;
  align-items: center;
  gap: 8px;
  margin: 0 12px 10px;
  padding: 0 10px;
  height: var(--control-h);
  background: rgba(255, 255, 255, .07);
  border: 1px solid transparent;
  border-radius: var(--radius);
  color: rgba(255, 255, 255, .55);
}
.nav-search:focus-within { border-color: var(--primary); color: var(--white); }
.nav-search__field {
  flex: 1;
  min-width: 0;
  background: none;
  border: 0;
  outline: none;
  color: var(--white);
  font: inherit;
  font-size: var(--text-body-sm);
}
.nav-search__field::placeholder { color: rgba(255, 255, 255, .45); }
/* Крестик очистки в Chrome рисуется тёмным и на тёмном фоне пропадает. */
.nav-search__field::-webkit-search-cancel-button { filter: invert(1); opacity: .5; }
.nav-search__hint {
  flex-shrink: 0;
  padding: 2px 6px;
  border: 1px solid rgba(255, 255, 255, .2);
  border-radius: var(--radius-badge);
  font-size: var(--text-badge-min);
  font-family: inherit;
  color: rgba(255, 255, 255, .5);
}

.menu-scrim {
  display: none;
  position: fixed;
  inset: 0;
  background: rgba(19, 23, 71, .45);
  border: 0;
  z-index: 40;
}

[x-cloak] { display: none !important; }

@media (max-width: 900px) {
  .topbar { padding: 12px 14px; }
  .topbar h1 { font-size: 16px; }
  .content { padding: 14px; }

  /* На телефоне слой отступает содержимое на --gap, а не на --gap-xl —
     шапка берёт то же поле, чтобы края опять совпали. */
  .app-shell .topbar { margin: var(--shell-gap) var(--gap, 14px) 0; }
  .card { padding: 14px; }

  /* Название компании и имя пользователя в шапке телефона не помещаются,
     а кнопка выхода нужна всегда — прячем текст, оставляем действия. */
  .topbar .user-chip { font-size: 0; gap: 6px; }
  .topbar .user-chip * { font-size: 13px; }

  .menu-toggle { display: inline-flex; align-items: center; justify-content: center; }
  .menu-scrim { display: block; }

  .sidebar {
    position: fixed;
    top: 0;
    bottom: 0;
    left: 0;
    z-index: 50;
    /* Меню уезжает за край, а не скрывается display:none — так оно
       возвращается плавно и не перестраивает раскладку рывком. */
    transform: translateX(-100%);
    transition: transform .18s ease-out;
    overflow-y: auto;

    /* На телефоне меню — не плавающий блок, а выезжающая шторка:
       отступы дали бы щель по краю экрана, а скруглённый угол на
       полноэкранной шторке читается как недоехавшая анимация.
       Высота — ровно экран, и написана явно: с height: auto шторка
       вытягивалась по содержимому (1104px при экране 812) и нижние
       разделы уезжали за край без возможности до них добраться. */
    margin: 0;
    height: 100%;
    border: 0;
    border-radius: var(--radius-none);
  }

  .menu-open .sidebar { transform: translateX(0); }

  /* Кнопки в панели действий на телефоне встают в строку и переносятся,
     а не выстраиваются в узкий столбец. */
  .toolbar { gap: 8px; }
  .toolbar .btn { min-height: 40px; }

  /* Таблицы шире экрана прокручиваются внутри карточки. Ломать их в
     карточки-строки нельзя: в журналах и ведомостях смысл именно
     в сопоставлении колонок по строке. */
  table.table { min-width: 640px; }

  /* Палец тянет таблицу вбок, и ссылка под ним не должна принимать это
     за нажатие. pan-x/pan-y отдаёт жест прокрутке: браузер перестаёт
     ждать, тап это или протяжка, и промахов не случается. */
  .u-scroll-x, .card { -webkit-overflow-scrolling: touch; touch-action: pan-x pan-y; }

  /* Ссылка в таблице на телефоне — цель для пальца, а не для курсора. */
  table.table a { display: inline-block; padding: 2px 0; }
}

@media (max-width: 600px) {
  .topbar .user-chip form { margin-left: 0; }
  .stat-grid { gap: 10px; }

  /* Поля ввода не меньше 16 px: иначе Safari на iPhone зумит страницу
     при фокусе и пользователь оказывается в увеличенной вёрстке. */
  .form-control, .btn { font-size: 16px; }
}

/* --- Плавающий чат ---

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

   Скруглений нет и здесь: круглая кнопка чата — привычка внешних
   виджетов, но в системе, где ни один угол не скруглён, она читалась бы
   как чужеродная вставка. */
.chat-widget {
  position: fixed;
  right: 20px;
  bottom: 20px;
  z-index: 60;
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: 10px;
}

.chat-launcher {
  width: 52px;
  height: 52px;
  border: none;
  background: var(--primary);
  color: var(--white);
  font-size: 24px;
  line-height: 1;
  cursor: pointer;
  box-shadow: 0 2px 10px rgba(19, 23, 71, 0.25);
  display: flex;
  align-items: center;
  justify-content: center;
}
.chat-launcher:hover { background: var(--primary-dark); }

.chat-panel {
  width: 360px;
  max-width: calc(100vw - 40px);
  height: 520px;
  max-height: calc(100vh - 120px);
  background: var(--surface);
  border: 1px solid var(--border);
  box-shadow: 0 6px 28px rgba(19, 23, 71, 0.18);
  display: flex;
  flex-direction: column;
  overflow: hidden;
}

.chat-head {
  background: var(--primary);
  color: var(--white);
  padding: 10px 12px;
  display: flex;
  align-items: flex-start;
  gap: 8px;
}
.chat-title { font-weight: 600; font-size: 14px; }
.chat-sub { font-size: 12px; opacity: 0.8; }
.chat-sub a { color: var(--white); text-decoration: underline; }
.chat-close {
  margin-left: auto;
  background: none;
  border: none;
  color: var(--white);
  font-size: 22px;
  line-height: 1;
  cursor: pointer;
}

.chat-channels {
  display: flex;
  gap: 6px;
  padding: 8px 10px;
  overflow-x: auto;
  border-bottom: 1px solid var(--border);
  flex-shrink: 0;
}
.chat-chip {
  border: 1px solid var(--border);
  background: var(--bg);
  padding: 4px 9px;
  font-size: 12px;
  white-space: nowrap;
  cursor: pointer;
  font-family: inherit;
  color: inherit;
}
.chat-chip.active { background: var(--primary); color: var(--white); border-color: var(--primary); }
/* Сколько ждёт именно в этом канале: без разбивки человек открывает чат,
   видит общую цифру и не знает, где искать. */
.chat-chip-count {
  display: inline-block;
  min-width: 16px;
  margin-left: 5px;
  padding: 0 4px;
  background: var(--danger);
  color: var(--white);
  font-size: 10.5px;
  line-height: 16px;
  font-weight: 600;
  text-align: center;
}

/* position: relative нужен списку найденного — он выпадает поверх ленты. */
.chat-search { padding: 8px 10px; border-bottom: 1px solid var(--border); flex-shrink: 0; position: relative; }

/* Найденное — ВЫПАДАЮЩИЙ список поверх ленты, а не блок в потоке.
   Раньше он раздвигал панель: пока человек набирал название объекта,
   лента под ним прыгала на каждую букву — список то появлялся, то
   исчезал, и всё содержимое ездило вверх-вниз. Наложение не двигает
   ничего: панель под ним стоит на месте. */
.chat-found {
  position: absolute;
  left: 10px;
  right: 10px;
  top: calc(100% - 4px);
  z-index: 3;
  max-height: 220px;
  overflow-y: auto;
  border: 1px solid var(--border);
  background: var(--surface);
  box-shadow: var(--shadow-card);
}
.chat-found-row {
  display: block;
  width: 100%;
  text-align: left;
  border: none;
  border-bottom: 1px solid var(--border);
  background: var(--surface);
  padding: 7px 9px;
  font-size: 12.5px;
  cursor: pointer;
  font-family: inherit;
  color: inherit;
}
.chat-found-row:last-child { border-bottom: none; }
.chat-found-row:hover { background: var(--bg); }
.chat-found-kind { color: var(--text-muted); font-size: 11px; display: block; }

/* Лента как в библиотеке автора (Ч1): подложка отличается от пузырей,
   свои сообщения справа в primary, чужие слева белым с аватаром. */
.chat-body { flex: 1; overflow-y: auto; padding: 12px; display: flex; flex-direction: column; gap: 4px; background: var(--bg); }
.chat-empty { color: var(--text-muted); font-size: 12.5px; margin: auto 0; text-align: center; }

/* Плашка дня по центру: время у сообщений короткое (Ч:М), день читается
   один раз на группу, а не в каждой строке. */
.chat-day {
  align-self: center;
  font-size: 10px;
  font-weight: 600;
  color: var(--text-muted);
  background: var(--surface);
  border-radius: var(--radius-pill);
  padding: 3px 10px;
  margin: 6px 0;
}

.chat-msg { display: flex; gap: 8px; }
.chat-msg--mine { flex-direction: row-reverse; }
/* Первое сообщение новой группы отделено воздухом от предыдущей. */
.chat-msg:has(.chat-msg__bubble--first) { margin-top: 6px; }

.chat-msg__av {
  flex: 0 0 28px;
  width: 28px;
  height: 28px;
  border-radius: 50%;
  background: var(--user-chip-avatar);
  color: #fff;
  font-size: 9px;
  font-weight: 700;
  display: flex;
  align-items: center;
  justify-content: center;
  margin-top: 2px;
}
/* У продолжения группы аватар пустой, но место держит: пузыри одной
   группы стоят одной колонкой, а не лесенкой. */
.chat-msg__av:empty { background: transparent; }

.chat-msg__col { display: flex; flex-direction: column; gap: 3px; min-width: 0; max-width: 85%; }
.chat-msg--mine .chat-msg__col { align-items: flex-end; }

.chat-msg__meta { font-size: 11px; color: var(--text-muted); }
.chat-msg__meta strong { color: var(--text-secondary); font-weight: 700; }

.chat-msg__bubble {
  background: var(--surface);
  border-radius: var(--radius-lg);
  padding: 8px 12px;
  font-size: 13px;
  line-height: 19px;
  word-break: break-word;
  box-shadow: var(--shadow-card);
}
/* Ответы noneAI — простой текст со строками через \n: переносы рисует
   white-space, а не разметка. Комментариям людей это нельзя — их текст
   уже приходит с разметкой, и pre-line задвоил бы переносы. */
.chat-msg__bubble--plain { white-space: pre-line; }

/* Хвостик у первого пузыря группы — меньший угол со стороны автора.
   Отдельным свойством, а не сокращённой записью: сторож скруглений
   разрешает border-radius только целиком из шкалы, и это правильно —
   а здесь оба значения тоже из шкалы, просто на разных углах. */
.chat-msg__bubble--first { border-top-left-radius: var(--radius-badge); }
.chat-msg--mine .chat-msg__bubble { background: var(--primary); color: var(--white); }
.chat-msg--mine .chat-msg__bubble--first {
  border-top-left-radius: var(--radius-lg);
  border-top-right-radius: var(--radius-badge);
}
.chat-msg--mine .chat-msg__bubble a { color: var(--white); text-decoration: underline; }

/* ----------------------------------------------------------------
   noneAI (библиотека, Ч2/Ч4). Знак помощника — бирюзовая искра ✦:
   она одна и та же у поля вопроса, в метке ответа и на подсказках,
   чтобы «это сказал noneAI» узнавалось до чтения.
   ---------------------------------------------------------------- */
.ai-spark {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 26px;
  height: 26px;
  flex-shrink: 0;
  border-radius: var(--radius-sm);
  background: var(--info-bg);
  color: var(--ai-accent);
  font-size: 13px;
}

.ai-badge {
  font-size: var(--text-caption);
  font-weight: 700;
  color: var(--ai-accent);
}

/* Пузырь ответа: как сообщение в чате, но со своим знаком автора.
   Хвостик снизу слева — ответ «говорит» из угла, где стоит метка. */
.ai-answer {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  border-bottom-left-radius: var(--radius-badge);
  padding: 12px 14px;
  box-shadow: var(--shadow-card);
}
.ai-answer--warning { border-color: var(--warning); }

.ai-answer__head {
  display: flex;
  align-items: center;
  gap: 6px;
  margin-bottom: 6px;
}
.ai-answer__scope {
  font-size: var(--text-caption);
  color: var(--text-muted);
}

.chat-form { border-top: 1px solid var(--border); padding: 8px 10px; flex-shrink: 0; }
.chat-form textarea { font-size: 13px; resize: none; }
.chat-picker { border: 1px solid var(--border); padding: 8px; margin-top: 6px; max-height: 200px; overflow-y: auto; }
.chat-actions { display: flex; gap: 6px; align-items: center; margin-top: 6px; }
.chat-actions .spacer { flex: 1; }

@media (max-width: 600px) {
  /* На телефоне панель занимает почти весь экран: читать переписку в
     окошке 360 px на экране 360 px невозможно. */
  .chat-widget { right: 12px; bottom: 12px; left: 12px; align-items: stretch; }
  .chat-panel { width: auto; height: calc(100vh - 100px); }
  .chat-launcher { align-self: flex-end; }
}

@media print { .chat-widget { display: none; } }

/* Файл в сообщении чата. */
.chat-file {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  margin-top: 4px;
  padding: 3px 7px;
  border: 1px solid var(--border);
  background: var(--bg);
  font-size: 12.5px;
}
.chat-file:hover { text-decoration: none; background: var(--primary-light); }
/* Срок хранения виден сразу: файл, пропавший без предупреждения, —
   потерянный файл, даже если так и задумано. */
.chat-file-life { color: var(--warning); font-size: 11.5px; }

.chat-pending-files { margin-top: 6px; font-size: 12.5px; }
.chat-pending-files span {
  display: inline-block;
  background: var(--primary-light);
  padding: 2px 7px;
  margin: 0 4px 4px 0;
}

/* --- Конструктор актов: строка поля --- */
.field-row {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 6px 0;
  border-bottom: 1px solid var(--border);
}
.field-row:last-of-type { border-bottom: none; }
.field-row .form-control { flex: 1; min-width: 0; }
/* Захват для перетаскивания: курсор говорит, что строку можно двигать,
   до того как человек попробует. */
.field-grip { cursor: grab; color: var(--text-muted); user-select: none; font-size: 15px; }
.field-row:active .field-grip { cursor: grabbing; }

@media (max-width: 900px) {
  /* На телефоне перетаскивание не работает, поэтому строка разворачивается
     в столбец: править поля важнее, чем менять их порядок. */
  .field-row { flex-wrap: wrap; }
  .field-row .form-control { flex-basis: 100%; }
  .field-grip { display: none; }
}

/* --- Подпись на объекте ---
   touch-action: none обязателен: без него палец на холсте прокручивает
   страницу вместо того, чтобы рисовать, и подписаться с телефона нельзя. */
.signature-pad {
  width: 100%;
  max-width: 520px;
  height: 180px;
  border: 1px dashed var(--border);
  background: var(--white);
  touch-action: none;
  display: block;
}
.signature-image { max-height: 110px; display: block; border: 1px solid var(--border); background: var(--white); }

/* Быстрое добавление справочной записи прямо из формы: кнопка живёт под
   выпадающим списком и не должна выглядеть равной ему по весу. */
.quick-add {
    margin-top: 6px;
    font-size: 12px;
}

/* --- Разделы сущности и группы действий ---

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

   Теперь: разделы — вкладками, действия — группой справа, разрушительное
   отделено промежутком, «К списку» — слева и вне групп. */

.section-tabs {
  display: flex;
  flex-wrap: wrap;
  gap: 2px;
  margin-bottom: 16px;
  border-bottom: 1px solid var(--border);
}

.section-tab {
  padding: 9px 14px;
  color: var(--text-muted);
  text-decoration: none;
  border-bottom: 2px solid transparent;
  white-space: nowrap;
}

.section-tab:hover { color: var(--primary); background: rgba(19, 23, 71, .04); }

.section-tab.is-active {
  color: var(--primary);
  font-weight: 600;
  border-bottom-color: var(--primary);
}

/* Группа однородных действий. Промежуток между группами вдвое больше,
   чем между кнопками внутри, — граница видна без разделительных линий. */
.toolbar-group { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; }
.toolbar-group + .toolbar-group { margin-left: 12px; }

/* Разрушительное действие стоит отдельно: «Удалить» вплотную к
   «Редактировать» нажимают промахом. */
.toolbar-group-danger { margin-left: 20px; }

@media (max-width: 960px) {
  /* Промежутки между группами на узком экране только гонят перенос:
     кнопка, уехавшая на свою строку, выглядит потерянной. */
  .toolbar-group + .toolbar-group,
  .toolbar-group-danger { margin-left: 0; }
}

@media (max-width: 700px) {
  .section-tabs { overflow-x: auto; flex-wrap: nowrap; }
}

/* --- Заголовки широких таблиц ---

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

   Здесь только заголовки: перенос слов в шапке делает таблицу выше
   экрана, и читать её становится нечем. */
@media (max-width: 900px) {
  table.table th { white-space: nowrap; }
}

/* --- Канбан на телефоне: как в Trello ---

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

   Смахивание доводится доводчиком до края следующей колонки, а не
   останавливается посередине: полторы колонки на экране читать нельзя.

   overscroll-behavior-x: contain обязателен. Без него смахивание вправо
   на краю доски уводит браузер назад по истории, и человек вылетает со
   страницы, пытаясь долистать до последней колонки. */
@media (max-width: 760px) {
  .board {
    scroll-snap-type: x mandatory;
    overscroll-behavior-x: contain;
    /* Отступ по краям, чтобы первая и последняя колонки не липли к
       границе экрана. */
    padding-inline: 2px;
  }

  .board-col {
    flex: 0 0 86vw;
    scroll-snap-align: start;
  }
}

/* Пока карточку тащат, доводчик выключается.

   Он тянет доску к ближайшей колонке, а перетаскивание тянет её к краю
   экрана, чтобы показать соседнюю. Два притяжения одновременно — и
   доска дёргается под пальцем, а карточка падает не туда. */
.board.is-dragging { scroll-snap-type: none; }

/* Кнопки в карточке реестра — палец, а не мышь: 40px это нижняя
   граница, ниже которой промахиваются. */
@media (max-width: 760px) {
  table.table-cards td .btn { min-height: 40px; min-width: 40px; }
}

/* --- Реестр на телефоне: строка становится карточкой ---

   Таблица шириной в пять колонок на экране 375px читается только
   горизонтальной прокруткой, а прокручивать таблицу и одновременно
   помнить, к какой строке относится ячейка, невозможно.

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

   Признак .table-cards вешает js/table-cards.js — он же переносит подписи
   из заголовка таблицы в ячейки. Руками расставить их в ста тридцати шести
   реестрах можно, но следующую таблицу забудут, и в карточке останутся
   голые цифры, где не отличить сумму от остатка.

   Исключение помечается классом .table-wide: сверка, план-факт, бюджет,
   табель по дням. Там сравнение по колонкам и есть смысл, и прокрутка
   честнее карточек. */
@media (max-width: 760px) {
  table.table-cards { display: block; overflow-x: visible; }
  table.table-cards thead { display: none; }
  table.table-cards tbody { display: block; }

  table.table-cards tr {
    display: block;
    border: 1px solid var(--border);
    padding: 10px 12px;
    margin-bottom: 10px;
    background: var(--surface);
  }

  table.table-cards td {
    display: flex;
    justify-content: space-between;
    gap: 12px;
    border: none;
    padding: 4px 0;
    text-align: left;
  }

  table.table-cards td::before {
    content: attr(data-label);
    color: var(--text-muted);
    font-size: 12px;
    flex-shrink: 0;
  }

  /* Ячейка без подписи — обычно первая (название) и последняя (кнопки).
     Название занимает всю ширину и набирается крупнее: это заголовок
     карточки. Кнопки прижимаются вправо. */
  /* Снимаем жёсткий min-width, заданный для прокручиваемых таблиц
     выше по файлу. Без этого карточка остаётся шириной 640px внутри
     экрана в 375: подписи видны, а значения уезжают за край, и
     карточка прокручивается вбок ровно так же, как таблица. Это и была
     причина, по которой карточный вид выглядел нерабочим. */
  table.table-cards { min-width: 0; }

  table.table-cards td:not([data-label])::before { content: none; }
  table.table-cards td:first-child { font-weight: 600; font-size: 15px; }
  table.table-cards td:last-child { justify-content: flex-end; padding-top: 8px; }
}

/* --- Многоколоночные формы на телефоне: в одну колонку ---

   Здесь единственное место во всём стиле, где стоит !important, и вот
   почему. Число колонок у .form-row задаётся инлайном прямо в разметке —
   «1fr 1fr auto», «repeat(4, 1fr)», «160px 200px 200px 150px 1fr auto» —
   потому что у каждой формы своё. Инлайн-стиль сильнее любого правила в
   файле, и обычным медиазапросом его не перебить: на телефоне форма из
   шести колонок так и осталась бы шестиколоночной, где каждое поле шире
   экрана вдвое.

   Переписать полсотни таких мест на классы можно, но до тех пор формы
   на телефоне нечитаемы, а правило ниже чинит их все сразу. */
@media (max-width: 760px) {
  .form-row,
  form.form-row {
    grid-template-columns: 1fr !important;
    align-items: stretch !important;
  }

  /* Кнопка в конце такой строки была прижата к нижнему краю ячейки —
     в одну колонку ей ровняться не по чему, и она растягивается. */
  .form-row > .btn { width: 100%; }
}

/* --- Кнопки и поля под палец ---

   На мыши промах стоит движения запястья, на телефоне — попадания
   в соседнюю кнопку. Между «Изменить» и «Удалить» промахнуться особенно
   обидно, поэтому 40px по высоте здесь не про красоту.

   btn-sm на телефоне перестаёт быть маленькой: она задумывалась мелкой,
   чтобы не спорить с содержимым таблицы, а в карточке спорить уже не
   с чем — там она единственное, по чему нажимают. */
@media (max-width: 760px) {
  .btn, .btn-sm {
    min-height: 40px;
    padding: 9px 14px;
    font-size: 14px;
  }

  /* Значок без подписи — квадрат: полоска шириной в иконку под палец
     не попадает. */
  .btn-icon { min-width: 40px; justify-content: center; }

  /* На телефоне подпись показывается в строке-карточке.
     Наведения там нет вовсе, а подсказка `title` пальцем не вызывается —
     значок остался бы загадкой. Место в карточке есть, слово рядом со
     значком снимает догадки.

     Возвращаем подпись в поток целиком: на широком экране она спрятана
     приёмом «видно только программе чтения» (position: absolute и
     обрезка), и половинчатая отмена оставила бы её висеть поверх. */
  table.table-cards .btn-icon { justify-content: flex-start; }
  table.table-cards .btn-icon .btn-label {
    position: static;
    width: auto;
    height: auto;
    overflow: visible;
    clip-path: none;
    margin-left: 7px;
  }

  /* Группа кнопок в карточке разъезжается по ширине, а не жмётся
     в правый угол: в углу они стоят вплотную и промахи неизбежны. */
  table.table-cards td:last-child { flex-wrap: wrap; gap: 8px; }

  /* Ничто не шире экрана.

     Ширины в разметке заданы под широкий экран — 320px, 480px, встречается
     и 720px. На 375px такой блок выталкивает страницу вбок целиком, и
     горизонтально едет уже всё, включая шапку.

     Таблиц это не касается: у прокручиваемых стоит min-width, он сильнее,
     и прокрутка внутри карточки сохраняется намеренно. */
  .content input,
  .content select,
  .content textarea,
  .content .form-control,
  .content .card > div[style*="width"],
  .content img { max-width: 100%; }
}

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

/* ПЛАШКА И ШАПКА ЛИПНУТ ВМЕСТЕ, А НЕ ПОРОЗНЬ (#435).

   Обе были `position: sticky; top: 0`, и при прокрутке занимали одно
   место: плашка со слоем 60 закрывала шапку со слоем 20 — вместе с «?»,
   темой и колокольчиком. В песочнице их нельзя было нажать.

   Высоту плашки числом задать нельзя: текст в ней переносится и занимает
   то одну строку, то три. Любое `top: 42px` у шапки было бы верным ровно
   на одной ширине экрана.

   Поэтому липнет контейнер, а дети внутри него обычные — встают друг под
   другом сами, при любой высоте.

   `!important` СТОИТ ПРОТИВ НАЗВАННОГО ПРАВИЛА, а не «на всякий случай»:
   `noneerp-theme.css`, строка 103 — `.topbar { position: sticky; top: 0;
   z-index: 20 }`. Слой макета грузится ПОСЛЕ нас и при равной силе
   выигрывает; без `!important` шапка внутри стека снова стала бы липкой
   на нуле, и мы вернулись бы ровно туда, откуда ушли.

   Пометка нужна не мне, а тому, кто через месяц увидит `!important` и
   сочтёт его мусором: снимет — и беда вернётся молча, потому что в
   прогоне её не видно (геометрии у отрисованной страницы нет). Обновится
   слой макета и перестанет делать шапку липкой — эту строку можно
   убрать, но сперва проверить, что в песочнице ничего не наезжает. */
.topbar-stack {
  position: sticky;
  top: 0;
  z-index: 60;
}
.topbar-stack > .sandbox-bar,
.topbar-stack > .topbar {
  position: static !important;
}

.sandbox-bar {
  position: sticky;
  top: 0;
  z-index: 60;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  gap: 6px 14px;
  padding: 7px 14px;
  background: var(--navy);
  color: var(--white);
  font-size: 13px;
  line-height: 1.4;
  text-align: center;
}

/* Последние пять минут — другим цветом. Тот же текст мелким шрифтом человек
   уже не читает: он к нему привык за пятьдесят пять минут. */
.sandbox-bar--soon { background: var(--danger); }

/* Предложение купить — светлой заливкой, а не ссылкой.

   В полосе три действия, и они не равны: счётчик сообщает, «выйти
   сейчас» уводит, а это единственное, ради чего экскурсия и затевалась.
   Ссылка того же вида, что и «Выйти сейчас», делала их равными. */
.sandbox-bar__cta {
  background: var(--white);
  color: var(--navy);
  font-weight: 600;
}

.sandbox-bar__cta:hover { background: var(--white); text-decoration: underline; }


.sandbox-bar strong { font-weight: 700; }

.sandbox-bar a,
.sandbox-bar button {
  color: var(--white);
  background: none;
  border: 1px solid rgba(255, 255, 255, .55);
  padding: 2px 10px;
  font: inherit;
  cursor: pointer;
  text-decoration: none;
}

.sandbox-bar a:hover,
.sandbox-bar button:hover { background: rgba(255, 255, 255, .15); }

/* Экран занят: запрос в пути. Кнопки гасит busy-forms.js, здесь только
   вид. Приглушаем весь блок, а не одну кнопку: человек нажал и ждёт
   ответа от всего экрана, а не от кнопки. Скруглений нет — их нет нигде.

   position: relative нужен ради подложки ::after, но ставить его вслепую
   нельзя: класс вешается на КОРЕНЬ любого компонента Livewire, и у
   плавающего чата он перебивал position: fixed. Виджет на время запроса
   срывался вниз, в конец страницы, и возвращался обратно — со стороны
   это выглядело так, будто чат моргает и уезжает сам по себе.

   Поэтому исключаем то, что уже спозиционировано само: у таких блоков
   подложке и так есть от чего считаться. Список — это ровно наши
   плавающие штуки; :where не добавляет веса, и правило по-прежнему
   легко перебить. */
.is-busy:where(:not(.chat-widget, .modal, .modal-backdrop, [style*="position: fixed"])) {
  position: relative;
}
.is-busy .btn[disabled] { opacity: .55; cursor: progress; }
.is-busy::after {
  content: "";
  position: absolute; inset: 0;
  background: rgba(255,255,255,.35);
  pointer-events: none;
}
@media (prefers-reduced-motion: no-preference) {
  .is-busy::after { transition: opacity .12s linear; }
}

/* --- Выделение строк и панель «Выбрано N» (#310) ---
   Автор макета добавит .bulkbar и tr.is-selected в следующей версии темы.
   Наш слой подключается РАНЬШЕ темы, поэтому её вид перекроет этот, а
   разметка останется той же. До тех прогонов это не заглушка, а рабочий
   вид: выделение должно быть видно с первого дня. */

/* Без жирного шрифта: он сдвигает соседние столбцы, и таблица «прыгает»
   на каждой галке. Подложка и полоса слева ничего не двигают. */
.table tr.is-selected > td { background: var(--tint); }
.table tr.is-selected > td:first-child { box-shadow: inset 3px 0 0 var(--primary); }

/* Итоговая строка таблицы: черта потолще обычной разлиновки, чтобы взгляд
   останавливался на ней, а не проскакивал к следующей строке. */
.table tr.table__total > td { border-top: 2px solid var(--border); }

/* Список без маркеров — акты у заявки, журнал переходов, короткие перечни
   внутри блока. Заведён затем, чтобы разметка не носила эти четыре
   свойства инлайном на каждом экране заново. */
.plain-list { list-style: none; padding-left: 0; margin: 0; }
.plain-list > li { padding: 4px 0; }
.plain-list > li.plain-list__ruled {
  padding: 6px 0;
  border-bottom: 1px solid var(--border);
  font-size: 13px;
}

/* Разделитель внутри блока: отделяет смену состояния от закрытия заявки. */
.card-rule { margin: 14px 0; border: 0; border-top: 1px solid var(--border); }

.u-mt-0 { margin-top: 0; }

/* Строка погашения долга контрагента: способ оплаты, итог и кнопка в один
   ряд — иначе взгляд теряет связь между отмеченными счетами и суммой. */
.settle-row {
  display: grid;
  grid-template-columns: 1fr 1fr auto;
  gap: var(--gap, 14px);
  align-items: end;
  max-width: 480px;
}
.settle-row__total { padding: 9px 0; font-weight: 600; }

/* Итоговая строка баланса крупнее соседних: это ответ на главный вопрос
   экрана — должен нам контрагент или мы ему. */
.balance-total { font-size: 16px; }

@media (max-width: 900px) {
  .settle-row { grid-template-columns: 1fr; }
}

.bulk-col { width: 34px; text-align: center; }

.bulkbar {
    position: sticky;
    top: 0;
    z-index: 5;
    display: flex;
    align-items: center;
    gap: 12px;
    flex-wrap: wrap;
    padding: 10px 14px;
    margin-bottom: 12px;
    background: var(--tint-strong, var(--tint));
    border-left: 3px solid var(--primary);
}

.bulkbar__count { white-space: nowrap; }
.bulkbar__actions { display: flex; gap: 8px; flex-wrap: wrap; flex: 1 1 auto; }

/* Подпись у заголовочной галки. Мелкая, но обязательная: без неё человек
   считает, что галка берёт весь отфильтрованный набор. */
.bulk-hint { font-size: 11px; color: var(--text-muted); }

/* ============================================================
   Реестр (архетип 1 из UX-DECISIONS.md)
   ============================================================ */

.registry-count { font-size: 13px; color: var(--text-muted); }

/* Строка управления таблицей: поиск слева, отборы рядом, вид справа. */
.registry-controls {
  display: flex;
  gap: 10px;
  align-items: center;
  flex-wrap: wrap;
  margin-bottom: 12px;
}
.registry-controls .spacer { flex: 1; }
.registry-controls .form-control { max-width: 240px; }
.registry-search { min-width: 260px; flex: 0 1 320px; }

/* Прокрутка таблицы внутри карточки: sticky-шапка работает только
   в прокручиваемом контейнере с заданной высотой. */
.registry-scroll {
  overflow: auto;
  max-height: calc(100vh - 380px);
  min-height: 220px;
}

.table-registry {
  /* Значение по умолчанию: экран подменяет его инлайном при смене
     плотности, но таблица обязана быть верной и без подмены. */
  --row-h: 36px;
}
/* Селектор с тегом намеренно: базовое правило записано как
   `table.table th`, и без тега здесь нулевые отступы проигрывали ему по
   весу. Плотность реестра держится на них — с чужими отступами строка
   в 36px превращается в 63px, а переключатель плотности перестаёт
   что-либо менять. */
table.table-registry th,
table.table-registry td { height: var(--row-h, 36px); padding-top: 0; padding-bottom: 0; }

/* Шапка не уезжает: на третьем экране прокрутки колонки «Оплачено» и
   «Остаток» иначе неразличимы, а это деньги. */
.table-registry thead th {
  position: sticky;
  top: 0;
  z-index: 2;
  background: var(--surface);
  box-shadow: inset 0 -1px 0 var(--border);
}

/* Первая колонка — опора при горизонтальной прокрутке: по номеру счёта
   человек понимает, чью строку читает. */
.table-registry tbody td:first-child,
.table-registry thead th:first-child {
  position: sticky;
  left: 0;
  background: var(--surface);
  z-index: 1;
}
.table-registry thead th:first-child { z-index: 3; }
.table-registry tbody td:first-child { box-shadow: 1px 0 0 var(--border); }

/* Заголовок-кнопка: сортировка обязана работать с клавиатуры. */
.th-sort {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  border: 0;
  background: none;
  padding: 0;
  font: inherit;
  font-weight: 600;
  color: inherit;
  cursor: pointer;
}
.th-sort:hover { color: var(--primary); }
.u-right .th-sort { flex-direction: row-reverse; }
.th-sort-arrow { font-size: 11px; color: var(--primary); }
.table-registry th.is-sorted { color: var(--primary); }

/* Числа в колонку: разряды под разрядами — и число не рвётся на две
   строки. «10 000», разорванное после тысячи, читается как два разных
   числа, а в счётчике это разница между тысячей и десятью тысячами. */
.u-num { font-variant-numeric: tabular-nums; white-space: nowrap; }

/* Деньги: цифры одной ширины, чтобы разряды в колонке стояли столбиком,
   и запрет переноса — сумма и знак теңге не должны оказаться на разных
   строках. Пробел перед знаком обычный: &nbsp; из шаблонов вычищали
   отдельной задачей, и место ему тут не вернули. */
.u-money { font-variant-numeric: tabular-nums; white-space: nowrap; }

.registry-foot {
  display: flex;
  align-items: center;
  gap: 12px;
  flex-wrap: wrap;
  padding-top: 12px;
  border-top: 1px solid var(--border);
}
.registry-foot-count {
  display: flex;
  align-items: center;
  gap: 10px;
  font-size: 13px;
  color: var(--text-muted);
}
.registry-foot-pages { margin-left: auto; }
.registry-per-page { display: flex; align-items: center; gap: 6px; margin: 0; }
.registry-per-page .form-control { width: auto; padding: 4px 8px; font-size: 13px; }

@media (max-width: 900px) {
  /* На планшете высота экрана уже занята шапкой и плитками — отдаём
     таблице больше места, иначе видно три строки. */
  .registry-scroll { max-height: none; overflow-x: auto; }
  .table-registry thead th { position: static; }
  .registry-search { flex: 1 1 100%; max-width: none; }
  .registry-controls .form-control { max-width: none; flex: 1 1 200px; }
}

/* ======================================================================
   АРХЕТИП 2: КАРТОЧКА ЗАПИСИ (UX-DECISIONS.md)

   Шапка карточки, вкладки внутри записи, меню «⋯», плашки состояния.
   Лежат в app.css, то есть слоем НИЖЕ будущей темы: когда автор макета
   нарисует карточку с вкладками, её вид перекроет наш, а разметка
   останется той же.
   ====================================================================== */

/* Шапка липкая: уйдя вниз к графику платежей, человек должен видеть,
   какой это договор и в каком он статусе, а не прокручивать обратно
   ради сверки номера. Плитки итогов при этом уезжают — они сводка, а
   не опора. */
.card-head {
  position: sticky;
  top: 0;
  z-index: 20;
  background: var(--surface);
  border-bottom: 1px solid var(--border);
  margin: -24px -24px 16px;
  padding: 14px 24px 0;
}

.card-head__row {
  display: flex;
  align-items: flex-start;
  gap: 16px;
  flex-wrap: wrap;
  padding-bottom: 12px;
}

.card-head__main { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; min-width: 0; }
.card-head__back { color: var(--text-muted); text-decoration: none; white-space: nowrap; font-size: 13px; }
.card-head__back:hover { color: var(--primary); }
.card-head__title { margin: 0; font-size: 18px; font-weight: 600; }
.card-head__badges { display: flex; gap: 6px; align-items: center; flex-wrap: wrap; }
.card-head__actions { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; margin-left: auto; }

/* Разделитель перед действием другой природы: «Подписать» — не ещё одна
   кнопка в ряду, а конец документа как черновика. */
.card-head__sep { width: 1px; align-self: stretch; background: var(--border); margin: 0 4px; }

/* Вкладки прилипают вместе с шапкой: переключить раздел человек хочет
   с той же точки, где читал, а не вернувшись наверх. */
.card-tabs {
  display: flex;
  gap: 2px;
  overflow-x: auto;
  scrollbar-width: none;
}

.card-tabs::-webkit-scrollbar { display: none; }

.card-tab {
  padding: 9px 14px;
  background: none;
  border: 0;
  border-bottom: 2px solid transparent;
  color: var(--text-muted);
  font: inherit;
  font-size: 13.5px;
  cursor: pointer;
  white-space: nowrap;
}

.card-tab:hover { color: var(--primary); }

.card-tab.is-active {
  color: var(--primary);
  font-weight: 600;
  border-bottom-color: var(--primary);
}

/* Счётчик у списковой вкладки: «Документы 7» отвечает, есть ли там
   что-то, не заставляя туда заходить. */
.card-tab__count {
  display: inline-block;
  min-width: 18px;
  margin-left: 6px;
  padding: 0 5px;
  border-radius: var(--radius-pill);
  background: var(--primary-light);
  color: var(--primary);
  font-size: 11px;
  font-weight: 600;
  text-align: center;
}

/* Меню «⋯» */
.menu { position: relative; display: inline-block; }
.menu__toggle { min-width: var(--control-h-sm); padding-left: 10px; padding-right: 10px; }

.menu__list {
  position: absolute;
  top: calc(100% + 4px);
  min-width: 220px;
  z-index: 40;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  box-shadow: var(--shadow-lg);
  padding: 4px 0;
}

.menu__list--right { right: 0; }
.menu__list--left { left: 0; }

.menu-item {
  display: block;
  width: 100%;
  padding: 8px 14px;
  background: none;
  border: 0;
  color: var(--text);
  font: inherit;
  font-size: 13.5px;
  text-align: left;
  text-decoration: none;
  cursor: pointer;
}

.menu-item:hover { background: var(--primary-light); color: var(--primary); }

/* Опасный пункт красный уже в покое, а не только при наведении: цвет —
   единственное, что отличает его от соседей до нажатия. */
.menu-item.is-danger { color: var(--danger); }
.menu-item.is-danger:hover { background: var(--danger-light); color: var(--danger); }

.menu__sep { height: 1px; background: var(--border); margin: 4px 0; }

/* Плашка состояния записи под шапкой */
.notice {
  display: flex;
  gap: 8px;
  align-items: baseline;
  flex-wrap: wrap;
  padding: 10px 14px;
  margin-bottom: 16px;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--bg);
  font-size: 13px;
}

.notice__title { font-weight: 600; }
.notice-info { background: var(--info-light); border-color: transparent; }
.notice-warning { background: var(--warning-light); border-color: transparent; }
.notice-muted { background: var(--secondary-light); border-color: transparent; }

/* Подтверждение: узкое окно, потому что читать в нём нечего, кроме
   названия того, что сейчас исчезнет. */
.modal-confirm .modal { max-width: 400px; }
.confirm__subject { margin: 0 0 10px; font-weight: 600; }

@media (max-width: 900px) {
  /* Шапка в две строки: заголовок с бейджами, под ним действия. На узком
     экране тулбар, ужатый в остаток строки, переносится по одной кнопке
     и занимает больше места, чем своей строкой. */
  .card-head { margin: -16px -16px 12px; padding: 12px 16px 0; }
  .card-head__actions { margin-left: 0; width: 100%; }
  .card-head__title { font-size: 16px; }
}

/* ======================================================================
   АРХЕТИП 3: ФОРМА (UX-DECISIONS.md)
   ====================================================================== */

/* Колонка формы. Ширина ограничена намеренно: строка ввода длиной во весь
   экран заставляет глаз возвращаться через полметра, а поля «Номер» и
   «Предмет» в таком ряду выглядят равнозначными. */
.form-column { max-width: 720px; }

/* Сетка полей: максимум две колонки. auto-fit в формах запрещён — он даёт
   пять-шесть случайных колонок, и порядок чтения расходится с порядком
   обхода по Tab. */
.form-pair {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 0 16px;
}

/* Строка под полем занята всегда, даже когда сказать нечего: иначе при
   отправке с тремя ошибками разметка подпрыгивает, и человек теряет то
   поле, которое правил. */
.form-note {
  min-height: 18px;
  margin-top: 4px;
  font-size: 11.5px;
  line-height: 18px;
  color: var(--text-muted);
}

.form-note.form-error { color: var(--danger); }

/* Липкий подвал: слева состояние, справа действия. */
.form-footer {
  position: sticky;
  bottom: 0;
  z-index: 15;
  display: flex;
  align-items: center;
  gap: 16px;
  min-height: 64px;
  margin: 16px -24px 0;
  padding: 12px 24px;
  background: var(--surface);
  border-top: 1px solid var(--border);
  box-shadow: 0 -2px 8px rgba(75, 70, 92, .08);
}

.form-footer__state { flex: 1 1 auto; font-size: 12px; }
.form-footer__actions { display: flex; gap: 8px; align-items: center; }

/* Предупреждение цветом, а не только словами: человек скользит по подвалу
   взглядом и читает его, только если что-то зацепило. */
.form-footer__dirty { color: var(--warning); font-weight: 600; }

@media (max-width: 900px) {
  .form-pair { grid-template-columns: 1fr; }
  /* Две колонки на телефоне не помещаются: сопутствующее уходит вниз. */
  .card-split { grid-template-columns: 1fr; }
  .form-footer {
    margin: 12px -16px 0;
    padding: 10px 16px calc(10px + env(safe-area-inset-bottom));
  }
}

/* Доска: подпись под названием колонки и досыпка карточек (архетип 4).
   Инлайновых стилей в разметке доски не бывает — только классы. */
.board-col-sum { font-size: 11px; color: var(--text-muted); font-weight: 400; }
.board-col-empty { padding: 16px 0; }
.board-col-more { width: 100%; margin-top: 8px; }

/* Подвал ленты биржи. Это ещё и мишень наблюдателя за прокруткой:
   у элемента нулевой высоты IntersectionObserver не срабатывает, поэтому
   высота задана явно. */
.lot-feed-more {
  display: flex;
  justify-content: center;
  align-items: center;
  min-height: 56px;
  padding: 8px 0;
}
.board-card__object { display: flex; align-items: center; gap: 4px; }

/* Отозванная подпись в списке.

   Приглушаем строку, а не прячем: подпись была, и документ обязан это
   помнить. Но читаться она должна как история, а не как действующая. */
.table tr.is-revoked td { color: var(--text-muted); }
.table tr.is-revoked td:first-child { text-decoration: line-through; }

/* Опасное действие отделено от рабочих кнопок чертой сверху: рядом с
   «Подписать» отзыв слишком легко нажать не глядя. */
.toolbar-danger { border-top: 1px solid var(--border); padding-top: 12px; }

/* ═══ Архетип 5: Конструктор ═══════════════════════════════════

   Смета — не форма и не реестр: человек сидит в ней час и набирает
   двести строк. Всё здесь подчинено скорости набора: узкие колонки под
   числа, широкая под наименование, итоги всегда на виду. */

.builder-layout { grid-template-columns: 3fr 1fr; align-items: start; }
.builder-params { grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); }
.builder-params__check { display: flex; align-items: flex-end; }
.builder-add { grid-template-columns: 2fr 1fr; align-items: end; }

/* Итоги не уезжают при прокрутке: цена сметы — то, ради чего человек
   вообще правит строки, и сверяться с ней он будет постоянно. */
.builder-totals { position: sticky; top: calc(62px + var(--gap)); }

.builder-table .builder-col-type { width: 90px; }
.builder-table .builder-col-unit { width: 60px; }
.builder-table .builder-col-qty { width: 80px; }
.builder-table .builder-col-money { width: 100px; }
.builder-table .builder-col-act { width: 40px; }

/* Маржа строки — полосой слева от цены, а не отдельной колонкой:
   колонок и так девять, а взгляд всё равно идёт по цене. */
.builder-margin { border-left: 3px solid var(--border); }
.builder-margin.margin-red { border-left-color: var(--danger); }
.builder-margin.margin-yellow { border-left-color: var(--warning); }
.builder-margin.margin-green { border-left-color: var(--success); }

.builder-suggest { margin-top: 8px; max-height: 200px; overflow-y: auto; }
.builder-suggest__row {
  display: flex;
  justify-content: space-between;
  width: 100%;
  text-align: left;
  border: none;
  background: none;
  cursor: pointer;
  padding: 8px;
  border-bottom: 1px solid var(--border);
  font-family: inherit;
  color: inherit;
  font-size: 14px;
}
.builder-suggest__row:hover { background: var(--primary-light); }

.builder-section-head { padding: 0; margin-bottom: 10px; }
.builder-section-title { max-width: 340px; font-weight: 600; }

.builder-paste { font-family: inherit; color: inherit; }
.builder-discount { color: var(--danger); }
.builder-grand td { border-top: 2px solid var(--border); font-size: 15px; }

.builder-secret {
  margin-top: 12px;
  padding-top: 12px;
  border-top: 1px dashed var(--border);
  font-size: 12px;
  color: var(--text-muted);
}
.builder-secret__title { font-weight: 600; margin-bottom: 4px; }

@media (max-width: 1199px) {
  .builder-layout { grid-template-columns: 1fr; }
  .builder-totals { position: static; }
}
/* ------------------------------------------------------------
   ГЛАВНАЯ ПО МАКЕТУ 1a/1f

   Тревоги — карточки в ряд, а не строки во всю ширину: директор
   читает сводку с телефона у машины, восемь строк-простыней
   значат, что не прочитана ни одна.
   ------------------------------------------------------------ */
.alert-cards__head {
  display: flex;
  align-items: center;
  gap: 10px;
  margin-bottom: 12px;
}

.alert-cards {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: var(--space-3);
}

.alert-card {
  display: flex;
  gap: 12px;
  align-items: flex-start;
  padding: 12px 14px;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  color: var(--text);
  text-decoration: none;
}
.alert-card:hover { border-color: var(--primary); text-decoration: none; }

.alert-card__icon {
  display: flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  width: 38px;
  height: 38px;
  border-radius: var(--radius);
}
.alert-card--danger  .alert-card__icon { background: var(--danger-bg);  color: var(--danger); }
.alert-card--warning .alert-card__icon { background: var(--warning-bg); color: var(--warning-alt); }
.alert-card--info    .alert-card__icon { background: var(--info-bg);    color: var(--info); }

.alert-card__body { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.alert-card__body strong { font-size: var(--text-body-sm); line-height: var(--lh-body-sm); }
.alert-card__detail {
  font-size: var(--text-label);
  line-height: var(--lh-label);
  color: var(--text-tertiary);
}

/* Пилюль-хинт на плитке показателя: статус читается цветом подложки,
   не чтением серого текста (макет, карточки метрик). */
.stat .stat-hint {
  display: inline-block;
  align-self: flex-start;
  margin-top: 6px;
  padding: 2px 8px;
  border-radius: var(--radius-pill);
  font-size: var(--text-caption);
  line-height: var(--lh-caption);
  background: var(--secondary-bg);
  color: var(--text-muted);
}
.stat-success .stat-hint { background: var(--success-bg); color: var(--success); }
.stat-danger  .stat-hint { background: var(--danger-bg);  color: var(--danger); }
.stat-warning .stat-hint { background: var(--warning-bg); color: var(--warning-alt); }
.stat-info    .stat-hint { background: var(--info-bg);    color: var(--info); }

/* «Всё в порядке» — чипы одной строкой внизу сводки. Пустая карточка
   с сообщением «ничего нет» занимает экран, ради которого всё
   сокращали; чип сообщает то же самое в тридцать раз дешевле. */
.ok-chips { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 4px; }
.ok-chip {
  padding: 5px 12px;
  border-radius: var(--radius-pill);
  background: var(--success-bg);
  color: var(--success);
  font-size: var(--text-label);
}

@media (max-width: 900px) {
  .alert-cards { grid-template-columns: 1fr; }
}

/* ---------- Зона перетаскивания файлов (#360) ----------
   Пунктирная рамка во всю ширину блока: она читается как «сюда можно
   бросить», чего системная кнопка «Выберите файл» не сообщает никак.
   Кнопка при этом остаётся — на телефоне перетаскивать нечем, а половина
   фотоотчётов приходит именно оттуда. */
.file-drop {
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 8px;
  padding: 20px 16px;
  text-align: center;
  border: 1px dashed var(--border);
  border-radius: var(--radius);
  background: var(--bg);
  transition: border-color .15s, background-color .15s;
}
/* Настоящее поле спрятано, но остаётся в потоке: Livewire слушает именно
   его, и подменять его своим путём загрузки нельзя. */
.file-drop__input { position: absolute; width: 1px; height: 1px; opacity: 0; pointer-events: none; }
.file-drop--over { border-color: var(--primary); background: var(--primary-bg); }
.file-drop__arrow { color: var(--text-muted); line-height: 0; }
.file-drop--over .file-drop__arrow { color: var(--primary); }
.file-drop__title { font-weight: 600; font-size: 14px; }
.file-drop__hint { font-size: 12px; color: var(--text-muted); max-width: 460px; }
.file-drop__busy { font-size: 12px; color: var(--text-muted); }

/* ---------- Свои список и календарь (#361) ----------
   Раскрытый список у select и всплывающий календарь у input[type=date]
   рисует операционная система: оформить их через CSS нельзя ни в одном
   браузере. Поэтому они нарисованы нами, а настоящее поле остаётся в
   разметке и остаётся хозяином значения — см. public/js/native-controls.js. */

/* Настоящее поле прячем от глаз, но не от программ: display:none снял бы
   его с формы, а visibility:hidden — с обхода клавиатурой. Оно остаётся
   доступным для Livewire и для тестов. */
.nc-hidden-field {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip-path: inset(50%);
  border: 0;
}

.nc-select, .nc-date { position: relative; }

.nc-select__button, .nc-date__field {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  width: 100%;
  text-align: left;
  cursor: pointer;
}
.nc-select__button:disabled, .nc-date__field:disabled { cursor: default; opacity: var(--disabled-opacity); }
.nc-select__label--empty, .nc-date__empty { color: var(--text-muted); }
.nc-select__caret, .nc-date__icon { color: var(--text-muted); font-size: 11px; flex-shrink: 0; }

/* ОТНОСИТЕЛЬНО ОКНА, А НЕ СТРАНИЦЫ.
 *
 * Было position: absolute — список висел внутри страницы, и его резал
 * любой предок с прокруткой. Владелец видел это дважды и назвал двумя
 * жалобами: «выпадающие меню и календари уходят под другой блок» и
 * «почему блок скроллится, когда открывается выпадающее меню».
 *
 * Вторая жалоба — то же самое, вид сбоку: список открывался ВНУТРЬ
 * контейнера реестра, у которого overflow: auto, и контейнер послушно
 * отращивал полосу прокрутки, чтобы список поместился.
 *
 * fixed уводит список из потока целиком: ни один предок его больше не
 * обрежет и не поедет из-за него. Цена — координаты приходится считать
 * при открытии и держать при прокрутке; это делает native-controls.js. */
.nc-select__list, .nc-date__pop {
  position: fixed;
  z-index: var(--z-panel);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  box-shadow: var(--shadow-card);
}

.nc-select__list { min-width: 100%; max-height: 240px; overflow-y: auto; padding: 4px; }
.nc-select__option {
  display: block;
  width: 100%;
  text-align: left;
  padding: 7px 10px;
  border: none;
  background: none;
  border-radius: var(--radius-sm);
  font: inherit;
  color: var(--text);
  cursor: pointer;
  white-space: nowrap;
}
.nc-select__option:hover:not(:disabled) { background: var(--bg); }
.nc-select__option:focus-visible { outline: 2px solid var(--primary); outline-offset: -2px; }
.nc-select__option.is-chosen { background: var(--primary-bg); color: var(--primary); font-weight: 600; }
.nc-select__option:disabled { color: var(--text-disabled); cursor: default; }

.nc-date__pop { width: 268px; padding: 10px; }

/* Сетка месяцев. Ставится на ту же всплывающую основу, что и календарь:
 * оформление у них одно, разная только начинка.
 *
 * Четыре колонки, а не три и не шесть: год делится на кварталы, и в
 * четыре столбца строка совпадает с кварталом — «янв фев мар апр» глазом
 * читается как первый квартал с началом второго. Три буквы вместо полного
 * названия по той же причине: полное не встаёт в четыре колонки, а
 * перенос посреди слова читается хуже сокращения. */
.nc-month__grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 4px; }
.nc-month__cell {
  border: 1px solid transparent;
  background: none;
  color: var(--text);
  border-radius: var(--radius);
  padding: 8px 0;
  font: inherit;
  font-size: 13px;
  cursor: pointer;
}
.nc-month__cell:hover { background: var(--bg); }
.nc-month__cell:focus-visible { outline: none; box-shadow: 0 0 0 3px var(--primary-light); }
/* Текущий месяц обведён, выбранный залит: обводка говорит «вы здесь»,
   заливка — «это ваш выбор», и путать их нельзя. */
.nc-month__cell.is-today { border-color: var(--border); }
.nc-month__cell.is-chosen { background: var(--primary); color: var(--white); }
.nc-date__head { display: flex; align-items: center; justify-content: space-between; margin-bottom: 8px; }
.nc-date__title { font-weight: 600; font-size: 13px; text-transform: capitalize; }
.nc-date__nav {
  width: 26px; height: 26px;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: none; color: var(--text); cursor: pointer; line-height: 1;
}
.nc-date__grid { display: grid; grid-template-columns: repeat(7, 1fr); gap: 2px; }
.nc-date__dow { font-size: 11px; color: var(--text-muted); text-align: center; padding: 4px 0; }
.nc-date__day {
  height: 32px;
  border: none;
  background: none;
  border-radius: var(--radius-sm);
  font: inherit; font-size: 13px;
  color: var(--text);
  cursor: pointer;
}
.nc-date__day:hover { background: var(--bg); }
.nc-date__day:focus-visible { outline: 2px solid var(--primary); outline-offset: -2px; }
/* Сегодня помечено рамкой, выбранное — заливкой: это разные вещи, и
   одинаковый вид у них означал бы, что «сегодня» уже выбрано. */
.nc-date__day.is-today { box-shadow: inset 0 0 0 1px var(--primary); }
.nc-date__day.is-chosen { background: var(--primary); color: var(--white); font-weight: 600; }
.nc-date__foot { display: flex; justify-content: space-between; margin-top: 8px; }

/* Строка времени у поля «дата со временем» (#361). Два поля по две
   цифры вместо списка на шестьдесят минут: набрать быстрее, чем
   открыть список, а на телефоне выскакивает цифровая клавиатура. */
.nc-date__time {
  display: flex; align-items: center; gap: 6px;
  margin-top: 8px; padding-top: 8px; border-top: 1px solid var(--border);
}
.nc-date__time-label { font-size: 12px; color: var(--text-muted); margin-right: auto; }
.nc-date__time-part { width: 44px; text-align: center; padding: 4px 6px; }
.nc-date__link {
  border: none; background: none; padding: 4px 6px;
  color: var(--primary); font: inherit; font-size: 12px; cursor: pointer;
}

/* ---------- Остальные системные элементы (#361) ----------
   Чекбоксы, радио, полосы прокрутки, ручка растягивания textarea,
   стрелки у чисел, треугольник details, ползунок и подложка
   автозаполнения. Всё это рисовала операционная система, и на одном
   экране получались наши поля рядом с чужими квадратиками. */

/* Чекбокс и радио. Их больше полусотни экранов — самое заметное. */
input[type="checkbox"], input[type="radio"] {
  appearance: none;
  -webkit-appearance: none;
  width: 16px;
  height: 16px;
  flex-shrink: 0;
  margin: 0;
  border: 1px solid var(--border);
  background: var(--surface);
  cursor: pointer;
  display: inline-grid;
  place-content: center;
  transition: background-color .12s, border-color .12s;
}
input[type="checkbox"] { border-radius: var(--radius-sm); }
input[type="radio"] { border-radius: var(--radius-circle); }
input[type="checkbox"]:checked, input[type="radio"]:checked {
  background: var(--primary);
  border-color: var(--primary);
}
/* Галка и точка рисуются содержимым псевдоэлемента: картинкой пришлось бы
   тащить файл, а он не переживёт смену цвета темы. */
input[type="checkbox"]:checked::before {
  content: '';
  width: 9px; height: 5px;
  border: 2px solid var(--white);
  border-top: none; border-right: none;
  transform: rotate(-45deg) translate(1px, -1px);
}
input[type="radio"]:checked::before {
  content: '';
  width: 6px; height: 6px;
  border-radius: var(--radius-circle);
  background: var(--white);
}
/* appearance: none снимает и видимую отметку фокуса — вернуть обязательно,
   иначе обход формы клавиатурой становится вслепую. */
/* Отметка рисуется тенью, а не рамкой, и это не вкусовщина.
   Было: `outline: var(--focus-ring-w) solid var(--focus-ring)`. А
   `--focus-ring` — значение ДЛЯ ТЕНИ (`0 0 0 2px var(--primary)`), не цвет.
   После подстановки выходило `outline: 2px solid 0 0 0 2px #…` — набор,
   которого у `outline` не бывает, и браузер отбрасывал объявление целиком,
   молча. Флажки и переключатели оставались без отметки фокуса, ровно
   вопреки пояснению строкой выше. Тенью — как у всех остальных полей. */
input[type="checkbox"]:focus-visible, input[type="radio"]:focus-visible {
  outline: none;
  box-shadow: var(--focus-ring);
}
input[type="checkbox"]:disabled, input[type="radio"]:disabled {
  opacity: var(--disabled-opacity);
  cursor: default;
}

/* Стрелки у чисел. В суммах они мешают: попасть по ним легче, чем
   набрать, и число меняется незаметно для человека. */
input[type="number"] { appearance: textfield; -moz-appearance: textfield; }
input[type="number"]::-webkit-outer-spin-button,
input[type="number"]::-webkit-inner-spin-button { appearance: none; margin: 0; }

/* Ручка растягивания textarea: тянуть можно только вниз — вбок она ломает
   раскладку формы. */
textarea.form-control { resize: vertical; }

/* Треугольник у details — свой, и он поворачивается. */
details > summary { list-style: none; cursor: pointer; }
details > summary::-webkit-details-marker { display: none; }
details > summary::before {
  content: '▸';
  display: inline-block;
  margin-right: 6px;
  color: var(--text-muted);
  transition: transform .15s;
}
details[open] > summary::before { transform: rotate(90deg); }

/* Ползунок. Их в системе всего один, но системный рядом с нашими полями
   виден сразу. */
input[type="range"] {
  appearance: none;
  -webkit-appearance: none;
  width: 100%;
  height: 4px;
  border-radius: var(--radius-pill);
  background: var(--border);
}
input[type="range"]::-webkit-slider-thumb {
  appearance: none;
  width: 16px; height: 16px;
  border-radius: var(--radius-circle);
  background: var(--primary);
  cursor: pointer;
}
input[type="range"]::-moz-range-thumb {
  width: 16px; height: 16px; border: none;
  border-radius: var(--radius-circle);
  background: var(--primary);
  cursor: pointer;
}

/* Полосы прокрутки — приём тот же, что уже проверен на боковом меню. */
* { scrollbar-width: thin; scrollbar-color: var(--border) transparent; }
::-webkit-scrollbar { width: 10px; height: 10px; }
::-webkit-scrollbar-track { background: transparent; }
::-webkit-scrollbar-thumb {
  background: var(--border);
  border-radius: var(--radius-pill);
  border: 2px solid transparent;
  background-clip: content-box;
}
::-webkit-scrollbar-thumb:hover { background: var(--text-disabled); background-clip: content-box; }

/* Жёлтая подложка автозаполнения Chrome. Убрать её напрямую нельзя —
   перебиваем внутренней тенью в цвет поля. */
input:-webkit-autofill,
input:-webkit-autofill:hover,
input:-webkit-autofill:focus {
  -webkit-box-shadow: 0 0 0 1000px var(--surface) inset;
  -webkit-text-fill-color: var(--text);
}

/* ---------- Код из письма: шесть ячеек (#345) ----------
   Длина кода видна самим видом поля, а не подписью под ним: человек
   набирает шесть цифр, видя, сколько уже введено. */
.code-input { display: flex; gap: 8px; }
.code-input__cell {
  width: 46px;
  height: 54px;
  padding: 0;
  text-align: center;
  font-size: 22px;
  font-weight: 700;
  font-family: inherit;
  color: var(--text);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
}
.code-input__cell:focus {
  outline: none;
  border-color: var(--primary);
  box-shadow: 0 0 0 1px var(--primary);
}
/* Заполненная ячейка отличается от пустой: иначе на середине кода не
   понять, где остановился. */
.code-input__cell:not(:placeholder-shown) { border-color: var(--primary); }

@media (max-width: 420px) {
  .code-input { gap: 6px; }
  .code-input__cell { width: 40px; height: 48px; font-size: 19px; }
}

/* Полоса «вы в кабинете клиента» (#246).

   Во всю ширину и поверх всего: человек за экраном обязан всё время
   видеть, что смотрит чужое. Цвет тревожный намеренно — это не подсказка,
   а предупреждение. */
.impersonation-bar {
    position: sticky;
    top: 0;
    z-index: 100;
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
    padding: 8px 16px;
    background: #8a1f1f;
    color: #fff;
    font-size: 13px;
    line-height: 1.35;
}

.impersonation-bar .btn {
    background: #fff;
    color: #8a1f1f;
    border-color: #fff;
    white-space: nowrap;
}

/* Подпись под знаком в панели вендора (#334).

   Панель теперь устроена как кабинет клиента, и отличать её должна не
   вёрстка, а одно слово. Поэтому знак и подпись становятся столбиком, а
   сама подпись — приглушённой: она не заголовок, а метка «где я». */
.sidebar .brand:has(.brand__note) {
  flex-direction: column;
  align-items: flex-start;
  gap: 2px;
}

.brand__note {
  font-size: 11px;
  font-weight: 500;
  letter-spacing: .06em;
  text-transform: uppercase;
  color: rgba(255, 255, 255, .55);
}

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

   Живёт здесь, а не в public.css, потому что нужна В ДВУХ кабинетах: в
   панели вендора при сбросе пароля и в кабинете компании при выдаче
   доступа соисполнителю. В public.css она была видна только панели, а на
   экране компании этот блок всё это время показывался неоформленным —
   нашлось при переводе панели на вид кабинета (#334). */
/* Моноширинный сюда не вписан намеренно: в проекте он объявлен ровно
   один раз, в .u-mono, и второе объявление сторож основы дизайна ловит
   сразу. Разметка ставит оба класса — это дороже на одно слово, но
   шрифтов в системе по-прежнему два, а не три. */
.one-time-link {
  margin-top: 10px;
  padding: 10px;
  background: var(--surface);
  border: 1px solid var(--border);
  font-size: 13px;
  user-select: all;
}

/* Отдел в иерархии сотрудников на карточке компании. */
.vendor-dept {
  margin: 12px 0 20px;
  padding-left: 14px;
  border-left: 2px solid var(--border);
}

.vendor-dept__name {
  font-weight: 700;
  font-size: 14px;
  margin-bottom: 8px;
}

/* Период отчёта (#221): поля и четыре быстрых ответа под ними.
   Кнопки мельче полей намеренно — они помощь, а не главное
   действие экрана. */
.period-pick__label { display: block; margin-bottom: 4px; }
.period-pick__row { display: flex; align-items: center; gap: 6px; }
.period-pick__row .form-control { width: auto; }
.period-pick__presets { display: flex; flex-wrap: wrap; gap: 4px; margin-top: 6px; }
.period-pick__presets .btn { padding: 2px 8px; font-size: 12px; }

/* Склад с телефона — карточки вместо таблицы (макет «Склад»).

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

   ЧИСЛА В КАРТОЧКАХ И В ТАБЛИЦЕ — ИЗ ОДНОГО МАССИВА, посчитанного в
   компоненте. Считай их в разметке — у одной строки стало бы два расчёта, и
   разошлись бы они молча: каждый вид сам по себе верен, и оба зелёные.

   Цвета — только переменными слоя: литерал здесь однажды остался бы светлым
   при тёмной теме. */
.sklad-kartochki { display: none; }

@media (max-width: 768px) {
  .sklad-tablica { display: none; }
  .sklad-kartochki { display: block; }
}

.sklad-kartochka {
  padding: 10px 12px;
  border-bottom: 1px solid var(--border);
  display: grid;
  grid-template-columns: minmax(0, 1fr) max-content;
  gap: 2px 12px;
  align-items: center;
}

/* Имя переносится, но не больше двух строк: наименование из справочника бывает
   длиной в абзац, и карточка растянулась бы на пол-экрана. */
.sklad-kartochka__imya {
  font-weight: 500;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

/* Остаток — главное число карточки: за ним и открывают склад с телефона. */
.sklad-kartochka__chislo {
  font-weight: 600;
  font-size: 15px;
  text-align: right;
  white-space: nowrap;
  font-variant-numeric: tabular-nums;
}

.sklad-kartochka .badge { justify-self: end; }

/* Приёмка крупной кнопкой внизу списка склада (макет «Склад», телефон).

   Липкая, и это не украшение: кладовщик мотает список остатков одной рукой, а
   кнопка, ушедшая за край экрана, всё равно что её нет — за ней придётся
   возвращаться к началу. 48 px — размер, на который уверенно попадают большим
   пальцем; меньше даёт промахи в перчатке.

   Показывается только вместе с карточками: правило выше прячет весь блок на
   столе, поэтому отдельного медиазапроса ей не нужно. */
.sklad-priyomka {
  position: sticky;
  bottom: 0;
  display: block;
  width: 100%;
  min-height: 48px;
  margin-top: 8px;
  text-align: center;
  font-size: 16px;
}
